oidc-lib
Version:
A library for creating OIDC Service Providers
326 lines (295 loc) • 8.69 kB
JavaScript
module.exports = {
'registerEndpoints': registerEndpoints,
'random': random,
'encrypt_symmetric': encrypt_symmetric,
'decrypt_symmetric': decrypt_symmetric,
'verify_signature': verify_signature,
'digestSha256': digestSha256,
'jwkThumbprint': jwkThumbprint,
'createNumericCode': createNumericCode,
'createB64Code': createB64Code,
'claimHash': claimHash,
'randomString': randomString,
'oidc3136Hash': oidc3136Hash
};
var pk;
var default_sym_key;
var platform_crypto;
async function registerEndpoints(global_pk){
pk = global_pk;
var conf_key = pk.util.config.default_sym_key;
if (conf_key){
default_sym_key = JSON.parse(pk.util.config.default_sym_key);
}
platform_crypto = pk.util.usingNode() ? require('crypto') : window.crypto;
}
function random(options){
if (options.kind !== 'code'){
options.kind = 'base64url';
}
if (!options.length){
options.length = 48;
}
return new Promise((resolve, reject) => {
if (pk.util.usingNode()){
platform_crypto.randomBytes(options.length, (err, buf) => {
if (err){
reject(err);
} else {
process(options, buf, resolve, reject);
}
});
}
else{
var buf = crypto.getRandomValues(new Uint8Array(options.length));
process(options, buf, resolve, reject);
}
});
function process(options, buf, resolve, reject){
if (options.kind === 'code'){
var result = "";
for (var i=0; i<options.length; i++){
var digit = buf[i] % 10;
result += String.fromCharCode(0x30 + digit);
}
resolve(result);
}
else{
resolve(pk.base64url.encode(buf));
}
}
}
async function encrypt_symmetric(input){
if (!pk.util.usingNode()){
alert('simple_crypto encrypt_symetric called in windows')
return;
}
if (typeof input === "object"){
input = JSON.stringify(input);
}
var b64 = pk.base64url(input);
var key = await pk.jose.JWK.asKey(default_sym_key);
var cipher = await pk.jose.JWE.createEncrypt({ format: 'compact' }, key).update(b64).final();
return cipher;
}
async function decrypt_symmetric(cipher, isString, label){
if (!pk.util.usingNode()){
alert('simple_crypto encrypt_symetric called in windows')
return;
}
if (!cipher){
throw("no cipher in decrypt" + (label ? (" of " + label) : ""));
}
var key = await pk.jose.JWK.asKey(default_sym_key)
var decryptResult = await pk.jose.JWE.createDecrypt(key).decrypt(cipher);
var result = pk.base64url.decode(new Buffer(decryptResult.plaintext).toString('utf8'));
if (!isString){
result = JSON.parse(result);
}
return (result);
}
async function verify_signature(signature, input_jwk){
if (!pk.util.usingNode()){
alert('simple_crypto verify_signature called in windows but not implemented there')
return;
}
var possible_err;
try{
var jwk = input_jwk;
if (typeof input_jwk !== "object"){
jwk = JSON.parse(input_jwk);
}
var signature_components = signature.split('.');
if (!signature_components){
throw('invalid signature in verify signature - ' + signature);
}
possible_err = 'Error parsing signature components[0]';
var signature_header = JSON.parse(pk.base64url.decode(signature_components[0]));
var signature_alg = signature_header.alg;
var verified_result;
switch (signature_alg){
case 'RS256':
case 'ES256':
case 'ES384':
case 'ES512':
possible_err = 'Error creating jose key: ';
var keyStore = pk.jose.JWK.createKeyStore();
var jose_key = await keyStore.add(jwk);
var permittedAlgs = ['RS256', 'ES256', 'ES384', 'ES512'];
verified_result = await pk.jose.JWS.createVerify(jose_key, {algorithms:permittedAlgs}).verify(signature);
break;
case 'EdDSA':
case 'Ed25519':
possible_err = 'Error performing EdDSA verify';
var publicKey_buffer = pk.base64url.toBuffer(jwk.x);
// ed25519.Verify(Buffer.from(message, 'utf8'), signature, aliceKeypair.publicKey))
var toBeSigned = signature_components[0] + '.' + signature_components[1];
var toBeSigned_buffer = Buffer.from(toBeSigned, 'utf8');
var signature_buffer = pk.base64url.toBuffer(signature_components[2]);
verified_result = pk.forge.pki.ed25519.verify({
message: toBeSigned_buffer,
signature: signature_buffer,
publicKey: publicKey_buffer
});
break;
default:
throw('unsupported algorithm - ' + signature_alg);
}
return verified_result;
}
catch(err){
pk.util.log_error('verify_signature', possible_err, err);
var msg = 'verify_signature - ' + possible_err + ': ' + err.message;
throw(msg);
}
}
function digestSha256(input, asBuffer){
return new Promise((resolve, reject) => {
if (pk.util.usingNode()){
var hasher = platform_crypto.createHash("sha256");
hasher.update(input, "utf-8");
if (asBuffer){
resolve(hasher.digest());
}
else{
var b64 = hasher.digest("base64");
resolve(pk.base64url.fromBase64(b64));
}
}
else {
var buffer = Buffer(input, 'utf8');
platform_crypto.subtle.digest("SHA-256", buffer)
.then( buf => {
if (asBuffer){
resolve(buf)
}
else{
resolve(pk.base64url.encode(buf));
}
})
}
});
}
function jwkThumbprint(jwk_key){
var toBeSigned;
switch (jwk_key.kty){
case 'RSA':
toBeSigned = {
e: jwk_key.e,
kty: jwk_key.kty,
n: jwk_key.n
};
break;
case 'EC':
toBeSigned = {
crv: jwk_key.crv,
kty: jwk_key.kty,
x: jwk_key.x,
y: jwk_key.y
};
break;
case 'oct':
toBeSigned = {
k: jwk_key.k,
kty: jwk_key.kty
};
break;
case 'OKP':
toBeSigned = {
crv: jwk_key.crv,
kty: 'OKP',
x: jwk_key.x
}
break;
default:
throw "Undefined key type " + jwk_key.kty + " in jwkThumbprint";
}
var toBeSignedString = JSON.stringify(toBeSigned);
return digestSha256(toBeSignedString);
}
function createNumericCode(size){
return new Promise((resolve, reject) => {
platform_crypto.randomBytes(size, function(err, buf){
if (err){
reject(err);
} else {
var result = "";
for (var i=0; i<size; i++){
var digit = buf[i] % 10;
result += String.fromCharCode(0x30 + digit);
}
resolve(result);
}
});
});
}
function createB64Code(size){
return new Promise((resolve, reject) => {
if (pk.util.usingNode()){
platform_crypto.randomBytes(size, function(err, buf){
if (err){
reject(err);
} else {
resolve(pk.base64url.encode(buf));
}
});
}
else {
var vector = crypto.getRandomValues(new Uint8Array(size));
var result = pk.base64url.encode(vector);
resolve(result);
}
});
}
function claimHash(alg, salt, claimValue){
var hasher = platform_crypto.createHash(alg);
if (typeof claimValue !== 'string'){
claimValue = JSON.stringify(claimValue);
}
hasher.update(salt + claimValue, "utf-8");
var b64 = hasher.digest("base64");
return b64;
}
function randomString() {
return new Promise((resolve, reject) => {
if (pk.util.usingNode()){
platform_crypto.randomBytes(48, function(err, buf){
if (err){
reject(err);
} else {
resolve(pk.base64url.encode(buf));
}
});
}
else {
var vector = crypto.getRandomValues(new Uint8Array(16));
var result = pk.base64url.encode(vector);
resolve(result);
}
});
}
// Defined in OIDC 3.1.3.6 access_token hash.
function oidc3136Hash(response_type, match, input){
return new Promise((resolve, reject) => {
// if the response_type doesn't require the hash, return null
var response_type_array = response_type.split(' ');
if (response_type_array.indexOf(match) < 0){
resolve(null);
}
else{
if (pk.util.usingNode()){
// var buffer = Buffer(input, 'ascii');
// resolve(platform_crypto.subtle.digest("SHA-256", buffer));
var hasher = platform_crypto.createHash("sha256");
hasher.update(input, "ascii");
var full = hasher.digest();
var half = full.slice(0, full.length/2);
resolve(pk.base64url(half, "ascii"));
}
else {
var buffer = Buffer(input, 'ascii');
resolve(platform_crypto.subtle.digest("SHA-256", buffer));
}
}
});
}