Skip to content

Commit 4277d0b

Browse files
committed
C++ work on update/final, added tests
1 parent 2e1a778 commit 4277d0b

3 files changed

Lines changed: 159 additions & 49 deletions

File tree

example/src/tests/cipher/cipher_tests.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
1+
import { Buffer } from '@craftzdog/react-native-buffer';
12
import {
23
getCiphers,
34
createCipheriv,
5+
createDecipheriv,
46
randomFillSync,
7+
type CipherType,
8+
type BinaryLikeNode,
9+
type BinaryLike,
510
} from 'react-native-quick-crypto';
611
import { expect } from 'chai';
712
import { test } from '../util';
813

914
const SUITE = 'cipher';
15+
const ciphers = getCiphers();
1016
const key = 'secret';
1117
const iv = randomFillSync(new Uint8Array(16));
18+
const plaintext =
19+
'32|RmVZZkFUVmpRRkp0TmJaUm56ZU9qcnJkaXNNWVNpTTU*|iXmckfRWZBGWWELw' +
20+
'eCBsThSsfUHLeRe0KCsK8ooHgxie0zOINpXxfZi/oNG7uq9JWFVCk70gfzQH8ZUJ' +
21+
'jAfaFg**';
1222

1323
test(SUITE, 'cipher - valid algorithm', async () => {
1424
expect(() => {
@@ -24,7 +34,43 @@ test(SUITE, 'cipher - invalid algorithm', async () => {
2434
});
2535

2636
test(SUITE, 'cipher - getSupportedCiphers', async () => {
27-
const ciphers = getCiphers();
2837
expect(ciphers).to.be.instanceOf(Array);
2938
expect(ciphers).to.have.length.greaterThan(0);
3039
});
40+
41+
// different value types
42+
test(SUITE, 'cipher - strings', async () => {
43+
roundtrip('aes-128-cbc', '0123456789abcd0123456789', '12345678', plaintext);
44+
});
45+
46+
test(SUITE, 'cipher - buffers', async () => {
47+
roundtrip(
48+
'aes-128-cbc',
49+
Buffer.from('0123456789abcd0123456789'),
50+
Buffer.from('12345678'),
51+
plaintext,
52+
);
53+
});
54+
55+
// update/final
56+
ciphers.forEach(cipherName => {
57+
test(SUITE, `cipher - non-stream - ${cipherName}`, async () => {
58+
roundtrip(cipherName as CipherType, key, iv, plaintext);
59+
});
60+
});
61+
62+
function roundtrip(
63+
cipherName: CipherType,
64+
lKey: BinaryLikeNode,
65+
lIv: BinaryLike,
66+
payload: string,
67+
) {
68+
const cipher = createCipheriv(cipherName, lKey, lIv, {});
69+
let ciph = cipher.update(payload, 'utf8', 'buffer') as Uint8Array;
70+
ciph = Buffer.concat([ciph, cipher.final('buffer') as Uint8Array]);
71+
72+
const decipher = createDecipheriv(cipherName, lKey, lIv, {});
73+
let deciph = decipher.update(ciph, 'buffer', 'utf8');
74+
deciph += decipher.final('utf8') as string;
75+
expect(deciph).to.equal(plaintext);
76+
}

packages/react-native-quick-crypto/cpp/cipher/HybridCipher.cpp

Lines changed: 111 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,120 @@
88
namespace margelo::nitro::crypto {
99

1010
HybridCipher::~HybridCipher() {
11-
if (this->ctx) {
12-
EVP_CIPHER_CTX_free(this->ctx);
11+
if (ctx) {
12+
EVP_CIPHER_CTX_free(ctx);
1313
}
14-
if (this->cipher) {
15-
EVP_CIPHER_free(this->cipher);
14+
}
15+
16+
void
17+
HybridCipher::init() {
18+
// check if args are set
19+
if (!args.has_value()) {
20+
throw std::runtime_error("CipherArgs not set");
1621
}
22+
const auto& argsRef = args.value();
23+
24+
// fetch cipher
25+
EVP_CIPHER *cipher = EVP_CIPHER_fetch(
26+
nullptr,
27+
argsRef.cipherType.c_str(),
28+
nullptr
29+
);
30+
if (cipher == nullptr) {
31+
throw std::runtime_error("Invalid Cipher Algorithm: " + argsRef.cipherType);
32+
}
33+
34+
// Create cipher context
35+
ctx = EVP_CIPHER_CTX_new();
36+
if (!ctx) {
37+
EVP_CIPHER_free(cipher);
38+
throw std::runtime_error("Failed to create cipher context");
39+
}
40+
41+
// Initialize cipher operation
42+
if (
43+
EVP_CipherInit_ex2(
44+
ctx,
45+
cipher,
46+
argsRef.cipherKey->data(),
47+
argsRef.iv->data(),
48+
argsRef.isCipher ? 1 : 0,
49+
nullptr
50+
) != 1
51+
) {
52+
EVP_CIPHER_CTX_free(ctx);
53+
EVP_CIPHER_free(cipher);
54+
ctx = nullptr;
55+
throw std::runtime_error("Failed to initialize encryption");
56+
}
57+
58+
EVP_CIPHER_free(cipher);
1759
}
1860

1961
std::shared_ptr<ArrayBuffer>
2062
HybridCipher::update(
2163
const std::shared_ptr<ArrayBuffer>& data
2264
) {
23-
return nullptr;
65+
if (!ctx) {
66+
throw std::runtime_error("Cipher not initialized. Did you call setArgs()?");
67+
}
68+
69+
// Calculate the maximum output length
70+
int outLen = data->size() + EVP_MAX_BLOCK_LENGTH;
71+
int updateLen = 0;
72+
73+
// Create a temporary buffer for the operation
74+
unsigned char* tempBuf = new unsigned char[outLen];
75+
76+
// Perform the cipher update operation
77+
if (
78+
EVP_CipherUpdate(
79+
ctx,
80+
tempBuf,
81+
&updateLen,
82+
reinterpret_cast<const unsigned char*>(data->data()),
83+
data->size()
84+
) != 1
85+
) {
86+
delete[] tempBuf;
87+
throw std::runtime_error("Failed to update cipher");
88+
}
89+
90+
// Create and return a new buffer of exact size needed
91+
return std::make_shared<NativeArrayBuffer>(
92+
tempBuf,
93+
updateLen,
94+
[=]() { delete[] tempBuf; }
95+
);
2496
}
2597

2698
std::shared_ptr<ArrayBuffer>
2799
HybridCipher::final() {
28-
return nullptr;
100+
if (!ctx) {
101+
throw std::runtime_error("Cipher not initialized. Did you call setArgs()?");
102+
}
103+
104+
int finalLen = 0;
105+
unsigned char tempBuf[EVP_MAX_BLOCK_LENGTH];
106+
107+
// Finalize the encryption/decryption
108+
if (EVP_CipherFinal_ex(
109+
ctx,
110+
tempBuf,
111+
&finalLen) != 1) {
112+
throw std::runtime_error("Failed to finalize cipher");
113+
}
114+
115+
// Create and return a new buffer of exact size needed
116+
return std::make_shared<NativeArrayBuffer>(
117+
tempBuf,
118+
finalLen,
119+
nullptr
120+
);
29121
}
30122

31123
void
32-
HybridCipher::copy() {}
124+
HybridCipher::copy() { /* TODO */ }
33125

34126
bool
35127
HybridCipher::setAAD(
@@ -62,49 +154,21 @@ void
62154
HybridCipher::setArgs(
63155
const CipherArgs& args
64156
) {
65-
this->args = args;
66-
init();
67-
}
68-
69-
void
70-
HybridCipher::init() {
71-
// check if args are set
72-
if (!args.has_value()) {
73-
throw std::runtime_error("CipherArgs not set");
157+
if (this->args.has_value()) {
158+
// Reset existing value if any
159+
this->args.reset();
74160
}
75-
auto args = this->args.value();
76161

77-
// fetch cipher
78-
this->cipher = EVP_CIPHER_fetch(
79-
nullptr,
80-
args.cipherType.c_str(),
81-
nullptr
82-
);
83-
if (cipher == nullptr) {
84-
throw std::runtime_error("Invalid Cipher Algorithm: " + args.cipherType);
85-
}
86-
87-
// Create cipher context
88-
this->ctx = EVP_CIPHER_CTX_new();
89-
if (!this->ctx) {
90-
throw std::runtime_error("Failed to create cipher context");
91-
}
162+
// Use std::optional::emplace with direct member initialization
163+
this->args.emplace(CipherArgs{
164+
args.isCipher,
165+
args.cipherType,
166+
args.cipherKey,
167+
args.iv,
168+
args.authTagLen
169+
});
92170

93-
// Initialize cipher operation
94-
if (
95-
EVP_CipherInit_ex2(
96-
this->ctx,
97-
this->cipher,
98-
this->args->cipherKey->data(),
99-
this->args->iv->data(),
100-
this->args->isCipher ? 1 : 0,
101-
nullptr
102-
) != 1
103-
) {
104-
EVP_CIPHER_CTX_free(this->ctx);
105-
this->ctx = nullptr;
106-
throw std::runtime_error("Failed to initialize encryption");
107-
}
171+
init();
108172
}
109173

110174
void collect_ciphers(EVP_CIPHER *cipher, void *arg) {

packages/react-native-quick-crypto/cpp/cipher/HybridCipher.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "HybridCipherSpec.hpp"
88
#include "CipherArgs.hpp"
9+
#include <NitroModules/ArrayBuffer.hpp>
910

1011
namespace margelo::nitro::crypto {
1112

@@ -64,7 +65,6 @@ class HybridCipher : public HybridCipherSpec {
6465
// Properties
6566
std::optional<CipherArgs> args = std::nullopt;
6667
EVP_CIPHER_CTX *ctx = nullptr;
67-
EVP_CIPHER *cipher = nullptr;
6868
};
6969

7070
} // namespace margelo::nitro::crypto

0 commit comments

Comments
 (0)