Compare commits
No commits in common. "master" and "v1.1.0" have entirely different histories.
27
README.md
27
README.md
@ -5,23 +5,20 @@ and convert it into a public JWK.
|
|||||||
|
|
||||||
Works for RSA and ECDSA public keys.
|
Works for RSA and ECDSA public keys.
|
||||||
|
|
||||||
# Features
|
Features
|
||||||
|
========
|
||||||
|
|
||||||
< 100 lines of code | <1kb gzipped | 1.8kb minified | 3.1kb with comments
|
< 100 lines of code | <1kb gzipped | 1.8kb minified | 3.1kb with comments
|
||||||
|
|
||||||
* [x] SSH Public Keys ([RFC 4253](https://coolaj86.com/articles/the-ssh-public-key-format/))
|
* [x] SSH Public Keys
|
||||||
* fingerprint
|
* fingerprint
|
||||||
* [x] OpenSSH Private Keys
|
* [x] RSA Public Keys
|
||||||
* [x] RSA
|
|
||||||
* 2048, 3072, 4096
|
|
||||||
* [x] EC Public Keys
|
* [x] EC Public Keys
|
||||||
* P-256 (prime256v1, secp256r1)
|
* P-256 (prime256v1, secp256r1)
|
||||||
* P-384 (secp384r1)
|
* P-384 (secp384r1)
|
||||||
* [x] Browser Version
|
* [x] Browser Version
|
||||||
* [Bluecrypt SSH to JWK](https://git.coolaj86.com/coolaj86/bluecrypt-ssh-to-jwk.js)
|
* [Bluecrypt SSH to JWK](https://git.coolaj86.com/coolaj86/bluecrypt-ssh-to-jwk.js)
|
||||||
|
|
||||||
Note: Lines of code have increased by about 2x since adding private key support.
|
|
||||||
|
|
||||||
### Need JWK to SSH? SSH to PEM?
|
### Need JWK to SSH? SSH to PEM?
|
||||||
|
|
||||||
Try one of these:
|
Try one of these:
|
||||||
@ -32,12 +29,9 @@ Try one of these:
|
|||||||
|
|
||||||
### Need SSH Private Keys?
|
### Need SSH Private Keys?
|
||||||
|
|
||||||
Many SSH private keys are just normal PEM files,
|
SSH private keys are just normal PEM files,
|
||||||
so you can use Eckles or Rasha, as mentioned above.
|
so you can use Eckles or Rasha, as mentioned above.
|
||||||
|
|
||||||
As for the [OpenSSH-specific Private Keys](https://coolaj86.com/articles/the-openssh-private-key-format/),
|
|
||||||
both EC and RSA are fully supported.
|
|
||||||
|
|
||||||
# CLI
|
# CLI
|
||||||
|
|
||||||
You can install `ssh-to-jwk` and use it from command line:
|
You can install `ssh-to-jwk` and use it from command line:
|
||||||
@ -50,10 +44,6 @@ npm install -g ssh-to-jwk
|
|||||||
ssh-to-jwk ~/.ssh/id_rsa.pub
|
ssh-to-jwk ~/.ssh/id_rsa.pub
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
|
||||||
ssh-to-jwk ~/.ssh/id_rsa
|
|
||||||
```
|
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
You can also use it from JavaScript:
|
You can also use it from JavaScript:
|
||||||
@ -63,13 +53,10 @@ You can also use it from JavaScript:
|
|||||||
```js
|
```js
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var sshtojwk = require('ssh-to-jwk');
|
var sshtojwk = require('ssh-to-jwk');
|
||||||
var ssh;
|
|
||||||
|
|
||||||
ssh = sshtojwk.parse({ pub: fs.readFileSync("./id_rsa.pub") });
|
var pub = fs.readFileSync("./id_rsa.pub");
|
||||||
console.info(ssh.jwk);
|
var ssh = sshtojwk.parse({ pub: pub });
|
||||||
|
|
||||||
// For OpenSSH PEMs only, use Rasha for standard RSA or Eckles for standard EC
|
|
||||||
ssh = sshtojwk.parse({ pem: fs.readFileSync("./id_rsa") });
|
|
||||||
console.info(ssh.jwk);
|
console.info(ssh.jwk);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -6,27 +6,17 @@ var path = require('path');
|
|||||||
var sshtojwk = require('../index.js');
|
var sshtojwk = require('../index.js');
|
||||||
|
|
||||||
var pubfile = process.argv[2];
|
var pubfile = process.argv[2];
|
||||||
var pub = process.argv[3];
|
|
||||||
|
|
||||||
if (!pubfile) {
|
if (!pubfile) {
|
||||||
pubfile = path.join(require('os').homedir(), '.ssh/id_rsa.pub');
|
pubfile = path.join(require('os').homedir(), '.ssh/id_rsa.pub');
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf = fs.readFileSync(pubfile);
|
var buf = fs.readFileSync(pubfile);
|
||||||
var txt = buf.toString('ascii');
|
var pub = buf.toString('ascii');
|
||||||
var opts = { public: 'public' === pub };
|
var ssh = sshtojwk.parse({ pub: pub });
|
||||||
var ssh;
|
|
||||||
|
|
||||||
if ('-' === txt[0]) {
|
|
||||||
opts.pem = txt;
|
|
||||||
} else {
|
|
||||||
opts.pub = txt;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssh = sshtojwk.parse(opts);
|
|
||||||
|
|
||||||
// Finally! https://superuser.com/a/714195
|
// Finally! https://superuser.com/a/714195
|
||||||
sshtojwk.fingerprint(ssh).then(function (fingerprint) {
|
sshtojwk.fingerprint({ pub: pub }).then(function (fingerprint) {
|
||||||
console.warn('The key fingerprint is:\n' + fingerprint + ' ' + ssh.comment);
|
console.warn('The key fingerprint is:\n' + fingerprint + ' ' + ssh.comment);
|
||||||
console.info(JSON.stringify(ssh.jwk, null, 2));
|
console.info(JSON.stringify(ssh.jwk, null, 2));
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"kty": "EC",
|
|
||||||
"crv": "P-256",
|
|
||||||
"d": "iYydo27aNGO9DBUWeGEPD8oNi1LZDqfxPmQlieLBjVQ",
|
|
||||||
"x": "IT1SWLxsacPiE5Z16jkopAn8_-85rMjgyCokrnjDft4",
|
|
||||||
"y": "mP2JwOAOdMmXuwpxbKng3KZz27mz-nKWIlXJ3rzSGMo"
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
||||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
|
|
||||||
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQhPVJYvGxpw+ITlnXqOSikCfz/7zms
|
|
||||||
yODIKiSueMN+3pj9icDgDnTJl7sKcWyp4Nymc9u5s/pyliJVyd680hjKAAAAqGJjanNiY2
|
|
||||||
pzAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCE9Uli8bGnD4hOW
|
|
||||||
deo5KKQJ/P/vOazI4MgqJK54w37emP2JwOAOdMmXuwpxbKng3KZz27mz+nKWIlXJ3rzSGM
|
|
||||||
oAAAAhAImMnaNu2jRjvQwVFnhhDw/KDYtS2Q6n8T5kJYniwY1UAAAADnJvb3RAbG9jYWxo
|
|
||||||
b3N0AQ==
|
|
||||||
-----END OPENSSH PRIVATE KEY-----
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"kty": "EC",
|
|
||||||
"crv": "P-384",
|
|
||||||
"d": "XlyuCEWSTTS8U79O_Mz05z18vh4kb10szvu_7pdXuGWV6lfEyPExyUYWsA6A2kdV",
|
|
||||||
"x": "2zEU0bKCa7ejKLIJ8oPGnLhqhxyiv4_w38K2a0SPC6dsSd9_glNJ8lcqv0sff5Gb",
|
|
||||||
"y": "VD4jnu83S6scn6_TeAj3EZOREGbOs6dzoVpaugn-XQMMyC9O4VLbDDFGBZTJlMsb"
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
||||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS
|
|
||||||
1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQTbMRTRsoJrt6Mosgnyg8acuGqHHKK/
|
|
||||||
j/DfwrZrRI8Lp2xJ33+CU0nyVyq/Sx9/kZtUPiOe7zdLqxyfr9N4CPcRk5EQZs6zp3OhWl
|
|
||||||
q6Cf5dAwzIL07hUtsMMUYFlMmUyxsAAADYYmNqc2JjanMAAAATZWNkc2Etc2hhMi1uaXN0
|
|
||||||
cDM4NAAAAAhuaXN0cDM4NAAAAGEE2zEU0bKCa7ejKLIJ8oPGnLhqhxyiv4/w38K2a0SPC6
|
|
||||||
dsSd9/glNJ8lcqv0sff5GbVD4jnu83S6scn6/TeAj3EZOREGbOs6dzoVpaugn+XQMMyC9O
|
|
||||||
4VLbDDFGBZTJlMsbAAAAMF5crghFkk00vFO/TvzM9Oc9fL4eJG9dLM77v+6XV7hllepXxM
|
|
||||||
jxMclGFrAOgNpHVQAAAA5yb290QGxvY2FsaG9zdAEC
|
|
||||||
-----END OPENSSH PRIVATE KEY-----
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"kty": "RSA",
|
|
||||||
"n": "m2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhDNzUJefLukC-xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57qAZM6-I70on0_iDZm7-jcqOPgADAmbWHhy67BXkk4yy_YzD4yOGZFXZcNp915_TW5bRd__AKPHUHxJasPiyEFqlNKBR2DSD-LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By5AB0Qi49OykUCfNZeQlEz7UNNj9RGps_50-CNw",
|
|
||||||
"e": "AQAB",
|
|
||||||
"d": "Cpfo7Mm9Nu8YMC_xrZ54W9mKHPkCG9rZ93Ds9PNp-RXUgb-ljTbFPZWsYxGNKLllFz8LNosr1pT2ZDMrwNk0Af1iWNvD6gkyXaiQdCyiDPSBsJyNv2LJZon-e85X74nv53UlIkmo9SYxdLz2JaJ-iIWEe8Qh-7llLktrTJV_xr98_tbhgSppz_IeOymq3SEZaQHM8pTU7w7XvCj2pb9r8fN0M0XcgWZIaf3LGEfkhF_WtX67XJ0C6-LbkT51jtlLRNGX6haGdscXS0OWWjKOJzKGuV-NbthEn5rmRtVnjRZ3yaxQ0ud8vC-NONn7yvGUlOur1IdDzJ_YfHPt9sHMQQ",
|
|
||||||
"p": "ynG-t9HwKCN3MWRYFdnFzi9-02Qcy3p8B5pu3ary2E70hYn2pHlUG2a9BNE8c5xHQ3Hx43WoWf6s0zOunPV1G28LkU_UYEbAtPv_PxSmzpQp9n9XnYvBLBF8Y3z7gxgLn1vVFNARrQdRtj87qY3aw7E9S4DsGcAarIuOT2TsTCE",
|
|
||||||
"q": "xIkAjgUzB1zaUzJtW2Zgvp9cYYr1DmpH30ePZl3c_8397_DZDDo46fnFYjs6uPa03HpmKUnbjwr14QHlfXlntJBEuXxcqLjkdKdJ4ob7xueLTK4suo9V8LSrkLChVxlZQwnFD2E5ll0sVeeDeMJHQw38ahSrBFEVnxjpnPh1Q1c",
|
|
||||||
"dp": "tzDGjECFOU0ehqtuqhcuT63a7h8hj19-7MJqoFwY9HQ-ALkfXyYLXeBSGxHbyiIYuodZg6LsfMNgUJ3r3Eyhc_nAVfYPEC_2IdAG4WYmq7iXYF9LQV09qEsKbFykm7QekE3hO7wswo5k-q2tp3ieBYdVGAXJoGOdv5VpaZ7B1QE",
|
|
||||||
"dq": "kh5dyDk7YCz7sUFbpsmuAeuPjoH2ghooh2u3xN7iUVmAg-ToKjwbVnG5-7eXiC779rQVwnrD_0yh1AFJ8wjRPqDIR7ObXGHikIxT1VSQWqiJm6AfZzDsL0LUD4YS3iPdhob7-NxLKWzqao_u4lhnDQaX9PKa12HFlny6K1daL48",
|
|
||||||
"qi": "AlHWbx1gp6Z9pbw_1hlS7HuXAgWoX7IjbTUelldf4gkriDWLOrj3QCZcO4ZvZvEwJhVlsny9LO8IkbwGJEL6cXraK08ByVS2mwQyflgTgGNnpzixyEUL_mrQLx6y145FHcxfeqNInMhep-0Mxn1D5nlhmIOgRApS0t9VoXtHhFU"
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
||||||
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
|
|
||||||
-----END OPENSSH PRIVATE KEY-----
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH1Zk94FK+LGSGNA6H3IJdeUTHkI30zKH3gIknWuKB/zr3eBz3QeZqEhHmgwYfCWoyhLuQqtHD7gqnGqwXssa/E= aj@bowie.local
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBMM1wvONZSobkf6CtT9n5E06y+ofhXmpTVSWRTOpMiZi+bsKViBi1eptF4LMufUT4M5DDU3V/kT9iM8124uWSWwS/ernmTsENNOI2TlzXkKkJshXh5Z4tFIVIkEZAeW/6w== aj@bowie.local
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTNGUXvKQQ2sbdXdILzeI7u60ziBDHqCbrYxUkdro0LA8SqimkQexHKd9SBVvrxNm3l1LsCRmeIgrxlhwuptsygK80rRX5Jit2DCRP62tyMBIEIqLxAksMEW1z6M3XIdIj2jWkL6ei6Qver03JWt4TfxZG94axSPxPaHYf6Psb+lZnMfYrWXtVD9hJA6vQjKNqmpUo1I2dWq6e6F3Oepejfu9Cz1yMnEsNJG3COubJSXUkEdsJYSrW3wfUnMPBrlnjs8uP4NBTik+5SHmpb+pU3+2DjgC4tsnTIkiusaLIp/qJmS+X18pFzRCMy2jfX1QeHMPsXnBSvdfyIdZfIVs5 aj@bowie.local
|
|
||||||
@ -10,20 +10,6 @@ Enc.base64ToBuf = function (str) {
|
|||||||
return Buffer.from(str, 'base64');
|
return Buffer.from(str, 'base64');
|
||||||
};
|
};
|
||||||
|
|
||||||
Enc.base64ToHex = function (str) {
|
|
||||||
return Buffer.from(str, 'base64').toString('hex');
|
|
||||||
};
|
|
||||||
|
|
||||||
Enc.base64ToUrlBase64 = function (b64) {
|
|
||||||
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
||||||
};
|
|
||||||
|
|
||||||
Enc.bnToUrlBase64 = function (bn) {
|
|
||||||
var hex = bn.toString(16);
|
|
||||||
if (hex.length % 2) { hex = '0' + hex; }
|
|
||||||
return Enc.base64ToUrlBase64(Buffer.from(hex, 'hex').toString('base64'));
|
|
||||||
};
|
|
||||||
|
|
||||||
Enc.bufToBase64 = function (u8) {
|
Enc.bufToBase64 = function (u8) {
|
||||||
return Buffer.from(u8).toString('base64');
|
return Buffer.from(u8).toString('base64');
|
||||||
};
|
};
|
||||||
@ -37,5 +23,6 @@ Enc.bufToHex = function (u8) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Enc.bufToUrlBase64 = function (u8) {
|
Enc.bufToUrlBase64 = function (u8) {
|
||||||
return Enc.base64ToUrlBase64(Enc.bufToBase64(u8));
|
return Enc.bufToBase64(u8)
|
||||||
|
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
||||||
};
|
};
|
||||||
|
|||||||
28
lib/pem.js
28
lib/pem.js
@ -1,28 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var PEM = module.exports;
|
|
||||||
var Enc = require('./encoding.js');
|
|
||||||
|
|
||||||
PEM.parseBlock = function pemToDer(pem) {
|
|
||||||
var lines = pem.trim().split(/\n/);
|
|
||||||
var end = lines.length - 1;
|
|
||||||
var head = lines[0].match(/-----BEGIN (.*)-----/);
|
|
||||||
var foot = lines[end].match(/-----END (.*)-----/);
|
|
||||||
|
|
||||||
if (head) {
|
|
||||||
lines = lines.slice(1, end);
|
|
||||||
head = head[1];
|
|
||||||
if (head !== foot[1]) {
|
|
||||||
throw new Error("headers and footers do not match");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { type: head, bytes: Enc.base64ToBuf(lines.join('')) };
|
|
||||||
};
|
|
||||||
|
|
||||||
PEM.packBlock = function (opts) {
|
|
||||||
return '-----BEGIN ' + opts.type + '-----\n'
|
|
||||||
+ Enc.bufToBase64(opts.bytes).match(/.{1,64}/g).join('\n') + '\n'
|
|
||||||
+ '-----END ' + opts.type + '-----'
|
|
||||||
;
|
|
||||||
};
|
|
||||||
@ -1,28 +1,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/*global BigInt*/
|
|
||||||
var SSH = module.exports;
|
var SSH = module.exports;
|
||||||
var Enc = require('./encoding.js');
|
var Enc = require('./encoding.js');
|
||||||
var PEM = require('./pem.js');
|
|
||||||
var bnwarn = false;
|
|
||||||
|
|
||||||
SSH.parse = function (opts) {
|
SSH.parse = function (opts) {
|
||||||
var pub = opts.pem || opts.pub || opts;
|
var pub = opts.pub || opts;
|
||||||
var ssh = SSH.parseBlock(pub);
|
var ssh = SSH.parseBlock(pub);
|
||||||
if ('OPENSSH PRIVATE KEY' === ssh.type) {
|
ssh = SSH.parseElements(ssh);
|
||||||
ssh = SSH.parsePrivateElements(ssh);
|
|
||||||
if (7 === ssh.elements.length) {
|
|
||||||
// RSA Private Keys have the `e` and `n` swapped (which is actually more normal)
|
|
||||||
// but we have to reswap them to make them consistent with the public key format
|
|
||||||
ssh.elements.splice(1, 0, ssh.elements.splice(2 ,1)[0]);
|
|
||||||
}
|
|
||||||
if (opts.public) {
|
|
||||||
ssh.elements = ssh.elements.slice(0, 3);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ssh.elements = SSH.parseElements(ssh.bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
//delete ssh.bytes;
|
//delete ssh.bytes;
|
||||||
return SSH.parsePublicKey(ssh);
|
return SSH.parsePublicKey(ssh);
|
||||||
};
|
};
|
||||||
@ -43,9 +27,6 @@ SSH.fingerprint = function (opts) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SSH.parseBlock = function (ssh) {
|
SSH.parseBlock = function (ssh) {
|
||||||
if (/^-----BEGIN OPENSSH PRIVATE KEY-----/.test(ssh)) {
|
|
||||||
return PEM.parseBlock(ssh);
|
|
||||||
}
|
|
||||||
ssh = ssh.split(/\s+/g);
|
ssh = ssh.split(/\s+/g);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -55,72 +36,10 @@ SSH.parseBlock = function (ssh) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
SSH.parsePrivateElements = function (ssh) {
|
SSH.parseElements = function (ssh) {
|
||||||
// https://coolaj86.com/articles/the-openssh-private-key-format/
|
|
||||||
var buf = ssh.bytes;
|
var buf = ssh.bytes;
|
||||||
var fulllen = buf.byteLength || buf.length;
|
var fulllen = buf.byteLength || buf.length;
|
||||||
var offset = (buf.byteOffset || 0);
|
var offset = (buf.byteOffset || 0);
|
||||||
var dv = new DataView(buf.buffer.slice(offset, offset + fulllen));
|
|
||||||
var index = 0;
|
|
||||||
var padlen = 0;
|
|
||||||
var len;
|
|
||||||
var pub;
|
|
||||||
|
|
||||||
// The last byte will be either
|
|
||||||
// * a non-printable pad character
|
|
||||||
// * a printable comment character
|
|
||||||
function lastByteIsPad() {
|
|
||||||
var n = ssh.bytes[(ssh.bytes.bytesLength || ssh.bytes.length) - 1];
|
|
||||||
return n >= 0x01 && n <= 0x07;
|
|
||||||
}
|
|
||||||
while (lastByteIsPad()) {
|
|
||||||
padlen += 1;
|
|
||||||
len = (ssh.bytes.bytesLength || ssh.bytes.length);
|
|
||||||
ssh.bytes = ssh.bytes.slice(0, len - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// o p e n s s h - k e y - v 1 NULL
|
|
||||||
// 6f 70 65 6e 73 73 68 2d 6b 65 79 2d 76 31 00
|
|
||||||
// 15 characters
|
|
||||||
// 4-byte len, "none" (encryption)
|
|
||||||
// 4-byte len, "none" (kdfname)
|
|
||||||
if ('none' !== Enc.bufToBin(ssh.bytes.slice(15 + 8 + 4, 15 + 8 + 8))) {
|
|
||||||
throw new Error("Key is either encrypted (not yet supported), corrupt, or not openssh-key-v1");
|
|
||||||
}
|
|
||||||
if (padlen >= 8) {
|
|
||||||
throw new Error("Padding length should be between 0 and 7, not '" + padlen + "'."
|
|
||||||
+ " Probably not an ssh private key.");
|
|
||||||
}
|
|
||||||
// 4-byte len, nil (kdf)
|
|
||||||
// 4-byte number of keys
|
|
||||||
index += 15 + 8 + 8 + 4 + 4;
|
|
||||||
|
|
||||||
// length of public key
|
|
||||||
len = dv.getUint32(index, false);
|
|
||||||
// throw away public key (it's in the private key)
|
|
||||||
index += 4 + len;
|
|
||||||
pub = ssh.bytes.slice(index - len, index);
|
|
||||||
|
|
||||||
// length of dummy checksum + private key + padding
|
|
||||||
len = dv.getUint32(index, false) - padlen;
|
|
||||||
// throw away dummy checksum
|
|
||||||
index += 4 + 8;
|
|
||||||
|
|
||||||
ssh.elements = SSH.parseElements(ssh.bytes.slice(index, index + (len - 8)));
|
|
||||||
index += Array.prototype.reduce.call(ssh.elements, function (el, sum) {
|
|
||||||
// 32-bit len + element len
|
|
||||||
return 4 + (el.byteLength || el.length) + sum;
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
// comment will exist, even if it's an empty string
|
|
||||||
ssh.comment = Enc.bufToBin(ssh.elements.pop());
|
|
||||||
ssh.bytes = pub;
|
|
||||||
return ssh;
|
|
||||||
};
|
|
||||||
SSH.parseElements = function (buf) {
|
|
||||||
var fulllen = buf.byteLength || buf.length;
|
|
||||||
// Note: node has weird offsets
|
|
||||||
var offset = (buf.byteOffset || 0);
|
|
||||||
var i = 0;
|
var i = 0;
|
||||||
var index = 0;
|
var index = 0;
|
||||||
// using dataview to be browser-compatible (I do want _some_ code reuse)
|
// using dataview to be browser-compatible (I do want _some_ code reuse)
|
||||||
@ -134,7 +53,6 @@ SSH.parseElements = function (buf) {
|
|||||||
if (i > 15) { throw new Error("15+ elements, probably not a public ssh key"); }
|
if (i > 15) { throw new Error("15+ elements, probably not a public ssh key"); }
|
||||||
len = dv.getUint32(index, false);
|
len = dv.getUint32(index, false);
|
||||||
index += 4;
|
index += 4;
|
||||||
if (0 === len) { continue; }
|
|
||||||
el = buf.slice(index, index + len);
|
el = buf.slice(index, index + len);
|
||||||
// remove BigUInt '00' prefix
|
// remove BigUInt '00' prefix
|
||||||
if (0x00 === el[0]) {
|
if (0x00 === el[0]) {
|
||||||
@ -149,7 +67,8 @@ SSH.parseElements = function (buf) {
|
|||||||
}).join('\n'));
|
}).join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return els;
|
ssh.elements = els;
|
||||||
|
return ssh;
|
||||||
};
|
};
|
||||||
|
|
||||||
SSH.parsePublicKey = function (ssh) {
|
SSH.parsePublicKey = function (ssh) {
|
||||||
@ -159,43 +78,11 @@ SSH.parsePublicKey = function (ssh) {
|
|||||||
|
|
||||||
// RSA keys are all the same
|
// RSA keys are all the same
|
||||||
if (SSH.types.rsa === typ) {
|
if (SSH.types.rsa === typ) {
|
||||||
if (3 === els.length) {
|
ssh.jwk = {
|
||||||
ssh.jwk = {
|
kty: 'RSA'
|
||||||
kty: 'RSA'
|
, n: Enc.bufToUrlBase64(els[2])
|
||||||
, n: Enc.bufToUrlBase64(els[2])
|
, e: Enc.bufToUrlBase64(els[1])
|
||||||
, e: Enc.bufToUrlBase64(els[1])
|
};
|
||||||
};
|
|
||||||
} else {
|
|
||||||
ssh.jwk = {
|
|
||||||
kty: 'RSA'
|
|
||||||
, n: Enc.bufToUrlBase64(els[2])
|
|
||||||
, e: Enc.bufToUrlBase64(els[1])
|
|
||||||
, d: Enc.bufToUrlBase64(els[3])
|
|
||||||
, p: Enc.bufToUrlBase64(els[5])
|
|
||||||
, q: Enc.bufToUrlBase64(els[6])
|
|
||||||
, dp: 0
|
|
||||||
, dq: 0
|
|
||||||
, qi: Enc.bufToUrlBase64(els[4])
|
|
||||||
};
|
|
||||||
if ('undefined' !== typeof BigInt) {
|
|
||||||
// BigInt doesn't use new
|
|
||||||
/*jshint newcap: false*/
|
|
||||||
// d mod (p - 1)
|
|
||||||
ssh.jwk.dp = Enc.bnToUrlBase64(BigInt('0x' + Enc.base64ToHex(ssh.jwk.d))
|
|
||||||
% (BigInt('0x' + Enc.base64ToHex(ssh.jwk.p)) - BigInt(1)));
|
|
||||||
ssh.jwk.dq = Enc.bnToUrlBase64(BigInt('0x' + Enc.base64ToHex(ssh.jwk.d))
|
|
||||||
% (BigInt('0x' + Enc.base64ToHex(ssh.jwk.q)) - BigInt(1)));
|
|
||||||
} else {
|
|
||||||
if (!bnwarn) {
|
|
||||||
bnwarn = true;
|
|
||||||
// TODO maybe conditionally bring in BigInt polyfill?
|
|
||||||
console.warn("ssh-to-jwk.js: Your version of node is outdated doesn't support BigInt");
|
|
||||||
console.log("JWKs will be missing `dp` and `dq` values. Update or use a BigInt polyfill.");
|
|
||||||
}
|
|
||||||
delete ssh.jwk.dp;
|
|
||||||
delete ssh.jwk.dq;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ssh;
|
return ssh;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,17 +101,11 @@ SSH.parsePublicKey = function (ssh) {
|
|||||||
// els[1] is just a repeat of a subset of els[0]
|
// els[1] is just a repeat of a subset of els[0]
|
||||||
var x = els[2].slice(1, 1 + len);
|
var x = els[2].slice(1, 1 + len);
|
||||||
var y = els[2].slice(1 + len, 1 + len + len);
|
var y = els[2].slice(1 + len, 1 + len + len);
|
||||||
var d;
|
|
||||||
|
|
||||||
// I don't think EC keys use 0x00 padding, but just in case
|
// I don't think EC keys use 0x00 padding, but just in case
|
||||||
while (0x00 === x[0]) { x = x.slice(1); }
|
while (0x00 === x[0]) { x = x.slice(1); }
|
||||||
while (0x00 === y[0]) { y = y.slice(1); }
|
while (0x00 === y[0]) { y = y.slice(1); }
|
||||||
|
|
||||||
if (els[3]) {
|
|
||||||
d = els[3];
|
|
||||||
while (0x00 === d[0]) { d = d.slice(1); }
|
|
||||||
ssh.jwk.d = Enc.bufToUrlBase64(d);
|
|
||||||
}
|
|
||||||
ssh.jwk.x = Enc.bufToUrlBase64(x);
|
ssh.jwk.x = Enc.bufToUrlBase64(x);
|
||||||
ssh.jwk.y = Enc.bufToUrlBase64(y);
|
ssh.jwk.y = Enc.bufToUrlBase64(y);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ssh-to-jwk",
|
"name": "ssh-to-jwk",
|
||||||
"version": "1.2.6",
|
"version": "1.1.0",
|
||||||
"description": "💯 SSH to JWK in a lightweight, zero-dependency library.",
|
"description": "💯 SSH to JWK in a lightweight, zero-dependency library.",
|
||||||
"homepage": "https://git.coolaj86.com/coolaj86/ssh-to-jwk.js",
|
"homepage": "https://git.coolaj86.com/coolaj86/ssh-to-jwk.js",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
@ -29,7 +29,6 @@
|
|||||||
"RSA",
|
"RSA",
|
||||||
"EC",
|
"EC",
|
||||||
"SSH",
|
"SSH",
|
||||||
"OpenSSH",
|
|
||||||
"fingerprint",
|
"fingerprint",
|
||||||
"JWK",
|
"JWK",
|
||||||
"ECDSA"
|
"ECDSA"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user