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