react-native-quick-crypto 1.0.0 → 1.0.1
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 +14 -5
- package/android/CMakeLists.txt +4 -2
- package/android/build.gradle +1 -1
- package/cpp/cipher/HybridCipher.cpp +3 -2
- package/cpp/cipher/HybridRsaCipher.cpp +20 -1
- 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/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 +111 -6
- package/lib/commonjs/subtle.js.map +1 -1
- package/lib/commonjs/utils/types.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 +111 -6
- 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/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.map +1 -1
- package/lib/typescript/utils/types.d.ts +5 -3
- 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/keys/classes.ts +9 -0
- package/src/mldsa.ts +125 -0
- package/src/specs/mlDsaKeyPair.nitro.ts +29 -0
- package/src/subtle.ts +148 -8
- package/src/utils/types.ts +11 -3
package/QuickCrypto.podspec
CHANGED
|
@@ -92,10 +92,12 @@ Pod::Spec.new do |s|
|
|
|
92
92
|
"ios/**/*.{h,m,mm}",
|
|
93
93
|
# implementation (C++)
|
|
94
94
|
"cpp/**/*.{hpp,cpp}",
|
|
95
|
-
# dependencies (C++)
|
|
96
|
-
"deps
|
|
95
|
+
# dependencies (C++) - ncrypto
|
|
96
|
+
"deps/ncrypto/include/*.{h}",
|
|
97
|
+
"deps/ncrypto/src/*.{cpp}",
|
|
97
98
|
# dependencies (C) - exclude BLAKE3 x86 SIMD files (only use portable + NEON for ARM)
|
|
98
|
-
"deps
|
|
99
|
+
"deps/blake3/c/*.{h,c}",
|
|
100
|
+
"deps/fastpbkdf2/*.{h,c}",
|
|
99
101
|
]
|
|
100
102
|
|
|
101
103
|
# Exclude BLAKE3 x86-specific SIMD implementations (SSE2, SSE4.1, AVX2, AVX-512)
|
|
@@ -148,7 +150,7 @@ Pod::Spec.new do |s|
|
|
|
148
150
|
# Add cpp subdirectories to header search paths
|
|
149
151
|
cpp_headers = [
|
|
150
152
|
"\"$(PODS_TARGET_SRCROOT)/cpp/utils\"",
|
|
151
|
-
"\"$(PODS_TARGET_SRCROOT)/deps/ncrypto\"",
|
|
153
|
+
"\"$(PODS_TARGET_SRCROOT)/deps/ncrypto/include\"",
|
|
152
154
|
"\"$(PODS_TARGET_SRCROOT)/deps/blake3/c\"",
|
|
153
155
|
"\"$(PODS_TARGET_SRCROOT)/deps/fastpbkdf2\""
|
|
154
156
|
]
|
|
@@ -175,6 +177,13 @@ Pod::Spec.new do |s|
|
|
|
175
177
|
|
|
176
178
|
s.dependency "React-jsi"
|
|
177
179
|
s.dependency "React-callinvoker"
|
|
178
|
-
|
|
180
|
+
|
|
181
|
+
# OpenSSL 3.6+ via SPM for ML-DSA (post-quantum cryptography) support
|
|
182
|
+
spm_dependency(s,
|
|
183
|
+
url: 'https://github.com/krzyzanowskim/OpenSSL.git',
|
|
184
|
+
requirement: {kind: 'upToNextMajorVersion', minimumVersion: '3.6.0'},
|
|
185
|
+
products: ['OpenSSL']
|
|
186
|
+
)
|
|
187
|
+
|
|
179
188
|
install_modules_dependencies(s)
|
|
180
189
|
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
|
|
@@ -297,8 +297,9 @@ void collect_ciphers(EVP_CIPHER* cipher, void* arg) {
|
|
|
297
297
|
if (name_str == "NULL" || name_str.find("CTS") != std::string::npos ||
|
|
298
298
|
name_str.find("SIV") != std::string::npos || // Covers -SIV and -GCM-SIV
|
|
299
299
|
name_str.find("WRAP") != std::string::npos || // Covers -WRAP-INV and -WRAP-PAD-INV
|
|
300
|
-
name_str.find("SM4-") != std::string::npos
|
|
301
|
-
|
|
300
|
+
name_str.find("SM4-") != std::string::npos ||
|
|
301
|
+
name_str.find("-ETM") != std::string::npos) { // TLS-internal ciphers, not for general use
|
|
302
|
+
return; // Skip adding this cipher
|
|
302
303
|
}
|
|
303
304
|
|
|
304
305
|
// 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
|
|
|
@@ -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
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
#include <openssl/evp.h>
|
|
10
10
|
#include <openssl/rsa.h>
|
|
11
11
|
|
|
12
|
+
#if OPENSSL_VERSION_NUMBER >= 0x30500000L
|
|
13
|
+
#define RNQC_HAS_ML_DSA 1
|
|
14
|
+
#else
|
|
15
|
+
#define RNQC_HAS_ML_DSA 0
|
|
16
|
+
#endif
|
|
17
|
+
|
|
12
18
|
namespace margelo::nitro::crypto {
|
|
13
19
|
|
|
14
20
|
using margelo::nitro::NativeArrayBuffer;
|
|
@@ -22,17 +28,28 @@ HybridSignHandle::~HybridSignHandle() {
|
|
|
22
28
|
|
|
23
29
|
void HybridSignHandle::init(const std::string& algorithm) {
|
|
24
30
|
algorithm_name = algorithm;
|
|
25
|
-
md = getDigestByName(algorithm);
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
if (!
|
|
29
|
-
|
|
30
|
-
}
|
|
32
|
+
// For ML-DSA and other pure signature schemes, algorithm may be empty/null
|
|
33
|
+
if (!algorithm.empty()) {
|
|
34
|
+
md = getDigestByName(algorithm);
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
md_ctx = EVP_MD_CTX_new();
|
|
37
|
+
if (!md_ctx) {
|
|
38
|
+
throw std::runtime_error("Failed to create message digest context");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (EVP_DigestInit_ex(md_ctx, md, nullptr) <= 0) {
|
|
42
|
+
EVP_MD_CTX_free(md_ctx);
|
|
43
|
+
md_ctx = nullptr;
|
|
44
|
+
throw std::runtime_error("Failed to initialize message digest");
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
// No digest for pure signature schemes like ML-DSA
|
|
48
|
+
md = nullptr;
|
|
49
|
+
md_ctx = EVP_MD_CTX_new();
|
|
50
|
+
if (!md_ctx) {
|
|
51
|
+
throw std::runtime_error("Failed to create message digest context");
|
|
52
|
+
}
|
|
36
53
|
}
|
|
37
54
|
}
|
|
38
55
|
|
|
@@ -43,22 +60,60 @@ void HybridSignHandle::update(const std::shared_ptr<ArrayBuffer>& data) {
|
|
|
43
60
|
|
|
44
61
|
auto native_data = ToNativeArrayBuffer(data);
|
|
45
62
|
|
|
46
|
-
// Accumulate raw data for potential one-shot signing (Ed25519/Ed448)
|
|
63
|
+
// Accumulate raw data for potential one-shot signing (Ed25519/Ed448/ML-DSA)
|
|
47
64
|
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(native_data->data());
|
|
48
65
|
data_buffer.insert(data_buffer.end(), ptr, ptr + native_data->size());
|
|
49
66
|
|
|
50
|
-
if
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
67
|
+
// Only update digest if we have one (not needed for pure signature schemes)
|
|
68
|
+
if (md != nullptr) {
|
|
69
|
+
if (EVP_DigestUpdate(md_ctx, native_data->data(), native_data->size()) <= 0) {
|
|
70
|
+
unsigned long err = ERR_get_error();
|
|
71
|
+
char err_buf[256];
|
|
72
|
+
ERR_error_string_n(err, err_buf, sizeof(err_buf));
|
|
73
|
+
throw std::runtime_error("Failed to update digest: " + std::string(err_buf));
|
|
74
|
+
}
|
|
55
75
|
}
|
|
56
76
|
}
|
|
57
77
|
|
|
58
|
-
// Check if key type requires one-shot signing (Ed25519, Ed448)
|
|
78
|
+
// Check if key type requires one-shot signing (Ed25519, Ed448, ML-DSA)
|
|
59
79
|
static bool isOneShotVariant(EVP_PKEY* pkey) {
|
|
60
80
|
int type = EVP_PKEY_id(pkey);
|
|
81
|
+
#if RNQC_HAS_ML_DSA
|
|
82
|
+
return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448 || type == EVP_PKEY_ML_DSA_44 || type == EVP_PKEY_ML_DSA_65 ||
|
|
83
|
+
type == EVP_PKEY_ML_DSA_87;
|
|
84
|
+
#else
|
|
61
85
|
return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448;
|
|
86
|
+
#endif
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get the algorithm name for creating PKEY_CTX (for ML-DSA variants)
|
|
90
|
+
static const char* getAlgorithmName(EVP_PKEY* pkey) {
|
|
91
|
+
int type = EVP_PKEY_id(pkey);
|
|
92
|
+
#if RNQC_HAS_ML_DSA
|
|
93
|
+
switch (type) {
|
|
94
|
+
case EVP_PKEY_ML_DSA_44:
|
|
95
|
+
return "ML-DSA-44";
|
|
96
|
+
case EVP_PKEY_ML_DSA_65:
|
|
97
|
+
return "ML-DSA-65";
|
|
98
|
+
case EVP_PKEY_ML_DSA_87:
|
|
99
|
+
return "ML-DSA-87";
|
|
100
|
+
case EVP_PKEY_ED25519:
|
|
101
|
+
return "ED25519";
|
|
102
|
+
case EVP_PKEY_ED448:
|
|
103
|
+
return "ED448";
|
|
104
|
+
default:
|
|
105
|
+
return nullptr;
|
|
106
|
+
}
|
|
107
|
+
#else
|
|
108
|
+
switch (type) {
|
|
109
|
+
case EVP_PKEY_ED25519:
|
|
110
|
+
return "ED25519";
|
|
111
|
+
case EVP_PKEY_ED448:
|
|
112
|
+
return "ED448";
|
|
113
|
+
default:
|
|
114
|
+
return nullptr;
|
|
115
|
+
}
|
|
116
|
+
#endif
|
|
62
117
|
}
|
|
63
118
|
|
|
64
119
|
std::shared_ptr<ArrayBuffer> HybridSignHandle::sign(const std::shared_ptr<HybridKeyObjectHandleSpec>& keyHandle,
|
|
@@ -78,18 +133,35 @@ std::shared_ptr<ArrayBuffer> HybridSignHandle::sign(const std::shared_ptr<Hybrid
|
|
|
78
133
|
size_t sig_len = 0;
|
|
79
134
|
std::unique_ptr<uint8_t[]> sig_buf;
|
|
80
135
|
|
|
81
|
-
|
|
82
|
-
|
|
136
|
+
int pkey_type = EVP_PKEY_id(pkey);
|
|
137
|
+
bool is_one_shot = isOneShotVariant(pkey);
|
|
138
|
+
|
|
139
|
+
// Ed25519/Ed448/ML-DSA require one-shot signing with EVP_DigestSign
|
|
140
|
+
// Also use one-shot path if no digest was specified (md == nullptr)
|
|
141
|
+
if (is_one_shot || md == nullptr) {
|
|
83
142
|
// Create a new context for one-shot signing
|
|
84
143
|
EVP_MD_CTX* sign_ctx = EVP_MD_CTX_new();
|
|
85
144
|
if (!sign_ctx) {
|
|
86
145
|
throw std::runtime_error("Failed to create signing context");
|
|
87
146
|
}
|
|
88
147
|
|
|
89
|
-
//
|
|
90
|
-
|
|
148
|
+
// Get algorithm name and create PKEY_CTX for ML-DSA
|
|
149
|
+
const char* alg_name = getAlgorithmName(pkey);
|
|
150
|
+
EVP_PKEY_CTX* pkey_ctx = nullptr;
|
|
151
|
+
if (alg_name != nullptr) {
|
|
152
|
+
pkey_ctx = EVP_PKEY_CTX_new_from_name(nullptr, alg_name, nullptr);
|
|
153
|
+
if (!pkey_ctx) {
|
|
154
|
+
EVP_MD_CTX_free(sign_ctx);
|
|
155
|
+
throw std::runtime_error(std::string("Failed to create signing context for ") + alg_name);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Initialize for one-shot signing (pass nullptr for md - these algorithms have built-in hash)
|
|
160
|
+
if (EVP_DigestSignInit(sign_ctx, pkey_ctx ? &pkey_ctx : nullptr, nullptr, nullptr, pkey) <= 0) {
|
|
91
161
|
EVP_MD_CTX_free(sign_ctx);
|
|
92
|
-
|
|
162
|
+
if (pkey_ctx)
|
|
163
|
+
EVP_PKEY_CTX_free(pkey_ctx);
|
|
164
|
+
throw std::runtime_error("Failed to initialize one-shot signing");
|
|
93
165
|
}
|
|
94
166
|
|
|
95
167
|
// Get the accumulated data from the digest context
|
|
@@ -130,7 +202,10 @@ std::shared_ptr<ArrayBuffer> HybridSignHandle::sign(const std::shared_ptr<Hybrid
|
|
|
130
202
|
|
|
131
203
|
if (EVP_PKEY_sign_init(pkey_ctx) <= 0) {
|
|
132
204
|
EVP_PKEY_CTX_free(pkey_ctx);
|
|
133
|
-
|
|
205
|
+
char err_buf[512];
|
|
206
|
+
snprintf(err_buf, sizeof(err_buf), "Failed to initialize signing for key type %d (expected one-shot: %s, RNQC_HAS_ML_DSA=%d)",
|
|
207
|
+
pkey_type, is_one_shot ? "true" : "false", RNQC_HAS_ML_DSA);
|
|
208
|
+
throw std::runtime_error(std::string(err_buf) + ": " + getOpenSSLError());
|
|
134
209
|
}
|
|
135
210
|
|
|
136
211
|
if (padding.has_value()) {
|