Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Run linting and formatting on staged files
bun lint-staged

# Check C++ formatting
find packages/react-native-quick-crypto/cpp -name "*.cpp" -o -name "*.hpp" | xargs clang-format --dry-run --Werror --style=file

# Run prepare script
bun --filter="react-native-quick-crypto" prepare
24 changes: 12 additions & 12 deletions docs/implementation-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ This document attempts to describe the implementation status of Crypto APIs/Inte
* ❌ static `supports(operation, algorithm[, lengthOrAdditionalAlgorithm])`
* ❌ `subtle.decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext)`
* ❌ `subtle.decapsulateKey(decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages)`
* 🚧 `subtle.decrypt(algorithm, key, data)`
* `subtle.decrypt(algorithm, key, data)`
* 🚧 `subtle.deriveBits(algorithm, baseKey, length)`
* ❌ `subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)`
* 🚧 `subtle.digest(algorithm, data)`
Expand All @@ -263,10 +263,10 @@ This document attempts to describe the implementation status of Crypto APIs/Inte
## `subtle.decrypt`
| Algorithm | Status |
| --------- | :----: |
| `RSA-OAEP` | |
| `AES-CTR` | |
| `AES-CBC` | |
| `AES-GCM` | |
| `RSA-OAEP` | |
| `AES-CTR` | |
| `AES-CBC` | |
| `AES-GCM` | |

## `subtle.deriveBits`
| Algorithm | Status |
Expand Down Expand Up @@ -302,12 +302,12 @@ This document attempts to describe the implementation status of Crypto APIs/Inte
## `subtle.encrypt`
| Algorithm | Status |
| ------------------- | :----: |
| `AES-CTR` | |
| `AES-CBC` | |
| `AES-GCM` | |
| `AES-CTR` | |
| `AES-CBC` | |
| `AES-GCM` | |
| `AES-OCB` | ❌ |
| `ChaCha20-Poly1305` | ❌ |
| `RSA-OAEP` | |
| `RSA-OAEP` | |

