TLS error when using C-MOVE on mTLS-secured Orthanc deployment

Hello everyone!

I am having an issue I cannot wrap my head around. I have configured Orthanc with mTLS with appropriate trusted certificates to three applications. Specific to TLS, my configuration looks like:

  "DicomTlsCertificate": "/etc/orthanc/tls/orthanc.crt",
  "DicomTlsEnabled": true,
  "DicomTlsPrivateKey": "/etc/orthanc/tls/orthanc.key",
  "DicomTlsTrustedCertificates": "/etc/orthanc/tls/trusted.crt",

This Orthanc is deployed in a K8s cluster and DICOM port 4242 is exposed via a NodePort. Complete configuration posted at the bottom.

Certificate Generation

All self-signed certificates were generated with

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout application_1.key -out application_1.crt -subj "/CN=app1.iesbx"

Working Commands (echoscu, storescu, findscu)

Sending C-ECHO command to Orthanc server works, and logs the following:

$ echoscu -ll trace +tls certs/application_1.key certs/application_1.crt +cf certs/orthanc.crt 10.43.157.109 4242
D: $dcmtk: echoscu v3.6.6 2021-01-14 $
D: 
D: DcmDataDictionary: Loading file: /usr/share/libdcmtk16/dicom.dic
D: DcmDataDictionary: Loading file: /usr/share/libdcmtk16/private.dic
D: Request Parameters:
D: ====================== BEGIN A-ASSOCIATE-RQ =====================
D: Our Implementation Class UID:      1.2.276.0.7230010.3.0.3.6.6
D: Our Implementation Version Name:   OFFIS_DCMTK_366
D: Their Implementation Class UID:    
D: Their Implementation Version Name: 
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    ECHOSCU
D: Called Application Name:     ANY-SCP
D: Responding Application Name: ANY-SCP
D: Our Max PDU Receive Size:    16384
D: Their Max PDU Receive Size:  0
D: Presentation Contexts:
D:   Context ID:        1 (Proposed)
D:     Abstract Syntax: =VerificationSOPClass
D:     Proposed SCP/SCU Role: Default
D:     Proposed Transfer Syntax(es):
D:       =LittleEndianImplicit
D: Requested Extended Negotiation: none
D: Accepted Extended Negotiation:  none
D: Requested User Identity Negotiation: none
D: User Identity Negotiation Response:  none
D: ======================= END A-ASSOCIATE-RQ ======================
I: Requesting Association
T: DUL  FSM Table: State: 1 Event: 0
T: DUL  Event:  A-ASSOCIATE request (local user)
T: DUL  Action: AE 1 Transport Connect
D: setting network send timeout to 60 seconds
D: setting network receive timeout to 60 seconds
T: checking whether environment variable TCP_BUFFER_LENGTH is set
T:   environment variable TCP_BUFFER_LENGTH not set, using the system defaults
T: checking whether environment variable TCP_NODELAY is set
T:   environment variable TCP_NODELAY not set, using the default value (0)
T: Starting TLS client handshake
D: ================== BEGIN TLS CONNECTION DETAILS =================
D: Transport connection: TLS over TCP/IP
D:   Protocol    : TLSv1.3
D:   Ciphersuite : TLS_AES_256_GCM_SHA384, encryption: 256 bits
D: Peer X.509v3 Certificate
D:   Subject     : /CN=orthanc.iesbx.dev.flywheel.io
D:   Issued by   : /CN=orthanc.iesbx.dev.flywheel.io
D:   Serial no.  : -1
D:   Validity    : not before May  5 16:19:32 2025 GMT, not after May  5 16:19:32 2026 GMT
D:   Public key  : RSA, 2048 bits
D: =================== END TLS CONNECTION DETAILS ==================
T: DUL  FSM Table: State: 4 Event: 1
T: DUL  Event:  Transport conn confirmation (local)
T: DUL  Action: AE 2 Send Associate RQ PDU
D: Constructing Associate RQ PDU
T: setting timeout for first PDU to be read to 30 seconds
T: Receiving data via select()
T: Read PDU HEAD TCP: 02 00 00 00 00 b8
T: Read PDU HEAD TCP: type: 02, length: 184 (b8)
T: DUL  FSM Table: State: 5 Event: 2
T: DUL  Event:  A-ASSOCIATE-AC PDU (on transport)
T: DUL  Action: AE 3 Associate Confirmation Accept
D: PDU Type: Associate Accept, PDU Length: 184 + 6 bytes PDU header
D:   02  00  00  00  00  b8  00  01  00  00  41  4e  59  2d  53  43
D:   50  20  20  20  20  20  20  20  20  20  45  43  48  4f  53  43
D:   55  20  20  20  20  20  20  20  20  20  00  00  00  00  00  00
D:   00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
D:   00  00  00  00  00  00  00  00  00  00  10  00  00  15  31  2e
D:   32  2e  38  34  30  2e  31  30  30  30  38  2e  33  2e  31  2e
D:   31  2e  31  21  00  00  19  01  00  00  00  40  00  00  11  31
D:   2e  32  2e  38  34  30  2e  31  30  30  30  38  2e  31  2e  32
D:   50  00  00  3a  51  00  00  04  00  00  40  00  52  00  00  1b
D:   31  2e  32  2e  32  37  36  2e  30  2e  37  32  33  30  30  31
D:   30  2e  33  2e  30  2e  33  2e  36  2e  39  55  00  00  0f  4f
D:   46  46  49  53  5f  44  43  4d  54  4b  5f  33  36  39
D: Parsing an A-ASSOCIATE PDU
T: PDU type: 2 (A-ASSOCIATE AC), PDU Length: 184
T: DICOM Protocol: 1
T: Called AP Title:  ANY-SCP
T: Calling AP Title: ECHOSCU
T: Parsing remaining 116 bytes of A-ASSOCIATE PDU
T: Next item type: 10
T: Subitem parse: Type 10, Length 0021, Content: 1.2.840.10008.3.1.1.1
T: Successfully parsed Application Context
T: Parsing remaining 91 bytes of A-ASSOCIATE PDU
T: Next item type: 21
T: Parsing Presentation Context: (21), Length: 25
T: Presentation Context ID: 01
T: Parsing remaining 21 bytes of Presentation Context
T: Next item type: 40
T: Subitem parse: Type 40, Length 0017, Content: 1.2.840.10008.1.2
T: Successfully parsed Transfer Syntax
T: Successfully parsed Presentation Context
T: Parsing remaining 62 bytes of A-ASSOCIATE PDU
T: Next item type: 50
T: Parsing user info field (50), Length: 58
T: Parsing remaining 58 bytes of User Information
T: Next item type: 51
T: Maximum PDU Length: 16384
T: Successfully parsed Maximum PDU Length
T: Parsing remaining 50 bytes of User Information
T: Next item type: 52
T: Subitem parse: Type 52, Length 0027, Content: 1.2.276.0.7230010.3.0.3.6.9
T: Parsing remaining 19 bytes of User Information
T: Next item type: 55
T: Subitem parse: Type 55, Length 0015, Content: OFFIS_DCMTK_369
T: Successfully parsed User Information
D: Association Parameters Negotiated:
D: ====================== BEGIN A-ASSOCIATE-AC =====================
D: Our Implementation Class UID:      1.2.276.0.7230010.3.0.3.6.6
D: Our Implementation Version Name:   OFFIS_DCMTK_366
D: Their Implementation Class UID:    1.2.276.0.7230010.3.0.3.6.9
D: Their Implementation Version Name: OFFIS_DCMTK_369
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    ECHOSCU
D: Called Application Name:     ANY-SCP
D: Responding Application Name: ANY-SCP
D: Our Max PDU Receive Size:    16384
D: Their Max PDU Receive Size:  16384
D: Presentation Contexts:
D:   Context ID:        1 (Accepted)
D:     Abstract Syntax: =VerificationSOPClass
D:     Proposed SCP/SCU Role: Default
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =LittleEndianImplicit
D: Requested Extended Negotiation: none
D: Accepted Extended Negotiation:  none
D: Requested User Identity Negotiation: none
D: User Identity Negotiation Response:  none
D: ======================= END A-ASSOCIATE-AC ======================
I: Association Accepted (Max Send PDV: 16372)
I: Sending Echo Request (MsgID 1)
T: DcmItem::insert() Element (0000,0000) VR="UL" inserted at beginning
T: DcmItem::insert() Element (0000,0100) VR="US" inserted
T: DcmItem::insert() Element (0000,0110) VR="US" inserted
T: DcmItem::insert() Element (0000,0800) VR="US" inserted
T: DcmItem::insert() Element (0000,0002) VR="UI" inserted
T: DIMSE Command to be sent on Presentation Context ID: 1
T: DIMSE Command to send:
T: 
T: # Dicom-Data-Set
T: # Used TransferSyntax: Little Endian Explicit
T: (0000,0000) UL 0                                        #   4, 1 CommandGroupLength
T: (0000,0002) UI =VerificationSOPClass                    #  18, 1 AffectedSOPClassUID
T: (0000,0100) US 48                                       #   2, 1 CommandField
T: (0000,0110) US 1                                        #   2, 1 MessageID
T: (0000,0800) US 257                                      #   2, 1 CommandDataSetType
T: 
T: DIMSE sendDcmDataset: sending 68 bytes
T: DUL  FSM Table: State: 6 Event: 8
T: DUL  Event:  P-DATA request primitive
T: DUL  Action: DT 1 Send P DATA PDU
T: DIMSE receiveCommand
T: Read PDU HEAD TCP: 04 00 00 00 00 54
T: Read PDU HEAD TCP: type: 04, length: 84 (54)
T: DUL  FSM Table: State: 6 Event: 9
T: DUL  Event:  P-DATA-TF PDU (on transport)
T: DUL  Action: DT 2 Indicate P DATA PDU Received
D: DcmDataset::read() TransferSyntax="Little Endian Implicit"
T: DcmItem::insert() Element (0000,0000) VR="UL" inserted at beginning
T: DcmItem::readSubItem() returns error = Normal
T: DcmItem::insert() Element (0000,0002) VR="UI" inserted
T: DcmItem::readSubItem() returns error = Normal
T: DcmItem::insert() Element (0000,0100) VR="US" inserted
T: DcmItem::readSubItem() returns error = Normal
T: DcmItem::insert() Element (0000,0120) VR="US" inserted
T: DcmItem::readSubItem() returns error = Normal
T: DcmItem::insert() Element (0000,0800) VR="US" inserted
T: DcmItem::readSubItem() returns error = Normal
T: DcmItem::insert() Element (0000,0900) VR="US" inserted
T: DcmItem::readSubItem() returns error = Normal
T: DcmItem::read() returns error = Normal
T: DcmDataset::read() returns error = Normal
T: DIMSE receiveCommand: 1 PDVs (78 bytes), PresID=1
T: DIMSE Command Received:
T: 
T: # Dicom-Data-Set
T: # Used TransferSyntax: Little Endian Implicit
T: (0000,0002) UI =VerificationSOPClass                    #  18, 1 AffectedSOPClassUID
T: (0000,0100) US 32816                                    #   2, 1 CommandField
T: (0000,0120) US 1                                        #   2, 1 MessageIDBeingRespondedTo
T: (0000,0800) US 257                                      #   2, 1 CommandDataSetType
T: (0000,0900) US 0                                        #   2, 1 Status
T: 
T: DcmItem::searchSubFromHere() Element (0000,0100) found
T: DcmItem::searchSubFromHere() Element (0000,0100) found
T: DcmItem::searchSubFromHere() Element (0000,0120) found
T: DcmItem::searchSubFromHere() Element (0000,0800) found
T: DcmItem::searchSubFromHere() Element (0000,0900) found
T: DcmItem::searchSubFromHere() Element (0000,0002) found
I: Received Echo Response (Success)
I: Releasing Association
T: DUL  FSM Table: State: 6 Event: 10
T: DUL  Event:  A-RELEASE request primitive
T: DUL  Action: AR 1 Send Release RQ
T: Receiving data via select()
T: Read PDU HEAD TCP: 06 00 00 00 00 04
T: Read PDU HEAD TCP: type: 06, length: 4 (04)
T: DUL  FSM Table: State: 7 Event: 12
T: DUL  Event:  A-RELEASE-RP PDU (on transport)
T: DUL  Action: AR 3 Confirm Release

