Starting work on Implementing SSH Server Service

This commit is contained in:
Raven Scott
2026-04-08 20:02:12 -04:00
parent ef14f23a10
commit 4757b2a4b0
219 changed files with 72548 additions and 518 deletions
@@ -0,0 +1,159 @@
'use strict'
const bareCrypto = require('bare-crypto')
const { Ber } = require('asn1')
const { Buffer: Asn1Buffer } = require('safer-buffer')
const { getCiphers, getHashes } = require('./lists.js')
const nextTick =
typeof queueMicrotask === 'function'
? (fn) => queueMicrotask(fn)
: (fn) => setTimeout(fn, 0)
/**
* Encode Ed25519 raw keys as SPKI / PKCS#8 DER (same layout as Node/OpenSSL RFC 8410),
* which bare-ssh2 `lib/keygen.js` parses via `convertKeys`.
*/
function ed25519RawToDer(pub32, privSeed32) {
const pub = Asn1Buffer.from(pub32)
const priv = Asn1Buffer.from(privSeed32)
const pubWriter = new Ber.Writer()
pubWriter.startSequence()
pubWriter.startSequence()
pubWriter.writeOID('1.3.101.112')
pubWriter.endSequence()
pubWriter.startSequence(Ber.BitString)
pubWriter.writeByte(0x00)
pubWriter._ensure(pub.length)
pubWriter._buf.set(pub, pubWriter._offset)
pubWriter._offset += pub.length
pubWriter.endSequence()
pubWriter.endSequence()
const privWriter = new Ber.Writer()
privWriter.startSequence()
privWriter.writeInt(0x00, Ber.Integer)
privWriter.startSequence()
privWriter.writeOID('1.3.101.112')
privWriter.endSequence()
privWriter.startSequence(Ber.OctetString)
privWriter.writeBuffer(priv, Ber.OctetString)
privWriter.endSequence()
privWriter.endSequence()
return { publicKey: pubWriter.buffer, privateKey: privWriter.buffer }
}
function generateKeyPairSyncBareEd25519(type, options) {
const pubEnc = (options && options.publicKeyEncoding) || {}
const privEnc = (options && options.privateKeyEncoding) || {}
if (
String(type).toLowerCase() !== 'ed25519' ||
pubEnc.type !== 'spki' ||
pubEnc.format !== 'der' ||
privEnc.type !== 'pkcs8' ||
privEnc.format !== 'der'
) {
throw new Error(
'Bare crypto shim: generateKeyPairSync only supports ed25519 with { spki, der } / { pkcs8, der } encodings'
)
}
const { publicKey, privateKey } = bareCrypto.generateKeyPair('ed25519')
const pubRaw = publicKey._key
const privRaw = privateKey._key
const pub32 = Asn1Buffer.from(pubRaw)
let priv32 = Asn1Buffer.from(privRaw)
if (priv32.length === 64) priv32 = Asn1Buffer.from(priv32.subarray(0, 32))
if (pub32.length !== 32 || priv32.length !== 32) {
throw new Error('Unexpected Ed25519 key material length from bare-crypto')
}
return ed25519RawToDer(pub32, priv32)
}
function generateKeyPairSync(type, options) {
if (typeof bareCrypto.generateKeyPairSync === 'function') {
return bareCrypto.generateKeyPairSync(type, options)
}
const t = String(type || '').toLowerCase()
if (t === 'x25519') {
const x = require('./x25519-compat.js')
if (options && Object.keys(options).length > 0) {
const pubEnc = options.publicKeyEncoding || {}
const privEnc = options.privateKeyEncoding || {}
if (
pubEnc.type !== 'spki' ||
pubEnc.format !== 'der' ||
privEnc.type !== 'pkcs8' ||
privEnc.format !== 'der'
) {
throw new Error(
'Bare crypto shim: x25519 generateKeyPairSync only supports default or spki/pkcs8 der'
)
}
}
return x.generateKeyPairSync()
}
if (t === 'ed25519') return generateKeyPairSyncBareEd25519(type, options)
throw new Error(`Bare crypto shim: generateKeyPairSync not supported for type ${type}`)
}
function generateKeyPair(type, options, callback) {
if (typeof options === 'function') {
callback = options
options = undefined
}
const native = bareCrypto.generateKeyPair
if (typeof callback === 'function') {
if (typeof native === 'function' && native.length >= 3) {
return native.call(bareCrypto, type, options, callback)
}
nextTick(() => {
try {
const pair = generateKeyPairSync(type, options)
callback(null, pair.publicKey, pair.privateKey)
} catch (err) {
callback(err)
}
})
return
}
if (typeof native === 'function') {
return native.call(bareCrypto, type, options)
}
}
function createPublicKey(descriptor) {
if (
descriptor &&
descriptor.type === 'spki' &&
descriptor.format === 'der' &&
descriptor.key
) {
return require('./x25519-compat.js').createPublicKeyFromSpkiDer(descriptor.key)
}
if (typeof bareCrypto.createPublicKey === 'function') {
return bareCrypto.createPublicKey(descriptor)
}
throw new Error('Bare crypto shim: createPublicKey only supports X25519 SPKI DER on Bare')
}
function diffieHellman(opts) {
return require('./x25519-compat.js').diffieHellman(opts)
}
function createECDH(curveName) {
throw new Error(
`Bare crypto shim: createECDH(${curveName}) is not implemented; bare-ssh2 should negotiate curve25519-sha256`
)
}
module.exports = Object.assign({}, bareCrypto, {
getCiphers,
getHashes,
generateKeyPairSync,
generateKeyPair,
createPublicKey,
diffieHellman,
createECDH
})
@@ -0,0 +1,153 @@
'use strict'
const { Ber } = require('asn1')
const { Buffer: Asn1Buffer } = require('safer-buffer')
const { Buffer: RuntimeBuffer } = require('buffer')
const X25519_OID = '1.3.101.110'
function getNacl() {
return require('tweetnacl')
}
/** @param {import('safer-buffer').Buffer | Uint8Array} buf */
function u8(buf) {
const b = Asn1Buffer.from(buf)
return new Uint8Array(b.buffer, b.byteOffset, b.byteLength)
}
function berSpkiX25519(pub32) {
const pub = Asn1Buffer.from(pub32)
const w = new Ber.Writer()
w.startSequence()
w.startSequence()
w.writeOID(X25519_OID)
w.endSequence()
w.startSequence(Ber.BitString)
w.writeByte(0x00)
w._ensure(pub.length)
w._buf.set(pub, w._offset)
w._offset += pub.length
w.endSequence()
w.endSequence()
return w.buffer
}
function berPkcs8X25519(priv32) {
const priv = Asn1Buffer.from(priv32)
const w = new Ber.Writer()
w.startSequence()
w.writeInt(0x00, Ber.Integer)
w.startSequence()
w.writeOID(X25519_OID)
w.endSequence()
w.startSequence(Ber.OctetString)
w.writeBuffer(priv, Ber.OctetString)
w.endSequence()
w.endSequence()
return w.buffer
}
/** Match bare-ssh2 kex: SPKI DER ends with 32-byte X25519 public key. */
function parseSpkiX25519PubDer(der) {
const b = Asn1Buffer.from(der)
if (b.length < 32) throw new Error('Invalid X25519 SPKI DER')
return u8(b.slice(-32))
}
function parsePkcs8X25519PrivDer(der) {
const { Ber: BerR } = require('asn1')
let reader = new BerR.Reader(Asn1Buffer.from(der))
reader.readSequence()
if (reader.readInt() !== 0) throw new Error('Invalid PKCS#8 version')
reader.readSequence()
if (reader.readOID() !== X25519_OID) throw new Error('Invalid X25519 PKCS#8 OID')
reader = new BerR.Reader(reader.readString(BerR.OctetString, true))
return u8(reader.readString(BerR.OctetString, true))
}
class X25519PublicKey {
/** @param {Uint8Array | import('safer-buffer').Buffer} pub32 */
constructor(pub32) {
this.type = 'public'
this.asymmetricKeyType = 'x25519'
this._pub = Asn1Buffer.from(pub32)
if (this._pub.length !== 32) throw new Error('X25519 public key must be 32 bytes')
}
/** @param {{ type: string, format: string }} opts */
export(opts) {
if (!opts || opts.format !== 'der') throw new Error('unsupported export format')
if (opts.type === 'spki') return berSpkiX25519(this._pub)
if (opts.type === 'raw') return Asn1Buffer.from(this._pub)
throw new Error('unsupported export type')
}
}
class X25519PrivateKey {
/**
* @param {Uint8Array | import('safer-buffer').Buffer} priv32
* @param {Uint8Array | import('safer-buffer').Buffer | null} pub32
*/
constructor(priv32, pub32) {
this.type = 'private'
this.asymmetricKeyType = 'x25519'
this._priv = Asn1Buffer.from(priv32)
if (this._priv.length !== 32) throw new Error('X25519 private key must be 32 bytes')
this._pub = pub32 ? Asn1Buffer.from(pub32) : null
}
/** @param {{ type: string, format: string }} opts */
export(opts) {
if (!opts || opts.format !== 'der') throw new Error('unsupported export format')
if (opts.type === 'pkcs8') return berPkcs8X25519(this._priv)
throw new Error('unsupported export type')
}
}
function generateKeyPairSync() {
const nacl = getNacl()
const pair = nacl.box.keyPair()
return {
publicKey: new X25519PublicKey(pair.publicKey),
privateKey: new X25519PrivateKey(pair.secretKey, pair.publicKey)
}
}
/** @param {import('safer-buffer').Buffer} der */
function createPublicKeyFromSpkiDer(der) {
return new X25519PublicKey(parseSpkiX25519PubDer(der))
}
/**
* @param {{ privateKey: unknown, publicKey: unknown }} opts
* @returns {import('safer-buffer').Buffer}
*/
function diffieHellman(opts) {
const nacl = getNacl()
let privU8
let pubU8
const sk = opts.privateKey
const pk = opts.publicKey
if (sk instanceof X25519PrivateKey) privU8 = u8(sk._priv)
else if (sk && typeof sk.export === 'function')
privU8 = parsePkcs8X25519PrivDer(sk.export({ type: 'pkcs8', format: 'der' }))
else throw new Error('diffieHellman: unsupported private key')
if (pk instanceof X25519PublicKey) pubU8 = u8(pk._pub)
else if (pk && typeof pk.export === 'function')
pubU8 = parseSpkiX25519PubDer(pk.export({ type: 'spki', format: 'der' }))
else throw new Error('diffieHellman: unsupported public key')
const shared = nacl.scalarMult(privU8, pubU8)
// Use runtime `buffer` (Bare node-buffer) so kex `convertToMpint` / `hash.update` agree on `Buffer.isBuffer`.
return RuntimeBuffer.from(shared)
}
module.exports = {
generateKeyPairSync,
createPublicKeyFromSpkiDer,
diffieHellman,
X25519PublicKey,
X25519PrivateKey
}