1 | //======================================================================== |
2 | // |
3 | // pdfsig.cc |
4 | // |
5 | // This file is licensed under the GPLv2 or later |
6 | // |
7 | // Copyright 2015 André Guerreiro <aguerreiro1985@gmail.com> |
8 | // Copyright 2015 André Esser <bepandre@hotmail.com> |
9 | // Copyright 2015, 2017-2023 Albert Astals Cid <aacid@kde.org> |
10 | // Copyright 2016 Markus Kilås <digital@markuspage.com> |
11 | // Copyright 2017, 2019 Hans-Ulrich Jüttner <huj@froreich-bioscientia.de> |
12 | // Copyright 2017, 2019 Adrian Johnson <ajohnson@redneon.com> |
13 | // Copyright 2018 Chinmoy Ranjan Pradhan <chinmoyrp65@protonmail.com> |
14 | // Copyright 2019 Alexey Pavlov <alexpux@gmail.com> |
15 | // Copyright 2019. 2023 Oliver Sander <oliver.sander@tu-dresden.de> |
16 | // Copyright 2019 Nelson Efrain A. Cruz <neac03@gmail.com> |
17 | // Copyright 2021 Georgiy Sgibnev <georgiy@sgibnev.com>. Work sponsored by lab50.net. |
18 | // Copyright 2021 Theofilos Intzoglou <int.teo@gmail.com> |
19 | // Copyright 2022 Felix Jung <fxjung@posteo.de> |
20 | // Copyright 2022, 2024 Erich E. Hoover <erich.e.hoover@gmail.com> |
21 | // Copyright 2023, 2024 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk> |
22 | // |
23 | //======================================================================== |
24 | |
25 | #include "config.h" |
26 | #include <poppler-config.h> |
27 | #include <cstdio> |
28 | #include <cstdlib> |
29 | #include <cstddef> |
30 | #include <cstring> |
31 | #include <ctime> |
32 | #include <fstream> |
33 | #include <random> |
34 | #include "parseargs.h" |
35 | #include "Object.h" |
36 | #include "Array.h" |
37 | #include "goo/gbasename.h" |
38 | #include "Page.h" |
39 | #include "PDFDoc.h" |
40 | #include "PDFDocFactory.h" |
41 | #include "DateInfo.h" |
42 | #include "Error.h" |
43 | #include "GlobalParams.h" |
44 | #ifdef ENABLE_NSS3 |
45 | # include "NSSCryptoSignBackend.h" |
46 | #endif |
47 | #include "CryptoSignBackend.h" |
48 | #include "SignatureInfo.h" |
49 | #include "Win32Console.h" |
50 | #include "numberofcharacters.h" |
51 | #include "UTF.h" |
52 | #if __has_include(<libgen.h>) |
53 | # include <libgen.h> |
54 | #endif |
55 | |
56 | #ifdef HAVE_GETTEXT |
57 | # include <libintl.h> |
58 | # include <clocale> |
59 | # define _(STRING) gettext(STRING) |
60 | #else |
61 | # define _(STRING) STRING |
62 | #endif |
63 | |
64 | static const char *getReadableSigState(SignatureValidationStatus sig_vs) |
65 | { |
66 | switch (sig_vs) { |
67 | case SIGNATURE_VALID: |
68 | return "Signature is Valid." ; |
69 | |
70 | case SIGNATURE_INVALID: |
71 | return "Signature is Invalid." ; |
72 | |
73 | case SIGNATURE_DIGEST_MISMATCH: |
74 | return "Digest Mismatch." ; |
75 | |
76 | case SIGNATURE_DECODING_ERROR: |
77 | return "Document isn't signed or corrupted data." ; |
78 | |
79 | case SIGNATURE_NOT_VERIFIED: |
80 | return "Signature has not yet been verified." ; |
81 | |
82 | default: |
83 | return "Unknown Validation Failure." ; |
84 | } |
85 | } |
86 | |
87 | static const char *getReadableCertState(CertificateValidationStatus cert_vs) |
88 | { |
89 | switch (cert_vs) { |
90 | case CERTIFICATE_TRUSTED: |
91 | return "Certificate is Trusted." ; |
92 | |
93 | case CERTIFICATE_UNTRUSTED_ISSUER: |
94 | return "Certificate issuer isn't Trusted." ; |
95 | |
96 | case CERTIFICATE_UNKNOWN_ISSUER: |
97 | return "Certificate issuer is unknown." ; |
98 | |
99 | case CERTIFICATE_REVOKED: |
100 | return "Certificate has been Revoked." ; |
101 | |
102 | case CERTIFICATE_EXPIRED: |
103 | return "Certificate has Expired" ; |
104 | |
105 | case CERTIFICATE_NOT_VERIFIED: |
106 | return "Certificate has not yet been verified." ; |
107 | |
108 | default: |
109 | return "Unknown issue with Certificate or corrupted data." ; |
110 | } |
111 | } |
112 | |
113 | static char *getReadableTime(time_t unix_time) |
114 | { |
115 | char *time_str = (char *)gmalloc(size: 64); |
116 | strftime(s: time_str, maxsize: 64, format: "%b %d %Y %H:%M:%S" , tp: localtime(timer: &unix_time)); |
117 | return time_str; |
118 | } |
119 | |
120 | static bool dumpSignature(int sig_num, int sigCount, FormFieldSignature *s, const char *filename) |
121 | { |
122 | const GooString *signature = s->getSignature(); |
123 | if (!signature) { |
124 | printf(format: "Cannot dump signature #%d\n" , sig_num); |
125 | return false; |
126 | } |
127 | |
128 | const int sigCountLength = numberOfCharacters(n: sigCount); |
129 | // We want format to be {0:s}.sig{1:Xd} where X is sigCountLength |
130 | // since { is the magic character to replace things we need to put it twice where |
131 | // we don't want it to be replaced |
132 | const std::unique_ptr<GooString> format = GooString::format(fmt: "{{0:s}}.sig{{1:{0:d}d}}" , sigCountLength); |
133 | const std::unique_ptr<GooString> path = GooString::format(fmt: format->c_str(), gbasename(filename).c_str(), sig_num); |
134 | printf(format: "Signature #%d (%u bytes) => %s\n" , sig_num, signature->getLength(), path->c_str()); |
135 | std::ofstream outfile(path->c_str(), std::ofstream::binary); |
136 | outfile.write(s: signature->c_str(), n: signature->getLength()); |
137 | outfile.close(); |
138 | |
139 | return true; |
140 | } |
141 | |
142 | static GooString nssDir; |
143 | static GooString nssPassword; |
144 | static char ownerPassword[33] = "\001" ; |
145 | static char userPassword[33] = "\001" ; |
146 | static bool printVersion = false; |
147 | static bool printHelp = false; |
148 | static bool printCryptoSignBackends = false; |
149 | static bool dontVerifyCert = false; |
150 | static bool noOCSPRevocationCheck = false; |
151 | static bool noAppearance = false; |
152 | static bool dumpSignatures = false; |
153 | static bool etsiCAdESdetached = false; |
154 | static char backendString[256] = "" ; |
155 | static char signatureName[256] = "" ; |
156 | static char certNickname[256] = "" ; |
157 | static char password[256] = "" ; |
158 | static char digestName[256] = "SHA256" ; |
159 | static GooString reason; |
160 | static bool listNicknames = false; |
161 | static bool addNewSignature = false; |
162 | static bool useAIACertFetch = false; |
163 | static GooString newSignatureFieldName; |
164 | |
165 | static const ArgDesc argDesc[] = { { .arg: "-nssdir" , .kind: argGooString, .val: &nssDir, .size: 0, .usage: "path to directory of libnss3 database" }, |
166 | { .arg: "-nss-pwd" , .kind: argGooString, .val: &nssPassword, .size: 0, .usage: "password to access the NSS database (if any)" }, |
167 | { .arg: "-nocert" , .kind: argFlag, .val: &dontVerifyCert, .size: 0, .usage: "don't perform certificate validation" }, |
168 | { .arg: "-no-ocsp" , .kind: argFlag, .val: &noOCSPRevocationCheck, .size: 0, .usage: "don't perform online OCSP certificate revocation check" }, |
169 | { .arg: "-no-appearance" , .kind: argFlag, .val: &noAppearance, .size: 0, .usage: "don't add appearance information when signing existing fields" }, |
170 | { .arg: "-aia" , .kind: argFlag, .val: &useAIACertFetch, .size: 0, .usage: "use Authority Information Access (AIA) extension for certificate fetching" }, |
171 | { .arg: "-dump" , .kind: argFlag, .val: &dumpSignatures, .size: 0, .usage: "dump all signatures into current directory" }, |
172 | { .arg: "-add-signature" , .kind: argFlag, .val: &addNewSignature, .size: 0, .usage: "adds a new signature to the document" }, |
173 | { .arg: "-new-signature-field-name" , .kind: argGooString, .val: &newSignatureFieldName, .size: 0, .usage: "field name used for the newly added signature. A random ID will be used if empty" }, |
174 | { .arg: "-sign" , .kind: argString, .val: &signatureName, .size: 256, .usage: "sign the document in the given signature field (by name or number)" }, |
175 | { .arg: "-etsi" , .kind: argFlag, .val: &etsiCAdESdetached, .size: 0, .usage: "create a signature of type ETSI.CAdES.detached instead of adbe.pkcs7.detached" }, |
176 | { .arg: "-backend" , .kind: argString, .val: &backendString, .size: 256, .usage: "use given backend for signing/verification" }, |
177 | { .arg: "-nick" , .kind: argString, .val: &certNickname, .size: 256, .usage: "use the certificate with the given nickname/fingerprint for signing" }, |
178 | { .arg: "-kpw" , .kind: argString, .val: &password, .size: 256, .usage: "password for the signing key (might be missing if the key isn't password protected)" }, |
179 | { .arg: "-digest" , .kind: argString, .val: &digestName, .size: 256, .usage: "name of the digest algorithm (default: SHA256)" }, |
180 | { .arg: "-reason" , .kind: argGooString, .val: &reason, .size: 0, .usage: "reason for signing (default: no reason given)" }, |
181 | { .arg: "-list-nicks" , .kind: argFlag, .val: &listNicknames, .size: 0, .usage: "list available nicknames in the NSS database" }, |
182 | { .arg: "-list-backends" , .kind: argFlag, .val: &printCryptoSignBackends, .size: 0, .usage: "print cryptographic signature backends" }, |
183 | { .arg: "-opw" , .kind: argString, .val: ownerPassword, .size: sizeof(ownerPassword), .usage: "owner password (for encrypted files)" }, |
184 | { .arg: "-upw" , .kind: argString, .val: userPassword, .size: sizeof(userPassword), .usage: "user password (for encrypted files)" }, |
185 | { .arg: "-v" , .kind: argFlag, .val: &printVersion, .size: 0, .usage: "print copyright and version info" }, |
186 | { .arg: "-h" , .kind: argFlag, .val: &printHelp, .size: 0, .usage: "print usage information" }, |
187 | { .arg: "-help" , .kind: argFlag, .val: &printHelp, .size: 0, .usage: "print usage information" }, |
188 | { .arg: "--help" , .kind: argFlag, .val: &printHelp, .size: 0, .usage: "print usage information" }, |
189 | { .arg: "-?" , .kind: argFlag, .val: &printHelp, .size: 0, .usage: "print usage information" }, |
190 | {} }; |
191 | |
192 | static void print_version_usage(bool usage) |
193 | { |
194 | fprintf(stderr, format: "pdfsig version %s\n" , PACKAGE_VERSION); |
195 | fprintf(stderr, format: "%s\n" , popplerCopyright); |
196 | fprintf(stderr, format: "%s\n" , xpdfCopyright); |
197 | if (usage) { |
198 | printUsage(program: "pdfsig" , otherArgs: "<PDF-file> [<output-file>]" , args: argDesc); |
199 | } |
200 | } |
201 | |
202 | static void print_backends() |
203 | { |
204 | fprintf(stderr, format: "pdfsig backends:\n" ); |
205 | for (const auto &backend : CryptoSign::Factory::getAvailable()) { |
206 | switch (backend) { |
207 | case CryptoSign::Backend::Type::NSS3: |
208 | fprintf(stderr, format: "NSS" ); |
209 | break; |
210 | case CryptoSign::Backend::Type::GPGME: |
211 | fprintf(stderr, format: "GPG" ); |
212 | break; |
213 | } |
214 | if (backend == CryptoSign::Factory::getActive()) { |
215 | fprintf(stderr, format: " (active)\n" ); |
216 | } else { |
217 | fprintf(stderr, format: "\n" ); |
218 | } |
219 | } |
220 | } |
221 | |
222 | static std::vector<std::unique_ptr<X509CertificateInfo>> getAvailableSigningCertificates(bool *error) |
223 | { |
224 | #ifdef ENABLE_NSS3 |
225 | bool wrongPassword = false; |
226 | bool passwordNeeded = false; |
227 | auto passwordCallback = [&passwordNeeded, &wrongPassword](const char *) -> char * { |
228 | static bool firstTime = true; |
229 | if (!firstTime) { |
230 | wrongPassword = true; |
231 | return nullptr; |
232 | } |
233 | firstTime = false; |
234 | if (nssPassword.getLength() > 0) { |
235 | return strdup(s: nssPassword.c_str()); |
236 | } else { |
237 | passwordNeeded = true; |
238 | return nullptr; |
239 | } |
240 | }; |
241 | NSSSignatureConfiguration::setNSSPasswordCallback(passwordCallback); |
242 | #endif |
243 | auto backend = CryptoSign::Factory::createActive(); |
244 | if (!backend) { |
245 | *error = true; |
246 | printf(format: "No backends for cryptographic signatures available" ); |
247 | return {}; |
248 | } |
249 | std::vector<std::unique_ptr<X509CertificateInfo>> vCerts = backend->getAvailableSigningCertificates(); |
250 | #ifdef ENABLE_NSS3 |
251 | NSSSignatureConfiguration::setNSSPasswordCallback({}); |
252 | if (passwordNeeded) { |
253 | *error = true; |
254 | printf(format: "Password is needed to access the NSS database.\n" ); |
255 | printf(format: "\tPlease provide one with -nss-pwd.\n" ); |
256 | return {}; |
257 | } |
258 | if (wrongPassword) { |
259 | *error = true; |
260 | printf(format: "Password was not accepted to open the NSS database.\n" ); |
261 | printf(format: "\tPlease provide the correct one with -nss-pwd.\n" ); |
262 | return {}; |
263 | } |
264 | |
265 | #endif |
266 | *error = false; |
267 | return vCerts; |
268 | } |
269 | |
270 | static std::string locationToString(KeyLocation location) |
271 | { |
272 | switch (location) { |
273 | case KeyLocation::Unknown: |
274 | return {}; |
275 | case KeyLocation::Other: |
276 | return "(Other)" ; |
277 | case KeyLocation::Computer: |
278 | return "(Computer)" ; |
279 | case KeyLocation::HardwareToken: |
280 | return "(Hardware Token)" ; |
281 | } |
282 | return {}; |
283 | } |
284 | |
285 | static std::string TextStringToUTF8(const std::string &str) |
286 | { |
287 | const UnicodeMap *utf8Map = globalParams->getUtf8Map(); |
288 | |
289 | std::vector<Unicode> u = TextStringToUCS4(textStr: str); |
290 | |
291 | std::string convertedStr; |
292 | for (auto &c : u) { |
293 | char buf[8]; |
294 | const int n = utf8Map->mapUnicode(u: c, buf, bufSize: sizeof(buf)); |
295 | convertedStr.append(s: buf, n: n); |
296 | } |
297 | |
298 | return convertedStr; |
299 | } |
300 | |
301 | int main(int argc, char *argv[]) |
302 | { |
303 | char *time_str = nullptr; |
304 | globalParams = std::make_unique<GlobalParams>(); |
305 | |
306 | Win32Console win32Console(&argc, &argv); |
307 | |
308 | const bool ok = parseArgs(args: argDesc, argc: &argc, argv); |
309 | |
310 | if (!ok) { |
311 | print_version_usage(usage: true); |
312 | return 99; |
313 | } |
314 | |
315 | if (printVersion) { |
316 | print_version_usage(usage: false); |
317 | return 0; |
318 | } |
319 | |
320 | if (printHelp) { |
321 | print_version_usage(usage: true); |
322 | return 0; |
323 | } |
324 | |
325 | if (strlen(s: backendString) > 0) { |
326 | auto backend = CryptoSign::Factory::typeFromString(string: backendString); |
327 | if (backend) { |
328 | CryptoSign::Factory::setPreferredBackend(backend.value()); |
329 | } else { |
330 | fprintf(stderr, format: "Unsupported backend\n" ); |
331 | return 98; |
332 | } |
333 | } |
334 | |
335 | if (printCryptoSignBackends) { |
336 | print_backends(); |
337 | return 0; |
338 | } |
339 | |
340 | #ifdef ENABLE_NSS3 |
341 | NSSSignatureConfiguration::setNSSDir(nssDir); |
342 | #endif |
343 | |
344 | if (listNicknames) { |
345 | bool getCertsError; |
346 | const std::vector<std::unique_ptr<X509CertificateInfo>> vCerts = getAvailableSigningCertificates(error: &getCertsError); |
347 | if (getCertsError) { |
348 | return 2; |
349 | } else { |
350 | if (vCerts.empty()) { |
351 | printf(format: "There are no certificates available.\n" ); |
352 | } else { |
353 | printf(format: "Certificate nicknames available:\n" ); |
354 | for (auto &cert : vCerts) { |
355 | const GooString &nick = cert->getNickName(); |
356 | const auto location = locationToString(location: cert->getKeyLocation()); |
357 | printf(format: "%s %s\n" , nick.c_str(), location.c_str()); |
358 | } |
359 | } |
360 | } |
361 | return 0; |
362 | } |
363 | |
364 | if (argc < 2) { |
365 | // no filename was given |
366 | print_version_usage(usage: true); |
367 | return 99; |
368 | } |
369 | |
370 | std::unique_ptr<GooString> fileName = std::make_unique<GooString>(args&: argv[1]); |
371 | |
372 | std::optional<GooString> ownerPW, userPW; |
373 | if (ownerPassword[0] != '\001') { |
374 | ownerPW = GooString(ownerPassword); |
375 | } |
376 | if (userPassword[0] != '\001') { |
377 | userPW = GooString(userPassword); |
378 | } |
379 | // open PDF file |
380 | std::unique_ptr<PDFDoc> doc(PDFDocFactory().createPDFDoc(uri: *fileName, ownerPassword: ownerPW, userPassword: userPW)); |
381 | |
382 | if (!doc->isOk()) { |
383 | return 1; |
384 | } |
385 | |
386 | int signatureNumber; |
387 | if (strlen(s: signatureName) > 0) { |
388 | signatureNumber = atoi(nptr: signatureName); |
389 | if (signatureNumber == 0) { |
390 | signatureNumber = -1; |
391 | } |
392 | } else { |
393 | signatureNumber = 0; |
394 | } |
395 | |
396 | if (addNewSignature && signatureNumber > 0) { |
397 | // incompatible options |
398 | print_version_usage(usage: true); |
399 | return 99; |
400 | } |
401 | |
402 | if (addNewSignature) { |
403 | if (argc == 2) { |
404 | fprintf(stderr, format: "An output filename for the signed document must be given\n" ); |
405 | return 2; |
406 | } |
407 | |
408 | if (strlen(s: certNickname) == 0) { |
409 | printf(format: "A nickname of the signing certificate must be given\n" ); |
410 | return 2; |
411 | } |
412 | |
413 | if (etsiCAdESdetached) { |
414 | printf(format: "-etsi is not supported yet with -add-signature\n" ); |
415 | printf(format: "Please file a bug report if this is important for you\n" ); |
416 | return 2; |
417 | } |
418 | |
419 | if (digestName != std::string("SHA256" )) { |
420 | printf(format: "Only digest SHA256 is supported at the moment with -add-signature\n" ); |
421 | printf(format: "Please file a bug report if this is important for you\n" ); |
422 | return 2; |
423 | } |
424 | |
425 | if (doc->getPage(page: 1) == nullptr) { |
426 | printf(format: "Error getting first page of the document.\n" ); |
427 | return 2; |
428 | } |
429 | |
430 | bool getCertsError; |
431 | // We need to call this otherwise NSS spins forever |
432 | getAvailableSigningCertificates(error: &getCertsError); |
433 | if (getCertsError) { |
434 | return 2; |
435 | } |
436 | |
437 | const auto rs = std::unique_ptr<GooString>(reason.toStr().empty() ? nullptr : std::make_unique<GooString>(args: utf8ToUtf16WithBom(utf8: reason.toStr()))); |
438 | |
439 | if (newSignatureFieldName.getLength() == 0) { |
440 | // Create a random field name, it could be anything but 32 hex numbers should |
441 | // hopefully give us something that is not already in the document |
442 | std::random_device rd; |
443 | std::mt19937 gen(rd()); |
444 | std::uniform_int_distribution<> distrib(1, 15); |
445 | for (int i = 0; i < 32; ++i) { |
446 | const int value = distrib(gen); |
447 | newSignatureFieldName.append(c: value < 10 ? 48 + value : 65 + (value - 10)); |
448 | } |
449 | } |
450 | |
451 | // We don't provide a way to customize the UI from pdfsig for now |
452 | const bool success = doc->sign(saveFilename: std::string { argv[2] }, certNickname: std::string { certNickname }, password: std::string { password }, partialFieldName: newSignatureFieldName.copy(), /*page*/ 1, |
453 | /*rect */ { 0, 0, 0, 0 }, /*signatureText*/ {}, /*signatureTextLeft*/ {}, /*fontSize */ 0, /*leftFontSize*/ 0, |
454 | /*fontColor*/ {}, /*borderWidth*/ 0, /*borderColor*/ {}, /*backgroundColor*/ {}, reason: rs.get(), /* location */ nullptr, /* image path */ imagePath: "" , ownerPassword: ownerPW, userPassword: userPW); |
455 | return success ? 0 : 3; |
456 | } |
457 | |
458 | const std::vector<FormFieldSignature *> signatures = doc->getSignatureFields(); |
459 | const unsigned int sigCount = signatures.size(); |
460 | |
461 | if (signatureNumber == -1) { |
462 | for (unsigned int i = 0; i < sigCount; i++) { |
463 | const GooString *goo = signatures.at(n: i)->getCreateWidget()->getField()->getFullyQualifiedName(); |
464 | if (!goo) { |
465 | continue; |
466 | } |
467 | |
468 | const std::string name = TextStringToUTF8(str: goo->toStr()); |
469 | if (name == signatureName) { |
470 | signatureNumber = i + 1; |
471 | break; |
472 | } |
473 | } |
474 | |
475 | if (signatureNumber == -1) { |
476 | fprintf(stderr, format: "Signature field not found by name\n" ); |
477 | return 2; |
478 | } |
479 | } |
480 | |
481 | if (signatureNumber > 0) { |
482 | // We are signing an existing signature field |
483 | if (argc == 2) { |
484 | fprintf(stderr, format: "An output filename for the signed document must be given\n" ); |
485 | return 2; |
486 | } |
487 | |
488 | if (signatureNumber > static_cast<int>(sigCount)) { |
489 | printf(format: "File '%s' does not contain a signature with number %d\n" , fileName->c_str(), signatureNumber); |
490 | return 2; |
491 | } |
492 | |
493 | if (strlen(s: certNickname) == 0) { |
494 | printf(format: "A nickname of the signing certificate must be given\n" ); |
495 | return 2; |
496 | } |
497 | |
498 | if (digestName != std::string("SHA256" )) { |
499 | printf(format: "Only digest SHA256 is supported at the moment\n" ); |
500 | printf(format: "Please file a bug report if this is important for you\n" ); |
501 | return 2; |
502 | } |
503 | |
504 | bool getCertsError; |
505 | // We need to call this otherwise NSS spins forever |
506 | getAvailableSigningCertificates(error: &getCertsError); |
507 | if (getCertsError) { |
508 | return 2; |
509 | } |
510 | |
511 | FormFieldSignature *ffs = signatures.at(n: signatureNumber - 1); |
512 | Goffset file_size = 0; |
513 | const std::optional<GooString> sig = ffs->getCheckedSignature(checkedFileSize: &file_size); |
514 | if (sig) { |
515 | printf(format: "Signature number %d is already signed\n" , signatureNumber); |
516 | return 2; |
517 | } |
518 | if (etsiCAdESdetached) { |
519 | ffs->setSignatureType(ETSI_CAdES_detached); |
520 | } |
521 | const auto rs = std::unique_ptr<GooString>(reason.toStr().empty() ? nullptr : std::make_unique<GooString>(args: utf8ToUtf16WithBom(utf8: reason.toStr()))); |
522 | if (ffs->getNumWidgets() != 1) { |
523 | printf(format: "Unexpected number of widgets for the signature: %d\n" , ffs->getNumWidgets()); |
524 | return 2; |
525 | } |
526 | #ifdef HAVE_GETTEXT |
527 | if (!noAppearance) { |
528 | setlocale(LC_ALL, locale: "" ); |
529 | bindtextdomain(domainname: "pdfsig" , CMAKE_INSTALL_LOCALEDIR); |
530 | textdomain(domainname: "pdfsig" ); |
531 | } |
532 | #endif |
533 | FormWidgetSignature *fws = static_cast<FormWidgetSignature *>(ffs->getWidget(i: 0)); |
534 | auto backend = CryptoSign::Factory::createActive(); |
535 | auto sigHandler = backend->createSigningHandler(certID: certNickname, digestAlgTag: HashAlgorithm::Sha256); |
536 | std::unique_ptr<X509CertificateInfo> certInfo = sigHandler->getCertificateInfo(); |
537 | if (!certInfo) { |
538 | fprintf(stderr, format: "signDocument: error getting signature info\n" ); |
539 | return 2; |
540 | } |
541 | const std::string signerName = certInfo->getSubjectInfo().commonName; |
542 | const std::string timestamp = timeToStringWithFormat(timeA: nullptr, format: "%Y.%m.%d %H:%M:%S %z" ); |
543 | const AnnotColor blackColor(0, 0, 0); |
544 | const std::string signatureText(GooString::format(_("Digitally signed by {0:s}" ), signerName.c_str())->toStr() + "\n" + GooString::format(_("Date: {0:s}" ), timestamp.c_str())->toStr()); |
545 | const auto gSignatureText = std::make_unique<GooString>(args: (signatureText.empty() || noAppearance) ? "" : utf8ToUtf16WithBom(utf8: signatureText)); |
546 | const auto gSignatureLeftText = std::make_unique<GooString>(args: (signerName.empty() || noAppearance) ? "" : utf8ToUtf16WithBom(utf8: signerName)); |
547 | const bool success = fws->signDocumentWithAppearance(filename: argv[2], certNickname: std::string { certNickname }, password: std::string { password }, reason: rs.get(), location: nullptr, ownerPassword: {}, userPassword: {}, signatureText: *gSignatureText, signatureTextLeft: *gSignatureLeftText, fontSize: 0, leftFontSize: 0, fontColor: std::make_unique<AnnotColor>(args: blackColor)); |
548 | return success ? 0 : 3; |
549 | } |
550 | |
551 | if (argc > 2) { |
552 | // We are not signing and more than 1 filename was given |
553 | print_version_usage(usage: true); |
554 | return 99; |
555 | } |
556 | |
557 | if (sigCount >= 1) { |
558 | if (dumpSignatures) { |
559 | printf(format: "Dumping Signatures: %u\n" , sigCount); |
560 | for (unsigned int i = 0; i < sigCount; i++) { |
561 | const bool dumpingOk = dumpSignature(sig_num: i, sigCount, s: signatures.at(n: i), filename: fileName->c_str()); |
562 | if (!dumpingOk) { |
563 | // for now, do nothing. We have logged a message |
564 | // to the user before returning false in dumpSignature |
565 | // and it is possible to have "holes" in the signatures |
566 | continue; |
567 | } |
568 | } |
569 | return 0; |
570 | } else { |
571 | printf(format: "Digital Signature Info of: %s\n" , fileName->c_str()); |
572 | } |
573 | } else { |
574 | printf(format: "File '%s' does not contain any signatures\n" , fileName->c_str()); |
575 | return 2; |
576 | } |
577 | std::unordered_map<int, SignatureInfo *> signatureInfos; |
578 | for (unsigned int i = 0; i < sigCount; i++) { |
579 | // Let's start the signature check first for signatures. |
580 | // we can always wait for completion later |
581 | FormFieldSignature *ffs = signatures.at(n: i); |
582 | if (ffs->getSignatureType() == unsigned_signature_field) { |
583 | continue; |
584 | } |
585 | signatureInfos[i] = ffs->validateSignatureAsync(doVerifyCert: !dontVerifyCert, forceRevalidation: false, validationTime: -1 /* now */, ocspRevocationCheck: !noOCSPRevocationCheck, enableAIA: useAIACertFetch, doneCallback: {}); |
586 | } |
587 | |
588 | for (unsigned int i = 0; i < sigCount; i++) { |
589 | FormFieldSignature *ffs = signatures.at(n: i); |
590 | printf(format: "Signature #%u:\n" , i + 1); |
591 | const GooString *goo = ffs->getCreateWidget()->getField()->getFullyQualifiedName(); |
592 | if (goo) { |
593 | const std::string name = TextStringToUTF8(str: goo->toStr()); |
594 | printf(format: " - Signature Field Name: %s\n" , name.c_str()); |
595 | } |
596 | |
597 | if (ffs->getSignatureType() == unsigned_signature_field) { |
598 | printf(format: " The signature form field is not signed.\n" ); |
599 | continue; |
600 | } |
601 | |
602 | const SignatureInfo *sig_info = signatureInfos[i]; |
603 | CertificateValidationStatus certificateStatus = ffs->validateSignatureResult(); |
604 | printf(format: " - Signer Certificate Common Name: %s\n" , sig_info->getSignerName().c_str()); |
605 | printf(format: " - Signer full Distinguished Name: %s\n" , sig_info->getSubjectDN().c_str()); |
606 | printf(format: " - Signing Time: %s\n" , time_str = getReadableTime(unix_time: sig_info->getSigningTime())); |
607 | printf(format: " - Signing Hash Algorithm: " ); |
608 | switch (sig_info->getHashAlgorithm()) { |
609 | case HashAlgorithm::Md2: |
610 | printf(format: "MD2\n" ); |
611 | break; |
612 | case HashAlgorithm::Md5: |
613 | printf(format: "MD5\n" ); |
614 | break; |
615 | case HashAlgorithm::Sha1: |
616 | printf(format: "SHA1\n" ); |
617 | break; |
618 | case HashAlgorithm::Sha256: |
619 | printf(format: "SHA-256\n" ); |
620 | break; |
621 | case HashAlgorithm::Sha384: |
622 | printf(format: "SHA-384\n" ); |
623 | break; |
624 | case HashAlgorithm::Sha512: |
625 | printf(format: "SHA-512\n" ); |
626 | break; |
627 | case HashAlgorithm::Sha224: |
628 | printf(format: "SHA-224\n" ); |
629 | break; |
630 | default: |
631 | printf(format: "unknown\n" ); |
632 | } |
633 | printf(format: " - Signature Type: " ); |
634 | switch (ffs->getSignatureType()) { |
635 | case adbe_pkcs7_sha1: |
636 | printf(format: "adbe.pkcs7.sha1\n" ); |
637 | break; |
638 | case adbe_pkcs7_detached: |
639 | printf(format: "adbe.pkcs7.detached\n" ); |
640 | break; |
641 | case ETSI_CAdES_detached: |
642 | printf(format: "ETSI.CAdES.detached\n" ); |
643 | break; |
644 | default: |
645 | printf(format: "unknown\n" ); |
646 | } |
647 | const std::vector<Goffset> ranges = ffs->getSignedRangeBounds(); |
648 | if (ranges.size() == 4) { |
649 | printf(format: " - Signed Ranges: [%lld - %lld], [%lld - %lld]\n" , ranges[0], ranges[1], ranges[2], ranges[3]); |
650 | Goffset checked_file_size; |
651 | const std::optional<GooString> signature = signatures.at(n: i)->getCheckedSignature(checkedFileSize: &checked_file_size); |
652 | if (signature && checked_file_size == ranges[3]) { |
653 | printf(format: " - Total document signed\n" ); |
654 | } else { |
655 | printf(format: " - Not total document signed\n" ); |
656 | } |
657 | } |
658 | printf(format: " - Signature Validation: %s\n" , getReadableSigState(sig_vs: sig_info->getSignatureValStatus())); |
659 | gfree(p: time_str); |
660 | if (sig_info->getSignatureValStatus() != SIGNATURE_VALID || dontVerifyCert) { |
661 | continue; |
662 | } |
663 | printf(format: " - Certificate Validation: %s\n" , getReadableCertState(cert_vs: certificateStatus)); |
664 | } |
665 | |
666 | return 0; |
667 | } |
668 | |