2014-12-13

Something about certificates, CA, SSL and alike

Disclaimer: this article is not about security best practices nor contains advices about how to set-up a secure and trusted communication between two peers or a correct and working PKI. Its aim is to give a general picture of a broader topic, whose surface is slightly scratched by this text.

We start with at least three actors:

  • a client,
  • a server,
  • a CA (Certification Authority).

The server wants to give its services via https. In order to be able to do so, it creates its own secret private-key and generates a CSR (Certificate Signing Request); then it sends this CSR to the chosen CA.

The CA will sign the certificate and send it back to the requester.

Now, the server is ready to deliver its services via https.

The client, on its part, needs to trust that “signature” (i.e. the signer who certified that the signee is trustworthy); in this scenario, the client needs to trust a self-signed certificate of the CA that signed the server certificate; or, if the certificate of the CA (let's call it Alfa) was signed by another CA (Beta), the client needs to trust Beta certificate… and so on. But this chain of trust can't be infinite and so, at its root, we have a self-signed certificate, which we have to trust.

Let's play the different roles. (I am going to use mainly two tools: openssl and curl)

Server

Generate a secret private key (by default protected by a password)1.

openssl genrsa -des3 -out server_private_key.pem

Issue a request for the CA:

openssl req -new -key server_private_key.pem -out server.csr

We must fill some informations; CN (Common Name), in particular, must match the FQDN name.

Send server.csr to the CA.

The CA

The CA has its own private key and certificate. Here, we have a powerful CA that is trusted by everyone, so it will self-sign the certificate.

openssl req -new -x509 -extensions v3_ca \
            -keyout CA.key -out CA.crt -days 3650

Its own self-signed certificate expires in ten years or so. This is arbitrary and of course the CA did it once at the beginning of the 10-yrs time window. Before the CA certificate expires, the CA receives the CSR from our server.

Note: not all certificates can be used to sign other certificate: only a CA certificate fits the requirements. This is, again, a matter of trust, and there's nothing magic that assures the CA is, indeed, a good CA…

This certificate, CA.crt, can be disseminated to every client who decides to trust it. Maybe it will be bundled, altogether with other certificates, in a PKCS7 file, like this:

openssl crl2pkcs7 -nocrl -certfile CA.crt -out CA.p7b

In this example there's just one certificate. Clients may have their own preferred format.

When the CA has the CSR from our server, it does (we suppose) some kind of checks needed to verify the identity of the server, its intentions and so on, and at the end it decides to sign it.

openssl x509 -req -days 350 -in server.csr \
             -CA CA.crt -CAkey CA.key \
             -set_serial 01 -out server.crt

The signed certificate will expire in 350 days (a little bit less than a year). No special reason. (The choice for the serial number is arbitary, too).

The file server.crt is sent back to the requester (the server).

Clients

In this scenario, clients who want to communicate via https with the server, needs the CA.crt.

The CA itself will disseminate this certificate, so that clients can use it to check certificates and see if they are among those they trust — thus, the client will trust them, too.

Testing 1

Once the server has its own certificate signed by the CA, it can configure its web server. In this example, we use openssl, just to check if everything is alright.

openssl s_server -accept 4433 -key server_private_key.pem \
                 -cert server.crt -www

The server is waiting connections on port 4433.

A client makes a request:

curl https://serveraddress:4433/ --cacert /path/to/CA.crt

You should see the HTML page answered by openssl. If something goes wrong, you can check the certificate of the server:

openssl s_client -connect serveraddress:4433 -showcerts

In particular, take a look at the subject line.

A client may decide to skip peer verification: then, the client trusts the server, whoever it really is. With curl command line, you achieve this by adding the -k option (long option --insecure).

Data between client and server is still crypted, anyway. But the client doesn't know if the server is who it expects or not: it can't trust its identity. In this simple scenario, we trust blindly the CA with its CA.crt. If we don't trust the CA, then there's no difference, from a security point of view, in using the -cacert /path/to/CA.crt option or replace it with -k. In the first case, we are going to trust an issuer that, for what we can know about it, issues tons of identity cards to every server, posing as the one we are interested in or not.

Chain of trust

When playing with certificates, we are playing with trust. If I have decided to install CA.crt, it's because I trust that CA, for some reason, and so I am going to trust all the certificates the CA signed.

More complex and real scenarios see a chain of trust (not to mention the possibility to revoke a certificate…)

A subject Alfa-CA has a certificate issued by an issuer, Beta-CA. This Beta-CA is a subject itself, and its certificate was issued by another issuer, Gamma-CA. And so on. At the root of this chain, there's a self-signed certificate of a root CA2.

Let's keep the previous CA with its CA.crt and CA.key (note: private keys never go around and must be kept secret and enshrined properly3), but let's pretend to be another CA, CA-B.

This CA-B won't self-sign its certificate; rather, it will make a CSR, as the server did. Thus, first create a private key:

openssl genrsa -out CA-B.key

Then

openssl req -new -key CA-B.key -extensions v3_ca \
            -out CA-B.csr

Now, CA-B sends the CA-B.csr to the root CA, which is, in our case, our powerful “master” root CA. After several, deep checks, the CA signs the CSR this way:

openssl x509 -req -days 1500 -in CA-B.csr \
             -CA CA.crt -CAkey CA.key \
             -set_serial 01 -out CA-B.crt

Now, CA-B has its own private secret key, CA-B.key, and its certificate, CA-B.crt, which is signed by CA.

The server CSR is sent to CA-B this time, and CA-B will sign it:

openssl x509 -req -days 350 -in server.csr \
             -CA CA-B.crt -CAkey CA-B.key \
             -set_serial 01 -out server.crt

The server starts listening, as before:

openssl s_server -accept 4433 \
                 -key server_private_key.pem \
                 -cert server.crt -www

And a client can try to start a communication. How?

Our root CA disseminated a bundle with all the certificates we need. As said before, it can distribute a PKCS7 file (common file extension is p7b):

openssl crl2pkcs7 -nocrl -certfile CA.crt \
                  -certfile CA-B.crt -out CA.p7b

Unluckly, the tool we are using for our tests do not understand p7b files. So we need to convert it4. Indeed, we just need to extract the content, i.e. the certificates. We can take a look at their details using

openssl pkcs7 -in CA.p7b -print_certs -text

Instead, to extract the PEM5 file needed by cURL, we just append each certificate to a file, let's call it all-CA.pem.

We obtain what we need by using openssl again, then editing the file to delete all the unwanted lines (i.e. lines containing issuers and subjects, and no more). Like this

openssl pkcs7 -in CA.p7b \
              -print_certs |
    awk '!/^subject=/&&!/^issuer=/{print}' >all-CA.pem

Testing 2

Let's test with this server which is using a certificate signed by an intermediate CA. The server is already running and “simulated” with

openssl s_server -accept 4433 \
                 -key server_private_key.pem \
                 -cert server.crt -www

Now, try these:

curl https://localhost:4433 --cacert ./CA.crt

curl https://localhost:4433 --cacert ./CA-B.crt

They will fail.

This is because we need to check the whole chain. So we need all-CA.pem, which contains such a chain, with its “crossed” subjects and issuers6. Thus

curl https://localhost:4433 --cacert ./all-CA.crt

will work as expected.

Purposes

X.509 certificates have purposes. If a certificate can't be used to, say, sign a CSR, since it hasn't this among its purposes, then something will go wrong.

You can take a look at purposes with openssl x509 -in certificate.pem -purpose. E.g. for our powerful root certificate:

openssl x509 -in CA.crt  -purpose

will output:


SSL client : Yes
SSL client CA : Yes
SSL server : Yes
SSL server CA : Yes
Netscape SSL server : Yes
Netscape SSL server CA : Yes
S/MIME signing : Yes
S/MIME signing CA : Yes
S/MIME encryption : Yes
S/MIME encryption CA : Yes
CRL signing : Yes
CRL signing CA : Yes
Any Purpose : Yes
Any Purpose CA : Yes
OCSP helper : Yes
OCSP helper CA : Yes
Time Stamp signing : No
Time Stamp signing CA : Yes

While CA-B.crt purposes are slightly different:

openssl x509 -in CA-B.crt  -purpose

will output:


SSL client : Yes
SSL client CA : No
SSL server : Yes
SSL server CA : No
Netscape SSL server : Yes
Netscape SSL server CA : No
S/MIME signing : Yes
S/MIME signing CA : No
S/MIME encryption : Yes
S/MIME encryption CA : No
CRL signing : Yes
CRL signing CA : No
Any Purpose : Yes
Any Purpose CA : Yes
OCSP helper : Yes
OCSP helper CA : No
Time Stamp signing : No
Time Stamp signing CA : No

And, of course, server certificate is not a CA, so

openssl x509 -in server.crt  -purpose

will output:


SSL client : Yes
SSL client CA : No
SSL server : Yes
SSL server CA : No
Netscape SSL server : Yes
Netscape SSL server CA : No
S/MIME signing : Yes
S/MIME signing CA : No
S/MIME encryption : Yes
S/MIME encryption CA : No
CRL signing : Yes
CRL signing CA : No
Any Purpose : Yes
Any Purpose CA : Yes
OCSP helper : Yes
OCSP helper CA : No
Time Stamp signing : No
Time Stamp signing CA : No

About these, read OpenSSL doc, for instance.

Installing the CA's certificate on Debian/Ubuntu

Take the CA.crt file (or all-CA.pem file in our second scenario) and put it into /etc/ssl/certs. Then run c_rehash. You need root privilegies in order to be able to do both the operations — prepend with sudo if you are a sudoer. (Be sure a file with the same name does not exist, or you'll overwrite a real CA cert!).

Better option: copy CA.crt into /usr/local/share/ca-certificates/ and run update-ca-certificates (again, you need superpowers to be allowed to do it).

This works for all those clients using OpenSSL. E.g. w3m (and wget). Other clients may use a different certificate store; notably all the main browsers have their own way of installing certificates.

See also this Q and its As on Superuser.com

Conclusion

The web has plenty of valuable information on the topic. The matter, as a whole, is broad and can be not so simple to grasp each bit correctly and to tame every aspect swiftly.

If you need a cleaner and more correct picture, I suggest you to look for other readings. On the other hand, however, I hope this text does not scatter confusion and completely wrong ideas.


  1. Just to stress again what is already said in the disclaimer: do not take these informations and command lines as ground for serious security set-ups. E.g. all the options for openssl regarding keys strength and cryptoalgorithms are taken carelessly or left as default (and defaults could be not so good, e.g. SHA1) — for real, you should pick other algorithms, longer keys (instead here I leave it to the default, whatever it is), and in general take your decisions according to best practices and suggestions by experts. I am just exploring the matter from zero to draw a general picture and vague understanding of what's behind the scenes. Even this picture must not be trusted too much, since I could have misunderstood something, or oversemplified to stop at my own “needed” depth of knowledge of the matter.

  2. We expect that there are few “roots”. Here, in a comment, it is said that a root certificate is used to sign an intermediate CA certificate, and this one will be used to sign CSR from servers. Intermediate CA are trusted by the root CA, and can sign CSR and in the same time, if something goes wrong and this intermediate CA loses its trustworthiness, it's easy to revoke intermediate CA certificates, and, implicitly, to make distrusted all the certificates signed by these revoked certificates. We are dealing with a chain of trust, again, where the root CA is “blindly” trusted.

  3. No-one, except the owner, must be able to read that file. The data storage that holds it, must be secure, and if there are other users on the same system, they must not be able to read that file — so, a chmod 400 on is not a bad idea, altogether with limited physical access to the device.

  4. The root CA could also distribute a “PEM” file which has, inside it, one certificate (in PEM format) after another and is already usable by cURL. I have chosen this path for a reason; in particular, it's unlikely that a root CA decides the way it distributes certificates according to the needs of some client.

  5. Default certificate format should be PEM, but if it is, instead, DER, add the -outform PEM option to openssl.

  6. I mean this: each certificate has a subject, describing the “entity” the certificate belongs to, and an issuer, who issued (yes…) the certificate and signed it. For the certificate number 1 in the chain, let's label subject as S1 and issuer as I1. In the root certificate (number 0, the head of the chain), being it self-signed, we have S0=I0. The next certificate (number 1) has S1 and I1, where I1=S0 (hence the maybe misleading idea of “crossing”). Our chain is very short, but we can go on: certificate 2 has S2 and I2, where I2=S1, certificate 3 has S3 and I3 where I3=S2…

No comments:

Post a Comment