'use strict' /** * bare-ssh2 `keyParser` calls `crypto.sign(alg, data, pemString)` / `verify(..., pemString)`. * Node accepts PEM strings; bare-crypto only accepts `Key` objects. Pear enforces bare-crypto * `exports` (no `bare-crypto/lib/key`), so PEM paths use tweetnacl (already a bare-ssh2 dep). */ const { Ber } = require('asn1') const { Buffer: Asn1Buffer } = require('safer-buffer') const nacl = require('tweetnacl') const ED25519_OID = '1.3.101.112' function pemDer(pem, label) { const re = new RegExp( '^-----BEGIN ' + label.replace(/ /g, '\\s+') + '-----\\r?\\n([\\s\\S]+)\\r?\\n-----END ' + label.replace(/ /g, '\\s+') + '-----', 'm' ) const m = re.exec(String(pem).trim()) if (!m) return null return Asn1Buffer.from(m[1].replace(/\s/g, ''), 'base64') } function parsePkcs8Ed25519Seed(der) { let r = new Ber.Reader(Asn1Buffer.from(der)) r.readSequence() if (r.readInt() !== 0) return null r.readSequence() if (r.readOID() !== ED25519_OID) return null r = new Ber.Reader(r.readString(Ber.OctetString, true)) const inner = r.readString(Ber.OctetString, true) if (!inner || inner.length !== 32) return null return Asn1Buffer.from(inner) } function parseSpkiEd25519Pub(der) { const r = new Ber.Reader(Asn1Buffer.from(der)) r.readSequence() r.readSequence() if (r.readOID() !== ED25519_OID) return null const bs = r.readString(Ber.BitString, true) if (!bs) return null let pub = Asn1Buffer.from(bs) if (pub.length === 33 && pub[0] === 0) pub = pub.slice(1) if (pub.length !== 32) return null return pub } function tryPkcs8Ed25519PrivateKeyFromPem(pem) { const der = pemDer(pem, 'PRIVATE KEY') if (!der) return null try { return parsePkcs8Ed25519Seed(der) } catch { return null } } function trySpkiEd25519PublicKeyFromPem(pem) { const der = pemDer(pem, 'PUBLIC KEY') if (!der) return null try { return parseSpkiEd25519Pub(der) } catch { return null } } function viewToUint8(buf) { const b = Asn1Buffer.from(buf) return new Uint8Array(b.buffer, b.byteOffset, b.byteLength) } /** * @param {import('bare-crypto')} bareCrypto * @param {typeof import('bare-crypto/lib/signature').sign} rawSign */ function wrapSign(bareCrypto, rawSign) { return function sign(algorithm, data, key) { if (typeof key === 'string' && key.includes('PRIVATE KEY')) { const seed = tryPkcs8Ed25519PrivateKeyFromPem(key) if (seed) { const kp = nacl.sign.keyPair.fromSeed(viewToUint8(seed)) const msg = viewToUint8(data) const sig = nacl.sign.detached(msg, kp.secretKey) return Asn1Buffer.from(sig) } } return rawSign.call(bareCrypto, algorithm, data, key) } } /** * @param {import('bare-crypto')} bareCrypto * @param {typeof import('bare-crypto/lib/signature').verify} rawVerify */ function wrapVerify(bareCrypto, rawVerify) { return function verify(algorithm, data, key, signature) { if (typeof key === 'string' && key.includes('PUBLIC KEY')) { const pub = trySpkiEd25519PublicKeyFromPem(key) if (pub) { const msg = viewToUint8(data) const sig = viewToUint8(signature) return nacl.sign.detached.verify(msg, sig, viewToUint8(pub)) } } return rawVerify.call(bareCrypto, algorithm, data, key, signature) } } module.exports = { wrapSign, wrapVerify }