OpenSSL error - unable to get local issuer certificate
Asked Answered
S

2

14

I have a simple chain setup and can successfully verify in this case:

$ openssl version
OpenSSL 1.0.2m  2 Nov 2017
$ openssl verify -CAfile chain.pem cert.pem
cert.pem: OK

However I get errors in these cases:

$ openssl verify -CAfile ca-cert.pem cert.pem
cert.pem: C = US...
error 2 at 1 depth lookup:unable to get issuer certificate

Specifically the unable to get issuer certificate.

Also get it here:

$ openssl verify chain.pem
chain.pem: C = US...
error 20 at 0 depth lookup:unable to get local issuer certificate

$ openssl verify cert.pem
cert.pem: C...
error 20 at 0 depth lookup:unable to get local issuer certificate

Finally, I get it in Node.js when I pass the keys to an HTTPS server:

events.js:193
      throw er; // Unhandled 'error' event
      ^

Error: unable to get local issuer certificate
    at TLSSocket.onConnectSecure (_tls_wrap.js:1036:34)
    at emitNone (events.js:115:13)
    at TLSSocket.emit (events.js:218:7)
    at TLSSocket._finishInit (_tls_wrap.js:637:8)

I tried passing it with { key, cert, ca }, but still same error.

Wondering how to go about debugging this or what the fix is to get an HTTPS server running.

If I use a pfx file I get the following:

events.js:193
      throw er; // Unhandled 'error' event
      ^

Error: self signed certificate in certificate chain
    at TLSSocket.onConnectSecure (_tls_wrap.js:1036:34)
    at emitNone (events.js:115:13)
    at TLSSocket.emit (events.js:218:7)
    at TLSSocket._finishInit (_tls_wrap.js:637:8)

If I leave only the cert.pem in the cert file, and make the ca attribute be the ca-cert.pem, it gives:

Error: unable to verify the first certificate
    at TLSSocket.<anonymous> (_tls_wrap.js:1108:38)
    at emitNone (events.js:105:13)
    at TLSSocket.emit (events.js:207:7)
    at TLSSocket._finishInit (_tls_wrap.js:638:8)
    at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:468:38)

Not sure what to do.

Here they say:

OpenSSL is unable to find a local certificate for the issuer (or the issuer of the first certificate in the chain received from the web server during the TLS handshake) with which to verify the signature(s).

Not sure what that means.

This error means the certificate path or chain is broken and you are missing certificate files.

- https://wiki.zimbra.com/wiki/Fix_depth_lookup:unable_to_get_issuer_certificate

Update

Slightly more help:

This problem is usually indicated by log messages saying something like "unable to get local issuer certificate" or "self signed certificate". When a certificate is verified its root CA must be "trusted" by OpenSSL this typically means that the CA certificate must be placed in a directory or file and the relevant program configured to read it. The OpenSSL program 'verify' behaves in a similar way and issues similar error messages: check the verify(1) program manual page for more information.

But still doesn't help very much.

Looks like Node.js is using a 1.0.2l instead of 1.0.2m but doesn't seem like a big deal.

$ node -pe process.versions | grep openssl
  openssl: '1.0.2l'

Update 2

Weird, I get this when I make a request from Node.js:

Uncaught Error: unable to verify the first certificate
      at TLSSocket.onConnectSecure (_tls_wrap.js:1036:34)
      at TLSSocket._finishInit (_tls_wrap.js:637:8)

But when I go to the browser, I don't see the "Proceed with caution" page, and can successfully log a request in Node.js. Maybe that helps somewhat. Please help :D

Saimon answered 1/12, 2017 at 2:25 Comment(8)
Just to be clear: there is an intermediate CA between cert.pem and ca-cert.pem? And this intermediate CA is in chain.cert alongside ca-cert.pem?Adlai
There is a toplevel self-signed cert above ca-cert.pem, so yeah.Saimon
github.com/openssl/openssl/blob/OpenSSL_1_0_2-stable/crypto/…Saimon
re: "I tried passing it with { key, cert, ca }, but still ...", try replacing cert and ca with a single argument that is the content of your chain.pem (i.e, a concatenation of the content of cert.pem followed by ca-cert.pem). This assumes that the server is using the root CA that is above ca-cert.pem as it's trusted CA.Adlai
Updated the question. Weird, I am now able to get it to work when visiting from the browser, but not from Node.js.Saimon
Related #47461000Saimon
gist.github.com/lancejpollard/919f4c8d09bbede6e8d6dae47506a6cdSaimon
Hello, i had the same issue with intermediate ca and https module and I have resolved any issue related with that.I can help you but I need you to add more code not just { key, cert, ca }Bessette
A
23

