From 8cda5c68f2807320edf87589c1fc9dd27e026373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sat, 20 Jul 2019 01:25:56 +0200 Subject: [PATCH 1/4] SSH2Stream: authPK: Rename algoLen to pubKeyTypeLen This is in preparation to support SSH certificates, where the signature type is different to the public key type. --- lib/ssh.js | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/ssh.js b/lib/ssh.js index 397f755b..92ccb2db 100644 --- a/lib/ssh.js +++ b/lib/ssh.js @@ -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; @@ -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 ); @@ -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) { @@ -1638,7 +1638,7 @@ SSH2Stream.prototype.authPK = function(username, pubKey, cbSign) { } cbSign(buf, function(signature) { - signature = convertSignature(signature, keyType); + signature = convertSignature(signature, pubKeyType); if (signature === false) throw new Error('Error while converting handshake signature'); @@ -1648,10 +1648,10 @@ SSH2Stream.prototype.authPK = function(username, pubKey, cbSign) { + 4 + 14 // "ssh-connection" + 4 + 9 // "publickey" + 1 - + 4 + algoLen + + 4 + pubKeyTypeLen + 4 + pubKeyLen - + 4 // 4 + algoLen + 4 + sigLen - + 4 + algoLen + + 4 // 4 + pubKeyTypeLen + 4 + sigLen + + 4 + pubKeyTypeLen + 4 + sigLen); p = 0; @@ -1669,15 +1669,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 + pubKeyTypeLen + 4 + sigLen, p += pubKeyLen); + writeUInt32BE(sigbuf, pubKeyTypeLen, p += 4); + sigbuf.write(pubKeyType, p += 4, pubKeyTypeLen, 'ascii'); + writeUInt32BE(sigbuf, sigLen, p += pubKeyTypeLen); signature.copy(sigbuf, p += 4); // Servers shouldn't send packet type 60 in response to signed publickey From 88943e616aad41bc5455f408f9ea468afe6970ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sat, 20 Jul 2019 01:37:55 +0200 Subject: [PATCH 2/4] SSH2Stream: authPK: Add new signatureType variable At this time it is identical to pubKeyType. It will differ when SSH certificates are being used. --- lib/ssh.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/ssh.js b/lib/ssh.js index 92ccb2db..e6b0450a 100644 --- a/lib/ssh.js +++ b/lib/ssh.js @@ -1638,6 +1638,8 @@ SSH2Stream.prototype.authPK = function(username, pubKey, cbSign) { } cbSign(buf, function(signature) { + var signatureType = pubKeyType; + var signatureTypeLen = Buffer.byteLength(signatureType); signature = convertSignature(signature, pubKeyType); if (signature === false) throw new Error('Error while converting handshake signature'); @@ -1650,8 +1652,8 @@ SSH2Stream.prototype.authPK = function(username, pubKey, cbSign) { + 1 + 4 + pubKeyTypeLen + 4 + pubKeyLen - + 4 // 4 + pubKeyTypeLen + 4 + sigLen - + 4 + pubKeyTypeLen + + 4 // 4 + signatureTypeLen + 4 + sigLen + + 4 + signatureTypeLen + 4 + sigLen); p = 0; @@ -1674,10 +1676,10 @@ SSH2Stream.prototype.authPK = function(username, pubKey, cbSign) { writeUInt32BE(sigbuf, pubKeyLen, p += pubKeyTypeLen); pubKey.copy(sigbuf, p += 4); - writeUInt32BE(sigbuf, 4 + pubKeyTypeLen + 4 + sigLen, p += pubKeyLen); - writeUInt32BE(sigbuf, pubKeyTypeLen, p += 4); - sigbuf.write(pubKeyType, p += 4, pubKeyTypeLen, 'ascii'); - writeUInt32BE(sigbuf, sigLen, p += pubKeyTypeLen); + 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 From a7abff54238d08f8e6449795d8e0d429f4d53f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sat, 20 Jul 2019 01:50:35 +0200 Subject: [PATCH 3/4] SSH2Stream: convertSignature: Convert the key type as well convertSignature was extended to convert certificate key types into their underlying key types. --- lib/ssh.js | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/lib/ssh.js b/lib/ssh.js index e6b0450a..d109f229 100644 --- a/lib/ssh.js +++ b/lib/ssh.js @@ -1638,11 +1638,13 @@ SSH2Stream.prototype.authPK = function(username, pubKey, cbSign) { } cbSign(buf, function(signature) { - var signatureType = pubKeyType; - var signatureTypeLen = Buffer.byteLength(signatureType); - signature = convertSignature(signature, pubKeyType); - 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 @@ -1755,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); @@ -5002,7 +5005,7 @@ 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'; @@ -5010,6 +5013,8 @@ function KEXDH_REPLY(self, e) { // Server self.disconnect(DISCONNECT_REASON.KEY_EXCHANGE_FAILED); return false; } + signature = converted.signature; + hostKeyAlgo = converted.keyType; /* byte SSH_MSG_KEXDH_REPLY @@ -5252,16 +5257,35 @@ function tryComputeSecret(dh, e) { } function convertSignature(signature, keyType) { + switch (keyType) { + case 'ssh-rsa-cert-v01@openssh.com': + case 'ssh-dss-cert-v01@openssh.com': + case 'ecdsa-sha2-nistp256': + case 'ecdsa-sha2-nistp256-cert-v01@openssh.com': + case 'ecdsa-sha2-nistp256-cert-v01@openssh.com': + case 'ssh-ed25519-cert-v01@openssh.com': + keyType = keyType.replace(/-cert-v01@openssh.com$/, ''); + } + 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() { From 447a0c6360b5f9606db6384b5ba3d7ad6327115e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sun, 21 Jul 2019 17:03:03 +0200 Subject: [PATCH 4/4] keyParser: Properly parse ssh certificates getPublicSSH() will simply pass through the original key blob. getPublicPEM() will return raw public key data of the certificate's public key, without incorporating the additional metadata. --- lib/keyParser.js | 14 ++++++++++---- test/fixtures/openssh_cert_rsa-cert.pub | 1 + test/fixtures/openssh_cert_rsa-cert.pub.result | 7 +++++++ 3 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/openssh_cert_rsa-cert.pub create mode 100644 test/fixtures/openssh_cert_rsa-cert.pub.result diff --git a/lib/keyParser.js b/lib/keyParser.js index 6515b5a5..e9e6f7bd 100644 --- a/lib/keyParser.js +++ b/lib/keyParser.js @@ -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': @@ -1240,7 +1240,7 @@ 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': @@ -1248,7 +1248,7 @@ function parseDER(data, baseType, comment, fullType) { 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': @@ -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); @@ -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); }; diff --git a/test/fixtures/openssh_cert_rsa-cert.pub b/test/fixtures/openssh_cert_rsa-cert.pub new file mode 100644 index 00000000..9c6285fb --- /dev/null +++ b/test/fixtures/openssh_cert_rsa-cert.pub @@ -0,0 +1 @@ +ssh-rsa-cert-v01@openssh.com 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 diff --git a/test/fixtures/openssh_cert_rsa-cert.pub.result b/test/fixtures/openssh_cert_rsa-cert.pub.result new file mode 100644 index 00000000..f0b00fa0 --- /dev/null +++ b/test/fixtures/openssh_cert_rsa-cert.pub.result @@ -0,0 +1,7 @@ +{ + "type": "ssh-rsa-cert-v01@openssh.com", + "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 +}