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