This commit is contained in:
Patrick Roumanoff 2018-02-13 17:41:00 +01:00
parent 4846fadc83
commit 63133c72e8
6 changed files with 115 additions and 142 deletions

View file

@ -1,23 +1,11 @@
{
"extends": ["airbnb-base", "prettier"],
"rules": {
"quotes": ["warn", "double"],
"no-param-reassign": ["error", { "props": false }]
},
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"node": true
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"sourceType": "module"
},
"rules": {
"no-const-assign": "warn",
"no-this-before-super": "warn",
"no-undef": "warn",
"no-unreachable": "warn",
"no-unused-vars": "warn",
"constructor-super": "warn",
"valid-typeof": "warn"
}
}

View file

@ -1,30 +1,31 @@
// adapted from https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-08#appendix-C
function base64urlEncode(arg) {
var s = window.btoa(arg); // Regular base64 encoder
s = s.split("=")[0]; // Remove any trailing '='s
s = s.replace(/\+/g, "-"); // 62nd char of encoding
s = s.replace(/\//g, "_"); // 63rd char of encoding
return s;
const step1 = window.btoa(arg); // Regular base64 encoder
const step2 = step1.split("=")[0]; // Remove any trailing '='s
const step3 = step2.replace(/\+/g, "-"); // 62nd char of encoding
const step4 = step3.replace(/\//g, "_"); // 63rd char of encoding
return step4;
}
function base64urlDecode(s) {
s = s.replace(/-/g, "+"); // 62nd char of encoding
s = s.replace(/_/g, "/"); // 63rd char of encoding
switch (s.length % 4) { // Pad with trailing '='s
const step1 = s.replace(/-/g, "+"); // 62nd char of encoding
const step2 = step1.replace(/_/g, "/"); // 63rd char of encoding
let step3 = step2;
switch (step2.length % 4) { // Pad with trailing '='s
case 0: // No pad chars in this case
break;
case 2: // Two pad chars
s += "==";
step3 += "==";
break;
case 3: // One pad char
s += "=";
step3 += "=";
break;
default:
throw "Illegal base64url string!";
throw new Error("Illegal base64url string!");
}
return window.atob(s); // Regular base64 decoder
return window.atob(step3); // Regular base64 decoder
}
module = window.module || {};
const module = window.module || {};
module.exports = { base64urlDecode, base64urlEncode };

View file

@ -3,6 +3,7 @@
<head>
<title>js-keygen</title>
<scrip>module = {};</scrip>
<script src="base64url.js"></script>
<script src="ssh-util.js"></script>
<script src="js-keygen-ui.js"></script>

View file

@ -1,26 +1,25 @@
var extractable = true;
var encodePrivateKey, encodePublicKey;
/* global encodePrivateKey, encodePublicKey */
const extractable = true;
function wrap(text, len) {
var length = len || 72,
i,
result = "";
for (i = 0; i < text.length; i += length) {
result += text.slice(i, i + length) + "\n";
const length = len || 72;
let result = "";
for (let i = 0; i < text.length; i += length) {
result += text.slice(i, i + length);
result += "\n";
}
return result;
}
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) {
var binary = "",
i;
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (i = 0; i < len; i += 1) {
let binary = "";
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i += 1) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
@ -39,16 +38,15 @@ function generateKeyPair(alg, size, name) {
["sign", "verify"]
)
.then(key => {
var privateKey = window.crypto.subtle
const privateKey = window.crypto.subtle
.exportKey("jwk", key.privateKey)
.then(encodePrivateKey)
.then(wrap)
.then(rsaPrivateKey);
var publicKey = window.crypto.subtle.exportKey("jwk", key.publicKey).then(jwk => encodePublicKey(jwk, name));
const publicKey = window.crypto.subtle.exportKey("jwk", key.publicKey).then(jwk => encodePublicKey(jwk, name));
return Promise.all([privateKey, publicKey]);
});
}
module = window.module || {};
module.exportKey = { arrayBufferToBase64, generateKeyPair };

View file

@ -1,4 +1,5 @@
var base64urlDecode;
/* eslint no-bitwise: 0 */
/* global base64urlDecode */
function arrayToString(a) {
return String.fromCharCode.apply(null, a);
@ -21,25 +22,24 @@ function arrayToPem(a) {
}
function arrayToLen(a) {
var result = 0,
i;
for (i = 0; i < a.length; i += 1) {
let result = 0;
for (let i = 0; i < a.length; i += 1) {
result = result * 256 + a[i];
}
return result;
}
function integerToOctet(n) {
var result = [];
for (true; n > 0; n = n >> 8) {
result.push(n & 0xff);
const result = [];
for (let i = n; i > 0; i >>= 8) {
result.push(i & 0xff);
}
return result.reverse();
}
function lenToArray(n) {
var oct = integerToOctet(n),
i;
const oct = integerToOctet(n);
let i;
for (i = oct.length; i < 4; i += 1) {
oct.unshift(0);
}
@ -47,22 +47,22 @@ function lenToArray(n) {
}
function decodePublicKey(s) {
var split = s.split(" ");
var prefix = split[0];
const split = s.split(" ");
const prefix = split[0];
if (prefix !== "ssh-rsa") {
throw "Unknown prefix:" + prefix;
throw new Error(`Unknown prefix: ${prefix}`);
}
var buffer = pemToArray(split[1]);
var nameLen = arrayToLen(buffer.splice(0, 4));
var type = arrayToString(buffer.splice(0, nameLen));
const buffer = pemToArray(split[1]);
const nameLen = arrayToLen(buffer.splice(0, 4));
const type = arrayToString(buffer.splice(0, nameLen));
if (type !== "ssh-rsa") {
throw "Unknown key type:" + type;
throw new Error(`Unknown key type: ${type}`);
}
var exponentLen = arrayToLen(buffer.splice(0, 4));
var exponent = buffer.splice(0, exponentLen);
var keyLen = arrayToLen(buffer.splice(0, 4));
var key = buffer.splice(0, keyLen);
return { type: type, exponent: exponent, key: key, name: split[2] };
const exponentLen = arrayToLen(buffer.splice(0, 4));
const exponent = buffer.splice(0, exponentLen);
const keyLen = arrayToLen(buffer.splice(0, 4));
const key = buffer.splice(0, keyLen);
return { type, exponent, key, name: split[2] };
}
function checkHighestBit(v) {
@ -83,18 +83,18 @@ function jwkToInternal(jwk) {
}
function encodePublicKey(jwk, name) {
var k = jwkToInternal(jwk);
const k = jwkToInternal(jwk);
k.name = name;
var keyLenA = lenToArray(k.key.length);
var exponentLenA = lenToArray(k.exponent.length);
var typeLenA = lenToArray(k.type.length);
var array = [].concat(typeLenA, stringToArray(k.type), exponentLenA, k.exponent, keyLenA, k.key);
var encoding = arrayToPem(array);
return k.type + " " + encoding + " " + k.name;
const keyLenA = lenToArray(k.key.length);
const exponentLenA = lenToArray(k.exponent.length);
const typeLenA = lenToArray(k.type.length);
const array = [].concat(typeLenA, stringToArray(k.type), exponentLenA, k.exponent, keyLenA, k.key);
const encoding = arrayToPem(array);
return `${k.type} ${encoding} ${k.name}`;
}
function asnEncodeLen(n) {
var result = [];
let result = [];
if (n >> 7) {
result = integerToOctet(n);
result.unshift(0x80 + result.length);
@ -105,18 +105,17 @@ function asnEncodeLen(n) {
}
function encodePrivateKey(jwk) {
var order = ["n", "e", "d", "p", "q", "dp", "dq", "qi"];
var list = order.map(prop => {
var v = checkHighestBit(stringToArray(base64urlDecode(jwk[prop])));
var len = asnEncodeLen(v.length);
const order = ["n", "e", "d", "p", "q", "dp", "dq", "qi"];
const list = order.map(prop => {
const v = checkHighestBit(stringToArray(base64urlDecode(jwk[prop])));
const len = asnEncodeLen(v.length);
return [0x02].concat(len, v); // int tag is 0x02
});
var seq = [0x02, 0x01, 0x00]; // extra seq for SSH
seq = seq.concat.apply(seq, list);
var len = asnEncodeLen(seq.length);
var a = [0x30].concat(len, seq); // seq is 0x30
let seq = [0x02, 0x01, 0x00]; // extra seq for SSH
seq = seq.concat(...list);
const len = asnEncodeLen(seq.length);
const a = [0x30].concat(len, seq); // seq is 0x30
return arrayToPem(a);
}
module = window.module || {};
module.exports = { base64urlToArray, decodePublicKey, encodePublicKey, encodePrivateKey };

View file

@ -1,9 +1,11 @@
var public_ssh = [
/* global QUnit,arrayToString,pemToArray,arrayToPem,encodePrivateKey,lenToArray,arrayToLen,asnEncodeLen,decodePublicKey,decodePublicKey,encodePublicKey,stringToArray,base64urlDecode,checkHighestBit,integerToOctet */
const publicSsh = [
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwi36YMW0eDS3NXSAM/Gcs0txeLOcZE0LQmGPYmHX09Fm1FC9AdzvDWQIfwVylqNy8G6X8+pE0TMuWav4rQjtWRls3j43LdrXkfaTZV2PNJH0ki2zaCND3cz46hBR1bSwi3O4LoN0ZHXoC4ZXoMBXKtYEOg+9jS+pE3vu2QSPruiRROTOYYvrjWx0Bwi8DJc90TmNVeqvPjewPAm4qaTdmh96jIgJQq+vAdhDHu90i31Kl3JUF94x6pzFmg8ZyXOv0Py2GtK9c5To3C33FXI8yTm/sf2Bp7fwd3MEGNcdVNqa7Tt0z2u5Jcmsws93SZuj4iVjbR6xqme9EmIa3BTB7 name",
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuew50MaphQgiuM6H7zxMspNojI2Ujf77MuWlAjmw1JxcTkfE7JKzV+9fqmESJNtnZSr3+I2dxQhJ72jttrz+2dFt9ol91muTPWzKrA8XXIBH2o7sEJ+QB8/q7S03d+Zgw6tlo+qdXLOWcKqL5MJhYwzTFEdGTSMF00cBFadcpDq1xFPygGTHRa7m3pK723nGz7TMGWmtBK2bHx+Zlp7geLK/7hl+NRG1lTyIbtdkP2T4Y81Z0bhz9kNHroUei3MFD6HvN93qMJWl3/LZZzTb++1BedNeybGKqbtsB3xp0v3c6bQy49wR3RwrAwL03AKbCwTawAufSeoXyRI+rtgZ/ name",
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCpNNtjZeldPuJ5ZgcjO4i6eSJb6kiuf1sULOoWaW9acwSxAfqrmN6Hn7VGg3GK3kSKJUmBKMsF2u+ECajVBec+OTMlbL7oZrYNl2neUYsI7O0G/8lpozZjADYu8CaMqVSAeTa3ORga9Ht/qgCpqXIyEcTsFSbZ45hhaZF0fXQ0GHDCkV/ylBduQHxheCe1SPBSWIO2BwqSlGx/Q76lkL/BnGdcx7xVi3h2yNbEGxqzFuPK75VADZfWria4x09rTqvu41GWIyqzFcbB7BxNImVNh6WVk/qKTcXbfWwH8ck9Cd5bX9g36QaImZ6tW8i/bl3o75bGgP2hSWpsNx8CMVn9 name",
];
var public_ssh_decoded = [
const publicSshDecoded = [
{
type: "ssh-rsa",
exponent: [1, 0, 1],
@ -270,7 +272,7 @@ var public_ssh_decoded = [
},
];
var jwk_public = {
const jwkPublic = {
alg: "RS1",
e: "AQAB",
ext: true,
@ -280,7 +282,7 @@ var jwk_public = {
"3PWJ6uDsFPgQo67of3IYw0Svyq95SNh9GS-2gorv68GxWIYYeAShaG_UtTf8mvf6u-VIUr54Re2FoLc78ICR3nRhFH5D1_fNaP9hkMAHBqaJ8ATiq4d7-PfeXTCi0yY0qfWkGjuPtOC3IK7WmnEkiA5qUVpy0oHFPiqoAyNynWJRDFJka00JEpM1QFyF1Tz3PEGp0XlFnClY48iJG9UqXlDgaysnG3ro2sDm8ftva0IjX1Sp7Z9FyWQci-yOYfST00wKHQd7z5-Eo3cTd5M0BhcVXeR0gprdK1TTDLZLznFJQ36HYwrUFEXvTyme6vkZfNPRb0z8KPq5Gs7dujE_cw",
};
var jwk_private = {
const jwkPrivate = {
kty: "RSA",
kid: "juliet@capulet.lit",
use: "enc",
@ -301,76 +303,60 @@ var jwk_private = {
"lSQi-w9CpyUReMErP1RsBLk7wNtOvs5EQpPqmuMvqW57NBUczScEoPwmUqqabu9V0-Py4dQ57_bapoKRu1R90bvuFnU63SHWEFglZQvJDMeAvmj4sm-Fp0oYu_neotgQ0hzbI5gry7ajdYy9-2lNx_76aBZoOUu9HCJ-UsfSOI8",
};
var tags = {
SEQUENCE: 0x30,
INTEGER: 0x02,
BIT_STRING: 0x03,
OCTET_STRING: 0x04,
NULL: 0x05,
OBJECT_IDENTIFIER: 0x06,
SET: 0x11,
PrintableString: 0x13,
T61String: 0x14,
IA5String: 0x16,
UTCTime: 0x17,
};
QUnit.test("array to PEM", function(assert) {
var a = [1, 2, 3];
var p = arrayToPem(a);
var a2 = pemToArray(p);
QUnit.test("array to PEM", assert => {
const a = [1, 2, 3];
const p = arrayToPem(a);
const a2 = pemToArray(p);
assert.deepEqual(a2, a, "can you count?");
});
QUnit.test("array to String", function(assert) {
var a = "ssh-rsa".split("").map(function(c) {
return c.charCodeAt();
});
QUnit.test("array to String", assert => {
const a = "ssh-rsa".split("").map(c => c.charCodeAt());
assert.equal(arrayToString(pemToArray(arrayToPem(a))), "ssh-rsa");
});
QUnit.test("lenToArray", function(assert) {
var a = 66051;
QUnit.test("lenToArray", assert => {
const a = 66051;
assert.deepEqual(lenToArray(a), [0, 1, 2, 3]);
});
QUnit.test("arrayToLen", function(assert) {
var a = [0, 1, 2, 3];
QUnit.test("arrayToLen", assert => {
const a = [0, 1, 2, 3];
assert.deepEqual(arrayToLen(a), 66051);
});
public_ssh.forEach(function(public, index) {
QUnit.test("decoding ssh public key " + index, function(assert) {
var key = decodePublicKey(public);
publicSsh.forEach((pub, index) => {
QUnit.test(`decoding ssh public key ${index}`, assert => {
const key = decodePublicKey(pub);
assert.equal(key.type, "ssh-rsa", "type");
assert.equal(key.name, "name", "name");
if (index === 0) {
assert.deepEqual(key.key, public_ssh_decoded[0].key, "key");
assert.deepEqual(key.key, publicSshDecoded[0].key, "key");
}
});
});
QUnit.test("Encoding ssh public key", function(assert) {
var result = encodePublicKey(jwk_public, "name");
QUnit.test("Encoding ssh public key", assert => {
const result = encodePublicKey(jwkPublic, "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) {
var result = stringToArray(base64urlDecode(jwk_public.n));
QUnit.test("base64url", assert => {
const result = stringToArray(base64urlDecode(jwkPublic.n));
assert.equal(result.length, 256);
});
QUnit.test("high bit", function(assert) {
QUnit.test("high bit", assert => {
assert.deepEqual(checkHighestBit([0x80]), [0x00, 0x80]);
assert.deepEqual(checkHighestBit([0x0f]), [0x0f]);
});
QUnit.test("jwk", function(assert) {
var sshkey = encodePublicKey(jwk_public, "name");
assert.deepEqual(stringToArray(base64urlDecode(jwk_public.e)), [1, 0, 1]);
QUnit.test("jwk", assert => {
const sshkey = encodePublicKey(jwkPublic, "name");
assert.deepEqual(stringToArray(base64urlDecode(jwkPublic.e)), [1, 0, 1]);
assert.equal(
sshkey,
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDc9Ynq4OwU+BCjruh/chjDRK/Kr3lI2H0ZL7aCiu/rwbFYhhh4BKFob9S1N/ya9/q75UhSvnhF7YWgtzvwgJHedGEUfkPX981o/2GQwAcGponwBOKrh3v4995dMKLTJjSp9aQaO4+04LcgrtaacSSIDmpRWnLSgcU+KqgDI3KdYlEMUmRrTQkSkzVAXIXVPPc8QanReUWcKVjjyIkb1SpeUOBrKycbeujawObx+29rQiNfVKntn0XJZByL7I5h9JPTTAodB3vPn4SjdxN3kzQGFxVd5HSCmt0rVNMMtkvOcUlDfodjCtQURe9PKZ7q+Rl809FvTPwo+rkazt26MT9z name"
@ -383,8 +369,8 @@ QUnit.test("jwk", function(assert) {
{ len: 0xff32, octet: [0xff, 0x32] },
{ len: 0x1000000, octet: [1, 0, 0, 0] },
{ len: 0x7fffffff, octet: [0x7f, 0xff, 0xff, 0xff] }, // biggest one
].forEach(function(t) {
QUnit.test("Integer to Octet:" + t.len, function(assert) {
].forEach(t => {
QUnit.test(`Integer to Octet: ${t.len}`, assert => {
assert.deepEqual(integerToOctet(t.len), t.octet, t.len);
});
});
@ -395,14 +381,14 @@ QUnit.test("jwk", function(assert) {
{ len: 0x134, asn: [0x80 + 2, 0x01, 0x34] },
{ len: 0x12345, asn: [0x80 + 3, 0x01, 0x23, 0x45] },
{ len: 0x123456, asn: [0x80 + 3, 0x12, 0x34, 0x56] },
].forEach(function(t) {
QUnit.test("ASN.1 Len Writing:" + t.len, function(assert) {
].forEach(t => {
QUnit.test(`ASN.1 Len Writing: ${t.len}`, assert => {
assert.deepEqual(asnEncodeLen(t.len), t.asn, t.len);
});
});
QUnit.test("encodePrivateKey", function(assert) {
var encoded = encodePrivateKey(jwk_private);
console.log(encoded);
QUnit.test("encodePrivateKey", assert => {
encodePrivateKey(jwkPrivate);
// console.log(encoded);
assert.ok(true);
});