react-native-quick-crypto 1.0.0-beta.15 → 1.0.0-beta.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,9 +17,45 @@ Pod::Spec.new do |s|
17
17
  s.macos.deployment_target = 10.13
18
18
  s.tvos.deployment_target = 13.4
19
19
 
20
- s.source = { :git => "https://github.com/margelo/react-native-quick-crypto.git", :tag => "#{s.version}" }
20
+ s.source = { :git => "https://github.com/margelo/react-native-quick-crypto.git", :tag => "#{s.version}" }
21
21
 
22
- s.source_files = [
22
+ sodium_enabled = ENV['SODIUM_ENABLED'] == '1'
23
+ Pod::UI.puts("[QuickCrypto] Has libsodium #{sodium_enabled ? "enabled" : "disabled"}!")
24
+
25
+ if sodium_enabled
26
+ # cocoapod for Sodium has not been updated for a while, so we need to build it ourselves
27
+ # https://github.com/jedisct1/swift-sodium/issues/264#issuecomment-2864963850
28
+ s.prepare_command = <<-CMD
29
+ # Create ios directory if it doesn't exist
30
+ mkdir -p ios
31
+
32
+ # Download libsodium
33
+ curl -L -s -o ios/libsodium.tar.gz https://download.libsodium.org/libsodium/releases/libsodium-1.0.20-stable.tar.gz
34
+
35
+ # Clean previous extraction
36
+ rm -rf ios/libsodium-stable
37
+
38
+ # Extract the full tarball
39
+ tar -xzf ios/libsodium.tar.gz -C ios
40
+
41
+ # Run configure and make to generate all headers including private ones
42
+ cd ios/libsodium-stable && \
43
+ ./configure --disable-shared --enable-static && \
44
+ make -j$(sysctl -n hw.ncpu)
45
+
46
+ # Cleanup
47
+ cd ../../
48
+ rm -f ios/libsodium.tar.gz
49
+ CMD
50
+ else
51
+ s.prepare_command = <<-CMD
52
+ # Clean up libsodium files if they exist
53
+ rm -rf ios/libsodium-stable
54
+ rm -f ios/libsodium.tar.gz
55
+ CMD
56
+ end
57
+
58
+ base_source_files = [
23
59
  # implementation (Swift)
24
60
  "ios/**/*.{swift}",
25
61
  # ios (Objective-C++)
@@ -32,17 +68,40 @@ Pod::Spec.new do |s|
32
68
  "deps/**/*.{h,c}",
33
69
  ]
34
70
 
35
- s.pod_target_xcconfig = {
71
+ if sodium_enabled
72
+ base_source_files += ["ios/libsodium-stable/src/libsodium/**/*.{h,c}"]
73
+ end
74
+
75
+ s.source_files = base_source_files
76
+
77
+ xcconfig = {
36
78
  # C++ compiler flags, mainly for folly.
37
- "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES"
79
+ "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES",
80
+ # Set C++ standard to C++20
81
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
82
+ "CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES" => "YES"
38
83
  }
39
84
 
85
+ if sodium_enabled
86
+ sodium_headers = [
87
+ "\"$(PODS_TARGET_SRCROOT)/ios/libsodium-stable/src/libsodium/include\"",
88
+ "\"$(PODS_TARGET_SRCROOT)/ios/libsodium-stable/src/libsodium/include/sodium\"",
89
+ "\"$(PODS_TARGET_SRCROOT)/ios/libsodium-stable\"",
90
+ "\"$(PODS_ROOT)/../../packages/react-native-quick-crypto/ios/libsodium-stable/src/libsodium/include\"",
91
+ "\"$(PODS_ROOT)/../../packages/react-native-quick-crypto/ios/libsodium-stable/src/libsodium/include/sodium\""
92
+ ]
93
+ xcconfig["HEADER_SEARCH_PATHS"] = sodium_headers.join(' ')
94
+ xcconfig["GCC_PREPROCESSOR_DEFINITIONS"] = "$(inherited) BLSALLOC_SODIUM=1"
95
+ end
96
+
97
+ s.pod_target_xcconfig = xcconfig
98
+
40
99
  # Add all files generated by Nitrogen
41
- load 'nitrogen/generated/ios/QuickCrypto+autolinking.rb'
100
+ load "nitrogen/generated/ios/QuickCrypto+autolinking.rb"
42
101
  add_nitrogen_files(s)
43
102
 
44
- s.dependency 'React-jsi'
45
- s.dependency 'React-callinvoker'
103
+ s.dependency "React-jsi"
104
+ s.dependency "React-callinvoker"
46
105
  s.dependency "OpenSSL-Universal"
47
106
  install_modules_dependencies(s)
48
107
  end
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
- <a href="https://margelo.io">
2
- <img src="./docs/img/banner.svg" width="100%" />
1
+ <a href="https://margelo.com">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="./docs/img/banner-dark.png" />
4
+ <source media="(prefers-color-scheme: light)" srcset="./docs/img/banner-light.png" />
5
+ <img alt="react-native-quick-crypto" src="./docs/img/banner-light.png" />
6
+ </picture>
3
7
  </a>
4
8
 
5
9
  # ⚡️ react-native-quick-crypto
@@ -27,7 +31,7 @@ QuickCrypto can be used as a drop-in replacement for your Web3/Crypto apps to sp
27
31
  | Version | RN Architecture | Modules |
28
32
  | ------- | ------ | ------- |
29
33
  | `1.x` | new [->](https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md) | Nitro Modules [->](https://github.com/mrousavy/nitro) |
30
- | `0.x` | old | Bridge & JSI |
34
+ | `0.x` | old, new 🤞 | Bridge & JSI |
31
35
 
32
36
  ## Benchmarks
33
37
 
@@ -12,6 +12,9 @@ add_library(
12
12
  ../cpp/cipher/CCMCipher.cpp
13
13
  ../cpp/cipher/HybridCipher.cpp
14
14
  ../cpp/cipher/OCBCipher.cpp
15
+ ../cpp/cipher/XSalsa20Cipher.cpp
16
+ ../cpp/cipher/ChaCha20Cipher.cpp
17
+ ../cpp/cipher/ChaCha20Poly1305Cipher.cpp
15
18
  ../cpp/ed25519/HybridEdKeyPair.cpp
16
19
  ../cpp/hash/HybridHash.cpp
17
20
  ../cpp/hmac/HybridHmac.cpp
@@ -44,20 +47,29 @@ find_package(openssl REQUIRED CONFIG)
44
47
  # Link all libraries together
45
48
  target_link_libraries(
46
49
  ${PACKAGE_NAME}
47
- ${LOG_LIB} # <-- Logcat logger
48
- android # <-- Android core
49
- openssl::crypto # <-- OpenSSL (Crypto)
50
+ ${LOG_LIB} # <-- Logcat logger
51
+ android # <-- Android core
52
+ openssl::crypto # <-- OpenSSL (Crypto)
53
+ )
54
+
55
+ if(SODIUM_ENABLED)
56
+ add_definitions(-DBLSALLOC_SODIUM)
57
+ find_package(sodium REQUIRED CONFIG)
58
+ target_link_libraries(
59
+ ${PACKAGE_NAME}
60
+ sodium::sodium
50
61
  )
62
+ endif()
51
63
 
52
64
  if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
53
65
  target_link_libraries(
54
66
  ${PACKAGE_NAME}
55
- ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab
67
+ ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab
56
68
  )
57
69
  else()
58
70
  target_link_libraries(
59
71
  ${PACKAGE_NAME}
60
- ReactAndroid::turbomodulejsijni
61
- ReactAndroid::react_nativemodule_core # <-- RN: React Native native module core
72
+ ReactAndroid::react_nativemodule_core # <-- RN: TurboModules Core
73
+ ReactAndroid::turbomodulejsijni # <-- RN: TurboModules utils (e.g. CallInvokerHolder)
62
74
  )
63
75
  endif()
@@ -37,6 +37,9 @@ def getExtOrIntegerDefault(name) {
37
37
  return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["QuickCrypto_" + name]).toInteger()
38
38
  }
39
39
 
40
+ def sodiumEnabled = hasProperty('sodiumEnabled') ? project.property('sodiumEnabled').toBoolean() : false // Default to false
41
+ logger.warn("[QuickCrypto] Has libsodium ${sodiumEnabled ? "enabled" : "disabled"}!")
42
+
40
43
  android {
41
44
  namespace "com.margelo.nitro.quickcrypto"
42
45
  ndkVersion getExtOrDefault("ndkVersion")
@@ -50,7 +53,7 @@ android {
50
53
  externalNativeBuild {
51
54
  cmake {
52
55
  cppFlags "-frtti -fexceptions -Wall -fstack-protector-all"
53
- arguments "-DANDROID_STL=c++_shared"
56
+ arguments "-DANDROID_STL=c++_shared", "-DSODIUM_ENABLED=${sodiumEnabled}"
54
57
  abiFilters (*reactNativeArchitectures())
55
58
 
56
59
  buildTypes {
@@ -139,6 +142,11 @@ dependencies {
139
142
 
140
143
  // Add a dependency on OpenSSL
141
144
  implementation 'io.github.ronickg:openssl:3.3.2'
145
+
146
+ if (sodiumEnabled) {
147
+ // Add a dependency on libsodium
148
+ implementation 'io.github.ronickg:sodium:1.0.20'
149
+ }
142
150
  }
143
151
 
144
152
  if (isNewArchitectureEnabled()) {
@@ -0,0 +1,97 @@
1
+ #include "ChaCha20Cipher.hpp"
2
+ #include "Utils.hpp"
3
+ #include <openssl/err.h>
4
+ #include <openssl/evp.h>
5
+ #include <stdexcept>
6
+
7
+ namespace margelo::nitro::crypto {
8
+
9
+ void ChaCha20Cipher::init(const std::shared_ptr<ArrayBuffer> cipher_key, const std::shared_ptr<ArrayBuffer> iv) {
10
+ // Clean up any existing context
11
+ if (ctx) {
12
+ EVP_CIPHER_CTX_free(ctx);
13
+ ctx = nullptr;
14
+ }
15
+
16
+ // Get ChaCha20 cipher implementation
17
+ const EVP_CIPHER* cipher = EVP_chacha20();
18
+ if (!cipher) {
19
+ throw std::runtime_error("Failed to get ChaCha20 cipher implementation");
20
+ }
21
+
22
+ // Create a new context
23
+ ctx = EVP_CIPHER_CTX_new();
24
+ if (!ctx) {
25
+ throw std::runtime_error("Failed to create cipher context");
26
+ }
27
+
28
+ // Initialize the encryption/decryption operation
29
+ if (EVP_CipherInit_ex(ctx, cipher, nullptr, nullptr, nullptr, is_cipher) != 1) {
30
+ unsigned long err = ERR_get_error();
31
+ char err_buf[256];
32
+ ERR_error_string_n(err, err_buf, sizeof(err_buf));
33
+ EVP_CIPHER_CTX_free(ctx);
34
+ ctx = nullptr;
35
+ throw std::runtime_error("ChaCha20Cipher: Failed initial CipherInit setup: " + std::string(err_buf));
36
+ }
37
+
38
+ // Set key and IV
39
+ auto native_key = ToNativeArrayBuffer(cipher_key);
40
+ auto native_iv = ToNativeArrayBuffer(iv);
41
+
42
+ // Validate key size
43
+ if (native_key->size() != kKeySize) {
44
+ throw std::runtime_error("ChaCha20 key must be 32 bytes");
45
+ }
46
+
47
+ // Validate IV size
48
+ if (native_iv->size() != kIVSize) {
49
+ throw std::runtime_error("ChaCha20 IV must be 16 bytes");
50
+ }
51
+
52
+ const unsigned char* key_ptr = reinterpret_cast<const unsigned char*>(native_key->data());
53
+ const unsigned char* iv_ptr = reinterpret_cast<const unsigned char*>(native_iv->data());
54
+
55
+ if (EVP_CipherInit_ex(ctx, nullptr, nullptr, key_ptr, iv_ptr, is_cipher) != 1) {
56
+ unsigned long err = ERR_get_error();
57
+ char err_buf[256];
58
+ ERR_error_string_n(err, err_buf, sizeof(err_buf));
59
+ EVP_CIPHER_CTX_free(ctx);
60
+ ctx = nullptr;
61
+ throw std::runtime_error("ChaCha20Cipher: Failed to set key/IV: " + std::string(err_buf));
62
+ }
63
+ }
64
+
65
+ std::shared_ptr<ArrayBuffer> ChaCha20Cipher::update(const std::shared_ptr<ArrayBuffer>& data) {
66
+ checkCtx();
67
+ auto native_data = ToNativeArrayBuffer(data);
68
+ size_t in_len = native_data->size();
69
+ if (in_len > INT_MAX) {
70
+ throw std::runtime_error("Message too long");
71
+ }
72
+
73
+ // For ChaCha20, output size equals input size since it's a stream cipher
74
+ int out_len = in_len;
75
+ uint8_t* out = new uint8_t[out_len];
76
+
77
+ // Perform the cipher update operation
78
+ if (EVP_CipherUpdate(ctx, out, &out_len, native_data->data(), in_len) != 1) {
79
+ delete[] out;
80
+ unsigned long err = ERR_get_error();
81
+ char err_buf[256];
82
+ ERR_error_string_n(err, err_buf, sizeof(err_buf));
83
+ throw std::runtime_error("ChaCha20Cipher: Failed to update: " + std::string(err_buf));
84
+ }
85
+
86
+ // Create and return a new buffer of exact size needed
87
+ return std::make_shared<NativeArrayBuffer>(out, out_len, [=]() { delete[] out; });
88
+ }
89
+
90
+ std::shared_ptr<ArrayBuffer> ChaCha20Cipher::final() {
91
+ checkCtx();
92
+ // For ChaCha20, final() should return an empty buffer since it's a stream cipher
93
+ unsigned char* empty_output = new unsigned char[0];
94
+ return std::make_shared<NativeArrayBuffer>(empty_output, 0, [=]() { delete[] empty_output; });
95
+ }
96
+
97
+ } // namespace margelo::nitro::crypto
@@ -0,0 +1,25 @@
1
+ #pragma once
2
+
3
+ #include "HybridCipher.hpp"
4
+
5
+ namespace margelo::nitro::crypto {
6
+
7
+ class ChaCha20Cipher : public HybridCipher {
8
+ public:
9
+ ChaCha20Cipher() : HybridObject(TAG) {}
10
+ ~ChaCha20Cipher() {
11
+ // Let parent destructor free the context
12
+ ctx = nullptr;
13
+ }
14
+
15
+ void init(const std::shared_ptr<ArrayBuffer> cipher_key, const std::shared_ptr<ArrayBuffer> iv) override;
16
+ std::shared_ptr<ArrayBuffer> update(const std::shared_ptr<ArrayBuffer>& data) override;
17
+ std::shared_ptr<ArrayBuffer> final() override;
18
+
19
+ private:
20
+ // ChaCha20 uses a 256-bit key (32 bytes) and a 128-bit IV (16 bytes)
21
+ static constexpr int kKeySize = 32;
22
+ static constexpr int kIVSize = 16;
23
+ };
24
+
25
+ } // namespace margelo::nitro::crypto
@@ -0,0 +1,170 @@
1
+ #include "ChaCha20Poly1305Cipher.hpp"
2
+ #include "Utils.hpp"
3
+ #include <openssl/err.h>
4
+ #include <openssl/evp.h>
5
+ #include <stdexcept>
6
+
7
+ namespace margelo::nitro::crypto {
8
+
9
+ void ChaCha20Poly1305Cipher::init(const std::shared_ptr<ArrayBuffer> cipher_key, const std::shared_ptr<ArrayBuffer> iv) {
10
+ // Clean up any existing context
11
+ if (ctx) {
12
+ EVP_CIPHER_CTX_free(ctx);
13
+ ctx = nullptr;
14
+ }
15
+
16
+ // Get ChaCha20-Poly1305 cipher implementation
17
+ const EVP_CIPHER* cipher = EVP_chacha20_poly1305();
18
+ if (!cipher) {
19
+ throw std::runtime_error("Failed to get ChaCha20-Poly1305 cipher implementation");
20
+ }
21
+
22
+ // Create a new context
23
+ ctx = EVP_CIPHER_CTX_new();
24
+ if (!ctx) {
25
+ throw std::runtime_error("Failed to create cipher context");
26
+ }
27
+
28
+ // Initialize the encryption/decryption operation
29
+ if (EVP_CipherInit_ex(ctx, cipher, nullptr, nullptr, nullptr, is_cipher) != 1) {
30
+ unsigned long err = ERR_get_error();
31
+ char err_buf[256];
32
+ ERR_error_string_n(err, err_buf, sizeof(err_buf));
33
+ EVP_CIPHER_CTX_free(ctx);
34
+ ctx = nullptr;
35
+ throw std::runtime_error("ChaCha20Poly1305Cipher: Failed initial CipherInit setup: " + std::string(err_buf));
36
+ }
37
+
38
+ // Set key and IV
39
+ auto native_key = ToNativeArrayBuffer(cipher_key);
40
+ auto native_iv = ToNativeArrayBuffer(iv);
41
+
42
+ // Validate key size
43
+ if (native_key->size() != kKeySize) {
44
+ throw std::runtime_error("ChaCha20-Poly1305 key must be 32 bytes");
45
+ }
46
+
47
+ // Validate nonce size
48
+ if (native_iv->size() != kNonceSize) {
49
+ throw std::runtime_error("ChaCha20-Poly1305 nonce must be 12 bytes");
50
+ }
51
+
52
+ const unsigned char* key_ptr = reinterpret_cast<const unsigned char*>(native_key->data());
53
+ const unsigned char* iv_ptr = reinterpret_cast<const unsigned char*>(native_iv->data());
54
+
55
+ if (EVP_CipherInit_ex(ctx, nullptr, nullptr, key_ptr, iv_ptr, is_cipher) != 1) {
56
+ unsigned long err = ERR_get_error();
57
+ char err_buf[256];
58
+ ERR_error_string_n(err, err_buf, sizeof(err_buf));
59
+ EVP_CIPHER_CTX_free(ctx);
60
+ ctx = nullptr;
61
+ throw std::runtime_error("ChaCha20Poly1305Cipher: Failed to set key/IV: " + std::string(err_buf));
62
+ }
63
+
64
+ // Reset final_called flag
65
+ final_called = false;
66
+ }
67
+
68
+ std::shared_ptr<ArrayBuffer> ChaCha20Poly1305Cipher::update(const std::shared_ptr<ArrayBuffer>& data) {
69
+ checkCtx();
70
+ auto native_data = ToNativeArrayBuffer(data);
71
+ size_t in_len = native_data->size();
72
+ if (in_len > INT_MAX) {
73
+ throw std::runtime_error("Message too long");
74
+ }
75
+
76
+ // For ChaCha20-Poly1305, output size equals input size since it's a stream cipher
77
+ int out_len = in_len;
78
+ uint8_t* out = new uint8_t[out_len];
79
+
80
+ // Perform the cipher update operation
81
+ if (EVP_CipherUpdate(ctx, out, &out_len, native_data->data(), in_len) != 1) {
82
+ delete[] out;
83
+ unsigned long err = ERR_get_error();
84
+ char err_buf[256];
85
+ ERR_error_string_n(err, err_buf, sizeof(err_buf));
86
+ throw std::runtime_error("ChaCha20Poly1305Cipher: Failed to update: " + std::string(err_buf));
87
+ }
88
+
89
+ // Create and return a new buffer of exact size needed
90
+ return std::make_shared<NativeArrayBuffer>(out, out_len, [=]() { delete[] out; });
91
+ }
92
+
93
+ std::shared_ptr<ArrayBuffer> ChaCha20Poly1305Cipher::final() {
94
+ checkCtx();
95
+
96
+ // For ChaCha20-Poly1305, we need to call final to generate the tag
97
+ int out_len = 0;
98
+ unsigned char* out = new unsigned char[0];
99
+
100
+ if (EVP_CipherFinal_ex(ctx, out, &out_len) != 1) {
101
+ delete[] out;
102
+ unsigned long err = ERR_get_error();
103
+ char err_buf[256];
104
+ ERR_error_string_n(err, err_buf, sizeof(err_buf));
105
+ throw std::runtime_error("ChaCha20Poly1305Cipher: Failed to finalize: " + std::string(err_buf));
106
+ }
107
+
108
+ final_called = true;
109
+ return std::make_shared<NativeArrayBuffer>(out, out_len, [=]() { delete[] out; });
110
+ }
111
+
112
+ bool ChaCha20Poly1305Cipher::setAAD(const std::shared_ptr<ArrayBuffer>& data, std::optional<double> plaintextLength) {
113
+ checkCtx();
114
+ auto native_aad = ToNativeArrayBuffer(data);
115
+ size_t aad_len = native_aad->size();
116
+
117
+ // Set AAD data
118
+ int out_len = 0;
119
+ if (EVP_CipherUpdate(ctx, nullptr, &out_len, native_aad->data(), aad_len) != 1) {
120
+ unsigned long err = ERR_get_error();
121
+ char err_buf[256];
122
+ ERR_error_string_n(err, err_buf, sizeof(err_buf));
123
+ throw std::runtime_error("ChaCha20Poly1305Cipher: Failed to set AAD: " + std::string(err_buf));
124
+ }
125
+ return true;
126
+ }
127
+
128
+ std::shared_ptr<ArrayBuffer> ChaCha20Poly1305Cipher::getAuthTag() {
129
+ checkCtx();
130
+ if (!is_cipher) {
131
+ throw std::runtime_error("getAuthTag can only be called during encryption");
132
+ }
133
+ if (!final_called) {
134
+ throw std::runtime_error("getAuthTag must be called after final()");
135
+ }
136
+
137
+ // Get the authentication tag
138
+ auto tag_buf = std::make_unique<uint8_t[]>(kTagSize);
139
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, kTagSize, tag_buf.get()) != 1) {
140
+ unsigned long err = ERR_get_error();
141
+ char err_buf[256];
142
+ ERR_error_string_n(err, err_buf, sizeof(err_buf));
143
+ throw std::runtime_error("ChaCha20Poly1305Cipher: Failed to get auth tag: " + std::string(err_buf));
144
+ }
145
+
146
+ uint8_t* raw_ptr = tag_buf.get();
147
+ return std::make_shared<NativeArrayBuffer>(tag_buf.release(), kTagSize, [raw_ptr]() { delete[] raw_ptr; });
148
+ }
149
+
150
+ bool ChaCha20Poly1305Cipher::setAuthTag(const std::shared_ptr<ArrayBuffer>& tag) {
151
+ checkCtx();
152
+ if (is_cipher) {
153
+ throw std::runtime_error("setAuthTag can only be called during decryption");
154
+ }
155
+
156
+ auto native_tag = ToNativeArrayBuffer(tag);
157
+ if (native_tag->size() != kTagSize) {
158
+ throw std::runtime_error("ChaCha20-Poly1305 tag must be 16 bytes");
159
+ }
160
+
161
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, kTagSize, native_tag->data()) != 1) {
162
+ unsigned long err = ERR_get_error();
163
+ char err_buf[256];
164
+ ERR_error_string_n(err, err_buf, sizeof(err_buf));
165
+ throw std::runtime_error("ChaCha20Poly1305Cipher: Failed to set auth tag: " + std::string(err_buf));
166
+ }
167
+ return true;
168
+ }
169
+
170
+ } // namespace margelo::nitro::crypto
@@ -0,0 +1,30 @@
1
+ #pragma once
2
+
3
+ #include "HybridCipher.hpp"
4
+
5
+ namespace margelo::nitro::crypto {
6
+
7
+ class ChaCha20Poly1305Cipher : public HybridCipher {
8
+ public:
9
+ ChaCha20Poly1305Cipher() : HybridObject(TAG), final_called(false) {}
10
+ ~ChaCha20Poly1305Cipher() {
11
+ // Let parent destructor free the context
12
+ ctx = nullptr;
13
+ }
14
+
15
+ void init(const std::shared_ptr<ArrayBuffer> cipher_key, const std::shared_ptr<ArrayBuffer> iv) override;
16
+ std::shared_ptr<ArrayBuffer> update(const std::shared_ptr<ArrayBuffer>& data) override;
17
+ std::shared_ptr<ArrayBuffer> final() override;
18
+ bool setAAD(const std::shared_ptr<ArrayBuffer>& data, std::optional<double> plaintextLength) override;
19
+ std::shared_ptr<ArrayBuffer> getAuthTag() override;
20
+ bool setAuthTag(const std::shared_ptr<ArrayBuffer>& tag) override;
21
+
22
+ private:
23
+ // ChaCha20-Poly1305 uses a 256-bit key (32 bytes) and a 96-bit nonce (12 bytes)
24
+ static constexpr int kKeySize = 32;
25
+ static constexpr int kNonceSize = 12;
26
+ static constexpr int kTagSize = 16; // Poly1305 tag is always 16 bytes
27
+ bool final_called;
28
+ };
29
+
30
+ } // namespace margelo::nitro::crypto
@@ -115,7 +115,6 @@ std::shared_ptr<ArrayBuffer> HybridCipher::final() {
115
115
  auto out_buf = std::make_unique<uint8_t[]>(block_size);
116
116
  int out_len = 0;
117
117
 
118
- int mode = EVP_CIPHER_CTX_mode(ctx);
119
118
  int ret = EVP_CipherFinal_ex(ctx, out_buf.get(), &out_len);
120
119
  if (!ret) {
121
120
  unsigned long err = ERR_get_error();
@@ -1,6 +1,5 @@
1
1
  #pragma once
2
2
 
3
- #include <memory>
4
3
  #include <openssl/core_names.h>
5
4
  #include <openssl/err.h>
6
5
  #include <openssl/evp.h>
@@ -5,8 +5,12 @@
5
5
  #include <string>
6
6
 
7
7
  #include "CCMCipher.hpp"
8
+ #include "ChaCha20Cipher.hpp"
9
+ #include "ChaCha20Poly1305Cipher.hpp"
8
10
  #include "HybridCipherFactorySpec.hpp"
9
11
  #include "OCBCipher.hpp"
12
+ #include "Utils.hpp"
13
+ #include "XSalsa20Cipher.hpp"
10
14
 
11
15
  namespace margelo::nitro::crypto {
12
16
 
@@ -20,39 +24,68 @@ class HybridCipherFactory : public HybridCipherFactorySpec {
20
24
  public:
21
25
  // Factory method exposed to JS
22
26
  inline std::shared_ptr<HybridCipherSpec> createCipher(const CipherArgs& args) {
23
- // Create a temporary cipher context to determine the mode
27
+ // Create the appropriate cipher instance based on mode
28
+ std::shared_ptr<HybridCipher> cipherInstance;
29
+
30
+ // OpenSSL
31
+ // temporary cipher context to determine the mode
24
32
  EVP_CIPHER* cipher = EVP_CIPHER_fetch(nullptr, args.cipherType.c_str(), nullptr);
25
- if (!cipher) {
26
- throw std::runtime_error("Invalid cipher type: " + args.cipherType);
27
- }
33
+ if (cipher) {
34
+ int mode = EVP_CIPHER_get_mode(cipher);
28
35
 
29
- int mode = EVP_CIPHER_get_mode(cipher);
36
+ switch (mode) {
37
+ case EVP_CIPH_OCB_MODE: {
38
+ cipherInstance = std::make_shared<OCBCipher>();
39
+ cipherInstance->setArgs(args);
40
+ // Pass tag length (default 16 if not present)
41
+ size_t tag_len = args.authTagLen.has_value() ? static_cast<size_t>(args.authTagLen.value()) : 16;
42
+ std::static_pointer_cast<OCBCipher>(cipherInstance)->init(args.cipherKey, args.iv, tag_len);
43
+ return cipherInstance;
44
+ }
45
+ case EVP_CIPH_CCM_MODE: {
46
+ cipherInstance = std::make_shared<CCMCipher>();
47
+ cipherInstance->setArgs(args);
48
+ cipherInstance->init(args.cipherKey, args.iv);
49
+ return cipherInstance;
50
+ }
51
+ case EVP_CIPH_STREAM_CIPHER: {
52
+ // Check for ChaCha20 variants specifically
53
+ std::string cipherName = toLower(args.cipherType);
54
+ if (cipherName == "chacha20") {
55
+ cipherInstance = std::make_shared<ChaCha20Cipher>();
56
+ cipherInstance->setArgs(args);
57
+ cipherInstance->init(args.cipherKey, args.iv);
58
+ return cipherInstance;
59
+ }
60
+ if (cipherName == "chacha20-poly1305") {
61
+ cipherInstance = std::make_shared<ChaCha20Poly1305Cipher>();
62
+ cipherInstance->setArgs(args);
63
+ cipherInstance->init(args.cipherKey, args.iv);
64
+ return cipherInstance;
65
+ }
66
+ }
67
+ default: {
68
+ // Default case for other ciphers
69
+ cipherInstance = std::make_shared<HybridCipher>();
70
+ cipherInstance->setArgs(args);
71
+ cipherInstance->init(args.cipherKey, args.iv);
72
+ return cipherInstance;
73
+ }
74
+ }
75
+ }
30
76
  EVP_CIPHER_free(cipher);
31
77
 
32
- // Create the appropriate cipher instance based on mode
33
- std::shared_ptr<HybridCipher> cipherInstance;
34
- switch (mode) {
35
- case EVP_CIPH_OCB_MODE: {
36
- cipherInstance = std::make_shared<OCBCipher>();
37
- cipherInstance->setArgs(args);
38
- // Pass tag length (default 16 if not present)
39
- size_t tag_len = args.authTagLen.has_value() ? static_cast<size_t>(args.authTagLen.value()) : 16;
40
- std::static_pointer_cast<OCBCipher>(cipherInstance)->init(args.cipherKey, args.iv, tag_len);
41
- return cipherInstance;
42
- }
43
- case EVP_CIPH_CCM_MODE: {
44
- cipherInstance = std::make_shared<CCMCipher>();
45
- cipherInstance->setArgs(args);
46
- cipherInstance->init(args.cipherKey, args.iv);
47
- return cipherInstance;
48
- }
49
- default: {
50
- cipherInstance = std::make_shared<HybridCipher>();
51
- cipherInstance->setArgs(args);
52
- cipherInstance->init(args.cipherKey, args.iv);
53
- return cipherInstance;
54
- }
78
+ // libsodium
79
+ std::string cipherName = toLower(args.cipherType);
80
+ if (cipherName == "xsalsa20") {
81
+ cipherInstance = std::make_shared<XSalsa20Cipher>();
82
+ cipherInstance->setArgs(args);
83
+ cipherInstance->init(args.cipherKey, args.iv);
84
+ return cipherInstance;
55
85
  }
86
+
87
+ // Unsupported cipher type
88
+ throw std::runtime_error("Unsupported or unknown cipher type: " + args.cipherType);
56
89
  }
57
90
  };
58
91