/* * * Copyright (C) 1998-2021, OFFIS e.V. * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation were developed by * * OFFIS e.V. * R&D Division Health * Escherweg 2 * D-26121 Oldenburg, Germany * * * Module: dcmtls * * Author: Marco Eichelberg * * Purpose: * classes: DcmTLSTransportLayer * */ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #include "dcmtk/dcmtls/tlslayer.h" #include "dcmtk/dcmtls/tlsdefin.h" #include "dcmtk/dcmtls/tlscond.h" #include "dcmtk/ofstd/ofdiag.h" /* for DCMTK_DIAGNOSTIC macros */ #ifdef WITH_OPENSSL BEGIN_EXTERN_C #ifdef HAVE_WINDOWS_H #define WIN32_LEAN_AND_MEAN #include #include #endif #include #include #include #include #include END_EXTERN_C #ifndef X509_V_ERR_UNSPECIFIED #define X509_V_ERR_UNSPECIFIED 1 #endif #include "dcmtk/dcmtls/tlslayer.h" #include "dcmtk/dcmtls/tlstrans.h" #include "dcmtk/dcmnet/dicom.h" #include "dcmtk/ofstd/ofrand.h" #ifdef HAVE_OPENSSL_PROTOTYPE_SSL_CTX_GET0_PARAM #define DCMTK_SSL_CTX_get0_param SSL_CTX_get0_param #else #define DCMTK_SSL_CTX_get0_param(A) (A)->param; #endif #ifndef HAVE_OPENSSL_PROTOTYPE_X509_GET_SIGNATURE_NID #define X509_get_signature_nid(x509) OBJ_obj2nid((x509)->sig_alg->algorithm) #endif #ifndef HAVE_OPENSSL_PROTOTYPE_SSL_CTX_GET_CERT_STORE #define SSL_CTX_get_cert_store(ctx) (ctx)->cert_store #endif #ifndef HAVE_OPENSSL_PROTOTYPE_EVP_PKEY_BASE_ID #define EVP_PKEY_base_id(key) EVP_PKEY_type((key)->type) #endif #ifndef HAVE_OPENSSL_PROTOTYPE_DH_BITS #define DH_bits(dh) BN_num_bits((dh)->p) #endif #ifndef HAVE_OPENSSL_PROTOTYPE_X509_STORE_GET0_PARAM #define X509_STORE_get0_param(A) (A)->param; #endif extern "C" int DcmTLSTransportLayer_certificateValidationCallback(int ok, X509_STORE_CTX *storeContext); OFLogger DCM_dcmtlsLogger = OFLog::getLogger("dcmtk.dcmtls"); /* This static sets a hard-coded set of Diffie-Hellman parameters * with 2048 bits key size that is used for ephemeral Diffie-Hellmane * (DHE_) ciphersuites unless the user replaces the parameter set * by calling DcmTLSTransportLayer::setTempDHParameters(). * Using a hard-coded DH parameter set is safe because the DH key exchange * does not require these parameters to be secret. It is, however, still * preferable to use a user-generated set of parameters. * * Generated by calling "openssl dhparam 2048". */ OFBool DcmTLSTransportLayer::setBuiltInDHParameters() { static char dh2048_p[] = "-----BEGIN DH PARAMETERS-----\n" "MIIBCAKCAQEAzEaoIXpuyK2D+If94J2iSxYqi1Ot+HD7FKvszu7Bxlh8izm1nyzk\n" "b0zUJfcXOaxnSsqmfGxLfPRm5+vD3aeD6mugrR1zZSemXUiq6CsONZZQ1MxStJvk\n" "Ems+9qRrbj9tA+/b2dZvUCc1pZAQTkbf7+CcbZnh21jExmDhf5QFZye/y4Arvj+a\n" "CZP/2Hd0veWUv2HzFRm52v0B69Y7I4BEWwGMDkvRREzc05fRAZiDftRLJGSkohLq\n" "65jDLNz4ZgiCvNH5SnixzuUBlys+78XEfWYu6k0FclBEFq+9rVaqugodK/SN0BOA\n" "pOEPHZJ77TwWvHEXA9ShsNjmBkqWK/tnKwIBAg==\n" "-----END DH PARAMETERS-----\n"; if (transportLayerContext==NULL) return OFFalse; BIO *bio = BIO_new_mem_buf(dh2048_p, sizeof(dh2048_p)); if (bio) { #ifdef HAVE_OPENSSL_PROTOTYPE_SSL_CTX_SET0_TMP_DH_PKEY EVP_PKEY *dhparams = PEM_read_bio_Parameters(bio,NULL); BIO_free(bio); if (dhparams) { SSL_CTX_set0_tmp_dh_pkey(transportLayerContext, dhparams); // transfers ownership of "dhparams" to transportLayerContext return OFTrue; } #else DH *dhparams = PEM_read_bio_DHparams(bio,NULL,NULL,NULL); BIO_free(bio); if (dhparams) { SSL_CTX_set_tmp_dh(transportLayerContext,dhparams); DH_free(dhparams); /* Safe because of reference counts in OpenSSL */ return OFTrue; } #endif } return OFFalse; } int DcmTLSTransportLayer_certificateValidationCallback(int ok, X509_STORE_CTX * /* storeContext */) { // this callback is called whenever OpenSSL has validated a X.509 certificate. // we could for example print it: // DcmTLSTransportLayer::printX509Certificate(cout, storeContext->cert); return ok; } /* buf : buffer to write password into * size : length of buffer in bytes * rwflag : nonzero if the password will be used as a new password, i.e. user should be asked to repeat the password * userdata: arbitrary pointer that can be set with SSL_CTX_set_default_passwd_cb_userdata() * returns : number of bytes written to password buffer, -1 upon error */ extern "C" int DcmTLSTransportLayer_passwordCallback(char *buf, int size, int rwflag, void *userdata); int DcmTLSTransportLayer_passwordCallback(char *buf, int size, int /* rwflag */, void *userdata) { if (userdata == NULL) return -1; OFString *password = OFreinterpret_cast(OFString *, userdata); int passwordSize = OFstatic_cast(int, password->length()); if (passwordSize > size) passwordSize = size; strncpy(buf, password->c_str(), passwordSize); return passwordSize; } // The TLS Supported Elliptic Curves extension (RFC 4492) is only supported in OpenSSL 1.0.2 and newer. // When compiling with OpenSSL 1.0.1, we are not using computeEllipticCurveList(). #ifdef HAVE_OPENSSL_PROTOTYPE_SSL_CTX_SET1_CURVES /** determine the list of elliptic curves supported by the OpenSSL library * for use with the TLS elliptic curve extension. * @param ecvector a list of supported elliptic curves that have 256 or * more bits is added to this vector upon return. */ static void computeEllipticCurveList(OFVector& ecvector) { // BCP 195: Curves of less than 192 bits SHOULD NOT be used. // Actually we only enable curves with at least 256 bits in DCMTK, following NIST and BSI recommendations. const int eclist[] = { // The list of elliptic curves actually supported by OpenSSL 1.0.2 // seems to be undocumented. See implementation of tls1_ec_nid2curve_id() // for a list of supported NIDs. Here are all elliptic curves // supported by OpenSSL 1.0.2 that have 256 or more bits. // // Compiled versions of OpenSSL may further reduce this list. // For example, OpenSSL on RHEL 7.6 only supports four of these curves. // We therefore simply test each curve and only retain those that are // accepted by SSL_CTX_set1_curves(). NID_X9_62_prime256v1, NID_secp256k1, NID_secp384r1, NID_secp521r1, NID_sect283k1, NID_sect283r1, NID_sect409k1, NID_sect409r1, NID_sect571k1, NID_sect571r1, NID_brainpoolP256r1, NID_brainpoolP384r1, NID_brainpoolP512r1 }; // create a SSL context object #ifndef HAVE_OPENSSL_PROTOTYPE_TLS_METHOD SSL_CTX *ctx = SSL_CTX_new(SSLv23_method()); if (ctx) SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3); #else SSL_CTX *ctx = SSL_CTX_new(TLS_method()); #ifdef HAVE_OPENSSL_PROTOTYPE_SSL_CTX_SET_SECURITY_LEVEL if (ctx) SSL_CTX_set_security_level(ctx, 0); #endif #endif if (ctx) { size_t numentries = sizeof(eclist) / sizeof(int); ecvector.reserve(numentries); for (size_t i = 0; i < numentries; ++i) { // try to set the given elliptic curve if (SSL_CTX_set1_curves(ctx, &eclist[i], 1)) { // if successful, add to the list of supported elliptic curves ecvector.push_back(eclist[i]); } } // delete the SSL context object SSL_CTX_free(ctx); } } #endif /* HAVE_OPENSSL_PROTOTYPE_SSL_CTX_SET1_CURVES */ DcmTLSTransportLayer::DcmTLSTransportLayer() : DcmTransportLayer() , transportLayerContext(NULL) , canWriteRandseed(OFFalse) , privateKeyPasswd() , role(NET_ACCEPTORREQUESTOR) { } // Depending on the OpenSSL version used, SSL_CTX_set_tmp_ecdh() will // cause this warning to be issued. In any case, this can safely be ignored. #include DCMTK_DIAGNOSTIC_IGNORE_CONST_EXPRESSION_WARNING DcmTLSTransportLayer::DcmTLSTransportLayer(T_ASC_NetworkRole networkRole, const char *randFile, OFBool initOpenSSL) : DcmTransportLayer() , transportLayerContext(NULL) , canWriteRandseed(OFFalse) , privateKeyPasswd() , role(networkRole) { if (initOpenSSL) initializeOpenSSL(); if (randFile) seedPRNG(randFile); #ifndef HAVE_OPENSSL_PROTOTYPE_TLS_METHOD // on versions of OpenSSL older than 1.1.0, we use the // SSLv23 methods and not the TLSv1 methods because the latter // only accept TLS 1.0 and prevent the negotiation of newer // versions of TLS. // We use SSL_CTX_set_options() to disable SSLv2 and SSLv3. switch (networkRole) { case NET_ACCEPTOR: transportLayerContext = SSL_CTX_new(SSLv23_server_method()); break; case NET_REQUESTOR: transportLayerContext = SSL_CTX_new(SSLv23_client_method()); break; case NET_ACCEPTORREQUESTOR: transportLayerContext = SSL_CTX_new(SSLv23_method()); break; } if (transportLayerContext) SSL_CTX_set_options(transportLayerContext, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3); #else // starting with OpenSSL 1.1.0, a new TLS_method() is offered // that automatically selects the highest version of the TLS // protocol supported by client and server. // The previous TLSv1_methods are now deprecated and generate // a warning. switch (networkRole) { case NET_ACCEPTOR: transportLayerContext = SSL_CTX_new(TLS_server_method()); break; case NET_REQUESTOR: transportLayerContext = SSL_CTX_new(TLS_client_method()); break; case NET_ACCEPTORREQUESTOR: transportLayerContext = SSL_CTX_new(TLS_method()); break; } #ifdef HAVE_OPENSSL_PROTOTYPE_SSL_CTX_SET_SECURITY_LEVEL // starting with OpenSSL 1.1.0, we explicitly need to set the security level to 0 // if we want to support any of the NULL ciphersuites. Since we manage the list // of supported ciphersuites ourselves and prevent a mix of NULL and non-NULL // ciphersuites, this is safe. if (transportLayerContext) SSL_CTX_set_security_level(transportLayerContext, 0); #endif #endif /* HAVE_OPENSSL_PROTOTYPE_TLS_METHOD */ if (transportLayerContext == NULL) { const char *result = ERR_reason_error_string(ERR_get_error()); if (result == NULL) result = "unknown error in SSL_CTX_new()"; DCMTLS_ERROR("unable to create TLS transport layer: " << result); } else { // create default set of DH parameters if (!setBuiltInDHParameters()) DCMTLS_ERROR("unable to create Diffie-Hellman parameters."); // set a random 32-bit number as TLS session ID OFRandom rnd; Uint32 session_id = rnd.getRND32(); if (0 == SSL_CTX_set_session_id_context(transportLayerContext, OFreinterpret_cast(const unsigned char *, &session_id), sizeof(session_id))) { DCMTLS_ERROR("unable to set TLS session ID context."); } // disable session caching (and, thus, session re-use) SSL_CTX_set_session_cache_mode(transportLayerContext, SSL_SESS_CACHE_OFF); // create Elliptic Curve DH parameters #ifndef OPENSSL_NO_ECDH #ifndef HAVE_OPENSSL_PROTOTYPE_SSL_CTX_SET_ECDH_AUTO // we create ECDH parameters for the NIST P-256 (secp256r1) curve // as recommended by BCP 195. EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); if (ecdh) { SSL_CTX_set_tmp_ecdh(transportLayerContext, ecdh); EC_KEY_free(ecdh); /* Safe because of reference counts */ } else DCMTLS_ERROR("unable to create Elliptic-Curve Diffie-Hellman parameters."); #else // OpenSSL 1.0.2 and newer have this function, which causes // the server to automatically select the most appropriate shared curve for each client. if (0 == SSL_CTX_set_ecdh_auto(transportLayerContext, 1)) { DCMTLS_ERROR("unable to create Elliptic-Curve Diffie-Hellman parameters."); } #endif /* HAVE_OPENSSL_PROTOTYPE_SSL_CTX_SET_ECDH_AUTO */ #endif /* OPENSSL_NO_ECDH */ // set default certificate verification strategy setCertificateVerification(DCV_requireCertificate); #if HAVE_OPENSSL_PROTOTYPE_SSL_CTX_SET1_SIGALGS // The TLS 1.2 Signature Algorithms extension is only supported in OpenSSL 1.0.2 and newer. if (networkRole != NET_ACCEPTOR) { // BCP 195: Clients SHOULD indicate to servers that they request SHA-256, // by using the "Signature Algorithms" extension defined in TLS 1.2. // We implement this by requesting SHA-256 OR BETTER, i.e. we also indicate // support for SHA-384 and SHA-512. const int slist[] = {NID_sha256, EVP_PKEY_RSA, NID_sha384, EVP_PKEY_RSA, NID_sha512, EVP_PKEY_RSA, #ifdef HAVE_OPENSSL_PROTOTYPE_EVP_PKEY_RSA_PSS // Connections between a client and a server that both use OpenSSL 1.1.1 // will fail unless RSA-PSS is also offered as a signature algorithm. NID_sha256, EVP_PKEY_RSA_PSS, NID_sha384, EVP_PKEY_RSA_PSS, NID_sha512, EVP_PKEY_RSA_PSS, #endif NID_sha256, EVP_PKEY_DSA, NID_sha384, EVP_PKEY_DSA, NID_sha512, EVP_PKEY_DSA, NID_sha256, EVP_PKEY_EC, NID_sha384, EVP_PKEY_EC, NID_sha512, EVP_PKEY_EC}; if (0 == SSL_CTX_set1_sigalgs(transportLayerContext, slist, sizeof(slist)/sizeof(int))) { DCMTLS_ERROR("unable to configure the TLS 1.2 Signature Algorithms extension."); } } #endif /* HAVE_OPENSSL_PROTOTYPE_SSL_CTX_SET1_SIGALGS */ #if HAVE_OPENSSL_PROTOTYPE_SSL_CTX_SET1_CURVES // The TLS Supported Elliptic Curves extension (RFC 4492) is only supported in OpenSSL 1.0.2 and newer. // BCP 195: Both clients and servers SHOULD include the "Supported Elliptic Curves" extension. // For interoperability, clients and servers SHOULD support the NIST P-256 (secp256r1) curve // (in OpenSSL this curve is called "prime256v1"). OFVector ecvector; computeEllipticCurveList(ecvector); if (ecvector.size() > 0) // only try to add the EC extension if we actually do support at least one curve { if (0 == SSL_CTX_set1_curves(transportLayerContext, &ecvector[0], OFstatic_cast(int, ecvector.size()))) { DCMTLS_ERROR("unable to configure the TLS Supported Elliptic Curves extension."); } } #endif /* HAVE_OPENSSL_PROTOTYPE_SSL_CTX_SET1_CURVES */ if (NET_REQUESTOR != networkRole) { // BCP 195: Servers MUST prefer TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 over weaker cipher suites whenever it is proposed, even if it is not the first proposal. // BCP 195: Servers SHOULD prefer stronger cipher suites unless there are compelling reasons to choose otherwise // BCP 195: Implementations MUST support and prefer to negotiate cipher suites offering forward secrecy // This all requires that when acting as a server we select the ciphersuites by our order of preference, // which implements all three recommendations by sorting the list of supported ciphersuites appropriately. if (0 == SSL_CTX_set_options(transportLayerContext, SSL_OP_CIPHER_SERVER_PREFERENCE)) { DCMTLS_ERROR("unable to configure the TLS layer to select ciphersuites by server preference."); } } } /* transportLayerContext != NULL */ } // move constructor DcmTLSTransportLayer::DcmTLSTransportLayer(OFrvalue_ref(DcmTLSTransportLayer) rhs) : DcmTransportLayer(OFrvalue_ref_upcast(DcmTransportLayer, rhs)) , transportLayerContext(rhs.transportLayerContext) , canWriteRandseed(OFmove(OFrvalue_access(rhs).canWriteRandseed)) , privateKeyPasswd(OFmove(OFrvalue_access(rhs).privateKeyPasswd)) { OFrvalue_access(rhs).transportLayerContext = NULL; } // move assignment DcmTLSTransportLayer& DcmTLSTransportLayer::operator=(OFrvalue_ref(DcmTLSTransportLayer) rhs) { if (this != &rhs) { clear(); DcmTransportLayer::operator=(OFrvalue_ref_upcast(DcmTransportLayer, rhs)); transportLayerContext = rhs.transportLayerContext; canWriteRandseed = OFmove(OFrvalue_access(rhs).canWriteRandseed); privateKeyPasswd = OFmove(OFrvalue_access(rhs).privateKeyPasswd); OFrvalue_access(rhs).transportLayerContext = NULL; } return *this; } void DcmTLSTransportLayer::clear() { if (transportLayerContext) { SSL_CTX_free(transportLayerContext); transportLayerContext = NULL; canWriteRandseed = OFFalse; privateKeyPasswd.clear(); } } DcmTLSTransportLayer::operator OFBool() const { return !!transportLayerContext; } OFBool DcmTLSTransportLayer::operator!() const { return !transportLayerContext; } OFBool DcmTLSTransportLayer::setTempDHParameters(const char *filename) { if ((filename==NULL)||(transportLayerContext==NULL)) return OFFalse; BIO *bio = BIO_new_file(filename,"r"); if (bio) { #ifdef HAVE_OPENSSL_PROTOTYPE_SSL_CTX_SET0_TMP_DH_PKEY EVP_PKEY *dh = PEM_read_bio_Parameters(bio,NULL); BIO_free(bio); if (dh) { // check BCP 195 recommendation: With a key exchange based on modular // exponential (MODP) Diffie-Hellman groups ("DHE" cipher suites), // DH key lengths of at least 2048 bits are RECOMMENDED. if (EVP_PKEY_bits(dh) < 2048) { DCMTLS_WARN("Key length of Diffie-Hellman parameter file too short: RFC 7525 recommends at least 2048 bits, but the key in file '" << filename << "' is only " << EVP_PKEY_bits(dh) << " bits."); if (ciphersuites.getTLSProfile() == TSP_Profile_BCP195_Extended) { // Extended BCP 195 profile: Reject DH parameter set, because it has less than 2048 bits // This will cause the default DH parameter set (which is large enough) to be used EVP_PKEY_free(dh); return OFFalse; } } SSL_CTX_set0_tmp_dh_pkey(transportLayerContext, dh); // transfers ownership of "dh" to transportLayerContext return OFTrue; } #else DH *dh = PEM_read_bio_DHparams(bio,NULL,NULL,NULL); BIO_free(bio); if (dh) { // check BCP 195 recommendation: With a key exchange based on modular // exponential (MODP) Diffie-Hellman groups ("DHE" cipher suites), // DH key lengths of at least 2048 bits are RECOMMENDED. if (DH_bits(dh) < 2048) { DCMTLS_WARN("Key length of Diffie-Hellman parameter file too short: RFC 7525 recommends at least 2048 bits, but the key in file '" << filename << "' is only " << DH_bits(dh) << " bits."); if (ciphersuites.getTLSProfile() == TSP_Profile_BCP195_Extended) { // Extended BCP 195 profile: Reject DH parameter set, because it has less than 2048 bits // This will cause the default DH parameter set (which is large enough) to be used DH_free(dh); return OFFalse; } } SSL_CTX_set_tmp_dh(transportLayerContext,dh); DH_free(dh); /* Safe because of reference counts in OpenSSL */ return OFTrue; } #endif } return OFFalse; } void DcmTLSTransportLayer::setPrivateKeyPasswd(const char *thePasswd) { if (thePasswd) privateKeyPasswd = thePasswd; else privateKeyPasswd.clear(); if (transportLayerContext) { /* register callback that replaces console input */ SSL_CTX_set_default_passwd_cb(transportLayerContext, DcmTLSTransportLayer_passwordCallback); SSL_CTX_set_default_passwd_cb_userdata(transportLayerContext, &privateKeyPasswd); } return; } void DcmTLSTransportLayer::setPrivateKeyPasswdFromConsole() { privateKeyPasswd.clear(); if (transportLayerContext) { /* deregister callback that replaces console input */ SSL_CTX_set_default_passwd_cb(transportLayerContext, NULL); SSL_CTX_set_default_passwd_cb_userdata(transportLayerContext, NULL); } return; } void DcmTLSTransportLayer::setCertificateVerification(DcmCertificateVerification verificationType) { if (transportLayerContext) { int vmode = 0; switch (verificationType) { case DCV_requireCertificate: vmode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; break; case DCV_checkCertificate: vmode = SSL_VERIFY_PEER; break; case DCV_ignoreCertificate: break; } SSL_CTX_set_verify(transportLayerContext, vmode, DcmTLSTransportLayer_certificateValidationCallback); } return; } OFCondition DcmTLSTransportLayer::activateCipherSuites() { OFString cslist; ciphersuites.getListOfCipherSuitesForOpenSSL(cslist, (role != NET_REQUESTOR)); if (transportLayerContext) { if (!SSL_CTX_set_cipher_list(transportLayerContext, cslist.c_str())) { return convertOpenSSLError(ERR_get_error(), OFTrue); } SSL_CTX_set_options(transportLayerContext, ciphersuites.getTLSOptions()); #ifdef HAVE_OPENSSL_PROTOTYPE_SSL_CTX_SET_MAX_PROTO_VERSION // when compiling with OpenSSL 1.1.1 or newer, set the maximum supported // TLS protocol version to TLS 1.2 if required (i.e. for the historic // security profiles, which would otherwise show unexpected behaviour). if (! ciphersuites.isTLS13Enabled()) { SSL_CTX_set_max_proto_version(transportLayerContext, TLS1_2_VERSION); } #endif } else return EC_IllegalCall; return EC_Normal; } OFCondition DcmTLSTransportLayer::setCipherSuites(const char *suites) { if (transportLayerContext && suites) { if (!SSL_CTX_set_cipher_list(transportLayerContext, suites)) { return convertOpenSSLError(ERR_get_error(), OFTrue); } } else return EC_IllegalCall; return EC_Normal; } DcmTLSTransportLayer::~DcmTLSTransportLayer() { clear(); } OFCondition DcmTLSTransportLayer::setPrivateKeyFile(const char *fileName, DcmKeyFileFormat fileType) { if (transportLayerContext) { if (0 >= SSL_CTX_use_PrivateKey_file(transportLayerContext, fileName, lookupOpenSSLCertificateFormat(fileType))) { return convertOpenSSLError(ERR_get_error(), OFTrue); } } else return EC_IllegalCall; return EC_Normal; } OFCondition DcmTLSTransportLayer::setCertificateFile(const char *fileName, DcmKeyFileFormat fileType) { if (transportLayerContext) { // we load the first certificate from the file and check the key length // and hash key against RFC 7525 recommendations. int result = 0; X509 *certificate = loadCertificateFile(fileName, fileType); if (certificate) { // Check if the certificate is RSA, and if so, if the public key is >= 2048 bits int bits = getRSAKeySize(certificate); if ((bits > 0) && (bits < 2048)) { DCMTLS_WARN("Key length of RSA public key too short: RFC 7525 recommends at least 2048 bits for RSA keys, but the key in certificate file '" << fileName << "' is only " << bits << " bits."); } const char *hash = checkRSAHashKeyIsSHA2(certificate); if (hash) { DCMTLS_WARN("Certificate hash key not SHA-256: RFC 7525 recommends the use of SHA-256 for RSA certificates, but certificate file '" << fileName << "' uses '" << hash << "'."); } if (fileType == DCF_Filetype_PEM) { // This will load the file again, this time processing multiple certificates // that might be present, establishing a full certificate chain. // This function only works with PEM files. result = SSL_CTX_use_certificate_chain_file(transportLayerContext, fileName); } else { // copy certificate into the SSL context result = SSL_CTX_use_certificate(transportLayerContext, certificate); } X509_free(certificate); } else result = -1; if (result <= 0) { return convertOpenSSLError(ERR_get_error(), OFTrue); } } else return EC_IllegalCall; return EC_Normal; } OFBool DcmTLSTransportLayer::checkPrivateKeyMatchesCertificate() { if (transportLayerContext) { if (SSL_CTX_check_private_key(transportLayerContext)) return OFTrue; } return OFFalse; } OFCondition DcmTLSTransportLayer::addVerificationFlags(unsigned long flags) { X509_VERIFY_PARAM* const parameter = DCMTK_SSL_CTX_get0_param(transportLayerContext); return parameter && X509_VERIFY_PARAM_set_flags(parameter,flags) ? EC_Normal : DCMTLS_EC_FailedToSetVerificationMode; } OFCondition DcmTLSTransportLayer::setCRLverification(DcmTLSCRLVerification crlmode) { X509_VERIFY_PARAM* const parameter = DCMTK_SSL_CTX_get0_param(transportLayerContext); if (parameter) { unsigned long flags = X509_VERIFY_PARAM_get_flags(parameter); switch (crlmode) { case TCR_noCRL: flags &= ~X509_V_FLAG_CRL_CHECK; flags &= ~X509_V_FLAG_CRL_CHECK_ALL; break; case TCR_checkLeafCRL: flags |= X509_V_FLAG_CRL_CHECK; flags &= ~X509_V_FLAG_CRL_CHECK_ALL; break; case TCR_checkAllCRL: flags |= X509_V_FLAG_CRL_CHECK; flags |= X509_V_FLAG_CRL_CHECK_ALL; break; } return X509_VERIFY_PARAM_set_flags(parameter,flags) ? EC_Normal : DCMTLS_EC_FailedToSetVerificationMode; } return EC_IllegalCall; } OFCondition DcmTLSTransportLayer::addTrustedCertificateFile(const char *fileName, DcmKeyFileFormat fileType) { if (transportLayerContext) { X509_LOOKUP *x509_lookup = X509_STORE_add_lookup(SSL_CTX_get_cert_store(transportLayerContext), X509_LOOKUP_file()); if (x509_lookup == NULL) { return convertOpenSSLError(ERR_get_error(), OFTrue); } if (! X509_LOOKUP_load_file(x509_lookup, fileName, lookupOpenSSLCertificateFormat(fileType))) { return convertOpenSSLError(ERR_get_error(), OFTrue); } } else return EC_IllegalCall; return EC_Normal; } OFCondition DcmTLSTransportLayer::addCertificateRevocationList(const char *fileName, DcmKeyFileFormat fileType) { // OpenSSL uses the same X509_LOOKUP_load_file() function for both certificates and CRLs return addTrustedCertificateFile(fileName, fileType); } OFCondition DcmTLSTransportLayer::addTrustedCertificateDir(const char *pathName, DcmKeyFileFormat fileType) { if (transportLayerContext) { X509_LOOKUP *x509_lookup = X509_STORE_add_lookup(SSL_CTX_get_cert_store(transportLayerContext), X509_LOOKUP_hash_dir()); if (x509_lookup == NULL) { return convertOpenSSLError(ERR_get_error(), OFTrue); } if (! X509_LOOKUP_add_dir(x509_lookup, pathName, lookupOpenSSLCertificateFormat(fileType))) { return convertOpenSSLError(ERR_get_error(), OFTrue); } } else return EC_IllegalCall; return EC_Normal; } OFCondition DcmTLSTransportLayer::addTrustedClientCertificateFile(const char *fileName) { if (transportLayerContext) { STACK_OF(X509_NAME) *caNames = sk_X509_NAME_dup(SSL_CTX_get_client_CA_list(transportLayerContext)); if (caNames == NULL) caNames = sk_X509_NAME_new_null(); STACK_OF(X509_NAME) *newCaNames = SSL_load_client_CA_file(fileName); for (int i = 0; i < sk_X509_NAME_num(newCaNames); ++i) { X509_NAME *newCaName = sk_X509_NAME_value(newCaNames,i); if (sk_X509_NAME_find(caNames,newCaName) == -1) { sk_X509_NAME_push(caNames,X509_NAME_dup(newCaName)); } } sk_X509_NAME_pop_free(newCaNames,X509_NAME_free); SSL_CTX_set_client_CA_list(transportLayerContext,caNames); } else return EC_IllegalCall; return EC_Normal; } DcmTransportConnection *DcmTLSTransportLayer::createConnection(DcmNativeSocketType openSocket, OFBool useSecureLayer) { if (useSecureLayer) { if (transportLayerContext) { SSL *newConnection = SSL_new(transportLayerContext); if (newConnection) { int s = OFstatic_cast(int, openSocket); if (openSocket != OFstatic_cast(DcmNativeSocketType, s)) { // On Win64, the native type for sockets there is an unsigned 64-bit integer, // and OpenSSL uses a signed 32-bit int file descriptor. // This should be fixed in OpenSSL, there is nothing we can do here // except to check whether the type conversion truncates the value and, // in this case, issue an error message. DCMTLS_ERROR("Conversion of 64-bit socket type to int in OpenSSL API causes loss of information."); } SSL_set_fd(newConnection, s); return new DcmTLSConnection(openSocket, newConnection); } } return NULL; } else return DcmTransportLayer::createConnection(openSocket, useSecureLayer); } void DcmTLSTransportLayer::seedPRNG(const char *randFile) { #ifdef _WIN32 RAND_screen(); #endif if (randFile) { #ifdef HAVE_OPENSSL_PROTOTYPE_RAND_EGD if (RAND_egd(randFile) <= 0) #endif { RAND_load_file(randFile ,-1); } } if (RAND_status()) canWriteRandseed = OFTrue; else { /* warn user */ DCMTLS_WARN("PRNG for TLS not seeded with sufficient random data."); } } void DcmTLSTransportLayer::addPRNGseed(void *buf, size_t bufSize) { RAND_seed(buf,OFstatic_cast(int, bufSize)); } OFBool DcmTLSTransportLayer::writeRandomSeed(const char *randFile) { if (canWriteRandseed && randFile) { if (RAND_write_file(randFile)) return OFTrue; } return OFFalse; } OFString DcmTLSTransportLayer::dumpX509Certificate(X509 *peerCertificate) { if (peerCertificate) { long certVersion = 0; /* certificate type */ long certSerialNumber = -1; /* certificate serial number */ OFString certValidNotBefore; /* certificate validity - not before */ OFString certValidNotAfter; /* certificate validity - not after */ char certSubjectName[1024]; /* certificate subject name (DN) */ char certIssuerName[1024]; /* certificate issuer name (DN) */ const char *certPubKeyType = "unknown"; /* certificate public key type */ int certPubKeyBits = 0; /* certificate number of bits in public key */ certSubjectName[0]= '\0'; certIssuerName[0]= '\0'; certVersion = X509_get_version(peerCertificate) +1; certSerialNumber = ASN1_INTEGER_get(X509_get_serialNumber(peerCertificate)); BIO *certValidNotBeforeBIO = BIO_new(BIO_s_mem()); char *bufptr = NULL; if (certValidNotBeforeBIO) { ASN1_UTCTIME_print(certValidNotBeforeBIO, X509_get_notBefore(peerCertificate)); BIO_write(certValidNotBeforeBIO,"\0",1); BIO_get_mem_data(certValidNotBeforeBIO, OFreinterpret_cast(char *, &bufptr)); if (bufptr) certValidNotBefore = bufptr; BIO_free(certValidNotBeforeBIO); } bufptr = NULL; BIO *certValidNotAfterBIO = BIO_new(BIO_s_mem()); if (certValidNotAfterBIO) { ASN1_UTCTIME_print(certValidNotAfterBIO, X509_get_notAfter(peerCertificate)); BIO_write(certValidNotAfterBIO,"\0",1); BIO_get_mem_data(certValidNotAfterBIO, OFreinterpret_cast(char *, &bufptr)); if (bufptr) certValidNotAfter = bufptr; BIO_free(certValidNotAfterBIO); } X509_NAME_oneline(X509_get_subject_name(peerCertificate), certSubjectName, 1024); X509_NAME_oneline(X509_get_issuer_name(peerCertificate), certIssuerName, 1024); EVP_PKEY *pubkey = X509_get_pubkey(peerCertificate); // creates copy of public key if (pubkey) { switch (EVP_PKEY_base_id(pubkey)) { case EVP_PKEY_RSA: certPubKeyType = "RSA"; break; case EVP_PKEY_DSA: certPubKeyType = "DSA"; break; case EVP_PKEY_DH: certPubKeyType = "DH"; break; default: /* nothing */ break; } certPubKeyBits = EVP_PKEY_bits(pubkey); EVP_PKEY_free(pubkey); } OFOStringStream out; out << "Peer X.509v" << certVersion << " Certificate" << OFendl << " Subject : " << certSubjectName << OFendl << " Issued by : " << certIssuerName << OFendl << " Serial no. : " << certSerialNumber << OFendl << " Validity : not before " << certValidNotBefore << ", not after " << certValidNotAfter << OFendl << " Public key : " << certPubKeyType << ", " << certPubKeyBits << " bits" << OFStringStream_ends; OFSTRINGSTREAM_GETOFSTRING(out, ret) return ret; } else { return "Peer did not provide a certificate or certificate verification is disabled."; } } OFCondition DcmTLSTransportLayer::setTLSProfile(DcmTLSSecurityProfile profile) { return ciphersuites.setTLSProfile(profile); } void DcmTLSTransportLayer::clearTLSProfile() { ciphersuites.clearTLSProfile(); } OFCondition DcmTLSTransportLayer::addCipherSuite(const char *suite) { return ciphersuites.addCipherSuite(suite); } DcmTLSTransportLayer::native_handle_type DcmTLSTransportLayer::getNativeHandle() { return transportLayerContext; } int DcmTLSTransportLayer::lookupOpenSSLCertificateFormat(DcmKeyFileFormat fileType) { int result = -1; switch (fileType) { case DCF_Filetype_PEM: result = SSL_FILETYPE_PEM; break; case DCF_Filetype_ASN1: result = SSL_FILETYPE_ASN1; break; } return result; } void DcmTLSTransportLayer::printSupportedCiphersuites(STD_NAMESPACE ostream& os) const { ciphersuites.printSupportedCiphersuites(os); } void DcmTLSTransportLayer::getListOfCipherSuitesForOpenSSL(OFString& cslist) const { ciphersuites.getListOfCipherSuitesForOpenSSL(cslist, (role != NET_REQUESTOR)); } int DcmTLSTransportLayer::getRSAKeySize(X509 *certificate) { if (certificate) { EVP_PKEY *pubkey = X509_get_pubkey(certificate); // creates a copy of the public key if (pubkey && (EVP_PKEY_base_id(pubkey) == EVP_PKEY_RSA)) { int certPubKeyBits = EVP_PKEY_bits(pubkey); // RSA public key size, in bits EVP_PKEY_free(pubkey); return certPubKeyBits; } } return 0; // certificate not present or not RSA } const char *DcmTLSTransportLayer::checkRSAHashKeyIsSHA2(X509 *certificate) { if (certificate) { EVP_PKEY *pubkey = X509_get_pubkey(certificate); // creates copy of public key if (pubkey && (EVP_PKEY_base_id(pubkey) == EVP_PKEY_RSA)) { int nid = X509_get_signature_nid(certificate); EVP_PKEY_free(pubkey); switch (nid) { case NID_sha256WithRSAEncryption: case NID_sha384WithRSAEncryption: case NID_sha512WithRSAEncryption: return NULL; // hash key uses SHA256 (or better) default: return OBJ_nid2sn(nid); // hash key does not follow BCP 195 recommendation to use SHA256 } } } return NULL; // default: everything is OK } X509 *DcmTLSTransportLayer::loadCertificateFile(const char *fileName, DcmKeyFileFormat fileType) { X509 *result = NULL; BIO *in=BIO_new_file(fileName, "rb"); if (in) { if (fileType == DCF_Filetype_ASN1) { result=d2i_X509_bio(in,NULL); } else if (fileType == DCF_Filetype_PEM) { result=PEM_read_bio_X509(in, NULL, NULL, NULL); } BIO_free(in); } return result; } OFCondition DcmTLSTransportLayer::verifyClientCertificate(const char *fileName, DcmKeyFileFormat fileType) { OFCondition result = EC_IllegalCall; if (transportLayerContext && fileName) { X509_STORE *trustStore = SSL_CTX_get_cert_store(transportLayerContext); if (trustStore) { // for some reason, the SSL context and the X509_STORE within that // context have different X509_VERIFY_PARAM parameter sets, in particular // they have different verification flags. We copy the flags from the // SSL context to the X509_STORE and restore the original value // after certificate verification. X509_VERIFY_PARAM *vparam_ssl = DCMTK_SSL_CTX_get0_param(transportLayerContext); X509_VERIFY_PARAM *vparam_store = X509_STORE_get0_param(trustStore); unsigned long ssl_vparam_flags = 0; unsigned long store_vparam_flags = 0; if (vparam_ssl) ssl_vparam_flags = X509_VERIFY_PARAM_get_flags(vparam_ssl); if (vparam_store) { store_vparam_flags = X509_VERIFY_PARAM_get_flags(vparam_store); X509_VERIFY_PARAM_set_flags(vparam_store, ssl_vparam_flags); } X509_STORE_CTX *storeCtx = X509_STORE_CTX_new(); if (storeCtx) { // we have a trust store and a context object for certificate verification. // Now let's load the client certificate chain X509 *clientCert = NULL; STACK_OF(X509) *chain = sk_X509_new(NULL); BIO *in=BIO_new_file(fileName, "rb"); if (in) { if (fileType == DCF_Filetype_ASN1) { clientCert = d2i_X509_bio(in,NULL); if (clientCert == NULL) { result = DCMTLS_EC_FailedToLoadCertificate(fileName); DCMTLS_ERROR("Not a DER certificate file: '" << fileName << "'"); } } else if (fileType == DCF_Filetype_PEM) { clientCert = PEM_read_bio_X509(in, NULL, NULL, NULL); if (clientCert == NULL) { result = DCMTLS_EC_FailedToLoadCertificate(fileName); DCMTLS_ERROR("Not a PEM certificate file: '" << fileName << "'"); } // in a PEM file, a certificate chain may follow after the client certificate. X509 *chainCert = NULL; while (NULL != (chainCert = PEM_read_bio_X509(in, NULL, NULL, NULL))) { sk_X509_push(chain, chainCert); } } BIO_free(in); } else { result = DCMTLS_EC_FailedToLoadCertificate(fileName); DCMTLS_ERROR("Cannot open certificate file '" << fileName << "'"); } if (clientCert) { if (X509_STORE_CTX_init(storeCtx, trustStore, clientCert, chain)) { if (X509_verify_cert(storeCtx)) { result = EC_Normal; } else { result = convertOpenSSLX509VerificationError(X509_STORE_CTX_get_error(storeCtx), OFTrue); } } else { result = DCMTLS_EC_CertStoreCtxInitFailed; DCMTLS_ERROR("certificate store context initialization failed"); } X509_free(clientCert); } X509_STORE_CTX_free(storeCtx); sk_X509_pop_free(chain, X509_free); } // restore original value of X509 store flags if (vparam_store) { X509_VERIFY_PARAM_set_flags(vparam_store, store_vparam_flags); } } } return result; } OFCondition DcmTLSTransportLayer::isRootCertificate(const char *fileName, DcmKeyFileFormat fileType) { OFCondition result = EC_IllegalCall; if (fileName) { X509_STORE *trustStore = X509_STORE_new(); X509_STORE_CTX *storeCtx = X509_STORE_CTX_new(); if (trustStore && storeCtx) { // we have a trust store and a context object for certificate verification. // Now let's load the client certificate X509 *clientCert = loadCertificateFile(fileName, fileType); if (clientCert == NULL) { result = DCMTLS_EC_FailedToLoadCertificate(fileName); DCMTLS_ERROR("Cannot read certificate file '" << fileName << "'"); } else { if (X509_STORE_add_cert(trustStore, clientCert)) { if (X509_STORE_CTX_init(storeCtx, trustStore, clientCert, NULL)) { if (X509_verify_cert(storeCtx)) result = EC_Normal; else result = convertOpenSSLX509VerificationError(X509_STORE_CTX_get_error(storeCtx), OFFalse); } else result = DCMTLS_EC_CertStoreCtxInitFailed; } else result = DCMTLS_EC_FailedToLoadCertificate(fileName);; } X509_free(clientCert); } if (storeCtx) X509_STORE_CTX_free(storeCtx); if (trustStore) X509_STORE_free(trustStore); } return result; } OFCondition DcmTLSTransportLayer::convertOpenSSLError(unsigned long errorCode, OFBool logAsError) { if (errorCode == 0) return EC_Normal; const char *err = ERR_reason_error_string(errorCode); if (err == NULL) err = "OpenSSL error"; // we generate special error codes for SSL errors if (ERR_LIB_SSL == ERR_GET_LIB(errorCode)) { OFOStringStream os; os << "TLS error: " << err; OFCondition cond; OFSTRINGSTREAM_GETSTR( os, c ) if (logAsError) DCMTLS_ERROR(c); cond = makeOFCondition(OFM_dcmtls, DCMTLS_EC_SSL_Offset + ERR_GET_REASON(errorCode), OF_error, c); OFSTRINGSTREAM_FREESTR( c ) return cond; } else { if (logAsError) DCMTLS_ERROR("OpenSSL error " << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(8) << errorCode << ": " << err); // we return a generic OpenSSL error for all other OpenSSL sublibraries return DCMTLS_EC_GenericOpenSSLError(errorCode); } } OFCondition DcmTLSTransportLayer::convertOpenSSLX509VerificationError(int errorCode, OFBool logAsError) { if (errorCode == 0) return EC_Normal; // check if this is a known error code, map to "unspecified error" otherwise and print a warning if (errorCode > DCMTLS_EC_X509Verify_Max) { DCMTLS_WARN("Unsupported OpenSSL X.509 verification error code " << errorCode << "; mapped to DCMTLS_EC_X509VerifyUnspecified."); errorCode = X509_V_ERR_UNSPECIFIED; } // retrieve error string const char *err = X509_verify_cert_error_string(errorCode); if (err == NULL) err = "unspecified error."; if (logAsError) DCMTLS_ERROR("certificate verification failed: " << err); return makeOFCondition(OFM_dcmtls, OFstatic_cast(Uint16, DCMTLS_EC_X509Verify_Offset + errorCode), OF_error, err); } void DcmTLSTransportLayer::initializeOpenSSL() { // the call to SSL_library_init was not needed in OpenSSL versions prior to 0.9.8, // but the API has been available at least since 0.9.5. SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); } const char *DcmTLSTransportLayer::getOpenSSLVersionName() { return OPENSSL_VERSION_TEXT; } #else /* WITH_OPENSSL */ /* make sure that the object file is not completely empty if compiled * without OpenSSL because some linkers might fail otherwise. */ DCMTK_DCMTLS_EXPORT void tlslayer_dummy_function() { return; } #endif /* WITH_OPENSSL */