menu

Questions & Answers

Using openssl to get the certificate from a server

I am trying to get the certificate of a remote server, which I can then use to add to my keystore and use within my Java application.

A senior dev (who is on holidays :( ) informed me I can run this:

openssl s_client -connect host.host:9999

to get a raw certificate dumped out, which I can then copy and export. I receive the following output:

depth=1 /C=NZ/ST=Test State or Province/O=Organization Name/OU=Organizational Unit Name/CN=Test CA
verify error:num=19:self signed certificate in certificate chain
verify return:0
23177:error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure:s3_pkt.c:1086:SSL alert number 40
23177:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:s23_lib.c:188:

I have also tried it with this option:

-showcerts

and this one (running on Debian mind you):

-CApath /etc/ssl/certs/

But I get the same error.

This source says I can use that CApath flag but it doesn't seem to help. I tried multiple paths to no avail.

Please let me know where I'm going wrong.

Answers(15) :

With SNI

If the remote server is using SNI (that is, sharing multiple SSL hosts on a single IP address) you will need to send the correct hostname in order to get the right certificate.

openssl s_client -showcerts -servername www.example.com -connect www.example.com:443 </dev/null

Without SNI

If the remote server is not using SNI, then you can skip -servername parameter:

openssl s_client -showcerts -connect www.example.com:443 </dev/null


To view the full details of a site's cert you can use this chain of commands as well:

$ echo | \
    openssl s_client -servername www.example.com -connect www.example.com:443 2>/dev/null | \
    openssl x509 -text
Comments:
2023-01-20 23:52:06
Hmm. I still get the same error when trying that command. I noticed my Openssl version is 'OpenSSL 0.9.8g 19 Oct 2007'. Do you have any ideas?
2023-01-20 23:52:06
Useful: echo "" | openssl s_client -connect server:port -prexit 2>/dev/null | sed -n -e '/BEGIN\ CERTIFICATE/,/END\ CERTIFICATE/ p' stackoverflow.com/a/12918442/843000
2023-01-20 23:52:06
Alternative useful script, from madboa.com: echo | openssl s_client -connect server:port 2>&1 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > cert.pem
2023-01-20 23:52:06
To make this a bit more concise, you can replace the sed with openssl x509, and read it in using a sub-shell: openssl x509 -in <(openssl s_client -connect server:port -prexit 2>/dev/null)
2023-01-20 23:52:06
Also echo | openssl s_client -connect google.com:443 2>/dev/null | openssl x509
2023-01-20 23:52:06
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -text to get human readable output as well. Also as an answer.
2023-01-20 23:52:06
If you get a write:errno=54 you may be communicating with a TLS1.2 only server using an older version of openssl (you need 1.0.1 or newer for TLS1.2). This happened to me on macOS 10.12
2023-01-20 23:52:06
And if it is a Mailserver (SMTP) using starttls you want to get the certificate from, use: openssl s_client -connect server:port -starttls smtp
2023-01-20 23:52:06
Since OpenSSL 1.1.1, SNI is enabled by default: "If -servername is not provided, the TLS SNI extension will be populated with the name given to -connect if it follows a DNS name format."
2023-01-20 23:52:06
As per @Alastair McCormack comment, from version 1.1.1 and up SNI is enabled by default. You need to pass the -noservername flag to disable it
2023-01-20 23:52:06
@mbrownnyc -prexit will print the certificate twice in the screen. Why is this flag needed?
2023-01-20 23:52:06
In my corporate environment these commands just hang and don't terminate. The reason is probably that we use a proxy, and commands like curl honor that, but openssl does not and gets blocked.
2023-01-20 23:52:06
In my case the -showcerts option made the difference because I need to validate the whole chain

It turns out there is more complexity here: I needed to provide many more details to get this rolling. I think its something to do with the fact that its a connection that needs client authentication, and the hankshake needed more info to continue to the stage where the certificates were dumped.

Here is my working command:

openssl s_client -connect host:port -key our_private_key.pem -showcerts \
                 -cert our_server-signed_cert.pem

Hopefully this is a nudge in the right direction for anyone who could do with some more info.

Comments:
2023-01-20 23:52:06
I am sorry, but your answer doesn't make much sense. You needed to pass the certificate to the server in order to get the certificate?
2023-01-20 23:52:06
Yep. Client authentication AFAIK.
2023-01-20 23:52:06
It turns out '-prexit' will return that data as well. E.g.; openssl s_client -connect host:port -prexit

While I agree with Ari's answer (and upvoted it :), I needed to do an extra step to get it to work with Java on Windows (where it needed to be deployed):

openssl s_client -showcerts -connect www.example.com:443 < /dev/null | openssl x509 -outform DER > derp.der

Before adding the openssl x509 -outform DER conversion, I was getting an error from keytool on Windows complaining about the certificate's format. Importing the .der file worked fine.

Comments:
2023-01-20 23:52:06
Odd. I've been using PEM certificates with keytool on Windows since Java 6 and never faced an issue.

To get the certificate of remote server you can use openssl tool and you can find it between BEGIN CERTIFICATE and END CERTIFICATE which you need to copy and paste into your certificate file (CRT).

Here is the command demonstrating it:

ex +'/BEGIN CERTIFICATE/,/END CERTIFICATE/p' <(echo | openssl s_client -showcerts -connect example.com:443) -scq > file.crt

To return all certificates from the chain, just add g (global) like:

ex +'g/BEGIN CERTIFICATE/,/END CERTIFICATE/p' <(echo | openssl s_client -showcerts -connect example.com:443) -scq

Then you can simply import your certificate file (file.crt) into your keychain and make it trusted, so Java shouldn't complain.

On OS X you can double-click on the file or drag and drop in your Keychain Access, so it'll appear in login/Certificates. Then double-click on the imported certificated and make it Always Trust for SSL.

On CentOS 5 you can append them into /etc/pki/tls/certs/ca-bundle.crt file (and run: sudo update-ca-trust force-enable), or in CentOS 6 copy them into /etc/pki/ca-trust/source/anchors/ and run sudo update-ca-trust extract.

In Ubuntu, copy them into /usr/local/share/ca-certificates and run sudo update-ca-certificates.

The easiest command line for this, which includes the PEM output to add it to the keystore, as well as a human readable output and also supports SNI, which is important if you are working with an HTTP server is:

openssl s_client -servername example.com -connect example.com:443 \
    </dev/null 2>/dev/null | openssl x509 -text

The -servername option is to enable SNI support and the openssl x509 -text prints the certificate in human readable format.

Comments:
2023-01-20 23:52:06
You may add to your -servername your subdomain, for instance ws.example.com instead of example.com (apply this to the -connect parameter too).

For the benefit of others like me who tried to follow the good advice here when accessing AWS CloudFront but failed, the trick is to add -servername domain.name...

Source: https://serverfault.com/a/780450/8972

You can get and store the server root certificate using next bash script:

CERTS=$(echo -n | openssl s_client -connect $HOST_NAME:$PORT -showcerts | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p')
echo "$CERTS" | awk -v RS="-----BEGIN CERTIFICATE-----" 'NR > 1 { printf RS $0 > "'$SERVER_ROOT_CERTIFICATE'"; close("'$SERVER_ROOT_CERTIFICATE'") }'

Just overwrite required variables.

HOST=gmail-pop.l.google.com
PORT=995

openssl s_client -servername $HOST -connect $HOST:$PORT < /dev/null 2>/dev/null | openssl x509 -outform pem

to print only the certificate chain and not the server's certificate:

# MYHOST=myhost.com
# MYPORT=443
# openssl s_client -connect ${MYHOST}:${MYPORT} -showcerts 2>/dev/null </dev/null | awk '/^.*'"${MYHOST}"'/,/-----END CERTIFICATE-----/{next;}/-----BEGIN/,/-----END CERTIFICATE-----/{print}'

to update CA trust on CentOS/RHEL 6/7 :

# update-ca-trust enable
# openssl s_client -connect ${MYHOST}:${MYPORT} -showcerts 2>/dev/null </dev/null | awk '/^.*'"${MYHOST}"'/,/-----END CERTIFICATE-----/{next;}/-----BEGIN/,/-----END CERTIFICATE-----/{print}' >/etc/pki/ca-trust/source/anchors/myca.cert
# update-ca-trust extract

on CentOS/RHEL 5:

# openssl s_client -connect ${MYHOST}:${MYPORT} -showcerts 2>/dev/null </dev/null | awk '/^.*'"${MYHOST}"'/,/-----END CERTIFICATE-----/{next;}/-----BEGIN/,/-----END CERTIFICATE-----/{print}' >>/etc/pki/tls/certs/ca-bundle.crt
Comments:
2023-01-20 23:52:07
Exactly what I needed on CentOS7. Thanks!

A one-liner to extract the certificate from a remote server in PEM format, this time using sed:

openssl s_client -connect www.google.com:443 2>/dev/null </dev/null |  sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'
Comments:
2023-01-20 23:52:07
This one is almost perfect to extract the certificate, just missing the -servername option, don't know why, but I had to use it to get the full certificate.
2023-01-20 23:52:07
-servername is required for server name indication (SNI). Web searching can expand on the rest.

If your server is an email server (MS Exchange or Zimbra) maybe you need to add the starttls and smtp flags:

openssl s_client -starttls smtp -connect HOST_EMAIL:SECURE_PORT 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > CERTIFICATE_NAME.pem

Where,

  • HOST_EMAIL is the server domain, for example, mail-server.com.

  • SECURE_PORT is the communication port, for example, 587 or 465

  • CERTIFICATE_NAME output's filename (BASE 64/PEM Format)

Start the client:

openssl s_client -showcerts stackoverflow.com:443

Quit by stopping STDIN (CTRL+D), or terminate the process (CTRL+C).

To disable input and force the client to quit after displaying the certificates:

openssl s_client -showcerts stackoverflow.com:443 < /dev/null

I also had the same challenge and next to that I discovered that openssl doesn't return the root ca. I have built an alternative for specifically for this purpose which might be useful for other developers, see here: GitHub - Certificate ripper

Usage

  • Printing to the console
crip print --url=https://stackoverflow.com/ --format=pem
  • Exporting to a p12 trustore
crip export pkcs12 --url=https://stackoverflow.com/

The pkcs12 option can be replaced for pem or der if you want a different output.

Comments:
2023-01-20 23:52:07
Pretty useful tool ! any way we can specify the target file for the export ? I'm thinking path and format. Thanks
2023-01-20 23:52:07
I was just about to fork and add that option, but I see that you already added it - thank you so much ! :)
2023-01-20 23:52:07
Yes I have added it and published it today. Thank you for mentioning this idea, it was good to have this option in the app
2023-01-20 23:52:07
One small issue I noticed, the published source code is not the latest one ^^
2023-01-20 23:52:07
I have republished it, hopefully it is now correct :) Can you retry?
2023-01-20 23:52:07
There is another crip utility in debian packages, so not to be confused.

There are already good answers. But it's worth to mention the latest curl version (>7.88.0) will do this simple for you...

curl https://example.com -w "%{certs}" -o /dev/null > cacert.pem

Add --insecure if the server uses self signed certificate. And add --head to avoid the response body.

See availability and requirements here: cURL -w certs feature

Comments:
2023-01-20 23:52:07
Just for clarity, curl 7.88 isn't actually out yet at the time of writing - due to be released Feb 2023.

I was in case where two certificates was output from aboves commands; so i get to this point to extract only the 2nd part : the server certificate

Sorry for the long command:

openssl s_client -servername token.actions.githubusercontent.com -showcerts -connect token.actions.githubusercontent.com:443 2>/dev/null </dev/null | awk '/BEGIN CERTIFICATE/ && c++,/END CERTIFICATE/'