Prettier all the things

This commit is contained in:
Patrick Roumanoff 2018-02-13 09:30:29 +01:00
parent e4a0c7f193
commit 355e22bcd6
10 changed files with 518 additions and 192 deletions

View file

@ -1,5 +1,4 @@
Generate a ssh keypair using the webcrypto API # Generate a ssh keypair using the webcrypto API
==
See the live demo at https://js-keygen.surge.sh See the live demo at https://js-keygen.surge.sh
@ -11,8 +10,6 @@ There is no way to generate a ssh keypair on the chrome book, but we have access
* I had to learn about ASN.1 to encode the private key for OpenSSH * I had to learn about ASN.1 to encode the private key for OpenSSH
* I had to lean about the open SSH public format to encode the public key for OpenSSH * I had to lean about the open SSH public format to encode the public key for OpenSSH
The end result is a usable single page app that will locally generate a keypair you can save to local drive. Allowing you to do that straight from chrome on a chrome book. The end result is a usable single page app that will locally generate a keypair you can save to local drive. Allowing you to do that straight from chrome on a chrome book.
Everywhere else, you should have access to ssh-keygen which is the recommended way to generate keypair for SSH. Everywhere else, you should have access to ssh-keygen which is the recommended way to generate keypair for SSH.

View file

@ -3,15 +3,15 @@
function base64urlEncode(arg) { function base64urlEncode(arg) {
var s = window.btoa(arg); // Regular base64 encoder var s = window.btoa(arg); // Regular base64 encoder
s = s.split('=')[0]; // Remove any trailing '='s s = s.split("=")[0]; // Remove any trailing '='s
s = s.replace(/\+/g, '-'); // 62nd char of encoding s = s.replace(/\+/g, "-"); // 62nd char of encoding
s = s.replace(/\//g, '_'); // 63rd char of encoding s = s.replace(/\//g, "_"); // 63rd char of encoding
return s; return s;
} }
function base64urlDecode(s) { function base64urlDecode(s) {
s = s.replace(/-/g, '+'); // 62nd char of encoding s = s.replace(/-/g, "+"); // 62nd char of encoding
s = s.replace(/_/g, '/'); // 63rd char of encoding s = s.replace(/_/g, "/"); // 63rd char of encoding
switch (s.length % 4) { // Pad with trailing '='s switch (s.length % 4) { // Pad with trailing '='s
case 0: // No pad chars in this case case 0: // No pad chars in this case
break; break;

View file

@ -1,5 +1,6 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<title>js-keygen</title> <title>js-keygen</title>
<script src="base64url.js"></script> <script src="base64url.js"></script>
@ -9,51 +10,77 @@
<link rel="stylesheet" href="js-keygen.css"> <link rel="stylesheet" href="js-keygen.css">
<link rel="icon" type="image/png" href="key.png"> <link rel="icon" type="image/png" href="key.png">
</head> </head>
<body> <body>
<div id="content"> <div id="content">
<h1>js-keygen</h1> <h1>js-keygen</h1>
Generate a keypair to be used with openSSH, this replicate ssh-keygen function in javascript in the browser, using the webcrypto api and a bit of glue.<br> Generate a keypair to be used with openSSH, this replicate ssh-keygen function in javascript in the browser, using the webcrypto
For an in-depth explanation on how this work, see the <a href="http://blog.roumanoff.com/2015/09/using-webcrypto-api-to-generate-keypair.html">blog post</a>.<br> api and a bit of glue.
Usually you would want to save the private key to the machine initiating the ssh connection, and you want to copy the public key to the system receiving the connection.<br> <br> For an in-depth explanation on how this work, see the
No data is being sent to the server, everything happens within the context of this web page.<br> <a href="http://blog.roumanoff.com/2015/09/using-webcrypto-api-to-generate-keypair.html">blog post</a>.
<br> Usually you would want to save the private key to the machine initiating the ssh connection, and you want to copy the
public key to the system receiving the connection.
<br> No data is being sent to the server, everything happens within the context of this web page.
<br>
<a href="https://twitter.com/share" class="twitter-share-button" data-via="pkr2">Tweet</a> <a href="https://twitter.com/share" class="twitter-share-button" data-via="pkr2">Tweet</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script><hr> <script>!function (d, s, id) { var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https'; if (!d.getElementById(id)) { js = d.createElement(s); js.id = id; js.src = p + '://platform.twitter.com/widgets.js'; fjs.parentNode.insertBefore(js, fjs); } }(document, 'script', 'twitter-wjs');</script>
<div><label for="name">Name:</label><input id="name" type="text" value="webcrypto"></div> <hr>
<div><label for="alg">Algorithm:</label><select id="alg" disabled> <div>
<label for="name">Name:</label>
<input id="name" type="text" value="webcrypto">
</div>
<div>
<label for="alg">Algorithm:</label>
<select id="alg" disabled>
<option value="RSASSA-PKCS1-v1_5" selected>RSASSA-PKCS1-v1_5</option> <option value="RSASSA-PKCS1-v1_5" selected>RSASSA-PKCS1-v1_5</option>
<option value="RSA-PSS">RSA-PSS</option> <option value="RSA-PSS">RSA-PSS</option>
<option value="ECDSA">ECDSA</option> <option value="ECDSA">ECDSA</option>
</select> </select>
<label for="size">Size:</label><select id="size" disabled> <label for="size">Size:</label>
<select id="size" disabled>
<option value="1024" selected>1024</option> <option value="1024" selected>1024</option>
<option value="2048">2048</option> <option value="2048">2048</option>
<option value="4096">4096</option> <option value="4096">4096</option>
</select> </select>
</div> </div>
<div><label for="hash">Hash:</label><select id="hash" disabled> <div>
<label for="hash">Hash:</label>
<select id="hash" disabled>
<option value="SHA-1" selected>SHA-1</option> <option value="SHA-1" selected>SHA-1</option>
<option value="SHA-256">SHA-256</option> <option value="SHA-256">SHA-256</option>
<option value="SHA-384">SHA-384</option> <option value="SHA-384">SHA-384</option>
<option value="SHA-512">SHA-512</option> <option value="SHA-512">SHA-512</option>
</select></div> </select>
<label for="generate"></label><button id="generate">Generate</button> </div>
<label for="generate"></label>
<button id="generate">Generate</button>
<br> <br>
<div id="result" style="display:none;"> <div id="result" style="display:none;">
<hr> <hr>
<a id="private" style="display: none;" href="" download="id_rsa">id_rsa</a> <a id="private" style="display: none;" href="" download="id_rsa">id_rsa</a>
<a id="public" style="display: none;" href="" download="id_rsa.pub">id_rsa.pub</a> <a id="public" style="display: none;" href="" download="id_rsa.pub">id_rsa.pub</a>
Private Key <button id="copyPrivate">Copy</button> or <button id="savePrivate">Save</button><br> Private Key
<button id="copyPrivate">Copy</button> or
<button id="savePrivate">Save</button>
<br>
<textarea id="privateKey" style="height: 150px;" spellcheck="false"></textarea> <textarea id="privateKey" style="height: 150px;" spellcheck="false"></textarea>
<hr> <hr> Public Key
Public Key <button id="copyPublic">Copy</button> or <button id="savePublic">Save</button><br> <button id="copyPublic">Copy</button> or
<button id="savePublic">Save</button>
<br>
<textarea id="publicKey" spellcheck="false"></textarea> <textarea id="publicKey" spellcheck="false"></textarea>
</div> </div>
<hr> <hr> Made with
Made with <span style="color:magenta;">&hearts;</span> by <a href="http://blog.roumanoff.com">Patrick Roumanoff</a> <span style="color:magenta;">&hearts;</span> by
<a href="https://github.com/PatrickRoumanoff/js-keygen"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a> <a href="http://blog.roumanoff.com">Patrick Roumanoff</a>
</div> <a href="https://github.com/PatrickRoumanoff/js-keygen">
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67"
alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png">
</a>
</div>
</body> </body>
</html> </html>

View file

@ -2,16 +2,16 @@
var console, generateKeyPair; var console, generateKeyPair;
function copy(id) { function copy(id) {
return function () { return function() {
var ta = document.querySelector(id); var ta = document.querySelector(id);
ta.focus(); ta.focus();
ta.select(); ta.select();
try { try {
var successful = document.execCommand('copy'); var successful = document.execCommand("copy");
var msg = successful ? 'successful' : 'unsuccessful'; var msg = successful ? "successful" : "unsuccessful";
console.log('Copy key command was ' + msg); console.log("Copy key command was " + msg);
} catch (err) { } catch (err) {
console.log('Oops, unable to copy'); console.log("Oops, unable to copy");
} }
window.getSelection().removeAllRanges(); window.getSelection().removeAllRanges();
ta.blur(); ta.blur();
@ -22,30 +22,32 @@ function buildHref(data) {
return "data:application/octet-stream;charset=utf-8;base64," + window.btoa(data); return "data:application/octet-stream;charset=utf-8;base64," + window.btoa(data);
} }
document.addEventListener("DOMContentLoaded", function (event) { document.addEventListener("DOMContentLoaded", function(event) {
document.querySelector('#savePrivate').addEventListener('click', function (event) { document.querySelector("#savePrivate").addEventListener("click", function(event) {
document.querySelector('a#private').click(); document.querySelector("a#private").click();
}); });
document.querySelector('#copyPrivate').addEventListener('click', copy('#privateKey')); document.querySelector("#copyPrivate").addEventListener("click", copy("#privateKey"));
document.querySelector('#savePublic').addEventListener('click', function (event) { document.querySelector("#savePublic").addEventListener("click", function(event) {
document.querySelector('a#public').click(); document.querySelector("a#public").click();
}); });
document.querySelector('#copyPublic').addEventListener('click', copy('#publicKey')); document.querySelector("#copyPublic").addEventListener("click", copy("#publicKey"));
document.querySelector('#generate').addEventListener('click', function (event) { document.querySelector("#generate").addEventListener("click", function(event) {
var name = document.querySelector('#name').value || "name"; var name = document.querySelector("#name").value || "name";
document.querySelector('a#private').setAttribute("download", name + "_rsa"); document.querySelector("a#private").setAttribute("download", name + "_rsa");
document.querySelector('a#public').setAttribute("download", name + "_rsa.pub"); document.querySelector("a#public").setAttribute("download", name + "_rsa.pub");
var alg = document.querySelector('#alg').value || "RSASSA-PKCS1-v1_5"; var alg = document.querySelector("#alg").value || "RSASSA-PKCS1-v1_5";
var size = parseInt(document.querySelector('#size').value || "2048", 10); var size = parseInt(document.querySelector("#size").value || "2048", 10);
generateKeyPair(alg, size, name).then(function (keys) { generateKeyPair(alg, size, name)
document.querySelector('#private').setAttribute("href", buildHref(keys[0])); .then(function(keys) {
document.querySelector('#public').setAttribute("href", buildHref(keys[1])); document.querySelector("#private").setAttribute("href", buildHref(keys[0]));
document.querySelector('#privateKey').textContent = keys[0]; document.querySelector("#public").setAttribute("href", buildHref(keys[1]));
document.querySelector('#publicKey').textContent = keys[1]; document.querySelector("#privateKey").textContent = keys[0];
document.querySelector('#result').style.display = "block"; document.querySelector("#publicKey").textContent = keys[1];
}).catch(function (err) { document.querySelector("#result").style.display = "block";
})
.catch(function(err) {
console.error(err); console.error(err);
}); });
}); });

View file

@ -4,7 +4,7 @@ body {
} }
button#generate { button#generate {
background-color: rgb(60,200,30); background-color: rgb(60, 200, 30);
padding: 10px; padding: 10px;
margin-top: 5px; margin-top: 5px;
color: white; color: white;
@ -29,7 +29,7 @@ label {
display: inline-block; display: inline-block;
width: 80px; width: 80px;
} }
select{ select {
width: 200px; width: 200px;
} }
input { input {

View file

@ -4,7 +4,9 @@ var extractable = true;
var encodePrivateKey, encodePublicKey; var encodePrivateKey, encodePublicKey;
function wrap(text, len) { function wrap(text, len) {
var length = len || 72, i, result = ""; var length = len || 72,
i,
result = "";
for (i = 0; i < text.length; i += length) { for (i = 0; i < text.length; i += length) {
result += text.slice(i, i + length) + "\n"; result += text.slice(i, i + length) + "\n";
} }
@ -16,7 +18,8 @@ function rsaPrivateKey(key) {
} }
function arrayBufferToBase64(buffer) { function arrayBufferToBase64(buffer) {
var binary = '', i; var binary = "",
i;
var bytes = new Uint8Array(buffer); var bytes = new Uint8Array(buffer);
var len = bytes.byteLength; var len = bytes.byteLength;
for (i = 0; i < len; i += 1) { for (i = 0; i < len; i += 1) {
@ -26,25 +29,25 @@ function arrayBufferToBase64(buffer) {
} }
function generateKeyPair(alg, size, name) { function generateKeyPair(alg, size, name) {
return window.crypto.subtle.generateKey({ return window.crypto.subtle
.generateKey(
{
name: "RSASSA-PKCS1-v1_5", name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048, //can be 1024, 2048, or 4096 modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-1"} //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512" hash: { name: "SHA-1" }, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
}, },
extractable, extractable,
["sign", "verify"] ["sign", "verify"]
).then(function (key) { )
.then(function(key) {
var privateKey = window.crypto.subtle
.exportKey("jwk", key.privateKey)
.then(encodePrivateKey)
.then(wrap)
.then(rsaPrivateKey);
var privateKey = window.crypto.subtle.exportKey( var publicKey = window.crypto.subtle.exportKey("jwk", key.publicKey).then(function(jwk) {
"jwk",
key.privateKey
).then(encodePrivateKey).then(wrap).then(rsaPrivateKey);
var publicKey = window.crypto.subtle.exportKey(
"jwk",
key.publicKey
).then(function (jwk) {
return encodePublicKey(jwk, name); return encodePublicKey(jwk, name);
}); });

View file

@ -2,12 +2,11 @@
"name": "js-keypair", "name": "js-keypair",
"version": "1.0.0", "version": "1.0.0",
"description": "Generate a key pair using the webcrypto API", "description": "Generate a key pair using the webcrypto API",
"main": "keypair.js", "main": "js-keygen.js",
"dependencies": { "dependencies": {},
},
"devDependencies": {}, "devDependencies": {},
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"To run test open test/index.html in your browser\""
}, },
"author": "patrick@roumanoff.com", "author": "patrick@roumanoff.com",
"license": "Apache 2.0" "license": "Apache 2.0"

View file

@ -7,7 +7,7 @@ function arrayToString(a) {
} }
function stringToArray(s) { function stringToArray(s) {
return s.split('').map(function (c) { return s.split("").map(function(c) {
return c.charCodeAt(); return c.charCodeAt();
}); });
} }
@ -21,13 +21,18 @@ function pemToArray(pem) {
} }
function arrayToPem(a) { function arrayToPem(a) {
return window.btoa(a.map(function (c) { return window.btoa(
a
.map(function(c) {
return String.fromCharCode(c); return String.fromCharCode(c);
}).join('')); })
.join("")
);
} }
function arrayToLen(a) { function arrayToLen(a) {
var result = 0, i; var result = 0,
i;
for (i = 0; i < a.length; i += 1) { for (i = 0; i < a.length; i += 1) {
result = result * 256 + a[i]; result = result * 256 + a[i];
} }
@ -37,13 +42,14 @@ function arrayToLen(a) {
function integerToOctet(n) { function integerToOctet(n) {
var result = []; var result = [];
for (true; n > 0; n = n >> 8) { for (true; n > 0; n = n >> 8) {
result.push(n & 0xFF); result.push(n & 0xff);
} }
return result.reverse(); return result.reverse();
} }
function lenToArray(n) { function lenToArray(n) {
var oct = integerToOctet(n), i; var oct = integerToOctet(n),
i;
for (i = oct.length; i < 4; i += 1) { for (i = oct.length; i < 4; i += 1) {
oct.unshift(0); oct.unshift(0);
} }
@ -54,23 +60,24 @@ function decodePublicKey(s) {
var split = s.split(" "); var split = s.split(" ");
var prefix = split[0]; var prefix = split[0];
if (prefix !== "ssh-rsa") { if (prefix !== "ssh-rsa") {
throw ("Unknown prefix:" + prefix); throw "Unknown prefix:" + prefix;
} }
var buffer = pemToArray(split[1]); var buffer = pemToArray(split[1]);
var nameLen = arrayToLen(buffer.splice(0, 4)); var nameLen = arrayToLen(buffer.splice(0, 4));
var type = arrayToString(buffer.splice(0, nameLen)); var type = arrayToString(buffer.splice(0, nameLen));
if (type !== "ssh-rsa") { if (type !== "ssh-rsa") {
throw ("Unknown key type:" + type); throw "Unknown key type:" + type;
} }
var exponentLen = arrayToLen(buffer.splice(0, 4)); var exponentLen = arrayToLen(buffer.splice(0, 4));
var exponent = buffer.splice(0, exponentLen); var exponent = buffer.splice(0, exponentLen);
var keyLen = arrayToLen(buffer.splice(0, 4)); var keyLen = arrayToLen(buffer.splice(0, 4));
var key = buffer.splice(0, keyLen); var key = buffer.splice(0, keyLen);
return {type: type, exponent: exponent, key: key, name: split[2]}; return { type: type, exponent: exponent, key: key, name: split[2] };
} }
function checkHighestBit(v) { function checkHighestBit(v) {
if (v[0] >> 7 === 1) { // add leading zero if first bit is set if (v[0] >> 7 === 1) {
// add leading zero if first bit is set
v.unshift(0); v.unshift(0);
} }
return v; return v;
@ -81,7 +88,7 @@ function jwkToInternal(jwk) {
type: "ssh-rsa", type: "ssh-rsa",
exponent: checkHighestBit(stringToArray(base64urlDecode(jwk.e))), exponent: checkHighestBit(stringToArray(base64urlDecode(jwk.e))),
name: "name", name: "name",
key: checkHighestBit(stringToArray(base64urlDecode(jwk.n))) key: checkHighestBit(stringToArray(base64urlDecode(jwk.n))),
}; };
} }
@ -109,7 +116,7 @@ function asnEncodeLen(n) {
function encodePrivateKey(jwk) { function encodePrivateKey(jwk) {
var order = ["n", "e", "d", "p", "q", "dp", "dq", "qi"]; var order = ["n", "e", "d", "p", "q", "dp", "dq", "qi"];
var list = order.map(function (prop) { var list = order.map(function(prop) {
var v = checkHighestBit(stringToArray(base64urlDecode(jwk[prop]))); var v = checkHighestBit(stringToArray(base64urlDecode(jwk[prop])));
var len = asnEncodeLen(v.length); var len = asnEncodeLen(v.length);
return [0x02].concat(len, v); // int tag is 0x02 return [0x02].concat(len, v); // int tag is 0x02

View file

@ -1,17 +1,22 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>QUnit Example</title> <title>QUnit Example</title>
<link rel="stylesheet" href="qunit/qunit.css"> <link rel="stylesheet" href="qunit/qunit.css">
</head> </head>
<body> <body>
<div id="qunit"></div> <div id="qunit"></div>
<div id="qunit-fixture"><div id="generate"></div></div> <div id="qunit-fixture">
<div id="generate"></div>
</div>
<script src="qunit/qunit.js"></script> <script src="qunit/qunit.js"></script>
<script src="../base64url.js"></script> <script src="../base64url.js"></script>
<script src="../ssh-util.js"></script> <script src="../ssh-util.js"></script>
<script src="../js-keygen.js"></script> <script src="../js-keygen.js"></script>
<script src="tests.js"></script> <script src="tests.js"></script>
</body> </body>
</html> </html>

View file

@ -1,27 +1,304 @@
var public_ssh = ["ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwi36YMW0eDS3NXSAM/Gcs0txeLOcZE0LQmGPYmHX09Fm1FC9AdzvDWQIfwVylqNy8G6X8+pE0TMuWav4rQjtWRls3j43LdrXkfaTZV2PNJH0ki2zaCND3cz46hBR1bSwi3O4LoN0ZHXoC4ZXoMBXKtYEOg+9jS+pE3vu2QSPruiRROTOYYvrjWx0Bwi8DJc90TmNVeqvPjewPAm4qaTdmh96jIgJQq+vAdhDHu90i31Kl3JUF94x6pzFmg8ZyXOv0Py2GtK9c5To3C33FXI8yTm/sf2Bp7fwd3MEGNcdVNqa7Tt0z2u5Jcmsws93SZuj4iVjbR6xqme9EmIa3BTB7 name", var public_ssh = [
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuew50MaphQgiuM6H7zxMspNojI2Ujf77MuWlAjmw1JxcTkfE7JKzV+9fqmESJNtnZSr3+I2dxQhJ72jttrz+2dFt9ol91muTPWzKrA8XXIBH2o7sEJ+QB8/q7S03d+Zgw6tlo+qdXLOWcKqL5MJhYwzTFEdGTSMF00cBFadcpDq1xFPygGTHRa7m3pK723nGz7TMGWmtBK2bHx+Zlp7geLK/7hl+NRG1lTyIbtdkP2T4Y81Z0bhz9kNHroUei3MFD6HvN93qMJWl3/LZZzTb++1BedNeybGKqbtsB3xp0v3c6bQy49wR3RwrAwL03AKbCwTawAufSeoXyRI+rtgZ/ name", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwi36YMW0eDS3NXSAM/Gcs0txeLOcZE0LQmGPYmHX09Fm1FC9AdzvDWQIfwVylqNy8G6X8+pE0TMuWav4rQjtWRls3j43LdrXkfaTZV2PNJH0ki2zaCND3cz46hBR1bSwi3O4LoN0ZHXoC4ZXoMBXKtYEOg+9jS+pE3vu2QSPruiRROTOYYvrjWx0Bwi8DJc90TmNVeqvPjewPAm4qaTdmh96jIgJQq+vAdhDHu90i31Kl3JUF94x6pzFmg8ZyXOv0Py2GtK9c5To3C33FXI8yTm/sf2Bp7fwd3MEGNcdVNqa7Tt0z2u5Jcmsws93SZuj4iVjbR6xqme9EmIa3BTB7 name",
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCpNNtjZeldPuJ5ZgcjO4i6eSJb6kiuf1sULOoWaW9acwSxAfqrmN6Hn7VGg3GK3kSKJUmBKMsF2u+ECajVBec+OTMlbL7oZrYNl2neUYsI7O0G/8lpozZjADYu8CaMqVSAeTa3ORga9Ht/qgCpqXIyEcTsFSbZ45hhaZF0fXQ0GHDCkV/ylBduQHxheCe1SPBSWIO2BwqSlGx/Q76lkL/BnGdcx7xVi3h2yNbEGxqzFuPK75VADZfWria4x09rTqvu41GWIyqzFcbB7BxNImVNh6WVk/qKTcXbfWwH8ck9Cd5bX9g36QaImZ6tW8i/bl3o75bGgP2hSWpsNx8CMVn9 name"]; "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuew50MaphQgiuM6H7zxMspNojI2Ujf77MuWlAjmw1JxcTkfE7JKzV+9fqmESJNtnZSr3+I2dxQhJ72jttrz+2dFt9ol91muTPWzKrA8XXIBH2o7sEJ+QB8/q7S03d+Zgw6tlo+qdXLOWcKqL5MJhYwzTFEdGTSMF00cBFadcpDq1xFPygGTHRa7m3pK723nGz7TMGWmtBK2bHx+Zlp7geLK/7hl+NRG1lTyIbtdkP2T4Y81Z0bhz9kNHroUei3MFD6HvN93qMJWl3/LZZzTb++1BedNeybGKqbtsB3xp0v3c6bQy49wR3RwrAwL03AKbCwTawAufSeoXyRI+rtgZ/ name",
var public_ssh_decoded = [{ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCpNNtjZeldPuJ5ZgcjO4i6eSJb6kiuf1sULOoWaW9acwSxAfqrmN6Hn7VGg3GK3kSKJUmBKMsF2u+ECajVBec+OTMlbL7oZrYNl2neUYsI7O0G/8lpozZjADYu8CaMqVSAeTa3ORga9Ht/qgCpqXIyEcTsFSbZ45hhaZF0fXQ0GHDCkV/ylBduQHxheCe1SPBSWIO2BwqSlGx/Q76lkL/BnGdcx7xVi3h2yNbEGxqzFuPK75VADZfWria4x09rTqvu41GWIyqzFcbB7BxNImVNh6WVk/qKTcXbfWwH8ck9Cd5bX9g36QaImZ6tW8i/bl3o75bGgP2hSWpsNx8CMVn9 name",
];
var public_ssh_decoded = [
{
type: "ssh-rsa", type: "ssh-rsa",
exponent: [1,0,1], exponent: [1, 0, 1],
name: "name", name: "name",
key: [0,176,139,126,152,49,109,30,13,45,205,93,32,12,252,103,44,210,220,94,44,231,25,19,66,208,152,99,216,152,117,244,244,89,181,20,47,64,119,59,195,89,2,31,193,92,165,168,220,188,27,165,252,250,145,52,76,203,150,106,254,43,66,59,86,70,91,55,143,141,203,118,181,228,125,164,217,87,99,205,36,125,36,139,108,218,8,208,247,115,62,58,132,20,117,109,44,34,220,238,11,160,221,25,29,122,2,225,149,232,48,21,202,181,129,14,131,239,99,75,234,68,222,251,182,65,35,235,186,36,81,57,51,152,98,250,227,91,29,1,194,47,3,37,207,116,78,99,85,122,171,207,141,236,15,2,110,42,105,55,102,135,222,163,34,2,80,171,235,192,118,16,199,187,221,34,223,82,165,220,149,5,247,140,122,167,49,102,131,198,114,92,235,244,63,45,134,180,175,92,229,58,55,11,125,197,92,143,50,78,111,236,127,96,105,237,252,29,220,193,6,53,199,85,54,166,187,78,221,51,218,238,73,114,107,48,179,221,210,102,232,248,137,88,219,71,172,106,153,239,68,152,134,183,5,48,123] key: [
}]; 0,
176,
139,
126,
152,
49,
109,
30,
13,
45,
205,
93,
32,
12,
252,
103,
44,
210,
220,
94,
44,
231,
25,
19,
66,
208,
152,
99,
216,
152,
117,
244,
244,
89,
181,
20,
47,
64,
119,
59,
195,
89,
2,
31,
193,
92,
165,
168,
220,
188,
27,
165,
252,
250,
145,
52,
76,
203,
150,
106,
254,
43,
66,
59,
86,
70,
91,
55,
143,
141,
203,
118,
181,
228,
125,
164,
217,
87,
99,
205,
36,
125,
36,
139,
108,
218,
8,
208,
247,
115,
62,
58,
132,
20,
117,
109,
44,
34,
220,
238,
11,
160,
221,
25,
29,
122,
2,
225,
149,
232,
48,
21,
202,
181,
129,
14,
131,
239,
99,
75,
234,
68,
222,
251,
182,
65,
35,
235,
186,
36,
81,
57,
51,
152,
98,
250,
227,
91,
29,
1,
194,
47,
3,
37,
207,
116,
78,
99,
85,
122,
171,
207,
141,
236,
15,
2,
110,
42,
105,
55,
102,
135,
222,
163,
34,
2,
80,
171,
235,
192,
118,
16,
199,
187,
221,
34,
223,
82,
165,
220,
149,
5,
247,
140,
122,
167,
49,
102,
131,
198,
114,
92,
235,
244,
63,
45,
134,
180,
175,
92,
229,
58,
55,
11,
125,
197,
92,
143,
50,
78,
111,
236,
127,
96,
105,
237,
252,
29,
220,
193,
6,
53,
199,
85,
54,
166,
187,
78,
221,
51,
218,
238,
73,
114,
107,
48,
179,
221,
210,
102,
232,
248,
137,
88,
219,
71,
172,
106,
153,
239,
68,
152,
134,
183,
5,
48,
123,
],
},
];
var jwk_public = {"alg":"RS1","e":"AQAB","ext":true,"key_ops":["verify"],"kty":"RSA","n":"3PWJ6uDsFPgQo67of3IYw0Svyq95SNh9GS-2gorv68GxWIYYeAShaG_UtTf8mvf6u-VIUr54Re2FoLc78ICR3nRhFH5D1_fNaP9hkMAHBqaJ8ATiq4d7-PfeXTCi0yY0qfWkGjuPtOC3IK7WmnEkiA5qUVpy0oHFPiqoAyNynWJRDFJka00JEpM1QFyF1Tz3PEGp0XlFnClY48iJG9UqXlDgaysnG3ro2sDm8ftva0IjX1Sp7Z9FyWQci-yOYfST00wKHQd7z5-Eo3cTd5M0BhcVXeR0gprdK1TTDLZLznFJQ36HYwrUFEXvTyme6vkZfNPRb0z8KPq5Gs7dujE_cw"}; var jwk_public = {
alg: "RS1",
e: "AQAB",
ext: true,
key_ops: ["verify"],
kty: "RSA",
n:
"3PWJ6uDsFPgQo67of3IYw0Svyq95SNh9GS-2gorv68GxWIYYeAShaG_UtTf8mvf6u-VIUr54Re2FoLc78ICR3nRhFH5D1_fNaP9hkMAHBqaJ8ATiq4d7-PfeXTCi0yY0qfWkGjuPtOC3IK7WmnEkiA5qUVpy0oHFPiqoAyNynWJRDFJka00JEpM1QFyF1Tz3PEGp0XlFnClY48iJG9UqXlDgaysnG3ro2sDm8ftva0IjX1Sp7Z9FyWQci-yOYfST00wKHQd7z5-Eo3cTd5M0BhcVXeR0gprdK1TTDLZLznFJQ36HYwrUFEXvTyme6vkZfNPRb0z8KPq5Gs7dujE_cw",
};
var jwk_private = { var jwk_private = {
"kty":"RSA", kty: "RSA",
"kid":"juliet@capulet.lit", kid: "juliet@capulet.lit",
"use":"enc", use: "enc",
"n":"t6Q8PWSi1dkJj9hTP8hNYFlvadM7DflW9mWepOJhJ66w7nyoK1gPNqFMSQRyO125Gp-TEkodhWr0iujjHVx7BcV0llS4w5ACGgPrcAd6ZcSR0-Iqom-QFcNP8Sjg086MwoqQU_LYywlAGZ21WSdS_PERyGFiNnj3QQlO8Yns5jCtLCRwLHL0Pb1fEv45AuRIuUfVcPySBWYnDyGxvjYGDSM-AqWS9zIQ2ZilgT-GqUmipg0XOC0Cc20rgLe2ymLHjpHciCKVAbY5-L32-lSeZO-Os6U15_aXrk9Gw8cPUaX1_I8sLGuSiVdt3C_Fn2PZ3Z8i744FPFGGcG1qs2Wz-Q", n:
"e":"AQAB", "t6Q8PWSi1dkJj9hTP8hNYFlvadM7DflW9mWepOJhJ66w7nyoK1gPNqFMSQRyO125Gp-TEkodhWr0iujjHVx7BcV0llS4w5ACGgPrcAd6ZcSR0-Iqom-QFcNP8Sjg086MwoqQU_LYywlAGZ21WSdS_PERyGFiNnj3QQlO8Yns5jCtLCRwLHL0Pb1fEv45AuRIuUfVcPySBWYnDyGxvjYGDSM-AqWS9zIQ2ZilgT-GqUmipg0XOC0Cc20rgLe2ymLHjpHciCKVAbY5-L32-lSeZO-Os6U15_aXrk9Gw8cPUaX1_I8sLGuSiVdt3C_Fn2PZ3Z8i744FPFGGcG1qs2Wz-Q",
"d":"GRtbIQmhOZtyszfgKdg4u_N-R_mZGU_9k7JQ_jn1DnfTuMdSNprTeaSTyWfSNkuaAwnOEbIQVy1IQbWVV25NY3ybc_IhUJtfri7bAXYEReWaCl3hdlPKXy9UvqPYGR0kIXTQRqns-dVJ7jahlI7LyckrpTmrM8dWBo4_PMaenNnPiQgO0xnuToxutRZJfJvG4Ox4ka3GORQd9CsCZ2vsUDmsXOfUENOyMqADC6p1M3h33tsurY15k9qMSpG9OX_IJAXmxzAh_tWiZOwk2K4yxH9tS3Lq1yX8C1EWmeRDkK2ahecG85-oLKQt5VEpWHKmjOi_gJSdSgqcN96X52esAQ", e: "AQAB",
"p":"2rnSOV4hKSN8sS4CgcQHFbs08XboFDqKum3sc4h3GRxrTmQdl1ZK9uw-PIHfQP0FkxXVrx-WE-ZEbrqivH_2iCLUS7wAl6XvARt1KkIaUxPPSYB9yk31s0Q8UK96E3_OrADAYtAJs-M3JxCLfNgqh56HDnETTQhH3rCT5T3yJws", d:
"q":"1u_RiFDP7LBYh3N4GXLT9OpSKYP0uQZyiaZwBtOCBNJgQxaj10RWjsZu0c6Iedis4S7B_coSKB0Kj9PaPaBzg-IySRvvcQuPamQu66riMhjVtG6TlV8CLCYKrYl52ziqK0E_ym2QnkwsUX7eYTB7LbAHRK9GqocDE5B0f808I4s", "GRtbIQmhOZtyszfgKdg4u_N-R_mZGU_9k7JQ_jn1DnfTuMdSNprTeaSTyWfSNkuaAwnOEbIQVy1IQbWVV25NY3ybc_IhUJtfri7bAXYEReWaCl3hdlPKXy9UvqPYGR0kIXTQRqns-dVJ7jahlI7LyckrpTmrM8dWBo4_PMaenNnPiQgO0xnuToxutRZJfJvG4Ox4ka3GORQd9CsCZ2vsUDmsXOfUENOyMqADC6p1M3h33tsurY15k9qMSpG9OX_IJAXmxzAh_tWiZOwk2K4yxH9tS3Lq1yX8C1EWmeRDkK2ahecG85-oLKQt5VEpWHKmjOi_gJSdSgqcN96X52esAQ",
"dp":"KkMTWqBUefVwZ2_Dbj1pPQqyHSHjj90L5x_MOzqYAJMcLMZtbUtwKqvVDq3tbEo3ZIcohbDtt6SbfmWzggabpQxNxuBpoOOf_a_HgMXK_lhqigI4y_kqS1wY52IwjUn5rgRrJ-yYo1h41KR-vz2pYhEAeYrhttWtxVqLCRViD6c", p:
"dq":"AvfS0-gRxvn0bwJoMSnFxYcK1WnuEjQFluMGfwGitQBWtfZ1Er7t1xDkbN9GQTB9yqpDoYaN06H7CFtrkxhJIBQaj6nkF5KKS3TQtQ5qCzkOkmxIe3KRbBymXxkb5qwUpX5ELD5xFc6FeiafWYY63TmmEAu_lRFCOJ3xDea-ots", "2rnSOV4hKSN8sS4CgcQHFbs08XboFDqKum3sc4h3GRxrTmQdl1ZK9uw-PIHfQP0FkxXVrx-WE-ZEbrqivH_2iCLUS7wAl6XvARt1KkIaUxPPSYB9yk31s0Q8UK96E3_OrADAYtAJs-M3JxCLfNgqh56HDnETTQhH3rCT5T3yJws",
"qi":"lSQi-w9CpyUReMErP1RsBLk7wNtOvs5EQpPqmuMvqW57NBUczScEoPwmUqqabu9V0-Py4dQ57_bapoKRu1R90bvuFnU63SHWEFglZQvJDMeAvmj4sm-Fp0oYu_neotgQ0hzbI5gry7ajdYy9-2lNx_76aBZoOUu9HCJ-UsfSOI8" q:
"1u_RiFDP7LBYh3N4GXLT9OpSKYP0uQZyiaZwBtOCBNJgQxaj10RWjsZu0c6Iedis4S7B_coSKB0Kj9PaPaBzg-IySRvvcQuPamQu66riMhjVtG6TlV8CLCYKrYl52ziqK0E_ym2QnkwsUX7eYTB7LbAHRK9GqocDE5B0f808I4s",
dp:
"KkMTWqBUefVwZ2_Dbj1pPQqyHSHjj90L5x_MOzqYAJMcLMZtbUtwKqvVDq3tbEo3ZIcohbDtt6SbfmWzggabpQxNxuBpoOOf_a_HgMXK_lhqigI4y_kqS1wY52IwjUn5rgRrJ-yYo1h41KR-vz2pYhEAeYrhttWtxVqLCRViD6c",
dq:
"AvfS0-gRxvn0bwJoMSnFxYcK1WnuEjQFluMGfwGitQBWtfZ1Er7t1xDkbN9GQTB9yqpDoYaN06H7CFtrkxhJIBQaj6nkF5KKS3TQtQ5qCzkOkmxIe3KRbBymXxkb5qwUpX5ELD5xFc6FeiafWYY63TmmEAu_lRFCOJ3xDea-ots",
qi:
"lSQi-w9CpyUReMErP1RsBLk7wNtOvs5EQpPqmuMvqW57NBUczScEoPwmUqqabu9V0-Py4dQ57_bapoKRu1R90bvuFnU63SHWEFglZQvJDMeAvmj4sm-Fp0oYu_neotgQ0hzbI5gry7ajdYy9-2lNx_76aBZoOUu9HCJ-UsfSOI8",
}; };
var tags = { var tags = {
@ -39,14 +316,16 @@ var tags = {
}; };
QUnit.test("array to PEM", function(assert) { QUnit.test("array to PEM", function(assert) {
var a = [1,2,3]; var a = [1, 2, 3];
var p = arrayToPem(a); var p = arrayToPem(a);
var a2 = pemToArray(p); var a2 = pemToArray(p);
assert.deepEqual(a2, a, "can you count?"); assert.deepEqual(a2, a, "can you count?");
}); });
QUnit.test("array to String", function(assert) { QUnit.test("array to String", function(assert) {
var a = "ssh-rsa".split('').map(function (c) {return c.charCodeAt();}); var a = "ssh-rsa".split("").map(function(c) {
return c.charCodeAt();
});
assert.equal(arrayToString(pemToArray(arrayToPem(a))), "ssh-rsa"); assert.equal(arrayToString(pemToArray(arrayToPem(a))), "ssh-rsa");
}); });
@ -60,62 +339,69 @@ QUnit.test("arrayToLen", function(assert) {
assert.deepEqual(arrayToLen(a), 66051); assert.deepEqual(arrayToLen(a), 66051);
}); });
public_ssh.forEach(function (public, index) { public_ssh.forEach(function(public, index) {
QUnit.test("decoding ssh public key " + index, function(assert) { QUnit.test("decoding ssh public key " + index, function(assert) {
var key = decodePublicKey(public); var key = decodePublicKey(public);
assert.equal(key.type, "ssh-rsa", "type"); assert.equal(key.type, "ssh-rsa", "type");
assert.equal(key.name, "name", "name"); assert.equal(key.name, "name", "name");
if(index===0) { if (index === 0) {
assert.deepEqual(key.key, public_ssh_decoded[0].key, "key"); assert.deepEqual(key.key, public_ssh_decoded[0].key, "key");
} }
}) });
}); });
QUnit.test("Encoding ssh public key", function (assert) { QUnit.test("Encoding ssh public key", function(assert) {
var result = encodePublicKey(jwk_public, "name"); var result = encodePublicKey(jwk_public, "name");
assert.equal(result, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDc9Ynq4OwU+BCjruh/chjDRK/Kr3lI2H0ZL7aCiu/rwbFYhhh4BKFob9S1N/ya9/q75UhSvnhF7YWgtzvwgJHedGEUfkPX981o/2GQwAcGponwBOKrh3v4995dMKLTJjSp9aQaO4+04LcgrtaacSSIDmpRWnLSgcU+KqgDI3KdYlEMUmRrTQkSkzVAXIXVPPc8QanReUWcKVjjyIkb1SpeUOBrKycbeujawObx+29rQiNfVKntn0XJZByL7I5h9JPTTAodB3vPn4SjdxN3kzQGFxVd5HSCmt0rVNMMtkvOcUlDfodjCtQURe9PKZ7q+Rl809FvTPwo+rkazt26MT9z name"); assert.equal(
result,
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDc9Ynq4OwU+BCjruh/chjDRK/Kr3lI2H0ZL7aCiu/rwbFYhhh4BKFob9S1N/ya9/q75UhSvnhF7YWgtzvwgJHedGEUfkPX981o/2GQwAcGponwBOKrh3v4995dMKLTJjSp9aQaO4+04LcgrtaacSSIDmpRWnLSgcU+KqgDI3KdYlEMUmRrTQkSkzVAXIXVPPc8QanReUWcKVjjyIkb1SpeUOBrKycbeujawObx+29rQiNfVKntn0XJZByL7I5h9JPTTAodB3vPn4SjdxN3kzQGFxVd5HSCmt0rVNMMtkvOcUlDfodjCtQURe9PKZ7q+Rl809FvTPwo+rkazt26MT9z name"
);
}); });
QUnit.test("base64url", function (assert){ QUnit.test("base64url", function(assert) {
var result = stringToArray(base64urlDecode(jwk_public.n)); var result = stringToArray(base64urlDecode(jwk_public.n));
assert.equal(result.length, 256); assert.equal(result.length, 256);
}); });
QUnit.test("high bit", function (assert) { QUnit.test("high bit", function(assert) {
assert.deepEqual(checkHighestBit([0x80]), [0x00, 0x80]); assert.deepEqual(checkHighestBit([0x80]), [0x00, 0x80]);
assert.deepEqual(checkHighestBit([0x0F]), [0x0F]); assert.deepEqual(checkHighestBit([0x0f]), [0x0f]);
}); });
QUnit.test("jwk", function (assert){ QUnit.test("jwk", function(assert) {
var sshkey = encodePublicKey(jwk_public, "name"); var sshkey = encodePublicKey(jwk_public, "name");
assert.deepEqual(stringToArray(base64urlDecode(jwk_public.e)), [1,0,1]); assert.deepEqual(stringToArray(base64urlDecode(jwk_public.e)), [1, 0, 1]);
assert.equal(sshkey, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDc9Ynq4OwU+BCjruh/chjDRK/Kr3lI2H0ZL7aCiu/rwbFYhhh4BKFob9S1N/ya9/q75UhSvnhF7YWgtzvwgJHedGEUfkPX981o/2GQwAcGponwBOKrh3v4995dMKLTJjSp9aQaO4+04LcgrtaacSSIDmpRWnLSgcU+KqgDI3KdYlEMUmRrTQkSkzVAXIXVPPc8QanReUWcKVjjyIkb1SpeUOBrKycbeujawObx+29rQiNfVKntn0XJZByL7I5h9JPTTAodB3vPn4SjdxN3kzQGFxVd5HSCmt0rVNMMtkvOcUlDfodjCtQURe9PKZ7q+Rl809FvTPwo+rkazt26MT9z name") assert.equal(
sshkey,
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDc9Ynq4OwU+BCjruh/chjDRK/Kr3lI2H0ZL7aCiu/rwbFYhhh4BKFob9S1N/ya9/q75UhSvnhF7YWgtzvwgJHedGEUfkPX981o/2GQwAcGponwBOKrh3v4995dMKLTJjSp9aQaO4+04LcgrtaacSSIDmpRWnLSgcU+KqgDI3KdYlEMUmRrTQkSkzVAXIXVPPc8QanReUWcKVjjyIkb1SpeUOBrKycbeujawObx+29rQiNfVKntn0XJZByL7I5h9JPTTAodB3vPn4SjdxN3kzQGFxVd5HSCmt0rVNMMtkvOcUlDfodjCtQURe9PKZ7q+Rl809FvTPwo+rkazt26MT9z name"
);
}); });
[{len:0x01, octet:[0x01]}, [
{len:0x104, octet:[0x01, 0x04]}, { len: 0x01, octet: [0x01] },
{len:0xFF32, octet:[0xFF,0x32]}, { len: 0x104, octet: [0x01, 0x04] },
{len:0x1000000, octet:[1,0,0,0]}, { len: 0xff32, octet: [0xff, 0x32] },
{len:0x7FFFFFFF, octet:[0x7F,0xFF,0xFF,0xFF]}, //biggest one { len: 0x1000000, octet: [1, 0, 0, 0] },
].forEach(function (t) { { len: 0x7fffffff, octet: [0x7f, 0xff, 0xff, 0xff] }, //biggest one
QUnit.test("Integer to Octet:" + t.len, function (assert) { ].forEach(function(t) {
QUnit.test("Integer to Octet:" + t.len, function(assert) {
assert.deepEqual(integerToOctet(t.len), t.octet, t.len); assert.deepEqual(integerToOctet(t.len), t.octet, t.len);
}); });
}); });
[{len: 0x34 , asn: [0x34]}, [
{len: 256 , asn: [0x80 + 2, 0x01, 0x00]}, { len: 0x34, asn: [0x34] },
{len: 0x134 , asn: [0x80 + 2, 0x01, 0x34]}, { len: 256, asn: [0x80 + 2, 0x01, 0x00] },
{len: 0x12345 , asn: [0x80 + 3, 0x01, 0x23, 0x45]}, { len: 0x134, asn: [0x80 + 2, 0x01, 0x34] },
{len: 0x123456 , asn: [0x80 + 3, 0x12, 0x34, 0x56]}, { len: 0x12345, asn: [0x80 + 3, 0x01, 0x23, 0x45] },
].forEach(function (t) { { len: 0x123456, asn: [0x80 + 3, 0x12, 0x34, 0x56] },
QUnit.test("ASN.1 Len Writing:" + t.len, function (assert) { ].forEach(function(t) {
QUnit.test("ASN.1 Len Writing:" + t.len, function(assert) {
assert.deepEqual(asnEncodeLen(t.len), t.asn, t.len); assert.deepEqual(asnEncodeLen(t.len), t.asn, t.len);
}); });
}); });
QUnit.test("encodePrivateKey", function (assert) { QUnit.test("encodePrivateKey", function(assert) {
var encoded = encodePrivateKey(jwk_private); var encoded = encodePrivateKey(jwk_private);
console.log(encoded); console.log(encoded);
assert.ok(true); assert.ok(true);