In previous post, I implemented a simple HTTPS
client, but the program has a small flaw, i.e., when connecting to “www.google.com:443
“, it will report following error in verifying certificate:
error code is 18:self signed certificate
error code is from SSL_get_verify_result:
long SSL_get_verify_result(const SSL *ssl)
{
return ssl->verify_result;
}
and 18
is mapping to X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT, which means “self-signed certificate”. But for other websites, e.g., facebook.com
, no error is outputted.
Use OpenSSL
‘s client-arg program to test:
# LD_LIBRARY_PATH=/root/openssl/build gdb --args ./client-arg -connect "www.google.com:443"
......
Thread 2 hit Breakpoint 1, main (argc=3, argv=0xfffffc7fffdf4c38) at client-arg.c:99
99 BIO_puts(sbio, "GET / HTTP/1.0\n\n");
(gdb) p ssl->verify_result
$1 = 18
(gdb)
The same error code: 18
. But openssl-s_client
can guarantee the certificate is not “self-signed”:
# LD_LIBRARY_PATH=/root/openssl/build openssl s_client -connect google.com:443
CONNECTED(00000004)
depth=2 OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign
verify return:1
depth=1 C = US, O = Google Trust Services, CN = GTS CA 1O1
verify return:1
depth=0 C = US, ST = California, L = Mountain View, O = Google LLC, CN = *.google.com
verify return:1
---
Certificate chain
0 s:C = US, ST = California, L = Mountain View, O = Google LLC, CN = *.google.com
i:C = US, O = Google Trust Services, CN = GTS CA 1O1
1 s:C = US, O = Google Trust Services, CN = GTS CA 1O1
i:OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign
---
......
Hmm, I need to find the root cause.
First of all, I searched the code to see when X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
is set, and found only one spot:
if (self_signed)
return verify_cb_cert(ctx, NULL, num - 1,
sk_X509_num(ctx->chain) == 1
? X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
: X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN);
The interesting thing is the amount of certificates in the chain is only 1
, but from above openssl-s_client
‘s output, there are 2
certificates in the chain. OK, let’s see the content of this “self-signed” certificate.
After some debugging, I finally found tls_process_server_certificate, which is used to process the server’s certificate. With the help of gdb
, I can dump the content of certificate:
# gdb --args ./client www.google.com:443
.......
(gdb) b tls_process_server_certificate
......
Thread 2 hit Breakpoint 1, tls_process_server_certificate (s=0xf09e90, pkt=0xfffffc7fffdefe30)
at ../ssl/statem/statem_clnt.c:1768
1768 X509 *x = NULL;
......
1806 if (certbytes != (certstart + cert_len)) {
(gdb)
1811 if (SSL_IS_TLS13(s)) {
(gdb) dump binary memory cert certstart certstart + cert_len
......
Try to check the cert
file:
# cat cert
......
�0� *�H�� (No SNI provided; please fix your client.10Uinvalid2.invalid0�"0
��bO����
.....
The reason is obvious: “No SNI provided; please fix your client.”. Ah, I need to set SNI
explicitly. After invoking SSL_set_tlsext_host_name, the certificate chain becomes correct (The new code can be downloaded here).
Summary: I am not an SSL/TLS
expert, and OpenSSL
project is complex and daunting. But with some basic SSL/TLS
knowledge and the help of debugger, I can find the root cause of issues independently. Don’t give up, digest code bit by bit, finally you will win!