Skip to content

[BUG / possible BUG]: No communication of bailing early on compatible ciphers when server preference is invalid #2883

@polarathene

Description

@polarathene

UPDATE: Please skip to follow-up comment, the original reproduction is flawed: #2883 (comment)


Before you open an issue please check which version you are running and whether it is the latest in stable / dev branch

GHCR.io 3.3dev image (published 3 hours ago)

Before you open an issue please whether this is a known problem by searching the issues

I couldn't find anything

Command line / docker command to reproduce

docker compose up -d --force-recreate
docker compose run --rm -it testssl --quiet --preference mail.example.test:993
Output
Testing all IPv4 addresses (port 993): 172.18.0.4
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Start 2025-09-06 08:56:03        -->> 172.18.0.4:993 (mail.example.test) <<--

 rDNS (172.18.0.4):      example-dms-1.example_default.
 Service detected:       IMAP, thus skipping HTTP specific checks

 Testing server's cipher preferences

Hexcode  Cipher Suite Name (OpenSSL)       KeyExch.   Encryption  Bits     Cipher Suite Name (IANA/RFC)
-----------------------------------------------------------------------------------------------------------------------------
SSLv2
 -
SSLv3
 -
TLSv1
 -
TLSv1.1
 -
TLSv1.2 (server order)
 xc02b   ECDHE-ECDSA-AES128-GCM-SHA256     ECDH 253   AESGCM      128      TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
 xc02c   ECDHE-ECDSA-AES256-GCM-SHA384     ECDH 253   AESGCM      256      TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
 xcca9   ECDHE-ECDSA-CHACHA20-POLY1305     ECDH 253   ChaCha20    256      TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
TLSv1.3 (server order)
 x1302   TLS_AES_256_GCM_SHA384            ECDH 253   AESGCM      256      TLS_AES_256_GCM_SHA384
 x1303   TLS_CHACHA20_POLY1305_SHA256      ECDH 253   ChaCha20    256      TLS_CHACHA20_POLY1305_SHA256
 x1301   TLS_AES_128_GCM_SHA256            ECDH 253   AESGCM      128      TLS_AES_128_GCM_SHA256

 Has server cipher order?     yes (OK) -- TLS 1.3 and below



 Done 2025-09-06 08:56:22 [  27s] -->> 172.18.0.4:993 (mail.example.test) <<--

------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Done testing now all IP addresses (on port 993): 172.18.0.4

Associated compose.yaml:

`compose.yaml` (collapsed for brevity)
name: example

services:
  testssl:
    scale: 0 # This avoids running the container during `docker compose up`
    image: ghcr.io/testssl/testssl.sh:3.3dev
    # Config for `testssl.sh` to trust the locally generated cert:
    environment:
      ADDTL_CA_FILES: "/tmp/tls/ca-cert.pem"
    configs:
      - source: tls-ca-cert
        target: /tmp/tls/ca-cert.pem

  # Service to test against:
  dms:
    image: mailserver/docker-mailserver:15.1.0
    hostname: mail.example.test
    environment:
      - SSL_TYPE=manual
      - SSL_KEY_PATH=/tmp/tls/key.pem
      - SSL_CERT_PATH=/tmp/tls/cert.pem
    configs:
      # Extra config bundled into the `compose.yaml` to ease testing:
      - source: dms-accounts
        target: /tmp/docker-mailserver/postfix-accounts.cf
      - source: tls-cert
        target: /tmp/tls/cert.pem
      - source: tls-key
        target: /tmp/tls/key.pem
      # Required to reproduce the observed bug by removing DHE support (while retaining DHE in server order):
      - source: dovecot-overrides
        target: /tmp/docker-mailserver/dovecot.cf
      # Optional - Allows clients in the container to verify cert trust with the CA that signed it:
      - source: tls-ca-cert
        target: /tmp/tls/ca-cert.pem

