Skip to content

Commit

Permalink
document creating a CA hierarchy: root -> int -> ee (#11031)
Browse files Browse the repository at this point in the history
* document creating a CA hierarchy: root -> int -> ee

* fix things
  • Loading branch information
reaperhulk authored May 27, 2024
1 parent a744422 commit 5dc620d
Showing 1 changed file with 192 additions and 0 deletions.
192 changes: 192 additions & 0 deletions docs/x509/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,198 @@ Then we generate the certificate itself:
And now we have a private key and certificate that can be used for local
testing.

Creating a CA hierarchy
-----------------------

When building your own root hierarchy you need to generate a CA and then
issue certificates (typically intermediates) using it. This example shows
how to generate a root CA, a signing intermediate, and issues a leaf
certificate off that intermediate. X.509 is a complex specification so
this example will require adaptation (typically different extensions)
for specific operating environments.

Note that this example does not add CRL distribution point or OCSP AIA
extensions, nor does it save the key/certs to persistent storage.

.. doctest::

>>> import datetime
>>> from cryptography.hazmat.primitives.asymmetric import ec
>>> from cryptography.hazmat.primitives import hashes
>>> from cryptography.x509.oid import NameOID
>>> from cryptography import x509
>>> # Generate our key
>>> root_key = ec.generate_private_key(ec.SECP256R1())
>>> subject = issuer = x509.Name([
... x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
... x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
... x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"),
... x509.NameAttribute(NameOID.COMMON_NAME, "PyCA Docs Root 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.now(datetime.timezone.utc)
... ).not_valid_after(
... # Our certificate will be valid for ~10 years
... datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=365*10)
... ).add_extension(
... x509.BasicConstraints(ca=True, path_length=None),
... critical=True,
... ).add_extension(
... x509.KeyUsage(
... digital_signature=True,
... content_commitment=False,
... key_encipherment=False,
... data_encipherment=False,
... key_agreement=False,
... key_cert_sign=True,
... crl_sign=True,
... encipher_only=False,
... decipher_only=False,
... ),
... critical=True,
... ).add_extension(
... x509.SubjectKeyIdentifier.from_public_key(root_key.public_key()),
... critical=False,
... ).sign(root_key, hashes.SHA256())

With a root certificate created we now want to create our intermediate.

.. doctest::

>>> # Generate our intermediate key
>>> int_key = ec.generate_private_key(ec.SECP256R1())
>>> subject = x509.Name([
... x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
... x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
... x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"),
... x509.NameAttribute(NameOID.COMMON_NAME, "PyCA Docs Intermediate CA"),
... ])
>>> int_cert = x509.CertificateBuilder().subject_name(
... subject
... ).issuer_name(
... root_cert.subject
... ).public_key(
... int_key.public_key()
... ).serial_number(
... x509.random_serial_number()
... ).not_valid_before(
... datetime.datetime.now(datetime.timezone.utc)
... ).not_valid_after(
... # Our intermediate will be valid for ~3 years
... datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=365*3)
... ).add_extension(
... # Allow no further intermediates (path length 0)
... x509.BasicConstraints(ca=True, path_length=0),
... critical=True,
... ).add_extension(
... x509.KeyUsage(
... digital_signature=True,
... content_commitment=False,
... key_encipherment=False,
... data_encipherment=False,
... key_agreement=False,
... key_cert_sign=True,
... crl_sign=True,
... encipher_only=False,
... decipher_only=False,
... ),
... critical=True,
... ).add_extension(
... x509.SubjectKeyIdentifier.from_public_key(int_key.public_key()),
... critical=False,
... ).add_extension(
... x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
... root_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value
... ),
... critical=False,
... ).sign(root_key, hashes.SHA256())

Now we can issue an end entity certificate off this chain.

.. doctest::

>>> ee_key = ec.generate_private_key(ec.SECP256R1())
>>> subject = x509.Name([
... x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
... x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
... x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"),
... ])
>>> ee_cert = x509.CertificateBuilder().subject_name(
... subject
... ).issuer_name(
... int_cert.subject
... ).public_key(
... ee_key.public_key()
... ).serial_number(
... x509.random_serial_number()
... ).not_valid_before(
... datetime.datetime.now(datetime.timezone.utc)
... ).not_valid_after(
... # Our cert will be valid for 10 days
... datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=10)
... ).add_extension(
... x509.SubjectAlternativeName([
... # Describe what sites we want this certificate for.
... x509.DNSName("cryptography.io"),
... x509.DNSName("www.cryptography.io"),
... ]),
... critical=False,
... ).add_extension(
... x509.BasicConstraints(ca=False, path_length=None),
... critical=True,
... ).add_extension(
... x509.KeyUsage(
... digital_signature=True,
... content_commitment=False,
... key_encipherment=True,
... data_encipherment=False,
... key_agreement=False,
... key_cert_sign=False,
... crl_sign=True,
... encipher_only=False,
... decipher_only=False,
... ),
... critical=True,
... ).add_extension(
... x509.ExtendedKeyUsage([
... x509.ExtendedKeyUsageOID.CLIENT_AUTH,
... x509.ExtendedKeyUsageOID.SERVER_AUTH,
... ]),
... critical=False,
... ).add_extension(
... x509.SubjectKeyIdentifier.from_public_key(ee_key.public_key()),
... critical=False,
... ).add_extension(
... x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
... int_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value
... ),
... critical=False,
... ).sign(int_key, hashes.SHA256())

And finally we use the verification APIs to validate the chain.

.. doctest::

>>> from cryptography.x509 import DNSName
>>> from cryptography.x509.verification import PolicyBuilder, Store
>>> store = Store([root_cert])
>>> builder = PolicyBuilder().store(store)
>>> verifier = builder.build_server_verifier(DNSName("cryptography.io"))
>>> chain = verifier.verify(ee_cert, [int_cert])
>>> len(chain)
3

Determining Certificate or Certificate Signing Request Key Type
---------------------------------------------------------------

Expand Down

0 comments on commit 5dc620d

Please sign in to comment.