show entire certificate chain for a local certificate file - ssl

I have a certificate (for example this one) saved in a local file. Using openssl from the command line, how can I display the entire chain from this certificate to a root CA? I tried:
openssl verify -verbose -purpose sslserver -CApath /etc/ssl/certs InCommonServerCA.txt
and got this confusing output that only seems to show the leaf certificate:
InCommonServerCA.txt: C = US, O = Internet2, OU = InCommon, CN = InCommon Server CA
error 26 at 0 depth lookup:unsupported certificate purpose
OK
Any ideas?

For local certificates you can see the subject and direct issuer using:
openssl x509 -noout -subject -issuer -in test.crt
subject= /C=US/ST=Utah/L=SLC/O=My Organization/CN=my.server.com
issuer= /C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
But that doesn't indicate if the certificate includes any intermediate certificates or the full chain of trust. The verify command you listed will fail if your system cannot validate the chain (example: you are missing an intermediate certificate or the root is not trusted), showing an error message like:
error 20 at 0 depth lookup:unable to get local issuer certificate
If you want to verify each entry in the file, you can use this script to show the chain of trust for a local certificate:
~ % ssl_chain.sh google.crt
0: subject= /C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com
issuer= /C=US/O=Google Inc/CN=Google Internet Authority G2
1: subject= /C=US/O=Google Inc/CN=Google Internet Authority G2
issuer= /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
2: subject= /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
issuer= /C=US/O=Equifax/OU=Equifax Secure Certificate Authority
google.crt: OK

If you want to verify the chain and purpose, your openssl command is correct. The "OK" indicates the chain verifies. The error indicates there is an issue with that certificate being used for an sslserver purpose. It looks like your certificate is a CA cert, not a leaf cert.
What kind of chain info are you trying to display? You could look at the subject and issuer fields to show chaining. The verify command you used above proves that the one cert signed the other cert.

Related

Openssl: error 20 at 0 depth lookup: unable to get local issuer certificate

I have 3 certificates rootca.pem, intermediateca.pem and server.pem
Openssl verify intermediateca by root is fine
openssl verify -verbose -CAfile rootca.pem intermediateca.pem
intermediateca.pem: OK
Server certificate, signed by intermediate - verification failed
openssl verify -verbose -CAfile rootca.pem -untrusted intermediateca.pem server.pem
CN = 2ip.ru
error 20 at 0 depth lookup: unable to get local issuer certificate
error server.pem: verification failed
I check hash subject-issuer of rootca intermediateca and intermediateca server. hash correct
I paste my certificate chain here
The Authority Key Identifier (AKI) is messed up in the certificates, which causes it to fail to build the trust path. Both the leaf certificate and the intermediate certificate have the AKI point to the root certificate:
# leaf
Issuer: C = RU, O = JSC Sberbank-AST, CN = int_ca
AKI: keyid:6C:C5:5B:22:4B:2D:CA:EC:C1:15:03:F6:5D:AD:C4:E8:4C:1D:06:89
# intermediate
Issuer: DC = ru, DC = sberbank-ast, CN = sberbank-ast-SUN-CA
AKI: keyid:6C:C5:5B:22:4B:2D:CA:EC:C1:15:03:F6:5D:AD:C4:E8:4C:1D:06:89
As can be seen, both leaf certificate and intermediate certificate wrongly claim to be issued by the same CA based on the Authority Key Identifier, while they correctly claim to be issued by different CA using the Issuer field.
But not only the Issuer field must match the Subject field of the issuer, the Authority Key Identifier must match the Subject Key Identifier of the issuer. While this is true for the relation between intermediate CA and root CA it is not true for the relation between leaf certificate and intermediate CA - and thus verify fails here.

Another situation where I get X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY

