The diagram below shows the Public Key Infrastructure (PKI) trust chain we will build. A local Certificate Authority (CA) generates a root private key and self-signed certificate. This CA then signs a server certificate used by a local HTTPS web server. Finally, the CA root certificate is imported into the browser's trust store so it recognises the server's identity and displays the green padlock.
First, create a dedicated folder for all your cryptography files — keys, certificates, and scripts.
crypto-lab in your home directory and immediately moves into it. The && means "run the second command only if the first succeeds".| Part | What It Does |
|---|---|
mkdir -p | Creates the directory; -p prevents errors if it already exists |
~/crypto-lab | ~ = your home folder. Creates /home/kali/crypto-lab |
&& | Runs the next command only if mkdir succeeded |
cd ~/crypto-lab | Changes your current working directory into the new folder |
~/crypto-lab$, confirming you are inside the project directory.A Certificate Authority (CA) needs a private key to sign certificates. This key is the foundation of your entire PKI trust chain — anyone who has this key can issue trusted certificates.
~/crypto-lab directory (check your terminal prompt).ca.key. RSA is an asymmetric encryption algorithm — it creates a mathematically-linked pair of keys (public and private). The 4096-bit size makes it extremely difficult to crack.| Part | What It Does |
|---|---|
openssl | The OpenSSL command-line tool for cryptographic operations |
genrsa | Generate RSA — tells OpenSSL to create a new RSA private key |
-out ca.key | Saves the generated key to a file named ca.key |
4096 | The key length in bits. 4096 bits is very strong (2048 is minimum recommended) |
Generating RSA private key, 4096 bit long modulus followed by dots and plus signs showing the key generation progress. When done, the file ca.key is created.ca.key with a size of approximately 3.2 KB. The -la flags show detailed info (-l) and include hidden files (-a).Now use the private key to create a self-signed certificate. This certificate identifies your CA and will be used to sign other certificates.
| Flag | What It Does |
|---|---|
req | The OpenSSL certificate request subcommand |
-x509 | Output a self-signed certificate instead of a certificate signing request (CSR). X.509 is the standard format for digital certificates |
-new | Generate a new certificate request |
-nodes | No DES — do not encrypt the private key with a passphrase (makes it easier for automated scripts to use) |
-key ca.key | Use the CA private key we just generated to sign the certificate |
-sha256 | Use the SHA-256 hashing algorithm for the certificate signature (secure and widely supported) |
-days 365 | The certificate is valid for 365 days (1 year) from today |
-out ca.crt | Save the certificate to a file called ca.crt |
-subj "/CN=LocalSecurityCA" | Sets the subject fields. CN = Common Name, which identifies this certificate as "LocalSecurityCA". This avoids interactive prompts |
ca.crt is now created. Run ls to confirm both ca.key and ca.crt exist.-noout flag prevents printing the raw base64, and head -20 shows only the first 20 lines.CN = LocalSecurityCA, Subject: CN = LocalSecurityCA (same because it's self-signed), Validity dates, and the SHA-256 signature algorithm.Now create a separate private key for the web server. This key is different from the CA key — the server uses its own key pair for TLS encryption.
| Part | What It Does |
|---|---|
genrsa | Generate a new RSA private key |
-out server.key | Save the key to server.key |
2048 | Key size in bits — standard for web server TLS certificates |
A CSR is a formal request asking the CA to sign a certificate for the server. It contains the server's public key and identity information.
localhost because our web server will run locally — this must match the domain name you'll access in the browser.| Flag | What It Does |
|---|---|
req -new | Create a new certificate signing request |
-key server.key | Use the server's private key (the CSR will contain the corresponding public key) |
-out server.csr | Save the CSR to server.csr |
-subj "/CN=localhost" | Set the Common Name to localhost. This must match the URL you'll visit in the browser, otherwise the browser shows a certificate mismatch error |
Now the CA signs the server's CSR, producing the final server certificate. This is the core of PKI — the CA vouches for the server's identity by signing its certificate.
| Flag | What It Does |
|---|---|
x509 -req | Process a certificate signing request (the CSR we just created) |
-in server.csr | Input: the server's CSR file |
-CA ca.crt | The CA certificate to use for signing |
-CAkey ca.key | The CA's private key to create the digital signature |
-CAcreateserial | Automatically create a serial number file (tracks issued certificates) |
-out server.crt | Save the signed server certificate to server.crt |
-days 365 | Certificate validity period: 365 days |
-sha256 | Use SHA-256 for the signature hash algorithm |
Signature ok and subject details. The file server.crt is created.server.crt was genuinely signed by the CA whose certificate is ca.crt. It validates the entire trust chain.server.crt: OK — this confirms the certificate chain is valid and the server cert was properly signed by the CA.ca.key, ca.crt, ca.srl, server.key, server.csr, server.crt.For the browser to trust your locally-signed server certificate, you need to import your CA's root certificate into the browser's trusted certificate store.
ca.crt file and click "Open".The diagram below shows the complete PKI certificate lifecycle pipeline: from generating the CA root key and self-signed certificate, to creating a server CSR, signing it with the CA, launching a TLS-enabled Python HTTPS server, and importing the CA root into the browser trust store.
Below is the Python HTTPS server script secure_server.py. We will explain each line individually, then show the complete combined script at the end.
# tells Python to ignore this line. This is a TLS-enabled HTTPS server using Python's built-in modules.HTTPServer (a basic web server class) and SimpleHTTPRequestHandler (handles incoming HTTP requests by serving files from the current directory). We'll wrap this with SSL to make it HTTPS.run_server that contains all the server setup logic. Using a function keeps the code organized and reusable.'localhost' means it only accepts connections from your own machine (127.0.0.1). 4433 is the port number — we use 4433 instead of the standard HTTPS port 443 because port 443 requires root/admin privileges.HTTPServer handles listening for connections, and SimpleHTTPRequestHandler serves files from the current directory. When someone visits the URL, they see a directory listing of files.ssl.PROTOCOL_TLS_SERVER constant tells Python to use the latest TLS protocol version for server-side connections. The context holds all the TLS configuration (certificates, keys, protocol settings).server.crt) and private key (server.key) into the SSL context. The certificate is sent to clients during the TLS handshake to prove the server's identity. The private key is used to decrypt data encrypted with the server's public key.wrap_socket() adds the encryption layer on top of the existing connection. server_side=True tells Python this is the server end of the connection (not a client).python secure_server.py.run_server() function to start the HTTPS server.secure_server.py and paste this code into it.
~/crypto-lab directory.nano secure_server.py# secure_server.py - Python TLS HTTPS Server Engine import http.server import ssl def run_server(): server_address = ('localhost', 4433) httpd = http.server.HTTPServer(server_address, http.server.SimpleHTTPRequestHandler) # Wrap socket with SSL context context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) context.load_cert_chain(certfile="server.crt", keyfile="server.key") httpd.socket = context.wrap_socket(httpd.socket, server_side=True) print("[STATUS] Local secure TLS server listening on https://localhost:4433...") httpd.serve_forever() if __name__ == "__main__": run_server()
[STATUS] Local secure TLS server listening on https://localhost:4433...https://localhost:4433 and press Enter.ca.key — Root CA RSA 4096-bit private keyca.crt — Self-signed root CA certificateserver.key — Web server RSA 2048-bit private keyserver.csr — Certificate signing request for the serverserver.crt — CA-signed server TLS certificatesecure_server.py — Python TLS HTTPS server scripthttps://localhost:4433openssl verify -CAfile ca.crt server.crt showing "OK"