From 931e884ba98b81a9c4a64a675dda2f9ecfc6392b Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sun, 9 Dec 2018 23:08:08 -0700 Subject: [PATCH] wip: pack ssh private keys --- bin/jwk-to-ssh.js | 7 +- fixtures/privkey-ec-p256.jwk.json | 7 ++ fixtures/privkey-ec-p256.jwk.json.2 | 7 ++ fixtures/privkey-ec-p256.openssh.b64 | 7 ++ fixtures/privkey-ec-p256.openssh.b64.2 | 7 ++ fixtures/privkey-ec-p256.openssh.hex | 20 +++++ fixtures/privkey-ec-p256.openssh.hex.2 | 20 +++++ fixtures/privkey-ec-p256.openssh.pem | 9 +++ fixtures/privkey-ec-p256.openssh.pem.2 | 9 +++ fixtures/privkey-ec-p256.openssh.pem.2.pub | 1 + fixtures/privkey-ec-p256.openssh.pem.pub | 1 + lib/encoding.js | 12 +++ lib/pem.js | 11 +++ lib/ssh-packer.js | 89 ++++++++++++++++++---- 14 files changed, 188 insertions(+), 19 deletions(-) create mode 100644 fixtures/privkey-ec-p256.jwk.json create mode 100644 fixtures/privkey-ec-p256.jwk.json.2 create mode 100644 fixtures/privkey-ec-p256.openssh.b64 create mode 100644 fixtures/privkey-ec-p256.openssh.b64.2 create mode 100644 fixtures/privkey-ec-p256.openssh.hex create mode 100644 fixtures/privkey-ec-p256.openssh.hex.2 create mode 100644 fixtures/privkey-ec-p256.openssh.pem create mode 100644 fixtures/privkey-ec-p256.openssh.pem.2 create mode 100644 fixtures/privkey-ec-p256.openssh.pem.2.pub create mode 100644 fixtures/privkey-ec-p256.openssh.pem.pub create mode 100644 lib/pem.js diff --git a/bin/jwk-to-ssh.js b/bin/jwk-to-ssh.js index 9493044..c4ea6fa 100755 --- a/bin/jwk-to-ssh.js +++ b/bin/jwk-to-ssh.js @@ -5,6 +5,8 @@ var path = require('path'); var jwktossh = require('../index.js'); var pubfile = process.argv[2]; +var comment = process.argv[3] || 'root@localhost'; +var pub = ('public' === process.argv[4]); if (!pubfile) { console.error("specify a path to JWK"); @@ -12,7 +14,6 @@ if (!pubfile) { } var jwk = require(path.join(process.cwd(), pubfile)); -var comment = process.argv[3] || 'root@localhost'; -var pub = jwktossh.pack({ jwk: jwk, comment: comment }); +var out = jwktossh.pack({ jwk: jwk, comment: comment, public: pub }); -console.info(pub); +console.info(out); diff --git a/fixtures/privkey-ec-p256.jwk.json b/fixtures/privkey-ec-p256.jwk.json new file mode 100644 index 0000000..0850145 --- /dev/null +++ b/fixtures/privkey-ec-p256.jwk.json @@ -0,0 +1,7 @@ +{ + "kty": "EC", + "crv": "P-256", + "d": "BzKpRmMyXZ4jnSt3ARz0ul6R79AXAr5gQqDAmoFeEKw", + "x": "fVmT3gUr4sZIY0Dofcgl15RMeQjfTMofeAiSda4oH_M", + "y": "r3eBz3QeZqEhHmgwYfCWoyhLuQqtHD7gqnGqwXssa_E" +} diff --git a/fixtures/privkey-ec-p256.jwk.json.2 b/fixtures/privkey-ec-p256.jwk.json.2 new file mode 100644 index 0000000..0850145 --- /dev/null +++ b/fixtures/privkey-ec-p256.jwk.json.2 @@ -0,0 +1,7 @@ +{ + "kty": "EC", + "crv": "P-256", + "d": "BzKpRmMyXZ4jnSt3ARz0ul6R79AXAr5gQqDAmoFeEKw", + "x": "fVmT3gUr4sZIY0Dofcgl15RMeQjfTMofeAiSda4oH_M", + "y": "r3eBz3QeZqEhHmgwYfCWoyhLuQqtHD7gqnGqwXssa_E" +} diff --git a/fixtures/privkey-ec-p256.openssh.b64 b/fixtures/privkey-ec-p256.openssh.b64 new file mode 100644 index 0000000..09b3ca0 --- /dev/null +++ b/fixtures/privkey-ec-p256.openssh.b64 @@ -0,0 +1,7 @@ +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR9WZPeBSvixkhjQOh9yCXXlEx5CN9M +yh94CJJ1rigf8693gc90HmahIR5oMGHwlqMoS7kKrRw+4KpxqsF7LGvxAAAAqJZtgRuWbY +EbAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH1Zk94FK+LGSGNA +6H3IJdeUTHkI30zKH3gIknWuKB/zr3eBz3QeZqEhHmgwYfCWoyhLuQqtHD7gqnGqwXssa/ +EAAAAgBzKpRmMyXZ4jnSt3ARz0ul6R79AXAr5gQqDAmoFeEKwAAAAOYWpAYm93aWUubG9j +YWwBAg== diff --git a/fixtures/privkey-ec-p256.openssh.b64.2 b/fixtures/privkey-ec-p256.openssh.b64.2 new file mode 100644 index 0000000..9ef4679 --- /dev/null +++ b/fixtures/privkey-ec-p256.openssh.b64.2 @@ -0,0 +1,7 @@ +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR9WZPeBSvixkhjQOh9yCXXlEx5CN9M +yh94CJJ1rigf8693gc90HmahIR5oMGHwlqMoS7kKrRw+4KpxqsF7LGvxAAAAqGJjanNiY2 +pzAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH1Zk94FK+LGSGNA +6H3IJdeUTHkI30zKH3gIknWuKB/zr3eBz3QeZqEhHmgwYfCWoyhLuQqtHD7gqnGqwXssa/ +EAAAAgBzKpRmMyXZ4jnSt3ARz0ul6R79AXAr5gQqDAmoFeEKwAAAAOYWpAYm93aWUubG9j +YWwBAg== diff --git a/fixtures/privkey-ec-p256.openssh.hex b/fixtures/privkey-ec-p256.openssh.hex new file mode 100644 index 0000000..9abb785 --- /dev/null +++ b/fixtures/privkey-ec-p256.openssh.hex @@ -0,0 +1,20 @@ +00000000: 6f70 656e 7373 682d 6b65 792d 7631 0000 openssh-key-v1.. +00000010: 0000 046e 6f6e 6500 0000 046e 6f6e 6500 ...none....none. +00000020: 0000 0000 0000 0100 0000 6800 0000 1365 ..........h....e +00000030: 6364 7361 2d73 6861 322d 6e69 7374 7032 cdsa-sha2-nistp2 +00000040: 3536 0000 0008 6e69 7374 7032 3536 0000 56....nistp256.. +00000050: 0041 047d 5993 de05 2be2 c648 6340 e87d .A.}Y...+..Hc@.} +00000060: c825 d794 4c79 08df 4cca 1f78 0892 75ae .%..Ly..L..x..u. +00000070: 281f f3af 7781 cf74 1e66 a121 1e68 3061 (...w..t.f.!.h0a +00000080: f096 a328 4bb9 0aad 1c3e e0aa 71aa c17b ...(K....>..q..{ +00000090: 2c6b f100 0000 a896 6d81 1b96 6d81 1b00 ,k......m...m... +000000a0: 0000 1365 6364 7361 2d73 6861 322d 6e69 ...ecdsa-sha2-ni +000000b0: 7374 7032 3536 0000 0008 6e69 7374 7032 stp256....nistp2 +000000c0: 3536 0000 0041 047d 5993 de05 2be2 c648 56...A.}Y...+..H +000000d0: 6340 e87d c825 d794 4c79 08df 4cca 1f78 c@.}.%..Ly..L..x +000000e0: 0892 75ae 281f f3af 7781 cf74 1e66 a121 ..u.(...w..t.f.! +000000f0: 1e68 3061 f096 a328 4bb9 0aad 1c3e e0aa .h0a...(K....>.. +00000100: 71aa c17b 2c6b f100 0000 2007 32a9 4663 q..{,k.... .2.Fc +00000110: 325d 9e23 9d2b 7701 1cf4 ba5e 91ef d017 2].#.+w....^.... +00000120: 02be 6042 a0c0 9a81 5e10 ac00 0000 0e61 ..`B....^......a +00000130: 6a40 626f 7769 652e 6c6f 6361 6c01 02 j@bowie.local.. diff --git a/fixtures/privkey-ec-p256.openssh.hex.2 b/fixtures/privkey-ec-p256.openssh.hex.2 new file mode 100644 index 0000000..3280769 --- /dev/null +++ b/fixtures/privkey-ec-p256.openssh.hex.2 @@ -0,0 +1,20 @@ +00000000: 6f70 656e 7373 682d 6b65 792d 7631 0000 openssh-key-v1.. +00000010: 0000 046e 6f6e 6500 0000 046e 6f6e 6500 ...none....none. +00000020: 0000 0000 0000 0100 0000 6800 0000 1365 ..........h....e +00000030: 6364 7361 2d73 6861 322d 6e69 7374 7032 cdsa-sha2-nistp2 +00000040: 3536 0000 0008 6e69 7374 7032 3536 0000 56....nistp256.. +00000050: 0041 047d 5993 de05 2be2 c648 6340 e87d .A.}Y...+..Hc@.} +00000060: c825 d794 4c79 08df 4cca 1f78 0892 75ae .%..Ly..L..x..u. +00000070: 281f f3af 7781 cf74 1e66 a121 1e68 3061 (...w..t.f.!.h0a +00000080: f096 a328 4bb9 0aad 1c3e e0aa 71aa c17b ...(K....>..q..{ +00000090: 2c6b f100 0000 a862 636a 7362 636a 7300 ,k.....bcjsbcjs. +000000a0: 0000 1365 6364 7361 2d73 6861 322d 6e69 ...ecdsa-sha2-ni +000000b0: 7374 7032 3536 0000 0008 6e69 7374 7032 stp256....nistp2 +000000c0: 3536 0000 0041 047d 5993 de05 2be2 c648 56...A.}Y...+..H +000000d0: 6340 e87d c825 d794 4c79 08df 4cca 1f78 c@.}.%..Ly..L..x +000000e0: 0892 75ae 281f f3af 7781 cf74 1e66 a121 ..u.(...w..t.f.! +000000f0: 1e68 3061 f096 a328 4bb9 0aad 1c3e e0aa .h0a...(K....>.. +00000100: 71aa c17b 2c6b f100 0000 2007 32a9 4663 q..{,k.... .2.Fc +00000110: 325d 9e23 9d2b 7701 1cf4 ba5e 91ef d017 2].#.+w....^.... +00000120: 02be 6042 a0c0 9a81 5e10 ac00 0000 0e61 ..`B....^......a +00000130: 6a40 626f 7769 652e 6c6f 6361 6c01 02 j@bowie.local.. diff --git a/fixtures/privkey-ec-p256.openssh.pem b/fixtures/privkey-ec-p256.openssh.pem new file mode 100644 index 0000000..124f002 --- /dev/null +++ b/fixtures/privkey-ec-p256.openssh.pem @@ -0,0 +1,9 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR9WZPeBSvixkhjQOh9yCXXlEx5CN9M +yh94CJJ1rigf8693gc90HmahIR5oMGHwlqMoS7kKrRw+4KpxqsF7LGvxAAAAqJZtgRuWbY +EbAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH1Zk94FK+LGSGNA +6H3IJdeUTHkI30zKH3gIknWuKB/zr3eBz3QeZqEhHmgwYfCWoyhLuQqtHD7gqnGqwXssa/ +EAAAAgBzKpRmMyXZ4jnSt3ARz0ul6R79AXAr5gQqDAmoFeEKwAAAAOYWpAYm93aWUubG9j +YWwBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/fixtures/privkey-ec-p256.openssh.pem.2 b/fixtures/privkey-ec-p256.openssh.pem.2 new file mode 100644 index 0000000..eaca6ae --- /dev/null +++ b/fixtures/privkey-ec-p256.openssh.pem.2 @@ -0,0 +1,9 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR9WZPeBSvixkhjQOh9yCXXlEx5CN9M +yh94CJJ1rigf8693gc90HmahIR5oMGHwlqMoS7kKrRw+4KpxqsF7LGvxAAAAqGJjanNiY2 +pzAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH1Zk94FK+LGSGNA +6H3IJdeUTHkI30zKH3gIknWuKB/zr3eBz3QeZqEhHmgwYfCWoyhLuQqtHD7gqnGqwXssa/ +EAAAAgBzKpRmMyXZ4jnSt3ARz0ul6R79AXAr5gQqDAmoFeEKwAAAAOYWpAYm93aWUubG9j +YWwBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/fixtures/privkey-ec-p256.openssh.pem.2.pub b/fixtures/privkey-ec-p256.openssh.pem.2.pub new file mode 100644 index 0000000..2b86013 --- /dev/null +++ b/fixtures/privkey-ec-p256.openssh.pem.2.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH1Zk94FK+LGSGNA6H3IJdeUTHkI30zKH3gIknWuKB/zr3eBz3QeZqEhHmgwYfCWoyhLuQqtHD7gqnGqwXssa/E= root@localhost diff --git a/fixtures/privkey-ec-p256.openssh.pem.pub b/fixtures/privkey-ec-p256.openssh.pem.pub new file mode 100644 index 0000000..2b86013 --- /dev/null +++ b/fixtures/privkey-ec-p256.openssh.pem.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH1Zk94FK+LGSGNA6H3IJdeUTHkI30zKH3gIknWuKB/zr3eBz3QeZqEhHmgwYfCWoyhLuQqtHD7gqnGqwXssa/E= root@localhost diff --git a/lib/encoding.js b/lib/encoding.js index a369e6c..8bc0d0d 100644 --- a/lib/encoding.js +++ b/lib/encoding.js @@ -6,6 +6,14 @@ Enc.base64ToHex = function (b64) { return Buffer.from(b64, 'base64').toString('hex'); }; +Enc.binToBuf = function (bin) { + return Buffer.from(bin, 'binary'); +}; + +Enc.bufToBase64 = function (u8) { + return Buffer.from(u8).toString('base64'); +}; + Enc.binToHex = function (bin) { return Buffer.from(bin, 'binary').toString('hex'); }; @@ -13,3 +21,7 @@ Enc.binToHex = function (bin) { Enc.hexToBase64 = function (hex) { return Buffer.from(hex, 'hex').toString('base64'); }; + +Enc.hexToBin = function (hex) { + return Buffer.from(hex, 'hex').toString('binary'); +}; diff --git a/lib/pem.js b/lib/pem.js new file mode 100644 index 0000000..468362b --- /dev/null +++ b/lib/pem.js @@ -0,0 +1,11 @@ +'use strict'; + +var PEM = module.exports; +var Enc = require('./encoding.js'); + +PEM.packBlock = function (opts) { + return '-----BEGIN ' + opts.type + '-----\n' + + Enc.bufToBase64(opts.bytes).match(/.{1,70}/g).join('\n') + '\n' + + '-----END ' + opts.type + '-----' + ; +}; diff --git a/lib/ssh-packer.js b/lib/ssh-packer.js index d6470b0..7c746d1 100644 --- a/lib/ssh-packer.js +++ b/lib/ssh-packer.js @@ -1,34 +1,87 @@ 'use strict'; var Enc = require('./encoding.js'); +var PEM = require('./pem.js'); var SSH = module.exports; SSH.pack = function (opts) { + if (opts.jwk.d && !opts.public) { + return SSH._packPrivate(opts); + } else { + delete opts.jwk.d; + return SSH._packPublic(opts); + } +}; + +// https://tools.ietf.org/html/rfc4253#section-6.6 +SSH._packPublic = function (opts) { + var els = SSH._packKey(opts); + var hex = SSH._packElements(els); + var typ = Enc.hexToBin(els[0]); + var parts = [ typ, Enc.hexToBase64(hex) ]; + if (opts.comment) { parts.push(opts.comment); } + return parts.join(' '); +}; + +SSH._packPrivate = function (opts) { + var pubjwk = JSON.parse(JSON.stringify(opts.jwk)); + delete pubjwk.d; + var pubels = SSH._packKey({ jwk: pubjwk }); + var pubhex = SSH._packElements(pubels); + var els = SSH._packKey(opts); + var hex = SSH._packElements(els); + var privlen = hex.length/2; + var padlen = (privlen % 8) && (8 - (privlen % 8)) || 0; // blocksize is 8 (no cipher) + var bin = "openssh-key-v1" + String.fromCharCode(0) + + Enc.hexToBin( + SSH._packElements([ + Enc.binToHex("none") // ciphername + , Enc.binToHex("none") // kdfname + , "" // empty kdf + ]) + + SSH._numToUint32Hex(1) // number of keys (always 1) + + SSH._numToUint32Hex(pubhex.length/2) // pubkey length + + pubhex + + SSH._numToUint32Hex(8 + privlen + padlen) // privkey length + + '62636a7362636a73' // 64-bit dummy checksum ("bcjs", "bcjs") + + hex // (only cihpered keys use real checksums) + ); + var pad = ''; + var i; + for (i = 1; i <= padlen; i += 1) { + pad += '0' + i; + } + return PEM.packBlock({ + type: "OPENSSH PRIVATE KEY" + , bytes: Enc.binToBuf(bin + Enc.hexToBin(pad)) + }); +}; + +SSH._packKey = function (opts) { var jwk = opts.jwk; var els = []; - var ssh = { - type: '' - , _elements: els - , comment: opts.comment || '' - }; var len; if ("RSA" === jwk.kty) { - ssh.type = 'ssh-rsa'; - els.push(Enc.binToHex(ssh.type)); + els.push(Enc.binToHex('ssh-rsa')); els.push(SSH._padRsa(Enc.base64ToHex(jwk.e))); els.push(SSH._padRsa(Enc.base64ToHex(jwk.n))); - return SSH._packElements(ssh); + if (jwk.d) { + els.push(SSH._padRsa(Enc.base64ToHex(jwk.d))); + els.push(SSH._padRsa(Enc.base64ToHex(jwk.qi))); + els.push(SSH._padRsa(Enc.base64ToHex(jwk.p))); + els.push(SSH._padRsa(Enc.base64ToHex(jwk.q))); + els.push(Enc.binToHex(opts.comment || '')); + } + return SSH._packElements(els); } if ("P-256" === jwk.crv) { - ssh.type = 'ecdsa-sha2-nistp256'; - els.push(Enc.binToHex(ssh.type)); + els.push(Enc.binToHex('ecdsa-sha2-nistp256')); els.push(Enc.binToHex('nistp256')); len = 32; } else if ("P-384" === jwk.crv) { - ssh.type = 'ecdsa-sha2-nistp384'; - els.push(Enc.binToHex(ssh.type)); + els.push(Enc.binToHex('ecdsa-sha2-nistp384')); els.push(Enc.binToHex('nistp384')); len = 48; } else { @@ -39,14 +92,18 @@ SSH.pack = function (opts) { + SSH._padEc(Enc.base64ToHex(jwk.x), len) + SSH._padEc(Enc.base64ToHex(jwk.y), len) ); - return SSH._packElements(ssh); + if (jwk.d) { + els.push(SSH._padEc(Enc.base64ToHex(jwk.d), len)); + els.push(Enc.binToHex(opts.comment || '')); + } + + return els; }; -SSH._packElements = function (ssh) { - var hex = ssh._elements.map(function (hex) { +SSH._packElements = function(els) { + return els.map(function (hex) { return SSH._numToUint32Hex(hex.length/2) + hex; }).join(''); - return [ ssh.type, Enc.hexToBase64(hex), ssh.comment ].join(' '); }; SSH._numToUint32Hex = function (num) {