@azure/msal-browser
Version:
Microsoft Authentication Library for js
254 lines (251 loc) • 12.4 kB
JavaScript
/*! @azure/msal-browser v2.28.1 2022-08-01 */
'use strict';
import { __awaiter, __generator } from '../_virtual/_tslib.js';
import { PerformanceEvents, JoseHeader } from '@azure/msal-common';
import { GuidGenerator } from './GuidGenerator.js';
import { Base64Encode } from '../encode/Base64Encode.js';
import { Base64Decode } from '../encode/Base64Decode.js';
import { PkceGenerator } from './PkceGenerator.js';
import { BrowserCrypto } from './BrowserCrypto.js';
import { BrowserStringUtils } from '../utils/BrowserStringUtils.js';
import { BrowserAuthError } from '../error/BrowserAuthError.js';
import { AsyncMemoryStorage } from '../cache/AsyncMemoryStorage.js';
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var CryptoKeyStoreNames;
(function (CryptoKeyStoreNames) {
CryptoKeyStoreNames["asymmetricKeys"] = "asymmetricKeys";
CryptoKeyStoreNames["symmetricKeys"] = "symmetricKeys";
})(CryptoKeyStoreNames || (CryptoKeyStoreNames = {}));
/**
* This class implements MSAL's crypto interface, which allows it to perform base64 encoding and decoding, generating cryptographically random GUIDs and
* implementing Proof Key for Code Exchange specs for the OAuth Authorization Code Flow using PKCE (rfc here: https://tools.ietf.org/html/rfc7636).
*/
var CryptoOps = /** @class */ (function () {
function CryptoOps(logger, performanceClient) {
this.logger = logger;
// Browser crypto needs to be validated first before any other classes can be set.
this.browserCrypto = new BrowserCrypto(this.logger);
this.b64Encode = new Base64Encode();
this.b64Decode = new Base64Decode();
this.guidGenerator = new GuidGenerator(this.browserCrypto);
this.pkceGenerator = new PkceGenerator(this.browserCrypto);
this.cache = {
asymmetricKeys: new AsyncMemoryStorage(this.logger, CryptoKeyStoreNames.asymmetricKeys),
symmetricKeys: new AsyncMemoryStorage(this.logger, CryptoKeyStoreNames.symmetricKeys)
};
this.performanceClient = performanceClient;
}
/**
* Creates a new random GUID - used to populate state and nonce.
* @returns string (GUID)
*/
CryptoOps.prototype.createNewGuid = function () {
return this.guidGenerator.generateGuid();
};
/**
* Encodes input string to base64.
* @param input
*/
CryptoOps.prototype.base64Encode = function (input) {
return this.b64Encode.encode(input);
};
/**
* Decodes input string from base64.
* @param input
*/
CryptoOps.prototype.base64Decode = function (input) {
return this.b64Decode.decode(input);
};
/**
* Generates PKCE codes used in Authorization Code Flow.
*/
CryptoOps.prototype.generatePkceCodes = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.pkceGenerator.generateCodes()];
});
});
};
/**
* Generates a keypair, stores it and returns a thumbprint
* @param request
*/
CryptoOps.prototype.getPublicKeyThumbprint = function (request) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var publicKeyThumbMeasurement, keyPair, publicKeyJwk, pubKeyThumprintObj, publicJwkString, publicJwkHash, privateKeyJwk, unextractablePrivateKey;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
publicKeyThumbMeasurement = (_a = this.performanceClient) === null || _a === void 0 ? void 0 : _a.startMeasurement(PerformanceEvents.CryptoOptsGetPublicKeyThumbprint, request.correlationId);
return [4 /*yield*/, this.browserCrypto.generateKeyPair(CryptoOps.EXTRACTABLE, CryptoOps.POP_KEY_USAGES)];
case 1:
keyPair = _b.sent();
return [4 /*yield*/, this.browserCrypto.exportJwk(keyPair.publicKey)];
case 2:
publicKeyJwk = _b.sent();
pubKeyThumprintObj = {
e: publicKeyJwk.e,
kty: publicKeyJwk.kty,
n: publicKeyJwk.n
};
publicJwkString = BrowserCrypto.getJwkString(pubKeyThumprintObj);
return [4 /*yield*/, this.hashString(publicJwkString)];
case 3:
publicJwkHash = _b.sent();
return [4 /*yield*/, this.browserCrypto.exportJwk(keyPair.privateKey)];
case 4:
privateKeyJwk = _b.sent();
return [4 /*yield*/, this.browserCrypto.importJwk(privateKeyJwk, false, ["sign"])];
case 5:
unextractablePrivateKey = _b.sent();
// Store Keypair data in keystore
return [4 /*yield*/, this.cache.asymmetricKeys.setItem(publicJwkHash, {
privateKey: unextractablePrivateKey,
publicKey: keyPair.publicKey,
requestMethod: request.resourceRequestMethod,
requestUri: request.resourceRequestUri
})];
case 6:
// Store Keypair data in keystore
_b.sent();
if (publicKeyThumbMeasurement) {
publicKeyThumbMeasurement.endMeasurement({
success: true
});
}
return [2 /*return*/, publicJwkHash];
}
});
});
};
/**
* Removes cryptographic keypair from key store matching the keyId passed in
* @param kid
*/
CryptoOps.prototype.removeTokenBindingKey = function (kid) {
return __awaiter(this, void 0, void 0, function () {
var keyFound;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.cache.asymmetricKeys.removeItem(kid)];
case 1:
_a.sent();
return [4 /*yield*/, this.cache.asymmetricKeys.containsKey(kid)];
case 2:
keyFound = _a.sent();
return [2 /*return*/, !keyFound];
}
});
});
};
/**
* Removes all cryptographic keys from IndexedDB storage
*/
CryptoOps.prototype.clearKeystore = function () {
return __awaiter(this, void 0, void 0, function () {
var e_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 3, , 4]);
this.logger.verbose("Deleting in-memory and persistent asymmetric key stores");
return [4 /*yield*/, this.cache.asymmetricKeys.clear()];
case 1:
_a.sent();
this.logger.verbose("Successfully deleted asymmetric key stores");
this.logger.verbose("Deleting in-memory and persistent symmetric key stores");
return [4 /*yield*/, this.cache.symmetricKeys.clear()];
case 2:
_a.sent();
this.logger.verbose("Successfully deleted symmetric key stores");
return [2 /*return*/, true];
case 3:
e_1 = _a.sent();
if (e_1 instanceof Error) {
this.logger.error("Clearing keystore failed with error: " + e_1.message);
}
else {
this.logger.error("Clearing keystore failed with unknown error");
}
return [2 /*return*/, false];
case 4: return [2 /*return*/];
}
});
});
};
/**
* Signs the given object as a jwt payload with private key retrieved by given kid.
* @param payload
* @param kid
*/
CryptoOps.prototype.signJwt = function (payload, kid, correlationId) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var signJwtMeasurement, cachedKeyPair, publicKeyJwk, publicKeyJwkString, encodedKeyIdThumbprint, shrHeader, encodedShrHeader, encodedPayload, tokenString, tokenBuffer, signatureBuffer, encodedSignature, signedJwt;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
signJwtMeasurement = (_a = this.performanceClient) === null || _a === void 0 ? void 0 : _a.startMeasurement(PerformanceEvents.CryptoOptsSignJwt, correlationId);
return [4 /*yield*/, this.cache.asymmetricKeys.getItem(kid)];
case 1:
cachedKeyPair = _b.sent();
if (!cachedKeyPair) {
throw BrowserAuthError.createSigningKeyNotFoundInStorageError(kid);
}
return [4 /*yield*/, this.browserCrypto.exportJwk(cachedKeyPair.publicKey)];
case 2:
publicKeyJwk = _b.sent();
publicKeyJwkString = BrowserCrypto.getJwkString(publicKeyJwk);
encodedKeyIdThumbprint = this.b64Encode.urlEncode(JSON.stringify({ kid: kid }));
shrHeader = JoseHeader.getShrHeaderString({ kid: encodedKeyIdThumbprint, alg: publicKeyJwk.alg });
encodedShrHeader = this.b64Encode.urlEncode(shrHeader);
// Generate payload
payload.cnf = {
jwk: JSON.parse(publicKeyJwkString)
};
encodedPayload = this.b64Encode.urlEncode(JSON.stringify(payload));
tokenString = encodedShrHeader + "." + encodedPayload;
tokenBuffer = BrowserStringUtils.stringToArrayBuffer(tokenString);
return [4 /*yield*/, this.browserCrypto.sign(cachedKeyPair.privateKey, tokenBuffer)];
case 3:
signatureBuffer = _b.sent();
encodedSignature = this.b64Encode.urlEncodeArr(new Uint8Array(signatureBuffer));
signedJwt = tokenString + "." + encodedSignature;
if (signJwtMeasurement) {
signJwtMeasurement.endMeasurement({
success: true
});
}
return [2 /*return*/, signedJwt];
}
});
});
};
/**
* Returns the SHA-256 hash of an input string
* @param plainText
*/
CryptoOps.prototype.hashString = function (plainText) {
return __awaiter(this, void 0, void 0, function () {
var hashBuffer, hashBytes;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.browserCrypto.sha256Digest(plainText)];
case 1:
hashBuffer = _a.sent();
hashBytes = new Uint8Array(hashBuffer);
return [2 /*return*/, this.b64Encode.urlEncodeArr(hashBytes)];
}
});
});
};
CryptoOps.POP_KEY_USAGES = ["sign", "verify"];
CryptoOps.EXTRACTABLE = true;
return CryptoOps;
}());
export { CryptoKeyStoreNames, CryptoOps };
//# sourceMappingURL=CryptoOps.js.map