diff --git a/.docs/implementation-coverage.md b/.docs/implementation-coverage.md
index 8ec4adfc..1ebf0532 100644
--- a/.docs/implementation-coverage.md
+++ b/.docs/implementation-coverage.md
@@ -1,13 +1,14 @@
# 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
-* 🚧 - work in progress
-* `-` - not applicable to React Native
+- ` ` - not implemented in Node
+- ❌ - implemented in Node, not RNQC
+- ✅ - implemented in Node and RNQC
+- 🚧 - work in progress
+- `-` - not applicable to React Native
## Post-Quantum Cryptography (PQC)
@@ -16,498 +17,521 @@ This document attempts to describe the implementation status of Crypto APIs/Inte
These algorithms provide quantum-resistant cryptography.
-
# `Crypto`
-* ✅ Class: `Certificate`
- * ✅ Static method: `Certificate.exportChallenge(spkac[, encoding])`
- * ✅ Static method: `Certificate.exportPublicKey(spkac[, encoding])`
- * ✅ Static method: `Certificate.verifySpkac(spkac[, encoding])`
-* ✅ Class: `Cipheriv`
- * ✅ `cipher.final([outputEncoding])`
- * ✅ `cipher.getAuthTag()`
- * ✅ `cipher.setAAD(buffer[, options])`
- * ✅ `cipher.setAutoPadding([autoPadding])`
- * ✅ `cipher.update(data[, inputEncoding][, outputEncoding])`
-* ✅ Class: `Decipheriv`
- * ✅ `decipher.final([outputEncoding])`
- * ✅ `decipher.setAAD(buffer[, options])`
- * ✅ `decipher.setAuthTag(buffer[, encoding])`
- * ✅ `decipher.setAutoPadding([autoPadding])`
- * ✅ `decipher.update(data[, inputEncoding][, outputEncoding])`
-* ✅ Class: `DiffieHellman`
- * ✅ `diffieHellman.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])`
- * ✅ `diffieHellman.generateKeys([encoding])`
- * ✅ `diffieHellman.getGenerator([encoding])`
- * ✅ `diffieHellman.getPrime([encoding])`
- * ✅ `diffieHellman.getPrivateKey([encoding])`
- * ✅ `diffieHellman.getPublicKey([encoding])`
- * ✅ `diffieHellman.setPrivateKey(privateKey[, encoding])`
- * ✅ `diffieHellman.setPublicKey(publicKey[, encoding])`
- * ✅ `diffieHellman.verifyError`
-* ✅ Class: `DiffieHellmanGroup`
-* ✅ Class: `ECDH`
- * ✅ static `ECDH.convertKey(key, curve[, inputEncoding[, outputEncoding[, format]]])`
- * ✅ `ecdh.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])`
- * ✅ `ecdh.generateKeys([encoding[, format]])`
- * ✅ `ecdh.getPrivateKey([encoding])`
- * ✅ `ecdh.getPublicKey([encoding][, format])`
- * ✅ `ecdh.setPrivateKey(privateKey[, encoding])`
- * ✅ `ecdh.setPublicKey(publicKey[, encoding])`
-* ✅ Class: `Hash`
- * ✅ `hash.copy([options])`
- * ✅ `hash.digest([encoding])`
- * ✅ `hash.update(data[, inputEncoding])`
-* ✅ Class: `Hmac`
- * ✅ `hmac.digest([encoding])`
- * ✅ `hmac.update(data[, inputEncoding])`
-* ✅ Class: `KeyObject`
- * ✅ static `KeyObject.from(key)`
- * ✅ `keyObject.asymmetricKeyDetails`
- * ✅ `keyObject.asymmetricKeyType`
- * ✅ `keyObject.export([options])`
- * ✅ `keyObject.equals(otherKeyObject)`
- * ✅ `keyObject.symmetricKeySize`
- * ✅ `keyObject.toCryptoKey(algorithm, extractable, keyUsages)`
- * ✅ `keyObject.type`
-* ✅ Class: `Sign`
- * ✅ `sign.sign(privateKey[, outputEncoding])`
- * ✅ `sign.update(data[, inputEncoding])`
-* ✅ Class: `Verify`
- * ✅ `verify.update(data[, inputEncoding])`
- * ✅ `verify.verify(object, signature[, signatureEncoding])`
-* ❌ Class: `X509Certificate`
- * ❌ `new X509Certificate(buffer)`
- * ❌ `x509.ca`
- * ❌ `x509.checkEmail(email[, options])`
- * ❌ `x509.checkHost(name[, options])`
- * ❌ `x509.checkIP(ip)`
- * ❌ `x509.checkIssued(otherCert)`
- * ❌ `x509.checkPrivateKey(privateKey)`
- * ❌ `x509.fingerprint`
- * ❌ `x509.fingerprint256`
- * ❌ `x509.fingerprint512`
- * ❌ `x509.infoAccess`
- * ❌ `x509.issuer`
- * ❌ `x509.issuerCertificate`
- * ❌ `x509.extKeyUsage`
- * ❌ `x509.publicKey`
- * ❌ `x509.raw`
- * ❌ `x509.serialNumber`
- * ❌ `x509.subject`
- * ❌ `x509.subjectAltName`
- * ❌ `x509.toJSON()`
- * ❌ `x509.toLegacyObject()`
- * ❌ `x509.toString()`
- * ❌ `x509.validFrom`
- * ❌ `x509.validTo`
- * ❌ `x509.verify(publicKey)`
-* 🚧 node:crypto module methods and properties
- * ✅ `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])`
- * ✅ `crypto.createDiffieHellman(primeLength[, generator])`
- * ✅ `crypto.createDiffieHellmanGroup(groupName)`
- * ✅ `crypto.getDiffieHellman(groupName)`
- * ✅ `crypto.createECDH(curveName)`
- * ✅ `crypto.createHash(algorithm[, options])`
- * ✅ `crypto.createHmac(algorithm, key[, options])`
- * ✅ `crypto.createPrivateKey(key)`
- * ✅ `crypto.createPublicKey(key)`
- * ✅ `crypto.createSecretKey(key[, encoding])`
- * ✅ `crypto.createSign(algorithm[, options])`
- * ✅ `crypto.createVerify(algorithm[, options])`
- * ❌ `crypto.decapsulate(key, ciphertext[, callback])`
- * ✅ `crypto.diffieHellman(options[, callback])`
- * ❌ `crypto.encapsulate(key[, callback])`
- * `-` `crypto.fips` deprecated, not applicable to RN
- * ✅ `crypto.generateKey(type, options, callback)`
- * ✅ `crypto.generateKeyPair(type, options, callback)`
- * ✅ `crypto.generateKeyPairSync(type, options)`
- * ✅ `crypto.generateKeySync(type, options)`
- * ✅ `crypto.generatePrime(size[, options[, callback]])`
- * ✅ `crypto.generatePrimeSync(size[, options])`
- * ✅ `crypto.getCipherInfo(nameOrNid[, options])`
- * ✅ `crypto.getCiphers()`
- * ✅ `crypto.getCurves()`
- * `-` `crypto.getFips()` not applicable to RN
- * ✅ `crypto.getHashes()`
- * ✅ `crypto.getRandomValues(typedArray)`
- * ✅ `crypto.hash(algorithm, data[, outputEncoding])`
- * ✅ `crypto.hkdf(digest, ikm, salt, info, keylen, callback)`
- * ✅ `crypto.hkdfSync(digest, ikm, salt, info, keylen)`
- * ✅ `crypto.pbkdf2(password, salt, iterations, keylen, digest, callback)`
- * ✅ `crypto.pbkdf2Sync(password, salt, iterations, keylen, digest)`
- * ✅ `crypto.privateDecrypt(privateKey, buffer)`
- * ✅ `crypto.privateEncrypt(privateKey, buffer)`
- * ✅ `crypto.publicDecrypt(key, buffer)`
- * ✅ `crypto.publicEncrypt(key, buffer)`
- * ✅ `crypto.randomBytes(size[, callback])`
- * ✅ `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)`
- * ✅ `crypto.scryptSync(password, salt, keylen[, options])`
- * `-` `crypto.secureHeapUsed()` not applicable to RN
- * `-` `crypto.setEngine(engine[, flags])` not applicable to RN
- * `-` `crypto.setFips(bool)` not applicable to RN
- * ✅ `crypto.sign(algorithm, data, key[, callback])`
- * ✅ `crypto.subtle` (see below)
- * ✅ `crypto.timingSafeEqual(a, b)`
- * ✅ `crypto.verify(algorithm, data, key, signature[, callback])`
- * ✅ `crypto.webcrypto` (see below)
+- ✅ Class: `Certificate`
+ - ✅ Static method: `Certificate.exportChallenge(spkac[, encoding])`
+ - ✅ Static method: `Certificate.exportPublicKey(spkac[, encoding])`
+ - ✅ Static method: `Certificate.verifySpkac(spkac[, encoding])`
+- ✅ Class: `Cipheriv`
+ - ✅ `cipher.final([outputEncoding])`
+ - ✅ `cipher.getAuthTag()`
+ - ✅ `cipher.setAAD(buffer[, options])`
+ - ✅ `cipher.setAutoPadding([autoPadding])`
+ - ✅ `cipher.update(data[, inputEncoding][, outputEncoding])`
+- ✅ Class: `Decipheriv`
+ - ✅ `decipher.final([outputEncoding])`
+ - ✅ `decipher.setAAD(buffer[, options])`
+ - ✅ `decipher.setAuthTag(buffer[, encoding])`
+ - ✅ `decipher.setAutoPadding([autoPadding])`
+ - ✅ `decipher.update(data[, inputEncoding][, outputEncoding])`
+- ✅ Class: `DiffieHellman`
+ - ✅ `diffieHellman.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])`
+ - ✅ `diffieHellman.generateKeys([encoding])`
+ - ✅ `diffieHellman.getGenerator([encoding])`
+ - ✅ `diffieHellman.getPrime([encoding])`
+ - ✅ `diffieHellman.getPrivateKey([encoding])`
+ - ✅ `diffieHellman.getPublicKey([encoding])`
+ - ✅ `diffieHellman.setPrivateKey(privateKey[, encoding])`
+ - ✅ `diffieHellman.setPublicKey(publicKey[, encoding])`
+ - ✅ `diffieHellman.verifyError`
+- ✅ Class: `DiffieHellmanGroup`
+- ✅ Class: `ECDH`
+ - ✅ static `ECDH.convertKey(key, curve[, inputEncoding[, outputEncoding[, format]]])`
+ - ✅ `ecdh.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])`
+ - ✅ `ecdh.generateKeys([encoding[, format]])`
+ - ✅ `ecdh.getPrivateKey([encoding])`
+ - ✅ `ecdh.getPublicKey([encoding][, format])`
+ - ✅ `ecdh.setPrivateKey(privateKey[, encoding])`
+ - ✅ `ecdh.setPublicKey(publicKey[, encoding])`
+- ✅ Class: `Hash`
+ - ✅ `hash.copy([options])`
+ - ✅ `hash.digest([encoding])`
+ - ✅ `hash.update(data[, inputEncoding])`
+- ✅ Class: `Hmac`
+ - ✅ `hmac.digest([encoding])`
+ - ✅ `hmac.update(data[, inputEncoding])`
+- ✅ Class: `KeyObject`
+ - ✅ static `KeyObject.from(key)`
+ - ✅ `keyObject.asymmetricKeyDetails`
+ - ✅ `keyObject.asymmetricKeyType`
+ - ✅ `keyObject.export([options])`
+ - ✅ `keyObject.equals(otherKeyObject)`
+ - ✅ `keyObject.symmetricKeySize`
+ - ✅ `keyObject.toCryptoKey(algorithm, extractable, keyUsages)`
+ - ✅ `keyObject.type`
+- ✅ Class: `Sign`
+ - ✅ `sign.sign(privateKey[, outputEncoding])`
+ - ✅ `sign.update(data[, inputEncoding])`
+- ✅ Class: `Verify`
+ - ✅ `verify.update(data[, inputEncoding])`
+ - ✅ `verify.verify(object, signature[, signatureEncoding])`
+- ✅ Class: `X509Certificate`
+ - ✅ `new X509Certificate(buffer)`
+ - ✅ `x509.ca`
+ - ✅ `x509.checkEmail(email[, options])`
+ - ✅ `x509.checkHost(name[, options])`
+ - ✅ `x509.checkIP(ip)`
+ - ✅ `x509.checkIssued(otherCert)`
+ - ✅ `x509.checkPrivateKey(privateKey)`
+ - ✅ `x509.fingerprint`
+ - ✅ `x509.fingerprint256`
+ - ✅ `x509.fingerprint512`
+ - ✅ `x509.infoAccess`
+ - ✅ `x509.issuer`
+ - ✅ `x509.issuerCertificate`
+ - ✅ `x509.extKeyUsage`
+ - ✅ `x509.keyUsage`
+ - ✅ `x509.signatureAlgorithm`
+ - ✅ `x509.signatureAlgorithmOid`
+ - ✅ `x509.publicKey`
+ - ✅ `x509.raw`
+ - ✅ `x509.serialNumber`
+ - ✅ `x509.subject`
+ - ✅ `x509.subjectAltName`
+ - ✅ `x509.toJSON()`
+ - ✅ `x509.toLegacyObject()`
+ - ✅ `x509.toString()`
+ - ✅ `x509.validFrom`
+ - ✅ `x509.validTo`
+ - ✅ `x509.verify(publicKey)`
+- 🚧 node:crypto module methods and properties
+ - ✅ `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])`
+ - ✅ `crypto.createDiffieHellman(primeLength[, generator])`
+ - ✅ `crypto.createDiffieHellmanGroup(groupName)`
+ - ✅ `crypto.getDiffieHellman(groupName)`
+ - ✅ `crypto.createECDH(curveName)`
+ - ✅ `crypto.createHash(algorithm[, options])`
+ - ✅ `crypto.createHmac(algorithm, key[, options])`
+ - ✅ `crypto.createPrivateKey(key)`
+ - ✅ `crypto.createPublicKey(key)`
+ - ✅ `crypto.createSecretKey(key[, encoding])`
+ - ✅ `crypto.createSign(algorithm[, options])`
+ - ✅ `crypto.createVerify(algorithm[, options])`
+ - ❌ `crypto.decapsulate(key, ciphertext[, callback])`
+ - ✅ `crypto.diffieHellman(options[, callback])`
+ - ❌ `crypto.encapsulate(key[, callback])`
+ - `-` `crypto.fips` deprecated, not applicable to RN
+ - ✅ `crypto.generateKey(type, options, callback)`
+ - ✅ `crypto.generateKeyPair(type, options, callback)`
+ - ✅ `crypto.generateKeyPairSync(type, options)`
+ - ✅ `crypto.generateKeySync(type, options)`
+ - ✅ `crypto.generatePrime(size[, options[, callback]])`
+ - ✅ `crypto.generatePrimeSync(size[, options])`
+ - ✅ `crypto.getCipherInfo(nameOrNid[, options])`
+ - ✅ `crypto.getCiphers()`
+ - ✅ `crypto.getCurves()`
+ - `-` `crypto.getFips()` not applicable to RN
+ - ✅ `crypto.getHashes()`
+ - ✅ `crypto.getRandomValues(typedArray)`
+ - ✅ `crypto.hash(algorithm, data[, outputEncoding])`
+ - ✅ `crypto.hkdf(digest, ikm, salt, info, keylen, callback)`
+ - ✅ `crypto.hkdfSync(digest, ikm, salt, info, keylen)`
+ - ✅ `crypto.pbkdf2(password, salt, iterations, keylen, digest, callback)`
+ - ✅ `crypto.pbkdf2Sync(password, salt, iterations, keylen, digest)`
+ - ✅ `crypto.privateDecrypt(privateKey, buffer)`
+ - ✅ `crypto.privateEncrypt(privateKey, buffer)`
+ - ✅ `crypto.publicDecrypt(key, buffer)`
+ - ✅ `crypto.publicEncrypt(key, buffer)`
+ - ✅ `crypto.randomBytes(size[, callback])`
+ - ✅ `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)`
+ - ✅ `crypto.scryptSync(password, salt, keylen[, options])`
+ - `-` `crypto.secureHeapUsed()` not applicable to RN
+ - `-` `crypto.setEngine(engine[, flags])` not applicable to RN
+ - `-` `crypto.setFips(bool)` not applicable to RN
+ - ✅ `crypto.sign(algorithm, data, key[, callback])`
+ - ✅ `crypto.subtle` (see below)
+ - ✅ `crypto.timingSafeEqual(a, b)`
+ - ✅ `crypto.verify(algorithm, data, key, signature[, callback])`
+ - ✅ `crypto.webcrypto` (see below)
## `crypto.diffieHellman`
-| type | Status |
-| --------- | :----: |
-| `dh` | ✅ |
-| `ec` | ✅ |
-| `x448` | ✅ |
-| `x25519` | ✅ |
+
+| type | Status |
+| -------- | :----: |
+| `dh` | ✅ |
+| `ec` | ✅ |
+| `x448` | ✅ |
+| `x25519` | ✅ |
## `crypto.generateKey`
-| type | Status |
-| --------- | :----: |
-| `aes` | ✅ |
-| `hmac` | ✅ |
+
+| type | Status |
+| ------ | :----: |
+| `aes` | ✅ |
+| `hmac` | ✅ |
## `crypto.generateKeyPair`
+
| type | Status |
| --------- | :----: |
-| `rsa` | ✅ |
-| `rsa-pss` | ✅ |
-| `dsa` | ✅ |
-| `ec` | ✅ |
-| `ed25519` | ✅ |
-| `ed448` | ✅ |
-| `x25519` | ✅ |
-| `x448` | ✅ |
-| `dh` | ✅ |
+| `rsa` | ✅ |
+| `rsa-pss` | ✅ |
+| `dsa` | ✅ |
+| `ec` | ✅ |
+| `ed25519` | ✅ |
+| `ed448` | ✅ |
+| `x25519` | ✅ |
+| `x448` | ✅ |
+| `dh` | ✅ |
## `crypto.generateKeyPairSync`
+
| type | Status |
| --------- | :----: |
-| `rsa` | ✅ |
-| `rsa-pss` | ✅ |
-| `dsa` | ✅ |
-| `ec` | ✅ |
-| `ed25519` | ✅ |
-| `ed448` | ✅ |
-| `x25519` | ✅ |
-| `x448` | ✅ |
-| `dh` | ✅ |
+| `rsa` | ✅ |
+| `rsa-pss` | ✅ |
+| `dsa` | ✅ |
+| `ec` | ✅ |
+| `ed25519` | ✅ |
+| `ed448` | ✅ |
+| `x25519` | ✅ |
+| `x448` | ✅ |
+| `dh` | ✅ |
## `crypto.generateKeySync`
-| type | Status |
-| --------- | :----: |
-| `aes` | ✅ |
-| `hmac` | ✅ |
+
+| type | Status |
+| ------ | :----: |
+| `aes` | ✅ |
+| `hmac` | ✅ |
## `crypto.sign`
+
| Algorithm | Status |
-| --------- | :----: |
-| `RSASSA-PKCS1-v1_5` | ✅ |
-| `RSA-PSS` | ✅ |
-| `ECDSA` | ✅ |
-| `Ed25519` | ✅ |
-| `Ed448` | ✅ |
-| `HMAC` | ✅ |
+| ------------------- | :----: |
+| `RSASSA-PKCS1-v1_5` | ✅ |
+| `RSA-PSS` | ✅ |
+| `ECDSA` | ✅ |
+| `Ed25519` | ✅ |
+| `Ed448` | ✅ |
+| `HMAC` | ✅ |
## `crypto.verify`
+
| Algorithm | Status |
-| --------- | :----: |
-| `RSASSA-PKCS1-v1_5` | ✅ |
-| `RSA-PSS` | ✅ |
-| `ECDSA` | ✅ |
-| `Ed25519` | ✅ |
-| `Ed448` | ✅ |
-| `HMAC` | ✅ |
+| ------------------- | :----: |
+| `RSASSA-PKCS1-v1_5` | ✅ |
+| `RSA-PSS` | ✅ |
+| `ECDSA` | ✅ |
+| `Ed25519` | ✅ |
+| `Ed448` | ✅ |
+| `HMAC` | ✅ |
## Extended Ciphers (Beyond Node.js API)
These ciphers are **not available in Node.js** but are provided by RNQC via libsodium for mobile use cases requiring extended nonces.
-| Cipher | Key | Nonce | Tag | AAD | Notes |
-| ------ | :-: | :---: | :-: | :-: | ----- |
-| `xchacha20-poly1305` | 32B | 24B | 16B | ✅ | AEAD with extended nonce |
-| `xsalsa20-poly1305` | 32B | 24B | 16B | ❌ | Authenticated encryption (secretbox) |
-| `xsalsa20` | 32B | 24B | - | - | Stream cipher (no authentication) |
+| Cipher | Key | Nonce | Tag | AAD | Notes |
+| -------------------- | :-: | :---: | :-: | :-: | ------------------------------------ |
+| `xchacha20-poly1305` | 32B | 24B | 16B | ✅ | AEAD with extended nonce |
+| `xsalsa20-poly1305` | 32B | 24B | 16B | ❌ | Authenticated encryption (secretbox) |
+| `xsalsa20` | 32B | 24B | - | - | Stream cipher (no authentication) |
> **Note:** These ciphers require `SODIUM_ENABLED=1` on both iOS and Android.
# `WebCrypto`
-* ✅ Class: `Crypto`
- * ✅ `crypto.subtle`
- * ✅ `crypto.getRandomValues(typedArray)`
- * ✅ `crypto.randomUUID()`
-* ✅ Class: `CryptoKey`
- * ✅ `cryptoKey.algorithm`
- * ✅ `cryptoKey.extractable`
- * ✅ `cryptoKey.type`
- * ✅ `cryptoKey.usages`
-* ✅ Class: `CryptoKeyPair`
- * ✅ `cryptoKeyPair.privateKey`
- * ✅ `cryptoKeyPair.publicKey`
-* 🚧 Class: `CryptoSubtle`
- * (see below)
+- ✅ Class: `Crypto`
+ - ✅ `crypto.subtle`
+ - ✅ `crypto.getRandomValues(typedArray)`
+ - ✅ `crypto.randomUUID()`
+- ✅ Class: `CryptoKey`
+ - ✅ `cryptoKey.algorithm`
+ - ✅ `cryptoKey.extractable`
+ - ✅ `cryptoKey.type`
+ - ✅ `cryptoKey.usages`
+- ✅ Class: `CryptoKeyPair`
+ - ✅ `cryptoKeyPair.privateKey`
+ - ✅ `cryptoKeyPair.publicKey`
+- 🚧 Class: `CryptoSubtle`
+ - (see below)
# `SubtleCrypto`
-* 🚧 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.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.wrapKey(format, key, wrappingKey, wrapAlgo)`
+- 🚧 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.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.wrapKey(format, key, wrappingKey, wrapAlgo)`
## `subtle.decrypt`
-| Algorithm | Status |
-| --------- | :----: |
-| `RSA-OAEP` | ✅ |
-| `AES-CTR` | ✅ |
-| `AES-CBC` | ✅ |
-| `AES-GCM` | ✅ |
-| `AES-OCB` | ✅ |
-| `ChaCha20-Poly1305` | ✅ |
+
+| Algorithm | Status |
+| ------------------- | :----: |
+| `RSA-OAEP` | ✅ |
+| `AES-CTR` | ✅ |
+| `AES-CBC` | ✅ |
+| `AES-GCM` | ✅ |
+| `AES-OCB` | ✅ |
+| `ChaCha20-Poly1305` | ✅ |
## `subtle.deriveBits`
+
| Algorithm | Status |
-| --------- | :----: |
-| `Argon2d` | ✅ |
-| `Argon2i` | ✅ |
-| `Argon2id` | ✅ |
-| `ECDH` | ✅ |
-| `X25519` | ✅ |
-| `X448` | ✅ |
-| `HKDF` | ✅ |
-| `PBKDF2` | ✅ |
+| ---------- | :----: |
+| `Argon2d` | ✅ |
+| `Argon2i` | ✅ |
+| `Argon2id` | ✅ |
+| `ECDH` | ✅ |
+| `X25519` | ✅ |
+| `X448` | ✅ |
+| `HKDF` | ✅ |
+| `PBKDF2` | ✅ |
## `subtle.deriveKey`
+
| Algorithm | Status |
-| --------- | :----: |
-| `Argon2d` | ✅ |
-| `Argon2i` | ✅ |
-| `Argon2id` | ✅ |
-| `ECDH` | ✅ |
-| `HKDF` | ✅ |
-| `PBKDF2` | ✅ |
-| `X25519` | ✅ |
-| `X448` | ✅ |
+| ---------- | :----: |
+| `Argon2d` | ✅ |
+| `Argon2i` | ✅ |
+| `Argon2id` | ✅ |
+| `ECDH` | ✅ |
+| `HKDF` | ✅ |
+| `PBKDF2` | ✅ |
+| `X25519` | ✅ |
+| `X448` | ✅ |
## `subtle.digest`
+
| Algorithm | Status |
-| --------- | :----: |
-| `cSHAKE128` | ❌ |
-| `cSHAKE256` | ❌ |
-| `SHA-1` | ✅ |
-| `SHA-256` | ✅ |
-| `SHA-384` | ✅ |
-| `SHA-512` | ✅ |
-| `SHA3-256` | ❌ |
-| `SHA3-384` | ❌ |
-| `SHA3-512` | ❌ |
+| ----------- | :----: |
+| `cSHAKE128` | ❌ |
+| `cSHAKE256` | ❌ |
+| `SHA-1` | ✅ |
+| `SHA-256` | ✅ |
+| `SHA-384` | ✅ |
+| `SHA-512` | ✅ |
+| `SHA3-256` | ❌ |
+| `SHA3-384` | ❌ |
+| `SHA3-512` | ❌ |
## `subtle.encrypt`
+
| Algorithm | Status |
| ------------------- | :----: |
-| `AES-CTR` | ✅ |
-| `AES-CBC` | ✅ |
-| `AES-GCM` | ✅ |
-| `AES-OCB` | ✅ |
-| `ChaCha20-Poly1305` | ✅ |
-| `RSA-OAEP` | ✅ |
+| `AES-CTR` | ✅ |
+| `AES-CBC` | ✅ |
+| `AES-GCM` | ✅ |
+| `AES-OCB` | ✅ |
+| `ChaCha20-Poly1305` | ✅ |
+| `RSA-OAEP` | ✅ |
## `subtle.exportKey`
+
| 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
-* ✅ - implemented in Node and RNQC
+| `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
+- ✅ - implemented in Node and RNQC
## `subtle.generateKey`
### `CryptoKeyPair` algorithms
+
| Algorithm | Status |
-| --------- | :----: |
-| `ECDH` | ✅ |
-| `ECDSA` | ✅ |
-| `Ed25519` | ✅ |
-| `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` | ✅ |
+| ------------------- | :----: |
+| `ECDH` | ✅ |
+| `ECDSA` | ✅ |
+| `Ed25519` | ✅ |
+| `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 |
-| --------- | :----: |
-| `AES-CTR` | ✅ |
-| `AES-CBC` | ✅ |
-| `AES-GCM` | ✅ |
-| `AES-KW` | ✅ |
-| `AES-OCB` | ✅ |
-| `ChaCha20-Poly1305` | ✅ |
-| `HMAC` | ✅ |
-| `KMAC128` | ❌ |
-| `KMAC256` | ❌ |
+| ------------------- | :----: |
+| `AES-CTR` | ✅ |
+| `AES-CBC` | ✅ |
+| `AES-GCM` | ✅ |
+| `AES-KW` | ✅ |
+| `AES-OCB` | ✅ |
+| `ChaCha20-Poly1305` | ✅ |
+| `HMAC` | ✅ |
+| `KMAC128` | ❌ |
+| `KMAC256` | ❌ |
## `subtle.importKey`
+
| Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` |
| ------------------- | :----: | :-----: | :---: | :---: | :----------: | :----------: | :--------: |
-| `Argon2d` | | | | | ✅ | | |
-| `Argon2i` | | | | | ✅ | | |
-| `Argon2id` | | | | | ✅ | | |
-| `AES-CBC` | | | ✅ | ✅ | ✅ | | |
-| `AES-CTR` | | | ✅ | ✅ | ✅ | | |
-| `AES-GCM` | | | ✅ | ✅ | ✅ | | |
-| `AES-KW` | | | ✅ | ✅ | ✅ | | |
-| `AES-OCB` | | | ✅ | ✅ | ✅ | | |
-| `ChaCha20-Poly1305` | | | ✅ | | ✅ | | |
-| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
-| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
-| `Ed25519` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
-| `Ed448` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
-| `HKDF` | | | | ✅ | ✅ | | |
-| `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` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `Argon2d` | | | | | ✅ | | |
+| `Argon2i` | | | | | ✅ | | |
+| `Argon2id` | | | | | ✅ | | |
+| `AES-CBC` | | | ✅ | ✅ | ✅ | | |
+| `AES-CTR` | | | ✅ | ✅ | ✅ | | |
+| `AES-GCM` | | | ✅ | ✅ | ✅ | | |
+| `AES-KW` | | | ✅ | ✅ | ✅ | | |
+| `AES-OCB` | | | ✅ | ✅ | ✅ | | |
+| `ChaCha20-Poly1305` | | | ✅ | | ✅ | | |
+| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `Ed25519` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `Ed448` | ✅ | ✅ | ✅ | ✅ | | ✅ | |
+| `HKDF` | | | | ✅ | ✅ | | |
+| `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 |
-| --------- | :----: |
-| `ECDSA` | ✅ |
-| `Ed25519` | ✅ |
-| `Ed448` | ✅ |
-| `HMAC` | ✅ |
-| `KMAC128` | ❌ |
-| `KMAC256` | ❌ |
-| `ML-DSA-44` | ✅ |
-| `ML-DSA-65` | ✅ |
-| `ML-DSA-87` | ✅ |
-| `RSA-PSS` | ✅ |
-| `RSASSA-PKCS1-v1_5` | ✅ |
+| ------------------- | :----: |
+| `ECDSA` | ✅ |
+| `Ed25519` | ✅ |
+| `Ed448` | ✅ |
+| `HMAC` | ✅ |
+| `KMAC128` | ❌ |
+| `KMAC256` | ❌ |
+| `ML-DSA-44` | ✅ |
+| `ML-DSA-65` | ✅ |
+| `ML-DSA-87` | ✅ |
+| `RSA-PSS` | ✅ |
+| `RSASSA-PKCS1-v1_5` | ✅ |
## `subtle.unwrapKey`
### wrapping algorithms
+
| Algorithm | Status |
| ------------------- | :----: |
-| `AES-CBC` | ✅ |
-| `AES-CTR` | ✅ |
-| `AES-GCM` | ✅ |
-| `AES-KW` | ✅ |
-| `AES-OCB` | ✅ |
-| `ChaCha20-Poly1305` | ✅ |
-| `RSA-OAEP` | ✅ |
+| `AES-CBC` | ✅ |
+| `AES-CTR` | ✅ |
+| `AES-GCM` | ✅ |
+| `AES-KW` | ✅ |
+| `AES-OCB` | ✅ |
+| `ChaCha20-Poly1305` | ✅ |
+| `RSA-OAEP` | ✅ |
### unwrapped key algorithms
+
| Algorithm | Status |
-| --------- | :----: |
-| `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` | ✅ |
+| ------------------- | :----: |
+| `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 |
-| --------- | :----: |
-| `ECDSA` | ✅ |
-| `Ed25519` | ✅ |
-| `Ed448` | ✅ |
-| `HMAC` | ✅ |
-| `KMAC128` | ❌ |
-| `KMAC256` | ❌ |
-| `ML-DSA-44` | ✅ |
-| `ML-DSA-65` | ✅ |
-| `ML-DSA-87` | ✅ |
-| `RSA-PSS` | ✅ |
-| `RSASSA-PKCS1-v1_5` | ✅ |
+| ------------------- | :----: |
+| `ECDSA` | ✅ |
+| `Ed25519` | ✅ |
+| `Ed448` | ✅ |
+| `HMAC` | ✅ |
+| `KMAC128` | ❌ |
+| `KMAC256` | ❌ |
+| `ML-DSA-44` | ✅ |
+| `ML-DSA-65` | ✅ |
+| `ML-DSA-87` | ✅ |
+| `RSA-PSS` | ✅ |
+| `RSASSA-PKCS1-v1_5` | ✅ |
## `subtle.wrapKey`
### wrapping algorithms
+
| Algorithm | Status |
| ------------------- | :----: |
-| `AES-CBC` | ✅ |
-| `AES-CTR` | ✅ |
-| `AES-GCM` | ✅ |
-| `AES-KW` | ✅ |
-| `AES-OCB` | ✅ |
-| `ChaCha20-Poly1305` | ✅ |
-| `RSA-OAEP` | ✅ |
+| `AES-CBC` | ✅ |
+| `AES-CTR` | ✅ |
+| `AES-GCM` | ✅ |
+| `AES-KW` | ✅ |
+| `AES-OCB` | ✅ |
+| `ChaCha20-Poly1305` | ✅ |
+| `RSA-OAEP` | ✅ |
diff --git a/docs/content/docs/api/meta.json b/docs/content/docs/api/meta.json
index 970d0711..a2e4696c 100644
--- a/docs/content/docs/api/meta.json
+++ b/docs/content/docs/api/meta.json
@@ -1,23 +1,24 @@
{
- "title": "API Reference",
- "defaultOpen": true,
- "pages": [
- "index",
- "install",
- "cipher",
- "hash",
- "hmac",
- "random",
- "keys",
- "signing",
- "public-cipher",
- "diffie-hellman",
- "ecdh",
- "ed25519",
- "pbkdf2",
- "scrypt",
- "hkdf",
- "blake3",
- "subtle"
- ]
-}
\ No newline at end of file
+ "title": "API Reference",
+ "defaultOpen": true,
+ "pages": [
+ "index",
+ "install",
+ "cipher",
+ "hash",
+ "hmac",
+ "random",
+ "keys",
+ "signing",
+ "public-cipher",
+ "diffie-hellman",
+ "ecdh",
+ "ed25519",
+ "pbkdf2",
+ "scrypt",
+ "hkdf",
+ "blake3",
+ "x509",
+ "subtle"
+ ]
+}
diff --git a/docs/content/docs/api/x509.mdx b/docs/content/docs/api/x509.mdx
new file mode 100644
index 00000000..584157a0
--- /dev/null
+++ b/docs/content/docs/api/x509.mdx
@@ -0,0 +1,297 @@
+---
+title: X509 Certificates
+description: Parse, inspect, and validate X.509 certificates
+---
+
+import { Callout } from 'fumadocs-ui/components/callout';
+import { TypeTable } from 'fumadocs-ui/components/type-table';
+
+The `X509Certificate` class provides a complete implementation for working with X.509 certificates — the standard format used in TLS/SSL, code signing, and PKI systems. Parse certificates, extract properties, validate hostnames, and verify signatures.
+
+
+ **Certificate pinning** in mobile apps, **mTLS client certificate**
+ validation, **certificate chain verification**, **hostname matching** for
+ custom TLS implementations, and **extracting public keys** from certificates.
+
+
+## Table of Contents
+
+- [Theory](#theory)
+- [Class: X509Certificate](#class-x509certificate)
+- [Properties](#properties)
+- [Methods](#methods)
+- [Real-World Examples](#real-world-examples)
+
+## Theory
+
+X.509 is the standard format for public key certificates. A certificate binds an identity (subject) to a public key, signed by a Certificate Authority (CA).
+
+Key concepts:
+
+1. **Subject / Issuer**: Distinguished Names identifying the certificate holder and signer.
+2. **Validity Period**: Time window during which the certificate is valid.
+3. **Subject Alternative Name (SAN)**: Additional identities (DNS names, IPs, emails) the certificate is valid for.
+4. **Fingerprint**: A hash of the certificate used for identification (not security).
+5. **CA flag**: Whether the certificate can sign other certificates.
+
+---
+
+## Class: X509Certificate
+
+### Constructor
+
+```ts
+import { X509Certificate } from 'react-native-quick-crypto';
+
+const cert = new X509Certificate(pemString);
+```
+
+**Parameters:**
+
+
+
+Accepts both PEM-encoded strings (beginning with `-----BEGIN CERTIFICATE-----`) and DER-encoded binary data.
+
+---
+
+## Properties
+
+All properties are lazily computed and cached on first access.
+
+| Property | Type | Description |
+| :---------------------- | :---------- | :--------------------------------------------------------- |
+| `subject` | `string` | Distinguished name of the certificate subject |
+| `issuer` | `string` | Distinguished name of the issuing CA |
+| `subjectAltName` | `string` | Subject Alternative Name extension |
+| `infoAccess` | `string` | Authority Information Access extension |
+| `validFrom` | `string` | "Not Before" date as a string |
+| `validTo` | `string` | "Not After" date as a string |
+| `validFromDate` | `Date` | "Not Before" as a JavaScript Date object |
+| `validToDate` | `Date` | "Not After" as a JavaScript Date object |
+| `serialNumber` | `string` | Certificate serial number (uppercase hex) |
+| `signatureAlgorithm` | `string` | Signature algorithm name (e.g., `sha256WithRSAEncryption`) |
+| `signatureAlgorithmOid` | `string` | Signature algorithm OID |
+| `fingerprint` | `string` | SHA-1 fingerprint (colon-separated hex) |
+| `fingerprint256` | `string` | SHA-256 fingerprint (colon-separated hex) |
+| `fingerprint512` | `string` | SHA-512 fingerprint (colon-separated hex) |
+| `extKeyUsage` | `string[]` | Extended key usage OIDs (also available as `keyUsage`) |
+| `ca` | `boolean` | Whether this is a CA certificate |
+| `raw` | `Buffer` | Raw DER-encoded certificate bytes |
+| `publicKey` | `KeyObject` | The certificate's public key as a KeyObject |
+| `issuerCertificate` | `undefined` | Always `undefined` (no TLS context in React Native) |
+
+```ts
+const cert = new X509Certificate(pemString);
+
+console.log(cert.subject);
+// C=US\nST=California\nO=Example\nCN=example.com
+
+console.log(cert.fingerprint256);
+// AB:CD:EF:12:34:...
+
+console.log(cert.ca);
+// true
+
+console.log(cert.publicKey.type);
+// 'public'
+```
+
+---
+
+## Methods
+
+### x509.checkHost(name[, options])
+
+Checks whether the certificate matches the given hostname.
+
+
+
+**Returns:** `string | undefined` — The matched hostname, or `undefined` if no match.
+
+```ts
+const cert = new X509Certificate(pemString);
+
+cert.checkHost('example.com'); // 'example.com'
+cert.checkHost('wrong.com'); // undefined
+
+// Disable wildcard matching
+cert.checkHost('sub.example.com', { wildcards: false });
+```
+
+#### CheckOptions
+
+| Option | Type | Default | Description |
+| :---------------------- | :--------------------------------- | :---------- | :---------------------------------- |
+| `subject` | `'default' \| 'always' \| 'never'` | `'default'` | When to check the subject CN |
+| `wildcards` | `boolean` | `true` | Allow wildcard certificate matching |
+| `partialWildcards` | `boolean` | `true` | Allow partial wildcard matching |
+| `multiLabelWildcards` | `boolean` | `false` | Allow multi-label wildcard matching |
+| `singleLabelSubdomains` | `boolean` | `false` | Match single-label subdomains |
+
+### x509.checkEmail(email[, options])
+
+Checks whether the certificate matches the given email address.
+
+**Returns:** `string | undefined` — The matched email, or `undefined` if no match.
+
+```ts
+cert.checkEmail('user@example.com'); // 'user@example.com' or undefined
+```
+
+### x509.checkIP(ip)
+
+Checks whether the certificate matches the given IP address.
+
+**Returns:** `string | undefined` — The matched IP, or `undefined` if no match.
+
+```ts
+cert.checkIP('127.0.0.1'); // '127.0.0.1'
+cert.checkIP('192.168.1.1'); // undefined
+```
+
+### x509.checkIssued(otherCert)
+
+Checks whether this certificate was issued by `otherCert`.
+
+**Returns:** `boolean`
+
+```ts
+// Self-signed certificate
+cert.checkIssued(cert); // true
+
+// Chain validation
+rootCert.checkIssued(intermediateCert); // true or false
+```
+
+### x509.checkPrivateKey(privateKey)
+
+Checks whether the given private key matches this certificate's public key.
+
+**Returns:** `boolean`
+
+```ts
+import { createPrivateKey } from 'react-native-quick-crypto';
+
+const privKey = createPrivateKey(privateKeyPem);
+cert.checkPrivateKey(privKey); // true
+```
+
+### x509.verify(publicKey)
+
+Verifies that the certificate was signed with the given public key.
+
+**Returns:** `boolean`
+
+```ts
+// For self-signed certificates
+cert.verify(cert.publicKey); // true
+```
+
+### x509.toString()
+
+Returns the PEM-encoded certificate string.
+
+**Returns:** `string`
+
+### x509.toJSON()
+
+Returns the PEM-encoded certificate string (same as `toString()`).
+
+**Returns:** `string`
+
+### x509.toLegacyObject()
+
+Returns a plain object with legacy certificate fields.
+
+**Returns:** `object`
+
+---
+
+## Real-World Examples
+
+### Certificate Pinning
+
+```ts
+import { X509Certificate } from 'react-native-quick-crypto';
+
+const PINNED_FINGERPRINT = 'AB:CD:EF:...';
+
+function validateServerCert(pemCert: string): boolean {
+ const cert = new X509Certificate(pemCert);
+
+ // Check fingerprint
+ if (cert.fingerprint256 !== PINNED_FINGERPRINT) {
+ return false;
+ }
+
+ // Check validity
+ const now = new Date();
+ if (now < cert.validFromDate || now > cert.validToDate) {
+ return false;
+ }
+
+ return true;
+}
+```
+
+### Hostname Verification
+
+```ts
+import { X509Certificate } from 'react-native-quick-crypto';
+
+function verifyHostname(pemCert: string, hostname: string): boolean {
+ const cert = new X509Certificate(pemCert);
+ return cert.checkHost(hostname) !== undefined;
+}
+```
+
+### Extract Public Key from Certificate
+
+```ts
+import { X509Certificate } from 'react-native-quick-crypto';
+
+const cert = new X509Certificate(pemCert);
+const publicKey = cert.publicKey;
+
+// Use the public key for encryption or verification
+console.log(publicKey.type); // 'public'
+console.log(publicKey.asymmetricKeyType); // 'rsa'
+```
+
+### Validate Certificate Chain
+
+```ts
+import { X509Certificate } from 'react-native-quick-crypto';
+
+function validateChain(leafPem: string, issuerPem: string): boolean {
+ const leaf = new X509Certificate(leafPem);
+ const issuerCert = new X509Certificate(issuerPem);
+
+ // Check the leaf was issued by the issuer
+ if (!issuerCert.checkIssued(leaf)) {
+ return false;
+ }
+
+ // Verify the leaf's signature with issuer's public key
+ if (!leaf.verify(issuerCert.publicKey)) {
+ return false;
+ }
+
+ return true;
+}
+```
diff --git a/docs/data/coverage.ts b/docs/data/coverage.ts
index ebd7a85a..33f8110c 100644
--- a/docs/data/coverage.ts
+++ b/docs/data/coverage.ts
@@ -130,7 +130,36 @@ export const COVERAGE_DATA: CoverageCategory[] = [
},
{
name: 'X509Certificate',
- status: 'missing',
+ subItems: [
+ { name: 'new X509Certificate(buffer)', status: 'implemented' },
+ { name: 'ca', status: 'implemented' },
+ { name: 'checkEmail', status: 'implemented' },
+ { name: 'checkHost', status: 'implemented' },
+ { name: 'checkIP', status: 'implemented' },
+ { name: 'checkIssued', status: 'implemented' },
+ { name: 'checkPrivateKey', status: 'implemented' },
+ { name: 'fingerprint', status: 'implemented' },
+ { name: 'fingerprint256', status: 'implemented' },
+ { name: 'fingerprint512', status: 'implemented' },
+ { name: 'infoAccess', status: 'implemented' },
+ { name: 'issuer', status: 'implemented' },
+ { name: 'issuerCertificate', status: 'implemented' },
+ { name: 'extKeyUsage', status: 'implemented' },
+ { name: 'keyUsage', status: 'implemented' },
+ { name: 'signatureAlgorithm', status: 'implemented' },
+ { name: 'signatureAlgorithmOid', status: 'implemented' },
+ { name: 'publicKey', status: 'implemented' },
+ { name: 'raw', status: 'implemented' },
+ { name: 'serialNumber', status: 'implemented' },
+ { name: 'subject', status: 'implemented' },
+ { name: 'subjectAltName', status: 'implemented' },
+ { name: 'toJSON', status: 'implemented' },
+ { name: 'toLegacyObject', status: 'implemented' },
+ { name: 'toString', status: 'implemented' },
+ { name: 'validFrom', status: 'implemented' },
+ { name: 'validTo', status: 'implemented' },
+ { name: 'verify', status: 'implemented' },
+ ],
},
],
},
diff --git a/example/src/hooks/useTestsList.ts b/example/src/hooks/useTestsList.ts
index 100c12a5..88b3cdcc 100644
--- a/example/src/hooks/useTestsList.ts
+++ b/example/src/hooks/useTestsList.ts
@@ -43,6 +43,7 @@ import '../tests/subtle/supports';
import '../tests/subtle/getPublicKey';
import '../tests/subtle/wrap_unwrap';
import '../tests/utils/utils_tests';
+import '../tests/x509/x509_tests';
export const useTestsList = (): [
TestSuites,
diff --git a/example/src/tests/x509/x509_tests.ts b/example/src/tests/x509/x509_tests.ts
new file mode 100644
index 00000000..6cb33520
--- /dev/null
+++ b/example/src/tests/x509/x509_tests.ts
@@ -0,0 +1,310 @@
+import { test } from '../util';
+import { assert } from 'chai';
+import {
+ X509Certificate,
+ createPrivateKey,
+ generateKeyPairSync,
+ Buffer,
+} from 'react-native-quick-crypto';
+
+const SUITE = 'x509';
+
+const certPem = `-----BEGIN CERTIFICATE-----
+MIIEgDCCA2igAwIBAgIUYX7QpAhywlWSvMIGfIhcXyF1S6kwDQYJKoZIhvcNAQEL
+BQAwezELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
+DVNhbiBGcmFuY2lzY28xEjAQBgNVBAoMCVJOUUMgVGVzdDEQMA4GA1UECwwHVGVz
+dGluZzEZMBcGA1UEAwwQdGVzdC5leGFtcGxlLmNvbTAgFw0yNjAyMTYyMjM5MTRa
+GA8yMTI2MDEyMzIyMzkxNFowezELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlm
+b3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEjAQBgNVBAoMCVJOUUMgVGVz
+dDEQMA4GA1UECwwHVGVzdGluZzEZMBcGA1UEAwwQdGVzdC5leGFtcGxlLmNvbTCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMNdAMBDbRU6Sowe7xs+N2Lr
+lrLXYjMjOxIm3ycfuQCK4EpmaJ+WLNctTF8DP7bfo9U0ItJawAbFdMVLWKOSzHmb
+ZpCpB9qUSycBOdKgcepgHm9seoF8IdQWSXF5MNx73e6KOITPNfQ1XAQ/bcNMQ52Z
+rDQBj/Usu4+VOKiL+9sjFoP8z2MLhHKrVcmuJFLmZek84wWT5zkbaBSRC4ZP6xTk
+wITP5OGGmpTliZ1ZfvZ1bce+H0pPiDDJB1P1sOFhUW+f9eABUQnNUB95XnqY78Sd
+zhwvgYLsBZIMFCu8tLv6TT/kp2eqIPnr7KVSI6PqVA2KeYaIzAtcJCrfyj59/0EC
+AwEAAaOB+TCB9jAdBgNVHQ4EFgQUyvtMod1JR/MyOywSthOoSzudfckwHwYDVR0j
+BBgwFoAUyvtMod1JR/MyOywSthOoSzudfckwQgYDVR0RBDswOYIQdGVzdC5leGFt
+cGxlLmNvbYINKi5leGFtcGxlLmNvbYcEfwAAAYEQdGVzdEBleGFtcGxlLmNvbTAP
+BgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwICpDAdBgNVHSUEFjAUBggrBgEFBQcD
+AQYIKwYBBQUHAwIwMwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdodHRwOi8v
+b2NzcC5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEARipMQsNnagHHLQxz
+zSbiKKB6Qrxt0k4IwEIyIKb4daZaXw9viMkS9ULm0uHmO7HcOr6wUYdmv+swFsC
+yu5E8ZgFqZHJGw62Yi6fhNSloaLNN9rYnOZfUj0aWnN0OA8vClfNom/vYTe4kENU
+VTDP1dkPbo12jWJ4bOhchW28GSjU7heosi8tNsFr5H7cdAwXKnOmU0MqeJ+dHCda
+1MiZWlDTeV2q8HRIKPuH5xmwgVZO3U7C85NekB7tZIvf5fArvKPRQ0/mzcvk+F6A
+/tQwqNjZv+XgUNnZJkUkYAQ5nJg50Osf1oxR182oAjR2yqXL3qBUfg563wgleVFY
+KxJhZsg==
+-----END CERTIFICATE-----`;
+
+const privKeyPem = `-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDXQDAQ20VOkqM
+Hu8bPjdi65ay12IzIzsSJt8nH7kAiuBKZmiflizXLUxfAz+236PVNCLSWsAGxXTF
+S1ijksx5m2aQqQfalEsnATnSoHHqYB5vbHqBfCHUFklxeTDce93uijiEzzX0NVwE
+P23DTEOdmaw0AY/1LLuPlTioi/vbIxaD/M9jC4Ryq1XJriRS5mXpPOMFk+c5G2gU
+kQuGT+sU5MCEz+ThhpqU5YmdWX72dW3Hvh9KT4gwyQdT9bDhYVFvn/XgAVEJzVAf
+eV56mO/Enc4cL4GC7AWSDBQrvLS7+k0/5KdnqiD56+ylUiOj6lQNinmGiMwLXCQq
+38o+ff9BAgMBAAECggEAG8LeBfQu4+WAzWY3QmRLbjudvQkyk6NjKVervegEm96g
+M60CG1dgRNgo3OwzGXNbLm32WspO35zJ1KAPLE4CehoKmkONcafsAVLBGudLeMDd
+jPuEcbJS2PatILpWJqaqvqhr5/d3/8gLV4aEkaGHOYsqTMjst4F6n6iBQMuE57qA
++ubXy8nQD7ufSKPIxdD4jHg226m1FjiKnDArD4H1iIHC1xdt7E5FG5KRzwPFXU4B
+kwSqh9GFFS7JdSoqwa+8MVZP2IHGrwpIkUnVEedgkA+pbVXFVfKvfOUUQ01t14Tc
+OjT819vIj2av+yDfW5q0fUWdOQrs7ZLsVBLrazJnzwKBgQDiYCdeZCFh2T8GdYd8
+ZDnpLaMrFSJAYiRXbNo1aKhLGE5gee9t0dLw5wM0ARBlXb/kCFnK5HmPi9CxukuQ
+EbCqIQ1+NsQNGS307QXSoQjT38uwrlp9tROb8RdaTE9xBc9gJVEcgmPDnrY0zesE
+9DrUfMR7auqyYzhWSwzuPdkvdwKBgQDc7eaVoDGf0cLfQ0ezOj5NLuLve+lza0Tf
+YlC2csTpzfn0GPGZdj1MOvUFYIk/r1mPDWT0vJbtlpn73lVGypKgDEAWUYvVLaKW
+escMQN9Xmf1a+0Cxi+rIFUhjkLdSAdyzGawl+7rXmDXNyFx5DWAUhQTfrF2gLbZ3
+Cpgf9zqlBwKBgHsVDrK6vI/IIAVyB51xnS8UOkBleD8LXXkPXUFmywIxkAPSqITM
+beW/pTU0UubaZ0gj5jZzrUiIG4tWoFkP1T9bQ0vZmRUKGLuv19ei6PrSFpzU36yz
+tJq4JhtZnGP2Zb9/6q8Wkgm9lJH3WA5UgFwiDm6QPlWJrwr0OW6bwCeXAoGBAMBH
+VR34M/hSiXXiim6UTFDEc8HWaFGJlIGOgYyoynRqThaB9xOG8sZ7sXAimpEQvbNh
+BvJxiDHzlsS8th9MgtxEjSpfgoHgm9a3uLETbM5DOVuLvLxJd+b3ju8IrmPzNu+x
+ckAEnJKy6HDW5pR8bZiuRJWe4EVeQ6XLVKbNdv7VAoGAPjEGKlGP+AVbrMQIQfEL
+8AiNUKgQsaxAAQfwY3VC0kO9KoLDda/Gcq1CL3stZQplQMl0fKcNilXvHxqR+9UF
+gtrj/IA4TEfNQrONszLvU5zJl4ENNLsZcEcUDVOXA+3WaNn2UZhJy8+Te8pXLhjI
+FRFj+ZJzGB1ap637vnnRI+U=
+-----END PRIVATE KEY-----`;
+
+const expectedSha1 =
+ 'EE:E2:BF:1F:B5:0C:AF:E4:AC:27:B7:88:2F:62:55:0D:A3:46:6F:94';
+const expectedSha256 =
+ '0A:71:7E:E8:7B:1C:C3:A7:2D:93:E5:13:DA:B9:69:99:D3:06:56:C8:66:62:EB:A3:F6:BF:87:75:64:2F:C3:11';
+const expectedSha512 =
+ 'BE:57:03:CD:09:14:7A:56:CB:CF:BF:57:FF:68:3A:EE:93:10:B3:04:39:C3:A9:99:1F:1B:4C:89:A2:1E:76:3B:96:49:79:6A:62:F1:F2:04:A9:6E:26:8A:0A:A4:4C:DF:C1:E9:39:25:8F:C1:D6:80:9A:20:B6:E7:2C:DC:6D:42';
+
+// --- Construction ---
+
+test(SUITE, 'constructs from PEM string', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.isOk(x509);
+});
+
+test(SUITE, 'constructs from Buffer', () => {
+ const x509 = new X509Certificate(Buffer.from(certPem));
+ assert.isOk(x509);
+});
+
+test(SUITE, 'throws on invalid input', () => {
+ assert.throws(() => {
+ new X509Certificate('invalid');
+ });
+});
+
+// --- String properties ---
+
+test(SUITE, 'subject contains CN', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.include(x509.subject, 'test.example.com');
+});
+
+test(SUITE, 'issuer matches subject (self-signed)', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.strictEqual(x509.subject, x509.issuer);
+});
+
+test(SUITE, 'subjectAltName contains DNS entries', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.include(x509.subjectAltName, 'test.example.com');
+});
+
+test(SUITE, 'subjectAltName contains IP', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.include(x509.subjectAltName, '127.0.0.1');
+});
+
+test(SUITE, 'subjectAltName contains email', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.include(x509.subjectAltName, 'test@example.com');
+});
+
+test(SUITE, 'infoAccess contains OCSP URI', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.include(x509.infoAccess, 'ocsp.example.com');
+});
+
+test(SUITE, 'validFrom is a date string', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.include(x509.validFrom, 'Feb 16');
+ assert.include(x509.validFrom, '2026');
+ assert.include(x509.validFrom, 'GMT');
+});
+
+test(SUITE, 'validTo is a date string', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.include(x509.validTo, 'Jan 23');
+ assert.include(x509.validTo, '2126');
+ assert.include(x509.validTo, 'GMT');
+});
+
+test(SUITE, 'serialNumber is hex string', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.strictEqual(
+ x509.serialNumber,
+ '617ED0A40872C25592BCC2067C885C5F21754BA9',
+ );
+});
+
+test(SUITE, 'signatureAlgorithm returns algorithm name', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.include(x509.signatureAlgorithm.toLowerCase(), 'sha256');
+});
+
+// --- Fingerprints ---
+
+test(SUITE, 'fingerprint returns SHA-1 colon hex', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.strictEqual(x509.fingerprint, expectedSha1);
+});
+
+test(SUITE, 'fingerprint256 returns SHA-256 colon hex', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.strictEqual(x509.fingerprint256, expectedSha256);
+});
+
+test(SUITE, 'fingerprint512 returns SHA-512 colon hex', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.strictEqual(x509.fingerprint512, expectedSha512);
+});
+
+// --- Date properties ---
+
+test(SUITE, 'validFromDate returns Date object', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.instanceOf(x509.validFromDate, Date);
+ assert.strictEqual(x509.validFromDate.getUTCFullYear(), 2026);
+});
+
+test(SUITE, 'validToDate returns Date object', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.instanceOf(x509.validToDate, Date);
+ assert.strictEqual(x509.validToDate.getUTCFullYear(), 2126);
+});
+
+// --- Key & CA ---
+
+test(SUITE, 'ca returns true for CA certificate', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.isTrue(x509.ca);
+});
+
+test(SUITE, 'publicKey returns a key object', () => {
+ const x509 = new X509Certificate(certPem);
+ const pk = x509.publicKey;
+ assert.strictEqual(pk.type, 'public');
+});
+
+test(SUITE, 'keyUsage returns array of strings', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.isArray(x509.keyUsage);
+ assert.isAbove(x509.keyUsage.length, 0);
+});
+
+// --- Raw/PEM ---
+
+test(SUITE, 'raw returns DER Buffer', () => {
+ const x509 = new X509Certificate(certPem);
+ const raw = x509.raw;
+ assert.isTrue(Buffer.isBuffer(raw));
+ assert.isAbove(raw.length, 0);
+});
+
+test(SUITE, 'toString returns PEM string', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.include(x509.toString(), '-----BEGIN CERTIFICATE-----');
+});
+
+test(SUITE, 'toJSON returns same as toString', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.strictEqual(x509.toJSON(), x509.toString());
+});
+
+// --- Name checks ---
+
+test(SUITE, 'checkHost matches exact hostname', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.strictEqual(x509.checkHost('test.example.com'), 'test.example.com');
+});
+
+test(SUITE, 'checkHost returns undefined for non-matching', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.isUndefined(x509.checkHost('other.domain.com'));
+});
+
+test(SUITE, 'checkHost matches wildcard', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.ok(x509.checkHost('sub.example.com'));
+});
+
+test(SUITE, 'checkEmail matches', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.strictEqual(x509.checkEmail('test@example.com'), 'test@example.com');
+});
+
+test(SUITE, 'checkEmail returns undefined for non-matching', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.isUndefined(x509.checkEmail('wrong@example.com'));
+});
+
+test(SUITE, 'checkIP matches 127.0.0.1', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.strictEqual(x509.checkIP('127.0.0.1'), '127.0.0.1');
+});
+
+test(SUITE, 'checkIP returns undefined for non-matching', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.isUndefined(x509.checkIP('192.168.1.1'));
+});
+
+// --- Verification ---
+
+test(SUITE, 'verify with matching public key returns true', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.isTrue(x509.verify(x509.publicKey));
+});
+
+test(SUITE, 'checkPrivateKey with matching key returns true', () => {
+ const x509 = new X509Certificate(certPem);
+ const privKey = createPrivateKey(privKeyPem);
+ assert.isTrue(x509.checkPrivateKey(privKey));
+});
+
+test(SUITE, 'checkPrivateKey with non-matching key returns false', () => {
+ const x509 = new X509Certificate(certPem);
+ const { privateKey } = generateKeyPairSync('rsa', {
+ modulusLength: 2048,
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
+ });
+ const otherKey = createPrivateKey(privateKey as string);
+ assert.isFalse(x509.checkPrivateKey(otherKey));
+});
+
+// --- Cross-cert ---
+
+test(SUITE, 'checkIssued returns true for self-signed', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.isTrue(x509.checkIssued(x509));
+});
+
+test(SUITE, 'issuerCertificate returns undefined', () => {
+ const x509 = new X509Certificate(certPem);
+ assert.isUndefined(x509.issuerCertificate);
+});
+
+// --- Serialization ---
+
+test(SUITE, 'toLegacyObject returns object with expected fields', () => {
+ const x509 = new X509Certificate(certPem);
+ const obj = x509.toLegacyObject();
+ assert.isObject(obj);
+ assert.property(obj, 'subject');
+ assert.property(obj, 'issuer');
+ assert.property(obj, 'serialNumber');
+ assert.property(obj, 'fingerprint');
+ assert.property(obj, 'fingerprint256');
+ assert.property(obj, 'fingerprint512');
+ assert.property(obj, 'valid_from');
+ assert.property(obj, 'valid_to');
+ assert.property(obj, 'raw');
+});
diff --git a/packages/react-native-quick-crypto/android/CMakeLists.txt b/packages/react-native-quick-crypto/android/CMakeLists.txt
index 823aaeee..d947a058 100644
--- a/packages/react-native-quick-crypto/android/CMakeLists.txt
+++ b/packages/react-native-quick-crypto/android/CMakeLists.txt
@@ -57,6 +57,7 @@ add_library(
../cpp/scrypt/HybridScrypt.cpp
../cpp/sign/HybridSignHandle.cpp
../cpp/sign/HybridVerifyHandle.cpp
+ ../cpp/x509/HybridX509Certificate.cpp
../cpp/utils/HybridUtils.cpp
../cpp/utils/QuickCryptoUtils.cpp
${BLAKE3_SOURCES}
@@ -93,6 +94,7 @@ include_directories(
"../cpp/sign"
"../cpp/scrypt"
"../cpp/utils"
+ "../cpp/x509"
"../deps/blake3/c"
"../deps/fastpbkdf2"
"../deps/ncrypto/include"
diff --git a/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.hpp b/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.hpp
index 21d64696..e92dfd75 100644
--- a/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.hpp
+++ b/packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.hpp
@@ -39,13 +39,14 @@ class HybridKeyObjectHandle : public HybridKeyObjectHandleSpec {
double getSymmetricKeySize() override;
- KeyObjectData& getKeyObjectData() {
- return data_;
- }
const KeyObjectData& getKeyObjectData() const {
return data_;
}
+ void setKeyObjectData(KeyObjectData data) {
+ data_ = std::move(data);
+ }
+
private:
KeyObjectData data_;
diff --git a/packages/react-native-quick-crypto/cpp/keys/KeyObjectData.hpp b/packages/react-native-quick-crypto/cpp/keys/KeyObjectData.hpp
index 282b59db..f597ec4f 100644
--- a/packages/react-native-quick-crypto/cpp/keys/KeyObjectData.hpp
+++ b/packages/react-native-quick-crypto/cpp/keys/KeyObjectData.hpp
@@ -1,3 +1,5 @@
+#pragma once
+
#include
#include
diff --git a/packages/react-native-quick-crypto/cpp/x509/HybridX509Certificate.cpp b/packages/react-native-quick-crypto/cpp/x509/HybridX509Certificate.cpp
new file mode 100644
index 00000000..0503cf3d
--- /dev/null
+++ b/packages/react-native-quick-crypto/cpp/x509/HybridX509Certificate.cpp
@@ -0,0 +1,174 @@
+#include "HybridX509Certificate.hpp"
+#include "../keys/HybridKeyObjectHandle.hpp"
+#include "../keys/KeyObjectData.hpp"
+#include "QuickCryptoUtils.hpp"
+#include
+
+namespace margelo::nitro::crypto {
+
+std::string HybridX509Certificate::bioToString(ncrypto::BIOPointer bio) const {
+ if (!bio)
+ return "";
+ BUF_MEM* mem = bio;
+ if (!mem || mem->length == 0)
+ return "";
+ return std::string(mem->data, mem->length);
+}
+
+void HybridX509Certificate::init(const std::shared_ptr& buffer) {
+ ncrypto::Buffer buf{.data = reinterpret_cast(buffer->data()), .len = buffer->size()};
+ auto result = ncrypto::X509Pointer::Parse(buf);
+ if (!result) {
+ throw std::runtime_error("Failed to parse X509 certificate");
+ }
+ cert_ = std::move(result.value);
+}
+
+std::string HybridX509Certificate::subject() {
+ return bioToString(cert_.view().getSubject());
+}
+
+std::string HybridX509Certificate::subjectAltName() {
+ return bioToString(cert_.view().getSubjectAltName());
+}
+
+std::string HybridX509Certificate::issuer() {
+ return bioToString(cert_.view().getIssuer());
+}
+
+std::string HybridX509Certificate::infoAccess() {
+ return bioToString(cert_.view().getInfoAccess());
+}
+
+std::string HybridX509Certificate::validFrom() {
+ return bioToString(cert_.view().getValidFrom());
+}
+
+std::string HybridX509Certificate::validTo() {
+ return bioToString(cert_.view().getValidTo());
+}
+
+double HybridX509Certificate::validFromDate() {
+ return static_cast(cert_.view().getValidFromTime()) * 1000.0;
+}
+
+double HybridX509Certificate::validToDate() {
+ return static_cast(cert_.view().getValidToTime()) * 1000.0;
+}
+
+std::string HybridX509Certificate::signatureAlgorithm() {
+ auto algo = cert_.view().getSignatureAlgorithm();
+ if (!algo.has_value())
+ return "";
+ return std::string(algo.value());
+}
+
+std::string HybridX509Certificate::signatureAlgorithmOid() {
+ return cert_.view().getSignatureAlgorithmOID().value_or("");
+}
+
+std::string HybridX509Certificate::serialNumber() {
+ auto serial = cert_.view().getSerialNumber();
+ if (!serial)
+ return "";
+ return std::string(static_cast(serial.get()), serial.size());
+}
+
+std::string HybridX509Certificate::fingerprint() {
+ return cert_.view().getFingerprint(ncrypto::Digest::SHA1).value_or("");
+}
+
+std::string HybridX509Certificate::fingerprint256() {
+ return cert_.view().getFingerprint(ncrypto::Digest::SHA256).value_or("");
+}
+
+std::string HybridX509Certificate::fingerprint512() {
+ return cert_.view().getFingerprint(ncrypto::Digest::SHA512).value_or("");
+}
+
+std::shared_ptr HybridX509Certificate::raw() {
+ auto bio = cert_.view().toDER();
+ if (!bio) {
+ throw std::runtime_error("Failed to export certificate as DER");
+ }
+ BUF_MEM* mem = bio;
+ return ToNativeArrayBuffer(reinterpret_cast(mem->data), mem->length);
+}
+
+std::string HybridX509Certificate::pem() {
+ return bioToString(cert_.view().toPEM());
+}
+
+std::shared_ptr HybridX509Certificate::publicKey() {
+ auto result = cert_.view().getPublicKey();
+ if (!result) {
+ throw std::runtime_error("Failed to extract public key from certificate");
+ }
+ auto handle = std::make_shared();
+ handle->setKeyObjectData(KeyObjectData::CreateAsymmetric(KeyType::PUBLIC, std::move(result.value)));
+ return handle;
+}
+
+std::vector HybridX509Certificate::keyUsage() {
+ std::vector usages;
+ cert_.view().enumUsages([&](const char* usage) { usages.emplace_back(usage); });
+ return usages;
+}
+
+bool HybridX509Certificate::ca() {
+ return cert_.view().isCA();
+}
+
+bool HybridX509Certificate::checkIssued(const std::shared_ptr& other) {
+ auto otherCert = std::dynamic_pointer_cast(other);
+ if (!otherCert) {
+ throw std::runtime_error("Invalid X509Certificate");
+ }
+ return cert_.view().isIssuedBy(otherCert->cert_.view());
+}
+
+bool HybridX509Certificate::checkPrivateKey(const std::shared_ptr& key) {
+ auto handle = std::dynamic_pointer_cast(key);
+ if (!handle) {
+ throw std::runtime_error("Invalid key object");
+ }
+ return cert_.view().checkPrivateKey(handle->getKeyObjectData().GetAsymmetricKey());
+}
+
+bool HybridX509Certificate::verify(const std::shared_ptr& key) {
+ auto handle = std::dynamic_pointer_cast(key);
+ if (!handle) {
+ throw std::runtime_error("Invalid key object");
+ }
+ return cert_.view().checkPublicKey(handle->getKeyObjectData().GetAsymmetricKey());
+}
+
+std::optional HybridX509Certificate::checkHost(const std::string& name, double flags) {
+ ncrypto::DataPointer peername;
+ auto match = cert_.view().checkHost(name, static_cast(flags), &peername);
+ if (match == ncrypto::X509View::CheckMatch::MATCH) {
+ if (peername) {
+ return std::string(static_cast(peername.get()), peername.size());
+ }
+ return name;
+ }
+ return std::nullopt;
+}
+
+std::optional HybridX509Certificate::checkEmail(const std::string& email, double flags) {
+ auto match = cert_.view().checkEmail(email, static_cast(flags));
+ if (match == ncrypto::X509View::CheckMatch::MATCH) {
+ return email;
+ }
+ return std::nullopt;
+}
+
+std::optional HybridX509Certificate::checkIP(const std::string& ip) {
+ auto match = cert_.view().checkIp(ip, 0);
+ if (match == ncrypto::X509View::CheckMatch::MATCH) {
+ return ip;
+ }
+ return std::nullopt;
+}
+
+} // namespace margelo::nitro::crypto
diff --git a/packages/react-native-quick-crypto/cpp/x509/HybridX509Certificate.hpp b/packages/react-native-quick-crypto/cpp/x509/HybridX509Certificate.hpp
new file mode 100644
index 00000000..705feb3e
--- /dev/null
+++ b/packages/react-native-quick-crypto/cpp/x509/HybridX509Certificate.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "HybridX509CertificateHandleSpec.hpp"
+#include
+#include
+
+namespace margelo::nitro::crypto {
+
+class HybridX509Certificate : public HybridX509CertificateHandleSpec {
+ public:
+ HybridX509Certificate() : HybridObject(TAG) {}
+
+ void init(const std::shared_ptr& buffer) override;
+
+ std::string subject() override;
+ std::string subjectAltName() override;
+ std::string issuer() override;
+ std::string infoAccess() override;
+ std::string validFrom() override;
+ std::string validTo() override;
+ double validFromDate() override;
+ double validToDate() override;
+ std::string signatureAlgorithm() override;
+ std::string signatureAlgorithmOid() override;
+ std::string serialNumber() override;
+
+ std::string fingerprint() override;
+ std::string fingerprint256() override;
+ std::string fingerprint512() override;
+
+ std::shared_ptr raw() override;
+ std::string pem() override;
+
+ std::shared_ptr publicKey() override;
+ std::vector keyUsage() override;
+
+ bool ca() override;
+ bool checkIssued(const std::shared_ptr& other) override;
+ bool checkPrivateKey(const std::shared_ptr& key) override;
+ bool verify(const std::shared_ptr& key) override;
+
+ std::optional checkHost(const std::string& name, double flags) override;
+ std::optional checkEmail(const std::string& email, double flags) override;
+ std::optional checkIP(const std::string& ip) override;
+
+ private:
+ ncrypto::X509Pointer cert_;
+ std::string bioToString(ncrypto::BIOPointer bio) const;
+};
+
+} // namespace margelo::nitro::crypto
diff --git a/packages/react-native-quick-crypto/nitro.json b/packages/react-native-quick-crypto/nitro.json
index 76e26fb8..dab16f3c 100644
--- a/packages/react-native-quick-crypto/nitro.json
+++ b/packages/react-native-quick-crypto/nitro.json
@@ -82,6 +82,9 @@
},
"VerifyHandle": {
"cpp": "HybridVerifyHandle"
+ },
+ "X509CertificateHandle": {
+ "cpp": "HybridX509Certificate"
}
},
"ignorePaths": ["node_modules", "lib"]
diff --git a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake
index d39000d6..4b1b0e8a 100644
--- a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake
+++ b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake
@@ -58,6 +58,7 @@ target_sources(
../nitrogen/generated/shared/c++/HybridSignHandleSpec.cpp
../nitrogen/generated/shared/c++/HybridVerifyHandleSpec.cpp
../nitrogen/generated/shared/c++/HybridUtilsSpec.cpp
+ ../nitrogen/generated/shared/c++/HybridX509CertificateHandleSpec.cpp
# Android-specific Nitrogen C++ sources
)
diff --git a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp
index 89711208..295474cb 100644
--- a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp
+++ b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp
@@ -40,6 +40,7 @@
#include "HybridSignHandle.hpp"
#include "HybridUtils.hpp"
#include "HybridVerifyHandle.hpp"
+#include "HybridX509Certificate.hpp"
namespace margelo::nitro::crypto {
@@ -278,6 +279,15 @@ int initialize(JavaVM* vm) {
return std::make_shared();
}
);
+ HybridObjectRegistry::registerHybridObjectConstructor(
+ "X509CertificateHandle",
+ []() -> std::shared_ptr {
+ static_assert(std::is_default_constructible_v,
+ "The HybridObject \"HybridX509Certificate\" is not default-constructible! "
+ "Create a public constructor that takes zero arguments to be able to autolink this HybridObject.");
+ return std::make_shared();
+ }
+ );
});
}
diff --git a/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm b/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm
index d942efe4..1699b8de 100644
--- a/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm
+++ b/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm
@@ -35,6 +35,7 @@
#include "HybridSignHandle.hpp"
#include "HybridUtils.hpp"
#include "HybridVerifyHandle.hpp"
+#include "HybridX509Certificate.hpp"
@interface QuickCryptoAutolinking : NSObject
@end
@@ -270,6 +271,15 @@ + (void) load {
return std::make_shared();
}
);
+ HybridObjectRegistry::registerHybridObjectConstructor(
+ "X509CertificateHandle",
+ []() -> std::shared_ptr {
+ static_assert(std::is_default_constructible_v,
+ "The HybridObject \"HybridX509Certificate\" is not default-constructible! "
+ "Create a public constructor that takes zero arguments to be able to autolink this HybridObject.");
+ return std::make_shared();
+ }
+ );
}
@end
diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridX509CertificateHandleSpec.cpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridX509CertificateHandleSpec.cpp
new file mode 100644
index 00000000..853e5d6e
--- /dev/null
+++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridX509CertificateHandleSpec.cpp
@@ -0,0 +1,46 @@
+///
+/// HybridX509CertificateHandleSpec.cpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#include "HybridX509CertificateHandleSpec.hpp"
+
+namespace margelo::nitro::crypto {
+
+ void HybridX509CertificateHandleSpec::loadHybridMethods() {
+ // load base methods/properties
+ HybridObject::loadHybridMethods();
+ // load custom methods/properties
+ registerHybrids(this, [](Prototype& prototype) {
+ prototype.registerHybridMethod("init", &HybridX509CertificateHandleSpec::init);
+ prototype.registerHybridMethod("subject", &HybridX509CertificateHandleSpec::subject);
+ prototype.registerHybridMethod("subjectAltName", &HybridX509CertificateHandleSpec::subjectAltName);
+ prototype.registerHybridMethod("issuer", &HybridX509CertificateHandleSpec::issuer);
+ prototype.registerHybridMethod("infoAccess", &HybridX509CertificateHandleSpec::infoAccess);
+ prototype.registerHybridMethod("validFrom", &HybridX509CertificateHandleSpec::validFrom);
+ prototype.registerHybridMethod("validTo", &HybridX509CertificateHandleSpec::validTo);
+ prototype.registerHybridMethod("validFromDate", &HybridX509CertificateHandleSpec::validFromDate);
+ prototype.registerHybridMethod("validToDate", &HybridX509CertificateHandleSpec::validToDate);
+ prototype.registerHybridMethod("signatureAlgorithm", &HybridX509CertificateHandleSpec::signatureAlgorithm);
+ prototype.registerHybridMethod("signatureAlgorithmOid", &HybridX509CertificateHandleSpec::signatureAlgorithmOid);
+ prototype.registerHybridMethod("serialNumber", &HybridX509CertificateHandleSpec::serialNumber);
+ prototype.registerHybridMethod("fingerprint", &HybridX509CertificateHandleSpec::fingerprint);
+ prototype.registerHybridMethod("fingerprint256", &HybridX509CertificateHandleSpec::fingerprint256);
+ prototype.registerHybridMethod("fingerprint512", &HybridX509CertificateHandleSpec::fingerprint512);
+ prototype.registerHybridMethod("raw", &HybridX509CertificateHandleSpec::raw);
+ prototype.registerHybridMethod("pem", &HybridX509CertificateHandleSpec::pem);
+ prototype.registerHybridMethod("publicKey", &HybridX509CertificateHandleSpec::publicKey);
+ prototype.registerHybridMethod("keyUsage", &HybridX509CertificateHandleSpec::keyUsage);
+ prototype.registerHybridMethod("ca", &HybridX509CertificateHandleSpec::ca);
+ prototype.registerHybridMethod("checkIssued", &HybridX509CertificateHandleSpec::checkIssued);
+ prototype.registerHybridMethod("checkPrivateKey", &HybridX509CertificateHandleSpec::checkPrivateKey);
+ prototype.registerHybridMethod("verify", &HybridX509CertificateHandleSpec::verify);
+ prototype.registerHybridMethod("checkHost", &HybridX509CertificateHandleSpec::checkHost);
+ prototype.registerHybridMethod("checkEmail", &HybridX509CertificateHandleSpec::checkEmail);
+ prototype.registerHybridMethod("checkIP", &HybridX509CertificateHandleSpec::checkIP);
+ });
+ }
+
+} // namespace margelo::nitro::crypto
diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridX509CertificateHandleSpec.hpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridX509CertificateHandleSpec.hpp
new file mode 100644
index 00000000..73544ab4
--- /dev/null
+++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridX509CertificateHandleSpec.hpp
@@ -0,0 +1,96 @@
+///
+/// HybridX509CertificateHandleSpec.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#if __has_include()
+#include
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+
+// Forward declaration of `HybridKeyObjectHandleSpec` to properly resolve imports.
+namespace margelo::nitro::crypto { class HybridKeyObjectHandleSpec; }
+// Forward declaration of `HybridX509CertificateHandleSpec` to properly resolve imports.
+namespace margelo::nitro::crypto { class HybridX509CertificateHandleSpec; }
+
+#include
+#include
+#include
+#include "HybridKeyObjectHandleSpec.hpp"
+#include
+#include "HybridX509CertificateHandleSpec.hpp"
+#include
+
+namespace margelo::nitro::crypto {
+
+ using namespace margelo::nitro;
+
+ /**
+ * An abstract base class for `X509CertificateHandle`
+ * Inherit this class to create instances of `HybridX509CertificateHandleSpec` in C++.
+ * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual.
+ * @example
+ * ```cpp
+ * class HybridX509CertificateHandle: public HybridX509CertificateHandleSpec {
+ * public:
+ * HybridX509CertificateHandle(...): HybridObject(TAG) { ... }
+ * // ...
+ * };
+ * ```
+ */
+ class HybridX509CertificateHandleSpec: public virtual HybridObject {
+ public:
+ // Constructor
+ explicit HybridX509CertificateHandleSpec(): HybridObject(TAG) { }
+
+ // Destructor
+ ~HybridX509CertificateHandleSpec() override = default;
+
+ public:
+ // Properties
+
+
+ public:
+ // Methods
+ virtual void init(const std::shared_ptr& buffer) = 0;
+ virtual std::string subject() = 0;
+ virtual std::string subjectAltName() = 0;
+ virtual std::string issuer() = 0;
+ virtual std::string infoAccess() = 0;
+ virtual std::string validFrom() = 0;
+ virtual std::string validTo() = 0;
+ virtual double validFromDate() = 0;
+ virtual double validToDate() = 0;
+ virtual std::string signatureAlgorithm() = 0;
+ virtual std::string signatureAlgorithmOid() = 0;
+ virtual std::string serialNumber() = 0;
+ virtual std::string fingerprint() = 0;
+ virtual std::string fingerprint256() = 0;
+ virtual std::string fingerprint512() = 0;
+ virtual std::shared_ptr raw() = 0;
+ virtual std::string pem() = 0;
+ virtual std::shared_ptr publicKey() = 0;
+ virtual std::vector keyUsage() = 0;
+ virtual bool ca() = 0;
+ virtual bool checkIssued(const std::shared_ptr& other) = 0;
+ virtual bool checkPrivateKey(const std::shared_ptr& key) = 0;
+ virtual bool verify(const std::shared_ptr& key) = 0;
+ virtual std::optional checkHost(const std::string& name, double flags) = 0;
+ virtual std::optional checkEmail(const std::string& email, double flags) = 0;
+ virtual std::optional checkIP(const std::string& ip) = 0;
+
+ protected:
+ // Hybrid Setup
+ void loadHybridMethods() override;
+
+ protected:
+ // Tag for logging
+ static constexpr auto TAG = "X509CertificateHandle";
+ };
+
+} // namespace margelo::nitro::crypto
diff --git a/packages/react-native-quick-crypto/src/index.ts b/packages/react-native-quick-crypto/src/index.ts
index 264f68d9..5c69b937 100644
--- a/packages/react-native-quick-crypto/src/index.ts
+++ b/packages/react-native-quick-crypto/src/index.ts
@@ -17,6 +17,7 @@ import * as random from './random';
import * as ecdh from './ecdh';
import * as dh from './diffie-hellman';
import { Certificate } from './certificate';
+import { X509Certificate } from './x509certificate';
import { getCurves } from './ec';
import { constants } from './constants';
@@ -46,6 +47,7 @@ const QuickCrypto = {
...utils,
...subtle,
Certificate,
+ X509Certificate,
getCurves,
constants,
Buffer,
@@ -81,6 +83,8 @@ export default QuickCrypto;
export * from './argon2';
export * from './blake3';
export { Certificate } from './certificate';
+export { X509Certificate } from './x509certificate';
+export type { CheckOptions, X509LegacyObject } from './x509certificate';
export * from './cipher';
export * from './ed';
export * from './keys';
diff --git a/packages/react-native-quick-crypto/src/specs/x509certificate.nitro.ts b/packages/react-native-quick-crypto/src/specs/x509certificate.nitro.ts
new file mode 100644
index 00000000..9a549894
--- /dev/null
+++ b/packages/react-native-quick-crypto/src/specs/x509certificate.nitro.ts
@@ -0,0 +1,38 @@
+import type { HybridObject } from 'react-native-nitro-modules';
+import type { KeyObjectHandle } from './keyObjectHandle.nitro';
+
+export interface X509CertificateHandle
+ extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
+ init(buffer: ArrayBuffer): void;
+
+ subject(): string;
+ subjectAltName(): string;
+ issuer(): string;
+ infoAccess(): string;
+ validFrom(): string;
+ validTo(): string;
+ validFromDate(): number;
+ validToDate(): number;
+ signatureAlgorithm(): string;
+ signatureAlgorithmOid(): string;
+ serialNumber(): string;
+
+ fingerprint(): string;
+ fingerprint256(): string;
+ fingerprint512(): string;
+
+ raw(): ArrayBuffer;
+ pem(): string;
+
+ publicKey(): KeyObjectHandle;
+ keyUsage(): string[];
+
+ ca(): boolean;
+ checkIssued(other: X509CertificateHandle): boolean;
+ checkPrivateKey(key: KeyObjectHandle): boolean;
+ verify(key: KeyObjectHandle): boolean;
+
+ checkHost(name: string, flags: number): string | undefined;
+ checkEmail(email: string, flags: number): string | undefined;
+ checkIP(ip: string): string | undefined;
+}
diff --git a/packages/react-native-quick-crypto/src/x509certificate.ts b/packages/react-native-quick-crypto/src/x509certificate.ts
new file mode 100644
index 00000000..318b05e7
--- /dev/null
+++ b/packages/react-native-quick-crypto/src/x509certificate.ts
@@ -0,0 +1,277 @@
+import { NitroModules } from 'react-native-nitro-modules';
+import { Buffer } from '@craftzdog/react-native-buffer';
+import type { X509CertificateHandle } from './specs/x509certificate.nitro';
+import { PublicKeyObject, KeyObject } from './keys';
+import type { BinaryLike } from './utils';
+import { binaryLikeToArrayBuffer } from './utils';
+
+const X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT = 0x1;
+const X509_CHECK_FLAG_NO_WILDCARDS = 0x2;
+const X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS = 0x4;
+const X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS = 0x8;
+const X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS = 0x10;
+const X509_CHECK_FLAG_NEVER_CHECK_SUBJECT = 0x20;
+
+export interface X509LegacyObject {
+ subject: string;
+ issuer: string;
+ subjectaltname: string;
+ infoAccess: string;
+ ca: boolean;
+ modulus: undefined;
+ bits: undefined;
+ exponent: undefined;
+ valid_from: string;
+ valid_to: string;
+ fingerprint: string;
+ fingerprint256: string;
+ fingerprint512: string;
+ ext_key_usage: string[];
+ serialNumber: string;
+ raw: Buffer;
+}
+
+export interface CheckOptions {
+ subject?: 'default' | 'always' | 'never';
+ wildcards?: boolean;
+ partialWildcards?: boolean;
+ multiLabelWildcards?: boolean;
+ singleLabelSubdomains?: boolean;
+}
+
+function getFlags(options?: CheckOptions): number {
+ if (!options) return 0;
+
+ let flags = 0;
+
+ if (options.subject === 'always') {
+ flags |= X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT;
+ } else if (options.subject === 'never') {
+ flags |= X509_CHECK_FLAG_NEVER_CHECK_SUBJECT;
+ }
+
+ if (options.wildcards === false) {
+ flags |= X509_CHECK_FLAG_NO_WILDCARDS;
+ }
+
+ if (options.partialWildcards === false) {
+ flags |= X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS;
+ }
+
+ if (options.multiLabelWildcards === true) {
+ flags |= X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS;
+ }
+
+ if (options.singleLabelSubdomains === true) {
+ flags |= X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS;
+ }
+
+ return flags;
+}
+
+export class X509Certificate {
+ private readonly handle: X509CertificateHandle;
+ private readonly cache = new Map();
+
+ constructor(buffer: BinaryLike) {
+ this.handle = NitroModules.createHybridObject(
+ 'X509CertificateHandle',
+ );
+
+ let ab: ArrayBuffer;
+ if (typeof buffer === 'string') {
+ ab = Buffer.from(buffer).buffer as ArrayBuffer;
+ } else {
+ ab = binaryLikeToArrayBuffer(buffer);
+ }
+
+ this.handle.init(ab);
+ }
+
+ private cached(key: string, compute: () => T): T {
+ if (this.cache.has(key)) {
+ return this.cache.get(key) as T;
+ }
+ const value = compute();
+ this.cache.set(key, value);
+ return value;
+ }
+
+ get subject(): string {
+ return this.cached('subject', () => this.handle.subject());
+ }
+
+ get subjectAltName(): string {
+ return this.cached('subjectAltName', () => this.handle.subjectAltName());
+ }
+
+ get issuer(): string {
+ return this.cached('issuer', () => this.handle.issuer());
+ }
+
+ get infoAccess(): string {
+ return this.cached('infoAccess', () => this.handle.infoAccess());
+ }
+
+ get validFrom(): string {
+ return this.cached('validFrom', () => this.handle.validFrom());
+ }
+
+ get validTo(): string {
+ return this.cached('validTo', () => this.handle.validTo());
+ }
+
+ get validFromDate(): Date {
+ return this.cached(
+ 'validFromDate',
+ () => new Date(this.handle.validFromDate()),
+ );
+ }
+
+ get validToDate(): Date {
+ return this.cached(
+ 'validToDate',
+ () => new Date(this.handle.validToDate()),
+ );
+ }
+
+ get fingerprint(): string {
+ return this.cached('fingerprint', () => this.handle.fingerprint());
+ }
+
+ get fingerprint256(): string {
+ return this.cached('fingerprint256', () => this.handle.fingerprint256());
+ }
+
+ get fingerprint512(): string {
+ return this.cached('fingerprint512', () => this.handle.fingerprint512());
+ }
+
+ get extKeyUsage(): string[] {
+ return this.cached('extKeyUsage', () => this.handle.keyUsage());
+ }
+
+ get keyUsage(): string[] {
+ return this.extKeyUsage;
+ }
+
+ get serialNumber(): string {
+ return this.cached('serialNumber', () => this.handle.serialNumber());
+ }
+
+ get signatureAlgorithm(): string {
+ return this.cached('signatureAlgorithm', () =>
+ this.handle.signatureAlgorithm(),
+ );
+ }
+
+ get signatureAlgorithmOid(): string {
+ return this.cached('signatureAlgorithmOid', () =>
+ this.handle.signatureAlgorithmOid(),
+ );
+ }
+
+ get ca(): boolean {
+ return this.cached('ca', () => this.handle.ca());
+ }
+
+ get raw(): Buffer {
+ return this.cached('raw', () => Buffer.from(this.handle.raw()));
+ }
+
+ get publicKey(): PublicKeyObject {
+ return this.cached(
+ 'publicKey',
+ () => new PublicKeyObject(this.handle.publicKey()),
+ );
+ }
+
+ get issuerCertificate(): undefined {
+ return undefined;
+ }
+
+ checkHost(name: string, options?: CheckOptions): string | undefined {
+ if (typeof name !== 'string') {
+ throw new TypeError('The "name" argument must be a string');
+ }
+ return this.handle.checkHost(name, getFlags(options));
+ }
+
+ checkEmail(email: string, options?: CheckOptions): string | undefined {
+ if (typeof email !== 'string') {
+ throw new TypeError('The "email" argument must be a string');
+ }
+ return this.handle.checkEmail(email, getFlags(options));
+ }
+
+ checkIP(ip: string): string | undefined {
+ if (typeof ip !== 'string') {
+ throw new TypeError('The "ip" argument must be a string');
+ }
+ return this.handle.checkIP(ip);
+ }
+
+ checkIssued(otherCert: X509Certificate): boolean {
+ if (!(otherCert instanceof X509Certificate)) {
+ throw new TypeError(
+ 'The "otherCert" argument must be an instance of X509Certificate',
+ );
+ }
+ return this.handle.checkIssued(otherCert.handle);
+ }
+
+ checkPrivateKey(pkey: KeyObject): boolean {
+ if (!(pkey instanceof KeyObject)) {
+ throw new TypeError(
+ 'The "pkey" argument must be an instance of KeyObject',
+ );
+ }
+ if (pkey.type !== 'private') {
+ throw new TypeError('The "pkey" argument must be a private key');
+ }
+ return this.handle.checkPrivateKey(pkey.handle);
+ }
+
+ verify(pkey: KeyObject): boolean {
+ if (!(pkey instanceof KeyObject)) {
+ throw new TypeError(
+ 'The "pkey" argument must be an instance of KeyObject',
+ );
+ }
+ if (pkey.type !== 'public') {
+ throw new TypeError(
+ `The "pkey" argument must be a public key, got '${pkey.type}'`,
+ );
+ }
+ return this.handle.verify(pkey.handle);
+ }
+
+ toString(): string {
+ return this.cached('pem', () => this.handle.pem());
+ }
+
+ toJSON(): string {
+ return this.toString();
+ }
+
+ toLegacyObject(): X509LegacyObject {
+ return {
+ subject: this.subject,
+ issuer: this.issuer,
+ subjectaltname: this.subjectAltName,
+ infoAccess: this.infoAccess,
+ ca: this.ca,
+ modulus: undefined,
+ bits: undefined,
+ exponent: undefined,
+ valid_from: this.validFrom,
+ valid_to: this.validTo,
+ fingerprint: this.fingerprint,
+ fingerprint256: this.fingerprint256,
+ fingerprint512: this.fingerprint512,
+ ext_key_usage: this.keyUsage,
+ serialNumber: this.serialNumber,
+ raw: this.raw,
+ };
+ }
+}