<aside> 🧭

Module 04 · CSRs & Self-Signed Certificates

Until now you have only read certificates other people made. This module is where you make your own — a signing request, a self-signed certificate, and finally a certificate you signed yourself. Along the way you will find out why the CA throws away most of what you put in your request, and why a certificate that passes every openssl and curl test can still be refused by a browser.

🧠 concept → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (answers hidden)

Prerequisite: Modules 01, 02 and 03. You need digital signatures (Module 01, B5), key files and PEM labels (Module 02), and every certificate field from Module 03 — especially SAN, basicConstraints and Key Usage.

</aside>


<aside> 📄

The picture to hold in your head for this whole module — applying for a passport.

You do not walk into the passport office and receive a passport. You fill in an application form.

On the form you write your name, attach your photo, and sign it yourself at the bottom — not to prove who you are, but to prove that you filled it in and nobody altered it on the way.

The office then does its own checks, ignores half of what you wrote, adds the things only it can add — the passport number, the issue date, its own stamp — and hands you a document that is not the form you submitted.

The form is a CSR. The passport is the certificate. Getting the difference between those two straight is most of this module.

</aside>

<aside> 🖥️

Set up a working directory before you start.

mkdir -p ~/tls-lab/m04 && cd ~/tls-lab/m04
umask 077
openssl version

Everything in this module runs offline. No network, no CA, no cost — which is exactly why it is the right place to experiment. Every command here is safe to run repeatedly.

All expected output in this module was produced on OpenSSL 3.0.13. If you are on OpenSSL 1.1.1 a few flags differ, and those differences are called out where they matter.

</aside>

Part A · The Certificate Signing Request

A1 · What a CSR is, and what is inside it

<aside> 📖

Official docs: RFC 2986 — PKCS #10 Certification Request Syntax · openssl req manual · RFC 5280 §4.1 — what a certificate has that a CSR does not

</aside>

A CSR carries exactly three things, and RFC 2986 says so in one sentence:

A certification request consists of a distinguished name, a public key, and optionally a set of attributes, collectively signed by the entity requesting certification.

In the CSR What it is Form analogy
Subject DN The name you are claiming — CN, O, C and so on Your details, handwritten
Public key The public half of the key you will use Your photo, stapled on
Attributes Optional extras — in practice, the extensions you would like, especially SAN The "other names" box
Signature Made with your own private key, over everything above Your signature at the bottom

<aside> 🔑

Now the more important list — what a CSR does NOT contain.

No validity dates. No serial number. No issuer. No AIA, no CRL distribution point, no Authority Key Identifier, no CT log receipts.

Every one of those is something only the CA can supply, because every one of them is a statement the CA is making, not a statement you are making. You cannot request a serial number any more than you can write your own passport number on the application form.

This is the cleanest way to remember the difference: a CSR is what you can say about yourself. A certificate is what someone else says about you.

</aside>

Before typing anything, it is worth knowing which of three artefacts you actually want. openssl req produces all three depending on the flags, which is why copied commands so often produce the wrong thing:

flowchart TD
    Q1{"Will a CA sign this,<br>or are you signing it<br>yourself?"}
    Q1 -->|"a CA will sign it"| Q2{"Do you already have<br>a private key?"}
    Q1 -->|"I am signing it myself"| Q3{"Is this a server cert,<br>or a CA cert?"}
    Q2 -->|"yes"| A["📝 CSR from existing key<br>req -new -key k.key"]
    Q2 -->|"no"| B["📝 CSR + new key<br>req -new -newkey rsa:2048 -noenc"]
    Q3 -->|"server"| C["📜 Self-signed leaf<br>req -x509 with<br>basicConstraints CA:FALSE"]
    Q3 -->|"CA"| D["🏢 Self-signed CA<br>req -x509 with<br>basicConstraints CA:TRUE"]
    A --> S["⚠️ ALL FOUR need<br>-addext subjectAltName<br>except the CA"]
    B --> S
    C --> S
    style A fill:#ffe6cc,stroke:#d79b00,stroke-width:2px
    style B fill:#ffe6cc,stroke:#d79b00,stroke-width:2px
    style C fill:#d5e8d4,stroke:#82b366,stroke-width:2px
    style S fill:#ffcccc,stroke:#cc0000,stroke-width:2px

<aside> 💡

One flag separates two completely different artefacts. -x509 present means you get a certificate; absent means you get a CSR. That is the whole difference, and it is why a command copied from the wrong tutorial produces something that looks plausible and is not what you needed.

The red box is the thing every path shares: a CA certificate does not need a SAN, because nothing connects to a CA by hostname. Everything else does.

</aside>

🧪 Exercise A1.1 — Make your first CSR

cd ~/tls-lab/m04

# A key first - the CSR is built around it (Module 02, A1)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out server.key

# Now the request
openssl req -new -key server.key -out server.csr \
  -subj "/C=MY/ST=Selangor/L=Kuala Lumpur/O=Zaeem Labs/CN=shop.example.com"

ls -l server.key server.csr
head -3 server.csr

🧪 Exercise A1.2 — Open the CSR up and confirm what is missing