Files
server_debian_macro/node_modules/@azure/keyvault-keys/dist/browser/cryptographyClient.js
2025-02-18 22:59:07 +00:00

433 lines
17 KiB
JavaScript

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { KnownKeyOperations, } from "./keysModels.js";
import { RemoteCryptographyProvider } from "./cryptography/remoteCryptographyProvider.js";
import { randomBytes } from "./cryptography/crypto.js";
import { RsaCryptographyProvider } from "./cryptography/rsaCryptographyProvider.js";
import { AesCryptographyProvider } from "./cryptography/aesCryptographyProvider.js";
import { tracingClient } from "./tracing.js";
import { isRestError } from "@azure/core-rest-pipeline";
import { logger } from "./log.js";
/**
* A client used to perform cryptographic operations on an Azure Key vault key
* or a local {@link JsonWebKey}.
*/
export class CryptographyClient {
/**
* Internal constructor implementation for either local or Key Vault backed keys.
* @param key - The key to use during cryptography tasks.
* @param credential - Teh credential to use when constructing a Key Vault Cryptography client.
*/
constructor(key, credential, pipelineOptions = {}) {
if (typeof key === "string") {
// Key URL for remote-local operations.
this.key = {
kind: "identifier",
value: key,
};
this.remoteProvider = new RemoteCryptographyProvider(key, credential, pipelineOptions);
}
else if ("name" in key) {
// KeyVault key for remote-local operations.
this.key = {
kind: "KeyVaultKey",
value: key,
};
this.remoteProvider = new RemoteCryptographyProvider(key, credential, pipelineOptions);
}
else {
// JsonWebKey for local-only operations.
this.key = {
kind: "JsonWebKey",
value: key,
};
}
}
/**
* The base URL to the vault. If a local {@link JsonWebKey} is used vaultUrl will be empty.
*/
get vaultUrl() {
var _a;
return ((_a = this.remoteProvider) === null || _a === void 0 ? void 0 : _a.vaultUrl) || "";
}
/**
* The ID of the key used to perform cryptographic operations for the client.
*/
get keyID() {
if (this.key.kind === "identifier" || this.key.kind === "remoteOnlyIdentifier") {
return this.key.value;
}
else if (this.key.kind === "KeyVaultKey") {
return this.key.value.id;
}
else {
return this.key.value.kid;
}
}
encrypt(...args) {
const [parameters, options] = this.disambiguateEncryptArguments(args);
return tracingClient.withSpan("CryptographyClient.encrypt", options, async (updatedOptions) => {
this.ensureValid(await this.fetchKey(updatedOptions), KnownKeyOperations.Encrypt);
this.initializeIV(parameters);
const provider = await this.getProvider("encrypt", parameters.algorithm, updatedOptions);
try {
return provider.encrypt(parameters, updatedOptions);
}
catch (error) {
if (this.remoteProvider) {
return this.remoteProvider.encrypt(parameters, updatedOptions);
}
throw error;
}
});
}
initializeIV(parameters) {
// For AES-GCM the service **must** generate the IV, so we only populate it for AES-CBC
const algorithmsRequiringIV = [
"A128CBC",
"A128CBCPAD",
"A192CBC",
"A192CBCPAD",
"A256CBC",
"A256CBCPAD",
];
if (parameters.algorithm in algorithmsRequiringIV) {
try {
const cbcParams = parameters;
if (!cbcParams.iv) {
cbcParams.iv = randomBytes(16);
}
}
catch (e) {
throw new Error(`Unable to initialize IV for algorithm ${parameters.algorithm}. You may pass a valid IV to avoid this error. Error: ${e.message}`);
}
}
}
/**
* Standardizes the arguments of multiple overloads into a single shape.
* @param args - The encrypt arguments
*/
disambiguateEncryptArguments(args) {
if (typeof args[0] === "string") {
// Sample shape: ["RSA1_5", buffer, options]
return [
{
algorithm: args[0],
plaintext: args[1],
},
args[2] || {},
];
}
else {
// Sample shape: [{ algorithm: "RSA1_5", plaintext: buffer }, options]
return [args[0], (args[1] || {})];
}
}
decrypt(...args) {
const [parameters, options] = this.disambiguateDecryptArguments(args);
return tracingClient.withSpan("CryptographyClient.decrypt", options, async (updatedOptions) => {
this.ensureValid(await this.fetchKey(updatedOptions), KnownKeyOperations.Decrypt);
const provider = await this.getProvider("decrypt", parameters.algorithm, updatedOptions);
try {
return provider.decrypt(parameters, updatedOptions);
}
catch (error) {
if (this.remoteProvider) {
return this.remoteProvider.decrypt(parameters, updatedOptions);
}
throw error;
}
});
}
/**
* Standardizes the arguments of multiple overloads into a single shape.
* @param args - The decrypt arguments
*/
disambiguateDecryptArguments(args) {
if (typeof args[0] === "string") {
// Sample shape: ["RSA1_5", encryptedBuffer, options]
return [
{
algorithm: args[0],
ciphertext: args[1],
},
args[2] || {},
];
}
else {
// Sample shape: [{ algorithm: "RSA1_5", ciphertext: encryptedBuffer }, options]
return [args[0], (args[1] || {})];
}
}
/**
* Wraps the given key using the specified cryptography algorithm
*
* Example usage:
* ```ts
* let client = new CryptographyClient(keyVaultKey, credentials);
* let result = await client.wrapKey("RSA1_5", keyToWrap);
* ```
* @param algorithm - The encryption algorithm to use to wrap the given key.
* @param key - The key to wrap.
* @param options - Additional options.
*/
wrapKey(algorithm, key, options = {}) {
return tracingClient.withSpan("CryptographyClient.wrapKey", options, async (updatedOptions) => {
this.ensureValid(await this.fetchKey(updatedOptions), KnownKeyOperations.WrapKey);
const provider = await this.getProvider("wrapKey", algorithm, updatedOptions);
try {
return provider.wrapKey(algorithm, key, updatedOptions);
}
catch (err) {
if (this.remoteProvider) {
return this.remoteProvider.wrapKey(algorithm, key, options);
}
throw err;
}
});
}
/**
* Unwraps the given wrapped key using the specified cryptography algorithm
*
* Example usage:
* ```ts
* let client = new CryptographyClient(keyVaultKey, credentials);
* let result = await client.unwrapKey("RSA1_5", keyToUnwrap);
* ```
* @param algorithm - The decryption algorithm to use to unwrap the key.
* @param encryptedKey - The encrypted key to unwrap.
* @param options - Additional options.
*/
unwrapKey(algorithm, encryptedKey, options = {}) {
return tracingClient.withSpan("CryptographyClient.unwrapKey", options, async (updatedOptions) => {
this.ensureValid(await this.fetchKey(updatedOptions), KnownKeyOperations.UnwrapKey);
const provider = await this.getProvider("unwrapKey", algorithm, updatedOptions);
try {
return provider.unwrapKey(algorithm, encryptedKey, updatedOptions);
}
catch (err) {
if (this.remoteProvider) {
return this.remoteProvider.unwrapKey(algorithm, encryptedKey, options);
}
throw err;
}
});
}
/**
* Cryptographically sign the digest of a message
*
* Example usage:
* ```ts
* let client = new CryptographyClient(keyVaultKey, credentials);
* let result = await client.sign("RS256", digest);
* ```
* @param algorithm - The signing algorithm to use.
* @param digest - The digest of the data to sign.
* @param options - Additional options.
*/
sign(algorithm, digest, options = {}) {
return tracingClient.withSpan("CryptographyClient.sign", options, async (updatedOptions) => {
this.ensureValid(await this.fetchKey(updatedOptions), KnownKeyOperations.Sign);
const provider = await this.getProvider("sign", algorithm, updatedOptions);
try {
return provider.sign(algorithm, digest, updatedOptions);
}
catch (err) {
if (this.remoteProvider) {
return this.remoteProvider.sign(algorithm, digest, updatedOptions);
}
throw err;
}
});
}
/**
* Verify the signed message digest
*
* Example usage:
* ```ts
* let client = new CryptographyClient(keyVaultKey, credentials);
* let result = await client.verify("RS256", signedDigest, signature);
* ```
* @param algorithm - The signing algorithm to use to verify with.
* @param digest - The digest to verify.
* @param signature - The signature to verify the digest against.
* @param options - Additional options.
*/
verify(algorithm, digest, signature, options = {}) {
return tracingClient.withSpan("CryptographyClient.verify", options, async (updatedOptions) => {
this.ensureValid(await this.fetchKey(updatedOptions), KnownKeyOperations.Verify);
const provider = await this.getProvider("verify", algorithm, updatedOptions);
try {
return provider.verify(algorithm, digest, signature, updatedOptions);
}
catch (err) {
if (this.remoteProvider) {
return this.remoteProvider.verify(algorithm, digest, signature, updatedOptions);
}
throw err;
}
});
}
/**
* Cryptographically sign a block of data
*
* Example usage:
* ```ts
* let client = new CryptographyClient(keyVaultKey, credentials);
* let result = await client.signData("RS256", message);
* ```
* @param algorithm - The signing algorithm to use.
* @param data - The data to sign.
* @param options - Additional options.
*/
signData(algorithm, data,
// eslint-disable-next-line @azure/azure-sdk/ts-naming-options
options = {}) {
return tracingClient.withSpan("CryptographyClient.signData", options, async (updatedOptions) => {
this.ensureValid(await this.fetchKey(updatedOptions), KnownKeyOperations.Sign);
const provider = await this.getProvider("signData", algorithm, updatedOptions);
try {
return provider.signData(algorithm, data, updatedOptions);
}
catch (err) {
if (this.remoteProvider) {
return this.remoteProvider.signData(algorithm, data, options);
}
throw err;
}
});
}
/**
* Verify the signed block of data
*
* Example usage:
* ```ts
* let client = new CryptographyClient(keyVaultKey, credentials);
* let result = await client.verifyData("RS256", signedMessage, signature);
* ```
* @param algorithm - The algorithm to use to verify with.
* @param data - The signed block of data to verify.
* @param signature - The signature to verify the block against.
* @param options - Additional options.
*/
verifyData(algorithm, data, signature,
// eslint-disable-next-line @azure/azure-sdk/ts-naming-options
options = {}) {
return tracingClient.withSpan("CryptographyClient.verifyData", options, async (updatedOptions) => {
this.ensureValid(await this.fetchKey(updatedOptions), KnownKeyOperations.Verify);
const provider = await this.getProvider("verifyData", algorithm, updatedOptions);
try {
return provider.verifyData(algorithm, data, signature, updatedOptions);
}
catch (err) {
if (this.remoteProvider) {
return this.remoteProvider.verifyData(algorithm, data, signature, updatedOptions);
}
throw err;
}
});
}
/**
* Retrieves the {@link JsonWebKey} from the Key Vault, if possible. Returns undefined if the key could not be retrieved due to insufficient permissions.
*
* Example usage:
* ```ts
* let client = new CryptographyClient(keyVaultKey, credentials);
* let result = await client.getKeyMaterial();
* ```
*/
async getKeyMaterial(options) {
const key = await this.fetchKey(options);
switch (key.kind) {
case "JsonWebKey":
return key.value;
case "KeyVaultKey":
return key.value.key;
default:
return undefined;
}
}
/**
* Returns the underlying key used for cryptographic operations.
* If needed, attempts to fetch the key from KeyVault and exchanges the ID for the actual key.
* @param options - The additional options.
*/
async fetchKey(options) {
if (this.key.kind === "identifier") {
// Exchange the identifier with the actual key when needed
let key;
try {
key = await this.remoteProvider.getKey(options);
}
catch (e) {
if (isRestError(e) && e.statusCode === 403) {
// If we don't have permission to get the key, we'll fall back to using the remote provider.
// Marking the key as a remoteOnlyIdentifier will ensure that we don't attempt to fetch the key again.
logger.verbose(`Permission denied to get key ${this.key.value}. Falling back to remote operation.`);
this.key = { kind: "remoteOnlyIdentifier", value: this.key.value };
}
else {
throw e;
}
}
if (key) {
this.key = { kind: "KeyVaultKey", value: key };
}
}
return this.key;
}
/**
* Gets the provider that support this algorithm and operation.
* The available providers are ordered by priority such that the first provider that supports this
* operation is the one we should use.
* @param operation - The {@link KeyOperation}.
* @param algorithm - The algorithm to use.
*/
async getProvider(operation, algorithm, options) {
if (!this.providers) {
const keyMaterial = await this.getKeyMaterial(options);
this.providers = [];
// Add local crypto providers as needed
if (keyMaterial) {
this.providers.push(new RsaCryptographyProvider(keyMaterial), new AesCryptographyProvider(keyMaterial));
}
// If the remote provider exists, we're in hybrid-mode. Otherwise we're in local-only mode.
// If we're in hybrid mode the remote provider is used as a catch-all and should be last in the list.
if (this.remoteProvider) {
this.providers.push(this.remoteProvider);
}
}
const providers = this.providers.filter((p) => p.isSupported(algorithm, operation));
if (providers.length === 0) {
throw new Error(`Unable to support operation: "${operation}" with algorithm: "${algorithm}" ${this.key.kind === "JsonWebKey" ? "using a local JsonWebKey" : ""}`);
}
// Return the first provider that supports this request
return providers[0];
}
ensureValid(key, operation) {
var _a;
if (key.kind === "KeyVaultKey") {
const keyOps = key.value.keyOperations;
const { notBefore, expiresOn } = key.value.properties;
const now = new Date();
// Check KeyVault Key Expiration
if (notBefore && now < notBefore) {
throw new Error(`Key ${key.value.id} can't be used before ${notBefore.toISOString()}`);
}
if (expiresOn && now > expiresOn) {
throw new Error(`Key ${key.value.id} expired at ${expiresOn.toISOString()}`);
}
// Check Key operations
if (operation && keyOps && !(keyOps === null || keyOps === void 0 ? void 0 : keyOps.includes(operation))) {
throw new Error(`Operation ${operation} is not supported on key ${key.value.id}`);
}
}
else if (key.kind === "JsonWebKey") {
// Check JsonWebKey Key operations
if (operation && key.value.keyOps && !((_a = key.value.keyOps) === null || _a === void 0 ? void 0 : _a.includes(operation))) {
throw new Error(`Operation ${operation} is not supported on key ${key.value.kid}`);
}
}
}
}
//# sourceMappingURL=cryptographyClient.js.map