This is a successful response, indicating that my mTLS configuration works fine. This TLS configuration is also working with C-FIND and C-STORE.

Failing Command (movescu)

However, C-MOVE, despite not supporting TLS, presents me with a TLS-related error. Output from C-MOVE command:

$ movescu -ll trace  -od tmp --port 5104 -S -aet FW-REAPER -aec FW-RECEIVER -k QueryRetrieveLevel="SERIES" -k SeriesInstanceUID="1.3.6.1.4.1.5962.1.3.4.1.20040826185059.5457" -k StudyInstanceUID="1.3.6.1.4.1.5962.1.2.4.20040826185059.5457" 10.43.157.109 4242
D: DcmDataDictionary: Loading file: /usr/share/libdcmtk16/dicom.dic
D: DcmDataDictionary: Loading file: /usr/share/libdcmtk16/private.dic
T: DcmItem::insert() Element (0020,000d) VR="UI" inserted at beginning
T: DcmItem::insert() Element (0020,000e) VR="UI" inserted
T: DcmItem::insert() Element (0008,0052) VR="CS" inserted at beginning
D: $dcmtk: movescu v3.6.6 2021-01-14 $
D: 
D: Request Parameters:
D: ====================== BEGIN A-ASSOCIATE-RQ =====================
D: Our Implementation Class UID:      1.2.276.0.7230010.3.0.3.6.6
D: Our Implementation Version Name:   OFFIS_DCMTK_366
D: Their Implementation Class UID:    
D: Their Implementation Version Name: 
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    FW-REAPER
D: Called Application Name:     FW-RECEIVER
D: Responding Application Name: FW-RECEIVER
D: Our Max PDU Receive Size:    16384
D: Their Max PDU Receive Size:  0
D: Presentation Contexts:
D:   Context ID:        1 (Proposed)
D:     Abstract Syntax: =FINDStudyRootQueryRetrieveInformationModel
D:     Proposed SCP/SCU Role: Default
D:     Proposed Transfer Syntax(es):
D:       =LittleEndianExplicit
D:       =BigEndianExplicit
D:       =LittleEndianImplicit
D:   Context ID:        3 (Proposed)
D:     Abstract Syntax: =MOVEStudyRootQueryRetrieveInformationModel
D:     Proposed SCP/SCU Role: Default
D:     Proposed Transfer Syntax(es):
D:       =LittleEndianExplicit
D:       =BigEndianExplicit
D:       =LittleEndianImplicit
D: Requested Extended Negotiation: none
D: Accepted Extended Negotiation:  none
D: Requested User Identity Negotiation: none
D: User Identity Negotiation Response:  none
D: ======================= END A-ASSOCIATE-RQ ======================
I: Requesting Association
T: DUL  FSM Table: State: 1 Event: 0
T: DUL  Event:  A-ASSOCIATE request (local user)
T: DUL  Action: AE 1 Transport Connect
D: setting network send timeout to 60 seconds
D: setting network receive timeout to 60 seconds
T: checking whether environment variable TCP_BUFFER_LENGTH is set
T:   environment variable TCP_BUFFER_LENGTH not set, using the system defaults
T: checking whether environment variable TCP_NODELAY is set
T:   environment variable TCP_NODELAY not set, using the default value (0)
T: DUL  FSM Table: State: 4 Event: 1
T: DUL  Event:  Transport conn confirmation (local)
T: DUL  Action: AE 2 Send Associate RQ PDU
D: Constructing Associate RQ PDU
T: setting timeout for first PDU to be read to 30 seconds
T: DUL  FSM Table: State: 5 Event: 16
T: DUL  Event:  Transport connection closed
T: DUL  Action: AA 4 Indicate AP Abort
F: Association Request Failed:
F: 0006:0317 Peer aborted Association (or never connected)

