Coding Like a Certificate Authority
00:00 In the previous lesson, you took the first steps to becoming a Certificate Authority by creating a private key and a public certificate with a public key inside of it.
00:08 In this lesson, you’ll switch roles—become Alice—and create a CSR and then switch roles back to Charlie, signing the CSR. Here’s a quick review. Charlie, the Certificate Authority, needs a public key, which you’ve created, a private key, which you’ve created, and the ability to sign CSRs. Alice, the site owner, needs a public key, a private key, and a Certificate Signing Request describing her site that she can submit to the CA. Now, you’re going to change roles.
00:41 You’re acting as Alice, and you’re going to create a Certificate Signing Request so that you can ask Charlie to sign it. In order to do this, Alice needs a private key and a list of hostnames to be associated with the certificate.
00:54
Alice is going to use the same PrivateKey
module you created earlier to create a private key and then use that to build the CSR
.
01:03
The CSR
will also be built in a class using a factory pattern similar to the key patterns. This factory will require a list of hostnames that are valid for the key, information about the subject, and needs to be signed by Alice’s private key.
01:16 Like the public and private key, you’ll also need a mechanism for saving the CSR to a PEM file so it can be sent to the CA. Finally, all of this will be combined together into that single key generating script, which will go through all of the steps in the process.
01:32 You’re about to dive deep into another long coding example. Don’t forget, you can download the code in the Supporting Material dropdown attached to this lesson.
01:42
This is the definition of the CSR
object. It’s similar to the PublicKey
object. It has a .generate()
factory method, which takes a private key, a list of hostnames, and a dictionary of subject information. Because it’s a certificate—just like the public key—you need to create subject information, reusing the make_x509_name()
function.
02:04
The resulting certificate from the CA has to be signed against a list of hostnames it’s valid for, so the CSR has to contain that information for the CA to approve. Lines 15 through 19 loops through a list of hosts and uses the SubjectAlternativeName
object from x509
to create a list of those names.
02:26
Similar to the PublicKey
, a builder factory is used. This time, it’s a CSR
builder. The subject for the certificate is included, as well as the alt names provided above, containing the hostnames. Finally, an empty CSR
object is created.
02:43
The builder factory is used to sign the CSR
using the private key passed in, and that is attached to the .key
attribute of the newly created CSR
object.
02:54
It is then returned to the caller. Because the output of the CSR
is the same kind of certificate as a public key, instead of reimplementing the .save()
method, I’ve just inherited from the PublicKey
class and overloaded the .generate()
method.
03:11
Once again, I’m back inside of the generate_keys
file. In this section, you’re acting as Alice with the intent of creating a CSR. In order to do that, you first need a private key. Line 25 and 26 generates a PrivateKey
. This is no different than the one for the CA, but this one is for Alice. The key is being saved in "server-private-key.pem"
, and there’s a different password assigned because Alice doesn’t know the CA’s password. Take careful note of this. In the next lesson when you go to use this key, you’re going to need to know this password. Lines 29 through 36 populates information for the CSR. First, the name dictionary.
03:52
This names Alice’s company and the host "alice.example.net"
where she would like to host an HTTPS server. The alt_names
list contains the list of servers that will be valid for this certificate. In this case, 'localhost'
and 'alice.example.net'
.
04:09
If you’re self-signing certificates for internal use, you may only need 'localhost'
in this situation. This is something else I want you to take careful note of. In lesson seven, when you go to use this, these hostnames will become important. Finally, the meat of it: line 38 and 39 generates the actual Certificate Signing Request.
04:29
It requires Alice’s server_private_key
, the list of hostnames in alt_names
, and the location information in the name_dict
. Following the same pattern as before, the object is generated, and then the .save()
method is called—this time, saving it in a PEM file called "server-csr.pem"
.
04:52
This is the contents of that PEM file. Doesn’t look much different. Once again, it’s a large ASCII blob of certificate information. The header, here, is the CERTIFICATE REQUEST
, indicating that this is a CSR.
05:07 For the last little bit, you’ve been acting like Alice, generating the Certificate Signing Request. It’s time to switch back into the role of Charlie, the CA, and sign Alice’s CSR.
05:18
In addition to the CSR PEM that Alice submits to Charlie, Charlie also needs her public key and private key. You’re going to need to add a .load()
method to the CSR
object class to load in the PEM file, and then create a SignedKey
class.
05:35 This will follow the same pattern as before. Using a factory object, it’ll take a CSR, the CA’s public and private keys, and it will sign the CSR—generating a signed certificate.
05:48
The SignedKey
object will also need a method for writing this certificate to a PEM file. Once all this is done, the final step will be use the SignedKey
object inside of the key generating script to finish off the whole process.
06:05
This file defines the SignedKey
object. Similar to the CSR
object, it inherits from PublicKey
. This is because, once again, it’s issuing a certificate and can use the inherited .save()
method. Using the pattern you’re now familiar with, there is a class method called .generate()
, which acts as the factory. This also uses the make_builder()
helper utility.
06:29 The only difference here is—because this is for a CA—the subject and the issuer are different. The subject is the subject from the CSR, and the issuer is the subject information from the CA’s public key.
06:43 Once the builder factory has been instantiated, a series of extensions are applied to it. You can think of the extensions as limitations that are being applied to the certificate.
06:53 These limitations are being read in from the CSR and were provided as part of that signed certificate request. Line 19 and 20 is where the work is done.
07:04
The builder factory is used to sign the actual certificate. The signature on the certificate comes from the CA’s private key. The builder returns the result, and—like before—this is stored on the .key
attribute of the object created and the factory method returns the object.
07:24
And here’s the relevant part of the generate_keys.py
file. Line 42 is where the CSR is signed. Signing it requires the CSR from Alice, the CA’s public key, and the CA’s private key.
07:38
Once the .generate()
method is called, the SignedKey
object contains the signed certificate. The .save()
method is then called on the object, saving it into a file "server-public-key.pem"
.
07:50 This file is needed by Alice when she starts her HTTPS server. It’s the signed certificate associated with her domain.
08:00 And all of that work was to produce this one certificate. This is the certificate for Alice’s server. She can now host HTTPS and anyone with a browser that has Charlie listed as a CA will consider her site secure.
08:16
That was a lot of code in a lot of different files, so here’s a bit of a recap. The generate_keys
file is a single program that acts as both Alice and Charlie—creating the appropriate keys, a CSR, and signing that CSR so Alice can use it. First off, a private key was created, stored in the file ca-private-key.pem
, which is Charlie the CA’s private key. Secondly, a public key was created, stored in ca-public-key.pem
.
08:46
This is also Charlie the CA’s key. To create a CSR, Alice needs a private key, so server-private-key.pem
was generated. That key was then used to create a CSR.
08:59 It was signed and submitted to Charlie. Charlie takes the CSR and creates a new certificate, which is a public certificate that has been signed by Charlie the CA. Alice can now use this to run TLS on her web server.
09:16 Of course, because this is all happening inside of one script, you’re acting as both Charlie and Alice in this situation.
09:24 You’ve done the hard part. You’ve got the certificates. Next up, I’ll show you how to install that in Flask.
Christopher Trudeau RP Team on Sept. 3, 2024
Hi alphafox28js,
Your code got a bit messed up when pasted into the chat box and the indenting is wrong, so there could be several problems, but I think the issue you’re having has nothing to do with certificates.
The error you are getting AttributeError: ‘NoneType’ object has no attribute ‘public_key’
tells you that the key
attribute of your csr
value is None
. That value is supposed to be set during the call to CSR.generate()
, but you haven’t included that code, so I can’t tell what is going wrong.
The code I used for that method is:
class CSR(PublicKey):
@classmethod
def generate(cls, private_key, alt_hosts, name_dict):
subject = make_x509_name(**name_dict)
# Generate alternative DNS names
hosts = []
for host in alt_hosts:
hosts.append( x509.DNSName(host) )
subject_alt_names = x509.SubjectAlternativeName(hosts)
builder = (
x509.CertificateSigningRequestBuilder()
.subject_name(subject)
.add_extension(subject_alt_names, critical=False)
)
csr = CSR()
# KEY ASSIGNMENT IS DONE HERE
csr.key = builder.sign(private_key.key, hashes.SHA256(),
default_backend())
return csr
So the options are 1) something is going wrong in CSR.generate()
and the line isn’t getting called, 2) builder.sign()
is returning None
for some reason, or the most likely, 3) you haven’t defined CSR.generate()
and as a result the parent PublicKey.generate()
is being called instead, which doesn’t create the key
attribute at all.
As you’re using an IDE, you should be able to set a breakpoint inside your SignedKey.generate()
method and see the values of the things being passed in. You’ll likely find that key == None
and then you want to work your way backwards to figure out where it should have been set.
I hope this helps. This kind of coding can be frustrating, especially when you’re so dependent on third party libraries. If it is any consolation, it has been four years since I wrote this course and I pretty much had to dig deep before I could even wrap my head around your question. There’s lots going on here and missing a single step can lead to hard to debug problems. Good luck with it.
alphafox28js on Sept. 3, 2024
Hello, I have attached the CSR, Sign, and Keygen code:
# certauth.csr.py
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from public import PublicKey
from util import make_x_509_name
class CSR(PublicKey):
'''
Generate alternative DNS names
Has to be signed against a list of host names it's valid for,
so the CSR has obtain the list of host names for the
CA to be approved.
'''
@classmethod
def generate(cls, private_key, alt_hosts, name_dict):
subject = make_x_509_name(**name_dict)
hosts = []
for host in alt_hosts:
hosts.append(x509.DNSName(host))
subject_alt_names = x509.SubjectAlternativeName(hosts)
builder = (
x509.CertificateSigningRequestBuilder()
.subject_name(subject)
.add_extension(subject_alt_names, critical=False)
)
# An empty CSR Object is create,
# Builder-Factory is used to sign CSR using the private_key passed in,
# and that is attatched to the key attribute and attached to CSR.
csr = CSR()
csr.key = builder.sign(private_key.key, hashes.SHA256(),
default_backend())
return csr
@classmethod
def load(cls,filename):
public_key = PublicKey()
with open(filename, "rb") as keyfile:
data = keyfile.read()
public_key = x509.load_pem_x509_csr(data, default_backend())
return public_key
#certauth.sign.py
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from public import PublicKey
from util import make_builder
import logging
class SignedKey(PublicKey):
'''
SignedKey(PuclicKey) is used as a key-generation factory.
'''
@classmethod
def generate(cls, csr, ca_public_key, ca_private_key):
'''
Generate a signed key using the provided CSR, CA public key, and
CA private key.
Parameters:
csr: The certificate signing request containing the public key.
ca_public_key: The public key of the certificate authority.
ca_private_key: The private key of the certificate authority.
Returns:
A SignedKey instance with the generated key.
'''
signed_key = SignedKey()
builder = make_builder(
csr.key.subject,
issuer = ca_public_key.key.subject
).public_key(csr.key.public_key())
for extension in csr.key.extensions:
builder = builder.add_extension(extension.value, extension.critical)
try:
signed_key.key = builder.sign(
private_key = ca_private_key.key, algorithm = hashes.SHA256(),
backend=default_backend()
)
except Exception as e:
"""
Handle exceptions that may occur during signing.
"""
logging.error(f"An Error occured during the signing process: {e}")
return None
return signed_key
# certauth.generate_keys.py
from private import PrivateKey
from public import PublicKey
from csr import CSR
from sign import SignedKey
from cryptography.hazmat.primitives import serialization
import logging
# Constants
CA_PRIVATE_KEY_FILENAME = "ca-private-key.pem"
SERVER_PRIVATE_KEY_FILENAME = "server-private-key.pem"
SERVER_CSR_FILENAME = "server-csr.pem"
SERVER_PUBLIC_KEY_FILENAME = "server-public-key.pem"
CA_PUBLIC_KEY_FILENAME = "ca-public-key.pem"
CA_PASSWORD = "PrivateP@ssw0rd"
SERVER_PASSWORD = "ServerP@ssw0rd"
'''
# Private keys are always encoded in a pem file
# To use the key at a later time, the below will save the password.
'''
def save_private_key(private_key, filename, password):
"""Save a private key to a file with encryption."""
utf8_password = password.encode("utf-8")
algorithm = serialization.BestAvailableEncryption(utf8_password)
try:
with open(filename, "wb") as keyfile:
key_bytes = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=algorithm
)
keyfile.write(key_bytes)
except IOError as e:
logging.error(f"Error saving private key to {filename}: {e}")
if __name__ == "__main__":
# Generate CA's Private Key
ca_private_key = PrivateKey.generate()
save_private_key(ca_private_key.key, CA_PRIVATE_KEY_FILENAME, CA_PASSWORD)
# Generate CA's Public Key:
name_dict = {
"country":"US",
"state":"Texas",
"locality":"Austin",
"org":"3-P Oracle Certificate of Authority",
"hostname":"3-P_CA.example.com",
}
ca_public_key = PublicKey.generate(ca_private_key, name_dict, is_ca=True)
ca_public_key.save(CA_PUBLIC_KEY_FILENAME)
# Generate private key for the server
server_private_key = PrivateKey.generate()
save_private_key(server_private_key.key, SERVER_PRIVATE_KEY_FILENAME, SERVER_PASSWORD)
name_dict = {
"country":"US",
"state":"Ohio",
"locality":"Cincinatti",
"org":"Alphafox28 Co.",
"hostname":"Alphafox28.future-website.com",
}
alt_names = ['localhost', 'Alphafox28.future-website.com', ]
csr = CSR.generate(server_private_key, alt_names, name_dict)
csr.save(SERVER_CSR_FILENAME)
# CA signs CSR, creating server's certificate / public key
signed_key = SignedKey.generate(csr, ca_public_key, ca_private_key)
signed_key.save(SERVER_PUBLIC_KEY_FILENAME)
The debugger throws an Exception: AttributeError within the [builder = make_builder()] where the ().public_key is not being referenced in the sign.py file. This also correlates with what the terminal returns when running the keygen.py file. While I have tracked the issue down, I am uncertain on best path for correction.
Christopher Trudeau RP Team on Sept. 4, 2024
Hi alphafox28js,
I’m at a loss. Wish I could be more help. The only thing I’d try next is to dissect the make_builder
command. Inside, it chains a lot of stuff together:
builder = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.serial_number(x509.random_serial_number())
.not_valid_before(valid_from)
.not_valid_after(valid_to)
)
First check if that builder
comes back as something valid, if it does, then follow on by checking the add_extension()
call after it.
If the builder
doesn’t come back as something valid, either use a debugger to step through each of the chained calls, or break it apart and see exactly where it fails. You might get a more helpful exception.
The other option, which is ugly, is stepping into the failed call. At this point you’re calling libraries, so you may not even be able to go into them (they could be compiled extensions). Stepping into a library is often brutal as the whole point of using the library is to abstract away from the messy stuff, and unfortunately crypto is very messy.
One other possible avenue: have you been coding along or using the sample code? If the former, maybe try downloading the sample code and see if it runs. If so, then you know it is some sort of difference. If the sample fails as well, then it has something to do with the underlying library. It could be a version mismatch, or it could be an OS specific thing.
As I mentioned before, this course was written four years ago. Try using Python 3.8 and Cryptography 2.9.2 and see if that changes anything (this may be easier said than done as it may mean underlying library incompatibility as well).
Good luck with it!
alphafox28js on Sept. 5, 2024
Hello, all issues were resolved. There was a discrepancy with the setup I was using and what seemed to be a random fluke in my system.
I was coding as we were going through it. Practice and repetition purposes. I eventually pulled the sample code, and after a reboot and upgrade on my end, everything is up to date and fully functional.
I appreciate the time you have taken to help correct my errors and provide insight.
Great Course Content and absolutely loved it!
Christopher Trudeau RP Team on Sept. 5, 2024
Coding along is definitely the best way to learn. Glad you worked it out!
Become a Member to join the conversation.
alphafox28js on Sept. 3, 2024
Hello sir,
So I am am able to achieve 4 of the 5
.pem
files. I am missing the [server-public-key.pem]. I believe after hours of sitting here and debugging, that this actually comes down to an import issue.I took your advice from a previous discussion we had, and went with a Debian-Ubuntu server from previous discussion thread, that has helped tremendously by the way, however, as I still have yet to learn VIM, KATE, or VI, I am sticking with VSCode Editor with a Ubuntu Bash Shell. This has proved successful thus far up to this point, but I believe this to be more of a structural coding issue versus’ previous issues between Windows and Iot.
I did modify the code just a bit by adding Constants, Try/Excepts, Logging, and correcting deprecated time functions, but other than that, nothing major.
I was hoping you might be able to shed some light as to where I failing to resolve the <AttributeError: ‘NoneType’ object has no attribute ‘public_key’>
I am at this point convinced it has to do solely with the one of two options. 1) from public import PublicKey as it is not throwing an error, rather just not letting it pass for utilization in the following modules:
csr.py
,sign.py
, &keygen.py
.or 2) the <.key.something> <extension.val and extension.crit> are not working correctly.
I apologize for the length, however, the more detail you are given, the better chances you are able to help resolve…
<csr.py>
<sign.py>
<keygen.py>
Side Note: All of the pretty colors for highlights, (PublicKey, .key, & .save) are the only ones not highlighted as they typically would be. (Im probably being trivial with that piece haha)
Again, I apologize for the length of this post.
Sincerely,
JS