1 /*
2  *
3  *  Copyright (C) 1998-2019, 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: Marco Eichelberg
17  *
18  *  Purpose:
19  *    classes: DcmTLSTransportLayer
20  *
21  */
22 
23 #ifndef TLSLAYER_H
24 #define TLSLAYER_H
25 
26 #include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
27 #include "dcmtk/dcmnet/dcmlayer.h"    /* for DcmTransportLayer */
28 #include "dcmtk/dcmnet/assoc.h"       /* for T_ASC_NetworkRole */
29 #include "dcmtk/ofstd/ofstream.h"     /* for ostream */
30 #include "dcmtk/oflog/oflog.h"
31 #include "dcmtk/dcmtls/tlsdefin.h"
32 #include "dcmtk/dcmtls/tlsciphr.h"    /* for DcmTLSCiphersuiteHandler */
33 
34 #ifdef WITH_OPENSSL
35 
36 // forward declarations of OpenSSL data structures
37 struct ssl_ctx_st;
38 typedef struct ssl_ctx_st SSL_CTX;
39 
40 struct x509_st;
41 typedef struct x509_st X509;
42 
43 extern DCMTK_DCMTLS_EXPORT OFLogger DCM_dcmtlsLogger;
44 
45 #define DCMTLS_TRACE(msg) OFLOG_TRACE(DCM_dcmtlsLogger, msg)
46 #define DCMTLS_DEBUG(msg) OFLOG_DEBUG(DCM_dcmtlsLogger, msg)
47 #define DCMTLS_INFO(msg)  OFLOG_INFO(DCM_dcmtlsLogger, msg)
48 #define DCMTLS_WARN(msg)  OFLOG_WARN(DCM_dcmtlsLogger, msg)
49 #define DCMTLS_ERROR(msg) OFLOG_ERROR(DCM_dcmtlsLogger, msg)
50 #define DCMTLS_FATAL(msg) OFLOG_FATAL(DCM_dcmtlsLogger, msg)
51 
52 // include this file in doxygen documentation
53 
54 /** @file tlslayer.h
55  *  @brief type definitions and classes for TLS transport connections
56  */
57 
58 
59 /** this enum describes how to handle X.509 certificates on a TLS based
60  *  secure transport connection. They can be ignored, validated if present
61  *  or validated and demanded.
62  *  @remark this enum is only available if DCMTK is compiled with
63  *  OpenSSL support enabled.
64  */
65 enum DcmCertificateVerification
66 {
67   /** check peer certificate, fail if no certificate is present
68    */
69   DCV_requireCertificate,
70 
71   /** check peer certificate if present, succeed if no certificate is present
72    */
73   DCV_checkCertificate,
74 
75   /** do not check peer certificate
76    */
77   DCV_ignoreCertificate
78 };
79 
80 
81 /** this enum describes the file format of a certificate or private key file.
82  *  @remark this enum is only available if DCMTK is compiled with
83  *  OpenSSL support enabled.
84  */
85 enum DcmKeyFileFormat
86 {
87  /** PEM (Privacy Enhanced Mail) format
88   */
89  DCF_Filetype_PEM,
90 
91  /** ASN.1 (Abstract Syntax Notation One) format
92   */
93  DCF_Filetype_ASN1
94 };
95 
96 
97 /** factory class which creates secure TLS transport layer connections
98  *  and maintains the parameters common to all TLS transport connections
99  *  in one application (e.g. the pool of trusted certificates, the key
100  *  and certificate to be used for authentication and the list of
101  *  ciphersuite to be used for association negotiation.
102  *  @remark this class is only available if DCMTK is compiled with
103  *  OpenSSL support enabled.
104  */
105 
106 class DCMTK_DCMTLS_EXPORT DcmTLSTransportLayer: public DcmTransportLayer
107 {
108 public:
109 
110   /** a type alias for the type of the underlying OpenSSL context handle to be
111    *  used in conjunction with the getNativeHandle() member function.
112    */
113   typedef SSL_CTX* native_handle_type;
114 
115   /** constructor.
116    *  Constructs a DcmTLSTransportLayer object without initializing it, e.g.
117    *  as a placeholder that may or may not be used later depending on user
118    *  input.
119    */
120   DcmTLSTransportLayer();
121 
122   /** constructor.
123    *  @param networkRole network role to be used by the application
124    *  @param randFile path to file used to feed the random generator
125    *  @param initializeOpenSSL Determines if OpenSSL library should be initialized.
126    *    Some setups (e.g. multi-threaded environments) may be interested in using
127    *    more than one TLS transport layer at a time and thus must make sure the
128    *    library is only initialized once.
129    */
130   DcmTLSTransportLayer(T_ASC_NetworkRole networkRole, const char *randFile, OFBool initializeOpenSSL);
131 
132   /** move constructor.
133    *  Transfer ownership from another DcmTLSTransportLayer object to the newly
134    *  constructed object (*this).
135    *  @param rhs an rvalue reference to another DcmTLSTransportLayer object.
136    */
137   DcmTLSTransportLayer(OFrvalue_ref(DcmTLSTransportLayer) rhs);
138 
139   /** move assignment.
140    *  Assign ownership from another DcmTLSTransportLayer object to *this,
141    *  freeing the existing object first (if any).
142    *  @param rhs an rvalue reference to another DcmTLSTransportLayer object.
143    *  @return *this.
144    */
145   DcmTLSTransportLayer& operator=(OFrvalue_ref(DcmTLSTransportLayer) rhs);
146 
147   /// destructor
148   virtual ~DcmTLSTransportLayer();
149 
150   /** Free resources, e.g. the OpenSSL context used by this object and reset
151    *  all members to the default values. Will do nothing if this object has
152    *  not been initialized, e.g. by using the default constructor.
153    */
154   void clear();
155 
156 #ifdef HAVE_CXX11
157   explicit
158 #endif // HAVE_CXX11
159   /** Query whether this object has been initialized successfully, i.e.
160    *  whether it owns a successfully created OpenSSL context.
161    *  @return OFTrue if *this owns refers to a valid OpenSSL context,
162    *    OFFalse otherwise.
163    *  @note If C++11 support is available, the conversion operator is marked as
164    *    <tt>explicit</tt>, which prevents <i>*this</i> to be interpreted as a
165    *    boolean value in an inappropriate context. You should use this operator
166    *    with caution when C++11 support is unavailable, as <i>*this</i> might
167    *    be converted to a boolean value automatically where it shouldn't.
168    */
169   operator OFBool() const;
170 
171   /** Query whether this object has not been initialized, e.g. has been
172    *  constructed using the default constructor or the initialization failed.
173    *  @return OFTrue if *this is not initialized, OFFalse otherwise.
174    */
175   OFBool operator!() const;
176 
177   /** factory method that returns a new transport connection for the
178    *  given socket.  Depending on the second parameter, either a transparent
179    *  or a secure connection is established.  If the object cannot be created
180    *  (e. g. because no secure layer is available), returns NULL.
181    *  @param openSocket TCP/IP socket to be used for the transport connection.
182    *    the connection must already be established on socket level. If a non-null
183    *    pointer is returned, the new connection object takes over control of the socket.
184    *  @param useSecureLayer if true, a secure layer is used. If false, a
185    *    transparent layer is used.
186    *  @return pointer to new connection object if successful, NULL otherwise.
187    */
188   virtual DcmTransportConnection *createConnection(DcmNativeSocketType openSocket, OFBool useSecureLayer);
189 
190   /** loads the private key used for authentication of this application from a file.
191    *  @param fileName path to the private key file
192    *  @param fileType, must be SSL_FILETYPE_PEM or SSL_FILETYPE_ASN1
193    *  @return TCS_ok if successful, an error code otherwise
194    */
195   DcmTransportLayerStatus setPrivateKeyFile(const char *fileName, DcmKeyFileFormat fileType);
196 
197   /** loads the certificate (public key) used for authentication of this application from a file.
198    *  @param fileName path to the certificate file
199    *  @param fileType, must be SSL_FILETYPE_PEM or SSL_FILETYPE_ASN1
200    *  @return TCS_ok if successful, an error code otherwise
201    */
202   DcmTransportLayerStatus setCertificateFile(const char *fileName, DcmKeyFileFormat fileType);
203 
204   /** checks if the private key and the certificate set using setPrivateKeyFile()
205    *  and setCertificateFile() match, i.e. if they establish a private/public key pair.
206    *  @return OFTrue if private key and certificate match, OFFalse otherwise.
207    */
208   OFBool checkPrivateKeyMatchesCertificate();
209 
210   /** loads a certificate from a file and adds it to the pool of trusted certificates.
211    *  @param fileName path to the certificate file
212    *  @param fileType, must be SSL_FILETYPE_PEM or SSL_FILETYPE_ASN1
213    *  @return TCS_ok if successful, an error code otherwise
214    */
215   DcmTransportLayerStatus addTrustedCertificateFile(const char *fileName, DcmKeyFileFormat fileType);
216 
217   /** loads all files as certificates from the specified directory and adds them
218    *  to the pool of trusted certificates.
219    *  @param fileName path to the directory containing certificate files
220    *  @param fileType, must be SSL_FILETYPE_PEM or SSL_FILETYPE_ASN1
221    *  @return TCS_ok if successful, an error code otherwise
222    */
223   DcmTransportLayerStatus addTrustedCertificateDir(const char *pathName, DcmKeyFileFormat fileType);
224 
225   /** loads certificates from a file and adds them to the pool of trusted client
226    *  certificates.
227    *  @param fileName path to the certificate file
228    *  @return TCS_ok if successful, an error code otherwise
229    */
230   DcmTransportLayerStatus addTrustedClientCertificateFile(const char *fileName);
231 
232   /** appends the given verification flags to the existing ones in this OpenSSL context
233    *  (using binary or).
234    *  @warning Documentation for the underlying OpenSSL functions is not available,
235    *    therefore, these semantics were guessed based on looking at the OpenSSL source
236    *    code!
237    *  @param flags the verification flags to append, e. g. X509_V_FLAG_CRL_CHECK.
238    *  @return TCS_ok if the flags were appended to the existing ones, TCS_unspecifiedError
239    *    if OpenSSL returns an (unspecified, since the documentation is missing) error.
240    */
241   DcmTransportLayerStatus addVerificationFlags(unsigned long flags);
242 
243   /** replace the current list of ciphersuites by the list of ciphersuites
244    *  for the given profile.
245    *  @param profile TLS Security Profile
246    *  @return TCS_ok if successful, an error code otherwise
247    */
248   DcmTransportLayerStatus setTLSProfile(DcmTLSSecurityProfile profile);
249 
250   /** clear the current list of ciphersuites. Equivalent to
251    *  calling setTLSProfile(TSP_Profile_None).
252    */
253   void clearTLSProfile();
254 
255   /** adds a ciphersuite to the list of ciphersuites for TLS negotiation.
256    *  It is the responsibility of the user to ensure that the added ciphersuite
257    *  does not break the rules of the selected profile. Use with care!
258    *  @param suite TLS ciphersuite name, in the official TLS name form.
259    *  @return TCS_ok if successful, an error code otherwise
260    */
261   DcmTransportLayerStatus addCipherSuite(const char *suite);
262 
263   /** activate the current list of ciphersuites by transferring to the OpenSSL layer
264    *  This method needs to be called once after the list of ciphersuites has been
265    *  defined used setTLSProfile() and addCipherSuite().
266    *  @return TCS_ok if successful, an error code otherwise
267    */
268   DcmTransportLayerStatus activateCipherSuites();
269 
270   /** sets the list of ciphersuites to negotiate, in OpenSSL syntax.
271    *  @note This method is deprecated because it breaks the encapsulation of the
272    *    underlying TLS library (i.e. the parameter string is OpenSSL specific)
273    *    and because this method can be used to violate the constraints of the
274    *    TLS profiles, which other otherwise enforced by this class.
275    *    The newer methods setTLSProfile() and addCipherSuite(), introduced with
276    *    DCMTK 3.6.4, offer a cleaner interface that should be preferred.
277    *  @param suites string containing the list of ciphersuites.
278    *    The list must be in OpenSSL syntax (use findOpenSSLCipherSuiteName to convert
279    *    from RFC 2246 ciphersuite names to OpenSSL names), with ciphersuites separated
280    *    by ':' characters.
281    *  @return TCS_ok if successful, an error code otherwise
282    */
283   DcmTransportLayerStatus setCipherSuites(const char *suites);
284 
285   /** checks if enough entropy data is available to write back a modified
286    *  random seed file.
287    *  @return OFTrue if random seed file can be written, OFFalse otherwise.
288    */
canWriteRandomSeed()289   OFBool canWriteRandomSeed() { return canWriteRandseed; }
290 
291   /** writes a modified random seed to file.
292    *  @param randFile path of file to write
293    *  @return OFTrue if successful, OFFalse otherwise.
294    */
295   OFBool writeRandomSeed(const char *randFile);
296 
297   /** adds the contents of a file to the seed for the cryptographic
298    *  pseudo-random number generator. The file should contain real
299    *  random entropy data gathered from keystrokes, system events,
300    *  /dev/random (on Linux) or something similar.
301    *  If the TLS layer object is not initialized with sufficient
302    *  random data, negotiation of TLS connections may fail.
303    *  @param randFile path of the file containing random data
304    */
305   void seedPRNG(const char *randFile);
306 
307   /** modifies the PRNG by adding random data from the given buffer
308    *  to the PRNG state.
309    *  @param buf pointer to buffer containing random data
310    *  @bufSize number of bytes in buffer
311    */
312   void addPRNGseed(void *buf, size_t bufSize);
313 
314   /** defines how peer certificates should be treated when
315    *  negotiating a TLS connection.
316    *  @param vtype certificate verification mode
317    */
318   void setCertificateVerification(DcmCertificateVerification vtype);
319 
320   /** sets the password string to be used when loading an
321    *  encrypted private key file.
322    *  Must be called prior to setPrivateKeyFile() in order to be effective.
323    *  @param thePasswd password string, may be "" or NULL in which case an empty
324    *    password is assumed.
325    */
326   void setPrivateKeyPasswd(const char *thePasswd);
327 
328   /** sets the password string to be used when loading an
329    *  encrypted private key file to be read from the console stdin.
330    */
331   void setPrivateKeyPasswdFromConsole();
332 
333   /** loads a set of Diffie-Hellman parameters from file.
334    *  These parameters are required for DH, DHE or DSS ciphersuites.
335    *  @param filename path to the DH parameter file
336    *  @return OFTrue if successful, OFFalse otherwise.
337    */
338   OFBool setTempDHParameters(const char *filename);
339 
340   /** print a list of supported ciphersuites to the given output stream.
341    *  @param os output stream
342    */
343   void printSupportedCiphersuites(STD_NAMESPACE ostream& os) const;
344 
345   /** Initialize OpenSSL Library. This function is THREAD UNSAFE
346    *  and should only be called once to initialize the OpenSSL library.
347    */
348   static void initializeOpenSSL();
349 
350   /** gets the most important attributes of the given X.509 certificate.
351    *  @param peerCertificate X.509 certificate, may be NULL
352    *  @return a string describing the certificate
353    */
354   static OFString dumpX509Certificate(X509 *peerCertificate);
355 
356   /** gets the size of the public key of an RSA certificate.
357    *  @param certificate X.509 certificate
358    *  @return public key size (in bits) if RSA certificate, 0 otherwise.
359    */
360   static int getRSAKeySize(X509 *certificate);
361 
362   /** checks the BCP 195 recommendations that RSA certificates
363    *  should use SHA-256 hash keys. We also accept better SHA-2
364    *  hash keys (SHA-384 and SHA-512).
365    *  @param certificate X.509 certificate
366    *  @return NULL if everything is OK (i.e. the certificate is
367    *    not RSA, or it is RSA and uses SHA-256 or better),
368    *    the name of the hash key algorithm used otherwise.
369    */
370   static const char *checkRSAHashKeyIsSHA2(X509 *certificate);
371 
372   /** returns the version name of the OpenSSL version used.
373    *  @return OpenSSL version name, never NULL.
374    */
375   static const char *getOpenSSLVersionName();
376 
377   /** load an X.509 certificate from file.
378    *  @param fileName path to the certificate file
379    *  @param fileType, must be SSL_FILETYPE_PEM or SSL_FILETYPE_ASN1
380    *  @return pointer to X509 object if successful, NULL otherwise.
381    *    The X509 object must be freed by the caller.
382    */
383   static X509 *loadCertificateFile(const char *fileName, DcmKeyFileFormat fileType);
384 
385   /** returns a string in OpenSSL syntax that contains the currently defined
386    *  list of TLS ciphersuites.
387    *  @param cslist The list of ciphersuites in OpenSSL syntax is written to this string.
388    */
389   void getListOfCipherSuitesForOpenSSL(OFString& cslist) const;
390 
391   /** provides access to the underlying OpenSSL context handle for implementing
392    *  custom functionality not accessible by the existing member functions of
393    *  DcmTLSTransportLayer.
394    *  @return the underlying OpenSSL context handle.
395    *  @details
396    *  <h4>Usage Example</h4>
397    *  @code{.cpp}
398    *  DcmTLSTransportLayer tLayer(DICOM_APPLICATION_REQUESTOR, "random.dat");
399    *  ...
400    *  DcmTLSTransportLayer::native_handle_type native = tlayer.getNativeHandle();
401    *  X509_VERIFY_PARAM* param = SSL_CTX_get0_param(native);
402    *
403    *  // Enable automatic hostname checks
404    *  X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
405    *  X509_VERIFY_PARAM_set1_host(param, "www.example.com", 0);
406    *
407    *  // Configure a non-zero callback if desired
408    *  SSL_CTX_set_verify(native, SSL_VERIFY_PEER, 0);
409    *  ...
410    *  @endcode
411    */
412   native_handle_type getNativeHandle();
413 
414 private:
415 
416   /// private undefined copy constructor
417   DcmTLSTransportLayer(const DcmTLSTransportLayer&);
418 
419   /// private undefined assignment operator
420   DcmTLSTransportLayer& operator=(const DcmTLSTransportLayer&);
421 
422   /** look up OpenSSL certificate format constant
423    *  @param fileType as DcmKeyFileFormat enum
424    *  @return fileType as OpenSSL integer constant
425    */
426   static int lookupOpenSSLCertificateFormat(DcmKeyFileFormat fileType);
427 
428   /// OpenSSL context data, needed only once per application
429   SSL_CTX *transportLayerContext;
430 
431   /// true if there is enough random data to write a new random seed file
432   OFBool canWriteRandseed;
433 
434   /// contains the password for the private key if set on command line
435   OFString privateKeyPasswd;
436 
437   /// ciphersuite handler
438   DcmTLSCiphersuiteHandler ciphersuites;
439 
440   /// network role for this TLS layer
441   T_ASC_NetworkRole role;
442 };
443 
444 #endif /* WITH_OPENSSL */
445 
446 #endif
447