diff --git a/docs/README.md b/docs/README.md index 742df20..75d5639 100644 --- a/docs/README.md +++ b/docs/README.md @@ -42,7 +42,7 @@ An ID of a known permission on Android. #### Defined in -[android.ts:880](https://github.com/tweaselORG/appstraction/blob/main/src/android.ts#L880) +[android.ts:877](https://github.com/tweaselORG/appstraction/blob/main/src/android.ts#L877) ___ @@ -112,7 +112,7 @@ An ID of a known permission on iOS. #### Defined in -[ios.ts:503](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L503) +[ios.ts:508](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L508) ___ @@ -320,7 +320,7 @@ The IDs of known permissions on Android. #### Defined in -[android.ts:749](https://github.com/tweaselORG/appstraction/blob/main/src/android.ts#L749) +[android.ts:746](https://github.com/tweaselORG/appstraction/blob/main/src/android.ts#L746) ___ @@ -332,7 +332,7 @@ The IDs of known permissions on iOS. #### Defined in -[ios.ts:486](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L486) +[ios.ts:491](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L491) ## Functions diff --git a/package.json b/package.json index 2f879c5..077a124 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "@napi-rs/lzma": "^1.1.2", "andromatic": "^1.0.0", "autopy": "1.0.0", - "asn1js": "^3.0.5", "bplist-creator": "^0.1.1", "bplist-parser": "^0.3.2", "cross-fetch": "^3.1.5", @@ -65,9 +64,9 @@ "fs-extra": "^11.1.0", "global-cache-dir": "^5.0.0", "ipa-extract-info": "^1.2.6", + "node-forge": "^1.3.1", "node-ssh": "^13.1.0", "p-retry": "^5.1.2", - "pkijs": "^3.0.14", "semver": "^7.3.8", "tempy": "^3.0.0", "ts-node": "^10.9.1", @@ -81,6 +80,7 @@ "@parcel/transformer-typescript-types": "2.8.2", "@types/fs-extra": "^11.0.0", "@types/node": "^18.11.18", + "@types/node-forge": "^1.3.2", "@types/plist": "^3.0.2", "@types/promise-timeout": "^1.3.0", "@types/semver": "^7.3.13", diff --git a/src/android.ts b/src/android.ts index a845fb4..1220d6f 100644 --- a/src/android.ts +++ b/src/android.ts @@ -6,6 +6,7 @@ import { createHash } from 'crypto'; import { fileTypeFromFile } from 'file-type'; import frida from 'frida'; import { open, rm, writeFile } from 'fs/promises'; +import forge from 'node-forge'; import pRetry from 'p-retry'; import { basename } from 'path'; import { major as semverMajor, minVersion as semverMinVersion } from 'semver'; @@ -21,15 +22,9 @@ import type { import { dependencies } from '../package.json'; import { venvOptions } from '../scripts/common/python'; import type { ParametersExceptFirst, XapkManifest } from './utils'; -import { - asyncUnimplemented, - getObjFromFridaScript, - isRecord, - parseAppMeta, - retryCondition, -} from './utils'; +import { asyncUnimplemented, getObjFromFridaScript, isRecord, parseAppMeta, retryCondition } from './utils'; +import { certSubjectToAsn1, parsePemCertificateFromFile } from './utils/crypto'; import { forEachInZip, getFileFromZip, tmpFileFromZipEntry } from './utils/zip'; -import { parsePemCertificateFromFile} from './utils/crypto'; const adb = (...args: ParametersExceptFirst) => runAndroidDevTool('adb', args[0], args[1]); const venv = getVenv(venvOptions); @@ -177,7 +172,9 @@ export const androidApi = >( getCertificateSubjectHashOld: async (path: string) => { const { cert } = await parsePemCertificateFromFile(path); - const hash = createHash('md5').update(Buffer.from(cert.subject.valueBeforeDecode)).digest(); + const hash = createHash('md5') + .update(forge.asn1.toDer(certSubjectToAsn1(cert)).toHex(), 'hex') + .digest(); const truncated = hash.subarray(0, 4); const ulong = (truncated[0]! | (truncated[1]! << 8) | (truncated[2]! << 16) | (truncated[3]! << 24)) >>> 0; diff --git a/src/ios.ts b/src/ios.ts index 6d6333f..9a1c2c9 100644 --- a/src/ios.ts +++ b/src/ios.ts @@ -4,18 +4,21 @@ import { parseFile } from 'bplist-parser'; import { createHash } from 'crypto'; import { execa } from 'execa'; import frida from 'frida'; -import { exists,mkdirp } from 'fs-extra'; -import { readFile,writeFile } from 'fs/promises'; +import { exists, mkdirp } from 'fs-extra'; +import { readFile, writeFile } from 'fs/promises'; import globalCacheDir from 'global-cache-dir'; import { NodeSSH } from 'node-ssh'; import { join } from 'path'; -import type { PlatformApi,PlatformApiOptions,Proxy,SupportedCapability,SupportedRunTarget } from '.'; +import type { PlatformApi, PlatformApiOptions, Proxy, SupportedCapability, SupportedRunTarget } from '.'; import { venvOptions } from '../scripts/common/python'; -import { asyncUnimplemented,getObjFromFridaScript,isRecord,retryCondition } from './utils'; +import { asyncUnimplemented, getObjFromFridaScript, isRecord, retryCondition } from './utils'; import { arrayBufferToPem, + asn1ValueToDer, certificateFingerprint, certificateHasExpired, + certSubjectToAsn1, + createPkcs12Container, generateCertificate, parsePemCertificateFromFile, pemToArrayBuffer, @@ -170,53 +173,54 @@ export const iosApi = >( if (!plist) throw new Error('Failed to ensure supervision mode: Invalid CloudConfiguration.'); let hostCert; - let hostKey; if ( (await exists(join(cacheDir, 'ios', 'supervisorCert.pem'))) && - (await exists(join(cacheDir, 'ios', 'supervisorPrivateKey.pem'))) + (await exists(join(cacheDir, 'ios', 'supervisorKeyStore.p12'))) ) { - hostCert = pemToArrayBuffer((await readFile(join(cacheDir, 'ios', 'supervisorCert.pem'))).toString()); - hostKey = pemToArrayBuffer( - (await readFile(join(cacheDir, 'ios', 'supervisorPrivateKey.pem'))).toString() - ); + hostCert = (await readFile(join(cacheDir, 'ios', 'supervisorCert.pem'))).toString(); if (!(await certificateHasExpired(hostCert))) { const hostCertFingerprint = await certificateFingerprint(hostCert); - // Test if the current host certificate is already controlling the device. - if ( - plist.IsSupervised && - plist.SupervisorHostCertificates && - plist.SupervisorHostCertificates.length > 0 && - plist.SupervisorHostCertificates.some( - async (cert) => (await certificateFingerprint(cert)) === hostCertFingerprint + try { + // Test if the current host certificate is already controlling the device. + if ( + plist.IsSupervised && + plist.SupervisorHostCertificates && + plist.SupervisorHostCertificates.length > 0 && + plist.SupervisorHostCertificates.some( + (cert) => + certificateFingerprint(arrayBufferToPem(cert, 'CERTIFICATE')) === + hostCertFingerprint + ) ) - ) - return; + return; + } catch (e) { + // The certificate is invalid, so we need to generate a new one. + hostCert = undefined; + } } else { hostCert = undefined; - hostKey = undefined; } } - if (!hostCert || !hostKey) { + if (!hostCert) { // We have no exsiting keys, so let’s generate one. const generated = await generateCertificate(OrganizationName); hostCert = generated.certificate; - hostKey = generated.privateKey; + const hostKey = generated.privateKey; + + const keyStore = createPkcs12Container(hostCert, hostKey, 'appstraction'); await mkdirp(join(cacheDir, 'ios')); - await writeFile(join(cacheDir, 'ios', 'supervisorCert.pem'), arrayBufferToPem(hostCert, 'CERTIFICATE')); - await writeFile( - join(cacheDir, 'ios', 'supervisorPrivateKey.pem'), - arrayBufferToPem(hostKey, 'PRIVATE KEY') - ); + await writeFile(join(cacheDir, 'ios', 'supervisorCert.pem'), hostCert); + await writeFile(join(cacheDir, 'ios', 'supervisorKeyStore.p12'), Buffer.from(keyStore.toHex(), 'hex')); } const newPlist = { ...plist, - SupervisorHostCertificates: [Buffer.from(hostCert)], + SupervisorHostCertificates: [Buffer.from(pemToArrayBuffer(hostCert))], IsSupervised: true, OrganizationName, AllowPairing: true, @@ -426,7 +430,7 @@ export const iosApi = >( const { cert, certDer } = await parsePemCertificateFromFile(path); const sha256 = createHash('sha256').update(certDer).digest('hex'); - const subj = Buffer.from(cert.subject.toSchema().valueBlock.toBER()).toString('hex'); + const subj = asn1ValueToDer(certSubjectToAsn1(cert)).toHex(); const tset = Buffer.from( ` @@ -445,6 +449,7 @@ export const iosApi = >( throw new Error('SSH is required for removing a certificate authority.'); const { certDer } = await parsePemCertificateFromFile(path); + const sha256 = createHash('sha256').update(certDer).digest('hex'); await this._internal.ssh( diff --git a/src/types/forge.d.ts b/src/types/forge.d.ts new file mode 100644 index 0000000..990c4b7 --- /dev/null +++ b/src/types/forge.d.ts @@ -0,0 +1,7 @@ +import type forge from 'node-forge'; + +declare module 'node-forge' { + namespace pki { + function distinguishedNameToAsn1(dn: forge.pki.Certificate['subject' | 'issuer']): forge.pki.Asn1; + } +} diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts index ed79a39..8c02d93 100644 --- a/src/utils/crypto.ts +++ b/src/utils/crypto.ts @@ -1,161 +1,65 @@ -import { BmpString, Integer } from 'asn1js'; -import { webcrypto } from 'crypto'; import { readFile } from 'fs/promises'; -import { - AttributeTypeAndValue, - AuthenticatedSafe, - CertBag, - Certificate, - CryptoEngine, - PFX, - PKCS8ShroudedKeyBag, - PrivateKeyInfo, - SafeBag, - SafeContents, - setEngine, -} from 'pkijs'; - -const crypto = new CryptoEngine({ name: 'node-webcrypto', crypto: webcrypto as Crypto }); -setEngine('node-webcrypto', crypto); // We need to do this, because there is a bug in pkijs (https://github.com/PeculiarVentures/PKI.js/issues/379) +import forge from 'node-forge'; +const { pki, md } = forge; export const generateCertificate = async (commonName: string, days?: number) => { - const algorithm = crypto.getAlgorithmParameters('RSA-PSS', 'generateKey'); - const { privateKey, publicKey } = await crypto.generateKey( - algorithm.algorithm as EcKeyAlgorithm, - true, - algorithm.usages - ); + const keyPair = await new Promise((res, rej) => { + pki.rsa.generateKeyPair({ bits: 2048 }, (err, keyPair) => (err ? rej(err) : res(keyPair))); + }); + const cert = pki.createCertificate(); - const cert = new Certificate(); + cert.publicKey = keyPair.publicKey; cert.version = 2; - cert.serialNumber = new Integer({ value: Date.now() }); - cert.notBefore.value = new Date(); - cert.notAfter.value = new Date(); - cert.notAfter.value.setDate(cert.notBefore.value.getDate() + (days || 365)); + cert.serialNumber = Date.now().toString(10); + cert.validity.notBefore = new Date(); + cert.validity.notAfter = new Date(); + cert.validity.notAfter.setDate(cert.validity.notBefore.getDate() + (days || 365)); - cert.issuer.typesAndValues.push( - new AttributeTypeAndValue({ - type: '2.5.4.3', // Common name - value: new BmpString({ value: commonName }), - }) - ); - cert.subject.typesAndValues.push( - new AttributeTypeAndValue({ - type: '2.5.4.3', // Common name - value: new BmpString({ value: commonName }), - }) - ); + const attributes = [ + { + name: 'commonName', + value: commonName, + }, + ]; + cert.setSubject(attributes); + cert.setIssuer(attributes); - await cert.subjectPublicKeyInfo.importKey(publicKey, crypto); - await cert.sign(privateKey, 'SHA-256', crypto); + cert.sign(keyPair.privateKey, md.sha256.create()); return { - certificate: cert.toSchema().toBER(false), - privateKey: await crypto.exportKey('pkcs8', privateKey), + certificate: pki.certificateToPem(cert), + privateKey: pki.privateKeyToPem(keyPair.privateKey), }; }; -export const certificateFingerprint = async (certificateBuffer: ArrayBuffer, hashAlgorithm?: 'SHA-256' | 'SHA-1') => { - const certificate = await Certificate.fromBER(certificateBuffer); - const hash = await crypto.digest( - hashAlgorithm || 'SHA-256', - certificate.subjectPublicKeyInfo.toSchema().toBER(false) - ); - return Buffer.from(hash).toString('hex'); +export const certificateFingerprint = (certificatePem: string, hashAlgorithm?: 'SHA-256' | 'SHA-1') => { + const cert = pki.certificateFromPem(certificatePem); + return pki.getPublicKeyFingerprint(cert.publicKey, { + type: 'SubjectPublicKeyInfo', + md: hashAlgorithm === 'SHA-1' ? md.sha1.create() : md.sha256.create(), + encoding: 'hex', + }); }; -export const certificateHasExpired = async (certificateBuffer: ArrayBuffer) => { - const certificate = await Certificate.fromBER(certificateBuffer); - return certificate.notAfter.value < new Date(); +export const certificateHasExpired = (certificatePem: string) => { + const cert = pki.certificateFromPem(certificatePem); + return cert.validity.notAfter < new Date(); }; -export const createPkcs12Container = async (cert: ArrayBuffer, key: ArrayBuffer, password?: string) => { - const encodedPassword = new TextEncoder().encode(password || '').buffer; - - const pkcs12 = new PFX({ - parsedValue: { - integrityMode: 0, // Password-Based Integrity Mode - authenticatedSafe: new AuthenticatedSafe({ - parsedValue: { - safeContents: [ - { - privacyMode: 0, // 0 - No privacy mode - value: new SafeContents({ - safeBags: [ - new SafeBag({ - bagId: '1.2.840.113549.1.12.10.1.2', // Shrouded Private Key Bag - bagValue: new PKCS8ShroudedKeyBag({ - parsedValue: PrivateKeyInfo.fromBER(key), - }), - }), - ], - }), - }, - { - privacyMode: 1, // 1 - Password based privacy mode, - value: new SafeContents({ - safeBags: [ - new SafeBag({ - bagId: '1.2.840.113549.1.12.10.1.3', // Certificate bag - bagValue: new CertBag({ - parsedValue: Certificate.fromBER(cert), - }), - }), - ], - }), - }, - ], - }, - }), - }, - }); - - if (!pkcs12.parsedValue?.authenticatedSafe) - throw new Error('Broken certificate container: pkcs12.parsedValue.authenticatedSafe is empty'); - - await pkcs12.parsedValue.authenticatedSafe.parsedValue.safeContents[0].value.safeBags[0].bagValue.makeInternalValues( - { - password: encodedPassword, - contentEncryptionAlgorithm: { - name: 'AES-CBC', // OpenSSL can only handle AES-CBC (https://github.com/PeculiarVentures/PKI.js/blob/469c403d102ee5149e8eb9ad19754c9696ed7c55/test/pkcs12SimpleExample.ts#L438) - length: 128, - }, - hmacHashAlgorithm: 'SHA-1', // OpenSSL can only handle SHA-1 (https://github.com/PeculiarVentures/PKI.js/blob/469c403d102ee5149e8eb9ad19754c9696ed7c55/test/pkcs12SimpleExample.ts#L441) - iterationCount: 100000, - }, - crypto - ); - - pkcs12.parsedValue.authenticatedSafe.makeInternalValues( - { - safeContents: [ - { - // Private key contents are encrypted differently, so this needs to be empty. - }, - { - password: encodedPassword, - contentEncryptionAlgorithm: { - name: 'AES-CBC', - length: 128, - }, - hmacHashAlgorithm: 'SHA-1', - iterationCount: 100000, - }, - ], - }, - crypto +export const createPkcs12Container = ( + certPem: string, + keyPem: string, + password?: string, + algorithm?: 'aes256' | '3des' +) => { + const p12 = forge.pkcs12.toPkcs12Asn1( + pki.privateKeyFromPem(keyPem), + pki.certificateFromPem(certPem), + password || '', + { algorithm: algorithm || '3des' } // Apparently any sane algorithm is not supported by the typical ingestors (like go), so we default to 3des ); - await pkcs12.makeInternalValues( - { - password: encodedPassword, - iterations: 100000, - pbkdf2HashAlgorithm: 'SHA-256', - hmacHashAlgorithm: 'SHA-256', - }, - crypto - ); - return pkcs12.toSchema().toBER(); + return forge.asn1.toDer(p12); }; export const arrayBufferToPem = (buffer: ArrayBuffer, tag: 'CERTIFICATE' | 'PRIVATE KEY' | 'PUBLIC KEY') => { @@ -163,16 +67,6 @@ export const arrayBufferToPem = (buffer: ArrayBuffer, tag: 'CERTIFICATE' | 'PRIV return `-----BEGIN ${tag}-----\n${base64.replace(/(.{64})/g, '$1\n').trim()}\n-----END ${tag}-----`; // Thanks Copilot! }; -export const parsePemCertificateFromFile = async (path: string) => { - const certPem = await readFile(path, 'utf8'); - - // A PEM certificate is just a base64-encoded DER certificate with a header and footer. - const certBase64 = certPem.replace(/(-----(BEGIN|END) CERTIFICATE-----|[\r\n])/g, ''); - const certDer = Buffer.from(certBase64, 'base64'); - - return { cert: Certificate.fromBER(certDer), certPem, certDer }; -}; - export const pemToArrayBuffer = (pem: string) => { const base64 = pem .replace(/-----BEGIN (.*)-----/, '') @@ -180,3 +74,20 @@ export const pemToArrayBuffer = (pem: string) => { .replace(/\n/g, ''); return Uint8Array.from(Buffer.from(base64, 'base64')).buffer; }; + +export const parsePemCertificateFromFile = async (path: string) => { + const certPem = await readFile(path, 'utf8'); + const cert = pki.certificateFromPem(certPem); + + return { cert, certPem, certDer: Buffer.from(pemToArrayBuffer(certPem)) }; +}; + +export const certSubjectToAsn1 = (cert: forge.pki.Certificate) => forge.pki.distinguishedNameToAsn1(cert.subject); + +export const asn1ValueToDer = (asn1: forge.asn1.Asn1) => + typeof asn1.value === 'string' + ? forge.util.createBuffer(asn1.value) + : asn1.value.reduce((acc, cur) => { + acc.putBuffer(forge.asn1.toDer(cur)); + return acc; + }, forge.util.createBuffer()); diff --git a/yarn.lock b/yarn.lock index 7fa40d1..9bc96bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1337,6 +1337,13 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== +"@types/node-forge@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.2.tgz#4aee4f269961fe288fe6dc35ad0ec6f71646d2bc" + integrity sha512-TzX3ahoi9xbmaoT58smrBu7oa6dQXb/+PTNCslZyD/55tlJ/osofIMClzZsoo6buDFrg7e4DvVGkZqVgv6OLxw== + dependencies: + "@types/node" "*" + "@types/node@*", "@types/node@^18.11.18": version "18.11.18" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" @@ -1639,15 +1646,6 @@ asn1@^0.2.4: dependencies: safer-buffer "~2.1.0" -asn1js@^3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38" - integrity sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ== - dependencies: - pvtsutils "^1.3.2" - pvutils "^1.1.3" - tslib "^2.4.0" - astral-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" @@ -1880,11 +1878,6 @@ buildcheck@0.0.5: resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.5.tgz#5b7c0830b25dc61422032eeb5c18bfcaa9eebb8d" integrity sha512-jYWpRy8eedl/JZqkOeq0X0bNcaK04hXKhIi4gYsDKZUJWRjJJWViYfsMXO0BJQ40zSLcdLoa+iqe48Kz2PtQag== -bytestreamjs@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/bytestreamjs/-/bytestreamjs-2.0.1.tgz#a32947c7ce389a6fa11a09a9a563d0a45889535e" - integrity sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ== - cachedir@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" @@ -4292,6 +4285,11 @@ node-fetch@^2.6.6, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" +node-forge@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + node-gyp-build-optional-packages@5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17" @@ -4628,17 +4626,6 @@ pkg-dir@^5.0.0: dependencies: find-up "^5.0.0" -pkijs@^3.0.14: - version "3.0.14" - resolved "https://registry.yarnpkg.com/pkijs/-/pkijs-3.0.14.tgz#92571f61122fd1e0ccdbc328b6efab9467b0bc30" - integrity sha512-Fi9++44BaOY0VcOEJql27D/HzHIeMU9R48XclfL98Cp8Wh/gGfPbuS1RUwReHQHRIUfzW32eoNO1izxoBMZi6w== - dependencies: - asn1js "^3.0.5" - bytestreamjs "^2.0.0" - pvtsutils "^1.3.2" - pvutils "^1.1.3" - tslib "^2.4.0" - please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" @@ -4742,18 +4729,6 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pvtsutils@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.2.tgz#9f8570d132cdd3c27ab7d51a2799239bf8d8d5de" - integrity sha512-+Ipe2iNUyrZz+8K/2IOo+kKikdtfhRKzNpQbruF2URmqPtoqAs8g3xS7TJvFF2GcPXjh7DkqMnpVveRFq4PgEQ== - dependencies: - tslib "^2.4.0" - -pvutils@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.3.tgz#f35fc1d27e7cd3dfbd39c0826d173e806a03f5a3" - integrity sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ== - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"