Monorepo for Tangled
tangled.org
1package crypto
2
3import (
4 "bytes"
5 "crypto/ed25519"
6 "crypto/rand"
7 "strings"
8 "testing"
9
10 "github.com/hiddeco/sshsig"
11 "golang.org/x/crypto/ssh"
12)
13
14// testKey generates an ephemeral ed25519 key pair and returns the ssh.Signer
15// and the public key in authorized_keys format.
16func testKey(t *testing.T) (ssh.Signer, []byte) {
17 t.Helper()
18 _, priv, err := ed25519.GenerateKey(rand.Reader)
19 if err != nil {
20 t.Fatalf("generate ed25519 key: %v", err)
21 }
22 signer, err := ssh.NewSignerFromKey(priv)
23 if err != nil {
24 t.Fatalf("create signer: %v", err)
25 }
26 return signer, ssh.MarshalAuthorizedKey(signer.PublicKey())
27}
28
29// testSign signs payload with signer using the same parameters as VerifySignature
30// expects (SHA-512, "git" namespace) and returns the armored signature.
31func testSign(t *testing.T, signer ssh.Signer, payload []byte) []byte {
32 t.Helper()
33 sig, err := sshsig.Sign(bytes.NewReader(payload), signer, sshsig.HashSHA512, "git")
34 if err != nil {
35 t.Fatalf("sign payload: %v", err)
36 }
37 return sshsig.Armor(sig)
38}
39
40func TestSSHFingerprint(t *testing.T) {
41 t.Run("valid key returns SHA256 fingerprint", func(t *testing.T) {
42 _, pubKeyBytes := testKey(t)
43 fp, err := SSHFingerprint(string(pubKeyBytes))
44 if err != nil {
45 t.Fatalf("unexpected error: %v", err)
46 }
47 if !strings.HasPrefix(fp, "SHA256:") {
48 t.Errorf("fingerprint %q does not start with SHA256:", fp)
49 }
50 })
51
52 t.Run("same key returns identical fingerprint", func(t *testing.T) {
53 _, pubKeyBytes := testKey(t)
54 fp1, _ := SSHFingerprint(string(pubKeyBytes))
55 fp2, _ := SSHFingerprint(string(pubKeyBytes))
56 if fp1 != fp2 {
57 t.Errorf("fingerprint not deterministic: %q != %q", fp1, fp2)
58 }
59 })
60
61 t.Run("different keys return different fingerprints", func(t *testing.T) {
62 _, pub1 := testKey(t)
63 _, pub2 := testKey(t)
64 fp1, _ := SSHFingerprint(string(pub1))
65 fp2, _ := SSHFingerprint(string(pub2))
66 if fp1 == fp2 {
67 t.Error("different keys produced the same fingerprint")
68 }
69 })
70
71 t.Run("malformed key returns error", func(t *testing.T) {
72 _, err := SSHFingerprint("not a valid ssh public key")
73 if err == nil {
74 t.Error("expected error for malformed key")
75 }
76 })
77}
78
79func TestVerifySignature(t *testing.T) {
80 signer, pubKeyBytes := testKey(t)
81 payload := []byte("test payload")
82 armoredSig := testSign(t, signer, payload)
83
84 t.Run("valid signature verifies successfully", func(t *testing.T) {
85 err, ok := VerifySignature(pubKeyBytes, armoredSig, payload)
86 if err != nil {
87 t.Errorf("unexpected error: %v", err)
88 }
89 if !ok {
90 t.Error("expected ok=true for valid signature")
91 }
92 })
93
94 t.Run("malformed public key returns error", func(t *testing.T) {
95 err, ok := VerifySignature([]byte("not a valid key"), armoredSig, payload)
96 if err == nil {
97 t.Error("expected error for malformed public key")
98 }
99 if ok {
100 t.Error("expected ok=false")
101 }
102 })
103
104 t.Run("malformed signature returns error", func(t *testing.T) {
105 err, ok := VerifySignature(pubKeyBytes, []byte("not a valid signature"), payload)
106 if err == nil {
107 t.Error("expected error for malformed signature")
108 }
109 if ok {
110 t.Error("expected ok=false")
111 }
112 })
113
114 t.Run("tampered payload fails verification", func(t *testing.T) {
115 err, ok := VerifySignature(pubKeyBytes, armoredSig, []byte("tampered"))
116 if err == nil {
117 t.Error("expected error for tampered payload")
118 }
119 if ok {
120 t.Error("expected ok=false for tampered payload")
121 }
122 })
123
124 t.Run("wrong public key fails verification", func(t *testing.T) {
125 _, otherPubKey := testKey(t)
126 err, ok := VerifySignature(otherPubKey, armoredSig, payload)
127 if err == nil {
128 t.Error("expected error for wrong public key")
129 }
130 if ok {
131 t.Error("expected ok=false for wrong public key")
132 }
133 })
134}