AJ ONeal
5 years ago
42 changed files with 2521 additions and 17 deletions
@ -0,0 +1,81 @@ |
|||||
|
#!/usr/bin/env node
|
||||
|
'use strict'; |
||||
|
|
||||
|
var fs = require('fs'); |
||||
|
var Eckles = require('../ecdsa'); |
||||
|
|
||||
|
var infile = process.argv[2]; |
||||
|
var format = process.argv[3]; |
||||
|
|
||||
|
if (!infile) { |
||||
|
infile = 'jwk'; |
||||
|
} |
||||
|
|
||||
|
if ( |
||||
|
-1 !== |
||||
|
['jwk', 'pem', 'json', 'der', 'sec1', 'pkcs8', 'spki', 'ssh'].indexOf( |
||||
|
infile |
||||
|
) |
||||
|
) { |
||||
|
console.log('Generating new key...'); |
||||
|
Eckles.generate({ |
||||
|
format: infile, |
||||
|
namedCurve: format === 'P-384' ? 'P-384' : 'P-256', |
||||
|
encoding: format === 'der' ? 'der' : 'pem' |
||||
|
}) |
||||
|
.then(function(key) { |
||||
|
if ('der' === infile || 'der' === format) { |
||||
|
key.private = key.private.toString('binary'); |
||||
|
key.public = key.public.toString('binary'); |
||||
|
} |
||||
|
console.log(key.private); |
||||
|
console.log(key.public); |
||||
|
}) |
||||
|
.catch(function(err) { |
||||
|
console.error(err); |
||||
|
process.exit(1); |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var key = fs.readFileSync(infile, 'ascii'); |
||||
|
|
||||
|
try { |
||||
|
key = JSON.parse(key); |
||||
|
} catch (e) { |
||||
|
// ignore
|
||||
|
} |
||||
|
|
||||
|
var thumbprint = 'thumbprint' === format; |
||||
|
if (thumbprint) { |
||||
|
format = 'public'; |
||||
|
} |
||||
|
|
||||
|
if ('string' === typeof key) { |
||||
|
if (thumbprint) { |
||||
|
Eckles.thumbprint({ pem: key }).then(console.log); |
||||
|
return; |
||||
|
} |
||||
|
var pub = -1 !== ['public', 'spki', 'pkix'].indexOf(format); |
||||
|
Eckles.import({ pem: key, public: pub || format }) |
||||
|
.then(function(jwk) { |
||||
|
console.log(JSON.stringify(jwk, null, 2)); |
||||
|
}) |
||||
|
.catch(function(err) { |
||||
|
console.error(err); |
||||
|
process.exit(1); |
||||
|
}); |
||||
|
} else { |
||||
|
if (thumbprint) { |
||||
|
Eckles.thumbprint({ jwk: key }).then(console.log); |
||||
|
return; |
||||
|
} |
||||
|
Eckles.export({ jwk: key, format: format }) |
||||
|
.then(function(pem) { |
||||
|
console.log(pem); |
||||
|
}) |
||||
|
.catch(function(err) { |
||||
|
console.error(err); |
||||
|
process.exit(2); |
||||
|
}); |
||||
|
} |
@ -0,0 +1,126 @@ |
|||||
|
#!/usr/bin/env node
|
||||
|
'use strict'; |
||||
|
|
||||
|
var fs = require('fs'); |
||||
|
var Rasha = require('../rsa'); |
||||
|
var PEM = require('@root/pem'); |
||||
|
var ASN1 = require('@root/asn1'); |
||||
|
|
||||
|
var infile = process.argv[2]; |
||||
|
var format = process.argv[3]; |
||||
|
var msg = process.argv[4]; |
||||
|
var sign; |
||||
|
if ('sign' === format) { |
||||
|
sign = true; |
||||
|
format = 'pkcs8'; |
||||
|
} |
||||
|
|
||||
|
if (!infile) { |
||||
|
infile = 'jwk'; |
||||
|
} |
||||
|
|
||||
|
if ( |
||||
|
-1 !== |
||||
|
['jwk', 'pem', 'json', 'der', 'pkcs1', 'pkcs8', 'spki'].indexOf(infile) |
||||
|
) { |
||||
|
console.info('Generating new key...'); |
||||
|
Rasha.generate({ |
||||
|
format: infile, |
||||
|
modulusLength: parseInt(format, 10) || 2048, |
||||
|
encoding: parseInt(format, 10) ? null : format |
||||
|
}) |
||||
|
.then(function(key) { |
||||
|
if ('der' === infile || 'der' === format) { |
||||
|
key.private = key.private.toString('binary'); |
||||
|
key.public = key.public.toString('binary'); |
||||
|
} |
||||
|
console.info(key.private); |
||||
|
console.info(key.public); |
||||
|
}) |
||||
|
.catch(function(err) { |
||||
|
console.error(err); |
||||
|
process.exit(1); |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
var key = fs.readFileSync(infile, 'ascii'); |
||||
|
|
||||
|
try { |
||||
|
key = JSON.parse(key); |
||||
|
} catch (e) { |
||||
|
// ignore
|
||||
|
} |
||||
|
|
||||
|
var thumbprint = 'thumbprint' === format; |
||||
|
if (thumbprint) { |
||||
|
format = 'public'; |
||||
|
} |
||||
|
|
||||
|
if ('string' === typeof key) { |
||||
|
if (thumbprint) { |
||||
|
Rasha.thumbprint({ pem: key }).then(console.info); |
||||
|
return; |
||||
|
} |
||||
|
if ('tpl' === format) { |
||||
|
var block = PEM.parseBlock(key); |
||||
|
var asn1 = ASN1.parse(block.der); |
||||
|
ASN1.tpl(asn1); |
||||
|
return; |
||||
|
} |
||||
|
if (sign) { |
||||
|
signMessage(key, msg); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var pub = -1 !== ['public', 'spki', 'pkix'].indexOf(format); |
||||
|
Rasha.import({ pem: key, public: pub || format }) |
||||
|
.then(function(jwk) { |
||||
|
console.info(JSON.stringify(jwk, null, 2)); |
||||
|
}) |
||||
|
.catch(function(err) { |
||||
|
console.error(err); |
||||
|
process.exit(1); |
||||
|
}); |
||||
|
} else { |
||||
|
if (thumbprint) { |
||||
|
Rasha.thumbprint({ jwk: key }).then(console.info); |
||||
|
return; |
||||
|
} |
||||
|
Rasha.export({ jwk: key, format: format }) |
||||
|
.then(function(pem) { |
||||
|
if (sign) { |
||||
|
signMessage(pem, msg); |
||||
|
return; |
||||
|
} |
||||
|
console.info(pem); |
||||
|
}) |
||||
|
.catch(function(err) { |
||||
|
console.error(err); |
||||
|
process.exit(2); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function signMessage(pem, name) { |
||||
|
var msg; |
||||
|
try { |
||||
|
msg = fs.readFileSync(name); |
||||
|
} catch (e) { |
||||
|
console.warn( |
||||
|
'[info] input string did not exist as a file, signing the string itself' |
||||
|
); |
||||
|
msg = Buffer.from(name, 'binary'); |
||||
|
} |
||||
|
var crypto = require('crypto'); |
||||
|
var sign = crypto.createSign('SHA256'); |
||||
|
sign.write(msg); |
||||
|
sign.end(); |
||||
|
var buf = sign.sign(pem); |
||||
|
console.info(buf.toString('base64')); |
||||
|
/* |
||||
|
Rasha.sign({ pem: pem, message: msg, alg: 'SHA256' }).then(function (sig) { |
||||
|
}).catch(function () { |
||||
|
console.error(err); |
||||
|
process.exit(3); |
||||
|
}); |
||||
|
*/ |
||||
|
} |
@ -0,0 +1,240 @@ |
|||||
|
/*global Promise*/ |
||||
|
'use strict'; |
||||
|
|
||||
|
var Enc = require('@root/encoding'); |
||||
|
|
||||
|
var EC = module.exports; |
||||
|
var native = require('./lib/node/ecdsa.js'); |
||||
|
|
||||
|
// TODO SSH
|
||||
|
var SSH; |
||||
|
|
||||
|
var X509 = require('@root/x509'); |
||||
|
var PEM = require('@root/pem'); |
||||
|
//var SSH = require('./ssh-keys.js');
|
||||
|
var sha2 = require('./lib/node/sha2.js'); |
||||
|
|
||||
|
// 1.2.840.10045.3.1.7
|
||||
|
// prime256v1 (ANSI X9.62 named elliptic curve)
|
||||
|
var OBJ_ID_EC = '06 08 2A8648CE3D030107'.replace(/\s+/g, '').toLowerCase(); |
||||
|
// 1.3.132.0.34
|
||||
|
// secp384r1 (SECG (Certicom) named elliptic curve)
|
||||
|
var OBJ_ID_EC_384 = '06 05 2B81040022'.replace(/\s+/g, '').toLowerCase(); |
||||
|
|
||||
|
EC._stance = |
||||
|
"We take the stance that if you're knowledgeable enough to" + |
||||
|
" properly and securely use non-standard crypto then you shouldn't need Bluecrypt anyway."; |
||||
|
native._stance = EC._stance; |
||||
|
EC._universal = |
||||
|
'Bluecrypt only supports crypto with standard cross-browser and cross-platform support.'; |
||||
|
EC.generate = native.generate; |
||||
|
|
||||
|
EC.export = function(opts) { |
||||
|
return Promise.resolve().then(function() { |
||||
|
if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) { |
||||
|
throw new Error('must pass { jwk: jwk } as a JSON object'); |
||||
|
} |
||||
|
var jwk = JSON.parse(JSON.stringify(opts.jwk)); |
||||
|
var format = opts.format; |
||||
|
if ( |
||||
|
opts.public || |
||||
|
-1 !== ['spki', 'pkix', 'ssh', 'rfc4716'].indexOf(format) |
||||
|
) { |
||||
|
jwk.d = null; |
||||
|
} |
||||
|
if ('EC' !== jwk.kty) { |
||||
|
throw new Error("options.jwk.kty must be 'EC' for EC keys"); |
||||
|
} |
||||
|
if (!jwk.d) { |
||||
|
if (!format || -1 !== ['spki', 'pkix'].indexOf(format)) { |
||||
|
format = 'spki'; |
||||
|
} else if (-1 !== ['ssh', 'rfc4716'].indexOf(format)) { |
||||
|
format = 'ssh'; |
||||
|
} else { |
||||
|
throw new Error( |
||||
|
"options.format must be 'spki' or 'ssh' for public EC keys, not (" + |
||||
|
typeof format + |
||||
|
') ' + |
||||
|
format |
||||
|
); |
||||
|
} |
||||
|
} else { |
||||
|
if (!format || 'sec1' === format) { |
||||
|
format = 'sec1'; |
||||
|
} else if ('pkcs8' !== format) { |
||||
|
throw new Error( |
||||
|
"options.format must be 'sec1' or 'pkcs8' for private EC keys, not '" + |
||||
|
format + |
||||
|
"'" |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
if (-1 === ['P-256', 'P-384'].indexOf(jwk.crv)) { |
||||
|
throw new Error( |
||||
|
"options.jwk.crv must be either P-256 or P-384 for EC keys, not '" + |
||||
|
jwk.crv + |
||||
|
"'" |
||||
|
); |
||||
|
} |
||||
|
if (!jwk.y) { |
||||
|
throw new Error( |
||||
|
'options.jwk.y must be a urlsafe base64-encoded either P-256 or P-384' |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
if ('sec1' === format) { |
||||
|
return PEM.packBlock({ |
||||
|
type: 'EC PRIVATE KEY', |
||||
|
bytes: X509.packSec1(jwk) |
||||
|
}); |
||||
|
} else if ('pkcs8' === format) { |
||||
|
return PEM.packBlock({ |
||||
|
type: 'PRIVATE KEY', |
||||
|
bytes: X509.packPkcs8(jwk) |
||||
|
}); |
||||
|
} else if (-1 !== ['spki', 'pkix'].indexOf(format)) { |
||||
|
return PEM.packBlock({ |
||||
|
type: 'PUBLIC KEY', |
||||
|
bytes: X509.packSpki(jwk) |
||||
|
}); |
||||
|
} else if (-1 !== ['ssh', 'rfc4716'].indexOf(format)) { |
||||
|
return SSH.packSsh(jwk); |
||||
|
} else { |
||||
|
throw new Error( |
||||
|
'Sanity Error: reached unreachable code block with format: ' + |
||||
|
format |
||||
|
); |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
native.export = EC.export; |
||||
|
|
||||
|
EC.import = function(opts) { |
||||
|
return Promise.resolve().then(function() { |
||||
|
if (!opts || !opts.pem || 'string' !== typeof opts.pem) { |
||||
|
throw new Error('must pass { pem: pem } as a string'); |
||||
|
} |
||||
|
if (0 === opts.pem.indexOf('ecdsa-sha2-')) { |
||||
|
//return SSH.parseSsh(opts.pem);
|
||||
|
throw new Error('SSH not yet re-supported'); |
||||
|
} |
||||
|
var pem = opts.pem; |
||||
|
var u8 = PEM.parseBlock(pem).bytes; |
||||
|
var hex = Enc.bufToHex(u8); |
||||
|
var jwk = { kty: 'EC', crv: null, x: null, y: null }; |
||||
|
|
||||
|
//console.log();
|
||||
|
if ( |
||||
|
-1 !== hex.indexOf(OBJ_ID_EC) || |
||||
|
-1 !== hex.indexOf(OBJ_ID_EC_384) |
||||
|
) { |
||||
|
if (-1 !== hex.indexOf(OBJ_ID_EC_384)) { |
||||
|
jwk.crv = 'P-384'; |
||||
|
} else { |
||||
|
jwk.crv = 'P-256'; |
||||
|
} |
||||
|
|
||||
|
// PKCS8
|
||||
|
if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) { |
||||
|
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
|
||||
|
jwk = X509.parsePkcs8(u8, jwk); |
||||
|
// EC-only
|
||||
|
} else if (0x02 === u8[2] && 0x04 === u8[5] && 0xa0 === u8[39]) { |
||||
|
//console.log("EC---", u8[2].toString(16), u8[5].toString(16), u8[39].toString(16));
|
||||
|
jwk = X509.parseSec1(u8, jwk); |
||||
|
// EC-only
|
||||
|
} else if (0x02 === u8[3] && 0x04 === u8[6] && 0xa0 === u8[56]) { |
||||
|
//console.log("EC---", u8[3].toString(16), u8[6].toString(16), u8[56].toString(16));
|
||||
|
jwk = X509.parseSec1(u8, jwk); |
||||
|
// SPKI/PKIK (Public)
|
||||
|
} else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) { |
||||
|
//console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
|
||||
|
jwk = X509.parseSpki(u8, jwk); |
||||
|
// Error
|
||||
|
} else { |
||||
|
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
|
||||
|
//console.log("EC---", u8[2].toString(16), u8[5].toString(16), u8[39].toString(16));
|
||||
|
//console.log("EC---", u8[3].toString(16), u8[6].toString(16), u8[56].toString(16));
|
||||
|
//console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
|
||||
|
throw new Error('unrecognized key format'); |
||||
|
} |
||||
|
} else { |
||||
|
throw new Error('Supported key types are P-256 and P-384'); |
||||
|
} |
||||
|
if (opts.public) { |
||||
|
if (true !== opts.public) { |
||||
|
throw new Error( |
||||
|
'options.public must be either `true` or `false` not (' + |
||||
|
typeof opts.public + |
||||
|
") '" + |
||||
|
opts.public + |
||||
|
"'" |
||||
|
); |
||||
|
} |
||||
|
delete jwk.d; |
||||
|
} |
||||
|
return jwk; |
||||
|
}); |
||||
|
}; |
||||
|
native.import = EC.import; |
||||
|
|
||||
|
EC.pack = function(opts) { |
||||
|
return Promise.resolve().then(function() { |
||||
|
return EC.export(opts); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
// Chopping off the private parts is now part of the public API.
|
||||
|
// I thought it sounded a little too crude at first, but it really is the best name in every possible way.
|
||||
|
EC.neuter = function(opts) { |
||||
|
// trying to find the best balance of an immutable copy with custom attributes
|
||||
|
var jwk = {}; |
||||
|
Object.keys(opts.jwk).forEach(function(k) { |
||||
|
if ('undefined' === typeof opts.jwk[k]) { |
||||
|
return; |
||||
|
} |
||||
|
// ignore EC private parts
|
||||
|
if ('d' === k) { |
||||
|
return; |
||||
|
} |
||||
|
jwk[k] = JSON.parse(JSON.stringify(opts.jwk[k])); |
||||
|
}); |
||||
|
return jwk; |
||||
|
}; |
||||
|
native.neuter = EC.neuter; |
||||
|
|
||||
|
// https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
|
||||
|
EC.__thumbprint = function(jwk) { |
||||
|
// Use the same entropy for SHA as for key
|
||||
|
var alg = 'SHA-256'; |
||||
|
if (/384/.test(jwk.crv)) { |
||||
|
alg = 'SHA-384'; |
||||
|
} |
||||
|
var payload = |
||||
|
'{"crv":"' + |
||||
|
jwk.crv + |
||||
|
'","kty":"EC","x":"' + |
||||
|
jwk.x + |
||||
|
'","y":"' + |
||||
|
jwk.y + |
||||
|
'"}'; |
||||
|
return sha2.sum(alg, payload).then(function(hash) { |
||||
|
return Enc.bufToUrlBase64(Uint8Array.from(hash)); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
EC.thumbprint = function(opts) { |
||||
|
return Promise.resolve().then(function() { |
||||
|
var jwk; |
||||
|
if ('EC' === opts.kty) { |
||||
|
jwk = opts; |
||||
|
} else if (opts.jwk) { |
||||
|
jwk = opts.jwk; |
||||
|
} else { |
||||
|
return native.import(opts).then(function(jwk) { |
||||
|
return EC.__thumbprint(jwk); |
||||
|
}); |
||||
|
} |
||||
|
return EC.__thumbprint(jwk); |
||||
|
}); |
||||
|
}; |
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"kty": "EC", |
||||
|
"crv": "P-256", |
||||
|
"d": "iYydo27aNGO9DBUWeGEPD8oNi1LZDqfxPmQlieLBjVQ", |
||||
|
"x": "IT1SWLxsacPiE5Z16jkopAn8_-85rMjgyCokrnjDft4", |
||||
|
"y": "mP2JwOAOdMmXuwpxbKng3KZz27mz-nKWIlXJ3rzSGMo" |
||||
|
} |
@ -0,0 +1,5 @@ |
|||||
|
-----BEGIN PRIVATE KEY----- |
||||
|
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgiYydo27aNGO9DBUW |
||||
|
eGEPD8oNi1LZDqfxPmQlieLBjVShRANCAAQhPVJYvGxpw+ITlnXqOSikCfz/7zms |
||||
|
yODIKiSueMN+3pj9icDgDnTJl7sKcWyp4Nymc9u5s/pyliJVyd680hjK |
||||
|
-----END PRIVATE KEY----- |
@ -0,0 +1,5 @@ |
|||||
|
-----BEGIN EC PRIVATE KEY----- |
||||
|
MHcCAQEEIImMnaNu2jRjvQwVFnhhDw/KDYtS2Q6n8T5kJYniwY1UoAoGCCqGSM49 |
||||
|
AwEHoUQDQgAEIT1SWLxsacPiE5Z16jkopAn8/+85rMjgyCokrnjDft6Y/YnA4A50 |
||||
|
yZe7CnFsqeDcpnPbubP6cpYiVcnevNIYyg== |
||||
|
-----END EC PRIVATE KEY----- |
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"kty": "EC", |
||||
|
"crv": "P-384", |
||||
|
"d": "XlyuCEWSTTS8U79O_Mz05z18vh4kb10szvu_7pdXuGWV6lfEyPExyUYWsA6A2kdV", |
||||
|
"x": "2zEU0bKCa7ejKLIJ8oPGnLhqhxyiv4_w38K2a0SPC6dsSd9_glNJ8lcqv0sff5Gb", |
||||
|
"y": "VD4jnu83S6scn6_TeAj3EZOREGbOs6dzoVpaugn-XQMMyC9O4VLbDDFGBZTJlMsb" |
||||
|
} |
@ -0,0 +1,6 @@ |
|||||
|
-----BEGIN PRIVATE KEY----- |
||||
|
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBeXK4IRZJNNLxTv078 |
||||
|
zPTnPXy+HiRvXSzO+7/ul1e4ZZXqV8TI8THJRhawDoDaR1WhZANiAATbMRTRsoJr |
||||
|
t6Mosgnyg8acuGqHHKK/j/DfwrZrRI8Lp2xJ33+CU0nyVyq/Sx9/kZtUPiOe7zdL |
||||
|
qxyfr9N4CPcRk5EQZs6zp3OhWlq6Cf5dAwzIL07hUtsMMUYFlMmUyxs= |
||||
|
-----END PRIVATE KEY----- |
@ -0,0 +1,6 @@ |
|||||
|
-----BEGIN EC PRIVATE KEY----- |
||||
|
MIGkAgEBBDBeXK4IRZJNNLxTv078zPTnPXy+HiRvXSzO+7/ul1e4ZZXqV8TI8THJ |
||||
|
RhawDoDaR1WgBwYFK4EEACKhZANiAATbMRTRsoJrt6Mosgnyg8acuGqHHKK/j/Df |
||||
|
wrZrRI8Lp2xJ33+CU0nyVyq/Sx9/kZtUPiOe7zdLqxyfr9N4CPcRk5EQZs6zp3Oh |
||||
|
Wlq6Cf5dAwzIL07hUtsMMUYFlMmUyxs= |
||||
|
-----END EC PRIVATE KEY----- |
@ -0,0 +1,11 @@ |
|||||
|
{ |
||||
|
"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", |
||||
|
"dp": "tzDGjECFOU0ehqtuqhcuT63a7h8hj19-7MJqoFwY9HQ-ALkfXyYLXeBSGxHbyiIYuodZg6LsfMNgUJ3r3Eyhc_nAVfYPEC_2IdAG4WYmq7iXYF9LQV09qEsKbFykm7QekE3hO7wswo5k-q2tp3ieBYdVGAXJoGOdv5VpaZ7B1QE", |
||||
|
"dq": "kh5dyDk7YCz7sUFbpsmuAeuPjoH2ghooh2u3xN7iUVmAg-ToKjwbVnG5-7eXiC779rQVwnrD_0yh1AFJ8wjRPqDIR7ObXGHikIxT1VSQWqiJm6AfZzDsL0LUD4YS3iPdhob7-NxLKWzqao_u4lhnDQaX9PKa12HFlny6K1daL48", |
||||
|
"qi": "AlHWbx1gp6Z9pbw_1hlS7HuXAgWoX7IjbTUelldf4gkriDWLOrj3QCZcO4ZvZvEwJhVlsny9LO8IkbwGJEL6cXraK08ByVS2mwQyflgTgGNnpzixyEUL_mrQLx6y145FHcxfeqNInMhep-0Mxn1D5nlhmIOgRApS0t9VoXtHhFU" |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
-----BEGIN RSA PRIVATE KEY----- |
||||
|
MIIEpAIBAAKCAQEAm2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhD |
||||
|
NzUJefLukC+xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ |
||||
|
38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57 |
||||
|
qAZM6+I70on0/iDZm7+jcqOPgADAmbWHhy67BXkk4yy/YzD4yOGZFXZcNp915/TW |
||||
|
5bRd//AKPHUHxJasPiyEFqlNKBR2DSD+LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By |
||||
|
5AB0Qi49OykUCfNZeQlEz7UNNj9RGps/50+CNwIDAQABAoIBAAqX6OzJvTbvGDAv |
||||
|
8a2eeFvZihz5Ahva2fdw7PTzafkV1IG/pY02xT2VrGMRjSi5ZRc/CzaLK9aU9mQz |
||||
|
K8DZNAH9Yljbw+oJMl2okHQsogz0gbCcjb9iyWaJ/nvOV++J7+d1JSJJqPUmMXS8 |
||||
|
9iWifoiFhHvEIfu5ZS5La0yVf8a/fP7W4YEqac/yHjspqt0hGWkBzPKU1O8O17wo |
||||
|
9qW/a/HzdDNF3IFmSGn9yxhH5IRf1rV+u1ydAuvi25E+dY7ZS0TRl+oWhnbHF0tD |
||||
|
lloyjicyhrlfjW7YRJ+a5kbVZ40Wd8msUNLnfLwvjTjZ+8rxlJTrq9SHQ8yf2Hxz |
||||
|
7fbBzEECgYEAynG+t9HwKCN3MWRYFdnFzi9+02Qcy3p8B5pu3ary2E70hYn2pHlU |
||||
|
G2a9BNE8c5xHQ3Hx43WoWf6s0zOunPV1G28LkU/UYEbAtPv/PxSmzpQp9n9XnYvB |
||||
|
LBF8Y3z7gxgLn1vVFNARrQdRtj87qY3aw7E9S4DsGcAarIuOT2TsTCECgYEAxIkA |
||||
|
jgUzB1zaUzJtW2Zgvp9cYYr1DmpH30ePZl3c/8397/DZDDo46fnFYjs6uPa03Hpm |
||||
|
KUnbjwr14QHlfXlntJBEuXxcqLjkdKdJ4ob7xueLTK4suo9V8LSrkLChVxlZQwnF |
||||
|
D2E5ll0sVeeDeMJHQw38ahSrBFEVnxjpnPh1Q1cCgYEAtzDGjECFOU0ehqtuqhcu |
||||
|
T63a7h8hj19+7MJqoFwY9HQ+ALkfXyYLXeBSGxHbyiIYuodZg6LsfMNgUJ3r3Eyh |
||||
|
c/nAVfYPEC/2IdAG4WYmq7iXYF9LQV09qEsKbFykm7QekE3hO7wswo5k+q2tp3ie |
||||
|
BYdVGAXJoGOdv5VpaZ7B1QECgYEAkh5dyDk7YCz7sUFbpsmuAeuPjoH2ghooh2u3 |
||||
|
xN7iUVmAg+ToKjwbVnG5+7eXiC779rQVwnrD/0yh1AFJ8wjRPqDIR7ObXGHikIxT |
||||
|
1VSQWqiJm6AfZzDsL0LUD4YS3iPdhob7+NxLKWzqao/u4lhnDQaX9PKa12HFlny6 |
||||
|
K1daL48CgYACUdZvHWCnpn2lvD/WGVLse5cCBahfsiNtNR6WV1/iCSuINYs6uPdA |
||||
|
Jlw7hm9m8TAmFWWyfL0s7wiRvAYkQvpxetorTwHJVLabBDJ+WBOAY2enOLHIRQv+ |
||||
|
atAvHrLXjkUdzF96o0icyF6n7QzGfUPmeWGYg6BEClLS31Whe0eEVQ== |
||||
|
-----END RSA PRIVATE KEY----- |
@ -0,0 +1,28 @@ |
|||||
|
-----BEGIN PRIVATE KEY----- |
||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCba21UHE+VbDTp |
||||
|
mYYFZUOV+OQ8AngOCdjROsPC0KiEfMvEaEM3NQl58u6QL7G7QsErKViiNPm9OTFo |
||||
|
6HF5JijfWzK7haHFuRMEsgI4VwIYyhvqlJDfw/wt0AiVvSmoMfEQn1p1aiaO4V/R |
||||
|
JSE3Vw/uz2bxiT22uSkSqOyShyfYE6dMHnuoBkzr4jvSifT+INmbv6Nyo4+AAMCZ |
||||
|
tYeHLrsFeSTjLL9jMPjI4ZkVdlw2n3Xn9NbltF3/8Ao8dQfElqw+LIQWqU0oFHYN |
||||
|
IP4ttfl5ObMKHaKSvBMyNruZR0El/ZsrcHLkAHRCLj07KRQJ81l5CUTPtQ02P1Ea |
||||
|
mz/nT4I3AgMBAAECggEACpfo7Mm9Nu8YMC/xrZ54W9mKHPkCG9rZ93Ds9PNp+RXU |
||||
|
gb+ljTbFPZWsYxGNKLllFz8LNosr1pT2ZDMrwNk0Af1iWNvD6gkyXaiQdCyiDPSB |
||||
|
sJyNv2LJZon+e85X74nv53UlIkmo9SYxdLz2JaJ+iIWEe8Qh+7llLktrTJV/xr98 |
||||
|
/tbhgSppz/IeOymq3SEZaQHM8pTU7w7XvCj2pb9r8fN0M0XcgWZIaf3LGEfkhF/W |
||||
|
tX67XJ0C6+LbkT51jtlLRNGX6haGdscXS0OWWjKOJzKGuV+NbthEn5rmRtVnjRZ3 |
||||
|
yaxQ0ud8vC+NONn7yvGUlOur1IdDzJ/YfHPt9sHMQQKBgQDKcb630fAoI3cxZFgV |
||||
|
2cXOL37TZBzLenwHmm7dqvLYTvSFifakeVQbZr0E0TxznEdDcfHjdahZ/qzTM66c |
||||
|
9XUbbwuRT9RgRsC0+/8/FKbOlCn2f1edi8EsEXxjfPuDGAufW9UU0BGtB1G2Pzup |
||||
|
jdrDsT1LgOwZwBqsi45PZOxMIQKBgQDEiQCOBTMHXNpTMm1bZmC+n1xhivUOakff |
||||
|
R49mXdz/zf3v8NkMOjjp+cViOzq49rTcemYpSduPCvXhAeV9eWe0kES5fFyouOR0 |
||||
|
p0nihvvG54tMriy6j1XwtKuQsKFXGVlDCcUPYTmWXSxV54N4wkdDDfxqFKsEURWf |
||||
|
GOmc+HVDVwKBgQC3MMaMQIU5TR6Gq26qFy5PrdruHyGPX37swmqgXBj0dD4AuR9f |
||||
|
Jgtd4FIbEdvKIhi6h1mDoux8w2BQnevcTKFz+cBV9g8QL/Yh0AbhZiaruJdgX0tB |
||||
|
XT2oSwpsXKSbtB6QTeE7vCzCjmT6ra2neJ4Fh1UYBcmgY52/lWlpnsHVAQKBgQCS |
||||
|
Hl3IOTtgLPuxQVumya4B64+OgfaCGiiHa7fE3uJRWYCD5OgqPBtWcbn7t5eILvv2 |
||||
|
tBXCesP/TKHUAUnzCNE+oMhHs5tcYeKQjFPVVJBaqImboB9nMOwvQtQPhhLeI92G |
||||
|
hvv43EspbOpqj+7iWGcNBpf08prXYcWWfLorV1ovjwKBgAJR1m8dYKemfaW8P9YZ |
||||
|
Uux7lwIFqF+yI201HpZXX+IJK4g1izq490AmXDuGb2bxMCYVZbJ8vSzvCJG8BiRC |
||||
|
+nF62itPAclUtpsEMn5YE4BjZ6c4schFC/5q0C8esteORR3MX3qjSJzIXqftDMZ9 |
||||
|
Q+Z5YZiDoEQKUtLfVaF7R4RV |
||||
|
-----END PRIVATE KEY----- |
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"kty": "EC", |
||||
|
"crv": "P-256", |
||||
|
"x": "IT1SWLxsacPiE5Z16jkopAn8_-85rMjgyCokrnjDft4", |
||||
|
"y": "mP2JwOAOdMmXuwpxbKng3KZz27mz-nKWIlXJ3rzSGMo" |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
-----BEGIN PUBLIC KEY----- |
||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIT1SWLxsacPiE5Z16jkopAn8/+85 |
||||
|
rMjgyCokrnjDft6Y/YnA4A50yZe7CnFsqeDcpnPbubP6cpYiVcnevNIYyg== |
||||
|
-----END PUBLIC KEY----- |
@ -0,0 +1 @@ |
|||||
|
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCE9Uli8bGnD4hOWdeo5KKQJ/P/vOazI4MgqJK54w37emP2JwOAOdMmXuwpxbKng3KZz27mz+nKWIlXJ3rzSGMo= P-256@localhost |
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"kty": "EC", |
||||
|
"crv": "P-384", |
||||
|
"x": "2zEU0bKCa7ejKLIJ8oPGnLhqhxyiv4_w38K2a0SPC6dsSd9_glNJ8lcqv0sff5Gb", |
||||
|
"y": "VD4jnu83S6scn6_TeAj3EZOREGbOs6dzoVpaugn-XQMMyC9O4VLbDDFGBZTJlMsb" |
||||
|
} |
@ -0,0 +1,5 @@ |
|||||
|
-----BEGIN PUBLIC KEY----- |
||||
|
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE2zEU0bKCa7ejKLIJ8oPGnLhqhxyiv4/w |
||||
|
38K2a0SPC6dsSd9/glNJ8lcqv0sff5GbVD4jnu83S6scn6/TeAj3EZOREGbOs6dz |
||||
|
oVpaugn+XQMMyC9O4VLbDDFGBZTJlMsb |
||||
|
-----END PUBLIC KEY----- |
@ -0,0 +1 @@ |
|||||
|
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBNsxFNGygmu3oyiyCfKDxpy4aoccor+P8N/CtmtEjwunbEnff4JTSfJXKr9LH3+Rm1Q+I57vN0urHJ+v03gI9xGTkRBmzrOnc6FaWroJ/l0DDMgvTuFS2wwxRgWUyZTLGw== P-384@localhost |
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"kty": "RSA", |
||||
|
"n": "m2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhDNzUJefLukC-xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57qAZM6-I70on0_iDZm7-jcqOPgADAmbWHhy67BXkk4yy_YzD4yOGZFXZcNp915_TW5bRd__AKPHUHxJasPiyEFqlNKBR2DSD-LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By5AB0Qi49OykUCfNZeQlEz7UNNj9RGps_50-CNw", |
||||
|
"e": "AQAB" |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
-----BEGIN RSA PUBLIC KEY----- |
||||
|
MIIBCgKCAQEAm2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhDNzUJ |
||||
|
efLukC+xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ38P8 |
||||
|
LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57qAZM |
||||
|
6+I70on0/iDZm7+jcqOPgADAmbWHhy67BXkk4yy/YzD4yOGZFXZcNp915/TW5bRd |
||||
|
//AKPHUHxJasPiyEFqlNKBR2DSD+LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By5AB0 |
||||
|
Qi49OykUCfNZeQlEz7UNNj9RGps/50+CNwIDAQAB |
||||
|
-----END RSA PUBLIC KEY----- |
@ -0,0 +1,9 @@ |
|||||
|
-----BEGIN PUBLIC KEY----- |
||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm2ttVBxPlWw06ZmGBWVD |
||||
|
lfjkPAJ4DgnY0TrDwtCohHzLxGhDNzUJefLukC+xu0LBKylYojT5vTkxaOhxeSYo |
||||
|
31syu4WhxbkTBLICOFcCGMob6pSQ38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP |
||||
|
7s9m8Yk9trkpEqjskocn2BOnTB57qAZM6+I70on0/iDZm7+jcqOPgADAmbWHhy67 |
||||
|
BXkk4yy/YzD4yOGZFXZcNp915/TW5bRd//AKPHUHxJasPiyEFqlNKBR2DSD+LbX5 |
||||
|
eTmzCh2ikrwTMja7mUdBJf2bK3By5AB0Qi49OykUCfNZeQlEz7UNNj9RGps/50+C |
||||
|
NwIDAQAB |
||||
|
-----END PUBLIC KEY----- |
@ -0,0 +1 @@ |
|||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCba21UHE+VbDTpmYYFZUOV+OQ8AngOCdjROsPC0KiEfMvEaEM3NQl58u6QL7G7QsErKViiNPm9OTFo6HF5JijfWzK7haHFuRMEsgI4VwIYyhvqlJDfw/wt0AiVvSmoMfEQn1p1aiaO4V/RJSE3Vw/uz2bxiT22uSkSqOyShyfYE6dMHnuoBkzr4jvSifT+INmbv6Nyo4+AAMCZtYeHLrsFeSTjLL9jMPjI4ZkVdlw2n3Xn9NbltF3/8Ao8dQfElqw+LIQWqU0oFHYNIP4ttfl5ObMKHaKSvBMyNruZR0El/ZsrcHLkAHRCLj07KRQJ81l5CUTPtQ02P1Eamz/nT4I3 rsa@localhost |
@ -1,3 +1,410 @@ |
|||||
|
/*global Promise*/ |
||||
'use strict'; |
'use strict'; |
||||
|
|
||||
module.exports = require('@root/acme/keypairs'); |
require('@root/encoding/bytes'); |
||||
|
var Enc = require('@root/encoding/base64'); |
||||
|
|
||||
|
var Keypairs = module.exports; |
||||
|
var Rasha = require('./rsa.js'); |
||||
|
var Eckles = require('./ecdsa.js'); |
||||
|
var native = require('./lib/node/keypairs.js'); |
||||
|
|
||||
|
Keypairs.parse = function(opts) { |
||||
|
opts = opts || {}; |
||||
|
|
||||
|
var err; |
||||
|
var jwk; |
||||
|
var pem; |
||||
|
var p; |
||||
|
|
||||
|
if (!opts.key || !opts.key.kty) { |
||||
|
try { |
||||
|
jwk = JSON.parse(opts.key); |
||||
|
p = Keypairs.export({ jwk: jwk }) |
||||
|
.catch(function(e) { |
||||
|
pem = opts.key; |
||||
|
err = new Error( |
||||
|
"Not a valid jwk '" + |
||||
|
JSON.stringify(jwk) + |
||||
|
"':" + |
||||
|
e.message |
||||
|
); |
||||
|
err.code = 'EINVALID'; |
||||
|
return Promise.reject(err); |
||||
|
}) |
||||
|
.then(function() { |
||||
|
return jwk; |
||||
|
}); |
||||
|
} catch (e) { |
||||
|
p = Keypairs.import({ pem: opts.key }).catch(function(e) { |
||||
|
err = new Error( |
||||
|
'Could not parse key (type ' + |
||||
|
typeof opts.key + |
||||
|
") '" + |
||||
|
opts.key + |
||||
|
"': " + |
||||
|
e.message |
||||
|
); |
||||
|
err.code = 'EPARSE'; |
||||
|
return Promise.reject(err); |
||||
|
}); |
||||
|
} |
||||
|
} else { |
||||
|
p = Promise.resolve(opts.key); |
||||
|
} |
||||
|
|
||||
|
return p.then(function(jwk) { |
||||
|
var pubopts = JSON.parse(JSON.stringify(opts)); |
||||
|
pubopts.jwk = jwk; |
||||
|
return Keypairs.publish(pubopts).then(function(pub) { |
||||
|
// 'd' happens to be the name of a private part of both RSA and ECDSA keys
|
||||
|
if (opts.public || opts.publish || !jwk.d) { |
||||
|
if (opts.private) { |
||||
|
// TODO test that it can actually sign?
|
||||
|
err = new Error( |
||||
|
"Not a private key '" + JSON.stringify(jwk) + "'" |
||||
|
); |
||||
|
err.code = 'ENOTPRIVATE'; |
||||
|
return Promise.reject(err); |
||||
|
} |
||||
|
return { public: pub }; |
||||
|
} else { |
||||
|
return { private: jwk, public: pub }; |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
Keypairs.parseOrGenerate = function(opts) { |
||||
|
if (!opts.key) { |
||||
|
return Keypairs.generate(opts); |
||||
|
} |
||||
|
opts.private = true; |
||||
|
return Keypairs.parse(opts).catch(function(e) { |
||||
|
return Keypairs.generate(opts).then(function(pair) { |
||||
|
pair.parseError = e; |
||||
|
return pair; |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
Keypairs._stance = |
||||
|
"We take the stance that if you're knowledgeable enough to" + |
||||
|
" properly and securely use non-standard crypto then you shouldn't need Bluecrypt anyway."; |
||||
|
Keypairs._universal = |
||||
|
'Bluecrypt only supports crypto with standard cross-browser and cross-platform support.'; |
||||
|
Keypairs.generate = function(opts) { |
||||
|
opts = opts || {}; |
||||
|
var p; |
||||
|
if (!opts.kty) { |
||||
|
opts.kty = opts.type; |
||||
|
} |
||||
|
if (!opts.kty) { |
||||
|
opts.kty = 'EC'; |
||||
|
} |
||||
|
if (/^EC/i.test(opts.kty)) { |
||||
|
p = Eckles.generate(opts); |
||||
|
} else if (/^RSA$/i.test(opts.kty)) { |
||||
|
p = Rasha.generate(opts); |
||||
|
} else { |
||||
|
return Promise.Reject( |
||||
|
new Error( |
||||
|
"'" + |
||||
|
opts.kty + |
||||
|
"' is not a well-supported key type." + |
||||
|
Keypairs._universal + |
||||
|
" Please choose 'EC', or 'RSA' if you have good reason to." |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
return p.then(function(pair) { |
||||
|
return Keypairs.thumbprint({ jwk: pair.public }).then(function(thumb) { |
||||
|
pair.private.kid = thumb; // maybe not the same id on the private key?
|
||||
|
pair.public.kid = thumb; |
||||
|
return pair; |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
Keypairs.import = function(opts) { |
||||
|
return Eckles.import(opts) |
||||
|
.catch(function() { |
||||
|
return Rasha.import(opts); |
||||
|
}) |
||||
|
.then(function(jwk) { |
||||
|
return Keypairs.thumbprint({ jwk: jwk }).then(function(thumb) { |
||||
|
jwk.kid = thumb; |
||||
|
return jwk; |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
Keypairs.export = function(opts) { |
||||
|
return Eckles.export(opts).catch(function(err) { |
||||
|
return Rasha.export(opts).catch(function() { |
||||
|
return Promise.reject(err); |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
// XXX
|
||||
|
native.export = Keypairs.export; |
||||
|
|
||||
|
/** |
||||
|
* Chopping off the private parts is now part of the public API. |
||||
|
* I thought it sounded a little too crude at first, but it really is the best name in every possible way. |
||||
|
*/ |
||||
|
Keypairs.neuter = function(opts) { |
||||
|
/** trying to find the best balance of an immutable copy with custom attributes */ |
||||
|
var jwk = {}; |
||||
|
Object.keys(opts.jwk).forEach(function(k) { |
||||
|
if ('undefined' === typeof opts.jwk[k]) { |
||||
|
return; |
||||
|
} |
||||
|
// ignore RSA and EC private parts
|
||||
|
if (-1 !== ['d', 'p', 'q', 'dp', 'dq', 'qi'].indexOf(k)) { |
||||
|
return; |
||||
|
} |
||||
|
jwk[k] = JSON.parse(JSON.stringify(opts.jwk[k])); |
||||
|
}); |
||||
|
return jwk; |
||||
|
}; |
||||
|
|
||||
|
Keypairs.thumbprint = function(opts) { |
||||
|
return Promise.resolve().then(function() { |
||||
|
if (/EC/i.test(opts.jwk.kty)) { |
||||
|
return Eckles.thumbprint(opts); |
||||
|
} else { |
||||
|
return Rasha.thumbprint(opts); |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
Keypairs.publish = function(opts) { |
||||
|
if ('object' !== typeof opts.jwk || !opts.jwk.kty) { |
||||
|
throw new Error('invalid jwk: ' + JSON.stringify(opts.jwk)); |
||||
|
} |
||||
|
|
||||
|
/** returns a copy */ |
||||
|
var jwk = Keypairs.neuter(opts); |
||||
|
|
||||
|
if (jwk.exp) { |
||||
|
jwk.exp = setTime(jwk.exp); |
||||
|
} else { |
||||
|
if (opts.exp) { |
||||
|
jwk.exp = setTime(opts.exp); |
||||
|
} else if (opts.expiresIn) { |
||||
|
jwk.exp = Math.round(Date.now() / 1000) + opts.expiresIn; |
||||
|
} else if (opts.expiresAt) { |
||||
|
jwk.exp = opts.expiresAt; |
||||
|
} |
||||
|
} |
||||
|
if (!jwk.use && false !== jwk.use) { |
||||
|
jwk.use = 'sig'; |
||||
|
} |
||||
|
|
||||
|
if (jwk.kid) { |
||||
|
return Promise.resolve(jwk); |
||||
|
} |
||||
|
return Keypairs.thumbprint({ jwk: jwk }).then(function(thumb) { |
||||
|
jwk.kid = thumb; |
||||
|
return jwk; |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
// JWT a.k.a. JWS with Claims using Compact Serialization
|
||||
|
Keypairs.signJwt = function(opts) { |
||||
|
return Keypairs.thumbprint({ jwk: opts.jwk }).then(function(thumb) { |
||||
|
var header = opts.header || {}; |
||||
|
var claims = JSON.parse(JSON.stringify(opts.claims || {})); |
||||
|
header.typ = 'JWT'; |
||||
|
|
||||
|
if (!header.kid && false !== header.kid) { |
||||
|
header.kid = thumb; |
||||
|
} |
||||
|
if (!header.alg && opts.alg) { |
||||
|
header.alg = opts.alg; |
||||
|
} |
||||
|
if (!claims.iat && (false === claims.iat || false === opts.iat)) { |
||||
|
claims.iat = undefined; |
||||
|
} else if (!claims.iat) { |
||||
|
claims.iat = Math.round(Date.now() / 1000); |
||||
|
} |
||||
|
|
||||
|
if (opts.exp) { |
||||
|
claims.exp = setTime(opts.exp); |
||||
|
} else if ( |
||||
|
!claims.exp && |
||||
|
(false === claims.exp || false === opts.exp) |
||||
|
) { |
||||
|
claims.exp = undefined; |
||||
|
} else if (!claims.exp) { |
||||
|
throw new Error( |
||||
|
"opts.claims.exp should be the expiration date as seconds, human form (i.e. '1h' or '15m') or false" |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
if (opts.iss) { |
||||
|
claims.iss = opts.iss; |
||||
|
} |
||||
|
if (!claims.iss && (false === claims.iss || false === opts.iss)) { |
||||
|
claims.iss = undefined; |
||||
|
} else if (!claims.iss) { |
||||
|
throw new Error( |
||||
|
'opts.claims.iss should be in the form of https://example.com/, a secure OIDC base url' |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return Keypairs.signJws({ |
||||
|
jwk: opts.jwk, |
||||
|
pem: opts.pem, |
||||
|
protected: header, |
||||
|
header: undefined, |
||||
|
payload: claims |
||||
|
}).then(function(jws) { |
||||
|
return [jws.protected, jws.payload, jws.signature].join('.'); |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
Keypairs.signJws = function(opts) { |
||||
|
return Keypairs.thumbprint(opts).then(function(thumb) { |
||||
|
function alg() { |
||||
|
if (!opts.jwk) { |
||||
|
throw new Error("opts.jwk must exist and must declare 'typ'"); |
||||
|
} |
||||
|
if (opts.jwk.alg) { |
||||
|
return opts.jwk.alg; |
||||
|
} |
||||
|
var typ = 'RSA' === opts.jwk.kty ? 'RS' : 'ES'; |
||||
|
return typ + Keypairs._getBits(opts); |
||||
|
} |
||||
|
|
||||
|
function sign() { |
||||
|
var protect = opts.protected; |
||||
|
var payload = opts.payload; |
||||
|
|
||||
|
// Compute JWS signature
|
||||
|
var protectedHeader = ''; |
||||
|
// Because unprotected headers are allowed, regrettably...
|
||||
|
// https://stackoverflow.com/a/46288694
|
||||
|
if (false !== protect) { |
||||
|
if (!protect) { |
||||
|
protect = {}; |
||||
|
} |
||||
|
if (!protect.alg) { |
||||
|
protect.alg = alg(); |
||||
|
} |
||||
|
// There's a particular request where ACME / Let's Encrypt explicitly doesn't use a kid
|
||||
|
if (false === protect.kid) { |
||||
|
protect.kid = undefined; |
||||
|
} else if (!protect.kid) { |
||||
|
protect.kid = thumb; |
||||
|
} |
||||
|
protectedHeader = JSON.stringify(protect); |
||||
|
} |
||||
|
|
||||
|
// Not sure how to handle the empty case since ACME POST-as-GET must be empty
|
||||
|
//if (!payload) {
|
||||
|
// throw new Error("opts.payload should be JSON, string, or ArrayBuffer (it may be empty, but that must be explicit)");
|
||||
|
//}
|
||||
|
// Trying to detect if it's a plain object (not Buffer, ArrayBuffer, Array, Uint8Array, etc)
|
||||
|
if ( |
||||
|
payload && |
||||
|
'string' !== typeof payload && |
||||
|
'undefined' === typeof payload.byteLength && |
||||
|
'undefined' === typeof payload.buffer |
||||
|
) { |
||||
|
payload = JSON.stringify(payload); |
||||
|
} |
||||
|
// Converting to a buffer, even if it was just converted to a string
|
||||
|
if ('string' === typeof payload) { |
||||
|
payload = Enc.strToBuf(payload); |
||||
|
} |
||||
|
|
||||
|
var protected64 = Enc.strToUrlBase64(protectedHeader); |
||||
|
var payload64 = Enc.bufToUrlBase64(payload); |
||||
|
var msg = protected64 + '.' + payload64; |
||||
|
|
||||
|
return native._sign(opts, msg).then(function(buf) { |
||||
|
var signedMsg = { |
||||
|
protected: protected64, |
||||
|
payload: payload64, |
||||
|
signature: Enc.bufToUrlBase64(buf) |
||||
|
}; |
||||
|
|
||||
|
return signedMsg; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (opts.jwk) { |
||||
|
return sign(); |
||||
|
} else { |
||||
|
return Keypairs.import({ pem: opts.pem }).then(function(pair) { |
||||
|
opts.jwk = pair.private; |
||||
|
return sign(); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
// TODO expose consistently
|
||||
|
Keypairs.sign = native._sign; |
||||
|
|
||||
|
Keypairs._getBits = function(opts) { |
||||
|
if (opts.alg) { |
||||
|
return opts.alg.replace(/[a-z\-]/gi, ''); |
||||
|
} |
||||
|
if (opts.protected && opts.protected.alg) { |
||||
|
return opts.protected.alg.replace(/[a-z\-]/gi, ''); |
||||
|
} |
||||
|
// base64 len to byte len
|
||||
|
var len = Math.floor((opts.jwk.n || '').length * 0.75); |
||||
|
|
||||
|
// TODO this may be a bug
|
||||
|
// need to confirm that the padding is no more or less than 1 byte
|
||||
|
if (/521/.test(opts.jwk.crv) || len >= 511) { |
||||
|
return '512'; |
||||
|
} else if (/384/.test(opts.jwk.crv) || len >= 383) { |
||||
|
return '384'; |
||||
|
} |
||||
|
|
||||
|
return '256'; |
||||
|
}; |
||||
|
// XXX
|
||||
|
native._getBits = Keypairs._getBits; |
||||
|
|
||||
|
function setTime(time) { |
||||
|
if ('number' === typeof time) { |
||||
|
return time; |
||||
|
} |
||||
|
|
||||
|
var t = time.match(/^(\-?\d+)([dhms])$/i); |
||||
|
if (!t || !t[0]) { |
||||
|
throw new Error( |
||||
|
"'" + |
||||
|
time + |
||||
|
"' should be datetime in seconds or human-readable format (i.e. 3d, 1h, 15m, 30s" |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
var now = Math.round(Date.now() / 1000); |
||||
|
var num = parseInt(t[1], 10); |
||||
|
var unit = t[2]; |
||||
|
var mult = 1; |
||||
|
switch (unit) { |
||||
|
// fancy fallthrough, what fun!
|
||||
|
case 'd': |
||||
|
mult *= 24; |
||||
|
/*falls through*/ |
||||
|
case 'h': |
||||
|
mult *= 60; |
||||
|
/*falls through*/ |
||||
|
case 'm': |
||||
|
mult *= 60; |
||||
|
/*falls through*/ |
||||
|
case 's': |
||||
|
mult *= 1; |
||||
|
} |
||||
|
|
||||
|
return now + mult * num; |
||||
|
} |
||||
|
@ -0,0 +1,57 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var native = module.exports; |
||||
|
// XXX received from caller
|
||||
|
var EC = native; |
||||
|
|
||||
|
native.generate = function(opts) { |
||||
|
var wcOpts = {}; |
||||
|
if (!opts) { |
||||
|
opts = {}; |
||||
|
} |
||||
|
if (!opts.kty) { |
||||
|
opts.kty = 'EC'; |
||||
|
} |
||||
|
|
||||
|
// ECDSA has only the P curves and an associated bitlength
|
||||
|
wcOpts.name = 'ECDSA'; |
||||
|
if (!opts.namedCurve) { |
||||
|
opts.namedCurve = 'P-256'; |
||||
|
} |
||||
|
wcOpts.namedCurve = opts.namedCurve; // true for supported curves
|
||||
|
if (/256/.test(wcOpts.namedCurve)) { |
||||
|
wcOpts.namedCurve = 'P-256'; |
||||
|
wcOpts.hash = { name: 'SHA-256' }; |
||||
|
} else if (/384/.test(wcOpts.namedCurve)) { |
||||
|
wcOpts.namedCurve = 'P-384'; |
||||
|
wcOpts.hash = { name: 'SHA-384' }; |
||||
|
} else { |
||||
|
return Promise.Reject( |
||||
|
new Error( |
||||
|
"'" + |
||||
|
wcOpts.namedCurve + |
||||
|
"' is not an NIST approved ECDSA namedCurve. " + |
||||
|
" Please choose either 'P-256' or 'P-384'. " + |
||||
|
// XXX received from caller
|
||||
|
EC._stance |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
var extractable = true; |
||||
|
return window.crypto.subtle |
||||
|
.generateKey(wcOpts, extractable, ['sign', 'verify']) |
||||
|
.then(function(result) { |
||||
|
return window.crypto.subtle |
||||
|
.exportKey('jwk', result.privateKey) |
||||
|
.then(function(privJwk) { |
||||
|
privJwk.key_ops = undefined; |
||||
|
privJwk.ext = undefined; |
||||
|
return { |
||||
|
private: privJwk, |
||||
|
// XXX received from caller
|
||||
|
public: EC.neuter({ jwk: privJwk }) |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
}; |
@ -0,0 +1,108 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var Keypairs = module.exports; |
||||
|
|
||||
|
Keypairs._sign = function(opts, payload) { |
||||
|
return Keypairs._import(opts).then(function(privkey) { |
||||
|
if ('string' === typeof payload) { |
||||
|
payload = new TextEncoder().encode(payload); |
||||
|
} |
||||
|
|
||||
|
return window.crypto.subtle |
||||
|
.sign( |
||||
|
{ |
||||
|
name: Keypairs._getName(opts), |
||||
|
hash: { name: 'SHA-' + Keypairs._getBits(opts) } |
||||
|
}, |
||||
|
privkey, |
||||
|
payload |
||||
|
) |
||||
|
.then(function(signature) { |
||||
|
signature = new Uint8Array(signature); // ArrayBuffer -> u8
|
||||
|
// This will come back into play for CSRs, but not for JOSE
|
||||
|
if ('EC' === opts.jwk.kty && /x509|asn1/i.test(opts.format)) { |
||||
|
return Keypairs._ecdsaJoseSigToAsn1Sig(signature); |
||||
|
} else { |
||||
|
// jose/jws/jwt
|
||||
|
return signature; |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
Keypairs._import = function(opts) { |
||||
|
return Promise.resolve().then(function() { |
||||
|
var ops; |
||||
|
// all private keys just happen to have a 'd'
|
||||
|
if (opts.jwk.d) { |
||||
|
ops = ['sign']; |
||||
|
} else { |
||||
|
ops = ['verify']; |
||||
|
} |
||||
|
// gotta mark it as extractable, as if it matters
|
||||
|
opts.jwk.ext = true; |
||||
|
opts.jwk.key_ops = ops; |
||||
|
|
||||
|
return window.crypto.subtle |
||||
|
.importKey( |
||||
|
'jwk', |
||||
|
opts.jwk, |
||||
|
{ |
||||
|
name: Keypairs._getName(opts), |
||||
|
namedCurve: opts.jwk.crv, |
||||
|
hash: { name: 'SHA-' + Keypairs._getBits(opts) } |
||||
|
}, |
||||
|
true, |
||||
|
ops |
||||
|
) |
||||
|
.then(function(privkey) { |
||||
|
delete opts.jwk.ext; |
||||
|
return privkey; |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
// ECDSA JOSE / JWS / JWT signatures differ from "normal" ASN1/X509 ECDSA signatures
|
||||
|
// https://tools.ietf.org/html/rfc7518#section-3.4
|
||||
|
Keypairs._ecdsaJoseSigToAsn1Sig = function(bufsig) { |
||||
|
// it's easier to do the manipulation in the browser with an array
|
||||
|
bufsig = Array.from(bufsig); |
||||
|
var hlen = bufsig.length / 2; // should be even
|
||||
|
var r = bufsig.slice(0, hlen); |
||||
|
var s = bufsig.slice(hlen); |
||||
|
// unpad positive ints less than 32 bytes wide
|
||||
|
while (!r[0]) { |
||||
|
r = r.slice(1); |
||||
|
} |
||||
|
while (!s[0]) { |
||||
|
s = s.slice(1); |
||||
|
} |
||||
|
// pad (or re-pad) ambiguously non-negative BigInts, up to 33 bytes wide
|
||||
|
if (0x80 & r[0]) { |
||||
|
r.unshift(0); |
||||
|
} |
||||
|
if (0x80 & s[0]) { |
||||
|
s.unshift(0); |
||||
|
} |
||||
|
|
||||
|
var len = 2 + r.length + 2 + s.length; |
||||
|
var head = [0x30]; |
||||
|
// hard code 0x80 + 1 because it won't be longer than
|
||||
|
// two SHA512 plus two pad bytes (130 bytes <= 256)
|
||||
|
if (len >= 0x80) { |
||||
|
head.push(0x81); |
||||
|
} |
||||
|
head.push(len); |
||||
|
|
||||
|
return Uint8Array.from( |
||||
|
head.concat([0x02, r.length], r, [0x02, s.length], s) |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
Keypairs._getName = function(opts) { |
||||
|
if (/EC/i.test(opts.jwk.kty)) { |
||||
|
return 'ECDSA'; |
||||
|
} else { |
||||
|
return 'RSASSA-PKCS1-v1_5'; |
||||
|
} |
||||
|
}; |
@ -0,0 +1,59 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var native = module.exports; |
||||
|
// XXX added by caller: _stance, neuter
|
||||
|
var RSA = native; |
||||
|
|
||||
|
native.generate = function(opts) { |
||||
|
var wcOpts = {}; |
||||
|
if (!opts) { |
||||
|
opts = {}; |
||||
|
} |
||||
|
if (!opts.kty) { |
||||
|
opts.kty = 'RSA'; |
||||
|
} |
||||
|
|
||||
|
// Support PSS? I don't think it's used for Let's Encrypt
|
||||
|
wcOpts.name = 'RSASSA-PKCS1-v1_5'; |
||||
|
if (!opts.modulusLength) { |
||||
|
opts.modulusLength = 2048; |
||||
|
} |
||||
|
wcOpts.modulusLength = opts.modulusLength; |
||||
|
if (wcOpts.modulusLength >= 2048 && wcOpts.modulusLength < 3072) { |
||||
|
// erring on the small side... for no good reason
|
||||
|
wcOpts.hash = { name: 'SHA-256' }; |
||||
|
} else if (wcOpts.modulusLength >= 3072 && wcOpts.modulusLength < 4096) { |
||||
|
wcOpts.hash = { name: 'SHA-384' }; |
||||
|
} else if (wcOpts.modulusLength < 4097) { |
||||
|
wcOpts.hash = { name: 'SHA-512' }; |
||||
|
} else { |
||||
|
// Public key thumbprints should be paired with a hash of similar length,
|
||||
|
// so anything above SHA-512's keyspace would be left under-represented anyway.
|
||||
|
return Promise.Reject( |
||||
|
new Error( |
||||
|
"'" + |
||||
|
wcOpts.modulusLength + |
||||
|
"' is not within the safe and universally" + |
||||
|
' acceptable range of 2048-4096. Typically you should pick 2048, 3072, or 4096, though other values' + |
||||
|
' divisible by 8 are allowed. ' + |
||||
|
RSA._stance |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
// TODO maybe allow this to be set to any of the standard values?
|
||||
|
wcOpts.publicExponent = new Uint8Array([0x01, 0x00, 0x01]); |
||||
|
|
||||
|
var extractable = true; |
||||
|
return window.crypto.subtle |
||||
|
.generateKey(wcOpts, extractable, ['sign', 'verify']) |
||||
|
.then(function(result) { |
||||
|
return window.crypto.subtle |
||||
|
.exportKey('jwk', result.privateKey) |
||||
|
.then(function(privJwk) { |
||||
|
return { |
||||
|
private: privJwk, |
||||
|
public: RSA.neuter({ jwk: privJwk }) |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
}; |
@ -0,0 +1,13 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var sha2 = module.exports; |
||||
|
|
||||
|
var encoder = new TextEncoder(); |
||||
|
sha2.sum = function(alg, str) { |
||||
|
var data = str; |
||||
|
if ('string' === typeof data) { |
||||
|
data = encoder.encode(str); |
||||
|
} |
||||
|
var sha = 'SHA-' + String(alg).replace(/^sha-?/i, ''); |
||||
|
return window.crypto.subtle.digest(sha, data); |
||||
|
}; |
@ -0,0 +1,113 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var native = module.exports; |
||||
|
// XXX provided by caller: import, export
|
||||
|
var EC = native; |
||||
|
// TODO SSH
|
||||
|
|
||||
|
native.generate = function(opts) { |
||||
|
return Promise.resolve().then(function() { |
||||
|
var typ = 'ec'; |
||||
|
var format = opts.format; |
||||
|
var encoding = opts.encoding; |
||||
|
var priv; |
||||
|
var pub = 'spki'; |
||||
|
|
||||
|
if (!format) { |
||||
|
format = 'jwk'; |
||||
|
} |
||||
|
if (-1 !== ['spki', 'pkcs8', 'ssh'].indexOf(format)) { |
||||
|
format = 'pkcs8'; |
||||
|
} |
||||
|
|
||||
|
if ('pem' === format) { |
||||
|
format = 'sec1'; |
||||
|
encoding = 'pem'; |
||||
|
} else if ('der' === format) { |
||||
|
format = 'sec1'; |
||||
|
encoding = 'der'; |
||||
|
} |
||||
|
|
||||
|
if ('jwk' === format || 'json' === format) { |
||||
|
format = 'jwk'; |
||||
|
encoding = 'json'; |
||||
|
} else { |
||||
|
priv = format; |
||||
|
} |
||||
|
|
||||
|
if (!encoding) { |
||||
|
encoding = 'pem'; |
||||
|
} |
||||
|
|
||||
|
if (priv) { |
||||
|
priv = { type: priv, format: encoding }; |
||||
|
pub = { type: pub, format: encoding }; |
||||
|
} else { |
||||
|
// jwk
|
||||
|
priv = { type: 'sec1', format: 'pem' }; |
||||
|
pub = { type: 'spki', format: 'pem' }; |
||||
|
} |
||||
|
|
||||
|
return new Promise(function(resolve, reject) { |
||||
|
return require('crypto').generateKeyPair( |
||||
|
typ, |
||||
|
{ |
||||
|
namedCurve: opts.crv || opts.namedCurve || 'P-256', |
||||
|
privateKeyEncoding: priv, |
||||
|
publicKeyEncoding: pub |
||||
|
}, |
||||
|
function(err, pubkey, privkey) { |
||||
|
if (err) { |
||||
|
reject(err); |
||||
|
} |
||||
|
resolve({ |
||||
|
private: privkey, |
||||
|
public: pubkey |
||||
|
}); |
||||
|
} |
||||
|
); |
||||
|
}).then(function(keypair) { |
||||
|
if ('jwk' === format) { |
||||
|
return Promise.all([ |
||||
|
native.import({ |
||||
|
pem: keypair.private, |
||||
|
format: priv.type |
||||
|
}), |
||||
|
native.import({ |
||||
|
pem: keypair.public, |
||||
|
format: pub.type, |
||||
|
public: true |
||||
|
}) |
||||
|
]).then(function(pair) { |
||||
|
return { |
||||
|
private: pair[0], |
||||
|
public: pair[1] |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if ('ssh' !== opts.format) { |
||||
|
return keypair; |
||||
|
} |
||||
|
|
||||
|
return native |
||||
|
.import({ |
||||
|
pem: keypair.public, |
||||
|
format: format, |
||||
|
public: true |
||||
|
}) |
||||
|
.then(function(jwk) { |
||||
|
return EC.export({ |
||||
|
jwk: jwk, |
||||
|
format: opts.format, |
||||
|
public: true |
||||
|
}).then(function(pub) { |
||||
|
return { |
||||
|
private: keypair.private, |
||||
|
public: pub |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}; |
@ -0,0 +1,55 @@ |
|||||
|
// Copyright 2016-2018 AJ ONeal. All rights reserved
|
||||
|
/* 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/. */
|
||||
|
'use strict'; |
||||
|
|
||||
|
module.exports = function(bitlen, exp) { |
||||
|
var k = require('node-forge').pki.rsa.generateKeyPair({ |
||||
|
bits: bitlen || 2048, |
||||
|
e: exp || 0x10001 |
||||
|
}).privateKey; |
||||
|
var jwk = { |
||||
|
kty: 'RSA', |
||||
|
n: _toUrlBase64(k.n), |
||||
|
e: _toUrlBase64(k.e), |
||||
|
d: _toUrlBase64(k.d), |
||||
|
p: _toUrlBase64(k.p), |
||||
|
q: _toUrlBase64(k.q), |
||||
|
dp: _toUrlBase64(k.dP), |
||||
|
dq: _toUrlBase64(k.dQ), |
||||
|
qi: _toUrlBase64(k.qInv) |
||||
|
}; |
||||
|
return { |
||||
|
private: jwk, |
||||
|
public: { |
||||
|
kty: jwk.kty, |
||||
|
n: jwk.n, |
||||
|
e: jwk.e |
||||
|
} |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
function _toUrlBase64(fbn) { |
||||
|
var hex = fbn.toRadix(16); |
||||
|
if (hex.length % 2) { |
||||
|
// Invalid hex string
|
||||
|
hex = '0' + hex; |
||||
|
} |
||||
|
while ('00' === hex.slice(0, 2)) { |
||||
|
hex = hex.slice(2); |
||||
|
} |
||||
|
return Buffer.from(hex, 'hex') |
||||
|
.toString('base64') |
||||
|
.replace(/\+/g, '-') |
||||
|
.replace(/\//g, '_') |
||||
|
.replace(/=/g, ''); |
||||
|
} |
||||
|
|
||||
|
if (require.main === module) { |
||||
|
var keypair = module.exports(2048, 0x10001); |
||||
|
console.info(keypair.private); |
||||
|
console.warn(keypair.public); |
||||
|
//console.info(keypair.privateKeyJwk);
|
||||
|
//console.warn(keypair.publicKeyJwk);
|
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
// Copyright 2016-2018 AJ ONeal. All rights reserved
|
||||
|
/* 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/. */
|
||||
|
'use strict'; |
||||
|
|
||||
|
module.exports = function(bitlen, exp) { |
||||
|
var keypair = require('crypto').generateKeyPairSync('rsa', { |
||||
|
modulusLength: bitlen, |
||||
|
publicExponent: exp, |
||||
|
privateKeyEncoding: { type: 'pkcs1', format: 'pem' }, |
||||
|
publicKeyEncoding: { type: 'pkcs1', format: 'pem' } |
||||
|
}); |
||||
|
var result = { privateKeyPem: keypair.privateKey.trim() }; |
||||
|
return result; |
||||
|
}; |
||||
|
|
||||
|
if (require.main === module) { |
||||
|
var keypair = module.exports(2048, 0x10001); |
||||
|
console.info(keypair.privateKeyPem); |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
// Copyright 2016-2018 AJ ONeal. All rights reserved
|
||||
|
/* 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/. */
|
||||
|
'use strict'; |
||||
|
|
||||
|
module.exports = function(bitlen, exp) { |
||||
|
var ursa; |
||||
|
try { |
||||
|
ursa = require('ursa'); |
||||
|
} catch (e) { |
||||
|
ursa = require('ursa-optional'); |
||||
|
} |
||||
|
var keypair = ursa.generatePrivateKey(bitlen, exp); |
||||
|
var result = { |
||||
|
privateKeyPem: keypair |
||||
|
.toPrivatePem() |
||||
|
.toString('ascii') |
||||
|
.trim() |
||||
|
}; |
||||
|
return result; |
||||
|
}; |
||||
|
|
||||
|
if (require.main === module) { |
||||
|
var keypair = module.exports(2048, 0x10001); |
||||
|
console.info(keypair.privateKeyPem); |
||||
|
} |
@ -0,0 +1,90 @@ |
|||||
|
// Copyright 2016-2018 AJ ONeal. All rights reserved
|
||||
|
/* 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/. */
|
||||
|
'use strict'; |
||||
|
|
||||
|
var oldver = false; |
||||
|
|
||||
|
module.exports = function(bitlen, exp) { |
||||
|
bitlen = parseInt(bitlen, 10) || 2048; |
||||
|
exp = parseInt(exp, 10) || 65537; |
||||
|
|
||||
|
try { |
||||
|
return require('./generate-privkey-node.js')(bitlen, exp); |
||||
|
} catch (e) { |
||||
|
if (!/generateKeyPairSync is not a function/.test(e.message)) { |
||||
|
throw e; |
||||
|
} |
||||
|
try { |
||||
|
return require('./generate-privkey-ursa.js')(bitlen, exp); |
||||
|
} catch (e) { |
||||
|
if (e.code !== 'MODULE_NOT_FOUND') { |
||||
|
console.error( |
||||
|
"[rsa-compat] Unexpected error when using 'ursa':" |
||||
|
); |
||||
|
console.error(e); |
||||
|
} |
||||
|
if (!oldver) { |
||||
|
oldver = true; |
||||
|
console.warn( |
||||
|
'[WARN] rsa-compat: Your version of node does not have crypto.generateKeyPair()' |
||||
|
); |
||||
|
console.warn( |
||||
|
"[WARN] rsa-compat: Please update to node >= v10.12 or 'npm install --save ursa node-forge'" |
||||
|
); |
||||
|
console.warn( |
||||
|
'[WARN] rsa-compat: Using node-forge as a fallback may be unacceptably slow.' |
||||
|
); |
||||
|
if (/arm|mips/i.test(require('os').arch)) { |
||||
|
console.warn( |
||||
|
'================================================================' |
||||
|
); |
||||
|
console.warn(' WARNING'); |
||||
|
console.warn( |
||||
|
'================================================================' |
||||
|
); |
||||
|
console.warn(''); |
||||
|
console.warn( |
||||
|
'WARNING: You are generating an RSA key using pure JavaScript on' |
||||
|
); |
||||
|
console.warn( |
||||
|
' a VERY SLOW cpu. This could take DOZENS of minutes!' |
||||
|
); |
||||
|
console.warn(''); |
||||
|
console.warn( |
||||
|
" We recommend installing node >= v10.12, or 'gcc' and 'ursa'" |
||||
|
); |
||||
|
console.warn(''); |
||||
|
console.warn('EXAMPLE:'); |
||||
|
console.warn(''); |
||||
|
console.warn( |
||||
|
' sudo apt-get install build-essential && npm install ursa' |
||||
|
); |
||||
|
console.warn(''); |
||||
|
console.warn( |
||||
|
'================================================================' |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
try { |
||||
|
return require('./generate-privkey-forge.js')(bitlen, exp); |
||||
|
} catch (e) { |
||||
|
if (e.code !== 'MODULE_NOT_FOUND') { |
||||
|
throw e; |
||||
|
} |
||||
|
console.error( |
||||
|
'[ERROR] rsa-compat: could not generate a private key.' |
||||
|
); |
||||
|
console.error( |
||||
|
'None of crypto.generateKeyPair, ursa, nor node-forge are present' |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
if (require.main === module) { |
||||
|
var keypair = module.exports(2048, 0x10001); |
||||
|
console.info(keypair.privateKeyPem); |
||||
|
} |
@ -0,0 +1,84 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var Keypairs = module.exports; |
||||
|
var crypto = require('crypto'); |
||||
|
|
||||
|
Keypairs._sign = function(opts, payload) { |
||||
|
return Keypairs._import(opts).then(function(pem) { |
||||
|
payload = Buffer.from(payload); |
||||
|
|
||||
|
// node specifies RSA-SHAxxx even when it's actually ecdsa (it's all encoded x509 shasums anyway)
|
||||
|
// TODO opts.alg = (protect||header).alg
|
||||
|
var nodeAlg = 'SHA' + Keypairs._getBits(opts); |
||||
|
var binsig = crypto |
||||
|
.createSign(nodeAlg) |
||||
|
.update(payload) |
||||
|
.sign(pem); |
||||
|
|
||||
|
if ('EC' === opts.jwk.kty && !/x509|asn1/i.test(opts.format)) { |
||||
|
// ECDSA JWT signatures differ from "normal" ECDSA signatures
|
||||
|
// https://tools.ietf.org/html/rfc7518#section-3.4
|
||||
|
binsig = Keypairs._ecdsaAsn1SigToJoseSig(binsig); |
||||
|
} |
||||
|
|
||||
|
return binsig; |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
Keypairs._import = function(opts) { |
||||
|
if (opts.pem && opts.jwk) { |
||||
|
return Promise.resolve(opts.pem); |
||||
|
} else { |
||||
|
// XXX added by caller
|
||||
|
return Keypairs.export({ jwk: opts.jwk }); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
Keypairs._ecdsaAsn1SigToJoseSig = function(binsig) { |
||||
|
// should have asn1 sequence header of 0x30
|
||||
|
if (0x30 !== binsig[0]) { |
||||
|
throw new Error('Impossible EC SHA head marker'); |
||||
|
} |
||||
|
var index = 2; // first ecdsa "R" header byte
|
||||
|
var len = binsig[1]; |
||||
|
var lenlen = 0; |
||||
|
// Seek length of length if length is greater than 127 (i.e. two 512-bit / 64-byte R and S values)
|
||||
|
if (0x80 & len) { |
||||
|
lenlen = len - 0x80; // should be exactly 1
|
||||
|
len = binsig[2]; // should be <= 130 (two 64-bit SHA-512s, plus padding)
|
||||
|
index += lenlen; |
||||
|
} |
||||
|
// should be of BigInt type
|
||||
|
if (0x02 !== binsig[index]) { |
||||
|
throw new Error('Impossible EC SHA R marker'); |
||||
|
} |
||||
|
index += 1; |
||||
|
|
||||
|
var rlen = binsig[index]; |
||||
|
var bits = 32; |
||||
|
if (rlen > 49) { |
||||
|
bits = 64; |
||||
|
} else if (rlen > 33) { |
||||
|
bits = 48; |
||||
|
} |
||||
|
var r = binsig.slice(index + 1, index + 1 + rlen).toString('hex'); |
||||
|
var slen = binsig[index + 1 + rlen + 1]; // skip header and read length
|
||||
|
var s = binsig.slice(index + 1 + rlen + 1 + 1).toString('hex'); |
||||
|
if (2 * slen !== s.length) { |
||||
|
throw new Error('Impossible EC SHA S length'); |
||||
|
} |
||||
|
// There may be one byte of padding on either
|
||||
|
while (r.length < 2 * bits) { |
||||
|
r = '00' + r; |
||||
|
} |
||||
|
while (s.length < 2 * bits) { |
||||
|
s = '00' + s; |
||||
|
} |
||||
|
if (2 * (bits + 1) === r.length) { |
||||
|
r = r.slice(2); |
||||
|
} |
||||
|
if (2 * (bits + 1) === s.length) { |
||||
|
s = s.slice(2); |
||||
|
} |
||||
|
return Buffer.concat([Buffer.from(r, 'hex'), Buffer.from(s, 'hex')]); |
||||
|
}; |
@ -0,0 +1,116 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var native = module.exports; |
||||
|
|
||||
|
// XXX provided by caller: export
|
||||
|
var RSA = native; |
||||
|
var PEM = require('@root/pem'); |
||||
|
var X509 = require('@root/x509'); |
||||
|
|
||||
|
native.generate = function(opts) { |
||||
|
opts.kty = 'RSA'; |
||||
|
return native._generate(opts).then(function(pair) { |
||||
|
var format = opts.format; |
||||
|
var encoding = opts.encoding; |
||||
|
|
||||
|
// The easy way
|
||||
|
if ('json' === format && !encoding) { |
||||
|
format = 'jwk'; |
||||
|
encoding = 'json'; |
||||
|
} |
||||
|
if ( |
||||
|
('jwk' === format || !format) && |
||||
|
('json' === encoding || !encoding) |
||||
|
) { |
||||
|
return pair; |
||||
|
} |
||||
|
if ('jwk' === format || 'json' === encoding) { |
||||
|
throw new Error( |
||||
|
"format '" + |
||||
|
format + |
||||
|
"' is incompatible with encoding '" + |
||||
|
encoding + |
||||
|
"'" |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// The... less easy way
|
||||
|
/* |
||||
|
var priv; |
||||
|
var pub; |
||||
|
|
||||
|
if ('spki' === format || 'pkcs8' === format) { |
||||
|
format = 'pkcs8'; |
||||
|
pub = 'spki'; |
||||
|
} |
||||
|
|
||||
|
if ('pem' === format) { |
||||
|
format = 'pkcs1'; |
||||
|
encoding = 'pem'; |
||||
|
} else if ('der' === format) { |
||||
|
format = 'pkcs1'; |
||||
|
encoding = 'der'; |
||||
|
} |
||||
|
|
||||
|
priv = format; |
||||
|
pub = pub || format; |
||||
|
|
||||
|
if (!encoding) { |
||||
|
encoding = 'pem'; |
||||
|
} |
||||
|
|
||||
|
if (priv) { |
||||
|
priv = { type: priv, format: encoding }; |
||||
|
pub = { type: pub, format: encoding }; |
||||
|
} else { |
||||
|
// jwk
|
||||
|
priv = { type: 'pkcs1', format: 'pem' }; |
||||
|
pub = { type: 'pkcs1', format: 'pem' }; |
||||
|
} |
||||
|
*/ |
||||
|
if (('pem' === format || 'der' === format) && !encoding) { |
||||
|
encoding = format; |
||||
|
format = 'pkcs1'; |
||||
|
} |
||||
|
|
||||
|
var exOpts = { jwk: pair.private, format: format, encoding: encoding }; |
||||
|
return RSA.export(exOpts).then(function(priv) { |
||||
|
exOpts.public = true; |
||||
|
if ('pkcs8' === exOpts.format) { |
||||
|
exOpts.format = 'spki'; |
||||
|
} |
||||
|
return RSA.export(exOpts).then(function(pub) { |
||||
|
return { private: priv, public: pub }; |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
native._generate = function(opts) { |
||||
|
if (!opts) { |
||||
|
opts = {}; |
||||
|
} |
||||
|
return new Promise(function(resolve, reject) { |
||||
|
try { |
||||
|
var modlen = opts.modulusLength || 2048; |
||||
|
var exp = opts.publicExponent || 0x10001; |
||||
|
var pair = require('./generate-privkey.js')(modlen, exp); |
||||
|
if (pair.private) { |
||||
|
resolve(pair); |
||||
|
return; |
||||
|
} |
||||
|
pair = toJwks(pair); |
||||
|
resolve({ private: pair.private, public: pair.public }); |
||||
|
} catch (e) { |
||||
|
reject(e); |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
// PKCS1 to JWK only
|
||||
|
function toJwks(oldpair) { |
||||
|
var block = PEM.parseBlock(oldpair.privateKeyPem); |
||||
|
var jwk = { kty: 'RSA', n: null, e: null }; |
||||
|
jwk = X509.parsePkcs1(block.bytes, jwk); |
||||
|
return { private: jwk, public: RSA.neuter({ jwk: jwk }) }; |
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
/* global Promise */ |
||||
|
'use strict'; |
||||
|
|
||||
|
var sha2 = module.exports; |
||||
|
var crypto = require('crypto'); |
||||
|
|
||||
|
sha2.sum = function(alg, str) { |
||||
|
return Promise.resolve().then(function() { |
||||
|
var sha = 'sha' + String(alg).replace(/^sha-?/i, ''); |
||||
|
// utf8 is the default for strings
|
||||
|
var buf = Buffer.from(str); |
||||
|
return crypto |
||||
|
.createHash(sha) |
||||
|
.update(buf) |
||||
|
.digest(); |
||||
|
}); |
||||
|
}; |
@ -1,30 +1,35 @@ |
|||||
{ |
{ |
||||
"name": "@root/keypairs", |
"name": "@root/keypairs", |
||||
"version": "1.0.0-wip.0", |
"version": "0.9.0", |
||||
"lockfileVersion": 1, |
"lockfileVersion": 1, |
||||
"requires": true, |
"requires": true, |
||||
"dependencies": { |
"dependencies": { |
||||
"@root/acme": { |
"@root/asn1": { |
||||
"version": "3.0.0-wip.0", |
"version": "1.0.0", |
||||
"resolved": "https://registry.npmjs.org/@root/acme/-/acme-3.0.0-wip.0.tgz", |
"resolved": "https://registry.npmjs.org/@root/asn1/-/asn1-1.0.0.tgz", |
||||
"integrity": "sha512-IwnG3ZFt1fl81O1M+FFV91b5Kpw7GYAD1jXwvOWnq9KF50AVO6+L7MUQIAFCK1q/u/weC73DCFrw/6kFN+Vi9A==", |
"integrity": "sha512-0lfZNuOULKJDJmdIkP8V9RnbV3XaK6PAHD3swnFy4tZwtlMDzLKoM/dfNad7ut8Hu3r91wy9uK0WA/9zym5mig==", |
||||
"requires": { |
"requires": { |
||||
"@root/csr": "^1.0.0-wip.0", |
|
||||
"@root/encoding": "^1.0.1" |
"@root/encoding": "^1.0.1" |
||||
} |
} |
||||
}, |
}, |
||||
"@root/csr": { |
|
||||
"version": "1.0.0-wip.0", |
|
||||
"resolved": "https://registry.npmjs.org/@root/csr/-/csr-1.0.0-wip.0.tgz", |
|
||||
"integrity": "sha512-ZrZeGgf/hvfIyMDAZXfD45rYriaZF6LJu7+l0ioPPKgLWVEUAUBkV53z7JbzlcPvXXr6/ZjECzWQ7MYQfMBUAg==", |
|
||||
"requires": { |
|
||||
"@root/acme": "^3.0.0-wip.0" |
|
||||
} |
|
||||
}, |
|
||||
"@root/encoding": { |
"@root/encoding": { |
||||
"version": "1.0.1", |
"version": "1.0.1", |
||||
"resolved": "https://registry.npmjs.org/@root/encoding/-/encoding-1.0.1.tgz", |
"resolved": "https://registry.npmjs.org/@root/encoding/-/encoding-1.0.1.tgz", |
||||
"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ==" |
"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ==" |
||||
|
}, |
||||
|
"@root/pem": { |
||||
|
"version": "1.0.4", |
||||
|
"resolved": "https://registry.npmjs.org/@root/pem/-/pem-1.0.4.tgz", |
||||
|
"integrity": "sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA==" |
||||
|
}, |
||||
|
"@root/x509": { |
||||
|
"version": "0.7.2", |
||||
|
"resolved": "https://registry.npmjs.org/@root/x509/-/x509-0.7.2.tgz", |
||||
|
"integrity": "sha512-ENq3LGYORK5NiMFHEVeNMt+fTXaC7DTS6sQXoqV+dFdfT0vmiL5cDLjaXQhaklJQq0NiwicZegzJRl1ZOTp3WQ==", |
||||
|
"requires": { |
||||
|
"@root/asn1": "^1.0.0", |
||||
|
"@root/encoding": "^1.0.1" |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
@ -0,0 +1,192 @@ |
|||||
|
/*global Promise*/ |
||||
|
'use strict'; |
||||
|
|
||||
|
var RSA = module.exports; |
||||
|
var native = require('./lib/node/rsa.js'); |
||||
|
var X509 = require('@root/x509'); |
||||
|
var PEM = require('@root/pem'); |
||||
|
//var SSH = require('./ssh-keys.js');
|
||||
|
var sha2 = require('./lib/node/sha2.js'); |
||||
|
var Enc = require('@root/encoding/base64'); |
||||
|
|
||||
|
RSA._universal = |
||||
|
'Bluecrypt only supports crypto with standard cross-browser and cross-platform support.'; |
||||
|
RSA._stance = |
||||
|
"We take the stance that if you're knowledgeable enough to" + |
||||
|
" properly and securely use non-standard crypto then you shouldn't need Bluecrypt anyway."; |
||||
|
native._stance = RSA._stance; |
||||
|
|
||||
|
RSA.generate = native.generate; |
||||
|
|
||||
|
// Chopping off the private parts is now part of the public API.
|
||||
|
// I thought it sounded a little too crude at first, but it really is the best name in every possible way.
|
||||
|
RSA.neuter = function(opts) { |
||||
|
// trying to find the best balance of an immutable copy with custom attributes
|
||||
|
var jwk = {}; |
||||
|
Object.keys(opts.jwk).forEach(function(k) { |
||||
|
if ('undefined' === typeof opts.jwk[k]) { |
||||
|
return; |
||||
|
} |
||||
|
// ignore RSA private parts
|
||||
|
if (-1 !== ['d', 'p', 'q', 'dp', 'dq', 'qi'].indexOf(k)) { |
||||
|
return; |
||||
|
} |
||||
|
jwk[k] = JSON.parse(JSON.stringify(opts.jwk[k])); |
||||
|
}); |
||||
|
return jwk; |
||||
|
}; |
||||
|
native.neuter = RSA.neuter; |
||||
|
|
||||
|
// https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
|
||||
|
RSA.__thumbprint = function(jwk) { |
||||
|
// Use the same entropy for SHA as for key
|
||||
|
var len = Math.floor(jwk.n.length * 0.75); |
||||
|
var alg = 'SHA-256'; |
||||
|
// TODO this may be a bug
|
||||
|
// need to confirm that the padding is no more or less than 1 byte
|
||||
|
if (len >= 511) { |
||||
|
alg = 'SHA-512'; |
||||
|
} else if (len >= 383) { |
||||
|
alg = 'SHA-384'; |
||||
|
} |
||||
|
return sha2 |
||||
|
.sum(alg, '{"e":"' + jwk.e + '","kty":"RSA","n":"' + jwk.n + '"}') |
||||
|
.then(function(hash) { |
||||
|
return Enc.bufToUrlBase64(Uint8Array.from(hash)); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
RSA.thumbprint = function(opts) { |
||||
|
return Promise.resolve().then(function() { |
||||
|
var jwk; |
||||
|
if ('EC' === opts.kty) { |
||||
|
jwk = opts; |
||||
|
} else if (opts.jwk) { |
||||
|
jwk = opts.jwk; |
||||
|
} else { |
||||
|
return RSA.import(opts).then(function(jwk) { |
||||
|
return RSA.__thumbprint(jwk); |
||||
|
}); |
||||
|
} |
||||
|
return RSA.__thumbprint(jwk); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
RSA.export = function(opts) { |
||||
|
return Promise.resolve().then(function() { |
||||
|
if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) { |
||||
|
throw new Error('must pass { jwk: jwk }'); |
||||
|
} |
||||
|
var jwk = JSON.parse(JSON.stringify(opts.jwk)); |
||||
|
var format = opts.format; |
||||
|
var pub = opts.public; |
||||
|
if (pub || -1 !== ['spki', 'pkix', 'ssh', 'rfc4716'].indexOf(format)) { |
||||
|
jwk = RSA.neuter({ jwk: jwk }); |
||||
|
} |
||||
|
if ('RSA' !== jwk.kty) { |
||||
|
throw new Error( |
||||
|
"options.jwk.kty must be 'RSA' for RSA keys: " + |
||||
|
JSON.stringify(jwk) |
||||
|
); |
||||
|
} |
||||
|
if (!jwk.p) { |
||||
|
// TODO test for n and e
|
||||
|
pub = true; |
||||
|
if (!format || 'pkcs1' === format) { |
||||
|
format = 'pkcs1'; |
||||
|
} else if (-1 !== ['spki', 'pkix'].indexOf(format)) { |
||||
|
format = 'spki'; |
||||
|
} else if (-1 !== ['ssh', 'rfc4716'].indexOf(format)) { |
||||
|
format = 'ssh'; |
||||
|
} else { |
||||
|
throw new Error( |
||||
|
"options.format must be 'spki', 'pkcs1', or 'ssh' for public RSA keys, not (" + |
||||
|
typeof format + |
||||
|
') ' + |
||||
|
format |
||||
|
); |
||||
|
} |
||||
|
} else { |
||||
|
// TODO test for all necessary keys (d, p, q ...)
|
||||
|
if (!format || 'pkcs1' === format) { |
||||
|
format = 'pkcs1'; |
||||
|
} else if ('pkcs8' !== format) { |
||||
|
throw new Error( |
||||
|
"options.format must be 'pkcs1' or 'pkcs8' for private RSA keys" |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if ('pkcs1' === format) { |
||||
|
if (jwk.d) { |
||||
|
return PEM.packBlock({ |
||||
|
type: 'RSA PRIVATE KEY', |
||||
|
bytes: X509.packPkcs1(jwk) |
||||
|
}); |
||||
|
} else { |
||||
|
return PEM.packBlock({ |
||||
|
type: 'RSA PUBLIC KEY', |
||||
|
bytes: X509.packPkcs1(jwk) |
||||
|
}); |
||||
|
} |
||||
|
} else if ('pkcs8' === format) { |
||||
|
return PEM.packBlock({ |
||||
|
type: 'PRIVATE KEY', |
||||
|
bytes: X509.packPkcs8(jwk) |
||||
|
}); |
||||
|
} else if (-1 !== ['spki', 'pkix'].indexOf(format)) { |
||||
|
return PEM.packBlock({ |
||||
|
type: 'PUBLIC KEY', |
||||
|
bytes: X509.packSpki(jwk) |
||||
|
}); |
||||
|
} else if (-1 !== ['ssh', 'rfc4716'].indexOf(format)) { |
||||
|
//return SSH.pack({ jwk: jwk, comment: opts.comment });
|
||||
|
throw new Error('not supported yet'); |
||||
|
} else { |
||||
|
throw new Error( |
||||
|
'Sanity Error: reached unreachable code block with format: ' + |
||||
|
format |
||||
|
); |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
native.export = RSA.export; |
||||
|
|
||||
|
RSA.pack = function(opts) { |
||||
|
// wrapped in a promise for API compatibility
|
||||
|
// with the forthcoming browser version
|
||||
|
// (and potential future native node capability)
|
||||
|
return Promise.resolve().then(function() { |
||||
|
return RSA.export(opts); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
RSA._importSync = function(opts) { |
||||
|
if (!opts || !opts.pem || 'string' !== typeof opts.pem) { |
||||
|
throw new Error('must pass { pem: pem } as a string'); |
||||
|
} |
||||
|
|
||||
|
if (0 === opts.pem.indexOf('ssh-rsa ')) { |
||||
|
//return SSH.parse(opts.pem, jwk);
|
||||
|
throw new Error('not supported, yet'); |
||||
|
} |
||||
|
var pem = opts.pem; |
||||
|
var block = PEM.parseBlock(pem); |
||||
|
//var hex = toHex(u8);
|
||||
|
|
||||
|
var jwk = X509._parseRsa(block.bytes); |
||||
|
|
||||
|
if (opts.public) { |
||||
|
jwk = RSA.nueter(jwk); |
||||
|
} |
||||
|
return jwk; |
||||
|
}; |
||||
|
RSA.parse = function parseRsa(opts) { |
||||
|
// wrapped in a promise for API compatibility
|
||||
|
// with the forthcoming browser version
|
||||
|
// (and potential future native node capability)
|
||||
|
return Promise.resolve().then(function() { |
||||
|
return RSA._importSync(opts); |
||||
|
}); |
||||
|
}; |
||||
|
RSA.toJwk = RSA.import = RSA.parse; |
@ -0,0 +1,139 @@ |
|||||
|
#/bin/bash |
||||
|
set -e |
||||
|
|
||||
|
echo "" |
||||
|
echo "Testing PEM-to-JWK P-256" |
||||
|
node bin/eckles.js fixtures/privkey-ec-p256.sec1.pem \ |
||||
|
> fixtures/privkey-ec-p256.jwk.2 |
||||
|
diff fixtures/privkey-ec-p256.jwk.json fixtures/privkey-ec-p256.jwk.2 |
||||
|
node bin/eckles.js fixtures/privkey-ec-p256.pkcs8.pem \ |
||||
|
> fixtures/privkey-ec-p256.jwk.2 |
||||
|
diff fixtures/privkey-ec-p256.jwk.json fixtures/privkey-ec-p256.jwk.2 |
||||
|
node bin/eckles.js fixtures/pub-ec-p256.spki.pem \ |
||||
|
> fixtures/pub-ec-p256.jwk.2 |
||||
|
diff fixtures/pub-ec-p256.jwk.json fixtures/pub-ec-p256.jwk.2 |
||||
|
# |
||||
|
echo '[SKIP] SSH-to-JWK P-256' |
||||
|
# node bin/eckles.js fixtures/pub-ec-p256.ssh.pub > fixtures/pub-ec-p256.jwk.2 |
||||
|
diff fixtures/pub-ec-p256.jwk.2 fixtures/pub-ec-p256.jwk.2 |
||||
|
echo "PASS" |
||||
|
|
||||
|
|
||||
|
echo "" |
||||
|
echo "Testing PEM-to-JWK P-384" |
||||
|
node bin/eckles.js fixtures/privkey-ec-p384.sec1.pem \ |
||||
|
> fixtures/privkey-ec-p384.jwk.2 |
||||
|
diff fixtures/privkey-ec-p384.jwk.json fixtures/privkey-ec-p384.jwk.2 |
||||
|
node bin/eckles.js fixtures/privkey-ec-p384.pkcs8.pem \ |
||||
|
> fixtures/privkey-ec-p384.jwk.2.2 |
||||
|
diff fixtures/privkey-ec-p384.jwk.json fixtures/privkey-ec-p384.jwk.2.2 |
||||
|
node bin/eckles.js fixtures/pub-ec-p384.spki.pem \ |
||||
|
> fixtures/pub-ec-p384.jwk.2 |
||||
|
diff fixtures/pub-ec-p384.jwk.json fixtures/pub-ec-p384.jwk.2 |
||||
|
# |
||||
|
echo '[SKIP] SSH-to-JWK P-384' |
||||
|
# node bin/eckles.js fixtures/pub-ec-p384.ssh.pub fixtures/pub-ec-p384.jwk.2 |
||||
|
diff fixtures/pub-ec-p384.jwk.2 fixtures/pub-ec-p384.jwk.2 |
||||
|
echo "PASS" |
||||
|
|
||||
|
|
||||
|
echo "" |
||||
|
echo "Testing JWK-to-PEM P-256" |
||||
|
node bin/eckles.js fixtures/privkey-ec-p256.jwk.json sec1 \ |
||||
|
> fixtures/privkey-ec-p256.sec1.pem.2 |
||||
|
diff fixtures/privkey-ec-p256.sec1.pem fixtures/privkey-ec-p256.sec1.pem.2 |
||||
|
# |
||||
|
node bin/eckles.js fixtures/privkey-ec-p256.jwk.json pkcs8 \ |
||||
|
> fixtures/privkey-ec-p256.pkcs8.pem.2 |
||||
|
diff fixtures/privkey-ec-p256.pkcs8.pem fixtures/privkey-ec-p256.pkcs8.pem.2 |
||||
|
# |
||||
|
node bin/eckles.js fixtures/pub-ec-p256.jwk.json spki \ |
||||
|
> fixtures/pub-ec-p256.spki.pem.2 |
||||
|
diff fixtures/pub-ec-p256.spki.pem fixtures/pub-ec-p256.spki.pem.2 |
||||
|
# ssh-keygen -f fixtures/pub-ec-p256.spki.pem -i -mPKCS8 > fixtures/pub-ec-p256.ssh.pub |
||||
|
echo '[SKIP] JWK-to-SSH P-256' |
||||
|
#node bin/eckles.js fixtures/pub-ec-p256.jwk.json ssh > fixtures/pub-ec-p256.ssh.pub.2 |
||||
|
#diff fixtures/pub-ec-p256.ssh.pub fixtures/pub-ec-p256.ssh.pub.2 |
||||
|
echo "PASS" |
||||
|
|
||||
|
|
||||
|
echo "" |
||||
|
echo "Testing JWK-to-PEM P-384" |
||||
|
node bin/eckles.js fixtures/privkey-ec-p384.jwk.json sec1 \ |
||||
|
> fixtures/privkey-ec-p384.sec1.pem.2 |
||||
|
diff fixtures/privkey-ec-p384.sec1.pem fixtures/privkey-ec-p384.sec1.pem.2 |
||||
|
# |
||||
|
node bin/eckles.js fixtures/privkey-ec-p384.jwk.json pkcs8 \ |
||||
|
> fixtures/privkey-ec-p384.pkcs8.pem.2 |
||||
|
diff fixtures/privkey-ec-p384.pkcs8.pem fixtures/privkey-ec-p384.pkcs8.pem.2 |
||||
|
# |
||||
|
node bin/eckles.js fixtures/pub-ec-p384.jwk.json spki \ |
||||
|
> fixtures/pub-ec-p384.spki.pem.2 |
||||
|
diff fixtures/pub-ec-p384.spki.pem fixtures/pub-ec-p384.spki.pem.2 |
||||
|
# ssh-keygen -f fixtures/pub-ec-p384.spki.pem -i -mPKCS8 > fixtures/pub-ec-p384.ssh.pub |
||||
|
echo '[SKIP] JWK-to-SSH P-384' |
||||
|
#node bin/eckles.js fixtures/pub-ec-p384.jwk.json ssh > fixtures/pub-ec-p384.ssh.pub.2 |
||||
|
#diff fixtures/pub-ec-p384.ssh.pub fixtures/pub-ec-p384.ssh.pub.2 |
||||
|
echo "PASS" |
||||
|
|
||||
|
|
||||
|
rm fixtures/*.2 |
||||
|
|
||||
|
|
||||
|
mkdir -p tmp |
||||
|
echo "" |
||||
|
echo "Testing freshly generated keypair" |
||||
|
# Generate EC P-256 Keypair |
||||
|
openssl ecparam -genkey -name prime256v1 -noout -out ./tmp/privkey-ec-p256.sec1.pem |
||||
|
# Export Public-only EC Key (as SPKI) |
||||
|
openssl ec -in ./tmp/privkey-ec-p256.sec1.pem -pubout -out ./tmp/pub-ec-p256.spki.pem |
||||
|
# Convert SEC1 (traditional) EC Keypair to PKCS8 format |
||||
|
openssl pkcs8 -topk8 -nocrypt -in ./tmp/privkey-ec-p256.sec1.pem -out ./tmp/privkey-ec-p256.pkcs8.pem |
||||
|
# Convert EC public key to SSH format |
||||
|
sshpub=$(ssh-keygen -f ./tmp/pub-ec-p256.spki.pem -i -mPKCS8) |
||||
|
echo "$sshpub P-256@localhost" > ./tmp/pub-ec-p256.ssh.pub |
||||
|
# |
||||
|
node bin/eckles.js ./tmp/privkey-ec-p256.sec1.pem > ./tmp/privkey-ec-p256.jwk.json |
||||
|
node bin/eckles.js ./tmp/privkey-ec-p256.jwk.json sec1 > ./tmp/privkey-ec-p256.sec1.pem.2 |
||||
|
diff ./tmp/privkey-ec-p256.sec1.pem ./tmp/privkey-ec-p256.sec1.pem.2 |
||||
|
# |
||||
|
node bin/eckles.js ./tmp/privkey-ec-p256.pkcs8.pem > ./tmp/privkey-ec-p256.jwk.json |
||||
|
node bin/eckles.js ./tmp/privkey-ec-p256.jwk.json pkcs8 > ./tmp/privkey-ec-p256.pkcs8.pem.2 |
||||
|
diff ./tmp/privkey-ec-p256.pkcs8.pem ./tmp/privkey-ec-p256.pkcs8.pem.2 |
||||
|
# |
||||
|
node bin/eckles.js ./tmp/pub-ec-p256.spki.pem > ./tmp/pub-ec-p256.jwk.json |
||||
|
node bin/eckles.js ./tmp/pub-ec-p256.jwk.json spki > ./tmp/pub-ec-p256.spki.pem.2 |
||||
|
diff ./tmp/pub-ec-p256.spki.pem ./tmp/pub-ec-p256.spki.pem.2 |
||||
|
# |
||||
|
echo '[SKIP] Gen SSH and Convert' |
||||
|
#node bin/eckles.js ./tmp/pub-ec-p256.ssh.pub > ./tmp/pub-ec-p256.jwk.json |
||||
|
#node bin/eckles.js ./tmp/pub-ec-p256.jwk.json ssh > ./tmp/pub-ec-p256.ssh.pub.2 |
||||
|
#diff ./tmp/pub-ec-p256.ssh.pub ./tmp/pub-ec-p256.ssh.pub.2 |
||||
|
echo "PASS" |
||||
|
|
||||
|
echo "" |
||||
|
echo "Testing key generation" |
||||
|
node bin/eckles.js jwk > /dev/null |
||||
|
node bin/eckles.js jwk P-384 > /dev/null |
||||
|
node bin/eckles.js sec1 > /dev/null |
||||
|
node bin/eckles.js pkcs8 > /dev/null |
||||
|
echo '[SKIP] Gen SSH' |
||||
|
#node bin/eckles.js ssh #> /dev/null |
||||
|
echo "PASS" |
||||
|
|
||||
|
echo "" |
||||
|
echo "Testing Thumbprints" |
||||
|
node bin/eckles.js ./fixtures/privkey-ec-p256.sec1.pem thumbprint |
||||
|
node bin/eckles.js ./fixtures/pub-ec-p256.jwk.json thumbprint |
||||
|
echo "PASS" |
||||
|
|
||||
|
rm ./tmp/*.* |
||||
|
|
||||
|
|
||||
|
echo "" |
||||
|
echo "" |
||||
|
echo "PASSED:" |
||||
|
echo "• All inputs produced valid outputs" |
||||
|
echo "• All outputs matched known-good values" |
||||
|
echo "• Generated keys in each format (sec1, pkcs8, jwk, [SKIP] ssh)" |
||||
|
echo "" |
@ -0,0 +1,165 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var Keypairs = require('../'); |
||||
|
|
||||
|
/* global Promise*/ |
||||
|
Keypairs.parseOrGenerate({ key: null }) |
||||
|
.then(function(pair) { |
||||
|
// should NOT have any warning output
|
||||
|
if (!pair.private || !pair.public) { |
||||
|
throw new Error('missing key pairs'); |
||||
|
} |
||||
|
|
||||
|
return Promise.all([ |
||||
|
// Testing Public Part of key
|
||||
|
Keypairs.export({ jwk: pair.public }).then(function(pem) { |
||||
|
if (!/--BEGIN PUBLIC/.test(pem)) { |
||||
|
throw new Error('did not export public pem'); |
||||
|
} |
||||
|
return Promise.all([ |
||||
|
Keypairs.parse({ key: pem }).then(function(pair) { |
||||
|
if (pair.private) { |
||||
|
throw new Error("shouldn't have private part"); |
||||
|
} |
||||
|
return true; |
||||
|
}), |
||||
|
Keypairs.parse({ key: pem, private: true }) |
||||
|
.then(function() { |
||||
|
var err = new Error( |
||||
|
'should have thrown an error when private key was required and public pem was given' |
||||
|
); |
||||
|
err.code = 'NOERR'; |
||||
|
throw err; |
||||
|
}) |
||||
|
.catch(function(e) { |
||||
|
if ('NOERR' === e.code) { |
||||
|
throw e; |
||||
|
} |
||||
|
return true; |
||||
|
}) |
||||
|
]).then(function() { |
||||
|
return true; |
||||
|
}); |
||||
|
}), |
||||
|
// Testing Private Part of Key
|
||||
|
Keypairs.export({ jwk: pair.private }).then(function(pem) { |
||||
|
if (!/--BEGIN .*PRIVATE KEY--/.test(pem)) { |
||||
|
throw new Error('did not export private pem: ' + pem); |
||||
|
} |
||||
|
return Promise.all([ |
||||
|
Keypairs.parse({ key: pem }).then(function(pair) { |
||||
|
if (!pair.private) { |
||||
|
throw new Error('should have private part'); |
||||
|
} |
||||
|
if (!pair.public) { |
||||
|
throw new Error('should have public part also'); |
||||
|
} |
||||
|
return true; |
||||
|
}), |
||||
|
Keypairs.parse({ key: pem, public: true }).then(function( |
||||
|
pair |
||||
|
) { |
||||
|
if (pair.private) { |
||||
|
throw new Error('should NOT have private part'); |
||||
|
} |
||||
|
if (!pair.public) { |
||||
|
throw new Error( |
||||
|
'should have the public part though' |
||||
|
); |
||||
|
} |
||||
|
return true; |
||||
|
}) |
||||
|
]).then(function() { |
||||
|
return true; |
||||
|
}); |
||||
|
}), |
||||
|
Keypairs.parseOrGenerate({ key: 'not a key', public: true }).then( |
||||
|
function(pair) { |
||||
|
// SHOULD have warning output
|
||||
|
if (!pair.private || !pair.public) { |
||||
|
throw new Error( |
||||
|
"missing key pairs (should ignore 'public')" |
||||
|
); |
||||
|
} |
||||
|
if (!pair.parseError) { |
||||
|
throw new Error( |
||||
|
'should pass parseError for malformed string' |
||||
|
); |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
), |
||||
|
Keypairs.parse({ key: JSON.stringify(pair.private) }).then(function( |
||||
|
pair |
||||
|
) { |
||||
|
if (!pair.private || !pair.public) { |
||||
|
throw new Error('missing key pairs (stringified jwt)'); |
||||
|
} |
||||
|
return true; |
||||
|
}), |
||||
|
Keypairs.parse({ |
||||
|
key: JSON.stringify(pair.private), |
||||
|
public: true |
||||
|
}).then(function(pair) { |
||||
|
if (pair.private) { |
||||
|
throw new Error("has private key when it shouldn't"); |
||||
|
} |
||||
|
if (!pair.public) { |
||||
|
throw new Error("doesn't have public key when it should"); |
||||
|
} |
||||
|
return true; |
||||
|
}), |
||||
|
Keypairs.parse({ key: JSON.stringify(pair.public), private: true }) |
||||
|
.then(function() { |
||||
|
var err = new Error( |
||||
|
'should have thrown an error when private key was required and public jwk was given' |
||||
|
); |
||||
|
err.code = 'NOERR'; |
||||
|
throw err; |
||||
|
}) |
||||
|
.catch(function(e) { |
||||
|
if ('NOERR' === e.code) { |
||||
|
throw e; |
||||
|
} |
||||
|
return true; |
||||
|
}), |
||||
|
Keypairs.signJwt({ |
||||
|
jwk: pair.private, |
||||
|
// Note: using ES512 won't actually increase the length
|
||||
|
// (it would be truncated to fit into the key size)
|
||||
|
alg: 'ES256', |
||||
|
iss: 'https://example.com/', |
||||
|
exp: '1h' |
||||
|
}).then(function(jwt) { |
||||
|
var parts = jwt.split('.'); |
||||
|
var now = Math.round(Date.now() / 1000); |
||||
|
var token = { |
||||
|
header: JSON.parse(Buffer.from(parts[0], 'base64')), |
||||
|
payload: JSON.parse(Buffer.from(parts[1], 'base64')), |
||||
|
signature: parts[2] //Buffer.from(parts[2], 'base64')
|
||||
|
}; |
||||
|
// allow some leeway just in case we happen to hit a 1ms boundary
|
||||
|
if (token.payload.exp - now > 60 * 59.99) { |
||||
|
return true; |
||||
|
} |
||||
|
throw new Error('token was not properly generated'); |
||||
|
}) |
||||
|
]).then(function(results) { |
||||
|
if ( |
||||
|
results.length && |
||||
|
results.every(function(v) { |
||||
|
return true === v; |
||||
|
}) |
||||
|
) { |
||||
|
console.log('PASS'); |
||||
|
process.exit(0); |
||||
|
} else { |
||||
|
throw new Error("didn't get all passes (but no errors either)"); |
||||
|
} |
||||
|
}); |
||||
|
}) |
||||
|
.catch(function(e) { |
||||
|
console.error('Caught an unexpected (failing) error:'); |
||||
|
console.error(e); |
||||
|
process.exit(1); |
||||
|
}); |
@ -0,0 +1,186 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
# cause errors to hard-fail |
||||
|
# (and diff non-0 exit status will cause failure) |
||||
|
set -e |
||||
|
|
||||
|
pemtojwk() { |
||||
|
keyid=$1 |
||||
|
if [ -z "$keyid" ]; then |
||||
|
echo "" |
||||
|
echo "Testing PEM-to-JWK PKCS#1" |
||||
|
fi |
||||
|
# |
||||
|
node bin/rasha.js ./fixtures/privkey-rsa-2048.pkcs1.${keyid}pem \ |
||||
|
> ./fixtures/privkey-rsa-2048.jwk.1.json |
||||
|
diff ./fixtures/privkey-rsa-2048.jwk.${keyid}json ./fixtures/privkey-rsa-2048.jwk.1.json |
||||
|
# |
||||
|
node bin/rasha.js ./fixtures/pub-rsa-2048.pkcs1.${keyid}pem \ |
||||
|
> ./fixtures/pub-rsa-2048.jwk.1.json |
||||
|
diff ./fixtures/pub-rsa-2048.jwk.${keyid}json ./fixtures/pub-rsa-2048.jwk.1.json |
||||
|
if [ -z "$keyid" ]; then |
||||
|
echo "Pass" |
||||
|
fi |
||||
|
|
||||
|
|
||||
|
if [ -z "$keyid" ]; then |
||||
|
echo "" |
||||
|
echo "Testing PEM-to-JWK PKCS#8" |
||||
|
fi |
||||
|
# |
||||
|
node bin/rasha.js ./fixtures/privkey-rsa-2048.pkcs8.${keyid}pem \ |
||||
|
> ./fixtures/privkey-rsa-2048.jwk.1.json |
||||
|
diff ./fixtures/privkey-rsa-2048.jwk.${keyid}json ./fixtures/privkey-rsa-2048.jwk.1.json |
||||
|
# |
||||
|
node bin/rasha.js ./fixtures/pub-rsa-2048.spki.${keyid}pem \ |
||||
|
> ./fixtures/pub-rsa-2048.jwk.1.json |
||||
|
diff ./fixtures/pub-rsa-2048.jwk.${keyid}json ./fixtures/pub-rsa-2048.jwk.1.json |
||||
|
if [ -z "$keyid" ]; then |
||||
|
echo "Pass" |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
jwktopem() { |
||||
|
keyid=$1 |
||||
|
if [ -z "$keyid" ]; then |
||||
|
echo "" |
||||
|
echo "Testing JWK-to-PEM PKCS#1" |
||||
|
fi |
||||
|
# |
||||
|
node bin/rasha.js ./fixtures/privkey-rsa-2048.jwk.${keyid}json pkcs1 \ |
||||
|
> ./fixtures/privkey-rsa-2048.pkcs1.1.pem |
||||
|
diff ./fixtures/privkey-rsa-2048.pkcs1.${keyid}pem ./fixtures/privkey-rsa-2048.pkcs1.1.pem |
||||
|
# |
||||
|
node bin/rasha.js ./fixtures/pub-rsa-2048.jwk.${keyid}json pkcs1 \ |
||||
|
> ./fixtures/pub-rsa-2048.pkcs1.1.pem |
||||
|
diff ./fixtures/pub-rsa-2048.pkcs1.${keyid}pem ./fixtures/pub-rsa-2048.pkcs1.1.pem |
||||
|
if [ -z "$keyid" ]; then |
||||
|
echo "Pass" |
||||
|
fi |
||||
|
|
||||
|
if [ -z "$keyid" ]; then |
||||
|
echo "" |
||||
|
echo "Testing JWK-to-PEM PKCS#8" |
||||
|
fi |
||||
|
# |
||||
|
node bin/rasha.js ./fixtures/privkey-rsa-2048.jwk.${keyid}json pkcs8 \ |
||||
|
> ./fixtures/privkey-rsa-2048.pkcs8.1.pem |
||||
|
diff ./fixtures/privkey-rsa-2048.pkcs8.${keyid}pem ./fixtures/privkey-rsa-2048.pkcs8.1.pem |
||||
|
# |
||||
|
node bin/rasha.js ./fixtures/pub-rsa-2048.jwk.${keyid}json spki \ |
||||
|
> ./fixtures/pub-rsa-2048.spki.1.pem |
||||
|
diff ./fixtures/pub-rsa-2048.spki.${keyid}pem ./fixtures/pub-rsa-2048.spki.1.pem |
||||
|
if [ -z "$keyid" ]; then |
||||
|
echo "Pass" |
||||
|
fi |
||||
|
|
||||
|
if [ -z "$keyid" ]; then |
||||
|
echo "" |
||||
|
echo "[SKIP] Testing JWK-to-SSH" |
||||
|
fi |
||||
|
# |
||||
|
#node bin/rasha.js ./fixtures/privkey-rsa-2048.jwk.${keyid}json ssh > ./fixtures/pub-rsa-2048.ssh.1.pub |
||||
|
#diff ./fixtures/pub-rsa-2048.ssh.${keyid}pub ./fixtures/pub-rsa-2048.ssh.1.pub |
||||
|
# |
||||
|
#node bin/rasha.js ./fixtures/pub-rsa-2048.jwk.${keyid}json ssh > ./fixtures/pub-rsa-2048.ssh.1.pub |
||||
|
#diff ./fixtures/pub-rsa-2048.ssh.${keyid}pub ./fixtures/pub-rsa-2048.ssh.1.pub |
||||
|
if [ -z "$keyid" ]; then |
||||
|
echo "Pass" |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
rndkey() { |
||||
|
keyid="rnd.1." |
||||
|
keysize=$1 |
||||
|
# Generate 2048-bit RSA Keypair |
||||
|
openssl genrsa -out fixtures/privkey-rsa-2048.pkcs1.${keyid}pem $keysize |
||||
|
# Convert PKCS1 (traditional) RSA Keypair to PKCS8 format |
||||
|
openssl rsa -in fixtures/privkey-rsa-2048.pkcs1.${keyid}pem -pubout \ |
||||
|
-out fixtures/pub-rsa-2048.spki.${keyid}pem |
||||
|
# Export Public-only RSA Key in PKCS1 (traditional) format |
||||
|
openssl pkcs8 -topk8 -nocrypt -in fixtures/privkey-rsa-2048.pkcs1.${keyid}pem \ |
||||
|
-out fixtures/privkey-rsa-2048.pkcs8.${keyid}pem |
||||
|
# Convert PKCS1 (traditional) RSA Public Key to SPKI/PKIX format |
||||
|
openssl rsa -in fixtures/pub-rsa-2048.spki.${keyid}pem -pubin -RSAPublicKey_out \ |
||||
|
-out fixtures/pub-rsa-2048.pkcs1.${keyid}pem |
||||
|
# Convert RSA public key to SSH format |
||||
|
sshpub=$(ssh-keygen -f fixtures/pub-rsa-2048.spki.${keyid}pem -i -mPKCS8) |
||||
|
echo "$sshpub rsa@localhost" > fixtures/pub-rsa-2048.ssh.${keyid}pub |
||||
|
|
||||
|
|
||||
|
# to JWK |
||||
|
node bin/rasha.js ./fixtures/privkey-rsa-2048.pkcs1.${keyid}pem \ |
||||
|
> ./fixtures/privkey-rsa-2048.jwk.${keyid}json |
||||
|
node bin/rasha.js ./fixtures/pub-rsa-2048.pkcs1.${keyid}pem \ |
||||
|
> ./fixtures/pub-rsa-2048.jwk.${keyid}json |
||||
|
|
||||
|
pemtojwk "$keyid" |
||||
|
jwktopem "$keyid" |
||||
|
} |
||||
|
|
||||
|
pemtojwk "" |
||||
|
jwktopem "" |
||||
|
|
||||
|
echo "" |
||||
|
echo "testing node key generation" |
||||
|
echo "defaults" |
||||
|
node bin/rasha.js > /dev/null |
||||
|
echo "jwk" |
||||
|
node bin/rasha.js jwk > /dev/null |
||||
|
echo "json 2048" |
||||
|
node bin/rasha.js json 2048 > /dev/null |
||||
|
echo "der" |
||||
|
node bin/rasha.js der > /dev/null |
||||
|
echo "pkcs8 der" |
||||
|
node bin/rasha.js pkcs8 der > /dev/null |
||||
|
echo "pem" |
||||
|
node bin/rasha.js pem > /dev/null |
||||
|
echo "pkcs1" |
||||
|
node bin/rasha.js pkcs1 pem > /dev/null |
||||
|
echo "spki" |
||||
|
node bin/rasha.js spki > /dev/null |
||||
|
echo "PASS" |
||||
|
|
||||
|
echo "" |
||||
|
echo "" |
||||
|
echo "Re-running tests with random keys of varying sizes" |
||||
|
echo "" |
||||
|
|
||||
|
# commented out sizes below 512, since they are below minimum size on some systems. |
||||
|
# rndkey 32 # minimum key size |
||||
|
# rndkey 64 |
||||
|
# rndkey 128 |
||||
|
# rndkey 256 |
||||
|
|
||||
|
rndkey 512 |
||||
|
rndkey 768 |
||||
|
rndkey 1024 |
||||
|
rndkey 2048 # first secure key size |
||||
|
|
||||
|
if [ "${RASHA_TEST_LARGE_KEYS}" == "true" ]; then |
||||
|
rndkey 3072 |
||||
|
rndkey 4096 # largest reasonable key size |
||||
|
else |
||||
|
echo "" |
||||
|
echo "Note:" |
||||
|
echo "Keys larger than 2048 have been tested and work, but are omitted from automated tests to save time." |
||||
|
echo "Set RASHA_TEST_LARGE_KEYS=true to enable testing of keys up to 4096." |
||||
|
fi |
||||
|
|
||||
|
echo "" |
||||
|
echo "Pass" |
||||
|
|
||||
|
rm fixtures/*.1.* |
||||
|
|
||||
|
echo "" |
||||
|
echo "Testing Thumbprints" |
||||
|
node bin/rasha.js ./fixtures/privkey-rsa-2048.pkcs1.pem thumbprint |
||||
|
node bin/rasha.js ./fixtures/pub-rsa-2048.jwk.json thumbprint |
||||
|
echo "PASS" |
||||
|
|
||||
|
echo "" |
||||
|
echo "" |
||||
|
echo "PASSED:" |
||||
|
echo "• All inputs produced valid outputs" |
||||
|
echo "• All outputs matched known-good values" |
||||
|
echo "• All random tests passed reciprosity" |
Loading…
Reference in new issue