224 lines
4.9 KiB
JavaScript
224 lines
4.9 KiB
JavaScript
/*!
|
|
* jwk/octkey.js - Symmetric Octet Key Representation
|
|
*
|
|
* Copyright (c) 2015 Cisco Systems, Inc. See LICENSE file.
|
|
*/
|
|
"use strict";
|
|
|
|
var util = require("../util");
|
|
|
|
var JWK = {
|
|
BaseKey: require("./basekey.js"),
|
|
helpers: require("./helpers.js")
|
|
};
|
|
|
|
var SIG_ALGS = [
|
|
"HS256",
|
|
"HS384",
|
|
"HS512"
|
|
];
|
|
var ENC_ALGS = [
|
|
"A128GCM",
|
|
"A192GCM",
|
|
"A256GCM",
|
|
"A128CBC-HS256",
|
|
"A192CBC-HS384",
|
|
"A256CBC-HS512",
|
|
"A128CBC+HS256",
|
|
"A192CBC+HS384",
|
|
"A256CBC+HS512"
|
|
];
|
|
var WRAP_ALGS = [
|
|
"A128KW",
|
|
"A192KW",
|
|
"A256KW",
|
|
"A128GCMKW",
|
|
"A192GCMKW",
|
|
"A256GCMKW",
|
|
"PBES2-HS256+A128KW",
|
|
"PBES2-HS384+A192KW",
|
|
"PBES2-HS512+A256KW",
|
|
"dir"
|
|
];
|
|
|
|
function adjustDecryptProps(alg, props) {
|
|
if ("iv" in props) {
|
|
props.iv = Buffer.isBuffer(props.iv) ?
|
|
props.iv :
|
|
util.base64url.decode(props.iv || "");
|
|
}
|
|
if ("adata" in props) {
|
|
props.adata = Buffer.isBuffer(props.adata) ?
|
|
props.adata :
|
|
Buffer.from(props.adata || "", "utf8");
|
|
}
|
|
if ("mac" in props) {
|
|
props.mac = Buffer.isBuffer(props.mac) ?
|
|
props.mac :
|
|
util.base64url.decode(props.mac || "");
|
|
}
|
|
if ("tag" in props) {
|
|
props.tag = Buffer.isBuffer(props.tag) ?
|
|
props.tag :
|
|
util.base64url.decode(props.tag || "");
|
|
}
|
|
|
|
return props;
|
|
}
|
|
function adjustEncryptProps(alg, props) {
|
|
if ("iv" in props) {
|
|
props.iv = Buffer.isBuffer(props.iv) ?
|
|
props.iv :
|
|
util.base64url.decode(props.iv || "");
|
|
}
|
|
if ("adata" in props) {
|
|
props.adata = Buffer.isBuffer(props.adata) ?
|
|
props.adata :
|
|
Buffer.from(props.adata || "", "utf8");
|
|
}
|
|
|
|
return props;
|
|
}
|
|
|
|
var JWKOctetCfg = {
|
|
publicKey: function(props) {
|
|
var fields = JWK.helpers.COMMON_PROPS.concat([
|
|
]);
|
|
|
|
var pk;
|
|
pk = JWK.helpers.unpackProps(props, fields);
|
|
|
|
return pk;
|
|
},
|
|
privateKey: function(props) {
|
|
var fields = JWK.helpers.COMMON_PROPS.concat([
|
|
{name: "k", type: "binary"}
|
|
]);
|
|
|
|
var pk;
|
|
pk = JWK.helpers.unpackProps(props, fields);
|
|
if (pk && pk.k) {
|
|
pk.length = pk.k.length * 8;
|
|
} else {
|
|
pk = undefined;
|
|
}
|
|
|
|
return pk;
|
|
},
|
|
|
|
thumbprint: function(json) {
|
|
if (json.private) {
|
|
json = json.private;
|
|
}
|
|
var fields;
|
|
fields = {
|
|
k: json.k || "",
|
|
kty: "oct"
|
|
};
|
|
return fields;
|
|
},
|
|
algorithms: function(keys, mode) {
|
|
var len = keys.private && (keys.private.k.length * 8);
|
|
var mins = [256, 384, 512];
|
|
|
|
if (!len) {
|
|
return [];
|
|
}
|
|
switch (mode) {
|
|
case "encrypt":
|
|
case "decrypt":
|
|
return ENC_ALGS.filter(function(a) {
|
|
return (a === ("A" + (len / 2) + "CBC-HS" + len)) ||
|
|
(a === ("A" + (len / 2) + "CBC+HS" + len)) ||
|
|
(a === ("A" + len + "GCM"));
|
|
});
|
|
case "sign":
|
|
case "verify":
|
|
// TODO: allow for HS{less-than-keysize}
|
|
return SIG_ALGS.filter(function(a) {
|
|
var result = false;
|
|
mins.forEach(function(m) {
|
|
if (m > len) { return; }
|
|
result = result | (a === ("HS" + m));
|
|
});
|
|
return result;
|
|
});
|
|
case "wrap":
|
|
case "unwrap":
|
|
return WRAP_ALGS.filter(function(a) {
|
|
return (a === ("A" + len + "KW")) ||
|
|
(a === ("A" + len + "GCMKW")) ||
|
|
(a.indexOf("PBES2-") === 0) ||
|
|
(a === "dir");
|
|
});
|
|
}
|
|
|
|
return [];
|
|
},
|
|
encryptKey: function(alg, keys) {
|
|
return keys.private && keys.private.k;
|
|
},
|
|
encryptProps: adjustEncryptProps,
|
|
|
|
decryptKey: function(alg, keys) {
|
|
return keys.private && keys.private.k;
|
|
},
|
|
decryptProps: adjustDecryptProps,
|
|
|
|
wrapKey: function(alg, keys) {
|
|
return keys.private && keys.private.k;
|
|
},
|
|
wrapProps: adjustEncryptProps,
|
|
|
|
unwrapKey: function(alg, keys) {
|
|
return keys.private && keys.private.k;
|
|
},
|
|
unwrapProps: adjustDecryptProps,
|
|
|
|
signKey: function(alg, keys) {
|
|
return keys.private && keys.private.k;
|
|
},
|
|
verifyKey: function(alg, keys) {
|
|
return keys.private && keys.private.k;
|
|
}
|
|
};
|
|
|
|
// Factory
|
|
var JWKOctetFactory = {
|
|
kty: "oct",
|
|
prepare: function(props) {
|
|
// TODO: validate key properties
|
|
var cfg = JWKOctetCfg;
|
|
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) {
|
|
// TODO: validate key sizes
|
|
var key = util.randomBytes(size / 8);
|
|
|
|
return Promise.resolve({
|
|
k: key
|
|
});
|
|
}
|
|
};
|
|
|
|
// public API
|
|
module.exports = Object.freeze({
|
|
config: JWKOctetCfg,
|
|
factory: JWKOctetFactory
|
|
});
|
|
|
|
// registration
|
|
(function(REGISTRY) {
|
|
REGISTRY.register(JWKOctetFactory);
|
|
})(require("./keystore").registry);
|