Skip to content

Add support for SSH certificates #137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions lib/keyParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1223,7 +1223,7 @@ function parseDER(data, baseType, comment, fullType) {
if (n === false)
return new Error('Malformed OpenSSH public key');
pubPEM = genOpenSSLRSAPub(n, e);
pubSSH = genOpenSSHRSAPub(n, e);
pubSSH = data;
algo = 'sha1';
break;
case 'ssh-dss':
Expand All @@ -1240,15 +1240,15 @@ function parseDER(data, baseType, comment, fullType) {
if (y === false)
return new Error('Malformed OpenSSH public key');
pubPEM = genOpenSSLDSAPub(p, q, g, y);
pubSSH = genOpenSSHDSAPub(p, q, g, y);
pubSSH = data;
algo = 'sha1';
break;
case 'ssh-ed25519':
var edpub = utils.readString(data, data._pos);
if (edpub === false || edpub.length !== 32)
return new Error('Malformed OpenSSH public key');
pubPEM = genOpenSSLEdPub(edpub);
pubSSH = genOpenSSHEdPub(edpub);
pubSSH = data;
algo = null;
break;
case 'ecdsa-sha2-nistp256':
Expand All @@ -1271,7 +1271,7 @@ function parseDER(data, baseType, comment, fullType) {
if (ecpub === false)
return new Error('Malformed OpenSSH public key');
pubPEM = genOpenSSLECDSAPub(oid, ecpub);
pubSSH = genOpenSSHECDSAPub(oid, ecpub);
pubSSH = data;
break;
default:
return new Error('Unsupported OpenSSH public key type: ' + baseType);
Expand Down Expand Up @@ -1316,6 +1316,12 @@ OpenSSH_Public.prototype = BaseKey;
var type = utils.readString(data, data._pos, 'ascii');
if (type === false || type.indexOf(baseType) !== 0)
return new Error('Malformed OpenSSH public key');
if (/-cert-v0[01]@openssh.com/.test(type)) {
var nonce = utils.readString(data, data._pos);
if (nonce === false) {
return new Error('Malformed OpenSSH certificate');
}
}

return parseDER(data, baseType, comment, fullType);
};
Expand Down
76 changes: 51 additions & 25 deletions lib/ssh.js
Original file line number Diff line number Diff line change
Expand Up @@ -1580,19 +1580,19 @@ SSH2Stream.prototype.authPK = function(username, pubKey, cbSign) {

var self = this;
var outstate = this._state.outgoing;
var keyType;
var pubKeyType;

if (typeof pubKey.getPublicSSH === 'function') {
keyType = pubKey.type;
pubKeyType = pubKey.type;
pubKey = pubKey.getPublicSSH();
} else {
keyType = pubKey.toString('ascii',
pubKeyType = pubKey.toString('ascii',
4,
4 + readUInt32BE(pubKey, 0));
}

var userLen = Buffer.byteLength(username);
var algoLen = Buffer.byteLength(keyType);
var pubKeyTypeLen = Buffer.byteLength(pubKeyType);
var pubKeyLen = pubKey.length;
var sesLen = outstate.sessionId.length;
var p = 0;
Expand All @@ -1602,7 +1602,7 @@ SSH2Stream.prototype.authPK = function(username, pubKey, cbSign) {
+ 4 + 14 // "ssh-connection"
+ 4 + 9 // "publickey"
+ 1
+ 4 + algoLen
+ 4 + pubKeyTypeLen
+ 4 + pubKeyLen
);

Expand All @@ -1625,10 +1625,10 @@ SSH2Stream.prototype.authPK = function(username, pubKey, cbSign) {

buf[p += 9] = (cbSign ? 1 : 0);

writeUInt32BE(buf, algoLen, ++p);
buf.write(keyType, p += 4, algoLen, 'ascii');
writeUInt32BE(buf, pubKeyTypeLen, ++p);
buf.write(pubKeyType, p += 4, pubKeyTypeLen, 'ascii');

writeUInt32BE(buf, pubKeyLen, p += algoLen);
writeUInt32BE(buf, pubKeyLen, p += pubKeyTypeLen);
pubKey.copy(buf, p += 4);

if (!cbSign) {
Expand All @@ -1638,20 +1638,24 @@ SSH2Stream.prototype.authPK = function(username, pubKey, cbSign) {
}

cbSign(buf, function(signature) {
signature = convertSignature(signature, keyType);
if (signature === false)
var converted = convertSignature(signature, pubKeyType);

if (converted === false)
throw new Error('Error while converting handshake signature');
signature = converted.signature;
var signatureType = converted.keyType;
var signatureTypeLen = Buffer.byteLength(signatureType);

var sigLen = signature.length;
var sigbuf = Buffer.allocUnsafe(1
+ 4 + userLen
+ 4 + 14 // "ssh-connection"
+ 4 + 9 // "publickey"
+ 1
+ 4 + algoLen
+ 4 + pubKeyTypeLen
+ 4 + pubKeyLen
+ 4 // 4 + algoLen + 4 + sigLen
+ 4 + algoLen
+ 4 // 4 + signatureTypeLen + 4 + sigLen
+ 4 + signatureTypeLen
+ 4 + sigLen);

p = 0;
Expand All @@ -1669,15 +1673,15 @@ SSH2Stream.prototype.authPK = function(username, pubKey, cbSign) {

sigbuf[p += 9] = 1;

writeUInt32BE(sigbuf, algoLen, ++p);
sigbuf.write(keyType, p += 4, algoLen, 'ascii');
writeUInt32BE(sigbuf, pubKeyTypeLen, ++p);
sigbuf.write(pubKeyType, p += 4, pubKeyTypeLen, 'ascii');

writeUInt32BE(sigbuf, pubKeyLen, p += algoLen);
writeUInt32BE(sigbuf, pubKeyLen, p += pubKeyTypeLen);
pubKey.copy(sigbuf, p += 4);
writeUInt32BE(sigbuf, 4 + algoLen + 4 + sigLen, p += pubKeyLen);
writeUInt32BE(sigbuf, algoLen, p += 4);
sigbuf.write(keyType, p += 4, algoLen, 'ascii');
writeUInt32BE(sigbuf, sigLen, p += algoLen);
writeUInt32BE(sigbuf, 4 + signatureTypeLen + 4 + sigLen, p += pubKeyLen);
writeUInt32BE(sigbuf, signatureTypeLen, p += 4);
sigbuf.write(signatureType, p += 4, signatureTypeLen, 'ascii');
writeUInt32BE(sigbuf, sigLen, p += signatureTypeLen);
signature.copy(sigbuf, p += 4);

// Servers shouldn't send packet type 60 in response to signed publickey
Expand Down Expand Up @@ -1753,10 +1757,11 @@ SSH2Stream.prototype.authHostbased = function(username, pubKey, hostname,
buf.write(userlocal, p += 4, userlocalLen, 'utf8');

cbSign(buf, function(signature) {
signature = convertSignature(signature, keyType);
var converted = convertSignature(signature, keyType);
if (signature === false)
throw new Error('Error while converting handshake signature');

signature = converted.signature;
var sigLen = signature.length;
var sigbuf = Buffer.allocUnsafe((buf.length - sesLen) + sigLen);

Expand Down Expand Up @@ -5000,14 +5005,16 @@ function KEXDH_REPLY(self, e) { // Server
return false;
}

signature = convertSignature(signature, hostkeyAlgo);
var converted = convertSignature(signature, hostkeyAlgo);
if (signature === false) {
signature.message = 'Error while converting handshake signature';
signature.level = 'handshake';
self.emit('error', signature);
self.disconnect(DISCONNECT_REASON.KEY_EXCHANGE_FAILED);
return false;
}
signature = converted.signature;
hostKeyAlgo = converted.keyType;

/*
byte SSH_MSG_KEXDH_REPLY
Expand Down Expand Up @@ -5250,16 +5257,35 @@ function tryComputeSecret(dh, e) {
}

function convertSignature(signature, keyType) {
switch (keyType) {
case '[email protected]':
case '[email protected]':
case 'ecdsa-sha2-nistp256':
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest this entire switch statement is unnecessary and you could just perform the replace blind. But this particular case statement seems redundant as it doesn't end in the appropriate string to qualify for the replace call?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact the two cases below are also identical. These should've been [email protected], [email protected], and [email protected].

Not going to fix it, because this PR realistically is not going to be merged due to the planned rewrite of this library.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not going to fix it, because this PR realistically is not going to be merged due to the planned rewrite of this library.

Are this issues the same in mscdex/ssh2#808 (which also "primarily awaits an updated PR")?

case '[email protected]':
case '[email protected]':
case '[email protected]':
keyType = keyType.replace(/[email protected]$/, '');
}

switch (keyType) {
case 'ssh-dss':
return DSASigBERToBare(signature);
signature = DSASigBERToBare(signature);
break;
case 'ecdsa-sha2-nistp256':
case 'ecdsa-sha2-nistp384':
case 'ecdsa-sha2-nistp521':
return ECDSASigASN1ToSSH(signature);
signature = ECDSASigASN1ToSSH(signature);
break;
}

return signature;
if (signature === false) {
return false;
}

return {
signature: signature,
keyType: keyType
}
}

var timingSafeEqual = (function() {
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/openssh_cert_rsa-cert.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[email protected] AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgz5fIcgoIkQsJZQDfctoMKo7Iq/3X0DMXdPjncT7qzAAAAAADAQABAAABAQC23GMXX23QWyEy0GLUvR4fIXhyxqyfFfoJG/bBIi4br8CTkvAqkt6dMuOHOPKRlDwrMjcM0fIuSm87LDrZ1at1z0xtuVjHBoBXfp1FyGfKc2qFtqVnIX6pq2PFoVf8dcL/xR/OP9VMqx6O+PMv/jEXkENw6hEy0PtXuwYDg9VLI04QDtrx6v44BUbUvu8Qq9f8G5zcFBxMr8BdaPP6sdyWGVk05MRa7j+uC5zS3KS8dGV0PIOEeUmcVuhJsRRSVgBzzUq2mA1doui3GWA7vMQOyn4x89fMi3gXfjM8XMZA7Kc/35Nc0GxqtTYQT8G/axp2WdA7vyI1uz+4lk29ITW/AAAAAAAAAAAAAAABAAAAFHNzaDItc3RyZWFtcy1maXh0dXJlAAAAAAAAAABdNHdgAAAAAF00hcAAAAAdAAAADWZvcmNlLWNvbW1hbmQAAAAIAAAABGxzIC8AAAAdAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAqQ9PJtVu1y4XS8SvQnmV5va1RtiaSrcPdAcT7PE93lQMLpvsx2qDHSRy8KfqD2rZO9IjU8H6fEqKJEcuLkt+sECk3UlHLFgxhD7MYYW0KFpxfzoE8h5W/8qppXkqIu8uzwn3/+DcgTx2Ce6XN8B/yXBT1kFpnmpiRnyXS8CVKX0HMVYpdlsfUexy3BtXIphSUJsyYGs/1SUuybO0mYPguoYORtp0Od0/vScFgz/h6rzQtJgMsDns+XG4EoRmt1JMzdbBVEC/f154RCBqV2w1CGYlZ09nqOExZTEwGKktAImYn3LElqEcjzWf0PSBJ7awNVF/bGwMO+kQRJCfeKGlFwAAAQ8AAAAHc3NoLXJzYQAAAQCDThDHnBzeoEIlMYr7vzhl8hC7AxiXlsuLathqkYzn7H0AU5eGspfvJysV38vnXt/21TzFBorQ66be8cc/YHLfAaJqdpZEJWsxxqSRkVmAkpzaVN8k9OcVx9BqBS2VFwuanDoAw5JM2NEeZ6byQGd0cgWJcdGZ1K/EXzhYXCFcMPe1ye2Y2mdViDH+mellwuSw+H6Uq+UQbpbHf9fyJjReJ4Pu8C7PRMlD0JaZCFTKi58QlQcneOaQuVrtvZ4wDmvgLtWl+Zsqt9lUTpXZjwazXYDr8zyJ0cU+HMMVZ4E5StOKzTGg91jcOuChhZvkVOeJ2B4+KAsL2X9pu51iJj9h ssh certificate
7 changes: 7 additions & 0 deletions test/fixtures/openssh_cert_rsa-cert.pub.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "[email protected]",
"comment": "ssh certificate",
"public": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAttxjF19t0FshMtBi1L0e\nHyF4csasnxX6CRv2wSIuG6/Ak5LwKpLenTLjhzjykZQ8KzI3DNHyLkpvOyw62dWr\ndc9MbblYxwaAV36dRchnynNqhbalZyF+qatjxaFX/HXC/8Ufzj/VTKsejvjzL/4x\nF5BDcOoRMtD7V7sGA4PVSyNOEA7a8er+OAVG1L7vEKvX/Buc3BQcTK/AXWjz+rHc\nlhlZNOTEWu4/rguc0tykvHRldDyDhHlJnFboSbEUUlYAc81KtpgNXaLotxlgO7zE\nDsp+MfPXzIt4F34zPFzGQOynP9+TXNBsarU2EE/Bv2sadlnQO78iNbs/uJZNvSE1\nvwIDAQAB\n-----END PUBLIC KEY-----",
"publicSSH": "AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgz5fIcgoIkQsJZQDfctoMKo7Iq/3X0DMXdPjncT7qzAAAAAADAQABAAABAQC23GMXX23QWyEy0GLUvR4fIXhyxqyfFfoJG/bBIi4br8CTkvAqkt6dMuOHOPKRlDwrMjcM0fIuSm87LDrZ1at1z0xtuVjHBoBXfp1FyGfKc2qFtqVnIX6pq2PFoVf8dcL/xR/OP9VMqx6O+PMv/jEXkENw6hEy0PtXuwYDg9VLI04QDtrx6v44BUbUvu8Qq9f8G5zcFBxMr8BdaPP6sdyWGVk05MRa7j+uC5zS3KS8dGV0PIOEeUmcVuhJsRRSVgBzzUq2mA1doui3GWA7vMQOyn4x89fMi3gXfjM8XMZA7Kc/35Nc0GxqtTYQT8G/axp2WdA7vyI1uz+4lk29ITW/AAAAAAAAAAAAAAABAAAAFHNzaDItc3RyZWFtcy1maXh0dXJlAAAAAAAAAABdNHdgAAAAAF00hcAAAAAdAAAADWZvcmNlLWNvbW1hbmQAAAAIAAAABGxzIC8AAAAdAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAqQ9PJtVu1y4XS8SvQnmV5va1RtiaSrcPdAcT7PE93lQMLpvsx2qDHSRy8KfqD2rZO9IjU8H6fEqKJEcuLkt+sECk3UlHLFgxhD7MYYW0KFpxfzoE8h5W/8qppXkqIu8uzwn3/+DcgTx2Ce6XN8B/yXBT1kFpnmpiRnyXS8CVKX0HMVYpdlsfUexy3BtXIphSUJsyYGs/1SUuybO0mYPguoYORtp0Od0/vScFgz/h6rzQtJgMsDns+XG4EoRmt1JMzdbBVEC/f154RCBqV2w1CGYlZ09nqOExZTEwGKktAImYn3LElqEcjzWf0PSBJ7awNVF/bGwMO+kQRJCfeKGlFwAAAQ8AAAAHc3NoLXJzYQAAAQCDThDHnBzeoEIlMYr7vzhl8hC7AxiXlsuLathqkYzn7H0AU5eGspfvJysV38vnXt/21TzFBorQ66be8cc/YHLfAaJqdpZEJWsxxqSRkVmAkpzaVN8k9OcVx9BqBS2VFwuanDoAw5JM2NEeZ6byQGd0cgWJcdGZ1K/EXzhYXCFcMPe1ye2Y2mdViDH+mellwuSw+H6Uq+UQbpbHf9fyJjReJ4Pu8C7PRMlD0JaZCFTKi58QlQcneOaQuVrtvZ4wDmvgLtWl+Zsqt9lUTpXZjwazXYDr8zyJ0cU+HMMVZ4E5StOKzTGg91jcOuChhZvkVOeJ2B4+KAsL2X9pu51iJj9h",
"private": null
}