13

I've created a self-signed certificate for foo.localhost using a Let's Encrypt recommendation using this Makefile:

include ../.env

configuration = csr.cnf
certificate = self-signed.crt
key = self-signed.key

.PHONY: all
all: $(certificate)

$(certificate): $(configuration)
    openssl req -x509 -out $@ -keyout $(key) -newkey rsa:2048 -nodes -sha256 -subj '/CN=$(HOSTNAME)' -extensions EXT -config $(configuration)

$(configuration):
    printf "[dn]\nCN=$(HOSTNAME)\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:$(HOSTNAME)\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth" > $@

.PHONY: clean
clean:
    $(RM) $(configuration)

I've then assigned that to a web server. I've verified that the server returns the relevant certificate:

$ openssl s_client -showcerts -connect foo.localhost:8443 < /dev/null
CONNECTED(00000003)
depth=0 CN = foo.localhost
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = foo.localhost
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
 0 s:/CN=foo.localhost
   i:/CN=foo.localhost
-----BEGIN CERTIFICATE-----
[…]
-----END CERTIFICATE-----
---
Server certificate
subject=/CN=foo.localhost
issuer=/CN=foo.localhost
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 1330 bytes and written 269 bytes
Verification error: unable to verify the first certificate
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES128-GCM-SHA256
    Session-ID: […]
    Session-ID-ctx: 
    Master-Key: […]
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket:
    […]

    Start Time: 1529622990
    Timeout   : 7200 (sec)
    Verify return code: 21 (unable to verify the first certificate)
    Extended master secret: no
---
DONE

How do I make cURL trust it without modifying anything in /etc? --cacert does not work, presumably because there is no CA:

$ curl --cacert tls/foo.localhost.crt 'https://foo.localhost:8443/'
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

The goal is to enable HTTPS during development:

  • I can't have a completely production-like certificate without a lot of work to enable DNS verification in all development environments. Therefore I have to use a self-signed certificate.
  • I still obviously want to make my development environment as similar as possible to production, so I can't simply ignore any and all certificate issues. curl -k is like catch (Exception e) {} in this case - nothing at all like a browser talking to a web server.

In other words, when running curl [something] https://project.local/api/foo I want to be confident that

  1. if TLS is configured properly except for having a self-signed certificate the command will succeed and
  2. if I have any issues with my TLS configuration except for having a self-signed certificate the command will fail.

Using HTTP or --insecure fails the second criterion.

  • There seems to be a solution there: stackoverflow.com/a/21262787/6368697 – Patrick Mevzek Jun 22 '18 at 2:20
  • @PatrickMevzek No, "without modifying anything in /etc" is not satisfied by that solution. – l0b0 Jun 22 '18 at 2:58
  • Although there's no real CA, a selfsigned cert is effectively treated as its own CA for validation purposes. Try openssl x509 <file to make sure it's in the right format and openssl s_client ... -CAfile file to see if that validates. (BTW -showcerts only applies to chain certs from the server and is meaningless when there are no chain certs.) Also, curl doesn't always use OpenSSL and if not it doesn't always accept exactly the same formats; check curl -V (uppercase V). – dave_thompson_085 Jun 22 '18 at 8:44
  • What do you mean by "make cURL trust it"? In general there is no notion of "trust" for self-signed certificates since anyone can make them. What is that you want? Only to accept that one certificate's fingerprint? Only a certain certificate including the extensions? Something else? – V13 Oct 17 '18 at 22:49
  • I'm having a similar issue. I get the certificate chain of a self-signed CA of our corporate proxy using the openssl s_client -showcerts answer, but curl -v --cacert cacert.pem URL won't add the self-signed CA as an explicit whitelisting of trust with CERT_TRUST_REVOCATION_STATUS_UNKNOWN. – Josh Peak Oct 24 '18 at 23:13
4

Following these steps should solve your issue:

  1. Download and save the self-signed certificate: echo quit | openssl s_client -showcerts -servername "${API_HOST}" -connect "${API_HOST}":443 > cacert.pem
  2. Tell the curl client about it: curl --cacert cacert.pem --location --silent https://${API_HOST}

Also one could use wget and ignore certificates with: wget --no-check-certificate https://${API_HOST}

| improve this answer | |
  • 8
    -1 I already tried --cacert. And I'm definitely not interested in ignoring the certificate. This is terrible advice. – l0b0 Sep 11 '18 at 19:35
  • I understand your frustration. Did you try the openssl command and save the cert from the server you are connecting to? The ignore is just another option for someone else to try if they don't really need to test with HTTPS. – Robert Brisita Sep 11 '18 at 23:55
  • I already have the certificate. Please read my question. – l0b0 Sep 12 '18 at 1:39
  • That is where the problem lies. I believe you are using the certificate from the --out argument rather than following the steps in my answer. – Robert Brisita Sep 12 '18 at 16:55
  • I created the certificate, so I don't need to retrieve it. That is not what the question is about, and rather than assuming that I don't have the certificate I would suggest using comments to ask such follow-up questions. – l0b0 Sep 12 '18 at 19:27
