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