v1.2.0: add support for OpenSSH private keys (EC and RSA)
This commit is contained in:
parent
ce26bf4e95
commit
093c156ea6
62
README.md
62
README.md
@ -11,13 +11,17 @@ Features
|
||||
< 75 lines of code | < 0.7kb gzipped | 1.5kb minified | 2.1kb with comments
|
||||
|
||||
* [x] SSH Public Keys
|
||||
* [x] RSA Public Keys
|
||||
* [x] EC Public Keys
|
||||
* [x] OpenSSH Private Keys
|
||||
* [x] RSA Keys
|
||||
* [x] EC Keys
|
||||
* P-256 (prime256v1, secp256r1)
|
||||
* P-384 (secp384r1)
|
||||
* [x] Browser Version
|
||||
* [Bluecrypt JWK to SSH](https://git.coolaj86.com/coolaj86/bluecrypt-jwk-to-ssh.js)
|
||||
|
||||
Note: the file size stats are for v1.0 which did not include private key packing.
|
||||
I plan to go back and update the stats, but just know that it grew a little over 2x.
|
||||
|
||||
### Need JWK to SSH? PEM to SSH?
|
||||
|
||||
Try one of these:
|
||||
@ -26,12 +30,35 @@ Try one of these:
|
||||
* [Eckles.js](https://git.coolaj86.com/coolaj86/eckles.js) (more EC utils)
|
||||
* [Rasha.js](https://git.coolaj86.com/coolaj86/eckles.js) (more RSA utils)
|
||||
|
||||
### Need SSH Private Keys?
|
||||
### Need Alternate SSH Private Keys?
|
||||
|
||||
SSH private keys are just normal PEM files,
|
||||
so you can use Eckles or Rasha, as mentioned above.
|
||||
This library supports OpenSSH private keys.
|
||||
|
||||
# CLI
|
||||
* [x] OpenSSH
|
||||
* [ ] Normal PKCS1 / SEC1 / PKCS8
|
||||
* [x] Rasha.js
|
||||
* [x] Eckles.js
|
||||
* [ ] Putty
|
||||
|
||||
# Library Usage
|
||||
|
||||
You can also use it from JavaScript:
|
||||
|
||||
```js
|
||||
var fs = require('fs');
|
||||
var jwktossh = require('jwk-to-ssh');
|
||||
|
||||
var jwk = JSON.parse(fs.readFileSync("./privkey.jwk.json"));
|
||||
var pub = jwktossh.pack({
|
||||
jwk: jwk
|
||||
, comment: 'root@localhost'
|
||||
, public: true
|
||||
});
|
||||
|
||||
console.info(pub);
|
||||
```
|
||||
|
||||
# CLI Usage
|
||||
|
||||
You can install `jwk-to-ssh` and use it from command line:
|
||||
|
||||
@ -39,25 +66,20 @@ You can install `jwk-to-ssh` and use it from command line:
|
||||
npm install -g jwk-to-ssh
|
||||
```
|
||||
|
||||
```bash
|
||||
jwk-to-ssh [keyfile] [comment] [public]
|
||||
```
|
||||
|
||||
```bash
|
||||
jwk-to-ssh pubkey.jwk.json
|
||||
```
|
||||
|
||||
# Usage
|
||||
```bash
|
||||
jwk-to-ssh privkey.jwk.json root@localhost
|
||||
```
|
||||
|
||||
You can also use it from JavaScript:
|
||||
|
||||
```js
|
||||
var fs = require('fs');
|
||||
var jwktossh = require('jwk-to-ssh');
|
||||
|
||||
var jwk = JSON.parse(fs.readFileSync("./pubkey.jwk.json"));
|
||||
var pub = jwktossh.pack({
|
||||
jwk: jwk
|
||||
, comment: 'root@localhost'
|
||||
});
|
||||
|
||||
console.info(pub);
|
||||
```bash
|
||||
jwk-to-ssh privkey.jwk.json root@localhost public
|
||||
```
|
||||
|
||||
Legal
|
||||
|
@ -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);
|
||||
|
7
fixtures/privkey-ec-p256.jwk.json
Normal file
7
fixtures/privkey-ec-p256.jwk.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"kty": "EC",
|
||||
"crv": "P-256",
|
||||
"d": "iYydo27aNGO9DBUWeGEPD8oNi1LZDqfxPmQlieLBjVQ",
|
||||
"x": "IT1SWLxsacPiE5Z16jkopAn8_-85rMjgyCokrnjDft4",
|
||||
"y": "mP2JwOAOdMmXuwpxbKng3KZz27mz-nKWIlXJ3rzSGMo"
|
||||
}
|
9
fixtures/privkey-ec-p256.openssh.pem
Normal file
9
fixtures/privkey-ec-p256.openssh.pem
Normal file
@ -0,0 +1,9 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
|
||||
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQhPVJYvGxpw+ITlnXqOSikCfz/7zms
|
||||
yODIKiSueMN+3pj9icDgDnTJl7sKcWyp4Nymc9u5s/pyliJVyd680hjKAAAAqGJjanNiY2
|
||||
pzAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCE9Uli8bGnD4hOW
|
||||
deo5KKQJ/P/vOazI4MgqJK54w37emP2JwOAOdMmXuwpxbKng3KZz27mz+nKWIlXJ3rzSGM
|
||||
oAAAAhAImMnaNu2jRjvQwVFnhhDw/KDYtS2Q6n8T5kJYniwY1UAAAADnJvb3RAbG9jYWxo
|
||||
b3N0AQ==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
7
fixtures/privkey-ec-p384.jwk.json
Normal file
7
fixtures/privkey-ec-p384.jwk.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"kty": "EC",
|
||||
"crv": "P-384",
|
||||
"d": "XlyuCEWSTTS8U79O_Mz05z18vh4kb10szvu_7pdXuGWV6lfEyPExyUYWsA6A2kdV",
|
||||
"x": "2zEU0bKCa7ejKLIJ8oPGnLhqhxyiv4_w38K2a0SPC6dsSd9_glNJ8lcqv0sff5Gb",
|
||||
"y": "VD4jnu83S6scn6_TeAj3EZOREGbOs6dzoVpaugn-XQMMyC9O4VLbDDFGBZTJlMsb"
|
||||
}
|
10
fixtures/privkey-ec-p384.openssh.pem
Normal file
10
fixtures/privkey-ec-p384.openssh.pem
Normal file
@ -0,0 +1,10 @@
|
||||
-----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-----
|
9
fixtures/privkey-rsa-2048.jwk.json
Normal file
9
fixtures/privkey-rsa-2048.jwk.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"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",
|
||||
"qi": "AlHWbx1gp6Z9pbw_1hlS7HuXAgWoX7IjbTUelldf4gkriDWLOrj3QCZcO4ZvZvEwJhVlsny9LO8IkbwGJEL6cXraK08ByVS2mwQyflgTgGNnpzixyEUL_mrQLx6y145FHcxfeqNInMhep-0Mxn1D5nlhmIOgRApS0t9VoXtHhFU"
|
||||
}
|
27
fixtures/privkey-rsa-2048.openssh.pem
Normal file
27
fixtures/privkey-rsa-2048.openssh.pem
Normal file
@ -0,0 +1,27 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
|
||||
NhAAAAAwEAAQAAAQEAm2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhDNzUJ
|
||||
efLukC+xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ38P8LdAIlb
|
||||
0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57qAZM6+I70on0/iDZ
|
||||
m7+jcqOPgADAmbWHhy67BXkk4yy/YzD4yOGZFXZcNp915/TW5bRd//AKPHUHxJasPiyEFq
|
||||
lNKBR2DSD+LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By5AB0Qi49OykUCfNZeQlEz7UNNj9R
|
||||
Gps/50+CNwAAA8hiY2pzYmNqcwAAAAdzc2gtcnNhAAABAQCba21UHE+VbDTpmYYFZUOV+O
|
||||
Q8AngOCdjROsPC0KiEfMvEaEM3NQl58u6QL7G7QsErKViiNPm9OTFo6HF5JijfWzK7haHF
|
||||
uRMEsgI4VwIYyhvqlJDfw/wt0AiVvSmoMfEQn1p1aiaO4V/RJSE3Vw/uz2bxiT22uSkSqO
|
||||
yShyfYE6dMHnuoBkzr4jvSifT+INmbv6Nyo4+AAMCZtYeHLrsFeSTjLL9jMPjI4ZkVdlw2
|
||||
n3Xn9NbltF3/8Ao8dQfElqw+LIQWqU0oFHYNIP4ttfl5ObMKHaKSvBMyNruZR0El/ZsrcH
|
||||
LkAHRCLj07KRQJ81l5CUTPtQ02P1Eamz/nT4I3AAAAAwEAAQAAAQAKl+jsyb027xgwL/Gt
|
||||
nnhb2Yoc+QIb2tn3cOz082n5FdSBv6WNNsU9laxjEY0ouWUXPws2iyvWlPZkMyvA2TQB/W
|
||||
JY28PqCTJdqJB0LKIM9IGwnI2/Yslmif57zlfvie/ndSUiSaj1JjF0vPYlon6IhYR7xCH7
|
||||
uWUuS2tMlX/Gv3z+1uGBKmnP8h47KardIRlpAczylNTvDte8KPalv2vx83QzRdyBZkhp/c
|
||||
sYR+SEX9a1frtcnQLr4tuRPnWO2UtE0ZfqFoZ2xxdLQ5ZaMo4nMoa5X41u2ESfmuZG1WeN
|
||||
FnfJrFDS53y8L4042fvK8ZSU66vUh0PMn9h8c+32wcxBAAAAgAJR1m8dYKemfaW8P9YZUu
|
||||
x7lwIFqF+yI201HpZXX+IJK4g1izq490AmXDuGb2bxMCYVZbJ8vSzvCJG8BiRC+nF62itP
|
||||
AclUtpsEMn5YE4BjZ6c4schFC/5q0C8esteORR3MX3qjSJzIXqftDMZ9Q+Z5YZiDoEQKUt
|
||||
LfVaF7R4RVAAAAgQDKcb630fAoI3cxZFgV2cXOL37TZBzLenwHmm7dqvLYTvSFifakeVQb
|
||||
Zr0E0TxznEdDcfHjdahZ/qzTM66c9XUbbwuRT9RgRsC0+/8/FKbOlCn2f1edi8EsEXxjfP
|
||||
uDGAufW9UU0BGtB1G2PzupjdrDsT1LgOwZwBqsi45PZOxMIQAAAIEAxIkAjgUzB1zaUzJt
|
||||
W2Zgvp9cYYr1DmpH30ePZl3c/8397/DZDDo46fnFYjs6uPa03HpmKUnbjwr14QHlfXlntJ
|
||||
BEuXxcqLjkdKdJ4ob7xueLTK4suo9V8LSrkLChVxlZQwnFD2E5ll0sVeeDeMJHQw38ahSr
|
||||
BFEVnxjpnPh1Q1cAAAAOcm9vdEBsb2NhbGhvc3QBAgMEBQ==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
1
fixtures/pub-ec-p256.ssh.pub
Normal file
1
fixtures/pub-ec-p256.ssh.pub
Normal file
@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCE9Uli8bGnD4hOWdeo5KKQJ/P/vOazI4MgqJK54w37emP2JwOAOdMmXuwpxbKng3KZz27mz+nKWIlXJ3rzSGMo= root@localhost
|
1
fixtures/pub-ec-p384.ssh.pub
Normal file
1
fixtures/pub-ec-p384.ssh.pub
Normal file
@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBNsxFNGygmu3oyiyCfKDxpy4aoccor+P8N/CtmtEjwunbEnff4JTSfJXKr9LH3+Rm1Q+I57vN0urHJ+v03gI9xGTkRBmzrOnc6FaWroJ/l0DDMgvTuFS2wwxRgWUyZTLGw== root@localhost
|
1
fixtures/pub-rsa-2048.ssh.pub
Normal file
1
fixtures/pub-rsa-2048.ssh.pub
Normal file
@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCba21UHE+VbDTpmYYFZUOV+OQ8AngOCdjROsPC0KiEfMvEaEM3NQl58u6QL7G7QsErKViiNPm9OTFo6HF5JijfWzK7haHFuRMEsgI4VwIYyhvqlJDfw/wt0AiVvSmoMfEQn1p1aiaO4V/RJSE3Vw/uz2bxiT22uSkSqOyShyfYE6dMHnuoBkzr4jvSifT+INmbv6Nyo4+AAMCZtYeHLrsFeSTjLL9jMPjI4ZkVdlw2n3Xn9NbltF3/8Ao8dQfElqw+LIQWqU0oFHYNIP4ttfl5ObMKHaKSvBMyNruZR0El/ZsrcHLkAHRCLj07KRQJ81l5CUTPtQ02P1Eamz/nT4I3 root@localhost
|
@ -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');
|
||||
};
|
||||
|
11
lib/pem.js
Normal file
11
lib/pem.js
Normal file
@ -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 + '-----'
|
||||
;
|
||||
};
|
@ -1,34 +1,92 @@
|
||||
'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(SSH._padRsa(Enc.base64ToHex(jwk.e)));
|
||||
els.push(SSH._padRsa(Enc.base64ToHex(jwk.n)));
|
||||
return SSH._packElements(ssh);
|
||||
els.push(Enc.binToHex('ssh-rsa'));
|
||||
if (jwk.d) {
|
||||
// unswap n and e for private key format
|
||||
els.push(SSH._padBigInt(Enc.base64ToHex(jwk.n)));
|
||||
els.push(SSH._padBigInt(Enc.base64ToHex(jwk.e)));
|
||||
els.push(SSH._padBigInt(Enc.base64ToHex(jwk.d)));
|
||||
els.push(SSH._padBigInt(Enc.base64ToHex(jwk.qi)));
|
||||
els.push(SSH._padBigInt(Enc.base64ToHex(jwk.p)));
|
||||
els.push(SSH._padBigInt(Enc.base64ToHex(jwk.q)));
|
||||
els.push(Enc.binToHex(opts.comment || ''));
|
||||
} else {
|
||||
// swap n and e for public key format
|
||||
els.push(SSH._padBigInt(Enc.base64ToHex(jwk.e)));
|
||||
els.push(SSH._padBigInt(Enc.base64ToHex(jwk.n)));
|
||||
}
|
||||
return 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 {
|
||||
@ -36,17 +94,26 @@ SSH.pack = function (opts) {
|
||||
}
|
||||
|
||||
els.push('04'
|
||||
+ SSH._padEc(Enc.base64ToHex(jwk.x), len)
|
||||
+ SSH._padEc(Enc.base64ToHex(jwk.y), len)
|
||||
+ SSH._padBytes(Enc.base64ToHex(jwk.x), len)
|
||||
+ SSH._padBytes(Enc.base64ToHex(jwk.y), len)
|
||||
);
|
||||
return SSH._packElements(ssh);
|
||||
if (jwk.d) {
|
||||
// I was able to empirically confirm that the leading 00 is expected for
|
||||
// ambiguous BigInt negatives (0x80 set), and that the length can dip down
|
||||
// to 31 bytes when the leading byte is 0x00. I suspect that if I had tried
|
||||
// 65k iterations that I'd have seen at least one 30 byte number
|
||||
els.push(SSH._padBigInt(Enc.base64ToHex(jwk.d)));
|
||||
//console.warn('els:', els[els.length - 1]);
|
||||
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) {
|
||||
@ -57,18 +124,20 @@ SSH._numToUint32Hex = function (num) {
|
||||
return hex;
|
||||
};
|
||||
|
||||
SSH._padRsa = function (hex) {
|
||||
SSH._padBigInt = function (hex) {
|
||||
// BigInt is negative if the high order bit 0x80 is set,
|
||||
// so ASN1, SSH, and many other formats pad with '0x00'
|
||||
// to signifiy a positive number.
|
||||
var i = parseInt(hex.slice(0, 2), 16);
|
||||
//console.warn('l', hex.length/2, 'i', i);
|
||||
if (0x80 & i) {
|
||||
//console.warn('0x80 true');
|
||||
return '00' + hex;
|
||||
}
|
||||
return hex;
|
||||
};
|
||||
|
||||
SSH._padEc = function (hex, len) {
|
||||
SSH._padBytes = function (hex, len) {
|
||||
while (hex.length < len * 2) {
|
||||
hex = '00' + hex;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jwk-to-ssh",
|
||||
"version": "1.0.1",
|
||||
"version": "1.2.0",
|
||||
"description": "💯 JWK to SSH in a lightweight, zero-dependency library.",
|
||||
"homepage": "https://git.coolaj86.com/coolaj86/jwk-to-ssh.js",
|
||||
"main": "index.js",
|
||||
|
32
test.sh
Executable file
32
test.sh
Executable file
@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
chmod 0600 fixtures/*
|
||||
|
||||
# creates a new key
|
||||
#ssh-keygen -t rsa -b 2048 -C root@localhost -N '' -f fixtures/privkey-rsa-2048.openssh.pem.3
|
||||
node bin/jwk-to-ssh.js fixtures/privkey-rsa-2048.jwk.json root@localhost > fixtures/privkey-rsa-2048.openssh.pem
|
||||
node bin/jwk-to-ssh.js fixtures/privkey-rsa-2048.jwk.json root@localhost public > fixtures/pub-rsa-2048.ssh.pub
|
||||
|
||||
node bin/jwk-to-ssh.js fixtures/privkey-ec-p256.jwk.json root@localhost > fixtures/privkey-ec-p256.openssh.pem
|
||||
node bin/jwk-to-ssh.js fixtures/privkey-ec-p256.jwk.json root@localhost public > fixtures/pub-ec-p256.ssh.pub
|
||||
|
||||
node bin/jwk-to-ssh.js fixtures/privkey-ec-p384.jwk.json root@localhost > fixtures/privkey-ec-p384.openssh.pem
|
||||
node bin/jwk-to-ssh.js fixtures/privkey-ec-p384.jwk.json root@localhost public > fixtures/pub-ec-p384.ssh.pub
|
||||
|
||||
# changes embedded comment and creates new random dummy checksum
|
||||
ssh-keygen -c -C root@localhost -f fixtures/privkey-rsa-2048.openssh.pem
|
||||
ssh-keygen -c -C root@localhost -f fixtures/privkey-ec-p256.openssh.pem
|
||||
ssh-keygen -c -C root@localhost -f fixtures/privkey-ec-p384.openssh.pem
|
||||
|
||||
# convert to public key
|
||||
ssh-keygen -y -C root@localhost -f fixtures/privkey-rsa-2048.openssh.pem
|
||||
ssh-keygen -y -C root@localhost -f fixtures/privkey-ec-p256.openssh.pem
|
||||
ssh-keygen -y -C root@localhost -f fixtures/privkey-ec-p384.openssh.pem
|
||||
|
||||
diff fixtures/pub-rsa-2048.ssh.pub fixtures/privkey-rsa-2048.openssh.pem.pub
|
||||
diff fixtures/pub-ec-p256.ssh.pub fixtures/privkey-ec-p256.openssh.pem.pub
|
||||
diff fixtures/pub-ec-p384.ssh.pub fixtures/privkey-ec-p384.openssh.pem.pub
|
||||
|
||||
#ssh-keygen -e -m PKCS8 -f fixtures/privkey-ec-p256.openssh.pem
|
||||
#ssh-keygen -e -m RFC4716 -f fixtures/privkey-ec-p256.openssh.pem
|
Loading…
x
Reference in New Issue
Block a user