Skip to content

Commit db08164

Browse files
committed
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.
1 parent bcb32bc commit db08164

File tree

2 files changed

+28
-5
lines changed

2 files changed

+28
-5
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,8 @@ You can find more examples in the `examples` directory of this repository.
716716

717717
* **privateKey** - _mixed_ - _Buffer_ or _string_ that contains a private key for either key-based or hostbased user authentication (OpenSSH format). **Default:** (none)
718718

719+
* **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)
720+
719721
* **passphrase** - _string_ - For an encrypted private key, this is the passphrase used to decrypt it. **Default:** (none)
720722

721723
* **localHostname** - _string_ - Along with **localUsername** and **privateKey**, set this to a non-empty string for hostbased user authentication. **Default:** (none)

lib/client.js

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ function Client() {
4444
username: undefined,
4545
password: undefined,
4646
privateKey: undefined,
47+
publicKey: undefined,
4748
tryKeyboard: undefined,
4849
agent: undefined,
4950
allowAgentFwd: undefined,
@@ -198,6 +199,10 @@ Client.prototype.connect = function(cfg) {
198199
|| Buffer.isBuffer(cfg.privateKey)
199200
? cfg.privateKey
200201
: undefined);
202+
this.config.publicKey = (typeof cfg.publicKey === 'string'
203+
|| Buffer.isBuffer(cfg.publicKey)
204+
? cfg.publicKey
205+
: undefined);
201206
this.config.localHostname = (typeof cfg.localHostname === 'string'
202207
&& cfg.localHostname.length
203208
? cfg.localHostname
@@ -235,7 +240,7 @@ Client.prototype.connect = function(cfg) {
235240
this._agentFwdEnabled = false;
236241
this._curChan = -1;
237242
this._remoteVer = undefined;
238-
var privateKey;
243+
var privateKey, publicKey;
239244

240245
if (this.config.privateKey) {
241246
privateKey = parseKey(this.config.privateKey, cfg.passphrase);
@@ -245,6 +250,22 @@ Client.prototype.connect = function(cfg) {
245250
privateKey = privateKey[0]; // OpenSSH's newer format only stores 1 key for now
246251
if (privateKey.getPrivatePEM() === null)
247252
throw new Error('privateKey value does not contain a (valid) private key');
253+
254+
if (this.config.publicKey) {
255+
publicKey = parseKey(this.config.publicKey);
256+
if (publicKey instanceof Error)
257+
throw new Error('Cannot parse publicKey: ' + publicKey.message);
258+
if (Array.isArray(publicKey))
259+
publicKey = publicKey[0]; // OpenSSH's newer format only stores 1 key for now
260+
if (publicKey.getPublicSSH() === null)
261+
throw new Error('publicKey value does not contain a (valid) public key');
262+
if (publicKey.getPublicPEM() !== privateKey.getPublicPEM()) {
263+
throw new Error('publicKey does not belong to the private key');
264+
}
265+
}
266+
else {
267+
publicKey = privateKey;
268+
}
248269
}
249270

250271
var stream = this._sshstream = new SSH2Stream({
@@ -383,7 +404,7 @@ Client.prototype.connect = function(cfg) {
383404
var authsAllowed = ['none'];
384405
if (this.config.password !== undefined)
385406
authsAllowed.push('password');
386-
if (privateKey !== undefined)
407+
if (privateKey !== undefined && publicKey !== undefined)
387408
authsAllowed.push('publickey');
388409
if (this.config.agent !== undefined)
389410
authsAllowed.push('agent');
@@ -425,7 +446,7 @@ Client.prototype.connect = function(cfg) {
425446
stream.authPassword(self.config.username, self.config.password);
426447
break;
427448
case 'publickey':
428-
stream.authPK(self.config.username, privateKey);
449+
stream.authPK(self.config.username, publicKey);
429450
stream.once('USERAUTH_PK_OK', onUSERAUTH_PK_OK);
430451
break;
431452
case 'hostbased':
@@ -442,7 +463,7 @@ Client.prototype.connect = function(cfg) {
442463
cb(signature);
443464
}
444465
stream.authHostbased(self.config.username,
445-
privateKey,
466+
publicKey,
446467
self.config.localHostname,
447468
self.config.localUsername,
448469
hostbasedCb);
@@ -569,7 +590,7 @@ Client.prototype.connect = function(cfg) {
569590
});
570591
});
571592
} else if (curAuth === 'publickey') {
572-
stream.authPK(self.config.username, privateKey, function(buf, cb) {
593+
stream.authPK(self.config.username, publicKey, function(buf, cb) {
573594
var signature = privateKey.sign(buf);
574595
if (signature instanceof Error) {
575596
signature.message = 'Error while signing data with privateKey: '

0 commit comments

Comments
 (0)