mongodbgosslhandshakeca

Failed to connect to a server with Golang due x509 certificate relies on legacy Common Name field


I'm trying to connect on a mongodb server, to connect I have to provide a CA cert file and also tls cert file.

When I use the following command I don't have issue

$ mongo --host customhost:port DB --authenticationDatabase=DB -u ACCOUNT -p PWD --tls --tlsCAFile /etc/ca-files/new-mongo.ca.crt --tlsCertificateKeyFile /etc/ca-files/new-mongo-client.pem 

But when I try to connect with mongo (and also tested with just a tls client) I have the following error:

failed to connect: x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0

If I use the env variable everything works well but I would like to know how to fix it without having to use it.

const CONFIG_DB_CA = "/etc/ca-files/new-mongo.ca.crt"

func main() {
    cer, err := tls.LoadX509KeyPair("mongo-server.crt", "mongo-server.key")
    if err != nil {
        log.Println(err)
        return
    }

    roots := x509.NewCertPool()
    ca, err := ioutil.ReadFile(CONFIG_DB_CA)
    if err != nil {
        fmt.Printf("Failed to read or open CA File: %s.\n", CONFIG_DB_CA)
        return
    }
    roots.AppendCertsFromPEM(ca)

    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cer},
        RootCAs:      roots,
    }

    conn, err := tls.Dial("tcp", "customhost:port", tlsConfig)
    if err != nil {
        fmt.Printf("failed to connect: %v.\n", err)
        return
    }

    err = conn.VerifyHostname("customhost")
    if err != nil {
        panic("Hostname doesn't match with certificate: " + err.Error())
    }
    for i, cert := range conn.ConnectionState().PeerCertificates {
        prefix := fmt.Sprintf("CERT%d::", i+1)
        fmt.Printf("%sIssuer: %s\n", prefix, cert.Issuer)
        fmt.Printf("%sExpiry: %v\n", prefix, cert.NotAfter.Format(time.RFC850))
        fmt.Printf("%sDNSNames: %v\n\n", prefix, cert.DNSNames)
    }
    
    fmt.Printf("Success!")
}

Certificates:

$ openssl x509 -in /etc/ca-files/new-mongo.ca.crt -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            ....
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = FR, ST = IDF, L = Paris, O = COMP, OU = IT, CN = newmongo
        Validity
            Not Before: Jun 30 13:02:12 2021 GMT
            Not After : Jun 30 13:02:12 2023 GMT
        Subject: C = FR, ST = IDF, L = Paris, O = COMP, OU = IT, CN = newmongo

...

        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                ...
            X509v3 Authority Key Identifier: 
                ....

            X509v3 Basic Constraints: critical
                CA:TRUE
$ openssl x509 -in /etc/ca-files/newmongo-client.pem -text -noout 
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            ...
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = FR, ST = IDF, L = Paris, O = COMP, OU = IT, CN = newmongo
        Validity
            Not Before: Jun 30 13:17:25 2021 GMT
            Not After : Jun 30 13:17:25 2023 GMT
        Subject: C = FR, ST = IDF, L = Paris, O = COMP, OU = IT, CN = newmongo-client
...
        X509v3 extensions:
            X509v3 Subject Alternative Name: 
                DNS:customhost:port, DNS:customhost, DNS:newmongo-client

I'm a bit stuck and don't know if the problem is my code configuration of tls and the way I loaded certificates or if it comes from the SSL certificate misconfiguration but from what certificates look fine. I feel like loaded certificate are ignored for any reason.


Solution

  • You need to fix the problem at the source and generate a certificate with a DNS SAN field - then the Go runtime check will disappear.

    This is achievable with openssl but is tricky as it requires a config file - as SAN field options are too broad to fit into simple command-line options.

    The general gist is, create a CSR:

    openssl req -new \
        -subj "${SUBJ_PREFIX}/CN=${DNS}/emailAddress=${EMAIL}" \
                -key "${KEY}" \
        -addext "subjectAltName = DNS:${DNS}" \
        -out "${CSR}"
    

    and then sign the CSR with your root CA:

    openssl ca \
            -create_serial \
                    -cert "${ROOT_CRT}" \
            -keyfile "${ROOT_KEY}" \
                    -days "${CERT_LIFETIME}" \
                    -in "${CSR}" \
            -batch \
            -config "${CA_CONF}" \
                    -out "${CRT}"
    

    CA_CONF referenced above looks something like this:

    [ ca ]
    default_ca      = my_ca
    
    [ my_ca ]
    dir             = ./db
    database            = $dir/index.txt
    serial              = $dir/serial
    new_certs_dir   = $dir/tmp
    x509_extensions = my_cert
    name_opt            = ca_default
    cert_opt            = ca_default
    default_md          = default
    policy              = policy_match
    # 'copy_extensions' will copy over SAN ("X509v3 Subject Alternative Name") from CSR
    copy_extensions = copy
    
    [ my_cert ]
    basicConstraints        = CA:FALSE
    nsComment               = "generated by https://github.com/me/my-pki"
    subjectKeyIdentifier    = hash
    authorityKeyIdentifier  = keyid,issuer
    
    [ policy_match ]
    # ensure CSR fields match that of delivered Cert
    countryName             = match
    stateOrProvinceName     = match
    organizationName        = match
    organizationalUnitName  = optional
    commonName              = supplied
    emailAddress            = optional
    

    Inspecting the resulting server cert:

    openssl x509 -in server.crt -noout -text
    

    should then have a SAN section like:

    X509v3 Subject Alternative Name: 
        DNS:myserver.com