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