As a followup to this original question:
Unable to get certificate locally
I did solve the original problem as jww answered.
And I have now followed the same steps of importing the certificate chain for our company site, from "comodo". I simply added them to the file which I originally used with the "google.com" root certs.
Now, although this still works fine with "google", when I connect to our company website, I still get the error code 20 on the SSL_get_verify_result() call.
Is this a result of our using a "wildcard" certificate? i.e.: *.domain.com.
The version of OpenSSL I am currently using is 1.0.1g.
I don't see any other differences from my perspective.
Thanks for any advice.
----- Updated ------
First, let me comment that I am not mentioning our domain, and not posting too much material from the OpenSSL command, as I am not familiar enough with what should be kept confidential.
What I did was combine the base64 encoded certificates into one big file, as the previous post instructed. And I obtained them via the browser "export" utility in the same manner for both. That means the certificates that we use, as well as the google certificates from my previous post are all concatenated. Specifically it looks like this now:
Our Company Cert
-----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----
and it is signed by these guys - ComodoRSA
-----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----
and that is signed at the root here - ComodoRoot
-----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----
and this is the GOOGLE G3 who signed the "www.google.com"
-----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----
and the GOOGLE G3 is signed by this one - globalSign
-----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----
Then the code segment I use to test looks like this:
strcpy(host,"our.domain.com");
// strcpy(host,"www.google.com");
/* Build our SSL context*/
ctx = initialize_ctx(KEYFILE,NULL);
/* Connect the TCP socket*/
sock = tcp_connect(host,port);
And then later...
result = SSL_get_verify_result(ssl);
switch(result) {
case X509_V_ERR_CERT_HAS_EXPIRED : break;
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN : break;
case X509_V_OK : break;
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT : break;
default :puts("Certificate doesn't verify");
}
Simply put, this same code, using this same CRT file, does not give me a "20" error when I use the www.google.com" host, but does give me an error "20" when I use our server. The extent of the test involves changing that commented out name of the host.
Connections to the HTTPS server with commercial clients (Chrome, IE, FF...) have no errors.
As for the comment that recommended the command, I get the following (hopefully I pasted the necessary information):
For Google:
depth=2 OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign verify return:1
depth=1 C = US, O = Google Trust Services, CN = Google Internet Authority G3
verify return:1
depth=0 C = US, ST = California, L = Mountain View, O = Google LLC, CN = www.google.com
verify return:1
read:errno=0
---- other stuff ----
Certificate chain
0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=www.google.com
i:/C=US/O=Google Trust Services/CN=Google Internet Authority G3
1 s:/C=US/O=Google Trust Services/CN=Google Internet Authority G3
i:/OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign
---
And for our domain, I am getting the following (company specifics hidden):
depth=2 C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Certification Authority
verify error:num=20:unable to get local issuer certificate
verify return:0
read:errno=10054
---- other stuff ---
Certificate chain
0 s:/C=US/postalCode=00000/ST=IL/L=city/street=main/O=company./OU=PremiumSSL Wildcard/CN=*.domain.com
i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Organization Validation Secure Server CA
1 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Organization Validation Secure Server CA
i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
2 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
----
Now, the chain is that our certificate is signed by "ComodoRSA", and that it is signed by "ComodoRoot".
However, as I originally indicated, our certificate is a "wildcard" certificate, where the google certificate isn't.
So that was my question: Is there a problem using wild card certificates with the version of openSSL 1.0.1g?
--- EDIT2 ----
I am adding more content to the post, so I can include an image from the browser.
Our certificate is on a live web site, and is not self signed.
I am checking the common name in a portion of the code not shown. In this post I am only hoping for advice on this error.
I found a site that uses the same chain we use: DrudgeReport.com
I simply extracted the certificates with the browser, and saved them into a file. This was identical to the steps I used form the google.com site. (View certificate and copy to file)
The result from Drudge is error 19 which is "self signed", not error 20 which was my error. The root level (comodo secure) is the same when I copy to file from either site (as I would expect).
As I learn this from your comments and the link, I see the next intermediate question is: how do I know what certificates are being sent by the server, and which were in the store? Then I would know which to import. What, if any, is the indicator from the openssl command output you provided?
The educational material on openssl and this is rather difficult to find. Just a lot of doc, which to a novice such as myself, is rather circular in it's definitions.
Thanks for your feedback so far.
So that was my question: Is there a problem using wild card certificates with the version of openSSL 1.0.1g?
It is not a problem of wildcard certificates. In fact, your code does not even check the subject of the certificate at all (i.e. your code is insecure), it mostly checks the certificate chain, expiration and purpose of the certificate. And the error message from openssl s_client clearly points out the problem:
verify error:num=20:unable to get local issuer certificate
Thus, the problem is not the certificate subject but that it cannot find a local trust anchor. Looking at the certificate chain provided by your server gives the following certificate chain:
[1] CN=*.domain.com, issued by [2]
[2] CN=COMODO RSA Organization Validation Secure Server CA, issued by [3]
[3] CN=COMODO RSA Certification Authority, issued by [ROOT]
The expected [ROOT] is "CN=AddTrust External CA Root" - only this CA is not in your list of trusted root CA.
While you give not the detailed names of the certificates you have in your local trust store my guess is that the CA you call "ComodoRoot" is similar to what I have as "[3] CN=COMODO RSA Certification Authority" in the list. Only, in your trust store it is likely the self-signed version of the certificate while in the certificate chain provided by the server it is a certificate issued by "[ROOT] CN=AddTrust External CA Root". Both certificates have the same public and private key which means that the signatures in certificate chain can be successfully validated with both.
But, the old (and long unsupported version) of OpenSSL you are using can not properly deal with this kind of situation. It will follow the certificate chain send by the server and then expect the last certificate in the chain signed by on of the certificates in your trust store. If this fails it will not check if a shorter chain might be validated successfully instead.
This means it will succeed if you either have "CN=AddTrust External CA Root" in your trust store or if the server sends a short chain which ends with "[2] CN=COMODO RSA Organization Validation Secure Server CA" since then it will find the issuer for this (your "ComodoRoot", i.e. "CN=COMODO RSA Certification Authority") in your trust store.
For a more detailed explanation of this problem see this answer at stackoverflow.com or this article. Note that there is no way to fix this in code with OpenSSL 1.0.1 - you need to either add the missing certificate to your trust store or make changes to the certificate chain send by the server.