configs:
  # Pre-provisioned account for local reproduction use:
  # NOTE: `$` is escaped by repeating it to avoid the Docker Compose ENV interpolation feature
  # Password is `secret`
  dms-accounts:
    content: |
      [email protected]|{SHA512-CRYPT}$$6$$sbgFRCmQ.KWS5ryb$$EsWrlYosiadgdUOxCBHY0DQ3qFbeudDhNMqHs6jZt.8gmxUwiLVy738knqkHD4zj4amkb296HFqQ3yDq4UXt8.

  # Example ECDSA cert files for testing locally (provisioned for `mail.example.test`):
  tls-ca-cert:
    content: |
      -----BEGIN CERTIFICATE-----
      MIIBfTCCASKgAwIBAgIRAMAZttlRlkcuSun0yV0z4RwwCgYIKoZIzj0EAwIwHDEa
      MBgGA1UEAxMRU21hbGxzdGVwIFJvb3QgQ0EwHhcNMjEwMTAxMDAwMDAwWhcNMzEw
      MTAxMDAwMDAwWjAcMRowGAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTBZMBMGByqG
      SM49AgEGCCqGSM49AwEHA0IABJX2hCtoK3+bM5I3rmyApXLJ1gOcVhtoSSwM8XXR
      SEl25Kkc0n6mINuMK8UrBkiBUgexf6CYayx3xVr9TmMkg4KjRTBDMA4GA1UdDwEB
      /wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBQD8sBrApbyYyqU
      y+/TlwGynx2V5jAKBggqhkjOPQQDAgNJADBGAiEAi8N2eOETI+6hY3+G+kzNMd3K
      Sd3Ke8b++/nlwr5Fb/sCIQDYAjpKp/MpTDWICeHC2tcB5ptxoTdWkTBuG4rKcktA
      0w==
      -----END CERTIFICATE-----

  tls-key:
    content: |
      -----BEGIN EC PRIVATE KEY-----
      MHcCAQEEIOc6wqZmSDmT336K4O26dMk1RCVc0+cmnsO2eK4P5K5yoAoGCCqGSM49
      AwEHoUQDQgAEFOWNgekKKvUZE89vJ7henUYxODYIvCiHitRc2ylwttjqt1KUY1cp
      q3jof2fhURHfBUH3dHPXLHig5V9Jw5gqeg==
      -----END EC PRIVATE KEY-----

  tls-cert:
    content: |
      -----BEGIN CERTIFICATE-----
      MIIB9DCCAZqgAwIBAgIQE53a/y2c//YXRsz2kLm6gDAKBggqhkjOPQQDAjAcMRow
      GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0yMTAxMDEwMDAwMDBaFw0zMTAx
      MDEwMDAwMDBaMBkxFzAVBgNVBAMTDlNtYWxsc3RlcCBMZWFmMFkwEwYHKoZIzj0C
      AQYIKoZIzj0DAQcDQgAEFOWNgekKKvUZE89vJ7henUYxODYIvCiHitRc2ylwttjq
      t1KUY1cpq3jof2fhURHfBUH3dHPXLHig5V9Jw5gqeqOBwDCBvTAOBgNVHQ8BAf8E
      BAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSz
      w74g+O6dcBbwienD70D8A9ESmDAfBgNVHSMEGDAWgBQD8sBrApbyYyqUy+/TlwGy
      nx2V5jBMBgNVHREERTBDghFtYWlsLmV4YW1wbGUudGVzdIIVbWFpbC5kZXN0aW5h
      dGlvbi50ZXN0ghdzbXRwLnJlbGF5LXNlcnZpY2UudGVzdDAKBggqhkjOPQQDAgNI
      ADBFAiEAoety5oClZtuBMkvlUIWRmWlyg1VIOZ544LSEbplsIhcCIHb6awMwNdXP
      m/xHjFkuwH1+UjDDRW53Ih7KZoLrQ6Cp
      -----END CERTIFICATE-----

  dovecot-overrides:
    content: |
      # Unset this setting to no longer support negotiating DHE cipher suites
      ssl_dh =

      # This setting is the equivalent value configured in the image,
      # except the DHE cipher suites have been removed. Uncomment this setting to get expected output:
      # Ref: https://github.com/docker-mailserver/docker-mailserver/blob/5f4e868c541b142c705a643315ba8828d1836660/target/scripts/helpers/ssl.sh#L145-L146
      #ssl_cipher_list = ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384

Expected behavior

This took a while to troubleshoot, but eventually realized OpenSSL could negotiate it successfully (EDIT: Bad recall on my part), but testssl.sh was failing to report the cipher, I have found I could successfully negotiate the cipher suites that are expected to remain compatible:

# Directly verify `-cipher` negotiates successfully (requires uncommenting `ssl_cipher_list` in `compose.yaml`):
$ docker compose exec dms bash
$ timeout 1 openssl s_client -tls1_2 -cipher ECDHE-ECDSA-AES256-SHA384 -connect mail.example.test:993 -CAfile /tmp/tls/ca-cert.pem

