diff --git a/lib/client.js b/lib/client.js index aa94ace0..50048d7b 100644 --- a/lib/client.js +++ b/lib/client.js @@ -84,6 +84,7 @@ class Client extends EventEmitter { username: undefined, password: undefined, privateKey: undefined, + cert: undefined, tryKeyboard: undefined, agent: undefined, allowAgentFwd: undefined, @@ -205,6 +206,10 @@ class Client extends EventEmitter { this.config.password = (typeof cfg.password === 'string' ? cfg.password : undefined); + this.config.cert = (typeof cfg.cert === 'string' + || Buffer.isBuffer(cfg.cert) + ? cfg.cert + : undefined); this.config.privateKey = (typeof cfg.privateKey === 'string' || Buffer.isBuffer(cfg.privateKey) ? cfg.privateKey @@ -253,7 +258,7 @@ class Client extends EventEmitter { this._agentFwdEnabled = false; this._agent = (this.config.agent ? this.config.agent : undefined); this._remoteVer = undefined; - let privateKey; + let privateKey, cert; if (this.config.privateKey) { privateKey = parseKey(this.config.privateKey, cfg.passphrase); @@ -270,6 +275,13 @@ class Client extends EventEmitter { } } + if (this.config.cert) { + cert = parseKey(this.config.cert); + if (cert instanceof Error) { + throw new Error(`Cannot parse certificate: ${cert.message}`); + } + } + let hostVerifier; if (typeof cfg.hostVerifier === 'function') { const hashCb = cfg.hostVerifier; @@ -461,7 +473,9 @@ class Client extends EventEmitter { }); }); } else if (curAuth.type === 'publickey') { - proto.authPK(curAuth.username, curAuth.key, keyAlgo, (buf, cb) => { + const pk = curAuth.cert ? curAuth.cert : curAuth.key; + const keyAlgo = curAuth.key.type; + proto.authPK(curAuth.username, pk, keyAlgo, (buf, cb) => { const signature = curAuth.key.sign(buf, hashAlgo); if (signature instanceof Error) { signature.message = @@ -882,7 +896,7 @@ class Client extends EventEmitter { nextAuth = { type, username, password: this.config.password }; break; case 'publickey': - nextAuth = { type, username, key: privateKey }; + nextAuth = { type, username, key: privateKey, cert }; break; case 'hostbased': nextAuth = { @@ -937,11 +951,14 @@ class Client extends EventEmitter { } case 'publickey': { const key = parseKey(nextAuth.key, nextAuth.passphrase); + const cert = nextAuth.cert ? parseKey(nextAuth.cert) : null; if (key instanceof Error) return skipAuth('Skipping invalid key auth attempt'); if (!key.isPrivateKey()) return skipAuth('Skipping non-private key'); - nextAuth = { type, username, key }; + if (cert instanceof Error) + return skipAuth('Skipping invalid cert auth attempt'); + nextAuth = { type, username, key, cert }; break; } case 'hostbased': { @@ -1009,7 +1026,14 @@ class Client extends EventEmitter { ); } } - proto.authPK(username, curAuth.key, keyAlgo); + + if (!keyAlgo) { + keyAlgo = curAuth.key.type; + } + + const pk = curAuth.cert ? curAuth.cert : curAuth.key; + proto.authPK(username, pk, keyAlgo); + break; } case 'hostbased': { diff --git a/lib/protocol/Protocol.js b/lib/protocol/Protocol.js index 73024881..f96d9930 100644 --- a/lib/protocol/Protocol.js +++ b/lib/protocol/Protocol.js @@ -633,6 +633,7 @@ class Protocol { throw new Error('Client-only method called in server mode'); pubKey = parseKey(pubKey); + if (pubKey instanceof Error) throw new Error('Invalid key'); @@ -643,11 +644,10 @@ class Protocol { cbSign = keyAlgo; keyAlgo = undefined; } - if (!keyAlgo) - keyAlgo = keyType; const userLen = Buffer.byteLength(username); - const algoLen = Buffer.byteLength(keyAlgo); + const algoLen = Buffer.byteLength(keyType); + const keyAlgoLen = Buffer.byteLength(keyAlgo); const pubKeyLen = pubKey.length; const sessionID = this._kex.sessionID; const sesLen = sessionID.length; @@ -681,7 +681,7 @@ class Protocol { packet[p += 9] = (cbSign ? 1 : 0); writeUInt32BE(packet, algoLen, ++p); - packet.utf8Write(keyAlgo, p += 4, algoLen); + packet.utf8Write(keyType, p += 4, algoLen); writeUInt32BE(packet, pubKeyLen, p += algoLen); packet.set(pubKey, p += 4); @@ -705,7 +705,7 @@ class Protocol { p = this._packetRW.write.allocStart; packet = this._packetRW.write.alloc( 1 + 4 + userLen + 4 + 14 + 4 + 9 + 1 + 4 + algoLen + 4 + pubKeyLen + 4 - + 4 + algoLen + 4 + sigLen + + 4 + keyAlgoLen + 4 + sigLen ); // TODO: simply copy from original "packet" to new `packet` to avoid @@ -724,17 +724,17 @@ class Protocol { packet[p += 9] = 1; writeUInt32BE(packet, algoLen, ++p); - packet.utf8Write(keyAlgo, p += 4, algoLen); + packet.utf8Write(keyType, p += 4, algoLen); writeUInt32BE(packet, pubKeyLen, p += algoLen); packet.set(pubKey, p += 4); - writeUInt32BE(packet, 4 + algoLen + 4 + sigLen, p += pubKeyLen); + writeUInt32BE(packet, 4 + keyAlgoLen + 4 + sigLen, p += pubKeyLen); - writeUInt32BE(packet, algoLen, p += 4); - packet.utf8Write(keyAlgo, p += 4, algoLen); + writeUInt32BE(packet, keyAlgoLen, p += 4); + packet.utf8Write(keyAlgo, p += 4, keyAlgoLen); - writeUInt32BE(packet, sigLen, p += algoLen); + writeUInt32BE(packet, sigLen, p += keyAlgoLen); packet.set(signature, p += 4); // Servers shouldn't send packet type 60 in response to signed publickey diff --git a/lib/protocol/keyParser.js b/lib/protocol/keyParser.js index a276c1ae..b90fa7e9 100644 --- a/lib/protocol/keyParser.js +++ b/lib/protocol/keyParser.js @@ -34,6 +34,7 @@ const SYM_HASH_ALGO = Symbol('Hash Algorithm'); const SYM_PRIV_PEM = Symbol('Private key PEM'); const SYM_PUB_PEM = Symbol('Public key PEM'); const SYM_PUB_SSH = Symbol('Public key SSH'); +const SYM_CERT_SSH = Symbol('Certificate SSH'); const SYM_DECRYPTED = Symbol('Decrypted Key'); // Create OpenSSL cipher name -> SSH cipher name conversion table @@ -1350,6 +1351,11 @@ function parseDER(data, baseType, comment, fullType) { break; } case 'ssh-ed25519': { + if (fullType.endsWith('-cert-v01@openssh.com')) { + pubSSH = data; + break; + } + const edpub = readString(data, data._pos || 0); if (edpub === undefined || edpub.length !== 32) return new Error('Malformed OpenSSH public key');