0

I am creating a certificate signed by own self-signed root authority. This is used to serve a domain hosted as a local app, which is mapped to a localhost address by /etc/hosts file. When the website is requested inside the browser my local app (written in flask) serves the app content while supplying the self-signed certificates I created for the domain. I have code written in Python's Cryptography module, and it follows these basic steps:

a) create a self-signed root authority with its own .CRT and .KEY

b) generate a CSR corresponding to my domain name after generating a separate private key for it

c) have my certificate authority sign the CSR and generate the certificate.

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.serialization import load_der_private_key
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
import datetime
import uuid
import os
import sys
import subprocess

def generate_root_CA():
    """
    a) generate rootCA key
    b) generate rootCA crt
    """

    ##generating root key

    root_private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend())


    ##self-sign and generate the root certificate

    root_public_key = root_private_key.public_key()
    builder = x509.CertificateBuilder()
    builder = builder.subject_name(x509.Name([
    x509.NameAttribute(NameOID.COMMON_NAME, u'Test CA'),
    x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Org'),
    x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'Testing unit'),
    ]))

    builder = builder.issuer_name(x509.Name([
    x509.NameAttribute(NameOID.COMMON_NAME, u'Test CA'),
    ]))
    builder = builder.not_valid_before(datetime.datetime.today() - datetime.timedelta(days=1))
    builder = builder.not_valid_after(datetime.datetime(2019, 12, 31))
    builder = builder.serial_number(int(uuid.uuid4()))
    builder = builder.public_key(root_public_key)
    builder = builder.add_extension(
    x509.BasicConstraints(ca=True, path_length=None), critical=True,)

    root_certificate = builder.sign(
        private_key=root_private_key, algorithm=hashes.SHA256(),
        backend=default_backend()
    )


    ##write to disk

    with open("rootCA.key", "wb") as f:
        f.write(root_private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.TraditionalOpenSSL,
            encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase")
        ))

    with open("rootCA.crt", "wb") as f:
        f.write(root_certificate.public_bytes(
            encoding=serialization.Encoding.PEM,
        ))

    return root_private_key, root_certificate

def generate_key():
    """
    a) generate key for the certificate being created
    """
    key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048,
        backend=default_backend()
    )

    return key

def generate_csr(key, domain_name):
    """
    generate csr for the client certificate
    """
    csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
    # Provide various details about who we are.
        x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"MA"),
        x509.NameAttribute(NameOID.LOCALITY_NAME, u"Boston"),
        x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Org"),
        x509.NameAttribute(NameOID.COMMON_NAME, domain_name),
    ])).add_extension(
        x509.SubjectAlternativeName([
        x509.DNSName(domain_name),
        x509.DNSName(u"www." + domain_name),
    ]),
    critical=False,

    # Sign the CSR with our private key.
    ).sign(key, hashes.SHA256(), default_backend())

    # Write our CSR out to disk.
    with open(domain_name + ".csr", "wb") as f:
        f.write(csr.public_bytes(serialization.Encoding.PEM))

    return csr

def sign_certificate_request(csr, rootkey, rootcrt, client_key, domain_name):
    """
    generate the certificate based on the csr created
    """


    crt = x509.CertificateBuilder().subject_name(
        csr.subject
    ).issuer_name(
        rootcrt.subject
    ).public_key(
        csr.public_key()
    ).serial_number(
        uuid.uuid4().int  # pylint: disable=no-member
    ).not_valid_before(
        datetime.datetime.utcnow()
    ).not_valid_after(
        datetime.datetime.utcnow() + datetime.timedelta(days=2)
    ).add_extension(
        extension=x509.KeyUsage(
            digital_signature=True, key_encipherment=True, content_commitment=True,
            data_encipherment=False, key_agreement=False, encipher_only=False, decipher_only=False, key_cert_sign=False, crl_sign=False
        ),
        critical=True
    ).add_extension(
        extension=x509.BasicConstraints(ca=False, path_length=None),
        critical=True
    ).add_extension(
        extension=x509.AuthorityKeyIdentifier.from_issuer_public_key(rootkey.public_key()),
        critical=False
    ).sign(
        private_key=rootkey,
        algorithm=hashes.SHA256(),
        backend=default_backend()
    )

    with open(domain_name + ".crt", 'wb') as f:
        f.write(crt.public_bytes(encoding=serialization.Encoding.PEM)) 



def main():

    domain_name = "domain.org"
    root_key, root_crt = generate_root_CA()
    domain_key = generate_key()
    csr = generate_csr(domain_key, domain_name)
    sign_certificate_request(csr, root_key, root_crt, domain_key, domain_name)


if __name__ == "__main__":
   main()

In Chrome, however I am getting the 'ERR: CERT_COMMON_NAME_INVALID' error. Reading up online, it seems that for this to go away, one needs to specify ones domain name within the Subject Alternative Field inside the CSR request, and it has to match the Common Name. That, however, is being already done inside the code (as can be seen in the generate_csr function). Additionally, I've imported the root certificate inside Chrome's root store. Could anyone help what could be the error over here?

QPTR
  • 1,620
  • 7
  • 26
  • 47

1 Answers1

1

Extensions in the CSR are not automatically copied into the certificate. You'll need to explicitly call add_extension with the SubjectAlternativeName object again in sign_certificate_request.

Paul Kehrer
  • 13,466
  • 4
  • 40
  • 57