(This answer extracted from X509_verify_cert at crypto/x509/x509_vfy.c:204, in openssl-1.0.2m)

The OpenSSL verify application verifies a certificate in the following way: It builds the certificate chain starting with the target certificate, and tracing the issuer chain, searching any untrusted certificates supplied along with the target cert first. Upon failing to find an untrusted issuer cert, OpenSSL switches to the trusted certificate store and continues building the chain. This process stops when

  1. an issuer is not found in the trusted store.
  2. a self-signed certificate is encountered.
  3. the max-verify depth is encountered.

At this point we have a chain that may end prematurely (if we failed to find an issuer, or if we exceeded the verify depth).

OpenSSL then scans over each trusted certificate on the chain looking for SSLv3 extensions that specify the purpose of the trusted certificate. If the trusted certificate has the right "trust" attributes for the "purpose" of the verification operation (or has the anyExtendedKeyUsage attribute) the chain is trusted. (Forgive the hand-wave on trust attributes, that part of the code was difficult to read.)

So lets test it out. First, let's repro the OP's error cases:

#
echo "Making Root CA..."
openssl req -newkey rsa:4096 -nodes -keyout ca-key.pem -sha384 -x509 -days 365 -out ca-crt.pem -subj /C=XX/ST=YY/O=RootCA

echo "Making Intermediate CA..."
openssl req -newkey rsa:3072 -nodes -keyout int-key.pem -new -sha384 -out int-csr.pem -subj /C=XX/ST=YY/O=IntermediateCA
openssl x509 -req -days 360 -in int-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out int-crt.pem

echo "Making User Cert..."
openssl req -newkey rsa:2048 -nodes -keyout usr-key.pem -new -sha256 -out usr-csr.pem -subj /C=XX/ST=YY/O=LockCmpXchg8b
openssl x509 -req -days 360 -in usr-csr.pem -CA int-crt.pem -CAkey int-key.pem -CAcreateserial -out usr-crt.pem

echo ""
echo "Making Chain..."
cat ca-crt.pem int-crt.pem > chain.pem

echo ""
echo "Verfying UserCert via RootCA..."
openssl verify -CAfile ca-crt.pem usr-crt.pem

echo ""
echo "Verfying UserCert via IntermediateCA..."
openssl verify -CAfile int-crt.pem usr-crt.pem

echo ""
echo "Verfying UserCert via chain..."
openssl verify -CAfile chain.pem usr-crt.pem

yields

[... Skipping OpenSSL KeyGen / CertGen verbosity ...]
Making Chain...

Verfying UserCert via RootCA...
usr-crt.pem: C = XX, ST = YY, O = LockCmpXchg8b
error 20 at 0 depth lookup:unable to get local issuer certificate

Verfying UserCert via IntermediateCA...
usr-crt.pem: C = XX, ST = YY, O = IntermediateCA
error 2 at 1 depth lookup:unable to get issuer certificate

Verfying UserCert via chain...
usr-crt.pem: OK