2

Try -k:

curl -k https://yourhost/

It should "accept" self-signed certificates

| improve this answer | |
  • 8
    The question is how to trust self-signed certificates, not how to bypass certificate validation. – l0b0 Oct 17 '18 at 0:08
  • 1
    @l0b0: To make curl trust self-signed certificates. And it also says: "The goal is to enable HTTPS during development". curl -k achieves both. There is no validation in self-signed certificates, unless you are implying that you want to accept only a certain self-signed certificate, but this is not what the question says. Can you explain what is your objection? – V13 Oct 17 '18 at 0:14
  • 1
    I wrote "How do I make cURL trust it". If I asked you how to open SSH to a specific IP, would you tell me to open it to every IP? – l0b0 Oct 17 '18 at 23:37
  • What does "trust it" means in your mind? Is that a CA certificate (i.e. has the CA extensions) that you want to trust as CA? or is it a plain certificate and you want to make sure that this is the one you're receiving? Do you just want to look at the fingerprint of the key or the whole set of extensions? Do you maybe care just about the CN? – V13 Oct 17 '18 at 23:48
  • You can see from the certificate that it does not have CA extensions. It's a self-signed certificate. I want cURL to do the same validations it does for any certificate. As man curl puts it "The server connection is verified by making sure the server's certificate contains the right name and verifies successfully using the cert store." Maybe that means I need a way to create a cert store for a self-signed certificate. – l0b0 Oct 18 '18 at 0:03
0

It is not valid to have a trust chain that include a self-signed cert. If that were the case anyone could provide a (made up) valid trust chain. If a self-signed cert appears in a trust chain it must be ignored. A self-signed cert could only be valid in a local directory (controlled by the computer owner). The cert given to any server must be chained to the self-signed cert.

A general guide without most of the little details.

  • Your output of the openssl s_client command is showing two errors:

    verify error:num=20:unable to get local issuer certificate
    verify error:num=21:unable to verify the first certificate
    

    That means that the default cert store in your machine is missing a cert that validates the chain given from the web site you used. You need a directory with a self-signed cert and a cert chained to that for the web server.

Steps:

  1. You could build a new directory (anywhere), process it with the c_rehash script and tell openssl to use it to verify the certs with the option -CApath Directory. Make changes until you get rid of both errors while using the -CApath option.

  2. Generate a chained cert for the web server.

  3. Then, tell curl about the certificate directory with:

    curl --capath <dir>
    

    and all the other options needed.

That will clear both errors.

| improve this answer | |
  • When creating self-signed certificates there is no separate root certificate. I've added the certificate creation Makefile to my question to clarify this. – l0b0 Oct 17 '18 at 23:35
  • If I need to create a self-signed CA certificate to do this, that would be fine. I just thought (naively, it seems) that there would be a simple way to do this. – l0b0 Oct 18 '18 at 0:06
  • Alas, I sure hope that certificates would be so easy (at least once), sigh.... I am working on it. I'll post something probably tomorrow. Sorry for the delay. @l0b0 – Isaac Oct 18 '18 at 2:11
0

I had this issue, exact same problem and error messages, but I used GNUTLS's certtool to generate my cert rather than openssl.

My problem was that I had not made my self signed cert a CA. It was only configure for acting as a web server cert. Which is all I wanted to do with it and I wasn't going to use it as a CA to sign other certs.

But when you want to add a cert into the trust chain as the Issuer of other certs, that cert must be a CA, or it's rejected by openssl!

With certtool -i < mycert.crt, one needs to see this:

    Extensions:
            Basic Constraints (critical):
                    Certificate Authority (CA): TRUE

Try adding -addext basicConstraints=critical,CA:TRUE,pathlen:1 to your openssl command or modifying your cnf file to the same effect.

Or, use certtool, it's much easier for one-off cert generation:

certtool -p --outfile localhost.key
certtool -s --load-privkey localhost.key --outfile localhost.crt

And then answer the prompts to supply the cert's CN and so on. And say yes when asked if it's for a certificate authority!

| improve this answer | |
0

If you save off the self-signed.crt from your server, you can pass it to curl via "--cacert self-signed.crt" and curl will validate the certificate of your server using the given CA Cert.

| improve this answer | |
  • 2
    This looks the same as this answer. If it's not, can you please give some details why it's not? – l0b0 May 4 at 20:59
  • I think it's similar. Your comment on that answer is that you created the cert and that answer wasnt working. I have a very similar system. I have a web server that, if not configured, auto-creates it's own ca cert, then server cert and signs the server cert with the ca cert. So in my system, I just directly point curl at my ca cert using the --cacert option and it all works well for me. – user2679859 May 6 at 14:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.