nfets 0.0.58 → 0.0.60
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/addons/wincrypt_certificate_store.cpp +725 -0
- package/addons/winhttp_ssl_client.cpp +436 -0
- package/addons/xml_validator.cpp +422 -0
- package/binding.gyp +92 -0
- package/build/addons/linux-arm64/libxml2.so.2 +0 -0
- package/build/addons/linux-arm64/xml_validator.node +0 -0
- package/build/addons/linux-x64/libxml2.so.2 +0 -0
- package/build/addons/linux-x64/xml_validator.node +0 -0
- package/build/addons/win32-x64/wincrypt_certificate_store.node +0 -0
- package/build/addons/win32-x64/winhttp_ssl_client.node +0 -0
- package/build/addons/win32-x64/xml_validator.node +0 -0
- package/dist/{chunk-YLUIYK2U.mjs → chunk-2TZTVVLH.mjs} +48 -48
- package/dist/{chunk-NGK3NG4O.mjs → chunk-KAPZOI6G.mjs} +1 -1
- package/dist/core/index.cjs +48 -48
- package/dist/core/index.mjs +1 -1
- package/dist/index.cjs +57 -57
- package/dist/index.mjs +1 -1
- package/dist/nfe/index.cjs +58 -58
- package/dist/nfe/index.mjs +1 -1
- package/package.json +5 -2
- package/build/addons/linux-arm64/libicudata.so.74 +0 -0
- package/build/addons/linux-arm64/libicuuc.so.74 +0 -0
- package/build/addons/linux-arm64/liblzma.so.5 +0 -0
- package/build/addons/linux-arm64/libstdc++.so.6 +0 -0
- package/build/addons/linux-arm64/libz.so.1 +0 -0
- package/build/addons/linux-x64/libicudata.so.74 +0 -0
- package/build/addons/linux-x64/libicuuc.so.74 +0 -0
- package/build/addons/linux-x64/liblzma.so.5 +0 -0
- package/build/addons/linux-x64/libstdc++.so.6 +0 -0
- package/build/addons/linux-x64/libz.so.1 +0 -0
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
#include <node_api.h>
|
|
2
|
+
|
|
3
|
+
#include <windows.h>
|
|
4
|
+
#include <wincrypt.h>
|
|
5
|
+
#include <ncrypt.h>
|
|
6
|
+
#include <bcrypt.h>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <vector>
|
|
9
|
+
#include <stdexcept>
|
|
10
|
+
|
|
11
|
+
#pragma comment(lib, "crypt32.lib")
|
|
12
|
+
#pragma comment(lib, "ncrypt.lib")
|
|
13
|
+
#pragma comment(lib, "bcrypt.lib")
|
|
14
|
+
|
|
15
|
+
std::wstring utf8_to_wstring(const std::string &str)
|
|
16
|
+
{
|
|
17
|
+
if (str.empty())
|
|
18
|
+
return std::wstring();
|
|
19
|
+
int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0);
|
|
20
|
+
std::wstring wstrTo(size_needed, 0);
|
|
21
|
+
MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed);
|
|
22
|
+
return wstrTo;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
napi_value SignDataWithCertificate(napi_env env, napi_callback_info info)
|
|
26
|
+
{
|
|
27
|
+
size_t argc = 3;
|
|
28
|
+
napi_value args[3];
|
|
29
|
+
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
|
|
30
|
+
|
|
31
|
+
if (argc < 3)
|
|
32
|
+
{
|
|
33
|
+
napi_value error_msg;
|
|
34
|
+
napi_create_string_utf8(env, "Expected: subject (string), data (Buffer), algorithm (string)", NAPI_AUTO_LENGTH, &error_msg);
|
|
35
|
+
napi_value error;
|
|
36
|
+
napi_create_type_error(env, nullptr, error_msg, &error);
|
|
37
|
+
napi_throw(env, error);
|
|
38
|
+
return nullptr;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
napi_valuetype arg0_type, arg1_type, arg2_type;
|
|
42
|
+
napi_typeof(env, args[0], &arg0_type);
|
|
43
|
+
napi_typeof(env, args[1], &arg1_type);
|
|
44
|
+
napi_typeof(env, args[2], &arg2_type);
|
|
45
|
+
|
|
46
|
+
if (arg0_type != napi_string || arg1_type != napi_object || arg2_type != napi_string)
|
|
47
|
+
{
|
|
48
|
+
napi_value error_msg;
|
|
49
|
+
napi_create_string_utf8(env, "Expected: subject (string), data (Buffer), algorithm (string)", NAPI_AUTO_LENGTH, &error_msg);
|
|
50
|
+
napi_value error;
|
|
51
|
+
napi_create_type_error(env, nullptr, error_msg, &error);
|
|
52
|
+
napi_throw(env, error);
|
|
53
|
+
return nullptr;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
bool is_buffer;
|
|
57
|
+
napi_is_buffer(env, args[1], &is_buffer);
|
|
58
|
+
if (!is_buffer)
|
|
59
|
+
{
|
|
60
|
+
napi_value error_msg;
|
|
61
|
+
napi_create_string_utf8(env, "Second argument must be a Buffer", NAPI_AUTO_LENGTH, &error_msg);
|
|
62
|
+
napi_value error;
|
|
63
|
+
napi_create_type_error(env, nullptr, error_msg, &error);
|
|
64
|
+
napi_throw(env, error);
|
|
65
|
+
return nullptr;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
size_t subject_len = 0;
|
|
69
|
+
napi_get_value_string_utf8(env, args[0], nullptr, 0, &subject_len);
|
|
70
|
+
std::string subject_str(subject_len, '\0');
|
|
71
|
+
napi_get_value_string_utf8(env, args[0], &subject_str[0], subject_len + 1, nullptr);
|
|
72
|
+
std::wstring subject = utf8_to_wstring(subject_str);
|
|
73
|
+
|
|
74
|
+
size_t algorithm_len = 0;
|
|
75
|
+
napi_get_value_string_utf8(env, args[2], nullptr, 0, &algorithm_len);
|
|
76
|
+
std::string algorithm_str(algorithm_len, '\0');
|
|
77
|
+
napi_get_value_string_utf8(env, args[2], &algorithm_str[0], algorithm_len + 1, nullptr);
|
|
78
|
+
|
|
79
|
+
BYTE *data = nullptr;
|
|
80
|
+
size_t dataLenSize = 0;
|
|
81
|
+
napi_get_buffer_info(env, args[1], (void **)&data, &dataLenSize);
|
|
82
|
+
DWORD dataLen = (DWORD)dataLenSize;
|
|
83
|
+
|
|
84
|
+
HCERTSTORE hStore = NULL;
|
|
85
|
+
PCCERT_CONTEXT pCertContext = NULL;
|
|
86
|
+
HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hCryptProv = NULL;
|
|
87
|
+
DWORD dwKeySpec = 0;
|
|
88
|
+
BOOL fCallerFreeProvOrNCryptKey = FALSE;
|
|
89
|
+
HCRYPTHASH hHash = NULL;
|
|
90
|
+
napi_value result = nullptr;
|
|
91
|
+
|
|
92
|
+
try
|
|
93
|
+
{
|
|
94
|
+
hStore = CertOpenStore(
|
|
95
|
+
CERT_STORE_PROV_SYSTEM_W,
|
|
96
|
+
0,
|
|
97
|
+
NULL,
|
|
98
|
+
CERT_SYSTEM_STORE_CURRENT_USER,
|
|
99
|
+
L"MY");
|
|
100
|
+
if (!hStore)
|
|
101
|
+
{
|
|
102
|
+
throw std::runtime_error("Failed to open certificate store");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
pCertContext = CertFindCertificateInStore(
|
|
106
|
+
hStore,
|
|
107
|
+
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
|
108
|
+
0,
|
|
109
|
+
CERT_FIND_SUBJECT_STR_W,
|
|
110
|
+
(LPVOID)subject.c_str(),
|
|
111
|
+
NULL);
|
|
112
|
+
|
|
113
|
+
if (!pCertContext)
|
|
114
|
+
{
|
|
115
|
+
throw std::runtime_error("Certificate not found in store");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
ALG_ID hashAlg = 0;
|
|
119
|
+
if (algorithm_str == "sha1")
|
|
120
|
+
{
|
|
121
|
+
hashAlg = CALG_SHA1;
|
|
122
|
+
}
|
|
123
|
+
else if (algorithm_str == "sha256")
|
|
124
|
+
{
|
|
125
|
+
hashAlg = CALG_SHA_256;
|
|
126
|
+
}
|
|
127
|
+
else
|
|
128
|
+
{
|
|
129
|
+
throw std::runtime_error("Unsupported algorithm: " + algorithm_str);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// For SHA256, prefer CNG from the start since many CSPs don't support it
|
|
133
|
+
// Don't use SILENT flag for SHA256 to allow user interaction (e.g., smart card PIN)
|
|
134
|
+
// For SHA1, prefer CSP first (legacy certificates work better with CSP), fallback to CNG if needed
|
|
135
|
+
DWORD acquireFlags;
|
|
136
|
+
BOOL acquired = FALSE;
|
|
137
|
+
|
|
138
|
+
if (hashAlg == CALG_SHA_256)
|
|
139
|
+
{
|
|
140
|
+
acquireFlags = CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG;
|
|
141
|
+
acquired = CryptAcquireCertificatePrivateKey(
|
|
142
|
+
pCertContext,
|
|
143
|
+
acquireFlags,
|
|
144
|
+
NULL,
|
|
145
|
+
&hCryptProv,
|
|
146
|
+
&dwKeySpec,
|
|
147
|
+
&fCallerFreeProvOrNCryptKey);
|
|
148
|
+
}
|
|
149
|
+
else
|
|
150
|
+
{
|
|
151
|
+
// For SHA1, try CSP first (without allowing CNG) to avoid CNG issues with legacy CSP certificates
|
|
152
|
+
acquireFlags = CRYPT_ACQUIRE_SILENT_FLAG;
|
|
153
|
+
acquired = CryptAcquireCertificatePrivateKey(
|
|
154
|
+
pCertContext,
|
|
155
|
+
acquireFlags,
|
|
156
|
+
NULL,
|
|
157
|
+
&hCryptProv,
|
|
158
|
+
&dwKeySpec,
|
|
159
|
+
&fCallerFreeProvOrNCryptKey);
|
|
160
|
+
|
|
161
|
+
// If CSP failed, try with CNG as fallback (for certificates stored in CNG providers)
|
|
162
|
+
if (!acquired)
|
|
163
|
+
{
|
|
164
|
+
DWORD firstError = GetLastError();
|
|
165
|
+
acquireFlags = CRYPT_ACQUIRE_SILENT_FLAG | CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG;
|
|
166
|
+
acquired = CryptAcquireCertificatePrivateKey(
|
|
167
|
+
pCertContext,
|
|
168
|
+
acquireFlags,
|
|
169
|
+
NULL,
|
|
170
|
+
&hCryptProv,
|
|
171
|
+
&dwKeySpec,
|
|
172
|
+
&fCallerFreeProvOrNCryptKey);
|
|
173
|
+
|
|
174
|
+
if (!acquired)
|
|
175
|
+
{
|
|
176
|
+
DWORD secondError = GetLastError();
|
|
177
|
+
std::string errorMsg = "Failed to acquire private key. First attempt (CSP only) error: " +
|
|
178
|
+
std::to_string(firstError) +
|
|
179
|
+
". Second attempt (with CNG) error: " +
|
|
180
|
+
std::to_string(secondError);
|
|
181
|
+
|
|
182
|
+
if (firstError == 0x80090014 || secondError == 0x80090014) // NTE_BAD_KEYSET
|
|
183
|
+
{
|
|
184
|
+
errorMsg += " (NTE_BAD_KEYSET: The keyset does not exist or the private key is not accessible. "
|
|
185
|
+
"Ensure the certificate is installed with exportable key and the process has access to it.)";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
throw std::runtime_error(errorMsg);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!acquired)
|
|
194
|
+
{
|
|
195
|
+
DWORD error = GetLastError();
|
|
196
|
+
std::string errorMsg = "Failed to acquire private key. Error: " + std::to_string(error);
|
|
197
|
+
|
|
198
|
+
if (error == 0x80090014) // NTE_BAD_KEYSET
|
|
199
|
+
{
|
|
200
|
+
errorMsg += " (NTE_BAD_KEYSET: The keyset does not exist or the private key is not accessible. "
|
|
201
|
+
"Ensure the certificate is installed with exportable key and the process has access to it.)";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
throw std::runtime_error(errorMsg);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (dwKeySpec == CERT_NCRYPT_KEY_SPEC)
|
|
208
|
+
{
|
|
209
|
+
// CNG (Next Generation Crypto API) - para certificados A3
|
|
210
|
+
NCRYPT_KEY_HANDLE hKey = (NCRYPT_KEY_HANDLE)hCryptProv;
|
|
211
|
+
|
|
212
|
+
LPCWSTR hashAlgorithm = nullptr;
|
|
213
|
+
if (algorithm_str == "sha1")
|
|
214
|
+
{
|
|
215
|
+
hashAlgorithm = BCRYPT_SHA1_ALGORITHM;
|
|
216
|
+
}
|
|
217
|
+
else if (algorithm_str == "sha256")
|
|
218
|
+
{
|
|
219
|
+
hashAlgorithm = BCRYPT_SHA256_ALGORITHM;
|
|
220
|
+
}
|
|
221
|
+
else
|
|
222
|
+
{
|
|
223
|
+
throw std::runtime_error("Unsupported algorithm for CNG: " + algorithm_str);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
BCRYPT_ALG_HANDLE hAlg = NULL;
|
|
227
|
+
BCRYPT_HASH_HANDLE hHashHandle = NULL;
|
|
228
|
+
BYTE *pbHashObject = NULL;
|
|
229
|
+
DWORD cbHashObject = 0;
|
|
230
|
+
DWORD cbData = 0;
|
|
231
|
+
BYTE *pbHash = NULL;
|
|
232
|
+
DWORD cbHash = 0;
|
|
233
|
+
DWORD cbSignature = 0;
|
|
234
|
+
BYTE *pbSignature = NULL;
|
|
235
|
+
|
|
236
|
+
NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlg, hashAlgorithm, NULL, 0);
|
|
237
|
+
if (status != 0)
|
|
238
|
+
{
|
|
239
|
+
throw std::runtime_error("Failed to open hash algorithm provider. Error: " + std::to_string(status));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
status = BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbHashObject, sizeof(DWORD), &cbData, 0);
|
|
243
|
+
if (status != 0)
|
|
244
|
+
{
|
|
245
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
246
|
+
throw std::runtime_error("Failed to get hash object length. Error: " + std::to_string(status));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
status = BCryptGetProperty(hAlg, BCRYPT_HASH_LENGTH, (PBYTE)&cbHash, sizeof(DWORD), &cbData, 0);
|
|
250
|
+
if (status != 0)
|
|
251
|
+
{
|
|
252
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
253
|
+
throw std::runtime_error("Failed to get hash length. Error: " + std::to_string(status));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
pbHashObject = (BYTE *)LocalAlloc(LMEM_FIXED, cbHashObject);
|
|
257
|
+
pbHash = (BYTE *)LocalAlloc(LMEM_FIXED, cbHash);
|
|
258
|
+
if (!pbHashObject || !pbHash)
|
|
259
|
+
{
|
|
260
|
+
if (pbHashObject) LocalFree(pbHashObject);
|
|
261
|
+
if (pbHash) LocalFree(pbHash);
|
|
262
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
263
|
+
throw std::runtime_error("Failed to allocate memory for hash");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
status = BCryptCreateHash(hAlg, &hHashHandle, pbHashObject, cbHashObject, NULL, 0, 0);
|
|
267
|
+
if (status != 0)
|
|
268
|
+
{
|
|
269
|
+
LocalFree(pbHashObject);
|
|
270
|
+
LocalFree(pbHash);
|
|
271
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
272
|
+
throw std::runtime_error("Failed to create hash. Error: " + std::to_string(status));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
status = BCryptHashData(hHashHandle, data, dataLen, 0);
|
|
276
|
+
if (status != 0)
|
|
277
|
+
{
|
|
278
|
+
BCryptDestroyHash(hHashHandle);
|
|
279
|
+
LocalFree(pbHashObject);
|
|
280
|
+
LocalFree(pbHash);
|
|
281
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
282
|
+
throw std::runtime_error("Failed to hash data. Error: " + std::to_string(status));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
status = BCryptFinishHash(hHashHandle, pbHash, cbHash, 0);
|
|
286
|
+
if (status != 0)
|
|
287
|
+
{
|
|
288
|
+
BCryptDestroyHash(hHashHandle);
|
|
289
|
+
LocalFree(pbHashObject);
|
|
290
|
+
LocalFree(pbHash);
|
|
291
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
292
|
+
throw std::runtime_error("Failed to finish hash. Error: " + std::to_string(status));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
BCryptDestroyHash(hHashHandle);
|
|
296
|
+
hHashHandle = NULL;
|
|
297
|
+
|
|
298
|
+
status = NCryptSignHash(hKey, NULL, pbHash, cbHash, NULL, 0, &cbSignature, NCRYPT_PAD_PKCS1_FLAG);
|
|
299
|
+
if (status != 0 && status != NTE_BUFFER_TOO_SMALL)
|
|
300
|
+
{
|
|
301
|
+
LocalFree(pbHashObject);
|
|
302
|
+
LocalFree(pbHash);
|
|
303
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
304
|
+
throw std::runtime_error("Failed to get signature size. Error: " + std::to_string(status));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
pbSignature = (BYTE *)LocalAlloc(LMEM_FIXED, cbSignature);
|
|
308
|
+
if (!pbSignature)
|
|
309
|
+
{
|
|
310
|
+
LocalFree(pbHashObject);
|
|
311
|
+
LocalFree(pbHash);
|
|
312
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
313
|
+
throw std::runtime_error("Failed to allocate memory for signature");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
status = NCryptSignHash(hKey, NULL, pbHash, cbHash, pbSignature, cbSignature, &cbSignature, NCRYPT_PAD_PKCS1_FLAG);
|
|
317
|
+
if (status != 0)
|
|
318
|
+
{
|
|
319
|
+
// For SHA1, if CNG signing fails with NTE_BAD_KEY_STATE, try to fallback to CSP
|
|
320
|
+
// This can happen when the certificate is in a legacy CSP that doesn't fully support CNG
|
|
321
|
+
if ((status == NTE_BAD_KEY_STATE || status == 0x80090027 || status == -2146893785) &&
|
|
322
|
+
algorithm_str == "sha1" && hashAlg == CALG_SHA1)
|
|
323
|
+
{
|
|
324
|
+
// Free CNG resources
|
|
325
|
+
LocalFree(pbHashObject);
|
|
326
|
+
LocalFree(pbHash);
|
|
327
|
+
LocalFree(pbSignature);
|
|
328
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
329
|
+
if (fCallerFreeProvOrNCryptKey && hCryptProv)
|
|
330
|
+
{
|
|
331
|
+
NCryptFreeObject(hCryptProv);
|
|
332
|
+
hCryptProv = NULL;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Try to re-acquire as CSP
|
|
336
|
+
acquireFlags = CRYPT_ACQUIRE_SILENT_FLAG;
|
|
337
|
+
if (CryptAcquireCertificatePrivateKey(
|
|
338
|
+
pCertContext,
|
|
339
|
+
acquireFlags,
|
|
340
|
+
NULL,
|
|
341
|
+
&hCryptProv,
|
|
342
|
+
&dwKeySpec,
|
|
343
|
+
&fCallerFreeProvOrNCryptKey) &&
|
|
344
|
+
dwKeySpec != CERT_NCRYPT_KEY_SPEC)
|
|
345
|
+
{
|
|
346
|
+
// Successfully acquired as CSP, now do CSP signing
|
|
347
|
+
// Create hash using CSP
|
|
348
|
+
if (!CryptCreateHash(hCryptProv, hashAlg, 0, 0, &hHash))
|
|
349
|
+
{
|
|
350
|
+
DWORD cspError = GetLastError();
|
|
351
|
+
throw std::runtime_error("Failed to create CSP hash after CNG fallback. Error: " + std::to_string(cspError));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (!CryptHashData(hHash, data, dataLen, 0))
|
|
355
|
+
{
|
|
356
|
+
CryptDestroyHash(hHash);
|
|
357
|
+
hHash = NULL;
|
|
358
|
+
throw std::runtime_error("Failed to hash data with CSP. Error: " + std::to_string(GetLastError()));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
DWORD signatureLen = 0;
|
|
362
|
+
if (!CryptSignHash(hHash, dwKeySpec, NULL, 0, NULL, &signatureLen))
|
|
363
|
+
{
|
|
364
|
+
CryptDestroyHash(hHash);
|
|
365
|
+
hHash = NULL;
|
|
366
|
+
throw std::runtime_error("Failed to get CSP signature size. Error: " + std::to_string(GetLastError()));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
BYTE *signature = (BYTE *)LocalAlloc(LMEM_FIXED, signatureLen);
|
|
370
|
+
if (!signature)
|
|
371
|
+
{
|
|
372
|
+
CryptDestroyHash(hHash);
|
|
373
|
+
hHash = NULL;
|
|
374
|
+
throw std::runtime_error("Failed to allocate memory for CSP signature");
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!CryptSignHash(hHash, dwKeySpec, NULL, 0, signature, &signatureLen))
|
|
378
|
+
{
|
|
379
|
+
LocalFree(signature);
|
|
380
|
+
CryptDestroyHash(hHash);
|
|
381
|
+
hHash = NULL;
|
|
382
|
+
throw std::runtime_error("Failed to sign hash with CSP. Error: " + std::to_string(GetLastError()));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
std::vector<BYTE> reversedSignature(signatureLen);
|
|
386
|
+
for (DWORD i = 0; i < signatureLen; i++)
|
|
387
|
+
{
|
|
388
|
+
reversedSignature[i] = signature[signatureLen - 1 - i];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
napi_create_buffer_copy(env, signatureLen, reversedSignature.data(), nullptr, &result);
|
|
392
|
+
|
|
393
|
+
LocalFree(signature);
|
|
394
|
+
CryptDestroyHash(hHash);
|
|
395
|
+
hHash = NULL;
|
|
396
|
+
|
|
397
|
+
// Skip the rest of CNG code and go to cleanup
|
|
398
|
+
goto cleanup;
|
|
399
|
+
}
|
|
400
|
+
else
|
|
401
|
+
{
|
|
402
|
+
// CSP acquisition also failed, throw error
|
|
403
|
+
throw std::runtime_error(
|
|
404
|
+
"CNG signing failed and CSP fallback also failed. The certificate's key provider may not fully support signing operations. "
|
|
405
|
+
"CNG error: " + std::to_string(status) + ", CSP acquisition error: " + std::to_string(GetLastError()));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
LocalFree(pbHashObject);
|
|
410
|
+
LocalFree(pbHash);
|
|
411
|
+
LocalFree(pbSignature);
|
|
412
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
413
|
+
|
|
414
|
+
if (status == NTE_BAD_KEY_STATE || status == 0x80090027 || status == -2146893785)
|
|
415
|
+
{
|
|
416
|
+
throw std::runtime_error(
|
|
417
|
+
"CNG signing failed. The certificate's key provider may not fully support CNG signing operations, "
|
|
418
|
+
"or the key may require user interaction (e.g., smart card PIN). Error: " + std::to_string(status));
|
|
419
|
+
}
|
|
420
|
+
else
|
|
421
|
+
{
|
|
422
|
+
throw std::runtime_error("Failed to sign hash with CNG. Error: " + std::to_string(status));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
std::vector<BYTE> reversedSignature(cbSignature);
|
|
427
|
+
for (DWORD i = 0; i < cbSignature; i++)
|
|
428
|
+
{
|
|
429
|
+
reversedSignature[i] = pbSignature[cbSignature - 1 - i];
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
napi_create_buffer_copy(env, cbSignature, reversedSignature.data(), nullptr, &result);
|
|
433
|
+
|
|
434
|
+
LocalFree(pbHashObject);
|
|
435
|
+
LocalFree(pbHash);
|
|
436
|
+
LocalFree(pbSignature);
|
|
437
|
+
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
438
|
+
}
|
|
439
|
+
else
|
|
440
|
+
{
|
|
441
|
+
// CSP (Crypto Service Provider) - para certificados A1
|
|
442
|
+
DWORD error = 0;
|
|
443
|
+
if (!CryptCreateHash(hCryptProv, hashAlg, 0, 0, &hHash))
|
|
444
|
+
{
|
|
445
|
+
error = GetLastError();
|
|
446
|
+
if (error == NTE_BAD_ALGID && hashAlg == CALG_SHA_256)
|
|
447
|
+
{
|
|
448
|
+
if (fCallerFreeProvOrNCryptKey && hCryptProv)
|
|
449
|
+
{
|
|
450
|
+
CryptReleaseContext(hCryptProv, 0);
|
|
451
|
+
hCryptProv = NULL;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hNCryptKey = NULL;
|
|
455
|
+
DWORD dwNCryptKeySpec = 0;
|
|
456
|
+
BOOL fCallerFreeNCryptKey = FALSE;
|
|
457
|
+
|
|
458
|
+
if (CryptAcquireCertificatePrivateKey(
|
|
459
|
+
pCertContext,
|
|
460
|
+
CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG,
|
|
461
|
+
NULL,
|
|
462
|
+
&hNCryptKey,
|
|
463
|
+
&dwNCryptKeySpec,
|
|
464
|
+
&fCallerFreeNCryptKey) && dwNCryptKeySpec == CERT_NCRYPT_KEY_SPEC)
|
|
465
|
+
{
|
|
466
|
+
NCRYPT_KEY_HANDLE hKey = (NCRYPT_KEY_HANDLE)hNCryptKey;
|
|
467
|
+
|
|
468
|
+
DWORD dwKeyUsage = 0;
|
|
469
|
+
DWORD cbResult = 0;
|
|
470
|
+
NTSTATUS testStatus = NCryptGetProperty(hKey, NCRYPT_KEY_USAGE_PROPERTY, (PBYTE)&dwKeyUsage, sizeof(DWORD), &cbResult, 0);
|
|
471
|
+
if (testStatus == 0 && !(dwKeyUsage & NCRYPT_ALLOW_SIGNING_FLAG))
|
|
472
|
+
{
|
|
473
|
+
if (fCallerFreeNCryptKey) NCryptFreeObject(hNCryptKey);
|
|
474
|
+
throw std::runtime_error("CNG key does not allow signing operations. The certificate's key provider may not fully support CNG signing. Try using SHA1 instead, or use a certificate stored in a CNG-compatible provider.");
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
BCRYPT_ALG_HANDLE hBcryptAlg = NULL;
|
|
478
|
+
BCRYPT_HASH_HANDLE hBcryptHash = NULL;
|
|
479
|
+
BYTE *pbBcryptHashObject = NULL;
|
|
480
|
+
DWORD cbBcryptHashObject = 0;
|
|
481
|
+
DWORD cbData = 0;
|
|
482
|
+
BYTE *pbHash = NULL;
|
|
483
|
+
DWORD cbHash = 0;
|
|
484
|
+
DWORD cbSignature = 0;
|
|
485
|
+
BYTE *pbSignature = NULL;
|
|
486
|
+
NTSTATUS status = 0;
|
|
487
|
+
|
|
488
|
+
status = BCryptOpenAlgorithmProvider(&hBcryptAlg, BCRYPT_SHA256_ALGORITHM, NULL, 0);
|
|
489
|
+
if (status != 0)
|
|
490
|
+
{
|
|
491
|
+
if (fCallerFreeNCryptKey) NCryptFreeObject(hNCryptKey);
|
|
492
|
+
throw std::runtime_error("Failed to open BCrypt SHA256 provider. Error: " + std::to_string(status));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
status = BCryptGetProperty(hBcryptAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbBcryptHashObject, sizeof(DWORD), &cbData, 0);
|
|
496
|
+
if (status != 0)
|
|
497
|
+
{
|
|
498
|
+
BCryptCloseAlgorithmProvider(hBcryptAlg, 0);
|
|
499
|
+
if (fCallerFreeNCryptKey) NCryptFreeObject(hNCryptKey);
|
|
500
|
+
throw std::runtime_error("Failed to get BCrypt hash object length. Error: " + std::to_string(status));
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
status = BCryptGetProperty(hBcryptAlg, BCRYPT_HASH_LENGTH, (PBYTE)&cbHash, sizeof(DWORD), &cbData, 0);
|
|
504
|
+
if (status != 0)
|
|
505
|
+
{
|
|
506
|
+
BCryptCloseAlgorithmProvider(hBcryptAlg, 0);
|
|
507
|
+
if (fCallerFreeNCryptKey) NCryptFreeObject(hNCryptKey);
|
|
508
|
+
throw std::runtime_error("Failed to get BCrypt hash length. Error: " + std::to_string(status));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
pbBcryptHashObject = (BYTE *)LocalAlloc(LMEM_FIXED, cbBcryptHashObject);
|
|
512
|
+
pbHash = (BYTE *)LocalAlloc(LMEM_FIXED, cbHash);
|
|
513
|
+
if (!pbBcryptHashObject || !pbHash)
|
|
514
|
+
{
|
|
515
|
+
if (pbBcryptHashObject) LocalFree(pbBcryptHashObject);
|
|
516
|
+
if (pbHash) LocalFree(pbHash);
|
|
517
|
+
BCryptCloseAlgorithmProvider(hBcryptAlg, 0);
|
|
518
|
+
if (fCallerFreeNCryptKey) NCryptFreeObject(hNCryptKey);
|
|
519
|
+
throw std::runtime_error("Failed to allocate memory for BCrypt hash");
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
status = BCryptCreateHash(hBcryptAlg, &hBcryptHash, pbBcryptHashObject, cbBcryptHashObject, NULL, 0, 0);
|
|
523
|
+
if (status != 0)
|
|
524
|
+
{
|
|
525
|
+
LocalFree(pbBcryptHashObject);
|
|
526
|
+
LocalFree(pbHash);
|
|
527
|
+
BCryptCloseAlgorithmProvider(hBcryptAlg, 0);
|
|
528
|
+
if (fCallerFreeNCryptKey) NCryptFreeObject(hNCryptKey);
|
|
529
|
+
throw std::runtime_error("Failed to create BCrypt hash. Error: " + std::to_string(status));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
status = BCryptHashData(hBcryptHash, data, dataLen, 0);
|
|
533
|
+
if (status != 0)
|
|
534
|
+
{
|
|
535
|
+
BCryptDestroyHash(hBcryptHash);
|
|
536
|
+
LocalFree(pbBcryptHashObject);
|
|
537
|
+
LocalFree(pbHash);
|
|
538
|
+
BCryptCloseAlgorithmProvider(hBcryptAlg, 0);
|
|
539
|
+
if (fCallerFreeNCryptKey) NCryptFreeObject(hNCryptKey);
|
|
540
|
+
throw std::runtime_error("Failed to hash data with BCrypt. Error: " + std::to_string(status));
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
status = BCryptFinishHash(hBcryptHash, pbHash, cbHash, 0);
|
|
544
|
+
if (status != 0)
|
|
545
|
+
{
|
|
546
|
+
BCryptDestroyHash(hBcryptHash);
|
|
547
|
+
LocalFree(pbBcryptHashObject);
|
|
548
|
+
LocalFree(pbHash);
|
|
549
|
+
BCryptCloseAlgorithmProvider(hBcryptAlg, 0);
|
|
550
|
+
if (fCallerFreeNCryptKey) NCryptFreeObject(hNCryptKey);
|
|
551
|
+
throw std::runtime_error("Failed to finish BCrypt hash. Error: " + std::to_string(status));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
BCryptDestroyHash(hBcryptHash);
|
|
555
|
+
LocalFree(pbBcryptHashObject);
|
|
556
|
+
BCryptCloseAlgorithmProvider(hBcryptAlg, 0);
|
|
557
|
+
|
|
558
|
+
status = NCryptSignHash(hKey, NULL, pbHash, cbHash, NULL, 0, &cbSignature, NCRYPT_PAD_PKCS1_FLAG);
|
|
559
|
+
if (status != 0 && status != NTE_BUFFER_TOO_SMALL)
|
|
560
|
+
{
|
|
561
|
+
LocalFree(pbHash);
|
|
562
|
+
if (fCallerFreeNCryptKey) NCryptFreeObject(hNCryptKey);
|
|
563
|
+
throw std::runtime_error("Failed to get CNG signature size. Error: " + std::to_string(status));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
pbSignature = (BYTE *)LocalAlloc(LMEM_FIXED, cbSignature);
|
|
567
|
+
if (!pbSignature)
|
|
568
|
+
{
|
|
569
|
+
LocalFree(pbHash);
|
|
570
|
+
if (fCallerFreeNCryptKey) NCryptFreeObject(hNCryptKey);
|
|
571
|
+
throw std::runtime_error("Failed to allocate memory for signature");
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
status = NCryptSignHash(hKey, NULL, pbHash, cbHash, pbSignature, cbSignature, &cbSignature, NCRYPT_PAD_PKCS1_FLAG);
|
|
575
|
+
if (status != 0)
|
|
576
|
+
{
|
|
577
|
+
status = NCryptSignHash(hKey, NULL, pbHash, cbHash, pbSignature, cbSignature, &cbSignature, 0);
|
|
578
|
+
if (status != 0)
|
|
579
|
+
{
|
|
580
|
+
LocalFree(pbHash);
|
|
581
|
+
LocalFree(pbSignature);
|
|
582
|
+
if (fCallerFreeNCryptKey) NCryptFreeObject(hNCryptKey);
|
|
583
|
+
|
|
584
|
+
// Provide more specific error messages with helpful suggestions
|
|
585
|
+
if (status == NTE_BAD_KEY_STATE || status == 0x80090027 || status == -2146893785)
|
|
586
|
+
{
|
|
587
|
+
throw std::runtime_error(
|
|
588
|
+
"SHA256 signing failed: The certificate's key provider does not fully support CNG signing operations. "
|
|
589
|
+
"This typically occurs with certificates stored in legacy CSP (Cryptographic Service Provider) providers that don't support SHA256. "
|
|
590
|
+
"Even though Windows can acquire a CNG handle, the actual signing operation fails because the underlying provider is CSP-based. "
|
|
591
|
+
"Solutions: 1) Use SHA1 instead of SHA256 (if acceptable for your use case), "
|
|
592
|
+
"2) Import/use a certificate stored in a CNG-compatible provider (e.g., Microsoft Software Key Storage Provider), "
|
|
593
|
+
"3) Check if your certificate requires user interaction (smart card PIN entry). "
|
|
594
|
+
"Error code: " + std::to_string(status));
|
|
595
|
+
}
|
|
596
|
+
else
|
|
597
|
+
{
|
|
598
|
+
throw std::runtime_error("Failed to sign hash with CNG. Error: " + std::to_string(status));
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
std::vector<BYTE> reversedSignature(cbSignature);
|
|
604
|
+
for (DWORD i = 0; i < cbSignature; i++)
|
|
605
|
+
{
|
|
606
|
+
reversedSignature[i] = pbSignature[cbSignature - 1 - i];
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
napi_create_buffer_copy(env, cbSignature, reversedSignature.data(), nullptr, &result);
|
|
610
|
+
|
|
611
|
+
LocalFree(pbHash);
|
|
612
|
+
LocalFree(pbSignature);
|
|
613
|
+
|
|
614
|
+
hCryptProv = hNCryptKey;
|
|
615
|
+
dwKeySpec = dwNCryptKeySpec;
|
|
616
|
+
fCallerFreeProvOrNCryptKey = fCallerFreeNCryptKey;
|
|
617
|
+
}
|
|
618
|
+
else
|
|
619
|
+
{
|
|
620
|
+
if (CryptAcquireCertificatePrivateKey(
|
|
621
|
+
pCertContext,
|
|
622
|
+
CRYPT_ACQUIRE_SILENT_FLAG | CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG,
|
|
623
|
+
NULL,
|
|
624
|
+
&hCryptProv,
|
|
625
|
+
&dwKeySpec,
|
|
626
|
+
&fCallerFreeProvOrNCryptKey))
|
|
627
|
+
{
|
|
628
|
+
throw std::runtime_error("SHA256 is not supported by the certificate's CSP. The certificate may require a CSP that supports SHA256, or you may need to use SHA1 instead. Error: " + std::to_string(error));
|
|
629
|
+
}
|
|
630
|
+
else
|
|
631
|
+
{
|
|
632
|
+
throw std::runtime_error("Failed to re-acquire certificate key. Error: " + std::to_string(GetLastError()));
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
else
|
|
637
|
+
{
|
|
638
|
+
throw std::runtime_error("Failed to create hash. CSP does not support the requested algorithm. Error: " + std::to_string(error));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
else
|
|
642
|
+
{
|
|
643
|
+
if (!CryptHashData(hHash, data, dataLen, 0))
|
|
644
|
+
{
|
|
645
|
+
throw std::runtime_error("Failed to hash data. Error: " + std::to_string(GetLastError()));
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
DWORD signatureLen = 0;
|
|
649
|
+
if (!CryptSignHash(hHash, dwKeySpec, NULL, 0, NULL, &signatureLen))
|
|
650
|
+
{
|
|
651
|
+
throw std::runtime_error("Failed to get signature size. Error: " + std::to_string(GetLastError()));
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
BYTE *signature = (BYTE *)LocalAlloc(LMEM_FIXED, signatureLen);
|
|
655
|
+
if (!signature)
|
|
656
|
+
{
|
|
657
|
+
throw std::runtime_error("Failed to allocate memory for signature");
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (!CryptSignHash(hHash, dwKeySpec, NULL, 0, signature, &signatureLen))
|
|
661
|
+
{
|
|
662
|
+
LocalFree(signature);
|
|
663
|
+
throw std::runtime_error("Failed to sign hash. Error: " + std::to_string(GetLastError()));
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
std::vector<BYTE> reversedSignature(signatureLen);
|
|
667
|
+
for (DWORD i = 0; i < signatureLen; i++)
|
|
668
|
+
{
|
|
669
|
+
reversedSignature[i] = signature[signatureLen - 1 - i];
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
napi_create_buffer_copy(env, signatureLen, reversedSignature.data(), nullptr, &result);
|
|
673
|
+
|
|
674
|
+
LocalFree(signature);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
catch (const std::exception &e)
|
|
679
|
+
{
|
|
680
|
+
napi_throw_error(env, nullptr, e.what());
|
|
681
|
+
result = nullptr;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
cleanup:
|
|
685
|
+
if (hHash)
|
|
686
|
+
{
|
|
687
|
+
CryptDestroyHash(hHash);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (fCallerFreeProvOrNCryptKey && hCryptProv)
|
|
691
|
+
{
|
|
692
|
+
if (dwKeySpec == CERT_NCRYPT_KEY_SPEC)
|
|
693
|
+
{
|
|
694
|
+
NCryptFreeObject(hCryptProv);
|
|
695
|
+
}
|
|
696
|
+
else
|
|
697
|
+
{
|
|
698
|
+
CryptReleaseContext(hCryptProv, 0);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (pCertContext)
|
|
703
|
+
{
|
|
704
|
+
CertFreeCertificateContext(pCertContext);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (hStore)
|
|
708
|
+
{
|
|
709
|
+
CertCloseStore(hStore, 0);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return result;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
napi_value Init(napi_env env, napi_value exports)
|
|
716
|
+
{
|
|
717
|
+
napi_value fn;
|
|
718
|
+
|
|
719
|
+
napi_create_function(env, nullptr, 0, SignDataWithCertificate, nullptr, &fn);
|
|
720
|
+
napi_set_named_property(env, exports, "sign", fn);
|
|
721
|
+
|
|
722
|
+
return exports;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|