From 392e6a553cc6377589848b9a3806b7ce7431b659 Mon Sep 17 00:00:00 2001 From: Marco Eichelberg Date: Mon, 29 Jan 2024 12:15:10 +0100 Subject: [PATCH] Added TLS support to getscu. Added TLS support to the getscu tool. --- dcmnet/apps/CMakeLists.txt | 2 +- dcmnet/apps/Makefile.in | 2 +- dcmnet/apps/getscu.cc | 49 +++++++++++-- dcmnet/docs/getscu.man | 118 ++++++++++++++++++++++++++++++ dcmnet/include/dcmtk/dcmnet/scu.h | 23 +++--- dcmnet/libsrc/scu.cc | 5 +- 6 files changed, 181 insertions(+), 18 deletions(-) diff --git a/dcmnet/apps/CMakeLists.txt b/dcmnet/apps/CMakeLists.txt index 48061ecf1b..8ac501c403 100644 --- a/dcmnet/apps/CMakeLists.txt +++ b/dcmnet/apps/CMakeLists.txt @@ -29,7 +29,7 @@ endif() foreach(PROGRAM dcmrecv dcmsend echoscu findscu getscu movescu storescp storescu termscu) DCMTK_TARGET_LINK_MODULES(${PROGRAM} dcmnet dcmdata oflog ofstd) endforeach() -foreach(PROGRAM dcmrecv echoscu findscu storescp storescu) +foreach(PROGRAM dcmrecv echoscu findscu storescp storescu getscu) DCMTK_TARGET_LINK_MODULES(${PROGRAM} dcmtls) endforeach() diff --git a/dcmnet/apps/Makefile.in b/dcmnet/apps/Makefile.in index d230e1c11f..4818e031bf 100644 --- a/dcmnet/apps/Makefile.in +++ b/dcmnet/apps/Makefile.in @@ -63,7 +63,7 @@ termscu: termscu.o $(CXX) $(CXXFLAGS) $(LIBDIRS) $(LDFLAGS) -o $@ $@.o $(LOCALLIBS) $(LIBS) getscu: getscu.o - $(CXX) $(CXXFLAGS) $(LIBDIRS) $(LDFLAGS) -o $@ $@.o $(LOCALLIBS) $(LIBS) + $(CXX) $(CXXFLAGS) $(LIBDIRS) $(LDFLAGS) -o $@ $@.o $(LOCALLIBS) $(DCMTLSLIBS) $(OPENSSLLIBS) $(LIBS) dcmsend: dcmsend.o $(CXX) $(CXXFLAGS) $(LIBDIRS) $(LDFLAGS) -o $@ $@.o $(COMPR_LIBS) $(LOCALLIBS) $(TIFFLIBS) $(PNGLIBS) $(LIBS) diff --git a/dcmnet/apps/getscu.cc b/dcmnet/apps/getscu.cc index 5782cf0761..9777f10628 100644 --- a/dcmnet/apps/getscu.cc +++ b/dcmnet/apps/getscu.cc @@ -1,6 +1,6 @@ /* * - * Copyright (C) 2011-2022, OFFIS e.V. + * Copyright (C) 2011-2024, OFFIS e.V. * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation were developed by @@ -27,6 +27,7 @@ #include "dcmtk/dcmdata/dcuid.h" /* for dcmtk version name */ #include "dcmtk/dcmdata/dcostrmz.h" /* for dcmZlibCompressionLevel */ #include "dcmtk/dcmdata/dcpath.h" /* for DcmPathProcessor */ +#include "dcmtk/dcmtls/tlsopt.h" /* for DcmTLSOptions */ #ifdef WITH_ZLIB #include /* for zlibVersion() */ @@ -85,12 +86,16 @@ main(int argc, char *argv[]) const char *opt_peerTitle = PEERAPPLICATIONTITLE; const char *opt_ourTitle = APPLICATIONTITLE; OFList fileNameList; - + DcmTLSOptions tlsOptions(NET_REQUESTOR); char tempstr[20]; OFString temp_str; OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION , "DICOM retrieve (C-GET) SCU", rcsid); OFCommandLine cmd; +#ifdef WITH_OPENSSL + DcmTLSTransportLayer::initializeOpenSSL(); +#endif + cmd.setParamColumn(LONGCOL + SHORTCOL + 4); cmd.addParam("peer", "hostname of DICOM peer"); cmd.addParam("port", "tcp/ip port number of peer"); @@ -172,6 +177,10 @@ main(int argc, char *argv[]) cmd.addOption("--max-pdu", "-pdu", 1, opt4.c_str(), opt3.c_str()); cmd.addOption("--repeat", 1, "[n]umber: integer", "repeat n times"); cmd.addOption("--abort", "abort association instead of releasing it"); + + // add TLS specific command line options if (and only if) we are compiling with OpenSSL + tlsOptions.addTLSCommandlineOptions(cmd); + cmd.addGroup("output options:"); cmd.addSubGroup("general:"); cmd.addOption("--output-directory", "-od", 1, "[d]irectory: string (default: \".\")", "write received objects to existing directory d"); @@ -191,15 +200,26 @@ main(int argc, char *argv[]) { app.printHeader(OFTrue /*print host identifier*/); COUT << OFendl << "External libraries used:"; - #ifdef WITH_ZLIB +#ifdef WITH_ZLIB COUT << OFendl << "- ZLIB, Version " << zlibVersion() << OFendl; - #else +#endif + // print OpenSSL version if (and only if) we are compiling with OpenSSL + tlsOptions.printLibraryVersion(); + +#if !defined(WITH_ZLIB) && !defined(WITH_OPENSSL) COUT << " none" << OFendl; - #endif +#endif return 0; } } + // check if the command line contains the --list-ciphers option + if (tlsOptions.listOfCiphersRequested(cmd)) + { + tlsOptions.printSupportedCiphersuites(app, COUT); + return 0; + } + /* general options */ OFLog::configureFromCommandLine(cmd, app); if (cmd.findOption("--verbose-pc")) @@ -292,6 +312,9 @@ main(int argc, char *argv[]) if (cmd.findOption("--abort")) opt_abortAssociation = OFTrue; if (cmd.findOption("--ignore")) opt_storageMode = DCMSCU_STORAGE_IGNORE; + // evaluate (most of) the TLS command line options (if we are compiling with OpenSSL) + tlsOptions.parseArguments(app, cmd); + /* output options */ if (cmd.findOption("--output-directory")) { @@ -396,13 +419,27 @@ main(int argc, char *argv[]) scu.setStorageDir(opt_outputDirectory); } + /* create a secure transport layer if requested and OpenSSL is available */ + OFCondition cond = tlsOptions.createTransportLayer(NULL, NULL, app, cmd); + if (cond.bad()) + { + OFString tempStr; + OFLOG_FATAL(getscuLogger, DimseCondition::dump(tempStr, cond)); + exit(1); + } + /* initialize network and negotiate association */ - OFCondition cond = scu.initNetwork(); + cond = scu.initNetwork(); if (cond.bad()) { OFLOG_FATAL(getscuLogger, DimseCondition::dump(temp_str, cond)); exit(1); } + + /* make sure the server connection uses TLS if requested */ + if (tlsOptions.secureConnectionRequested()) + scu.useSecureConnection(tlsOptions.getTransportLayer()); + cond = scu.negotiateAssociation(); if (cond.bad()) { diff --git a/dcmnet/docs/getscu.man b/dcmnet/docs/getscu.man index a33e3b1727..fe6c9bd938 100644 --- a/dcmnet/docs/getscu.man +++ b/dcmnet/docs/getscu.man @@ -199,6 +199,124 @@ other network options: abort association instead of releasing it \endverbatim +\subsection getscu_tls_options transport layer security (TLS) options +\verbatim +transport protocol stack: + + -tls --disable-tls + use normal TCP/IP connection (default) + + +tls --enable-tls [p]rivate key file, [c]ertificate file: string + use authenticated secure TLS connection + + +tla --anonymous-tls + use secure TLS connection without certificate + +private key password (only with --enable-tls): + + +ps --std-passwd + prompt user to type password on stdin (default) + + +pw --use-passwd [p]assword: string + use specified password + + -pw --null-passwd + use empty string as password + +key and certificate file format: + + -pem --pem-keys + read keys and certificates as PEM file (default) + + -der --der-keys + read keys and certificates as DER file + +certification authority: + + +cf --add-cert-file [f]ilename: string + add certificate file to list of certificates + + +cd --add-cert-dir [d]irectory: string + add certificates in d to list of certificates + + +crl --add-crl-file [f]ilename: string + add certificate revocation list file + (implies --enable-crl-vfy) + + +crv --enable-crl-vfy + enable leaf CRL verification + + +cra --enable-crl-all + enable full chain CRL verification + +security profile: + + +pg --profile-8996 + BCP 195 RFC 8996 TLS Profile (default) + + +pm --profile-8996-mod + Modified BCP 195 RFC 8996 TLS Profile + + # only available if underlying TLS library supports + # all TLS features required for this profile + + +py --profile-bcp195-nd + Non-downgrading BCP 195 TLS Profile (retired) + + +px --profile-bcp195 + BCP 195 TLS Profile (retired) + + +pz --profile-bcp195-ex + Extended BCP 195 TLS Profile (retired) + + +pb --profile-basic + Basic TLS Secure Transport Connection Profile (retired) + + # only available if underlying TLS library supports 3DES + + +pa --profile-aes + AES TLS Secure Transport Connection Profile (retired) + + +pn --profile-null + Authenticated unencrypted communication + (retired, was used in IHE ATNA) + +ciphersuite: + + +cc --list-ciphers + list supported TLS ciphersuites and exit + + +cs --cipher [c]iphersuite name: string + add ciphersuite to list of negotiated suites + +server name indication: + + --no-sni + do not use SNI (default) + + --request-sni [s]erver name: string + request server name s + +pseudo random generator: + + +rs --seed [f]ilename: string + seed random generator with contents of f + + +ws --write-seed + write back modified seed (only with --seed) + + +wf --write-seed-file [f]ilename: string (only with --seed) + write modified seed to file f + +peer authentication: + + -rc --require-peer-cert + verify peer certificate, fail if absent (default) + + -ic --ignore-peer-cert + don't verify peer certificate +\endverbatim + \subsection getscu_output_options output options \verbatim general: diff --git a/dcmnet/include/dcmtk/dcmnet/scu.h b/dcmnet/include/dcmtk/dcmnet/scu.h index 5237dfdc21..6f8e59c953 100644 --- a/dcmnet/include/dcmtk/dcmnet/scu.h +++ b/dcmnet/include/dcmtk/dcmnet/scu.h @@ -1,6 +1,6 @@ /* * - * Copyright (C) 2008-2023, OFFIS e.V. + * Copyright (C) 2008-2024, OFFIS e.V. * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation were developed by @@ -822,13 +822,21 @@ class DCMTK_DCMNET_EXPORT DcmSCU OFBool getProgressNotificationMode() const; /** Returns whether SCU is configured to create a TLS connection with the SCP - * @return OFFalse for this class but may be overridden by derived classes + * @return OFTrue if TLS mode has been enabled, OFFalse otherwise */ - OFBool getTLSEnabled() const; + virtual OFBool getTLSEnabled() const; /** Deletes internal networking structures from memory */ void freeNetwork(); + /** Tells DcmSCU to use a secure TLS connection described by the given TLS layer. + * The DcmSCU instance does not take ownership of the TLS layer object, i.e. + * it is the caller's responsibility to delete it after its use has ended. + * @param tlayer [in] The TLS transport layer including all TLS parameters + * @return EC_Normal if given transport layer is ok, an error code otherwise + */ + OFCondition useSecureConnection(DcmTransportLayer* tlayer); + protected: /** Sends a DIMSE command and possibly also a dataset from a data object via network to * another DICOM application @@ -861,12 +869,6 @@ class DCMTK_DCMNET_EXPORT DcmSCU OFString& sopInstanceUID, E_TransferSyntax& transferSyntax); - /** Tells DcmSCU to use a secure TLS connection described by the given TLS layer - * @param tlayer [in] The TLS transport layer including all TLS parameters - * @return EC_Normal if given transport layer is ok, an error code otherwise - */ - OFCondition useSecureConnection(DcmTransportLayer* tlayer); - /** Receive DIMSE command (excluding dataset!) over the currently open association * @param presID [out] Contains in the end the ID of the presentation context * which was specified in the DIMSE command received @@ -1120,6 +1122,9 @@ class DCMTK_DCMNET_EXPORT DcmSCU /// Progress notification mode (default: enabled) OFBool m_progressNotificationMode; + + /// Flag indicating whether secure mode has been enabled (default: disabled) + OFBool m_secureConnectionEnabled; }; #endif // SCU_H diff --git a/dcmnet/libsrc/scu.cc b/dcmnet/libsrc/scu.cc index b9f8356386..8d85d33fdc 100644 --- a/dcmnet/libsrc/scu.cc +++ b/dcmnet/libsrc/scu.cc @@ -55,6 +55,7 @@ DcmSCU::DcmSCU() , m_verbosePCMode(OFFalse) , m_datasetConversionMode(OFFalse) , m_progressNotificationMode(OFTrue) + , m_secureConnectionEnabled(OFFalse) { OFStandard::initializeNetwork(); } @@ -337,6 +338,8 @@ OFCondition DcmSCU::useSecureConnection(DcmTransportLayer* tlayer) OFCondition cond = ASC_setTransportLayer(m_net, tlayer, OFFalse /* do not take over ownership */); if (cond.good()) cond = ASC_setTransportLayerType(m_params, OFTrue /* use TLS */); + + if (cond.good()) m_secureConnectionEnabled = OFTrue; return cond; } @@ -2607,7 +2610,7 @@ Uint32 DcmSCU::getMaxReceivePDULength() const OFBool DcmSCU::getTLSEnabled() const { - return OFFalse; + return m_secureConnectionEnabled; } T_DIMSE_BlockingMode DcmSCU::getDIMSEBlockingMode() const