109 lines
2.5 KiB
JavaScript
109 lines
2.5 KiB
JavaScript
|
'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';
|
||
|
}
|
||
|
};
|