1 | //======================================================================== |
2 | // |
3 | // SignatureHandler.cc |
4 | // |
5 | // This file is licensed under the GPLv2 or later |
6 | // |
7 | // Copyright 2015, 2016 André Guerreiro <aguerreiro1985@gmail.com> |
8 | // Copyright 2015 André Esser <bepandre@hotmail.com> |
9 | // Copyright 2015, 2016, 2018, 2019, 2021-2023 Albert Astals Cid <aacid@kde.org> |
10 | // Copyright 2015 Markus Kilås <digital@markuspage.com> |
11 | // Copyright 2017 Sebastian Rasmussen <sebras@gmail.com> |
12 | // Copyright 2017 Hans-Ulrich Jüttner <huj@froreich-bioscientia.de> |
13 | // Copyright 2018 Chinmoy Ranjan Pradhan <chinmoyrp65@protonmail.com> |
14 | // Copyright 2018 Oliver Sander <oliver.sander@tu-dresden.de> |
15 | // Copyright 2020 Thorsten Behrens <Thorsten.Behrens@CIB.de> |
16 | // Copyright 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by Technische Universität Dresden |
17 | // Copyright 2021 Theofilos Intzoglou <int.teo@gmail.com> |
18 | // Copyright 2021 Marek Kasik <mkasik@redhat.com> |
19 | // Copyright 2022 Erich E. Hoover <erich.e.hoover@gmail.com> |
20 | // Copyright 2023 Tobias Deiminger <tobias.deiminger@posteo.de> |
21 | // Copyright 2023, 2024 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk> |
22 | // Copyright 2023 Ingo Klöcker <kloecker@kde.org> |
23 | // |
24 | //======================================================================== |
25 | |
26 | #include <config.h> |
27 | |
28 | #include "NSSCryptoSignBackend.h" |
29 | #include "goo/gdir.h" |
30 | #include "goo/gmem.h" |
31 | |
32 | #include <optional> |
33 | #include <vector> |
34 | |
35 | #include <Error.h> |
36 | |
37 | /* NSS headers */ |
38 | #include <secmod.h> |
39 | #include <secoid.h> |
40 | #include <keyhi.h> |
41 | #include <secder.h> |
42 | #include <pk11pub.h> |
43 | #include <secpkcs7.h> |
44 | |
45 | #include <cert.h> |
46 | #include <hasht.h> |
47 | #include <secerr.h> |
48 | #include <sechash.h> |
49 | #include <cms.h> |
50 | #include <cmst.h> |
51 | |
52 | /** |
53 | * General name, defined by RFC 3280. |
54 | */ |
55 | struct GeneralName |
56 | { |
57 | CERTName name; |
58 | }; |
59 | |
60 | /** |
61 | * List of general names (only one for now), defined by RFC 3280. |
62 | */ |
63 | struct GeneralNames |
64 | { |
65 | GeneralName names; |
66 | }; |
67 | |
68 | /** |
69 | * Supplies different fields to identify a certificate, defined by RFC 5035. |
70 | */ |
71 | struct IssuerSerial |
72 | { |
73 | GeneralNames issuer; |
74 | SECItem serialNumber; |
75 | }; |
76 | |
77 | /** |
78 | * Supplies different fields that are used to identify certificates, defined by |
79 | * RFC 5035. |
80 | */ |
81 | struct ESSCertIDv2 |
82 | { |
83 | SECAlgorithmID hashAlgorithm; |
84 | SECItem certHash; |
85 | IssuerSerial issuerSerial; |
86 | }; |
87 | |
88 | /** |
89 | * This attribute uses the ESSCertIDv2 structure, defined by RFC 5035. |
90 | */ |
91 | struct SigningCertificateV2 |
92 | { |
93 | ESSCertIDv2 **certs; |
94 | |
95 | SigningCertificateV2() : certs(nullptr) { } |
96 | }; |
97 | |
98 | /** |
99 | * GeneralName ::= CHOICE { |
100 | * otherName [0] OtherName, |
101 | * rfc822Name [1] IA5String, |
102 | * dNSName [2] IA5String, |
103 | * x400Address [3] ORAddress, |
104 | * directoryName [4] Name, |
105 | * ediPartyName [5] EDIPartyName, |
106 | * uniformResourceIdentifier [6] IA5String, |
107 | * iPAddress [7] OCTET STRING, |
108 | * registeredID [8] OBJECT IDENTIFIER |
109 | * } |
110 | */ |
111 | const SEC_ASN1Template GeneralNameTemplate[] = { { SEC_ASN1_SEQUENCE, .offset: 0, .sub: nullptr, .size: sizeof(GeneralName) }, { SEC_ASN1_INLINE, offsetof(GeneralName, name), SEC_ASN1_GET(CERT_NameTemplate), .size: 0 }, { .kind: 0, .offset: 0, .sub: nullptr, .size: 0 } }; |
112 | |
113 | /** |
114 | * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName |
115 | */ |
116 | const SEC_ASN1Template GeneralNamesTemplate[] = { { SEC_ASN1_SEQUENCE, .offset: 0, .sub: nullptr, .size: sizeof(GeneralNames) }, { SEC_ASN1_INLINE | SEC_ASN1_CONTEXT_SPECIFIC | 4, offsetof(GeneralNames, names), .sub: GeneralNameTemplate, .size: 0 }, { .kind: 0, .offset: 0, .sub: nullptr, .size: 0 } }; |
117 | |
118 | /** |
119 | * IssuerSerial ::= SEQUENCE { |
120 | * issuer GeneralNames, |
121 | * serialNumber CertificateSerialNumber |
122 | * } |
123 | */ |
124 | const SEC_ASN1Template IssuerSerialTemplate[] = { |
125 | { SEC_ASN1_SEQUENCE, .offset: 0, .sub: nullptr, .size: sizeof(IssuerSerial) }, { SEC_ASN1_INLINE, offsetof(IssuerSerial, issuer), .sub: GeneralNamesTemplate, .size: 0 }, { SEC_ASN1_INTEGER, offsetof(IssuerSerial, serialNumber), .sub: nullptr, .size: 0 }, { .kind: 0, .offset: 0, .sub: nullptr, .size: 0 } |
126 | }; |
127 | |
128 | /** |
129 | * Hash ::= OCTET STRING |
130 | * |
131 | * ESSCertIDv2 ::= SEQUENCE { |
132 | * hashAlgorithm AlgorithmIdentifier DEFAULT {algorithm id-sha256}, |
133 | * certHash Hash, |
134 | * issuerSerial IssuerSerial OPTIONAL |
135 | * } |
136 | */ |
137 | |
138 | const SEC_ASN1Template ESSCertIDv2Template[] = { { SEC_ASN1_SEQUENCE, .offset: 0, .sub: nullptr, .size: sizeof(ESSCertIDv2) }, |
139 | { SEC_ASN1_INLINE, offsetof(ESSCertIDv2, hashAlgorithm), SEC_ASN1_GET(SECOID_AlgorithmIDTemplate), .size: 0 }, |
140 | { SEC_ASN1_OCTET_STRING, offsetof(ESSCertIDv2, certHash), .sub: nullptr, .size: 0 }, |
141 | { SEC_ASN1_INLINE, offsetof(ESSCertIDv2, issuerSerial), .sub: IssuerSerialTemplate, .size: 0 }, |
142 | { .kind: 0, .offset: 0, .sub: nullptr, .size: 0 } }; |
143 | |
144 | /** |
145 | * SigningCertificateV2 ::= SEQUENCE { |
146 | * } |
147 | */ |
148 | const SEC_ASN1Template SigningCertificateV2Template[] = { { SEC_ASN1_SEQUENCE, .offset: 0, .sub: nullptr, .size: sizeof(SigningCertificateV2) }, { SEC_ASN1_SEQUENCE_OF, offsetof(SigningCertificateV2, certs), .sub: ESSCertIDv2Template, .size: 0 }, { .kind: 0, .offset: 0, .sub: nullptr, .size: 0 } }; |
149 | |
150 | /* |
151 | struct PKIStatusInfo |
152 | { |
153 | SECItem status; |
154 | SECItem statusString; |
155 | SECItem failInfo; |
156 | }; |
157 | |
158 | const SEC_ASN1Template PKIStatusInfo_Template[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(PKIStatusInfo) }, |
159 | { SEC_ASN1_INTEGER, offsetof(PKIStatusInfo, status), nullptr, 0 }, |
160 | { SEC_ASN1_CONSTRUCTED | SEC_ASN1_SEQUENCE | SEC_ASN1_OPTIONAL, offsetof(PKIStatusInfo, statusString), nullptr, 0 }, |
161 | { SEC_ASN1_BIT_STRING | SEC_ASN1_OPTIONAL, offsetof(PKIStatusInfo, failInfo), nullptr, 0 }, |
162 | { 0, 0, nullptr, 0 } }; |
163 | |
164 | const SEC_ASN1Template Any_Template[] = { { SEC_ASN1_ANY, 0, nullptr, sizeof(SECItem) } }; |
165 | |
166 | struct TimeStampResp |
167 | { |
168 | PKIStatusInfo status; |
169 | SECItem timeStampToken; |
170 | }; |
171 | |
172 | const SEC_ASN1Template TimeStampResp_Template[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(TimeStampResp) }, |
173 | { SEC_ASN1_INLINE, offsetof(TimeStampResp, status), PKIStatusInfo_Template, 0 }, |
174 | { SEC_ASN1_ANY | SEC_ASN1_OPTIONAL, offsetof(TimeStampResp, timeStampToken), Any_Template, 0 }, |
175 | { 0, 0, nullptr, 0 } }; |
176 | |
177 | const SEC_ASN1Template MessageImprint_Template[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(MessageImprint) }, |
178 | { SEC_ASN1_INLINE, offsetof(MessageImprint, hashAlgorithm), SECOID_AlgorithmIDTemplate, 0 }, |
179 | { SEC_ASN1_OCTET_STRING, offsetof(MessageImprint, hashedMessage), nullptr, 0 }, |
180 | { 0, 0, nullptr, 0 } }; |
181 | |
182 | const SEC_ASN1Template Extension_Template[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(Extension) }, |
183 | { SEC_ASN1_OBJECT_ID, offsetof(Extension, extnID), nullptr, 0 }, |
184 | { SEC_ASN1_BOOLEAN, offsetof(Extension, critical), nullptr, 0 }, |
185 | { SEC_ASN1_OCTET_STRING, offsetof(Extension, extnValue), nullptr, 0 }, |
186 | { 0, 0, nullptr, 0 } }; |
187 | |
188 | const SEC_ASN1Template Extensions_Template[] = { { SEC_ASN1_SEQUENCE_OF, 0, Extension_Template, 0 } }; |
189 | |
190 | const SEC_ASN1Template TimeStampReq_Template[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(TimeStampReq) }, |
191 | { SEC_ASN1_INTEGER, offsetof(TimeStampReq, version), nullptr, 0 }, |
192 | { SEC_ASN1_INLINE, offsetof(TimeStampReq, messageImprint), MessageImprint_Template, 0 }, |
193 | { SEC_ASN1_OBJECT_ID | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, reqPolicy), nullptr, 0 }, |
194 | { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, nonce), nullptr, 0 }, |
195 | { SEC_ASN1_BOOLEAN | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, certReq), nullptr, 0 }, |
196 | { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | 0, offsetof(TimeStampReq, extensions), Extensions_Template, 0 }, |
197 | { 0, 0, nullptr, 0 } }; |
198 | */ |
199 | |
200 | static NSSCMSMessage *CMS_MessageCreate(SECItem *cms_item); |
201 | static NSSCMSSignedData *CMS_SignedDataCreate(NSSCMSMessage *cms_msg); |
202 | static NSSCMSSignerInfo *CMS_SignerInfoCreate(NSSCMSSignedData *cms_sig_data); |
203 | |
204 | // a dummy, actually |
205 | static char *passwordCallback(PK11SlotInfo * /*slot*/, PRBool /*retry*/, void *arg) |
206 | { |
207 | return PL_strdup(s: static_cast<char *>(arg)); |
208 | } |
209 | |
210 | static void shutdownNss() |
211 | { |
212 | if (NSS_Shutdown() != SECSuccess) { |
213 | fprintf(stderr, format: "NSS_Shutdown failed: %s\n" , PR_ErrorToString(code: PORT_GetError(), PR_LANGUAGE_I_DEFAULT)); |
214 | } |
215 | } |
216 | |
217 | // SEC_StringToOID() and NSS_CMSSignerInfo_AddUnauthAttr() are |
218 | // not exported from libsmime, so copy them here. Sigh. |
219 | |
220 | static SECStatus my_SEC_StringToOID(PLArenaPool *arena, SECItem *to, const char *from, PRUint32 len) |
221 | { |
222 | PRUint32 decimal_numbers = 0; |
223 | PRUint32 result_bytes = 0; |
224 | SECStatus rv; |
225 | PRUint8 result[1024]; |
226 | |
227 | static const PRUint32 max_decimal = 0xffffffff / 10; |
228 | static const char OIDstring[] = { "OID." }; |
229 | |
230 | if (!from || !to) { |
231 | PORT_SetError(value: SEC_ERROR_INVALID_ARGS); |
232 | return SECFailure; |
233 | } |
234 | if (!len) { |
235 | len = PL_strlen(str: from); |
236 | } |
237 | if (len >= 4 && !PL_strncasecmp(a: from, b: OIDstring, max: 4)) { |
238 | from += 4; /* skip leading "OID." if present */ |
239 | len -= 4; |
240 | } |
241 | if (!len) { |
242 | bad_data: |
243 | PORT_SetError(value: SEC_ERROR_BAD_DATA); |
244 | return SECFailure; |
245 | } |
246 | do { |
247 | PRUint32 decimal = 0; |
248 | while (len > 0 && (*from >= '0' && *from <= '9')) { |
249 | PRUint32 addend = *from++ - '0'; |
250 | --len; |
251 | if (decimal > max_decimal) { /* overflow */ |
252 | goto bad_data; |
253 | } |
254 | decimal = (decimal * 10) + addend; |
255 | if (decimal < addend) { /* overflow */ |
256 | goto bad_data; |
257 | } |
258 | } |
259 | if (len != 0 && *from != '.') { |
260 | goto bad_data; |
261 | } |
262 | if (decimal_numbers == 0) { |
263 | if (decimal > 2) { |
264 | goto bad_data; |
265 | } |
266 | result[0] = decimal * 40; |
267 | result_bytes = 1; |
268 | } else if (decimal_numbers == 1) { |
269 | if (decimal > 40) { |
270 | goto bad_data; |
271 | } |
272 | result[0] += decimal; |
273 | } else { |
274 | /* encode the decimal number, */ |
275 | PRUint8 *rp; |
276 | PRUint32 num_bytes = 0; |
277 | PRUint32 tmp = decimal; |
278 | while (tmp) { |
279 | num_bytes++; |
280 | tmp >>= 7; |
281 | } |
282 | if (!num_bytes) { |
283 | ++num_bytes; /* use one byte for a zero value */ |
284 | } |
285 | if (num_bytes + result_bytes > sizeof result) { |
286 | goto bad_data; |
287 | } |
288 | tmp = num_bytes; |
289 | rp = result + result_bytes - 1; |
290 | rp[tmp] = static_cast<PRUint8>(decimal & 0x7f); |
291 | decimal >>= 7; |
292 | while (--tmp > 0) { |
293 | rp[tmp] = static_cast<PRUint8>(decimal | 0x80); |
294 | decimal >>= 7; |
295 | } |
296 | result_bytes += num_bytes; |
297 | } |
298 | ++decimal_numbers; |
299 | if (len > 0) { /* skip trailing '.' */ |
300 | ++from; |
301 | --len; |
302 | } |
303 | } while (len > 0); |
304 | /* now result contains result_bytes of data */ |
305 | if (to->data && to->len >= result_bytes) { |
306 | to->len = result_bytes; |
307 | PORT_Memcpy(dest: to->data, src: result, n: to->len); |
308 | rv = SECSuccess; |
309 | } else { |
310 | SECItem result_item = { .type: siBuffer, .data: nullptr, .len: 0 }; |
311 | result_item.data = result; |
312 | result_item.len = result_bytes; |
313 | rv = SECITEM_CopyItem(arena, to, from: &result_item); |
314 | } |
315 | return rv; |
316 | } |
317 | |
318 | static NSSCMSAttribute *my_NSS_CMSAttributeArray_FindAttrByOidTag(NSSCMSAttribute **attrs, SECOidTag oidtag, PRBool only) |
319 | { |
320 | SECOidData *oid; |
321 | NSSCMSAttribute *attr1, *attr2; |
322 | |
323 | if (attrs == nullptr) { |
324 | return nullptr; |
325 | } |
326 | |
327 | oid = SECOID_FindOIDByTag(tagnum: oidtag); |
328 | if (oid == nullptr) { |
329 | return nullptr; |
330 | } |
331 | |
332 | while ((attr1 = *attrs++) != nullptr) { |
333 | if (attr1->type.len == oid->oid.len && PORT_Memcmp(s1: attr1->type.data, s2: oid->oid.data, n: oid->oid.len) == 0) { |
334 | break; |
335 | } |
336 | } |
337 | |
338 | if (attr1 == nullptr) { |
339 | return nullptr; |
340 | } |
341 | |
342 | if (!only) { |
343 | return attr1; |
344 | } |
345 | |
346 | while ((attr2 = *attrs++) != nullptr) { |
347 | if (attr2->type.len == oid->oid.len && PORT_Memcmp(s1: attr2->type.data, s2: oid->oid.data, n: oid->oid.len) == 0) { |
348 | break; |
349 | } |
350 | } |
351 | |
352 | if (attr2 != nullptr) { |
353 | return nullptr; |
354 | } |
355 | |
356 | return attr1; |
357 | } |
358 | |
359 | static SECStatus my_NSS_CMSArray_Add(PLArenaPool *poolp, void ***array, void *obj) |
360 | { |
361 | int n = 0; |
362 | void **dest; |
363 | |
364 | PORT_Assert(array != NULL); |
365 | if (array == nullptr) { |
366 | return SECFailure; |
367 | } |
368 | |
369 | if (*array == nullptr) { |
370 | dest = static_cast<void **>(PORT_ArenaAlloc(arena: poolp, size: 2 * sizeof(void *))); |
371 | } else { |
372 | void **p = *array; |
373 | while (*p++) { |
374 | n++; |
375 | } |
376 | dest = static_cast<void **>(PORT_ArenaGrow(arena: poolp, ptr: *array, oldsize: (n + 1) * sizeof(void *), newsize: (n + 2) * sizeof(void *))); |
377 | } |
378 | |
379 | if (dest == nullptr) { |
380 | return SECFailure; |
381 | } |
382 | |
383 | dest[n] = obj; |
384 | dest[n + 1] = nullptr; |
385 | *array = dest; |
386 | return SECSuccess; |
387 | } |
388 | |
389 | static SECOidTag my_NSS_CMSAttribute_GetType(NSSCMSAttribute *attr) |
390 | { |
391 | SECOidData *typetag; |
392 | |
393 | typetag = SECOID_FindOID(oid: &(attr->type)); |
394 | if (typetag == nullptr) { |
395 | return SEC_OID_UNKNOWN; |
396 | } |
397 | |
398 | return typetag->offset; |
399 | } |
400 | |
401 | static SECStatus my_NSS_CMSAttributeArray_AddAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, NSSCMSAttribute *attr) |
402 | { |
403 | NSSCMSAttribute *oattr; |
404 | void *mark; |
405 | SECOidTag type; |
406 | |
407 | mark = PORT_ArenaMark(arena: poolp); |
408 | |
409 | /* find oidtag of attr */ |
410 | type = my_NSS_CMSAttribute_GetType(attr); |
411 | |
412 | /* see if we have one already */ |
413 | oattr = my_NSS_CMSAttributeArray_FindAttrByOidTag(attrs: *attrs, oidtag: type, PR_FALSE); |
414 | PORT_Assert(oattr == NULL); |
415 | if (oattr != nullptr) { |
416 | goto loser; /* XXX or would it be better to replace it? */ |
417 | } |
418 | |
419 | /* no, shove it in */ |
420 | if (my_NSS_CMSArray_Add(poolp, array: reinterpret_cast<void ***>(attrs), obj: static_cast<void *>(attr)) != SECSuccess) { |
421 | goto loser; |
422 | } |
423 | |
424 | PORT_ArenaUnmark(arena: poolp, mark); |
425 | return SECSuccess; |
426 | |
427 | loser: |
428 | PORT_ArenaRelease(arena: poolp, mark); |
429 | return SECFailure; |
430 | } |
431 | |
432 | static SECStatus my_NSS_CMSSignerInfo_AddAuthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr) |
433 | { |
434 | return my_NSS_CMSAttributeArray_AddAttr(poolp: signerinfo->cmsg->poolp, attrs: &(signerinfo->authAttr), attr); |
435 | } |
436 | |
437 | static SECOidTag ConvertHashAlgorithmToNss(HashAlgorithm digestAlgId) |
438 | { |
439 | switch (digestAlgId) { |
440 | case HashAlgorithm::Md2: |
441 | return SEC_OID_MD2; |
442 | case HashAlgorithm::Md5: |
443 | return SEC_OID_MD5; |
444 | case HashAlgorithm::Sha1: |
445 | return SEC_OID_SHA1; |
446 | case HashAlgorithm::Sha256: |
447 | return SEC_OID_SHA256; |
448 | case HashAlgorithm::Sha384: |
449 | return SEC_OID_SHA384; |
450 | case HashAlgorithm::Sha512: |
451 | return SEC_OID_SHA512; |
452 | case HashAlgorithm::Sha224: |
453 | return SEC_OID_SHA224; |
454 | case HashAlgorithm::Unknown: |
455 | return SEC_OID_UNKNOWN; |
456 | } |
457 | return SEC_OID_UNKNOWN; |
458 | } |
459 | |
460 | static HashAlgorithm ConvertHashTypeFromNss(HASH_HashType type) |
461 | { |
462 | switch (type) { |
463 | case HASH_AlgMD2: |
464 | return HashAlgorithm::Md2; |
465 | case HASH_AlgMD5: |
466 | return HashAlgorithm::Md5; |
467 | case HASH_AlgSHA1: |
468 | return HashAlgorithm::Sha1; |
469 | case HASH_AlgSHA256: |
470 | return HashAlgorithm::Sha256; |
471 | case HASH_AlgSHA384: |
472 | return HashAlgorithm::Sha384; |
473 | case HASH_AlgSHA512: |
474 | return HashAlgorithm::Sha512; |
475 | case HASH_AlgSHA224: |
476 | return HashAlgorithm::Sha224; |
477 | #if NSS_VMAJOR >= 3 && NSS_VMINOR >= 91 |
478 | // TODO Expose this in HashAlgorithm if PDF supports them |
479 | case HASH_AlgSHA3_224: |
480 | case HASH_AlgSHA3_256: |
481 | case HASH_AlgSHA3_384: |
482 | case HASH_AlgSHA3_512: |
483 | #endif |
484 | case HASH_AlgNULL: |
485 | case HASH_AlgTOTAL: |
486 | return HashAlgorithm::Unknown; |
487 | } |
488 | return HashAlgorithm::Unknown; |
489 | } |
490 | |
491 | static unsigned int digestLength(HashAlgorithm digestAlgId) |
492 | { |
493 | switch (digestAlgId) { |
494 | case HashAlgorithm::Sha1: |
495 | return 20; |
496 | case HashAlgorithm::Sha256: |
497 | return 32; |
498 | case HashAlgorithm::Sha384: |
499 | return 48; |
500 | case HashAlgorithm::Sha512: |
501 | return 64; |
502 | default: |
503 | printf(format: "ERROR: Unrecognized Hash ID\n" ); |
504 | return 0; |
505 | } |
506 | } |
507 | |
508 | std::string NSSSignatureVerification::getSignerName() const |
509 | { |
510 | if (!NSS_IsInitialized()) { |
511 | return {}; |
512 | } |
513 | if (!CMSSignerInfo) { |
514 | return {}; |
515 | } |
516 | |
517 | auto signing_cert = NSS_CMSSignerInfo_GetSigningCertificate(signerinfo: CMSSignerInfo, certdb: CERT_GetDefaultCertDB()); |
518 | if (!signing_cert) { |
519 | return {}; |
520 | } |
521 | |
522 | char *commonName = CERT_GetCommonName(name: &signing_cert->subject); |
523 | if (!commonName) { |
524 | return {}; |
525 | } |
526 | std::string name(commonName); |
527 | PORT_Free(ptr: commonName); |
528 | |
529 | return name; |
530 | } |
531 | |
532 | std::string NSSSignatureVerification::getSignerSubjectDN() const |
533 | { |
534 | if (!CMSSignerInfo) { |
535 | return {}; |
536 | } |
537 | auto signing_cert = NSS_CMSSignerInfo_GetSigningCertificate(signerinfo: CMSSignerInfo, certdb: CERT_GetDefaultCertDB()); |
538 | if (!signing_cert) { |
539 | return {}; |
540 | } |
541 | return std::string { signing_cert->subjectName }; |
542 | } |
543 | |
544 | std::chrono::system_clock::time_point NSSSignatureVerification::getSigningTime() const |
545 | { |
546 | if (!CMSSignerInfo) { |
547 | return {}; |
548 | } |
549 | PRTime sTime; // time in microseconds since the epoch |
550 | |
551 | if (NSS_CMSSignerInfo_GetSigningTime(sinfo: CMSSignerInfo, stime: &sTime) != SECSuccess) { |
552 | return {}; |
553 | } |
554 | |
555 | return std::chrono::system_clock::from_time_t(t: static_cast<time_t>(sTime / 1000000)); |
556 | } |
557 | |
558 | static X509CertificateInfo::EntityInfo getEntityInfo(CERTName *entityName) |
559 | { |
560 | X509CertificateInfo::EntityInfo info; |
561 | |
562 | if (!entityName) { |
563 | return info; |
564 | } |
565 | |
566 | char *dn = CERT_NameToAscii(name: entityName); |
567 | if (dn) { |
568 | info.distinguishedName = dn; |
569 | PORT_Free(ptr: dn); |
570 | } |
571 | |
572 | char *cn = CERT_GetCommonName(name: entityName); |
573 | if (cn) { |
574 | info.commonName = cn; |
575 | PORT_Free(ptr: cn); |
576 | } |
577 | |
578 | char *email = CERT_GetCertEmailAddress(name: entityName); |
579 | if (email) { |
580 | info.email = email; |
581 | PORT_Free(ptr: email); |
582 | } |
583 | |
584 | char *org = CERT_GetOrgName(name: entityName); |
585 | if (org) { |
586 | info.organization = org; |
587 | PORT_Free(ptr: org); |
588 | } |
589 | |
590 | return info; |
591 | } |
592 | |
593 | static GooString SECItemToGooString(const SECItem &secItem) |
594 | { |
595 | // TODO do we need to handle secItem.type; |
596 | return GooString((const char *)secItem.data, secItem.len); |
597 | } |
598 | |
599 | static std::unique_ptr<X509CertificateInfo> getCertificateInfoFromCERT(CERTCertificate *cert) |
600 | { |
601 | auto certInfo = std::make_unique<X509CertificateInfo>(); |
602 | |
603 | certInfo->setVersion(DER_GetInteger(src: &cert->version) + 1); |
604 | certInfo->setSerialNumber(SECItemToGooString(secItem: cert->serialNumber)); |
605 | |
606 | // issuer info |
607 | certInfo->setIssuerInfo(getEntityInfo(entityName: &cert->issuer)); |
608 | |
609 | // validity |
610 | PRTime notBefore, notAfter; |
611 | CERT_GetCertTimes(c: cert, notBefore: ¬Before, notAfter: ¬After); |
612 | X509CertificateInfo::Validity certValidity; |
613 | certValidity.notBefore = static_cast<time_t>(notBefore / 1000000); |
614 | certValidity.notAfter = static_cast<time_t>(notAfter / 1000000); |
615 | certInfo->setValidity(certValidity); |
616 | |
617 | // subject info |
618 | certInfo->setSubjectInfo(getEntityInfo(entityName: &cert->subject)); |
619 | |
620 | // nickname (as a handle to refer to the CERT later) |
621 | certInfo->setNickName(GooString(cert->dbnickname)); |
622 | |
623 | // public key info |
624 | X509CertificateInfo::PublicKeyInfo pkInfo; |
625 | SECKEYPublicKey *pk = CERT_ExtractPublicKey(cert); |
626 | if (pk) { |
627 | switch (pk->keyType) { |
628 | case rsaKey: |
629 | pkInfo.publicKey = SECItemToGooString(secItem: pk->u.rsa.modulus); |
630 | pkInfo.publicKeyType = RSAKEY; |
631 | break; |
632 | case dsaKey: |
633 | pkInfo.publicKey = SECItemToGooString(secItem: pk->u.dsa.publicValue); |
634 | pkInfo.publicKeyType = DSAKEY; |
635 | break; |
636 | case ecKey: |
637 | pkInfo.publicKey = SECItemToGooString(secItem: pk->u.ec.publicValue); |
638 | pkInfo.publicKeyType = ECKEY; |
639 | break; |
640 | default: |
641 | pkInfo.publicKey = SECItemToGooString(secItem: cert->subjectPublicKeyInfo.subjectPublicKey); |
642 | pkInfo.publicKeyType = OTHERKEY; |
643 | break; |
644 | } |
645 | pkInfo.publicKeyStrength = SECKEY_PublicKeyStrengthInBits(pubk: pk); |
646 | SECKEY_DestroyPublicKey(key: pk); |
647 | } else { |
648 | pkInfo.publicKey = SECItemToGooString(secItem: cert->subjectPublicKeyInfo.subjectPublicKey); |
649 | pkInfo.publicKeyType = OTHERKEY; |
650 | } |
651 | certInfo->setPublicKeyInfo(std::move(pkInfo)); |
652 | |
653 | certInfo->setKeyUsageExtensions(cert->keyUsage); |
654 | certInfo->setCertificateDER(SECItemToGooString(secItem: cert->derCert)); |
655 | certInfo->setIsSelfSigned(CERT_CompareName(a: &cert->subject, b: &cert->issuer) == SECEqual); |
656 | |
657 | return certInfo; |
658 | } |
659 | |
660 | std::unique_ptr<X509CertificateInfo> NSSSignatureVerification::getCertificateInfo() const |
661 | { |
662 | if (!CMSSignerInfo) { |
663 | return nullptr; |
664 | } |
665 | CERTCertificate *cert = NSS_CMSSignerInfo_GetSigningCertificate(signerinfo: CMSSignerInfo, certdb: CERT_GetDefaultCertDB()); |
666 | if (!cert) { |
667 | return nullptr; |
668 | } |
669 | return getCertificateInfoFromCERT(cert); |
670 | } |
671 | |
672 | std::unique_ptr<X509CertificateInfo> NSSSignatureCreation::getCertificateInfo() const |
673 | { |
674 | if (!signing_cert) { |
675 | return nullptr; |
676 | } |
677 | return getCertificateInfoFromCERT(cert: signing_cert); |
678 | } |
679 | |
680 | static std::optional<std::string> getDefaultFirefoxCertDB() |
681 | { |
682 | #ifdef _WIN32 |
683 | const char *env = getenv("APPDATA" ); |
684 | if (!env) { |
685 | return {}; |
686 | } |
687 | const std::string firefoxPath = std::string(env) + "/Mozilla/Firefox/Profiles/" ; |
688 | #else |
689 | const char *env = getenv(name: "HOME" ); |
690 | if (!env) { |
691 | return {}; |
692 | } |
693 | const std::string firefoxPath = std::string(env) + "/.mozilla/firefox/" ; |
694 | #endif |
695 | |
696 | GDir firefoxDir(firefoxPath.c_str()); |
697 | std::unique_ptr<GDirEntry> entry; |
698 | while (entry = firefoxDir.getNextEntry(), entry != nullptr) { |
699 | if (entry->isDir() && entry->getName()->toStr().find(s: "default" ) != std::string::npos) { |
700 | return entry->getFullPath()->toStr(); |
701 | } |
702 | } |
703 | return {}; |
704 | } |
705 | |
706 | std::string NSSSignatureConfiguration::sNssDir; |
707 | |
708 | /** |
709 | * Initialise NSS |
710 | */ |
711 | void NSSSignatureConfiguration::setNSSDir(const GooString &nssDir) |
712 | { |
713 | static bool setNssDirCalled = false; |
714 | |
715 | if (NSS_IsInitialized() && nssDir.getLength() > 0) { |
716 | error(category: errInternal, pos: 0, msg: "You need to call setNSSDir before signature validation related operations happen" ); |
717 | return; |
718 | } |
719 | |
720 | if (setNssDirCalled) { |
721 | return; |
722 | } |
723 | |
724 | setNssDirCalled = true; |
725 | |
726 | atexit(func: shutdownNss); |
727 | |
728 | bool initSuccess = false; |
729 | if (nssDir.getLength() > 0) { |
730 | initSuccess = (NSS_Init(configdir: nssDir.c_str()) == SECSuccess); |
731 | sNssDir = nssDir.toStr(); |
732 | } else { |
733 | const std::optional<std::string> certDBPath = getDefaultFirefoxCertDB(); |
734 | if (!certDBPath) { |
735 | initSuccess = (NSS_Init(configdir: "sql:/etc/pki/nssdb" ) == SECSuccess); |
736 | sNssDir = "sql:/etc/pki/nssdb" ; |
737 | } else { |
738 | initSuccess = (NSS_Init(configdir: certDBPath->c_str()) == SECSuccess); |
739 | sNssDir = *certDBPath; |
740 | } |
741 | if (!initSuccess) { |
742 | GooString homeNssDb(getenv(name: "HOME" )); |
743 | homeNssDb.append(str: "/.pki/nssdb" ); |
744 | initSuccess = (NSS_Init(configdir: homeNssDb.c_str()) == SECSuccess); |
745 | sNssDir = homeNssDb.toStr(); |
746 | } |
747 | } |
748 | |
749 | if (initSuccess) { |
750 | // Make sure NSS root certificates module is loaded |
751 | SECMOD_AddNewModule(moduleName: "Root Certs" , dllPath: "libnssckbi.so" , defaultMechanismFlags: 0, cipherEnableFlags: 0); |
752 | } else { |
753 | fprintf(stderr, format: "NSS_Init failed: %s\n" , PR_ErrorToString(code: PORT_GetError(), PR_LANGUAGE_I_DEFAULT)); |
754 | NSS_NoDB_Init(configdir: nullptr); |
755 | } |
756 | } |
757 | |
758 | std::string NSSSignatureConfiguration::getNSSDir() |
759 | { |
760 | return sNssDir; |
761 | } |
762 | |
763 | static std::function<char *(const char *)> PasswordFunction; |
764 | |
765 | void NSSSignatureConfiguration::setNSSPasswordCallback(const std::function<char *(const char *)> &f) |
766 | { |
767 | PasswordFunction = f; |
768 | } |
769 | |
770 | NSSSignatureVerification::NSSSignatureVerification(std::vector<unsigned char> &&p7data) : p7(std::move(p7data)), CMSMessage(nullptr), CMSSignedData(nullptr), CMSSignerInfo(nullptr) |
771 | { |
772 | NSSSignatureConfiguration::setNSSDir({}); |
773 | CMSitem.data = p7.data(); |
774 | CMSitem.len = p7.size(); |
775 | CMSMessage = CMS_MessageCreate(cms_item: &CMSitem); |
776 | CMSSignedData = CMS_SignedDataCreate(cms_msg: CMSMessage); |
777 | if (CMSSignedData) { |
778 | CMSSignerInfo = CMS_SignerInfoCreate(cms_sig_data: CMSSignedData); |
779 | SECAlgorithmID **algs = NSS_CMSSignedData_GetDigestAlgs(sigd: CMSSignedData); |
780 | while (*algs != nullptr) { |
781 | SECItem usedAlgorithm = (*algs)->algorithm; |
782 | auto hashAlgorithm = SECOID_FindOIDTag(oid: &usedAlgorithm); |
783 | HASH_HashType hashType = HASH_GetHashTypeByOidTag(hashOid: hashAlgorithm); |
784 | hashContext = HashContext::create(algorithm: ConvertHashTypeFromNss(type: hashType)); |
785 | |
786 | if (hashContext) { |
787 | break; |
788 | } |
789 | ++algs; |
790 | } |
791 | } |
792 | } |
793 | |
794 | NSSSignatureCreation::NSSSignatureCreation(const std::string &certNickname, HashAlgorithm digestAlgTag) : hashContext(HashContext::create(algorithm: digestAlgTag)), signing_cert(nullptr) |
795 | { |
796 | NSSSignatureConfiguration::setNSSDir({}); |
797 | signing_cert = CERT_FindCertByNickname(handle: CERT_GetDefaultCertDB(), nickname: certNickname.c_str()); |
798 | } |
799 | |
800 | HashAlgorithm NSSSignatureVerification::getHashAlgorithm() const |
801 | { |
802 | if (hashContext) { |
803 | return hashContext->getHashAlgorithm(); |
804 | } else { |
805 | return HashAlgorithm::Unknown; |
806 | } |
807 | } |
808 | |
809 | void NSSSignatureVerification::addData(unsigned char *data_block, int data_len) |
810 | { |
811 | if (hashContext) { |
812 | hashContext->updateHash(data_block, data_len); |
813 | } |
814 | } |
815 | |
816 | void NSSSignatureCreation::addData(unsigned char *data_block, int data_len) |
817 | { |
818 | hashContext->updateHash(data_block, data_len); |
819 | } |
820 | |
821 | NSSSignatureCreation::~NSSSignatureCreation() |
822 | { |
823 | if (signing_cert) { |
824 | CERT_DestroyCertificate(cert: signing_cert); |
825 | } |
826 | } |
827 | NSSSignatureVerification::~NSSSignatureVerification() |
828 | { |
829 | if (CMSMessage) { |
830 | // in the CMS_SignedDataCreate, we malloc some memory |
831 | // inside the CMSSignedData structure |
832 | // which is otherwise destructed by NSS_CMSMessage_Destroy |
833 | // but given we did the malloc ourselves |
834 | // we also need to free it ourselves. |
835 | // After we free the surrounding memory but we need |
836 | // a handle to it before. |
837 | CERTCertificate **toFree = nullptr; |
838 | if (CMSSignedData) { |
839 | toFree = CMSSignedData->tempCerts; |
840 | } |
841 | NSS_CMSMessage_Destroy(cmsg: CMSMessage); |
842 | free(ptr: toFree); |
843 | } |
844 | } |
845 | |
846 | static NSSCMSMessage *CMS_MessageCreate(SECItem *cms_item) |
847 | { |
848 | if (cms_item->data) { |
849 | return NSS_CMSMessage_CreateFromDER(DERmessage: cms_item, cb: nullptr, cb_arg: nullptr /* Content callback */ |
850 | , |
851 | pwfn: nullptr, pwfn_arg: nullptr /*Password callback*/ |
852 | , |
853 | decrypt_key_cb: nullptr, decrypt_key_cb_arg: nullptr /*Decrypt callback*/); |
854 | } else { |
855 | return nullptr; |
856 | } |
857 | } |
858 | |
859 | static NSSCMSSignedData *CMS_SignedDataCreate(NSSCMSMessage *cms_msg) |
860 | { |
861 | if (!NSS_CMSMessage_IsSigned(cmsg: cms_msg)) { |
862 | error(category: errInternal, pos: 0, msg: "Input couldn't be parsed as a CMS signature" ); |
863 | return nullptr; |
864 | } |
865 | |
866 | NSSCMSContentInfo *cinfo = NSS_CMSMessage_ContentLevel(cmsg: cms_msg, n: 0); |
867 | if (!cinfo) { |
868 | error(category: errInternal, pos: 0, msg: "Error in NSS_CMSMessage_ContentLevel" ); |
869 | return nullptr; |
870 | } |
871 | |
872 | NSSCMSSignedData *signedData = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent(cinfo); |
873 | if (!signedData) { |
874 | error(category: errInternal, pos: 0, msg: "CError in NSS_CMSContentInfo_GetContent()" ); |
875 | return nullptr; |
876 | } |
877 | |
878 | if (signedData->rawCerts) { |
879 | size_t i; |
880 | for (i = 0; signedData->rawCerts[i]; ++i) { } // just count the length of the certificate chain |
881 | |
882 | // tempCerts field needs to be filled for complete memory release by NSSCMSSignedData_Destroy |
883 | signedData->tempCerts = (CERTCertificate **)gmallocn(count: i + 1, size: sizeof(CERTCertificate *)); |
884 | memset(s: signedData->tempCerts, c: 0, n: (i + 1) * sizeof(CERTCertificate *)); |
885 | // store the addresses of these temporary certificates for future release |
886 | for (i = 0; signedData->rawCerts[i]; ++i) { |
887 | signedData->tempCerts[i] = CERT_NewTempCertificate(handle: CERT_GetDefaultCertDB(), derCert: signedData->rawCerts[i], nickname: nullptr, isperm: 0, copyDER: 0); |
888 | } |
889 | return signedData; |
890 | } else { |
891 | return nullptr; |
892 | } |
893 | } |
894 | |
895 | static NSSCMSSignerInfo *CMS_SignerInfoCreate(NSSCMSSignedData *cms_sig_data) |
896 | { |
897 | NSSCMSSignerInfo *signerInfo = NSS_CMSSignedData_GetSignerInfo(sigd: cms_sig_data, i: 0); |
898 | if (!signerInfo) { |
899 | printf(format: "Error in NSS_CMSSignedData_GetSignerInfo()\n" ); |
900 | return nullptr; |
901 | } else { |
902 | return signerInfo; |
903 | } |
904 | } |
905 | |
906 | static SignatureValidationStatus NSS_SigTranslate(NSSCMSVerificationStatus nss_code) |
907 | { |
908 | switch (nss_code) { |
909 | case NSSCMSVS_GoodSignature: |
910 | return SIGNATURE_VALID; |
911 | |
912 | case NSSCMSVS_BadSignature: |
913 | return SIGNATURE_INVALID; |
914 | |
915 | case NSSCMSVS_DigestMismatch: |
916 | return SIGNATURE_DIGEST_MISMATCH; |
917 | |
918 | case NSSCMSVS_ProcessingError: |
919 | return SIGNATURE_DECODING_ERROR; |
920 | |
921 | default: |
922 | return SIGNATURE_GENERIC_ERROR; |
923 | } |
924 | } |
925 | |
926 | SignatureValidationStatus NSSSignatureVerification::validateSignature() |
927 | { |
928 | if (!CMSSignedData) { |
929 | return SIGNATURE_GENERIC_ERROR; |
930 | } |
931 | |
932 | if (!NSS_IsInitialized()) { |
933 | return SIGNATURE_GENERIC_ERROR; |
934 | } |
935 | |
936 | if (!hashContext) { |
937 | return SIGNATURE_GENERIC_ERROR; |
938 | } |
939 | |
940 | std::vector<unsigned char> digest_buffer = hashContext->endHash(); |
941 | |
942 | SECItem digest; |
943 | digest.data = digest_buffer.data(); |
944 | digest.len = digest_buffer.size(); |
945 | |
946 | if ((NSS_CMSSignerInfo_GetSigningCertificate(signerinfo: CMSSignerInfo, certdb: CERT_GetDefaultCertDB())) == nullptr) { |
947 | CMSSignerInfo->verificationStatus = NSSCMSVS_SigningCertNotFound; |
948 | } |
949 | |
950 | SECItem *content_info_data = CMSSignedData->contentInfo.content.data; |
951 | if (content_info_data != nullptr && content_info_data->data != nullptr) { |
952 | /* |
953 | This means it's not a detached type signature |
954 | so the digest is contained in SignedData->contentInfo |
955 | */ |
956 | if (digest.len == content_info_data->len && memcmp(s1: digest.data, s2: content_info_data->data, n: digest.len) == 0) { |
957 | return SIGNATURE_VALID; |
958 | } else { |
959 | return SIGNATURE_DIGEST_MISMATCH; |
960 | } |
961 | |
962 | } else if (NSS_CMSSignerInfo_Verify(signerinfo: CMSSignerInfo, digest: &digest, contentType: nullptr) != SECSuccess) { |
963 | return NSS_SigTranslate(nss_code: CMSSignerInfo->verificationStatus); |
964 | } else { |
965 | return SIGNATURE_VALID; |
966 | } |
967 | } |
968 | |
969 | void NSSSignatureVerification::validateCertificateAsync(std::chrono::system_clock::time_point validation_time, bool ocspRevocationCheck, bool useAIACertFetch, const std::function<void()> &doneCallback) |
970 | { |
971 | cachedValidationStatus.reset(); |
972 | CERTCertificate *cert; |
973 | |
974 | if (!CMSSignerInfo) { |
975 | validationStatus = std::async(fn: [doneCallback]() { |
976 | if (doneCallback) { |
977 | doneCallback(); |
978 | } |
979 | return CERTIFICATE_GENERIC_ERROR; |
980 | }); |
981 | return; |
982 | } |
983 | |
984 | if ((cert = NSS_CMSSignerInfo_GetSigningCertificate(signerinfo: CMSSignerInfo, certdb: CERT_GetDefaultCertDB())) == nullptr) { |
985 | CMSSignerInfo->verificationStatus = NSSCMSVS_SigningCertNotFound; |
986 | } |
987 | |
988 | PRTime vTime = 0; // time in microseconds since the epoch, special value 0 means now |
989 | if (validation_time > std::chrono::system_clock::time_point {}) { |
990 | vTime = 1000000 * (PRTime)std::chrono::system_clock::to_time_t(t: validation_time); |
991 | } |
992 | CERTValInParam inParams[4]; |
993 | inParams[0].type = cert_pi_revocationFlags; |
994 | if (ocspRevocationCheck) { |
995 | inParams[0].value.pointer.revocation = CERT_GetClassicOCSPEnabledSoftFailurePolicy(); |
996 | } else { |
997 | inParams[0].value.pointer.revocation = CERT_GetClassicOCSPDisabledPolicy(); |
998 | } |
999 | inParams[1].type = cert_pi_date; |
1000 | inParams[1].value.scalar.time = vTime; |
1001 | if (useAIACertFetch) { |
1002 | inParams[2].type = cert_pi_useAIACertFetch; |
1003 | inParams[2].value.scalar.b = PR_TRUE; |
1004 | inParams[3].type = cert_pi_end; |
1005 | } else { |
1006 | inParams[2].type = cert_pi_end; |
1007 | } |
1008 | |
1009 | CERT_PKIXVerifyCert(cert, certificateUsageEmailSigner, paramsIn: inParams, paramsOut: nullptr, wincx: CMSSignerInfo->cmsg->pwfn_arg); |
1010 | |
1011 | // Here we are just faking the asynchronousness. It should |
1012 | // somehow be the call to CERT_PXIXVerifyCert that would |
1013 | // be put in the thread, but I'm not sure about all of the |
1014 | // thread safety of nss. |
1015 | |
1016 | validationStatus = std::async(fn: [result = PORT_GetError(), doneCallback]() { |
1017 | if (doneCallback) { |
1018 | doneCallback(); |
1019 | } |
1020 | |
1021 | switch (result) { |
1022 | // 0 not defined in SECErrorCodes, it means success for this purpose. |
1023 | case 0: |
1024 | return CERTIFICATE_TRUSTED; |
1025 | |
1026 | case SEC_ERROR_UNKNOWN_ISSUER: |
1027 | return CERTIFICATE_UNKNOWN_ISSUER; |
1028 | |
1029 | case SEC_ERROR_UNTRUSTED_ISSUER: |
1030 | return CERTIFICATE_UNTRUSTED_ISSUER; |
1031 | |
1032 | case SEC_ERROR_REVOKED_CERTIFICATE: |
1033 | return CERTIFICATE_REVOKED; |
1034 | |
1035 | case SEC_ERROR_EXPIRED_CERTIFICATE: |
1036 | return CERTIFICATE_EXPIRED; |
1037 | } |
1038 | |
1039 | return CERTIFICATE_GENERIC_ERROR; |
1040 | }); |
1041 | } |
1042 | |
1043 | CertificateValidationStatus NSSSignatureVerification::validateCertificateResult() |
1044 | { |
1045 | if (cachedValidationStatus) { |
1046 | return cachedValidationStatus.value(); |
1047 | } |
1048 | if (!validationStatus.valid()) { |
1049 | return CERTIFICATE_NOT_VERIFIED; |
1050 | } |
1051 | validationStatus.wait(); |
1052 | cachedValidationStatus = validationStatus.get(); |
1053 | return cachedValidationStatus.value(); |
1054 | } |
1055 | |
1056 | std::optional<GooString> NSSSignatureCreation::signDetached(const std::string &password) |
1057 | { |
1058 | if (!hashContext) { |
1059 | return {}; |
1060 | } |
1061 | std::vector<unsigned char> digest_buffer = hashContext->endHash(); |
1062 | SECItem digest; |
1063 | digest.data = digest_buffer.data(); |
1064 | digest.len = digest_buffer.size(); |
1065 | |
1066 | ///////////////////////////////////// |
1067 | /// Code from LibreOffice under MPLv2 |
1068 | ///////////////////////////////////// |
1069 | struct NSSCMSMessageDestroyer |
1070 | { |
1071 | void operator()(NSSCMSMessage *message) { NSS_CMSMessage_Destroy(cmsg: message); } |
1072 | }; |
1073 | std::unique_ptr<NSSCMSMessage, NSSCMSMessageDestroyer> cms_msg { NSS_CMSMessage_Create(poolp: nullptr) }; |
1074 | if (!cms_msg) { |
1075 | return {}; |
1076 | } |
1077 | |
1078 | NSSCMSSignedData *cms_sd = NSS_CMSSignedData_Create(cmsg: cms_msg.get()); |
1079 | if (!cms_sd) { |
1080 | return {}; |
1081 | } |
1082 | |
1083 | NSSCMSContentInfo *cms_cinfo = NSS_CMSMessage_GetContentInfo(cmsg: cms_msg.get()); |
1084 | |
1085 | if (NSS_CMSContentInfo_SetContent_SignedData(cmsg: cms_msg.get(), cinfo: cms_cinfo, sigd: cms_sd) != SECSuccess) { |
1086 | return {}; |
1087 | } |
1088 | |
1089 | cms_cinfo = NSS_CMSSignedData_GetContentInfo(sigd: cms_sd); |
1090 | |
1091 | // Attach NULL data as detached data |
1092 | if (NSS_CMSContentInfo_SetContent_Data(cmsg: cms_msg.get(), cinfo: cms_cinfo, data: nullptr, PR_TRUE) != SECSuccess) { |
1093 | return {}; |
1094 | } |
1095 | |
1096 | // hardcode SHA256 these days... |
1097 | NSSCMSSignerInfo *cms_signer = NSS_CMSSignerInfo_Create(cmsg: cms_msg.get(), cert: signing_cert, digestalgtag: SEC_OID_SHA256); |
1098 | if (!cms_signer) { |
1099 | return {}; |
1100 | } |
1101 | |
1102 | if (NSS_CMSSignerInfo_IncludeCerts(signerinfo: cms_signer, cm: NSSCMSCM_CertChain, usage: certUsageEmailSigner) != SECSuccess) { |
1103 | return {}; |
1104 | } |
1105 | |
1106 | if (NSS_CMSSignedData_AddCertificate(sigd: cms_sd, cert: signing_cert) != SECSuccess) { |
1107 | return {}; |
1108 | } |
1109 | |
1110 | if (NSS_CMSSignedData_AddSignerInfo(sigd: cms_sd, signerinfo: cms_signer) != SECSuccess) { |
1111 | return {}; |
1112 | } |
1113 | |
1114 | if (NSS_CMSSignedData_SetDigestValue(sigd: cms_sd, digestalgtag: SEC_OID_SHA256, digestdata: &digest) != SECSuccess) { |
1115 | return {}; |
1116 | } |
1117 | |
1118 | struct PLArenaFreeFalse |
1119 | { |
1120 | void operator()(PLArenaPool *arena) { PORT_FreeArena(arena, PR_FALSE); } |
1121 | }; |
1122 | std::unique_ptr<PLArenaPool, PLArenaFreeFalse> arena { PORT_NewArena(chunksize: CryptoSign::maxSupportedSignatureSize) }; |
1123 | |
1124 | // Add the signing certificate as a signed attribute. |
1125 | ESSCertIDv2 *aCertIDs[2]; |
1126 | ESSCertIDv2 aCertID; |
1127 | // Write ESSCertIDv2.hashAlgorithm. |
1128 | aCertID.hashAlgorithm.algorithm.data = nullptr; |
1129 | aCertID.hashAlgorithm.parameters.data = nullptr; |
1130 | SECOID_SetAlgorithmID(arena: arena.get(), aid: &aCertID.hashAlgorithm, tag: SEC_OID_SHA256, params: nullptr); |
1131 | |
1132 | // Write ESSCertIDv2.certHash. |
1133 | SECItem aCertHashItem; |
1134 | unsigned char certhash[32]; |
1135 | SECStatus rv = PK11_HashBuf(hashAlg: SEC_OID_SHA256, out: certhash, in: signing_cert->derCert.data, len: signing_cert->derCert.len); |
1136 | if (rv != SECSuccess) { |
1137 | return {}; |
1138 | } |
1139 | |
1140 | aCertHashItem.type = siBuffer; |
1141 | aCertHashItem.data = certhash; |
1142 | aCertHashItem.len = 32; |
1143 | aCertID.certHash = aCertHashItem; |
1144 | |
1145 | // Write ESSCertIDv2.issuerSerial. |
1146 | IssuerSerial aSerial; |
1147 | GeneralName aName; |
1148 | aName.name = signing_cert->issuer; |
1149 | aSerial.issuer.names = aName; |
1150 | aSerial.serialNumber = signing_cert->serialNumber; |
1151 | aCertID.issuerSerial = aSerial; |
1152 | // Write SigningCertificateV2.certs. |
1153 | aCertIDs[0] = &aCertID; |
1154 | aCertIDs[1] = nullptr; |
1155 | SigningCertificateV2 aCertificate; |
1156 | aCertificate.certs = &aCertIDs[0]; |
1157 | |
1158 | SECItem *pEncodedCertificate = SEC_ASN1EncodeItem(pool: nullptr, dest: nullptr, src: &aCertificate, t: SigningCertificateV2Template); |
1159 | if (!pEncodedCertificate) { |
1160 | return {}; |
1161 | } |
1162 | |
1163 | NSSCMSAttribute aAttribute; |
1164 | SECItem aAttributeValues[2]; |
1165 | SECItem *pAttributeValues[2]; |
1166 | pAttributeValues[0] = aAttributeValues; |
1167 | pAttributeValues[1] = nullptr; |
1168 | aAttributeValues[0] = *pEncodedCertificate; |
1169 | aAttributeValues[1].type = siBuffer; |
1170 | aAttributeValues[1].data = nullptr; |
1171 | aAttributeValues[1].len = 0; |
1172 | aAttribute.values = pAttributeValues; |
1173 | |
1174 | SECOidData aOidData; |
1175 | aOidData.oid.data = nullptr; |
1176 | /* |
1177 | * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::= |
1178 | * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) |
1179 | * smime(16) id-aa(2) 47 } |
1180 | */ |
1181 | if (my_SEC_StringToOID(arena: arena.get(), to: &aOidData.oid, from: "1.2.840.113549.1.9.16.2.47" , len: 0) != SECSuccess) { |
1182 | return {}; |
1183 | } |
1184 | |
1185 | aOidData.offset = SEC_OID_UNKNOWN; |
1186 | aOidData.desc = "id-aa-signingCertificateV2" ; |
1187 | aOidData.mechanism = CKM_SHA_1; |
1188 | aOidData.supportedExtension = UNSUPPORTED_CERT_EXTENSION; |
1189 | aAttribute.typeTag = &aOidData; |
1190 | aAttribute.type = aOidData.oid; |
1191 | aAttribute.encoded = PR_TRUE; |
1192 | |
1193 | if (my_NSS_CMSSignerInfo_AddAuthAttr(signerinfo: cms_signer, attr: &aAttribute) != SECSuccess) { |
1194 | return {}; |
1195 | } |
1196 | |
1197 | SECItem cms_output; |
1198 | cms_output.data = nullptr; |
1199 | cms_output.len = 0; |
1200 | |
1201 | NSSCMSEncoderContext *cms_ecx = NSS_CMSEncoder_Start(cmsg: cms_msg.get(), outputfn: nullptr, outputarg: nullptr, dest: &cms_output, destpoolp: arena.get(), pwfn: passwordCallback, pwfn_arg: password.empty() ? nullptr : const_cast<char *>(password.c_str()), decrypt_key_cb: nullptr, decrypt_key_cb_arg: nullptr, detached_digestalgs: nullptr, detached_digests: nullptr); |
1202 | if (!cms_ecx) { |
1203 | return {}; |
1204 | } |
1205 | |
1206 | if (NSS_CMSEncoder_Finish(p7ecx: cms_ecx) != SECSuccess) { |
1207 | return {}; |
1208 | } |
1209 | |
1210 | auto signature = GooString(reinterpret_cast<const char *>(cms_output.data), cms_output.len); |
1211 | |
1212 | SECITEM_FreeItem(zap: pEncodedCertificate, PR_TRUE); |
1213 | |
1214 | return signature; |
1215 | } |
1216 | |
1217 | static char *GetPasswordFunction(PK11SlotInfo *slot, PRBool /*retry*/, void * /*arg*/) |
1218 | { |
1219 | const char *name = PK11_GetTokenName(slot); |
1220 | if (PasswordFunction) { |
1221 | return PasswordFunction(name); |
1222 | } |
1223 | return nullptr; |
1224 | } |
1225 | |
1226 | std::unique_ptr<CryptoSign::VerificationInterface> NSSCryptoSignBackend::createVerificationHandler(std::vector<unsigned char> &&pkcs7) |
1227 | { |
1228 | return std::make_unique<NSSSignatureVerification>(args: std::move(pkcs7)); |
1229 | } |
1230 | |
1231 | std::unique_ptr<CryptoSign::SigningInterface> NSSCryptoSignBackend::createSigningHandler(const std::string &certID, HashAlgorithm digestAlgTag) |
1232 | { |
1233 | return std::make_unique<NSSSignatureCreation>(args: certID, args&: digestAlgTag); |
1234 | } |
1235 | |
1236 | std::vector<std::unique_ptr<X509CertificateInfo>> NSSCryptoSignBackend::getAvailableSigningCertificates() |
1237 | { |
1238 | // set callback, in case one of the slots has a password set |
1239 | PK11_SetPasswordFunc(func: GetPasswordFunction); |
1240 | NSSSignatureConfiguration::setNSSDir({}); |
1241 | |
1242 | std::vector<std::unique_ptr<X509CertificateInfo>> certsList; |
1243 | PK11SlotList *slotList = PK11_GetAllTokens(CKM_INVALID_MECHANISM, PR_FALSE, PR_FALSE, wincx: nullptr); |
1244 | if (slotList) { |
1245 | for (PK11SlotListElement *slotElement = slotList->head; slotElement; slotElement = slotElement->next) { |
1246 | PK11SlotInfo *pSlot = slotElement->slot; |
1247 | if (PK11_NeedLogin(slot: pSlot)) { |
1248 | SECStatus nRet = PK11_Authenticate(slot: pSlot, PR_TRUE, wincx: nullptr); |
1249 | // PK11_Authenticate may fail in case the a slot has not been initialized. |
1250 | // this is the case if the user has a new profile, so that they have never |
1251 | // added a personal certificate. |
1252 | if (nRet != SECSuccess && PORT_GetError() != SEC_ERROR_IO) { |
1253 | continue; |
1254 | } |
1255 | } |
1256 | |
1257 | SECKEYPrivateKeyList *privKeyList = PK11_ListPrivateKeysInSlot(slot: pSlot); |
1258 | if (privKeyList) { |
1259 | for (SECKEYPrivateKeyListNode *curPri = PRIVKEY_LIST_HEAD(privKeyList); !PRIVKEY_LIST_END(curPri, privKeyList) && curPri != nullptr; curPri = PRIVKEY_LIST_NEXT(curPri)) { |
1260 | if (curPri->key) { |
1261 | CERTCertificate *cert = PK11_GetCertFromPrivateKey(privKey: curPri->key); |
1262 | if (cert) { |
1263 | certsList.push_back(x: getCertificateInfoFromCERT(cert)); |
1264 | CERT_DestroyCertificate(cert); |
1265 | } |
1266 | } |
1267 | } |
1268 | SECKEY_DestroyPrivateKeyList(keys: privKeyList); |
1269 | } |
1270 | } |
1271 | PK11_FreeSlotList(list: slotList); |
1272 | } |
1273 | |
1274 | PK11_SetPasswordFunc(func: nullptr); |
1275 | |
1276 | return certsList; |
1277 | } |
1278 | |
1279 | void HashContext::updateHash(unsigned char *data_block, int data_len) |
1280 | { |
1281 | HASH_Update(context: hash_context.get(), src: data_block, len: data_len); |
1282 | } |
1283 | |
1284 | std::vector<unsigned char> HashContext::endHash() |
1285 | { |
1286 | auto hash_length = digestLength(digestAlgId: digest_alg_tag); |
1287 | std::vector<unsigned char> digestBuffer(hash_length); |
1288 | unsigned int result_length = 0; |
1289 | HASH_End(context: hash_context.get(), result: digestBuffer.data(), result_len: &result_length, max_result_len: digestBuffer.size()); |
1290 | digestBuffer.resize(new_size: result_length); |
1291 | |
1292 | return digestBuffer; |
1293 | } |
1294 | |
1295 | HashContext::HashContext(HashAlgorithm algorithm, private_tag) : hash_context { HASH_Create(type: HASH_GetHashTypeByOidTag(hashOid: ConvertHashAlgorithmToNss(digestAlgId: algorithm))) }, digest_alg_tag(algorithm) { } |
1296 | |
1297 | std::unique_ptr<HashContext> HashContext::create(HashAlgorithm algorithm) |
1298 | { |
1299 | auto ctx = std::make_unique<HashContext>(args&: algorithm, args: private_tag {}); |
1300 | if (ctx->hash_context) { |
1301 | return ctx; |
1302 | } |
1303 | return {}; |
1304 | } |
1305 | |
1306 | HashAlgorithm HashContext::getHashAlgorithm() const |
1307 | { |
1308 | return digest_alg_tag; |
1309 | } |
1310 | |
1311 | NSSCryptoSignBackend::~NSSCryptoSignBackend() = default; |
1312 | |