v1.0.0: pack EC and RSA ssh public keys

This commit is contained in:
AJ ONeal 2018-12-02 01:59:33 -07:00
commit 1e730abff9
4 changed files with 291 additions and 0 deletions

79
README.md Normal file
View File

@ -0,0 +1,79 @@
# Bluecrypt JWK to SSH (for Browsers)
A minimal library to parse an SSH public key (`id_rsa.pub`)
and convert it into a public JWK using Vanilla JS.
Works for RSA and ECDSA public keys.
# Features
< 100 lines of code | < 1.0kb gzipped | 2.0kb minified | 2.9kb with comments
* [x] SSH Public Keys
* [x] RSA Public Keys
* [x] EC Public Keys
* P-256 (prime256v1, secp256r1)
* P-384 (secp384r1)
* [x] node.js version
* [jwk-to-ssh.js](https://git.coolaj86.com/coolaj86/jwk-to-ssh.js)
* [x] on npm as [bluecrypt-jwk-to-ssh](https://www.npmjs.com/package/bluecrypt-jwk-to-ssh)
### Need SSH Private Keys?
SSH private keys (`id_rsa`) are just normal PEM files.
# Web Demo
<https://coolaj86.com/demos/jwk-to-ssh/>
<img border="1" src="https://i.imgur.com/LY3JGPY.png" />
```bash
git clone https://git.coolaj86.com/coolaj86/bluecrypt-jwk-to-ssh.js
pushd bluecrypt-jwk-to-ssh.js/
```
```bash
open index.html
```
# Install
You can use it as a plain-old javascript library:
```html
<script src="https://git.coolaj86.com/coolaj86/bluecrypt-jwk-to-ssh.js/raw/branch/master/jwk-to-ssh.js"></script>
```
It's also on npm:
```bash
npm install bluecrypt-jwk-to-ssh
```
# Usage
Very simple:
```js
var pub = 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCE9Uli8bGnD4hOWdeo5KKQJ/P/vOazI4MgqJK54w37emP2JwOAOdMmXuwpxbKng3KZz27mz+nKWIlXJ3rzSGMo= root@localhost';
var ssh = SSH.parse(pub);
console.info(ssh.jwk);
```
# Other Tools in the Bluecrypt Suite
* [Bluecrypt JWK to SSH](https://git.coolaj86.com/coolaj86/bluecrypt-jwk-to-ssh.js) (RSA, EC, SSH)
* [Bluecrypt ASN.1 decoder](https://git.coolaj86.com/coolaj86/asn1-parser.js) (x509, RSA, EC, etc)
* [Bluecrypt ASN.1 builder](https://git.coolaj86.com/coolaj86/asn1-packer.js) (x509, RSA, EC, etc)
# Legal
[jwk-to-ssh.js](https://git.coolaj86.com/coolaj86/jwk-to-ssh.js) |
MPL-2.0 |
[Terms of Use](https://therootcompany.com/legal/#terms) |
[Privacy Policy](https://therootcompany.com/legal/#privacy)
Bluecrypt&trade; is owned by AJ ONeal

64
index.html Normal file
View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<title>SSH Pub Generator - Bluecrypt</title>
<style>
textarea {
width: 42em;
height: 10em;
}
pre {
white-space: pre-wrap;
}
.code {
width: 29em;
word-wrap: break-word;
}
</style>
</head>
<body>
<h1>Bluecrypt SSH Public Key Generator</h1>
<textarea class="js-input" placeholder="Paste a PEM here">{
"kty": "EC",
"crv": "P-256",
"x": "IT1SWLxsacPiE5Z16jkopAn8_-85rMjgyCokrnjDft4",
"y": "mP2JwOAOdMmXuwpxbKng3KZz27mz-nKWIlXJ3rzSGMo"
}</textarea>
<br>
<input type="text" class="js-comment" placeholder="SSH comment (i.e. root@localhost" value="root@localhost">
<!-- pre><code class="js-hex"> </code></pre -->
<div class="code"><pre><code class="js-pub"> </code></pre></div>
<br>
<p>Made with <a href="https://git.coolaj86.com/coolaj86/bluecrypt-jwk-to-ssh.js/">jwk-to-ssh.js</a></p>
<script src="./jwk-to-ssh.js"></script>
<script>
'use strict';
var $input = document.querySelector('.js-input');
function convert() {
console.log('keyup');
try {
var text = document.querySelector('.js-input').value;
var comment = document.querySelector('.js-comment').value;
var jwk = JSON.parse(text);
var pub = SSH.pack({ jwk: jwk, comment: comment });
//document.querySelector('.js-hex').innerText = hex
// .match(/.{2}/g).join(' ').match(/.{1,24}/g).join(' ').match(/.{1,50}/g).join('\n');
document.querySelector('.js-pub').innerText = pub;
} catch(e) {
var msg = { error: { message: e.message } };
document.querySelector('.js-pub').innerText = JSON.stringify(msg, null, 2);
}
}
$input.addEventListener('keyup', convert);
convert();
</script>
</body>
</html>

120
jwk-to-ssh.js Normal file
View File

@ -0,0 +1,120 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
;(function (exports) {
'use strict';
if (!exports.Enc) { exports.Enc = {}; }
if (!exports.SSH) { exports.SSH = {}; }
var Enc = exports.Enc;
var SSH = exports.SSH;
SSH.pack = 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);
}
if ("P-256" === jwk.crv) {
ssh.type = 'ecdsa-sha2-nistp256';
els.push(Enc.binToHex(ssh.type));
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('nistp384'));
len = 48;
} else {
throw new Error("unknown key type " + (jwk.crv || jwk.kty));
}
els.push('04'
+ SSH._padEc(Enc.base64ToHex(jwk.x), len)
+ SSH._padEc(Enc.base64ToHex(jwk.y), len)
);
return SSH._packElements(ssh);
};
SSH._packElements = function (ssh) {
var hex = ssh._elements.map(function (hex) {
console.log(hex);
return SSH._numToUint32Hex(hex.length/2) + hex;
}).join('');
return [ ssh.type, Enc.hexToBase64(hex), ssh.comment ].join(' ');
};
SSH._numToUint32Hex = function (num) {
var hex = num.toString(16);
while (hex.length < 8) {
hex = '0' + hex;
}
console.log('length', hex);
return hex;
};
SSH._padRsa = 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);
if (0x80 & i) {
return '00' + hex;
}
return hex;
};
SSH._padEc = function (hex, len) {
while (hex.length < len * 2) {
hex = '00' + hex;
}
return hex;
};
Enc.base64ToHex = function (b64) {
var bin = atob(Enc.urlBase64ToBase64(b64));
return Enc.binToHex(bin);
};
Enc.binToHex = function (bin) {
return bin.split('').map(function (ch) {
var h = ch.charCodeAt(0).toString(16);
if (h.length % 2) { h = '0' + h; }
return h;
}).join('');
};
Enc.hexToBase64 = function (hex) {
return btoa(Enc.hexToBin(hex));
};
Enc.hexToBin = function (hex) {
return hex.match(/.{2}/g).map(function (h) {
return String.fromCharCode(parseInt(h, 16));
}).join('');
};
Enc.urlBase64ToBase64 = function urlsafeBase64ToBase64(str) {
var r = str % 4;
if (2 === r) {
str += '==';
} else if (3 === r) {
str += '=';
}
return str.replace(/-/g, '+').replace(/_/g, '/');
};
}('undefined' !== typeof window ? window : module.exports));

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "bluecrypt-jwk-to-ssh",
"version": "1.0.0",
"description": "JWK to SSH in < 100 lines of VanillaJS.",
"homepage": "https://git.coolaj86.com/coolaj86/bluecrypt-jwk-to-ssh.js",
"main": "jwk-to-ssh.js",
"scripts": {
"prepare": "uglifyjs jwk-to-ssh.js > jwk-to-ssh.min.js"
},
"directories": {
"lib": "lib"
},
"repository": {
"type": "git",
"url": "https://git.coolaj86.com/coolaj86/bluecrypt-jwk-to-ssh.js"
},
"keywords": [
"zero-dependency",
"JWK-to-SSH",
"RSA",
"EC",
"SSH",
"JWK",
"ECDSA"
],
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "MPL-2.0"
}