@web5/agent
Version:
247 lines • 12.9 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
// ! TODO : Make sure I remove `@noble/ciphers` from the Agent package.json once this is moved to the `@web5/crypto` package.
import { getWebcryptoSubtle } from '@noble/ciphers/webcrypto';
import { Convert } from '@web5/common';
import { computeJwkThumbprint, isOctPrivateJwk } from '@web5/crypto';
import { CryptoError, CryptoErrorCode } from '../crypto-error.js';
/**
* Constant defining the AES key length values in bits.
*
* @remarks
* NIST publication FIPS 197 states:
* > The AES algorithm is capable of using cryptographic keys of 128, 192, and 256 bits to encrypt
* > and decrypt data in blocks of 128 bits.
*
* This implementation does not support key lengths that are different from the three values
* defined by this constant.
*
* @see {@link https://doi.org/10.6028/NIST.FIPS.197-upd1 | NIST FIPS 197}
*/
const AES_KEY_LENGTHS = [128, 192, 256];
export class AesKw {
/**
* Converts a raw private key in bytes to its corresponding JSON Web Key (JWK) format.
*
* @remarks
* This method takes a symmetric key represented as a byte array (Uint8Array) and
* converts it into a JWK object for use with AES (Advanced Encryption Standard)
* for key wrapping. The conversion process involves encoding the key into
* base64url format and setting the appropriate JWK parameters.
*
* The resulting JWK object includes the following properties:
* - `kty`: Key Type, set to 'oct' for Octet Sequence (representing a symmetric key).
* - `k`: The symmetric key, base64url-encoded.
* - `kid`: Key ID, generated based on the JWK thumbprint.
*
* @example
* ```ts
* const privateKeyBytes = new Uint8Array([...]); // Replace with actual symmetric key bytes
* const privateKey = await AesKw.bytesToPrivateKey({ privateKeyBytes });
* ```
*
* @param params - The parameters for the symmetric key conversion.
* @param params.privateKeyBytes - The raw symmetric key as a Uint8Array.
*
* @returns A Promise that resolves to the symmetric key in JWK format.
*/
static bytesToPrivateKey({ privateKeyBytes }) {
return __awaiter(this, void 0, void 0, function* () {
// Construct the private key in JWK format.
const privateKey = {
k: Convert.uint8Array(privateKeyBytes).toBase64Url(),
kty: 'oct'
};
// Compute the JWK thumbprint and set as the key ID.
privateKey.kid = yield computeJwkThumbprint({ jwk: privateKey });
// Add algorithm identifier based on key length.
const lengthInBits = privateKeyBytes.length * 8;
privateKey.alg = { 128: 'A128KW', 192: 'A192KW', 256: 'A256KW' }[lengthInBits];
return privateKey;
});
}
/**
* Generates a symmetric key for AES for key wrapping in JSON Web Key (JWK) format.
*
* @remarks
* This method creates a new symmetric key of a specified length suitable for use with
* AES key wrapping. It uses cryptographically secure random number generation to
* ensure the uniqueness and security of the key. The generated key adheres to the JWK
* format, making it compatible with common cryptographic standards and easy to use in
* various cryptographic processes.
*
* The generated key includes the following components:
* - `kty`: Key Type, set to 'oct' for Octet Sequence.
* - `k`: The symmetric key component, base64url-encoded.
* - `kid`: Key ID, generated based on the JWK thumbprint.
* - `alg`: Algorithm, set to 'A128KW', 'A192KW', or 'A256KW' for AES Key Wrap with the
* specified key length.
*
* @example
* ```ts
* const length = 256; // Length of the key in bits (e.g., 128, 192, 256)
* const privateKey = await AesKw.generateKey({ length });
* ```
*
* @param params - The parameters for the key generation.
* @param params.length - The length of the key in bits. Common lengths are 128, 192, and 256 bits.
*
* @returns A Promise that resolves to the generated symmetric key in JWK format.
*/
static generateKey({ length }) {
return __awaiter(this, void 0, void 0, function* () {
// Validate the key length.
if (!AES_KEY_LENGTHS.includes(length)) {
throw new RangeError(`The key length is invalid: Must be ${AES_KEY_LENGTHS.join(', ')} bits`);
}
// Get the Web Crypto API interface.
const webCrypto = getWebcryptoSubtle();
// Generate a random private key.
// See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#usage_notes for
// an explanation for why Web Crypto generateKey() is used instead of getRandomValues().
const webCryptoKey = yield webCrypto.generateKey({ name: 'AES-KW', length }, true, ['wrapKey', 'unwrapKey']);
// Export the private key in JWK format.
const _a = yield webCrypto.exportKey('jwk', webCryptoKey), { ext, key_ops } = _a, privateKey = __rest(_a, ["ext", "key_ops"]);
// Compute the JWK thumbprint and set as the key ID.
privateKey.kid = yield computeJwkThumbprint({ jwk: privateKey });
return privateKey;
});
}
/**
* Converts a private key from JSON Web Key (JWK) format to a raw byte array (Uint8Array).
*
* @remarks
* This method takes a symmetric key in JWK format and extracts its raw byte representation.
* It decodes the 'k' parameter of the JWK value, which represents the symmetric key in base64url
* encoding, into a byte array.
*
* @example
* ```ts
* const privateKey = { ... }; // A symmetric key in JWK format
* const privateKeyBytes = await AesKw.privateKeyToBytes({ privateKey });
* ```
*
* @param params - The parameters for the symmetric key conversion.
* @param params.privateKey - The symmetric key in JWK format.
*
* @returns A Promise that resolves to the symmetric key as a Uint8Array.
*/
static privateKeyToBytes({ privateKey }) {
return __awaiter(this, void 0, void 0, function* () {
// Verify the provided JWK represents a valid oct private key.
if (!isOctPrivateJwk(privateKey)) {
throw new Error(`AesKw: The provided key is not a valid oct private key.`);
}
// Decode the provided private key to bytes.
const privateKeyBytes = Convert.base64Url(privateKey.k).toUint8Array();
return privateKeyBytes;
});
}
static unwrapKey({ wrappedKeyBytes, wrappedKeyAlgorithm, decryptionKey }) {
return __awaiter(this, void 0, void 0, function* () {
if (!('alg' in decryptionKey && decryptionKey.alg)) {
throw new CryptoError(CryptoErrorCode.InvalidJwk, `The decryption key is missing the 'alg' property.`);
}
if (!['A128KW', 'A192KW', 'A256KW'].includes(decryptionKey.alg)) {
throw new CryptoError(CryptoErrorCode.AlgorithmNotSupported, `The 'decryptionKey' algorithm is not supported: ${decryptionKey.alg}`);
}
// Get the Web Crypto API interface.
const webCrypto = getWebcryptoSubtle();
// Import the decryption key for use with the Web Crypto API.
const decryptionCryptoKey = yield webCrypto.importKey('jwk', // key format
decryptionKey, // key data
{ name: 'AES-KW' }, // algorithm identifier
true, // key is extractable
['unwrapKey'] // key usages
);
// Map the private key's JOSE algorithm name to the Web Crypto API algorithm identifier.
const webCryptoAlgorithm = {
A128KW: 'AES-KW', A192KW: 'AES-KW', A256KW: 'AES-KW',
A128GCM: 'AES-GCM', A192GCM: 'AES-GCM', A256GCM: 'AES-GCM',
}[wrappedKeyAlgorithm];
if (!webCryptoAlgorithm) {
throw new CryptoError(CryptoErrorCode.AlgorithmNotSupported, `The 'wrappedKeyAlgorithm' is not supported: ${wrappedKeyAlgorithm}`);
}
// Unwrap the key using the Web Crypto API.
const unwrappedCryptoKey = yield webCrypto.unwrapKey('raw', // output format
wrappedKeyBytes.buffer, // key to unwrap
decryptionCryptoKey, // unwrapping key
'AES-KW', // algorithm identifier
{ name: webCryptoAlgorithm }, // unwrapped key algorithm identifier
true, // key is extractable
['unwrapKey'] // key usages
);
// Export the unwrapped key in JWK format.
const _a = yield webCrypto.exportKey('jwk', unwrappedCryptoKey), { ext, key_ops } = _a, unwrappedJsonWebKey = __rest(_a, ["ext", "key_ops"]);
const unwrappedKey = unwrappedJsonWebKey;
// Compute the JWK thumbprint and set as the key ID.
unwrappedKey.kid = yield computeJwkThumbprint({ jwk: unwrappedKey });
return unwrappedKey;
});
}
static wrapKey({ unwrappedKey, encryptionKey }) {
return __awaiter(this, void 0, void 0, function* () {
if (!('alg' in encryptionKey && encryptionKey.alg)) {
throw new CryptoError(CryptoErrorCode.InvalidJwk, `The encryption key is missing the 'alg' property.`);
}
if (!['A128KW', 'A192KW', 'A256KW'].includes(encryptionKey.alg)) {
throw new CryptoError(CryptoErrorCode.AlgorithmNotSupported, `The 'encryptionKey' algorithm is not supported: ${encryptionKey.alg}`);
}
if (!('alg' in unwrappedKey && unwrappedKey.alg)) {
throw new CryptoError(CryptoErrorCode.InvalidJwk, `The private key to wrap is missing the 'alg' property.`);
}
// Get the Web Crypto API interface.
const webCrypto = getWebcryptoSubtle();
// Import the encryption key for use with the Web Crypto API.
const encryptionCryptoKey = yield webCrypto.importKey('jwk', // key format
encryptionKey, // key data
{ name: 'AES-KW' }, // algorithm identifier
true, // key is extractable
['wrapKey'] // key usages
);
// Map the private key's JOSE algorithm name to the Web Crypto API algorithm identifier.
const webCryptoAlgorithm = {
A128KW: 'AES-KW', A192KW: 'AES-KW', A256KW: 'AES-KW',
A128GCM: 'AES-GCM', A192GCM: 'AES-GCM', A256GCM: 'AES-GCM',
}[unwrappedKey.alg];
if (!webCryptoAlgorithm) {
throw new CryptoError(CryptoErrorCode.AlgorithmNotSupported, `The 'unwrappedKey' algorithm is not supported: ${unwrappedKey.alg}`);
}
// Import the private key to wrap for use with the Web Crypto API.
const unwrappedCryptoKey = yield webCrypto.importKey('jwk', // key format
unwrappedKey, // key data
{ name: webCryptoAlgorithm }, // algorithm identifier
true, // key is extractable
['unwrapKey'] // key usages
);
// Wrap the key using the Web Crypto API.
const wrappedKeyBuffer = yield webCrypto.wrapKey('raw', // output format
unwrappedCryptoKey, // key to wrap
encryptionCryptoKey, // wrapping key
'AES-KW' // algorithm identifier
);
// Convert from ArrayBuffer to Uint8Array.
const wrappedKeyBytes = new Uint8Array(wrappedKeyBuffer);
return wrappedKeyBytes;
});
}
}
//# sourceMappingURL=aes-kw.js.map