Get certificate information after connection error
<p>I'm writing a simple SSL client using the OpenSSL library. I'd like to be able to print the certificate chain presented by the server after the connection completes. When the connection completes successfully, this isn't a problem. However, if the connection fails for some reason, I'm unable to get the failing certificate the server presented. Here's a <a href="http://sscce.org/" rel="nofollow">SSCCE</a> that demonstrates this.</p>

<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;stdarg.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;

#include &lt;openssl/ssl.h&gt;
#include &lt;openssl/x509_vfy.h&gt;
#include &lt;openssl/err.h&gt;

#define CIPHER_LIST "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"
// #define HOST "google.com" // works
#define HOST "expired.badssl.com" // does not print presented certificate


void print_certificates(SSL *ssl){
    STACK_OF(X509) * sk = SSL_get_peer_cert_chain(ssl);
    X509* cert = NULL;
    char sbuf[1024];
    char ibuf[1024];

    if(sk == NULL){
        printf("Cert chain is null!\n");
    }

    for (int i = 0; i &lt; sk_X509_num(sk); i++) {
        cert = sk_X509_value(sk, i);
        fprintf(stdout, "Subject: %s\n", X509_NAME_oneline(X509_get_subject_name(cert), sbuf, 1024));
        fprintf(stdout, "Issuer: %s\n", X509_NAME_oneline(X509_get_issuer_name(cert), ibuf, 1024));
        PEM_write_X509(stdout, cert);
    }
}

void verify_cert(SSL *ssl, char* host){
    print_certificates(ssl);
    X509* cert = SSL_get_peer_certificate(ssl);
    if(cert) { X509_free(cert); }

    int res = SSL_get_verify_result(ssl);

    if(!(X509_V_OK == res)){
        printf("ERROR (NOT VERIFIED - %s): %s\n", X509_verify_cert_error_string(res), host);
        return;
    }

    printf("SUCCESS: %s\n", host);
    fflush(stdout);

}

int main(int argc, char **argv){
    SSL * ssl = NULL;
    SSL_CTX *ctx = NULL;
    BIO *bio = NULL;
    int res;

    SSL_library_init();
    SSL_load_error_strings();

    const SSL_METHOD* method = SSLv23_method();
    if(method == NULL)
        goto End;

    ctx = SSL_CTX_new(method);
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
    if (ctx == NULL)
        goto End;
    SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 |
        SSL_OP_NO_SSLv3);

    res = SSL_CTX_set_default_verify_paths(ctx);
    if (res != 1)
        goto End;

    bio = BIO_new_ssl_connect(ctx);
    if (bio == NULL)
        return 0;

    BIO_set_conn_hostname(bio, HOST);
    BIO_set_conn_port(bio, "443");
    BIO_set_nbio(bio, 1);

    BIO_get_ssl(bio, &amp;ssl);
    if(ssl == NULL)
        goto End;

    SSL_set_cipher_list(ssl, CIPHER_LIST);
    res = SSL_set_tlsext_host_name(ssl, HOST);

    int still_connecting = 1;
    while(still_connecting){
        int res = SSL_connect(ssl);
        if (res &lt;= 0){
            unsigned long error = SSL_get_error(ssl, res);
            if ( (error != SSL_ERROR_WANT_CONNECT) &amp;&amp;
                 (error != SSL_ERROR_WANT_READ) &amp;&amp; (error != SSL_ERROR_WANT_WRITE) )
            {
                printf("Connection encountered fatal error\n");
                ERR_print_errors_fp(stdout);
                still_connecting = 0;
            }
        }
        else{
            printf("Connection completed succesfully\n");
            still_connecting = 0;
        }
    }

    verify_cert(ssl, HOST);

End:
return 0;
}
</code></pre>

<p>(Fastest way to compile this is <code>gcc sscce.c -lcrypto -lssl -o sscce</code>). </p>

<p>Whenever <code>SSL_connect(ssl)</code> <a href="https://www.openssl.org/docs/manmaster/ssl/SSL_connect.html" rel="nofollow">returns 1</a> (whenever the connection is successful), <code>print_certificates(ssl)</code> works as expected. However, if <code>SSL_connect(ssl)</code> returns anything other than 1 (the connection failed), <code>print_certificates(ssl)</code> does not print anything, because <code>SSL_get_peer_cert_chain(ssl)</code> returns null. While this is a logical behavior for servers that don't present a certificate, on servers that present an invalid certificate, not having access to the certificate makes it difficult to debug server configuration issues.</p>

<p>Interestingly, <code>SSL_get_verify_result(ssl)</code> returns the correct error code when the connection fails, despite the fact that I can't get the certificate chain myself.* I've looked through the OpenSSL codebase a little bit to try and figure out why this is, and while I'm still struggling to understand how everything fits together, it looks like there's some code in the <a href="https://github.com/openssl/openssl/blob/6c1f368d882846e5624f8b47be951d16be65a30c/ssl/ssl_cert.c#L378" rel="nofollow"><code>ssl_verify_cert_chain</code> function</a> that <a href="https://github.com/openssl/openssl/blob/6c1f368d882846e5624f8b47be951d16be65a30c/ssl/ssl_cert.c#L443-L452" rel="nofollow">frees the certificates after performing the error checking</a>. I'm guessing that in the example code above, <code>SSL_connect</code>, once it has the complete certificate chain, runs some built-in validation code that frees the certificate before it gets to <code>print_certificates</code>. This confuses me, because I don't understand why the certificates would be freed when validation fails, but not when it succeeds. Perhaps someone who knows more about OpenSSL's internal behavior can shed some light on this.</p>

<p>I've noted that the stock <code>s_client</code> provided by the <code>openssl</code> utility, when run with the showcerts option (<code>openssl s_client -showcerts -connect expired.badssl.com:443</code>) does not exhibit this behavior. Regardless of whether the connection succeeds or fails, the certificates are printed. The <code>print_certificates</code> function in my SSCCE is just a modified version of the <a href="https://github.com/openssl/openssl/blob/6c1f368d882846e5624f8b47be951d16be65a30c/apps/s_client.c#L2505-L2520" rel="nofollow">s_client cert printing code</a>, but s_client doesn't use <code>SSL_connect</code>, so it's unsurprising that it exhibits different behavior. I note that s_client <a href="https://github.com/openssl/openssl/blob/6c1f368d882846e5624f8b47be951d16be65a30c/apps/s_client.c#L1620" rel="nofollow">sets a custom certificate verify callback</a> (<a href="https://github.com/openssl/openssl/blob/6c1f368d882846e5624f8b47be951d16be65a30c/apps/s_cb.c#L46-L106" rel="nofollow">defined here</a>), but I'm reluctant to use anything but the default** verify function.</p>

<p><strong>tl;dr <code>SSL_get_peer_cert_chain(ssl)</code> returns null if the server presents an invalid certificate chain. How can I get around this to print the failing certificate chain?</strong></p>

<p>Edit: I've confirmed that this problem still occurs when I set the BIO state to blocking, and also, (for what it's worth), when the above code is compiled using LibreSSL.</p>

<p>Edit 2: I found that creating a function that just returns 1 and passing that to <code>SSL_CTX_set_verify</code> as the callback function (instead of NULL) causes <code>SSL_get_peer_cert_chain(ssl)</code> to return the certificate chain as expected, despite invalid certs in the chain. However, I'm reluctant to call this a solution to the problem, since I pretty clearly must be overriding one of OpenSSL's built-in functions here.</p>

<p>* - The obvious response to this is that since OpenSSL tells me why the connection failed, I don't need to access the raw cert in order to debug my issues. In any other situation, this would be true, but since I'm using this client as part of a research project involving invalid certificate use on the internet, I need to be able to save the failing certificates to a file.</p>

<p>** - As far as I can tell, passing NULL as the verify_callback parameter of <code>SSL_CTX_set_verify</code> tells OpenSSL to use a builtin default function for certificate validation. <a href="https://www.openssl.org/docs/manmaster/ssl/SSL_CTX_set_verify.html" rel="nofollow">The documentation</a> isn't tremendously clear on this.</p>