5

I'm using PHP and trying to verify a SSL certificate belongs to the SMTP domain/IP I'm connecting to.

Currently I can verify the certificate is valid using the following code

$resource = fsockopen( "tcp://mail.example.com", 25, $errno, $errstr ); 

...

stream_set_blocking($resource, true);

stream_context_set_option($resource, 'ssl', 'verify_host', true);
stream_context_set_option($resource, 'ssl', 'verify_peer', true);
stream_context_set_option($resource, 'ssl', 'allow_self_signed', false);

stream_context_set_option($resource, 'ssl', 'cafile', __DIR__ . '/cacert.pem');

$secure = stream_socket_enable_crypto($resource, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
stream_set_blocking($resource, false);

if( ! $secure)
{
    die("failed to connect securely\n");
}

Based on the documentation it seems like I need to do something like this.

stream_context_set_option($resource, 'ssl', 'SNI_enabled', true);
stream_context_set_option($resource, 'ssl', 'SNI_server_name', 'expected.example.com');

How do I verify that the server I'm connecting too has a valid cerificate for expected.example.com? Should I do a rDNS check first? What if the DNS was altered by a MITM attack?

Adi
  • 43,808
  • 16
  • 135
  • 167
Xeoncross
  • 313
  • 2
  • 12
  • No you use the one you used when you opened the socket and check it matches the cname in the certificate you got back. – ewanm89 Nov 18 '12 at 00:01
  • The extra code is for enabling server name indication sending the right certificate for the domain the client tells the server it's connected to, this is so hosting shared off the same IP can handle certificates for multiple vhost domains. – ewanm89 Nov 18 '12 at 00:04

3 Answers3

5

SNI is Server Name Indication; it is not part of any verification. A client uses SNI to inform the server about the name that the client is trying to reach; the name is sent in the early steps of the SSL handshake. This is meant to support multiple servers (that is, multiple names) sharing the same IP address: the SNI tells the server which certificate it should use for this particular connection.

This certificate-choosing is important because clients expects the server's certificate to contain the name that they want to reach. And this "expectation" is precisely what you should do: you need to inform your SSL implementation that you want to reach a server with a specific name (mail.example.com, apparently) and that the server's certificate MUST contain that name (this is crucial: without this test, an active attacker could feed you his own certificate and silently hijack your connection). It seems that this is done with the CN_match option.

(Of course you can use SNI; it is even recommended because it may really help the server. But that's not SNI which protects you against MitM attacks.)

Thomas Pornin
  • 320,799
  • 57
  • 780
  • 949
3

The typical way of defining validity is a series of checks - sometimes later checks are skipped, if the risk of not doing them is deemed sufficiently low.

  1. There is a proof that the sender has control of the private key - in an SSL transaction, this is handled for you as part of the handshake.

  2. Certificate is constructed and signed properly - has a valid signature - I would bet that the command line for "verify_peer" accomplishes this (qualification - I've never done this in PHP)

  3. Certificate is signed by a trusted source - means you can't use a self-signed certificate without caching a copy of it. Typically you need to provision the server with a collection of trusted CAs. Probably covered by your command referencing that allow_self_signed is false.

  4. Certificate is not expired - checking the validity date will do it. Not sure that any of your code is doing this - it's not a given, you need to test it and check the documentation more deeply.

  5. Certificate is in good standing (not revoked) with the issuing CA - involves checking the CRL of the CA or making an OCSP request. The CA will revoke the certificate if there's reason to doubt that the key pair remains privately held by the source. I don't see any code in your library that provides this. It's not unusual - several times I've had to have groups I work with use additional libraries to add this check in. This is where many web developers start skipping steps of the risk is low enough.

As an add on - some security policies will dictate that the certificate must correctly represent the server that is providing it. For a web server, this is typically that the Common Name (CN) of the certificate matches the DNS of the host you are contacting (see https://www.google.com for an example). Other systems may also check the Subject Alt Name (also available in Google's cert for example).

This isn't totally standardized. The RFCs will describe the potential uses and formats of these fields, but it's up to your entity's security policy to state clearly what's required here.

Typically for this type of check, I've:

  • Had access to the desired DNS for the connection
  • Made the connection, gotten the certificate
  • Pulled the certificate from the connection and verified that the CN or Subject Alt Name matched what I originally asked for in the first step.

That, combined with the trusted CA is enough to verify that:

  • an entity that you trust
  • verified that you got what you asked for

Step 4 in the list above is the added assurance that in the time between the CA issuing the certificate and now, nothing has changed.

bethlakshmi
  • 11,606
  • 1
  • 27
  • 58
2

Doing this yourself is full of problems. One that was mentioned is that you need to check whether the certificate has been revoked, which involves contacting the trusted Certificate Authority. And rejecting the certificate if that contact fails. Another one is that the Common Name in the certificate is a sequence of bytes, which may include zero bytes. For example, when you contact www.amazon.com you expect the Common Name to be 14 bytes. It could be more bytes, with byte #14 being zero - an attack that many implementations failed to detect. Much better to use some library that is regularly updated.

gnasher729
  • 1,823
  • 10
  • 14