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 of charlie.cert should have the same subject name as espionage.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

generating keys

Let’s call the certificates:

  • root1.cert
  • root2.cert

creating the self signed root certificates

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

creating certificates for doe

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

cert for dod

Espionage Agency

cert for ea

Creating certs for the employees of the departments

Creating cert for employees of ea

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:

Charlie verifying his identity

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 * 

ISSUER_NAME = "fake_cert_authority1"

SUBJECT_KEY = "subject"
ISSUER_KEY = "issuer"
PUBLICKEY_KEY = "public_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:
        data = f.read() 
    raw_cert_bytes, signature = data[:-256], data[-256:]
    cert_data = json.loads(raw_cert_bytes.decode('utf-8')) 

    public_key = serialization.load_pem_public_key(
        data=cert_data[PUBLICKEY_KEY].encode(),
        backend=default_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: 
            certificate_bytes = f.read() 
        
        issuer_public_key, expected_issuer = read_public_key_from_certificate(cert_files[i+1])

        raw_cert_bytes, signature = certificate_bytes[:-256], certificate_bytes[-256:]

        issuer_public_key.verify(
            signature,
            raw_cert_bytes,
            padding.PSS(
                mgf=padding.MGF1(hashes.SHA256()),
                salt_length=padding.PSS.MAX_LENGTH
            ),
            hashes.SHA256(),)
        current_cert = json.loads(raw_cert_bytes.decode('utf-8'))
        if current_cert[ISSUER_KEY] != expected_issuer: 
            raise IssuerNotAsExpected(
                actual_issuer_name=current_cert[ISSUER_KEY],
                expected_issuer_name=expected_issuer,
            ) 
    
    with open(cert_files[0], 'rb') as f: 
        data = f.read() 
    raw_cert_bytes = data[:-256]
    cert_data: dict[str, str | bytes] = json.loads(raw_cert_bytes.decode('utf-8'))
    cert_data[PUBLICKEY_KEY] = cert_data[PUBLICKEY_KEY].encode('utf-8')
    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")

    certificate_public_key: rsa.RSAPublicKey = serialization.load_pem_public_key(
        data=certificate_data[PUBLICKEY_KEY],
        backend=default_backend(),
    )

    certificate_public_key.verify(
        signature=response,
        data=challenge,
        padding=padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        algorithm=hashes.SHA256(),
    )

if __name__ == "__main__":
    if len(sys.argv) < 3: 
        print(f"Usage: python3 {sys.argv[0]} <claimed-identity> [cert-file...]")
        exit(1)

    claimed_identity = sys.argv[1]
    cert_files = sys.argv[2:]
    
    cert_data = validate_certificate_chain(
        cert_files=cert_files,
    )

    print("Certificate has a valid signature from {}".format(ISSUER_NAME))

    challenge_file = input("Enter a name for a challenge file: ")
    print(f"Generating challenge to file {challenge_file}")

    challenge_bytes = os.urandom(32) 
    with open(challenge_file, 'wb+') as f: 
        f.write(challenge_bytes)

    response_file = input("Enter a name of the response file: ")
    with open(response_file, 'rb') as f: 
        response_bytes = f.read() 

    verify_identity(
        identity=claimed_identity,
        certificate_data=cert_data,
        challenge=challenge_bytes,
        response=response_bytes,
    )

    print(f"[+] Identity validated. You really are {claimed_identity}.")