The Crystal Programming Language Forum

Specifying the SNI hostname for an OpenSSL Context?

Is there a way to specify a SNI hostname on an OpenSSL::SSL::Context?

I spent some time digging around the stdlib implementation and the closest thing I see is crystal/context.cr at 6d9a1d5830db5f276bf3df37fceeb0d5333e7e14 · crystal-lang/crystal · GitHub – which is used when OpenSSL::SSL::Socket::Client.new contains the hostname argument, but I don’t see any way that functionality is exposed through to OpenSSL::SSL::Context. I’m honestly not even clear if that hostname is even the right place to do so.

Yep, I had to do it for the Neo4j driver. You’ve gotta pass hostname to the OpenSSL::SSL::Socket::Client constructor rather than setting it on the SSL context.

Nice, thank you for the link. That’s what I was assuming was possible but I hadn’t actually stumbled through the process to correctly open a socket with SNI. Unfortunately it seems like there isn’t a way to pass an existing OpenSSL::SSL::Socket::Client in to HTTP::Client in order to use the native HTTP library.

No, but when it establishes its own TLS connection it does pass in the hostname for SNI.

For most use cases that works; yea. I’m mapping out IP addresses which are supposed to respond for a given domain name and validating that each of them does in fact respond correctly. In order to do this I’m sending the request to the IP address and specifying the HOST header manually — the stdlib supports this intelligently.

I want to do certificate tracking which can be done without HTTP but when the server speaks HTTP I also want to validate that it responds to the right Host header.

Manually forming the request like this works great, unless the server is using SNI, which is fairly common. The SNI request then fails to establish a connection because that line asks the server for the certificate match for an IP address rather than the name. I’d argue that if present the SSL connect should take the host name front the SSL context instead.

Can you expand more on this use case? Because right now it kinda sounds like you’re doing the work a load balancer should be doing. Unless … are you writing a load balancer?

Unless … are you writing a load balancer?

No, not quite. Good guess, but it’s almost the reverse.

The web applications I’m deploying are load balanced in several ways. One of those ways is to provide multiple A/AAAA records on DNS resolution, and the client resolver can decide on it’s own which IP address to hit. Typically each of those addresses are targeted at a load balancer, and behind the load balancer you have however many servers doing whatever.

On top of that DNS resolution is often geography sensitive so that requests can be “more local” – traffic is directed at local data centers.

Once in a rare while there is a network problem where one of the IP addresses published is not actually able to serve traffic for the published domain.

I’m writing software which attempts to detect this particular type of failure. It resolves a list of IP addresses from different geographically sourced IP addresses and targets each of those to ensure that they can all route traffic correctly. In order to do this I need to be able to target an SSL connection at each specific ip address and create an http request to the server as well.

Ah, you need this for something more related to infrastructure than a typical application. That makes perfect sense. Infrastructure code always has weird requirements. :grinning_face_with_smiling_eyes:

I was going to recommend possibly subclassing HTTP::Client to add a @hostname ivar and override how the connection is made but I think there may actually be a way to do this as-is. It requires setting up the TLS connection yourself and passing it to this constructor along with the hostname ():

socket = TCPSocket.new(ip_address, port)
ssl = OpenSSL::SSL::Socket::Client.new(socket, hostname: hostname, sync_close: true)
http = HTTP::Client.new(ssl, host: hostname)

http.get(“/“)

I haven’t tried this because I’m writing on an iPad at the moment but from what I can tell in the docs and code I’m almost 100% a solid 80% confident that this might even compile once you supply ip_address, port, and hostname.

1 Like

@jgaskins that’s brilliant, thank you. I’ve been tripping over this configuration for literally months.

1 Like