Skip to content

Commit 180e91e

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 180e91e

File tree

2 files changed

+61
-36
lines changed

2 files changed

+61
-36
lines changed

README.md

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,39 @@ Development/testing is done against OpenSSH (7.6 currently).
88

99
# Table of Contents
1010

11-
* [Requirements](#requirements)
12-
* [Installation](#installation)
13-
* [Client Examples](#client-examples)
14-
* [Execute `uptime` on a server](#execute-uptime-on-a-server)
15-
* [Start an interactive shell session](#start-an-interactive-shell-session)
16-
* [Send a raw HTTP request to port 80 on the server](#send-a-raw-http-request-to-port-80-on-the-server)
17-
* [Forward local connections to port 8000 on the server to us](#forward-local-connections-to-port-8000-on-the-server-to-us)
18-
* [Get a directory listing via SFTP](#get-a-directory-listing-via-sftp)
19-
* [Connection hopping](#connection-hopping)
20-
* [Forward remote X11 connections](#forward-remote-x11-connections)
21-
* [Dynamic (1:1) port forwarding using a SOCKSv5 proxy (using `socksv5`)](#dynamic-11-port-forwarding-using-a-socksv5-proxy-using-socksv5)
22-
* [Make HTTP(S) connections easily using a custom http(s).Agent](#make-https-connections-easily-using-a-custom-httpsagent)
23-
* [Invoke an arbitrary subsystem (e.g. netconf)](#invoke-an-arbitrary-subsystem)
24-
* [Server Examples](#server-examples)
25-
* [Password and public key authentication and non-interactive (exec) command execution](#password-and-public-key-authentication-and-non-interactive-exec-command-execution)
26-
* [SFTP-only server](#sftp-only-server)
27-
* [API](#api)
28-
* [Client](#client)
29-
* [Client events](#client-events)
30-
* [Client methods](#client-methods)
31-
* [Server](#server)
32-
* [Server events](#server-events)
33-
* [Server methods](#server-methods)
34-
* [Connection events](#connection-events)
35-
* [Connection methods](#connection-methods)
36-
* [Session events](#session-events)
37-
* [Channel](#channel)
38-
* [Pseudo-TTY settings](#pseudo-tty-settings)
39-
* [Terminal modes](#terminal-modes)
40-
* [HTTPAgent](#httpagent)
41-
* [HTTPAgent methods](#httpagent-methods)
11+
- [Description](#Description)
12+
- [Table of Contents](#Table-of-Contents)
13+
- [Requirements](#Requirements)
14+
- [Installation](#Installation)
15+
- [Client Examples](#Client-Examples)
16+
- [Execute `uptime` on a server](#Execute-uptime-on-a-server)
17+
- [Start an interactive shell session](#Start-an-interactive-shell-session)
18+
- [Send a raw HTTP request to port 80 on the server](#Send-a-raw-HTTP-request-to-port-80-on-the-server)
19+
- [Forward local connections to port 8000 on the server to us](#Forward-local-connections-to-port-8000-on-the-server-to-us)
20+
- [Get a directory listing via SFTP](#Get-a-directory-listing-via-SFTP)
21+
- [Connection hopping](#Connection-hopping)
22+
- [Forward remote X11 connections](#Forward-remote-X11-connections)
23+
- [Dynamic (1:1) port forwarding using a SOCKSv5 proxy (using socksv5)](#Dynamic-11-port-forwarding-using-a-SOCKSv5-proxy-using-socksv5)
24+
- [Make HTTP(S) connections easily using a custom http(s).Agent](#Make-HTTPS-connections-easily-using-a-custom-httpsAgent)
25+
- [Invoke an arbitrary subsystem](#Invoke-an-arbitrary-subsystem)
26+
- [Server Examples](#Server-Examples)
27+
- [Password and public key authentication and non-interactive (exec) command execution](#Password-and-public-key-authentication-and-non-interactive-exec-command-execution)
28+
- [SFTP-only server](#SFTP-only-server)
29+
- [API](#API)
30+
- [Client](#Client)
31+
- [Client events](#Client-events)
32+
- [Client methods](#Client-methods)
33+
- [Server](#Server)
34+
- [Server events](#Server-events)
35+
- [Server methods](#Server-methods)
36+
- [Connection events](#Connection-events)
37+
- [Connection methods](#Connection-methods)
38+
- [Session events](#Session-events)
39+
- [Channel](#Channel)
40+
- [Pseudo-TTY settings](#Pseudo-TTY-settings)
41+
- [Terminal modes](#Terminal-modes)
42+
- [HTTPAgent](#HTTPAgent)
43+
- [HTTPAgent methods](#HTTPAgent-methods)
4244

4345
## Requirements
4446

@@ -716,6 +718,8 @@ You can find more examples in the `examples` directory of this repository.
716718

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

721+
* **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)
722+
719723
* **passphrase** - _string_ - For an encrypted private key, this is the passphrase used to decrypt it. **Default:** (none)
720724

721725
* **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)