1 /** 2 * Orthanc - A Lightweight, RESTful DICOM Store 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics 4 * Department, University Hospital of Liege, Belgium 5 * Copyright (C) 2017-2021 Osimis S.A., Belgium 6 * 7 * This program is free software: you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public License 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this program. If not, see 19 * <http://www.gnu.org/licenses/>. 20 **/ 21 22 23 #include "../PrecompiledHeaders.h" 24 #include "DicomAssociationParameters.h" 25 26 #include "../Compatibility.h" 27 #include "../Logging.h" 28 #include "../OrthancException.h" 29 #include "../SerializationToolbox.h" 30 #include "../SystemToolbox.h" 31 #include "NetworkingCompatibility.h" 32 33 #include <dcmtk/dcmnet/diutil.h> // For ASC_DEFAULTMAXPDU 34 35 #include <boost/thread/mutex.hpp> 36 37 // By default, the default timeout for client DICOM connections is set to 10 seconds 38 static boost::mutex defaultConfigurationMutex_; 39 static uint32_t defaultTimeout_ = 10; 40 static std::string defaultOwnPrivateKeyPath_; 41 static std::string defaultOwnCertificatePath_; 42 static std::string defaultTrustedCertificatesPath_; 43 static unsigned int defaultMaximumPduLength_ = ASC_DEFAULTMAXPDU; 44 static bool defaultRemoteCertificateRequired_ = true; 45 46 47 namespace Orthanc 48 { CheckHost(const std::string & host)49 void DicomAssociationParameters::CheckHost(const std::string& host) 50 { 51 if (host.size() > HOST_NAME_MAX - 10) 52 { 53 throw OrthancException(ErrorCode_ParameterOutOfRange, 54 "Invalid host name (too long): " + host); 55 } 56 } 57 58 GetDefaultTimeout()59 uint32_t DicomAssociationParameters::GetDefaultTimeout() 60 { 61 boost::mutex::scoped_lock lock(defaultConfigurationMutex_); 62 return defaultTimeout_; 63 } 64 65 SetDefaultParameters()66 void DicomAssociationParameters::SetDefaultParameters() 67 { 68 boost::mutex::scoped_lock lock(defaultConfigurationMutex_); 69 timeout_ = defaultTimeout_; 70 ownPrivateKeyPath_ = defaultOwnPrivateKeyPath_; 71 ownCertificatePath_ = defaultOwnCertificatePath_; 72 trustedCertificatesPath_ = defaultTrustedCertificatesPath_; 73 maximumPduLength_ = defaultMaximumPduLength_; 74 remoteCertificateRequired_ = defaultRemoteCertificateRequired_; 75 } 76 77 DicomAssociationParameters()78 DicomAssociationParameters::DicomAssociationParameters() : 79 localAet_("ORTHANC"), 80 timeout_(0), // Will be set by SetDefaultParameters() 81 maximumPduLength_(0) // Will be set by SetDefaultParameters() 82 { 83 SetDefaultParameters(); 84 remote_.SetApplicationEntityTitle("ANY-SCP"); 85 } 86 87 DicomAssociationParameters(const std::string & localAet,const RemoteModalityParameters & remote)88 DicomAssociationParameters::DicomAssociationParameters(const std::string& localAet, 89 const RemoteModalityParameters& remote) : 90 localAet_(localAet), 91 timeout_(0), // Will be set by SetDefaultParameters() 92 maximumPduLength_(0) // Will be set by SetDefaultParameters() 93 { 94 SetDefaultParameters(); 95 SetRemoteModality(remote); 96 } 97 GetLocalApplicationEntityTitle() const98 const std::string &DicomAssociationParameters::GetLocalApplicationEntityTitle() const 99 { 100 return localAet_; 101 } 102 SetLocalApplicationEntityTitle(const std::string & aet)103 void DicomAssociationParameters::SetLocalApplicationEntityTitle(const std::string &aet) 104 { 105 localAet_ = aet; 106 } 107 GetRemoteModality() const108 const RemoteModalityParameters &DicomAssociationParameters::GetRemoteModality() const 109 { 110 return remote_; 111 } 112 113 SetRemoteModality(const RemoteModalityParameters & remote)114 void DicomAssociationParameters::SetRemoteModality(const RemoteModalityParameters& remote) 115 { 116 CheckHost(remote.GetHost()); 117 remote_ = remote; 118 119 if (remote.HasTimeout()) 120 { 121 timeout_ = remote.GetTimeout(); 122 assert(timeout_ != 0); 123 } 124 } 125 SetRemoteApplicationEntityTitle(const std::string & aet)126 void DicomAssociationParameters::SetRemoteApplicationEntityTitle(const std::string &aet) 127 { 128 remote_.SetApplicationEntityTitle(aet); 129 } 130 131 SetRemoteHost(const std::string & host)132 void DicomAssociationParameters::SetRemoteHost(const std::string& host) 133 { 134 CheckHost(host); 135 remote_.SetHost(host); 136 } 137 SetRemotePort(uint16_t port)138 void DicomAssociationParameters::SetRemotePort(uint16_t port) 139 { 140 remote_.SetPortNumber(port); 141 } 142 SetRemoteManufacturer(ModalityManufacturer manufacturer)143 void DicomAssociationParameters::SetRemoteManufacturer(ModalityManufacturer manufacturer) 144 { 145 remote_.SetManufacturer(manufacturer); 146 } 147 148 IsEqual(const DicomAssociationParameters & other) const149 bool DicomAssociationParameters::IsEqual(const DicomAssociationParameters& other) const 150 { 151 return (localAet_ == other.localAet_ && 152 remote_.GetApplicationEntityTitle() == other.remote_.GetApplicationEntityTitle() && 153 remote_.GetHost() == other.remote_.GetHost() && 154 remote_.GetPortNumber() == other.remote_.GetPortNumber() && 155 remote_.GetManufacturer() == other.remote_.GetManufacturer() && 156 timeout_ == other.timeout_ && 157 ownPrivateKeyPath_ == other.ownPrivateKeyPath_ && 158 ownCertificatePath_ == other.ownCertificatePath_ && 159 trustedCertificatesPath_ == other.trustedCertificatesPath_ && 160 maximumPduLength_ == other.maximumPduLength_); 161 } 162 SetTimeout(uint32_t seconds)163 void DicomAssociationParameters::SetTimeout(uint32_t seconds) 164 { 165 timeout_ = seconds; 166 } 167 GetTimeout() const168 uint32_t DicomAssociationParameters::GetTimeout() const 169 { 170 return timeout_; 171 } 172 HasTimeout() const173 bool DicomAssociationParameters::HasTimeout() const 174 { 175 return timeout_ != 0; 176 } 177 178 CheckDicomTlsConfiguration() const179 void DicomAssociationParameters::CheckDicomTlsConfiguration() const 180 { 181 if (!remote_.IsDicomTlsEnabled()) 182 { 183 throw OrthancException(ErrorCode_BadSequenceOfCalls, "DICOM TLS is not enabled"); 184 } 185 else if (ownPrivateKeyPath_.empty()) 186 { 187 throw OrthancException(ErrorCode_BadSequenceOfCalls, 188 "DICOM TLS - No path to the private key of the local certificate was provided"); 189 } 190 else if (ownCertificatePath_.empty()) 191 { 192 throw OrthancException(ErrorCode_BadSequenceOfCalls, 193 "DICOM TLS - No path to the local certificate was provided"); 194 } 195 else if (trustedCertificatesPath_.empty()) 196 { 197 throw OrthancException(ErrorCode_BadSequenceOfCalls, 198 "DICOM TLS - No path to the trusted remote certificates was provided"); 199 } 200 } 201 SetOwnCertificatePath(const std::string & privateKeyPath,const std::string & certificatePath)202 void DicomAssociationParameters::SetOwnCertificatePath(const std::string& privateKeyPath, 203 const std::string& certificatePath) 204 { 205 ownPrivateKeyPath_ = privateKeyPath; 206 ownCertificatePath_ = certificatePath; 207 } 208 SetTrustedCertificatesPath(const std::string & path)209 void DicomAssociationParameters::SetTrustedCertificatesPath(const std::string& path) 210 { 211 trustedCertificatesPath_ = path; 212 } 213 GetOwnPrivateKeyPath() const214 const std::string& DicomAssociationParameters::GetOwnPrivateKeyPath() const 215 { 216 CheckDicomTlsConfiguration(); 217 return ownPrivateKeyPath_; 218 } 219 GetOwnCertificatePath() const220 const std::string& DicomAssociationParameters::GetOwnCertificatePath() const 221 { 222 CheckDicomTlsConfiguration(); 223 return ownCertificatePath_; 224 } 225 GetTrustedCertificatesPath() const226 const std::string& DicomAssociationParameters::GetTrustedCertificatesPath() const 227 { 228 CheckDicomTlsConfiguration(); 229 return trustedCertificatesPath_; 230 } 231 GetMaximumPduLength() const232 unsigned int DicomAssociationParameters::GetMaximumPduLength() const 233 { 234 return maximumPduLength_; 235 } 236 SetMaximumPduLength(unsigned int pdu)237 void DicomAssociationParameters::SetMaximumPduLength(unsigned int pdu) 238 { 239 CheckMaximumPduLength(pdu); 240 maximumPduLength_ = pdu; 241 } 242 SetRemoteCertificateRequired(bool required)243 void DicomAssociationParameters::SetRemoteCertificateRequired(bool required) 244 { 245 remoteCertificateRequired_ = required; 246 } 247 IsRemoteCertificateRequired() const248 bool DicomAssociationParameters::IsRemoteCertificateRequired() const 249 { 250 return remoteCertificateRequired_; 251 } 252 253 254 255 static const char* const LOCAL_AET = "LocalAet"; 256 static const char* const REMOTE = "Remote"; 257 static const char* const TIMEOUT = "Timeout"; // New in Orthanc in 1.7.0 258 static const char* const OWN_PRIVATE_KEY = "OwnPrivateKey"; // New in Orthanc 1.9.0 259 static const char* const OWN_CERTIFICATE = "OwnCertificate"; // New in Orthanc 1.9.0 260 static const char* const TRUSTED_CERTIFICATES = "TrustedCertificates"; // New in Orthanc 1.9.0 261 static const char* const MAXIMUM_PDU_LENGTH = "MaximumPduLength"; // New in Orthanc 1.9.0 262 static const char* const REMOTE_CERTIFICATE_REQUIRED = "RemoteCertificateRequired"; // New in Orthanc 1.9.3 263 264 SerializeJob(Json::Value & target) const265 void DicomAssociationParameters::SerializeJob(Json::Value& target) const 266 { 267 if (target.type() != Json::objectValue) 268 { 269 throw OrthancException(ErrorCode_InternalError); 270 } 271 else 272 { 273 target[LOCAL_AET] = localAet_; 274 remote_.Serialize(target[REMOTE], true /* force advanced format */); 275 target[TIMEOUT] = timeout_; 276 target[MAXIMUM_PDU_LENGTH] = maximumPduLength_; 277 target[REMOTE_CERTIFICATE_REQUIRED] = remoteCertificateRequired_; 278 279 // Don't write the DICOM TLS parameters if they are not required 280 if (ownPrivateKeyPath_.empty()) 281 { 282 target.removeMember(OWN_PRIVATE_KEY); 283 } 284 else 285 { 286 target[OWN_PRIVATE_KEY] = ownPrivateKeyPath_; 287 } 288 289 if (ownCertificatePath_.empty()) 290 { 291 target.removeMember(OWN_CERTIFICATE); 292 } 293 else 294 { 295 target[OWN_CERTIFICATE] = ownCertificatePath_; 296 } 297 298 if (trustedCertificatesPath_.empty()) 299 { 300 target.removeMember(TRUSTED_CERTIFICATES); 301 } 302 else 303 { 304 target[TRUSTED_CERTIFICATES] = trustedCertificatesPath_; 305 } 306 } 307 } 308 309 UnserializeJob(const Json::Value & serialized)310 DicomAssociationParameters DicomAssociationParameters::UnserializeJob(const Json::Value& serialized) 311 { 312 if (serialized.type() == Json::objectValue) 313 { 314 DicomAssociationParameters result; 315 316 if (!serialized.isMember(REMOTE)) 317 { 318 throw OrthancException(ErrorCode_BadFileFormat); 319 } 320 321 result.remote_ = RemoteModalityParameters(serialized[REMOTE]); 322 result.localAet_ = SerializationToolbox::ReadString(serialized, LOCAL_AET); 323 result.timeout_ = SerializationToolbox::ReadInteger(serialized, TIMEOUT, GetDefaultTimeout()); 324 325 // The calls to "isMember()" below are for compatibility with Orthanc <= 1.8.2 serialization 326 if (serialized.isMember(MAXIMUM_PDU_LENGTH)) 327 { 328 result.maximumPduLength_ = SerializationToolbox::ReadUnsignedInteger( 329 serialized, MAXIMUM_PDU_LENGTH, defaultMaximumPduLength_); 330 } 331 332 if (serialized.isMember(OWN_PRIVATE_KEY)) 333 { 334 result.ownPrivateKeyPath_ = SerializationToolbox::ReadString(serialized, OWN_PRIVATE_KEY); 335 } 336 else 337 { 338 result.ownPrivateKeyPath_.clear(); 339 } 340 341 if (serialized.isMember(OWN_CERTIFICATE)) 342 { 343 result.ownCertificatePath_ = SerializationToolbox::ReadString(serialized, OWN_CERTIFICATE); 344 } 345 else 346 { 347 result.ownCertificatePath_.clear(); 348 } 349 350 if (serialized.isMember(TRUSTED_CERTIFICATES)) 351 { 352 result.trustedCertificatesPath_ = SerializationToolbox::ReadString(serialized, TRUSTED_CERTIFICATES); 353 } 354 else 355 { 356 result.trustedCertificatesPath_.clear(); 357 } 358 359 if (serialized.isMember(REMOTE_CERTIFICATE_REQUIRED)) 360 { 361 result.remoteCertificateRequired_ = SerializationToolbox::ReadBoolean(serialized, REMOTE_CERTIFICATE_REQUIRED); 362 } 363 364 return result; 365 } 366 else 367 { 368 throw OrthancException(ErrorCode_BadFileFormat); 369 } 370 } 371 372 SetDefaultTimeout(uint32_t seconds)373 void DicomAssociationParameters::SetDefaultTimeout(uint32_t seconds) 374 { 375 CLOG(INFO, DICOM) << "Default timeout for DICOM connections if Orthanc acts as SCU (client): " 376 << seconds << " seconds (0 = no timeout)"; 377 378 { 379 boost::mutex::scoped_lock lock(defaultConfigurationMutex_); 380 defaultTimeout_ = seconds; 381 } 382 } 383 384 SetDefaultOwnCertificatePath(const std::string & privateKeyPath,const std::string & certificatePath)385 void DicomAssociationParameters::SetDefaultOwnCertificatePath(const std::string& privateKeyPath, 386 const std::string& certificatePath) 387 { 388 if (!privateKeyPath.empty() && 389 !certificatePath.empty()) 390 { 391 CLOG(INFO, DICOM) << "Setting the default TLS certificate for DICOM SCU connections: " 392 << privateKeyPath << " (key), " << certificatePath << " (certificate)"; 393 394 if (certificatePath.empty()) 395 { 396 throw OrthancException(ErrorCode_ParameterOutOfRange, "No path to the default DICOM TLS certificate was provided"); 397 } 398 399 if (privateKeyPath.empty()) 400 { 401 throw OrthancException(ErrorCode_ParameterOutOfRange, 402 "No path to the private key for the default DICOM TLS certificate was provided"); 403 } 404 405 if (!SystemToolbox::IsRegularFile(privateKeyPath)) 406 { 407 throw OrthancException(ErrorCode_InexistentFile, "Inexistent file: " + privateKeyPath); 408 } 409 410 if (!SystemToolbox::IsRegularFile(certificatePath)) 411 { 412 throw OrthancException(ErrorCode_InexistentFile, "Inexistent file: " + certificatePath); 413 } 414 415 { 416 boost::mutex::scoped_lock lock(defaultConfigurationMutex_); 417 defaultOwnPrivateKeyPath_ = privateKeyPath; 418 defaultOwnCertificatePath_ = certificatePath; 419 } 420 } 421 else 422 { 423 boost::mutex::scoped_lock lock(defaultConfigurationMutex_); 424 defaultOwnPrivateKeyPath_.clear(); 425 defaultOwnCertificatePath_.clear(); 426 } 427 } 428 429 SetDefaultTrustedCertificatesPath(const std::string & path)430 void DicomAssociationParameters::SetDefaultTrustedCertificatesPath(const std::string& path) 431 { 432 if (!path.empty()) 433 { 434 CLOG(INFO, DICOM) << "Setting the default trusted certificates for DICOM SCU connections: " << path; 435 436 if (!SystemToolbox::IsRegularFile(path)) 437 { 438 throw OrthancException(ErrorCode_InexistentFile, "Inexistent file: " + path); 439 } 440 441 { 442 boost::mutex::scoped_lock lock(defaultConfigurationMutex_); 443 defaultTrustedCertificatesPath_ = path; 444 } 445 } 446 else 447 { 448 boost::mutex::scoped_lock lock(defaultConfigurationMutex_); 449 defaultTrustedCertificatesPath_.clear(); 450 } 451 } 452 453 454 CheckMaximumPduLength(unsigned int pdu)455 void DicomAssociationParameters::CheckMaximumPduLength(unsigned int pdu) 456 { 457 if (pdu > ASC_MAXIMUMPDUSIZE) 458 { 459 throw OrthancException(ErrorCode_ParameterOutOfRange, "Maximum PDU length must be smaller than " + 460 boost::lexical_cast<std::string>(ASC_MAXIMUMPDUSIZE)); 461 } 462 else if (pdu < ASC_MINIMUMPDUSIZE) 463 { 464 throw OrthancException(ErrorCode_ParameterOutOfRange, "Maximum PDU length must be greater than " + 465 boost::lexical_cast<std::string>(ASC_MINIMUMPDUSIZE)); 466 } 467 } 468 469 SetDefaultMaximumPduLength(unsigned int pdu)470 void DicomAssociationParameters::SetDefaultMaximumPduLength(unsigned int pdu) 471 { 472 CheckMaximumPduLength(pdu); 473 474 { 475 boost::mutex::scoped_lock lock(defaultConfigurationMutex_); 476 defaultMaximumPduLength_ = pdu; 477 } 478 } 479 480 GetDefaultMaximumPduLength()481 unsigned int DicomAssociationParameters::GetDefaultMaximumPduLength() 482 { 483 boost::mutex::scoped_lock lock(defaultConfigurationMutex_); 484 return defaultMaximumPduLength_; 485 } 486 487 SetDefaultRemoteCertificateRequired(bool required)488 void DicomAssociationParameters::SetDefaultRemoteCertificateRequired(bool required) 489 { 490 boost::mutex::scoped_lock lock(defaultConfigurationMutex_); 491 defaultRemoteCertificateRequired_ = required; 492 } 493 494 GetDefaultRemoteCertificateRequired()495 bool DicomAssociationParameters::GetDefaultRemoteCertificateRequired() 496 { 497 boost::mutex::scoped_lock lock(defaultConfigurationMutex_); 498 return defaultRemoteCertificateRequired_; 499 } 500 } 501