1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package ssh
17
18 import (
19 "bytes"
20 "os"
21 "os/exec"
22 "path/filepath"
23 "strings"
24 "testing"
25 )
26
27 var (
28
29 sshPrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
30 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
66 -----END OPENSSH PRIVATE KEY-----
67 `
68 sshPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDXofkiahE7uavjWvxnwkUF27qMgz7pdTwzSv0XzVG6EtirOv3PDWct4YKoXE9c0EqbxnIfYEKwEextdvB7zkgwczdJSHxf/18jQumLn/FuoCmugVSk1H5Qli/qzwBpaTnOk3WuakGuoYUl8ZAokKKgOKLA0aZJ1WRQ2ZCZggA3EkwNZiY17y9Q6HqdgQcH6XN8aAMADNVJdMAJb33hSRJjjsAPTmzBTishP8lYDoGRSsSE7/8XRBCEV5E4I8mI9GElcZwV/1KJx98mpH8QvMzXM1idFcwPRtt1NTAOshwgUU0Fu1x8lU5RQIa6ZKW36qNQLvLxy/BscC7B/mdLptoDs/ot9NimUXZcgCR1a2Q3o7Wi6jIgcgJcyV10Nba81ol4RdN4qPHnVZIzuo+dBkqwG3CMtB4Rj84+Qi+7zyU01hIPreoxQDXaayiGPBUUIiAlW9gsiuRWJzNnu3cvuWDLVfQIkjh7Wug58z+v2NOJ7IMdyERillhzDcvVHaq14+U= test@rekor.dev
69 `
70
71 otherSSHPrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
72 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
73 NhAAAAAwEAAQAAAYEAw/WCSWC9TEvCQOwO+T68EvNa3OSIv1Y0+sT8uSvyjPyEO0+p0t8C
74 g/zy67vOxiQpU5jN6MItjXAjMmeCm8GKMt6gk+cDoaAev/ZfjuzSL7RayExpmhBleh2X3G
75 KLkkXF9ABFNchlTqSLOZiEjDoNpbFv16KT1sE6CqW8DjxXQkQk9JK65hLH+BxeWMNCEJVa
76 Cma4X04aJmC7zJAi5yGeeT0SKVqMohavF90O6XiYFCQHuwXPPyHfocqgudmXnozz+6D6ax
77 JKZMwQsNp3WKumOjlzWnxBCCB1l2jN6Rag8aJ2277iMFXRwjTL/8jaEsW4KkysDf0GjV2/
78 iqbr0q5b0arDYbv7CrGBR+uH0wGz/Zog1x5iZANObhZULpDrLVJidEMc27HXBb7PMsNDy7
79 BGYRB1yc0d0y83p8mUqvOlWSArxn1WnAZO04pAgTrclrhEh4ZXOkn2Sn82eu3DpQ8inkol
80 Y4IfnhIfbOIeemoUNq1tOUquhow9GLRM6INieHLBAAAFkPPnA1jz5wNYAAAAB3NzaC1yc2
81 EAAAGBAMP1gklgvUxLwkDsDvk+vBLzWtzkiL9WNPrE/Lkr8oz8hDtPqdLfAoP88uu7zsYk
82 KVOYzejCLY1wIzJngpvBijLeoJPnA6GgHr/2X47s0i+0WshMaZoQZXodl9xii5JFxfQART
83 XIZU6kizmYhIw6DaWxb9eik9bBOgqlvA48V0JEJPSSuuYSx/gcXljDQhCVWgpmuF9OGiZg
84 u8yQIuchnnk9EilajKIWrxfdDul4mBQkB7sFzz8h36HKoLnZl56M8/ug+msSSmTMELDad1
85 irpjo5c1p8QQggdZdozekWoPGidtu+4jBV0cI0y//I2hLFuCpMrA39Bo1dv4qm69KuW9Gq
86 w2G7+wqxgUfrh9MBs/2aINceYmQDTm4WVC6Q6y1SYnRDHNux1wW+zzLDQ8uwRmEQdcnNHd
87 MvN6fJlKrzpVkgK8Z9VpwGTtOKQIE63Ja4RIeGVzpJ9kp/Nnrtw6UPIp5KJWOCH54SH2zi
88 HnpqFDatbTlKroaMPRi0TOiDYnhywQAAAAMBAAEAAAGAYycx4oEhp55Zz1HijblxnsEmQ8
89 kbbH1pV04fdm7HTxFis0Qu8PVIp5JxNFiWWunnQ1Z5MgI23G9WT+XST4+RpwXBCLWGv9xu
90 UsGOPpqUC/FdUiZf9MXBIxYgRjJS3xORA1KzsnAQ2sclb2I+B1pEl4d9yQWJesvQ25xa2H
91 Utzej/LgWkrk/ogSGRl6ZNImj/421wc0DouGyP+gUgtATt0/jT3LrlmAqUVCXVqssLYH2O
92 r9JTuGUibBJEW2W/c0lsM0jaHa5bGAdL3nhDuF1Q6KFB87mZoNw8c2znYoTzQ3FyWtIEZI
93 V/9oWrkS7V6242SKSR9tJoEzK0jtrKC/FZwBiI4hPcwoqY6fZbT1701i/n50xWEfEUOLVm
94 d6VqNKyAbIaZIPN0qfZuD+xdrHuM3V6k/rgFxGl4XTrp/N4AsruiQs0nRQKNTw3fHE0zPq
95 UTxSeMvjywRCepxhBFCNh8NHydapclHtEPEGdTVHohL3krJehstPO/IuRyKLfSVtL1AAAA
96 wQCmGA8k+uW6mway9J3jp8mlMhhp3DCX6DAcvalbA/S5OcqMyiTM3c/HD5OJ6OYFDldcqu
97 MPEgLRL2HfxL29LsbQSzjyOIrfp5PLJlo70P5lXS8u2QPbo4/KQJmQmsIX18LDyU2zRtNA
98 C2WfBiHSZV+guLhmHms9S5gQYKt2T5OnY/W0tmnInx9lmFCMC+XKS1iSQ2o433IrtCPQJp
99 IXZd59OQpO9QjJABgJIDtXxFIXt45qpXduDPJuggrhg81stOwAAADBAPX73u/CY+QUPts+
100 LV185Z4mZ2y+qu2ZMCAU3BnpHktGZZ1vFN1Xq9o8KdnuPZ+QJRdO8eKMWpySqrIdIbTYLm
101 9nXmVH0uNECIEAvdU+wgKeR+BSHxCRVuTF4YSygmNadgH/z+oRWLgOblGo2ywFBoXsIAKQ
102 paNu1MFGRUmhz67+dcpkkBUDRU9loAgBKexMo8D9vkR0YiHLOUjCrtmEZRNm0YRZt0gQhD
103 ZSD1fOH0fZDcCVNpGP2zqAKos4EGLnkwAAAMEAy/AuLtPKA2u9oCA8e18ZnuQRAi27FBVU
104 rU2D7bMg1eS0IakG8v0gE9K6WdYzyArY1RoKB3ZklK5VmJ1cOcWc2x3Ejc5jcJgc8cC6lZ
105 wwjpE8HfWL1kIIYgPdcexqFc+l6MdgH6QMKU3nLg1LsM4v5FEldtk/2dmnw620xnFfstpF
106 VxSZNdKrYfM/v9o6sRaDRqSfH1dG8BvkUxPznTAF+JDxBENcKXYECcq9f6dcl1w5IEnNTD
107 Wry/EKQvgvOUjbAAAAFG90aGVyLXRlc3RAcmVrb3IuZGV2AQIDBAUG
108 -----END OPENSSH PRIVATE KEY-----
109 `
110 otherSSHPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDD9YJJYL1MS8JA7A75PrwS81rc5Ii/VjT6xPy5K/KM/IQ7T6nS3wKD/PLru87GJClTmM3owi2NcCMyZ4KbwYoy3qCT5wOhoB6/9l+O7NIvtFrITGmaEGV6HZfcYouSRcX0AEU1yGVOpIs5mISMOg2lsW/XopPWwToKpbwOPFdCRCT0krrmEsf4HF5Yw0IQlVoKZrhfThomYLvMkCLnIZ55PRIpWoyiFq8X3Q7peJgUJAe7Bc8/Id+hyqC52ZeejPP7oPprEkpkzBCw2ndYq6Y6OXNafEEIIHWXaM3pFqDxonbbvuIwVdHCNMv/yNoSxbgqTKwN/QaNXb+KpuvSrlvRqsNhu/sKsYFH64fTAbP9miDXHmJkA05uFlQukOstUmJ0QxzbsdcFvs8yw0PLsEZhEHXJzR3TLzenyZSq86VZICvGfVacBk7TikCBOtyWuESHhlc6SfZKfzZ67cOlDyKeSiVjgh+eEh9s4h56ahQ2rW05Sq6GjD0YtEzog2J4csE= other-test@rekor.dev
111 `
112
113
114 ed25519PrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
115 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
116 QyNTUxOQAAACBB45zRHxPPFtabwS3Vd6Lb9vMe+tIHZj2qN5VQ+bgLfQAAAJgyRa3cMkWt
117 3AAAAAtzc2gtZWQyNTUxOQAAACBB45zRHxPPFtabwS3Vd6Lb9vMe+tIHZj2qN5VQ+bgLfQ
118 AAAED7y4N/DsVnRQiBZNxEWdsJ9RmbranvtQ3X9jnb6gFed0HjnNEfE88W1pvBLdV3otv2
119 8x760gdmPao3lVD5uAt9AAAADnRlc3RAcmVrb3IuZGV2AQIDBAUGBw==
120 -----END OPENSSH PRIVATE KEY-----
121 `
122 ed25519PublicKey = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEHjnNEfE88W1pvBLdV3otv28x760gdmPao3lVD5uAt9 test@rekor.dev
123 `
124 )
125
126 func TestFromOpenSSH(t *testing.T) {
127 for _, tt := range []struct {
128 name string
129 pub string
130 priv string
131 }{
132 {
133 name: "rsa",
134 pub: sshPublicKey,
135 priv: sshPrivateKey,
136 },
137 {
138 name: "ed25519",
139 pub: ed25519PublicKey,
140 priv: ed25519PrivateKey,
141 },
142 } {
143 if _, err := exec.LookPath("ssh-keygen"); err != nil {
144 t.Skip("skip TestFromOpenSSH: missing ssh-keygen in PATH")
145 }
146 t.Run(tt.name, func(t *testing.T) {
147 tt := tt
148
149
150 td := t.TempDir()
151
152 data := []byte("hello, ssh world")
153 dataPath := write(t, []byte(data), td, "data")
154
155 privPath := write(t, []byte(tt.priv), td, "id")
156 write(t, []byte(tt.pub), td, "id.pub")
157
158 sigPath := dataPath + ".sig"
159 run(t, nil, "ssh-keygen", "-Y", "sign", "-n", "file", "-f", privPath, dataPath)
160
161 sigBytes, err := os.ReadFile(sigPath)
162 if err != nil {
163 t.Fatal(err)
164 }
165 if err := Verify(bytes.NewReader(data), sigBytes, []byte(tt.pub)); err != nil {
166 t.Error(err)
167 }
168
169
170 if err := Verify(bytes.NewReader(data), sigBytes, []byte(otherSSHPublicKey)); err == nil {
171 t.Error("expected error with incorrect key")
172 }
173
174
175 if err := Verify(strings.NewReader("bad data"), sigBytes, []byte(sshPublicKey)); err == nil {
176 t.Error("expected error with incorrect data")
177 }
178 })
179 }
180 }
181
182 func TestToOpenSSH(t *testing.T) {
183 for _, tt := range []struct {
184 name string
185 pub string
186 priv string
187 }{
188 {
189 name: "rsa",
190 pub: sshPublicKey,
191 priv: sshPrivateKey,
192 },
193 {
194 name: "ed25519",
195 pub: ed25519PublicKey,
196 priv: ed25519PrivateKey,
197 },
198 } {
199 if _, err := exec.LookPath("ssh-keygen"); err != nil {
200 t.Skip("skip TestToOpenSSH: missing ssh-keygen in PATH")
201 }
202 t.Run(tt.name, func(t *testing.T) {
203 tt := tt
204
205 td := t.TempDir()
206
207 data := []byte("hello, ssh world")
208 write(t, []byte(data), td, "data")
209
210 armored, err := Sign(tt.priv, bytes.NewReader(data))
211 if err != nil {
212 t.Fatal(err)
213 }
214
215 sigPath := write(t, []byte(armored), td, "oursig")
216
217
218 allowedSigner := "test@rekor.dev " + tt.pub + "\n"
219 allowedSigner += "othertest@rekor.dev " + otherSSHPublicKey + "\n"
220 allowedSigners := write(t, []byte(allowedSigner), td, "allowed_signer")
221
222
223 run(t, data, "ssh-keygen", "-Y", "verify", "-f", allowedSigners,
224 "-I", "test@rekor.dev", "-n", "file", "-s", sigPath)
225
226
227 runErr(t, data, "ssh-keygen", "-Y", "verify", "-f", allowedSigners,
228 "-I", "othertest@rekor.dev", "-n", "file", "-s", sigPath)
229
230
231 data = []byte("other data!")
232 runErr(t, data, "ssh-keygen", "-Y", "check-novalidate", "-n", "file", "-s", sigPath)
233 })
234 }
235 }
236
237 func TestRoundTrip(t *testing.T) {
238 data := []byte("my good data to be signed!")
239
240
241 otherSig, err := Sign(otherSSHPrivateKey, bytes.NewReader(data))
242 if err != nil {
243 t.Fatal(err)
244 }
245
246 for _, tt := range []struct {
247 name string
248 pub string
249 priv string
250 }{
251 {
252 name: "rsa",
253 pub: sshPublicKey,
254 priv: sshPrivateKey,
255 },
256 {
257 name: "ed25519",
258 pub: ed25519PublicKey,
259 priv: ed25519PrivateKey,
260 },
261 } {
262 t.Run(tt.name, func(t *testing.T) {
263 tt := tt
264 sig, err := Sign(tt.priv, bytes.NewReader(data))
265 if err != nil {
266 t.Fatal(err)
267 }
268
269
270 if err := Verify(bytes.NewReader(data), sig, []byte(tt.pub)); err != nil {
271 t.Error(err)
272 }
273
274
275 if err := Verify(strings.NewReader("invalid data!"), sig, []byte(tt.pub)); err == nil {
276 t.Error("expected error!")
277 }
278
279
280 if err := Verify(bytes.NewReader(data), sig, []byte(otherSSHPublicKey)); err == nil {
281 t.Error("expected error!")
282 }
283
284
285 if err := Verify(bytes.NewReader(data), []byte("invalid signature!"), []byte(tt.pub)); err == nil {
286 t.Error("expected error!")
287 }
288
289
290 if err := Verify(bytes.NewReader(data), otherSig, []byte(tt.pub)); err == nil {
291 t.Error("expected error!")
292 }
293
294 if err := Verify(bytes.NewReader(data), otherSig, []byte(otherSSHPublicKey)); err != nil {
295 t.Error(err)
296 }
297 })
298 }
299
300 }
301
302 func write(t *testing.T, d []byte, fp ...string) string {
303 p := filepath.Join(fp...)
304 if err := os.WriteFile(p, d, 0600); err != nil {
305 t.Fatal(err)
306 }
307 return p
308 }
309
310 func run(t *testing.T, stdin []byte, args ...string) {
311 t.Helper()
312
313 cmd := exec.Command(args[0], args[1:]...)
314 cmd.Stdin = bytes.NewReader(stdin)
315 out, err := cmd.CombinedOutput()
316 t.Logf("cmd %v: %s", cmd, string(out))
317 if err != nil {
318 t.Fatal(err)
319 }
320 }
321
322 func runErr(t *testing.T, stdin []byte, args ...string) {
323 t.Helper()
324
325 cmd := exec.Command(args[0], args[1:]...)
326 cmd.Stdin = bytes.NewReader(stdin)
327 out, err := cmd.CombinedOutput()
328 t.Logf("cmd %v: %s", cmd, string(out))
329 if err == nil {
330 t.Fatal("expected error")
331 }
332 }
333
View as plain text