Adding HTTPS with a Private Certificate and Client Verification for Internal APIs

This article explains how to add a self-signed certificate to a private API and verify that the client is genuine.

Server Certificate

Generate a private certificate

Start by creating a private key:

openssl genrsa -out ssl.key 2048

Next, generate a certificate signing request (CSR):

openssl req -new -key ssl.key -days 3650 -out ssl.csr

The days parameter controls the certificate validity period.

You will be prompted for a few pieces of information that go into the certificate. Apart from the Common Name, which must match the real domain name or IP, the other fields can be filled with anything.

The resulting ssl.csr is a CSR. Normally you would send it to a public CA for signing, but that is unnecessary for an internal API.

You can sign the CSR with the private key directly:

openssl x509 -req -in ssl.csr -signkey ssl.key -out ssl.crt

If you plan to sign more CSRs or prefer to issue certificates through a CA certificate, generate your own private CA first:

openssl x509 -req -in ssl.csr -extensions v3_ca -signkey ssl.key -out sign.crt

Then use the CA certificate to sign the CSR:

openssl x509 -req -in ssl.csr -extensions v3_usr -CA sign.crt -CAkey ssl.key -CAcreateserial -days 3650 -out ssl.crt

You now have the server-side private key ssl.key and certificate ssl.crt. Deploy them with your server application.

Make the client trust the certificate

Because this certificate is self-signed, clients only trust it if you explicitly configure them to. For a browser, follow the browser-specific flow to trust the certificate.

In my case the client is the Python requests module. To trust the certificate I specify the verify parameter when sending the request, pointing it at the CA bundle. You can generate the bundle on the server, but distributing it to every client can be inconvenient. I simply export the certificate (X.509 with chain) from Firefox and reference it in the request:

requests.get('https://example.com', verify='ca.crt')

Validate client requests

HTTPS supports mutual authentication with client certificates, but asking every client to install a certificate is cumbersome, and not every server framework can validate them. Public APIs usually rely on token-based authentication. For sensitive internal APIs you can add a Time-based One Time Password (TOTP) on top of tokens, or adapt the algorithm to your needs. That part is beyond the scope of this article.

References:

  1. Primer: TLS, SSL, HTTPS, and Certificates
  2. Building a CA and issuing SSL certificates with OpenSSL
  3. Generating a CA bundle from CRT files in OpenSSL