117 lines
3.3 KiB
JavaScript
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 }
|