And logs from Orthanc:

D: DUL: disabling Nagle algorithm as defined at compilation time (DISABLE_NAGLE_ALGORITHM)                                                                                                                                                           
D: Association Received: 10.42.0.1                                                                                                                                                                                                                   
D: setting network send timeout to 60 seconds                                                                                                                                                                                                        
D: setting network receive timeout to 60 seconds                                                                                                                                                                                                     
E0507 03:10:52.682534     DICOM-SERVER CommandDispatcher.cpp:285] Receiving Association failed: TLS error: wrong version number 

Full Configuration

Orthanc

Compiled from source Orthanc 1.12.7. Problem also occurs on 1.12.6.

{
  "DicomAet": "FWRECEIVER",
  "DicomPort": 4242,
  "DicomCheckCalledAet": false,
  "AcceptedTransferSyntaxes": [
    "1.2.840.10008.1.2.*"
  ],
  "UnknownSopClassAccepted": true,
  "OverwriteInstances": true,
  "IndexDirectory": "/data/index",
  "StorageDirectory": "/data/storage",
  "StorageCompression": true,
  "MaximumStorageSize": 102400,
  "Name": "Orthanc",
  "HttpPort": 8042,
  "HttpCompressionEnabled": true,
  "RemoteAccessAllowed": true,
  "AuthenticationEnabled": false,
  "ExecuteLuaEnabled": true,
  "WebDavEnabled": false,
  "Plugins": [
    "/opt/share/orthanc/plugins/libConnectivityChecks.so",
    "/opt/share/orthanc/plugins/libDelayedDeletion.so",
    "/opt/share/orthanc/plugins/libHousekeeper.so",
    "/opt/share/orthanc/plugins/libOrthancExplorer2.so",
    "/opt/share/orthanc/plugins/libOrthancDicomWeb.so",
    "/opt/share/orthanc/plugins/libOrthancGdcm.so",
    "/opt/share/orthanc/plugins/libOrthancOHIF.so",
    "/opt/share/orthanc/plugins/libOrthancPostgreSQLIndex.so",
    "/opt/share/orthanc/plugins/libOrthancPython.so"
  ],
  "DicomWeb": {
    "Enable": true
  },
  "Gdcm": {
    "Throttling": 4,
    "RestrictTransferSyntaxes": [
      "1.2.840.10008.1.2.4.90",
      "1.2.840.10008.1.2.4.91",
      "1.2.840.10008.1.2.4.92",
      "1.2.840.10008.1.2.4.93"
    ]
  },
  "Housekeeper": {
    "Enable": true,
    "LimitMainDicomTagsReconstructLevel": "Series"
  },
  "OrthancExplorer2": {
    "Enable": true,
    "IsDefaultOrthancUI": true
  },
  "PythonScript": "/orthanc/script.py",
  "DicomModalities": {
    "FW-REAPER": {
      "AET": "FW-REAPER",
      "Host": "reaper-push-reaper-secure",
      "Port": 5104,
    }
  },
  "DicomTlsCertificate": "/etc/orthanc/tls/orthanc.crt",
  "DicomTlsEnabled": true,
  "DicomTlsPrivateKey": "/etc/orthanc/tls/orthanc.key",
  "DicomTlsTrustedCertificates": "/etc/orthanc/tls/trusted.crt",
  "RegisteredUsers": {}
}

