UNPKG

@microsoft/useragent-sdk

Version:

SDK for building decentralized identity wallets and enterprise agents.

187 lines (167 loc) 7.58 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import CryptoFactory from "../../plugin/CryptoFactory"; import { CryptoAlgorithm } from "../../keyStore/IKeyStore"; import { KeyType } from "../KeyTypeFactory"; import W3cCryptoApiConstants from "../../utilities/W3cCryptoApiConstants"; import PrivateKey from "../PrivateKey"; import base64url from "base64url"; import KeyUseFactory from "../KeyUseFactory"; import RsaPrivateKey from "./RsaPrivateKey"; const bigInt = require('big-integer'); import { BigIntegerStatic } from 'big-integer'; import { SubtleCrypto } from 'webcrypto-core'; import JoseConstants from "../../protocols/jose/JoseConstants"; // tslint:disable-next-line:prefer-array-literal type PrimeDelegate = Array<(cryptoFactory: CryptoFactory, inx: number, key: Buffer, data: Buffer, deterministicKey: Buffer) => Promise<Buffer>>; /** * Class to model RSA pairwise keys */ export default class RsaPairwiseKey { /** * Statistics on the number of prime tests */ public static numberOfPrimeTests: number = 0; /** * Generate a pairwise key for the specified algorithms * @param cryptoFactory defining the key store and the used crypto api * @param personaMasterKey Master key for the current selected persona * @param algorithm for the key * @param peerId Id for the peer */ public static async generate(cryptoFactory: CryptoFactory, personaMasterKey: Buffer, algorithm: RsaHashedKeyGenParams, peerId: string): Promise<PrivateKey> { // This method is currently breaking the subtle crypto pattern and needs to be fixed to be platform independent // Set the key size const keySize = algorithm.modulusLength || 1024; // Get deterministic base number for p const peerIdBuffer = Buffer.from(peerId); const pBase: Buffer = await RsaPairwiseKey.generateDeterministicNumberForPrime(cryptoFactory, keySize / 2, personaMasterKey, peerIdBuffer); // Get deterministic base number for q const qBase: Buffer = await this.generateDeterministicNumberForPrime(cryptoFactory, keySize / 2, pBase, peerIdBuffer); const p = RsaPairwiseKey.getPrime(pBase); const q = RsaPairwiseKey.getPrime(qBase); // compute key components const modulus = p.multiply(q); const pMinus = p.subtract(bigInt.one); const qMinus = q.subtract(bigInt.one); const phi = pMinus.multiply(qMinus); const e = bigInt(65537); const d = e.modInv(phi); const dp = d.mod(pMinus); const dq = d.mod(qMinus); const qi = q.modInv(p); const pairwise = <RsaPrivateKey> { kty: KeyType.RSA, use: KeyUseFactory.createViaWebCrypto(algorithm), e: RsaPairwiseKey.toBase(e), n: RsaPairwiseKey.toBase(modulus), d: RsaPairwiseKey.toBase(d), p: RsaPairwiseKey.toBase(p), q: RsaPairwiseKey.toBase(q), dp: RsaPairwiseKey.toBase(dp), dq: RsaPairwiseKey.toBase(dq), qi: RsaPairwiseKey.toBase(qi), // Need an algorithm for kid generation - todo kid: '#key1' }; return new RsaPrivateKey(pairwise); } /** * Uses primeBase as reference and generate the closest prime number */ private static getPrime (primeBase: Buffer): any { const qArray = Array.from(primeBase); const prime: bigInt.BigIntegerStatic = RsaPairwiseKey.generatePrime(qArray); return new bigInt(prime); } /** * Generate a deterministic number that can be used as prime * @param cryptoFactory The crypto factory. * @param keySize Desired key size * @param personaMasterKey The persona master key * @param peerId The peer id */ public static async generateDeterministicNumberForPrime (cryptoFactory: CryptoFactory, primeSize: number, personaMasterKey: Buffer, peerId: Buffer): Promise<Buffer> { const numberOfRounds: number = primeSize / (8 * 64); let deterministicKey: Buffer = Buffer.from(''); const rounds: PrimeDelegate = []; for (let inx = 0; inx < numberOfRounds ; inx++) { rounds.push(async (cryptoFactory: CryptoFactory, inx: number, key: Buffer, data: Buffer, deterministicKey: Buffer) => { deterministicKey = await this.generateHashForPrime(cryptoFactory, inx, key, data, deterministicKey); return deterministicKey; }); } return this.executeRounds(cryptoFactory, rounds, 0, personaMasterKey, peerId, deterministicKey); } /** * Generate a hash used as component for prime number * @param crypto The crypto object. * @param inx Round number * @param key Signature key * @param data Data to sign */ private static async generateHashForPrime (cryptoFactory: CryptoFactory, _inx: number, key: Buffer, data: Buffer, deterministicKey: Buffer): Promise<Buffer> { // Get the subtle crypto const crypto: SubtleCrypto = cryptoFactory.getMessageAuthenticationCodeSigners(W3cCryptoApiConstants.Hmac); // Generate the master key const alg: CryptoAlgorithm = { name: W3cCryptoApiConstants.Hmac, hash: W3cCryptoApiConstants.Sha512 }; const signingKey: JsonWebKey = { kty: 'oct', alg: JoseConstants.Hs512, k: base64url.encode(key) }; const importedKey = await crypto.importKey('jwk', signingKey, alg, false, ['sign']); const signature = await crypto.sign(alg, importedKey, data); return Buffer.concat([deterministicKey, Buffer.from(signature)]); } /** * Execute all rounds * @param rounds Array of functions to execute * @param inx Current step * @param key Key to sign * @param data Data to sign */ private static async executeRounds (cryptoFactory: CryptoFactory, rounds: PrimeDelegate, inx: number, key: Buffer, data: Buffer, deterministicKey: Buffer): Promise<Buffer> { deterministicKey = await rounds[inx](cryptoFactory, inx, key, data, deterministicKey); if (inx + 1 === rounds.length) { return deterministicKey; } else { deterministicKey = await this.executeRounds(cryptoFactory, rounds, inx + 1, key, Buffer.from(deterministicKey), deterministicKey); return deterministicKey; } } /** * Generate a prime number from the seed. * isProbablyPrime is based on the Miller-Rabin prime test. * @param primeSeed seed for prime generator */ // tslint:disable-next-line:prefer-array-literal private static generatePrime (primeSeed: Array<number>): BigIntegerStatic { // make sure candidate is uneven, set high order bit primeSeed[primeSeed.length - 1] |= 0x1; primeSeed[0] |= 0x80; const two = bigInt(2); let prime = bigInt.fromArray(primeSeed, 256, false); RsaPairwiseKey.numberOfPrimeTests = 1; // tslint:disable-next-line:no-constant-condition while (true) { // 64 tests give 128 bit security if (prime.isProbablePrime(64)) { break; } prime = prime.add(two); RsaPairwiseKey.numberOfPrimeTests++; } return prime; } /** * Convert big number to base64 url. * @param bigNumber Number to convert */ private static toBase (bigNumber: any): string { let buf = Buffer.from(bigNumber.toArray(256).value); return base64url(buf); } }