This commit is contained in:
Patrick Roumanoff 2015-09-04 17:13:08 +10:00
parent 6ff1e44602
commit e4a0c7f193
4 changed files with 77 additions and 60 deletions

View file

@ -1,21 +1,28 @@
/*jslint browser: true, sloppy: true */
//adapted from https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-08#appendix-C //adapted from https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-08#appendix-C
function base64urlEncode(s) { 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: break; // No pad chars in this case case 0: // No pad chars in this case
case 2: s += "=="; break; // Two pad chars break;
case 3: s += "="; break; // One pad char case 2: // Two pad chars
default: throw "Illegal base64url string!"; s += "==";
break;
case 3: // One pad char
s += "=";
break;
default:
throw "Illegal base64url string!";
} }
return window.atob(s); // Standard base64 decoder return window.atob(s); // Standard base64 decoder
} }

View file

@ -1,5 +1,8 @@
/*jslint browser: true, sloppy: true, vars: true, indent: 2*/
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();
@ -7,43 +10,43 @@ function copy(id) {
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();
} };
} }
function buildHref(data) { 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"); var size = parseInt(document.querySelector('#size').value || "2048", 10);
generateKeyPair(alg, size, name).then(function (keys) { generateKeyPair(alg, size, name).then(function (keys) {
document.querySelector('#private').setAttribute("href", buildHref(keys[0])); document.querySelector('#private').setAttribute("href", buildHref(keys[0]));
document.querySelector('#public').setAttribute("href", buildHref(keys[1])); document.querySelector('#public').setAttribute("href", buildHref(keys[1]));
document.querySelector('#privateKey').textContent = keys[0]; document.querySelector('#privateKey').textContent = keys[0];
document.querySelector('#publicKey').textContent = keys[1]; document.querySelector('#publicKey').textContent = keys[1];
document.querySelector('#result').style.display = "block"; document.querySelector('#result').style.display = "block";
}).catch(function(err){ }).catch(function (err) {
console.error(err); console.error(err);
}); });
}); });
}); });

View file

@ -1,8 +1,11 @@
/*jslint browser: true, devel: true, sloppy: true, vars: true*/
/*globals Uint8Array, Promise */
var extractable = true; var extractable = true;
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";
} }
return result; return result;
@ -12,39 +15,39 @@ function rsaPrivateKey(key) {
return "-----BEGIN RSA PRIVATE KEY-----\n" + key + "-----END RSA PRIVATE KEY-----"; return "-----BEGIN RSA PRIVATE KEY-----\n" + key + "-----END RSA PRIVATE KEY-----";
} }
function arrayBufferToBase64( buffer ) { function arrayBufferToBase64(buffer) {
var binary = ''; var binary = '', i;
var bytes = new Uint8Array( buffer ); var bytes = new Uint8Array(buffer);
var len = bytes.byteLength; var len = bytes.byteLength;
for (var i = 0; i < len; i++) { for (i = 0; i < len; i += 1) {
binary += String.fromCharCode( bytes[ i ] ); binary += String.fromCharCode(bytes[i]);
} }
return window.btoa( binary ); return window.btoa(binary);
} }
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 private = window.crypto.subtle.exportKey( var privateKey = window.crypto.subtle.exportKey(
"jwk", "jwk",
key.privateKey key.privateKey
).then(encodePrivateKey).then(wrap).then(rsaPrivateKey); ).then(encodePrivateKey).then(wrap).then(rsaPrivateKey);
var public = window.crypto.subtle.exportKey( var publicKey = window.crypto.subtle.exportKey(
"jwk", "jwk",
key.publicKey key.publicKey
).then(function(jwk){ ).then(function (jwk) {
return encodePublicKey(jwk, name); return encodePublicKey(jwk, name);
}); });
return Promise.all([private, public]); return Promise.all([privateKey, publicKey]);
}); });
} }

View file

@ -1,3 +1,7 @@
/*jslint browser: true, devel: true, bitwise: true, sloppy: true, vars: true*/
var base64urlDecode;
function arrayToString(a) { function arrayToString(a) {
return String.fromCharCode.apply(null, a); return String.fromCharCode.apply(null, a);
} }
@ -17,14 +21,14 @@ 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++) { for (i = 0; i < a.length; i += 1) {
result = result * 256 + a[i]; result = result * 256 + a[i];
} }
return result; return result;
@ -32,7 +36,7 @@ function arrayToLen(a) {
function integerToOctet(n) { function integerToOctet(n) {
var result = []; var result = [];
for(;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();
@ -40,7 +44,7 @@ function integerToOctet(n) {
function lenToArray(n) { function lenToArray(n) {
var oct = integerToOctet(n), i; var oct = integerToOctet(n), i;
for(i = oct.length; i < 4; i++) { for (i = oct.length; i < 4; i += 1) {
oct.unshift(0); oct.unshift(0);
} }
return oct; return oct;
@ -49,13 +53,13 @@ function lenToArray(n) {
function decodePublicKey(s) { 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));
@ -65,22 +69,22 @@ function decodePublicKey(s) {
return {type: type, exponent: exponent, key: key, name: split[2]}; return {type: type, exponent: exponent, key: key, name: split[2]};
} }
function checkHighestBit(v) {
if (v[0] >> 7 === 1) { // add leading zero if first bit is set
v.unshift(0);
}
return v;
}
function jwkToInternal(jwk) { function jwkToInternal(jwk) {
return { return {
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)))
}; };
} }
function checkHighestBit(v) {
if(v[0] >> 7 === 1) { // add leading zero if first bit is set
v.unshift(0);
}
return v;
}
function encodePublicKey(jwk, name) { function encodePublicKey(jwk, name) {
var k = jwkToInternal(jwk); var k = jwkToInternal(jwk);
k.name = name; k.name = name;
@ -94,7 +98,7 @@ function encodePublicKey(jwk, name) {
function asnEncodeLen(n) { function asnEncodeLen(n) {
var result = []; var result = [];
if(n >> 7) { if (n >> 7) {
result = integerToOctet(n); result = integerToOctet(n);
result.unshift(0x80 + result.length); result.unshift(0x80 + result.length);
} else { } else {
@ -113,6 +117,6 @@ function encodePrivateKey(jwk) {
var seq = [0x02, 0x01, 0x00]; // extra seq for SSH var seq = [0x02, 0x01, 0x00]; // extra seq for SSH
seq = seq.concat.apply(seq, list); seq = seq.concat.apply(seq, list);
var len = asnEncodeLen(seq.length); var len = asnEncodeLen(seq.length);
var a = [0x30].concat(len , seq); // seq is 0x30 var a = [0x30].concat(len, seq); // seq is 0x30
return arrayToPem(a); return arrayToPem(a);
} }