Files
bare-operating-system/packages/bare-os-openssh/vendor/bare-ssh2/shims/crypto/pem-ed25519.js
T
2026-04-08 20:12:29 -04:00

117 lines
3.3 KiB
JavaScript

'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 }