Be your own certificate authority

Originalartikel

Backup

<html> <p>The Transport Layer Security (<a href=„https://en.wikipedia.org/wiki/Transport_Layer_Security“ target=„_blank“>TLS</a>) model, which is sometimes referred to by the older name SSL, is based on the concept of <a href=„https://en.wikipedia.org/wiki/Certificate_authority“ target=„_blank“>certificate authorities</a> (CAs). These authorities are trusted by browsers and operating systems and, in turn, <em>sign</em> servers' certificates to validate their ownership.</p> <p>However, for an intranet, a microservice architecture, or integration testing, it is sometimes useful to have a <em>local CA</em>: one that is trusted only internally and, in turn, signs local servers' certificates.</p> <p>This especially makes sense for integration tests. Getting certificates can be a burden because the servers will be up for minutes. But having an „ignore certificate“ option in the code could allow it to be activated in production, leading to a security catastrophe.</p> <p>A CA certificate is not much different from a regular server certificate; what matters is that it is trusted by local code. For example, in the <strong>requests</strong> library, this can be done by setting the <strong>REQUESTS_CA_BUNDLE</strong> variable to a directory containing this certificate.</p> <p>In the example of creating a certificate for integration tests, there is no need for a <em>long-lived</em> certificate: if your integration tests take more than a day, you have already failed.</p> <p>So, calculate <strong>yesterday</strong> and <strong>tomorrow</strong> as the validity interval:</p> <p>&gt;&gt;&gt; import datetime<br/>&gt;&gt;&gt; one_day = datetime.timedelta(days=1)<br/>&gt;&gt;&gt; today = datetime.date.today()<br/>&gt;&gt;&gt; yesterday = today - one_day<br/>&gt;&gt;&gt; tomorrow = today - one_day</p> <p>Now you are ready to create a simple CA certificate. You need to generate a private key, create a public key, set up the „parameters“ of the CA, and then self-sign the certificate: a CA certificate is <em>always</em> self-signed. Finally, write out both the certificate file as well as the private key file.</p> <div class=„geshifilter text geshifilter-text“ readability=„55“>from cryptography.hazmat.primitives.asymmetric import rsa<br/>from cryptography.hazmat.primitives import hashes, serialization<br/>from cryptography import x509<br/>from cryptography.x509.oid import NameOID <p>private_key = rsa.generate_private_key(<br/>&#160; &#160; public_exponent=65537,<br/>&#160; &#160; key_size=2048,<br/>&#160; &#160; backend=default_backend()<br/>)<br/>public_key = private_key.public_key()<br/>builder = x509.CertificateBuilder()<br/>builder = builder.subject_name(x509.Name([<br/>&#160; &#160; x509.NameAttribute(NameOID.COMMON_NAME, 'Simple Test CA'),<br/>]))<br/>builder = builder.issuer_name(x509.Name([<br/>&#160; &#160; x509.NameAttribute(NameOID.COMMON_NAME, 'Simple Test CA'),<br/>]))<br/>builder = builder.not_valid_before(yesterday)<br/>builder = builder.not_valid_after(tomorrow)<br/>builder = builder.serial_number(x509.random_serial_number())<br/>builder = builder.public_key(public_key)<br/>builder = builder.add_extension(<br/>&#160; &#160; x509.BasicConstraints(ca=True, path_length=None),<br/>&#160; &#160; critical=True)<br/>certificate = builder.sign(<br/>&#160; &#160; private_key=private_key, algorithm=hashes.SHA256(),<br/>&#160; &#160; backend=default_backend()<br/>)<br/>private_bytes = private_key.private_bytes(<br/>&#160; &#160; encoding=serialization.Encoding.PEM,<br/>&#160; &#160; format=serialization.PrivateFormat.TraditionalOpenSSL,<br/>&#160; &#160; encryption_algorithm=serialization.NoEncrption())<br/>public_bytes = certificate.public_bytes(<br/>&#160; &#160; encoding=serialization.Encoding.PEM)<br/>with open(„ca.pem“, „wb“) as fout:<br/>&#160; &#160; fout.write(private_bytes + public_bytes)<br/>with open(„ca.crt“, „wb“) as fout:<br/>&#160; &#160; fout.write(public_bytes)</p> </div> <p>In general, a real CA will expect a <a href=„https://en.wikipedia.org/wiki/Certificate_signing_request“ target=„_blank“>certificate signing request</a> (CSR) to sign a certificate. However, when you are your own CA, you can make your own rules! Just go ahead and sign what you want.</p> <p>Continuing with the integration test example, you can create the private keys and sign the corresponding public keys right then. Notice <strong>COMMON_NAME</strong> needs to be the „server name“ in the <strong>https</strong> URL. If you've configured name lookup, the needed server will respond on <strong>service.test.local</strong>.</p> <p>service_private_key = rsa.generate_private_key(<br/>&#160; &#160; public_exponent=65537,<br/>&#160; &#160; key_size=2048,<br/>&#160; &#160; backend=default_backend()<br/>)<br/>service_public_key = service_private_key.public_key()<br/>builder = x509.CertificateBuilder()<br/>builder = builder.subject_name(x509.Name([<br/>&#160; &#160;x509.NameAttribute(NameOID.COMMON_NAME, 'service.test.local')<br/>]))<br/>builder = builder.not_valid_before(yesterday)<br/>builder = builder.not_valid_after(tomorrow)<br/>builder = builder.public_key(public_key)<br/>certificate = builder.sign(<br/>&#160; &#160; private_key=private_key, algorithm=hashes.SHA256(),<br/>&#160; &#160; backend=default_backend()<br/>)<br/>private_bytes = service_private_key.private_bytes(<br/>&#160; &#160; encoding=serialization.Encoding.PEM,<br/>&#160; &#160; format=serialization.PrivateFormat.TraditionalOpenSSL,<br/>&#160; &#160; encryption_algorithm=serialization.NoEncrption())<br/>public_bytes = certificate.public_bytes(<br/>&#160; &#160; encoding=serialization.Encoding.PEM)<br/>with open(„service.pem“, „wb“) as fout:<br/>&#160; &#160; fout.write(private_bytes + public_bytes)</p> <p>Now the <strong>service.pem</strong> file has a private key and a certificate that is „valid“: it has been signed by your local CA. The file is in a format that can be given to, say, Nginx, HAProxy, or most other HTTPS servers.</p> <p>By applying this logic to testing scripts, it's easy to create servers that look like authentic HTTPS servers, as long as the client is configured to trust the right CA.</p> </html>