Exchange SSL certificate via CLI in Plesk

I'm generating SSL certificates with Let's Encrypt. It works fine, I have the CSR, the key and the certificate chain including the intermediate certificate for those browsers which don't know Let's Encrypt Certificate Authority yet. When creating a new certificate in Plesk (12), I copy-and-paste the given data, click on apply, apply the created certificate to the domain and it works as expected. Testing with openssl says
$ openssl s_client -crlf -connect mydomain.com:443 -servername mydomain.com
<snip>
Certificate chain
0 s:/CN=www.mydomain.com
i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
i:/O=Digital Signature Trust Co./CN=DST Root CA X3
2 s:/O=Digital Signature Trust Co./CN=DST Root CA X3
i:/O=Digital Signature Trust Co./CN=DST Root CA X3
</snip>
Now, three month later, I have to re-issue those certificates. As I want to automate it, I want to use a CLI solution, like Plesk offers:
$ plesk bin certificate \
-u mycertificatename \
-domain mydomain.com \
-cert-file mynewcert.crt \
-key-file mykey.key
The problem: The certificate gets updated and is shown as expected in Plesk, but the intermediate certificate doesn't show up when testing with openssl:
Certificate chain
0 s:/CN=www.mydomain.com
i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
Any ideas? Did I forget to restart any service? Or any (un)documented feature?
Try to use option
-cacert-file <path> CA certificate file
with path to CA file which containts all intermediate certificates

openssl verify not working with GeoTrust Certificate

I have a newly purchased GeoTrust domain certificate and a matching CA file and would like to verify with openssl.
openssl verify -verbose -purpose any
-CAfile /full/path/sub.domain.com-geotrust.crt /full/path/sub.domain.com.crt
From this page: https://secure.marumoto.us/motowiki/tiki-index.php?page=Verify+a+Certificate+Chain
The issuer of each certificate in the chain should match the subject of the next certificate in the chain. For example the issuer of myserver.mydomain.com.cert should match the subject of myintermediate_ca.cert, and the issuer of myintermediate_ca.cert should match the subject of myroot_ca.cert. You can use the following command to view a certificate in .pem or base64 format.
From
openssl x509 -text -in sub.domain.crt and
openssl x509 -text -in sub.domain-geotrust.crt (CA root file)
sub.domain.com
Subject: OU=GT44865949,
OU=See www.geotrust.com/resources/cps (c)15,
OU=Domain Control Validated - QuickSSL(R),
CN=sub.domain.com
Issuer:
commonName = GeoTrust DV SSL CA - G4
organizationalUnitName = Domain Validated SSL
organizationName = GeoTrust Inc.
countryName = US
Intermediate:
Subject:
commonName = GeoTrust DV SSL CA - G4
organizationalUnitName = Domain Validated SSL
organizationName = GeoTrust Inc.
countryName = US
Issuer:
commonName = GeoTrust Global CA
organizationName = GeoTrust Inc.
countryName = US
Root:
Subject:
commonName = GeoTrust Global CA
organizationName = GeoTrust Inc.
countryName = US
Issuer:
organizationalUnitName = Equifax Secure Certificate Authority
organizationName = Equifax
countryName = US
It appears my Issuer and Subject fields are matching up properly but I am getting the following error with openssl:
error 20 at 0 depth lookup:unable to get local issuer certificate
/full/path/sub.domain.com.crt: /OU=GT44865949/OU=See www.geotrust.com/resources/cps (c)15/OU=Domain Control Validated - QuickSSL(R)/CN=sub.domain.com
error 20 at 0 depth lookup:unable to get local issuer certificate
I'd like to use this and a similar GeoTrust certificate at a different subdomain to do two-way SSL authentication at a restful web url but the certs won't verify with 'openssl verify' against the CA files issued with them. Any suggestions much appreciated.
As per discussion, it is a matter of allowing openssl to see the entire validation chain: with both GeoTrust CA intermediates and the root.
openssl should be run with CADir parameter containing all the 3 CAs in PEM format. The actual root for GeoTrust can either be extracted from your favourite browser, or here: filedropper.com/geotrustglobalca.
When you configure your actual server, just make sure that you send the intermediates on the Server Hello, some sites don't do that and break clients that do not have the intermediates cached.
Following on from RomanK's answer, you can get the GeoTrust Global CA from their root certificate store. They have a number of primary/universal/global certificates listed there, so make sure to get the right one for your intermediate certificate.
As per the chain display, the root certificate is not self signed . Its shows its issued by Equifax. Openssl will continue giving the error "local issuer certificate not found" till it gets a self signed root certificate. When it comes across a root self signed then it's able to verify the certificate chain as complete.

