oidc-client
Version:
OpenID Connect (OIDC) & OAuth2 client library
296 lines (252 loc) • 8.3 kB
JavaScript
/*
Based on the work of Auth0
https://github.com/auth0/idtoken-verifier
https://github.com/auth0/idtoken-verifier/blob/master/LICENSE
Which is based on the work of Tom Wu
http://www-cs-students.stanford.edu/~tjw/jsbn/
http://www-cs-students.stanford.edu/~tjw/jsbn/LICENSE
*/
/*
* To support most basic OpenId use cases (using RSA256), we can get away without
* requiring the full jrsasign feature set (and resulting massive bundle).
*
* - Support RSA 256 algorithm (optionally could support RSA* family)
* - Parse JWT tokens using the (n) parameter.
* - Verify signature of id_tokens
* - Verify at_hash of access_tokens
* - Perform common base64 encoding/decoding tasks.
*/
import JSBN from 'jsbn';
import SHA256 from 'crypto-js/sha256';
import base64Js from 'base64-js';
var BigInteger = JSBN.BigInteger;
/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/
*/
var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var b64pad = "=";
const Base64 = {
b64tohex(s) {
var ret = "";
var i;
var k = 0; // b64 state, 0-3
var slop;
for(i = 0; i < s.length; ++i) {
if(s.charAt(i) === b64pad) break;
var v = b64map.indexOf(s.charAt(i));
if(v < 0) continue;
if(k === 0) {
ret += String.fromCharCode(v >> 2);
slop = v & 3;
k = 1;
}
else if(k === 1) {
ret += String.fromCharCode((slop << 2) | (v >> 4));
slop = v & 0xf;
k = 2;
}
else if(k === 2) {
ret += String.fromCharCode(slop);
ret += String.fromCharCode(v >> 2);
slop = v & 3;
k = 3;
}
else {
ret += String.fromCharCode((slop << 2) | (v >> 4));
ret += String.fromCharCode(v & 0xf);
k = 0;
}
}
if(k === 1)
ret += String.fromCharCode(slop << 2);
return ret;
},
hexToBase64(h) {
var i;
var c;
var ret = "";
for(i = 0; i+3 <= h.length; i+=3) {
c = parseInt(h.substring(i,i+3),16);
ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63);
}
if(i+1 === h.length) {
c = parseInt(h.substring(i,i+1),16);
ret += b64map.charAt(c << 2);
}
else if(i+2 === h.length) {
c = parseInt(h.substring(i,i+2),16);
ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4);
}
if (b64pad) while((ret.length & 3) > 0) ret += b64pad;
return ret;
},
padding(str) {
var mod = (str.length % 4);
var pad = 4 - mod;
if (mod === 0) {
return str;
}
return str + (new Array(1 + pad)).join('=');
},
byteArrayToHex(raw) {
var HEX = '';
for (var i = 0; i < raw.length; i++) {
var _hex = raw[i].toString(16);
HEX += (_hex.length === 2 ? _hex : '0' + _hex);
}
return HEX;
},
decodeToHEX(str) {
return Base64.byteArrayToHex(base64Js.toByteArray(Base64.padding(str)));
},
base64ToBase64Url(s) {
s = s.replace(/=/g, "");
s = s.replace(/\+/g, "-");
s = s.replace(/\//g, "_");
return s;
},
urlDecode(str) {
str = str.replace(/-/g, '+') // Convert '-' to '+'
.replace(/_/g, '/') // Convert '_' to '/'
.replace(/\s/g, ' '); // Convert '\s' to ' '
return atob(str);
}
};
var DigestInfoHead = {
sha1: '3021300906052b0e03021a05000414',
sha224: '302d300d06096086480165030402040500041c',
sha256: '3031300d060960864801650304020105000420',
sha384: '3041300d060960864801650304020205000430',
sha512: '3051300d060960864801650304020305000440',
md2: '3020300c06082a864886f70d020205000410',
md5: '3020300c06082a864886f70d020505000410',
ripemd160: '3021300906052b2403020105000414'
};
var DigestAlgs = {
sha256: SHA256,
SHA256:SHA256
};
function RSAVerifier(modulus, exp) {
this.n = null;
this.e = 0;
if (modulus != null && exp != null && modulus.length > 0 && exp.length > 0) {
this.n = new BigInteger(modulus, 16);
this.e = parseInt(exp, 16);
} else {
throw new Error('Invalid key data');
}
}
function getAlgorithmFromDigest(hDigestInfo) {
for (var algName in DigestInfoHead) {
var head = DigestInfoHead[algName];
var len = head.length;
if (hDigestInfo.substring(0, len) === head) {
return {
alg: algName,
hash: hDigestInfo.substring(len)
};
}
}
return [];
}
RSAVerifier.prototype.verify = function (msg, encsig) {
encsig = Base64.decodeToHEX(encsig);
encsig = encsig.replace(/[^0-9a-f]|[\s\n]]/ig, '');
var sig = new BigInteger(encsig, 16);
if (sig.bitLength() > this.n.bitLength()) {
throw new Error('Signature does not match with the key modulus.');
}
var decryptedSig = sig.modPowInt(this.e, this.n);
var digest = decryptedSig.toString(16).replace(/^1f+00/, '');
var digestInfo = getAlgorithmFromDigest(digest);
if (digestInfo.length === 0) {
return false;
}
if (!DigestAlgs.hasOwnProperty(digestInfo.alg)) {
throw new Error('Hashing algorithm is not supported.');
}
var msgHash = DigestAlgs[digestInfo.alg](msg).toString();
return (digestInfo.hash === msgHash);
};
const AllowedSigningAlgs = ['RS256'];
const jws = {
JWS: {
parse: function(token) {
var parts = token.split('.');
var header;
var payload;
// This diverges from Auth0's implementation, which throws rather than
// returning undefined
if (parts.length !== 3) {
return undefined;
}
try {
header = JSON.parse(Base64.urlDecode(parts[0]));
payload = JSON.parse(Base64.urlDecode(parts[1]));
} catch (e) {
return new Error('Token header or payload is not valid JSON');
}
return {
headerObj: header,
payloadObj: payload,
};
},
verify: function(jwt, key, allowedSigningAlgs = []) {
allowedSigningAlgs.forEach((alg) => {
if (AllowedSigningAlgs.indexOf(alg) === -1) {
throw new Error('Invalid signing algorithm: ' + alg);
}
});
var verify = new RSAVerifier(key.n, key.e);
var parts = jwt.split('.');
var headerAndPayload = [parts[0], parts[1]].join('.');
return verify.verify(headerAndPayload, parts[2]);
},
},
};
const KeyUtil = {
/**
* Returns decoded keys in Hex format for use in crypto functions.
* Supports modulus/exponent-style keys.
*
* @param {object} key the security key
* @returns
*/
getKey(key) {
if (key.kty === 'RSA') {
return {
e: Base64.decodeToHEX(key.e),
n: Base64.decodeToHEX(key.n),
};
}
return null;
},
};
const X509 = {
getPublicKeyFromCertPEM: function() {
throw new Error('Not implemented. Use the full oidc-client library if you need support for X509.');
},
};
const crypto = {
Util: {
hashString: function(value, alg) {
var hashFunc = DigestAlgs[alg];
return hashFunc(value).toString();
},
}
};
function hextob64u(s) {
if (s.length % 2 === 1) {
s = '0' + s;
}
return Base64.base64ToBase64Url(Base64.hexToBase64(s));
}
const {b64tohex} = Base64;
export {
jws,
KeyUtil,
X509,
crypto,
hextob64u,
b64tohex,
AllowedSigningAlgs
};