1package archesrv
2
3import (
4 "bytes"
5 "encoding/hex"
6 "time"
7
8 "arche/internal/object"
9 "arche/internal/repo"
10
11 "golang.org/x/crypto/ssh"
12)
13
14type CommitSigStatus struct {
15 CommitID [32]byte
16 Status string
17 KeyID string
18 VerifiedAt time.Time
19}
20
21func (d *DB) RecordCommitSignature(r *repo.Repo, commitID [32]byte, c *object.Commit, userID int64) error {
22 var existing int
23 _ = d.db.QueryRow("SELECT COUNT(*) FROM commit_signatures WHERE commit_id=?", commitID[:]).Scan(&existing)
24 if existing > 0 {
25 return nil
26 }
27
28 status := "unsigned"
29 keyID := ""
30
31 if len(c.CommitSig) > 0 {
32 body := object.CommitBodyForSigning(c)
33
34 keys, err := d.ListSSHKeys(userID)
35 if err == nil {
36 for _, k := range keys {
37 pub, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k.PublicKey))
38 if err != nil {
39 continue
40 }
41 if err := object.VerifyCommitSig(body, c.CommitSig, pub); err == nil {
42 status = "verified"
43 keyID = ssh.FingerprintSHA256(pub)
44 break
45 }
46 }
47 }
48
49 if status == "unsigned" {
50 var sig ssh.Signature
51 if err := ssh.Unmarshal(c.CommitSig, &sig); err == nil {
52 status = "unknown_key"
53 } else {
54 status = "invalid"
55 }
56 }
57 }
58
59 _, err := d.db.Exec(
60 "INSERT OR IGNORE INTO commit_signatures (commit_id, status, key_id, verified_at) VALUES (?,?,?,?)",
61 commitID[:], status, keyID, time.Now().Unix(),
62 )
63 return err
64}
65
66func (d *DB) GetCommitSigStatus(commitID [32]byte) string {
67 var status string
68 if err := d.db.QueryRow(
69 "SELECT status FROM commit_signatures WHERE commit_id=?", commitID[:],
70 ).Scan(&status); err != nil {
71 return "unsigned"
72 }
73 return status
74}
75
76func collectNewCommitIDs(r *repo.Repo, oldHex, newHex string) [][32]byte {
77 if len(newHex) != 64 {
78 return nil
79 }
80 newBytes, err := hex.DecodeString(newHex)
81 if err != nil || len(newBytes) != 32 {
82 return nil
83 }
84 var newID [32]byte
85 copy(newID[:], newBytes)
86
87 var oldID [32]byte
88 if len(oldHex) == 64 {
89 if b, err := hex.DecodeString(oldHex); err == nil && len(b) == 32 {
90 copy(oldID[:], b)
91 }
92 }
93
94 seen := make(map[[32]byte]bool)
95 queue := [][32]byte{newID}
96 var result [][32]byte
97 for len(queue) > 0 {
98 id := queue[0]
99 queue = queue[1:]
100 if seen[id] || bytes.Equal(id[:], oldID[:]) {
101 continue
102 }
103 seen[id] = true
104 result = append(result, id)
105 c, err := r.ReadCommit(id)
106 if err != nil {
107 continue
108 }
109 for _, p := range c.Parents {
110 if !seen[p] {
111 queue = append(queue, p)
112 }
113 }
114 }
115 return result
116}