'How to get the currently used cipher suite of a website?

I want to get a cipher suite that currently used of any target host such as: stackoverflow.com. Normally, I would use an openssl command for this: openssl s_client -connect stackoverflow.com:443

Output:

CONNECTED(00000005)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = *.stackexchange.com
verify return:1
---
Certificate chain
 0 s:CN = *.stackexchange.com
   i:C = US, O = Let's Encrypt, CN = R3
 1 s:C = US, O = Let's Encrypt, CN = R3
   i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
 2 s:C = US, O = Internet Security Research Group, CN = ISRG Root X1
   i:O = Digital Signature Trust Co., CN = DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
subject=CN = *.stackexchange.com

issuer=C = US, O = Let's Encrypt, CN = R3

---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 5138 bytes and written 360 bytes
Verification: OK
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES128-GCM-SHA256
    Session-ID: 27AFEC2CF0888EFFABF09081F331BA6B64FC5CD59FF8BD4A396CC0228E459EA6
    Session-ID-ctx: 
    Master-Key: 23F30C3F4FA581F3D1ACDC4B001859B8908469C03FEB4C26A9B34876DC13576A15B26260C7B00FB108DBF91CA16AC55E
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 7200 (seconds)
    TLS session ticket:
    0000 - 06 ba da fe 3e 7f 04 6c-61 ee 97 f5 07 6a 9a 21   ....>..la....j.!
    0010 - e5 90 2c 8e c5 20 a2 fa-8c 2e 49 57 5e 55 4c f8   ..,.. ....IW^UL.
    0020 - b8 ac fa 34 8e 98 d0 76-90 76 cf b2 74 82 37 f0   ...4...v.v..t.7.
    0030 - b4 c2 1a d3 0e b6 f2 b3-4b 93 ec 2e ed 58 4d c8   ........K....XM.
    0040 - cd c9 63 ad 46 3e c9 77-1a 9f 5d c9 41 61 1b 56   ..c.F>.w..].Aa.V
    0050 - f4 d2 d0 f4 15 5e ad f6-e8 dd 00 11 74 4c 89 d8   .....^......tL..
    0060 - b2 7b 0c e5 d6 17 e4 9f-41 5e 2c 02 cd c5 74 20   .{......A^,...t 
    0070 - 82 ea 5f dc 52 bc 0a c7-69 13 50 a3 78 cd 0c 94   .._.R...i.P.x...
    0080 - 0c aa ad 33 b6 b0 26 63-20 6b ae 2e 1c 75 ef 79   ...3..&c k...u.y
    0090 - fc ee 54 a4 4f 86 d2 a8-72 83 c3 d2 1e 78 5d 8b   ..T.O...r....x].
    00a0 - 3e b1 e2 b8 bb 5d b5 b3-01 3b 3d 27 5d ba 40 1c   >....]...;='].@.
    00b0 - bd 11 2e 92 96 c5 ab 48-10 ac 47 e7 b0 db 79 d0   .......H..G...y.

    Start Time: 1646821102
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: yes
---

And ECDHE-RSA-AES128-GCM-SHA256 is exactly the result I wanted. And I want to make a program that give the same output written in Go: Input: the target host string Output: the currently used cipher suite of that host This is my Go program, but the output cipher suite doesn't seem to be the same as the openssl s_client -connect stackoverflow.com:443 command.

package main

import (
    "crypto/tls"
    "fmt"
    "net"
    "os"
)

var (
    targetHost, tlsVersion string
    TLS12Ciphers           = []uint16{
        tls.TLS_RSA_WITH_RC4_128_SHA,
        tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
        tls.TLS_RSA_WITH_AES_128_CBC_SHA,
        tls.TLS_RSA_WITH_AES_256_CBC_SHA,
        tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
        tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
        tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
        tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
        tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
        tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
        tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
        tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
        tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
        tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
        tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
        tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
        tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
        tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
        tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
        tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
        tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
        tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
    }
    TLS13Ciphers = []uint16{
        tls.TLS_AES_128_GCM_SHA256,
        tls.TLS_AES_256_GCM_SHA384,
        tls.TLS_CHACHA20_POLY1305_SHA256,
    }
)

func showError(message string) {
    fmt.Println(message)
    os.Exit(0)
}

func supportedCiphers(targetHost string) []uint16 {
    var ciphersLists, supportedCiphers []uint16
    var tlsMinVer, tlsMaxVer int
    if tlsVersion == "1.2" {
        ciphersLists = TLS12Ciphers
        tlsMinVer = tls.VersionTLS10
        tlsMaxVer = tls.VersionTLS12
    } else if tlsVersion == "1.3" {
        ciphersLists = TLS13Ciphers
        tlsMinVer = tls.VersionTLS13
        tlsMaxVer = tls.VersionTLS13
    } else {
        showError("Invalid TLS version!")
    }
    for _, cipherSuite := range ciphersLists {
        tlsConfigs := &tls.Config{
            ServerName:   targetHost,
            CipherSuites: []uint16{cipherSuite},
            MinVersion:   uint16(tlsMinVer),
            MaxVersion:   uint16(tlsMaxVer),
        }
        conn, err := net.Dial("tcp", targetHost+":443")
        if err != nil {
            showError("Error when connecting to target server!")
        }
        tlsClient := tls.Client(conn, tlsConfigs)
        tlsClient.Handshake()
        tlsClient.Close()
        if tlsClient.ConnectionState().CipherSuite == cipherSuite {
            supportedCiphers = append(supportedCiphers, cipherSuite)
        }
    }
    return supportedCiphers
}

func main() {
    targetHost = os.Args[1]
    tlsVersion = os.Args[2]
    fmt.Println("Supported TLS " + tlsVersion + " ciphers")
    for _, cipherSuite := range supportedCiphers(targetHost) {
        fmt.Printf(tls.CipherSuiteName(cipherSuite) + "\n")
    }
}

How can I make a Go program that get the currently used cipher suite of a target website and give the same output as the openssl s_client -connect stackoverflow.com:443 command?



Solution 1:[1]

the currently used cipher suite of a target website

is ill defined. There is no "currently used cipher suite": Which cipher is used is negotiated between the client and the server based on their announced preferences. The Go client has different preferences and thus negotiates a different cipher. Note that the same is true for OpenSSL: Ich you change your OpenSSL preferences then openssl will negotiate a different cipher too.

If you want to make your Go code "give the same output as the openssl [...] command" you will have to tweak the Go preferences, the OpenSSL preferences or both. E.g. by using the same ciphers in your Go code (which doesn't announce ECDHE-RSA-AES128-GCM-SHA256 as available).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Volker