229 lines
8.1 KiB
JavaScript
229 lines
8.1 KiB
JavaScript
const encoder = new TextEncoder();
|
|
const decoder = new TextDecoder();
|
|
function buf(input) {
|
|
if (typeof input === 'string') {
|
|
return encoder.encode(input);
|
|
}
|
|
return decoder.decode(input);
|
|
}
|
|
function checkRsaKeyAlgorithm(algorithm) {
|
|
if (typeof algorithm.modulusLength !== 'number' || algorithm.modulusLength < 2048) {
|
|
throw new OperationProcessingError(`${algorithm.name} modulusLength must be at least 2048 bits`);
|
|
}
|
|
}
|
|
function subtleAlgorithm(key) {
|
|
switch (key.algorithm.name) {
|
|
case 'ECDSA':
|
|
return { name: key.algorithm.name, hash: 'SHA-256' };
|
|
case 'RSA-PSS':
|
|
checkRsaKeyAlgorithm(key.algorithm);
|
|
return {
|
|
name: key.algorithm.name,
|
|
saltLength: 256 >> 3,
|
|
};
|
|
case 'RSASSA-PKCS1-v1_5':
|
|
checkRsaKeyAlgorithm(key.algorithm);
|
|
return { name: key.algorithm.name };
|
|
case 'Ed25519':
|
|
return { name: key.algorithm.name };
|
|
}
|
|
throw new UnsupportedOperationError();
|
|
}
|
|
async function jwt(header, claimsSet, key) {
|
|
if (key.usages.includes('sign') === false) {
|
|
throw new TypeError('private CryptoKey instances used for signing assertions must include "sign" in their "usages"');
|
|
}
|
|
const input = `${b64u(buf(JSON.stringify(header)))}.${b64u(buf(JSON.stringify(claimsSet)))}`;
|
|
const signature = b64u(await crypto.subtle.sign(subtleAlgorithm(key), key, buf(input)));
|
|
return `${input}.${signature}`;
|
|
}
|
|
let encodeBase64Url;
|
|
if (Uint8Array.prototype.toBase64) {
|
|
encodeBase64Url = (input) => {
|
|
if (input instanceof ArrayBuffer) {
|
|
input = new Uint8Array(input);
|
|
}
|
|
return input.toBase64({ alphabet: 'base64url', omitPadding: true });
|
|
};
|
|
}
|
|
else {
|
|
const CHUNK_SIZE = 0x8000;
|
|
encodeBase64Url = (input) => {
|
|
if (input instanceof ArrayBuffer) {
|
|
input = new Uint8Array(input);
|
|
}
|
|
const arr = [];
|
|
for (let i = 0; i < input.byteLength; i += CHUNK_SIZE) {
|
|
arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
|
|
}
|
|
return btoa(arr.join('')).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
};
|
|
}
|
|
function b64u(input) {
|
|
return encodeBase64Url(input);
|
|
}
|
|
class UnsupportedOperationError extends Error {
|
|
constructor(message) {
|
|
var _a;
|
|
super(message !== null && message !== void 0 ? message : 'operation not supported');
|
|
this.name = this.constructor.name;
|
|
(_a = Error.captureStackTrace) === null || _a === void 0 ? void 0 : _a.call(Error, this, this.constructor);
|
|
}
|
|
}
|
|
class OperationProcessingError extends Error {
|
|
constructor(message) {
|
|
var _a;
|
|
super(message);
|
|
this.name = this.constructor.name;
|
|
(_a = Error.captureStackTrace) === null || _a === void 0 ? void 0 : _a.call(Error, this, this.constructor);
|
|
}
|
|
}
|
|
function psAlg(key) {
|
|
switch (key.algorithm.hash.name) {
|
|
case 'SHA-256':
|
|
return 'PS256';
|
|
default:
|
|
throw new UnsupportedOperationError('unsupported RsaHashedKeyAlgorithm hash name');
|
|
}
|
|
}
|
|
function rsAlg(key) {
|
|
switch (key.algorithm.hash.name) {
|
|
case 'SHA-256':
|
|
return 'RS256';
|
|
default:
|
|
throw new UnsupportedOperationError('unsupported RsaHashedKeyAlgorithm hash name');
|
|
}
|
|
}
|
|
function esAlg(key) {
|
|
switch (key.algorithm.namedCurve) {
|
|
case 'P-256':
|
|
return 'ES256';
|
|
default:
|
|
throw new UnsupportedOperationError('unsupported EcKeyAlgorithm namedCurve');
|
|
}
|
|
}
|
|
function determineJWSAlgorithm(key) {
|
|
switch (key.algorithm.name) {
|
|
case 'RSA-PSS':
|
|
return psAlg(key);
|
|
case 'RSASSA-PKCS1-v1_5':
|
|
return rsAlg(key);
|
|
case 'ECDSA':
|
|
return esAlg(key);
|
|
case 'Ed25519':
|
|
return 'Ed25519';
|
|
default:
|
|
throw new UnsupportedOperationError('unsupported CryptoKey algorithm name');
|
|
}
|
|
}
|
|
function isCryptoKey(key) {
|
|
return key instanceof CryptoKey;
|
|
}
|
|
function isPrivateKey(key) {
|
|
return isCryptoKey(key) && key.type === 'private';
|
|
}
|
|
function isPublicKey(key) {
|
|
return isCryptoKey(key) && key.type === 'public';
|
|
}
|
|
function epochTime() {
|
|
return Math.floor(Date.now() / 1000);
|
|
}
|
|
export async function generateProof(keypair, htu, htm, nonce, accessToken, additional) {
|
|
const privateKey = keypair === null || keypair === void 0 ? void 0 : keypair.privateKey;
|
|
const publicKey = keypair === null || keypair === void 0 ? void 0 : keypair.publicKey;
|
|
if (!isPrivateKey(privateKey)) {
|
|
throw new TypeError('"keypair.privateKey" must be a private CryptoKey');
|
|
}
|
|
if (!isPublicKey(publicKey)) {
|
|
throw new TypeError('"keypair.publicKey" must be a public CryptoKey');
|
|
}
|
|
if (publicKey.extractable !== true) {
|
|
throw new TypeError('"keypair.publicKey.extractable" must be true');
|
|
}
|
|
if (typeof htu !== 'string') {
|
|
throw new TypeError('"htu" must be a string');
|
|
}
|
|
if (typeof htm !== 'string') {
|
|
throw new TypeError('"htm" must be a string');
|
|
}
|
|
if (nonce !== undefined && typeof nonce !== 'string') {
|
|
throw new TypeError('"nonce" must be a string or undefined');
|
|
}
|
|
if (accessToken !== undefined && typeof accessToken !== 'string') {
|
|
throw new TypeError('"accessToken" must be a string or undefined');
|
|
}
|
|
if (additional !== undefined &&
|
|
(typeof additional !== 'object' || additional === null || Array.isArray(additional))) {
|
|
throw new TypeError('"additional" must be an object');
|
|
}
|
|
return jwt({
|
|
alg: determineJWSAlgorithm(privateKey),
|
|
typ: 'dpop+jwt',
|
|
jwk: await publicJwk(publicKey),
|
|
}, Object.assign(Object.assign({}, additional), { iat: epochTime(), jti: crypto.randomUUID(), htm,
|
|
nonce,
|
|
htu, ath: accessToken ? b64u(await crypto.subtle.digest('SHA-256', buf(accessToken))) : undefined }), privateKey);
|
|
}
|
|
async function publicJwk(key) {
|
|
const { kty, e, n, x, y, crv } = await crypto.subtle.exportKey('jwk', key);
|
|
return { kty, crv, e, n, x, y };
|
|
}
|
|
export async function generateKeyPair(alg, options) {
|
|
var _a;
|
|
let algorithm;
|
|
if (typeof alg !== 'string' || alg.length === 0) {
|
|
throw new TypeError('"alg" must be a non-empty string');
|
|
}
|
|
switch (alg) {
|
|
case 'PS256':
|
|
algorithm = {
|
|
name: 'RSA-PSS',
|
|
hash: 'SHA-256',
|
|
modulusLength: 2048,
|
|
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
|
};
|
|
break;
|
|
case 'RS256':
|
|
algorithm = {
|
|
name: 'RSASSA-PKCS1-v1_5',
|
|
hash: 'SHA-256',
|
|
modulusLength: 2048,
|
|
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
|
};
|
|
break;
|
|
case 'ES256':
|
|
algorithm = { name: 'ECDSA', namedCurve: 'P-256' };
|
|
break;
|
|
case 'Ed25519':
|
|
algorithm = { name: 'Ed25519' };
|
|
break;
|
|
default:
|
|
throw new UnsupportedOperationError();
|
|
}
|
|
return (crypto.subtle.generateKey(algorithm, (_a = options === null || options === void 0 ? void 0 : options.extractable) !== null && _a !== void 0 ? _a : false, ['sign', 'verify']));
|
|
}
|
|
export async function calculateThumbprint(publicKey) {
|
|
if (!isPublicKey(publicKey)) {
|
|
throw new TypeError('"publicKey" must be a public CryptoKey');
|
|
}
|
|
if (publicKey.extractable !== true) {
|
|
throw new TypeError('"publicKey.extractable" must be true');
|
|
}
|
|
const jwk = await publicJwk(publicKey);
|
|
let components;
|
|
switch (jwk.kty) {
|
|
case 'EC':
|
|
components = { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y };
|
|
break;
|
|
case 'OKP':
|
|
components = { crv: jwk.crv, kty: jwk.kty, x: jwk.x };
|
|
break;
|
|
case 'RSA':
|
|
components = { e: jwk.e, kty: jwk.kty, n: jwk.n };
|
|
break;
|
|
default:
|
|
throw new UnsupportedOperationError('unsupported JWK kty');
|
|
}
|
|
return b64u(await crypto.subtle.digest({ name: 'SHA-256' }, buf(JSON.stringify(components))));
|
|
}
|
|
//# sourceMappingURL=index.js.map
|