I am creating a CA like so:
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.cert
This gives me two PEM files.
I then call this function where cert_authority
and private_key
are the strings of the data generated above.
def create_cert(cert_authority, private_key):
one_day = datetime.timedelta(1, 0, 0)
# Use our private key to generate a public key
private_key = serialization.load_pem_private_key(
private_key.encode("ascii"), password=None, backend=default_backend()
)
public_key = private_key.public_key()
ca = x509.load_pem_x509_certificate(
cert_authority.encode("ascii"), default_backend()
)
builder = x509.CertificateBuilder()
builder = builder.subject_name(
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io")])
)
builder = builder.issuer_name(ca.issuer)
builder = builder.not_valid_before(datetime.datetime.today() - one_day)
builder = builder.not_valid_after(datetime.datetime.today() + (one_day * 30))
builder = builder.serial_number(x509.random_serial_number())
builder = builder.public_key(public_key)
cert = builder.sign(
private_key=private_key, algorithm=hashes.SHA256(), backend=default_backend()
)
print(cert.public_bytes(serialization.Encoding.PEM))
This then generates what appears to be a cert, however on copying and pasting the data to a file (and wrapping by 64 lines and using Unix newlines as per http://srdevspot.blogspot.com/2011/08/openssl-error0906d064pem.html) I get this error when trying to validate:
$ openssl verify -CAfile ca.crt -untrusted phone.crt
unable to load certificates
Hoping I am missing something simple as I'm new to all this!
Finally I'll note that I'm open to using another crypto library if cryptography isn't the best.
EDIT:
Now using this per Paul's very helpful response:
def create_cert(cert_authority, private_key):
one_day = datetime.timedelta(1, 0, 0)
# Use our private key to generate a public key
root_key = serialization.load_pem_private_key(
private_key.encode("ascii"), password=None, backend=default_backend()
)
root_cert = x509.load_pem_x509_certificate(
cert_authority.encode("ascii"), default_backend()
)
# Now we want to generate a cert from that root
cert_key = rsa.generate_private_key(
public_exponent=65537, key_size=2048, backend=default_backend()
)
new_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Texas"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Austin"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"New Org Name!"),
]
)
cert = (
x509.CertificateBuilder()
.subject_name(new_subject)
.issuer_name(root_cert.issuer)
.public_key(cert_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=30))
.add_extension(
x509.SubjectAlternativeName([x509.DNSName(u"somedomain.com")]),
critical=False,
)
.sign(root_key, hashes.SHA256(), default_backend())
)
# Dump to scratch
with open("scratch/phone_cert.pem", "wb") as f:
f.write(cert.public_bytes(encoding=serialization.Encoding.PEM))
# Return PEM
cert_pem = cert.public_bytes(encoding=serialization.Encoding.PEM)
cert_key_pem = cert_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
return cert_pem, cert_key_pem
Would that be the correct way to both save a file and return the created cert and private key as PEM strings?
I am also finding that when I attempt to verify the created cert against the saved PEM with openssl verify -verbose -CAfile ca.crt -untrusted phone_cert.pem
the command never returns -- probably a separate issue but would appreciate any ideas.
First, you will generate a private key. For this example we will be using RSA having a key size of 2048, the lowest recommended bit size. Next, generate the self signed certificate. The certificate will contain data about who you are and who your organization is.
There are two issues that I see here. First, you're creating another self-signed certificate so the certificate you've generated is not signed by the CA, it is itself a CA. To correct this you sign with the private key of your CA (e.g. private_key
in your example), but you need to create a new private key associated with the new certificate and embed the public key of that in the cert.
certificate_private_key = <generate an ec or rsa key here>
certificate_public_key = certificate_private_key.public_key()
Then do
builder = builder.public_key(certificate_public_key)
You also have an issue with your output because you're trying to copy and paste things out of a print statement. The output of cert.public_bytes(serialization.Encoding.PEM)
will be a valid X509 certificate with delimiters and proper PEM line lengths, so write it directly to a file:
with open("cert.crt", "wb") as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
The result can be parsed with openssl x509 -noout -text -in cert.crt
Here is a complete example utilizing cryptography
to create a self-signed root CA and sign a certificate using that CA.
import datetime
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
root_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Texas"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Austin"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Company"),
x509.NameAttribute(NameOID.COMMON_NAME, u"My CA"),
])
root_cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
root_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.utcnow()
).not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=3650)
).sign(root_key, hashes.SHA256(), default_backend())
# Now we want to generate a cert from that root
cert_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
new_subject = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Texas"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Austin"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"New Org Name!"),
])
cert = x509.CertificateBuilder().subject_name(
new_subject
).issuer_name(
root_cert.issuer
).public_key(
cert_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.utcnow()
).not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=30)
).add_extension(
x509.SubjectAlternativeName([x509.DNSName(u"somedomain.com")]),
critical=False,
).sign(root_key, hashes.SHA256(), default_backend())
I have to post an answer since I'm new and can't comment yet 🙄
I heavily relied on Pauls answer for my own implementation, that was very informative and helpful. But I had to add one more extension on the CA certificate in order to get openssl verify -verbose -CAfile ca.crt client.crt
to work properly.
Adding .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
to the root CertificateBuilder did the trick.
ca_crt = x509.CertificateBuilder() \
.subject_name(subject) \
.issuer_name(issuer) \
.public_key(ca_key.public_key()) \
.serial_number(x509.random_serial_number()) \
.not_valid_before(datetime.datetime.today() - one_day) \
.not_valid_after(datetime.datetime.today() + (one_day * 365)) \
.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) \
.sign(ca_key, hashes.SHA256(), default_backend())
Did everything else just like Paul.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With