Search code examples
python-3.xcryptographycertificatemosquittopaho

Trying to automate certificate generation with python3.cryptography; not having luck


I'm trying to programmatically create client certificates for mosquitto clients to use. Using XCA, I've been able to create a self signed CA, an intermediate, and then a certificate from the intermediate. I've configured mosquitto with the `require_certificate true' option. Using keys exported from XCA, I'm able to use the paho clients for testing:

 mosquitto_sub -h ubuntu -p 8765 -t /1/2/3 --cafile ~/CA2/Chain.crt -d --cert ./test1.crt --key ./test1.pem

This works.

Now I'm trying to automate the key generation with a python3 script using the cryptography module.

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.x509.oid import NameOID
from pathlib import Path
from datetime import datetime, timezone, timedelta
import uuid

privateKey = rsa.generate_private_key(
    public_exponent=65537,
    key_size=4096,
    backend=default_backend())
with Path('test2.pem').open('wb') as stream:
    stream.write(privateKey.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.TraditionalOpenSSL,
        encryption_algorithm=serialization.NoEncryption()))

builder = x509.CertificateBuilder()
builder = builder.subject_name(x509.Name([
    x509.NameAttribute(NameOID.COMMON_NAME, uuid.uuid4().hex),
]))
builder = builder.issuer_name(x509.Name([
    x509.NameAttribute(NameOID.COMMON_NAME, u'ubuntu'),
]))
builder = builder.not_valid_before(datetime.now(timezone.utc) - timedelta(days=1))
builder = builder.not_valid_after(datetime.now(timezone.utc) + timedelta(days=365*50))
builder = builder.serial_number(x509.random_serial_number())
builder = builder.public_key(privateKey.public_key())
builder = builder.add_extension(
    x509.BasicConstraints(ca=False, path_length=None), critical=True,
)
builder = builder.add_extension(
    x509.SubjectKeyIdentifier.from_public_key(privateKey.public_key()), critical=False,
)
builder = builder.add_extension(
    x509.KeyUsage(digital_signature=True,
        content_commitment=False,
        key_encipherment=True,
        data_encipherment=True,
        key_agreement=False,
        key_cert_sign=False,
        crl_sign=False,
        encipher_only=False,
        decipher_only=False), critical=False,
)
certificate = builder.sign(
    private_key=privateKey, algorithm=hashes.SHA256(),
    backend=default_backend()
)

print(certificate)
with Path('test2.crt').open('wb') as stream:
    stream.write(certificate.public_bytes(encoding=serialization.Encoding.PEM))

I've used openssl x509 -in certFile.crt -text -noout to compare the XCA variant vs my automated one. They look darn near identical to me. However, the sub doesn't work:

$ mosquitto_sub -h ubuntu -p 8765 -t /1/2/3 --cafile ~/CA2/Chain.crt -d --cert ./cert.crt --key ./key.pem 
Client mosqsub|34613-ubuntu sending CONNECT
Error: A TLS error occurred.

Solution

  • The problem as pointed out in another source, was that I was signing the new certificate with the same key as I was generating the certificate from. Like the issue name is to be the same name as the issuing certificate, the signing key should be the private key of that same issuing certificate. Replacing the signing clause with something like:

    parentKey = serialization.load_pem_private_key(
        Path('path/to/intermediate_key.pem').read_bytes(),
        password=None,
        backend=default_backend())
    certificate = builder.sign(
        private_key=parentKey,
        algorithm=hashes.SHA256(),
        backend=default_backend()
    )