1 /*
2  *
3  *  Copyright (C) 2017-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: dcmtls
15  *
16  *  Author: Jan Schlamelcher, Marco Eichelberg
17  *
18  *  Purpose:
19  *    classes: DcmTLSOptions
20  *
21  */
22 
23 #include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
24 #include "dcmtk/dcmtls/tlsopt.h"
25 #include "dcmtk/ofstd/ofconapp.h"
26 #include "dcmtk/dcmtls/tlscond.h"
27 #include "dcmtk/dcmnet/assoc.h"       /* for ASC_setTransportLayer() */
28 
printLibraryVersion()29 void DcmTLSOptions::printLibraryVersion()
30 {
31 #ifdef WITH_OPENSSL
32     COUT << "- " << DcmTLSTransportLayer::getOpenSSLVersionName() << OFendl;
33 #endif // WITH_OPENSSL
34 }
35 
DcmTLSOptions(T_ASC_NetworkRole networkRole)36 DcmTLSOptions::DcmTLSOptions(T_ASC_NetworkRole networkRole)
37 #ifdef WITH_OPENSSL
38 : opt_keyFileFormat( DCF_Filetype_PEM )
39 , opt_doAuthenticate( OFFalse )
40 , opt_privateKeyFile( OFnullptr )
41 , opt_certificateFile( OFnullptr )
42 , opt_passwd( OFnullptr )
43 , opt_tlsProfile( TSP_Profile_BCP195 ) // default: BCP 195 profile
44 , opt_readSeedFile( OFnullptr )
45 , opt_writeSeedFile( OFnullptr )
46 , opt_certVerification( DCV_requireCertificate )
47 , opt_dhparam( OFnullptr )
48 , opt_secureConnection( OFFalse ) // default: no secure connection
49 , opt_networkRole( networkRole )
50 , tLayer( OFnullptr )
51 #endif
52 {
53 }
54 
~DcmTLSOptions()55 DcmTLSOptions::~DcmTLSOptions()
56 {
57 #ifdef WITH_OPENSSL
58   delete tLayer;
59 #endif
60 }
61 
addTLSCommandlineOptions(OFCommandLine & cmd)62 void DcmTLSOptions::addTLSCommandlineOptions(OFCommandLine& cmd)
63 {
64 #ifdef WITH_OPENSSL
65   DcmTLSCiphersuiteHandler csh;
66 
67   cmd.addGroup("transport layer security (TLS) options:");
68     cmd.addSubGroup("transport protocol stack:");
69       cmd.addOption("--disable-tls",        "-tls",    "use normal TCP/IP connection (default)");
70       cmd.addOption("--enable-tls",         "+tls", 2, "[p]rivate key file, [c]ertificate file: string",
71                                                        "use authenticated secure TLS connection");
72       if (opt_networkRole == NET_REQUESTOR)
73       {
74          // this command line options only makes sense for association requesters (TLS clients)
75          cmd.addOption("--anonymous-tls",   "+tla",    "use secure TLS connection without certificate");
76       }
77     cmd.addSubGroup("private key password (only with --enable-tls):");
78       cmd.addOption("--std-passwd",         "+ps",     "prompt user to type password on stdin (default)");
79       cmd.addOption("--use-passwd",         "+pw",  1, "[p]assword: string ",
80                                                        "use specified password");
81       cmd.addOption("--null-passwd",        "-pw",     "use empty string as password");
82     cmd.addSubGroup("key and certificate file format:");
83       cmd.addOption("--pem-keys",           "-pem",    "read keys and certificates as PEM file (default)");
84       cmd.addOption("--der-keys",           "-der",    "read keys and certificates as DER file");
85     cmd.addSubGroup("certification authority:");
86       cmd.addOption("--add-cert-file",      "+cf",  1, "[f]ilename: string",
87                                                        "add certificate file to list of certificates");
88       cmd.addOption("--add-cert-dir",       "+cd",  1, "[d]irectory: string",
89                                                        "add certificates in d to list of certificates");
90     cmd.addSubGroup("security profile:");
91       cmd.addOption("--profile-bcp195",     "+px",     "BCP 195 TLS Profile (default)");
92       cmd.addOption("--profile-bcp195-nd",  "+py",     "Non-downgrading BCP 195 TLS Profile");
93       cmd.addOption("--profile-bcp195-ex",  "+pz",     "Extended BCP 195 TLS Profile");
94       if (csh.cipher3DESsupported())
95       {
96         cmd.addOption("--profile-basic",    "+pb",     "Basic TLS Secure Transport Connection Profile\n(retired)");
97       }
98       cmd.addOption("--profile-aes" ,       "+pa",     "AES TLS Secure Transport Connection Profile\n(retired)");
99       if (csh.cipherNULLsupported())
100       {
101         cmd.addOption("--profile-null",     "+pn",     "Authenticated unencrypted communication\n(retired, was used in IHE ATNA)");
102       }
103 
104     cmd.addSubGroup("ciphersuite:");
105       cmd.addOption("--list-ciphers",       "+cc",     "show list of supported TLS ciphersuites and exit", OFCommandLine::AF_Exclusive);
106       cmd.addOption("--cipher",             "+cs",  1, "[c]iphersuite name: string",
107                                                        "add ciphersuite to list of negotiated suites\n(not with --profile-bcp195-ex)");
108       if (opt_networkRole != NET_REQUESTOR)
109       {
110         // this command line options only makes sense for association acceptors (TLS servers)
111         // or systems that accept and request associations
112         cmd.addOption("--dhparam",          "+dp",  1, "[f]ilename: string",
113                                                        "read DH parameters for DH/DSS ciphersuites");
114       }
115     cmd.addSubGroup("pseudo random generator:");
116       cmd.addOption("--seed",               "+rs",  1, "[f]ilename: string",
117                                                        "seed random generator with contents of f");
118       cmd.addOption("--write-seed",         "+ws",     "write back modified seed (only with --seed)");
119       cmd.addOption("--write-seed-file",    "+wf",  1, "[f]ilename: string (only with --seed)",
120                                                        "write modified seed to file f");
121     cmd.addSubGroup("peer authentication:");
122       cmd.addOption("--require-peer-cert",  "-rc",     "verify peer certificate, fail if absent (default)");
123       if (opt_networkRole != NET_REQUESTOR)
124       {
125         // this command line options only makes sense for association acceptors (TLS servers)
126         // or systems that accept and request associations
127         cmd.addOption("--verify-peer-cert", "-vc",     "verify peer certificate if present");
128       }
129       cmd.addOption("--ignore-peer-cert",   "-ic",     "don't verify peer certificate");
130 
131 #endif // WITH_OPENSSL
132 }
133 
parseArguments(OFConsoleApplication & app,OFCommandLine & cmd)134 void DcmTLSOptions::parseArguments(OFConsoleApplication& app, OFCommandLine& cmd)
135 {
136 #ifdef WITH_OPENSSL
137     DcmTLSCiphersuiteHandler csh;
138 
139     const char *tlsopts = (opt_networkRole == NET_REQUESTOR ?
140         "--enable-tls or --anonymous-tls" : "--enable-tls");
141 
142     cmd.beginOptionBlock();
143     if( cmd.findOption( "--disable-tls" ) )
144         opt_secureConnection = OFFalse;
145     if( cmd.findOption( "--enable-tls" ) )
146     {
147         opt_secureConnection = OFTrue;
148         opt_doAuthenticate = OFTrue;
149         app.checkValue( cmd.getValue( opt_privateKeyFile ) );
150         app.checkValue( cmd.getValue( opt_certificateFile ) );
151     }
152     if (opt_networkRole == NET_REQUESTOR)
153     {
154         if( cmd.findOption( "--anonymous-tls" ) ) opt_secureConnection = OFTrue;
155     }
156     cmd.endOptionBlock();
157 
158     cmd.beginOptionBlock();
159     if( cmd.findOption( "--std-passwd" ) )
160     {
161         app.checkDependence("--std-passwd", "--enable-tls", opt_doAuthenticate);
162         opt_passwd = OFnullptr;
163     }
164     if( cmd.findOption("--use-passwd") )
165     {
166         app.checkDependence( "--use-passwd", "--enable-tls", opt_doAuthenticate );
167         app.checkValue( cmd.getValue( opt_passwd ) );
168     }
169     if( cmd.findOption( "--null-passwd" ) )
170     {
171         app.checkDependence( "--null-passwd", "--enable-tls", opt_doAuthenticate );
172         opt_passwd = "";
173     }
174     cmd.endOptionBlock();
175 
176     cmd.beginOptionBlock();
177     if( cmd.findOption( "--pem-keys" ) )
178     {
179         app.checkDependence("--pem-keys", tlsopts, opt_secureConnection);
180         opt_keyFileFormat = DCF_Filetype_PEM;
181     }
182     if( cmd.findOption( "--der-keys" ) )
183     {
184         app.checkDependence("--der-keys", tlsopts, opt_secureConnection);
185         opt_keyFileFormat = DCF_Filetype_ASN1;
186     }
187     cmd.endOptionBlock();
188 
189     if( (opt_networkRole != NET_REQUESTOR) && cmd.findOption( "--dhparam" ) )
190     {
191         app.checkDependence("--dhparam", tlsopts, opt_secureConnection);
192         app.checkValue( cmd.getValue( opt_dhparam ) );
193     }
194     if( cmd.findOption( "--seed" ) )
195     {
196         app.checkDependence("--seed", tlsopts, opt_secureConnection);
197         app.checkValue( cmd.getValue( opt_readSeedFile ) );
198     }
199 
200     cmd.beginOptionBlock();
201     if( cmd.findOption( "--write-seed" ) )
202     {
203         app.checkDependence("--write-seed", tlsopts, opt_secureConnection);
204         app.checkDependence( "--write-seed", "--seed", opt_readSeedFile != OFnullptr );
205         opt_writeSeedFile = opt_readSeedFile;
206     }
207     if( cmd.findOption( "--write-seed-file" ) )
208     {
209         app.checkDependence("--write-seed-file", tlsopts, opt_secureConnection);
210         app.checkDependence( "--write-seed-file", "--seed", opt_readSeedFile != OFnullptr );
211         app.checkValue( cmd.getValue( opt_writeSeedFile ) );
212     }
213     cmd.endOptionBlock();
214 
215     cmd.beginOptionBlock();
216     if( cmd.findOption( "--require-peer-cert" ) )
217     {
218         app.checkDependence("--require-peer-cert", tlsopts, opt_secureConnection);
219         opt_certVerification = DCV_requireCertificate;
220     }
221     if( cmd.findOption( "--ignore-peer-cert" ) )
222     {
223         app.checkDependence("--ignore-peer-cert", tlsopts, opt_secureConnection);
224         opt_certVerification = DCV_ignoreCertificate;
225     }
226     if ( (opt_networkRole != NET_REQUESTOR) && cmd.findOption( "--verify-peer-cert" ) )
227     {
228         app.checkDependence("--verify-peer-cert", tlsopts, opt_secureConnection);
229         opt_certVerification = DCV_checkCertificate;
230     }
231     cmd.endOptionBlock();
232 
233     cmd.beginOptionBlock();
234     if (cmd.findOption("--profile-bcp195"))
235     {
236         app.checkDependence("--profile-bcp195", tlsopts, opt_secureConnection);
237         opt_tlsProfile = TSP_Profile_BCP195;
238     }
239     if (cmd.findOption("--profile-bcp195-nd"))
240     {
241         app.checkDependence("--profile-bcp195-nd", tlsopts, opt_secureConnection);
242         opt_tlsProfile = TSP_Profile_BCP195_ND;
243     }
244     if (cmd.findOption("--profile-bcp195-ex"))
245     {
246         app.checkDependence("--profile-bcp195-ex", tlsopts, opt_secureConnection);
247         opt_tlsProfile = TSP_Profile_BCP195_Extended;
248     }
249     if (csh.cipher3DESsupported())
250     {
251       if (cmd.findOption("--profile-basic"))
252       {
253           app.checkDependence("--profile-basic", tlsopts, opt_secureConnection);
254           opt_tlsProfile = TSP_Profile_Basic;
255       }
256     }
257     if (cmd.findOption("--profile-aes"))
258     {
259         app.checkDependence("--profile-basic", tlsopts, opt_secureConnection);
260         opt_tlsProfile = TSP_Profile_AES;
261     }
262     if (csh.cipherNULLsupported())
263     {
264       if (cmd.findOption("--profile-null"))
265       {
266           app.checkDependence("--profile-null", tlsopts, opt_secureConnection);
267           opt_tlsProfile = TSP_Profile_IHE_ATNA_Unencrypted;
268       }
269     }
270     cmd.endOptionBlock();
271 
272     // check the other TLS specific options that will only be evaluated
273     // later in DcmTLSOptions::createTransportLayer().
274     if (cmd.findOption("--add-cert-file", 0, OFCommandLine::FOM_First))
275       app.checkDependence("--add-cert-file", tlsopts, opt_secureConnection);
276     if (cmd.findOption("--add-cert-dir", 0, OFCommandLine::FOM_First))
277       app.checkDependence("--add-cert-dir", tlsopts, opt_secureConnection);
278     if (cmd.findOption("--cipher", 0, OFCommandLine::FOM_First))
279     {
280       app.checkDependence("--cipher", tlsopts, opt_secureConnection);
281       app.checkConflict("--cipher", "--profile-bcp195-ex", (opt_tlsProfile == TSP_Profile_BCP195_Extended));
282     }
283 
284 #endif
285 }
286 
createTransportLayer(T_ASC_Network * net,T_ASC_Parameters * params,OFConsoleApplication & app,OFCommandLine & cmd)287 OFCondition DcmTLSOptions::createTransportLayer(
288       T_ASC_Network *net,
289       T_ASC_Parameters *params,
290       OFConsoleApplication& app,
291       OFCommandLine& cmd)
292 {
293 
294 #ifdef WITH_OPENSSL
295     if (opt_secureConnection)
296     {
297       delete tLayer;
298       tLayer = new DcmTLSTransportLayer(opt_networkRole, opt_readSeedFile, OFFalse);
299       if (tLayer == NULL) return DCMTLS_EC_FailedToCreateTLSTransportLayer;
300 
301       if (cmd.findOption("--add-cert-file", 0, OFCommandLine::FOM_First))
302       {
303         const char *current = NULL;
304         do
305         {
306           app.checkValue(cmd.getValue(current));
307           if (TCS_ok != tLayer->addTrustedCertificateFile(current, opt_keyFileFormat))
308           {
309               DCMTLS_WARN("unable to load certificate file '" << current << "', ignoring");
310           }
311         } while (cmd.findOption("--add-cert-file", 0, OFCommandLine::FOM_Next));
312       }
313 
314       if (cmd.findOption("--add-cert-dir", 0, OFCommandLine::FOM_First))
315       {
316         const char *current = NULL;
317         do
318         {
319           app.checkValue(cmd.getValue(current));
320           if (TCS_ok != tLayer->addTrustedCertificateDir(current, opt_keyFileFormat))
321           {
322             DCMTLS_WARN("unable to load certificates from directory '" << current << "', ignoring");
323           }
324         } while (cmd.findOption("--add-cert-dir", 0, OFCommandLine::FOM_Next));
325       }
326 
327       if (opt_doAuthenticate)
328       {
329         if (opt_passwd)
330            tLayer->setPrivateKeyPasswd(opt_passwd);
331            else tLayer->setPrivateKeyPasswdFromConsole();
332 
333         if (TCS_ok != tLayer->setPrivateKeyFile(opt_privateKeyFile, opt_keyFileFormat))
334            return DCMTLS_EC_FailedToLoadPrivateKey( opt_privateKeyFile );
335         if (TCS_ok != tLayer->setCertificateFile(opt_certificateFile, opt_keyFileFormat))
336            return DCMTLS_EC_FailedToLoadCertificate( opt_certificateFile );
337         if (! tLayer->checkPrivateKeyMatchesCertificate())
338            return DCMTLS_EC_MismatchedPrivateKeyAndCertificate( opt_privateKeyFile, opt_certificateFile );
339       }
340 
341       // set TLS profile
342       if (TCS_ok != tLayer->setTLSProfile(opt_tlsProfile))
343          return DCMTLS_EC_FailedToSetCiphersuites;
344 
345       // add additional ciphersuites
346       if (cmd.findOption("--cipher", 0, OFCommandLine::FOM_First))
347       {
348         const char *current = NULL;
349         do
350         {
351           app.checkValue(cmd.getValue(current));
352           if (TCS_ok != tLayer->addCipherSuite(current))
353              return DCMTLS_EC_UnknownCiphersuite( current );
354         } while (cmd.findOption("--cipher", 0, OFCommandLine::FOM_Next));
355       }
356 
357       if (TCS_ok != tLayer->activateCipherSuites())
358          return DCMTLS_EC_FailedToSetCiphersuites;
359 
360       // Loading of DH parameters should happen after the call to setTLSProfile()
361       // because otherwise we cannot check profile specific restrictions
362       if (opt_dhparam && ! (tLayer->setTempDHParameters(opt_dhparam)))
363          DCMTLS_WARN("unable to load temporary DH parameter file '" << opt_dhparam << "', ignoring");
364 
365       tLayer->setCertificateVerification(opt_certVerification);
366 
367       if (net)
368       {
369         OFCondition cond = ASC_setTransportLayer(net, tLayer, 0);
370         if (cond.bad()) return cond;
371       }
372 
373       if (params)
374       {
375         OFCondition cond2 = ASC_setTransportLayerType(params, opt_secureConnection);
376         if (cond2.bad()) return cond2;
377       }
378     }
379 
380 #endif // WITH_OPENSSL
381     return EC_Normal;
382 }
383 
listOfCiphersRequested(OFCommandLine & cmd)384 OFBool DcmTLSOptions::listOfCiphersRequested(OFCommandLine& cmd)
385 {
386 #ifdef WITH_OPENSSL
387   if (cmd.findOption("--list-ciphers")) return OFTrue;
388 #endif
389   return OFFalse;
390 }
391 
printSupportedCiphersuites(OFConsoleApplication & app,STD_NAMESPACE ostream & os)392 void DcmTLSOptions::printSupportedCiphersuites(OFConsoleApplication& app, STD_NAMESPACE ostream& os)
393 {
394 #ifdef WITH_OPENSSL
395   DcmTLSCiphersuiteHandler csh;
396   app.printHeader(OFTrue /*print host identifier*/);
397   os << OFendl << "Supported TLS ciphersuites are:" << OFendl;
398   csh.printSupportedCiphersuites(os);
399 #endif
400 }
401 
secureConnectionRequested() const402 OFBool DcmTLSOptions::secureConnectionRequested() const
403 {
404 #ifdef WITH_OPENSSL
405   return opt_secureConnection;
406 #else
407   return OFFalse;
408 #endif
409 }
410 
getTransportLayer()411 DcmTransportLayer *DcmTLSOptions::getTransportLayer()
412 {
413 #ifdef WITH_OPENSSL
414   return tLayer;
415 #else
416   return NULL;
417 #endif
418 }
419 
writeRandomSeed()420 OFCondition DcmTLSOptions::writeRandomSeed()
421 {
422 #ifdef WITH_OPENSSL
423     if( opt_writeSeedFile && tLayer)
424     {
425         if( tLayer->canWriteRandomSeed() )
426         {
427             if( ! tLayer->writeRandomSeed( opt_writeSeedFile ) )
428                 return DCMTLS_EC_FailedToWriteRandomSeedFile( opt_writeSeedFile );
429         }
430         else return DCMTLS_EC_FailedToWriteRandomSeedFile;
431     }
432 #endif
433     return EC_Normal;
434 }
435