Get complete certificate chain including the root certificate

How do I get complete certificate chain for a server? Though some claim one should be able to do just that with openssl s_client -showcerts, it turns not always to be the case.
echo | openssl s_client -CApath /etc/ssl/certs -connect www.ssllabs.com:443 \
-showcerts | grep -B2 BEGIN
depth=3 C = SE, O = AddTrust AB, OU = AddTrust External TTP Network, CN = AddTrust External CA Root
verify return:1
depth=2 C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Certification Authority
verify return:1
depth=1 C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Domain Validation Secure Server CA
verify return:1
depth=0 OU = Domain Control Validated, OU = PositiveSSL, CN = www.ssllabs.com
verify return:1
0 s:/OU=Domain Control Validated/OU=PositiveSSL/CN=www.ssllabs.com
i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
-----BEGIN CERTIFICATE-----
--
1 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
-----BEGIN CERTIFICATE-----
--
2 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
-----BEGIN CERTIFICATE-----
DONE
Here we have three certificates our of four. All except of the AddTrust External CA Root certificate. (Possibly because it is not included into the certificate bundle. And not like this is required. And yes, I can find the missing one at /etc/ssl/certs)
How do I get all certificates for a server in a fully automatic fashion?
Meta: I tried to answer this in superuser but you deleted it. Fortunately when I found this copy most of my work was still sitting in a scratch notepad I hadn't closed, otherwise I wouldn't have been willing to do the research work twice.
s_client -showcerts shows the certs sent by the server; according to the RFCs, this should be a valid chain in upward order except that the root MAY (in RFC2119 definition i.e. allowed but not particularly recommended) be omitted. However, not all servers are configured correctly, and some may send extra, missing, and/or out-of-order certs. Also depending on the CA used there may be more than one valid chain but the server can only send one. openssl currently will use only the chain sent, but this will change soon in 1.0.2, and other reliers already sometimes find a different chain than the one sent.
openssl: if the received chain is complete up to and maybe including a root which is in the truststore used (whose default location depends on system or build, and in any case can always be overridden) then openssl client will validate it as okay -- unless it is expired, or revoked and that info is available which usually it isn't. In that case you can write a client program that connects after setting a cert-verify callback function that outputs the full certs as processed by the validation loop, or other info from them you want, as opposed to s_client which uses a callback that logs (only) the subject name in the depth=n lines, which you can see in your example includes all 4 certs in the chain here. openssl is opensource, so a client program that does things mostly like s_client could be a modified copy of s_client (in this case specifically s_cb.c).
Java can also do this and is a good bit shorter to write, but requires Java be installed. If the received chain validates against an anchor in the truststore used (which defaults to a set of public roots but can be modified or overridden, and can have non-root anchors) you similarly can write a program (maybe 20 lines) to connect using a HandshakeCompletedListener which dumps the info from event.getPeerCertificates(). However if the chain doesn't validate, Java aborts the handshake with an exception and you get no certs at all, unlike the openssl case where you might get partial information before the error occurs -- plus openssl's checking, at least by default, isn't quite as strict anyway.
UPDATE: for completeness, in Java 7+, commandline keytool -printcert -sslserver displays the chain sent, in a rather cluttered format.
Among the browsers I can easily check, Firefox and Chrome on Windows (at least) can write out the chain they found and validated. ISTR but can't easily retest the Firefox error/exception dialog can also do this for a chain that fails to validate and may be incomplete. These are not automatic as-is, but I've seen ads for numerous "simulate GUI user input" tools that apparently could drive them as desired.
You get the chain including the builtin trusted root certificate inside the verify_callback (see SSL_CTX_set_verify. With a small Perl program you can dump the chain like this:
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket::SSL;
IO::Socket::SSL->new(
PeerHost => 'www.google.com:443',
SSL_verify_callback => sub {
my $cert = $_[4];
my $subject = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_subject_name($cert));
my $issuer = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_issuer_name($cert));
print "# $subject (issuer=$issuer)\n";
print Net::SSLeay::PEM_get_string_X509($cert),"\n";
return 1;
}
) or die $SSL_ERROR||$!;