1 /*
2 *
3 * Copyright (C) 2000-2020, OFFIS e.V.
4 * All rights reserved. See COPYRIGHT file for details.
5 *
6 * This software and supporting documentation were developed by
7 *
8 * OFFIS e.V.
9 * R&D Division Health
10 * Escherweg 2
11 * D-26121 Oldenburg, Germany
12 *
13 *
14 * Module: dcmsign
15 *
16 * Author: Marco Eichelberg
17 *
18 * Purpose: Create and Verify DICOM Digital Signatures
19 *
20 */
21
22 #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
23
24 #define INCLUDE_CSTDLIB
25 #define INCLUDE_CSTDIO
26 #define INCLUDE_CSTRING
27 #define INCLUDE_CCTYPE
28 #include "dcmtk/ofstd/ofstdinc.h"
29
30 #include "dcmtk/dcmdata/cmdlnarg.h"
31 #include "dcmtk/oflog/oflog.h"
32 #include "dcmtk/ofstd/ofconapp.h"
33 #include "dcmtk/dcmdata/dcuid.h" /* for dcmtk version name */
34
35 #ifdef WITH_ZLIB
36 #include <zlib.h> /* for zlibVersion() */
37 #endif
38
39 #define OFFIS_CONSOLE_APPLICATION "dcmsign"
40
41 static char rcsid[] = "$dcmtk: " OFFIS_CONSOLE_APPLICATION " v"
42 OFFIS_DCMTK_VERSION " " OFFIS_DCMTK_RELEASEDATE " $";
43
44 #define APPLICATION_ABSTRACT "Sign and Verify DICOM Files"
45
46 #ifdef WITH_OPENSSL
47
48 #include "dcmtk/dcmsign/dcsignat.h"
49 #include "dcmtk/dcmsign/dcsighlp.h"
50 #include "dcmtk/dcmsign/sinullpr.h"
51 #include "dcmtk/dcmsign/sibrsapr.h"
52 #include "dcmtk/dcmsign/siautopr.h"
53 #include "dcmtk/dcmsign/sicreapr.h"
54 #include "dcmtk/dcmsign/sisrpr.h"
55 #include "dcmtk/dcmsign/sisrvpr.h"
56 #include "dcmtk/dcmsign/simac.h"
57 #include "dcmtk/dcmsign/simd5.h"
58 #include "dcmtk/dcmsign/sisha1.h"
59 #include "dcmtk/dcmsign/sisha256.h"
60 #include "dcmtk/dcmsign/sisha384.h"
61 #include "dcmtk/dcmsign/sisha512.h"
62 #include "dcmtk/dcmsign/siripemd.h"
63 #include "dcmtk/dcmsign/siprivat.h"
64 #include "dcmtk/dcmsign/sicert.h"
65 #include "dcmtk/dcmsign/sitsfs.h"
66 #include "dcmtk/dcmsign/sicertvf.h"
67 #include "dcmtk/dcmsign/siexit.h"
68 #include "dcmtk/dcmdata/dctk.h"
69
70 BEGIN_EXTERN_C
71 #include <openssl/x509.h>
72 #include <openssl/evp.h> /* for OPENSSL_NO_EC */
73 END_EXTERN_C
74
75
76 // ********************************************
77
78 enum DcmSignOperation
79 {
80 DSO_verify,
81 DSO_sign,
82 DSO_signItem,
83 DSO_insertTimestamp,
84 DSO_remove,
85 DSO_removeAll
86 };
87
88
89 #define SHORTCOL 4
90 #define LONGCOL 21
91
main(int argc,char * argv[])92 int main(int argc, char *argv[])
93 {
94 DcmSignature::initializeLibrary(); // initialize dcmsign
95 const char * opt_certfile = NULL;
96 OFCmdUnsignedInt opt_filepad = 0;
97 E_FileReadMode opt_readMode = ERM_autoDetect;
98 const char * opt_ifname = NULL;
99 E_TransferSyntax opt_ixfer = EXS_Unknown;
100 OFCmdUnsignedInt opt_itempad = 0;
101 const char * opt_keyfile = NULL; // private key file
102 int opt_keyFileFormat = X509_FILETYPE_PEM; // file format for certificates and private keys
103 const char * opt_location = NULL; // location (path) within dataset
104 SiMAC * opt_mac = NULL; // MAC object
105 E_EncodingType opt_oenctype = EET_ExplicitLength;
106 const char * opt_ofname = NULL;
107 E_GrpLenEncoding opt_oglenc = EGL_recalcGL;
108 E_PaddingEncoding opt_opadenc = EPD_noChange;
109 DcmSignOperation opt_operation = DSO_verify; // command to execute
110 E_TransferSyntax opt_oxfer = EXS_Unknown;
111 const char * opt_passwd = NULL; // password for private key
112 SiSecurityProfile * opt_profile = NULL; // security profile
113 const char * opt_tagFile = NULL; // text file with attribute tags
114 DcmAttributeTag * opt_tagList = NULL; // list of attribute tags
115 E_TransferSyntax opt_signatureXfer = EXS_Unknown;
116 FILE * opt_dumpFile = NULL;
117 SiTimeStampFS * opt_timeStamp = NULL;
118 const char * opt_ts_queryfile = NULL;
119 const char * opt_ts_responsefile = NULL;
120 const char * opt_ts_uidfile = NULL;
121 const char * opt_ts_policyoid = NULL;
122 E_SignatureVerificationPolicy opt_verificationPolicy = ESVP_verifyIfPresent;
123 E_TimestampVerificationPolicy opt_timestampPolicy = ETVP_verifyTSIfPresent;
124 SiCertificateVerifier certVerifier;
125 DcmDataset *dataset = NULL;
126 SiCertificate cert;
127 SiPrivateKey key;
128 OFCondition sicond;
129 DcmFileFormat fileformat;
130
131 SiSignaturePurpose::E_SignaturePurposeType opt_sigPurpose = SiSignaturePurpose::ESP_none;
132 int result = EXITCODE_NO_ERROR;
133 OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION , APPLICATION_ABSTRACT, rcsid);
134 OFCommandLine cmd;
135 cmd.setOptionColumns(LONGCOL, SHORTCOL);
136 cmd.setParamColumn(LONGCOL + SHORTCOL + 4);
137 cmd.addParam("dcmfile-in", "DICOM input filename to be processed");
138 cmd.addParam("dcmfile-out", "DICOM output filename", OFCmdParam::PM_Optional);
139
140 cmd.addGroup("general options:", LONGCOL, SHORTCOL + 2);
141 cmd.addOption("--help", "-h", "print this help text and exit", OFCommandLine::AF_Exclusive);
142 cmd.addOption("--version", "print version information and exit", OFCommandLine::AF_Exclusive);
143 OFLog::addOptions(cmd);
144
145 cmd.addGroup("input options:");
146 cmd.addSubGroup("input file format:");
147 cmd.addOption("--read-file", "+f", "read file format or data set (default)");
148 cmd.addOption("--read-file-only", "+fo", "read file format only");
149 cmd.addOption("--read-dataset", "-f", "read data set without file meta information");
150 cmd.addSubGroup("input transfer syntax:", LONGCOL, SHORTCOL);
151 cmd.addOption("--read-xfer-auto", "-t=", "use TS recognition (default)");
152 cmd.addOption("--read-xfer-detect", "-td", "ignore TS specified in the file meta header");
153 cmd.addOption("--read-xfer-little", "-te", "read with explicit VR little endian TS");
154 cmd.addOption("--read-xfer-big", "-tb", "read with explicit VR big endian TS");
155 cmd.addOption("--read-xfer-implicit", "-ti", "read with implicit VR little endian TS");
156 cmd.addSubGroup("handling of defined length UN elements:");
157 cmd.addOption("--retain-un", "-uc", "retain elements as UN (default)");
158 cmd.addOption("--convert-un", "+uc", "convert to real VR if known");
159
160 cmd.addGroup("signature commands:", LONGCOL, SHORTCOL + 2);
161 cmd.addOption("--verify", "verify all signatures (default)");
162 cmd.addOption("--sign", "+s", 2, "[p]rivate key file, [c]ertificate file: string",
163 "create signature in main object");
164 cmd.addOption("--sign-item", "+si", 3, "[k]eyfile, [c]ertfile, [i]tem location: string",
165 "create signature in sequence item");
166 cmd.addOption("--insert-timestamp", "+t", 3, "ts[q]file, ts[r]file [u]idfile: string",
167 "insert certified timestamp from ts response r\n"
168 "from timestamp query q at signature UID u");
169 cmd.addOption("--remove", "+r", 1, "[s]ignature UID: string", "remove signature");
170 cmd.addOption("--remove-all", "+ra", "remove all signatures from data set");
171
172 cmd.addGroup("general signature options:");
173 cmd.addSubGroup("key and certificate file format:");
174 cmd.addOption("--pem-keys", "-pem", "read keys/certificates as PEM file (default)");
175 cmd.addOption("--der-keys", "-der", "read keys/certificates as DER file");
176 cmd.addSubGroup("signature format:");
177 cmd.addOption("--format-new", "-fn", "use correct DICOM signature format (default)");
178 cmd.addOption("--format-old", "-fo", "use old (pre-3.5.4) DCMTK signature format");
179
180 cmd.addGroup("signature verification options (only with --verify):");
181 cmd.addSubGroup("signature verification:");
182 cmd.addOption("--verify-if-present", "+rv", "verify signatures if present, pass otherwise\n(default)");
183 cmd.addOption("--require-sig", "+rg", "fail if no signature at all is present");
184 cmd.addOption("--require-creator", "+rc", "fail if no creator RSA signature is present");
185 cmd.addOption("--require-auth", "+ru", "fail if no auth RSA signature is present");
186 cmd.addOption("--require-sr", "+rs", "fail if no SR RSA signature is present");
187 cmd.addSubGroup("timestamp verification:");
188 cmd.addOption("--verify-ts", "+tv", "verify certified timestamp if present (default)");
189 cmd.addOption("--ignore-ts", "-tv", "ignore certified timestamps");
190 cmd.addOption("--require-ts", "+tr", "fail if no certified timestamp is present");
191 cmd.addSubGroup("certification authority:");
192 cmd.addOption("--add-cert-file", "+cf", 1, "[f]ilename: string",
193 "add trusted certificate file to cert store");
194 cmd.addOption("--add-ucert-file", "+uf", 1, "[f]ilename: string",
195 "add untrusted intermediate certificate file");
196 cmd.addOption("--add-cert-dir", "+cd", 1, "[d]irectory: string",
197 "add certificates in d to cert store");
198 cmd.addOption("--add-crl-file", "+cr", 1, "[f]ilename: string",
199 "add certificate revocation list file\n(implies --enable-crl-vfy)");
200 cmd.addOption("--enable-crl-vfy", "+cl", "enable CRL verification");
201
202 cmd.addGroup("signature creation options (only with --sign or --sign-item):");
203 cmd.addSubGroup("private key password:");
204 cmd.addOption("--std-passwd", "+ps", "prompt user to type password on stdin (default)");
205 cmd.addOption("--use-passwd", "+pw", 1, "[p]assword: string ",
206 "use specified password");
207 cmd.addOption("--null-passwd", "-pw", "use empty string as password");
208 cmd.addSubGroup("digital signature profile:");
209 cmd.addOption("--profile-none", "-pf", "don't enforce any signature profile (default)");
210 cmd.addOption("--profile-base", "+pb", "enforce base RSA signature profile");
211 cmd.addOption("--profile-creator", "+pc", "enforce creator RSA signature profile");
212 cmd.addOption("--profile-auth", "+pa", "enforce authorization signature profile");
213 cmd.addOption("--profile-sr", "+pr", "enforce SR RSA signature profile");
214 cmd.addOption("--profile-srv" , "+pv", "enforce SR RSA signature profile (verification)");
215 cmd.addSubGroup("MAC algorithm:");
216 cmd.addOption("--mac-ripemd160", "+mr", "use RIPEMD 160 (default)");
217 cmd.addOption("--mac-sha1", "+ms", "use SHA-1 (not recommended)");
218 cmd.addOption("--mac-md5", "+mm", "use MD5 (not recommended)");
219 cmd.addOption("--mac-sha256", "+m2", "use SHA-256");
220 cmd.addOption("--mac-sha384", "+m3", "use SHA-384");
221 cmd.addOption("--mac-sha512", "+m5", "use SHA-512");
222 cmd.addSubGroup("signature purpose:");
223 cmd.addOption("--list-purposes", "+lp", "show list of signature purpose codes and exit", OFCommandLine::AF_Exclusive);
224 cmd.addOption("--no-sig-purpose", "-sp", "do not add signature purpose (default)");
225 cmd.addOption("--sig-purpose", "+sp", 1, "[p]urpose code: integer (1..18)",
226 "add digital signature purpose code p");
227 cmd.addSubGroup("tag selection:");
228 cmd.addOption("--tag", "-t", 1, "[t]ag: \"gggg,eeee\" or dictionary name",
229 "sign only specified tag\n(this option can be specified multiple times)");
230 cmd.addOption("--tag-file", "-tf", 1, "[f]ilename: string",
231 "read list of tags from text file");
232
233 cmd.addGroup("timestamp creation options (only with --sign or --sign-item):");
234 cmd.addSubGroup("timestamp creation:");
235 cmd.addOption("--timestamp-off", "-ts", "do not create timestamp (default)");
236 cmd.addOption("--timestamp-file", "+ts", 2, "[t]sq-filename, [u]id-filename: string",
237 "create timestamp query file t and uid file u");
238 cmd.addSubGroup("timestamp MAC algorithm (only with --timestamp-file):");
239 cmd.addOption("--ts-mac-sha256", "+tm2", "use SHA-256 (default)");
240 cmd.addOption("--ts-mac-sha384", "+tm3", "use SHA-384");
241 cmd.addOption("--ts-mac-sha512", "+tm5", "use SHA-512");
242 cmd.addOption("--ts-mac-ripemd160", "+tmr", "use RIPEMD 160");
243 cmd.addOption("--ts-mac-sha1", "+tms", "use SHA-1 (not recommended)");
244 cmd.addOption("--ts-mac-md5", "+tmm", "use MD5 (not recommended)");
245 cmd.addSubGroup("timestamp query nonce options (only with --timestamp-file):");
246 cmd.addOption("--ts-use-nonce", "+tn", "include random nonce (default)");
247 cmd.addOption("--ts-no-nonce", "-tn", "do not include nonce");
248 cmd.addSubGroup("timestamp certificate inclusion options (only with --timestamp-file):");
249 cmd.addOption("--ts-request-cert", "+tc", "request TSA certificate in timestamp (default)");
250 cmd.addOption("--ts-no-cert", "-tc", "do not request TSA certificate in timestamp");
251 cmd.addSubGroup("timestamp policy options (only with --timestamp-file):");
252 cmd.addOption("--ts-no-policy", "-tp", "do not specify ts policy (default)");
253 cmd.addOption("--ts-policy", "+tp", 1, "[p]olicy-OID: string",
254 "request timestamp policy p");
255 cmd.addGroup("output options:");
256 cmd.addSubGroup("output transfer syntax:");
257 cmd.addOption("--write-xfer-same", "+t=", "write with same TS as input (default)");
258 cmd.addOption("--write-xfer-little", "+te", "write with explicit VR little endian TS");
259 cmd.addOption("--write-xfer-big", "+tb", "write with explicit VR big endian TS");
260 cmd.addOption("--write-xfer-implicit", "+ti", "write with implicit VR little endian TS");
261 cmd.addSubGroup("length encoding in sequences and items:");
262 cmd.addOption("--length-explicit", "+e", "write with explicit lengths (default)");
263 cmd.addOption("--length-undefined", "-e", "write with undefined lengths");
264 cmd.addSubGroup("other output options:");
265 cmd.addOption("--dump", "+d", 1, "[f]ilename: string",
266 "dump byte stream fed into the MAC codec to file\n(only with --sign or --sign-item)");
267 /* evaluate command line */
268 prepareCmdLineArgs(argc, argv, OFFIS_CONSOLE_APPLICATION);
269 if (app.parseCommandLine(cmd, argc, argv))
270 {
271 /* check exclusive options first */
272 if (cmd.hasExclusiveOption())
273 {
274 if (cmd.findOption("--version"))
275 {
276 app.printHeader(OFTrue /*print host identifier*/);
277 COUT << OFendl << "External libraries used:";
278 #if !defined(WITH_ZLIB) && !defined(WITH_OPENSSL)
279 COUT << " none" << OFendl;
280 #else
281 COUT << OFendl;
282 #endif
283 #ifdef WITH_ZLIB
284 COUT << "- ZLIB, Version " << zlibVersion() << OFendl;
285 #endif
286 #ifdef WITH_OPENSSL
287 #ifdef OPENSSL_NO_EC
288 COUT << "- " << OPENSSL_VERSION_TEXT << ", without ECDSA support" << OFendl;
289 #else
290 COUT << "- " << OPENSSL_VERSION_TEXT << ", with ECDSA support" << OFendl;
291 #endif
292 #endif
293 return EXITCODE_NO_ERROR;
294 }
295 if (cmd.findOption("--list-purposes"))
296 {
297 app.printHeader(OFTrue /*print host identifier*/);
298 SiSignaturePurpose::printSignatureCodes(COUT);
299 return EXITCODE_NO_ERROR;
300 }
301 }
302 /* command line parameters */
303 cmd.getParam(1, opt_ifname);
304 if (cmd.getParamCount() > 1) cmd.getParam(2, opt_ofname);
305 OFLog::configureFromCommandLine(cmd, app);
306
307 cmd.beginOptionBlock();
308 if (cmd.findOption("--read-file")) opt_readMode = ERM_autoDetect;
309 if (cmd.findOption("--read-file-only")) opt_readMode = ERM_fileOnly;
310 if (cmd.findOption("--read-dataset")) opt_readMode = ERM_dataset;
311 cmd.endOptionBlock();
312 cmd.beginOptionBlock();
313 if (cmd.findOption("--read-xfer-auto")) opt_ixfer = EXS_Unknown;
314 if (cmd.findOption("--read-xfer-detect")) dcmAutoDetectDatasetXfer.set(OFTrue);
315 if (cmd.findOption("--read-xfer-little"))
316 {
317 app.checkDependence("--read-xfer-little", "--read-dataset", opt_readMode == ERM_dataset);
318 opt_ixfer = EXS_LittleEndianExplicit;
319 }
320 if (cmd.findOption("--read-xfer-big"))
321 {
322 app.checkDependence("--read-xfer-big", "--read-dataset", opt_readMode == ERM_dataset);
323 opt_ixfer = EXS_BigEndianExplicit;
324 }
325 if (cmd.findOption("--read-xfer-implicit"))
326 {
327 app.checkDependence("--read-xfer-implicit", "--read-dataset", opt_readMode == ERM_dataset);
328 opt_ixfer = EXS_LittleEndianImplicit;
329 }
330 cmd.endOptionBlock();
331
332 cmd.beginOptionBlock();
333 if (cmd.findOption("--retain-un")) dcmEnableUnknownVRConversion.set(OFFalse);
334 if (cmd.findOption("--convert-un")) dcmEnableUnknownVRConversion.set(OFTrue);
335 cmd.endOptionBlock();
336
337 cmd.beginOptionBlock();
338 if (cmd.findOption("--verify"))
339 {
340 opt_operation = DSO_verify;
341 }
342 if (cmd.findOption("--sign"))
343 {
344 opt_operation = DSO_sign;
345 if (opt_ofname == NULL) app.printError("parameter dcmfile-out required for --sign");
346 app.checkValue(cmd.getValue(opt_keyfile));
347 app.checkValue(cmd.getValue(opt_certfile));
348 }
349 if (cmd.findOption("--sign-item"))
350 {
351 opt_operation = DSO_signItem;
352 if (opt_ofname == NULL) app.printError("parameter dcmfile-out required for --sign-item");
353 app.checkValue(cmd.getValue(opt_keyfile));
354 app.checkValue(cmd.getValue(opt_certfile));
355 app.checkValue(cmd.getValue(opt_location));
356 }
357 if (cmd.findOption("--insert-timestamp"))
358 {
359 opt_operation = DSO_insertTimestamp;
360 if (opt_ofname == NULL) app.printError("parameter dcmfile-out required for --insert-timestamp");
361 app.checkValue(cmd.getValue(opt_ts_queryfile));
362 app.checkValue(cmd.getValue(opt_ts_responsefile));
363 app.checkValue(cmd.getValue(opt_ts_uidfile));
364 opt_timeStamp = new SiTimeStampFS();
365 opt_timeStamp->setTSQFilename(opt_ts_queryfile);
366 opt_timeStamp->setTSRFilename(opt_ts_responsefile);
367 opt_timeStamp->setUIDFilename(opt_ts_uidfile);
368 }
369 if (cmd.findOption("--remove"))
370 {
371 opt_operation = DSO_remove;
372 if (opt_ofname == NULL) app.printError("parameter dcmfile-out required for --remove");
373 app.checkValue(cmd.getValue(opt_location));
374 }
375 if (cmd.findOption("--remove-all"))
376 {
377 opt_operation = DSO_removeAll;
378 if (opt_ofname == NULL) app.printError("parameter dcmfile-out required for --remove-all");
379 }
380 cmd.endOptionBlock();
381 if ((opt_operation == DSO_verify) && opt_ofname) app.printError("parameter dcmfile-out not allowed for --verify");
382
383 cmd.beginOptionBlock();
384 if (cmd.findOption("--pem-keys")) opt_keyFileFormat = X509_FILETYPE_PEM;
385 if (cmd.findOption("--der-keys")) opt_keyFileFormat = X509_FILETYPE_ASN1;
386 cmd.endOptionBlock();
387
388 cmd.beginOptionBlock();
389 if (cmd.findOption("--verify-if-present"))
390 {
391 app.checkDependence("--verify-if-present", "--verify", (opt_operation == DSO_verify));
392 opt_verificationPolicy = ESVP_verifyIfPresent;
393 }
394 if (cmd.findOption("--require-sig"))
395 {
396 app.checkDependence("--require-sig", "--verify", (opt_operation == DSO_verify));
397 opt_verificationPolicy = ESVP_requireSignature;
398 }
399 if (cmd.findOption("--require-creator"))
400 {
401 app.checkDependence("--require-creator", "--verify", (opt_operation == DSO_verify));
402 opt_verificationPolicy = ESVP_requireCreatorRSASignature;
403 }
404 if (cmd.findOption("--require-auth"))
405 {
406 app.checkDependence("--require-auth", "--verify", (opt_operation == DSO_verify));
407 opt_verificationPolicy = ESVP_requireAuthorizationRSASignature;
408 }
409 if (cmd.findOption("--require-sr"))
410 {
411 app.checkDependence("--require-sr", "--verify", (opt_operation == DSO_verify));
412 opt_verificationPolicy = ESVP_requireSRRSASignature;
413 }
414 cmd.endOptionBlock();
415
416 cmd.beginOptionBlock();
417 if (cmd.findOption("--verify-ts"))
418 {
419 app.checkDependence("--verify-ts", "--verify", (opt_operation == DSO_verify));
420 opt_timestampPolicy = ETVP_verifyTSIfPresent;
421 }
422 if (cmd.findOption("--ignore-ts"))
423 {
424 app.checkDependence("--ignore-ts", "--verify", (opt_operation == DSO_verify));
425 opt_timestampPolicy = ETVP_ignoreTS;
426 }
427 if (cmd.findOption("--require-ts"))
428 {
429 app.checkDependence("--require-ts", "--verify", (opt_operation == DSO_verify));
430 opt_timestampPolicy = ETVP_requireTS;
431 }
432 cmd.endOptionBlock();
433
434 if (cmd.findOption("--add-cert-file", 0, OFCommandLine::FOM_First))
435 {
436 app.checkDependence("--add-cert-file", "--verify", (opt_operation == DSO_verify));
437 const char *current = NULL;
438 do
439 {
440 app.checkValue(cmd.getValue(current));
441 if (certVerifier.addTrustedCertificateFile(current, opt_keyFileFormat).bad())
442 {
443 DCMSIGN_WARN("unable to load certificate file '" << current << "', ignoring");
444 }
445 } while (cmd.findOption("--add-cert-file", 0, OFCommandLine::FOM_Next));
446 }
447 if (cmd.findOption("--add-ucert-file", 0, OFCommandLine::FOM_First))
448 {
449 app.checkDependence("--add-ucert-file", "--verify", (opt_operation == DSO_verify));
450 const char *current = NULL;
451 do
452 {
453 app.checkValue(cmd.getValue(current));
454 if (certVerifier.addUntrustedCertificateFile(current, opt_keyFileFormat).bad())
455 {
456 DCMSIGN_WARN("unable to load certificate file '" << current << "', ignoring");
457 }
458 } while (cmd.findOption("--add-ucert-file", 0, OFCommandLine::FOM_Next));
459 }
460 if (cmd.findOption("--add-cert-dir", 0, OFCommandLine::FOM_First))
461 {
462 app.checkDependence("--add-cert-dir", "--verify", (opt_operation == DSO_verify));
463 const char *current = NULL;
464 do
465 {
466 app.checkValue(cmd.getValue(current));
467 if (certVerifier.addTrustedCertificateDir(current, opt_keyFileFormat).bad())
468 {
469 DCMSIGN_WARN("unable to load certificates from directory '" << current << "', ignoring");
470 }
471 } while (cmd.findOption("--add-cert-dir", 0, OFCommandLine::FOM_Next));
472 }
473 if (cmd.findOption("--add-crl-file", 0, OFCommandLine::FOM_First))
474 {
475 app.checkDependence("--add-crl-file", "--verify", (opt_operation == DSO_verify));
476 const char *current = NULL;
477 do
478 {
479 app.checkValue(cmd.getValue(current));
480 if (certVerifier.addCertificateRevocationList(current, opt_keyFileFormat).bad())
481 {
482 DCMSIGN_WARN("unable to load CRL file '" << current << "', ignoring");
483 }
484 } while (cmd.findOption("--add-crl-file", 0, OFCommandLine::FOM_Next));
485 }
486 if (cmd.findOption("--enable-crl-vfy"))
487 {
488 app.checkDependence("--enable-crl-vfy", "--verify", (opt_operation == DSO_verify));
489 certVerifier.setCRLverification(OFTrue);
490 }
491
492 cmd.beginOptionBlock();
493 if (cmd.findOption("--std-passwd"))
494 {
495 app.checkDependence("--std-passwd", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
496 opt_passwd = NULL;
497 }
498 if (cmd.findOption("--use-passwd"))
499 {
500 app.checkDependence("--use-passwd", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
501 app.checkValue(cmd.getValue(opt_passwd));
502 }
503 if (cmd.findOption("--null-passwd"))
504 {
505 app.checkDependence("--null-passwd", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
506 opt_passwd = "";
507 }
508 cmd.endOptionBlock();
509 cmd.beginOptionBlock();
510 if (cmd.findOption("--profile-none"))
511 {
512 app.checkDependence("--profile-none", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
513 opt_profile = new SiNullProfile();
514 }
515 if (cmd.findOption("--profile-base"))
516 {
517 // the RSA base profile can be used on dataset and item level
518 app.checkDependence("--profile-base", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
519 opt_profile = new SiBaseRSAProfile();
520 }
521 if (cmd.findOption("--profile-creator"))
522 {
523 // the creator RSA profile only makes sense when applied on main dataset level
524 app.checkDependence("--profile-creator", "--sign", (opt_operation == DSO_sign));
525 opt_profile = new SiCreatorProfile();
526 }
527 if (cmd.findOption("--profile-auth"))
528 {
529 // the authorization RSA profile only makes sense when applied on main dataset level
530 app.checkDependence("--profile-auth", "--sign", (opt_operation == DSO_sign));
531 opt_profile = new SiAuthorizationProfile();
532 DCMSIGN_WARN(
533 "The Authorization RSA Digital Signature Profile requires that any\n"
534 "attributes whose values are verifiable by the technician or physician\n"
535 "(e.g., their values are displayed to the technician or physician) must\n"
536 "be included in the signature. Please assure this using --tag options." );
537 }
538 if (cmd.findOption("--profile-sr"))
539 {
540 // the SR RSA profile only makes sense when applied on main dataset level
541 app.checkDependence("--profile-sr", "--sign", (opt_operation == DSO_sign));
542 opt_profile = new SiStructuredReportingProfile();
543 }
544 if (cmd.findOption("--profile-srv"))
545 {
546 // the SR RSA profile only makes sense when applied on main dataset level
547 app.checkDependence("--profile-srv", "--sign", (opt_operation == DSO_sign));
548 opt_profile = new SiStructuredReportingVerificationProfile();
549 }
550 cmd.endOptionBlock();
551 if (opt_profile == NULL) opt_profile = new SiNullProfile();
552 cmd.beginOptionBlock();
553 if (cmd.findOption("--mac-ripemd160"))
554 {
555 app.checkDependence("--mac-ripemd160", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
556 opt_mac = new SiRIPEMD160();
557 }
558 if (cmd.findOption("--mac-sha1"))
559 {
560 app.checkDependence("--mac-sha1", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
561 opt_mac = new SiSHA1();
562 }
563 if (cmd.findOption("--mac-md5"))
564 {
565 app.checkDependence("--mac-md5", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
566 opt_mac = new SiMD5();
567 }
568 if (cmd.findOption("--mac-sha256"))
569 {
570 app.checkDependence("--mac-sha256", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
571 opt_mac = new SiSHA256();
572 }
573 if (cmd.findOption("--mac-sha384"))
574 {
575 app.checkDependence("--mac-sha384", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
576 opt_mac = new SiSHA384();
577 }
578 if (cmd.findOption("--mac-sha512"))
579 {
580 app.checkDependence("--mac-sha512", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
581 opt_mac = new SiSHA512();
582 }
583 cmd.endOptionBlock();
584 if (opt_mac == NULL) opt_mac = new SiRIPEMD160();
585
586 cmd.beginOptionBlock();
587 if (cmd.findOption("--no-sig-purpose"))
588 {
589 app.checkDependence("--no-sig-purpose", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
590 opt_sigPurpose = SiSignaturePurpose::ESP_none;
591 }
592 if (cmd.findOption("--sig-purpose"))
593 {
594 app.checkDependence("--sig-purpose", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
595 OFCmdUnsignedInt val = 0;
596 app.checkValue(cmd.getValueAndCheckMinMax(val, 1, 18));
597 opt_sigPurpose = SiSignaturePurpose::lookup(OFstatic_cast(size_t, val));
598 if (opt_sigPurpose == SiSignaturePurpose::ESP_none)
599 {
600 // should never happen
601 DCMSIGN_WARN("unknown digital signature purpose code, ignoring.");
602 }
603 }
604 cmd.endOptionBlock();
605
606 if (cmd.findOption("--tag-file"))
607 {
608 app.checkValue(cmd.getValue(opt_tagFile));
609 opt_tagList = new DcmAttributeTag(DCM_DataElementsSigned);
610 result = DcmSignatureHelper::parseTextFile(opt_tagFile, *opt_tagList);
611 if (result > 0)
612 {
613 DCMSIGN_FATAL("Error while reading tag file '" << opt_tagFile << "', giving up.");
614 goto cleanup;
615 }
616 }
617 if (cmd.findOption("--tag", 0, OFCommandLine::FOM_First))
618 {
619 const char *current=NULL;
620 if (opt_tagList == NULL) opt_tagList = new DcmAttributeTag(DCM_DataElementsSigned);
621 do
622 {
623 app.checkValue(cmd.getValue(current));
624 if (! DcmSignatureHelper::addTag(current, *opt_tagList))
625 {
626 DCMSIGN_FATAL("unknown attribute tag '" << current << "'");
627 result = EXITCODE_COMMANDLINE_SYNTAX_ERROR;
628 goto cleanup;
629 }
630 } while (cmd.findOption("--tag", 0, OFCommandLine::FOM_Next));
631 }
632 cmd.beginOptionBlock();
633 if (cmd.findOption("--format-new")) dcmEnableOldSignatureFormat.set(OFFalse);
634 if (cmd.findOption("--format-old")) dcmEnableOldSignatureFormat.set(OFTrue);
635 cmd.endOptionBlock();
636
637 // timestamp command line options
638 cmd.beginOptionBlock();
639 if (cmd.findOption("--timestamp-off"))
640 {
641 app.checkDependence("--timestamp-off", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
642 opt_timeStamp = NULL;
643 }
644 if (cmd.findOption("--timestamp-file"))
645 {
646 app.checkDependence("--timestamp-file", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
647 app.checkValue(cmd.getValue(opt_ts_queryfile));
648 app.checkValue(cmd.getValue(opt_ts_uidfile));
649 opt_timeStamp = new SiTimeStampFS();
650 opt_timeStamp->setTSQFilename(opt_ts_queryfile);
651 opt_timeStamp->setUIDFilename(opt_ts_uidfile);
652 }
653 cmd.endOptionBlock();
654
655 cmd.beginOptionBlock();
656 if (cmd.findOption("--ts-mac-sha256"))
657 {
658 app.checkDependence("--ts-mac-sha256", "--timestamp-file", (opt_timeStamp != NULL));
659 opt_timeStamp->setMAC(EMT_SHA256);
660 }
661 if (cmd.findOption("--ts-mac-sha384"))
662 {
663 app.checkDependence("--ts-mac-sha384", "--timestamp-file", (opt_timeStamp != NULL));
664 opt_timeStamp->setMAC(EMT_SHA384);
665 }
666 if (cmd.findOption("--ts-mac-sha512"))
667 {
668 app.checkDependence("--ts-mac-sha512", "--timestamp-file", (opt_timeStamp != NULL));
669 opt_timeStamp->setMAC(EMT_SHA512);
670 }
671 if (cmd.findOption("--ts-mac-ripemd160"))
672 {
673 app.checkDependence("--ts-mac-ripemd160", "--timestamp-file", (opt_timeStamp != NULL));
674 opt_timeStamp->setMAC(EMT_RIPEMD160);
675 }
676 if (cmd.findOption("--ts-mac-sha1"))
677 {
678 app.checkDependence("--ts-mac-sha1", "--timestamp-file", (opt_timeStamp != NULL));
679 opt_timeStamp->setMAC(EMT_SHA1);
680 }
681 if (cmd.findOption("--ts-mac-md5"))
682 {
683 app.checkDependence("--ts-mac-md5", "--timestamp-file", (opt_timeStamp != NULL));
684 opt_timeStamp->setMAC(EMT_MD5);
685 }
686 cmd.endOptionBlock();
687
688 cmd.beginOptionBlock();
689 if (cmd.findOption("--ts-use-nonce"))
690 {
691 app.checkDependence("--ts-use-nonce", "--timestamp-file", (opt_timeStamp != NULL));
692 opt_timeStamp->setNonce(OFTrue);
693 }
694 if (cmd.findOption("--ts-no-nonce"))
695 {
696 app.checkDependence("--ts-no-nonce", "--timestamp-file", (opt_timeStamp != NULL));
697 opt_timeStamp->setNonce(OFFalse);
698 }
699 cmd.endOptionBlock();
700
701 cmd.beginOptionBlock();
702 if (cmd.findOption("--ts-request-cert"))
703 {
704 app.checkDependence("--ts-request-cert", "--timestamp-file", (opt_timeStamp != NULL));
705 opt_timeStamp->setCertificateRequested(OFTrue);
706 }
707 if (cmd.findOption("--ts-no-cert"))
708 {
709 app.checkDependence("--ts-no-cert", "--timestamp-file", (opt_timeStamp != NULL));
710 opt_timeStamp->setCertificateRequested(OFFalse);
711 }
712 cmd.endOptionBlock();
713
714 cmd.beginOptionBlock();
715 if (cmd.findOption("--ts-no-policy"))
716 {
717 app.checkDependence("--ts-no-policy", "--timestamp-file", (opt_timeStamp != NULL));
718 opt_timeStamp->setPolicyOID(NULL);
719 }
720 if (cmd.findOption("--ts-policy"))
721 {
722 app.checkDependence("--ts-policy", "--timestamp-file", (opt_timeStamp != NULL));
723 app.checkValue(cmd.getValue(opt_ts_policyoid));
724 opt_timeStamp->setPolicyOID(opt_ts_policyoid);
725 }
726 cmd.endOptionBlock();
727
728 // output command line options
729 cmd.beginOptionBlock();
730 if (cmd.findOption("--write-xfer-same")) opt_oxfer = EXS_Unknown;
731 if (cmd.findOption("--write-xfer-little")) opt_oxfer = EXS_LittleEndianExplicit;
732 if (cmd.findOption("--write-xfer-big")) opt_oxfer = EXS_BigEndianExplicit;
733 if (cmd.findOption("--write-xfer-implicit")) opt_oxfer = EXS_LittleEndianImplicit;
734 cmd.endOptionBlock();
735 cmd.beginOptionBlock();
736 if (cmd.findOption("--length-explicit")) opt_oenctype = EET_ExplicitLength;
737 if (cmd.findOption("--length-undefined")) opt_oenctype = EET_UndefinedLength;
738 cmd.endOptionBlock();
739 if (cmd.findOption("--dump"))
740 {
741 app.checkDependence("--dump", "--sign or --sign-item", (opt_operation == DSO_sign) || (opt_operation == DSO_signItem));
742 const char *fileName = NULL;
743 app.checkValue(cmd.getValue(fileName));
744 opt_dumpFile = fopen(fileName, "wb");
745 if (opt_dumpFile == NULL)
746 {
747 DCMSIGN_FATAL("unable to create dump file '" << fileName << "'");
748 result = EXITCODE_CANNOT_WRITE_SUPPORT_FILE;
749 goto cleanup;
750 }
751 }
752 }
753 /* print resource identifier */
754 DCMSIGN_DEBUG(rcsid << OFendl);
755 /* make sure data dictionary is loaded */
756 if (!dcmDataDict.isDictionaryLoaded())
757 {
758 DCMSIGN_WARN("no data dictionary loaded, "
759 << "check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE);
760 }
761 // open input file
762 if ((opt_ifname == NULL) || (strlen(opt_ifname) == 0))
763 {
764 DCMSIGN_FATAL("invalid filename: <empty string>");
765 result = EXITCODE_NO_INPUT_FILES;
766 goto cleanup;
767 }
768 DCMSIGN_INFO("open input file " << opt_ifname);
769 sicond = fileformat.loadFile(opt_ifname, opt_ixfer, EGL_noChange, DCM_MaxReadLength, opt_readMode);
770 if (sicond.bad())
771 {
772 DCMSIGN_FATAL(sicond.text() << ": reading file: " << opt_ifname);
773 result = EXITCODE_CANNOT_READ_INPUT_FILE;
774 goto cleanup;
775 }
776 dataset = fileformat.getDataset();
777 if (opt_certfile)
778 {
779 sicond = cert.loadCertificate(opt_certfile, opt_keyFileFormat);
780 if (sicond != EC_Normal)
781 {
782 DCMSIGN_FATAL(sicond.text() << ": while loading certificate file '" << opt_certfile << "'");
783 result = EXITCODE_CANNOT_READ_INPUT_FILE;
784 goto cleanup;
785 }
786 }
787 if (opt_keyfile)
788 {
789 if (opt_passwd) key.setPrivateKeyPasswd(opt_passwd);
790 sicond = key.loadPrivateKey(opt_keyfile, opt_keyFileFormat);
791 if (sicond != EC_Normal)
792 {
793 DCMSIGN_FATAL(sicond.text() << ": while loading private key file '" << opt_keyfile << "'");
794 result = EXITCODE_CANNOT_READ_INPUT_FILE;
795 goto cleanup;
796 }
797 }
798 // need to load all data into memory before signing the document,
799 // otherwise the pixel data would be empty for compressed images
800 fileformat.loadAllDataIntoMemory();
801 // select transfer syntax in which digital signatures are created
802 opt_signatureXfer = dataset->getOriginalXfer();
803 // use Little Endian Explicit for uncompressed files
804 if ((opt_signatureXfer == EXS_LittleEndianImplicit) ||
805 (opt_signatureXfer == EXS_BigEndianExplicit))
806 opt_signatureXfer = EXS_LittleEndianExplicit;
807 // now do the real work
808 switch (opt_operation)
809 {
810 case DSO_verify:
811 DCMSIGN_INFO("verifying all signatures.");
812 result = DcmSignatureHelper::do_verify(dataset, certVerifier, opt_verificationPolicy, opt_timestampPolicy);
813 if (result != 0) goto cleanup;
814 break;
815 case DSO_sign:
816 DCMSIGN_INFO("create signature in main object.");
817 result = DcmSignatureHelper::do_sign(dataset, key, cert, opt_mac, opt_profile, opt_tagList, opt_signatureXfer, opt_dumpFile, opt_sigPurpose, opt_timeStamp);
818 if (result != 0) goto cleanup;
819 break;
820 case DSO_signItem:
821 DCMSIGN_INFO("create signature in sequence item.");
822 result = DcmSignatureHelper::do_sign_item(dataset, key, cert, opt_mac, opt_profile, opt_tagList, opt_location, opt_signatureXfer, opt_dumpFile, opt_sigPurpose, opt_timeStamp);
823 if (result != 0) goto cleanup;
824 break;
825 case DSO_insertTimestamp:
826 DCMSIGN_INFO("inserting certified timestamp.");
827 result = DcmSignatureHelper::do_insert_ts(dataset, opt_timeStamp);
828 if (result != 0) goto cleanup;
829 break;
830 case DSO_remove:
831 DCMSIGN_INFO("removing signature from sequence item.");
832 result = DcmSignatureHelper::do_remove(dataset, opt_location);
833 if (result != 0) goto cleanup;
834 break;
835 case DSO_removeAll:
836 DCMSIGN_INFO("removing all signatures.");
837 result = DcmSignatureHelper::do_remove_all(dataset);
838 if (result != 0) goto cleanup;
839 break;
840 }
841 if (opt_dumpFile)
842 {
843 if (0 != fclose(opt_dumpFile))
844 {
845 DCMSIGN_FATAL("Error while closing dump file, content may be incomplete.");
846 }
847 opt_dumpFile = NULL;
848 }
849 if (opt_ofname)
850 {
851 DCMSIGN_INFO("create output file " << opt_ofname);
852 if (opt_oxfer == EXS_Unknown) opt_oxfer = dataset->getOriginalXfer();
853 DcmXfer opt_oxferSyn(opt_oxfer);
854 if (dataset->chooseRepresentation(opt_oxfer, NULL).bad() || (! dataset->canWriteXfer(opt_oxfer)))
855 {
856 DCMSIGN_FATAL("No conversion to transfer syntax " << opt_oxferSyn.getXferName() << " possible!");
857 result = EXITCODE_CANNOT_WRITE_OUTPUT_FILE;
858 goto cleanup;
859 }
860 sicond = fileformat.saveFile(opt_ofname, opt_oxfer, opt_oenctype, opt_oglenc, opt_opadenc, OFstatic_cast(Uint32, opt_filepad), OFstatic_cast(Uint32, opt_itempad));
861 if (sicond.bad())
862 {
863 DCMSIGN_FATAL(sicond.text() << ": writing file: " << opt_ofname);
864 result = EXITCODE_CANNOT_WRITE_OUTPUT_FILE;
865 goto cleanup;
866 }
867 }
868
869 cleanup:
870
871 delete opt_timeStamp;
872 delete opt_mac;
873 delete opt_profile;
874 delete opt_tagList;
875 return result;
876 }
877
878 #else /* WITH_OPENSSL */
879
main(int,char * [])880 int main(int, char *[])
881 {
882 CERR << rcsid << OFendl << APPLICATION_ABSTRACT << OFendl << OFendl
883 << OFFIS_CONSOLE_APPLICATION " requires the OpenSSL library." << OFendl
884 << "This " OFFIS_CONSOLE_APPLICATION " has been configured and compiled without OpenSSL." << OFendl
885 << "Please reconfigure your system and recompile if appropriate." << OFendl;
886 return EXITCODE_NOOPENSSL;
887 }
888
889 #endif /* WITH_OPENSSL */
890