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.
- package/QuickCrypto.podspec +66 -7
- package/README.md +7 -3
- package/android/CMakeLists.txt +18 -6
- package/android/build.gradle +9 -1
- package/cpp/cipher/ChaCha20Cipher.cpp +97 -0
- package/cpp/cipher/ChaCha20Cipher.hpp +25 -0
- package/cpp/cipher/ChaCha20Poly1305Cipher.cpp +170 -0
- package/cpp/cipher/ChaCha20Poly1305Cipher.hpp +30 -0
- package/cpp/cipher/HybridCipher.cpp +0 -1
- package/cpp/cipher/HybridCipher.hpp +0 -1
- package/cpp/cipher/HybridCipherFactory.hpp +61 -28
- package/cpp/cipher/XSalsa20Cipher.cpp +61 -0
- package/cpp/cipher/XSalsa20Cipher.hpp +33 -0
- package/cpp/random/HybridRandom.cpp +2 -2
- package/cpp/utils/Utils.hpp +15 -0
- package/deps/fastpbkdf2/fastpbkdf2.c +5 -1
- package/lib/commonjs/cipher.js +29 -6
- package/lib/commonjs/cipher.js.map +1 -1
- package/lib/commonjs/index.js +5 -5
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/cipher.js +28 -5
- package/lib/module/cipher.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/typescript/cipher.d.ts +17 -9
- package/lib/typescript/cipher.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +11 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/cipher.ts +41 -9
- package/src/index.ts +1 -1
package/QuickCrypto.podspec
CHANGED
|
@@ -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
|
|
20
|
+
s.source = { :git => "https://github.com/margelo/react-native-quick-crypto.git", :tag => "#{s.version}" }
|
|
21
21
|
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
100
|
+
load "nitrogen/generated/ios/QuickCrypto+autolinking.rb"
|
|
42
101
|
add_nitrogen_files(s)
|
|
43
102
|
|
|
44
|
-
s.dependency
|
|
45
|
-
s.dependency
|
|
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.
|
|
2
|
-
<
|
|
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
|
|
package/android/CMakeLists.txt
CHANGED
|
@@ -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}
|
|
48
|
-
android
|
|
49
|
-
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
|
|
67
|
+
ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab
|
|
56
68
|
)
|
|
57
69
|
else()
|
|
58
70
|
target_link_libraries(
|
|
59
71
|
${PACKAGE_NAME}
|
|
60
|
-
ReactAndroid::
|
|
61
|
-
ReactAndroid::
|
|
72
|
+
ReactAndroid::react_nativemodule_core # <-- RN: TurboModules Core
|
|
73
|
+
ReactAndroid::turbomodulejsijni # <-- RN: TurboModules utils (e.g. CallInvokerHolder)
|
|
62
74
|
)
|
|
63
75
|
endif()
|
package/android/build.gradle
CHANGED
|
@@ -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();
|
|
@@ -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
|
|
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 (
|
|
26
|
-
|
|
27
|
-
}
|
|
33
|
+
if (cipher) {
|
|
34
|
+
int mode = EVP_CIPHER_get_mode(cipher);
|
|
28
35
|
|
|
29
|
-
|
|
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
|
-
//
|
|
33
|
-
std::
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|