react-native-quick-crypto 1.0.0 → 1.0.2
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 +19 -52
- package/android/CMakeLists.txt +4 -2
- package/android/build.gradle +1 -1
- package/cpp/cipher/HybridCipher.cpp +20 -3
- package/cpp/cipher/HybridRsaCipher.cpp +20 -1
- package/cpp/ed25519/HybridEdKeyPair.cpp +8 -2
- package/cpp/keys/HybridKeyObjectHandle.cpp +8 -0
- package/cpp/keys/KeyObjectData.hpp +1 -1
- package/cpp/mldsa/HybridMlDsaKeyPair.cpp +264 -0
- package/cpp/mldsa/HybridMlDsaKeyPair.hpp +47 -0
- package/cpp/sign/HybridSignHandle.cpp +97 -22
- package/cpp/sign/HybridVerifyHandle.cpp +90 -21
- package/deps/ncrypto/.bazelignore +4 -0
- package/deps/ncrypto/.bazelrc +2 -0
- package/deps/ncrypto/.bazelversion +1 -0
- package/deps/ncrypto/.clang-format +111 -0
- package/deps/ncrypto/.github/workflows/bazel.yml +58 -0
- package/deps/ncrypto/.github/workflows/linter.yml +38 -0
- package/deps/ncrypto/.github/workflows/macos.yml +43 -0
- package/deps/ncrypto/.github/workflows/ubuntu.yml +46 -0
- package/deps/ncrypto/.github/workflows/visual-studio.yml +49 -0
- package/deps/ncrypto/.python-version +1 -0
- package/deps/ncrypto/BUILD.bazel +36 -0
- package/deps/ncrypto/CMakeLists.txt +55 -0
- package/deps/ncrypto/LICENSE +21 -0
- package/deps/ncrypto/MODULE.bazel +1 -0
- package/deps/ncrypto/MODULE.bazel.lock +280 -0
- package/deps/ncrypto/README.md +18 -0
- package/deps/ncrypto/WORKSPACE +15 -0
- package/deps/ncrypto/cmake/CPM.cmake +1225 -0
- package/deps/ncrypto/cmake/ncrypto-flags.cmake +16 -0
- package/deps/ncrypto/include/dh-primes.h +67 -0
- package/deps/ncrypto/{ncrypto.h → include/ncrypto.h} +361 -89
- package/deps/ncrypto/patches/0001-Expose-libdecrepit-so-NodeJS-can-use-it-for-ncrypto.patch +28 -0
- package/deps/ncrypto/pyproject.toml +38 -0
- package/deps/ncrypto/src/CMakeLists.txt +15 -0
- package/deps/ncrypto/src/engine.cpp +93 -0
- package/deps/ncrypto/{ncrypto.cc → src/ncrypto.cpp} +1168 -234
- package/deps/ncrypto/tests/BUILD.bazel +9 -0
- package/deps/ncrypto/tests/CMakeLists.txt +7 -0
- package/deps/ncrypto/tests/basic.cpp +86 -0
- package/deps/ncrypto/tools/run-clang-format.sh +42 -0
- package/lib/commonjs/ed.js +68 -0
- package/lib/commonjs/ed.js.map +1 -1
- package/lib/commonjs/keys/classes.js +6 -0
- package/lib/commonjs/keys/classes.js.map +1 -1
- package/lib/commonjs/mldsa.js +69 -0
- package/lib/commonjs/mldsa.js.map +1 -0
- package/lib/commonjs/specs/mlDsaKeyPair.nitro.js +6 -0
- package/lib/commonjs/specs/mlDsaKeyPair.nitro.js.map +1 -0
- package/lib/commonjs/subtle.js +483 -13
- package/lib/commonjs/subtle.js.map +1 -1
- package/lib/commonjs/utils/types.js.map +1 -1
- package/lib/module/ed.js +66 -0
- package/lib/module/ed.js.map +1 -1
- package/lib/module/keys/classes.js +6 -0
- package/lib/module/keys/classes.js.map +1 -1
- package/lib/module/mldsa.js +63 -0
- package/lib/module/mldsa.js.map +1 -0
- package/lib/module/specs/mlDsaKeyPair.nitro.js +4 -0
- package/lib/module/specs/mlDsaKeyPair.nitro.js.map +1 -0
- package/lib/module/subtle.js +484 -14
- package/lib/module/subtle.js.map +1 -1
- package/lib/module/utils/types.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/typescript/ed.d.ts +4 -1
- package/lib/typescript/ed.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/keys/classes.d.ts +2 -0
- package/lib/typescript/keys/classes.d.ts.map +1 -1
- package/lib/typescript/mldsa.d.ts +18 -0
- package/lib/typescript/mldsa.d.ts.map +1 -0
- package/lib/typescript/specs/mlDsaKeyPair.nitro.d.ts +16 -0
- package/lib/typescript/specs/mlDsaKeyPair.nitro.d.ts.map +1 -0
- package/lib/typescript/subtle.d.ts +4 -1
- package/lib/typescript/subtle.d.ts.map +1 -1
- package/lib/typescript/utils/types.d.ts +14 -6
- package/lib/typescript/utils/types.d.ts.map +1 -1
- package/nitrogen/generated/android/QuickCrypto+autolinking.cmake +1 -0
- package/nitrogen/generated/android/QuickCryptoOnLoad.cpp +10 -0
- package/nitrogen/generated/ios/QuickCryptoAutolinking.mm +10 -0
- package/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp +12 -0
- package/nitrogen/generated/shared/c++/HybridMlDsaKeyPairSpec.cpp +29 -0
- package/nitrogen/generated/shared/c++/HybridMlDsaKeyPairSpec.hpp +73 -0
- package/package.json +7 -3
- package/src/ed.ts +102 -0
- package/src/keys/classes.ts +9 -0
- package/src/mldsa.ts +125 -0
- package/src/specs/mlDsaKeyPair.nitro.ts +29 -0
- package/src/subtle.ts +667 -17
- package/src/utils/types.ts +27 -6
package/QuickCrypto.podspec
CHANGED
|
@@ -23,63 +23,21 @@ Pod::Spec.new do |s|
|
|
|
23
23
|
Pod::UI.puts("[QuickCrypto] 🧂 has libsodium #{sodium_enabled ? "enabled" : "disabled"}!")
|
|
24
24
|
|
|
25
25
|
if sodium_enabled
|
|
26
|
-
#
|
|
27
|
-
#
|
|
26
|
+
# Build libsodium from source for XSalsa20 cipher support
|
|
27
|
+
# CocoaPods packages are outdated (1.0.12) and SPM causes module conflicts
|
|
28
28
|
s.prepare_command = <<-CMD
|
|
29
|
-
set -e
|
|
30
|
-
set -x # Print commands as they execute
|
|
31
|
-
|
|
32
|
-
# Create ios directory if it doesn't exist
|
|
29
|
+
set -e
|
|
33
30
|
mkdir -p ios
|
|
34
|
-
|
|
35
|
-
# Download libsodium with verbose output
|
|
36
|
-
echo "Downloading libsodium..."
|
|
37
|
-
curl -L -v -o ios/libsodium.tar.gz https://download.libsodium.org/libsodium/releases/libsodium-1.0.20-stable.tar.gz
|
|
38
|
-
|
|
39
|
-
# Verify download
|
|
40
|
-
if [ ! -f ios/libsodium.tar.gz ]; then
|
|
41
|
-
echo "ERROR: Failed to download libsodium.tar.gz"
|
|
42
|
-
exit 1
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
echo "Download size: $(wc -c < ios/libsodium.tar.gz) bytes"
|
|
46
|
-
|
|
47
|
-
# Clean previous extraction
|
|
48
|
-
rm -rf ios/libsodium-stable
|
|
49
|
-
|
|
50
|
-
# Extract the full tarball
|
|
51
|
-
echo "Extracting libsodium..."
|
|
31
|
+
curl -L -o ios/libsodium.tar.gz https://download.libsodium.org/libsodium/releases/libsodium-1.0.20-stable.tar.gz
|
|
52
32
|
tar -xzf ios/libsodium.tar.gz -C ios
|
|
53
|
-
|
|
54
|
-
# Verify extraction
|
|
55
|
-
if [ ! -d ios/libsodium-stable ]; then
|
|
56
|
-
echo "ERROR: Failed to extract libsodium"
|
|
57
|
-
exit 1
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
# Run configure and make to generate all headers including private ones
|
|
61
|
-
echo "Configuring libsodium..."
|
|
62
33
|
cd ios/libsodium-stable
|
|
63
34
|
./configure --disable-shared --enable-static
|
|
64
|
-
|
|
65
|
-
echo "Building libsodium..."
|
|
66
35
|
make -j$(sysctl -n hw.ncpu)
|
|
67
|
-
|
|
68
|
-
# Verify build success
|
|
69
|
-
if [ ! -f src/libsodium/.libs/libsodium.a ]; then
|
|
70
|
-
echo "ERROR: libsodium build failed - static library not found"
|
|
71
|
-
exit 1
|
|
72
|
-
fi
|
|
73
|
-
|
|
74
|
-
echo "libsodium build completed successfully"
|
|
75
|
-
|
|
76
|
-
# Cleanup
|
|
77
36
|
cd ../../
|
|
78
37
|
rm -f ios/libsodium.tar.gz
|
|
79
38
|
CMD
|
|
80
39
|
else
|
|
81
40
|
s.prepare_command = <<-CMD
|
|
82
|
-
# Clean up libsodium files if they exist
|
|
83
41
|
rm -rf ios/libsodium-stable
|
|
84
42
|
rm -f ios/libsodium.tar.gz
|
|
85
43
|
CMD
|
|
@@ -92,10 +50,12 @@ Pod::Spec.new do |s|
|
|
|
92
50
|
"ios/**/*.{h,m,mm}",
|
|
93
51
|
# implementation (C++)
|
|
94
52
|
"cpp/**/*.{hpp,cpp}",
|
|
95
|
-
# dependencies (C++)
|
|
96
|
-
"deps
|
|
53
|
+
# dependencies (C++) - ncrypto
|
|
54
|
+
"deps/ncrypto/include/*.{h}",
|
|
55
|
+
"deps/ncrypto/src/*.{cpp}",
|
|
97
56
|
# dependencies (C) - exclude BLAKE3 x86 SIMD files (only use portable + NEON for ARM)
|
|
98
|
-
"deps
|
|
57
|
+
"deps/blake3/c/*.{h,c}",
|
|
58
|
+
"deps/fastpbkdf2/*.{h,c}",
|
|
99
59
|
]
|
|
100
60
|
|
|
101
61
|
# Exclude BLAKE3 x86-specific SIMD implementations (SSE2, SSE4.1, AVX2, AVX-512)
|
|
@@ -148,7 +108,7 @@ Pod::Spec.new do |s|
|
|
|
148
108
|
# Add cpp subdirectories to header search paths
|
|
149
109
|
cpp_headers = [
|
|
150
110
|
"\"$(PODS_TARGET_SRCROOT)/cpp/utils\"",
|
|
151
|
-
"\"$(PODS_TARGET_SRCROOT)/deps/ncrypto\"",
|
|
111
|
+
"\"$(PODS_TARGET_SRCROOT)/deps/ncrypto/include\"",
|
|
152
112
|
"\"$(PODS_TARGET_SRCROOT)/deps/blake3/c\"",
|
|
153
113
|
"\"$(PODS_TARGET_SRCROOT)/deps/fastpbkdf2\""
|
|
154
114
|
]
|
|
@@ -162,7 +122,7 @@ Pod::Spec.new do |s|
|
|
|
162
122
|
"\"$(PODS_ROOT)/../../packages/react-native-quick-crypto/ios/libsodium-stable/src/libsodium/include/sodium\""
|
|
163
123
|
]
|
|
164
124
|
xcconfig["HEADER_SEARCH_PATHS"] = (cpp_headers + sodium_headers).join(' ')
|
|
165
|
-
xcconfig["GCC_PREPROCESSOR_DEFINITIONS"] = "$(inherited) BLSALLOC_SODIUM=1"
|
|
125
|
+
xcconfig["GCC_PREPROCESSOR_DEFINITIONS"] = "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES BLSALLOC_SODIUM=1"
|
|
166
126
|
else
|
|
167
127
|
xcconfig["HEADER_SEARCH_PATHS"] = cpp_headers.join(' ')
|
|
168
128
|
end
|
|
@@ -175,6 +135,13 @@ Pod::Spec.new do |s|
|
|
|
175
135
|
|
|
176
136
|
s.dependency "React-jsi"
|
|
177
137
|
s.dependency "React-callinvoker"
|
|
178
|
-
|
|
138
|
+
|
|
139
|
+
# OpenSSL 3.6+ via SPM for ML-DSA (post-quantum cryptography) support
|
|
140
|
+
spm_dependency(s,
|
|
141
|
+
url: 'https://github.com/krzyzanowskim/OpenSSL.git',
|
|
142
|
+
requirement: {kind: 'upToNextMajorVersion', minimumVersion: '3.6.0'},
|
|
143
|
+
products: ['OpenSSL']
|
|
144
|
+
)
|
|
145
|
+
|
|
179
146
|
install_modules_dependencies(s)
|
|
180
147
|
end
|
package/android/CMakeLists.txt
CHANGED
|
@@ -40,6 +40,7 @@ add_library(
|
|
|
40
40
|
../cpp/hmac/HybridHmac.cpp
|
|
41
41
|
../cpp/keys/HybridKeyObjectHandle.cpp
|
|
42
42
|
../cpp/keys/KeyObjectData.cpp
|
|
43
|
+
../cpp/mldsa/HybridMlDsaKeyPair.cpp
|
|
43
44
|
../cpp/pbkdf2/HybridPbkdf2.cpp
|
|
44
45
|
../cpp/random/HybridRandom.cpp
|
|
45
46
|
../cpp/rsa/HybridRsaKeyPair.cpp
|
|
@@ -47,7 +48,7 @@ add_library(
|
|
|
47
48
|
../cpp/sign/HybridVerifyHandle.cpp
|
|
48
49
|
${BLAKE3_SOURCES}
|
|
49
50
|
../deps/fastpbkdf2/fastpbkdf2.c
|
|
50
|
-
../deps/ncrypto/ncrypto.
|
|
51
|
+
../deps/ncrypto/src/ncrypto.cpp
|
|
51
52
|
)
|
|
52
53
|
|
|
53
54
|
# add Nitrogen specs
|
|
@@ -63,6 +64,7 @@ include_directories(
|
|
|
63
64
|
"../cpp/hash"
|
|
64
65
|
"../cpp/hmac"
|
|
65
66
|
"../cpp/keys"
|
|
67
|
+
"../cpp/mldsa"
|
|
66
68
|
"../cpp/pbkdf2"
|
|
67
69
|
"../cpp/random"
|
|
68
70
|
"../cpp/rsa"
|
|
@@ -70,7 +72,7 @@ include_directories(
|
|
|
70
72
|
"../cpp/utils"
|
|
71
73
|
"../deps/blake3/c"
|
|
72
74
|
"../deps/fastpbkdf2"
|
|
73
|
-
"../deps/ncrypto"
|
|
75
|
+
"../deps/ncrypto/include"
|
|
74
76
|
)
|
|
75
77
|
|
|
76
78
|
# Third party libraries (Prefabs)
|
package/android/build.gradle
CHANGED
|
@@ -143,7 +143,7 @@ dependencies {
|
|
|
143
143
|
implementation project(":react-native-nitro-modules")
|
|
144
144
|
|
|
145
145
|
// Add a dependency on OpenSSL
|
|
146
|
-
implementation 'io.github.ronickg:openssl:3.
|
|
146
|
+
implementation 'io.github.ronickg:openssl:3.6.0-1'
|
|
147
147
|
|
|
148
148
|
if (sodiumEnabled) {
|
|
149
149
|
// Add a dependency on libsodium
|
|
@@ -86,6 +86,14 @@ void HybridCipher::init(const std::shared_ptr<ArrayBuffer> cipher_key, const std
|
|
|
86
86
|
ctx = nullptr;
|
|
87
87
|
throw std::runtime_error("HybridCipher: Failed to set key/IV: " + std::string(err_buf));
|
|
88
88
|
}
|
|
89
|
+
|
|
90
|
+
// For AES-KW (wrap ciphers), set the WRAP_ALLOW flag and disable padding
|
|
91
|
+
std::string cipher_name(cipher_type);
|
|
92
|
+
if (cipher_name.find("-wrap") != std::string::npos) {
|
|
93
|
+
// This flag is required for AES-KW in OpenSSL 3.x
|
|
94
|
+
EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
|
|
95
|
+
EVP_CIPHER_CTX_set_padding(ctx, 0);
|
|
96
|
+
}
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
std::shared_ptr<ArrayBuffer> HybridCipher::update(const std::shared_ptr<ArrayBuffer>& data) {
|
|
@@ -100,7 +108,15 @@ std::shared_ptr<ArrayBuffer> HybridCipher::update(const std::shared_ptr<ArrayBuf
|
|
|
100
108
|
uint8_t* out = new uint8_t[out_len];
|
|
101
109
|
// Perform the cipher update operation. The real size of the output is
|
|
102
110
|
// returned in out_len
|
|
103
|
-
EVP_CipherUpdate(ctx, out, &out_len, native_data->data(), in_len);
|
|
111
|
+
int ret = EVP_CipherUpdate(ctx, out, &out_len, native_data->data(), in_len);
|
|
112
|
+
|
|
113
|
+
if (!ret) {
|
|
114
|
+
unsigned long err = ERR_get_error();
|
|
115
|
+
char err_buf[256];
|
|
116
|
+
ERR_error_string_n(err, err_buf, sizeof(err_buf));
|
|
117
|
+
delete[] out;
|
|
118
|
+
throw std::runtime_error("Cipher update failed: " + std::string(err_buf));
|
|
119
|
+
}
|
|
104
120
|
|
|
105
121
|
// Create and return a new buffer of exact size needed
|
|
106
122
|
return std::make_shared<NativeArrayBuffer>(out, out_len, [=]() { delete[] out; });
|
|
@@ -297,8 +313,9 @@ void collect_ciphers(EVP_CIPHER* cipher, void* arg) {
|
|
|
297
313
|
if (name_str == "NULL" || name_str.find("CTS") != std::string::npos ||
|
|
298
314
|
name_str.find("SIV") != std::string::npos || // Covers -SIV and -GCM-SIV
|
|
299
315
|
name_str.find("WRAP") != std::string::npos || // Covers -WRAP-INV and -WRAP-PAD-INV
|
|
300
|
-
name_str.find("SM4-") != std::string::npos
|
|
301
|
-
|
|
316
|
+
name_str.find("SM4-") != std::string::npos ||
|
|
317
|
+
name_str.find("-ETM") != std::string::npos) { // TLS-internal ciphers, not for general use
|
|
318
|
+
return; // Skip adding this cipher
|
|
302
319
|
}
|
|
303
320
|
|
|
304
321
|
// If not filtered out, add it to the list
|
|
@@ -320,13 +320,32 @@ std::shared_ptr<ArrayBuffer> HybridRsaCipher::privateDecrypt(const std::shared_p
|
|
|
320
320
|
throw std::runtime_error("Failed to determine output length: " + std::string(err_buf));
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
if (outlen == 0) {
|
|
324
|
+
EVP_PKEY_CTX_free(ctx);
|
|
325
|
+
uint8_t* empty_buf = new uint8_t[1];
|
|
326
|
+
return std::make_shared<NativeArrayBuffer>(empty_buf, 0, [empty_buf]() { delete[] empty_buf; });
|
|
327
|
+
}
|
|
328
|
+
|
|
323
329
|
auto out_buf = std::make_unique<uint8_t[]>(outlen);
|
|
324
330
|
|
|
325
331
|
if (EVP_PKEY_verify_recover(ctx, out_buf.get(), &outlen, in, inlen) <= 0) {
|
|
326
|
-
|
|
332
|
+
// OpenSSL 3.x may return failure when recovering empty plaintext
|
|
333
|
+
// In this case outlen is not updated from the initial buffer size
|
|
334
|
+
// Check the error and attempt to handle the empty data case
|
|
327
335
|
unsigned long err = ERR_get_error();
|
|
328
336
|
char err_buf[256];
|
|
329
337
|
ERR_error_string_n(err, err_buf, sizeof(err_buf));
|
|
338
|
+
|
|
339
|
+
// Check if this is an RSA library error that might indicate empty recovered data
|
|
340
|
+
// Error code 0x1C880004 is "RSA lib" error from OpenSSL 3.x provider
|
|
341
|
+
if ((err & 0xFFFFFFF) == 0x1C880004 || (err & 0xFF) == 0x04) {
|
|
342
|
+
ERR_clear_error();
|
|
343
|
+
EVP_PKEY_CTX_free(ctx);
|
|
344
|
+
uint8_t* empty_buf = new uint8_t[1];
|
|
345
|
+
return std::make_shared<NativeArrayBuffer>(empty_buf, 0, [empty_buf]() { delete[] empty_buf; });
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
EVP_PKEY_CTX_free(ctx);
|
|
330
349
|
throw std::runtime_error("Private decryption failed: " + std::string(err_buf));
|
|
331
350
|
}
|
|
332
351
|
|
|
@@ -14,14 +14,20 @@ std::shared_ptr<ArrayBuffer> HybridEdKeyPair::diffieHellman(const std::shared_pt
|
|
|
14
14
|
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>;
|
|
15
15
|
using EVP_PKEY_CTX_ptr = std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)>;
|
|
16
16
|
|
|
17
|
+
// Determine key type from curve name
|
|
18
|
+
int keyType = EVP_PKEY_X25519;
|
|
19
|
+
if (this->curve == "x448" || this->curve == "X448") {
|
|
20
|
+
keyType = EVP_PKEY_X448;
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
// 1. Create EVP_PKEY for private key (our key)
|
|
18
|
-
EVP_PKEY_ptr pkey_priv(EVP_PKEY_new_raw_private_key(
|
|
24
|
+
EVP_PKEY_ptr pkey_priv(EVP_PKEY_new_raw_private_key(keyType, NULL, privateKey->data(), privateKey->size()), EVP_PKEY_free);
|
|
19
25
|
if (!pkey_priv) {
|
|
20
26
|
throw std::runtime_error("Failed to create private key: " + getOpenSSLError());
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
// 2. Create EVP_PKEY for public key (peer's key)
|
|
24
|
-
EVP_PKEY_ptr pkey_pub(EVP_PKEY_new_raw_public_key(
|
|
30
|
+
EVP_PKEY_ptr pkey_pub(EVP_PKEY_new_raw_public_key(keyType, NULL, publicKey->data(), publicKey->size()), EVP_PKEY_free);
|
|
25
31
|
if (!pkey_pub) {
|
|
26
32
|
throw std::runtime_error("Failed to create public key: " + getOpenSSLError());
|
|
27
33
|
}
|
|
@@ -322,6 +322,14 @@ AsymmetricKeyType HybridKeyObjectHandle::getAsymmetricKeyType() {
|
|
|
322
322
|
return AsymmetricKeyType::ED25519;
|
|
323
323
|
case EVP_PKEY_ED448:
|
|
324
324
|
return AsymmetricKeyType::ED448;
|
|
325
|
+
#if OPENSSL_VERSION_NUMBER >= 0x30500000L
|
|
326
|
+
case EVP_PKEY_ML_DSA_44:
|
|
327
|
+
return AsymmetricKeyType::ML_DSA_44;
|
|
328
|
+
case EVP_PKEY_ML_DSA_65:
|
|
329
|
+
return AsymmetricKeyType::ML_DSA_65;
|
|
330
|
+
case EVP_PKEY_ML_DSA_87:
|
|
331
|
+
return AsymmetricKeyType::ML_DSA_87;
|
|
332
|
+
#endif
|
|
325
333
|
default:
|
|
326
334
|
throw std::runtime_error("Unsupported asymmetric key type");
|
|
327
335
|
}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
#include <NitroModules/ArrayBuffer.hpp>
|
|
4
4
|
|
|
5
|
-
#include "../../deps/ncrypto/ncrypto.h"
|
|
6
5
|
#include "KFormatType.hpp"
|
|
7
6
|
#include "KeyEncoding.hpp"
|
|
8
7
|
#include "KeyType.hpp"
|
|
9
8
|
#include "Utils.hpp"
|
|
9
|
+
#include <ncrypto.h>
|
|
10
10
|
|
|
11
11
|
namespace margelo::nitro::crypto {
|
|
12
12
|
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#include "HybridMlDsaKeyPair.hpp"
|
|
2
|
+
|
|
3
|
+
#include <NitroModules/ArrayBuffer.hpp>
|
|
4
|
+
#include <openssl/bio.h>
|
|
5
|
+
#include <openssl/err.h>
|
|
6
|
+
#include <openssl/pem.h>
|
|
7
|
+
|
|
8
|
+
#include "Utils.hpp"
|
|
9
|
+
|
|
10
|
+
#if OPENSSL_VERSION_NUMBER >= 0x30500000L
|
|
11
|
+
#define RNQC_HAS_ML_DSA 1
|
|
12
|
+
#else
|
|
13
|
+
#define RNQC_HAS_ML_DSA 0
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
namespace margelo::nitro::crypto {
|
|
17
|
+
|
|
18
|
+
HybridMlDsaKeyPair::~HybridMlDsaKeyPair() {
|
|
19
|
+
if (pkey_ != nullptr) {
|
|
20
|
+
EVP_PKEY_free(pkey_);
|
|
21
|
+
pkey_ = nullptr;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
int HybridMlDsaKeyPair::getEvpPkeyType() const {
|
|
26
|
+
#if RNQC_HAS_ML_DSA
|
|
27
|
+
if (variant_ == "ML-DSA-44")
|
|
28
|
+
return EVP_PKEY_ML_DSA_44;
|
|
29
|
+
if (variant_ == "ML-DSA-65")
|
|
30
|
+
return EVP_PKEY_ML_DSA_65;
|
|
31
|
+
if (variant_ == "ML-DSA-87")
|
|
32
|
+
return EVP_PKEY_ML_DSA_87;
|
|
33
|
+
#endif
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
void HybridMlDsaKeyPair::setVariant(const std::string& variant) {
|
|
38
|
+
#if !RNQC_HAS_ML_DSA
|
|
39
|
+
throw std::runtime_error("ML-DSA requires OpenSSL 3.5+");
|
|
40
|
+
#endif
|
|
41
|
+
if (variant != "ML-DSA-44" && variant != "ML-DSA-65" && variant != "ML-DSA-87") {
|
|
42
|
+
throw std::runtime_error("Invalid ML-DSA variant: " + variant + ". Must be ML-DSA-44, ML-DSA-65, or ML-DSA-87");
|
|
43
|
+
}
|
|
44
|
+
variant_ = variant;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
std::shared_ptr<Promise<void>> HybridMlDsaKeyPair::generateKeyPair(double publicFormat, double publicType, double privateFormat,
|
|
48
|
+
double privateType) {
|
|
49
|
+
return Promise<void>::async([this, publicFormat, publicType, privateFormat, privateType]() {
|
|
50
|
+
this->generateKeyPairSync(publicFormat, publicType, privateFormat, privateType);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void HybridMlDsaKeyPair::generateKeyPairSync(double publicFormat, double publicType, double privateFormat, double privateType) {
|
|
55
|
+
#if !RNQC_HAS_ML_DSA
|
|
56
|
+
throw std::runtime_error("ML-DSA requires OpenSSL 3.5+");
|
|
57
|
+
#else
|
|
58
|
+
clearOpenSSLErrors();
|
|
59
|
+
|
|
60
|
+
if (variant_.empty()) {
|
|
61
|
+
throw std::runtime_error("ML-DSA variant not set. Call setVariant() first.");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
publicFormat_ = static_cast<int>(publicFormat);
|
|
65
|
+
publicType_ = static_cast<int>(publicType);
|
|
66
|
+
privateFormat_ = static_cast<int>(privateFormat);
|
|
67
|
+
privateType_ = static_cast<int>(privateType);
|
|
68
|
+
|
|
69
|
+
if (pkey_ != nullptr) {
|
|
70
|
+
EVP_PKEY_free(pkey_);
|
|
71
|
+
pkey_ = nullptr;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_from_name(nullptr, variant_.c_str(), nullptr);
|
|
75
|
+
if (pctx == nullptr) {
|
|
76
|
+
throw std::runtime_error("Failed to create key context for " + variant_ + ": " + getOpenSSLError());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (EVP_PKEY_keygen_init(pctx) <= 0) {
|
|
80
|
+
EVP_PKEY_CTX_free(pctx);
|
|
81
|
+
throw std::runtime_error("Failed to initialize keygen: " + getOpenSSLError());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (EVP_PKEY_keygen(pctx, &pkey_) <= 0) {
|
|
85
|
+
EVP_PKEY_CTX_free(pctx);
|
|
86
|
+
throw std::runtime_error("Failed to generate ML-DSA key pair: " + getOpenSSLError());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
EVP_PKEY_CTX_free(pctx);
|
|
90
|
+
#endif
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
std::shared_ptr<ArrayBuffer> HybridMlDsaKeyPair::getPublicKey() {
|
|
94
|
+
#if !RNQC_HAS_ML_DSA
|
|
95
|
+
throw std::runtime_error("ML-DSA requires OpenSSL 3.5+");
|
|
96
|
+
#else
|
|
97
|
+
checkKeyPair();
|
|
98
|
+
|
|
99
|
+
BIO* bio = BIO_new(BIO_s_mem());
|
|
100
|
+
if (!bio) {
|
|
101
|
+
throw std::runtime_error("Failed to create BIO for public key export");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
int result;
|
|
105
|
+
if (publicFormat_ == 1) {
|
|
106
|
+
result = PEM_write_bio_PUBKEY(bio, pkey_);
|
|
107
|
+
} else {
|
|
108
|
+
result = i2d_PUBKEY_bio(bio, pkey_);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (result != 1) {
|
|
112
|
+
BIO_free(bio);
|
|
113
|
+
throw std::runtime_error("Failed to export public key: " + getOpenSSLError());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
BUF_MEM* bptr;
|
|
117
|
+
BIO_get_mem_ptr(bio, &bptr);
|
|
118
|
+
|
|
119
|
+
uint8_t* data = new uint8_t[bptr->length];
|
|
120
|
+
memcpy(data, bptr->data, bptr->length);
|
|
121
|
+
size_t len = bptr->length;
|
|
122
|
+
|
|
123
|
+
BIO_free(bio);
|
|
124
|
+
|
|
125
|
+
return std::make_shared<NativeArrayBuffer>(data, len, [=]() { delete[] data; });
|
|
126
|
+
#endif
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
std::shared_ptr<ArrayBuffer> HybridMlDsaKeyPair::getPrivateKey() {
|
|
130
|
+
#if !RNQC_HAS_ML_DSA
|
|
131
|
+
throw std::runtime_error("ML-DSA requires OpenSSL 3.5+");
|
|
132
|
+
#else
|
|
133
|
+
checkKeyPair();
|
|
134
|
+
|
|
135
|
+
BIO* bio = BIO_new(BIO_s_mem());
|
|
136
|
+
if (!bio) {
|
|
137
|
+
throw std::runtime_error("Failed to create BIO for private key export");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
int result;
|
|
141
|
+
if (privateFormat_ == 1) {
|
|
142
|
+
result = PEM_write_bio_PrivateKey(bio, pkey_, nullptr, nullptr, 0, nullptr, nullptr);
|
|
143
|
+
} else {
|
|
144
|
+
// Use PKCS8 format for DER export (not raw private key format)
|
|
145
|
+
result = i2d_PKCS8PrivateKey_bio(bio, pkey_, nullptr, nullptr, 0, nullptr, nullptr);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (result != 1) {
|
|
149
|
+
BIO_free(bio);
|
|
150
|
+
throw std::runtime_error("Failed to export private key: " + getOpenSSLError());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
BUF_MEM* bptr;
|
|
154
|
+
BIO_get_mem_ptr(bio, &bptr);
|
|
155
|
+
|
|
156
|
+
uint8_t* data = new uint8_t[bptr->length];
|
|
157
|
+
memcpy(data, bptr->data, bptr->length);
|
|
158
|
+
size_t len = bptr->length;
|
|
159
|
+
|
|
160
|
+
BIO_free(bio);
|
|
161
|
+
|
|
162
|
+
return std::make_shared<NativeArrayBuffer>(data, len, [=]() { delete[] data; });
|
|
163
|
+
#endif
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> HybridMlDsaKeyPair::sign(const std::shared_ptr<ArrayBuffer>& message) {
|
|
167
|
+
auto nativeMessage = ToNativeArrayBuffer(message);
|
|
168
|
+
return Promise<std::shared_ptr<ArrayBuffer>>::async([this, nativeMessage]() { return this->signSync(nativeMessage); });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
std::shared_ptr<ArrayBuffer> HybridMlDsaKeyPair::signSync(const std::shared_ptr<ArrayBuffer>& message) {
|
|
172
|
+
#if !RNQC_HAS_ML_DSA
|
|
173
|
+
throw std::runtime_error("ML-DSA requires OpenSSL 3.5+");
|
|
174
|
+
#else
|
|
175
|
+
clearOpenSSLErrors();
|
|
176
|
+
checkKeyPair();
|
|
177
|
+
|
|
178
|
+
EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();
|
|
179
|
+
if (md_ctx == nullptr) {
|
|
180
|
+
throw std::runtime_error("Failed to create signing context");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
EVP_PKEY_CTX* pkey_ctx = EVP_PKEY_CTX_new_from_name(nullptr, variant_.c_str(), nullptr);
|
|
184
|
+
if (pkey_ctx == nullptr) {
|
|
185
|
+
EVP_MD_CTX_free(md_ctx);
|
|
186
|
+
throw std::runtime_error("Failed to create signing context for " + variant_);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (EVP_DigestSignInit(md_ctx, &pkey_ctx, nullptr, nullptr, pkey_) <= 0) {
|
|
190
|
+
EVP_MD_CTX_free(md_ctx);
|
|
191
|
+
EVP_PKEY_CTX_free(pkey_ctx);
|
|
192
|
+
throw std::runtime_error("Failed to initialize signing: " + getOpenSSLError());
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
size_t sig_len = 0;
|
|
196
|
+
if (EVP_DigestSign(md_ctx, nullptr, &sig_len, message->data(), message->size()) <= 0) {
|
|
197
|
+
EVP_MD_CTX_free(md_ctx);
|
|
198
|
+
throw std::runtime_error("Failed to calculate signature size: " + getOpenSSLError());
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
uint8_t* sig = new uint8_t[sig_len];
|
|
202
|
+
|
|
203
|
+
if (EVP_DigestSign(md_ctx, sig, &sig_len, message->data(), message->size()) <= 0) {
|
|
204
|
+
EVP_MD_CTX_free(md_ctx);
|
|
205
|
+
delete[] sig;
|
|
206
|
+
throw std::runtime_error("Failed to sign message: " + getOpenSSLError());
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
EVP_MD_CTX_free(md_ctx);
|
|
210
|
+
|
|
211
|
+
return std::make_shared<NativeArrayBuffer>(sig, sig_len, [=]() { delete[] sig; });
|
|
212
|
+
#endif
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
std::shared_ptr<Promise<bool>> HybridMlDsaKeyPair::verify(const std::shared_ptr<ArrayBuffer>& signature,
|
|
216
|
+
const std::shared_ptr<ArrayBuffer>& message) {
|
|
217
|
+
auto nativeSignature = ToNativeArrayBuffer(signature);
|
|
218
|
+
auto nativeMessage = ToNativeArrayBuffer(message);
|
|
219
|
+
return Promise<bool>::async([this, nativeSignature, nativeMessage]() { return this->verifySync(nativeSignature, nativeMessage); });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
bool HybridMlDsaKeyPair::verifySync(const std::shared_ptr<ArrayBuffer>& signature, const std::shared_ptr<ArrayBuffer>& message) {
|
|
223
|
+
#if !RNQC_HAS_ML_DSA
|
|
224
|
+
throw std::runtime_error("ML-DSA requires OpenSSL 3.5+");
|
|
225
|
+
#else
|
|
226
|
+
clearOpenSSLErrors();
|
|
227
|
+
checkKeyPair();
|
|
228
|
+
|
|
229
|
+
EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();
|
|
230
|
+
if (md_ctx == nullptr) {
|
|
231
|
+
throw std::runtime_error("Failed to create verify context");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
EVP_PKEY_CTX* pkey_ctx = EVP_PKEY_CTX_new_from_name(nullptr, variant_.c_str(), nullptr);
|
|
235
|
+
if (pkey_ctx == nullptr) {
|
|
236
|
+
EVP_MD_CTX_free(md_ctx);
|
|
237
|
+
throw std::runtime_error("Failed to create verify context for " + variant_);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, nullptr, nullptr, pkey_) <= 0) {
|
|
241
|
+
EVP_MD_CTX_free(md_ctx);
|
|
242
|
+
EVP_PKEY_CTX_free(pkey_ctx);
|
|
243
|
+
throw std::runtime_error("Failed to initialize verification: " + getOpenSSLError());
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
int result = EVP_DigestVerify(md_ctx, signature->data(), signature->size(), message->data(), message->size());
|
|
247
|
+
|
|
248
|
+
EVP_MD_CTX_free(md_ctx);
|
|
249
|
+
|
|
250
|
+
if (result < 0) {
|
|
251
|
+
throw std::runtime_error("Verification error: " + getOpenSSLError());
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return result == 1;
|
|
255
|
+
#endif
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
void HybridMlDsaKeyPair::checkKeyPair() {
|
|
259
|
+
if (pkey_ == nullptr) {
|
|
260
|
+
throw std::runtime_error("Key pair not initialized");
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
} // namespace margelo::nitro::crypto
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <memory>
|
|
4
|
+
#include <openssl/evp.h>
|
|
5
|
+
#include <string>
|
|
6
|
+
|
|
7
|
+
#include "HybridMlDsaKeyPairSpec.hpp"
|
|
8
|
+
|
|
9
|
+
namespace margelo::nitro::crypto {
|
|
10
|
+
|
|
11
|
+
class HybridMlDsaKeyPair : public HybridMlDsaKeyPairSpec {
|
|
12
|
+
public:
|
|
13
|
+
HybridMlDsaKeyPair() : HybridObject(TAG) {}
|
|
14
|
+
~HybridMlDsaKeyPair();
|
|
15
|
+
|
|
16
|
+
std::shared_ptr<Promise<void>> generateKeyPair(double publicFormat, double publicType, double privateFormat, double privateType) override;
|
|
17
|
+
|
|
18
|
+
void generateKeyPairSync(double publicFormat, double publicType, double privateFormat, double privateType) override;
|
|
19
|
+
|
|
20
|
+
std::shared_ptr<ArrayBuffer> getPublicKey() override;
|
|
21
|
+
std::shared_ptr<ArrayBuffer> getPrivateKey() override;
|
|
22
|
+
|
|
23
|
+
std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> sign(const std::shared_ptr<ArrayBuffer>& message) override;
|
|
24
|
+
|
|
25
|
+
std::shared_ptr<ArrayBuffer> signSync(const std::shared_ptr<ArrayBuffer>& message) override;
|
|
26
|
+
|
|
27
|
+
std::shared_ptr<Promise<bool>> verify(const std::shared_ptr<ArrayBuffer>& signature,
|
|
28
|
+
const std::shared_ptr<ArrayBuffer>& message) override;
|
|
29
|
+
|
|
30
|
+
bool verifySync(const std::shared_ptr<ArrayBuffer>& signature, const std::shared_ptr<ArrayBuffer>& message) override;
|
|
31
|
+
|
|
32
|
+
void setVariant(const std::string& variant) override;
|
|
33
|
+
|
|
34
|
+
private:
|
|
35
|
+
std::string variant_;
|
|
36
|
+
EVP_PKEY* pkey_ = nullptr;
|
|
37
|
+
|
|
38
|
+
int publicFormat_ = -1;
|
|
39
|
+
int publicType_ = -1;
|
|
40
|
+
int privateFormat_ = -1;
|
|
41
|
+
int privateType_ = -1;
|
|
42
|
+
|
|
43
|
+
void checkKeyPair();
|
|
44
|
+
int getEvpPkeyType() const;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
} // namespace margelo::nitro::crypto
|