react-native-quick-crypto 0.2.0

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.
Files changed (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/android/.DS_Store +0 -0
  4. package/android/CMakeLists.txt +117 -0
  5. package/android/build.gradle +367 -0
  6. package/android/gradle/.DS_Store +0 -0
  7. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  8. package/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  9. package/android/gradle.properties +6 -0
  10. package/android/gradlew +183 -0
  11. package/android/gradlew.bat +100 -0
  12. package/android/src/.DS_Store +0 -0
  13. package/android/src/main/.DS_Store +0 -0
  14. package/android/src/main/AndroidManifest.xml +4 -0
  15. package/android/src/main/cpp/cpp-adapter.cpp +60 -0
  16. package/android/src/main/java/.DS_Store +0 -0
  17. package/android/src/main/java/com/.DS_Store +0 -0
  18. package/android/src/main/java/com/reactnativequickcrypto/QuickCryptoModule.java +70 -0
  19. package/android/src/main/java/com/reactnativequickcrypto/QuickCryptoPackage.java +26 -0
  20. package/cpp/.DS_Store +0 -0
  21. package/cpp/Cipher/MGLCipherHostObject.cpp +632 -0
  22. package/cpp/Cipher/MGLCipherHostObject.h +90 -0
  23. package/cpp/Cipher/MGLCreateCipherInstaller.cpp +74 -0
  24. package/cpp/Cipher/MGLCreateCipherInstaller.h +17 -0
  25. package/cpp/Cipher/MGLCreateDecipherInstaller.cpp +74 -0
  26. package/cpp/Cipher/MGLCreateDecipherInstaller.h +17 -0
  27. package/cpp/HMAC/MGLHmacHostObject.cpp +95 -0
  28. package/cpp/HMAC/MGLHmacHostObject.h +39 -0
  29. package/cpp/HMAC/MGLHmacInstaller.cpp +43 -0
  30. package/cpp/HMAC/MGLHmacInstaller.h +20 -0
  31. package/cpp/Hash/MGLHashHostObject.cpp +147 -0
  32. package/cpp/Hash/MGLHashHostObject.h +53 -0
  33. package/cpp/Hash/MGLHashInstaller.cpp +46 -0
  34. package/cpp/Hash/MGLHashInstaller.h +20 -0
  35. package/cpp/JSIUtils/MGLJSIMacros.h +37 -0
  36. package/cpp/JSIUtils/MGLSmartHostObject.cpp +43 -0
  37. package/cpp/JSIUtils/MGLSmartHostObject.h +46 -0
  38. package/cpp/JSIUtils/MGLThreadAwareHostObject.cpp +24 -0
  39. package/cpp/JSIUtils/MGLThreadAwareHostObject.h +43 -0
  40. package/cpp/JSIUtils/MGLTypedArray.cpp +325 -0
  41. package/cpp/JSIUtils/MGLTypedArray.h +160 -0
  42. package/cpp/MGLQuickCryptoHostObject.cpp +64 -0
  43. package/cpp/MGLQuickCryptoHostObject.h +30 -0
  44. package/cpp/Random/MGLRandomHostObject.cpp +89 -0
  45. package/cpp/Random/MGLRandomHostObject.h +27 -0
  46. package/cpp/Utils/MGLDispatchQueue.cpp +75 -0
  47. package/cpp/Utils/MGLDispatchQueue.h +55 -0
  48. package/cpp/Utils/logs.h +38 -0
  49. package/cpp/fastpbkdf2/MGLPbkdf2HostObject.cpp +164 -0
  50. package/cpp/fastpbkdf2/MGLPbkdf2HostObject.h +29 -0
  51. package/cpp/fastpbkdf2/fastpbkdf2.c +352 -0
  52. package/cpp/fastpbkdf2/fastpbkdf2.h +68 -0
  53. package/ios/.DS_Store +0 -0
  54. package/ios/QuickCrypto.xcodeproj/project.pbxproj +274 -0
  55. package/ios/QuickCryptoModule.h +5 -0
  56. package/ios/QuickCryptoModule.mm +42 -0
  57. package/lib/.DS_Store +0 -0
  58. package/lib/commonjs/Cipher.js +293 -0
  59. package/lib/commonjs/Cipher.js.map +1 -0
  60. package/lib/commonjs/Hash.js +102 -0
  61. package/lib/commonjs/Hash.js.map +1 -0
  62. package/lib/commonjs/Hmac.js +104 -0
  63. package/lib/commonjs/Hmac.js.map +1 -0
  64. package/lib/commonjs/NativeQuickCrypto/Cipher.js +6 -0
  65. package/lib/commonjs/NativeQuickCrypto/Cipher.js.map +1 -0
  66. package/lib/commonjs/NativeQuickCrypto/NativeQuickCrypto.js +61 -0
  67. package/lib/commonjs/NativeQuickCrypto/NativeQuickCrypto.js.map +1 -0
  68. package/lib/commonjs/NativeQuickCrypto/hash.js +2 -0
  69. package/lib/commonjs/NativeQuickCrypto/hash.js.map +1 -0
  70. package/lib/commonjs/NativeQuickCrypto/hmac.js +2 -0
  71. package/lib/commonjs/NativeQuickCrypto/hmac.js.map +1 -0
  72. package/lib/commonjs/NativeQuickCrypto/pbkdf2.js +2 -0
  73. package/lib/commonjs/NativeQuickCrypto/pbkdf2.js.map +1 -0
  74. package/lib/commonjs/NativeQuickCrypto/random.js +2 -0
  75. package/lib/commonjs/NativeQuickCrypto/random.js.map +1 -0
  76. package/lib/commonjs/QuickCrypto.js +35 -0
  77. package/lib/commonjs/QuickCrypto.js.map +1 -0
  78. package/lib/commonjs/Utils.js +152 -0
  79. package/lib/commonjs/Utils.js.map +1 -0
  80. package/lib/commonjs/index.js +19 -0
  81. package/lib/commonjs/index.js.map +1 -0
  82. package/lib/commonjs/pbkdf2.js +64 -0
  83. package/lib/commonjs/pbkdf2.js.map +1 -0
  84. package/lib/commonjs/random.js +213 -0
  85. package/lib/commonjs/random.js.map +1 -0
  86. package/lib/module/Cipher.js +287 -0
  87. package/lib/module/Cipher.js.map +1 -0
  88. package/lib/module/Hash.js +87 -0
  89. package/lib/module/Hash.js.map +1 -0
  90. package/lib/module/Hmac.js +90 -0
  91. package/lib/module/Hmac.js.map +1 -0
  92. package/lib/module/NativeQuickCrypto/Cipher.js +2 -0
  93. package/lib/module/NativeQuickCrypto/Cipher.js.map +1 -0
  94. package/lib/module/NativeQuickCrypto/NativeQuickCrypto.js +53 -0
  95. package/lib/module/NativeQuickCrypto/NativeQuickCrypto.js.map +1 -0
  96. package/lib/module/NativeQuickCrypto/hash.js +2 -0
  97. package/lib/module/NativeQuickCrypto/hash.js.map +1 -0
  98. package/lib/module/NativeQuickCrypto/hmac.js +2 -0
  99. package/lib/module/NativeQuickCrypto/hmac.js.map +1 -0
  100. package/lib/module/NativeQuickCrypto/pbkdf2.js +2 -0
  101. package/lib/module/NativeQuickCrypto/pbkdf2.js.map +1 -0
  102. package/lib/module/NativeQuickCrypto/random.js +2 -0
  103. package/lib/module/NativeQuickCrypto/random.js.map +1 -0
  104. package/lib/module/QuickCrypto.js +18 -0
  105. package/lib/module/QuickCrypto.js.map +1 -0
  106. package/lib/module/Utils.js +131 -0
  107. package/lib/module/Utils.js.map +1 -0
  108. package/lib/module/index.js +2 -0
  109. package/lib/module/index.js.map +1 -0
  110. package/lib/module/pbkdf2.js +51 -0
  111. package/lib/module/pbkdf2.js.map +1 -0
  112. package/lib/module/random.js +190 -0
  113. package/lib/module/random.js.map +1 -0
  114. package/lib/typescript/Cipher.d.ts +30 -0
  115. package/lib/typescript/Hash.d.ts +44 -0
  116. package/lib/typescript/Hmac.d.ts +37 -0
  117. package/lib/typescript/NativeQuickCrypto/Cipher.d.ts +22 -0
  118. package/lib/typescript/NativeQuickCrypto/NativeQuickCrypto.d.ts +19 -0
  119. package/lib/typescript/NativeQuickCrypto/hash.d.ts +6 -0
  120. package/lib/typescript/NativeQuickCrypto/hmac.d.ts +5 -0
  121. package/lib/typescript/NativeQuickCrypto/pbkdf2.d.ts +4 -0
  122. package/lib/typescript/NativeQuickCrypto/random.d.ts +4 -0
  123. package/lib/typescript/QuickCrypto.d.ts +31 -0
  124. package/lib/typescript/Utils.d.ts +13 -0
  125. package/lib/typescript/index.d.ts +1 -0
  126. package/lib/typescript/pbkdf2.d.ts +9 -0
  127. package/lib/typescript/random.d.ts +20 -0
  128. package/package.json +168 -0
  129. package/react-native-quick-crypto.podspec +44 -0
  130. package/src/Cipher.ts +322 -0
  131. package/src/Hash.ts +98 -0
  132. package/src/Hmac.ts +107 -0
  133. package/src/NativeQuickCrypto/Cipher.ts +25 -0
  134. package/src/NativeQuickCrypto/NativeQuickCrypto.ts +79 -0
  135. package/src/NativeQuickCrypto/hash.ts +10 -0
  136. package/src/NativeQuickCrypto/hmac.ts +9 -0
  137. package/src/NativeQuickCrypto/pbkdf2.ts +16 -0
  138. package/src/NativeQuickCrypto/random.ts +12 -0
  139. package/src/QuickCrypto.ts +23 -0
  140. package/src/Utils.ts +151 -0
  141. package/src/index.ts +1 -0
  142. package/src/pbkdf2.ts +96 -0
  143. package/src/random.ts +277 -0
@@ -0,0 +1,632 @@
1
+ //
2
+ // Created by Oscar on 07.06.22.
3
+ //
4
+ #include "MGLCipherHostObject.h"
5
+
6
+ #ifdef ANDROID
7
+ #include "JSIUtils/MGLTypedArray.h"
8
+ #else
9
+ #include "MGLTypedArray.h"
10
+ #endif
11
+
12
+ #include <openssl/evp.h>
13
+
14
+ #include <algorithm>
15
+ #include <memory>
16
+ #include <string>
17
+ #include <utility>
18
+ #include <vector>
19
+
20
+ #define OUT
21
+
22
+ // TODO(osp) Some of the code is inspired or copied from node-js, check if
23
+ // attribution is needed
24
+ namespace margelo {
25
+
26
+ namespace jsi = facebook::jsi;
27
+
28
+ // TODO(osp) move this to constants file (crypto_aes.cpp in node)
29
+ constexpr unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
30
+
31
+ bool IsSupportedAuthenticatedMode(const EVP_CIPHER *cipher) {
32
+ switch (EVP_CIPHER_mode(cipher)) {
33
+ case EVP_CIPH_CCM_MODE:
34
+ case EVP_CIPH_GCM_MODE:
35
+ #ifndef OPENSSL_NO_OCB
36
+ case EVP_CIPH_OCB_MODE:
37
+ #endif
38
+ return true;
39
+ case EVP_CIPH_STREAM_CIPHER:
40
+ return EVP_CIPHER_nid(cipher) == NID_chacha20_poly1305;
41
+ default:
42
+ return false;
43
+ }
44
+ }
45
+
46
+ bool IsSupportedAuthenticatedMode(const EVP_CIPHER_CTX *ctx) {
47
+ const EVP_CIPHER *cipher = EVP_CIPHER_CTX_cipher(ctx);
48
+ return IsSupportedAuthenticatedMode(cipher);
49
+ }
50
+
51
+ bool IsValidGCMTagLength(unsigned int tag_len) {
52
+ return tag_len == 4 || tag_len == 8 || (tag_len >= 12 && tag_len <= 16);
53
+ }
54
+
55
+ void CopyTo(jsi::Runtime &runtime, jsi::ArrayBuffer *src, char *dest,
56
+ size_t len) {
57
+ // static_assert(sizeof(M) == 1, "sizeof(M) must equal 1");
58
+ len = std::min(len, src->size(runtime));
59
+ if (len > 0 && src->data(runtime) != nullptr)
60
+ memcpy(dest, src->data(runtime), len);
61
+ }
62
+
63
+ MGLCipherHostObject::MGLCipherHostObject(
64
+ std::shared_ptr<react::CallInvoker> jsCallInvoker,
65
+ std::shared_ptr<DispatchQueue::dispatch_queue> workerQueue)
66
+ : MGLSmartHostObject(jsCallInvoker, workerQueue) {
67
+ installMethods();
68
+ }
69
+
70
+ MGLCipherHostObject::MGLCipherHostObject(
71
+ MGLCipherHostObject *other,
72
+ std::shared_ptr<react::CallInvoker> jsCallInvoker,
73
+ std::shared_ptr<DispatchQueue::dispatch_queue> workerQueue)
74
+ : MGLSmartHostObject(jsCallInvoker, workerQueue),
75
+ isCipher_(other->isCipher_) {
76
+ installMethods();
77
+ }
78
+
79
+ MGLCipherHostObject::MGLCipherHostObject(
80
+ const std::string &cipher_type, jsi::ArrayBuffer *cipher_key, bool isCipher,
81
+ unsigned int auth_tag_len, jsi::Runtime &runtime,
82
+ std::shared_ptr<react::CallInvoker> jsCallInvoker,
83
+ std::shared_ptr<DispatchQueue::dispatch_queue> workerQueue)
84
+ : MGLSmartHostObject(jsCallInvoker, workerQueue),
85
+ isCipher_(isCipher),
86
+ pending_auth_failed_(false) {
87
+ // TODO(osp) is this needed on the SSL version we are using?
88
+ // #if OPENSSL_VERSION_MAJOR >= 3
89
+ // if (EVP_default_properties_is_fips_enabled(nullptr)) {
90
+ // #else
91
+ // if (FIPS_mode()) {
92
+ // #endif
93
+ // return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env(),
94
+ // "crypto.createCipher()
95
+ // is not supported in
96
+ // FIPS mode.");
97
+ // }
98
+
99
+ const EVP_CIPHER *const cipher = EVP_get_cipherbyname(cipher_type.c_str());
100
+ if (cipher == nullptr) {
101
+ throw jsi::JSError(runtime, "Invalid Cipher Algorithm!");
102
+ }
103
+
104
+ unsigned char key[EVP_MAX_KEY_LENGTH];
105
+ unsigned char iv[EVP_MAX_IV_LENGTH];
106
+
107
+ int key_len =
108
+ EVP_BytesToKey(cipher, EVP_md5(), nullptr, cipher_key->data(runtime),
109
+ cipher_key->size(runtime), 1, key, iv);
110
+
111
+ // TODO(osp) this looks like a macro, check if necessary
112
+ // CHECK_NE(key_len, 0);
113
+
114
+ // TODO(osp) this seems like a runtime check
115
+ // const int mode = EVP_CIPHER_mode(cipher);
116
+ // if (isCipher && (mode == EVP_CIPH_CTR_MODE ||
117
+ // mode == EVP_CIPH_GCM_MODE ||
118
+ // mode == EVP_CIPH_CCM_MODE)) {
119
+ // // Ignore the return value (i.e. possible exception) because we are
120
+ // // not calling back into JS anyway.
121
+ // ProcessEmitWarning(env(),
122
+ // "Use Cipheriv for counter mode of %s",
123
+ // cipher_type);
124
+ // }
125
+
126
+ commonInit(runtime, cipher_type.c_str(), cipher, key, key_len, iv,
127
+ EVP_CIPHER_iv_length(cipher), auth_tag_len);
128
+
129
+ installMethods();
130
+ }
131
+
132
+ MGLCipherHostObject::MGLCipherHostObject(
133
+ const std::string &cipher_type, jsi::ArrayBuffer *cipher_key, bool isCipher,
134
+ unsigned int auth_tag_len, jsi::ArrayBuffer *iv, jsi::Runtime &runtime,
135
+ std::shared_ptr<react::CallInvoker> jsCallInvoker,
136
+ std::shared_ptr<DispatchQueue::dispatch_queue> workerQueue)
137
+ : MGLSmartHostObject(jsCallInvoker, workerQueue),
138
+ isCipher_(isCipher),
139
+ pending_auth_failed_(false) {
140
+ // TODO(osp) is this needed on the SSL version we are using?
141
+ // #if OPENSSL_VERSION_MAJOR >= 3
142
+ // if (EVP_default_properties_is_fips_enabled(nullptr)) {
143
+ // #else
144
+ // if (FIPS_mode()) {
145
+ // #endif
146
+ // return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env(),
147
+ // "crypto.createCipher()
148
+ // is not supported in
149
+ // FIPS mode.");
150
+ // }
151
+
152
+ const EVP_CIPHER *const cipher = EVP_get_cipherbyname(cipher_type.c_str());
153
+ if (cipher == nullptr) {
154
+ throw jsi::JSError(runtime, "Invalid Cipher Algorithm!");
155
+ }
156
+
157
+ const int expected_iv_len = EVP_CIPHER_iv_length(cipher);
158
+ const int is_authenticated_mode = IsSupportedAuthenticatedMode(cipher);
159
+ const bool has_iv = iv->size(runtime) > 0;
160
+
161
+ // Throw if an IV was passed which does not match the cipher's fixed IV length
162
+ // static_cast<int> for the iv_buf.size() is safe because we've verified
163
+ // prior that the value is not larger than MAX_INT.
164
+ if (!is_authenticated_mode && has_iv &&
165
+ static_cast<int>(iv->size(runtime)) != expected_iv_len) {
166
+ throw jsi::JSError(runtime, "Invalid iv");
167
+ }
168
+
169
+ if (EVP_CIPHER_nid(cipher) == NID_chacha20_poly1305) {
170
+ // CHECK(has_iv);
171
+ // Check for invalid IV lengths, since OpenSSL does not under some
172
+ // conditions:
173
+ // https://www.openssl.org/news/secadv/20190306.txt.
174
+ if (iv->size(runtime) > 12) throw jsi::JSError(runtime, "Invalid iv");
175
+ }
176
+
177
+ commonInit(runtime, cipher_type.c_str(), cipher, cipher_key->data(runtime),
178
+ cipher_key->size(runtime), iv->data(runtime), iv->size(runtime),
179
+ auth_tag_len);
180
+
181
+ installMethods();
182
+ }
183
+
184
+ void MGLCipherHostObject::commonInit(jsi::Runtime &runtime,
185
+ const char *cipher_type,
186
+ const EVP_CIPHER *cipher,
187
+ const unsigned char *key, int key_len,
188
+ const unsigned char *iv, int iv_len,
189
+ unsigned int auth_tag_len) {
190
+ // TODO(osp) check for this macro
191
+ // CHECK(!ctx_);
192
+
193
+ EVP_CIPHER_CTX_free(ctx_);
194
+ ctx_ = EVP_CIPHER_CTX_new();
195
+
196
+ const int mode = EVP_CIPHER_mode(cipher);
197
+ if (mode == EVP_CIPH_WRAP_MODE) {
198
+ EVP_CIPHER_CTX_set_flags(ctx_, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
199
+ }
200
+
201
+ if (1 !=
202
+ EVP_CipherInit_ex(ctx_, cipher, nullptr, nullptr, nullptr, isCipher_)) {
203
+ throw jsi::JSError(runtime, "Failed to initialize cipher");
204
+ }
205
+
206
+ if (IsSupportedAuthenticatedMode(cipher)) {
207
+ // TODO(osp) implement this check macro
208
+ // CHECK_GE(iv_len, 0);
209
+ if (!InitAuthenticated(cipher_type, iv_len, auth_tag_len)) {
210
+ return;
211
+ }
212
+ }
213
+
214
+ if (!EVP_CIPHER_CTX_set_key_length(ctx_, key_len)) {
215
+ EVP_CIPHER_CTX_free(ctx_);
216
+ ctx_ = nullptr;
217
+ throw std::runtime_error("Invalid Cipher key length!");
218
+ }
219
+
220
+ if (1 != EVP_CipherInit_ex(ctx_, nullptr, nullptr, key, iv, isCipher_)) {
221
+ throw std::runtime_error("Failed to initialize cipher!");
222
+ }
223
+ }
224
+
225
+ void MGLCipherHostObject::installMethods() {
226
+ // Instance methods
227
+
228
+ // update
229
+ this->fields.push_back(buildPair(
230
+ "update", JSIF([this]) {
231
+ if (count != 1) {
232
+ throw jsi::JSError(runtime,
233
+ "cipher.update requires at least 2 parameters");
234
+ }
235
+
236
+ if (!arguments[0].isObject() ||
237
+ !arguments[0].asObject(runtime).isArrayBuffer(runtime)) {
238
+ throw jsi::JSError(runtime,
239
+ "cipher.update first argument ('data') needs to "
240
+ "be an ArrayBuffer");
241
+ }
242
+
243
+ auto dataArrayBuffer =
244
+ arguments[0].asObject(runtime).getArrayBuffer(runtime);
245
+
246
+ const unsigned char *data = dataArrayBuffer.data(runtime);
247
+ auto len = dataArrayBuffer.length(runtime);
248
+
249
+ if (ctx_ == nullptr || len > INT_MAX) {
250
+ // On the node version there are several layers of wrapping and errors
251
+ // are not immediately surfaced On our version we can simply throw an
252
+ // error as soon as something goes wrong
253
+ throw jsi::JSError(runtime, 'kErrorState');
254
+ }
255
+
256
+ const int mode = EVP_CIPHER_CTX_mode(ctx_);
257
+
258
+ if (mode == EVP_CIPH_CCM_MODE && !CheckCCMMessageLength(len)) {
259
+ throw jsi::JSError(runtime, "kErrorMessageSize");
260
+ }
261
+
262
+ // Pass the authentication tag to OpenSSL if possible. This will only
263
+ // happen once, usually on the first update.
264
+ if (!isCipher_ && IsAuthenticatedMode()) {
265
+ // TODO(osp) check
266
+ MaybePassAuthTagToOpenSSL();
267
+ }
268
+
269
+ int buf_len = len + EVP_CIPHER_CTX_block_size(ctx_);
270
+ // For key wrapping algorithms, get output size by calling
271
+ // EVP_CipherUpdate() with null output.
272
+ if (isCipher_ && mode == EVP_CIPH_WRAP_MODE &&
273
+ EVP_CipherUpdate(ctx_, nullptr, &buf_len, data, len) != 1) {
274
+ throw jsi::JSError(runtime, "kErrorState");
275
+ }
276
+
277
+ MGLTypedArray<MGLTypedArrayKind::Uint8Array> out(runtime, buf_len);
278
+
279
+ // Important this function returns the real size of the data in buf_len
280
+ // Output needs to be truncated to not send extra 0s
281
+ int r = EVP_CipherUpdate(ctx_, out.getBuffer(runtime).data(runtime),
282
+ &buf_len, data, len);
283
+
284
+ // Trim exceeding bytes
285
+ MGLTypedArray<MGLTypedArrayKind::Uint8Array> ret(runtime, buf_len);
286
+ std::vector<unsigned char> vec(
287
+ out.getBuffer(runtime).data(runtime),
288
+ out.getBuffer(runtime).data(runtime) + buf_len);
289
+ ret.update(runtime, vec);
290
+
291
+ // When in CCM mode, EVP_CipherUpdate will fail if the authentication
292
+ // tag is invalid. In that case, remember the error and throw in
293
+ // final().
294
+ if (!r && !isCipher_ && mode == EVP_CIPH_CCM_MODE) {
295
+ pending_auth_failed_ = true;
296
+ return ret;
297
+ }
298
+
299
+ // return r == 1 ? jsi::Value((int)kSuccess) :
300
+ // jsi::Value((int)kErrorState);
301
+ return ret;
302
+ }));
303
+
304
+ // final
305
+ this->fields.push_back(HOST_LAMBDA("final", {
306
+ if (ctx_ == nullptr) {
307
+ throw jsi::JSError(runtime, "kErrorState");
308
+ }
309
+
310
+ const int mode = EVP_CIPHER_CTX_mode(ctx_);
311
+
312
+ int buf_len = EVP_CIPHER_CTX_block_size(ctx_);
313
+ MGLTypedArray<MGLTypedArrayKind::Uint8Array> out(runtime, buf_len);
314
+
315
+ if (!isCipher_ && IsSupportedAuthenticatedMode(ctx_)) {
316
+ MaybePassAuthTagToOpenSSL();
317
+ }
318
+
319
+ // In CCM mode, final() only checks whether authentication failed in
320
+ // update(). EVP_CipherFinal_ex must not be called and will fail.
321
+ bool ok;
322
+ int out_len = out.byteLength(runtime);
323
+ if (!isCipher_ && mode == EVP_CIPH_CCM_MODE) {
324
+ ok = !pending_auth_failed_;
325
+ MGLTypedArray<MGLTypedArrayKind::Uint8Array> out(runtime, 0);
326
+ } else {
327
+ ok = EVP_CipherFinal_ex(ctx_, out.getBuffer(runtime).data(runtime),
328
+ &out_len) == 1;
329
+
330
+ if (ok && isCipher_ && IsAuthenticatedMode()) {
331
+ // In GCM mode, the authentication tag length can be specified in
332
+ // advance, but defaults to 16 bytes when encrypting. In CCM and OCB
333
+ // mode, it must always be given by the user.
334
+ if (auth_tag_len_ == kNoAuthTagLength) {
335
+ // TODO(osp) check
336
+ // CHECK(mode == EVP_CIPH_GCM_MODE);
337
+ auth_tag_len_ = sizeof(auth_tag_);
338
+ }
339
+ ok = (1 == EVP_CIPHER_CTX_ctrl(
340
+ ctx_, EVP_CTRL_AEAD_GET_TAG, auth_tag_len_,
341
+ reinterpret_cast<unsigned char *>(auth_tag_)));
342
+ }
343
+ }
344
+
345
+ MGLTypedArray<MGLTypedArrayKind::Uint8Array> ret(runtime, out_len);
346
+ if (out_len > 0) {
347
+ std::vector<unsigned char> vec(
348
+ out.getBuffer(runtime).data(runtime),
349
+ out.getBuffer(runtime).data(runtime) + out_len);
350
+ ret.update(runtime, vec);
351
+ }
352
+
353
+ EVP_CIPHER_CTX_free(ctx_);
354
+ ctx_ = nullptr;
355
+
356
+ return ret;
357
+ }));
358
+
359
+ // setAAD
360
+ this->fields.push_back(HOST_LAMBDA("setAAD", {
361
+ if (count != 1) {
362
+ throw jsi::JSError(runtime, "cipher.setAAD requires an argument record");
363
+ }
364
+
365
+ if (!arguments[0].isObject()) {
366
+ throw jsi::JSError(runtime,
367
+ "cipher.setAAD first argument needs to be a record");
368
+ }
369
+
370
+ auto args = arguments[0].asObject(runtime);
371
+
372
+ if (!args.hasProperty(runtime, "data") ||
373
+ !args.getProperty(runtime, "data").isObject() ||
374
+ !args.getProperty(runtime, "data")
375
+ .asObject(runtime)
376
+ .isArrayBuffer(runtime)) {
377
+ throw jsi::JSError(runtime, "data is missing in arguments record");
378
+ }
379
+
380
+ auto dataArrayBuffer = args.getProperty(runtime, "data")
381
+ .asObject(runtime)
382
+ .getArrayBuffer(runtime);
383
+
384
+ int plaintext_len = -1;
385
+ if (args.hasProperty(runtime, "plaintextLength") &&
386
+ !args.getProperty(runtime, "plaintextLength").isNull() &&
387
+ !args.getProperty(runtime, "plaintextLength").isUndefined()) {
388
+ if (args.getProperty(runtime, "plaintextLength").isNumber()) {
389
+ plaintext_len =
390
+ (int)args.getProperty(runtime, "plaintextLength").asNumber();
391
+ } else {
392
+ throw new jsi::JSError(runtime,
393
+ "plaintextLength property needs to be a number");
394
+ }
395
+ }
396
+
397
+ const unsigned char *data = dataArrayBuffer.data(runtime);
398
+ auto len = dataArrayBuffer.length(runtime);
399
+
400
+ if (!ctx_ || !IsAuthenticatedMode()) return false;
401
+
402
+ int outlen;
403
+ const int mode = EVP_CIPHER_CTX_mode(ctx_);
404
+
405
+ // When in CCM mode, we need to set the authentication tag and the plaintext
406
+ // length in advance.
407
+ if (mode == EVP_CIPH_CCM_MODE) {
408
+ if (plaintext_len < 0) {
409
+ throw jsi::JSError(runtime,
410
+ "plaintextLength required for CCM mode with AAD");
411
+ return false;
412
+ }
413
+
414
+ if (!CheckCCMMessageLength(plaintext_len)) return false;
415
+
416
+ if (!isCipher_) {
417
+ if (!MaybePassAuthTagToOpenSSL()) return false;
418
+ }
419
+
420
+ // Specify the plaintext length.
421
+ if (!EVP_CipherUpdate(ctx_, nullptr, &outlen, nullptr, plaintext_len))
422
+ return false;
423
+ }
424
+
425
+ return 1 == EVP_CipherUpdate(ctx_, nullptr, &outlen, data, len);
426
+ }));
427
+
428
+ // setAutoPadding
429
+ this->fields.push_back(HOST_LAMBDA("setAutoPadding", {
430
+ if (count != 1) {
431
+ throw jsi::JSError(
432
+ runtime, "cipher.setAutoPadding requires at least one argument");
433
+ }
434
+
435
+ if (!arguments[0].isBool()) {
436
+ throw jsi::JSError(
437
+ runtime, "cipher.setAutoPadding first argument must be a boolean");
438
+ }
439
+
440
+ if (ctx_ == nullptr) {
441
+ return false;
442
+ }
443
+
444
+ return EVP_CIPHER_CTX_set_padding(ctx_, arguments[0].getBool());
445
+ }));
446
+
447
+ // setAuthTag
448
+ this->fields.push_back(HOST_LAMBDA("setAuthTag", {
449
+ if (count != 1 || !arguments[0].isObject() ||
450
+ !arguments[0].asObject(runtime).isArrayBuffer(runtime)) {
451
+ throw jsi::JSError(
452
+ runtime, "cipher.setAuthTag requires an ArrayBuffer tag argument");
453
+ }
454
+
455
+ if (!ctx_ || !IsAuthenticatedMode() || isCipher_ ||
456
+ auth_tag_state_ != kAuthTagUnknown) {
457
+ return false;
458
+ }
459
+
460
+ auto authTagArrayBuffer =
461
+ arguments[0].asObject(runtime).getArrayBuffer(runtime);
462
+ const unsigned char *data = authTagArrayBuffer.data(runtime);
463
+ auto tag_len = authTagArrayBuffer.length(runtime);
464
+
465
+ // ArrayBufferOrViewContents<char> auth_tag(args[0]);
466
+ // TODO(osp) implement this check
467
+ // if (UNLIKELY(!auth_tag.CheckSizeInt32()))
468
+ // return THROW_ERR_OUT_OF_RANGE(env, "buffer is too big");
469
+
470
+ // unsigned int tag_len = auth_tag.size();
471
+
472
+ const int mode = EVP_CIPHER_CTX_mode(ctx_);
473
+ bool is_valid;
474
+ if (mode == EVP_CIPH_GCM_MODE) {
475
+ // Restrict GCM tag lengths according to NIST 800-38d, page 9.
476
+ is_valid =
477
+ (auth_tag_len_ == kNoAuthTagLength || auth_tag_len_ == tag_len) &&
478
+ IsValidGCMTagLength(tag_len);
479
+ } else {
480
+ // At this point, the tag length is already known and must match the
481
+ // length of the given authentication tag.
482
+ // TODO(osp) add CHECK here
483
+ IsSupportedAuthenticatedMode(ctx_);
484
+ // CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength);
485
+ is_valid = auth_tag_len_ == tag_len;
486
+ }
487
+
488
+ if (!is_valid) {
489
+ throw jsi::JSError(runtime,
490
+ "Invalid authentication tag length: " + tag_len);
491
+ // return THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
492
+ // env, "Invalid authentication tag length: %u", tag_len);
493
+ }
494
+
495
+ auth_tag_len_ = tag_len;
496
+ auth_tag_state_ = kAuthTagKnown;
497
+ // CHECK_LE(cipher->auth_tag_len_, sizeof(cipher->auth_tag_));
498
+
499
+ memset(auth_tag_, 0, sizeof(auth_tag_));
500
+ CopyTo(runtime, &authTagArrayBuffer, auth_tag_, auth_tag_len_);
501
+
502
+ return true;
503
+ }));
504
+ }
505
+
506
+ bool MGLCipherHostObject::MaybePassAuthTagToOpenSSL() {
507
+ if (auth_tag_state_ == kAuthTagKnown) {
508
+ if (!EVP_CIPHER_CTX_ctrl(ctx_, EVP_CTRL_AEAD_SET_TAG, auth_tag_len_,
509
+ reinterpret_cast<unsigned char *>(auth_tag_))) {
510
+ return false;
511
+ }
512
+ auth_tag_state_ = kAuthTagPassedToOpenSSL;
513
+ }
514
+ return true;
515
+ }
516
+
517
+ bool MGLCipherHostObject::IsAuthenticatedMode() const {
518
+ // Check if this cipher operates in an AEAD mode that we support.
519
+ // CHECK(ctx_);
520
+ return IsSupportedAuthenticatedMode(ctx_);
521
+ }
522
+
523
+ bool MGLCipherHostObject::InitAuthenticated(const char *cipher_type, int iv_len,
524
+ unsigned int auth_tag_len) {
525
+ // TODO(osp) implement this check
526
+ // CHECK(IsAuthenticatedMode());
527
+ // TODO(osp) what is this? some sort of node error?
528
+ // MarkPopErrorOnReturn mark_pop_error_on_return;
529
+
530
+ if (!EVP_CIPHER_CTX_ctrl(ctx_, EVP_CTRL_AEAD_SET_IVLEN, iv_len, nullptr)) {
531
+ // throw std::runtime_error("Invalid Cipher IV");
532
+ // THROW_ERR_CRYPTO_INVALID_IV(env());
533
+ return false;
534
+ }
535
+
536
+ const int mode = EVP_CIPHER_CTX_mode(ctx_);
537
+ if (mode == EVP_CIPH_GCM_MODE) {
538
+ if (auth_tag_len != kNoAuthTagLength) {
539
+ if (!IsValidGCMTagLength(auth_tag_len)) {
540
+ // throw std::runtime_error("Invalid Cipher authentication tag
541
+ // length!");
542
+ // THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
543
+ // env(),
544
+ // "Invalid authentication tag length: %u",
545
+ // auth_tag_len);
546
+ return false;
547
+ }
548
+
549
+ // Remember the given authentication tag length for later.
550
+ auth_tag_len_ = auth_tag_len;
551
+ }
552
+ } else {
553
+ if (auth_tag_len == kNoAuthTagLength) {
554
+ // We treat ChaCha20-Poly1305 specially. Like GCM, the authentication tag
555
+ // length defaults to 16 bytes when encrypting. Unlike GCM, the
556
+ // authentication tag length also defaults to 16 bytes when decrypting,
557
+ // whereas GCM would accept any valid authentication tag length.
558
+ if (EVP_CIPHER_CTX_nid(ctx_) == NID_chacha20_poly1305) {
559
+ auth_tag_len = 16;
560
+ } else {
561
+ // throw std::runtime_error("authTagLength required for cipher
562
+ // type");
563
+ // THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
564
+ // env(), "authTagLength required for %s",
565
+ // cipher_type);
566
+ return false;
567
+ }
568
+ }
569
+
570
+ // TODO(tniessen) Support CCM decryption in FIPS mode
571
+
572
+ #if OPENSSL_VERSION_MAJOR >= 3
573
+ if (mode == EVP_CIPH_CCM_MODE && kind_ == kDecipher &&
574
+ EVP_default_properties_is_fips_enabled(nullptr)) {
575
+ #else
576
+ if (mode == EVP_CIPH_CCM_MODE && !isCipher_ && FIPS_mode()) {
577
+ #endif
578
+ // throw std::runtime_error("CCM encryption not supported in FIPS
579
+ // mode");
580
+ // THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env(),
581
+ // "CCM encryption not
582
+ // supported in FIPS
583
+ // mode");
584
+ return false;
585
+ }
586
+
587
+ // Tell OpenSSL about the desired length.
588
+ if (!EVP_CIPHER_CTX_ctrl(ctx_, EVP_CTRL_AEAD_SET_TAG, auth_tag_len,
589
+ nullptr)) {
590
+ // throw std::runtime_error("Invalid authentication tag length");
591
+ // THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
592
+ // env(), "Invalid authentication tag length: %u",
593
+ // auth_tag_len);
594
+ return false;
595
+ }
596
+
597
+ // Remember the given authentication tag length for later.
598
+ auth_tag_len_ = auth_tag_len;
599
+
600
+ if (mode == EVP_CIPH_CCM_MODE) {
601
+ // Restrict the message length to min(INT_MAX, 2^(8*(15-iv_len))-1) bytes.
602
+ // TODO(osp) implement this check
603
+ // CHECK(iv_len >= 7 && iv_len <= 13);
604
+ max_message_size_ = INT_MAX;
605
+ if (iv_len == 12) max_message_size_ = 16777215;
606
+ if (iv_len == 13) max_message_size_ = 65535;
607
+ }
608
+ }
609
+
610
+ return true;
611
+ }
612
+
613
+ bool MGLCipherHostObject::CheckCCMMessageLength(int message_len) {
614
+ // TODO(osp) Implement this check
615
+ // CHECK(EVP_CIPHER_CTX_mode(ctx_) == EVP_CIPH_CCM_MODE);
616
+
617
+ if (message_len > max_message_size_) {
618
+ // THROW_ERR_CRYPTO_INVALID_MESSAGELEN(env());
619
+ return false;
620
+ }
621
+
622
+ return true;
623
+ }
624
+
625
+ MGLCipherHostObject::~MGLCipherHostObject() {
626
+ if (this->ctx_ != nullptr) {
627
+ EVP_CIPHER_CTX_free(this->ctx_);
628
+ }
629
+
630
+ // TODO(osp) go over destructor
631
+ }
632
+ } // namespace margelo