diff --git a/.rules b/.rules index 4054e1a2..53d5ecfd 100644 --- a/.rules +++ b/.rules @@ -26,7 +26,7 @@ Every time you choose to apply a rule(s), explicitly state the rule(s) in the ou - Use modern C++ features. - Attempt to reduce the amount of code rather than add more. - Prefer iteration and modularization over code duplication. -- Do not add comments unless explicitly told to do so. +- Do not add comments unless explicitly told to do so, or the code is sufficiently complex to warrant comments. ## TypeScript Best Practices @@ -34,6 +34,7 @@ Every time you choose to apply a rule(s), explicitly state the rule(s) in the ou - Use lowercase with dashes for directories (e.g., `components/auth-wizard`). - Favor named exports for components. - Avoid `any` and enums; use explicit types and maps instead. +- Do not cast to `unknown` and then another type. - Use functional components with TypeScript interfaces. - Enable strict mode in TypeScript for better type safety. - Suggest the optimal implementation considering: diff --git a/bun.lock b/bun.lock index 5ff51249..014ae957 100644 --- a/bun.lock +++ b/bun.lock @@ -41,6 +41,7 @@ "react": "19.1.0", "react-native": "0.81.1", "react-native-bouncy-checkbox": "4.1.2", + "react-native-fast-encoder": "0.2.0", "react-native-nitro-modules": "0.29.1", "react-native-quick-base64": "2.2.1", "react-native-quick-crypto": "workspace:*", @@ -1395,6 +1396,8 @@ "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + "flatbuffers": ["flatbuffers@2.0.6", "", {}, "sha512-QTTZTXTbVfuOVQu2X6eLOw4vefUxnFJZxAKeN3rEPhjEzBtIbehimJLfVGHPM8iX0Na+9i76SBEg0skf0c0sCA=="], + "flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="], "flow-enums-runtime": ["flow-enums-runtime@0.0.6", "", {}, "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw=="], @@ -2187,6 +2190,8 @@ "react-native-builder-bob": ["react-native-builder-bob@0.39.1", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-transform-strict-mode": "^7.24.7", "@babel/preset-env": "^7.25.2", "@babel/preset-flow": "^7.24.7", "@babel/preset-react": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "babel-plugin-module-resolver": "^5.0.2", "browserslist": "^4.20.4", "cross-spawn": "^7.0.3", "dedent": "^0.7.0", "del": "^6.1.1", "escape-string-regexp": "^4.0.0", "fs-extra": "^10.1.0", "glob": "^8.0.3", "is-git-dirty": "^2.0.1", "json5": "^2.2.1", "kleur": "^4.1.4", "metro-config": "^0.80.9", "prompts": "^2.4.2", "which": "^2.0.2", "yargs": "^17.5.1" }, "bin": { "bob": "bin/bob" } }, "sha512-nEG9FB5a2Rxw0251dnlM9QtqvuM2os8avRhYDWDdvsZOnQJhQI4fGV5wF5FypAqHNWPQUNXmvhPUFrPSwiPnAQ=="], + "react-native-fast-encoder": ["react-native-fast-encoder@0.2.0", "", { "dependencies": { "big-integer": "^1.6.51", "flatbuffers": "2.0.6" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-E4mx81fRMVs0qq8is3cZTrbuEJdsDo8Nfe7qTxKZwsCianpYpA2QfyH6cEYumSOEht6l+KeRJ4RqcyfxMDyesg=="], + "react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.2.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q=="], "react-native-nitro-modules": ["react-native-nitro-modules@0.29.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-91A/Lc4Zc1Bvzj1iMSnD6vA5Swqv8aVcwGcv8ddjoPd9mahNvVS2arFh3o7kAqRH4RIh3KcQ0NpYslu7AYn55Q=="], diff --git a/docs/implementation-coverage.md b/docs/implementation-coverage.md index e74cdce1..d8816750 100644 --- a/docs/implementation-coverage.md +++ b/docs/implementation-coverage.md @@ -1,8 +1,9 @@ -# Implementation Coverage +# Implementation Coverage - NodeJS This document attempts to describe the implementation status of Crypto APIs/Interfaces from Node.js in the `react-native-quick-crypto` library. > Note: This is the status for version 1.x and higher. For version `0.x` see [this document](https://github.com/margelo/react-native-quick-crypto/blob/0.x/docs/implementation-coverage.md) and the [0.x branch](https://github.com/margelo/react-native-quick-crypto/tree/0.x). +* ` ` - not implemented in Node * ❌ - implemented in Node, not RNQC * ✅ - implemented in Node and RNQC @@ -12,13 +13,13 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * ❌ Static method: `Certificate.exportChallenge(spkac[, encoding])` * ❌ Static method: `Certificate.exportPublicKey(spkac[, encoding])` * ❌ Static method: `Certificate.verifySpkac(spkac[, encoding])` -* ✅ Class: `Cipher` +* ✅ Class: `Cipheriv` * ✅ `cipher.final([outputEncoding])` * ✅ `cipher.getAuthTag()` * ✅ `cipher.setAAD(buffer[, options])` * ✅ `cipher.setAutoPadding([autoPadding])` * ✅ `cipher.update(data[, inputEncoding][, outputEncoding])` -* ✅ Class: `Decipher` +* ✅ Class: `Decipheriv` * ✅ `decipher.final([outputEncoding])` * ✅ `decipher.setAAD(buffer[, options])` * ✅ `decipher.setAuthTag(buffer[, encoding])` @@ -36,7 +37,7 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * ❌ `diffieHellman.verifyError` * ❌ Class: `DiffieHellmanGroup` * ❌ Class: `ECDH` - * ❌ `Static method: ECDH.convertKey(key, curve[, inputEncoding[, outputEncoding[, format]]])` + * ❌ static `ECDH.convertKey(key, curve[, inputEncoding[, outputEncoding[, format]]])` * ❌ `ecdh.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])` * ❌ `ecdh.generateKeys([encoding[, format]])` * ❌ `ecdh.getPrivateKey([encoding])` @@ -50,14 +51,15 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * ✅ Class: `Hmac` * ✅ `hmac.digest([encoding])` * ✅ `hmac.update(data[, inputEncoding])` -* ❌ Class: `KeyObject` - * ❌ `Static method: KeyObject.from(key)` +* 🚧 Class: `KeyObject` + * ❌ static `KeyObject.from(key)` * ❌ `keyObject.asymmetricKeyDetails` - * ❌ `keyObject.asymmetricKeyType` - * ❌ `keyObject.export([options])` + * ✅ `keyObject.asymmetricKeyType` + * ✅ `keyObject.export([options])` * ❌ `keyObject.equals(otherKeyObject)` * ❌ `keyObject.symmetricKeySize` - * ❌ `keyObject.type` + * ❌ `keyObject.toCryptoKey(algorithm, extractable, keyUsages)` + * ✅ `keyObject.type` * ❌ Class: `Sign` * ❌ `sign.sign(privateKey[, outputEncoding])` * ❌ `sign.update(data[, inputEncoding])` @@ -91,10 +93,11 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * ❌ `x509.validTo` * ❌ `x509.verify(publicKey)` * 🚧 node:crypto module methods and properties - * ❌ `crypto.constants` - * ❌ `crypto.fips` + * ❌ `crypto.argon2(algorithm, parameters, callback)` + * ❌ `crypto.argon2Sync(algorithm, parameters)` * ❌ `crypto.checkPrime(candidate[, options], callback)` * ❌ `crypto.checkPrimeSync(candidate[, options])` + * ❌ `crypto.constants` * ✅ `crypto.createCipheriv(algorithm, key, iv[, options])` * ✅ `crypto.createDecipheriv(algorithm, key, iv[, options])` * ❌ `crypto.createDiffieHellman(prime[, primeEncoding][, generator][, generatorEncoding])` @@ -108,8 +111,10 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * ❌ `crypto.createSecretKey(key[, encoding])` * ❌ `crypto.createSign(algorithm[, options])` * ❌ `crypto.createVerify(algorithm[, options])` - * 🚧 `crypto.diffieHellman(options[, callback])` - * ❌ `crypto.hash(algorithm, data[, outputEncoding])` + * ❌ `crypto.decapsulate(key, ciphertext[, callback])` + * ❌ `crypto.diffieHellman(options[, callback])` + * ❌ `crypto.encapsulate(key[, callback])` + * ❌ `crypto.fips` deprecated * ❌ `crypto.generateKey(type, options, callback)` * 🚧 `crypto.generateKeyPair(type, options, callback)` * 🚧 `crypto.generateKeyPairSync(type, options)` @@ -123,6 +128,7 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * ❌ `crypto.getFips()` * ✅ `crypto.getHashes()` * ✅ `crypto.getRandomValues(typedArray)` + * ❌ `crypto.hash(algorithm, data[, options])` * ❌ `crypto.hkdf(digest, ikm, salt, info, keylen, callback)` * ❌ `crypto.hkdfSync(digest, ikm, salt, info, keylen)` * ✅ `crypto.pbkdf2(password, salt, iterations, keylen, digest, callback)` @@ -132,8 +138,8 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * ❌ `crypto.publicDecrypt(key, buffer)` * ❌ `crypto.publicEncrypt(key, buffer)` * ✅ `crypto.randomBytes(size[, callback])` - * ✅ `crypto.randomFillSync(buffer[, offset][, size])` * ✅ `crypto.randomFill(buffer[, offset][, size], callback)` + * ✅ `crypto.randomFillSync(buffer[, offset][, size])` * ✅ `crypto.randomInt([min, ]max[, callback])` * ✅ `crypto.randomUUID([options])` * ❌ `crypto.scrypt(password, salt, keylen[, options], callback)` @@ -142,7 +148,7 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * ❌ `crypto.setEngine(engine[, flags])` * ❌ `crypto.setFips(bool)` * 🚧 `crypto.sign(algorithm, data, key[, callback])` - * ❌ `crypto.subtle` (see below) + * 🚧 `crypto.subtle` (see below) * ❌ `crypto.timingSafeEqual(a, b)` * 🚧 `crypto.verify(algorithm, data, key, signature[, callback])` * ❌ `crypto.webcrypto` (see below) @@ -198,22 +204,22 @@ This document attempts to describe the implementation status of Crypto APIs/Inte ## `crypto.sign` | Algorithm | Status | | --------- | :----: | -| `RSASSA-PKCS1-v1_5` | | -| `RSA-PSS` | | -| `ECDSA` | | +| `RSASSA-PKCS1-v1_5` | ❌ | +| `RSA-PSS` | ❌ | +| `ECDSA` | ❌ | | `Ed25519` | ✅ | | `Ed448` | ✅ | -| `HMAC` | | +| `HMAC` | ❌ | ## `crypto.verify` | Algorithm | Status | | --------- | :----: | -| `RSASSA-PKCS1-v1_5` | | -| `RSA-PSS` | | -| `ECDSA` | | +| `RSASSA-PKCS1-v1_5` | ❌ | +| `RSA-PSS` | ❌ | +| `ECDSA` | ❌ | | `Ed25519` | ✅ | | `Ed448` | ✅ | -| `HMAC` | | +| `HMAC` | ❌ | # `WebCrypto` @@ -221,31 +227,37 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * ❌ `crypto.subtle` * ❌ `crypto.getRandomValues(typedArray)` * ❌ `crypto.randomUUID()` -* ❌ Class: `CryptoKey` - * ❌ `cryptoKey.algorithm` - * ❌ `cryptoKey.extractable` - * ❌ `cryptoKey.type` - * ❌ `cryptoKey.usages` -* ❌ Class: `CryptoKeyPair` - * ❌ `cryptoKeyPair.privateKey` - * ❌ `cryptoKeyPair.publicKey` +* ✅ Class: `CryptoKey` + * ✅ `cryptoKey.algorithm` + * ✅ `cryptoKey.extractable` + * ✅ `cryptoKey.type` + * ✅ `cryptoKey.usages` +* ✅ Class: `CryptoKeyPair` + * ✅ `cryptoKeyPair.privateKey` + * ✅ `cryptoKeyPair.publicKey` * ❌ Class: `CryptoSubtle` * (see below) # `SubtleCrypto` -* ❌ Class: `SubtleCrypto` - * ❌ `subtle.decrypt(algorithm, key, data)` - * ❌ `subtle.deriveBits(algorithm, baseKey, length)` +* 🚧 Class: `SubtleCrypto` + * ❌ static `supports(operation, algorithm[, lengthOrAdditionalAlgorithm])` + * ❌ `subtle.decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext)` + * ❌ `subtle.decapsulateKey(decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages)` + * 🚧 `subtle.decrypt(algorithm, key, data)` + * 🚧 `subtle.deriveBits(algorithm, baseKey, length)` * ❌ `subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)` - * ❌ `subtle.digest(algorithm, data)` - * ❌ `subtle.encrypt(algorithm, key, data)` - * ❌ `subtle.exportKey(format, key)` - * ❌ `subtle.generateKey(algorithm, extractable, keyUsages)` - * ❌ `subtle.importKey(format, keyData, algorithm, extractable, keyUsages)` - * ❌ `subtle.sign(algorithm, key, data)` + * 🚧 `subtle.digest(algorithm, data)` + * ❌ `subtle.encapsulateBits(encapsulationAlgorithm, encapsulationKey)` + * ❌ `subtle.encapsulateKey(encapsulationAlgorithm, encapsulationKey, sharedKeyAlgorithm, extractable, usages)` + * 🚧 `subtle.encrypt(algorithm, key, data)` + * 🚧 `subtle.exportKey(format, key)` + * 🚧 `subtle.generateKey(algorithm, extractable, keyUsages)` + * ❌ `subtle.getPublicKey(key, keyUsages)` + * 🚧 `subtle.importKey(format, keyData, algorithm, extractable, keyUsages)` + * 🚧 `subtle.sign(algorithm, key, data)` * ❌ `subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)` - * ❌ `subtle.verify(algorithm, key, signature, data)` + * 🚧 `subtle.verify(algorithm, key, signature, data)` * ❌ `subtle.wrapKey(format, key, wrappingKey, wrapAlgo)` ## `subtle.decrypt` @@ -259,54 +271,67 @@ This document attempts to describe the implementation status of Crypto APIs/Inte ## `subtle.deriveBits` | Algorithm | Status | | --------- | :----: | -| `ECDH` | | -| `X25519` | | -| `X448` | | -| `HKDF` | | -| `PBKDF2` | ❌ | +| `ECDH` | ❌ | +| `X25519` | ❌ | +| `X448` | ❌ | +| `HKDF` | ❌ | +| `PBKDF2` | ✅ | ## `subtle.deriveKey` | Algorithm | Status | | --------- | :----: | -| `ECDH` | | -| `X25519` | | -| `X448` | | -| `HKDF` | | -| `PBKDF2` | | +| `ECDH` | ❌ | +| `HKDF` | ❌ | +| `PBKDF2` | ❌ | +| `X25519` | ❌ | +| `X448` | ❌ | ## `subtle.digest` -| Algorithm | Status | -| --------- | :----: | -| `SHA-1` | ❌ | -| `SHA-256` | ❌ | -| `SHA-384` | ❌ | -| `SHA-512` | ❌ | +| Algorithm | Status | +| --------- | :----: | +| `cSHAKE128` | ❌ | +| `cSHAKE256` | ❌ | +| `SHA-1` | ✅ | +| `SHA-256` | ✅ | +| `SHA-384` | ✅ | +| `SHA-512` | ✅ | +| `SHA3-256` | ❌ | +| `SHA3-384` | ❌ | +| `SHA3-512` | ❌ | ## `subtle.encrypt` -| Algorithm | Status | -| --------- | :----: | -| `RSA-OAEP` | ❌ | -| `AES-CTR` | ❌ | -| `AES-CBC` | ❌ | -| `AES-GCM` | ❌ | +| Algorithm | Status | +| ------------------- | :----: | +| `AES-CTR` | ❌ | +| `AES-CBC` | ❌ | +| `AES-GCM` | ❌ | +| `AES-OCB` | ❌ | +| `ChaCha20-Poly1305` | ❌ | +| `RSA-OAEP` | ❌ | ## `subtle.exportKey` -| Key Type | `spki` | `pkcs8` | `jwk` | `raw` | -| ------------------- | :----: | :-----: | :---: | :---: | -| `AES-CBC` | | | ❌ | ❌ | -| `AES-CTR` | | | ❌ | ❌ | -| `AES-GCM` | | | ❌ | ❌ | -| `AES-KW` | | | ❌ | ❌ | -| `ECDH` | ❌ | ❌ | ❌ | ❌ | -| `ECDSA` | ❌ | ❌ | ❌ | ❌ | -| `Ed25519` | ❌ | ❌ | ❌ | ❌ | -| `Ed448` | ❌ | ❌ | ❌ | ❌ | -| `HDKF` | | | | | -| `HMAC` | | | ❌ | ❌ | -| `PBKDF2` | | | | | -| `RSA-OAEP` | ❌ | ❌ | ❌ | | -| `RSA-PSS` | ❌ | ❌ | ❌ | | -| `RSASSA-PKCS1-v1_5` | ❌ | ❌ | ❌ | | +| Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` | +| ------------------- | :----: | :-----: | :---: | :---: | :---: | :---: | :---: | +| `AES-CBC` | | | ❌ | ❌ | ❌ | | | +| `AES-CTR` | | | ❌ | ❌ | ❌ | | | +| `AES-GCM` | | | ❌ | ❌ | ❌ | | | +| `AES-KW` | | | ❌ | ❌ | ❌ | | | +| `AES-OCB` | | | ❌ | | ❌ | | | +| `ChaCha20-Poly1305` | | | ❌ | | ❌ | | | +| `ECDH` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `ECDSA` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `Ed25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `Ed448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `HMAC` | | | ❌ | ❌ | ❌ | | | +| `ML-DSA-44` | ❌ | ❌ | ❌ | | | ❌ | ❌ | +| `ML-DSA-65` | ❌ | ❌ | ❌ | | | ❌ | ❌ | +| `ML-DSA-87` | ❌ | ❌ | ❌ | | | ❌ | ❌ | +| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | +| `RSA-OAEP` | ❌ | ❌ | ❌ | | | | | +| `RSA-PSS` | ❌ | ❌ | ❌ | | | | | +| `RSASSA-PKCS1-v1_5` | ❌ | ❌ | ❌ | | | | | * ` ` - not implemented in Node * ❌ - implemented in Node, not RNQC @@ -317,105 +342,135 @@ This document attempts to describe the implementation status of Crypto APIs/Inte ### `CryptoKeyPair` algorithms | Algorithm | Status | | --------- | :----: | -| `RSASSA-PKCS1-v1_5` | ❌ | -| `RSA-PSS` | ❌ | -| `RSA-OAEP` | ❌ | +| `ECDH` | ❌ | | `ECDSA` | ❌ | | `Ed25519` | ❌ | -| `Ed448` | | -| `ECDH` | | -| `X25519` | | -| `X448` | | +| `Ed448` | ❌ | +| `ML-DSA-44` | ❌ | +| `ML-DSA-65` | ❌ | +| `ML-DSA-87` | ❌ | +| `ML-KEM-512` | ❌ | +| `ML-KEM-768` | ❌ | +| `ML-KEM-1024` | ❌ | +| `RSA-OAEP` | ❌ | +| `RSA-PSS` | ❌ | +| `RSASSA-PKCS1-v1_5` | ❌ | +| `X25519` | ❌ | +| `X448` | ❌ | ### `CryptoKey` algorithms -| Algorithm | Status | -| --------- | :----: | -| `HMAC` | | -| `AES-CTR` | ❌ | -| `AES-CBC` | ❌ | -| `AES-GCM` | ❌ | -| `AES-KW` | ❌ | +| Algorithm | Status | +| --------- | :----: | +| `AES-CTR` | ❌ | +| `AES-CBC` | ❌ | +| `AES-GCM` | ❌ | +| `AES-KW` | ❌ | +| `AES-OCB` | ❌ | +| `ChaCha20-Poly1305` | ❌ | +| `HMAC` | ❌ | ## `subtle.importKey` -| Key Type | `spki` | `pkcs8` | `jwk` | `raw` | -| ------------------- | :----: | :-----: | :---: | :---: | -| `AES-CBC` | | | ❌ | ❌ | -| `AES-CTR` | | | ❌ | ❌ | -| `AES-GCM` | | | ❌ | ❌ | -| `AES-KW` | | | ❌ | ❌ | -| `ECDH` | ❌ | ❌ | ❌ | ❌ | -| `X25519` | ❌ | ❌ | ❌ | ❌ | -| `X448` | ❌ | ❌ | ❌ | ❌ | -| `ECDSA` | ❌ | ❌ | ❌ | ❌ | -| `Ed25519` | ❌ | ❌ | ❌ | ❌ | -| `Ed448` | ❌ | ❌ | ❌ | ❌ | -| `HDKF` | | | | | -| `HMAC` | | | ❌ | ❌ | -| `PBKDF2` | | | | ❌ | -| `RSA-OAEP` | ❌ | ❌ | ❌ | | -| `RSA-PSS` | ❌ | ❌ | ❌ | | -| `RSASSA-PKCS1-v1_5` | ❌ | ❌ | ❌ | | - -* ` ` - not implemented in Node -* ❌ - implemented in Node, not RNQC -* ✅ - implemented in Node and RNQC +| Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` | +| ------------------- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| `AES-CBC` | | | ❌ | ❌ | ❌ | | | +| `AES-CTR` | | | ❌ | ❌ | ❌ | | | +| `AES-GCM` | | | ❌ | ❌ | ❌ | | | +| `AES-KW` | | | ❌ | ❌ | ❌ | | | +| `AES-OCB` | | | ❌ | | ❌ | | | +| `ChaCha20-Poly1305` | | | ❌ | | ❌ | | | +| `ECDH` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `ECDSA` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `Ed25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `Ed448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `HDKF` | | | | ❌ | ❌ | | | +| `HMAC` | | | ❌ | ❌ | ❌ | | | +| `ML-DSA-44` | ❌ | ❌ | ❌ | | | ❌ | ❌ | +| `ML-DSA-65` | ❌ | ❌ | ❌ | | | ❌ | ❌ | +| `ML-DSA-87` | ❌ | ❌ | ❌ | | | ❌ | ❌ | +| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | +| `PBKDF2` | | | | ❌ | ❌ | | | +| `RSA-OAEP` | ❌ | ❌ | ❌ | | | | | +| `RSA-PSS` | ❌ | ❌ | ❌ | | | | | +| `RSASSA-PKCS1-v1_5` | ❌ | ❌ | ❌ | | | | | +| `X25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `X448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | ## `subtle.sign` | Algorithm | Status | | --------- | :----: | -| `RSASSA-PKCS1-v1_5` | | -| `RSA-PSS` | | | `ECDSA` | ❌ | -| `Ed25519` | | -| `Ed448` | | -| `HMAC` | | +| `Ed25519` | ❌ | +| `Ed448` | ❌ | +| `HMAC` | ❌ | +| `ML-DSA-44` | ❌ | +| `ML-DSA-65` | ❌ | +| `ML-DSA-87` | ❌ | +| `RSA-PSS` | ❌ | +| `RSASSA-PKCS1-v1_5` | ❌ | ## `subtle.unwrapKey` ### wrapping algorithms -| Algorithm | Status | -| --------- | :----: | -| `RSA-OAEP` | | -| `AES-CTR` | | -| `AES-CBC` | | -| `AES-GCM` | | -| `AES-KW` | | +| Algorithm | Status | +| ------------------- | :----: | +| `AES-CBC` | ❌ | +| `AES-CTR` | ❌ | +| `AES-GCM` | ❌ | +| `AES-KW` | ❌ | +| `AES-OCB` | ❌ | +| `ChaCha20-Poly1305` | ❌ | +| `RSA-OAEP` | ❌ | ### unwrapped key algorithms | Algorithm | Status | | --------- | :----: | -| `RSASSA-PKCS1-v1_5` | | -| `RSA-PSS` | | -| `RSA-OAEP` | | -| `ECDSA` | | -| `Ed25519` | | -| `Ed448` | | -| `ECDH` | | -| `X25519` | | -| `X448` | | -| `HMAC` | | -| `AES-CTR` | | -| `AES-CBC` | | -| `AES-GCM` | | -| `AES-KW` | | +| `AES-CBC` | ❌ | +| `AES-CTR` | ❌ | +| `AES-GCM` | ❌ | +| `AES-KW` | ❌ | +| `AES-OCB` | ❌ | +| `ChaCha20-Poly1305` | ❌ | +| `ECDH` | ❌ | +| `ECDSA` | ❌ | +| `Ed25519` | ❌ | +| `Ed448` | ❌ | +| `HMAC` | ❌ | +| `ML-DSA-44` | ❌ | +| `ML-DSA-65` | ❌ | +| `ML-DSA-87` | ❌ | +| `ML-KEM-512` | ❌ | +| `ML-KEM-768` | ❌ | +| `ML-KEM-1024` | ❌ | +| `RSA-OAEP` | ❌ | +| `RSA-PSS` | ❌ | +| `RSASSA-PKCS1-v1_5` | ❌ | +| `X25519` | ❌ | +| `X448` | ❌ | ## `subtle.verify` | Algorithm | Status | | --------- | :----: | -| `RSASSA-PKCS1-v1_5` | | -| `RSA-PSS` | | | `ECDSA` | ❌ | -| `Ed25519` | | -| `Ed448` | | -| `HMAC` | | +| `Ed25519` | ❌ | +| `Ed448` | ❌ | +| `HMAC` | ❌ | +| `ML-DSA-44` | ❌ | +| `ML-DSA-65` | ❌ | +| `ML-DSA-87` | ❌ | +| `RSA-PSS` | ❌ | +| `RSASSA-PKCS1-v1_5` | ❌ | ## `subtle.wrapKey` ### wrapping algorithms -| Algorithm | Status | -| --------- | :----: | -| `RSA-OAEP` | | -| `AES-CTR` | | -| `AES-CBC` | | -| `AES-GCM` | | -| `AES-KW` | | +| Algorithm | Status | +| ------------------- | :----: | +| `AES-CBC` | ❌ | +| `AES-CTR` | ❌ | +| `AES-GCM` | ❌ | +| `AES-KW` | ❌ | +| `AES-OCB` | ❌ | +| `ChaCha20-Poly1305` | ❌ | +| `RSA-OAEP` | ❌ | diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 3639019f..79b23c90 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,26 +1,15 @@ PODS: - - boost (1.84.0) - - DoubleConversion (1.1.6) - - fast_float (8.0.0) - FBLazyVector (0.81.1) - - fmt (11.0.2) - - glog (0.3.5) - hermes-engine (0.81.1): - hermes-engine/Pre-built (= 0.81.1) - hermes-engine/Pre-built (0.81.1) - NitroModules (0.29.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-callinvoker - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -35,24 +24,18 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - OpenSSL-Universal (3.3.3001) - QuickCrypto (1.0.0-beta.20): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - hermes-engine - NitroModules - OpenSSL-Universal (= 3.3.3001) - - RCT-Folly - - RCT-Folly/Fabric - RCTRequired - RCTTypeSafety - React-callinvoker - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -67,27 +50,8 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - - RCT-Folly (2024.11.18.00): - - boost - - DoubleConversion - - fast_float (= 8.0.0) - - fmt (= 11.0.2) - - glog - - RCT-Folly/Default (= 2024.11.18.00) - - RCT-Folly/Default (2024.11.18.00): - - boost - - DoubleConversion - - fast_float (= 8.0.0) - - fmt (= 11.0.2) - - glog - - RCT-Folly/Fabric (2024.11.18.00): - - boost - - DoubleConversion - - fast_float (= 8.0.0) - - fmt (= 11.0.2) - - glog - RCTDeprecation (0.81.1) - RCTRequired (0.81.1) - RCTTypeSafety (0.81.1): @@ -109,15 +73,9 @@ PODS: - React-RCTVibration (= 0.81.1) - React-callinvoker (0.81.1) - React-Core (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default (= 0.81.1) - React-cxxreact - React-featureflags @@ -131,18 +89,14 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga + - React-Core-prebuilt (0.81.1): + - ReactNativeDependencies - React-Core/CoreModulesHeaders (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -156,18 +110,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/Default (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-cxxreact - React-featureflags - React-hermes @@ -180,18 +128,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/DevSupport (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default (= 0.81.1) - React-Core/RCTWebSocket (= 0.81.1) - React-cxxreact @@ -206,18 +148,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTActionSheetHeaders (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -231,18 +167,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTAnimationHeaders (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -256,18 +186,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTBlobHeaders (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -281,18 +205,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTImageHeaders (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -306,18 +224,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTLinkingHeaders (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -331,18 +243,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTNetworkHeaders (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -356,18 +262,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTSettingsHeaders (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -381,18 +281,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTTextHeaders (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -406,18 +300,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTVibrationHeaders (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -431,18 +319,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTWebSocket (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default (= 0.81.1) - React-cxxreact - React-featureflags @@ -456,17 +338,11 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-CoreModules (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - RCTTypeSafety (= 0.81.1) + - React-Core-prebuilt - React-Core/CoreModulesHeaders (= 0.81.1) - React-jsi (= 0.81.1) - React-jsinspector @@ -478,17 +354,11 @@ PODS: - React-RCTImage (= 0.81.1) - React-runtimeexecutor - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-cxxreact (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker (= 0.81.1) + - React-Core-prebuilt - React-debug (= 0.81.1) - React-jsi (= 0.81.1) - React-jsinspector @@ -498,17 +368,11 @@ PODS: - React-perflogger (= 0.81.1) - React-runtimeexecutor - React-timing (= 0.81.1) - - SocketRocket + - ReactNativeDependencies - React-debug (0.81.1) - React-defaultsnativemodule (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-domnativemodule - React-featureflagsnativemodule - React-idlecallbacksnativemodule @@ -516,16 +380,10 @@ PODS: - React-jsiexecutor - React-microtasksnativemodule - React-RCTFBReactNativeSpec - - SocketRocket + - ReactNativeDependencies - React-domnativemodule (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-Fabric - React-Fabric/bridging - React-FabricComponents @@ -535,20 +393,14 @@ PODS: - React-RCTFBReactNativeSpec - React-runtimeexecutor - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-Fabric (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric/animations (= 0.81.1) @@ -578,19 +430,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/animations (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -603,19 +449,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/attributedstring (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -628,19 +468,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/bridging (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -653,19 +487,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/componentregistry (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -678,19 +506,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/componentregistrynative (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -703,19 +525,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/components (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric/components/legacyviewmanagerinterop (= 0.81.1) @@ -732,19 +548,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/components/legacyviewmanagerinterop (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -757,19 +567,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/components/root (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -782,19 +586,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/components/scrollview (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -807,19 +605,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/components/view (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -833,20 +625,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-Fabric/consistency (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -859,19 +645,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/core (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -884,19 +664,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/dom (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -909,19 +683,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/imagemanager (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -934,19 +702,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/leakchecker (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -959,19 +721,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/mounting (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -984,19 +740,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/observers (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric/observers/events (= 0.81.1) @@ -1010,19 +760,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/observers/events (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1035,19 +779,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/scheduler (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric/observers/events @@ -1062,19 +800,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/telemetry (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1087,19 +819,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/templateprocessor (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1112,19 +838,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/uimanager (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric/uimanager/consistency (= 0.81.1) @@ -1139,19 +859,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/uimanager/consistency (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1165,19 +879,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-FabricComponents (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1193,20 +901,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1231,20 +933,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/inputaccessory (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1258,20 +954,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/iostextinput (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1285,20 +975,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/modal (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1312,20 +996,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/rncore (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1339,20 +1017,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/safeareaview (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1366,20 +1038,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/scrollview (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1393,20 +1059,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/switch (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1420,20 +1080,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/text (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1447,20 +1101,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/textinput (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1474,20 +1122,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/unimplementedview (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1501,20 +1143,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/virtualview (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1528,20 +1164,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/textlayoutmanager (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1555,19 +1185,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricImage (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired (= 0.81.1) - RCTTypeSafety (= 0.81.1) + - React-Core-prebuilt - React-Fabric - React-featureflags - React-graphics @@ -1578,54 +1202,30 @@ PODS: - React-rendererdebug - React-utils - ReactCommon - - SocketRocket + - ReactNativeDependencies - Yoga - React-featureflags (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket + - React-Core-prebuilt + - ReactNativeDependencies - React-featureflagsnativemodule (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-featureflags - React-jsi - React-jsiexecutor - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-graphics (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-jsi - React-jsiexecutor - React-utils - - SocketRocket + - ReactNativeDependencies - React-hermes (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-cxxreact (= 0.81.1) - React-jsi - React-jsiexecutor (= 0.81.1) @@ -1634,72 +1234,42 @@ PODS: - React-jsinspectortracing - React-perflogger (= 0.81.1) - React-runtimeexecutor - - SocketRocket + - ReactNativeDependencies - React-idlecallbacksnativemodule (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-jsi - React-jsiexecutor - React-RCTFBReactNativeSpec - React-runtimeexecutor - React-runtimescheduler - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-ImageManager (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-Core/Default - React-debug - React-Fabric - React-graphics - React-rendererdebug - React-utils - - SocketRocket + - ReactNativeDependencies - React-jserrorhandler (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags - React-jsi - ReactCommon/turbomodule/bridging - - SocketRocket + - ReactNativeDependencies - React-jsi (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket + - hermes-engine + - React-Core-prebuilt + - ReactNativeDependencies - React-jsiexecutor (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-cxxreact (= 0.81.1) - React-jsi (= 0.81.1) - React-jsinspector @@ -1707,16 +1277,10 @@ PODS: - React-jsinspectortracing - React-perflogger (= 0.81.1) - React-runtimeexecutor - - SocketRocket + - ReactNativeDependencies - React-jsinspector (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-featureflags - React-jsi - React-jsinspectorcdp @@ -1725,104 +1289,78 @@ PODS: - React-oscompat - React-perflogger (= 0.81.1) - React-runtimeexecutor - - SocketRocket + - ReactNativeDependencies - React-jsinspectorcdp (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket + - React-Core-prebuilt + - ReactNativeDependencies - React-jsinspectornetwork (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-featureflags - React-jsinspectorcdp - React-performancetimeline - React-timing - - SocketRocket + - ReactNativeDependencies - React-jsinspectortracing (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-oscompat - React-timing - - SocketRocket + - ReactNativeDependencies - React-jsitooling (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-cxxreact (= 0.81.1) - React-jsi (= 0.81.1) - React-jsinspector - React-jsinspectorcdp - React-jsinspectortracing - React-runtimeexecutor - - SocketRocket + - ReactNativeDependencies - React-jsitracing (0.81.1): - React-jsi - React-logger (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket + - React-Core-prebuilt + - ReactNativeDependencies - React-Mapbuffer (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-debug - - SocketRocket + - ReactNativeDependencies - React-microtasksnativemodule (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-jsi - React-jsiexecutor - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies + - react-native-fast-encoder (0.2.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga - react-native-quick-base64 (2.2.1): - React-Core - react-native-safe-area-context (5.6.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -1839,20 +1377,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-safe-area-context/common (5.6.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -1867,20 +1399,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-safe-area-context/fabric (5.6.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -1896,19 +1422,13 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-NativeModulesApple (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker - React-Core + - React-Core-prebuilt - React-cxxreact - React-featureflags - React-jsi @@ -1917,60 +1437,36 @@ PODS: - React-runtimeexecutor - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-oscompat (0.81.1) - React-perflogger (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket + - React-Core-prebuilt + - ReactNativeDependencies - React-performancetimeline (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-featureflags - React-jsinspectortracing - React-perflogger - React-timing - - SocketRocket + - ReactNativeDependencies - React-RCTActionSheet (0.81.1): - React-Core/RCTActionSheetHeaders (= 0.81.1) - React-RCTAnimation (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - RCTTypeSafety + - React-Core-prebuilt - React-Core/RCTAnimationHeaders - React-featureflags - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTAppDelegate (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-CoreModules - React-debug - React-defaultsnativemodule @@ -1992,16 +1488,10 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTBlob (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-Core/RCTBlobHeaders - React-Core/RCTWebSocket - React-jsi @@ -2011,17 +1501,11 @@ PODS: - React-RCTFBReactNativeSpec - React-RCTNetwork - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTFabric (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-FabricComponents @@ -2045,37 +1529,25 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-RCTFBReactNativeSpec (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec/components (= 0.81.1) - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTFBReactNativeSpec/components (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2085,24 +1557,18 @@ PODS: - React-rendererdebug - React-utils - ReactCommon - - SocketRocket + - ReactNativeDependencies - Yoga - React-RCTImage (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - RCTTypeSafety + - React-Core-prebuilt - React-Core/RCTImageHeaders - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec - React-RCTNetwork - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTLinking (0.81.1): - React-Core/RCTLinkingHeaders (= 0.81.1) - React-jsi (= 0.81.1) @@ -2111,14 +1577,8 @@ PODS: - ReactCommon - ReactCommon/turbomodule/core (= 0.81.1) - React-RCTNetwork (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - RCTTypeSafety + - React-Core-prebuilt - React-Core/RCTNetworkHeaders - React-featureflags - React-jsi @@ -2127,17 +1587,11 @@ PODS: - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTRuntime (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-Core + - React-Core-prebuilt - React-jsi - React-jsinspector - React-jsinspectorcdp @@ -2147,63 +1601,39 @@ PODS: - React-RuntimeCore - React-runtimeexecutor - React-RuntimeHermes - - SocketRocket + - ReactNativeDependencies - React-RCTSettings (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - RCTTypeSafety + - React-Core-prebuilt - React-Core/RCTSettingsHeaders - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTText (0.81.1): - React-Core/RCTTextHeaders (= 0.81.1) - Yoga - React-RCTVibration (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-Core/RCTVibrationHeaders - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-rendererconsistency (0.81.1) - React-renderercss (0.81.1): - React-debug - React-utils - React-rendererdebug (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-debug - - SocketRocket + - ReactNativeDependencies - React-RuntimeApple (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker + - React-Core-prebuilt - React-Core/Default - React-CoreModules - React-cxxreact @@ -2222,16 +1652,10 @@ PODS: - React-RuntimeHermes - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - React-RuntimeCore (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-cxxreact - React-Fabric - React-featureflags @@ -2244,29 +1668,17 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - React-runtimeexecutor (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-debug - React-featureflags - React-jsi (= 0.81.1) - React-utils - - SocketRocket + - ReactNativeDependencies - React-RuntimeHermes (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-featureflags - React-hermes - React-jsi @@ -2278,17 +1690,11 @@ PODS: - React-RuntimeCore - React-runtimeexecutor - React-utils - - SocketRocket + - ReactNativeDependencies - React-runtimescheduler (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -2300,35 +1706,23 @@ PODS: - React-runtimeexecutor - React-timing - React-utils - - SocketRocket + - ReactNativeDependencies - React-timing (0.81.1): - React-debug - React-utils (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-debug - React-jsi (= 0.81.1) - - SocketRocket + - ReactNativeDependencies - ReactAppDependencyProvider (0.81.1): - ReactCodegen - ReactCodegen (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-FabricImage @@ -2342,59 +1736,35 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - ReactCommon (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - ReactCommon/turbomodule (= 0.81.1) - - SocketRocket + - ReactNativeDependencies - ReactCommon/turbomodule (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker (= 0.81.1) + - React-Core-prebuilt - React-cxxreact (= 0.81.1) - React-jsi (= 0.81.1) - React-logger (= 0.81.1) - React-perflogger (= 0.81.1) - ReactCommon/turbomodule/bridging (= 0.81.1) - ReactCommon/turbomodule/core (= 0.81.1) - - SocketRocket + - ReactNativeDependencies - ReactCommon/turbomodule/bridging (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker (= 0.81.1) + - React-Core-prebuilt - React-cxxreact (= 0.81.1) - React-jsi (= 0.81.1) - React-logger (= 0.81.1) - React-perflogger (= 0.81.1) - - SocketRocket + - ReactNativeDependencies - ReactCommon/turbomodule/core (0.81.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker (= 0.81.1) + - React-Core-prebuilt - React-cxxreact (= 0.81.1) - React-debug (= 0.81.1) - React-featureflags (= 0.81.1) @@ -2402,19 +1772,14 @@ PODS: - React-logger (= 0.81.1) - React-perflogger (= 0.81.1) - React-utils (= 0.81.1) - - SocketRocket + - ReactNativeDependencies + - ReactNativeDependencies (0.81.1) - RNScreens (4.15.4): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2430,21 +1795,15 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - RNScreens/common (= 4.15.4) - - SocketRocket - Yoga - RNScreens/common (4.15.4): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2460,20 +1819,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNVectorIcons (10.1.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2488,28 +1841,22 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - - SocketRocket (0.7.1) - Yoga (0.0.0) DEPENDENCIES: - - boost (from `../../node_modules/react-native/third-party-podspecs/boost.podspec`) - - DoubleConversion (from `../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - - fast_float (from `../../node_modules/react-native/third-party-podspecs/fast_float.podspec`) - FBLazyVector (from `../../node_modules/react-native/Libraries/FBLazyVector`) - - fmt (from `../../node_modules/react-native/third-party-podspecs/fmt.podspec`) - - glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - NitroModules (from `../../node_modules/react-native-nitro-modules`) - QuickCrypto (from `../../node_modules/react-native-quick-crypto`) - - RCT-Folly (from `../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTDeprecation (from `../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) - RCTRequired (from `../../node_modules/react-native/Libraries/Required`) - RCTTypeSafety (from `../../node_modules/react-native/Libraries/TypeSafety`) - React (from `../../node_modules/react-native/`) - React-callinvoker (from `../../node_modules/react-native/ReactCommon/callinvoker`) - React-Core (from `../../node_modules/react-native/`) + - React-Core-prebuilt (from `../../node_modules/react-native/React-Core-prebuilt.podspec`) - React-Core/RCTWebSocket (from `../../node_modules/react-native/`) - React-CoreModules (from `../../node_modules/react-native/React/CoreModules`) - React-cxxreact (from `../../node_modules/react-native/ReactCommon/cxxreact`) @@ -2537,6 +1884,7 @@ DEPENDENCIES: - React-logger (from `../../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../../node_modules/react-native/ReactCommon`) - React-microtasksnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - react-native-fast-encoder (from `../../node_modules/react-native-fast-encoder`) - react-native-quick-base64 (from `../node_modules/react-native-quick-base64`) - react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`) - React-NativeModulesApple (from `../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) @@ -2569,29 +1917,18 @@ DEPENDENCIES: - ReactAppDependencyProvider (from `build/generated/ios`) - ReactCodegen (from `build/generated/ios`) - ReactCommon/turbomodule/core (from `../../node_modules/react-native/ReactCommon`) + - ReactNativeDependencies (from `../../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`) - RNScreens (from `../../node_modules/react-native-screens`) - RNVectorIcons (from `../../node_modules/react-native-vector-icons`) - - SocketRocket (~> 0.7.1) - Yoga (from `../../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: trunk: - OpenSSL-Universal - - SocketRocket EXTERNAL SOURCES: - boost: - :podspec: "../../node_modules/react-native/third-party-podspecs/boost.podspec" - DoubleConversion: - :podspec: "../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" - fast_float: - :podspec: "../../node_modules/react-native/third-party-podspecs/fast_float.podspec" FBLazyVector: :path: "../../node_modules/react-native/Libraries/FBLazyVector" - fmt: - :podspec: "../../node_modules/react-native/third-party-podspecs/fmt.podspec" - glog: - :podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: :podspec: "../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782 @@ -2599,8 +1936,6 @@ EXTERNAL SOURCES: :path: "../../node_modules/react-native-nitro-modules" QuickCrypto: :path: "../../node_modules/react-native-quick-crypto" - RCT-Folly: - :podspec: "../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" RCTDeprecation: :path: "../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" RCTRequired: @@ -2613,6 +1948,8 @@ EXTERNAL SOURCES: :path: "../../node_modules/react-native/ReactCommon/callinvoker" React-Core: :path: "../../node_modules/react-native/" + React-Core-prebuilt: + :podspec: "../../node_modules/react-native/React-Core-prebuilt.podspec" React-CoreModules: :path: "../../node_modules/react-native/React/CoreModules" React-cxxreact: @@ -2665,6 +2002,8 @@ EXTERNAL SOURCES: :path: "../../node_modules/react-native/ReactCommon" React-microtasksnativemodule: :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-fast-encoder: + :path: "../../node_modules/react-native-fast-encoder" react-native-quick-base64: :path: "../node_modules/react-native-quick-base64" react-native-safe-area-context: @@ -2729,6 +2068,8 @@ EXTERNAL SOURCES: :path: build/generated/ios ReactCommon: :path: "../../node_modules/react-native/ReactCommon" + ReactNativeDependencies: + :podspec: "../../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec" RNScreens: :path: "../../node_modules/react-native-screens" RNVectorIcons: @@ -2737,85 +2078,81 @@ EXTERNAL SOURCES: :path: "../../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb - fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 - FBLazyVector: b8f1312d48447cca7b4abc21ed155db14742bd03 - fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd - glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 + FBLazyVector: 8da1272845e10a35c1e661a4c8ba644526c014fb hermes-engine: 4f8246b1f6d79f625e0d99472d1f3a71da4d28ca - NitroModules: 1715fe0e22defd9e2cdd48fb5e0dbfd01af54bec + NitroModules: e74e119e5c9f75902ef828936f75e4e078f0c4fc OpenSSL-Universal: 6082b0bf950e5636fe0d78def171184e2b3899c2 - QuickCrypto: d11c38c58b9736ced587e2999da13601b01501f9 - RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 - RCTDeprecation: c4b9e2fd0ab200e3af72b013ed6113187c607077 - RCTRequired: e97dd5dafc1db8094e63bc5031e0371f092ae92a - RCTTypeSafety: 720403058b7c1380c6a3ae5706981d6362962c89 + QuickCrypto: 55910fe2f97dc24a4e72cc1f1038a4a5b3fb8ebe + RCTDeprecation: 24056820c873bf5115c0441651440bfde2b82d51 + RCTRequired: d0b6e766be3c17896d92059c917d682433ff8a34 + RCTTypeSafety: 1954dcf545fe67e969e11e1b4fe6cf864e1ac632 React: f1486d005993b0af01943af1850d3d4f3b597545 - React-callinvoker: 133f69368c8559e744efa345223625d412f5dfbe - React-Core: 559823921b4f294c2840fa8238ca958a29ddc211 - React-CoreModules: c41e7bbfabbc420783bb926f45837a0d5e53341e - React-cxxreact: 9cb9fa738274a1b36b97ede09c8a6717dec1a20b - React-debug: e01581e1589f329e61c95b332bf7f4969b10564b - React-defaultsnativemodule: bbb39447caa6b6cf9405fa0099f828c083640faa - React-domnativemodule: 03744d12b6d56d098531a933730bf1d4cb79bdfb - React-Fabric: 530b3993a12a96e8a7cdb9f0ef48e605277b572e - React-FabricComponents: 271ec2a9b2c00ac66fd6d1fd24e9e964d907751d - React-FabricImage: d0af66e976dbab7f8b81e36dd369fc70727d2695 - React-featureflags: 269704c8eff86e0485c9d384e286350fcda6eb70 - React-featureflagsnativemodule: db1e5d88a912fb08a5ece33fcf64e1b732da8467 - React-graphics: b19d03a01b0722b4dc82f47acb56dc3ed41937e7 - React-hermes: 811606c0aca5a3f9c6fa8e4994e02ca8f677e68e - React-idlecallbacksnativemodule: 3a3df629cd50046c7e4354f9025aefe8f2c84601 - React-ImageManager: 0d53866c63132791e37bb2373f93044fdef14aa3 - React-jserrorhandler: d5700d6ab7162fd575287502a3c5d601d98e7f09 - React-jsi: ece95417fedbed0e7153a855cb8342b7c72ab75e - React-jsiexecutor: 2b0bb644b533df2f5c0cd6ade9a4560d0bf1dd84 - React-jsinspector: 0c160f8510a8852bdf2dac12f0b1949efc18200b - React-jsinspectorcdp: f4b84409f453f61ddd8614ad45139bc594ec6bb5 - React-jsinspectornetwork: 8f2f0ca8c871ca19b571f426002c0012e7fb2aee - React-jsinspectortracing: 33f6b977eb8a4bc1e3d1a4b948809aca083143f9 - React-jsitooling: 2c61529b589e17229a9f0a4a4fc35aa7ad495850 - React-jsitracing: 838a7b0c013c4aff7d382d7fdc78cf442013ba1d - React-logger: 7aef4d74123e5e3d267e5af1fbf5135b5a0d8381 - React-Mapbuffer: 91e0eab42a6ae7f3e34091a126d70fc53bd3823e - React-microtasksnativemodule: 1ead4fe154df3b1ba34b5a9e35ef3c4bdfa72ccb + React-callinvoker: 944c9e97ef08e3995b05cc29b40c071a7bd0eaec + React-Core: 33c2f29eb1054f2fe4b6c7bb1e595495170f6c15 + React-Core-prebuilt: 70adad8a28cf9ec5c1f349ced6375354644500ac + React-CoreModules: 6d0945fdc20d150789baaeefe2794b0dbc8f6379 + React-cxxreact: 1d8096423b2b3e7b05c24f40277d117896e8873f + React-debug: 0268bacb2cc38d98790ce15159e962a6b0b1895e + React-defaultsnativemodule: 5f2f1cdd3765b7fd7aed963e9b61cc6292343c46 + React-domnativemodule: 4407d05b3bf87d604be08307e71ca308c7201043 + React-Fabric: f68b67dd472fc90352fe80e2a6837a8642e7ef23 + React-FabricComponents: e138111ec8093b42a3d74dc75865091d99bace64 + React-FabricImage: cedf4cee2a69eb3518753291aa056f65189f09ef + React-featureflags: 7a70fce3a3c7c7745560d5c0ade1d46d7460c9c7 + React-featureflagsnativemodule: 267f61c8648729837851cefe9a5017eb40855473 + React-graphics: 70d3b71cde945c870d5528a4763abc34456a35d8 + React-hermes: 66d0e0adcd5d7d8852efcee7129b1d248d9e76fc + React-idlecallbacksnativemodule: 987fcd4d77fed9d7f3ed5595d08bc283ac302d28 + React-ImageManager: ceccf49e14a8c306e144ed192fe16f7704fbe6dd + React-jserrorhandler: e58b58bd3135795310b52d18d37d86b339d3753e + React-jsi: 1a403eb9781c8ab89d27bbe9a0573d9bb039b6a5 + React-jsiexecutor: 81ea81bfd3f2ef78858e11d13179b740e2ea46d8 + React-jsinspector: 5677b6102cc59b5c89ff471c1f461c8af99ed648 + React-jsinspectorcdp: b4f743cb748c30677faf600f093c6bbd2aa5352b + React-jsinspectornetwork: 4c3fe84b72157f483a292e2605af88ace0c20177 + React-jsinspectortracing: 4313f327e2bf0c6e4e0cfda1949734732e9f5530 + React-jsitooling: 8a6546ee9c511c5497e013c05abbbc4f2dd1e87c + React-jsitracing: 8d11a4f0d444dbc9cf24862728d16afbb0cf67bb + React-logger: a1c9c8dfc56711d06a21109b55908e177113e554 + React-Mapbuffer: bc36232966c55d5b1cbef3b84cb97c4317fa4403 + React-microtasksnativemodule: cbabfedcf6c2984e59d07f9ab553f3d277599b1b + react-native-fast-encoder: f65266038936a5ade3a54d34755fce3c7bd92483 react-native-quick-base64: 7b7f689c9b7b4d40916bf5d27e211db1040abac5 - react-native-safe-area-context: c6e2edd1c1da07bdce287fa9d9e60c5f7b514616 - React-NativeModulesApple: eff2eba56030eb0d107b1642b8f853bc36a833ac - React-oscompat: b12c633e9c00f1f99467b1e0e0b8038895dae436 - React-perflogger: 58d12c4e5df1403030c97b9c621375c312cca454 - React-performancetimeline: 0ee0a3236c77a4ee6d8a6189089e41e4003d292e - React-RCTActionSheet: 3f741a3712653611a6bfc5abceb8260af9d0b218 - React-RCTAnimation: 408ad69ea136e99a463dd33eadecc29e586b3d72 - React-RCTAppDelegate: f03b46e80b8a3dbfa84b35abfe123e02f3ceef83 - React-RCTBlob: bd42e92a00ad22eaab92ffe5c137e7a2f725887a - React-RCTFabric: b99ab638c73cf2d57b886eafdbfb2e4909b0eb9a - React-RCTFBReactNativeSpec: 7ad9aba0e0655e3f29be0a1c3fd4a888fab04dcf - React-RCTImage: 0f1c74f7cd20027f8c34976a211b35d4263a0add - React-RCTLinking: 6d7dfc3a74110df56c3a73cc7626bf4415656542 - React-RCTNetwork: 6a25d8645a80d5b86098675ca39bf8fcf1afa08b - React-RCTRuntime: 38bfe9766565ae3293ca230bc51c9c020a8bc98a - React-RCTSettings: 651d9ae2cdd32f547ad0d225a2c13886d6ad2358 - React-RCTText: 9bc66cd288478e23195e01f5cb45eba79986b2b4 - React-RCTVibration: 371226f5667a00c76d792dcdb5c2e0fcbcde0c3b - React-rendererconsistency: a05f6c37f9389c53213d1e28798e441fa6fbdbcd - React-renderercss: 6e4febfa014b0f53bc171a62b0f713ddbdbb9860 - React-rendererdebug: e94bf27b9d55ef2795caa8e43aa92abc4a373b8b - React-RuntimeApple: 723be5159519eba1cd92449acb29436d21571b82 - React-RuntimeCore: f58eb0f01065c9d27d91de10b2e4ab4c76d83b0e - React-runtimeexecutor: f615ec8742d0b5820170f7c8b4d2c7cb75d93ac9 - React-RuntimeHermes: fddb258e03d330d1132bb19e78fe51ac2f3f41ac - React-runtimescheduler: e92a31460e654ced8587debeec37553315e1b6a5 - React-timing: 97ada2c47b4c5932e7f773c7d239c52b90d6ca68 - React-utils: f0949d247a46b4c09f03e5a3cb1167602d0b729a + react-native-safe-area-context: 42a1b4f8774b577d03b53de7326e3d5757fe9513 + React-NativeModulesApple: 055e2d1417c663e7a26fc0847609f503e626e9e1 + React-oscompat: 8f2893713639e12c7558750a9f7de3f08ac255b0 + React-perflogger: 34b632f94b15e1068f7997e5a68b40a55162ad13 + React-performancetimeline: 6e067040e1af47a26e306c726e228e53718518ac + React-RCTActionSheet: 0280ff8cf63ef67a5a74898b18316b204d686c83 + React-RCTAnimation: a33e999cdfc9ebc8eb17f8f9f262ac765635d3b8 + React-RCTAppDelegate: 8ca4feb7290edf02f70ee824b0ef735c24d78b0e + React-RCTBlob: ab87e8283899ec563c429f55a7a584c8bae78f17 + React-RCTFabric: 465111d965717cbe4e1fe4a5caffc66723423d68 + React-RCTFBReactNativeSpec: 7637e1b9e3838c7a43e870a1c519346ec1787e90 + React-RCTImage: 7e07ef9065bf18dd8566421b550c041455f24bdb + React-RCTLinking: 0877fa82d7a04bbdd6167e7d93ebbab2147d4398 + React-RCTNetwork: f26cc6a43530af00bc6c3c73cfdb0df58e319126 + React-RCTRuntime: c22ce17d068c7447c7406c00050785776cb376a4 + React-RCTSettings: 58a19d845f0e05555246c8325b527bd90cdc88e7 + React-RCTText: 972fe79182d4082d36e988c094c5876f9ddfaab0 + React-RCTVibration: 56c26c46c7c574a46fb424e9ba9445e686ddf53f + React-rendererconsistency: 4917405e8864ded5d3b350f04ee7f1712435d8e7 + React-renderercss: dd4f24445dcb209267e1034df31cc156e336342d + React-rendererdebug: c335166d55d20a7c1a7a7ac0ca491d996f4adf5b + React-RuntimeApple: 9e7edf7c631cf2446fbb728e68bfbbc859d8aabd + React-RuntimeCore: a6a70ca5ab98f08191f6c7dfd3424b4fc86ed24d + React-runtimeexecutor: 2d4128882cf41c7824e9d8a72c6b9461897889af + React-RuntimeHermes: 4e74b3ea837c4bae9903b6a9210f97329007b7f2 + React-runtimescheduler: 5ec6bcc02a05de1e87b9830dbfccff4dc1bddcae + React-timing: b49069174c0330be78b67c8a24d7127dfca99bba + React-utils: de22e0dfdd4494e401dfd0194f552c7175480939 ReactAppDependencyProvider: 3eb9096cb139eb433965693bbe541d96eb3d3ec9 - ReactCodegen: c881aa301b4194d7d08b2a1afcb0b9e108917629 - ReactCommon: ce5d4226dfaf9d5dacbef57b4528819e39d3a120 - RNScreens: db22525a8ed56bb87ab038b8f03a050bf40e6ed8 - RNVectorIcons: c6c73d2c1b89a20536c869c73f6532a9183e7887 - SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 11c9686a21e2cd82a094a723649d9f4507200fb0 + ReactCodegen: c1ea19a3a9eb7813829ef4c4d2bcb7e14caef610 + ReactCommon: 3579ef7cd884fbb5218bed929f25b7b212299700 + ReactNativeDependencies: 9444683ddc7eaa98d2289224ce730a7632b705ff + RNScreens: cbd0bb6537baa89b54bb0493617bc1833fa3d0e0 + RNVectorIcons: 58f509d12a4832393fe7590cebb4716c73493df0 + Yoga: 18b87f28df0aee34fa7370518a35aa8107ba47b9 PODFILE CHECKSUM: 8bf59f4e86b38489f786b2878e119cdf1824ca75 diff --git a/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj b/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj index 636859b0..4223b0ff 100644 --- a/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj +++ b/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj @@ -399,6 +399,7 @@ REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; USE_HERMES = true; }; name = Debug; @@ -482,6 +483,7 @@ OTHER_LDFLAGS = "$(inherited)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; SDKROOT = iphoneos; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; USE_HERMES = true; VALIDATE_PRODUCT = YES; }; diff --git a/example/package.json b/example/package.json index aafd605f..ac01190b 100644 --- a/example/package.json +++ b/example/package.json @@ -36,6 +36,7 @@ "react": "19.1.0", "react-native": "0.81.1", "react-native-bouncy-checkbox": "4.1.2", + "react-native-fast-encoder": "0.2.0", "react-native-nitro-modules": "0.29.1", "react-native-quick-base64": "2.2.1", "react-native-quick-crypto": "workspace:*", diff --git a/example/src/fixtures/aes_cbc.ts b/example/src/fixtures/aes_cbc.ts new file mode 100644 index 00000000..bcb05348 --- /dev/null +++ b/example/src/fixtures/aes_cbc.ts @@ -0,0 +1,153 @@ +import { decodeHex } from '../tests/util'; +import type { + AesEncryptDecryptTestVector, + BadPaddingVectorValue, + VectorValue, +} from '../tests/webcryptoTests/encrypt_decrypt'; + +const kPlaintext = decodeHex( + '546869732073706563696669636174696f6e206465736372696265' + + '732061204a6176615363726970742041504920666f722070657266' + + '6f726d696e672062617369632063727970746f6772617068696320' + + '6f7065726174696f6e7320696e20776562206170706c6963617469' + + '6f6e732c20737563682061732068617368696e672c207369676e61' + + '747572652067656e65726174696f6e20616e642076657269666963' + + '6174696f6e2c20616e6420656e6372797074696f6e20616e642064' + + '656372797074696f6e2e204164646974696f6e616c6c792c206974' + + '2064657363726962657320616e2041504920666f72206170706c69' + + '636174696f6e7320746f2067656e657261746520616e642f6f7220' + + '6d616e61676520746865206b6579696e67206d6174657269616c20' + + '6e656365737361727920746f20706572666f726d20746865736520' + + '6f7065726174696f6e732e205573657320666f7220746869732041' + + '50492072616e67652066726f6d2075736572206f72207365727669' + + '63652061757468656e7469636174696f6e2c20646f63756d656e74' + + '206f7220636f6465207369676e696e672c20616e64207468652063' + + '6f6e666964656e7469616c69747920616e6420696e746567726974' + + '79206f6620636f6d6d756e69636174696f6e732e', +); + +const kKeyBytes: VectorValue = { + '128': decodeHex('dec0d4fcbf3c4741c892dabd1cd4c04e'), + '256': decodeHex( + '67693823fb1d58073f91ece9cc3af910e5532616a4d27b1' + '3eb7b74d8000bbf30', + ), +}; + +const iv = decodeHex('55aaf89ba89413d54ea727a76c27a284'); + +const kCipherText: VectorValue = { + '128': decodeHex( + '237f03fee70872e78faec148ddbd01bd77cb96e3381ef4ece2afea17a7afd37' + + 'ccbe461df9c4d58aea6bbbae1b05cfab1e129877cd756c6867c319a3ce05da5' + + '0cbef5f1a4f7dce345f269d06cdec1df00e2d927a04e93bf2699e8ceddfe19b' + + '9f907b5d76862a3c2a167a1eda70af2255002ffad60146aaa6e5026887f1055' + + 'f44eac386a0373823aba81ecfffbb270189f52fc01b2845c287d12877440b21' + + 'fae577272da4e6f00effc4f3f773a764e37f92482e1cd0d4c61d6faaee84367' + + 'd3b2ce2081bcf364473f9a9fc87d228a2749824b61cbcc6ff44bbab52bcfaf9' + + '262cf1b175a90a113ebc75d62ee48869ddccf42a7ec5e390003cafa371aa314' + + '85bf43143f96cb57d82c39bcec40506f441a0c0aa35203bf1347bac4b154f40' + + '74e29accb1be1e76cce8dddfdccdc8614823671517fc51b65799fdfc173be0c' + + '99aee7c45c8e9c3dbd031299cebe3aff9a7342176b5edc9cdce4f14206b82ce' + + 'ef933f06d8ed0bd0b7546aad9aad842e712af79dd101d8b37675bef6f1d6c5e' + + 'b38a8649821d45b6c0f996a54f2f5bcbe23f57343cacbfbeb3ab9bcd58ac6f3' + + 'b28c6fad194b173c8282ba5a74374409ff051fdeb898431dfd6ac35072fb8df' + + '783b33217c93dd1b3c10fe187373d64b496188d6d1b16a47fed35e3968aaa82' + + '3255dcbc7261c54', + ), + '256': decodeHex( + '29d5798cb5e3c86164853ae36a73193f4d331a39ee8c633f47d38054731aec3' + + '46751910e65a1b53a87c138a7d6dc053455deb71b6586569b40947cd4dbfb41' + + '2a202c80023280dd16ee38bd531c7a799dd7879780e9c141be5694bf8cc4780' + + '8ac64a6fe29f54b3806a6f4b26fea17046b061684bbe61147ac71ee4904b45a' + + '674d2533767081eec707de7aad1ee8b2e9ea90620eea704d443e3e9fe665622' + + 'b02cc459c566880228007ad5a7821683b2dfb5d33f0e83c5ebd865a14b87a1d' + + 'e155d526749f50456aa8ecc9458c62f02da085e16a2df5d4a0b0801b7299b69' + + '091d648c48ab7573df59638529ee032727d7aaca181ea463ff5881e880980dc' + + 'e59ddec395bd46084728c35d1b07eaa4af66c99573f8b37d427ac21a3ddac6b' + + '5988cc730941f0ef1c5034680ef20560fd756f5be5f8d296f00e81c984357c5' + + 'ff760dfb475416e786bcaf738a25c705eec70263cb4b3ee71596ef5ec9b9db3' + + 'ad2e497834c94683c4a5206a831fbb603e8add2c91365a6075e0bc2d392e54b' + + 'f10f32bb24af4ee362e0035fd15d7e70b21d126cf1e84fd22902eed0beab869' + + '3bcbfe57a20d1a67681df82d6c359435eda9bb90090ff84d5193b53f2394594' + + '6d853da31ed6fe36a903d94d427bc1ccc76d7b31badfe508e6a4abc491e10a6' + + 'ff86fa4d836e1fd', + ), +}; + +const kBadPadding: BadPaddingVectorValue = { + '128': { + zeroPadChar: decodeHex('ee1bf8a9da8aa456cf6624df06a64d0e'), + bigPadChar: decodeHex('5b437768fceeaf90114b0ca3d4342e33'), + inconsistentPadChars: decodeHex('876570d0036ae21419db4f5e3ad4f2c0'), + }, + '256': { + zeroPadChar: decodeHex('01fd8dd61ec1fe448cc89d6ec859b181'), + bigPadChar: decodeHex('58076edd4a22616d6319bdde5e5a1b3c'), + inconsistentPadChars: decodeHex('98363c943b88c1154d8caa43784a6a3e'), + }, +}; + +const kKeyLengths = ['128', '256']; + +const passing: AesEncryptDecryptTestVector[] = []; +kKeyLengths.forEach(keyLength => { + passing.push({ + keyBuffer: kKeyBytes[keyLength], + algorithm: { name: 'AES-CBC', iv }, + plaintext: kPlaintext, + result: kCipherText[keyLength], + keyLength, + }); +}); + +const failing: AesEncryptDecryptTestVector[] = []; +kKeyLengths.forEach(keyLength => { + failing.push({ + keyBuffer: kKeyBytes[keyLength], + algorithm: { + name: 'AES-CBC', + iv: iv.slice(0, 8), + }, + plaintext: kPlaintext, + result: kCipherText[keyLength], + keyLength, + }); + + const longIv = new Uint8Array(24); + longIv.set(iv, 0); + longIv.set(iv.slice(0, 8), 16); + failing.push({ + keyBuffer: kKeyBytes[keyLength], + algorithm: { name: 'AES-CBC', iv: longIv }, + plaintext: kPlaintext, + result: kCipherText[keyLength], + keyLength, + }); +}); + +// Scenarios that should fail decryption because of bad padding +const decryptionFailing: AesEncryptDecryptTestVector[] = []; +kKeyLengths.forEach(function (keyLength) { + ['zeroPadChar', 'bigPadChar', 'inconsistentPadChars'].forEach( + paddingProblem => { + // @ts-expect-error bad padding + const badCiphertext = new Uint8Array(kCipherText[keyLength].byteLength); + badCiphertext.set( + // @ts-expect-error bad padding + kCipherText[keyLength].slice(0, kCipherText[keyLength].byteLength - 16), + ); + // @ts-expect-error bad padding + badCiphertext.set(kBadPadding[keyLength][paddingProblem]); + + decryptionFailing.push({ + keyBuffer: kKeyBytes[keyLength], + algorithm: { name: 'AES-CBC', iv }, + plaintext: kPlaintext, + result: badCiphertext, + keyLength, + }); + }, + ); +}); + +export default { passing, failing, decryptionFailing }; diff --git a/example/src/fixtures/aes_ctr.ts b/example/src/fixtures/aes_ctr.ts new file mode 100644 index 00000000..990581d7 --- /dev/null +++ b/example/src/fixtures/aes_ctr.ts @@ -0,0 +1,108 @@ +import { decodeHex } from '../tests/util'; +import type { + AesEncryptDecryptTestVector, + VectorValue, +} from '../tests/webcryptoTests/encrypt_decrypt'; + +const kPlaintext = decodeHex( + '546869732073706563696669636174696f6e206465736372696265' + + '732061204a6176615363726970742041504920666f722070657266' + + '6f726d696e672062617369632063727970746f6772617068696320' + + '6f7065726174696f6e7320696e20776562206170706c6963617469' + + '6f6e732c20737563682061732068617368696e672c207369676e61' + + '747572652067656e65726174696f6e20616e642076657269666963' + + '6174696f6e2c20616e6420656e6372797074696f6e20616e642064' + + '656372797074696f6e2e204164646974696f6e616c6c792c206974' + + '2064657363726962657320616e2041504920666f72206170706c69' + + '636174696f6e7320746f2067656e657261746520616e642f6f7220' + + '6d616e61676520746865206b6579696e67206d6174657269616c20' + + '6e656365737361727920746f20706572666f726d20746865736520' + + '6f7065726174696f6e732e205573657320666f7220746869732041' + + '50492072616e67652066726f6d2075736572206f72207365727669' + + '63652061757468656e7469636174696f6e2c20646f63756d656e74' + + '206f7220636f6465207369676e696e672c20616e64207468652063' + + '6f6e666964656e7469616c69747920616e6420696e746567726974' + + '79206f6620636f6d6d756e69636174696f6e732e', +); + +const kKeyBytes: VectorValue = { + '128': decodeHex('dec0d4fcbf3c4741c892dabd1cd4c04e'), + '256': decodeHex( + '67693823fb1d58073f91ece9cc3af910e5532616a4d27b1' + '3eb7b74d8000bbf30', + ), +}; + +const counter = decodeHex('55aaf89ba89413d54ea727a76c27a284'); + +const kCiphertext: VectorValue = { + '128': decodeHex( + 'e91175fda4f5ea57c52b0d000bbe98af68c0a59058aeed8ab5b7063503a1ce4' + + '70d79dad174f90aaafaa5449d848dc8b2c557d1e7fa4b9a41a2fb1e9fea1414' + + 'b593dab40c04f14b4f81400fe43c93990181b096a15561169aea177f100416e' + + '20b6810b00ee1b04fef67f3bede28baf4d41d397daf1511e9020d7766e9e604' + + '10de38e1432dbffa0f992dc1f0d4756544e8c765af7df706f90e009db9384c3' + + '3e44dea543c2a77bbd52022de41e7d71a498de7feb9760eb47e503366c88dcc' + + '2d1a387788de2d8f78e72c2bdd8815bc8a54e8d0eee275683ca5041290f031a' + + 'd5a4454efa17cc4907718f3ef4b75fedbd13583254f441a15a8a3323b12f40b' + + '8fbebc816cf9b468d8d7a5a0fb548498c39a6ed84615f894929838aef8e3016' + + '60f76b632493f23709fedfd5e107f78267f331b60a38c146f9710484a4acdef' + + 'f110b3b7745ff83aa8cb5de9e15b11e20a785572041f2852a1981156edcf07e' + + '46eb64144449cce74b9cc94163a6fda8ae19219721d60b757b5b5ec718dabd5' + + '0954b6e6a393f656f6346f40229d0c50e01c15701f2a4fe5d25a174edf9b90e' + + 'e0c0ebf9e06b5fe00558638a1ea3781403b0c9206d9e814d6a79fb7a56060e1' + + 'c7176af36c6a1ad635981a9bfd8007d8cf6d9f93f0e8e22b93a9a2ccd7090ab' + + '1df63cea3f040', + ), + '256': decodeHex( + '37529a432f50ba4e53385f8266ec3deccceceade7ae29395e9291076c95bb9a' + + '24f4792fcdd6ea5894b815edb5d5e4022fabe055a06b1a7e01979555b579838' + + '64bf23019cb1b37ffdadb057f728cfb2af0a33d146344cfba0accb4dbf613a7' + + 'bee523ca6d6860e474a9c0f4d068d4c0acd94cc55cbf21e4285ca15116c9702' + + '0f2c33b4585008f8fe97c9e29c0627c5d47c48d94be88b9b16c7f2df740a8d2' + + 'a07556305b82b919f7a87ca2ed19db27262c277c213f2a7eca25e5a6adbea43' + + '0ba2e1061198171054285aff9e0869c638dcd524cbf1f255da675acad6d7867' + + '9a9958b7a8f9bb21dd9c580ad196f9a0e4c6a6500d7bb21df74cd5934ce3c4d' + + '8d1f39d34a2adb58d224c48097887cde9d3be146a3ea3bade4c6864cf9e445b' + + '5c4c2b3ef4e2b8f5eea0ab1c0b9abe7a4fe5b2c0b1d94df6b12953d3273260e' + + '80bd094dec97a3177a9cec0b5042be1804040c9439403b8f72f7426fa756ad6' + + '266cf2c8659e740329dd0d24f9f85497662cad739f71d6174011c77f8f31fb4' + + '4226288dfb86817ef17116321c71bb9ed97db6e990f62058580f006683431f2' + + '29662f1d5e3cdaffe0335467ca72635688c939ec8b32d6465f651a635f73c0a' + + '4e7f0aadb0e81f5bcbfaec2671ac97fdc2fd32f24c941775c37a6810d4b171b' + + 'c8aba90a86603', + ), +}; + +const kKeyLengths = ['128', '256']; + +const passing: AesEncryptDecryptTestVector[] = []; +kKeyLengths.forEach(keyLength => { + passing.push({ + keyBuffer: kKeyBytes[keyLength], + algorithm: { name: 'AES-CTR', counter, length: 64 }, + plaintext: kPlaintext, + result: kCiphertext[keyLength], + keyLength, + }); +}); + +const failing: AesEncryptDecryptTestVector[] = []; +kKeyLengths.forEach(keyLength => { + failing.push({ + keyBuffer: kKeyBytes[keyLength], + algorithm: { name: 'AES-CTR', counter, length: 0 }, + plaintext: kPlaintext, + result: kCiphertext[keyLength], + keyLength, + }); + + failing.push({ + keyBuffer: kKeyBytes[keyLength], + algorithm: { name: 'AES-CTR', counter, length: 129 }, + plaintext: kPlaintext, + result: kCiphertext[keyLength], + keyLength, + }); +}); + +export default { passing, failing, decryptionFailing: [] }; diff --git a/example/src/fixtures/aes_gcm.ts b/example/src/fixtures/aes_gcm.ts new file mode 100644 index 00000000..93c69225 --- /dev/null +++ b/example/src/fixtures/aes_gcm.ts @@ -0,0 +1,150 @@ +import type { TagLength } from '../../../../react-native-quick-crypto/src/keys'; +import { decodeHex } from '../tests/util'; +import type { + AesEncryptDecryptTestVector, + VectorValue, +} from '../tests/webcryptoTests/encrypt_decrypt'; + +const kPlaintext = decodeHex( + '546869732073706563696669636174696f6e206465736372696265' + + '732061204a6176615363726970742041504920666f722070657266' + + '6f726d696e672062617369632063727970746f6772617068696320' + + '6f7065726174696f6e7320696e20776562206170706c6963617469' + + '6f6e732c20737563682061732068617368696e672c207369676e61' + + '747572652067656e65726174696f6e20616e642076657269666963' + + '6174696f6e2c20616e6420656e6372797074696f6e20616e642064' + + '656372797074696f6e2e204164646974696f6e616c6c792c206974' + + '2064657363726962657320616e2041504920666f72206170706c69' + + '636174696f6e7320746f2067656e657261746520616e642f6f7220' + + '6d616e61676520746865206b6579696e67206d6174657269616c20' + + '6e656365737361727920746f20706572666f726d20746865736520' + + '6f7065726174696f6e732e205573657320666f7220746869732041' + + '50492072616e67652066726f6d2075736572206f72207365727669' + + '63652061757468656e7469636174696f6e2c20646f63756d656e74' + + '206f7220636f6465207369676e696e672c20616e64207468652063' + + '6f6e666964656e7469616c69747920616e6420696e746567726974' + + '79206f6620636f6d6d756e69636174696f6e732e', +); + +const kKeyBytes: VectorValue = { + '128': decodeHex('dec0d4fcbf3c4741c892dabd1cd4c04e'), + '256': decodeHex( + '67693823fb1d58073f91ece9cc3af910e5532616a4d27b1' + '3eb7b74d8000bbf30', + ), +}; + +const iv = decodeHex( + '3a92732aa6ea39bf3986e0c73fa920002' + '02175385ef8adeac2c87335eb928dd4', +); + +const additionalData = decodeHex( + '5468657265206172652037206675727468657220656469746f72696' + + '16c206e6f74657320696e2074686520646f63756d656e742e', +); + +const tag: VectorValue = { + '128': decodeHex('c2e2c6fdef1cc5f07bd8b097efc8b8b7'), + '256': decodeHex('bceff1309f15d500f12a554cc21c313c'), +}; + +const tag_with_empty_ad: VectorValue = { + '128': decodeHex('de330b1724defaf81b621e519623dcc6'), + '256': decodeHex('f4ba56cb9a25bff8f6398b82e02fd9ee'), +}; + +// AES-GCM produces ciphertext and a tag. +const kCiphertext: VectorValue = { + '128': decodeHex( + 'b4f128b7693493eee0afafeca8f4f17909cae1ed38d8fdfeba666fcfe4be82b19ff6' + + '0635f971e4fe517efdbf642bfb936b5ba6e7c9f1b4d6702f7ba4ba86364116b5c952' + + 'ec3b348bac2729597b3e66a75296fa5d60a98759f5ffa4c0a99f19108b914c049083' + + '94c5cc2e176ec1e47f78f21836f0b5a262f4f944867a7e97266c7444966d26c2159f' + + '8ccdb7236197ba789116eb16d2dfbb8fa2b75dc468336035eafab84ced9d25cbe257' + + 'de4bf05fdade4051a54bc9d8be0d74d945422fa144f74afd9db5a27935205b7ce669' + + 'e011bb323d4d674f4739a374ea951b69181f9f0380822a5e7dc88efb94c91195e854' + + '321112cbbae2a4e3ca4c4110a3e084341f658148ab9f2ab1fd6256c95f753e0ccd4e' + + '247ec47959b925a142b575ba477c846e781bf6a3120d5ac87f52d1f1aa49f78960f4' + + 'fefb77479c1b6b35212d16009030200b74157df6d9ab9ee08eea8df2a8599a42e3a1' + + 'b66001584e0c07ef1ece1f596f6b2a25f194e80108fb7592b70930275e3b46e61aa5' + + '619c8c8d1f3e0ace3730cf00c5cac56c85af5004109adfff04c4bcb2f01d0d7805e1' + + 'ca0323e19e5c9849cd6b9de0f563c2ab9cf5f7b7a5283ec86e1d97ce64af5824f25a' + + '045249fa8cf5d9099923f2ce4ec579730f508065bff05b97f93e3ef412031187ded2' + + '5d957b', + ), + '256': decodeHex( + '0861eb7146208783d2d17ca0ffb6091d7dc11bf0812e0289a98e3d079136aacf9f6f' + + '275f573fa21b0612dbd774225a3972f4669143063398f7a5f27464dbb148b1116e43' + + '5ddb64d914cf599a2d25695343a28ceb8128b1caae3694379cc1e8f986a3c3337274' + + '4126496360f9e0451177babcb52b4e9c4c8ae23f05f8095e1a0102eb27ae4a2fb716' + + '282f2f0d64770c43b2b838a7ee8f0d2cd0b9976c0611347ab6d2cf2adb254a5e7e24' + + 'f9252004da2cee4538db1f4dad2ebb672470d5fc2857a4f0a39f20817db26c2f1c1f' + + '242a73240e91c39cbf2ea3f9b51f5a491e4839df3f3c4f8c0e751f91de9c79ed2091' + + '8f600cfe2315153ba8ab9ad9003bcaaf67d6c0af1a122b36b0de4b16077afde0913d' + + '2ad049ed548dd1d5e42ef43b0944062358bd0a3e09551c2c521399a0b2f038a0f4c9' + + 'ad4d3d14e31eb4a71069b9c15fcf2917864ec6b65d1859f7e74be9c289f272c2be82' + + '8aee5e89c1c27389becfa9539b0ed2a081c3a1eaddff7243620c5d2941b7f467f765' + + '52f67d577d4e15ba66cd142820c9ae0f34f0d9b4a26c06d3291287e8b812bca99dbe' + + '4ca64bb07f27fb16cb995031f17c89977bcc2b9fbeb1c41275a92e98fb2d19a41b91' + + 'd6e4370f0283d850ffccaf643b910f6728212dffc8feac8a143a57b6c094db2958e6' + + 'e546f9', + ), +}; + +const kKeyLengths = ['128', '256']; +const kTagLengths: TagLength[] = [32, 64, 96, 104, 112, 120, 128]; + +const passing: AesEncryptDecryptTestVector[] = []; +kKeyLengths.forEach(keyLength => { + const cipherText = kCiphertext[keyLength] as Uint8Array; + kTagLengths.forEach(tagLength => { + const byteCount = tagLength / 8; + const result = new Uint8Array(cipherText.byteLength + byteCount); + result.set(cipherText, 0); + result.set( + (tag[keyLength] as Uint8Array).slice(0, byteCount), + cipherText.byteLength, + ); + passing.push({ + keyBuffer: kKeyBytes[keyLength], + algorithm: { name: 'AES-GCM', iv, additionalData, tagLength }, + plaintext: kPlaintext, + result, + keyLength, + }); + + const noadresult = new Uint8Array(cipherText.byteLength + byteCount); + noadresult.set(cipherText, 0); + noadresult.set( + (tag_with_empty_ad[keyLength] as Uint8Array).slice(0, byteCount), + cipherText.byteLength, + ); + passing.push({ + keyBuffer: kKeyBytes[keyLength], + algorithm: { name: 'AES-GCM', iv, tagLength }, + plaintext: kPlaintext, + result: noadresult, + keyLength, + }); + }); +}); + +const failing: AesEncryptDecryptTestVector[] = []; +kKeyLengths.forEach(keyLength => { + [24, 48, 72, 95, 129].forEach(badTagLength => { + failing.push({ + keyBuffer: kKeyBytes[keyLength], + algorithm: { + name: 'AES-GCM', + iv, + additionalData, + // @ts-expect-error bad tag length + tagLength: badTagLength, + }, + plaintext: kPlaintext, + result: kCiphertext[keyLength], + keyLength, + }); + }); +}); + +export default { passing, failing, decryptionFailing: [] }; diff --git a/example/src/fixtures/keys/ec_p256_private.ts b/example/src/fixtures/keys/ec_p256_private.ts new file mode 100644 index 00000000..c8cc743a --- /dev/null +++ b/example/src/fixtures/keys/ec_p256_private.ts @@ -0,0 +1,9 @@ +const key: string = ` +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDxBsPQPIgMuMyQbx +zbb9toew6Ev6e9O6ZhpxLNgmAEqhRANCAARfSYxhH+6V5lIg+M3O0iQBLf+53kuE +2luIgWnp81/Ya1Gybj8tl4tJVu1GEwcTyt8hoA7vRACmCHnI5B1+bNpS +-----END PRIVATE KEY----- +`; + +export default key; diff --git a/example/src/fixtures/keys/ec_p256_public.ts b/example/src/fixtures/keys/ec_p256_public.ts new file mode 100644 index 00000000..8cb8f5c9 --- /dev/null +++ b/example/src/fixtures/keys/ec_p256_public.ts @@ -0,0 +1,8 @@ +const key: string = ` +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX0mMYR/uleZSIPjNztIkAS3/ud5L +hNpbiIFp6fNf2GtRsm4/LZeLSVbtRhMHE8rfIaAO70QApgh5yOQdfmzaUg== +-----END PUBLIC KEY----- +`; + +export default key; diff --git a/example/src/fixtures/rsa.ts b/example/src/fixtures/rsa.ts new file mode 100644 index 00000000..ce5ae144 --- /dev/null +++ b/example/src/fixtures/rsa.ts @@ -0,0 +1,341 @@ +import { decodeHex } from '../tests/util'; +import type { RsaEncryptDecryptTestVector } from '../tests/webcryptoTests/encrypt_decrypt'; + +const pkcs8 = decodeHex( + '308204bf020100300d06092a864886f70d0101010500048204a930820' + + '4a50201000282010100d3576092e62957364544e7e4233b7bdb293db2' + + '085122c479328546f9f0f712f657c4b17868c930908cc594f7ed00c01' + + '442c1af04c2f678a48ba2c80fd1713e30b5ac50787ac3516589f17196' + + '7f6386ada34900a6bb04eecea42bf043ced9a0f94d0cc09e919b9d716' + + '6c08ab6ce204640aea4c4920db6d86eb916d0dcc0f4341a10380429e7' + + 'e1032144ea949de8f6c0ccbf95fa8e928d70d8a38ce168db45f6f1346' + + '63d6f656f5ceabc725da8c02aabeaaa13ac36a75cc0bae135df3114b6' + + '6589c7ed3cb61559ae5a384f162bfa80dbe4617f86c3f1d010c94fe2c' + + '9bf019a6e63b3efc028d43cee611c85ec263c906c463772c6911b19ee' + + 'c096ca76ec5e31e1e3020301000102820101008b375ccb87c825c5ff3' + + 'd53d009916e9641057e18527227a07ab226be1088813a3b38bb7b48f3' + + '77055165fa2a9339d24dc667d5c5ba3427e6a481176eac15ffd490683' + + '11e1c283b9f3a8e0cb809b4630c50aa8f3e45a60b359e19bf8cbb5eca' + + 'd64e761f1095743ff36aaf5cf0ecb97fedaddda60b5bf35d811a75b82' + + '2230cfaa0192fad40547e275448aa3316bf8e2b4ce0854fc7708b537b' + + 'a22d13210b09aec37a2759efc082a1531b23a91730037dde4ef26b5f9' + + '6efdcc39fd34c345ad51cbbe44fe58b8a3b4ec997866c086dff1b8831' + + 'ef0a1fea263cf7dacd03c04cbcc2b279e57fa5b953996bfb1dd68817a' + + 'f7fb42cdef7a5294a57fac2b8ad739f1b029902818100fbf833c2c631' + + 'c970240c8e7485f06a3ea2a84822511a8627dd464ef8afaf7148d1a42' + + '5b6b8657ddd5246832b8e533020c5bbb568855a6aec3e4221d793f1dc' + + '5b2f2584e2415e48e9a2bd292b134031f99c8eb42fc0bcd0449bf22ce' + + '6dec97014efe5ac93ebe835877656252cbbb16c415b67b184d2284568' + + 'a277d59335585cfd02818100d6b8ce27c7295d5d16fc3570ed64c8da9' + + '303fad29488c1a65e9ad711f90370187dbbfd81316d69648bc88cc5c8' + + '3551afff45debacfb61105f709e4c30809b90031ebd686244496c6f69' + + 'e692ebdc814f64239f4ad15756ecb78c5a5b09931db183077c546a38c' + + '4c743889ad3d3ed079b5622ed0120fa0e1f93b593db7d852e05f02818' + + '038874b9d83f78178ce2d9efc175c83897fd67f306bbfa69f64ee3423' + + '68ced47c80c3f1ce177a758d64bafb0c9786a44285fa01cdec3507cde' + + 'e7dc9b7e2b21d3cbbcc100eee9967843b057329fdcca62998ed0f11b3' + + '8ce8b0abc7de39017c71cfd0ae57546c559144cdd0afd0645f7ea8ff0' + + '7b974d1ed44fd1f8e00f560bf6d45028181008529ef9073cf8f7b5ff9' + + 'e21abadf3a4173d3900670dfaf59426abcdf0493c13d2f1d1b46b824a' + + '6ac1894b3d925250c181e3472c16078056eb19a8d28f71f3080927534' + + '81d49444fdf78c9ea6c24407dc018e77d3afef385b2ff7439e9623794' + + '1332dd446cebeffdb4404fe4f71595161d016402c334d0f57c61abe4f' + + 'f9f4cbf90281810087d87708d46763e4ccbeb2d1e9712e5bf0216d70d' + + 'e9420a5b2069b7459b99f5d9f7f2fad7cd79aaee67a7f9a34437e3c79' + + 'a84af0cd8de9dff268eb0c4793f501f988d540f6d3475c2079b8227a2' + + '3d968dec4e3c66503187193459630472bfdb6ba1de786c797fa6f4ea6' + + '5a2a8419262f29678856cb73c9bd4bc89b5e041b2277', +); + +const spki = decodeHex( + '30820122300d06092a864886f70d01010105000382010f003082010a0' + + '282010100d3576092e62957364544e7e4233b7bdb293db2085122c479' + + '328546f9f0f712f657c4b17868c930908cc594f7ed00c01442c1af04c' + + '2f678a48ba2c80fd1713e30b5ac50787ac3516589f171967f6386ada3' + + '4900a6bb04eecea42bf043ced9a0f94d0cc09e919b9d7166c08ab6ce2' + + '04640aea4c4920db6d86eb916d0dcc0f4341a10380429e7e1032144ea' + + '949de8f6c0ccbf95fa8e928d70d8a38ce168db45f6f134663d6f656f5' + + 'ceabc725da8c02aabeaaa13ac36a75cc0bae135df3114b66589c7ed3c' + + 'b61559ae5a384f162bfa80dbe4617f86c3f1d010c94fe2c9bf019a6e6' + + '3b3efc028d43cee611c85ec263c906c463772c6911b19eec096ca76ec' + + '5e31e1e30203010001', +); + +const label = decodeHex( + '5468657265206172652037206675727468657220656469746f7269616' + + 'c206e6f74657320696e2074686520646f63756d656e742e', +); + +// overlong plaintext for RSA-OAEP +const plaintext = decodeHex( + '5f4dba4f320c0ce876725afce5fbd25bf83e5a7125a08cafe73c3ebac4' + + '21779df9d55d180c3ae9942645e1d82fee8c9d294b3cb1a08a9931201b' + + '3c0e81fc47cacf8315a2af66324113c3b66230c34608c4f4593634ce02' + + 'b267362277f0a840ca74bc3d1a6236952c5ed7aaf8a8fecbddfa7584e6' + + '978cea5d2a5b9fb7f1b48c8b0be58a305202754d8376107374793cf026' + + 'aaee5300727d836cd71e71b345ddb2e44446ffc5b901635413890d910e' + + 'a380984a90191031323f16dbcc9d6be168b84885384ca03e12600ac1c2' + + '48028af3726cc93463882ea8c02aab', +); + +const ciphertext = { + 'sha-1, no label': decodeHex( + '901ef09cbbfe9a4939b9a9c43ccf22339e1575f7c74324494075c192' + + 'c40b68996eb0e04d7b151774fff7c00b66174a249fc3d1a6123c70a6' + + '6fd61b67cb54f683fe01049e4a44a5937e35cae1b73dce12aea09cd0' + + '1c4c48900fafdd753ac401566076b9b863807cf141a63044865e0cbc' + + '42b995fb639fe9994e94c0e381404ae1b554cdb27515f09798dc5a07' + + 'a60b162bf12f8cef7ce166314d942d80fe43d0df1fb0d7e9514ef729' + + 'dc6c845583f74ab2c36d9684d43b71962a18ff0e2b13ce74f537fb3a' + + '0b00ede329e77c11900a070e20f86dc07cacb56f7821d0249234106c' + + '6e0b4dda82e0febdb202ef0c7b10d560f0bafdc78f0624185783b522' + + '83ca14a1', + ), + 'sha-256, no label': decodeHex( + '0531eaeb4b8cef8eb77dd736d096b6dce840d164e9751f86a8d5ced0' + + '9999506ffaf000eb6153f7456f311ae99e47f1207150eb97f2982db7' + + '73be9e990e8a12985a5f115af906ef0c870eef3c9fca1c5b4d5e9904' + + '99b67d0094adaf8debdf9a5d72335b54b3d1ca26ea0957d63e064ce9' + + '69d52e53bd605c8fa9320d9eabe239eef47099555c194d1bb8dfeffe' + + 'ad6b4fd7f8f28990355cc8ee22a36c4867f0acead7f4a5025f1517f7' + + '52a7e8c093533d0cd659ad60a7dc05044220019887016437dcc94c6f' + + '9e8202b03bc955eb2c790d3fb7c7e77e2612ffa521daf467f640a749' + + 'e9e1191574be76e2d55c3cfe7a93551a7c28ddb2ba6b26c33a30c237' + + '1cd8974d', + ), + 'sha-384, no label': decodeHex( + '0c2392e30f92f1f4e4acd1b4a6fd99f983c61dcaf39bddde47b29ead' + + '3add104a7a86df1f7099f3683c65affe329d2bcab95035ec96c13dab' + + '9a0cb4b95960fee08aa5835566a1b57ddf545ac950509134ced00273' + + '9ea6ff3e3de4841f9fa9061660ecf10533e9f50ca5164f324944cc7b' + + '8e3aec6998a366f90cfaee7977651a0d1e8d194bcd1c2a008729aa01' + + '1a9d0d8ca271f98e015bf466bcd99cd97686b592f66fb1369f54a358' + + '937abcf917df6f39badc6f5ff630c7ac73b92fadbadd0c29fce04ca7' + + 'd62aab52b264e5a282bcbf02721c119e28e9426cd996b3791973d8a2' + + 'acf43a2c2f67ff884c1a77b83fc3268c640cab414336c31f7a6977e4' + + '951031d4', + ), + 'sha-512, no label': decodeHex( + '0626d346d5253113ddc0f8ced1918d88ebf00869f880af89c5e67bb3' + + '794bb58a9bf619e5a55909418f6c7e14a858a0c530427502db7afe60' + + '2493aa6f7ba83997198b0ae97ddb8d3a7dae5926dc190f0587452105' + + '0a311cfd94fbd535df08b840b95ef9d3403583882002a33d4c09a2bd' + + '50476ded93090b933dfe01b9d0e42cdb11a3efb8d4c5e5d223ec0475' + + '25bba91af85fa0a5fc1566fd4973917c588f7980ec469065b536b340' + + '39e899489e60f14fff5a43106e2bea9914394b5317b8d819d73409f1' + + '7215dfb8b1f742620d0fafcc5251cd160f5c1c63baeaf12125d20f08' + + 'c51ed061072a33add5ab3b9a47354e58f4329d216f8fb93d5b76edf5' + + '5d5b9c24', + ), + 'sha-1, with label': decodeHex( + '450c932bdb5f223d1d40dabe85457d230849499a57c25bc9826ff356' + + '5a7cfe82bb859e209fea96625bf678a639e96207a03a7d71354f02ca' + + 'd0687bc31b54fa6208a953cf5e1f611f5100f799d06340b9f4d5c23e' + + 'c8ab4ef50a3e90b0ce5e68ac2d3972c4f3a6224389294d0620e109ea' + + 'b72026281a5de6bf1bc0b743e09c40bd241bd8393aa430a44adaa7c4' + + 'd0dd4f6676177b1ae335b9c40ee99a068ce9cc996da3a4e2aacf4f7b' + + '1abc817c6252ff5f8e471a05d7c681b36e82fbde8cd2e225c87564ac' + + '1a8a610b57a168d244752e574afa9856c22a757afeebca96f93f6e6d' + + '17c520515927d99ca34eedfd19bce31f23aebe159da0253c27c10b5c' + + '0ffb7d97', + ), + 'sha-256, with label': decodeHex( + 'b4d46d087682180560797166729d951fea055f8ab80a10f9314e55de' + + 'e12fac6c2124ce2fd39fcaf134acdd1c6301b0335292be89a3cad44a' + + '96cc3a1a2b36875bf6c93b6ab5090a76bf6a7a66af7202b692a9377c' + + '54bbfeede1fc20c54f61dbfa3651347992c20dc458d40792ede81f71' + + 'd7c82a9eefb43398d6916a0b73a01547abbe2d19e51382d2343a0e37' + + '52fad7c16eb28f65a33f95d8b0a39142ef3eccc3660e9d029b72e6e1' + + '3779a7acb6bb4b9531d56890cae66f7d77ecd59b837a2b37f5b73c8c' + + '67582d549df743f3a966b0e1c0b59afc5a5f11a17060c4696837065c' + + 'd4127f55be0c697bdb6e826fb320b751f6f0873b05d2ad0f66d7575f' + + '8888ee0c', + ), + 'sha-384, with label': decodeHex( + 'cc3cbc830f7256f6be9bce3e44f9623fb29001f42af82f09fd088bba' + + 'd7b4bf5cf71392f241c369d38854e1ec4bcaf394c54473338741b43e' + + '7b5b1b43850eb7413101065e72f94224fabd49da9bd5ccf067b9cebd' + + '503cf9017fbe1cc4a7437bdecb7ccd99fbf24d97d5d7a2bd1f1cf401' + + 'a73a03a95d8f1b28a756e1553b5db052d1e0901523fcb66173c84675' + + '6df0af66d0647ce6b4e89f4db04b8b3a39fe0db7191bf6b633abc5e2' + + '1a0769e1ee933d446661f79527084446d7dccd4ac3b770985b467aa3' + + '1e423334beccd1df6f432c120e6c9c3ea5dd06f694e99432d9f82c63' + + 'e976ebf84e2dca3dd3dcc14a06e5cbd47274f2d655a5c7727d350557' + + 'eed091b8', + ), + 'sha-512, with label': decodeHex( + '8697b55eef5b0d5311398a15f2ce1856b8efee39e774718b0c806864' + + '9339e4b7a7e129b4f7858d0079c1eba8b8f86b2222e961d7f7f014f5' + + '0e00a711ebcc5161345109885b3b7ac8f94a3f440a12a2f30abe763c' + + '184ae75c62b3dd9605424e5dc8d41d4c32f6be5407f5b09461051016' + + 'deada5c8a95d3a087be57cdc427b22453121196b20fa623d29de6c70' + + '252ab2a3519d1ca00379580af9191916420024bbb0c79a7a8a2b48d9' + + '5a2b7732d2a6ca0279ac18ac334aa12d6b96bb590959b7e9a9954e91' + + 'e49c8ad7e8db2121e0b2ae100648b9d622cc9fa19a0b978e27f44a4b' + + 'f3bf7be7203676eb0c13c8a5fca1572e6333f892b47a2cd267eda932' + + '1cd27988', + ), +}; + +const passing: RsaEncryptDecryptTestVector[] = [ + { + name: 'RSA-OAEP with SHA-1 and no label', + publicKeyBuffer: spki, + publicKeyFormat: 'spki', + privateKey: null, + privateKeyBuffer: pkcs8, + privateKeyFormat: 'pkcs8', + publicKey: null, + algorithm: { name: 'RSA-OAEP' }, + hash: 'SHA-1', + plaintext: plaintext.slice(0, 214), + ciphertext: ciphertext['sha-1, no label'], + }, + { + name: 'RSA-OAEP with SHA-256 and no label', + publicKeyBuffer: spki, + publicKeyFormat: 'spki', + privateKey: null, + privateKeyBuffer: pkcs8, + privateKeyFormat: 'pkcs8', + publicKey: null, + algorithm: { name: 'RSA-OAEP' }, + hash: 'SHA-256', + plaintext: plaintext.slice(0, 190), + ciphertext: ciphertext['sha-256, no label'], + }, + { + name: 'RSA-OAEP with SHA-384 and no label', + publicKeyBuffer: spki, + publicKeyFormat: 'spki', + privateKey: null, + privateKeyBuffer: pkcs8, + privateKeyFormat: 'pkcs8', + publicKey: null, + algorithm: { name: 'RSA-OAEP' }, + hash: 'SHA-384', + plaintext: plaintext.slice(0, 158), + ciphertext: ciphertext['sha-384, no label'], + }, + { + name: 'RSA-OAEP with SHA-512 and no label', + publicKeyBuffer: spki, + publicKeyFormat: 'spki', + privateKey: null, + privateKeyBuffer: pkcs8, + privateKeyFormat: 'pkcs8', + publicKey: null, + algorithm: { name: 'RSA-OAEP' }, + hash: 'SHA-512', + plaintext: plaintext.slice(0, 126), + ciphertext: ciphertext['sha-512, no label'], + }, + { + name: 'RSA-OAEP with SHA-1 and empty label', + publicKeyBuffer: spki, + publicKeyFormat: 'spki', + privateKey: null, + privateKeyBuffer: pkcs8, + privateKeyFormat: 'pkcs8', + publicKey: null, + algorithm: { name: 'RSA-OAEP', label: new Uint8Array([]) }, + hash: 'SHA-1', + plaintext: plaintext.slice(0, 214), + ciphertext: ciphertext['sha-1, no label'], + }, + { + name: 'RSA-OAEP with SHA-256 and empty label', + publicKeyBuffer: spki, + publicKeyFormat: 'spki', + privateKey: null, + privateKeyBuffer: pkcs8, + privateKeyFormat: 'pkcs8', + publicKey: null, + algorithm: { name: 'RSA-OAEP', label: new Uint8Array([]) }, + hash: 'SHA-256', + plaintext: plaintext.slice(0, 190), + ciphertext: ciphertext['sha-256, no label'], + }, + { + name: 'RSA-OAEP with SHA-384 and empty label', + publicKeyBuffer: spki, + publicKeyFormat: 'spki', + privateKey: null, + privateKeyBuffer: pkcs8, + privateKeyFormat: 'pkcs8', + publicKey: null, + algorithm: { name: 'RSA-OAEP', label: new Uint8Array([]) }, + hash: 'SHA-384', + plaintext: plaintext.slice(0, 158), + ciphertext: ciphertext['sha-384, no label'], + }, + { + name: 'RSA-OAEP with SHA-512 and empty label', + publicKeyBuffer: spki, + publicKeyFormat: 'spki', + privateKey: null, + privateKeyBuffer: pkcs8, + privateKeyFormat: 'pkcs8', + publicKey: null, + algorithm: { name: 'RSA-OAEP', label: new Uint8Array([]) }, + hash: 'SHA-512', + plaintext: plaintext.slice(0, 126), + ciphertext: ciphertext['sha-512, no label'], + }, + { + name: 'RSA-OAEP with SHA-1 and a label', + publicKeyBuffer: spki, + publicKeyFormat: 'spki', + privateKey: null, + privateKeyBuffer: pkcs8, + privateKeyFormat: 'pkcs8', + publicKey: null, + algorithm: { name: 'RSA-OAEP', label }, + hash: 'SHA-1', + plaintext: plaintext.slice(0, 214), + ciphertext: ciphertext['sha-1, with label'], + }, + { + name: 'RSA-OAEP with SHA-256 and a label', + publicKeyBuffer: spki, + publicKeyFormat: 'spki', + privateKey: null, + privateKeyBuffer: pkcs8, + privateKeyFormat: 'pkcs8', + publicKey: null, + algorithm: { name: 'RSA-OAEP', label }, + hash: 'SHA-256', + plaintext: plaintext.slice(0, 190), + ciphertext: ciphertext['sha-256, with label'], + }, + { + name: 'RSA-OAEP with SHA-384 and a label', + publicKeyBuffer: spki, + publicKeyFormat: 'spki', + privateKey: null, + privateKeyBuffer: pkcs8, + privateKeyFormat: 'pkcs8', + publicKey: null, + algorithm: { name: 'RSA-OAEP', label }, + hash: 'SHA-384', + plaintext: plaintext.slice(0, 158), + ciphertext: ciphertext['sha-384, with label'], + }, + { + name: 'RSA-OAEP with SHA-512 and a label', + publicKeyBuffer: spki, + publicKeyFormat: 'spki', + privateKey: null, + privateKeyBuffer: pkcs8, + privateKeyFormat: 'pkcs8', + publicKey: null, + algorithm: { name: 'RSA-OAEP', label }, + hash: 'SHA-512', + plaintext: plaintext.slice(0, 126), + ciphertext: ciphertext['sha-512, with label'], + }, +]; + +const failing: RsaEncryptDecryptTestVector[] = []; + +export default { passing, failing }; diff --git a/example/src/hooks/useTestsList.ts b/example/src/hooks/useTestsList.ts index 8d8e92fc..2914c319 100644 --- a/example/src/hooks/useTestsList.ts +++ b/example/src/hooks/useTestsList.ts @@ -11,6 +11,12 @@ import '../tests/hash/hash_tests'; import '../tests/hmac/hmac_tests'; import '../tests/pbkdf2/pbkdf2_tests'; import '../tests/random/random_tests'; +import '../tests/subtle/deriveBits'; +import '../tests/subtle/digest'; +import '../tests/subtle/encrypt_decrypt'; +import '../tests/subtle/generateKey'; +import '../tests/subtle/import_export'; +import '../tests/subtle/sign_verify'; export const useTestsList = (): [ TestSuites, diff --git a/example/src/tests/subtle/deriveBits.ts b/example/src/tests/subtle/deriveBits.ts new file mode 100644 index 00000000..4866c41d --- /dev/null +++ b/example/src/tests/subtle/deriveBits.ts @@ -0,0 +1,76 @@ +import { expect } from 'chai'; +import { + subtle, + ab2str, + type HashAlgorithm, + normalizeHashName, +} from 'react-native-quick-crypto'; +import { test } from '../util'; + +type TestFixture = [ + string, + string, + number, + HashAlgorithm | string, + number, + string, +]; + +const SUITE = 'subtle.deriveBits'; + +// pbkdf2 deriveBits() +// { +const test_fn = async ( + pass: string, + salt: string, + iterations: number, + hash: HashAlgorithm | string, + length: number, + expected: string, +) => { + const key = await subtle.importKey( + 'raw', + pass, + { name: 'PBKDF2', hash: normalizeHashName(hash) }, + false, + ['deriveBits'], + ); + + const bits = await subtle.deriveBits( + { + name: 'PBKDF2', + salt, + iterations, + hash: normalizeHashName(hash), + }, + key, + length, + ); + expect(ab2str(bits)).to.equal(expected); +}; + +const kTests: TestFixture[] = [ + [ + 'hello', + 'there', + 10, + 'SHA-256', + 512, + 'f72d1cf4853fffbd16a42751765d11f8dc7939498ee7b7' + + 'ce7678b4cb16fad88098110a83e71f4483ce73203f7a64' + + '719d293280f780f9fafdcf46925c5c0588b3', + ], + ['hello', 'there', 5, 'SHA-384', 128, '201509b012c9cd2fbe7ea938f0c509b3'], +]; + +kTests.forEach(async ([pass, salt, iterations, hash, length, expected]) => { + test( + SUITE, + `PBKDF2 importKey raw/deriveBits - ${pass} ${salt} ${iterations} ${hash} ${length}`, + async () => { + await test_fn(pass, salt, iterations, hash, length, expected); + }, + ); +}); + +// ecdh deriveBits diff --git a/example/src/tests/subtle/digest.ts b/example/src/tests/subtle/digest.ts new file mode 100644 index 00000000..89a5bfbb --- /dev/null +++ b/example/src/tests/subtle/digest.ts @@ -0,0 +1,48 @@ +import { expect } from 'chai'; +import { Buffer } from '@craftzdog/react-native-buffer'; +import { + subtle, + ab2str, + toArrayBuffer, + type HashAlgorithm, + createHash, +} from 'react-native-quick-crypto'; +import { test } from '../util'; + +type Test = [HashAlgorithm, string, number]; + +const SUITE = 'subtle.digest'; +test(SUITE, 'empty hash just works', async () => { + await subtle.digest('SHA-512', Buffer.alloc(0)); +}); + +const kTests: Test[] = [ + ['SHA-1', 'sha1', 160], + ['SHA-256', 'sha256', 256], + ['SHA-384', 'sha384', 384], + ['SHA-512', 'sha512', 512], +]; + +const kData = toArrayBuffer(Buffer.from('hello')); + +kTests.forEach(([algorithm, legacyName, bitLength]) => { + test(SUITE, `hash: ${algorithm}`, async () => { + const checkValue = createHash(legacyName) + .update(kData) + .digest() + .toString('hex'); + + const values = await Promise.all([ + subtle.digest({ name: algorithm }, kData), + subtle.digest({ name: algorithm, length: bitLength }, kData), + subtle.digest(algorithm, kData), + subtle.digest(algorithm, Buffer.from(kData)), + ]); + + // Compare that the legacy crypto API and SubtleCrypto API + // produce the same results + values.forEach(v => { + expect(ab2str(v)).to.equal(checkValue); + }); + }); +}); diff --git a/example/src/tests/subtle/encrypt_decrypt.ts b/example/src/tests/subtle/encrypt_decrypt.ts new file mode 100644 index 00000000..c649d720 --- /dev/null +++ b/example/src/tests/subtle/encrypt_decrypt.ts @@ -0,0 +1,873 @@ +import { + bufferLikeToArrayBuffer, + getRandomValues, + subtle, +} from 'react-native-quick-crypto'; +import { Buffer } from '@craftzdog/react-native-buffer'; +import { test } from '../util'; +import { expect } from 'chai'; +import type { + AesCbcParams, + AesCtrParams, + AesGcmParams, + AnyAlgorithm, + CryptoKey, + DigestAlgorithm, + EncryptDecryptParams, + KeyUsage, + RsaOaepParams, +} from 'react-native-quick-crypto'; + +// Local interface to match what subtle.generateKey actually returns +interface TestCryptoKeyPair { + publicKey: CryptoKey; + privateKey: CryptoKey; +} +import rsa_oaep_fixtures from '../../fixtures/rsa'; +import aes_cbc_fixtures from '../../fixtures/aes_cbc'; +import aes_ctr_fixtures from '../../fixtures/aes_ctr'; +import aes_gcm_fixtures from '../../fixtures/aes_gcm'; +import { assertThrowsAsync } from '../util'; +import { ab2str } from 'react-native-quick-crypto'; + +import RNFE from 'react-native-fast-encoder'; +// @ts-expect-error polyfill types are wonky +globalThis.TextEncoder = () => RNFE; + +export type RsaEncryptDecryptTestVector = { + name: string; + publicKey: Buffer | null; + publicKeyBuffer: ArrayBuffer; + publicKeyFormat: string; + privateKey: Buffer | null; + privateKeyBuffer: ArrayBuffer | null; + privateKeyFormat: string | null; + algorithm: RsaOaepParams; + hash: DigestAlgorithm; + plaintext: ArrayBuffer; + ciphertext: ArrayBuffer; +}; + +export type AesEncryptDecryptTestVector = { + keyBuffer?: ArrayBuffer; + algorithm?: EncryptDecryptParams; + plaintext?: ArrayBuffer; + result?: ArrayBuffer; + keyLength?: string; +}; + +export type VectorValue = Record; +export type BadPadding = { + zeroPadChar: ArrayBuffer; + bigPadChar: ArrayBuffer; + inconsistentPadChars: ArrayBuffer; +}; +export type BadPaddingVectorValue = Record; + +// This is only a partial test. The WebCrypto Web Platform Tests +// will provide much greater coverage. + +const SUITE = 'subtle.encrypt/decrypt'; + +// from https://github.com/nodejs/node/blob/main/test/parallel/test-webcrypto-encrypt-decrypt.js + +// Test Encrypt/Decrypt RSA-OAEP +test(SUITE, 'RSA-OAEP', async () => { + const buf = getRandomValues(new Uint8Array(50)); + const ec = new TextEncoder(); + const keyPair = (await subtle.generateKey( + { + name: 'RSA-OAEP', + modulusLength: 4096, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256', + }, + false, + ['encrypt', 'decrypt'], + )) as TestCryptoKeyPair; + + const ciphertext = await subtle.encrypt( + { + name: 'RSA-OAEP', + label: ec.encode('a label'), + } as RsaOaepParams, + keyPair.publicKey, + buf, + ); + + const plaintext = await subtle.decrypt( + { + name: 'RSA-OAEP', + label: ec.encode('a label'), + } as RsaOaepParams, + keyPair.privateKey, + ciphertext, + ); + + expect(Buffer.from(plaintext).toString('hex')).to.equal( + Buffer.from(buf as Uint8Array).toString('hex'), + ); +}); + +// from https://github.com/nodejs/node/blob/main/test/parallel/test-webcrypto-encrypt-decrypt-rsa.js +async function importRSAVectorKey( + publicKeyBuffer: ArrayBuffer, + _privateKeyBuffer: ArrayBuffer | null, + name: AnyAlgorithm, + hash: DigestAlgorithm, + publicUsages: KeyUsage[], + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _privateUsages: KeyUsage[], +): Promise { + const publicKey = await subtle.importKey( + 'spki', + publicKeyBuffer, + { name, hash }, + false, + publicUsages, + ); + // const privateKey = await subtle.importKey( + // 'pkcs8', + // privateKeyBuffer, + // { name, hash }, + // false, + // privateUsages + // ), + + return { publicKey, privateKey: publicKey }; // Using publicKey as placeholder since privateKey import is commented out +} + +async function testRSADecryption({ + ciphertext, + algorithm, + plaintext, + hash, + publicKeyBuffer, + privateKeyBuffer, +}: RsaEncryptDecryptTestVector) { + if (ciphertext === undefined) { + return; + } + + const { privateKey } = await importRSAVectorKey( + publicKeyBuffer, + privateKeyBuffer, + algorithm.name, + hash, + ['encrypt'], + ['decrypt'], + ); + + // TODO: remove condition when importKey() rsa pkcs8 is implemented + if (privateKey !== undefined) { + const encodedPlaintext = Buffer.from(plaintext).toString('hex'); + const result = await subtle.decrypt( + algorithm, + privateKey as CryptoKey, + ciphertext, + ); + + expect(Buffer.from(result).toString('hex')).to.equal(encodedPlaintext); + + const ciphercopy = Buffer.from(ciphertext); + + // Modifying the ciphercopy after calling decrypt should just work + const result2 = await subtle.decrypt( + algorithm, + privateKey as CryptoKey, + ciphercopy, + ); + ciphercopy[0] = 255 - ciphercopy[0]!; + + expect(Buffer.from(result2).toString('hex')).to.equal(encodedPlaintext); + } +} + +async function testRSAEncryption( + { + algorithm, + plaintext, + hash, + publicKeyBuffer, + privateKeyBuffer, + }: RsaEncryptDecryptTestVector, + modify = false, +) { + const { publicKey, privateKey } = await importRSAVectorKey( + publicKeyBuffer, + privateKeyBuffer, + algorithm.name, + hash, + ['encrypt'], + ['decrypt'], + ); + + const plaintextCopy = Buffer.from(plaintext); // make a copy + if (modify) { + plaintext = bufferLikeToArrayBuffer(plaintextCopy); + } + + const result = await subtle.encrypt( + algorithm, + publicKey as CryptoKey, + plaintext, + ); + if (modify) { + const plaintextView = new Uint8Array(plaintext); + plaintextView[0] = 255 - plaintextView[0]!; + } + expect(result.byteLength).to.be.greaterThan(0); + + // TODO: remove condition when importKey() rsa pkcs8 is implemented + if (privateKey !== undefined) { + const encodedPlaintext = Buffer.from(plaintext).toString('hex'); + + expect(result.byteLength * 8).to.equal( + (privateKey as CryptoKey).algorithm.modulusLength, + ); + + const out = await subtle.decrypt( + algorithm, + privateKey as CryptoKey, + result, + ); + expect(Buffer.from(out).toString('hex')).to.equal(encodedPlaintext); + } +} + +async function testRSAEncryptionLongPlaintext({ + algorithm, + plaintext, + hash, + publicKeyBuffer, + privateKeyBuffer, +}: RsaEncryptDecryptTestVector) { + const { publicKey } = await importRSAVectorKey( + publicKeyBuffer, + privateKeyBuffer, + algorithm.name, + hash, + ['encrypt'], + ['decrypt'], + ); + const newplaintext = new Uint8Array(plaintext.byteLength + 1); + newplaintext.set(new Uint8Array(plaintext), 0); + newplaintext[plaintext.byteLength] = 32; + + return assertThrowsAsync( + async () => + await subtle.encrypt(algorithm, publicKey as CryptoKey, newplaintext), + 'error in DoCipher, status: 2', + ); +} + +async function testRSAEncryptionWrongKey({ + algorithm, + plaintext, + hash, + publicKeyBuffer, + privateKeyBuffer, +}: RsaEncryptDecryptTestVector) { + const { privateKey } = await importRSAVectorKey( + publicKeyBuffer, + privateKeyBuffer, + algorithm.name, + hash, + ['encrypt'], + ['decrypt'], + ); + return assertThrowsAsync( + async () => + await subtle.encrypt(algorithm, privateKey as CryptoKey, plaintext), + "Cannot read property 'algorithm' of undefined", + ); +} + +async function testRSAEncryptionBadUsage({ + algorithm, + plaintext, + hash, + publicKeyBuffer, + privateKeyBuffer, +}: RsaEncryptDecryptTestVector) { + const { publicKey } = await importRSAVectorKey( + publicKeyBuffer, + privateKeyBuffer, + algorithm.name, + hash, + ['wrapKey'], + ['decrypt'], + ); + return assertThrowsAsync( + async () => + await subtle.encrypt(algorithm, publicKey as CryptoKey, plaintext), + 'The requested operation is not valid', + ); +} + +async function testRSADecryptionWrongKey({ + ciphertext, + algorithm, + hash, + publicKeyBuffer, + privateKeyBuffer, +}: RsaEncryptDecryptTestVector) { + if (ciphertext === undefined) { + return; + } + + const { publicKey } = await importRSAVectorKey( + publicKeyBuffer, + privateKeyBuffer, + algorithm.name, + hash, + ['encrypt'], + ['decrypt'], + ); + + return assertThrowsAsync( + async () => + await subtle.decrypt(algorithm, publicKey as CryptoKey, ciphertext), + 'The requested operation is not valid', + ); +} + +async function testRSADecryptionBadUsage({ + ciphertext, + algorithm, + hash, + publicKeyBuffer, + privateKeyBuffer, +}: RsaEncryptDecryptTestVector) { + if (ciphertext === undefined) { + return; + } + + const { publicKey } = await importRSAVectorKey( + publicKeyBuffer, + privateKeyBuffer, + algorithm.name, + hash, + ['encrypt'], + ['unwrapKey'], + ); + + return assertThrowsAsync( + async () => + await subtle.decrypt(algorithm, publicKey as CryptoKey, ciphertext), + 'The requested operation is not valid', + ); +} + +{ + const { passing } = rsa_oaep_fixtures; + + passing.forEach((vector: RsaEncryptDecryptTestVector) => { + test(SUITE, `RSA-OAEP decryption ${vector.name}`, async () => { + await testRSADecryption(vector); + }); + test(SUITE, `RSA-OAEP decryption wrong key ${vector.name}`, async () => { + await testRSADecryptionWrongKey(vector); + }); + test(SUITE, `RSA-OAEP decryption bad usage ${vector.name}`, async () => { + await testRSADecryptionBadUsage(vector); + }); + test(SUITE, `RSA-OAEP encryption ${vector.name}`, async () => { + await testRSAEncryption(vector); + }); + test(SUITE, `RSA-OAEP encryption ${vector.name}`, async () => { + await testRSAEncryption(vector, true); + }); + test( + SUITE, + `RSA-OAEP encryption long plaintext ${vector.name}`, + async () => { + await testRSAEncryptionLongPlaintext(vector); + }, + ); + test(SUITE, `RSA-OAEP encryption wrong key ${vector.name}`, async () => { + await testRSAEncryptionWrongKey(vector); + }); + test(SUITE, `RSA-OAEP encryption bad usage ${vector.name}`, async () => { + await testRSAEncryptionBadUsage(vector); + }); + }); +} + +// from https://github.com/nodejs/node/blob/main/test/parallel/test-webcrypto-encrypt-decrypt.js +// Test Encrypt/Decrypt AES-CTR +test(SUITE, 'AES-CTR', async () => { + const buf = getRandomValues(new Uint8Array(50)); + const counter = getRandomValues(new Uint8Array(16)); + + const key = await subtle.generateKey( + { + name: 'AES-CTR', + length: 256, + }, + true, + ['encrypt', 'decrypt'], + ); + + const ciphertext = await subtle.encrypt( + { name: 'AES-CTR', counter, length: 64 }, + key as CryptoKey, + buf, + ); + + const plaintext = await subtle.decrypt( + { name: 'AES-CTR', counter, length: 64 }, + key as CryptoKey, + ciphertext, + ); + + expect(Buffer.from(plaintext).toString('hex')).to.equal( + Buffer.from(buf as Uint8Array).toString('hex'), + ); +}); + +// Test Encrypt/Decrypt AES-CBC +test(SUITE, 'AES-CBC', async () => { + const buf = getRandomValues(new Uint8Array(50)); + const iv = getRandomValues(new Uint8Array(16)); + + const key = await subtle.generateKey( + { + name: 'AES-CBC', + length: 256, + }, + true, + ['encrypt', 'decrypt'], + ); + + const ciphertext = await subtle.encrypt( + { name: 'AES-CBC', iv }, + key as CryptoKey, + buf, + ); + + const plaintext = await subtle.decrypt( + { name: 'AES-CBC', iv }, + key as CryptoKey, + ciphertext, + ); + + expect(Buffer.from(plaintext).toString('hex')).to.equal( + Buffer.from(buf as Uint8Array).toString('hex'), + ); +}); + +// Test Encrypt/Decrypt AES-GCM +test(SUITE, 'AES-GCM', async () => { + const buf = getRandomValues(new Uint8Array(50)); + const iv = getRandomValues(new Uint8Array(12)); + + const key = await subtle.generateKey( + { + name: 'AES-GCM', + length: 256, + }, + true, + ['encrypt', 'decrypt'], + ); + + const ciphertext = await subtle.encrypt( + { name: 'AES-GCM', iv }, + key as CryptoKey, + buf, + ); + + const plaintext = await subtle.decrypt( + { name: 'AES-GCM', iv }, + key as CryptoKey, + ciphertext, + ); + + expect(Buffer.from(plaintext).toString('hex')).to.equal( + Buffer.from(buf as Uint8Array).toString('hex'), + ); +}); + +// Test Encrypt/Decrypt AES-GCM with iv & additionalData +// default AuthTag length +test( + SUITE, + 'AES-GCM with iv & additionalData - default AuthTag length', + async () => { + const iv = getRandomValues(new Uint8Array(12)); + const aad = getRandomValues(new Uint8Array(32)); + + const secretKey = (await subtle.generateKey( + { + name: 'AES-GCM', + length: 256, + }, + false, + ['encrypt', 'decrypt'], + )) as CryptoKey; + + const encrypted = await subtle.encrypt( + { + name: 'AES-GCM', + iv, + additionalData: aad, + tagLength: 128, + }, + secretKey, + getRandomValues(new Uint8Array(32)), + ); + + await subtle.decrypt( + { + name: 'AES-GCM', + iv, + additionalData: aad, + tagLength: 128, + }, + secretKey, + new Uint8Array(encrypted), + ); + }, +); + +// from https://github.com/nodejs/node/blob/main/test/parallel/test-webcrypto-encrypt-decrypt-aes.js +async function testAESEncrypt({ + keyBuffer, + algorithm, + plaintext, + result, +}: AesEncryptDecryptTestVector): Promise { + // keeps typescript happy + if (!keyBuffer || !algorithm || !plaintext || !result) { + throw new Error('Missing test vector'); + } + + // Using a copy of plaintext to prevent tampering of the original + const plaintextBuffer = Buffer.from(plaintext); + + const key = await subtle.importKey( + 'raw', + keyBuffer, + { name: algorithm.name }, + false, + ['encrypt', 'decrypt'], + ); + const output = await subtle.encrypt(algorithm, key, plaintext); + plaintextBuffer[0] = 255 - plaintextBuffer[0]!; + + expect(ab2str(output)).to.equal(ab2str(result), 'output != result'); + + const checkAB = await subtle.decrypt(algorithm, key, output); + // Converting the returned ArrayBuffer into a Buffer right away, + // so that the next line works + const check = Buffer.from(checkAB); + check[0] = 255 - check[0]!; + + expect(ab2str(checkAB)).to.equal( + plaintextBuffer.toString('hex'), + 'check != plaintext', + ); +} + +async function testAESEncryptNoEncrypt({ + keyBuffer, + algorithm, + plaintext, +}: AesEncryptDecryptTestVector): Promise { + // keeps typescript happy + if (!keyBuffer || !algorithm || !plaintext) { + throw new Error('Missing test vector'); + } + + const key = await subtle.importKey( + 'raw', + keyBuffer, + { name: algorithm.name }, + false, + ['decrypt'], + ); + + await assertThrowsAsync( + async () => await subtle.encrypt(algorithm, key, plaintext), + 'The requested operation is not valid for the provided key', + ); +} + +async function testAESEncryptNoDecrypt({ + keyBuffer, + algorithm, + plaintext, +}: AesEncryptDecryptTestVector): Promise { + // keeps typescript happy + if (!keyBuffer || !algorithm || !plaintext) { + throw new Error('Missing test vector'); + } + + const key = await subtle.importKey( + 'raw', + keyBuffer, + { name: algorithm.name }, + false, + ['encrypt'], + ); + + const output = await subtle.encrypt(algorithm, key, plaintext); + + await assertThrowsAsync( + async () => await subtle.decrypt(algorithm, key, output), + 'The requested operation is not valid for the provided key', + ); +} + +async function testAESEncryptWrongAlg( + { keyBuffer, algorithm, plaintext }: AesEncryptDecryptTestVector, + alg: AnyAlgorithm, +): Promise { + // keeps typescript happy + if (!keyBuffer || !algorithm || !plaintext) { + throw new Error('Missing test vector'); + } + + expect(algorithm.name).to.not.equal(alg); + const key = await subtle.importKey('raw', keyBuffer, { name: alg }, false, [ + 'encrypt', + ]); + + await assertThrowsAsync( + async () => await subtle.encrypt(algorithm, key, plaintext), + 'The requested operation is not valid for the provided key', + ); +} + +async function testAESDecrypt({ + keyBuffer, + algorithm, + result, +}: AesEncryptDecryptTestVector): Promise { + // keeps typescript happy + if (!keyBuffer || !algorithm || !result) { + throw new Error('Missing test vector'); + } + + const key = await subtle.importKey( + 'raw', + keyBuffer, + { name: algorithm.name }, + false, + ['encrypt', 'decrypt'], + ); + + await subtle.decrypt(algorithm, key, result); +} + +// Test aes-cbc vectors +{ + const { passing, failing, decryptionFailing } = aes_cbc_fixtures; + + passing.forEach((vector: AesEncryptDecryptTestVector) => { + const { algorithm, keyLength } = vector; + const { name } = algorithm as AesCbcParams; + test(SUITE, `testEncrypt passing ${name} ${keyLength}`, async () => { + await testAESEncrypt(vector); + }); + test( + SUITE, + `testEncryptNoEncrypt passing ${name} ${keyLength}`, + async () => { + await testAESEncryptNoEncrypt(vector); + }, + ); + test( + SUITE, + `testEncryptNoDecrypt passing ${name} ${keyLength}`, + async () => { + await testAESEncryptNoDecrypt(vector); + }, + ); + test( + SUITE, + `testEncryptWrongAlg passing ${name} ${keyLength}`, + async () => { + await testAESEncryptWrongAlg(vector, 'AES-CTR'); + }, + ); + }); + + failing.forEach((vector: AesEncryptDecryptTestVector) => { + const { algorithm, keyLength } = vector; + const { name } = algorithm as AesCbcParams; + test(SUITE, `testEncrypt failing cbc ${name} ${keyLength}`, async () => { + await assertThrowsAsync( + async () => await testAESEncrypt(vector), + 'algorithm.iv must contain exactly 16 bytes', + ); + }); + test(SUITE, `testDecrypt failing cbc ${name} ${keyLength}`, async () => { + await assertThrowsAsync( + async () => await testAESDecrypt(vector), + 'algorithm.iv must contain exactly 16 bytes', + ); + }); + }); + + decryptionFailing.forEach((vector: AesEncryptDecryptTestVector) => { + const { algorithm, keyLength } = vector; + const { name } = algorithm as AesCbcParams; + test( + SUITE, + `testDecrypt decryptionFailing ${name} ${keyLength}`, + async () => { + await assertThrowsAsync( + async () => await testAESDecrypt(vector), + 'error in DoCipher, status: 2', + ); + }, + ); + }); +} + +// Test aes-ctr vectors +{ + const { passing, failing, decryptionFailing } = aes_ctr_fixtures; + + passing.forEach((vector: AesEncryptDecryptTestVector) => { + const { algorithm, keyLength } = vector; + const { name } = algorithm as AesCtrParams; + test(SUITE, `testEncrypt passing ${name} ${keyLength}`, async () => { + await testAESEncrypt(vector); + }); + test( + SUITE, + `testEncryptNoEncrypt passing ${name} ${keyLength}`, + async () => { + await testAESEncryptNoEncrypt(vector); + }, + ); + test( + SUITE, + `testEncryptNoDecrypt passing ${name} ${keyLength}`, + async () => { + await testAESEncryptNoDecrypt(vector); + }, + ); + test( + SUITE, + `testEncryptWrongAlg passing ${name} ${keyLength}`, + async () => { + await testAESEncryptWrongAlg(vector, 'AES-CBC'); + }, + ); + }); + + // TODO(@jasnell): These fail for different reasons. Need to + // make them consistent + failing.forEach((vector: AesEncryptDecryptTestVector) => { + const { algorithm, keyLength } = vector; + const { name } = algorithm as AesCtrParams; + test(SUITE, `testEncrypt failing ctr ${name} ${keyLength}`, async () => { + await assertThrowsAsync( + async () => await testAESEncrypt(vector), + 'AES-CTR algorithm.length must be between 1 and 128', + ); + }); + test(SUITE, `testDecrypt failing ctr ${name} ${keyLength}`, async () => { + await assertThrowsAsync( + async () => await testAESDecrypt(vector), + 'AES-CTR algorithm.length must be between 1 and 128', + ); + }); + }); + + decryptionFailing.forEach((vector: AesEncryptDecryptTestVector) => { + const { algorithm, keyLength } = vector; + const { name } = algorithm as AesCtrParams; + test( + SUITE, + `testDecrypt decryptionFailing ${name} ${keyLength}`, + async () => { + await assertThrowsAsync( + async () => await testAESDecrypt(vector), + 'error in DoCipher, status: 2', + ); + }, + ); + }); +} + +// Test aes-gcm vectors +{ + const { passing, failing, decryptionFailing } = aes_gcm_fixtures; + + passing.forEach((vector: AesEncryptDecryptTestVector) => { + const { algorithm, keyLength } = vector; + const { name, tagLength } = algorithm as AesGcmParams; + test( + SUITE, + `testEncrypt passing ${name} ${keyLength} ${tagLength}`, + async () => { + await testAESEncrypt(vector); + }, + ); + test( + SUITE, + `testEncryptNoEncrypt passing ${name} ${keyLength} ${tagLength}`, + async () => { + await testAESEncryptNoEncrypt(vector); + }, + ); + test( + SUITE, + `testEncryptNoDecrypt passing ${name} ${keyLength} ${tagLength}`, + async () => { + await testAESEncryptNoDecrypt(vector); + }, + ); + test( + SUITE, + `testEncryptWrongAlg passing ${name} ${keyLength} ${tagLength}`, + async () => { + await testAESEncryptWrongAlg(vector, 'AES-CBC'); + }, + ); + }); + + failing.forEach((vector: AesEncryptDecryptTestVector) => { + const { algorithm, keyLength } = vector; + const { name, tagLength } = algorithm as AesGcmParams; + test( + SUITE, + `testEncrypt failing gcm ${name} ${keyLength} ${tagLength}`, + async () => { + await assertThrowsAsync( + async () => await testAESEncrypt(vector), + 'is not a valid AES-GCM tag length', + ); + }, + ); + test( + SUITE, + `testDecrypt failing gcm ${name} ${keyLength} ${tagLength}`, + async () => { + await assertThrowsAsync( + async () => await testAESDecrypt(vector), + 'is not a valid AES-GCM tag length', + ); + }, + ); + }); + + decryptionFailing.forEach((vector: AesEncryptDecryptTestVector) => { + const { algorithm, keyLength } = vector; + const { name, tagLength } = algorithm as AesGcmParams; + test( + SUITE, + `testDecrypt decryptionFailing ${name} ${keyLength} ${tagLength}`, + async () => { + await assertThrowsAsync( + async () => await testAESDecrypt(vector), + 'error in DoCipher, status: 2', + ); + }, + ); + }); +} diff --git a/example/src/tests/subtle/generateKey.ts b/example/src/tests/subtle/generateKey.ts new file mode 100644 index 00000000..dc23acf1 --- /dev/null +++ b/example/src/tests/subtle/generateKey.ts @@ -0,0 +1,674 @@ +import { expect } from 'chai'; +import { + subtle, + type AESAlgorithm, + type AESLength, + type AnyAlgorithm, + type NamedCurve, +} from 'react-native-quick-crypto'; +import type { CryptoKey, KeyUsage } from 'react-native-quick-crypto'; + +// Local interface to match what subtle.generateKey actually returns +interface TestCryptoKeyPair { + publicKey: CryptoKey; + privateKey: CryptoKey; +} +import { test, assertThrowsAsync } from '../util'; + +const SUITE = 'subtle.generateKey'; + +const allUsages: KeyUsage[] = [ + 'encrypt', + 'decrypt', + 'sign', + 'verify', + 'deriveBits', + 'deriveKey', + 'wrapKey', + 'unwrapKey', +]; + +type Vector = { + algorithm?: object; + result: string; + usages: KeyUsage[]; +}; + +type Vectors = { + [key in string]: Vector; +}; + +const vectors: Vectors = { + // 'AES-CTR': { + // algorithm: { length: 256 }, + // result: 'CryptoKey', + // usages: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'], + // }, + // 'AES-CBC': { + // algorithm: { length: 256 }, + // result: 'CryptoKey', + // usages: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'], + // }, + // 'AES-GCM': { + // algorithm: { length: 256 }, + // result: 'CryptoKey', + // usages: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'], + // }, + // 'AES-KW': { + // algorithm: { length: 256 }, + // result: 'CryptoKey', + // usages: ['wrapKey', 'unwrapKey'], + // }, + // HMAC: { + // algorithm: { length: 256, hash: 'SHA-256' }, + // result: 'CryptoKey', + // usages: ['sign', 'verify'], + // }, + // 'RSASSA-PKCS1-v1_5': { + // algorithm: { + // modulusLength: 1024, + // publicExponent: new Uint8Array([1, 0, 1]), + // hash: 'SHA-256', + // }, + // result: 'CryptoKeyPair', + // usages: ['sign', 'verify'], + // }, + // 'RSA-PSS': { + // algorithm: { + // modulusLength: 1024, + // publicExponent: new Uint8Array([1, 0, 1]), + // hash: 'SHA-256', + // }, + // result: 'CryptoKeyPair', + // usages: ['sign', 'verify'], + // }, + // 'RSA-OAEP': { + // algorithm: { + // modulusLength: 1024, + // publicExponent: new Uint8Array([1, 0, 1]), + // hash: 'SHA-256', + // }, + // result: 'CryptoKeyPair', + // usages: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'], + // }, + ECDSA: { + algorithm: { namedCurve: 'P-521' }, + result: 'CryptoKeyPair', + usages: ['sign', 'verify'], + }, + ECDH: { + algorithm: { namedCurve: 'P-521' }, + result: 'CryptoKeyPair', + usages: ['deriveKey', 'deriveBits'], + }, + // Ed25519: { + // result: 'CryptoKeyPair', + // usages: ['sign', 'verify'], + // }, + // Ed448: { + // result: 'CryptoKeyPair', + // usages: ['sign', 'verify'], + // }, + // X25519: { + // result: 'CryptoKeyPair', + // usages: ['deriveKey', 'deriveBits'], + // }, + // X448: { + // result: 'CryptoKeyPair', + // usages: ['deriveKey', 'deriveBits'], + // }, +}; + +// Test invalid algorithms +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function testInvalidAlgorithm(algorithm: any) { + // one test is slightly different than the others + const errorText = + algorithm.hash === 'SHA' + ? 'Invalid Hash Algorithm' + : 'Unrecognized algorithm name'; + const algo = JSON.stringify(algorithm); + test(SUITE, `invalid algo: ${algo}`, async () => { + await assertThrowsAsync( + async () => + // @ts-expect-error bad extractable + // The extractable and usages values are invalid here also, + // but the unrecognized algorithm name should be caught first. + await subtle.generateKey(algorithm, 7, []), + errorText, + ); + }); +} + +const invalidAlgoTests = [ + 'AES', + { name: 'AES' }, + { name: 'AES-CMAC' }, + { name: 'AES-CFB' }, + { name: 'HMAC', hash: 'MD5' }, + { + name: 'RSA', + hash: 'SHA-256', + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + }, + { + name: 'RSA-PSS', + hash: 'SHA', + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + }, + { + name: 'EC', + namedCurve: 'P521', + }, +]; + +invalidAlgoTests.map(testInvalidAlgorithm); + +// Test bad usages +async function testBadUsage(name: string) { + test(SUITE, `bad usages: ${name}`, async () => { + await assertThrowsAsync( + async () => + await subtle.generateKey( + { + name: name as AnyAlgorithm, + ...vectors[name]?.algorithm, + }, + true, + [], + ), + 'Usages cannot be empty', + ); + + // For CryptoKeyPair results the private key + // usages must not be empty. + // - ECDH(-like) algorithm key pairs only have private key usages + // - Signing algorithm key pairs may pass a non-empty array but + // with only a public key usage + if ( + vectors[name]?.result === 'CryptoKeyPair' && + vectors[name]?.usages.includes('verify') + ) { + await assertThrowsAsync( + async () => + await subtle.generateKey( + { + name: name as AnyAlgorithm, + ...vectors[name]?.algorithm, + }, + true, + ['verify'], + ), + 'Usages cannot be empty', + ); + } + + const invalidUsages: KeyUsage[] = []; + allUsages.forEach(usage => { + if (!vectors[name]?.usages.includes(usage)) { + invalidUsages.push(usage); + } + }); + for (const invalidUsage of invalidUsages) { + await assertThrowsAsync( + async () => + await subtle.generateKey( + { + name: name as AnyAlgorithm, + ...vectors[name]?.algorithm, + }, + true, + [...(vectors[name]?.usages as KeyUsage[]), invalidUsage], + ), + 'Unsupported key usage', + ); + } + }); +} + +const badUsageTests = Object.keys(vectors); +badUsageTests.map(testBadUsage); + +/* + // Test RSA key generation + { + async function test( + name, + modulusLength, + publicExponent, + hash, + privateUsages, + publicUsages = privateUsages + ) { + let usages = privateUsages; + if (publicUsages !== privateUsages) usages = usages.concat(publicUsages); + const { publicKey, privateKey } = await subtle.generateKey( + { + name, + modulusLength, + publicExponent, + hash, + }, + true, + usages + ); + + assert(publicKey); + assert(privateKey); + assert(isCryptoKey(publicKey)); + assert(isCryptoKey(privateKey)); + + assert(publicKey instanceof CryptoKey); + assert(privateKey instanceof CryptoKey); + + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(publicKey.toString(), '[object CryptoKey]'); + assert.strictEqual(privateKey.toString(), '[object CryptoKey]'); + assert.strictEqual(publicKey.extractable, true); + assert.strictEqual(privateKey.extractable, true); + assert.deepStrictEqual(publicKey.usages, publicUsages); + assert.deepStrictEqual(privateKey.usages, privateUsages); + assert.strictEqual(publicKey.algorithm.name, name); + assert.strictEqual(publicKey.algorithm.modulusLength, modulusLength); + assert.deepStrictEqual(publicKey.algorithm.publicExponent, publicExponent); + assert.strictEqual( + KeyObject.from(publicKey).asymmetricKeyDetails.publicExponent, + bigIntArrayToUnsignedBigInt(publicExponent) + ); + assert.strictEqual(publicKey.algorithm.hash.name, hash); + assert.strictEqual(privateKey.algorithm.name, name); + assert.strictEqual(privateKey.algorithm.modulusLength, modulusLength); + assert.deepStrictEqual(privateKey.algorithm.publicExponent, publicExponent); + assert.strictEqual( + KeyObject.from(privateKey).asymmetricKeyDetails.publicExponent, + bigIntArrayToUnsignedBigInt(publicExponent) + ); + assert.strictEqual(privateKey.algorithm.hash.name, hash); + + // Missing parameters + await assert.rejects( + subtle.generateKey({ name, publicExponent, hash }, true, usages), + { + code: 'ERR_MISSING_OPTION', + } + ); + + await assert.rejects( + subtle.generateKey({ name, modulusLength, hash }, true, usages), + { + code: 'ERR_MISSING_OPTION', + } + ); + + await assert.rejects( + subtle.generateKey({ name, modulusLength }, true, usages), + { + code: 'ERR_MISSING_OPTION', + } + ); + + await Promise.all( + [{}].map((modulusLength) => { + return assert.rejects( + subtle.generateKey( + { + name, + modulusLength, + publicExponent, + hash, + }, + true, + usages + ), + { + code: 'ERR_INVALID_ARG_TYPE', + } + ); + }) + ); + + await Promise.all( + ['', true, {}, 1, [], new Uint32Array(2)].map((publicExponent) => { + return assert.rejects( + subtle.generateKey( + { name, modulusLength, publicExponent, hash }, + true, + usages + ), + { code: 'ERR_INVALID_ARG_TYPE' } + ); + }) + ); + + await Promise.all( + [true, 1].map((hash) => { + return assert.rejects( + subtle.generateKey( + { + name, + modulusLength, + publicExponent, + hash, + }, + true, + usages + ), + { + message: /Unrecognized algorithm name/, + name: 'NotSupportedError', + } + ); + }) + ); + + await Promise.all( + ['', {}, 1, false].map((usages) => { + return assert.rejects( + subtle.generateKey( + { + name, + modulusLength, + publicExponent, + hash, + }, + true, + usages + ), + { + code: 'ERR_INVALID_ARG_TYPE', + } + ); + }) + ); + + await Promise.all( + [[1], [1, 0, 0]].map((publicExponent) => { + return assert.rejects( + subtle.generateKey( + { + name, + modulusLength, + publicExponent: new Uint8Array(publicExponent), + hash, + }, + true, + usages + ), + { + name: 'OperationError', + } + ); + }) + ); + } + + const kTests = [ + [ + 'RSASSA-PKCS1-v1_5', + 1024, + Buffer.from([1, 0, 1]), + 'SHA-256', + ['sign'], + ['verify'], + ], + ['RSA-PSS', 2048, Buffer.from([1, 0, 1]), 'SHA-512', ['sign'], ['verify']], + [ + 'RSA-OAEP', + 1024, + Buffer.from([3]), + 'SHA-384', + ['decrypt', 'unwrapKey'], + ['encrypt', 'wrapKey'], + ], + ]; + + const tests = kTests.map((args) => test(...args)); + + Promise.all(tests).then(common.mustCall()); + } + */ + +// Test EC Key Generation +async function testECKeyGen( + name: AnyAlgorithm, + namedCurve: NamedCurve, + privateUsages: KeyUsage[], + publicUsages: KeyUsage[] = privateUsages, +) { + test( + SUITE, + `EC keygen: ${name} ${namedCurve} ${privateUsages} ${publicUsages}`, + async () => { + let usages = privateUsages; + if (publicUsages !== privateUsages) { + usages = usages.concat(publicUsages); + } + + const pair = await subtle.generateKey( + { + name, + namedCurve, + }, + true, + usages, + ); + const { publicKey, privateKey } = pair as TestCryptoKeyPair; + const pub = publicKey; + const priv = privateKey; + + expect(pub !== undefined); + expect(priv !== undefined); + expect(pub instanceof Object); + expect(priv instanceof Object); + expect(pub.type).to.equal('public'); + expect(priv.type).to.equal('private'); + expect(pub.keyExtractable).to.equal(true); + expect(priv.keyExtractable).to.equal(true); + expect(pub.keyUsages).to.deep.equal(publicUsages); + expect(priv.keyUsages).to.deep.equal(privateUsages); + expect(pub.algorithm.name, name); + expect(priv.algorithm.name, name); + expect(pub.algorithm.namedCurve, namedCurve); + expect(priv.algorithm.namedCurve, namedCurve); + + // Invalid parameters + [1, true, {}, [], null].forEach(async curve => { + await assertThrowsAsync( + async () => + await subtle.generateKey( + // @ts-expect-error bad named curve + { name, namedCurve: curve }, + true, + privateUsages, + ), + 'NotSupportedError', + ); + }); + await assertThrowsAsync( + async () => + subtle.generateKey( + { name, namedCurve: undefined }, + true, + privateUsages, + ), + "Unrecognized namedCurve 'undefined'", + ); + }, + ); +} + +testECKeyGen('ECDSA', 'P-384', ['sign'], ['verify']); +testECKeyGen('ECDSA', 'P-521', ['sign'], ['verify']); +testECKeyGen('ECDH', 'P-384', ['deriveKey', 'deriveBits'], []); +testECKeyGen('ECDH', 'P-521', ['deriveKey', 'deriveBits'], []); + +// Test AES Key Generation +type AESArgs = [AESAlgorithm, AESLength, KeyUsage[]]; +async function testAesKeyGen(args: AESArgs) { + const [name, length, usages] = args; + it(`AES keygen: ${name} ${length} ${usages}`, async () => { + const key = await subtle.generateKey({ name, length }, true, usages); + const k = key as CryptoKey; + expect(k !== undefined); + expect(k instanceof Object); + + expect(k.type).to.equal('secret'); + expect(k.extractable).to.equal(true); + expect(k.usages).to.deep.equal(usages); + expect(k.algorithm.name).to.equal(name); + expect(k.algorithm.length).to.equal(length); + + // Invalid parameters + [1, 100, 257, '', false, null, undefined].forEach(async invalidParam => { + await assertThrowsAsync( + async () => + subtle.generateKey( + // @ts-expect-error bad length + { name, length: invalidParam }, + true, + usages, + ), + 'AES key length must be 128, 192, or 256 bits', + ); + }); + }); +} + +const aesTests: AESArgs[] = [ + ['AES-CTR', 128, ['encrypt', 'decrypt', 'wrapKey']], + ['AES-CTR', 256, ['encrypt', 'decrypt', 'unwrapKey']], + ['AES-CBC', 128, ['encrypt', 'decrypt']], + ['AES-CBC', 256, ['encrypt', 'decrypt']], + ['AES-GCM', 128, ['encrypt', 'decrypt']], + ['AES-GCM', 256, ['encrypt', 'decrypt']], + ['AES-KW', 128, ['wrapKey', 'unwrapKey']], + ['AES-KW', 256, ['wrapKey', 'unwrapKey']], +]; + +aesTests.map(args => testAesKeyGen(args)); + +/* + // Test HMAC Key Generation + { + async function test(length, hash, usages) { + const key = await subtle.generateKey( + { + name: 'HMAC', + length, + hash, + }, + true, + usages + ); + + if (length === undefined) { + switch (hash) { + case 'SHA-1': + length = 512; + break; + case 'SHA-256': + length = 512; + break; + case 'SHA-384': + length = 1024; + break; + case 'SHA-512': + length = 1024; + break; + } + } + + assert(key); + assert(isCryptoKey(key)); + + assert.strictEqual(key.type, 'secret'); + assert.strictEqual(key.toString(), '[object CryptoKey]'); + assert.strictEqual(key.extractable, true); + assert.deepStrictEqual(key.usages, usages); + assert.strictEqual(key.algorithm.name, 'HMAC'); + assert.strictEqual(key.algorithm.length, length); + assert.strictEqual(key.algorithm.hash.name, hash); + + [1, false, null].forEach(async (hash) => { + await assert.rejects( + subtle.generateKey({ name: 'HMAC', length, hash }, true, usages), + { + message: /Unrecognized algorithm name/, + name: 'NotSupportedError', + } + ); + }); + } + + const kTests = [ + [undefined, 'SHA-1', ['sign', 'verify']], + [undefined, 'SHA-256', ['sign', 'verify']], + [undefined, 'SHA-384', ['sign', 'verify']], + [undefined, 'SHA-512', ['sign', 'verify']], + [128, 'SHA-256', ['sign', 'verify']], + [1024, 'SHA-512', ['sign', 'verify']], + ]; + + const tests = Promise.all(kTests.map((args) => test(...args))); + + tests.then(common.mustCall()); + } + + // End user code cannot create CryptoKey directly + assert.throws(() => new CryptoKey(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' }); + + { + const buffer = Buffer.from('Hello World'); + const keyObject = createSecretKey(buffer); + assert(!isCryptoKey(buffer)); + assert(!isCryptoKey(keyObject)); + } + + // Test OKP Key Generation + { + async function test(name, privateUsages, publicUsages = privateUsages) { + let usages = privateUsages; + if (publicUsages !== privateUsages) usages = usages.concat(publicUsages); + + const { publicKey, privateKey } = await subtle.generateKey( + { + name, + }, + true, + usages + ); + + assert(publicKey); + assert(privateKey); + assert(isCryptoKey(publicKey)); + assert(isCryptoKey(privateKey)); + + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(publicKey.toString(), '[object CryptoKey]'); + assert.strictEqual(privateKey.toString(), '[object CryptoKey]'); + assert.strictEqual(publicKey.extractable, true); + assert.strictEqual(privateKey.extractable, true); + assert.deepStrictEqual(publicKey.usages, publicUsages); + assert.deepStrictEqual(privateKey.usages, privateUsages); + assert.strictEqual(publicKey.algorithm.name, name); + assert.strictEqual(privateKey.algorithm.name, name); + } + + const kTests = [ + ['Ed25519', ['sign'], ['verify']], + ['Ed448', ['sign'], ['verify']], + ['X25519', ['deriveKey', 'deriveBits'], []], + ['X448', ['deriveKey', 'deriveBits'], []], + ]; + + const tests = kTests.map((args) => test(...args)); + + // Test bad parameters + + Promise.all(tests).then(common.mustCall()); + } + */ diff --git a/example/src/tests/subtle/import_export.ts b/example/src/tests/subtle/import_export.ts new file mode 100644 index 00000000..cd4ffd7e --- /dev/null +++ b/example/src/tests/subtle/import_export.ts @@ -0,0 +1,1780 @@ +import { Buffer } from '@craftzdog/react-native-buffer'; +import { assert, expect } from 'chai'; +import { + fromByteArray, + toByteArray, + trimBase64Padding, +} from 'react-native-quick-base64'; +import type { + CryptoKey, + CryptoKeyPair, + HashAlgorithm, + JWK, + KeyUsage, + NamedCurve, + RandomTypedArrays, + RSAKeyPairAlgorithm, + // SubtleAlgorithm, // TODO: for 'bad usages' test +} from 'react-native-quick-crypto'; +import { + ab2str, + binaryLikeToArrayBuffer, + // createPublicKey, // TODO: for 'bad usages' test + // createPrivateKey, // TODO: for 'bad usages' test + getRandomValues, + subtle, +} from 'react-native-quick-crypto'; +import { assertThrowsAsync, test } from '../util'; + +// TODO: for 'bad usages' test +// import privTestKeyEc256 from '../../fixtures/keys/ec_p256_private'; +// import pubTestKeyEc256 from '../../fixtures/keys/ec_p256_public'; + +// Tests that a key pair can be used for encryption / decryption. +// function testEncryptDecrypt(publicKey: any, privateKey: any) { +// const message = 'Hello Node.js world!'; +// const plaintext = Buffer.from(message, 'utf8'); +// for (const key of [publicKey, privateKey]) { +// const ciphertext = crypto.publicEncrypt(key, plaintext); +// const received = crypto.privateDecrypt(privateKey, ciphertext); +// chai.expect(received.toString('utf8')).to.equal(message); +// } +// } + +// I guess interally this functions use privateEncrypt/publicDecrypt (sign/verify) +// but the main function `sign` is not implemented yet +// Tests that a key pair can be used for signing / verification. +// function testSignVerify(publicKey: any, privateKey: any) { +// const message = Buffer.from('Hello Node.js world!'); + +// function oldSign(algo, data, key) { +// return createSign(algo).update(data).sign(key); +// } + +// function oldVerify(algo, data, key, signature) { +// return createVerify(algo).update(data).verify(key, signature); +// } + +// for (const signFn of [sign, oldSign]) { +// const signature = signFn('SHA256', message, privateKey); +// for (const verifyFn of [verify, oldVerify]) { +// for (const key of [publicKey, privateKey]) { +// const okay = verifyFn('SHA256', message, key, signature); +// assert(okay); +// } +// } +// } +// } + +function base64ToArrayBuffer(val: string): ArrayBuffer { + const arr = toByteArray(val); + return binaryLikeToArrayBuffer(arr); +} + +// TODO: add in `url` from react-native-quick-base64 when 2.1.1 is released +function arrayBufferToBase64(buffer: ArrayBuffer, urlSafe: boolean = false) { + const bytes = new Uint8Array(buffer); + return fromByteArray(bytes, urlSafe); +} + +const SUITE = 'subtle.importKey/exportKey'; + +// Import/Export test bad inputs +test(SUITE, 'Bad inputs', async () => { + const keyData = getRandomValues(new Uint8Array(32)); + [1, null, undefined, {}, []].map( + async format => + await assertThrowsAsync( + async () => + // @ts-expect-error bad format + await subtle.importKey(format, keyData, {}, false, ['wrapKey']), + '"subtle.importKey()" is not implemented for unknown', + ), + ); + await assertThrowsAsync( + async () => + await subtle.importKey( + // @ts-expect-error bad format + 'not valid', + keyData, + { name: 'PBKDF2' }, + false, + ['wrapKey'], + ), + 'Unsupported key usage for a PBKDF2 key', + ); + await assertThrowsAsync( + async () => + // @ts-expect-error bad key data + await subtle.importKey('raw', 1, { name: 'PBKDF2' }, false, [ + 'deriveBits', + ]), + 'Invalid argument type for "key". Need ArrayBuffer, TypedArray, KeyObject, CryptoKey, string', + ); +}); + +test(SUITE, 'Good Input - Uint8Array', async () => { + await subtle.importKey( + 'raw', + new Uint8Array([ + 117, 110, 102, 97, 105, 114, 32, 99, 117, 108, 116, 117, 114, 101, 32, + 115, 117, 105, 116, 32, 112, 97, 116, 104, 32, 119, 111, 114, 108, 100, + 32, 104, 105, 103, 104, 32, 116, 111, 109, 111, 114, 114, 111, 119, 32, + 118, 105, 100, 101, 111, 32, 114, 101, 99, 105, 112, 101, 32, 99, 114, + 105, 109, 101, 32, 101, 110, 103, 105, 110, 101, 32, 119, 105, 100, 116, + 104, 32, 111, 102, 116, 101, 110, 32, 116, 97, 112, 101, 32, 116, 114, + 101, 110, 100, 32, 99, 111, 112, 112, 101, 114, 32, 100, 111, 117, 98, + 108, 101, 32, 103, 108, 111, 114, 121, 32, 100, 111, 99, 116, 111, 114, + 32, 101, 110, 101, 114, 103, 121, 32, 103, 111, 111, 115, 101, 32, 115, + 101, 99, 111, 110, 100, 32, 97, 98, 115, 116, 114, 97, 99, 116, 32, 107, + 110, 111, 99, 107, + ]), + { name: 'PBKDF2' }, + false, + ['deriveBits'], + ); +}); + +test(SUITE, ' importKey - raw - pbkdf2 - empty byte source #735', async () => { + const key = await subtle.importKey( + 'raw', + new Uint8Array(), + { + name: 'PBKDF2', + hash: 'SHA-256', + }, + false, + ['deriveBits', 'deriveKey'], + ); + expect(key).to.not.equal(null); +}); + +// Import/Export AES Secret Key +test(SUITE, 'AES import raw / export raw', async () => { + const rawKeyData = getRandomValues(new Uint8Array(32)); + const keyData = binaryLikeToArrayBuffer(rawKeyData); + + // import raw + const key = await subtle.importKey( + 'raw', + keyData, + { name: 'AES-CTR', length: 256 }, + true, + ['encrypt', 'decrypt'], + ); + + // export raw + const raw = (await subtle.exportKey('raw', key)) as ArrayBuffer; + const actual = ab2str(raw, 'hex'); + + // test results + const expected = ab2str(keyData, 'hex'); + if (actual !== expected) { + console.log('actual ', actual); + console.log('expected', expected); + } + expect(actual).to.equal(expected, 'import raw, export raw'); +}); + +test(SUITE, 'importKey, raw, AES-GCM string algo', async () => { + const rawKeyData = getRandomValues(new Uint8Array(32)); + const keyData = binaryLikeToArrayBuffer(rawKeyData); + + const key = await subtle.importKey('raw', keyData, 'AES-GCM', false, [ + 'encrypt', + 'decrypt', + ]); + expect(key.keyAlgorithm.name).to.equal('AES-GCM'); + expect(key.keyAlgorithm.length).to.equal(256); +}); + +const testFn = (rawKeyData: RandomTypedArrays, descr: string): void => { + test(SUITE, `AES import raw / export jwk (${descr})`, async () => { + const keyData = binaryLikeToArrayBuffer(rawKeyData); + const keyB64 = arrayBufferToBase64(keyData, true); + + // import raw + const key = await subtle.importKey( + 'raw', + keyData, + { name: 'AES-CTR', length: 256 }, + true, + ['encrypt', 'decrypt'], + ); + + // export jwk + const jwk = (await subtle.exportKey('jwk', key)) as JWK; + expect(jwk.key_ops).to.have.all.members(['encrypt', 'decrypt']); + expect(jwk.ext); + expect(jwk.kty).to.equal('oct'); + const actual = ab2str(base64ToArrayBuffer(jwk.k as string)); + + // test results + const expected = ab2str(keyData, 'hex'); + if (actual !== expected) { + console.log('actual ', actual); + console.log('expected', expected); + console.log('keyB64 ', keyB64); + console.log('jwk.k ', jwk.k); + } + expect(actual).to.equal(expected, 'import raw, export jwk'); + + // error, no usages + await assertThrowsAsync( + async () => + await subtle.importKey( + 'raw', + keyData, + { name: 'AES-GCM', length: 256 }, + true, + [ + // empty usages + ], + ), + 'Usages cannot be empty when importing a secret key', + ); + }); +}; + +// test random Uint8Array +const random = getRandomValues(new Uint8Array(32)); +testFn(random as Uint8Array, 'random'); + +// test while ensuring at least one of the elements is zero +const withZero = getRandomValues(new Uint8Array(32)); +withZero[4] = 0; +testFn(withZero as Uint8Array, 'with zero'); + +// from https://gist.github.com/pedrouid/b4056fd1f754918ddae86b32cf7d803e#aes-gcm---importkey +test(SUITE, 'AES import jwk / export jwk', async () => { + const origKey: string = 'Y0zt37HgOx-BY7SQjYVmrqhPkO44Ii2Jcb9yydUDPfE.'; + const origJwk: JWK = { + kty: 'oct', + k: origKey, + alg: 'A256GCM', + ext: true, + }; + + // import jwk + const key = await subtle.importKey( + 'jwk', + origJwk, + { name: 'AES-GCM' }, + true, + ['encrypt', 'decrypt'], + ); + + // export jwk + const jwk = (await subtle.exportKey('jwk', key)) as JWK; + expect(jwk.key_ops).to.have.all.members(['encrypt', 'decrypt']); + expect(jwk.ext); + expect(jwk.kty).to.equal('oct'); + const actual = trimBase64Padding( + ab2str(base64ToArrayBuffer(jwk.k as string)), + ); + const expected = trimBase64Padding(ab2str(base64ToArrayBuffer(origKey))); + // if (actual !== expected) { + // console.log('actual ', actual); + // console.log('expected', expected); + // } + expect(actual).to.equal(expected, 'import jwk, export jwk'); +}); + +// Import/Export EC Key (osp) +test(SUITE, 'EC import raw / export spki (osp)', async () => { + const key = await subtle.importKey( + 'raw', + base64ToArrayBuffer( + 'BDZRaWzATXwmOi4Y/QP3JXn8sSVSFxidMugnGf3G28snm7zek9GjT76UMhXVMEbWLxR5WG6iGTjPAKKnT3J0jCA=', + ), + { name: 'ECDSA', namedCurve: 'P-256' }, + true, + ['verify'], + ); + + const buf = await subtle.exportKey('spki', key); + const spkiKey = arrayBufferToBase64(buf as ArrayBuffer); + expect(spkiKey).to.equal( + 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENlFpbMBNfCY6Lhj9A/clefyxJVIXGJ0y6CcZ/cbbyyebvN6T0aNPvpQyFdUwRtYvFHlYbqIZOM8AoqdPcnSMIA==', + ); +}); + +// // TODO: enable when generateKey() is implemented +// // from Node.js https://github.com/nodejs/node/blob/main/test/parallel/test-webcrypto-export-import.js#L217-L273 +// test(SUITE, 'EC import / export key pairs (node)', async () => { +// const { publicKey, privateKey } = await subtle.generateKey({ +// name: 'ECDSA', +// namedCurve: 'P-384' +// }, true, ['sign', 'verify']); + +// const [ +// spki, +// pkcs8, +// publicJwk, +// privateJwk, +// ] = await Promise.all([ +// subtle.exportKey('spki', publicKey), +// subtle.exportKey('pkcs8', privateKey), +// subtle.exportKey('jwk', publicKey), +// subtle.exportKey('jwk', privateKey), +// ]); + +// assert(spki); +// assert(pkcs8); +// assert(publicJwk); +// assert(privateJwk); + +// const [ +// importedSpkiPublicKey, +// importedPkcs8PrivateKey, +// importedJwkPublicKey, +// importedJwkPrivateKey, +// ] = await Promise.all([ +// subtle.importKey('spki', spki, { +// name: 'ECDSA', +// namedCurve: 'P-384' +// }, true, ['verify']), +// subtle.importKey('pkcs8', pkcs8, { +// name: 'ECDSA', +// namedCurve: 'P-384' +// }, true, ['sign']), +// subtle.importKey('jwk', publicJwk, { +// name: 'ECDSA', +// namedCurve: 'P-384' +// }, true, ['verify']), +// subtle.importKey('jwk', privateJwk, { +// name: 'ECDSA', +// namedCurve: 'P-384' +// }, true, ['sign']), +// ]); + +// assert(importedSpkiPublicKey); +// assert(importedPkcs8PrivateKey); +// assert(importedJwkPublicKey); +// assert(importedJwkPrivateKey); +// }); + +// from Node.js https://github.com/nodejs/node/blob/main/test/parallel/test-webcrypto-export-import-ec.js +{ + type TestKeyData = { + [key in NamedCurve]: TestKeyDatum; + }; + + type TestKeyDatum = { + jwsAlg: string; + spki: Buffer; + pkcs8: Buffer; + jwk: JWK; + }; + + type TestVector = { + name: 'ECDH' | 'ECDSA'; + publicUsages: KeyUsage[]; + privateUsages: KeyUsage[]; + }; + + const curves: NamedCurve[] = ['P-256', 'P-384', 'P-521']; + + const keyData: TestKeyData = { + 'P-521': { + jwsAlg: 'ES512', + spki: Buffer.from( + '30819b301006072a8648ce3d020106052b8104002303818600040156f479f8df' + + '1e20a7ffc04ce420c3e154ae251996bee42f034b84d41b743f34e45f311b813a' + + '9cdec8cda59bbbbd31d460b3292521e7c1b722e5667c03db2fae753f01501736' + + 'cfe247394320d8e4afc2fd39b5a9331061b81e2241282b9e17891822b5b79e05' + + '2f4597b59643fd39379c51bd5125c4f48bc3f025ce3cd36953286ccb38fb', + 'hex', + ), + pkcs8: Buffer.from( + '3081ee020100301006072a8648ce3d020106052b810400230481d63081d3020' + + '101044200f408758368ba930f30f76ae054fe5cd2ce7fda2c9f76a6d436cf75' + + 'd66c440bfe6331c7c172a12478193c8251487bc91263fa50217f85ff636f59c' + + 'd546e3ab483b4a1818903818600040156f479f8df1e20a7ffc04ce420c3e154' + + 'ae251996bee42f034b84d41b743f34e45f311b813a9cdec8cda59bbbbd31d46' + + '0b3292521e7c1b722e5667c03db2fae753f01501736cfe247394320d8e4afc2' + + 'fd39b5a9331061b81e2241282b9e17891822b5b79e052f4597b59643fd39379' + + 'c51bd5125c4f48bc3f025ce3cd36953286ccb38fb', + 'hex', + ), + jwk: { + kty: 'EC', + crv: 'P-521', + x: + 'AVb0efjfHiCn_8BM5CDD4VSuJRmWvuQvA0uE1Bt0PzTkXzEbgTqc3sjN' + + 'pZu7vTHUYLMpJSHnwbci5WZ8A9svrnU_', + y: + 'AVAXNs_iRzlDINjkr8L9ObWpMxBhuB4iQSgrnheJGCK1t54FL0W' + + 'XtZZD_Tk3nFG9USXE9IvD8CXOPNNpUyhsyzj7', + d: + 'APQIdYNoupMPMPdq4FT-XNLOf9osn3am1DbPddZsRAv-YzHHw' + + 'XKhJHgZPIJRSHvJEmP6UCF_hf9jb1nNVG46tIO0', + }, + }, + 'P-384': { + jwsAlg: 'ES384', + spki: Buffer.from( + '3076301006072a8648ce3d020106052b8104002203620004219c14d66617b36e' + + 'c6d8856b385b73a74d344fd8ae75ef046435dda54e3b44bd5fbdebd1d08dd69e' + + '2d7dc1dc218cb435bd28138cc778337a842f6bd61b240e74249f24667c2a5810' + + 'a76bfc28e0335f88a6501dec01976da85afb00869cb6ace8', + 'hex', + ), + pkcs8: Buffer.from( + '3081b6020100301006072a8648ce3d020106052b8104002204819e30819b0201' + + '0104304537b5990784d3c2d22e96a8f92fa1aa492ee873e576a41582e144183c' + + '9888d10e6b9eb4ced4b2cc4012e4ac5ea84073a16403620004219c14d66617b3' + + '6ec6d8856b385b73a74d344fd8ae75ef046435dda54e3b44bd5fbdebd1d08dd6' + + '9e2d7dc1dc218cb435bd28138cc778337a842f6bd61b240e74249f24667c2a58' + + '10a76bfc28e0335f88a6501dec01976da85afb00869cb6ace8', + 'hex', + ), + jwk: { + kty: 'EC', + crv: 'P-384', + x: 'IZwU1mYXs27G2IVrOFtzp000T9iude8EZDXdpU47RL1fvevR0I3Wni19wdwhjLQ1', + y: 'vSgTjMd4M3qEL2vWGyQOdCSfJGZ8KlgQp2v8KOAzX4imUB3sAZdtqFr7AIactqzo', + d: 'RTe1mQeE08LSLpao-S-hqkku6HPldqQVguFEGDyYiNEOa560ztSyzEAS5KxeqEBz', + }, + }, + 'P-256': { + jwsAlg: 'ES256', + spki: Buffer.from( + '3059301306072a8648ce3d020106082a8648ce3d03010703420004d6e8328a95' + + 'fe29afcdc30977b9251efbb219022807f6b14bb34695b6b4bdb93ee6684548a4' + + 'ad13c49d00433c45315e8274f3540f58f5d79ef7a1b184f4c21d17', + 'hex', + ), + pkcs8: Buffer.from( + '308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b02' + + '010104202bc2eda265e46866efa8f8f99da993175b6c85c246e15dceaed7e307' + + '0f13fbf8a14403420004d6e8328a95fe29afcdc30977b9251efbb219022807f6' + + 'b14bb34695b6b4bdb93ee6684548a4ad13c49d00433c45315e8274f3540f58f5' + + 'd79ef7a1b184f4c21d17', + 'hex', + ), + jwk: { + kty: 'EC', + crv: 'P-256', + x: '1ugyipX-Ka_Nwwl3uSUe-7IZAigH9rFLs0aVtrS9uT4.', + y: '5mhFSKStE8SdAEM8RTFegnTzVA9Y9dee96GxhPTCHRc.', + d: 'K8LtomXkaGbvqPj5namTF1tshcJG4V3OrtfjBw8T-_g.', + }, + }, + }; + + const testVectors: TestVector[] = [ + { + name: 'ECDSA', + privateUsages: ['sign'], + publicUsages: ['verify'], + }, + { + name: 'ECDH', + privateUsages: ['deriveKey', 'deriveBits'], + publicUsages: [], + }, + ]; + + // async function testImportSpki({ name, publicUsages }, namedCurve, extractable) { + // const key = await subtle.importKey( + // 'spki', + // keyData[namedCurve].spki, + // { name, namedCurve }, + // extractable, + // publicUsages); + // expect(key.type, 'public'); + // expect(key.extractable, extractable); + // expect(key.usages).to.have.all.members(publicUsages); + // expect(key.algorithm.name, name); + // expect(key.algorithm.namedCurve, namedCurve); + + // if (extractable) { + // // Test the roundtrip + // const spki = await subtle.exportKey('spki', key); + // expect( + // Buffer.from(spki).toString('hex'), + // keyData[namedCurve].spki.toString('hex')); + // } else { + // await assert.rejects( + // subtle.exportKey('spki', key), { + // message: /key is not extractable/ + // }); + // } + + // // Bad usage + // await assert.rejects( + // subtle.importKey( + // 'spki', + // keyData[namedCurve].spki, + // { name, namedCurve }, + // extractable, + // ['wrapKey']), + // { message: /Unsupported key usage/ }); + // } + + // async function testImportPkcs8( + // { name, privateUsages }, + // namedCurve, + // extractable) { + // const key = await subtle.importKey( + // 'pkcs8', + // keyData[namedCurve].pkcs8, + // { name, namedCurve }, + // extractable, + // privateUsages); + // expect(key.type).to.equal('private'); + // expect(key.extractable.to.equal(extractable); + // expect(key.usages).to.have.all.members(privateUsages); + // expect(key.algorithm.name, name); + // expect(key.algorithm.namedCurve, namedCurve); + + // if (extractable) { + // // Test the roundtrip + // const pkcs8 = await subtle.exportKey('pkcs8', key); + // expect( + // Buffer.from(pkcs8).toString('hex').to.equal( + // keyData[namedCurve].pkcs8.toString('hex')); + // } else { + // await assert.rejects( + // subtle.exportKey('pkcs8', key), { + // message: /key is not extractable/ + // }); + // } + + // await assert.rejects( + // subtle.importKey( + // 'pkcs8', + // keyData[namedCurve].pkcs8, + // { name, namedCurve }, + // extractable, + // [// empty usages ]), + // { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); + // } + + const testImportJwk = async ( + { name, publicUsages, privateUsages }: TestVector, + namedCurve: NamedCurve, + extractable: boolean, + ) => { + const jwk = keyData[namedCurve].jwk; + + const [publicKey, privateKey] = await Promise.all([ + subtle.importKey( + 'jwk', + { + kty: jwk.kty, + crv: jwk.crv, + x: jwk.x, + y: jwk.y, + }, + { name, namedCurve }, + extractable, + publicUsages, + ), + subtle.importKey( + 'jwk', + jwk, + { name, namedCurve }, + extractable, + privateUsages, + ), + subtle.importKey( + 'jwk', + { + alg: name === 'ECDSA' ? keyData[namedCurve].jwsAlg : 'ECDH-ES', + kty: jwk.kty, + crv: jwk.crv, + x: jwk.x, + y: jwk.y, + }, + { name, namedCurve }, + extractable, + publicUsages, + ), + subtle.importKey( + 'jwk', + { + ...jwk, + alg: name === 'ECDSA' ? keyData[namedCurve].jwsAlg : 'ECDH-ES', + }, + { name, namedCurve }, + extractable, + privateUsages, + ), + ]); + + expect(publicKey.type).to.equal('public'); + expect(privateKey.type).to.equal('private'); + expect(publicKey.extractable).to.equal(extractable); + expect(privateKey.extractable).to.equal(extractable); + expect(publicKey.usages).to.have.all.members(publicUsages); + expect(privateKey.usages).to.have.all.members(privateUsages); + expect(publicKey.algorithm.name).to.equal(name); + expect(privateKey.algorithm.name).to.equal(name); + expect(publicKey.algorithm.namedCurve).to.equal(namedCurve); + expect(privateKey.algorithm.namedCurve).to.equal(namedCurve); + + if (extractable) { + // Test the round trip + const [pubJwk, pvtJwk] = await Promise.all([ + subtle.exportKey('jwk', publicKey) as Promise, + subtle.exportKey('jwk', privateKey) as Promise, + ]); + + expect(pubJwk.key_ops).to.have.all.members(publicUsages, 'pub key_ops'); + expect(pubJwk.ext).to.equal(true, 'pub ext'); + expect(pubJwk.kty).to.equal('EC', 'pub kty'); + expect(pubJwk.x).to.equal(jwk.x, 'pub x'); + expect(pubJwk.y).to.equal(jwk.y, 'pub y'); + expect(pubJwk.crv).to.equal(jwk.crv, 'pub crv'); + + expect(pvtJwk.key_ops).to.have.all.members(privateUsages, 'pvt key_ops'); + expect(pvtJwk.ext).to.equal(true, 'pvt ext'); + expect(pvtJwk.kty).to.equal('EC', 'pvt kty'); + expect(pvtJwk.x).to.equal(jwk.x, 'pvt x'); + expect(pvtJwk.y).to.equal(jwk.y, 'pvt y'); + expect(pvtJwk.crv).to.equal(jwk.crv, 'pvt crv'); + expect(pvtJwk.d).to.equal(jwk.d, 'pvt d'); + } else { + await assertThrowsAsync( + async () => await subtle.exportKey('jwk', publicKey), + 'key is not extractable', + ); + await assertThrowsAsync( + async () => await subtle.exportKey('jwk', privateKey), + 'key is not extractable', + ); + } + + { + const invalidUse = name === 'ECDH' ? 'sig' : 'enc'; + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + { ...jwk, use: invalidUse }, + { name, namedCurve }, + extractable, + privateUsages, + ), + 'Invalid JWK "use" Parameter', + ); + } + + if (name === 'ECDSA') { + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + { + kty: jwk.kty, + x: jwk.x, + y: jwk.y, + crv: jwk.crv, + alg: jwk.crv === 'P-256' ? 'ES384' : 'ES256', + }, + { name, namedCurve }, + extractable, + publicUsages, + ), + 'JWK "alg" does not match the requested algorithm', + ); + + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + { ...jwk, alg: jwk.crv === 'P-256' ? 'ES384' : 'ES256' }, + { name, namedCurve }, + extractable, + privateUsages, + ), + 'JWK "alg" does not match the requested algorithm', + ); + } + + for (const crv of [undefined, namedCurve === 'P-256' ? 'P-384' : 'P-256']) { + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + { kty: jwk.kty, x: jwk.x, y: jwk.y, crv }, + { name, namedCurve }, + extractable, + publicUsages, + ), + 'JWK "crv" does not match the requested algorithm', + ); + + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + { ...jwk, crv }, + { name, namedCurve }, + extractable, + privateUsages, + ), + 'JWK "crv" does not match the requested algorithm', + ); + } + + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + { ...jwk }, + { name, namedCurve }, + extractable, + [ + // empty usages + ], + ), + 'Usages cannot be empty when importing a private key.', + ); + }; + + const testImportRaw = async ( + { name, publicUsages }: TestVector, + namedCurve: NamedCurve, + ) => { + const jwk = keyData[namedCurve].jwk; + if (jwk.x === undefined || jwk.y === undefined) { + throw new Error('invalid x, y args'); + } + + const [publicKey] = await Promise.all([ + subtle.importKey( + 'raw', + Buffer.concat([ + Buffer.alloc(1, 0x04), + toByteArray(jwk.x), // base64url? + toByteArray(jwk.y), // base64url? + ]), + { name, namedCurve }, + true, + publicUsages, + ), + subtle.importKey( + 'raw', + Buffer.concat([ + Buffer.alloc(1, 0x03), + toByteArray(jwk.x), // base64url? + ]), + { name, namedCurve }, + true, + publicUsages, + ), + ]); + + expect(publicKey.type).to.equal('public'); + expect(publicKey.usages).to.have.all.members(publicUsages); + expect(publicKey.algorithm.name).to.equal(name); + expect(publicKey.algorithm.namedCurve).to.equal(namedCurve); + }; + + for (const vector of testVectors) { + for (const namedCurve of curves) { + for (const extractable of [true, false]) { + // test(SUITE, `EC spki, ${vector}, ${namedCurve}, ${extractable}`, async () => { + // await testImportSpki(vector, namedCurve, extractable); + // }); + // test(SUITE, `EC pkcs8, ${vector}, ${namedCurve}, ${extractable}`, async () => { + // await testImportPkcs8(vector, namedCurve, extractable); + // }); + test( + SUITE, + `EC jwk, ${vector.name}, ${namedCurve}, ${extractable}`, + async () => { + await testImportJwk(vector, namedCurve, extractable); + }, + ); + } + test(SUITE, `EC raw, ${vector.name}, ${namedCurve}`, async () => { + await testImportRaw(vector, namedCurve); + }); + } + } +} + +// Import/Export HMAC Secret Key +test(SUITE, 'HMAC should import raw HMAC key', async () => { + const keyData = getRandomValues(new Uint8Array(32)); + const key = await subtle.importKey( + 'raw', + keyData, + { + name: 'HMAC', + hash: 'SHA-256', + }, + true, + ['sign', 'verify'], + ); + + assert.strictEqual(key.algorithm, key.algorithm); + assert.strictEqual(key.usages, key.usages); + + const raw = await subtle.exportKey('raw', key); + + assert.instanceOf(raw, ArrayBuffer); + + assert.deepStrictEqual( + Buffer.from(keyData as Uint8Array).toString('hex'), + Buffer.from(raw).toString('hex'), + ); + + const jwk = (await subtle.exportKey('jwk', key)) as JWK; + + assert.property(jwk, 'key_ops'); + assert.property(jwk, 'kty'); + + assert.deepStrictEqual(jwk.key_ops, ['sign', 'verify']); + assert(jwk.ext); + + assert.strictEqual(jwk.kty, 'oct'); + + assert.isDefined(jwk.k); + + assert.deepStrictEqual( + Buffer.from(jwk.k, 'base64').toString('hex'), + Buffer.from(raw).toString('hex'), + ); + + await assertThrowsAsync( + async () => + await subtle.importKey( + 'raw', + keyData, + { + name: 'HMAC', + hash: 'SHA-256', + }, + true, + [ + /* empty usages */ + ], + ), + 'Usages cannot be empty when importing a secret key.', + ); +}); + +test(SUITE, 'HMAC should import JWK HMAC key', async () => { + const jwk: JWK = { + kty: 'oct', + k: 'Y0zt37HgOx-BY7SQjYVmrqhPkO44Ii2Jcb9yydUDPfE', + alg: 'HS256', + ext: true, + key_ops: ['sign', 'verify'], + }; + + const key = await subtle.importKey( + 'jwk', + jwk, + { + name: 'HMAC', + hash: 'SHA-256', + }, + true, + ['sign', 'verify'], + ); + + expect(key.type).to.equal('secret'); + expect(key.extractable).to.equal(true); + expect(key.algorithm.name).to.equal('HMAC'); + expect(key.usages).to.have.members(['sign', 'verify']); +}); + +test(SUITE, 'HMAC should reject invalid key usages', async () => { + const keyData = getRandomValues(new Uint8Array(32)); + + await assertThrowsAsync( + async () => + await subtle.importKey( + 'raw', + keyData, + { + name: 'HMAC', + hash: 'SHA-256', + }, + true, + ['encrypt'], // invalid usage for HMAC + ), + 'Unsupported key usage for an HMAC key', + ); +}); + +test(SUITE, 'HMAC should reject invalid JWK format', async () => { + const invalidJwk: JWK = { + kty: 'RSA', // wrong key type + k: 'Y0zt37HgOx-BY7SQjYVmrqhPkO44Ii2Jcb9yydUDPfE', + }; + + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + invalidJwk, + { + name: 'HMAC', + hash: 'SHA-256', + }, + true, + ['sign', 'verify'], + ), + 'Invalid JWK format for HMAC key', + ); +}); + +test(SUITE, 'HMAC should reject invalid key length', async () => { + const jwk: JWK = { + kty: 'oct', + k: 'Y0zt37HgOx-BY7SQjYVmrqhPkO44Ii2Jcb9yydUDPfE', + alg: 'HS256', + ext: true, + }; + + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + jwk, + { + name: 'HMAC', + hash: 'SHA-256', + length: 128, // Doesn't match the actual key length + }, + true, + ['sign', 'verify'], + ), + 'Invalid key length', + ); +}); + +test(SUITE, 'HMAC should reject invalid zero key length', async () => { + const jwk: JWK = { + kty: 'oct', + k: 'Y0zt37HgOx-BY7SQjYVmrqhPkO44Ii2Jcb9yydUDPfE', + alg: 'HS256', + ext: true, + }; + + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + jwk, + { + name: 'HMAC', + hash: 'SHA-256', + length: 0, // Doesn't match the actual key length + }, + true, + ['sign', 'verify'], + ), + 'Zero-length key is not supported', + ); +}); + +test(SUITE, 'HMAC should reject invalid keyData', async () => { + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + /** + * Force JWT ts validation, it just ensure that if someone use an invalide type then + * we throw an error even if they don't use typescript + */ + null as unknown as JWK, + { + name: 'HMAC', + hash: 'SHA-256', + }, + true, + ['sign', 'verify'], + ), + 'Invalid keyData', + ); +}); + +test(SUITE, 'HMAC should reject unsupported import format', async () => { + const keyData = getRandomValues(new Uint8Array(32)); + + await assertThrowsAsync( + async () => + await subtle.importKey( + 'spki', // unsupported format for HMAC + keyData, + { + name: 'HMAC', + hash: 'SHA-256', + }, + true, + ['sign', 'verify'], + ), + 'Unable to import HMAC key with format spki', + ); +}); + +// Import/Export RSA Key Pairs +// from Node.js https://github.com/nodejs/node/blob/main/test/parallel/test-webcrypto-export-import.js#L157-L215 +test(SUITE, 'RSA spki', async () => { + const generated = await subtle.generateKey( + { + name: 'RSA-PSS', + modulusLength: 1024, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-384', + }, + true, + ['sign', 'verify'], + ); + const { publicKey } = generated as CryptoKeyPair; + + const exported = await subtle.exportKey('spki', publicKey as CryptoKey); + expect(exported !== undefined); + + const imported = await subtle.importKey( + 'spki', + exported, + { + name: 'RSA-PSS', + hash: 'SHA-384', + }, + true, + ['verify'], + ); + expect(imported !== undefined); +}); + +test(SUITE, 'RSA pkcs8', async () => { + const generated = await subtle.generateKey( + { + name: 'RSA-PSS', + modulusLength: 1024, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-384', + }, + true, + ['sign', 'verify'], + ); + const { privateKey } = generated as CryptoKeyPair; + + const exported = await subtle.exportKey('pkcs8', privateKey as CryptoKey); + expect(exported !== undefined); + + // TODO: enable when RSA pkcs8 importKey() is implemented + // const imported = await subtle.importKey( + // 'pkcs8', + // exported, + // { + // name: 'RSA-PSS', + // hash: 'SHA-384', + // }, + // true, + // ['verify'] + // ); + // expect(imported).to.not.be.undefined; +}); + +test(SUITE, 'RSA jwk', async () => { + const generated = await subtle.generateKey( + { + name: 'RSA-PSS', + modulusLength: 1024, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-384', + }, + true, + ['sign', 'verify'], + ); + const { publicKey, privateKey } = generated as CryptoKeyPair; + + const exportedPub = await subtle.exportKey('jwk', publicKey as CryptoKey); + expect(exportedPub !== undefined); + const importedPub = await subtle.importKey( + 'jwk', + exportedPub, + { + name: 'RSA-PSS', + hash: 'SHA-384', + }, + true, + ['verify'], + ); + expect(importedPub !== undefined); + + const exportedPriv = await subtle.exportKey('jwk', privateKey as CryptoKey); + expect(exportedPriv !== undefined); + const importedPriv = await subtle.importKey( + 'jwk', + exportedPriv, + { + name: 'RSA-PSS', + hash: 'SHA-384', + }, + true, + ['sign'], + ); + expect(importedPriv !== undefined); +}); + +// from https://github.com/nodejs/node/blob/main/test/parallel/test-webcrypto-export-import-rsa.js +type HashSize = '1024' | '2048' | '4096'; +const sizes: HashSize[] = ['1024', '2048', '4096']; + +const hashes: HashAlgorithm[] = ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']; + +const keyData = { + '1024': { + spki: Buffer.from( + '30819f300d06092a864886f70d010101050003818d0030818902818100cd99f8b111' + + '9f8d0a2ce7ac8bfd0cb547d348f931cc9c5ca79fde20e51c40eb01ab261e01253df1' + + 'e88f71d086e94b7abe77839103a476bee0cc87c743151afd4431fa5d8fa051271cf5' + + '4e49cf7500d8a9957ec09b9d43ef70098c57f10d03bfd31748af563b881687720d3c' + + '7b10a1cd553ac71d296b6edeeca5b99c8afb36dd970203010001', + 'hex', + ), + pkcs8: Buffer.from( + '30820278020100300d06092a864886f70d0101010500048202623082025e02010002' + + '818100cd99f8b1119f8d0a2ce7ac8bfd0cb547d348f931cc9c5ca79fde20e51c40eb' + + '01ab261e01253df1e88f71d086e94b7abe77839103a476bee0cc87c743151afd4431' + + 'fa5d8fa051271cf54e49cf7500d8a9957ec09b9d43ef70098c57f10d03bfd31748af' + + '563b881687720d3c7b10a1cd553ac71d296b6edeeca5b99c8afb36dd970203010001' + + '02818062a20afc6747f3917e19665d81f826bf5e4d13bf2039a2f9876838bfb0de33' + + 'df890bb0393c748b28d627f3b1c519c0b8befd0f048051b72080fe62497c468658e4' + + '5508e5d206958d7a9318a62a39da7df0e6e8f951912c0676ed65cd04b5685517602e' + + 'a9aed56e22ab59c414120108f15d201390f8b72060f065eff7def97501024100f41a' + + 'c08392f5cdfa863ee5890ee0c2057f939ad65dace23762ce1968dfb230f9538f0592' + + '10f3b4aa77e3119730d958171e024999b55ca3a4f172424298462a79024100d79ee3' + + '0c9d586b99e642f4cf6e12803c078c5a88310b26904e406ba77d2910a77a986481df' + + 'ce61aabe01224f2cddfecc757a4cf944a9699814a13e28ff65448f024100a9d77f41' + + '4cdc681fba8e42a8d5483ed712880200cb16c22325451f5adfe21cbf2d8b62a5d9d3' + + 'a74dc0b2a6079b3e6e534f56ea1cdf9a80660074ae73a57d948902410084d45fc0e4' + + 'a994d7e12efc4b50dedadaa037c989bed4c4b3ff50d640feecae52ce46551c60f86d' + + 'd85666b2711e0dc02aca70463d051c6c6d80bff8601f3d8e67024100cdba49400862' + + '9ebc526d52b1050d846461540f67b75825db009458a64f07550e40039d8e84a4e270' + + 'ec9eda11079eb82914acc2f22ce74ec086dc5324bf0723e1', + 'hex', + ), + jwk: { + kty: 'RSA', + n: + 'zZn4sRGfjQos56yL_Qy1R9NI-THMnFynn94g5RxA6wGrJh4BJT3x6I9x0IbpS3q-d' + + '4ORA6R2vuDMh8dDFRr9RDH6XY-gUScc9U5Jz3UA2KmVfsCbnUPvcAmMV_ENA7_TF0' + + 'ivVjuIFodyDTx7EKHNVTrHHSlrbt7spbmcivs23Zc', + e: 'AQAB', + d: + 'YqIK_GdH85F-GWZdgfgmv15NE78gOaL5h2g4v7DeM9-JC7A5PHSLKNYn87HFGcC4v' + + 'v0PBIBRtyCA_mJJfEaGWORVCOXSBpWNepMYpio52n3w5uj5UZEsBnbtZc0EtWhVF2' + + 'Auqa7VbiKrWcQUEgEI8V0gE5D4tyBg8GXv9975dQE', + p: + '9BrAg5L1zfqGPuWJDuDCBX-TmtZdrOI3Ys4ZaN-yMPlTjwWSEPO0qnfjEZcw2VgXH' + + 'gJJmbVco6TxckJCmEYqeQ', + q: + '157jDJ1Ya5nmQvTPbhKAPAeMWogxCyaQTkBrp30pEKd6mGSB385hqr4BIk8s3f7Md' + + 'XpM-USpaZgUoT4o_2VEjw', + dp: + 'qdd_QUzcaB-6jkKo1Ug-1xKIAgDLFsIjJUUfWt_iHL8ti2Kl2dOnTcCypgebPm5T' + + 'T1bqHN-agGYAdK5zpX2UiQ', + dq: + 'hNRfwOSplNfhLvxLUN7a2qA3yYm-1MSz_1DWQP7srlLORlUcYPht2FZmsnEeDcAq' + + 'ynBGPQUcbG2Av_hgHz2OZw', + qi: + 'zbpJQAhinrxSbVKxBQ2EZGFUD2e3WCXbAJRYpk8HVQ5AA52OhKTicOye2hEHnrgp' + + 'FKzC8iznTsCG3FMkvwcj4Q', + }, + }, + + '2048': { + spki: Buffer.from( + '30820122300d06092a864886f70d01010105000382010f003082010a0282010100d9' + + '8580eb2d1772f4a476bc5404bee60d9a3c2acbbcf24a74754d9f5a6812388f9e3f26' + + '0ad81687ddb366f8da559462b397f1c097896d0df6e6de31c04f8d47cd15600d11be' + + '4ec4e6309e200416257fabba8bbed33ab0c165da3c9b1fcec2c4e9e52aca6359a7cf' + + '54d5275b4486bf01a2b45f04fae20b717d01a794570728815297b2b7f22be00ef302' + + '3813ca87b7e0be8343335cfaf0769e366cf9256cf44239458bb47ebd6b32f0168980' + + '67009273f79d45b85b9f33f57318dfc5af981aa2964834e7f5b33012d369646a6738' + + 'b22bca55e59066f1e69f6a69f1eedecce881b7423fd44dfc7a7c989c426741d8813c' + + '3fcdc024b53d84290a3beda3c83872cafd0203010001', + 'hex', + ), + pkcs8: Buffer.from( + '308204be020100300d06092a864886f70d0101010500048204a8308204a402010002' + + '82010100d98580eb2d1772f4a476bc5404bee60d9a3c2acbbcf24a74754d9f5a6812' + + '388f9e3f260ad81687ddb366f8da559462b397f1c097896d0df6e6de31c04f8d47cd' + + '15600d11be4ec4e6309e200416257fabba8bbed33ab0c165da3c9b1fcec2c4e9e52a' + + 'ca6359a7cf54d5275b4486bf01a2b45f04fae20b717d01a794570728815297b2b7f2' + + '2be00ef3023813ca87b7e0be8343335cfaf0769e366cf9256cf44239458bb47ebd6b' + + '32f016898067009273f79d45b85b9f33f57318dfc5af981aa2964834e7f5b33012d3' + + '69646a6738b22bca55e59066f1e69f6a69f1eedecce881b7423fd44dfc7a7c989c42' + + '6741d8813c3fcdc024b53d84290a3beda3c83872cafd0203010001028201005ad2a7' + + '758aaa53d15a2a49903b3b0a0b7beecb5fae50ec4d9bfd01205a7be129f6451fb93f' + + '6888ea44d225ede3f5c5107fcced41589c344c7731274cc8ea90a44cdc82187a81a1' + + '2d0bf7ba1e7ab0c5920a9df6db739201ee69250d1046e0841fb5141cd546c60e87b9' + + '48698f3f43d986fa11029f4e6ac0c41540c76b5f0dc690d445ffe2bf792e1e67996f' + + 'aba68958e5568e42ee881848f81b2b7465d76327f6d46ff184a907fc1368ace90828' + + 'e3ac2a2f248622d661e4b3d7c104de81a5013bd8ab32116444c7e272af31065f817a' + + 'bdc6981171467968334b12d21bed5d57683140707ac6223dd107067916bf5f97f87c' + + '07578f2d7b168099c582c4f4a4e1f102818100fcdf6d12d3df7c92438ad38e9c9966' + + 'c0c0ec81150e9e1ce40cb845efa5c3d109ecf0583b8f68c7c57c53a8c9a6f99e9c43' + + '9e0f749be053ac70bb01e17ffeafafd6d6246fda556d21e49dc03dc3cf19889af486' + + '451267e1ac8310a846031e0562a22f58bf63f17f5d24044861e307463c8d19964daa' + + 'c956811d603c29e7bec86b02818100dc36288ccc4f0795f128e5ed0d0376ac4c3d89' + + '08fd48df77bd1357c7033dc52d6f123ae079be902e8fe107810a9a188c60f6d4e0e8' + + '90436206bca711e0d7a0b6f984aef9154e8a3bbab8ef0a47922ebdcea5393226f1e6' + + '39a94d4ce5352db85716c25e3044f6abff49c519400d843878f164c5f3ab54f62056' + + '3737d8794034370281806dddbd0c2315c48fdfdc9f5224e3d96b01e73fa62075bde3' + + 'af4b18c7a863cd9cdc5f0856c8562405bfa0b182fb9314c09bf83e8ad176c3a3f64e' + + 'a9e089b5e42b27d25e7e62841f284ca5e5727072b88b4b97d606889aadc84021aa9a' + + 'd09be88714243210e5a1754ec8693bf19babfb6e2f77e07fda2623f97103f0dfdc1a' + + '5e05028181009571bbbb31bc406da5a817c1f41ef19ea46eee5cc76779208d945ef1' + + '94658b36f635ecf702282d392c338f2027cdc3f320aae2756fded79be2ee8c83398f' + + '9c661097d716fb3abddd232ef62a87bfd130c6d8a2244301cf383a8957320610ed15' + + '4d40c32306ea507783dcdaf1f93a4e08e5e979dd8fdcacdbed26b42398c5d5a90281' + + '81009d221bcb65a15be795dfffbab2afa85dc2a3ab65ba5f6e26fa172612d5572129' + + 'bb120015ca4446ec3fdb9ec980a661d2aad23850511898f07c148716095cd1bd60d6' + + '31464ac89b524660bd465952d2e57d8740b7c3f3db79492b16b87a5cd1767e13526e' + + 'f66d79c691e2c7f2528b69652c29ba210a5e679d23b21a680cbf0d07', + 'hex', + ), + jwk: { + kty: 'RSA', + n: + '2YWA6y0XcvSkdrxUBL7mDZo8Ksu88kp0dU2fWmgSOI-ePyYK2BaH3bNm-NpVlGKzl' + + '_HAl4ltDfbm3jHAT41HzRVgDRG-TsTmMJ4gBBYlf6u6i77TOrDBZdo8mx_OwsTp5S' + + 'rKY1mnz1TVJ1tEhr8BorRfBPriC3F9AaeUVwcogVKXsrfyK-AO8wI4E8qHt-C-g0M' + + 'zXPrwdp42bPklbPRCOUWLtH69azLwFomAZwCSc_edRbhbnzP1cxjfxa-YGqKWSDTn' + + '9bMwEtNpZGpnOLIrylXlkGbx5p9qafHu3szogbdCP9RN_Hp8mJxCZ0HYgTw_zcAkt' + + 'T2EKQo77aPIOHLK_Q', + e: 'AQAB', + d: + 'WtKndYqqU9FaKkmQOzsKC3vuy1-uUOxNm_0BIFp74Sn2RR-5P2iI6kTSJe3j9cUQf' + + '8ztQVicNEx3MSdMyOqQpEzcghh6gaEtC_e6HnqwxZIKnfbbc5IB7mklDRBG4IQftR' + + 'Qc1UbGDoe5SGmPP0PZhvoRAp9OasDEFUDHa18NxpDURf_iv3kuHmeZb6umiVjlVo5' + + 'C7ogYSPgbK3Rl12Mn9tRv8YSpB_wTaKzpCCjjrCovJIYi1mHks9fBBN6BpQE72Ksy' + + 'EWREx-JyrzEGX4F6vcaYEXFGeWgzSxLSG-1dV2gxQHB6xiI90QcGeRa_X5f4fAdXj' + + 'y17FoCZxYLE9KTh8Q', + p: + '_N9tEtPffJJDitOOnJlmwMDsgRUOnhzkDLhF76XD0Qns8Fg7j2jHxXxTqMmm-Z6cQ' + + '54PdJvgU6xwuwHhf_6vr9bWJG_aVW0h5J3APcPPGYia9IZFEmfhrIMQqEYDHgVioi' + + '9Yv2Pxf10kBEhh4wdGPI0Zlk2qyVaBHWA8Kee-yGs', + q: + '3DYojMxPB5XxKOXtDQN2rEw9iQj9SN93vRNXxwM9xS1vEjrgeb6QLo_hB4EKmhiMY' + + 'PbU4OiQQ2IGvKcR4NegtvmErvkVToo7urjvCkeSLr3OpTkyJvHmOalNTOU1LbhXFs' + + 'JeMET2q_9JxRlADYQ4ePFkxfOrVPYgVjc32HlANDc', + dp: + 'bd29DCMVxI_f3J9SJOPZawHnP6Ygdb3jr0sYx6hjzZzcXwhWyFYkBb-gsYL7kxTA' + + 'm_g-itF2w6P2TqngibXkKyfSXn5ihB8oTKXlcnByuItLl9YGiJqtyEAhqprQm-iH' + + 'FCQyEOWhdU7IaTvxm6v7bi934H_aJiP5cQPw39waXgU', + dq: + 'lXG7uzG8QG2lqBfB9B7xnqRu7lzHZ3kgjZRe8ZRlizb2Nez3AigtOSwzjyAnzcPz' + + 'IKridW_e15vi7oyDOY-cZhCX1xb7Or3dIy72Koe_0TDG2KIkQwHPODqJVzIGEO0V' + + 'TUDDIwbqUHeD3Nrx-TpOCOXped2P3Kzb7Sa0I5jF1ak', + qi: + 'nSIby2WhW-eV3_-6sq-oXcKjq2W6X24m-hcmEtVXISm7EgAVykRG7D_bnsmApmHS' + + 'qtI4UFEYmPB8FIcWCVzRvWDWMUZKyJtSRmC9RllS0uV9h0C3w_PbeUkrFrh6XNF2' + + 'fhNSbvZtecaR4sfyUotpZSwpuiEKXmedI7IaaAy_DQc', + }, + }, + + '4096': { + spki: Buffer.from( + '30820222300d06092a864886f70d01010105000382020f003082020a0282020100da' + + 'aaf64cbd9cd8999bb0dd0e2c846768007f64a6f5f8687d1f4a9be25ac1b836aa916f' + + 'de14fc13f8922cbe7349bc34fb04b279eed4cc223e7a64cb6fe9e7d249359293d30e' + + 'a16d89d4afe212b7ad67671e801fda457eea4158e7a05b33f54d3604a7c02144f4a3' + + 'f2bb6fd1b4f1dd6bac0528862fd255087039ba1d83b05d74c6ca526cfbd103484b8f' + + '3b2cde385945679fd3a013d6ad4d850044dba44f40ee41bdc9f8adb492c4ee56e8d7' + + '6d27a5a210e62e86ea946a22e6c63fe78f10b3d06d1664369c6b841cd076cdd959e4' + + '4bc4a9b505559d906e81ba8d7768a2ceaa73076052f0218f51f3d7436089cfd116a2' + + 'fb6cd0e820eccda7aea1740df9bb16f0b9aca0675ea2931a0f8fb79362e77586b932' + + '40281e1b0d9884288a204e9ea2cfd4e5d2fb587443e5a4a4933b205ed9c5f295664a' + + 'db2e7f441c740a02f9e7827b1d2d493811c3d02d193cfc62bd6d1900fd97fe7cd330' + + '179c4ea39abc11450ebc10403bbe8846a2fded9c6f291b283fcdcc5e0032ed3e57d3' + + '735b44c26877486ae2a030a58a86028a99b526f93078480ff5e30fa440bc4a0454d5' + + '53434957b5485e2e36c1fcbc0ecf1c529f83a8eea8911ce61b7e975d0560447e42ae' + + '9b657b14da835c7c4e522c378b4d69b18879b12b4d0cf0004c14857981490fa0c896' + + '725f3b3ba5f0cc0d9c86c204469ed56fe567d8ef8410b897cefee53e173a7d3190d0' + + 'd70203010001', + 'hex', + ), + pkcs8: Buffer.from( + '30820944020100300d06092a864886f70d01010105000482092e3082092a02010002' + + '82020100daaaf64cbd9cd8999bb0dd0e2c846768007f64a6f5f8687d1f4a9be25ac1' + + 'b836aa916fde14fc13f8922cbe7349bc34fb04b279eed4cc223e7a64cb6fe9e7d249' + + '359293d30ea16d89d4afe212b7ad67671e801fda457eea4158e7a05b33f54d3604a7' + + 'c02144f4a3f2bb6fd1b4f1dd6bac0528862fd255087039ba1d83b05d74c6ca526cfb' + + 'd103484b8f3b2cde385945679fd3a013d6ad4d850044dba44f40ee41bdc9f8adb492' + + 'c4ee56e8d76d27a5a210e62e86ea946a22e6c63fe78f10b3d06d1664369c6b841cd0' + + '76cdd959e44bc4a9b505559d906e81ba8d7768a2ceaa73076052f0218f51f3d74360' + + '89cfd116a2fb6cd0e820eccda7aea1740df9bb16f0b9aca0675ea2931a0f8fb79362' + + 'e77586b93240281e1b0d9884288a204e9ea2cfd4e5d2fb587443e5a4a4933b205ed9' + + 'c5f295664adb2e7f441c740a02f9e7827b1d2d493811c3d02d193cfc62bd6d1900fd' + + '97fe7cd330179c4ea39abc11450ebc10403bbe8846a2fded9c6f291b283fcdcc5e00' + + '32ed3e57d3735b44c26877486ae2a030a58a86028a99b526f93078480ff5e30fa440' + + 'bc4a0454d553434957b5485e2e36c1fcbc0ecf1c529f83a8eea8911ce61b7e975d05' + + '60447e42ae9b657b14da835c7c4e522c378b4d69b18879b12b4d0cf0004c14857981' + + '490fa0c896725f3b3ba5f0cc0d9c86c204469ed56fe567d8ef8410b897cefee53e17' + + '3a7d3190d0d702030100010282020100b973d15c185c139f8359a6c144a42e871814' + + 'f32a5ee604c849679f7983fb53de991eabbfb010726798a1760c94f69800646571e0' + + '4a7dae754a9c7da536bdb3acff50872ab2f7d9ccd1a3319b2a4858b02e3fffc3c0b8' + + 'f8b7df4ce2c536f5ce3c080ab57a01df71c4858f3a4db9eb4e4c203bd4426ea24b7b' + + 'd299b43a61b3813caf8ee47b5532f17793cc5e2b41a304a7f3f7298669c5a53f2d91' + + '38aecbc087d11dc353b30eb883689830f5b3cfb23c17150154cf527c0989ab8dbb37' + + 'acb4b40a30b9614f9c27f9c01b624dfa5d129d8248d2736024847465e160ea4f59f3' + + '598761fc35486122e229292d90f3bda2f32b45888fb68cdf865d26f5247d2e5d305e' + + 'd7279c39565dcfcc486a70d7cbe6501489e0f22192216cbcb9fe75bdf052403cbaf7' + + 'be8aaa9f934b319465ae8215b1d379069990e6a6b59b5ee8020477ec2385fddf0e1e' + + 'c739d71ffb5aa713e79a36e1554411ea9e3532f3b695c1d63cbc062602c8a1e8c11e' + + '99e7dd398c374523159922eeaf41fdd2777d7874997f43cc0942d2c8a5d4d8023e13' + + '0fab4db7f77fe08a29d0aae3249eb06f80ac4649f194ac32ae7e50b1eb5d5966544c' + + 'dd1ed8317d8e232d60e03ca13f30558f144cb66f0f9c8b379b71e2f8ef82fcf1c5f7' + + '7c3d27c5aa774c88c3b4a96af0ea6572cf0ba0aa8bc2bb3016725440971ed463d5b0' + + '6a4fe87fc599850838d253436a7ce76002910282010100f4dad7c2ae2463d90104ec' + + '0ba0565541ce24248fcd6ca6bf5bd14b75075121b32c6591d72775c3511f6f24071a' + + '691ef95b0202ed7e8de799d5b564eadbc072b3d7e527d46b0937dc88e9ed1c4a6106' + + '161a2f9653525fba921626b0e7ffa6c7dfd9568e382bc719f7f97a3b8e981431930d' + + '84f9cbfb9274605851e82d6a64bb634920cb861edf64b3b38051f21955897d6099f0' + + 'e05614ce181ac5e9a49e32de67c5d39065b6cdc93317e77de5823d8bccc3f34526b9' + + 'bb30f98c6b8927ea150d2b18706c6d0f1939377f2898eee360569d72233436268c55' + + '2a7735632385d0f041ab0847fff3f8b0a611b25c3ecb389e1fa9df7b0776d8a68453' + + '3e70a063f4841d0282010100e49ef9f3f35e2abd573d988bc57a216104278742dbe1' + + '0b46675c730a08e10502dc201793386fed6230ae7acf6d98bb7ddcba497f2a5227e4' + + 'a30cbc24476b34ebdfc8072606a71c9e1ad57eba5a98852c359c3d825ca3031b23b9' + + '8d70ecf6d26b4bf5217e86d72901f4dc245d16e8323e448d99763e01a7c5ca71bbc4' + + 'bafba18042d391678545cf9b75414cfb7d2be069ab061dfe1f6f90059ea6b48fa3cd' + + 'd497070b32ea52258f4b687c6145dcf6ca2d1928dc175c747072ccc68c306fbf351c' + + '0986ea5aa8f36c4bc563a2ad1fc261e0b84ce3aac76a810e4deae726c0c5e9ae96f0' + + '37fcf11b61a931317309da41fd0efdd95b8d2c4420f7dbc71f2dd4442e8302820101' + + '00e18ec7bb9b580272e1317b90aa3f5d82a5373e470a61d0a9ef173a7fb021d8fd89' + + '2477d8cf8cf8443ec4cf578bc8d2b3ba567c03f3d51d48e549989191a61304011a24' + + '3ad5ef43fa7055ae0ba5a9034651110d55ec482b42700d6c620b6bc42c3db6328524' + + '2ee18941d48c10ab9fce9b3c9506d81603b01920c33332c313d05b81fe27fe816a21' + + '06399137ebe1d29e395547fa516e7af3efd89a00c598c61b835505b3bb3f4f0acd7a' + + '73d1d21ecc3b8081f213fdbc92e866ba2845ccf32239633dbc32e5b446f4225f8d32' + + '74be18fd3144f7911d611d5d47255194e6205b7d37c12a7bc919223af880cce19526' + + 'f81d11e616eceacf5c7ce8e116600220921b310282010100813e223db7f21f2544c1' + + '6c906f85f882b8ef83b6d748a4b01b549730300ecd5f6d83b2f0263298372f20240b' + + '4980d35576c7d52ecf84fc4a73a68a61d402163bd619657928bfa61cf73c8454e34c' + + '5fd4bb45e53be214c177c13d6f694c7cc83da20624f63b523d3b7eea48a05b87ce87' + + '8707a99ebfb4fddc81f2c3dc967c1433c713859ac92bcb0eae3dc9404ee5d40ac885' + + '3fc55e8e1a14233948cfff2128326ce7f6d3a2b6db081d3c5b5d3c6a43a73516f53d' + + '3ba613bfc265e7f0a5eba9217d7d48d511b7f31beeadc1d42f251b6207ae67f22ea3' + + 'd5eb793ef787dfe8c28f5182e193dbd5c7e2f70d6664467f9188bd16f87b996fb657' + + '88664c09037bbbf30282010024799529bd73c16e62451e9109e7b16278767e663edc' + + '3acf49d33c0f186bd05f1d6b28beb6546a11d9c6d21be9e399fc80b52c91659c07d1' + + '1795424e6d918a0df1aec6031ade0ff178b036be6150d763313ecc87e2208d66fb20' + + '986c71ed3b8e1eb9c3879101567338fdd7baddcac424e376b1823c3b38bec69d8e12' + + '602bdac7962aae2cc641678ba7b12e1a9bf8d1389bd1cc2a59e0d44b50876acb0451' + + 'b55580f749862930b7397f1cea1af4b19f715af97820f8864f637b9badc9b9d8a620' + + '98b5069a7612b5f56a1925927610d71e5360239a5d000d05ce9c81937657f89b3187' + + '07279de2ab6010707aad3a9113065a0bdd6dd010fbbc12786aaa8f954fc0', + 'hex', + ), + jwk: { + kty: 'RSA', + n: + '2qr2TL2c2JmbsN0OLIRnaAB_ZKb1-Gh9H0qb4lrBuDaqkW_eFPwT-JIsvnNJvDT7B' + + 'LJ57tTMIj56ZMtv6efSSTWSk9MOoW2J1K_iEretZ2cegB_aRX7qQVjnoFsz9U02BK' + + 'fAIUT0o_K7b9G08d1rrAUohi_SVQhwObodg7BddMbKUmz70QNIS487LN44WUVnn9O' + + 'gE9atTYUARNukT0DuQb3J-K20ksTuVujXbSelohDmLobqlGoi5sY_548Qs9BtFmQ2' + + 'nGuEHNB2zdlZ5EvEqbUFVZ2QboG6jXdoos6qcwdgUvAhj1Hz10Ngic_RFqL7bNDoI' + + 'OzNp66hdA35uxbwuaygZ16ikxoPj7eTYud1hrkyQCgeGw2YhCiKIE6eos_U5dL7WH' + + 'RD5aSkkzsgXtnF8pVmStsuf0QcdAoC-eeCex0tSTgRw9AtGTz8Yr1tGQD9l_580zA' + + 'XnE6jmrwRRQ68EEA7vohGov3tnG8pGyg_zcxeADLtPlfTc1tEwmh3SGrioDClioYC' + + 'ipm1JvkweEgP9eMPpEC8SgRU1VNDSVe1SF4uNsH8vA7PHFKfg6juqJEc5ht-l10FY' + + 'ER-Qq6bZXsU2oNcfE5SLDeLTWmxiHmxK00M8ABMFIV5gUkPoMiWcl87O6XwzA2chs' + + 'IERp7Vb-Vn2O-EELiXzv7lPhc6fTGQ0Nc', + e: 'AQAB', + d: + 'uXPRXBhcE5-DWabBRKQuhxgU8ype5gTISWefeYP7U96ZHqu_sBByZ5ihdgyU9pgAZ' + + 'GVx4Ep9rnVKnH2lNr2zrP9Qhyqy99nM0aMxmypIWLAuP__DwLj4t99M4sU29c48CA' + + 'q1egHfccSFjzpNuetOTCA71EJuokt70pm0OmGzgTyvjuR7VTLxd5PMXitBowSn8_c' + + 'phmnFpT8tkTiuy8CH0R3DU7MOuINomDD1s8-yPBcVAVTPUnwJiauNuzestLQKMLlh' + + 'T5wn-cAbYk36XRKdgkjSc2AkhHRl4WDqT1nzWYdh_DVIYSLiKSktkPO9ovMrRYiPt' + + 'ozfhl0m9SR9Ll0wXtcnnDlWXc_MSGpw18vmUBSJ4PIhkiFsvLn-db3wUkA8uve-iq' + + 'qfk0sxlGWughWx03kGmZDmprWbXugCBHfsI4X93w4exznXH_tapxPnmjbhVUQR6p4' + + '1MvO2lcHWPLwGJgLIoejBHpnn3TmMN0UjFZki7q9B_dJ3fXh0mX9DzAlC0sil1NgC' + + 'PhMPq02393_giinQquMknrBvgKxGSfGUrDKuflCx611ZZlRM3R7YMX2OIy1g4DyhP' + + 'zBVjxRMtm8PnIs3m3Hi-O-C_PHF93w9J8Wqd0yIw7SpavDqZXLPC6Cqi8K7MBZyVE' + + 'CXHtRj1bBqT-h_xZmFCDjSU0NqfOdgApE', + p: + '9NrXwq4kY9kBBOwLoFZVQc4kJI_NbKa_W9FLdQdRIbMsZZHXJ3XDUR9vJAcaaR75W' + + 'wIC7X6N55nVtWTq28Bys9flJ9RrCTfciOntHEphBhYaL5ZTUl-6khYmsOf_psff2V' + + 'aOOCvHGff5ejuOmBQxkw2E-cv7knRgWFHoLWpku2NJIMuGHt9ks7OAUfIZVYl9YJn' + + 'w4FYUzhgaxemknjLeZ8XTkGW2zckzF-d95YI9i8zD80Umubsw-YxriSfqFQ0rGHBs' + + 'bQ8ZOTd_KJju42BWnXIjNDYmjFUqdzVjI4XQ8EGrCEf_8_iwphGyXD7LOJ4fqd97B' + + '3bYpoRTPnCgY_SEHQ', + q: + '5J758_NeKr1XPZiLxXohYQQnh0Lb4QtGZ1xzCgjhBQLcIBeTOG_tYjCues9tmLt93' + + 'LpJfypSJ-SjDLwkR2s069_IByYGpxyeGtV-ulqYhSw1nD2CXKMDGyO5jXDs9tJrS_' + + 'UhfobXKQH03CRdFugyPkSNmXY-AafFynG7xLr7oYBC05FnhUXPm3VBTPt9K-BpqwY' + + 'd_h9vkAWeprSPo83UlwcLMupSJY9LaHxhRdz2yi0ZKNwXXHRwcszGjDBvvzUcCYbq' + + 'WqjzbEvFY6KtH8Jh4LhM46rHaoEOTernJsDF6a6W8Df88RthqTExcwnaQf0O_dlbj' + + 'SxEIPfbxx8t1EQugw', + dp: + '4Y7Hu5tYAnLhMXuQqj9dgqU3PkcKYdCp7xc6f7Ah2P2JJHfYz4z4RD7Ez1eLyNKz' + + 'ulZ8A_PVHUjlSZiRkaYTBAEaJDrV70P6cFWuC6WpA0ZREQ1V7EgrQnANbGILa8Qs' + + 'PbYyhSQu4YlB1IwQq5_OmzyVBtgWA7AZIMMzMsMT0FuB_if-gWohBjmRN-vh0p45' + + 'VUf6UW568-_YmgDFmMYbg1UFs7s_TwrNenPR0h7MO4CB8hP9vJLoZrooRczzIjlj' + + 'Pbwy5bRG9CJfjTJ0vhj9MUT3kR1hHV1HJVGU5iBbfTfBKnvJGSI6-IDM4ZUm-B0R' + + '5hbs6s9cfOjhFmACIJIbMQ', + dq: + 'gT4iPbfyHyVEwWyQb4X4grjvg7bXSKSwG1SXMDAOzV9tg7LwJjKYNy8gJAtJgNNV' + + 'dsfVLs-E_Epzpoph1AIWO9YZZXkov6Yc9zyEVONMX9S7ReU74hTBd8E9b2lMfMg9' + + 'ogYk9jtSPTt-6kigW4fOh4cHqZ6_tP3cgfLD3JZ8FDPHE4WaySvLDq49yUBO5dQK' + + 'yIU_xV6OGhQjOUjP_yEoMmzn9tOittsIHTxbXTxqQ6c1FvU9O6YTv8Jl5_Cl66kh' + + 'fX1I1RG38xvurcHULyUbYgeuZ_Iuo9XreT73h9_owo9RguGT29XH4vcNZmRGf5GI' + + 'vRb4e5lvtleIZkwJA3u78w', + qi: + 'JHmVKb1zwW5iRR6RCeexYnh2fmY-3DrPSdM8Dxhr0F8dayi-tlRqEdnG0hvp45n8' + + 'gLUskWWcB9EXlUJObZGKDfGuxgMa3g_xeLA2vmFQ12MxPsyH4iCNZvsgmGxx7TuO' + + 'HrnDh5EBVnM4_de63crEJON2sYI8Ozi-xp2OEmAr2seWKq4sxkFni6exLhqb-NE4' + + 'm9HMKlng1EtQh2rLBFG1VYD3SYYpMLc5fxzqGvSxn3Fa-Xgg-IZPY3ubrcm52KYg' + + 'mLUGmnYStfVqGSWSdhDXHlNgI5pdAA0FzpyBk3ZX-JsxhwcnneKrYBBweq06kRMG' + + 'WgvdbdAQ-7wSeGqqj5VPwA', + }, + }, +}; + +async function testImportSpki( + { name, publicUsages }: TestVector, + size: HashSize, + hash: HashAlgorithm, + extractable: boolean, +) { + const key = await subtle.importKey( + 'spki', + keyData[size].spki, + { name, hash }, + extractable, + publicUsages, + ); + + expect(key.type).to.equal('public'); + expect(key.extractable).to.equal(extractable); + expect(key.usages).to.deep.equal(publicUsages); + expect(key.algorithm.name).to.equal(name); + expect(key.algorithm.modulusLength).to.equal(parseInt(size, 10)); + expect(key.algorithm.publicExponent).to.deep.equal(new Uint8Array([1, 0, 1])); + expect(key.algorithm.hash).to.equal(hash); + + if (extractable) { + const spki = await subtle.exportKey('spki', key); + expect(Buffer.from(spki as ArrayBuffer).toString('hex')).to.equal( + keyData[size].spki.toString('hex'), + ); + } else { + await assertThrowsAsync( + async () => await subtle.exportKey('spki', key), + 'key is not extractable', + ); + } +} +/* +async function testImportPkcs8( + { name, privateUsages }: TestVector, + size: HashSize, + hash: HashAlgorithm, + extractable: boolean +) { + const key = await subtle.importKey( + 'pkcs8', + keyData[size].pkcs8, + { name, hash }, + extractable, + privateUsages + ); + + expect(key.type).to.equal('private'); + expect(key.extractable).to.equal(extractable); + expect(key.usages).to.deep.equal(privateUsages); + expect(key.algorithm.name).to.equal(name); + expect(key.algorithm.modulusLength).to.equal(parseInt(size, 10)); + expect(key.algorithm.publicExponent).to.deep.equal( + new Uint8Array([1, 0, 1]) + ); + expect(key.algorithm.hash).to.equal(hash); + + if (extractable) { + const pkcs8 = await subtle.exportKey('pkcs8', key); + expect(Buffer.from(pkcs8 as ArrayBuffer).toString('hex')).to.equal( + keyData[size].pkcs8.toString('hex') + ); + } else { + await assertThrowsAsync( + async () => await subtle.exportKey('pkcs8', key), + 'key is not extractable' + ); + } + + await assertThrowsAsync( + async () => + await subtle.importKey( + 'pkcs8', + keyData[size].pkcs8, + { name, hash } as SubtleAlgorithm, + extractable, + [ + // empty usages + ] + ), + 'Usages cannot be empty when importing a private key.' + ); +} +*/ + +/* +async function testImportJwk( + { name, publicUsages, privateUsages }: TestVector, + size: HashSize, + hash: HashAlgorithm, + extractable: boolean +) { + const jwk = keyData[size].jwk; + + const [publicKey, privateKey] = await Promise.all([ + subtle.importKey( + 'jwk', + { + kty: jwk.kty, + n: jwk.n, + e: jwk.e, + alg: `PS${hash.substring(4)}`, + }, + { name, hash }, + extractable, + publicUsages + ), + subtle.importKey( + 'jwk', + { ...jwk, alg: `PS${hash.substring(4)}` }, + { name, hash }, + extractable, + privateUsages + ), + ]); + + expect(publicKey.type).to.equal('public'); + expect(privateKey.type).to.equal('private'); + expect(publicKey.extractable).to.equal(extractable); + expect(privateKey.extractable).to.equal(extractable); + expect(publicKey.algorithm.name).to.equal(name); + expect(privateKey.algorithm.name).to.equal(name); + expect(publicKey.algorithm.modulusLength).to.equal(parseInt(size, 10)); + expect(privateKey.algorithm.modulusLength).to.equal(parseInt(size, 10)); + expect(publicKey.algorithm.publicExponent).to.deep.equal( + new Uint8Array([1, 0, 1]) + ); + expect(privateKey.algorithm.publicExponent).to.deep.equal( + privateKey.algorithm.publicExponent + ); + + if (extractable) { + const [pubJwk, pvtJwk] = await Promise.all([ + subtle.exportKey('jwk', publicKey) as Promise, + subtle.exportKey('jwk', privateKey) as Promise, + ]); + + expect(pubJwk.kty).to.equal('RSA'); + expect(pvtJwk.kty).to.equal('RSA'); + expect(pubJwk.n).to.equal(jwk.n); + expect(pvtJwk.n).to.equal(jwk.n); + expect(pubJwk.e).to.equal(jwk.e); + expect(pvtJwk.e).to.equal(jwk.e); + expect(pvtJwk.d).to.equal(jwk.d); + expect(pvtJwk.p).to.equal(jwk.p); + expect(pvtJwk.q).to.equal(jwk.q); + expect(pvtJwk.dp).to.equal(jwk.dp); + expect(pvtJwk.dq).to.equal(jwk.dq); + expect(pvtJwk.qi).to.equal(jwk.qi); + expect(pubJwk.d).to.equal(undefined); + expect(pubJwk.p).to.equal(undefined); + expect(pubJwk.q).to.equal(undefined); + expect(pubJwk.dp).to.equal(undefined); + expect(pubJwk.dq).to.equal(undefined); + expect(pubJwk.qi).to.equal(undefined); + } else { + await assertThrowsAsync( + () => async () => await subtle.exportKey('jwk', publicKey), + 'key is not extractable' + ); + await assertThrowsAsync( + () => async () => await subtle.exportKey('jwk', privateKey), + 'key is not extractable' + ); + } + + { + const invalidUse = name === 'RSA-OAEP' ? 'sig' : 'enc'; + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + // @ ts-expect-error + { kty: jwk.kty, n: jwk.n, e: jwk.e, use: invalidUse }, + { name, hash } as SubtleAlgorithm, + extractable, + publicUsages + ), + 'Invalid JWK "use" Parameter' + ); + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + // @ ts-expect-error + { ...jwk, use: invalidUse }, + { name, hash } as SubtleAlgorithm, + extractable, + privateUsages + ), + 'Invalid JWK "use" Parameter' + ); + } + + { + let invalidAlg = + name === 'RSA-OAEP' ? name : name === 'RSA-PSS' ? 'PS' : 'RS'; + switch (name) { + case 'RSA-OAEP': + if (hash === 'SHA-1') { + invalidAlg += '-256'; + } + break; + default: + if (hash === 'SHA-256') { + invalidAlg += '384'; + } else { + invalidAlg += '256'; + } + } + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + { kty: jwk.kty, n: jwk.n, e: jwk.e, alg: invalidAlg }, + { name, hash } as SubtleAlgorithm, + extractable, + publicUsages + ), + 'JWK "alg" does not match the requested algorithm' + ); + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + { ...jwk, alg: invalidAlg }, + { name, hash } as SubtleAlgorithm, + extractable, + privateUsages + ), + 'JWK "alg" does not match the requested algorithm' + ); + } + + await assertThrowsAsync( + async () => + await subtle.importKey( + 'jwk', + { ...jwk }, + { name, hash } as SubtleAlgorithm, + extractable, + [ + // empty usages + ] + ), + 'Usages cannot be empty when importing a private key.' + ); +} +*/ + +// combinations to test +type TestVector = { + name: RSAKeyPairAlgorithm; + privateUsages: KeyUsage[]; + publicUsages: KeyUsage[]; +}; +const testVectors: TestVector[] = [ + { + name: 'RSA-OAEP', + privateUsages: ['decrypt', 'unwrapKey'], + publicUsages: ['encrypt', 'wrapKey'], + }, + { + name: 'RSA-PSS', + privateUsages: ['sign'], + publicUsages: ['verify'], + }, + { + name: 'RSASSA-PKCS1-v1_5', + privateUsages: ['sign'], + publicUsages: ['verify'], + }, +]; + +sizes.forEach(size => { + hashes.forEach(hash => { + [true, false].forEach(extractable => { + testVectors.forEach(vector => { + test( + SUITE, + `rsa importKey spki ${vector.name} ${size} ${hash} ${extractable}`, + async () => { + await testImportSpki(vector, size, hash, extractable); + }, + ); + // test(SUITE, `rsa importKey pkcs8 ${vector.name} ${size} ${hash} ${extractable}`, async () => { + // await testImportPkcs8(vector, size, hash, extractable); + // }); + // test(SUITE, `rsa importKey jwk ${vector.name} ${size} ${hash} ${extractable}`, async () => { + // await testImportJwk(vector, size, hash, extractable); + // }); + }); + }); + }); +}); + +// TODO: re-enable after createPublicKey/createPrivateKey are ported from 0.x +// { +// const ecPublic = createPublicKey(pubTestKeyEc256); +// const ecPrivate = createPrivateKey(privTestKeyEc256); + +// const badUsages: Record = { +// 'RSA-PSS': ['verify', 'sign'], +// 'RSASSA-PKCS1-v1_5': ['verify', 'sign'], +// 'RSA-OAEP': ['encrypt', 'decrypt'], +// }; +// for (const [name, [publicUsage, privateUsage]] of Object.entries(badUsages)) { +// test( +// SUITE, +// `bad usages ${name} ${publicUsage} ${privateUsage}`, +// async () => { +// await assertThrowsAsync( +// async () => +// await subtle.importKey( +// 'spki', +// ecPublic.export({ format: 'der', type: 'spki' }), +// { name, hash: 'SHA-256' } as SubtleAlgorithm, +// true, +// [publicUsage], +// ), +// 'Invalid key type', +// ); + +// await assertThrowsAsync( +// async () => +// await subtle.importKey( +// 'pkcs8', +// ecPrivate.export({ format: 'der', type: 'pkcs8' }), +// { name, hash: 'SHA-256' } as SubtleAlgorithm, +// true, +// [privateUsage], +// ), +// 'Unable to import RSA key with format pkcs8', +// ); +// }, +// ); +// } +// } diff --git a/example/src/tests/subtle/sign_verify.ts b/example/src/tests/subtle/sign_verify.ts new file mode 100644 index 00000000..0c1ec953 --- /dev/null +++ b/example/src/tests/subtle/sign_verify.ts @@ -0,0 +1,171 @@ +import { normalizeHashName, subtle } from 'react-native-quick-crypto'; +import type { CryptoKey, CryptoKeyPair } from 'react-native-quick-crypto'; +import { test } from '../util'; +import { expect } from 'chai'; + +const encoder = new TextEncoder(); + +// // Test Sign/Verify RSASSA-PKCS1-v1_5 +// { +// async function test(data) { +// const ec = new TextEncoder(); +// const { publicKey, privateKey } = await subtle.generateKey({ +// name: 'RSASSA-PKCS1-v1_5', +// modulusLength: 1024, +// publicExponent: new Uint8Array([1, 0, 1]), +// hash: 'SHA-256' +// }, true, ['sign', 'verify']); + +// const signature = await subtle.sign({ +// name: 'RSASSA-PKCS1-v1_5' +// }, privateKey, ec.encode(data)); + +// assert(await subtle.verify({ +// name: 'RSASSA-PKCS1-v1_5' +// }, publicKey, signature, ec.encode(data))); +// } + +// test('hello world').then(common.mustCall()); +// } + +// // Test Sign/Verify RSA-PSS +// { +// async function test(data) { +// const ec = new TextEncoder(); +// const { publicKey, privateKey } = await subtle.generateKey({ +// name: 'RSA-PSS', +// modulusLength: 4096, +// publicExponent: new Uint8Array([1, 0, 1]), +// hash: 'SHA-256' +// }, true, ['sign', 'verify']); + +// const signature = await subtle.sign({ +// name: 'RSA-PSS', +// saltLength: 256, +// }, privateKey, ec.encode(data)); + +// assert(await subtle.verify({ +// name: 'RSA-PSS', +// saltLength: 256, +// }, publicKey, signature, ec.encode(data))); +// } + +// test('hello world').then(common.mustCall()); +// } + +test('subtle.sign_verify', 'ECDSA P-384', async () => { + const pair = await subtle.generateKey( + { name: 'ECDSA', namedCurve: 'P-384' }, + true, + ['sign', 'verify'], + ); + const { publicKey, privateKey } = pair as CryptoKeyPair; + + const data = 'hello world'; + const signature = await subtle.sign( + { name: 'ECDSA', hash: 'SHA-384' }, + privateKey as CryptoKey, + encoder.encode(data), + ); + + expect( + await subtle.verify( + { name: 'ECDSA', hash: 'SHA-384' }, + publicKey as CryptoKey, + signature, + encoder.encode(data), + ), + ).to.equal(true); +}); + +test('subtle.sign_verify', 'ECDSA with HashAlgorithmIdentifier', async () => { + const pair = await subtle.generateKey( + { name: 'ECDSA', namedCurve: 'P-256' }, + true, + ['sign', 'verify'], + ); + const { publicKey, privateKey } = pair as CryptoKeyPair; + const data = 'hello world'; + const signature = await subtle.sign( + { name: 'ECDSA', hash: normalizeHashName('SHA-256') }, + privateKey as CryptoKey, + encoder.encode(data), + ); + expect( + await subtle.verify( + { name: 'ECDSA', hash: normalizeHashName('SHA-256') }, + publicKey as CryptoKey, + signature, + encoder.encode(data), + ), + ).to.equal(true); +}); + +// // Test Sign/Verify HMAC +// { +// async function test(data) { +// const ec = new TextEncoder(); + +// const key = await subtle.generateKey({ +// name: 'HMAC', +// length: 256, +// hash: 'SHA-256' +// }, true, ['sign', 'verify']); + +// const signature = await subtle.sign({ +// name: 'HMAC', +// }, key, ec.encode(data)); + +// assert(await subtle.verify({ +// name: 'HMAC', +// }, key, signature, ec.encode(data))); +// } + +// test('hello world').then(common.mustCall()); +// } + +// // Test Sign/Verify Ed25519 +// { +// async function test(data) { +// const ec = new TextEncoder(); +// const { publicKey, privateKey } = await subtle.generateKey({ +// name: 'Ed25519', +// }, true, ['sign', 'verify']); + +// const signature = await subtle.sign({ +// name: 'Ed25519', +// }, privateKey, ec.encode(data)); + +// assert(await subtle.verify({ +// name: 'Ed25519', +// }, publicKey, signature, ec.encode(data))); +// } + +// test('hello world').then(common.mustCall()); +// } + +// // Test Sign/Verify Ed448 +// { +// async function test(data) { +// const ec = new TextEncoder(); +// const { publicKey, privateKey } = await subtle.generateKey({ +// name: 'Ed448', +// }, true, ['sign', 'verify']); + +// const signature = await subtle.sign({ +// name: 'Ed448', +// }, privateKey, ec.encode(data)); + +// assert(await subtle.verify({ +// name: 'Ed448', +// }, publicKey, signature, ec.encode(data))); +// } + +// test('hello world').then(common.mustCall()); +// } + +// TODO: when other algorithms are implemented, add the tests in +// * test-webcrypto-sign-verify-ecdsa.js +// * test-webcrypto-sign-verify-eddsa.js +// * test-webcrypto-sign-verify-hmac.js +// * test-webcrypto-sign-verify-rsa.js diff --git a/example/src/tests/util.ts b/example/src/tests/util.ts index 46c94c17..f88a1689 100644 --- a/example/src/tests/util.ts +++ b/example/src/tests/util.ts @@ -1,3 +1,4 @@ +import { assert } from 'chai'; import type { TestSuites } from '../types/tests'; export const TestsContext: TestSuites = {}; @@ -12,3 +13,31 @@ export const test = ( } TestsContext[suiteName].tests[testName] = fn; }; + +export const assertThrowsAsync = async ( + fn: () => Promise, + expectedMessage: string, +) => { + try { + await fn(); + } catch (error) { + const err = error as Error; + if (expectedMessage) { + assert.include( + err.message, + expectedMessage, + `Function failed as expected, but could not find message snippet '${expectedMessage}'. Saw '${err.message}' instead.`, + ); + } + return; + } + assert.fail('function did not throw as expected'); +}; + +export const decodeHex = (str: string): Uint8Array => { + const uint8array = new Uint8Array(Math.ceil(str.length / 2)); + for (let i = 0; i < str.length; ) { + uint8array[i / 2] = Number.parseInt(str.slice(i, (i += 2)), 16); + } + return uint8array; +}; diff --git a/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.cpp b/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.cpp index 06986639..9bf81c82 100644 --- a/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.cpp +++ b/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.cpp @@ -132,8 +132,8 @@ bool HybridKeyObjectHandle::init(KeyType keyType, const std::variant>(key); } - // Handle raw key material (when format and type are not provided) - if (!format.has_value() && !type.has_value()) { + // Handle raw asymmetric key material (curves only) + if (!format.has_value() && !type.has_value() && (keyType == KeyType::PUBLIC || keyType == KeyType::PRIVATE)) { return initRawKey(keyType, ab); } @@ -172,7 +172,7 @@ KeyDetail HybridKeyObjectHandle::keyDetail() { } bool HybridKeyObjectHandle::initRawKey(KeyType keyType, std::shared_ptr keyData) { - // For x25519/x448/ed25519/ed448 raw keys, we need to determine the curve type + // For asymmetric keys (x25519/x448/ed25519/ed448), we need to determine the curve type // Based on key size: x25519=32 bytes, x448=56 bytes, ed25519=32 bytes, ed448=57 bytes int curveId = -1; size_t keySize = keyData->size(); diff --git a/packages/react-native-quick-crypto/src/hash.ts b/packages/react-native-quick-crypto/src/hash.ts index cf79d45d..ac121f6e 100644 --- a/packages/react-native-quick-crypto/src/hash.ts +++ b/packages/react-native-quick-crypto/src/hash.ts @@ -2,8 +2,20 @@ import { Stream } from 'readable-stream'; import { NitroModules } from 'react-native-nitro-modules'; import type { TransformOptions } from 'readable-stream'; import type { Hash as NativeHash } from './specs/hash.nitro'; -import type { BinaryLike, Encoding } from './utils'; -import { ab2str, binaryLikeToArrayBuffer } from './utils'; +import type { + BinaryLike, + Encoding, + BufferLike, + SubtleAlgorithm, +} from './utils'; +import { + ab2str, + binaryLikeToArrayBuffer, + bufferLikeToArrayBuffer, +} from './utils'; +import { validateMaxBufferLength } from './utils/validation'; +import { lazyDOMException } from './utils/errors'; +import { normalizeHashName } from './utils/hashnames'; class HashUtils { private static native = NitroModules.createHybridObject('Hash'); @@ -210,7 +222,53 @@ export function createHash(algorithm: string, options?: HashOptions): Hash { }); } +// Implementation for WebCrypto subtle.digest() + +/** + * Asynchronous digest function for WebCrypto SubtleCrypto API + * @param algorithm The hash algorithm to use + * @param data The data to hash + * @returns Promise resolving to the hash digest as ArrayBuffer + */ +export const asyncDigest = async ( + algorithm: SubtleAlgorithm, + data: BufferLike, +): Promise => { + validateMaxBufferLength(data, 'data'); + + switch (algorithm.name) { + case 'SHA-1': + // Fall through + case 'SHA-256': + // Fall through + case 'SHA-384': + // Fall through + case 'SHA-512': + return internalDigest(algorithm, data); + } + + throw lazyDOMException( + `Unrecognized algorithm name: ${algorithm.name}`, + 'NotSupportedError', + ); +}; + +const internalDigest = ( + algorithm: SubtleAlgorithm, + data: BufferLike, +): ArrayBuffer => { + const normalizedHashName = normalizeHashName(algorithm.name); + const hash = createHash(normalizedHashName); + hash.update(bufferLikeToArrayBuffer(data)); + const result = hash.digest(); + const arrayBuffer = new ArrayBuffer(result.length); + const view = new Uint8Array(arrayBuffer); + view.set(result); + return arrayBuffer; +}; + export const hashExports = { createHash, getHashes, + asyncDigest, }; diff --git a/packages/react-native-quick-crypto/src/index.ts b/packages/react-native-quick-crypto/src/index.ts index 47ae35dc..a900f24d 100644 --- a/packages/react-native-quick-crypto/src/index.ts +++ b/packages/react-native-quick-crypto/src/index.ts @@ -12,6 +12,7 @@ import * as random from './random'; // utils import import * as utils from './utils'; +import * as subtle from './subtle'; /** * Loosely matches Node.js {crypto} with some unimplemented functionality. @@ -26,6 +27,7 @@ const QuickCrypto = { ...pbkdf2, ...random, ...utils, + ...subtle, }; /** @@ -53,6 +55,8 @@ export * from './hmac'; export * from './pbkdf2'; export * from './random'; export * from './utils'; +export * from './subtle'; +export { subtle, isCryptoKeyPair } from './subtle'; // Additional exports for CommonJS compatibility module.exports = QuickCrypto; diff --git a/packages/react-native-quick-crypto/src/keys/classes.ts b/packages/react-native-quick-crypto/src/keys/classes.ts index 9e2dffbb..b20b5eb9 100644 --- a/packages/react-native-quick-crypto/src/keys/classes.ts +++ b/packages/react-native-quick-crypto/src/keys/classes.ts @@ -153,21 +153,21 @@ export class KeyObject { } } + // For secret keys, return SecretKeyObject + if (type === 'secret') { + return new SecretKeyObject(handle); + } + // Return regular KeyObject for symmetric keys or if asymmetric detection failed return new KeyObject(type, handle); } - equals(otherKeyObject: unknown): boolean { - if (!(otherKeyObject instanceof KeyObject)) { - throw new TypeError( - `Invalid argument type for "otherKeyObject", expected "KeyObject" but got ${typeof otherKeyObject}`, - ); - } + getAsymmetricKeyType(): undefined { + return undefined; + } - return ( - this.type === otherKeyObject.type && - this.handle.equals(otherKeyObject.handle) - ); + getAsymmetricKeyDetails(): undefined { + return undefined; } } @@ -177,7 +177,7 @@ export class SecretKeyObject extends KeyObject { } // get symmetricKeySize() { - // return this[kHandle].getSymmetricKeySize(); + // return this.handle.getSymmetricKeySize(); // } export(options: { format: 'pem' } & EncodingOptions): never; diff --git a/packages/react-native-quick-crypto/src/keys/index.ts b/packages/react-native-quick-crypto/src/keys/index.ts index 748554fe..1cfc122b 100644 --- a/packages/react-native-quick-crypto/src/keys/index.ts +++ b/packages/react-native-quick-crypto/src/keys/index.ts @@ -14,10 +14,17 @@ import { parsePrivateKeyEncoding, parsePublicKeyEncoding, } from './utils'; +import type { BinaryLike } from '../utils'; +import { binaryLikeToArrayBuffer as toAB } from '../utils'; + +function createSecretKey(key: BinaryLike): SecretKeyObject { + const keyBuffer = toAB(key); + return KeyObject.createKeyObject('secret', keyBuffer) as SecretKeyObject; +} export { // Node Public API - // createSecretKey, + createSecretKey, // createPublicKey, // createPrivateKey, CryptoKey, diff --git a/packages/react-native-quick-crypto/src/keys/utils.ts b/packages/react-native-quick-crypto/src/keys/utils.ts index e9d84b67..0a3558ce 100644 --- a/packages/react-native-quick-crypto/src/keys/utils.ts +++ b/packages/react-native-quick-crypto/src/keys/utils.ts @@ -4,13 +4,21 @@ import { KeyEncoding, KFormatType, } from '../utils'; -import type { EncodingOptions } from '../utils'; +import type { CryptoKeyPair, EncodingOptions } from '../utils'; +import type { CryptoKey } from './classes'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const isCryptoKey = (obj: any): boolean => { return obj !== null && obj?.keyObject !== undefined; }; +export function getCryptoKeyPair( + key: CryptoKey | CryptoKeyPair, +): CryptoKeyPair { + if ('publicKey' in key && 'privateKey' in key) return key; + throw new Error('Invalid CryptoKeyPair'); +} + /** * Parses the public key encoding based on an object. keyType must be undefined * when this is used to parse an input encoding and must be a valid key type if diff --git a/packages/react-native-quick-crypto/src/subtle.ts b/packages/react-native-quick-crypto/src/subtle.ts new file mode 100644 index 00000000..c787cd05 --- /dev/null +++ b/packages/react-native-quick-crypto/src/subtle.ts @@ -0,0 +1,663 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { Buffer as SBuffer } from 'safe-buffer'; +import type { + SubtleAlgorithm, + KeyUsage, + BinaryLike, + BufferLike, + JWK, + AnyAlgorithm, + ImportFormat, + AesKeyGenParams, + EncryptDecryptParams, + Operation, +} from './utils'; +import { CryptoKey, KeyObject } from './keys'; +import type { CryptoKeyPair } from './utils/types'; +import { bufferLikeToArrayBuffer } from './utils/conversion'; +import { lazyDOMException } from './utils/errors'; +import { normalizeHashName, HashContext } from './utils/hashnames'; +import { validateMaxBufferLength } from './utils/validation'; +import { asyncDigest } from './hash'; +import { createSecretKey } from './keys'; +import { pbkdf2DeriveBits } from './pbkdf2'; + +// Placeholder imports - these modules need to be implemented or adapted +// import { ecImportKey, ecExportKey, ecGenerateKey, ecdsaSignVerify } from './ec'; +// import { pbkdf2DeriveBits } from './pbkdf2'; +// import { aesCipher, aesGenerateKey, aesImportKey, getAlgorithmName } from './aes'; +// import { rsaCipher, rsaExportKey, rsaImportKey, rsaKeyGenerate } from './rsa'; +// import { normalizeAlgorithm, type Operation } from './algorithms'; +// import { hmacImportKey } from './mac'; + +// Temporary enums that need to be defined + +enum KWebCryptoKeyFormat { + kWebCryptoKeyFormatRaw, + kWebCryptoKeyFormatSPKI, + kWebCryptoKeyFormatPKCS8, +} + +enum CipherOrWrapMode { + kWebCryptoCipherEncrypt, + kWebCryptoCipherDecrypt, +} + +// Placeholder functions that need to be implemented +function hasAnyNotIn(usages: KeyUsage[], allowed: KeyUsage[]): boolean { + return usages.some(usage => !allowed.includes(usage)); +} + +function normalizeAlgorithm( + algorithm: SubtleAlgorithm | AnyAlgorithm, + _operation: Operation, +): SubtleAlgorithm { + if (typeof algorithm === 'string') { + return { name: algorithm }; + } + return algorithm as SubtleAlgorithm; +} + +function getAlgorithmName(name: string, length: number): string { + return `${name}${length}`; +} + +// Placeholder implementations for missing functions +function ecExportKey( + _key: CryptoKey, + _format: KWebCryptoKeyFormat, +): ArrayBuffer { + throw new Error('ecExportKey not implemented'); +} + +function rsaExportKey( + _key: CryptoKey, + _format: KWebCryptoKeyFormat, +): ArrayBuffer { + throw new Error('rsaExportKey not implemented'); +} + +function ecdsaSignVerify( + _key: CryptoKey, + _data: BufferLike, + _algorithm: SubtleAlgorithm, + _signature?: BufferLike, +): ArrayBuffer | boolean { + throw new Error('ecdsaSignVerify not implemented'); +} + +function rsaCipher( + _mode: CipherOrWrapMode, + _key: CryptoKey, + _data: ArrayBuffer, + _algorithm: EncryptDecryptParams, +): Promise { + throw new Error('rsaCipher not implemented'); +} + +function aesCipher( + _mode: CipherOrWrapMode, + _key: CryptoKey, + _data: ArrayBuffer, + _algorithm: EncryptDecryptParams, +): Promise { + throw new Error('aesCipher not implemented'); +} + +async function rsaKeyGenerate( + _algorithm: SubtleAlgorithm, + _extractable: boolean, + _keyUsages: KeyUsage[], +): Promise { + throw new Error('rsaKeyGenerate not implemented'); +} + +async function ecGenerateKey( + algorithm: SubtleAlgorithm, + extractable: boolean, + keyUsages: KeyUsage[], +): Promise { + // Temporary implementation - create mock CryptoKey objects + const mockKeyObject = {} as KeyObject; + const publicKey = new CryptoKey( + mockKeyObject, + algorithm, + keyUsages, + extractable, + ); + const privateKey = new CryptoKey( + mockKeyObject, + algorithm, + keyUsages, + extractable, + ); + + return { + publicKey, + privateKey, + }; +} + +async function aesGenerateKey( + _algorithm: AesKeyGenParams, + _extractable: boolean, + _keyUsages: KeyUsage[], +): Promise { + throw new Error('aesGenerateKey not implemented'); +} + +function rsaImportKey( + _format: ImportFormat, + _data: BufferLike | JWK, + _algorithm: SubtleAlgorithm, + _extractable: boolean, + _keyUsages: KeyUsage[], +): CryptoKey { + throw new Error('rsaImportKey not implemented'); +} + +function ecImportKey( + _format: ImportFormat, + _data: BufferLike | BinaryLike | JWK, + _algorithm: SubtleAlgorithm, + _extractable: boolean, + _keyUsages: KeyUsage[], +): CryptoKey { + throw new Error('ecImportKey not implemented'); +} + +async function hmacImportKey( + _algorithm: SubtleAlgorithm, + _format: ImportFormat, + _data: BufferLike | JWK, + _extractable: boolean, + _keyUsages: KeyUsage[], +): Promise { + throw new Error('hmacImportKey not implemented'); +} + +async function aesImportKey( + _algorithm: SubtleAlgorithm, + _format: ImportFormat, + _data: BufferLike | JWK, + _extractable: boolean, + _keyUsages: KeyUsage[], +): Promise { + throw new Error('aesImportKey not implemented'); +} + +const exportKeySpki = async ( + key: CryptoKey, +): Promise => { + switch (key.algorithm.name) { + case 'RSASSA-PKCS1-v1_5': + // Fall through + case 'RSA-PSS': + // Fall through + case 'RSA-OAEP': + if (key.type === 'public') { + return rsaExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatSPKI); + } + break; + case 'ECDSA': + // Fall through + case 'ECDH': + if (key.type === 'public') { + return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatSPKI); + } + break; + } + + throw new Error( + `Unable to export a spki ${key.algorithm.name} ${key.type} key`, + ); +}; + +const exportKeyPkcs8 = async ( + key: CryptoKey, +): Promise => { + switch (key.algorithm.name) { + case 'RSASSA-PKCS1-v1_5': + // Fall through + case 'RSA-PSS': + // Fall through + case 'RSA-OAEP': + if (key.type === 'private') { + return rsaExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatPKCS8); + } + break; + case 'ECDSA': + // Fall through + case 'ECDH': + if (key.type === 'private') { + return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatPKCS8); + } + break; + } + + throw new Error( + `Unable to export a pkcs8 ${key.algorithm.name} ${key.type} key`, + ); +}; + +const exportKeyRaw = (key: CryptoKey): ArrayBuffer | unknown => { + switch (key.algorithm.name) { + case 'ECDSA': + // Fall through + case 'ECDH': + if (key.type === 'public') { + return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw); + } + break; + case 'AES-CTR': + // Fall through + case 'AES-CBC': + // Fall through + case 'AES-GCM': + // Fall through + case 'AES-KW': + // Fall through + case 'HMAC': + return key.keyObject.export(); + } + + throw lazyDOMException( + `Unable to export a raw ${key.algorithm.name} ${key.type} key`, + 'InvalidAccessError', + ); +}; + +const exportKeyJWK = (key: CryptoKey): ArrayBuffer | unknown => { + const jwk = key.keyObject.handle.exportJwk( + { + key_ops: key.usages, + ext: key.extractable, + }, + true, + ); + switch (key.algorithm.name) { + case 'RSASSA-PKCS1-v1_5': + jwk.alg = normalizeHashName(key.algorithm.hash, HashContext.JwkRsa); + return jwk; + case 'RSA-PSS': + jwk.alg = normalizeHashName(key.algorithm.hash, HashContext.JwkRsaPss); + return jwk; + case 'RSA-OAEP': + jwk.alg = normalizeHashName(key.algorithm.hash, HashContext.JwkRsaOaep); + return jwk; + case 'HMAC': + jwk.alg = normalizeHashName(key.algorithm.hash, HashContext.JwkHmac); + return jwk; + case 'ECDSA': + // Fall through + case 'ECDH': + jwk.crv ||= key.algorithm.namedCurve; + return jwk; + case 'AES-CTR': + // Fall through + case 'AES-CBC': + // Fall through + case 'AES-GCM': + // Fall through + case 'AES-KW': + if (key.algorithm.length === undefined) { + throw lazyDOMException( + `Algorithm ${key.algorithm.name} missing required length property`, + 'InvalidAccessError', + ); + } + jwk.alg = getAlgorithmName(key.algorithm.name, key.algorithm.length); + return jwk; + default: + // Fall through + } + + throw lazyDOMException( + `JWK export not yet supported: ${key.algorithm.name}`, + 'NotSupportedError', + ); +}; + +const importGenericSecretKey = async ( + { name, length }: SubtleAlgorithm, + format: ImportFormat, + keyData: BufferLike | BinaryLike, + extractable: boolean, + keyUsages: KeyUsage[], +): Promise => { + if (extractable) { + throw new Error(`${name} keys are not extractable`); + } + if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) { + throw new Error(`Unsupported key usage for a ${name} key`); + } + + switch (format) { + case 'raw': { + if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) { + throw new Error(`Unsupported key usage for a ${name} key`); + } + + const checkLength = + typeof keyData === 'string' || SBuffer.isBuffer(keyData) + ? keyData.length * 8 + : keyData.byteLength * 8; + + if (length !== undefined && length !== checkLength) { + throw new Error('Invalid key length'); + } + + const keyObject = createSecretKey(keyData as BinaryLike); + return new CryptoKey(keyObject, { name }, keyUsages, false); + } + } + + throw new Error(`Unable to import ${name} key with format ${format}`); +}; + +const checkCryptoKeyPairUsages = (pair: CryptoKeyPair) => { + if ( + pair.privateKey && + pair.privateKey instanceof CryptoKey && + pair.privateKey.keyUsages && + pair.privateKey.keyUsages.length > 0 + ) { + return; + } + throw lazyDOMException( + 'Usages cannot be empty when creating a key.', + 'SyntaxError', + ); +}; + +// Type guard to check if result is CryptoKeyPair +export function isCryptoKeyPair( + result: CryptoKey | CryptoKeyPair, +): result is CryptoKeyPair { + return 'publicKey' in result && 'privateKey' in result; +} + +const signVerify = ( + algorithm: SubtleAlgorithm, + key: CryptoKey, + data: BufferLike, + signature?: BufferLike, +): ArrayBuffer | boolean => { + const usage: Operation = signature === undefined ? 'sign' : 'verify'; + algorithm = normalizeAlgorithm(algorithm, usage); + + if (!key.usages.includes(usage) || algorithm.name !== key.algorithm.name) { + throw lazyDOMException( + `Unable to use this key to ${usage}`, + 'InvalidAccessError', + ); + } + + switch (algorithm.name) { + case 'ECDSA': + return ecdsaSignVerify(key, data, algorithm, signature); + } + throw lazyDOMException( + `Unrecognized algorithm name '${algorithm}' for '${usage}'`, + 'NotSupportedError', + ); +}; + +const cipherOrWrap = async ( + mode: CipherOrWrapMode, + algorithm: EncryptDecryptParams, + key: CryptoKey, + data: ArrayBuffer, + op: Operation, +): Promise => { + if ( + key.algorithm.name !== algorithm.name || + !key.usages.includes(op as KeyUsage) + ) { + throw lazyDOMException( + 'The requested operation is not valid for the provided key', + 'InvalidAccessError', + ); + } + + validateMaxBufferLength(data, 'data'); + + switch (algorithm.name) { + case 'RSA-OAEP': + return rsaCipher(mode, key, data, algorithm); + case 'AES-CTR': + // Fall through + case 'AES-CBC': + // Fall through + case 'AES-GCM': + return aesCipher(mode, key, data, algorithm); + } +}; + +export class Subtle { + async decrypt( + algorithm: EncryptDecryptParams, + key: CryptoKey, + data: BufferLike, + ): Promise { + const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'decrypt'); + return cipherOrWrap( + CipherOrWrapMode.kWebCryptoCipherDecrypt, + normalizedAlgorithm as EncryptDecryptParams, + key, + bufferLikeToArrayBuffer(data), + 'decrypt', + ); + } + + async digest( + algorithm: SubtleAlgorithm | AnyAlgorithm, + data: BufferLike, + ): Promise { + const normalizedAlgorithm = normalizeAlgorithm( + algorithm, + 'digest' as Operation, + ); + return asyncDigest(normalizedAlgorithm, data); + } + + async deriveBits( + algorithm: SubtleAlgorithm, + baseKey: CryptoKey, + length: number, + ): Promise { + if (!baseKey.keyUsages.includes('deriveBits')) { + throw new Error('baseKey does not have deriveBits usage'); + } + if (baseKey.algorithm.name !== algorithm.name) + throw new Error('Key algorithm mismatch'); + switch (algorithm.name) { + case 'PBKDF2': + return pbkdf2DeriveBits(algorithm, baseKey, length); + } + throw new Error( + `'subtle.deriveBits()' for ${algorithm.name} is not implemented.`, + ); + } + + async encrypt( + algorithm: EncryptDecryptParams, + key: CryptoKey, + data: BufferLike, + ): Promise { + const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'encrypt'); + return cipherOrWrap( + CipherOrWrapMode.kWebCryptoCipherEncrypt, + normalizedAlgorithm as EncryptDecryptParams, + key, + bufferLikeToArrayBuffer(data), + 'encrypt', + ); + } + + async exportKey( + format: ImportFormat, + key: CryptoKey, + ): Promise { + if (!key.extractable) throw new Error('key is not extractable'); + + switch (format) { + case 'spki': + return (await exportKeySpki(key)) as ArrayBuffer; + case 'pkcs8': + return (await exportKeyPkcs8(key)) as ArrayBuffer; + case 'jwk': + return exportKeyJWK(key) as JWK; + case 'raw': + return exportKeyRaw(key) as ArrayBuffer; + } + } + + async generateKey( + algorithm: SubtleAlgorithm, + extractable: boolean, + keyUsages: KeyUsage[], + ): Promise { + algorithm = normalizeAlgorithm(algorithm, 'generateKey'); + let result: CryptoKey | CryptoKeyPair; + switch (algorithm.name) { + case 'RSASSA-PKCS1-v1_5': + // Fall through + case 'RSA-PSS': + // Fall through + case 'RSA-OAEP': + result = await rsaKeyGenerate(algorithm, extractable, keyUsages); + break; + case 'ECDSA': + // Fall through + case 'ECDH': + result = await ecGenerateKey(algorithm, extractable, keyUsages); + checkCryptoKeyPairUsages(result); + break; + case 'AES-CTR': + // Fall through + case 'AES-CBC': + // Fall through + case 'AES-GCM': + // Fall through + case 'AES-KW': + result = await aesGenerateKey( + algorithm as AesKeyGenParams, + extractable, + keyUsages, + ); + break; + default: + throw new Error( + `'subtle.generateKey()' is not implemented for ${algorithm.name}. + Unrecognized algorithm name`, + ); + } + + return result; + } + + async importKey( + format: ImportFormat, + data: BufferLike | BinaryLike | JWK, + algorithm: SubtleAlgorithm | AnyAlgorithm, + extractable: boolean, + keyUsages: KeyUsage[], + ): Promise { + const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'importKey'); + let result: CryptoKey; + switch (normalizedAlgorithm.name) { + case 'RSASSA-PKCS1-v1_5': + // Fall through + case 'RSA-PSS': + // Fall through + case 'RSA-OAEP': + result = rsaImportKey( + format, + data as BufferLike | JWK, + normalizedAlgorithm, + extractable, + keyUsages, + ); + break; + case 'ECDSA': + // Fall through + case 'ECDH': + result = ecImportKey( + format, + data, + normalizedAlgorithm, + extractable, + keyUsages, + ); + break; + case 'HMAC': + result = await hmacImportKey( + normalizedAlgorithm, + format, + data as BufferLike | JWK, + extractable, + keyUsages, + ); + break; + case 'AES-CTR': + // Fall through + case 'AES-CBC': + // Fall through + case 'AES-GCM': + // Fall through + case 'AES-KW': + result = await aesImportKey( + normalizedAlgorithm, + format, + data as BufferLike | JWK, + extractable, + keyUsages, + ); + break; + case 'PBKDF2': + result = await importGenericSecretKey( + normalizedAlgorithm, + format, + data as BufferLike | BinaryLike, + extractable, + keyUsages, + ); + break; + default: + throw new Error( + `"subtle.importKey()" is not implemented for ${normalizedAlgorithm.name}`, + ); + } + + if ( + (result.type === 'secret' || result.type === 'private') && + result.usages.length === 0 + ) { + throw new Error( + `Usages cannot be empty when importing a ${result.type} key.`, + ); + } + + return result; + } + + async sign( + algorithm: SubtleAlgorithm, + key: CryptoKey, + data: BufferLike, + ): Promise { + return signVerify(algorithm, key, data) as ArrayBuffer; + } + + async verify( + algorithm: SubtleAlgorithm, + key: CryptoKey, + signature: BufferLike, + data: BufferLike, + ): Promise { + return signVerify(algorithm, key, data, signature) as ArrayBuffer; + } +} + +export const subtle = new Subtle(); diff --git a/packages/react-native-quick-crypto/src/utils/index.ts b/packages/react-native-quick-crypto/src/utils/index.ts index aad2b671..930d7cf9 100644 --- a/packages/react-native-quick-crypto/src/utils/index.ts +++ b/packages/react-native-quick-crypto/src/utils/index.ts @@ -3,3 +3,4 @@ export * from './errors'; export * from './hashnames'; export * from './types'; export * from './validation'; +export * from './cipher'; diff --git a/packages/react-native-quick-crypto/src/utils/types.ts b/packages/react-native-quick-crypto/src/utils/types.ts index 8b7d2923..08d7a4a4 100644 --- a/packages/react-native-quick-crypto/src/utils/types.ts +++ b/packages/react-native-quick-crypto/src/utils/types.ts @@ -3,7 +3,7 @@ import type { Buffer } from 'buffer'; import type { CipherKey } from 'node:crypto'; // @types/node import type { Buffer as SafeBuffer } from 'safe-buffer'; import type { KeyObjectHandle as KeyObjectHandleType } from '../specs/keyObjectHandle.nitro'; -import type { KeyObject } from '../keys'; +import type { KeyObject, CryptoKey } from '../keys'; export type ABV = TypedArray | DataView | ArrayBufferLike | CraftzdogBuffer; @@ -80,6 +80,49 @@ export type EncryptDecryptAlgorithm = | 'AES-CBC' | 'AES-GCM'; +export type RsaOaepParams = { + name: 'RSA-OAEP'; + label?: BufferLike; +}; + +export type AesCbcParams = { + name: 'AES-CBC'; + iv: BufferLike; +}; + +export type AesCtrParams = { + name: 'AES-CTR'; + counter: TypedArray; + length: number; +}; + +export type AesGcmParams = { + name: 'AES-GCM'; + iv: BufferLike; + tagLength?: TagLength; + additionalData?: BufferLike; +}; + +export type AesKwParams = { + name: 'AES-KW'; + wrappingKey?: BufferLike; +}; + +export type AesKeyGenParams = { + length: AESLength; + name?: AESAlgorithm; +}; + +export type TagLength = 32 | 64 | 96 | 104 | 112 | 120 | 128; + +export type AESLength = 128 | 192 | 256; + +export type EncryptDecryptParams = + | AesCbcParams + | AesCtrParams + | AesGcmParams + | RsaOaepParams; + export type AnyAlgorithm = | DigestAlgorithm | HashAlgorithm @@ -234,9 +277,12 @@ export type GenerateKeyPairOptions = { mgf1Hash?: string; }; -// Note: removed CryptoKey class from this type (from 0.x) because Nitro doesn't -// handle custom JS objects. We might need to make it a JS object. -export type KeyPairKey = ArrayBuffer | KeyObject | KeyObjectHandle | undefined; +export type KeyPairKey = + | ArrayBuffer + | KeyObject + | KeyObjectHandle + | CryptoKey + | undefined; export type GenerateKeyPairReturn = [ error?: Error, @@ -326,3 +372,15 @@ export type DiffieHellmanCallback = ( // from @paulmillr/noble-curves export type Hex = string | Uint8Array; + +export type ImportFormat = 'raw' | 'pkcs8' | 'spki' | 'jwk'; + +export type Operation = + | 'encrypt' + | 'decrypt' + | 'sign' + | 'verify' + | 'generateKey' + | 'importKey' + | 'exportKey' + | 'deriveBits'; diff --git a/packages/react-native-quick-crypto/src/utils/validation.ts b/packages/react-native-quick-crypto/src/utils/validation.ts index ddcc7c54..ca8cafce 100644 --- a/packages/react-native-quick-crypto/src/utils/validation.ts +++ b/packages/react-native-quick-crypto/src/utils/validation.ts @@ -1,3 +1,10 @@ +import { Buffer as SBuffer } from 'safe-buffer'; +import type { BinaryLike, BufferLike } from './types'; +import { lazyDOMException } from './errors'; + +// The maximum buffer size that we'll support in the WebCrypto impl +const kMaxBufferLength = 2 ** 31 - 1; + export function validateFunction(f: unknown): boolean { return f !== null && typeof f === 'function'; } @@ -29,7 +36,23 @@ export function validateObject( (typeof value !== 'object' && (!allowFunction || typeof value !== 'function')) ) { - throw new Error(`${name} is not a valid object $${value}`); + throw new Error(`${name} is not a valid object ${value}`); } return true; } + +export const validateMaxBufferLength = ( + data: BinaryLike | BufferLike, + name: string, +): void => { + const length = + typeof data === 'string' || data instanceof SBuffer + ? data.length + : data.byteLength; + if (length > kMaxBufferLength) { + throw lazyDOMException( + `${name} must be less than ${kMaxBufferLength + 1} bits`, + 'OperationError', + ); + } +};