Curl and sni-enabled server

curlssltls

I am running curl against a sni-enabled server with the following command

curl --cacert CustomCA.crt -H "Host: example.com" https://1.2.3.4/foo

However, I am not getting the right certificate where common name (CN) set as example.com (hence the certificate verification failed). I know the SNI config is done correctly because I see the right certificate is delivered when accessing it through web browser. Just in case, my current environment is

> curl --version
curl 7.35.0 (x86_64-pc-linux-gnu) libcurl/7.35.0 OpenSSL/1.0.1f zlib/1.2.8 libidn/1.28 librtmp/2.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smtp smtps telnet tftp 
Features: AsynchDNS GSS-Negotiate IDN IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP 
> lsb_release -a
LSB Version:    core-2.0-amd64:core-2.0-noarch:core-3.0-amd64:core-3.0-noarch:core-3.1-amd64:core-3.1-noarch:core-3.2-amd64:core-3.2-noarch:core-4.0-amd64:core-4.0-noarch:core-4.1-amd64:core-4.1-noarch:security-4.0-amd64:security-4.0-noarch:security-4.1-amd64:security-4.1-noarch
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.1 LTS
Release:        14.04
Codename:       trusty

EDIT: I forgot to mention that there is no dns entry to map example.com to 1.2.3.4. This is actually a part of my work, which I did it in ruby as follows:

  context = OpenSSL::SSL::SSLContext.new
  context.ca_file = 'CustomCA.crt'
  context.verify_mode = OpenSSL::SSL::VERIFY_PEER

  tcp_client = TCPSocket.new('1.2.3.4', 443)
  ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_client, context)
  ssl_client.hostname = 'example.com'
  ssl_client.connect
  cert = OpenSSL::X509::Certificate.new(ssl_client.peer_cert)
  cert_ca = OpenSSL::X509::Certificate.new(File.read(ca_path))
  ssl_client.sysclose
  tcp_client.close

I am just wondering how to re-create something like this by just using curl.

And… by editing my own copy of /etc/hosts, the command would work properly

1.2.3.4 example.com

why is that?

EDIT 2: doing it via openssl

openssl s_client -connect 1.2.3.4:443 -servername example.com

Best Answer

The host header has nothing to do with SNI. To use SNI you have to use the hostname in the URL, i.e. use https://example.com/ instead of http://1.2.3.4/ (and of course use https:// not http://).

More details: SNI sends the hostname inside the TLS handshake (ClientHello). The server then chooses the correct certificate based on this information. Only after the TLS connection is successfully established it will send the HTTP-Request, which contains the Host header you specified.

Regarding your edit/comments:

  • I wonder how you've accessed the site successfully with the browser if there is no DNS entry for the host. If the browser does not know the hostname it cannot use SNI either.
  • If you add the host/ip mapping to /etc/hosts it will work when accessing https://example.com with curl because you know you have the hostname for SNI and the matching IP for the TCP connection.
  • Your ruby code sets ssl_client.hostname. This is not the same as the HTTP host header you've set with curl.
  • I don't see an option for curl where you could set hostname for TLS SNI and IP for TCP connection independently.
Related Question