title: How do I verify a TLS SMTP certificate is valid in PHP?
body: <p>To prevent man-in-the-middle attacks (a server pretending to be someone else), I would like to verify that the SMTP server I connect too over SSL has a valid SSL certificate which proves it is who I think it is.</p>
  
  <p>For example, after connecting to an SMTP server on port 25, I can switch to a secure connection like so: </p>
  
  <pre><code>&lt;?php
  
  $smtp = fsockopen( "tcp://mail.example.com", 25, $errno, $errstr ); 
  fread( $smtp, 512 ); 
  
  fwrite($smtp,"HELO mail.example.me\r\n"); // .me is client, .com is server
  fread($smtp, 512); 
  
  fwrite($smtp,"STARTTLS\r\n");
  fread($smtp, 512); 
  
  stream_socket_enable_crypto( $smtp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT ); 
  
  fwrite($smtp,"HELO mail.example.me\r\n");
  </code></pre>
  
  <p>However, there is no mention of where PHP is checking the SSL certificate against. Does PHP have a built-in list of root CA's? Is it just accepting anything?</p>
  
  <p><strong>What is the proper way to verify the certificate is valid and that the SMTP server really is who I think it is?</strong></p>
  
  <h1>Update</h1>
  
  <p>Based on <a href="http://www.php.net/manual/en/function.stream-socket-client.php#85345" rel="nofollow noreferrer">this comment on PHP.net</a> it seems I can do SSL checks using some stream options. The best part is that the <a href="http://php.net/manual/en/function.stream-context-set-option.php" rel="nofollow noreferrer">stream_context_set_option</a> accepts a context <em>or a stream resource</em>. Therefore, at some point in your TCP connection you can switch to SSL using a <a href="http://curl.haxx.se/docs/caextract.html" rel="nofollow noreferrer">CA cert bundle</a>.</p>
  
  <pre><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");
  }
  </code></pre>
  
  <p>Also, see <a href="http://us1.php.net/manual/en/context.php" rel="nofollow noreferrer">Context options and parameters</a> which expands on the <a href="http://us1.php.net/manual/en/context.ssl.php" rel="nofollow noreferrer">SSL options</a>.</p>
  
  <p><strong>However, while this now solves the main problem - how do I verify that the <em>valid</em> certificate actually belongs to the domain/IP I'm connecting to?</strong></p>
  
  <p>In other words, the cert the server I'm connecting too may have a valid cert - but how do I know it's valid for "example.com" and not another server using a valid cert to act like "example.com"?</p>
  
  <h1>Update 2</h1>
  
  <p>It seems that you can <a href="https://stackoverflow.com/a/3081093/99923">capture the SSL certificate</a> using the steam context params and parse it with <a href="http://php.net/manual/en/function.openssl-x509-parse.php" rel="nofollow noreferrer">openssl_x509_parse</a>.</p>
  
  <pre><code>$cont = stream_context_get_params($r);
  print_r(openssl_x509_parse($cont["options"]["ssl"]["peer_certificate"]));
  </code></pre>