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