From db0816474f2e61416fb58577d1d68a9c42e8cec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sun, 21 Jul 2019 17:05:59 +0200 Subject: [PATCH] Client: Allow explicitely specifying a publicKey This is to support SSH certificates. As before the privateKey will be used for the publicKey (i.e. the derived publicKey) if nothing is given. The given publicKey is checked to match the given privateKey. --- README.md | 2 ++ lib/client.js | 31 ++++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8565d3b6..81c34f48 100644 --- a/README.md +++ b/README.md @@ -716,6 +716,8 @@ You can find more examples in the `examples` directory of this repository. * **privateKey** - _mixed_ - _Buffer_ or _string_ that contains a private key for either key-based or hostbased user authentication (OpenSSH format). **Default:** (none) + * **publicKey** - _mixed_ - _Buffer_ or _string_ that contains a public key or SSH certificate for either key-based or hostbased user authentication (OpenSSH format). **Default:** (derived from private key) + * **passphrase** - _string_ - For an encrypted private key, this is the passphrase used to decrypt it. **Default:** (none) * **localHostname** - _string_ - Along with **localUsername** and **privateKey**, set this to a non-empty string for hostbased user authentication. **Default:** (none) diff --git a/lib/client.js b/lib/client.js index e3ed0781..b076fa67 100644 --- a/lib/client.js +++ b/lib/client.js @@ -44,6 +44,7 @@ function Client() { username: undefined, password: undefined, privateKey: undefined, + publicKey: undefined, tryKeyboard: undefined, agent: undefined, allowAgentFwd: undefined, @@ -198,6 +199,10 @@ Client.prototype.connect = function(cfg) { || Buffer.isBuffer(cfg.privateKey) ? cfg.privateKey : undefined); + this.config.publicKey = (typeof cfg.publicKey === 'string' + || Buffer.isBuffer(cfg.publicKey) + ? cfg.publicKey + : undefined); this.config.localHostname = (typeof cfg.localHostname === 'string' && cfg.localHostname.length ? cfg.localHostname @@ -235,7 +240,7 @@ Client.prototype.connect = function(cfg) { this._agentFwdEnabled = false; this._curChan = -1; this._remoteVer = undefined; - var privateKey; + var privateKey, publicKey; if (this.config.privateKey) { privateKey = parseKey(this.config.privateKey, cfg.passphrase); @@ -245,6 +250,22 @@ Client.prototype.connect = function(cfg) { privateKey = privateKey[0]; // OpenSSH's newer format only stores 1 key for now if (privateKey.getPrivatePEM() === null) throw new Error('privateKey value does not contain a (valid) private key'); + + if (this.config.publicKey) { + publicKey = parseKey(this.config.publicKey); + if (publicKey instanceof Error) + throw new Error('Cannot parse publicKey: ' + publicKey.message); + if (Array.isArray(publicKey)) + publicKey = publicKey[0]; // OpenSSH's newer format only stores 1 key for now + if (publicKey.getPublicSSH() === null) + throw new Error('publicKey value does not contain a (valid) public key'); + if (publicKey.getPublicPEM() !== privateKey.getPublicPEM()) { + throw new Error('publicKey does not belong to the private key'); + } + } + else { + publicKey = privateKey; + } } var stream = this._sshstream = new SSH2Stream({ @@ -383,7 +404,7 @@ Client.prototype.connect = function(cfg) { var authsAllowed = ['none']; if (this.config.password !== undefined) authsAllowed.push('password'); - if (privateKey !== undefined) + if (privateKey !== undefined && publicKey !== undefined) authsAllowed.push('publickey'); if (this.config.agent !== undefined) authsAllowed.push('agent'); @@ -425,7 +446,7 @@ Client.prototype.connect = function(cfg) { stream.authPassword(self.config.username, self.config.password); break; case 'publickey': - stream.authPK(self.config.username, privateKey); + stream.authPK(self.config.username, publicKey); stream.once('USERAUTH_PK_OK', onUSERAUTH_PK_OK); break; case 'hostbased': @@ -442,7 +463,7 @@ Client.prototype.connect = function(cfg) { cb(signature); } stream.authHostbased(self.config.username, - privateKey, + publicKey, self.config.localHostname, self.config.localUsername, hostbasedCb); @@ -569,7 +590,7 @@ Client.prototype.connect = function(cfg) { }); }); } else if (curAuth === 'publickey') { - stream.authPK(self.config.username, privateKey, function(buf, cb) { + stream.authPK(self.config.username, publicKey, function(buf, cb) { var signature = privateKey.sign(buf); if (signature instanceof Error) { signature.message = 'Error while signing data with privateKey: '