Jushiti
Jushiti

Reputation: 165

Error "empty certificate chain" in a basic haskell app

I generated a certificate in the directory of my Haskell app:

openssl genrsa -out key.pem 2048
openssl req -new -key key.pem -out certificate.csr
openssl x509 -req -in certificate.csr -signkey key.pem -out certificate.pem

and then I ran my app:

import Network.Wai
import Network.Wai.Handler.Warp
import Servant
import Network.Wai.Handler.WarpTLS

startApp :: IO ()
startApp = do
  let port = 3345
  let tls = tlsSettings "certificate.csr" "key.pem"
  runTLS tls (setPort port defaultSettings) app

And then went to https://localhost:3345 and I got an error "empty certificate chain"

What's wrong with it? Maybe I've put my certificate somewhere such as "/opt/...."?

For now all 3 files are in the root directory of my application: key.pem, certificate.csr and certificate.pem.

UPDATE:

It's arch linux, whereas on hosting I have Ubuntu, therefore I'll need a solution for both.

This certificate is self-signed, whereas on hosting it's issued by let's encrypt.

I've changed a code a bit: "csr" to pem:

let tls = tlsSettings "certificate.pem" "key.pem"
runTLS tls (setPort port defaultSettings) app

Here's another error:

    $ curl -v https://localhost:3345
* Rebuilt URL to: https://localhost:3345/
*   Trying ::1...
* connect to ::1 port 3345 failed: Connection refused
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 3345 (#0)
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: none
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS alert, Server hello (2):
* SSL certificate problem: self signed certificate
* Closing connection 0
* TLSv1.2 (OUT), TLS alert, Client hello (1):
curl: (60) SSL certificate problem: self signed certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.

Upvotes: 0

Views: 3190

Answers (1)

Zeta
Zeta

Reputation: 105886

TL;DR: Handling certificates is hard. Use the self-signed .pem file (and the key) and add security exceptions to your browser or your operating system. The .csr file isn't something you can use. The server is fine (except for the .csr file), but you will end up with security warnings till you get your key signed by a CA (which isn't possible for local domain names).


What's wrong with it?

The server is (almost) fine. However, you want to send the .pem file. The .csr is a request for signing your certificate by a certificate authority (CA). A CA needs to get trusted by the user or another CA (that's trusted by the user). Let's have a look at your original commands:

openssl genrsa -out key.pem 2048

This will generate the private key, which will get used for the TLS handshake and other operations.

openssl req -new -key key.pem -out certificate.csr

This will generate the mentioned certificate sigining request. You would send this request to a CA, which checks your identity. For example, they would check whether the FQDN actually is yours. Otherwise, you could ask for a certificate for stackoverflow.com and use a MITM attack. This also concludes that you cannot ask a (non-local) CA for a certificate on a local name, like localhost or hostname.local.domain.name.

openssl x509 -req -in certificate.csr -signkey key.pem -out certificate.pem

This will take the original certificate request, use your own key to sign it, and generate a certificate (certificate.pem). Usually, this certificate contains a CA chain, e.g. who signed the CA's certificate, who signed the certificate of the one that signed the CA's certificate and so on, till we end up at a root certificate.

So, all you have to do is to use that certificate together with your private key. This will lead to security warnings though, since you cannot prove your own identity. That's why you need --insecure in curl and a security exception in most browsers. That aside, it will work. Note that the server doesn't know that its certificate is self-signed. It's just using two files to initiate the communication with the client.

Upvotes: 3

Related Questions