forked from tangled.org/core
Monorepo for Tangled

crypto: helpers to verify commit signatures

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.sh>

authored by anirudh.fi and committed by Tangled d0fbcd5e ea1b3729

Changed files
+74 -1
crypto
+70
crypto/verify.go
··· 1 + package crypto 2 + 3 + import ( 4 + "bytes" 5 + "crypto/sha256" 6 + "encoding/base64" 7 + "fmt" 8 + "strings" 9 + 10 + "github.com/hiddeco/sshsig" 11 + "golang.org/x/crypto/ssh" 12 + "tangled.sh/tangled.sh/core/types" 13 + ) 14 + 15 + func VerifySignature(pubKey, signature, payload []byte) (error, bool) { 16 + pub, _, _, _, err := ssh.ParseAuthorizedKey(pubKey) 17 + if err != nil { 18 + return fmt.Errorf("failed to parse public key: %w", err), false 19 + } 20 + 21 + sig, err := sshsig.Unarmor(signature) 22 + if err != nil { 23 + return fmt.Errorf("failed to parse signature: %w", err), false 24 + } 25 + 26 + buf := bytes.NewBuffer(payload) 27 + sshsig.Verify(buf, sig, pub, sshsig.HashSHA256, "git") 28 + return err, err == nil 29 + } 30 + 31 + // VerifyCommitSignature reconstructs the payload used to sign a commit. This is 32 + // essentially the git cat-file output but without the gpgsig header. 33 + // 34 + // Caveats: signature verification will fail on commits with more than one parent, 35 + // i.e. merge commits, because types.NiceDiff doesn't carry more than one Parent field 36 + // and we are unable to reconstruct the payload correctly. 37 + // 38 + // Ideally this should directly operate on an *object.Commit. 39 + func VerifyCommitSignature(pubKey string, commit types.NiceDiff) (error, bool) { 40 + signature := commit.Commit.PGPSignature 41 + 42 + author := bytes.NewBuffer([]byte{}) 43 + committer := bytes.NewBuffer([]byte{}) 44 + commit.Commit.Author.Encode(author) 45 + commit.Commit.Committer.Encode(committer) 46 + 47 + payload := strings.Builder{} 48 + 49 + fmt.Fprintf(&payload, "tree %s\n", commit.Commit.Tree) 50 + fmt.Fprintf(&payload, "parent %s\n", commit.Commit.Parent) 51 + fmt.Fprintf(&payload, "author %s\n", author.String()) 52 + fmt.Fprintf(&payload, "committer %s\n", committer.String()) 53 + if commit.Commit.ChangedId != "" { 54 + fmt.Fprintf(&payload, "change-id %s\n", commit.Commit.ChangedId) 55 + } 56 + fmt.Fprintf(&payload, "\n%s", commit.Commit.Message) 57 + 58 + return VerifySignature([]byte(pubKey), []byte(signature), []byte(payload.String())) 59 + } 60 + 61 + // SSHFingerprint computes the fingerprint of the supplied ssh pubkey. 62 + func SSHFingerprint(pubKey string) (string, error) { 63 + pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubKey)) 64 + if err != nil { 65 + return "", err 66 + } 67 + 68 + hash := sha256.Sum256(pk.Marshal()) 69 + return "SHA256:" + base64.StdEncoding.EncodeToString(hash[:]), nil 70 + }
+2 -1
go.mod
··· 21 21 github.com/go-git/go-git/v5 v5.14.0 22 22 github.com/google/uuid v1.6.0 23 23 github.com/gorilla/sessions v1.4.0 24 + github.com/hiddeco/sshsig v0.2.0 24 25 github.com/ipfs/go-cid v0.5.0 25 26 github.com/lestrrat-go/jwx/v2 v2.1.6 26 27 github.com/mattn/go-sqlite3 v1.14.24 ··· 31 32 github.com/urfave/cli/v3 v3.3.3 32 33 github.com/whyrusleeping/cbor-gen v0.3.1 33 34 github.com/yuin/goldmark v1.4.13 35 + golang.org/x/crypto v0.38.0 34 36 golang.org/x/net v0.39.0 35 37 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da 36 38 tangled.sh/icyphox.sh/atproto-oauth v0.0.0-20250526154904-3906c5336421 ··· 122 124 go.uber.org/atomic v1.11.0 // indirect 123 125 go.uber.org/multierr v1.11.0 // indirect 124 126 go.uber.org/zap v1.27.0 // indirect 125 - golang.org/x/crypto v0.38.0 // indirect 126 127 golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect 127 128 golang.org/x/sync v0.13.0 // indirect 128 129 golang.org/x/sys v0.33.0 // indirect
+2
go.sum
··· 160 160 github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 161 161 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 162 162 github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 163 + github.com/hiddeco/sshsig v0.2.0 h1:gMWllgKCITXdydVkDL+Zro0PU96QI55LwUwebSwNTSw= 164 + github.com/hiddeco/sshsig v0.2.0/go.mod h1:nJc98aGgiH6Yql2doqH4CTBVHexQA40Q+hMMLHP4EqE= 163 165 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 164 166 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 165 167 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=