diff --git a/packages/anastasis-core/src/crypto.ts b/packages/anastasis-core/src/crypto.ts index 5da3a4cce..32cf470c4 100644 --- a/packages/anastasis-core/src/crypto.ts +++ b/packages/anastasis-core/src/crypto.ts @@ -5,7 +5,9 @@ import { encodeCrock, getRandomBytes, kdf, + kdfKw, secretbox, + crypto_sign_keyPair_fromSeed, stringToBytes, } from "@gnu-taler/taler-util"; import { argon2id } from "hash-wasm"; @@ -25,6 +27,8 @@ export type EncryptedKeyShare = Flavor; export type EncryptedTruth = Flavor; export type EncryptedCoreSecret = Flavor; export type EncryptedMasterKey = Flavor; +export type EddsaPublicKey = Flavor; +export type EddsaPrivateKey = Flavor; /** * Truth key, found in the recovery document. */ @@ -53,6 +57,43 @@ export async function userIdentifierDerive( return encodeCrock(result); } +export interface AccountKeyPair { + priv: EddsaPrivateKey; + pub: EddsaPublicKey; +} + +export function accountKeypairDerive(userId: UserIdentifier): AccountKeyPair { + // FIXME: the KDF invocation looks fishy, but that's what the C code presently does. + const d = kdfKw({ + outputLength: 32, + ikm: stringToBytes("ver"), + salt: decodeCrock(userId), + }); + // FIXME: This bit twiddling seems wrong/unnecessary. + d[0] &= 248; + d[31] &= 127; + d[31] |= 64; + const pair = crypto_sign_keyPair_fromSeed(d); + return { + priv: encodeCrock(pair.secretKey), + pub: encodeCrock(pair.publicKey), + }; +} + +export async function encryptRecoveryDocument( + userId: UserIdentifier, + recoveryDoc: any, +): Promise { + const plaintext = stringToBytes(JSON.stringify(recoveryDoc)); + const nonce = encodeCrock(getRandomBytes(nonceSize)); + return anastasisEncrypt( + nonce, + asOpaque(userId), + encodeCrock(plaintext), + "erd", + ); +} + function taConcat(chunks: Uint8Array[]): Uint8Array { let payloadLen = 0; for (const c of chunks) { diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts index f33a0be46..8921433b7 100644 --- a/packages/anastasis-core/src/index.ts +++ b/packages/anastasis-core/src/index.ts @@ -36,8 +36,10 @@ import { } from "./reducer-types.js"; import fetchPonyfill from "fetch-ponyfill"; import { + accountKeypairDerive, coreSecretEncrypt, encryptKeyshare, + encryptRecoveryDocument, encryptTruth, PolicyKey, policyKeyDerive, @@ -492,14 +494,25 @@ async function uploadSecret( policies: policies.map((x, i) => { return { master_key: csr.encMasterKeys[i], + // FIXME: ... uuid: [], - salt: + salt: undefined as any, }; }), }; for (const prov of state.policy_providers!) { + const uid = uidMap[prov.provider_url] + const acctKeypair = accountKeypairDerive(uid); + const encRecoveryDoc = await encryptRecoveryDocument(uid, rd); // FIXME: Upload recovery document. + const resp = await fetch( + new URL(`policy/${acctKeypair.pub}`, prov.provider_url).href, + { + method: "POST", + body: decodeCrock(encRecoveryDoc), + }, + ); } return { diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts index ccb917f6e..4ad752954 100644 --- a/packages/taler-util/src/index.ts +++ b/packages/taler-util/src/index.ts @@ -22,4 +22,9 @@ export * from "./url.js"; export { fnutil } from "./fnutils.js"; export * from "./kdf.js"; export * from "./talerCrypto.js"; -export { randomBytes, secretbox, secretbox_open } from "./nacl-fast.js"; +export { + randomBytes, + secretbox, + secretbox_open, + crypto_sign_keyPair_fromSeed, +} from "./nacl-fast.js"; diff --git a/packages/taler-util/src/kdf.ts b/packages/taler-util/src/kdf.ts index af4d05035..7710de90c 100644 --- a/packages/taler-util/src/kdf.ts +++ b/packages/taler-util/src/kdf.ts @@ -59,15 +59,30 @@ export function hmacSha256(key: Uint8Array, message: Uint8Array): Uint8Array { return hmac(sha256, 64, key, message); } +/** + * HMAC-SHA512-SHA256 (see RFC 5869). + */ +export function kdfKw(args: { + outputLength: number; + ikm: Uint8Array; + salt?: Uint8Array; + info?: Uint8Array; +}) { + return kdf(args.outputLength, args.ikm, args.salt, args.info); +} + export function kdf( outputLength: number, ikm: Uint8Array, - salt: Uint8Array, - info: Uint8Array, + salt?: Uint8Array, + info?: Uint8Array, ): Uint8Array { + salt = salt ?? new Uint8Array(64); // extract const prk = hmacSha512(salt, ikm); + info = info ?? new Uint8Array(0); + // expand const N = Math.ceil(outputLength / 32); const output = new Uint8Array(N * 32); diff --git a/packages/taler-util/src/nacl-fast.ts b/packages/taler-util/src/nacl-fast.ts index 909c6a60a..6e721f32c 100644 --- a/packages/taler-util/src/nacl-fast.ts +++ b/packages/taler-util/src/nacl-fast.ts @@ -2894,7 +2894,6 @@ export function x25519_edwards_keyPair_fromSecretKey( throw new Error("bad secret key size"); } d.set(secretKey, 0); - //crypto_hash(d, secretKey, 32); d[0] &= 248; d[31] &= 127; @@ -2906,7 +2905,7 @@ export function x25519_edwards_keyPair_fromSecretKey( return pk; } -export function sign_keyPair_fromSecretKey( +export function crypto_sign_keyPair_fromSecretKey( secretKey: Uint8Array, ): { publicKey: Uint8Array; @@ -2920,7 +2919,7 @@ export function sign_keyPair_fromSecretKey( return { publicKey: pk, secretKey: new Uint8Array(secretKey) }; } -export function sign_keyPair_fromSeed( +export function crypto_sign_keyPair_fromSeed( seed: Uint8Array, ): { publicKey: Uint8Array; diff --git a/packages/taler-util/src/talerCrypto.test.ts b/packages/taler-util/src/talerCrypto.test.ts index ffd1d25cd..1e3ceef61 100644 --- a/packages/taler-util/src/talerCrypto.test.ts +++ b/packages/taler-util/src/talerCrypto.test.ts @@ -69,7 +69,7 @@ test("taler-exchange-tvg eddsa key", (t) => { const priv = "9TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD40"; const pub = "8GSJZ649T2PXMKZC01Y4ANNBE7MF14QVK9SQEC4E46ZHKCVG8AS0"; - const pair = nacl.sign_keyPair_fromSeed(decodeCrock(priv)); + const pair = nacl.crypto_sign_keyPair_fromSeed(decodeCrock(priv)); t.deepEqual(encodeCrock(pair.publicKey), pub); }); diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts index efa92a953..536c4dc48 100644 --- a/packages/taler-util/src/talerCrypto.ts +++ b/packages/taler-util/src/talerCrypto.ts @@ -126,7 +126,7 @@ export function decodeCrock(encoded: string): Uint8Array { } export function eddsaGetPublic(eddsaPriv: Uint8Array): Uint8Array { - const pair = nacl.sign_keyPair_fromSeed(eddsaPriv); + const pair = nacl.crypto_sign_keyPair_fromSeed(eddsaPriv); return pair.publicKey; } @@ -353,7 +353,7 @@ export function hash(d: Uint8Array): Uint8Array { } export function eddsaSign(msg: Uint8Array, eddsaPriv: Uint8Array): Uint8Array { - const pair = nacl.sign_keyPair_fromSeed(eddsaPriv); + const pair = nacl.crypto_sign_keyPair_fromSeed(eddsaPriv); return nacl.sign_detached(msg, pair.secretKey); } @@ -447,3 +447,56 @@ export function setupRefreshTransferPub( ecdhePub: ecdheGetPublic(out), }; } + +export enum TalerSignaturePurpose { + MERCHANT_TRACK_TRANSACTION = 1103, + WALLET_RESERVE_WITHDRAW = 1200, + WALLET_COIN_DEPOSIT = 1201, + MASTER_DENOMINATION_KEY_VALIDITY = 1025, + MASTER_WIRE_FEES = 1028, + MASTER_WIRE_DETAILS = 1030, + WALLET_COIN_MELT = 1202, + TEST = 4242, + MERCHANT_PAYMENT_OK = 1104, + MERCHANT_CONTRACT = 1101, + WALLET_COIN_RECOUP = 1203, + WALLET_COIN_LINK = 1204, + EXCHANGE_CONFIRM_RECOUP = 1039, + EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041, + ANASTASIS_POLICY_UPLOAD = 1400, + ANASTASIS_POLICY_DOWNLOAD = 1401, + SYNC_BACKUP_UPLOAD = 1450, +} + +export class SignaturePurposeBuilder { + private chunks: Uint8Array[] = []; + + constructor(private purposeNum: number) {} + + put(bytes: Uint8Array): SignaturePurposeBuilder { + this.chunks.push(Uint8Array.from(bytes)); + return this; + } + + build(): Uint8Array { + let payloadLen = 0; + for (const c of this.chunks) { + payloadLen += c.byteLength; + } + const buf = new ArrayBuffer(4 + 4 + payloadLen); + const u8buf = new Uint8Array(buf); + let p = 8; + for (const c of this.chunks) { + u8buf.set(c, p); + p += c.byteLength; + } + const dvbuf = new DataView(buf); + dvbuf.setUint32(0, payloadLen + 4 + 4); + dvbuf.setUint32(4, this.purposeNum); + return u8buf; + } +} + +export function buildSigPS(purposeNum: number): SignaturePurposeBuilder { + return new SignaturePurposeBuilder(purposeNum); +} diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts index 4ca0576b0..c42ece778 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts @@ -34,7 +34,14 @@ import { CoinSourceType, } from "../../db.js"; -import { CoinDepositPermission, RecoupRequest, RefreshPlanchetInfo } from "@gnu-taler/taler-util"; +import { + buildSigPS, + CoinDepositPermission, + RecoupRequest, + RefreshPlanchetInfo, + SignaturePurposeBuilder, + TalerSignaturePurpose, +} from "@gnu-taler/taler-util"; // FIXME: These types should be internal to the wallet! import { BenchmarkResult, @@ -80,24 +87,6 @@ import bigint from "big-integer"; const logger = new Logger("cryptoImplementation.ts"); -enum SignaturePurpose { - MERCHANT_TRACK_TRANSACTION = 1103, - WALLET_RESERVE_WITHDRAW = 1200, - WALLET_COIN_DEPOSIT = 1201, - MASTER_DENOMINATION_KEY_VALIDITY = 1025, - MASTER_WIRE_FEES = 1028, - MASTER_WIRE_DETAILS = 1030, - WALLET_COIN_MELT = 1202, - TEST = 4242, - MERCHANT_PAYMENT_OK = 1104, - MERCHANT_CONTRACT = 1101, - WALLET_COIN_RECOUP = 1203, - WALLET_COIN_LINK = 1204, - EXCHANGE_CONFIRM_RECOUP = 1039, - EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041, - SYNC_BACKUP_UPLOAD = 1450, -} - function amountToBuffer(amount: AmountJson): Uint8Array { const buffer = new ArrayBuffer(8 + 4 + 12); const dvbuf = new DataView(buffer); @@ -139,38 +128,6 @@ function timestampRoundedToBuffer(ts: Timestamp): Uint8Array { return new Uint8Array(b); } -class SignaturePurposeBuilder { - private chunks: Uint8Array[] = []; - - constructor(private purposeNum: number) { } - - put(bytes: Uint8Array): SignaturePurposeBuilder { - this.chunks.push(Uint8Array.from(bytes)); - return this; - } - - build(): Uint8Array { - let payloadLen = 0; - for (const c of this.chunks) { - payloadLen += c.byteLength; - } - const buf = new ArrayBuffer(4 + 4 + payloadLen); - const u8buf = new Uint8Array(buf); - let p = 8; - for (const c of this.chunks) { - u8buf.set(c, p); - p += c.byteLength; - } - const dvbuf = new DataView(buf); - dvbuf.setUint32(0, payloadLen + 4 + 4); - dvbuf.setUint32(4, this.purposeNum); - return u8buf; - } -} - -function buildSigPS(purposeNum: number): SignaturePurposeBuilder { - return new SignaturePurposeBuilder(purposeNum); -} export class CryptoImplementation { static enableTracing = false; @@ -192,7 +149,9 @@ export class CryptoImplementation { const denomPubHash = hash(denomPub); const evHash = hash(ev); - const withdrawRequest = buildSigPS(SignaturePurpose.WALLET_RESERVE_WITHDRAW) + const withdrawRequest = buildSigPS( + TalerSignaturePurpose.WALLET_RESERVE_WITHDRAW, + ) .put(reservePub) .put(amountToBuffer(amountWithFee)) .put(denomPubHash) @@ -236,7 +195,7 @@ export class CryptoImplementation { } signTrackTransaction(req: SignTrackTransactionRequest): string { - const p = buildSigPS(SignaturePurpose.MERCHANT_TRACK_TRANSACTION) + const p = buildSigPS(TalerSignaturePurpose.MERCHANT_TRACK_TRANSACTION) .put(decodeCrock(req.contractTermsHash)) .put(decodeCrock(req.wireHash)) .put(decodeCrock(req.merchantPub)) @@ -249,7 +208,7 @@ export class CryptoImplementation { * Create and sign a message to recoup a coin. */ createRecoupRequest(coin: CoinRecord): RecoupRequest { - const p = buildSigPS(SignaturePurpose.WALLET_COIN_RECOUP) + const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP) .put(decodeCrock(coin.coinPub)) .put(decodeCrock(coin.denomPubHash)) .put(decodeCrock(coin.blindingKey)) @@ -276,7 +235,7 @@ export class CryptoImplementation { contractHash: string, merchantPub: string, ): boolean { - const p = buildSigPS(SignaturePurpose.MERCHANT_PAYMENT_OK) + const p = buildSigPS(TalerSignaturePurpose.MERCHANT_PAYMENT_OK) .put(decodeCrock(contractHash)) .build(); const sigBytes = decodeCrock(sig); @@ -288,7 +247,7 @@ export class CryptoImplementation { * Check if a wire fee is correctly signed. */ isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean { - const p = buildSigPS(SignaturePurpose.MASTER_WIRE_FEES) + const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_FEES) .put(hash(stringToBytes(type + "\0"))) .put(timestampRoundedToBuffer(wf.startStamp)) .put(timestampRoundedToBuffer(wf.endStamp)) @@ -304,7 +263,7 @@ export class CryptoImplementation { * Check if the signature of a denomination is valid. */ isValidDenom(denom: DenominationRecord, masterPub: string): boolean { - const p = buildSigPS(SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY) + const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY) .put(decodeCrock(masterPub)) .put(timestampRoundedToBuffer(denom.stampStart)) .put(timestampRoundedToBuffer(denom.stampExpireWithdraw)) @@ -334,7 +293,9 @@ export class CryptoImplementation { stringToBytes(paytoUri + "\0"), new Uint8Array(0), ); - const p = buildSigPS(SignaturePurpose.MASTER_WIRE_DETAILS).put(h).build(); + const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS) + .put(h) + .build(); return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub)); } @@ -344,7 +305,7 @@ export class CryptoImplementation { merchantPub: string, ): boolean { const cthDec = decodeCrock(contractTermsHash); - const p = buildSigPS(SignaturePurpose.MERCHANT_CONTRACT) + const p = buildSigPS(TalerSignaturePurpose.MERCHANT_CONTRACT) .put(cthDec) .build(); return eddsaVerify(p, decodeCrock(sig), decodeCrock(merchantPub)); @@ -364,8 +325,8 @@ export class CryptoImplementation { eddsaGetPublic(key: string): { priv: string; pub: string } { return { priv: key, - pub: encodeCrock(eddsaGetPublic(decodeCrock(key))) - } + pub: encodeCrock(eddsaGetPublic(decodeCrock(key))), + }; } /** @@ -392,7 +353,7 @@ export class CryptoImplementation { * and deposit permissions for each given coin. */ signDepositPermission(depositInfo: DepositInfo): CoinDepositPermission { - const d = buildSigPS(SignaturePurpose.WALLET_COIN_DEPOSIT) + const d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT) .put(decodeCrock(depositInfo.contractTermsHash)) .put(decodeCrock(depositInfo.wireInfoHash)) .put(decodeCrock(depositInfo.denomPubHash)) @@ -503,7 +464,7 @@ export class CryptoImplementation { } const sessionHash = sessionHc.finish(); - const confirmData = buildSigPS(SignaturePurpose.WALLET_COIN_MELT) + const confirmData = buildSigPS(TalerSignaturePurpose.WALLET_COIN_MELT) .put(sessionHash) .put(decodeCrock(meltCoinDenomPubHash)) .put(amountToBuffer(valueWithFee)) @@ -549,7 +510,7 @@ export class CryptoImplementation { coinEv: string, ): string { const coinEvHash = hash(decodeCrock(coinEv)); - const coinLink = buildSigPS(SignaturePurpose.WALLET_COIN_LINK) + const coinLink = buildSigPS(TalerSignaturePurpose.WALLET_COIN_LINK) .put(decodeCrock(newDenomHash)) .put(decodeCrock(transferPub)) .put(coinEvHash) @@ -622,9 +583,7 @@ export class CryptoImplementation { } else { hOld = new Uint8Array(64); } - const sigBlob = new SignaturePurposeBuilder( - SignaturePurpose.SYNC_BACKUP_UPLOAD, - ) + const sigBlob = buildSigPS(TalerSignaturePurpose.SYNC_BACKUP_UPLOAD) .put(hOld) .put(hNew) .build(); diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 079ffcd9a..8fad55994 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -763,6 +763,8 @@ async function processDownloadProposalImpl( proposalResp.contract_terms, ); + logger.info(`Contract terms hash: ${contractTermsHash}`); + let parsedContractTerms: ContractTerms; try { diff --git a/packages/taler-wallet-core/src/util/contractTerms.ts b/packages/taler-wallet-core/src/util/contractTerms.ts index 652ef707d..b064079e9 100644 --- a/packages/taler-wallet-core/src/util/contractTerms.ts +++ b/packages/taler-wallet-core/src/util/contractTerms.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see */ -import { canonicalJson } from "@gnu-taler/taler-util"; +import { canonicalJson, Logger } from "@gnu-taler/taler-util"; import { kdf } from "@gnu-taler/taler-util"; import { decodeCrock, @@ -24,6 +24,8 @@ import { stringToBytes, } from "@gnu-taler/taler-util"; +const logger = new Logger("contractTerms.ts"); + export namespace ContractTermsUtil { export type PathPredicate = (path: string[]) => boolean; @@ -222,6 +224,8 @@ export namespace ContractTermsUtil { export function hashContractTerms(contractTerms: unknown): string { const cleaned = scrub(contractTerms); const canon = canonicalJson(cleaned) + "\0"; - return encodeCrock(hash(stringToBytes(canon))); + const bytes = stringToBytes(canon); + logger.info(`contract terms before hashing: ${encodeCrock(bytes)}`); + return encodeCrock(hash(bytes)); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66ab99b34..84fcccced 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,11 +16,15 @@ importers: specifiers: '@gnu-taler/taler-util': workspace:^0.8.3 ava: ^3.15.0 + fetch-ponyfill: ^7.1.0 hash-wasm: ^4.9.0 + node-fetch: ^3.0.0 typescript: ^4.4.3 dependencies: '@gnu-taler/taler-util': link:../taler-util + fetch-ponyfill: 7.1.0 hash-wasm: 4.9.0 + node-fetch: 3.0.0 devDependencies: ava: 3.15.0 typescript: 4.4.3 @@ -32,6 +36,7 @@ importers: '@types/jest': ^26.0.8 '@typescript-eslint/eslint-plugin': ^2.25.0 '@typescript-eslint/parser': ^2.25.0 + anastasis-core: workspace:^0.0.1 enzyme: ^3.11.0 enzyme-adapter-preact-pure: ^3.1.0 eslint: ^6.8.0 @@ -46,6 +51,7 @@ importers: typescript: ^3.7.5 dependencies: '@gnu-taler/taler-util': link:../taler-util + anastasis-core: link:../anastasis-core preact: 10.5.14 preact-render-to-string: 5.1.19_preact@10.5.14 preact-router: 3.2.1_preact@10.5.14 @@ -9701,6 +9707,11 @@ packages: assert-plus: 1.0.0 dev: true + /data-uri-to-buffer/3.0.1: + resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} + engines: {node: '>= 6'} + dev: false + /data-urls/1.1.0: resolution: {integrity: sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==} dependencies: @@ -11267,6 +11278,19 @@ packages: bser: 2.1.1 dev: true + /fetch-blob/3.1.2: + resolution: {integrity: sha512-hunJbvy/6OLjCD0uuhLdp0mMPzP/yd2ssd1t2FCJsaA7wkWhpbp9xfuNVpv7Ll4jFhzp6T4LAupSiV9uOeg0VQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + web-streams-polyfill: 3.1.1 + dev: false + + /fetch-ponyfill/7.1.0: + resolution: {integrity: sha512-FhbbL55dj/qdVO3YNK7ZEkshvj3eQ7EuIGV2I6ic/2YiocvyWv+7jg2s4AyS0wdRU75s3tA8ZxI/xPigb0v5Aw==} + dependencies: + node-fetch: 2.6.1 + dev: false + /fflate/0.6.0: resolution: {integrity: sha512-u4AdW/Xx7iinDhYQuS0B0vvbUX7JWXO07jEvYUlbNZvtoiDLkDvHR17LSwxhbawjZVDXczzLHAQUDSllISm4/A==} dev: false @@ -11658,6 +11682,7 @@ packages: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + requiresBuild: true dev: true optional: true @@ -15032,7 +15057,14 @@ packages: /node-fetch/2.6.1: resolution: {integrity: sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==} engines: {node: 4.x || >=6.0.0} - dev: true + + /node-fetch/3.0.0: + resolution: {integrity: sha512-bKMI+C7/T/SPU1lKnbQbwxptpCrG9ashG+VkytmXCPZyuM9jB6VU+hY0oi4lC8LxTtAeWdckNCTa3nrGsAdA3Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 3.0.1 + fetch-blob: 3.1.2 + dev: false /node-forge/0.10.0: resolution: {integrity: sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==} @@ -20331,6 +20363,11 @@ packages: resolution: {integrity: sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==} dev: true + /web-streams-polyfill/3.1.1: + resolution: {integrity: sha512-Czi3fG883e96T4DLEPRvufrF2ydhOOW1+1a6c3gNjH2aIh50DNFBdfwh2AKoOf1rXvpvavAoA11Qdq9+BKjE0Q==} + engines: {node: '>= 8'} + dev: false + /webidl-conversions/4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} dev: true