mirror your GitHub repos to tangled.org automatically
1import crypto from 'node:crypto'
2import { describe, expect, it } from 'vitest'
3import { generateKeypair } from '../../server/utils/ssh-keypair'
4
5describe('ssh-keypair', () => {
6 it('produces an OpenSSH-formatted ed25519 public key', () => {
7 const { publicKeyOpenSsh } = generateKeypair('synchub.to/123')
8 expect(publicKeyOpenSsh).toMatch(/^ssh-ed25519 [A-Za-z0-9+/=]+ synchub\.to\/123$/)
9 })
10
11 it('produces a PKCS#8 PEM private key Node can load', () => {
12 const { privateKeyPem } = generateKeypair('test')
13 expect(privateKeyPem).toMatch(/^-----BEGIN PRIVATE KEY-----/)
14 // Round-trip: Node loads it back and reports the right type.
15 const key = crypto.createPrivateKey(privateKeyPem)
16 expect(key.asymmetricKeyType).toBe('ed25519')
17 })
18
19 it('public + private from the same call match each other (sign/verify)', () => {
20 const { publicKeyOpenSsh, privateKeyPem } = generateKeypair('test')
21
22 // Decode the OpenSSH public key back to raw bytes and reconstruct an SPKI key.
23 const b64 = publicKeyOpenSsh.split(' ')[1]
24 const blob = Buffer.from(b64, 'base64')
25 // ssh-ed25519 framing: <4 bytes len><"ssh-ed25519"><4 bytes len><32 bytes raw key>
26 const algoLen = blob.readUInt32BE(0)
27 const keyLen = blob.readUInt32BE(4 + algoLen)
28 const rawPublic = blob.subarray(4 + algoLen + 4, 4 + algoLen + 4 + keyLen)
29 expect(rawPublic.length).toBe(32)
30
31 // Wrap raw key in the canonical 12-byte SPKI prefix for ed25519 (RFC 8410).
32 const spkiPrefix = Buffer.from('302a300506032b6570032100', 'hex')
33 const spki = Buffer.concat([spkiPrefix, rawPublic])
34 const publicKey = crypto.createPublicKey({ key: spki, format: 'der', type: 'spki' })
35 const privateKey = crypto.createPrivateKey(privateKeyPem)
36
37 const message = Buffer.from('test message')
38 const signature = crypto.sign(null, message, privateKey)
39 expect(crypto.verify(null, message, publicKey, signature)).toBe(true)
40 })
41
42 it('rejects keys with unexpected raw length', () => {
43 // Internal sanity \u2014 generateKeypair should never produce one, but the helper
44 // it relies on must error on wrong-sized input.
45 // (Tested indirectly via the keypair generator.)
46 const { publicKeyOpenSsh } = generateKeypair('comment with spaces ok')
47 expect(publicKeyOpenSsh).toContain('comment with spaces ok')
48 })
49})