@microsoft/useragent-sdk
Version:
SDK for building decentralized identity wallets and enterprise agents.
225 lines • 10.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const CryptoHelpers_1 = require("../utilities/CryptoHelpers");
const KeyTypeFactory_1 = require("../keys/KeyTypeFactory");
const PairwiseKey_1 = require("../keys/PairwiseKey");
const webcrypto_core_1 = require("webcrypto-core");
const CryptoError_1 = require("../CryptoError");
const clone = require('clone');
// Named curves
const CURVE_P256K = 'P-256K';
const CURVE_K256 = 'K-256';
/**
* The class extends the @class SubtleCrypto with addtional methods.
* Adds methods to work with key references.
* Extends SubtleCrypto to work with JWK keys.
*/
class SubtleCryptoExtension extends webcrypto_core_1.SubtleCrypto {
constructor(cryptoFactory) {
super();
this.keyStore = cryptoFactory.keyStore;
this.cryptoFactory = cryptoFactory;
}
/**
* Generate a pairwise key for the algorithm
* @param algorithm for the key
* @param seedReference Reference to the seed
* @param personaId Id for the persona
* @param peerId Id for the peer
* @param extractable True if key is exportable
* @param keyops Key operations
*/
async generatePairwiseKey(algorithm, seedReference, personaId, peerId) {
const pairwiseKey = new PairwiseKey_1.default(this.cryptoFactory);
return pairwiseKey.generatePairwiseKey(algorithm, seedReference, personaId, peerId);
}
/**
* Sign with a key referenced in the key store
* @param algorithm used for signature
* @param keyReference points to key in the key store
* @param data to sign
* @returns The signature in the requested algorithm
*/
async signByKeyStore(algorithm, keyReference, data) {
const jwk = (await this.keyStore.get(keyReference, false)).getKey();
const crypto = CryptoHelpers_1.default.getSubtleCryptoForAlgorithm(this.cryptoFactory, algorithm);
const keyImportAlgorithm = SubtleCryptoExtension.normalizeAlgorithm(CryptoHelpers_1.default.getKeyImportAlgorithm(algorithm, jwk));
const key = await crypto.importKey('jwk', SubtleCryptoExtension.normalizeJwk(jwk), keyImportAlgorithm, true, ['sign']);
const signature = await crypto.sign(jwk.kty === KeyTypeFactory_1.KeyType.EC || jwk.kty === KeyTypeFactory_1.KeyType.OKP ? SubtleCryptoExtension.normalizeAlgorithm(algorithm) : algorithm, key, data);
// only applicable for EC algorithms and when no encoding is applied
const isElliptic = algorithm.name === 'ECDSA' || algorithm.name === 'EDDSA';
// EDDSA/ECDSA returns two 32 bit values R & S. Some API's will encode these values in DER
const format = algorithm.format;
if (isElliptic && signature.byteLength <= 64 && format) {
if (format.toUpperCase() !== 'DER') {
throw new CryptoError_1.default(algorithm, 'Only DER format supported for signature');
}
// DER format needed for signature, specied in algorithm
const r = signature.slice(0, signature.byteLength / 2);
const s = signature.slice(signature.byteLength / 2, signature.byteLength);
return SubtleCryptoExtension.toDer([r, s]);
}
if (isElliptic && signature.byteLength > 64 && format) {
// DER encoded is not requested and signature is DER encoded
// In this case the encoding is removed and returned as 64 bytes
const decodedSignature = SubtleCryptoExtension.fromDer(new Uint8Array(signature));
const signed = new Uint8Array(decodedSignature[0].length + decodedSignature[1].length);
signed.set(decodedSignature[0]);
signed.set(decodedSignature[1], decodedSignature[1].length);
return signed;
}
return signature;
}
/**
* format the signature output to DER format
* @param elements Array of elements to encode in DER
*/
static toDer(elements) {
let index = 0;
// calculate total size.
let lengthOfRemaining = 0;
for (let element = 0; element < elements.length; element++) {
// Add element format bytes
lengthOfRemaining += 2;
const buffer = new Uint8Array(elements[element]);
const size = (buffer[0] & 0x80) === 0x80 ? buffer.length + 1 : buffer.length;
lengthOfRemaining += size;
}
// Prepare output
index = 0;
const result = new Uint8Array(lengthOfRemaining + 2);
result.set([0x30, lengthOfRemaining], index);
index += 2;
for (let element = 0; element < elements.length; element++) {
// Add element format bytes
const buffer = new Uint8Array(elements[element]);
const size = (buffer[0] & 0x80) === 0x80 ? buffer.length + 1 : buffer.length;
result.set([0x02, size], index);
index += 2;
if (size > buffer.length) {
result.set([0x0], index++);
}
result.set(buffer, index);
index += buffer.length;
}
return result;
}
/**
* Verify with JWK.
* @param algorithm used for verification
* @param jwk Json web key used to verify
* @param signature to verify
* @param payload which was signed
*/
async verifyByJwk(algorithm, jwk, signature, payload) {
const crypto = CryptoHelpers_1.default.getSubtleCryptoForAlgorithm(this.cryptoFactory, algorithm);
const keyImportAlgorithm = SubtleCryptoExtension.normalizeAlgorithm(CryptoHelpers_1.default.getKeyImportAlgorithm(algorithm, jwk));
const key = await crypto.importKey('jwk', SubtleCryptoExtension.normalizeJwk(jwk), keyImportAlgorithm, true, ['verify']);
const isElliptic = algorithm.name === 'ECDSA' || algorithm.name === 'EDDSA';
// The underlying signature validation does not support DER encoding so needs to be removed
if (isElliptic && signature.byteLength > 64) {
const elements = SubtleCryptoExtension.fromDer(signature);
signature = new Uint8Array(elements[0].length + elements[1].length);
signature.set(elements[0]);
signature.set(elements[1], elements[1].length);
}
return crypto.verify(isElliptic ?
SubtleCryptoExtension.normalizeAlgorithm(algorithm) :
algorithm, key, signature, payload);
}
/**
* format the signature output from DER format
* @param signature to decode from DER
*/
static fromDer(signature) {
if (signature[0] !== 0x30) {
throw new Error('No DER format to decode');
}
const lengthOfRemaining = signature[1];
const results = [];
let index = 2;
while (index < lengthOfRemaining) {
const marker = signature[index++];
if (marker !== 0x02) {
throw new Error(`Marker on index ${index - 1} must be 0x02`);
}
let length = signature[index++];
while (signature[index] === 0) {
index++;
length--;
}
const data = signature.slice(index, index + length);
results.push(data);
index = index + length;
}
return results;
}
/**
* Decrypt with a key referenced in the key store.
* The referenced key must be a jwk key.
* @param algorithm used for signature
* @param keyReference points to key in the key store
* @param cipher to decrypt
*/
async decryptByKeyStore(algorithm, keyReference, cipher) {
const jwk = (await this.keyStore.get(keyReference, false)).getKey();
const crypto = CryptoHelpers_1.default.getSubtleCryptoForAlgorithm(this.cryptoFactory, algorithm);
const keyImportAlgorithm = SubtleCryptoExtension.normalizeAlgorithm(CryptoHelpers_1.default.getKeyImportAlgorithm(algorithm, jwk));
const key = await crypto.importKey('jwk', SubtleCryptoExtension.normalizeJwk(jwk), SubtleCryptoExtension.normalizeAlgorithm(keyImportAlgorithm), true, ['decrypt']);
return crypto.decrypt(algorithm, key, cipher);
}
/**
* Decrypt with JWK.
* @param algorithm used for decryption
* @param jwk Json web key to decrypt
* @param cipher to decrypt
*/
async decryptByJwk(algorithm, jwk, cipher) {
const crypto = CryptoHelpers_1.default.getSubtleCryptoForAlgorithm(this.cryptoFactory, algorithm);
const keyImportAlgorithm = SubtleCryptoExtension.normalizeAlgorithm(CryptoHelpers_1.default.getKeyImportAlgorithm(algorithm, jwk));
const key = await crypto.importKey('jwk', SubtleCryptoExtension.normalizeJwk(jwk), SubtleCryptoExtension.normalizeAlgorithm(keyImportAlgorithm), true, ['decrypt']);
return crypto.decrypt(algorithm, key, cipher);
}
/**
* Encrypt with a jwk key referenced in the key store
* @param algorithm used for encryption
* @param jwk Json web key public key
* @param data to encrypt
*/
async encryptByJwk(algorithm, jwk, data) {
const keyImportAlgorithm = CryptoHelpers_1.default.getKeyImportAlgorithm(algorithm, jwk);
const crypto = CryptoHelpers_1.default.getSubtleCryptoForAlgorithm(this.cryptoFactory, algorithm);
const key = await crypto.importKey('jwk', SubtleCryptoExtension.normalizeJwk(jwk), SubtleCryptoExtension.normalizeAlgorithm(keyImportAlgorithm), true, ['encrypt']);
return crypto.encrypt(algorithm, key, data);
}
/**
* Normalize the algorithm so it can be used by underlying crypto.
* @param algorithm Algorithm to be normalized
*/
static normalizeAlgorithm(algorithm) {
if (algorithm.namedCurve) {
if (algorithm.namedCurve === CURVE_P256K) {
const alg = clone(algorithm);
alg.namedCurve = CURVE_K256;
return alg;
}
}
return algorithm;
}
/**
* Normalize the JWK parameters so it can be used by underlying crypto.
* @param jwk Json web key to be normalized
*/
static normalizeJwk(jwk) {
if (jwk.crv) {
if (jwk.crv === CURVE_P256K) {
const clonedKey = clone(jwk);
clonedKey.crv = CURVE_K256;
return clonedKey;
}
}
return jwk;
}
}
exports.default = SubtleCryptoExtension;
//# sourceMappingURL=SubtleCryptoExtension.js.map