5.11 THE CHAINS WE FORGED IN LIFE
EXERCISE 5.11: THE CHAINS WE FORGED IN LIFE
Modify the identity validation programs to support a chain of trust. First, create some self signed certificates for the EA goverment (at least two as described previously). The existing issuer script can already do this. Just make the issuer private key for the self-signed certificate to be the organization’s own private key. Thus, the organization is signing its own cert, and the private key used to sign the certificate matches the public key in the certificate.
Next, create certificates for intermediate CAs such as “Department of Education”, “Department of Defense”, “Espionage Agency” and so forth. These certificates should be signed by the self signed certificates in the previous step.
Finally, sign certificates for Alice, Bob, and Charlie by the espionage CA. Perhaps create some certificates for employees of the defense department and the education department. These certificates should be signed by the appropriate intermediate CA.
Now, modify the verification program to take a chain of certificates instead of just a single certificate. Get rid of the command-line parameter for the issuer’s public key and instead hard-code which of the root certificate filenames are trusted. To specify the chain of certificates, have the program take the claimed identity as the first input (as it already does) and then an arbitrary number of certificates to follow. Each certificate’s issuer field should indicate the next certificate in the chain. For example, to validate Charlie, there may be three certificates:
charlie.cert
,espionage.cert
,covert_root.cert
. The issuer ofcharlie.cert
should have the same subject name asespionage.cert
and so forth. The verify program should only accept an identity if the last certificate in the chain is already trusted.
Creating two self-signed certificates
Let’s call the keys:
root1_private.key
,root1_public.key
root2_private.key
,root2_public.key
Let’s call the certificates:
- root1.cert
- root2.cert
The contents of the bash script above is given below:
python3 fake_certs_issuer.py root1_private.key fake_cert_authority1 root1_public.key root1.cert
python3 fake_certs_issuer.py root2_private.key fake_cert_authority1 root2_public.key root2.cert
The usage of fake_certs_issuer.py
script is:
Usage: python3 fake_certs_issuer.py <issuer-private-key-file> <certificate-subject> <subject's-public-key-file> <certificate-output-file>
Creating certs for the departments
Department of Education
The contents of the above script is:
# this script is used to create the certificates
# for the DOE - Department of Education
# generate a pair of keys...
python3 generate_rsa_key_pairs.py doe_private.key doe_public.key
# sign doe's public key using one of the root certs.
python3 fake_certs_issuer.py root2_private.key doe doe_public.key doe.cert
Department of Defense
Espionage Agency
Creating certs for the employees of the departments
source code of the above script:
# Create certs for employees of EA
# generate a pair of keys...
python3 generate_rsa_key_pairs.py alice_private.key alice_public.key
python3 generate_rsa_key_pairs.py bob_private.key bob_public.key
python3 generate_rsa_key_pairs.py charlie_private.key charlie_public.key
# sign the public keys of the employees using the private key of ea.
python3 fake_certs_issuer.py ea_private.key alice alice_public.key alice.cert
python3 fake_certs_issuer.py ea_private.key bob bob_public.key bob.cert
python3 fake_certs_issuer.py ea_private.key charlie charlie_public.key charlie.cert
Charlie verifying his identity
I made a lot of changes to the script fake_certs_verify_identity.py
and fixed bugs in fake_certs_issuer.py
. But end result is as follows:
Contents of the above shell script is:
python3 fake_certs_verify_identity.py charlie charlie.cert ea.cert root2.cert
Contents of fake_certs_verify_identity.py
# Note that this script is being run by Alice,
# to verify the certificate of Charlie,
# that has been signed by HQ.
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
import sys, json, os
from read_write_rsa_keys import *
= "fake_cert_authority1"
ISSUER_NAME
= "subject"
SUBJECT_KEY = "issuer"
ISSUER_KEY = "public_key"
PUBLICKEY_KEY
= [
trusted_root_cert_fnames 'root1.cert',
'root2.cert',
]
class IssuerNotAsExpected(Exception):
def __init__(self, expected_issuer_name: str, actual_issuer_name: str):
self.expected_issuer_name = expected_issuer_name
self.actual_issuer_name = actual_issuer_name
def __str__(self):
return f"\nExpected: {self.expected_issuer_name}.\nBut got: {self.actual_issuer_name}"
class RootCertificateNotTrusted(Exception):
pass
def read_public_key_from_certificate(cert_fname: str):
if not os.path.exists(cert_fname):
raise FileNotFoundError()
with open(cert_fname, 'rb') as f:
= f.read()
data = data[:-256], data[-256:]
raw_cert_bytes, signature = json.loads(raw_cert_bytes.decode('utf-8'))
cert_data
= serialization.load_pem_public_key(
public_key =cert_data[PUBLICKEY_KEY].encode(),
data=default_backend(),
backend
)return (public_key, cert_data[SUBJECT_KEY])
def validate_certificate_chain(cert_files: list[str]):
if cert_files[-1] not in trusted_root_cert_fnames:
raise RootCertificateNotTrusted()
for i in range(len(cert_files) - 1):
# validate the i th certificate using the (i + 1)th certificate
print(f"[+] Validating {cert_files[i]} using {cert_files[i+1]}...")
with open(cert_files[i], 'rb') as f:
= f.read()
certificate_bytes
= read_public_key_from_certificate(cert_files[i+1])
issuer_public_key, expected_issuer
= certificate_bytes[:-256], certificate_bytes[-256:]
raw_cert_bytes, signature
issuer_public_key.verify(
signature,
raw_cert_bytes,
padding.PSS(=padding.MGF1(hashes.SHA256()),
mgf=padding.PSS.MAX_LENGTH
salt_length
),
hashes.SHA256(),)= json.loads(raw_cert_bytes.decode('utf-8'))
current_cert if current_cert[ISSUER_KEY] != expected_issuer:
raise IssuerNotAsExpected(
=current_cert[ISSUER_KEY],
actual_issuer_name=expected_issuer,
expected_issuer_name
)
with open(cert_files[0], 'rb') as f:
= f.read()
data = data[:-256]
raw_cert_bytes dict[str, str | bytes] = json.loads(raw_cert_bytes.decode('utf-8'))
cert_data: = cert_data[PUBLICKEY_KEY].encode('utf-8')
cert_data[PUBLICKEY_KEY] return cert_data
def verify_identity(identity: str, certificate_data: dict[str, str | bytes], challenge, response):
'''
* identity - the claimed identity. example: "Charlie"
* certificate_data - data of the certificate.
'''
if certificate_data[SUBJECT_KEY] != identity:
raise Exception("Claimed identity does not match")
= serialization.load_pem_public_key(
certificate_public_key: rsa.RSAPublicKey =certificate_data[PUBLICKEY_KEY],
data=default_backend(),
backend
)
certificate_public_key.verify(=response,
signature=challenge,
data=padding.PSS(
padding=padding.MGF1(hashes.SHA256()),
mgf=padding.PSS.MAX_LENGTH
salt_length
),=hashes.SHA256(),
algorithm
)
if __name__ == "__main__":
if len(sys.argv) < 3:
print(f"Usage: python3 {sys.argv[0]} <claimed-identity> [cert-file...]")
1)
exit(
= sys.argv[1]
claimed_identity = sys.argv[2:]
cert_files
= validate_certificate_chain(
cert_data =cert_files,
cert_files
)
print("Certificate has a valid signature from {}".format(ISSUER_NAME))
= input("Enter a name for a challenge file: ")
challenge_file print(f"Generating challenge to file {challenge_file}")
= os.urandom(32)
challenge_bytes with open(challenge_file, 'wb+') as f:
f.write(challenge_bytes)
= input("Enter a name of the response file: ")
response_file with open(response_file, 'rb') as f:
= f.read()
response_bytes
verify_identity(=claimed_identity,
identity=cert_data,
certificate_data=challenge_bytes,
challenge=response_bytes,
response
)
print(f"[+] Identity validated. You really are {claimed_identity}.")