OpenSSL on application

$ openssl -v
OpenSSL 3.3.2 3 Sep 2024 (Library: OpenSSL 3.3.2 3 Sep 2024)

OpenSSL on Orthanc container/pod

$ openssl -v
OpenSSL 3.5.0 8 Apr 2025 (Library: OpenSSL 3.5.0 8 Apr 2025)

Application built with OpenSSL 3.3.3 11 Feb 2025 (Library: OpenSSL 3.3.3 11 Feb 2025) also presents the same problem.

Summary:

C-ECHO, C-STORE, and C-FIND work with mTLS enabled, whereas C-MOVE produces the error Association failed: TLS error: wrong version number. Really not sure why this is happening and I would appreciate some help on this.

Hi @sdhiman

Not sure to understand the whole picture.

Is the movescu targetting Orthanc directly ? It seems so since it has the same IP and port as the echoscu.

Then why doesn’t it use the +tls options ?

Best regards,

Alain

@alainmazy

Then why doesn’t it use the +tls options ?

That’s because I’ve been using DCMTK’s movescu command, which does not support +tls parameters. And this is also the root cause to my issue here. I’ll have to switch my application to use either dcm4che or pynetdicom to make relevant C-MOVE calls with TLS.

2 Likes

Hello,

Correct me if I’m wrong, but shouldn’t your question be asked to the DCMTK community, if the movescu command doesn’t support TLS? It does not seem to be related to Orthanc.

Regards,
Sébastien-

Yes, that is correct. This ended up being an issue with DCMTK - I no longer need help with the issue.

Just going to leave this for others who may encounter the same issue: C-GET in DCMTK 3.6.9 supports mTLS. This is a useful DCMTK method to move DICOM images from Orthanc protected with DICOM mTLS.