mirror your GitHub repos to tangled.org automatically
1

Configure Feed

Select the types of activity you want to include in your feed.

1import { execFileSync } from 'node:child_process' 2import crypto from 'node:crypto' 3import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' 4import os from 'node:os' 5import path from 'node:path' 6import { describe, expect, it } from 'vitest' 7import { generateKeypair, pkcs8ToOpenSshPrivate } from '../../server/utils/ssh-keypair' 8 9describe('ssh-keypair', () => { 10 it('produces an OpenSSH-formatted ed25519 public key', () => { 11 const { publicKeyOpenSsh } = generateKeypair('synchub.to/123') 12 expect(publicKeyOpenSsh).toMatch(/^ssh-ed25519 [A-Za-z0-9+/=]+ synchub\.to\/123$/) 13 }) 14 15 it('produces a PKCS#8 PEM private key Node can load', () => { 16 const { privateKeyPem } = generateKeypair('test') 17 expect(privateKeyPem).toMatch(/^-----BEGIN PRIVATE KEY-----/) 18 // Round-trip: Node loads it back and reports the right type. 19 const key = crypto.createPrivateKey(privateKeyPem) 20 expect(key.asymmetricKeyType).toBe('ed25519') 21 }) 22 23 it('public + private from the same call match each other (sign/verify)', () => { 24 const { publicKeyOpenSsh, privateKeyPem } = generateKeypair('test') 25 26 // Decode the OpenSSH public key back to raw bytes and reconstruct an SPKI key. 27 const b64 = publicKeyOpenSsh.split(' ')[1] 28 const blob = Buffer.from(b64, 'base64') 29 // ssh-ed25519 framing: <4 bytes len><"ssh-ed25519"><4 bytes len><32 bytes raw key> 30 const algoLen = blob.readUInt32BE(0) 31 const keyLen = blob.readUInt32BE(4 + algoLen) 32 const rawPublic = blob.subarray(4 + algoLen + 4, 4 + algoLen + 4 + keyLen) 33 expect(rawPublic.length).toBe(32) 34 35 // Wrap raw key in the canonical 12-byte SPKI prefix for ed25519 (RFC 8410). 36 const spkiPrefix = Buffer.from('302a300506032b6570032100', 'hex') 37 const spki = Buffer.concat([spkiPrefix, rawPublic]) 38 const publicKey = crypto.createPublicKey({ key: spki, format: 'der', type: 'spki' }) 39 const privateKey = crypto.createPrivateKey(privateKeyPem) 40 41 const message = Buffer.from('test message') 42 const signature = crypto.sign(null, message, privateKey) 43 expect(crypto.verify(null, message, publicKey, signature)).toBe(true) 44 }) 45 46 it('rejects keys with unexpected raw length', () => { 47 // Internal sanity \u2014 generateKeypair should never produce one, but the helper 48 // it relies on must error on wrong-sized input. 49 // (Tested indirectly via the keypair generator.) 50 const { publicKeyOpenSsh } = generateKeypair('comment with spaces ok') 51 expect(publicKeyOpenSsh).toContain('comment with spaces ok') 52 }) 53 54 describe('pkcs8ToOpenSshPrivate', () => { 55 it('produces a PEM-wrapped OpenSSH private key block', () => { 56 const { privateKeyPem } = generateKeypair('test') 57 const openssh = pkcs8ToOpenSshPrivate(privateKeyPem, 'test-key') 58 expect(openssh).toMatch(/^-----BEGIN OPENSSH PRIVATE KEY-----\n/) 59 expect(openssh).toMatch(/-----END OPENSSH PRIVATE KEY-----\n$/) 60 // Lines between markers should be base64 and <=70 chars. 61 const innerLines = openssh.split('\n').slice(1, -2) 62 for (const line of innerLines) { 63 expect(line.length).toBeLessThanOrEqual(70) 64 expect(line).toMatch(/^[A-Za-z0-9+/=]+$/) 65 } 66 }) 67 68 it('rejects non-ed25519 keys', () => { 69 const { privateKey } = crypto.generateKeyPairSync('rsa', { 70 modulusLength: 2048, 71 privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, 72 }) 73 expect(() => pkcs8ToOpenSshPrivate(privateKey, 'x')) 74 .toThrow(/expected ed25519/) 75 }) 76 77 it('is parseable by the real ssh-keygen, with derived public matching ours', () => { 78 const { publicKeyOpenSsh, privateKeyPem } = generateKeypair('synchub-test') 79 const openssh = pkcs8ToOpenSshPrivate(privateKeyPem, 'synchub-test') 80 81 const dir = mkdtempSync(path.join(os.tmpdir(), 'synchub-ssh-test-')) 82 try { 83 const keyPath = path.join(dir, 'id_ed25519') 84 writeFileSync(keyPath, openssh, { mode: 0o600 }) 85 chmodSync(keyPath, 0o600) 86 87 const derived = execFileSync('ssh-keygen', ['-y', '-f', keyPath], { encoding: 'utf8' }).trim() 88 // ssh-keygen -y emits `ssh-ed25519 <base64>` (no comment). Compare 89 // ignoring the comment we put on `publicKeyOpenSsh`. 90 const ourBase64 = publicKeyOpenSsh.split(' ')[1] 91 const derivedBase64 = derived.split(' ')[1] 92 expect(derivedBase64).toBe(ourBase64) 93 } 94 finally { 95 rmSync(dir, { recursive: true, force: true }) 96 } 97 }) 98 }) 99})