## `subtle.exportKey`
| Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` |
Expand Down Expand Up @@ -361,9 +361,9 @@ This document attempts to describe the implementation status of Crypto APIs/Inte
### `CryptoKey` algorithms
| Algorithm | Status |
| --------- | :----: |
| `AES-CTR` | |
| `AES-CBC` | |
| `AES-GCM` | |
| `AES-CTR` | |
| `AES-CBC` | |
| `AES-GCM` | |
| `AES-KW` | ❌ |
| `AES-OCB` | ❌ |
| `ChaCha20-Poly1305` | ❌ |
Expand Down
2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"lint:fix": "eslint \"src/**/*.{js,ts,tsx}\" --fix",
"format": "prettier --check \"**/*.{js,ts,tsx}\"",
"format:fix": "prettier --write \"**/*.{js,ts,tsx}\"",
"start": "react-native start",
"start": "sh -c 'react-native start --client-logs \"$@\" 2>&1 | tee /tmp/rnqc-metro.log' --",
"pods": "RCT_USE_RN_DEP=1 RCT_USE_PREBUILT_RNCORE=1 bundle install && bundle exec pod install --project-directory=ios",
"build:android": "cd android && ./gradlew assembleDebug --no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a",
"build:ios": "cd ios && xcodebuild -workspace QuickCryptoExample.xcworkspace -scheme QuickCryptoExample -configuration Debug -sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO"
Expand Down
2 changes: 1 addition & 1 deletion example/src/hooks/useTestsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ 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/encrypt_decrypt';
import '../tests/subtle/generateKey';
import '../tests/subtle/import_export';
import '../tests/subtle/jwk_rfc7517_tests';
Expand Down
37 changes: 20 additions & 17 deletions example/src/tests/subtle/encrypt_decrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,11 @@ test(SUITE, 'RSA-OAEP', async () => {
// from https://github.com/nodejs/node/blob/main/test/parallel/test-webcrypto-encrypt-decrypt-rsa.js
async function importRSAVectorKey(
publicKeyBuffer: ArrayBuffer,
_privateKeyBuffer: ArrayBuffer | null,
privateKeyBuffer: ArrayBuffer | null,
name: AnyAlgorithm,
hash: DigestAlgorithm,
publicUsages: KeyUsage[],
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_privateUsages: KeyUsage[],
privateUsages: KeyUsage[],
): Promise<TestCryptoKeyPair> {
const publicKey = await subtle.importKey(
'spki',
Expand All @@ -122,15 +121,19 @@ async function importRSAVectorKey(
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
let privateKey: CryptoKey | undefined;
if (privateKeyBuffer !== null) {
privateKey = await subtle.importKey(
'pkcs8',
privateKeyBuffer,
{ name, hash },
false,
privateUsages,
);
}

return { publicKey, privateKey: privateKey || publicKey };
}

async function testRSADecryption({
Expand Down Expand Up @@ -216,7 +219,7 @@ async function testRSAEncryption(

// TODO: remove condition when importKey() rsa pkcs8 is implemented
if (privateKey !== undefined) {
const encodedPlaintext = Buffer.from(plaintext).toString('hex');
const encodedPlaintext = Buffer.from(plaintextCopy).toString('hex');

expect(result.byteLength * 8).to.equal(
(privateKey as CryptoKey).algorithm.modulusLength,
Expand Down Expand Up @@ -253,7 +256,7 @@ async function testRSAEncryptionLongPlaintext({
return assertThrowsAsync(
async () =>
await subtle.encrypt(algorithm, publicKey as CryptoKey, newplaintext),
'error in DoCipher, status: 2',
'data too large for key size',
);
}

Expand All @@ -275,7 +278,7 @@ async function testRSAEncryptionWrongKey({
return assertThrowsAsync(
async () =>
await subtle.encrypt(algorithm, privateKey as CryptoKey, plaintext),
"Cannot read property 'algorithm' of undefined",
'The requested operation is not valid for the provided key',
);
}

Expand Down Expand Up @@ -715,7 +718,7 @@ async function testAESDecrypt({
async () => {
await assertThrowsAsync(
async () => await testAESDecrypt(vector),
'error in DoCipher, status: 2',
'bad decrypt',
);
},
);
Expand Down Expand Up @@ -783,7 +786,7 @@ async function testAESDecrypt({
async () => {
await assertThrowsAsync(
async () => await testAESDecrypt(vector),
'error in DoCipher, status: 2',
'bad decrypt',
);
},
);
Expand Down Expand Up @@ -861,7 +864,7 @@ async function testAESDecrypt({
async () => {
await assertThrowsAsync(
async () => await testAESDecrypt(vector),
'error in DoCipher, status: 2',
'bad decrypt',
);
},
);
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native-quick-crypto/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ add_library(
src/main/cpp/cpp-adapter.cpp
../cpp/blake3/HybridBlake3.cpp
../cpp/cipher/CCMCipher.cpp
../cpp/cipher/GCMCipher.cpp
../cpp/cipher/HybridCipher.cpp
../cpp/cipher/HybridRsaCipher.cpp
../cpp/cipher/OCBCipher.cpp
../cpp/cipher/XSalsa20Cipher.cpp
../cpp/cipher/ChaCha20Cipher.cpp
Expand Down
68 changes: 68 additions & 0 deletions packages/react-native-quick-crypto/cpp/cipher/GCMCipher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include "GCMCipher.hpp"
#include "Utils.hpp"
#include <openssl/err.h>
#include <openssl/evp.h>
#include <stdexcept>

namespace margelo::nitro::crypto {

void GCMCipher::init(const std::shared_ptr<ArrayBuffer> cipher_key, const std::shared_ptr<ArrayBuffer> iv) {
// Clean up any existing context
if (ctx) {
EVP_CIPHER_CTX_free(ctx);
ctx = nullptr;
}

// 1. Get cipher implementation by name
const EVP_CIPHER* cipher = EVP_get_cipherbyname(cipher_type.c_str());
if (!cipher) {
throw std::runtime_error("Unknown cipher " + cipher_type);
}

// 2. Create a new context
ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
throw std::runtime_error("Failed to create cipher context");
}

// 3. Initialize with cipher type only (no key/IV yet)
if (EVP_CipherInit_ex(ctx, cipher, nullptr, nullptr, nullptr, is_cipher) != 1) {
unsigned long err = ERR_get_error();
char err_buf[256];
ERR_error_string_n(err, err_buf, sizeof(err_buf));
EVP_CIPHER_CTX_free(ctx);
ctx = nullptr;
throw std::runtime_error("GCMCipher: Failed initial CipherInit setup: " + std::string(err_buf));
}

// 4. Set IV length for non-standard IV sizes (GCM default is 96 bits/12 bytes)
auto native_iv = ToNativeArrayBuffer(iv);
size_t iv_len = native_iv->size();

if (iv_len != 12) { // Only set if not the default length
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, static_cast<int>(iv_len), nullptr) != 1) {
unsigned long err = ERR_get_error();
char err_buf[256];
ERR_error_string_n(err, err_buf, sizeof(err_buf));
EVP_CIPHER_CTX_free(ctx);
ctx = nullptr;
throw std::runtime_error("GCMCipher: Failed to set IV length: " + std::string(err_buf));
}
}

// 5. Now set the key and IV
auto native_key = ToNativeArrayBuffer(cipher_key);
const unsigned char* key_ptr = reinterpret_cast<const unsigned char*>(native_key->data());
const unsigned char* iv_ptr = reinterpret_cast<const unsigned char*>(native_iv->data());

if (EVP_CipherInit_ex(ctx, nullptr, nullptr, key_ptr, iv_ptr, is_cipher) != 1) {
unsigned long err = ERR_get_error();
char err_buf[256];
ERR_error_string_n(err, err_buf, sizeof(err_buf));
EVP_CIPHER_CTX_free(ctx);
ctx = nullptr;
throw std::runtime_error("GCMCipher: Failed to set key/IV: " + std::string(err_buf));
}
}

} // namespace margelo::nitro::crypto
14 changes: 14 additions & 0 deletions packages/react-native-quick-crypto/cpp/cipher/GCMCipher.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once

#include "HybridCipher.hpp"

namespace margelo::nitro::crypto {

class GCMCipher : public HybridCipher {
public:
GCMCipher() : HybridObject(TAG) {}

void init(const std::shared_ptr<ArrayBuffer> cipher_key, const std::shared_ptr<ArrayBuffer> iv) override;
};

} // namespace margelo::nitro::crypto
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "CCMCipher.hpp"
#include "ChaCha20Cipher.hpp"
#include "ChaCha20Poly1305Cipher.hpp"
#include "GCMCipher.hpp"
#include "HybridCipherFactorySpec.hpp"
#include "OCBCipher.hpp"
#include "Utils.hpp"
Expand Down Expand Up @@ -50,6 +51,13 @@ class HybridCipherFactory : public HybridCipherFactorySpec {
EVP_CIPHER_free(cipher);
return cipherInstance;
}
case EVP_CIPH_GCM_MODE: {
cipherInstance = std::make_shared<GCMCipher>();
cipherInstance->setArgs(args);
cipherInstance->init(args.cipherKey, args.iv);
EVP_CIPHER_free(cipher);
return cipherInstance;
}
case EVP_CIPH_STREAM_CIPHER: {
// Check for ChaCha20 variants specifically
std::string cipherName = toLower(args.cipherType);
Expand Down
Loading
Loading