UPDATE: I thought this was a testssl.sh issue, but realized while filing the report that my OpenSSL results were actually with the corrected Dovecot ssl_cipher_list setting (EDIT: this observation was flawed, OpenSSL can directly negotiate a compatible cipher successfully) 😓 Assuming that testssl.sh behaves the same as OpenSSL, it may not be able to negotiate any other compatible cipher due to the configured server preference order for Dovecot (which is where the actual bug of a contribution was coming from, caught thanks to testssl.sh! ❤️).


Instead of this incorrect result:

TLSv1.2 (server order)
 xc02b   ECDHE-ECDSA-AES128-GCM-SHA256     ECDH 253   AESGCM      128      TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
 xc02c   ECDHE-ECDSA-AES256-GCM-SHA384     ECDH 253   AESGCM      256      TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
 xcca9   ECDHE-ECDSA-CHACHA20-POLY1305     ECDH 253   ChaCha20    256      TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256

I expected the valid ciphers (or some warning emitted about bailing early):

TLSv1.2 (server order)
 xcca9   ECDHE-ECDSA-CHACHA20-POLY1305     ECDH 253   ChaCha20    256      TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
 xc02b   ECDHE-ECDSA-AES128-GCM-SHA256     ECDH 253   AESGCM      128      TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
 xc02c   ECDHE-ECDSA-AES256-GCM-SHA384     ECDH 253   AESGCM      256      TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
 xc023   ECDHE-ECDSA-AES128-SHA256         ECDH 253   AES         128      TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
 xc024   ECDHE-ECDSA-AES256-SHA384         ECDH 253   AES         256      TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384

or equivalent if results were stored via JSON using options --jsonfile-pretty port_993.json --overwrite:

$ docker compose run --rm -it \
  --volume ./testssl:/output \
  --workdir /output \
  --user "$(id -u):$(id -g)" \
  testssl --quiet --preference --jsonfile-pretty port_993.json --overwrite mail.example.test:993

# Get the TLS 1.2 cipher suite server order:
$ jq --raw-output '.scanResult[0].serverPreferences[] | select(.id == "cipherorder_TLSv1_2") | .finding' testssl/port_993.json
ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES256-SHA384

Your system (please complete the following information):

  • N/A (using the GHCR.io image)

Additional context

--debug 2 comparison:

Actual: (bailed early on (TLS) ServerHello empty for failed negotiation on DHE ciphersuite?)

Output
TLSv1.2
sending client hello... sending client hello... reading server hello...
sending close_notify...
  (46 lines returned)

sending client hello... sending client hello... reading server hello...
sending close_notify...
  (46 lines returned)

sending client hello... sending client hello... reading server hello...
sending close_notify...
  (46 lines returned)

sending client hello... sending client hello... reading server hello...
sending close_notify...
  (46 lines returned)

sending client hello... sending client hello... reading server hello...
(TLS) ServerHello empty, TCP connection closed
  (1 lines returned)

sending client hello... sending client hello... reading server hello...
sending close_notify...
  (46 lines returned)
 (server order)
 xc02b   ECDHE-ECDSA-AES128-GCM-SHA256     ECDH 253   AESGCM      128      TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
 xc02c   ECDHE-ECDSA-AES256-GCM-SHA384     ECDH 253   AESGCM      256      TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
 xcca9   ECDHE-ECDSA-CHACHA20-POLY1305     ECDH 253   ChaCha20    256      TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256

Expected:

Output
TLSv1.2
sending client hello... sending client hello... reading server hello...
sending close_notify...
  (46 lines returned)

sending client hello... sending client hello... reading server hello...
sending close_notify...
  (46 lines returned)

sending client hello... sending client hello... reading server hello...
sending close_notify...
  (46 lines returned)

sending client hello... sending client hello... reading server hello...
sending close_notify...
  (46 lines returned)

sending client hello... sending client hello... reading server hello...
sending close_notify...
  (46 lines returned)

sending client hello... sending client hello... reading server hello...
sending close_notify...
  (46 lines returned)

sending client hello... sending client hello... reading server hello...
(TLS) ServerHello empty, TCP connection closed
  (1 lines returned)
 (server order)
 xcca9   ECDHE-ECDSA-CHACHA20-POLY1305     ECDH 253   ChaCha20    256      TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
 xc02b   ECDHE-ECDSA-AES128-GCM-SHA256     ECDH 253   AESGCM      128      TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
 xc02c   ECDHE-ECDSA-AES256-GCM-SHA384     ECDH 253   AESGCM      256      TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
 xc023   ECDHE-ECDSA-AES128-SHA256         ECDH 253   AES         128      TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
 xc024   ECDHE-ECDSA-AES256-SHA384         ECDH 253   AES         256      TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions