0 s:/CN=a i:/CN=a 1 s:/CN=b i:/CN=a
The output shows two certificates with their Distinguished Name (DN), in this example consisting only of the Common Name (denoted 'CN'), for both the Subject (denoted 's') and Issuer (denoted 'i') of the respective certificate; it shows how in this example Certificate Authority (CA) A signed client certificate B. Checking out the source for s_client showed the following code at work in apps/s_client.c:
sk = SSL_get_peer_cert_chain(s); if (sk != NULL) { got_a_chain = 1; /* we don't have it for SSL2 (yet) */ BIO_printf(bio, "---\nCertificate chain\n"); for (i = 0; i < sk_X509_num(sk); i++) { X509_NAME_oneline(X509_get_subject_name(sk_X509_value(sk, i)), buf, sizeof buf); BIO_printf(bio, "%2d s:%s\n", i, buf); X509_NAME_oneline(X509_get_issuer_name(sk_X509_value(sk, i)), buf, sizeof buf); BIO_printf(bio, " i:%s\n", buf); if (c_showcerts) PEM_write_bio_X509(bio, sk_X509_value(sk, i)); }
This code, simple enough, detailed what functions would be necessary for my own needs.
OpenSSL handshake error 'error:1408A0C1:SSL routines:ssl3_get_client_hello:no shared cipher' for '71.6.146.185' port '6697' OpenSSL handshake error 'error:140760FC:SSL routines:SSL23_GET_CLIENT_HELLO:unknown protocol' for '71.6.146.185' port '6697' OpenSSL handshake error 'error:1408A10B:SSL routines:ssl3_get_client_hello:wrong version number' for '71.6.146.185' port '6697' OpenSSL handshake error 'error:1408A0C1:SSL routines:ssl3_get_client_hello:no shared cipher' for '71.6.146.185' port '6697'
The string returned by ERR_error_string() is the first single-quoted string in each of the above lines. With logging connection failures out of the way I could then move onto logging certificate verification.
unable to verify the first certificate certificate revoked
Notice that the errors don't always make the situation clear, but they are far better than nothing.
Logging the peer certificate chain was slightly more involved than in the s_client code; this is because, in server mode, SSL_get_peer_cert_chain() does not present the peer certificate and thus a separate call to SSL_get_peer_certificate() is necessary. It also may not be immediately apparent how to use the strange STACK_OF(X509*) object returned by the function; the STACK_OF object is a special data type defined in OpenSSL via macros that allows access of OpenSSL objects through a, well, stack abstraction for that specific type, perhaps analogous to a C++ template, though I am no C++ aficionado. In the STACK_OF(X509*) case this meant using the functions sk_509_num() and sk_509_value() to return the length of the stack and the value in the stack, respectively. Once the certificate had been obtained I could call X509_get_subject_name() and X509_get_issuer_name() to return the subject and issuer names, respectively, followed by X509_NAME_oneline() to turn it into human-readable ASCII text. Doing that for each certificate gave me enough information to log the peer's certificate chain.
Valid peer certificate chain from '127.0.0.1' port '6697': 0 s:/CN=leaf_ca i:/CN=root_ca
...while the same command with the Certificate Authority specified via CAfile, specifically, openssl s_client -verify_return_error -CAfile root_ca/certs/root_ca.pem -cert leaf_ca/certs/leaf_ca.pem -key leaf_ca/private/leaf_ca.pem -connect 127.0.0.1:6697 -ign_eof produced the output:
Valid peer certificate chain from '127.0.0.1' port '6697': 0 s:/CN=leaf_ca i:/CN=root_ca 1 s:/CN=root_ca i:/CN=root_ca
Even stranger, this did not happen with the chain of trust A -> B -> C and the client passing certificates B and C via openssl s_client -verify_return_error -CAfile a/certs/a.pem -cert c/certs/c.pem -key c/private/c.pem -cert_chain b/certs/b.pem -connect 127.0.0.1:6697 -ign_eof:
Valid peer certificate chain from '127.0.0.1' port '6697': 0 s:/CN=c i:/CN=b 1 s:/CN=b i:/CN=a
There doesn't appear to be any harm in this behavior as far as I can tell, but it is rather odd.
Slightly more annoying for me is how sending a certificate chain isn't cooperating with Certificate Revocation Lists (CRLs) in a way that I'd like. As mentioned in a previous blog post about CRLs, each certificate authority must have a CRL, even if it hasn't revoked any certificates. Well, this logic also applies when the client sends the CA in a chain, even if the server trusts said CA, thus a trust chain of A -> B -> C where the server has A and the client sends B -> C will fail unless the client sends a CRL for B. The problem is that I have no idea how to add B's CRL into the client's certificate chain. Perhaps I'll find out with more digging, perhaps it's not currently possible. At least I got the logging working, though.