Now, lets use the -addtrust option of openssl x509 to make sure we have one of the acceptable trust attributes on the intermediate CA (call this one IntermediateCAWithTrust; we'll use it to sign AnotherUserCert.):

echo ""
echo "Alternate Intermedate CA (using -addtrust anyExtendedKeyUsage)"
echo ""

echo "Making IntermediateCAWithTrust..."
openssl req -newkey rsa:3072 -nodes -keyout int-key2.pem -new -sha384 -out int-csr2.pem -subj /C=XX/ST=YY/O=IntermediateCAWithTrust
openssl x509 -req -days 360 -in int-csr2.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out int-crt2.pem -addtrust anyExtendedKeyUsage

echo "Making AnotherUser Cert..."
openssl req -newkey rsa:2048 -nodes -keyout usr-key2.pem -new -sha256 -out usr-csr2.pem -subj /C=XX/ST=YY/O=LockCmpXchg8b_2
openssl x509 -req -days 360 -in usr-csr2.pem -CA int-crt2.pem -CAkey int-key2.pem -CAcreateserial -out usr-crt2.pem

echo ""
echo "Verfying AnotherUserCert via IntermediateCAWithTrust..."
openssl verify -CAfile int-crt2.pem usr-crt2.pem

This yields

Alternate Intermedate CA (using -addtrust anyExtendedKeyUsage)

Making IntermediateCAWithTrust...
[... Snip more OpenSSL generation output ...]
Making AnotherUser Cert...
[... Snip more OpenSSL generation output ...]

Verfying AnotherUserCert via IntermediateCAWithTrust...
usr-crt2.pem: OK

Hey look! we just successfully verified AnotherUserCert via the IntermediateCAWithTrust, even though we didn't supply the whole chain. The key to this difference is that any one of the trusted certificates in the chain had an appropriate trust attribute for the verify operation.

Looking a little closer (via openssl x509 -in ca-crt.pem -noout -text), our CA certificate has

        X509v3 Basic Constraints:
            CA:TRUE

which I would imagine OpenSSL treats as a general "may verify for any purpose" extension. The new IntermediateCAWithTrust does not have X509v3 Basic Constraints, but instead has

Trusted Uses:
  Any Extended Key Usage
No Rejected Uses.

For more info in the -addtrust option, and the types of trust attributes that can be added, see https://www.openssl.org/docs/manmaster/man1/x509.html#TRUST_SETTINGS

Near the bottom of that page is a concise summary of the preceding discussion:

The basicConstraints extension CA flag is used to determine whether the certificate can be used as a CA. If the CA flag is true then it is a CA, if the CA flag is false then it is not a CA. All CAs should have the CA flag set to true.

If the basicConstraints extension is absent then the certificate is considered to be a "possible CA" other extensions are checked according to the intended use of the certificate. A warning is given in this case because the certificate should really not be regarded as a CA: however it is allowed to be a CA to work around some broken software.

So, in short, make sure your intermediate CAs are properly CAs (in their X509v3 Basic Constraints). This seems an excellent tutorial (and it explicitly generates the intermediate CA as a CA): https://jamielinux.com/docs/openssl-certificate-authority/create-the-root-pair.html

As a backup plan, you can always supply the whole chain, or you can make your intermediate CAs with the -addtrust hack.

Adlai answered 1/12, 2017 at 6:25 Comment(7)
In your example, there seem to be a few things: I don't think you want -name ca_y and -extensions ext_y for your machine cert, lest it become a CA. Second, your server loads the intermediate CA and it's cert. But the server's cert will be checked against the client's trusted CA certs. TLS doesn't transfer the whole chain automatically. The client's trusted CAs should include x or x and y. If the client only has x as its trusted CA, then the server must send both 'y and machine' (which is typically done by concatenating the PEMs into a single file, as the server 'cert').Adlai
If you don't control the client's trusted CA list, then you will have to have a well-known CA produce your server's certificate (e.g., VeriSign or Comodo) because most clients ship with the CA certificates for the well-known CAs already in the 'trusted CA' list.Adlai
I have tried to set node's ca property, I assume that would mean I control the client's trusted CA list. That's what gives me this error. nodejs.org/api/tls.html#tls_tls_createsecurecontext_optionsSaimon
Ah I see what you mean, I need to pass x with the client request. WOOHOO IT WORKS :DDDDDDD. Thank you so much :D :) :)Saimon
-1 This part cat ca-crt.pem int-crt.pem > chain.pem is REALLY BAD ADVICE and will open you up to vulnerabilities, as it implicitly trusts the intermediate cert. mail.python.org/pipermail/cryptography-dev/2016-August/…Adenoidal
@JohannesPille: That isn't advice. That is reproducing the test setup the OP was using to try to diagnose a problem so that we can show the difference. Do you see the part above where it says "Let's repro the OP's error cases"?Adlai
Thanks for that great answer! However, when I run that first script, strangely the verification via the chain doesn't work for me. I checked the certificates and it turned out that the root cert was a proper X509 v3 certificate, but for some reason the intermediate certificate was a X509 v1 non-CA certificate. I wonder why this worked for you and what can I do to get a proper certificate for the intermediate CA.Nicaea
T
-5

https://letsencrypt.org/ is really easy to use and free. Also, run node without SSL on a local HTTP port and use NGINX as a HTTPS proxy.

sudo apt-get install certbot nginx

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;

    ssl on;
    ssl_certificate /etc/letsencrypt/live/host.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/host.com/privkey.pem;

    access_log /var/log/nginx/host.access.log;
    error_log  /var/log/nginx/host.error.log;

    server_name _;

    gzip on;
    gzip_proxied any;
    gzip_types text/css text/javascript text/xml text/plain application/javascript application/x-javascript application/json;

    location / {
        include             /etc/nginx/proxy_params;
        proxy_pass          http://localhost:8080;
        proxy_read_timeout  90s;
        proxy_redirect      http://localhost:8080 https://www.host.com;
    }
}
Thanatopsis answered 10/12, 2017 at 1:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.