node-jose
Version:
A JavaScript implementation of the JSON Object Signing and Encryption (JOSE) for current web browsers and node.js-based servers
346 lines (327 loc) • 8.28 kB
JavaScript
/*!
* jwk/rsa.js - RSA Key Representation
*
* Copyright (c) 2015 Cisco Systems, Inc. See LICENSE file.
*/
;
var ecutil = require("../algorithms/ec-util.js"),
forge = require("../deps/forge"),
depsecc = require("../deps/ecc");
var JWK = {
BaseKey: require("./basekey.js"),
helpers: require("./helpers.js")
};
var SIG_ALGS = [
"ES256",
"ES384",
"ES512"
];
var WRAP_ALGS = [
"ECDH-ES",
"ECDH-ES+A128KW",
"ECDH-ES+A192KW",
"ECDH-ES+A256KW"
];
var EC_OID = ecutil.EC_OID;
function oidToCurveName(oid) {
switch (oid) {
case "1.2.840.10045.3.1.7":
return "P-256";
case "1.3.132.0.34":
return "P-384";
case "1.3.132.0.35":
return "P-521";
default:
return null;
}
}
var JWKEcCfg = {
publicKey: function(props) {
var fields = JWK.helpers.COMMON_PROPS.concat([
{name: "crv", type: "string"},
{name: "x", type: "binary"},
{name: "y", type: "binary"}
]);
var pk = JWK.helpers.unpackProps(props, fields);
if (pk && pk.crv && pk.x && pk.y) {
pk.length = ecutil.curveSize(pk.crv);
} else {
delete pk.crv;
delete pk.x;
delete pk.y;
}
return pk;
},
privateKey: function(props) {
var fields = JWK.helpers.COMMON_PROPS.concat([
{name: "crv", type: "string"},
{name: "x", type: "binary"},
{name: "y", type: "binary"},
{name: "d", type: "binary"}
]);
var pk = JWK.helpers.unpackProps(props, fields);
if (pk && pk.crv && pk.x && pk.y && pk.d) {
pk.length = ecutil.curveSize(pk.crv);
} else {
pk = undefined;
}
return pk;
},
thumbprint: function(json) {
if (json.public) {
json = json.public;
}
var fields = {
crv: json.crv,
kty: "EC",
x: json.x,
y: json.y
};
return fields;
},
algorithms: function(keys, mode) {
var len = (keys.public && keys.public.length) ||
(keys.private && keys.private.length) ||
0;
// NOTE: 521 is the actual, but 512 is the expected
if (len === 521) {
len = 512;
}
switch (mode) {
case "encrypt":
case "decrypt":
return [];
case "wrap":
return (keys.public && WRAP_ALGS) || [];
case "unwrap":
return (keys.private && WRAP_ALGS) || [];
case "sign":
if (!keys.private) {
return [];
}
return SIG_ALGS.filter(function(a) {
return (a === ("ES" + len));
});
case "verify":
if (!keys.public) {
return [];
}
return SIG_ALGS.filter(function(a) {
return (a === ("ES" + len));
});
}
},
encryptKey: function(alg, keys) {
return keys.public;
},
decryptKey: function(alg, keys) {
return keys.private;
},
wrapKey: function(alg, keys) {
return keys.public;
},
unwrapKey: function(alg, keys) {
return keys.private;
},
signKey: function(alg, keys) {
return keys.private;
},
verifyKey: function(alg, keys) {
return keys.public;
}
};
JWKEcCfg.convertToPEM = ecutil.convertToPEM;
// Inspired by digitalbaazar/node-forge/js/rsa.js
var validators = {
oid: EC_OID,
privateKey: {
// ECPrivateKey
name: "ECPrivateKey",
tagClass: forge.asn1.Class.UNIVERSAL,
type: forge.asn1.Type.SEQUENCE,
constructed: true,
value: [
{
// EC version
name: "ECPrivateKey.version",
tagClass: forge.asn1.Class.UNIVERSAL,
type: forge.asn1.Type.INTEGER,
constructed: false
},
{
// private value (d)
name: "ECPrivateKey.private",
tagClass: forge.asn1.Class.UNIVERSAL,
type: forge.asn1.Type.OCTETSTRING,
constructed: false,
capture: "d"
},
{
// EC parameters
tagClass: forge.asn1.Class.CONTEXT_SPECIFIC,
name: "ECPrivateKey.parameters",
constructed: true,
value: [
{
// namedCurve (crv)
name: "ECPrivateKey.namedCurve",
tagClass: forge.asn1.Class.UNIVERSAL,
type: forge.asn1.Type.OID,
constructed: false,
capture: "crv"
}
]
},
{
// publicKey
name: "ECPrivateKey.publicKey",
tagClass: forge.asn1.Class.CONTEXT_SPECIFIC,
constructed: true,
value: [
{
name: "ECPrivateKey.point",
tagClass: forge.asn1.Class.UNIVERSAL,
type: forge.asn1.Type.BITSTRING,
constructed: false,
capture: "point"
}
]
}
]
},
embeddedPrivateKey: {
// ECPrivateKey
name: "ECPrivateKey",
tagClass: forge.asn1.Class.UNIVERSAL,
type: forge.asn1.Type.SEQUENCE,
constructed: true,
value: [
{
// EC version
name: "ECPrivateKey.version",
tagClass: forge.asn1.Class.UNIVERSAL,
type: forge.asn1.Type.INTEGER,
constructed: false
},
{
// private value (d)
name: "ECPrivateKey.private",
tagClass: forge.asn1.Class.UNIVERSAL,
type: forge.asn1.Type.OCTETSTRING,
constructed: false,
capture: "d"
},
{
// publicKey
name: "ECPrivateKey.publicKey",
tagClass: forge.asn1.Class.CONTEXT_SPECIFIC,
constructed: true,
value: [
{
name: "ECPrivateKey.point",
tagClass: forge.asn1.Class.UNIVERSAL,
type: forge.asn1.Type.BITSTRING,
constructed: false,
capture: "point"
}
]
}
]
}
};
var JWKEcFactory = {
kty: "EC",
validators: validators,
prepare: function(props) {
// TODO: validate key properties
var cfg = JWKEcCfg;
var p = Promise.resolve(props);
p = p.then(function(json) {
return JWK.helpers.thumbprint(cfg, json);
});
p = p.then(function(hash) {
var prints = {};
prints[JWK.helpers.INTERNALS.THUMBPRINT_HASH] = hash;
props[JWK.helpers.INTERNALS.THUMBPRINT_KEY] = prints;
return cfg;
});
return p;
},
generate: function(size) {
var keypair = depsecc.generateKeyPair(size);
var result = {
"crv": size,
"x": keypair.public.x,
"y": keypair.public.y,
"d": keypair.private.d
};
return Promise.resolve(result);
},
import: function(input) {
if (validators.oid !== input.keyOid) {
return null;
}
// coerce key params to OID
var crv;
if (input.keyParams && forge.asn1.Type.OID === input.keyParams.type) {
crv = forge.asn1.derToOid(input.keyParams.value);
crv = oidToCurveName(crv);
} else if (input.crv) {
crv = forge.asn1.derToOid(input.crv);
crv = oidToCurveName(crv);
}
if (!crv) {
return null;
}
if (!input.parsed) {
var capture = {},
errors = [];
if ("private" === input.type) {
// coerce capture.value to DER *iff* private
if ("string" === typeof input.keyValue) {
input.keyValue = forge.asn1.fromDer(input.keyValue);
} else if (Array.isArray(input.keyValue)) {
input.keyValue = input.keyValue[0];
}
if (!forge.asn1.validate(input.keyValue,
validators.embeddedPrivateKey,
capture,
errors)) {
return null;
}
} else {
capture.point = input.keyValue;
}
input = capture;
}
// convert factors to Buffers
var output = {
kty: "EC",
crv: crv
};
if (input.d) {
output.d = Buffer.from(input.d, "binary");
}
if (input.point) {
var pt = Buffer.from(input.point, "binary");
// only support uncompressed
if (4 !== pt.readUInt16BE(0)) {
return null;
}
pt = pt.slice(2);
var len = pt.length / 2;
output.x = pt.slice(0, len);
output.y = pt.slice(len);
}
return output;
}
};
// public API
module.exports = Object.freeze({
config: JWKEcCfg,
factory: JWKEcFactory
});
// registration
(function(REGISTRY) {
REGISTRY.register(JWKEcFactory);
})(require("./keystore").registry);