diff --git a/README.md b/README.md index ddfda06..3e01e41 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Features < 100 lines of code | <1kb gzipped | 1.8kb minified | 3.1kb with comments * [x] SSH Public Keys + * fingerprint * [x] RSA Public Keys * [x] EC Public Keys * P-256 (prime256v1, secp256r1) @@ -47,18 +48,32 @@ ssh-to-jwk ~/.ssh/id_rsa.pub You can also use it from JavaScript: +**SSH to JWK** + ```js var fs = require('fs'); var sshtojwk = require('ssh-to-jwk'); var pub = fs.readFileSync("./id_rsa.pub"); -var ssh = sshtojwk.parse(pub); +var ssh = sshtojwk.parse({ pub: pub }); console.info(ssh.jwk); ``` -Legal ------ +**SSH Fingerprint** + +```js +var fs = require('fs'); +var sshtojwk = require('ssh-to-jwk'); +var pub = fs.readFileSync("./id_rsa.pub"); + +sshtojwk.fingerprint({ pub: pub }).then(function (fingerprint) { + console.info(fingerprint); + // SHA256:yCB62vBVsOwqksgYwy/WDbaMF2PhPijAwcrlzmrxfko +}); +``` + +# Legal [ssh-to-jwk.js](https://git.coolaj86.com/coolaj86/ssh-to-jwk.js) | MPL-2.0 | diff --git a/bin/ssh-to-jwk.js b/bin/ssh-to-jwk.js index 76ac5b2..be056c2 100755 --- a/bin/ssh-to-jwk.js +++ b/bin/ssh-to-jwk.js @@ -12,6 +12,11 @@ if (!pubfile) { } var buf = fs.readFileSync(pubfile); -var ssh = sshtojwk.parse(buf.toString('ascii')); +var pub = buf.toString('ascii'); +var ssh = sshtojwk.parse({ pub: pub }); -console.info(JSON.stringify(ssh.jwk, null, 2)); +// Finally! https://superuser.com/a/714195 +sshtojwk.fingerprint({ pub: pub }).then(function (fingerprint) { + console.warn('The key fingerprint is:\n' + fingerprint + ' ' + ssh.comment); + console.info(JSON.stringify(ssh.jwk, null, 2)); +}); diff --git a/lib/ssh-parser.js b/lib/ssh-parser.js index 790361a..7ac0fff 100644 --- a/lib/ssh-parser.js +++ b/lib/ssh-parser.js @@ -3,49 +3,37 @@ var SSH = module.exports; var Enc = require('./encoding.js'); -SSH.parse = function (ssh) { +SSH.parse = function (opts) { + var pub = opts.pub || opts; + var ssh = SSH.parseBlock(pub); + ssh = SSH.parseElements(ssh); + //delete ssh.bytes; + return SSH.parsePublicKey(ssh); +}; + +/*global Promise*/ +SSH.fingerprint = function (opts) { + var ssh; + if (opts.bytes) { + ssh = opts; + } else { + ssh = SSH.parseBlock(opts.pub); + } + // for browser compat + return Promise.resolve().then(function () { + return 'SHA256:' + require('crypto').createHash('sha256') + .update(ssh.bytes).digest('base64').replace(/=+$/g, ''); + }); +}; + +SSH.parseBlock = function (ssh) { ssh = ssh.split(/\s+/g); - var result = { type: ssh[0], jwk: null, comment: ssh[2] || '' }; - var buf = Enc.base64ToBuf(ssh[1]); - var els = SSH.parseElements({ bytes: buf }).elements; - var typ = Enc.bufToBin(els[0]); - var len; - - // RSA keys are all the same - if (SSH.types.rsa === typ) { - result.jwk = { - kty: 'RSA' - , n: Enc.bufToUrlBase64(els[2]) - , e: Enc.bufToUrlBase64(els[1]) - }; - return result; - } - - // EC keys are each different - if (SSH.types.p256 === typ) { - len = 32; - result.jwk = { kty: 'EC', crv: 'P-256' }; - } else if (SSH.types.p384 === typ) { - len = 48; - result.jwk = { kty: 'EC', crv: 'P-384' }; - } else { - throw new Error("Unsupported ssh public key type: " - + Enc.bufToBin(els[0])); - } - - // els[1] is just a repeat of a subset of els[0] - var x = els[2].slice(1, 1 + len); - var y = els[2].slice(1 + len, 1 + len + len); - - // I don't think EC keys use 0x00 padding, but just in case - while (0x00 === x[0]) { x = x.slice(1); } - while (0x00 === y[0]) { y = y.slice(1); } - - result.jwk.x = Enc.bufToUrlBase64(x); - result.jwk.y = Enc.bufToUrlBase64(y); - - return result; + return { + type: ssh[0] + , bytes: Enc.base64ToBuf(ssh[1]) + , comment: ssh[2] + }; }; SSH.parseElements = function (ssh) { @@ -74,15 +62,56 @@ SSH.parseElements = function (ssh) { index += len; } if (fulllen !== index) { - throw new Error(els.map(function (b) { + throw new Error("invalid ssh public key length \n" + els.map(function (b) { return Enc.bufToHex(b); - }).join('\n') + "invalid ssh public key length"); + }).join('\n')); } ssh.elements = els; return ssh; }; +SSH.parsePublicKey = function (ssh) { + var els = ssh.elements; + var typ = Enc.bufToBin(els[0]); + var len; + + // RSA keys are all the same + if (SSH.types.rsa === typ) { + ssh.jwk = { + kty: 'RSA' + , n: Enc.bufToUrlBase64(els[2]) + , e: Enc.bufToUrlBase64(els[1]) + }; + return ssh; + } + + // EC keys are each different + if (SSH.types.p256 === typ) { + len = 32; + ssh.jwk = { kty: 'EC', crv: 'P-256' }; + } else if (SSH.types.p384 === typ) { + len = 48; + ssh.jwk = { kty: 'EC', crv: 'P-384' }; + } else { + throw new Error("Unsupported ssh public key type: " + + Enc.bufToBin(els[0])); + } + + // els[1] is just a repeat of a subset of els[0] + var x = els[2].slice(1, 1 + len); + var y = els[2].slice(1 + len, 1 + len + len); + + // I don't think EC keys use 0x00 padding, but just in case + while (0x00 === x[0]) { x = x.slice(1); } + while (0x00 === y[0]) { y = y.slice(1); } + + ssh.jwk.x = Enc.bufToUrlBase64(x); + ssh.jwk.y = Enc.bufToUrlBase64(y); + + return ssh; +}; + SSH.types = { // 19 '00000013' // e c d s a - s h a 2 - n i s t p 2 5 6 diff --git a/package.json b/package.json index 2185a97..dd67052 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ssh-to-jwk", - "version": "1.0.2", + "version": "1.1.0", "description": "💯 SSH to JWK in a lightweight, zero-dependency library.", "homepage": "https://git.coolaj86.com/coolaj86/ssh-to-jwk.js", "main": "index.js", @@ -29,6 +29,7 @@ "RSA", "EC", "SSH", + "fingerprint", "JWK", "ECDSA" ],