/src/libreoffice/svl/source/crypto/cryptosign.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | */ |
9 | | |
10 | | #include <sal/config.h> |
11 | | |
12 | | #include <algorithm> |
13 | | #include <array> |
14 | | |
15 | | #include <svl/cryptosign.hxx> |
16 | | #include <svl/sigstruct.hxx> |
17 | | #include <config_crypto.h> |
18 | | #include <o3tl/numeric.hxx> |
19 | | |
20 | | #if USE_CRYPTO_NSS |
21 | | #include <systools/curlinit.hxx> |
22 | | #endif |
23 | | |
24 | | #include <rtl/character.hxx> |
25 | | #include <rtl/strbuf.hxx> |
26 | | #include <rtl/string.hxx> |
27 | | #include <sal/log.hxx> |
28 | | #include <tools/datetime.hxx> |
29 | | #include <tools/stream.hxx> |
30 | | #include <comphelper/base64.hxx> |
31 | | #include <comphelper/hash.hxx> |
32 | | #include <comphelper/processfactory.hxx> |
33 | | #include <comphelper/random.hxx> |
34 | | #include <comphelper/scopeguard.hxx> |
35 | | #include <comphelper/lok.hxx> |
36 | | #include <com/sun/star/security/XCertificate.hpp> |
37 | | #include <com/sun/star/uno/Sequence.hxx> |
38 | | #include <o3tl/char16_t2wchar_t.hxx> |
39 | | |
40 | | #if USE_CRYPTO_NSS |
41 | | // NSS headers for PDF signing |
42 | | #include <cert.h> |
43 | | #include <keyhi.h> |
44 | | #include <pk11pub.h> |
45 | | #include <hasht.h> |
46 | | #include <secerr.h> |
47 | | #include <sechash.h> |
48 | | #include <cms.h> |
49 | | #include <cmst.h> |
50 | | |
51 | | // We use curl for RFC3161 time stamp requests |
52 | | #include <curl/curl.h> |
53 | | |
54 | | #include <com/sun/star/xml/crypto/DigestID.hpp> |
55 | | #include <com/sun/star/xml/crypto/NSSInitializer.hpp> |
56 | | #include <mutex> |
57 | | #endif |
58 | | |
59 | | #if USE_CRYPTO_MSCAPI |
60 | | // WinCrypt headers for PDF signing |
61 | | // Note: this uses Windows 7 APIs and requires the relevant data types |
62 | | #include <prewin.h> |
63 | | #include <wincrypt.h> |
64 | | #include <postwin.h> |
65 | | #include <comphelper/windowserrorstring.hxx> |
66 | | #endif |
67 | | |
68 | | using namespace com::sun::star; |
69 | | |
70 | | namespace { |
71 | | #if USE_CRYPTO_NSS |
72 | | char *PDFSigningPKCS7PasswordCallback(PK11SlotInfo * /*slot*/, PRBool /*retry*/, void *arg) |
73 | | { |
74 | | return PL_strdup(static_cast<char *>(arg)); |
75 | | } |
76 | | |
77 | | // ASN.1 used in the (much simpler) time stamp request. From RFC3161 |
78 | | // and other sources. |
79 | | |
80 | | /* |
81 | | AlgorithmIdentifier ::= SEQUENCE { |
82 | | algorithm OBJECT IDENTIFIER, |
83 | | parameters ANY DEFINED BY algorithm OPTIONAL } |
84 | | -- contains a value of the type |
85 | | -- registered for use with the |
86 | | -- algorithm object identifier value |
87 | | |
88 | | MessageImprint ::= SEQUENCE { |
89 | | hashAlgorithm AlgorithmIdentifier, |
90 | | hashedMessage OCTET STRING } |
91 | | */ |
92 | | |
93 | | struct MessageImprint { |
94 | | SECAlgorithmID hashAlgorithm; |
95 | | SECItem hashedMessage; |
96 | | }; |
97 | | |
98 | | /* |
99 | | Extension ::= SEQUENCE { |
100 | | extnID OBJECT IDENTIFIER, |
101 | | critical BOOLEAN DEFAULT FALSE, |
102 | | extnValue OCTET STRING } |
103 | | */ |
104 | | |
105 | | struct Extension { |
106 | | SECItem extnID; |
107 | | SECItem critical; |
108 | | SECItem extnValue; |
109 | | }; |
110 | | |
111 | | /* |
112 | | Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension |
113 | | */ |
114 | | |
115 | | /* |
116 | | TSAPolicyId ::= OBJECT IDENTIFIER |
117 | | |
118 | | TimeStampReq ::= SEQUENCE { |
119 | | version INTEGER { v1(1) }, |
120 | | messageImprint MessageImprint, |
121 | | --a hash algorithm OID and the hash value of the data to be |
122 | | --time-stamped |
123 | | reqPolicy TSAPolicyId OPTIONAL, |
124 | | nonce INTEGER OPTIONAL, |
125 | | certReq BOOLEAN DEFAULT FALSE, |
126 | | extensions [0] IMPLICIT Extensions OPTIONAL } |
127 | | */ |
128 | | |
129 | | struct TimeStampReq { |
130 | | SECItem version; |
131 | | MessageImprint messageImprint; |
132 | | SECItem reqPolicy; |
133 | | SECItem nonce; |
134 | | SECItem certReq; |
135 | | Extension *extensions; |
136 | | }; |
137 | | |
138 | | /** |
139 | | * General name, defined by RFC 3280. |
140 | | */ |
141 | | struct GeneralName |
142 | | { |
143 | | CERTName name; |
144 | | }; |
145 | | |
146 | | /** |
147 | | * List of general names (only one for now), defined by RFC 3280. |
148 | | */ |
149 | | struct GeneralNames |
150 | | { |
151 | | GeneralName names; |
152 | | }; |
153 | | |
154 | | /** |
155 | | * Supplies different fields to identify a certificate, defined by RFC 5035. |
156 | | */ |
157 | | struct IssuerSerial |
158 | | { |
159 | | GeneralNames issuer; |
160 | | SECItem serialNumber; |
161 | | }; |
162 | | |
163 | | /** |
164 | | * Supplies different fields that are used to identify certificates, defined by |
165 | | * RFC 5035. |
166 | | */ |
167 | | struct ESSCertIDv2 |
168 | | { |
169 | | SECAlgorithmID hashAlgorithm; |
170 | | SECItem certHash; |
171 | | IssuerSerial issuerSerial; |
172 | | }; |
173 | | |
174 | | /** |
175 | | * This attribute uses the ESSCertIDv2 structure, defined by RFC 5035. |
176 | | */ |
177 | | struct SigningCertificateV2 |
178 | | { |
179 | | ESSCertIDv2** certs; |
180 | | |
181 | | SigningCertificateV2() |
182 | | : certs(nullptr) |
183 | | { |
184 | | } |
185 | | }; |
186 | | |
187 | | /** |
188 | | * GeneralName ::= CHOICE { |
189 | | * otherName [0] OtherName, |
190 | | * rfc822Name [1] IA5String, |
191 | | * dNSName [2] IA5String, |
192 | | * x400Address [3] ORAddress, |
193 | | * directoryName [4] Name, |
194 | | * ediPartyName [5] EDIPartyName, |
195 | | * uniformResourceIdentifier [6] IA5String, |
196 | | * iPAddress [7] OCTET STRING, |
197 | | * registeredID [8] OBJECT IDENTIFIER |
198 | | * } |
199 | | */ |
200 | | const SEC_ASN1Template GeneralNameTemplate[] = |
201 | | { |
202 | | {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(GeneralName)}, |
203 | | {SEC_ASN1_INLINE, offsetof(GeneralName, name), CERT_NameTemplate, 0}, |
204 | | {0, 0, nullptr, 0} |
205 | | }; |
206 | | |
207 | | /** |
208 | | * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName |
209 | | */ |
210 | | const SEC_ASN1Template GeneralNamesTemplate[] = |
211 | | { |
212 | | {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(GeneralNames)}, |
213 | | {SEC_ASN1_INLINE | SEC_ASN1_CONTEXT_SPECIFIC | 4, offsetof(GeneralNames, names), GeneralNameTemplate, 0}, |
214 | | {0, 0, nullptr, 0} |
215 | | }; |
216 | | |
217 | | /** |
218 | | * IssuerSerial ::= SEQUENCE { |
219 | | * issuer GeneralNames, |
220 | | * serialNumber CertificateSerialNumber |
221 | | * } |
222 | | */ |
223 | | const SEC_ASN1Template IssuerSerialTemplate[] = |
224 | | { |
225 | | {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(IssuerSerial)}, |
226 | | {SEC_ASN1_INLINE, offsetof(IssuerSerial, issuer), GeneralNamesTemplate, 0}, |
227 | | {SEC_ASN1_INTEGER, offsetof(IssuerSerial, serialNumber), nullptr, 0}, |
228 | | {0, 0, nullptr, 0} |
229 | | }; |
230 | | |
231 | | |
232 | | /** |
233 | | * Hash ::= OCTET STRING |
234 | | * |
235 | | * ESSCertIDv2 ::= SEQUENCE { |
236 | | * hashAlgorithm AlgorithmIdentifier DEFAULT {algorithm id-sha256}, |
237 | | * certHash Hash, |
238 | | * issuerSerial IssuerSerial OPTIONAL |
239 | | * } |
240 | | */ |
241 | | |
242 | | SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate) |
243 | | |
244 | | const SEC_ASN1Template ESSCertIDv2Template[] = |
245 | | { |
246 | | {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(ESSCertIDv2)}, |
247 | | {SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(ESSCertIDv2, hashAlgorithm), SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate), 0}, |
248 | | {SEC_ASN1_OCTET_STRING, offsetof(ESSCertIDv2, certHash), nullptr, 0}, |
249 | | {SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(ESSCertIDv2, issuerSerial), IssuerSerialTemplate, 0}, |
250 | | {0, 0, nullptr, 0} |
251 | | }; |
252 | | |
253 | | /** |
254 | | * SigningCertificateV2 ::= SEQUENCE { |
255 | | * } |
256 | | */ |
257 | | const SEC_ASN1Template SigningCertificateV2Template[] = |
258 | | { |
259 | | {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(SigningCertificateV2)}, |
260 | | {SEC_ASN1_SEQUENCE_OF, offsetof(SigningCertificateV2, certs), ESSCertIDv2Template, 0}, |
261 | | {0, 0, nullptr, 0} |
262 | | }; |
263 | | |
264 | | struct PKIStatusInfo { |
265 | | SECItem status; |
266 | | SECItem statusString; |
267 | | SECItem failInfo; |
268 | | }; |
269 | | |
270 | | const SEC_ASN1Template PKIStatusInfo_Template[] = |
271 | | { |
272 | | { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(PKIStatusInfo) }, |
273 | | { SEC_ASN1_INTEGER, offsetof(PKIStatusInfo, status), nullptr, 0 }, |
274 | | { SEC_ASN1_CONSTRUCTED | SEC_ASN1_SEQUENCE | SEC_ASN1_OPTIONAL, offsetof(PKIStatusInfo, statusString), nullptr, 0 }, |
275 | | { SEC_ASN1_BIT_STRING | SEC_ASN1_OPTIONAL, offsetof(PKIStatusInfo, failInfo), nullptr, 0 }, |
276 | | { 0, 0, nullptr, 0 } |
277 | | }; |
278 | | |
279 | | const SEC_ASN1Template Any_Template[] = |
280 | | { |
281 | | { SEC_ASN1_ANY, 0, nullptr, sizeof(SECItem) } |
282 | | }; |
283 | | |
284 | | struct TimeStampResp { |
285 | | PKIStatusInfo status; |
286 | | SECItem timeStampToken; |
287 | | }; |
288 | | |
289 | | const SEC_ASN1Template TimeStampResp_Template[] = |
290 | | { |
291 | | { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(TimeStampResp) }, |
292 | | { SEC_ASN1_INLINE, offsetof(TimeStampResp, status), PKIStatusInfo_Template, 0 }, |
293 | | { SEC_ASN1_ANY | SEC_ASN1_OPTIONAL, offsetof(TimeStampResp, timeStampToken), Any_Template, 0 }, |
294 | | { 0, 0, nullptr, 0 } |
295 | | }; |
296 | | |
297 | | const SEC_ASN1Template MessageImprint_Template[] = |
298 | | { |
299 | | { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(MessageImprint) }, |
300 | | { SEC_ASN1_INLINE, offsetof(MessageImprint, hashAlgorithm), SECOID_AlgorithmIDTemplate, 0 }, |
301 | | { SEC_ASN1_OCTET_STRING, offsetof(MessageImprint, hashedMessage), nullptr, 0 }, |
302 | | { 0, 0, nullptr, 0 } |
303 | | }; |
304 | | |
305 | | const SEC_ASN1Template Extension_Template[] = |
306 | | { |
307 | | { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(Extension) }, |
308 | | { SEC_ASN1_OBJECT_ID, offsetof(Extension, extnID), nullptr, 0 }, |
309 | | { SEC_ASN1_BOOLEAN, offsetof(Extension, critical), nullptr, 0 }, |
310 | | { SEC_ASN1_OCTET_STRING, offsetof(Extension, extnValue), nullptr, 0 }, |
311 | | { 0, 0, nullptr, 0 } |
312 | | }; |
313 | | |
314 | | const SEC_ASN1Template Extensions_Template[] = |
315 | | { |
316 | | { SEC_ASN1_SEQUENCE_OF, 0, Extension_Template, 0 } |
317 | | }; |
318 | | |
319 | | const SEC_ASN1Template TimeStampReq_Template[] = |
320 | | { |
321 | | { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(TimeStampReq) }, |
322 | | { SEC_ASN1_INTEGER, offsetof(TimeStampReq, version), nullptr, 0 }, |
323 | | { SEC_ASN1_INLINE, offsetof(TimeStampReq, messageImprint), MessageImprint_Template, 0 }, |
324 | | { SEC_ASN1_OBJECT_ID | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, reqPolicy), nullptr, 0 }, |
325 | | { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, nonce), nullptr, 0 }, |
326 | | { SEC_ASN1_BOOLEAN | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, certReq), nullptr, 0 }, |
327 | | { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | 0, offsetof(TimeStampReq, extensions), Extensions_Template, 0 }, |
328 | | { 0, 0, nullptr, 0 } |
329 | | }; |
330 | | |
331 | | // 1.2.840.113549.1.9.16.2.47 |
332 | | constexpr unsigned char OID_SIGNINGCERTIFICATEV2[] {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x2f}; |
333 | | // 1.2.840.113549.1.9.16.2.14 |
334 | | constexpr unsigned char OID_TIMESTAMPTOKEN[] {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x0e}; |
335 | | |
336 | | struct cms_recode_attribute { |
337 | | SECItem type; |
338 | | SECItem **values; |
339 | | }; |
340 | | |
341 | | struct cms_recode_signer_info { |
342 | | SECItem version; |
343 | | SECItem signerIdentifier; |
344 | | SECItem digestAlg; |
345 | | SECItem authAttr; |
346 | | SECItem digestEncAlg; |
347 | | SECItem encDigest; |
348 | | cms_recode_attribute **unAuthAttr; |
349 | | }; |
350 | | |
351 | | struct cms_recode_signed_data { |
352 | | SECItem version; |
353 | | SECItem digestAlgorithms; |
354 | | SECItem contentInfo; |
355 | | SECItem rawCerts; |
356 | | SECItem crls; |
357 | | cms_recode_signer_info **signerInfos; |
358 | | }; |
359 | | |
360 | | struct cms_recode_message { |
361 | | SECItem contentType; |
362 | | cms_recode_signed_data signedData; |
363 | | }; |
364 | | |
365 | | const SEC_ASN1Template recode_attribute_template[] = { |
366 | | { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(cms_recode_attribute) }, |
367 | | { SEC_ASN1_OBJECT_ID, offsetof(cms_recode_attribute, type), nullptr, 0 }, |
368 | | { SEC_ASN1_SET_OF, offsetof(cms_recode_attribute, values), SEC_AnyTemplate, 0 }, |
369 | | {0, 0, nullptr, 0}}; |
370 | | |
371 | | const SEC_ASN1Template recode_set_of_attribute_template[] = { |
372 | | { SEC_ASN1_SET_OF, 0, recode_attribute_template, 0 } |
373 | | }; |
374 | | |
375 | | const SEC_ASN1Template recode_signer_info_template[] = { |
376 | | { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(cms_recode_signer_info) }, |
377 | | { SEC_ASN1_ANY, offsetof(cms_recode_signer_info, version), nullptr, 0 }, |
378 | | { SEC_ASN1_ANY, offsetof(cms_recode_signer_info, signerIdentifier), nullptr, 0 }, |
379 | | { SEC_ASN1_ANY, offsetof(cms_recode_signer_info, digestAlg), nullptr, 0 }, |
380 | | { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, |
381 | | offsetof(cms_recode_signer_info, authAttr), SEC_AnyTemplate, 0 }, |
382 | | { SEC_ASN1_ANY, offsetof(cms_recode_signer_info, digestEncAlg), nullptr, 0 }, |
383 | | { SEC_ASN1_ANY, offsetof(cms_recode_signer_info, encDigest), nullptr, 0 }, |
384 | | { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, |
385 | | offsetof(cms_recode_signer_info, unAuthAttr), recode_set_of_attribute_template, 0 }, |
386 | | {0, 0, nullptr, 0}}; |
387 | | |
388 | | const SEC_ASN1Template recode_signed_data_template[] = { |
389 | | { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(cms_recode_signed_data) }, |
390 | | { SEC_ASN1_ANY, offsetof(cms_recode_signed_data, version), nullptr, 0 }, |
391 | | { SEC_ASN1_ANY, offsetof(cms_recode_signed_data, digestAlgorithms), nullptr, 0 }, |
392 | | { SEC_ASN1_ANY, offsetof(cms_recode_signed_data, contentInfo), nullptr, 0 }, |
393 | | { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, |
394 | | offsetof(cms_recode_signed_data, rawCerts), SEC_AnyTemplate, 0 }, |
395 | | { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, |
396 | | offsetof(cms_recode_signed_data, crls), SEC_AnyTemplate, 0 }, |
397 | | { SEC_ASN1_SET_OF, offsetof(cms_recode_signed_data, signerInfos), recode_signer_info_template, |
398 | | 0 }, |
399 | | {0, 0, nullptr, 0}}; |
400 | | |
401 | | const SEC_ASN1Template recode_message_template[] = { |
402 | | { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(cms_recode_message) }, |
403 | | { SEC_ASN1_ANY, offsetof(cms_recode_message, contentType), nullptr, 0 }, |
404 | | { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, |
405 | | offsetof(cms_recode_message, signedData), recode_signed_data_template, 0 }, |
406 | | {0, 0, nullptr, 0}}; |
407 | | |
408 | | size_t AppendToBuffer(char const *ptr, size_t size, size_t nmemb, void *userdata) |
409 | | { |
410 | | OStringBuffer *pBuffer = static_cast<OStringBuffer*>(userdata); |
411 | | pBuffer->append(ptr, size*nmemb); |
412 | | |
413 | | return size*nmemb; |
414 | | } |
415 | | |
416 | | OUString PKIStatusToString(int n) |
417 | | { |
418 | | switch (n) |
419 | | { |
420 | | case 0: return u"granted"_ustr; |
421 | | case 1: return u"grantedWithMods"_ustr; |
422 | | case 2: return u"rejection"_ustr; |
423 | | case 3: return u"waiting"_ustr; |
424 | | case 4: return u"revocationWarning"_ustr; |
425 | | case 5: return u"revocationNotification"_ustr; |
426 | | default: return "unknown (" + OUString::number(n) + ")"; |
427 | | } |
428 | | } |
429 | | |
430 | | OUString PKIStatusInfoToString(const PKIStatusInfo& rStatusInfo) |
431 | | { |
432 | | OUString result = u"{status="_ustr; |
433 | | if (rStatusInfo.status.len == 1) |
434 | | result += PKIStatusToString(rStatusInfo.status.data[0]); |
435 | | else |
436 | | result += "unknown (len=" + OUString::number(rStatusInfo.status.len); |
437 | | |
438 | | // FIXME: Perhaps look at rStatusInfo.statusString.data but note |
439 | | // that we of course can't assume it contains proper UTF-8. After |
440 | | // all, it is data from an external source. Also, RFC3161 claims |
441 | | // it should be a SEQUENCE (1..MAX) OF UTF8String, but another |
442 | | // source claimed it would be a single UTF8String, hmm? |
443 | | |
444 | | // FIXME: Worth it to decode failInfo to cleartext, probably not at least as long as this is only for a SAL_INFO |
445 | | |
446 | | result += "}"; |
447 | | |
448 | | return result; |
449 | | } |
450 | | |
451 | | // SEC_StringToOID() and NSS_CMSSignerInfo_AddUnauthAttr() are |
452 | | // not exported from libsmime, so copy them here. Sigh. |
453 | | |
454 | | NSSCMSAttribute * |
455 | | my_NSS_CMSAttributeArray_FindAttrByOidTag(NSSCMSAttribute **attrs, SECOidTag oidtag, PRBool only) |
456 | | { |
457 | | SECOidData *oid; |
458 | | NSSCMSAttribute *attr1, *attr2; |
459 | | |
460 | | if (attrs == nullptr) |
461 | | return nullptr; |
462 | | |
463 | | oid = SECOID_FindOIDByTag(oidtag); |
464 | | if (oid == nullptr) |
465 | | return nullptr; |
466 | | |
467 | | while ((attr1 = *attrs++) != nullptr) { |
468 | | if (attr1->type.len == oid->oid.len && PORT_Memcmp (attr1->type.data, |
469 | | oid->oid.data, |
470 | | oid->oid.len) == 0) |
471 | | break; |
472 | | } |
473 | | |
474 | | if (attr1 == nullptr) |
475 | | return nullptr; |
476 | | |
477 | | if (!only) |
478 | | return attr1; |
479 | | |
480 | | while ((attr2 = *attrs++) != nullptr) { |
481 | | if (attr2->type.len == oid->oid.len && PORT_Memcmp (attr2->type.data, |
482 | | oid->oid.data, |
483 | | oid->oid.len) == 0) |
484 | | break; |
485 | | } |
486 | | |
487 | | if (attr2 != nullptr) |
488 | | return nullptr; |
489 | | |
490 | | return attr1; |
491 | | } |
492 | | |
493 | | SECStatus |
494 | | my_NSS_CMSArray_Add(PLArenaPool *poolp, void ***array, void *obj) |
495 | | { |
496 | | int n = 0; |
497 | | void **dest; |
498 | | |
499 | | PORT_Assert(array != NULL); |
500 | | if (array == nullptr) |
501 | | return SECFailure; |
502 | | |
503 | | if (*array == nullptr) { |
504 | | dest = static_cast<void **>(PORT_ArenaAlloc(poolp, 2 * sizeof(void *))); |
505 | | } else { |
506 | | void **p = *array; |
507 | | while (*p++) |
508 | | n++; |
509 | | dest = static_cast<void **>(PORT_ArenaGrow (poolp, |
510 | | *array, |
511 | | (n + 1) * sizeof(void *), |
512 | | (n + 2) * sizeof(void *))); |
513 | | } |
514 | | |
515 | | if (dest == nullptr) |
516 | | return SECFailure; |
517 | | |
518 | | dest[n] = obj; |
519 | | dest[n+1] = nullptr; |
520 | | *array = dest; |
521 | | return SECSuccess; |
522 | | } |
523 | | |
524 | | SECOidTag |
525 | | my_NSS_CMSAttribute_GetType(const NSSCMSAttribute *attr) |
526 | | { |
527 | | SECOidData *typetag; |
528 | | |
529 | | typetag = SECOID_FindOID(&(attr->type)); |
530 | | if (typetag == nullptr) |
531 | | return SEC_OID_UNKNOWN; |
532 | | |
533 | | return typetag->offset; |
534 | | } |
535 | | |
536 | | SECStatus |
537 | | my_NSS_CMSAttributeArray_AddAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, NSSCMSAttribute *attr) |
538 | | { |
539 | | NSSCMSAttribute *oattr; |
540 | | void *mark; |
541 | | SECOidTag type; |
542 | | |
543 | | mark = PORT_ArenaMark(poolp); |
544 | | |
545 | | /* find oidtag of attr */ |
546 | | type = my_NSS_CMSAttribute_GetType(attr); |
547 | | |
548 | | /* see if we have one already */ |
549 | | oattr = my_NSS_CMSAttributeArray_FindAttrByOidTag(*attrs, type, PR_FALSE); |
550 | | PORT_Assert (oattr == NULL); |
551 | | if (oattr != nullptr) |
552 | | goto loser; /* XXX or would it be better to replace it? */ |
553 | | |
554 | | /* no, shove it in */ |
555 | | if (my_NSS_CMSArray_Add(poolp, reinterpret_cast<void ***>(attrs), static_cast<void *>(attr)) != SECSuccess) |
556 | | goto loser; |
557 | | |
558 | | PORT_ArenaUnmark(poolp, mark); |
559 | | return SECSuccess; |
560 | | |
561 | | loser: |
562 | | PORT_ArenaRelease(poolp, mark); |
563 | | return SECFailure; |
564 | | } |
565 | | |
566 | | SECStatus |
567 | | my_NSS_CMSSignerInfo_AddAuthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr) |
568 | | { |
569 | | return my_NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->authAttr), attr); |
570 | | } |
571 | | |
572 | | NSSCMSMessage *CreateCMSMessage(const PRTime* time, |
573 | | NSSCMSSignedData **cms_sd, |
574 | | NSSCMSSignerInfo **cms_signer, |
575 | | CERTCertificate *cert, |
576 | | SECItem *digest) |
577 | | { |
578 | | NSSCMSMessage *result = NSS_CMSMessage_Create(nullptr); |
579 | | if (!result) |
580 | | { |
581 | | SAL_WARN("svl.crypto", "NSS_CMSMessage_Create failed"); |
582 | | return nullptr; |
583 | | } |
584 | | |
585 | | *cms_sd = NSS_CMSSignedData_Create(result); |
586 | | if (!*cms_sd) |
587 | | { |
588 | | SAL_WARN("svl.crypto", "NSS_CMSSignedData_Create failed"); |
589 | | NSS_CMSMessage_Destroy(result); |
590 | | return nullptr; |
591 | | } |
592 | | |
593 | | NSSCMSContentInfo *cms_cinfo = NSS_CMSMessage_GetContentInfo(result); |
594 | | if (NSS_CMSContentInfo_SetContent_SignedData(result, cms_cinfo, *cms_sd) != SECSuccess) |
595 | | { |
596 | | SAL_WARN("svl.crypto", "NSS_CMSContentInfo_SetContent_SignedData failed"); |
597 | | NSS_CMSSignedData_Destroy(*cms_sd); |
598 | | NSS_CMSMessage_Destroy(result); |
599 | | return nullptr; |
600 | | } |
601 | | |
602 | | cms_cinfo = NSS_CMSSignedData_GetContentInfo(*cms_sd); |
603 | | |
604 | | // Attach NULL data as detached data |
605 | | if (NSS_CMSContentInfo_SetContent_Data(result, cms_cinfo, nullptr, PR_TRUE) != SECSuccess) |
606 | | { |
607 | | SAL_WARN("svl.crypto", "NSS_CMSContentInfo_SetContent_Data failed"); |
608 | | NSS_CMSSignedData_Destroy(*cms_sd); |
609 | | NSS_CMSMessage_Destroy(result); |
610 | | return nullptr; |
611 | | } |
612 | | |
613 | | // workaround: with legacy "dbm:", NSS can't find the private key - try out |
614 | | // if it works, and fallback if it doesn't. |
615 | | if (SECKEYPrivateKey * pPrivateKey = PK11_FindKeyByAnyCert(cert, nullptr)) |
616 | | { |
617 | | if (!comphelper::LibreOfficeKit::isActive()) |
618 | | { |
619 | | // pPrivateKey only exists in the memory in the LOK case, don't delete it. |
620 | | SECKEY_DestroyPrivateKey(pPrivateKey); |
621 | | } |
622 | | *cms_signer = NSS_CMSSignerInfo_Create(result, cert, SEC_OID_SHA256); |
623 | | } |
624 | | else |
625 | | { |
626 | | pPrivateKey = PK11_FindKeyByDERCert(cert->slot, cert, nullptr); |
627 | | SECKEYPublicKey *const pPublicKey = CERT_ExtractPublicKey(cert); |
628 | | if (pPublicKey && pPrivateKey) |
629 | | { |
630 | | *cms_signer = NSS_CMSSignerInfo_CreateWithSubjKeyID(result, &cert->subjectKeyID, pPublicKey, pPrivateKey, SEC_OID_SHA256); |
631 | | SECKEY_DestroyPrivateKey(pPrivateKey); |
632 | | SECKEY_DestroyPublicKey(pPublicKey); |
633 | | if (*cms_signer) |
634 | | { |
635 | | // this is required in NSS_CMSSignerInfo_IncludeCerts() |
636 | | // (and NSS_CMSSignerInfo_GetSigningCertificate() doesn't work) |
637 | | (**cms_signer).cert = CERT_DupCertificate(cert); |
638 | | } |
639 | | } |
640 | | } |
641 | | if (!*cms_signer) |
642 | | { |
643 | | SAL_WARN("svl.crypto", "NSS_CMSSignerInfo_Create failed"); |
644 | | NSS_CMSSignedData_Destroy(*cms_sd); |
645 | | NSS_CMSMessage_Destroy(result); |
646 | | return nullptr; |
647 | | } |
648 | | |
649 | | if (time && NSS_CMSSignerInfo_AddSigningTime(*cms_signer, *time) != SECSuccess) |
650 | | { |
651 | | SAL_WARN("svl.crypto", "NSS_CMSSignerInfo_AddSigningTime failed"); |
652 | | NSS_CMSSignedData_Destroy(*cms_sd); |
653 | | NSS_CMSMessage_Destroy(result); |
654 | | return nullptr; |
655 | | } |
656 | | |
657 | | if (NSS_CMSSignerInfo_IncludeCerts(*cms_signer, NSSCMSCM_CertChain, certUsageEmailSigner) != SECSuccess) |
658 | | { |
659 | | SAL_WARN("svl.crypto", "NSS_CMSSignerInfo_IncludeCerts failed"); |
660 | | NSS_CMSSignedData_Destroy(*cms_sd); |
661 | | NSS_CMSMessage_Destroy(result); |
662 | | return nullptr; |
663 | | } |
664 | | |
665 | | if (NSS_CMSSignedData_AddSignerInfo(*cms_sd, *cms_signer) != SECSuccess) |
666 | | { |
667 | | SAL_WARN("svl.crypto", "NSS_CMSSignedData_AddSignerInfo failed"); |
668 | | NSS_CMSSignedData_Destroy(*cms_sd); |
669 | | NSS_CMSMessage_Destroy(result); |
670 | | return nullptr; |
671 | | } |
672 | | |
673 | | if (NSS_CMSSignedData_SetDigestValue(*cms_sd, SEC_OID_SHA256, digest) != SECSuccess) |
674 | | { |
675 | | SAL_WARN("svl.crypto", "NSS_CMSSignedData_SetDigestValue failed"); |
676 | | NSS_CMSSignedData_Destroy(*cms_sd); |
677 | | NSS_CMSMessage_Destroy(result); |
678 | | return nullptr; |
679 | | } |
680 | | |
681 | | return result; |
682 | | } |
683 | | |
684 | | #elif USE_CRYPTO_MSCAPI // ends USE_CRYPTO_NSS |
685 | | |
686 | | /// Counts how many bytes are needed to encode a given length. |
687 | | size_t GetDERLengthOfLength(size_t nLength) |
688 | | { |
689 | | size_t nRet = 1; |
690 | | |
691 | | if(nLength > 127) |
692 | | { |
693 | | while (nLength >> (nRet * 8)) |
694 | | ++nRet; |
695 | | // Long form means one additional byte: the length of the length and |
696 | | // the length itself. |
697 | | ++nRet; |
698 | | } |
699 | | return nRet; |
700 | | } |
701 | | |
702 | | /// Writes the length part of the header. |
703 | | void WriteDERLength(SvStream& rStream, size_t nLength) |
704 | | { |
705 | | size_t nLengthOfLength = GetDERLengthOfLength(nLength); |
706 | | if (nLengthOfLength == 1) |
707 | | { |
708 | | // We can use the short form. |
709 | | rStream.WriteUInt8(nLength); |
710 | | return; |
711 | | } |
712 | | |
713 | | // 0x80 means that the we use the long form: the first byte is the length |
714 | | // of length with the highest bit set to 1, not the actual length. |
715 | | rStream.WriteUInt8(0x80 | (nLengthOfLength - 1)); |
716 | | for (size_t i = 1; i < nLengthOfLength; ++i) |
717 | | rStream.WriteUInt8(nLength >> ((nLengthOfLength - i - 1) * 8)); |
718 | | } |
719 | | |
720 | | const unsigned nASN1_INTEGER = 0x02; |
721 | | const unsigned nASN1_OCTET_STRING = 0x04; |
722 | | const unsigned nASN1_NULL = 0x05; |
723 | | const unsigned nASN1_OBJECT_IDENTIFIER = 0x06; |
724 | | const unsigned nASN1_SEQUENCE = 0x10; |
725 | | /// An explicit tag on a constructed value. |
726 | | const unsigned nASN1_TAGGED_CONSTRUCTED = 0xa0; |
727 | | const unsigned nASN1_CONSTRUCTED = 0x20; |
728 | | |
729 | | /// Create payload for the 'signing-certificate' signed attribute. |
730 | | bool CreateSigningCertificateAttribute(void const * pDerEncoded, int nDerEncoded, PCCERT_CONTEXT pCertContext, SvStream& rEncodedCertificate) |
731 | | { |
732 | | // CryptEncodeObjectEx() does not support encoding arbitrary ASN.1 |
733 | | // structures, like SigningCertificateV2 from RFC 5035, so let's build it |
734 | | // manually. |
735 | | |
736 | | // Count the certificate hash and put it to aHash. |
737 | | // 2.16.840.1.101.3.4.2.1, i.e. sha256. |
738 | | std::vector<unsigned char> aSHA256{0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01}; |
739 | | |
740 | | HCRYPTPROV hProv = 0; |
741 | | if (!CryptAcquireContextW(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) |
742 | | { |
743 | | SAL_WARN("svl.crypto", "CryptAcquireContext() failed"); |
744 | | return false; |
745 | | } |
746 | | |
747 | | HCRYPTHASH hHash = 0; |
748 | | if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) |
749 | | { |
750 | | SAL_WARN("svl.crypto", "CryptCreateHash() failed"); |
751 | | return false; |
752 | | } |
753 | | |
754 | | if (!CryptHashData(hHash, static_cast<const BYTE*>(pDerEncoded), nDerEncoded, 0)) |
755 | | { |
756 | | SAL_WARN("svl.crypto", "CryptHashData() failed"); |
757 | | return false; |
758 | | } |
759 | | |
760 | | DWORD nHash = 0; |
761 | | if (!CryptGetHashParam(hHash, HP_HASHVAL, nullptr, &nHash, 0)) |
762 | | { |
763 | | SAL_WARN("svl.crypto", "CryptGetHashParam() failed to provide the hash length"); |
764 | | return false; |
765 | | } |
766 | | |
767 | | std::vector<unsigned char> aHash(nHash); |
768 | | if (!CryptGetHashParam(hHash, HP_HASHVAL, aHash.data(), &nHash, 0)) |
769 | | { |
770 | | SAL_WARN("svl.crypto", "CryptGetHashParam() failed to provide the hash"); |
771 | | return false; |
772 | | } |
773 | | |
774 | | CryptDestroyHash(hHash); |
775 | | CryptReleaseContext(hProv, 0); |
776 | | |
777 | | // Collect info for IssuerSerial. |
778 | | BYTE* pIssuer = pCertContext->pCertInfo->Issuer.pbData; |
779 | | DWORD nIssuer = pCertContext->pCertInfo->Issuer.cbData; |
780 | | BYTE* pSerial = pCertContext->pCertInfo->SerialNumber.pbData; |
781 | | DWORD nSerial = pCertContext->pCertInfo->SerialNumber.cbData; |
782 | | // pSerial is LE, aSerial is BE. |
783 | | std::vector<BYTE> aSerial(nSerial); |
784 | | for (size_t i = 0; i < nSerial; ++i) |
785 | | aSerial[i] = *(pSerial + nSerial - i - 1); |
786 | | |
787 | | // We now have all the info to count the lengths. |
788 | | // The layout of the payload is: |
789 | | // SEQUENCE: SigningCertificateV2 |
790 | | // SEQUENCE: SEQUENCE OF ESSCertIDv2 |
791 | | // SEQUENCE: ESSCertIDv2 |
792 | | // SEQUENCE: AlgorithmIdentifier |
793 | | // OBJECT: algorithm |
794 | | // NULL: parameters |
795 | | // OCTET STRING: certHash |
796 | | // SEQUENCE: IssuerSerial |
797 | | // SEQUENCE: GeneralNames |
798 | | // cont [ 4 ]: Name |
799 | | // SEQUENCE: Issuer blob |
800 | | // INTEGER: CertificateSerialNumber |
801 | | |
802 | | size_t nAlgorithm = 1 + GetDERLengthOfLength(aSHA256.size()) + aSHA256.size(); |
803 | | size_t nParameters = 1 + GetDERLengthOfLength(1); |
804 | | size_t nAlgorithmIdentifier = 1 + GetDERLengthOfLength(nAlgorithm + nParameters) + nAlgorithm + nParameters; |
805 | | size_t nCertHash = 1 + GetDERLengthOfLength(aHash.size()) + aHash.size(); |
806 | | size_t nName = 1 + GetDERLengthOfLength(nIssuer) + nIssuer; |
807 | | size_t nGeneralNames = 1 + GetDERLengthOfLength(nName) + nName; |
808 | | size_t nCertificateSerialNumber = 1 + GetDERLengthOfLength(nSerial) + nSerial; |
809 | | size_t nIssuerSerial = 1 + GetDERLengthOfLength(nGeneralNames + nCertificateSerialNumber) + nGeneralNames + nCertificateSerialNumber; |
810 | | size_t nESSCertIDv2 = 1 + GetDERLengthOfLength(nAlgorithmIdentifier + nCertHash + nIssuerSerial) + nAlgorithmIdentifier + nCertHash + nIssuerSerial; |
811 | | size_t nESSCertIDv2s = 1 + GetDERLengthOfLength(nESSCertIDv2) + nESSCertIDv2; |
812 | | |
813 | | // Write SigningCertificateV2. |
814 | | rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED); |
815 | | WriteDERLength(rEncodedCertificate, nESSCertIDv2s); |
816 | | // Write SEQUENCE OF ESSCertIDv2. |
817 | | rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED); |
818 | | WriteDERLength(rEncodedCertificate, nESSCertIDv2); |
819 | | // Write ESSCertIDv2. |
820 | | rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED); |
821 | | WriteDERLength(rEncodedCertificate, nAlgorithmIdentifier + nCertHash + nIssuerSerial); |
822 | | // Write AlgorithmIdentifier. |
823 | | rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED); |
824 | | WriteDERLength(rEncodedCertificate, nAlgorithm + nParameters); |
825 | | // Write algorithm. |
826 | | rEncodedCertificate.WriteUInt8(nASN1_OBJECT_IDENTIFIER); |
827 | | WriteDERLength(rEncodedCertificate, aSHA256.size()); |
828 | | rEncodedCertificate.WriteBytes(aSHA256.data(), aSHA256.size()); |
829 | | // Write parameters. |
830 | | rEncodedCertificate.WriteUInt8(nASN1_NULL); |
831 | | rEncodedCertificate.WriteUInt8(0); |
832 | | // Write certHash. |
833 | | rEncodedCertificate.WriteUInt8(nASN1_OCTET_STRING); |
834 | | WriteDERLength(rEncodedCertificate, aHash.size()); |
835 | | rEncodedCertificate.WriteBytes(aHash.data(), aHash.size()); |
836 | | // Write IssuerSerial. |
837 | | rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED); |
838 | | WriteDERLength(rEncodedCertificate, nGeneralNames + nCertificateSerialNumber); |
839 | | // Write GeneralNames. |
840 | | rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED); |
841 | | WriteDERLength(rEncodedCertificate, nName); |
842 | | // Write Name. |
843 | | rEncodedCertificate.WriteUInt8(nASN1_TAGGED_CONSTRUCTED | 4); |
844 | | WriteDERLength(rEncodedCertificate, nIssuer); |
845 | | rEncodedCertificate.WriteBytes(pIssuer, nIssuer); |
846 | | // Write CertificateSerialNumber. |
847 | | rEncodedCertificate.WriteUInt8(nASN1_INTEGER); |
848 | | WriteDERLength(rEncodedCertificate, nSerial); |
849 | | rEncodedCertificate.WriteBytes(aSerial.data(), aSerial.size()); |
850 | | |
851 | | return true; |
852 | | } |
853 | | #endif // USE_CRYPTO_MSCAPI |
854 | | |
855 | | } // anonymous namespace |
856 | | |
857 | | namespace svl::crypto { |
858 | | |
859 | | std::vector<unsigned char> DecodeHexString(std::string_view rHex) |
860 | 0 | { |
861 | 0 | std::vector<unsigned char> aRet; |
862 | 0 | size_t nHexLen = rHex.size(); |
863 | 0 | { |
864 | 0 | int nByte = 0; |
865 | 0 | int nCount = 2; |
866 | 0 | for (size_t i = 0; i < nHexLen; ++i) |
867 | 0 | { |
868 | 0 | nByte = nByte << 4; |
869 | 0 | sal_Int8 nParsed = o3tl::convertToHex<int>(rHex[i]); |
870 | 0 | if (nParsed == -1) |
871 | 0 | { |
872 | 0 | SAL_WARN("svl.crypto", "DecodeHexString: invalid hex value"); |
873 | 0 | return aRet; |
874 | 0 | } |
875 | 0 | nByte += nParsed; |
876 | 0 | --nCount; |
877 | 0 | if (!nCount) |
878 | 0 | { |
879 | 0 | aRet.push_back(nByte); |
880 | 0 | nCount = 2; |
881 | 0 | nByte = 0; |
882 | 0 | } |
883 | 0 | } |
884 | 0 | } |
885 | | |
886 | 0 | return aRet; |
887 | 0 | } |
888 | | |
889 | | bool Signing::Sign(OStringBuffer& rCMSHexBuffer) |
890 | 0 | { |
891 | 0 | #if !USE_CRYPTO_ANY |
892 | 0 | (void)rCMSHexBuffer; |
893 | 0 | (void)m_rSigningContext; |
894 | 0 | return false; |
895 | | #else |
896 | | // Create the PKCS#7 object. |
897 | | css::uno::Sequence<sal_Int8> aDerEncoded; |
898 | | if (m_rSigningContext.m_xCertificate.is()) |
899 | | { |
900 | | aDerEncoded = m_rSigningContext.m_xCertificate->getEncoded(); |
901 | | if (!aDerEncoded.hasElements()) |
902 | | { |
903 | | SAL_WARN("svl.crypto", "Crypto::Signing: empty certificate"); |
904 | | return false; |
905 | | } |
906 | | } |
907 | | |
908 | | #if USE_CRYPTO_NSS |
909 | | std::vector<unsigned char> aHashResult; |
910 | | { |
911 | | comphelper::Hash aHash(comphelper::HashType::SHA256); |
912 | | |
913 | | for (const auto& pair : m_dataBlocks) |
914 | | aHash.update(pair.first, pair.second); |
915 | | |
916 | | aHashResult = aHash.finalize(); |
917 | | } |
918 | | SECItem digest; |
919 | | digest.data = aHashResult.data(); |
920 | | digest.len = aHashResult.size(); |
921 | | |
922 | | PRTime now = PR_Now(); |
923 | | |
924 | | // The context unit is milliseconds, PR_Now() unit is microseconds. |
925 | | if (m_rSigningContext.m_nSignatureTime) |
926 | | { |
927 | | now = m_rSigningContext.m_nSignatureTime * 1000; |
928 | | } |
929 | | else |
930 | | { |
931 | | m_rSigningContext.m_nSignatureTime = now / 1000; |
932 | | } |
933 | | |
934 | | if (!m_rSigningContext.m_xCertificate.is()) |
935 | | { |
936 | | m_rSigningContext.m_aDigest = std::move(aHashResult); |
937 | | // No certificate is provided: don't actually sign -- just update the context with the |
938 | | // parameters for the signing and return. |
939 | | return false; |
940 | | } |
941 | | |
942 | | CERTCertificate *cert = CERT_DecodeCertFromPackage(reinterpret_cast<char *>(aDerEncoded.getArray()), aDerEncoded.getLength()); |
943 | | |
944 | | if (!cert) |
945 | | { |
946 | | SAL_WARN("svl.crypto", "CERT_DecodeCertFromPackage failed"); |
947 | | return false; |
948 | | } |
949 | | |
950 | | NSSCMSSignedData *cms_sd(nullptr); |
951 | | NSSCMSSignerInfo *cms_signer(nullptr); |
952 | | NSSCMSMessage *cms_msg = CreateCMSMessage(nullptr, &cms_sd, &cms_signer, cert, &digest); |
953 | | if (!cms_msg) |
954 | | return false; |
955 | | |
956 | | OString pass(OUStringToOString( m_aSignPassword, RTL_TEXTENCODING_UTF8 )); |
957 | | |
958 | | // Add the signing certificate as a signed attribute. |
959 | | ESSCertIDv2* aCertIDs[2]; |
960 | | ESSCertIDv2 aCertID; |
961 | | // Write ESSCertIDv2.hashAlgorithm. |
962 | | aCertID.hashAlgorithm.algorithm.data = nullptr; |
963 | | aCertID.hashAlgorithm.parameters.data = nullptr; |
964 | | SECOID_SetAlgorithmID(nullptr, &aCertID.hashAlgorithm, SEC_OID_SHA256, nullptr); |
965 | | comphelper::ScopeGuard aAlgoGuard( |
966 | | [&aCertID] () { SECOID_DestroyAlgorithmID(&aCertID.hashAlgorithm, false); } ); |
967 | | // Write ESSCertIDv2.certHash. |
968 | | SECItem aCertHashItem; |
969 | | auto pDerEncoded = reinterpret_cast<const unsigned char *>(aDerEncoded.getArray()); |
970 | | std::vector<unsigned char> aCertHashResult = comphelper::Hash::calculateHash(pDerEncoded, aDerEncoded.getLength(), comphelper::HashType::SHA256); |
971 | | aCertHashItem.type = siBuffer; |
972 | | aCertHashItem.data = aCertHashResult.data(); |
973 | | aCertHashItem.len = aCertHashResult.size(); |
974 | | aCertID.certHash = aCertHashItem; |
975 | | // Write ESSCertIDv2.issuerSerial. |
976 | | IssuerSerial aSerial; |
977 | | GeneralName aName; |
978 | | aName.name = cert->issuer; |
979 | | aSerial.issuer.names = aName; |
980 | | aSerial.serialNumber = cert->serialNumber; |
981 | | aCertID.issuerSerial = aSerial; |
982 | | // Write SigningCertificateV2.certs. |
983 | | aCertIDs[0] = &aCertID; |
984 | | aCertIDs[1] = nullptr; |
985 | | SigningCertificateV2 aCertificate; |
986 | | aCertificate.certs = &aCertIDs[0]; |
987 | | SECItem* pEncodedCertificate = SEC_ASN1EncodeItem(nullptr, nullptr, &aCertificate, SigningCertificateV2Template); |
988 | | if (!pEncodedCertificate) |
989 | | { |
990 | | SAL_WARN("svl.crypto", "SEC_ASN1EncodeItem() failed"); |
991 | | return false; |
992 | | } |
993 | | |
994 | | NSSCMSAttribute aAttribute; |
995 | | SECItem aAttributeValues[2]; |
996 | | SECItem* pAttributeValues[2]; |
997 | | pAttributeValues[0] = aAttributeValues; |
998 | | pAttributeValues[1] = nullptr; |
999 | | aAttributeValues[0] = *pEncodedCertificate; |
1000 | | aAttributeValues[1].type = siBuffer; |
1001 | | aAttributeValues[1].data = nullptr; |
1002 | | aAttributeValues[1].len = 0; |
1003 | | aAttribute.values = pAttributeValues; |
1004 | | |
1005 | | SECOidData aOidData; |
1006 | | auto cert_oid_buffer = std::to_array(OID_SIGNINGCERTIFICATEV2); |
1007 | | aOidData.oid.data = cert_oid_buffer.data(); |
1008 | | aOidData.oid.len = cert_oid_buffer.size(); |
1009 | | /* |
1010 | | * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::= |
1011 | | * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) |
1012 | | * smime(16) id-aa(2) 47 } |
1013 | | */ |
1014 | | aOidData.offset = SEC_OID_UNKNOWN; |
1015 | | aOidData.desc = "id-aa-signingCertificateV2"; |
1016 | | aOidData.mechanism = CKM_SHA_1; |
1017 | | aOidData.supportedExtension = UNSUPPORTED_CERT_EXTENSION; |
1018 | | aAttribute.typeTag = &aOidData; |
1019 | | aAttribute.type = aOidData.oid; |
1020 | | aAttribute.encoded = PR_TRUE; |
1021 | | |
1022 | | if (my_NSS_CMSSignerInfo_AddAuthAttr(cms_signer, &aAttribute) != SECSuccess) |
1023 | | { |
1024 | | SAL_WARN("svl.crypto", "my_NSS_CMSSignerInfo_AddAuthAttr() failed"); |
1025 | | return false; |
1026 | | } |
1027 | | |
1028 | | SECItem cms_output; |
1029 | | cms_output.data = nullptr; |
1030 | | cms_output.len = 0; |
1031 | | PLArenaPool *arena = PORT_NewArena(10000); |
1032 | | const ::comphelper::ScopeGuard aScopeGuard( |
1033 | | [&arena]() mutable { PORT_FreeArena(arena, true); } ); |
1034 | | NSSCMSEncoderContext *cms_ecx; |
1035 | | |
1036 | | // Possibly it would work to even just pass NULL for the password callback function and its |
1037 | | // argument here. After all, at least with the hardware token and associated software I tested |
1038 | | // with, the software itself pops up a dialog asking for the PIN (password). But I am not going |
1039 | | // to test it and risk locking up my token... |
1040 | | |
1041 | | cms_ecx = NSS_CMSEncoder_Start(cms_msg, nullptr, nullptr, &cms_output, arena, PDFSigningPKCS7PasswordCallback, |
1042 | | const_cast<char*>(pass.getStr()), nullptr, nullptr, nullptr, nullptr); |
1043 | | |
1044 | | if (!cms_ecx) |
1045 | | { |
1046 | | SAL_WARN("svl.crypto", "NSS_CMSEncoder_Start failed"); |
1047 | | return false; |
1048 | | } |
1049 | | |
1050 | | if (NSS_CMSEncoder_Finish(cms_ecx) != SECSuccess) |
1051 | | { |
1052 | | SAL_WARN("svl.crypto", "NSS_CMSEncoder_Finish failed"); |
1053 | | return false; |
1054 | | } |
1055 | | |
1056 | | if( !m_aSignTSA.isEmpty() ) |
1057 | | { |
1058 | | TimeStampReq src; |
1059 | | OStringBuffer response_buffer; |
1060 | | TimeStampResp response; |
1061 | | SECItem response_item; |
1062 | | cms_recode_attribute timestamp; |
1063 | | SECItem values[2]; |
1064 | | SECItem *valuesp[2]; |
1065 | | valuesp[0] = values; |
1066 | | valuesp[1] = nullptr; |
1067 | | |
1068 | | std::vector<unsigned char> aTsHashResult = comphelper::Hash::calculateHash(cms_signer->encDigest.data, cms_signer->encDigest.len, comphelper::HashType::SHA256); |
1069 | | SECItem ts_digest; |
1070 | | ts_digest.type = siBuffer; |
1071 | | ts_digest.data = aTsHashResult.data(); |
1072 | | ts_digest.len = aTsHashResult.size(); |
1073 | | |
1074 | | unsigned char cOne = 1; |
1075 | | unsigned char cTRUE = 0xff; // under DER rules true is 0xff, false is 0x00 |
1076 | | src.version.type = siUnsignedInteger; |
1077 | | src.version.data = &cOne; |
1078 | | src.version.len = sizeof(cOne); |
1079 | | |
1080 | | src.messageImprint.hashAlgorithm.algorithm.data = nullptr; |
1081 | | src.messageImprint.hashAlgorithm.parameters.data = nullptr; |
1082 | | SECOID_SetAlgorithmID(arena, &src.messageImprint.hashAlgorithm, SEC_OID_SHA256, nullptr); |
1083 | | src.messageImprint.hashedMessage = ts_digest; |
1084 | | |
1085 | | src.reqPolicy.type = siBuffer; |
1086 | | src.reqPolicy.data = nullptr; |
1087 | | src.reqPolicy.len = 0; |
1088 | | |
1089 | | unsigned int nNonce = comphelper::rng::uniform_uint_distribution(0, SAL_MAX_UINT32); |
1090 | | src.nonce.type = siUnsignedInteger; |
1091 | | src.nonce.data = reinterpret_cast<unsigned char*>(&nNonce); |
1092 | | src.nonce.len = sizeof(nNonce); |
1093 | | |
1094 | | src.certReq.type = siUnsignedInteger; |
1095 | | src.certReq.data = &cTRUE; |
1096 | | src.certReq.len = sizeof(cTRUE); |
1097 | | |
1098 | | src.extensions = nullptr; |
1099 | | |
1100 | | SECItem* timestamp_request = SEC_ASN1EncodeItem(nullptr, nullptr, &src, TimeStampReq_Template); |
1101 | | if (timestamp_request == nullptr) |
1102 | | { |
1103 | | SAL_WARN("svl.crypto", "SEC_ASN1EncodeItem failed"); |
1104 | | return false; |
1105 | | } |
1106 | | |
1107 | | if (timestamp_request->data == nullptr) |
1108 | | { |
1109 | | SAL_WARN("svl.crypto", "SEC_ASN1EncodeItem succeeded but got NULL data"); |
1110 | | SECITEM_FreeItem(timestamp_request, PR_TRUE); |
1111 | | return false; |
1112 | | } |
1113 | | |
1114 | | SAL_INFO("svl.crypto", "request length=" << timestamp_request->len); |
1115 | | |
1116 | | // Send time stamp request to TSA server, receive response |
1117 | | |
1118 | | CURL* curl = curl_easy_init(); |
1119 | | CURLcode rc; |
1120 | | struct curl_slist* slist = nullptr; |
1121 | | |
1122 | | if (!curl) |
1123 | | { |
1124 | | SAL_WARN("svl.crypto", "curl_easy_init failed"); |
1125 | | SECITEM_FreeItem(timestamp_request, PR_TRUE); |
1126 | | return false; |
1127 | | } |
1128 | | |
1129 | | ::InitCurl_easy(curl); |
1130 | | |
1131 | | SAL_INFO("svl.crypto", "Setting curl to verbose: " << (curl_easy_setopt(curl, CURLOPT_VERBOSE, 1) == CURLE_OK ? "OK" : "FAIL")); |
1132 | | |
1133 | | if ((rc = curl_easy_setopt(curl, CURLOPT_URL, OUStringToOString(m_aSignTSA, RTL_TEXTENCODING_UTF8).getStr())) != CURLE_OK) |
1134 | | { |
1135 | | SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_URL) failed: " << curl_easy_strerror(rc)); |
1136 | | curl_easy_cleanup(curl); |
1137 | | SECITEM_FreeItem(timestamp_request, PR_TRUE); |
1138 | | return false; |
1139 | | } |
1140 | | |
1141 | | slist = curl_slist_append(slist, "Content-Type: application/timestamp-query"); |
1142 | | slist = curl_slist_append(slist, "Accept: application/timestamp-reply"); |
1143 | | |
1144 | | if ((rc = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist)) != CURLE_OK) |
1145 | | { |
1146 | | SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_HTTPHEADER) failed: " << curl_easy_strerror(rc)); |
1147 | | curl_slist_free_all(slist); |
1148 | | curl_easy_cleanup(curl); |
1149 | | SECITEM_FreeItem(timestamp_request, PR_TRUE); |
1150 | | return false; |
1151 | | } |
1152 | | |
1153 | | if ((rc = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<tools::Long>(timestamp_request->len))) != CURLE_OK || |
1154 | | (rc = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, timestamp_request->data)) != CURLE_OK) |
1155 | | { |
1156 | | SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_POSTFIELDSIZE or CURLOPT_POSTFIELDS) failed: " << curl_easy_strerror(rc)); |
1157 | | curl_easy_cleanup(curl); |
1158 | | SECITEM_FreeItem(timestamp_request, PR_TRUE); |
1159 | | return false; |
1160 | | } |
1161 | | |
1162 | | if ((rc = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_buffer)) != CURLE_OK || |
1163 | | (rc = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, AppendToBuffer)) != CURLE_OK) |
1164 | | { |
1165 | | SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_WRITEDATA or CURLOPT_WRITEFUNCTION) failed: " << curl_easy_strerror(rc)); |
1166 | | curl_easy_cleanup(curl); |
1167 | | SECITEM_FreeItem(timestamp_request, PR_TRUE); |
1168 | | return false; |
1169 | | } |
1170 | | |
1171 | | if ((rc = curl_easy_setopt(curl, CURLOPT_POST, 1)) != CURLE_OK) |
1172 | | { |
1173 | | SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_POST) failed: " << curl_easy_strerror(rc)); |
1174 | | curl_easy_cleanup(curl); |
1175 | | SECITEM_FreeItem(timestamp_request, PR_TRUE); |
1176 | | return false; |
1177 | | } |
1178 | | |
1179 | | char error_buffer[CURL_ERROR_SIZE]; |
1180 | | if ((rc = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buffer)) != CURLE_OK) |
1181 | | { |
1182 | | SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_ERRORBUFFER) failed: " << curl_easy_strerror(rc)); |
1183 | | curl_easy_cleanup(curl); |
1184 | | SECITEM_FreeItem(timestamp_request, PR_TRUE); |
1185 | | return false; |
1186 | | } |
1187 | | |
1188 | | // Use a ten second timeout |
1189 | | if ((rc = curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10)) != CURLE_OK || |
1190 | | (rc = curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10)) != CURLE_OK) |
1191 | | { |
1192 | | SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_TIMEOUT or CURLOPT_CONNECTTIMEOUT) failed: " << curl_easy_strerror(rc)); |
1193 | | curl_easy_cleanup(curl); |
1194 | | SECITEM_FreeItem(timestamp_request, PR_TRUE); |
1195 | | return false; |
1196 | | } |
1197 | | |
1198 | | if (curl_easy_perform(curl) != CURLE_OK) |
1199 | | { |
1200 | | SAL_WARN("svl.crypto", "curl_easy_perform failed: " << error_buffer); |
1201 | | curl_easy_cleanup(curl); |
1202 | | SECITEM_FreeItem(timestamp_request, PR_TRUE); |
1203 | | return false; |
1204 | | } |
1205 | | |
1206 | | SAL_INFO("svl.crypto", "PDF signing: got response, length=" << response_buffer.getLength()); |
1207 | | |
1208 | | curl_slist_free_all(slist); |
1209 | | curl_easy_cleanup(curl); |
1210 | | SECITEM_FreeItem(timestamp_request, PR_TRUE); |
1211 | | |
1212 | | memset(&response, 0, sizeof(response)); |
1213 | | |
1214 | | response_item.type = siBuffer; |
1215 | | response_item.data = reinterpret_cast<unsigned char*>(const_cast<char*>(response_buffer.getStr())); |
1216 | | response_item.len = response_buffer.getLength(); |
1217 | | |
1218 | | if (SEC_ASN1DecodeItem(arena, &response, TimeStampResp_Template, &response_item) != SECSuccess) |
1219 | | { |
1220 | | SAL_WARN("svl.crypto", "SEC_ASN1DecodeItem failed"); |
1221 | | return false; |
1222 | | } |
1223 | | |
1224 | | SAL_INFO("svl.crypto", "TimeStampResp received and decoded, status=" << PKIStatusInfoToString(response.status)); |
1225 | | |
1226 | | if (response.status.status.len != 1 || |
1227 | | (response.status.status.data[0] != 0 && response.status.status.data[0] != 1)) |
1228 | | { |
1229 | | SAL_WARN("svl.crypto", "Timestamp request was not granted"); |
1230 | | return false; |
1231 | | } |
1232 | | |
1233 | | // timestamp.type filled in below |
1234 | | |
1235 | | // Not sure if we actually need two entries in the values array, now when valuesp is an |
1236 | | // array too, the pointer to the values array followed by a null pointer. But I don't feel |
1237 | | // like experimenting. |
1238 | | values[0] = response.timeStampToken; |
1239 | | values[1].type = siBuffer; |
1240 | | values[1].data = nullptr; |
1241 | | values[1].len = 0; |
1242 | | |
1243 | | timestamp.values = valuesp; |
1244 | | |
1245 | | auto ts_oid_buffer = std::to_array(OID_TIMESTAMPTOKEN); |
1246 | | // id-aa-timeStampToken OBJECT IDENTIFIER ::= { iso(1) |
1247 | | // member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9) |
1248 | | // smime(16) aa(2) 14 } |
1249 | | |
1250 | | timestamp.type.data = ts_oid_buffer.data(); |
1251 | | timestamp.type.len = ts_oid_buffer.size(); |
1252 | | |
1253 | | cms_recode_message decoded_cms_output {}; |
1254 | | |
1255 | | if (SEC_ASN1DecodeItem(arena, &decoded_cms_output, recode_message_template, &cms_output) != SECSuccess) |
1256 | | { |
1257 | | SAL_WARN("svl.crypto", "SEC_ASN1DecodeItem failed"); |
1258 | | return false; |
1259 | | } |
1260 | | |
1261 | | // now insert the new attribute |
1262 | | |
1263 | | cms_recode_signer_info **decoded_signerinfos = decoded_cms_output.signedData.signerInfos; |
1264 | | if (!decoded_signerinfos || !*decoded_signerinfos) { |
1265 | | SAL_WARN("svl.crypto", "Decoded signed message invalid"); |
1266 | | return false; |
1267 | | } |
1268 | | |
1269 | | std::vector<cms_recode_attribute *> updated_attrs; |
1270 | | |
1271 | | // there are no unauthenticated attributes at the moment |
1272 | | // if this ever changes, make sure to preserve them |
1273 | | cms_recode_attribute **existing_attrs = (*decoded_signerinfos)->unAuthAttr ; |
1274 | | |
1275 | | if (existing_attrs) |
1276 | | while (*existing_attrs) |
1277 | | updated_attrs.push_back(*existing_attrs++); |
1278 | | |
1279 | | updated_attrs.push_back(×tamp); |
1280 | | updated_attrs.push_back(nullptr); |
1281 | | |
1282 | | (*decoded_signerinfos)->unAuthAttr = updated_attrs.data(); |
1283 | | |
1284 | | SECItem * ts_cms_output = SEC_ASN1EncodeItem(arena, nullptr, &decoded_cms_output, recode_message_template); |
1285 | | if (!ts_cms_output) |
1286 | | { |
1287 | | SAL_WARN("svl.crypto", "SEC_ASN1EncodeItem failed"); |
1288 | | return false; |
1289 | | } |
1290 | | |
1291 | | cms_output = *ts_cms_output; |
1292 | | } |
1293 | | |
1294 | | if (cms_output.len*2 > MAX_SIGNATURE_CONTENT_LENGTH) |
1295 | | { |
1296 | | SAL_WARN("svl.crypto", "Signature requires more space (" << cms_output.len*2 << ") than we reserved (" << MAX_SIGNATURE_CONTENT_LENGTH << ")"); |
1297 | | NSS_CMSMessage_Destroy(cms_msg); |
1298 | | return false; |
1299 | | } |
1300 | | |
1301 | | for (unsigned int i = 0; i < cms_output.len ; i++) |
1302 | | appendHex(cms_output.data[i], rCMSHexBuffer); |
1303 | | |
1304 | | SECITEM_FreeItem(pEncodedCertificate, PR_TRUE); |
1305 | | NSS_CMSMessage_Destroy(cms_msg); |
1306 | | |
1307 | | return true; |
1308 | | |
1309 | | #elif USE_CRYPTO_MSCAPI // ends USE_CRYPTO_NSS |
1310 | | |
1311 | | PCCERT_CONTEXT pCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, reinterpret_cast<const BYTE*>(aDerEncoded.getArray()), aDerEncoded.getLength()); |
1312 | | if (pCertContext == nullptr) |
1313 | | { |
1314 | | SAL_WARN("svl.crypto", "CertCreateCertificateContext failed: " << comphelper::WindowsErrorString(GetLastError())); |
1315 | | return false; |
1316 | | } |
1317 | | |
1318 | | CRYPT_SIGN_MESSAGE_PARA aPara = {}; |
1319 | | aPara.cbSize = sizeof(aPara); |
1320 | | aPara.dwMsgEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING; |
1321 | | aPara.pSigningCert = pCertContext; |
1322 | | aPara.HashAlgorithm.pszObjId = const_cast<LPSTR>(szOID_NIST_sha256); |
1323 | | aPara.HashAlgorithm.Parameters.cbData = 0; |
1324 | | aPara.cMsgCert = 1; |
1325 | | aPara.rgpMsgCert = &pCertContext; |
1326 | | |
1327 | | NCRYPT_KEY_HANDLE hCryptKey = 0; |
1328 | | DWORD dwFlags = CRYPT_ACQUIRE_CACHE_FLAG | CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG; |
1329 | | HCRYPTPROV_OR_NCRYPT_KEY_HANDLE* phCryptProvOrNCryptKey = &hCryptKey; |
1330 | | DWORD nKeySpec; |
1331 | | BOOL bFreeNeeded; |
1332 | | |
1333 | | if (!CryptAcquireCertificatePrivateKey(pCertContext, |
1334 | | dwFlags, |
1335 | | nullptr, |
1336 | | phCryptProvOrNCryptKey, |
1337 | | &nKeySpec, |
1338 | | &bFreeNeeded)) |
1339 | | { |
1340 | | SAL_WARN("svl.crypto", "CryptAcquireCertificatePrivateKey failed: " << comphelper::WindowsErrorString(GetLastError())); |
1341 | | CertFreeCertificateContext(pCertContext); |
1342 | | return false; |
1343 | | } |
1344 | | assert(!bFreeNeeded); |
1345 | | |
1346 | | CMSG_SIGNER_ENCODE_INFO aSignerInfo = {}; |
1347 | | aSignerInfo.cbSize = sizeof(aSignerInfo); |
1348 | | aSignerInfo.pCertInfo = pCertContext->pCertInfo; |
1349 | | aSignerInfo.hNCryptKey = hCryptKey; |
1350 | | aSignerInfo.dwKeySpec = nKeySpec; |
1351 | | aSignerInfo.HashAlgorithm.pszObjId = const_cast<LPSTR>(szOID_NIST_sha256); |
1352 | | aSignerInfo.HashAlgorithm.Parameters.cbData = 0; |
1353 | | |
1354 | | // Add the signing certificate as a signed attribute. |
1355 | | CRYPT_INTEGER_BLOB aCertificateBlob; |
1356 | | SvMemoryStream aEncodedCertificate; |
1357 | | if (!CreateSigningCertificateAttribute(aDerEncoded.getArray(), aDerEncoded.getLength(), pCertContext, aEncodedCertificate)) |
1358 | | { |
1359 | | SAL_WARN("svl.crypto", "CreateSigningCertificateAttribute() failed"); |
1360 | | return false; |
1361 | | } |
1362 | | aCertificateBlob.pbData = const_cast<BYTE*>(static_cast<const BYTE*>(aEncodedCertificate.GetData())); |
1363 | | aCertificateBlob.cbData = aEncodedCertificate.GetSize(); |
1364 | | CRYPT_ATTRIBUTE aCertificateAttribute; |
1365 | | /* |
1366 | | * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::= |
1367 | | * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) |
1368 | | * smime(16) id-aa(2) 47 } |
1369 | | */ |
1370 | | aCertificateAttribute.pszObjId = const_cast<LPSTR>("1.2.840.113549.1.9.16.2.47"); |
1371 | | aCertificateAttribute.cValue = 1; |
1372 | | aCertificateAttribute.rgValue = &aCertificateBlob; |
1373 | | aSignerInfo.cAuthAttr = 1; |
1374 | | aSignerInfo.rgAuthAttr = &aCertificateAttribute; |
1375 | | |
1376 | | CMSG_SIGNED_ENCODE_INFO aSignedInfo = {}; |
1377 | | aSignedInfo.cbSize = sizeof(aSignedInfo); |
1378 | | aSignedInfo.cSigners = 1; |
1379 | | aSignedInfo.rgSigners = &aSignerInfo; |
1380 | | |
1381 | | CERT_BLOB aCertBlob; |
1382 | | |
1383 | | aCertBlob.cbData = pCertContext->cbCertEncoded; |
1384 | | aCertBlob.pbData = pCertContext->pbCertEncoded; |
1385 | | |
1386 | | aSignedInfo.cCertEncoded = 1; |
1387 | | aSignedInfo.rgCertEncoded = &aCertBlob; |
1388 | | |
1389 | | HCRYPTMSG hMsg = CryptMsgOpenToEncode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, |
1390 | | CMSG_DETACHED_FLAG, |
1391 | | CMSG_SIGNED, |
1392 | | &aSignedInfo, |
1393 | | nullptr, |
1394 | | nullptr); |
1395 | | if (!hMsg) |
1396 | | { |
1397 | | SAL_WARN("svl.crypto", "CryptMsgOpenToEncode failed: " << comphelper::WindowsErrorString(GetLastError())); |
1398 | | CertFreeCertificateContext(pCertContext); |
1399 | | return false; |
1400 | | } |
1401 | | |
1402 | | for (size_t i = 0; i < m_dataBlocks.size(); ++i) |
1403 | | { |
1404 | | const bool last = (i == m_dataBlocks.size() - 1); |
1405 | | if (!CryptMsgUpdate(hMsg, static_cast<const BYTE *>(m_dataBlocks[i].first), m_dataBlocks[i].second, last)) |
1406 | | { |
1407 | | SAL_WARN("svl.crypto", "CryptMsgUpdate failed: " << comphelper::WindowsErrorString(GetLastError())); |
1408 | | CryptMsgClose(hMsg); |
1409 | | CertFreeCertificateContext(pCertContext); |
1410 | | return false; |
1411 | | } |
1412 | | } |
1413 | | CertFreeCertificateContext(pCertContext); |
1414 | | |
1415 | | PCRYPT_TIMESTAMP_CONTEXT pTsContext = nullptr; |
1416 | | DWORD dwEncodedMessageParamType = CMSG_CONTENT_PARAM; |
1417 | | |
1418 | | if( !m_aSignTSA.isEmpty() ) |
1419 | | { |
1420 | | HCRYPTMSG hDecodedMsg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, |
1421 | | CMSG_DETACHED_FLAG, |
1422 | | CMSG_SIGNED, |
1423 | | 0, |
1424 | | nullptr, |
1425 | | nullptr); |
1426 | | if (!hDecodedMsg) |
1427 | | { |
1428 | | SAL_WARN("svl.crypto", "CryptMsgOpenToDecode failed: " << comphelper::WindowsErrorString(GetLastError())); |
1429 | | CryptMsgClose(hMsg); |
1430 | | return false; |
1431 | | } |
1432 | | |
1433 | | DWORD nTsSigLen = 0; |
1434 | | |
1435 | | if (!CryptMsgGetParam(hMsg, CMSG_BARE_CONTENT_PARAM, 0, nullptr, &nTsSigLen)) |
1436 | | { |
1437 | | SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_BARE_CONTENT_PARAM) failed: " << comphelper::WindowsErrorString(GetLastError())); |
1438 | | CryptMsgClose(hDecodedMsg); |
1439 | | CryptMsgClose(hMsg); |
1440 | | return false; |
1441 | | } |
1442 | | |
1443 | | SAL_INFO("svl.crypto", "nTsSigLen=" << nTsSigLen); |
1444 | | |
1445 | | std::unique_ptr<BYTE[]> pTsSig(new BYTE[nTsSigLen]); |
1446 | | |
1447 | | if (!CryptMsgGetParam(hMsg, CMSG_BARE_CONTENT_PARAM, 0, pTsSig.get(), &nTsSigLen)) |
1448 | | { |
1449 | | SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_BARE_CONTENT_PARAM) failed: " << comphelper::WindowsErrorString(GetLastError())); |
1450 | | CryptMsgClose(hDecodedMsg); |
1451 | | CryptMsgClose(hMsg); |
1452 | | return false; |
1453 | | } |
1454 | | |
1455 | | CryptMsgClose(hMsg); |
1456 | | |
1457 | | if (!CryptMsgUpdate(hDecodedMsg, pTsSig.get(), nTsSigLen, TRUE)) |
1458 | | { |
1459 | | SAL_WARN("svl.crypto", "CryptMsgUpdate failed: " << comphelper::WindowsErrorString(GetLastError())); |
1460 | | CryptMsgClose(hDecodedMsg); |
1461 | | return false; |
1462 | | } |
1463 | | |
1464 | | DWORD nDecodedSignerInfoLen = 0; |
1465 | | if (!CryptMsgGetParam(hDecodedMsg, CMSG_SIGNER_INFO_PARAM, 0, nullptr, &nDecodedSignerInfoLen)) |
1466 | | { |
1467 | | SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_SIGNER_INFO_PARAM) failed: " << comphelper::WindowsErrorString(GetLastError())); |
1468 | | CryptMsgClose(hDecodedMsg); |
1469 | | return false; |
1470 | | } |
1471 | | |
1472 | | std::unique_ptr<BYTE[]> pDecodedSignerInfoBuf(new BYTE[nDecodedSignerInfoLen]); |
1473 | | |
1474 | | if (!CryptMsgGetParam(hDecodedMsg, CMSG_SIGNER_INFO_PARAM, 0, pDecodedSignerInfoBuf.get(), &nDecodedSignerInfoLen)) |
1475 | | { |
1476 | | SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_SIGNER_INFO_PARAM) failed: " << comphelper::WindowsErrorString(GetLastError())); |
1477 | | CryptMsgClose(hDecodedMsg); |
1478 | | return false; |
1479 | | } |
1480 | | |
1481 | | CMSG_SIGNER_INFO *pDecodedSignerInfo = reinterpret_cast<CMSG_SIGNER_INFO *>(pDecodedSignerInfoBuf.get()); |
1482 | | |
1483 | | CRYPT_TIMESTAMP_PARA aTsPara; |
1484 | | unsigned int nNonce = comphelper::rng::uniform_uint_distribution(0, SAL_MAX_UINT32); |
1485 | | |
1486 | | aTsPara.pszTSAPolicyId = nullptr; |
1487 | | aTsPara.fRequestCerts = TRUE; |
1488 | | aTsPara.Nonce.cbData = sizeof(nNonce); |
1489 | | aTsPara.Nonce.pbData = reinterpret_cast<BYTE *>(&nNonce); |
1490 | | aTsPara.cExtension = 0; |
1491 | | aTsPara.rgExtension = nullptr; |
1492 | | |
1493 | | if (!CryptRetrieveTimeStamp(o3tl::toW(m_aSignTSA.getStr()), |
1494 | | 0, |
1495 | | 10000, |
1496 | | szOID_NIST_sha256, |
1497 | | &aTsPara, |
1498 | | pDecodedSignerInfo->EncryptedHash.pbData, |
1499 | | pDecodedSignerInfo->EncryptedHash.cbData, |
1500 | | &pTsContext, |
1501 | | nullptr, |
1502 | | nullptr)) |
1503 | | { |
1504 | | SAL_WARN("svl.crypto", "CryptRetrieveTimeStamp failed: " << comphelper::WindowsErrorString(GetLastError())); |
1505 | | CryptMsgClose(hDecodedMsg); |
1506 | | return false; |
1507 | | } |
1508 | | |
1509 | | SAL_INFO("svl.crypto", "Time stamp size is " << pTsContext->cbEncoded << " bytes"); |
1510 | | |
1511 | | CRYPT_INTEGER_BLOB aTimestampBlob; |
1512 | | aTimestampBlob.cbData = pTsContext->cbEncoded; |
1513 | | aTimestampBlob.pbData = pTsContext->pbEncoded; |
1514 | | |
1515 | | CRYPT_ATTRIBUTE aTimestampAttribute; |
1516 | | aTimestampAttribute.pszObjId = const_cast<LPSTR>( |
1517 | | "1.2.840.113549.1.9.16.2.14"); |
1518 | | aTimestampAttribute.cValue = 1; |
1519 | | aTimestampAttribute.rgValue = &aTimestampBlob; |
1520 | | |
1521 | | DWORD nEncodedTsAttributeLen = 0; |
1522 | | if (!CryptEncodeObject(PKCS_7_ASN_ENCODING, PKCS_ATTRIBUTE, &aTimestampAttribute, nullptr, &nEncodedTsAttributeLen)) |
1523 | | { |
1524 | | SAL_WARN("svl.crypto", "CryptEncodeObject(PKCS_ATTRIBUTE) failed: " << comphelper::WindowsErrorString(GetLastError())); |
1525 | | CryptMsgClose(hDecodedMsg); |
1526 | | return false; |
1527 | | } |
1528 | | |
1529 | | std::unique_ptr<BYTE[]> pEncodedTsAttributeBuf(new BYTE[nEncodedTsAttributeLen]); |
1530 | | |
1531 | | CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR_PARA aAddTsAttrPara; |
1532 | | aAddTsAttrPara.dwSignerIndex = 0; |
1533 | | aAddTsAttrPara.cbSize = sizeof(aAddTsAttrPara); |
1534 | | |
1535 | | if (!CryptEncodeObject(PKCS_7_ASN_ENCODING, PKCS_ATTRIBUTE, &aTimestampAttribute, pEncodedTsAttributeBuf.get(), &nEncodedTsAttributeLen)) |
1536 | | { |
1537 | | SAL_WARN("svl.crypto", "CryptEncodeObject(PKCS_ATTRIBUTE) failed: " << comphelper::WindowsErrorString(GetLastError())); |
1538 | | CryptMsgClose(hDecodedMsg); |
1539 | | return false; |
1540 | | } |
1541 | | |
1542 | | aAddTsAttrPara.blob.cbData = nEncodedTsAttributeLen; |
1543 | | aAddTsAttrPara.blob.pbData = pEncodedTsAttributeBuf.get(); |
1544 | | |
1545 | | if (!CryptMsgControl(hDecodedMsg, 0, CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR, &aAddTsAttrPara)) |
1546 | | { |
1547 | | SAL_WARN("svl.crypto", "CryptMsgControl(CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR) failed: " << comphelper::WindowsErrorString(GetLastError())); |
1548 | | CryptMsgClose(hDecodedMsg); |
1549 | | return false; |
1550 | | } |
1551 | | hMsg = hDecodedMsg; |
1552 | | dwEncodedMessageParamType = CMSG_ENCODED_MESSAGE; |
1553 | | } |
1554 | | |
1555 | | DWORD nSigLen = 0; |
1556 | | |
1557 | | if (!CryptMsgGetParam(hMsg, dwEncodedMessageParamType, 0, nullptr, &nSigLen)) |
1558 | | { |
1559 | | SAL_WARN("svl.crypto", "Reading the encoded message via CryptMsgGetParam() failed: " << comphelper::WindowsErrorString(GetLastError())); |
1560 | | if (pTsContext) |
1561 | | CryptMemFree(pTsContext); |
1562 | | CryptMsgClose(hMsg); |
1563 | | return false; |
1564 | | } |
1565 | | |
1566 | | if (nSigLen*2 > MAX_SIGNATURE_CONTENT_LENGTH) |
1567 | | { |
1568 | | SAL_WARN("svl.crypto", "Signature requires more space (" << nSigLen*2 << ") than we reserved (" << MAX_SIGNATURE_CONTENT_LENGTH << ")"); |
1569 | | if (pTsContext) |
1570 | | CryptMemFree(pTsContext); |
1571 | | CryptMsgClose(hMsg); |
1572 | | return false; |
1573 | | } |
1574 | | |
1575 | | SAL_INFO("svl.crypto", "Signature size is " << nSigLen << " bytes"); |
1576 | | std::unique_ptr<BYTE[]> pSig(new BYTE[nSigLen]); |
1577 | | |
1578 | | if (!CryptMsgGetParam(hMsg, dwEncodedMessageParamType, 0, pSig.get(), &nSigLen)) |
1579 | | { |
1580 | | SAL_WARN("svl.crypto", "Reading the encoded message via CryptMsgGetParam() failed: " << comphelper::WindowsErrorString(GetLastError())); |
1581 | | if (pTsContext) |
1582 | | CryptMemFree(pTsContext); |
1583 | | CryptMsgClose(hMsg); |
1584 | | return false; |
1585 | | } |
1586 | | |
1587 | | // Release resources |
1588 | | if (pTsContext) |
1589 | | CryptMemFree(pTsContext); |
1590 | | CryptMsgClose(hMsg); |
1591 | | |
1592 | | for (unsigned int i = 0; i < nSigLen ; i++) |
1593 | | appendHex(pSig[i], rCMSHexBuffer); |
1594 | | |
1595 | | return true; |
1596 | | #endif // USE_CRYPTO_MSCAPI |
1597 | | #endif // USE_CRYPTO_ANY |
1598 | 0 | } |
1599 | | |
1600 | | namespace |
1601 | | { |
1602 | | #if USE_CRYPTO_NSS |
1603 | | /// Similar to NSS_CMSAttributeArray_FindAttrByOidTag(), but works directly with a SECOidData. |
1604 | | NSSCMSAttribute* CMSAttributeArray_FindAttrByOidData(NSSCMSAttribute** attrs, SECOidData const * oid, PRBool only) |
1605 | | { |
1606 | | NSSCMSAttribute* attr1, *attr2; |
1607 | | |
1608 | | if (attrs == nullptr) |
1609 | | return nullptr; |
1610 | | |
1611 | | if (oid == nullptr) |
1612 | | return nullptr; |
1613 | | |
1614 | | while ((attr1 = *attrs++) != nullptr) |
1615 | | { |
1616 | | if (attr1->type.len == oid->oid.len && PORT_Memcmp(attr1->type.data, |
1617 | | oid->oid.data, |
1618 | | oid->oid.len) == 0) |
1619 | | break; |
1620 | | } |
1621 | | |
1622 | | if (attr1 == nullptr) |
1623 | | return nullptr; |
1624 | | |
1625 | | if (!only) |
1626 | | return attr1; |
1627 | | |
1628 | | while ((attr2 = *attrs++) != nullptr) |
1629 | | { |
1630 | | if (attr2->type.len == oid->oid.len && PORT_Memcmp(attr2->type.data, |
1631 | | oid->oid.data, |
1632 | | oid->oid.len) == 0) |
1633 | | break; |
1634 | | } |
1635 | | |
1636 | | if (attr2 != nullptr) |
1637 | | return nullptr; |
1638 | | |
1639 | | return attr1; |
1640 | | } |
1641 | | |
1642 | | #elif USE_CRYPTO_MSCAPI // ends USE_CRYPTO_NSS |
1643 | | |
1644 | | /// Verifies a non-detached signature using CryptoAPI. |
1645 | | bool VerifyNonDetachedSignature(const std::vector<unsigned char>& aData, const std::vector<BYTE>& rExpectedHash) |
1646 | | { |
1647 | | HCRYPTPROV hProv = 0; |
1648 | | if (!CryptAcquireContextW(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) |
1649 | | { |
1650 | | SAL_WARN("svl.crypto", "CryptAcquireContext() failed"); |
1651 | | return false; |
1652 | | } |
1653 | | |
1654 | | HCRYPTHASH hHash = 0; |
1655 | | if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) |
1656 | | { |
1657 | | SAL_WARN("svl.crypto", "CryptCreateHash() failed"); |
1658 | | return false; |
1659 | | } |
1660 | | |
1661 | | if (!CryptHashData(hHash, aData.data(), aData.size(), 0)) |
1662 | | { |
1663 | | SAL_WARN("svl.crypto", "CryptHashData() failed"); |
1664 | | return false; |
1665 | | } |
1666 | | |
1667 | | DWORD nActualHash = 0; |
1668 | | if (!CryptGetHashParam(hHash, HP_HASHVAL, nullptr, &nActualHash, 0)) |
1669 | | { |
1670 | | SAL_WARN("svl.crypto", "CryptGetHashParam() failed to provide the hash length"); |
1671 | | return false; |
1672 | | } |
1673 | | |
1674 | | std::vector<unsigned char> aActualHash(nActualHash); |
1675 | | if (!CryptGetHashParam(hHash, HP_HASHVAL, aActualHash.data(), &nActualHash, 0)) |
1676 | | { |
1677 | | SAL_WARN("svl.crypto", "CryptGetHashParam() failed to provide the hash"); |
1678 | | return false; |
1679 | | } |
1680 | | |
1681 | | CryptDestroyHash(hHash); |
1682 | | CryptReleaseContext(hProv, 0); |
1683 | | |
1684 | | return aActualHash.size() == rExpectedHash.size() && |
1685 | | !std::memcmp(aActualHash.data(), rExpectedHash.data(), aActualHash.size()); |
1686 | | } |
1687 | | |
1688 | | OUString GetSubjectName(PCCERT_CONTEXT pCertContext) |
1689 | | { |
1690 | | OUString subjectName; |
1691 | | |
1692 | | // Get Subject name size. |
1693 | | DWORD dwData = CertGetNameStringW(pCertContext, |
1694 | | CERT_NAME_SIMPLE_DISPLAY_TYPE, |
1695 | | 0, |
1696 | | nullptr, |
1697 | | nullptr, |
1698 | | 0); |
1699 | | if (!dwData) |
1700 | | { |
1701 | | SAL_WARN("svl.crypto", "ValidateSignature: CertGetNameString failed"); |
1702 | | return subjectName; |
1703 | | } |
1704 | | |
1705 | | // Allocate memory for subject name. |
1706 | | LPWSTR szName = static_cast<LPWSTR>( |
1707 | | LocalAlloc(LPTR, dwData * sizeof(WCHAR))); |
1708 | | if (!szName) |
1709 | | { |
1710 | | SAL_WARN("svl.crypto", "ValidateSignature: Unable to allocate memory for subject name"); |
1711 | | return subjectName; |
1712 | | } |
1713 | | |
1714 | | // Get subject name. |
1715 | | if (!CertGetNameStringW(pCertContext, |
1716 | | CERT_NAME_SIMPLE_DISPLAY_TYPE, |
1717 | | 0, |
1718 | | nullptr, |
1719 | | szName, |
1720 | | dwData)) |
1721 | | { |
1722 | | LocalFree(szName); |
1723 | | SAL_WARN("svl.crypto", "ValidateSignature: CertGetNameString failed"); |
1724 | | return subjectName; |
1725 | | } |
1726 | | |
1727 | | subjectName = o3tl::toU(szName); |
1728 | | LocalFree(szName); |
1729 | | |
1730 | | return subjectName; |
1731 | | } |
1732 | | #endif // USE_CRYPTO_MSCAPI |
1733 | | |
1734 | | #if USE_CRYPTO_NSS |
1735 | | void ensureNssInit() |
1736 | | { |
1737 | | // e.g. tdf#122599 ensure NSS library is initialized for NSS_CMSMessage_CreateFromDER |
1738 | | css::uno::Reference<css::xml::crypto::XNSSInitializer> |
1739 | | xNSSInitializer = css::xml::crypto::NSSInitializer::create(comphelper::getProcessComponentContext()); |
1740 | | |
1741 | | // this calls NSS_Init |
1742 | | xNSSInitializer->getDigestContext(css::xml::crypto::DigestID::SHA256, |
1743 | | uno::Sequence<beans::NamedValue>()); |
1744 | | } |
1745 | | #endif |
1746 | | } // anonymous namespace |
1747 | | |
1748 | | bool Signing::Verify(const std::vector<unsigned char>& aData, |
1749 | | const bool bNonDetached, |
1750 | | const std::vector<unsigned char>& aSignature, |
1751 | | SignatureInformation& rInformation) |
1752 | 0 | { |
1753 | | #if USE_CRYPTO_NSS |
1754 | | // ensure NSS_Init() is called before using NSS_CMSMessage_CreateFromDER |
1755 | | static std::once_flag aInitOnce; |
1756 | | std::call_once(aInitOnce, ensureNssInit); |
1757 | | |
1758 | | // Validate the signature. |
1759 | | SECItem aSignatureItem; |
1760 | | aSignatureItem.data = const_cast<unsigned char*>(aSignature.data()); |
1761 | | aSignatureItem.len = aSignature.size(); |
1762 | | NSSCMSMessage* pCMSMessage = NSS_CMSMessage_CreateFromDER(&aSignatureItem, |
1763 | | /*cb=*/nullptr, |
1764 | | /*cb_arg=*/nullptr, |
1765 | | /*pwfn=*/nullptr, |
1766 | | /*pwfn_arg=*/nullptr, |
1767 | | /*decrypt_key_cb=*/nullptr, |
1768 | | /*decrypt_key_cb_arg=*/nullptr); |
1769 | | if (!NSS_CMSMessage_IsSigned(pCMSMessage)) |
1770 | | { |
1771 | | SAL_WARN("svl.crypto", "ValidateSignature: message is not signed"); |
1772 | | return false; |
1773 | | } |
1774 | | |
1775 | | NSSCMSContentInfo* pCMSContentInfo = NSS_CMSMessage_ContentLevel(pCMSMessage, 0); |
1776 | | if (!pCMSContentInfo) |
1777 | | { |
1778 | | SAL_WARN("svl.crypto", "ValidateSignature: NSS_CMSMessage_ContentLevel() failed"); |
1779 | | return false; |
1780 | | } |
1781 | | |
1782 | | auto pCMSSignedData = static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(pCMSContentInfo)); |
1783 | | if (!pCMSSignedData) |
1784 | | { |
1785 | | SAL_WARN("svl.crypto", "ValidateSignature: NSS_CMSContentInfo_GetContent() failed"); |
1786 | | return false; |
1787 | | } |
1788 | | |
1789 | | // Import certificates from the signed data temporarily, so it'll be |
1790 | | // possible to verify the signature, even if we didn't have the certificate |
1791 | | // previously. |
1792 | | std::vector<CERTCertificate*> aDocumentCertificates; |
1793 | | if (auto aCerts = pCMSSignedData->rawCerts) { |
1794 | | while (*aCerts) |
1795 | | aDocumentCertificates.push_back(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), *aCerts++, nullptr, 0, 0)); |
1796 | | } |
1797 | | |
1798 | | NSSCMSSignerInfo* pCMSSignerInfo = NSS_CMSSignedData_GetSignerInfo(pCMSSignedData, 0); |
1799 | | if (!pCMSSignerInfo) |
1800 | | { |
1801 | | SAL_WARN("svl.crypto", "ValidateSignature: NSS_CMSSignedData_GetSignerInfo() failed"); |
1802 | | return false; |
1803 | | } |
1804 | | |
1805 | | auto aDigestAlgs = NSS_CMSSignedData_GetDigestAlgs(pCMSSignedData); |
1806 | | if (!aDigestAlgs || !*aDigestAlgs) { |
1807 | | SAL_WARN("svl.crypto", "ValidateSignature: digestAlgorithms missing"); |
1808 | | return false; |
1809 | | } |
1810 | | |
1811 | | SECItem aAlgorithm = aDigestAlgs[0]->algorithm; |
1812 | | SECOidTag eOidTag = SECOID_FindOIDTag(&aAlgorithm); |
1813 | | |
1814 | | // Map a sign algorithm to a digest algorithm. |
1815 | | // See NSS_CMSUtil_MapSignAlgs(), which is private to us. |
1816 | | switch (eOidTag) |
1817 | | { |
1818 | | case SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION: |
1819 | | eOidTag = SEC_OID_SHA1; |
1820 | | break; |
1821 | | case SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION: |
1822 | | eOidTag = SEC_OID_SHA256; |
1823 | | break; |
1824 | | case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION: |
1825 | | eOidTag = SEC_OID_SHA512; |
1826 | | break; |
1827 | | default: |
1828 | | break; |
1829 | | } |
1830 | | |
1831 | | HASH_HashType eHashType = HASH_GetHashTypeByOidTag(eOidTag); |
1832 | | HASHContext* pHASHContext = HASH_Create(bNonDetached ? HASH_AlgSHA1 : eHashType); |
1833 | | if (!pHASHContext) |
1834 | | { |
1835 | | SAL_WARN("svl.crypto", "ValidateSignature: HASH_Create() failed"); |
1836 | | return false; |
1837 | | } |
1838 | | |
1839 | | // We have a hash, update it with the byte ranges. |
1840 | | HASH_Update(pHASHContext, aData.data(), aData.size()); |
1841 | | |
1842 | | // Find out what is the expected length of the hash. |
1843 | | unsigned int nMaxResultLen = 0; |
1844 | | switch (eOidTag) |
1845 | | { |
1846 | | case SEC_OID_SHA1: |
1847 | | nMaxResultLen = comphelper::SHA1_HASH_LENGTH; |
1848 | | rInformation.nDigestID = xml::crypto::DigestID::SHA1; |
1849 | | break; |
1850 | | case SEC_OID_SHA256: |
1851 | | nMaxResultLen = comphelper::SHA256_HASH_LENGTH; |
1852 | | rInformation.nDigestID = xml::crypto::DigestID::SHA256; |
1853 | | break; |
1854 | | case SEC_OID_SHA512: |
1855 | | nMaxResultLen = comphelper::SHA512_HASH_LENGTH; |
1856 | | rInformation.nDigestID = xml::crypto::DigestID::SHA512; |
1857 | | break; |
1858 | | default: |
1859 | | SAL_WARN("svl.crypto", "ValidateSignature: unrecognized algorithm"); |
1860 | | return false; |
1861 | | } |
1862 | | |
1863 | | auto pActualResultBuffer = static_cast<unsigned char*>(PORT_Alloc(nMaxResultLen)); |
1864 | | unsigned int nActualResultLen; |
1865 | | HASH_End(pHASHContext, pActualResultBuffer, &nActualResultLen, nMaxResultLen); |
1866 | | |
1867 | | CERTCertificate* pCertificate = NSS_CMSSignerInfo_GetSigningCertificate(pCMSSignerInfo, CERT_GetDefaultCertDB()); |
1868 | | if (!pCertificate) |
1869 | | { |
1870 | | SAL_WARN("svl.crypto", "ValidateSignature: NSS_CMSSignerInfo_GetSigningCertificate() failed"); |
1871 | | return false; |
1872 | | } |
1873 | | else |
1874 | | { |
1875 | | uno::Sequence<sal_Int8> aDerCert(pCertificate->derCert.len); |
1876 | | auto aDerCertRange = asNonConstRange(aDerCert); |
1877 | | for (size_t i = 0; i < pCertificate->derCert.len; ++i) |
1878 | | aDerCertRange[i] = pCertificate->derCert.data[i]; |
1879 | | OUStringBuffer aBuffer; |
1880 | | comphelper::Base64::encode(aBuffer, aDerCert); |
1881 | | SignatureInformation::X509Data temp; |
1882 | | temp.emplace_back(); |
1883 | | temp.back().X509Certificate = aBuffer.makeStringAndClear(); |
1884 | | temp.back().X509Subject = OUString(pCertificate->subjectName, PL_strlen(pCertificate->subjectName), RTL_TEXTENCODING_UTF8); |
1885 | | rInformation.X509Datas.clear(); |
1886 | | rInformation.X509Datas.emplace_back(temp); |
1887 | | } |
1888 | | |
1889 | | PRTime nSigningTime; |
1890 | | // This may fail, in which case the date should be taken from the PDF's dictionary's "M" key, |
1891 | | // so not critical for PDF at least. |
1892 | | if (NSS_CMSSignerInfo_GetSigningTime(pCMSSignerInfo, &nSigningTime) == SECSuccess) |
1893 | | { |
1894 | | // First convert the UTC UNIX timestamp to a tools::DateTime. |
1895 | | // nSigningTime is in microseconds. |
1896 | | DateTime aDateTime = DateTime::CreateFromUnixTime(static_cast<double>(nSigningTime) / 1000000); |
1897 | | |
1898 | | // Then convert to a local UNO DateTime. |
1899 | | aDateTime.ConvertToLocalTime(); |
1900 | | rInformation.stDateTime = aDateTime.GetUNODateTime(); |
1901 | | if (rInformation.ouDateTime.isEmpty()) |
1902 | | { |
1903 | | OUStringBuffer rBuffer; |
1904 | | rBuffer.append(static_cast<sal_Int32>(aDateTime.GetYear())); |
1905 | | rBuffer.append('-'); |
1906 | | if (aDateTime.GetMonth() < 10) |
1907 | | rBuffer.append('0'); |
1908 | | rBuffer.append(static_cast<sal_Int32>(aDateTime.GetMonth())); |
1909 | | rBuffer.append('-'); |
1910 | | if (aDateTime.GetDay() < 10) |
1911 | | rBuffer.append('0'); |
1912 | | rBuffer.append(static_cast<sal_Int32>(aDateTime.GetDay())); |
1913 | | rInformation.ouDateTime = rBuffer.makeStringAndClear(); |
1914 | | } |
1915 | | } |
1916 | | |
1917 | | // Check if we have a signing certificate attribute. |
1918 | | SECOidData aOidData; |
1919 | | auto cert_oid_buffer = std::to_array(OID_SIGNINGCERTIFICATEV2); |
1920 | | aOidData.oid.data = cert_oid_buffer.data(); |
1921 | | aOidData.oid.len = cert_oid_buffer.size(); |
1922 | | /* |
1923 | | * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::= |
1924 | | * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) |
1925 | | * smime(16) id-aa(2) 47 } |
1926 | | */ |
1927 | | aOidData.offset = SEC_OID_UNKNOWN; |
1928 | | aOidData.desc = "id-aa-signingCertificateV2"; |
1929 | | aOidData.mechanism = CKM_SHA_1; |
1930 | | aOidData.supportedExtension = UNSUPPORTED_CERT_EXTENSION; |
1931 | | NSSCMSAttribute* pAttribute = CMSAttributeArray_FindAttrByOidData(pCMSSignerInfo->authAttr, &aOidData, PR_TRUE); |
1932 | | if (pAttribute) |
1933 | | rInformation.bHasSigningCertificate = true; |
1934 | | |
1935 | | SECItem aSignedDigestItem {siBuffer, nullptr, 0}; |
1936 | | |
1937 | | SECItem* pContentInfoContentData = pCMSSignedData->contentInfo.content.data; |
1938 | | if (pContentInfoContentData && pContentInfoContentData->data) |
1939 | | { |
1940 | | // Not a detached signature. |
1941 | | if (bNonDetached && nActualResultLen == pContentInfoContentData->len && |
1942 | | !std::memcmp(pActualResultBuffer, pContentInfoContentData->data, nActualResultLen) && |
1943 | | HASH_HashBuf(eHashType, pActualResultBuffer, pContentInfoContentData->data, nActualResultLen) == SECSuccess) |
1944 | | { |
1945 | | aSignedDigestItem.data = pActualResultBuffer; |
1946 | | aSignedDigestItem.len = nMaxResultLen; |
1947 | | } |
1948 | | } |
1949 | | else if (!bNonDetached) |
1950 | | { |
1951 | | // Detached, the usual case. |
1952 | | aSignedDigestItem.data = pActualResultBuffer; |
1953 | | aSignedDigestItem.len = nActualResultLen; |
1954 | | } |
1955 | | |
1956 | | if (aSignedDigestItem.data && NSS_CMSSignerInfo_Verify(pCMSSignerInfo, &aSignedDigestItem, nullptr) == SECSuccess) |
1957 | | rInformation.nStatus = xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED; |
1958 | | |
1959 | | // Everything went fine |
1960 | | PORT_Free(pActualResultBuffer); |
1961 | | HASH_Destroy(pHASHContext); |
1962 | | NSS_CMSSignerInfo_Destroy(pCMSSignerInfo); |
1963 | | for (auto pDocumentCertificate : aDocumentCertificates) |
1964 | | CERT_DestroyCertificate(pDocumentCertificate); |
1965 | | |
1966 | | return true; |
1967 | | |
1968 | | #elif USE_CRYPTO_MSCAPI // ends USE_CRYPTO_NSS |
1969 | | |
1970 | | // Open a message for decoding. |
1971 | | HCRYPTMSG hMsg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, |
1972 | | CMSG_DETACHED_FLAG, |
1973 | | 0, |
1974 | | 0, |
1975 | | nullptr, |
1976 | | nullptr); |
1977 | | if (!hMsg) |
1978 | | { |
1979 | | SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgOpenToDecode() failed"); |
1980 | | return false; |
1981 | | } |
1982 | | |
1983 | | // Update the message with the encoded header blob. |
1984 | | if (!CryptMsgUpdate(hMsg, aSignature.data(), aSignature.size(), TRUE)) |
1985 | | { |
1986 | | SAL_WARN("svl.crypto", "ValidateSignature, CryptMsgUpdate() for the header failed: " << comphelper::WindowsErrorString(GetLastError())); |
1987 | | return false; |
1988 | | } |
1989 | | |
1990 | | if (!bNonDetached) |
1991 | | { |
1992 | | // Update the message with the content blob. |
1993 | | if (!CryptMsgUpdate(hMsg, aData.data(), aData.size(), FALSE)) |
1994 | | { |
1995 | | SAL_WARN("svl.crypto", "ValidateSignature, CryptMsgUpdate() for the content failed: " << comphelper::WindowsErrorString(GetLastError())); |
1996 | | return false; |
1997 | | } |
1998 | | |
1999 | | if (!CryptMsgUpdate(hMsg, nullptr, 0, TRUE)) |
2000 | | { |
2001 | | SAL_WARN("svl.crypto", "ValidateSignature, CryptMsgUpdate() for the last content failed: " << comphelper::WindowsErrorString(GetLastError())); |
2002 | | return false; |
2003 | | } |
2004 | | } |
2005 | | // Get the CRYPT_ALGORITHM_IDENTIFIER from the message. |
2006 | | DWORD nDigestID = 0; |
2007 | | if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_HASH_ALGORITHM_PARAM, 0, nullptr, &nDigestID)) |
2008 | | { |
2009 | | SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed: " << comphelper::WindowsErrorString(GetLastError())); |
2010 | | return false; |
2011 | | } |
2012 | | std::unique_ptr<BYTE[]> pDigestBytes(new BYTE[nDigestID]); |
2013 | | if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_HASH_ALGORITHM_PARAM, 0, pDigestBytes.get(), &nDigestID)) |
2014 | | { |
2015 | | SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed: " << comphelper::WindowsErrorString(GetLastError())); |
2016 | | return false; |
2017 | | } |
2018 | | auto pDigestID = reinterpret_cast<CRYPT_ALGORITHM_IDENTIFIER*>(pDigestBytes.get()); |
2019 | | if (std::string_view(szOID_NIST_sha256) == pDigestID->pszObjId) |
2020 | | rInformation.nDigestID = xml::crypto::DigestID::SHA256; |
2021 | | else if (std::string_view(szOID_RSA_SHA1RSA) == pDigestID->pszObjId || std::string_view(szOID_OIWSEC_sha1) == pDigestID->pszObjId) |
2022 | | rInformation.nDigestID = xml::crypto::DigestID::SHA1; |
2023 | | else |
2024 | | // Don't error out here, we can still verify the message digest correctly, just the digest ID won't be set. |
2025 | | SAL_WARN("svl.crypto", "ValidateSignature: unhandled algorithm identifier '"<<pDigestID->pszObjId<<"'"); |
2026 | | |
2027 | | // Get the signer CERT_INFO from the message. |
2028 | | DWORD nSignerCertInfo = 0; |
2029 | | if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_CERT_INFO_PARAM, 0, nullptr, &nSignerCertInfo)) |
2030 | | { |
2031 | | SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed"); |
2032 | | return false; |
2033 | | } |
2034 | | std::unique_ptr<BYTE[]> pSignerCertInfoBuf(new BYTE[nSignerCertInfo]); |
2035 | | if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_CERT_INFO_PARAM, 0, pSignerCertInfoBuf.get(), &nSignerCertInfo)) |
2036 | | { |
2037 | | SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed"); |
2038 | | return false; |
2039 | | } |
2040 | | PCERT_INFO pSignerCertInfo = reinterpret_cast<PCERT_INFO>(pSignerCertInfoBuf.get()); |
2041 | | |
2042 | | // Open a certificate store in memory using CERT_STORE_PROV_MSG, which |
2043 | | // initializes it with the certificates from the message. |
2044 | | HCERTSTORE hStoreHandle = CertOpenStore(CERT_STORE_PROV_MSG, |
2045 | | PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, |
2046 | | 0, |
2047 | | 0, |
2048 | | hMsg); |
2049 | | if (!hStoreHandle) |
2050 | | { |
2051 | | SAL_WARN("svl.crypto", "ValidateSignature: CertOpenStore() failed"); |
2052 | | return false; |
2053 | | } |
2054 | | |
2055 | | // Find the signer's certificate in the store. |
2056 | | PCCERT_CONTEXT pSignerCertContext = CertGetSubjectCertificateFromStore(hStoreHandle, |
2057 | | PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, |
2058 | | pSignerCertInfo); |
2059 | | if (!pSignerCertContext) |
2060 | | { |
2061 | | SAL_WARN("svl.crypto", "ValidateSignature: CertGetSubjectCertificateFromStore() failed"); |
2062 | | return false; |
2063 | | } |
2064 | | else |
2065 | | { |
2066 | | // Write rInformation.ouX509Certificate. |
2067 | | uno::Sequence<sal_Int8> aDerCert(pSignerCertContext->cbCertEncoded); |
2068 | | std::copy_n(pSignerCertContext->pbCertEncoded, pSignerCertContext->cbCertEncoded, |
2069 | | aDerCert.getArray()); |
2070 | | OUStringBuffer aBuffer; |
2071 | | comphelper::Base64::encode(aBuffer, aDerCert); |
2072 | | SignatureInformation::X509Data temp; |
2073 | | temp.emplace_back(); |
2074 | | temp.back().X509Certificate = aBuffer.makeStringAndClear(); |
2075 | | temp.back().X509Subject = GetSubjectName(pSignerCertContext); |
2076 | | rInformation.X509Datas.clear(); |
2077 | | rInformation.X509Datas.emplace_back(temp); |
2078 | | } |
2079 | | |
2080 | | std::vector<BYTE> aContentParam; |
2081 | | |
2082 | | if (bNonDetached) |
2083 | | { |
2084 | | // Not a detached signature. |
2085 | | DWORD nContentParam = 0; |
2086 | | if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, nullptr, &nContentParam)) |
2087 | | { |
2088 | | SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed"); |
2089 | | return false; |
2090 | | } |
2091 | | |
2092 | | aContentParam.resize(nContentParam); |
2093 | | if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, aContentParam.data(), &nContentParam)) |
2094 | | { |
2095 | | SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed"); |
2096 | | return false; |
2097 | | } |
2098 | | } |
2099 | | |
2100 | | if (!bNonDetached || VerifyNonDetachedSignature(aData, aContentParam)) |
2101 | | { |
2102 | | // Use the CERT_INFO from the signer certificate to verify the signature. |
2103 | | if (CryptMsgControl(hMsg, 0, CMSG_CTRL_VERIFY_SIGNATURE, pSignerCertContext->pCertInfo)) |
2104 | | rInformation.nStatus = xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED; |
2105 | | } |
2106 | | |
2107 | | // Check if we have a signing certificate attribute. |
2108 | | DWORD nSignedAttributes = 0; |
2109 | | if (CryptMsgGetParam(hMsg, CMSG_SIGNER_AUTH_ATTR_PARAM, 0, nullptr, &nSignedAttributes)) |
2110 | | { |
2111 | | std::unique_ptr<BYTE[]> pSignedAttributesBuf(new BYTE[nSignedAttributes]); |
2112 | | if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_AUTH_ATTR_PARAM, 0, pSignedAttributesBuf.get(), &nSignedAttributes)) |
2113 | | { |
2114 | | SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() authenticated failed"); |
2115 | | return false; |
2116 | | } |
2117 | | auto pSignedAttributes = reinterpret_cast<PCRYPT_ATTRIBUTES>(pSignedAttributesBuf.get()); |
2118 | | for (size_t nAttr = 0; nAttr < pSignedAttributes->cAttr; ++nAttr) |
2119 | | { |
2120 | | CRYPT_ATTRIBUTE& rAttr = pSignedAttributes->rgAttr[nAttr]; |
2121 | | /* |
2122 | | * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::= |
2123 | | * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) |
2124 | | * smime(16) id-aa(2) 47 } |
2125 | | */ |
2126 | | if (std::string_view("1.2.840.113549.1.9.16.2.47") == rAttr.pszObjId) |
2127 | | { |
2128 | | rInformation.bHasSigningCertificate = true; |
2129 | | break; |
2130 | | } |
2131 | | } |
2132 | | } |
2133 | | |
2134 | | // Get the unauthorized attributes. |
2135 | | nSignedAttributes = 0; |
2136 | | if (CryptMsgGetParam(hMsg, CMSG_SIGNER_UNAUTH_ATTR_PARAM, 0, nullptr, &nSignedAttributes)) |
2137 | | { |
2138 | | std::unique_ptr<BYTE[]> pSignedAttributesBuf(new BYTE[nSignedAttributes]); |
2139 | | if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_UNAUTH_ATTR_PARAM, 0, pSignedAttributesBuf.get(), &nSignedAttributes)) |
2140 | | { |
2141 | | SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() unauthenticated failed"); |
2142 | | return false; |
2143 | | } |
2144 | | auto pSignedAttributes = reinterpret_cast<PCRYPT_ATTRIBUTES>(pSignedAttributesBuf.get()); |
2145 | | for (size_t nAttr = 0; nAttr < pSignedAttributes->cAttr; ++nAttr) |
2146 | | { |
2147 | | CRYPT_ATTRIBUTE& rAttr = pSignedAttributes->rgAttr[nAttr]; |
2148 | | // Timestamp blob |
2149 | | if (std::string_view("1.2.840.113549.1.9.16.2.14") == rAttr.pszObjId) |
2150 | | { |
2151 | | PCRYPT_TIMESTAMP_CONTEXT pTsContext; |
2152 | | if (!CryptVerifyTimeStampSignature(rAttr.rgValue->pbData, rAttr.rgValue->cbData, nullptr, 0, nullptr, &pTsContext, nullptr, nullptr)) |
2153 | | { |
2154 | | SAL_WARN("svl.crypto", "CryptMsgUpdate failed: " << comphelper::WindowsErrorString(GetLastError())); |
2155 | | break; |
2156 | | } |
2157 | | |
2158 | | DateTime aDateTime = DateTime::CreateFromWin32FileDateTime(pTsContext->pTimeStamp->ftTime.dwLowDateTime, pTsContext->pTimeStamp->ftTime.dwHighDateTime); |
2159 | | |
2160 | | // Then convert to a local UNO DateTime. |
2161 | | aDateTime.ConvertToLocalTime(); |
2162 | | rInformation.stDateTime = aDateTime.GetUNODateTime(); |
2163 | | if (rInformation.ouDateTime.isEmpty()) |
2164 | | { |
2165 | | OUStringBuffer rBuffer; |
2166 | | rBuffer.append(static_cast<sal_Int32>(aDateTime.GetYear())); |
2167 | | rBuffer.append('-'); |
2168 | | if (aDateTime.GetMonth() < 10) |
2169 | | rBuffer.append('0'); |
2170 | | rBuffer.append(static_cast<sal_Int32>(aDateTime.GetMonth())); |
2171 | | rBuffer.append('-'); |
2172 | | if (aDateTime.GetDay() < 10) |
2173 | | rBuffer.append('0'); |
2174 | | rBuffer.append(static_cast<sal_Int32>(aDateTime.GetDay())); |
2175 | | rInformation.ouDateTime = rBuffer.makeStringAndClear(); |
2176 | | } |
2177 | | break; |
2178 | | } |
2179 | | } |
2180 | | } |
2181 | | |
2182 | | CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_FORCE_FLAG); |
2183 | | CryptMsgClose(hMsg); |
2184 | | return true; |
2185 | | #else |
2186 | | // Not implemented. |
2187 | 0 | (void)aData; |
2188 | 0 | (void)bNonDetached; |
2189 | 0 | (void)aSignature; |
2190 | 0 | (void)rInformation; |
2191 | 0 | return false; |
2192 | 0 | #endif |
2193 | 0 | } |
2194 | | |
2195 | | bool Signing::Verify(SvStream& rStream, |
2196 | | const std::vector<std::pair<size_t, size_t>>& aByteRanges, |
2197 | | const bool bNonDetached, |
2198 | | const std::vector<unsigned char>& aSignature, |
2199 | | SignatureInformation& rInformation) |
2200 | 0 | { |
2201 | | #if USE_CRYPTO_ANY |
2202 | | std::vector<unsigned char> buffer; |
2203 | | |
2204 | | // Copy the byte ranges into a single buffer. |
2205 | | for (const auto& rByteRange : aByteRanges) |
2206 | | { |
2207 | | rStream.Seek(rByteRange.first); |
2208 | | const size_t size = buffer.size(); |
2209 | | buffer.resize(size + rByteRange.second); |
2210 | | rStream.ReadBytes(buffer.data() + size, rByteRange.second); |
2211 | | } |
2212 | | |
2213 | | return Verify(buffer, bNonDetached, aSignature, rInformation); |
2214 | | |
2215 | | #else |
2216 | | // Not implemented. |
2217 | 0 | (void)rStream; |
2218 | 0 | (void)aByteRanges; |
2219 | 0 | (void)bNonDetached; |
2220 | 0 | (void)aSignature; |
2221 | 0 | (void)rInformation; |
2222 | 0 | return false; |
2223 | 0 | #endif |
2224 | 0 | } |
2225 | | |
2226 | | void Signing::appendHex(sal_Int8 nInt, OStringBuffer& rBuffer) |
2227 | 0 | { |
2228 | 0 | static const char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', |
2229 | 0 | '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; |
2230 | 0 | rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] ); |
2231 | 0 | rBuffer.append( pHexDigits[ nInt & 15 ] ); |
2232 | 0 | } |
2233 | | |
2234 | | bool CertificateOrName::Is() const |
2235 | 0 | { |
2236 | 0 | return m_xCertificate.is() || !m_aName.isEmpty(); |
2237 | 0 | } |
2238 | | } |
2239 | | |
2240 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |