Lesson 5 of 15 · 40 min
Certificates, Keys, and Your Own CA
Build a private certificate authority and one certificate per service with a short shell script, then open the files and read what is actually inside them.
Learning objectives
- Explain a certificate as a public key plus a name signed by a trusted issuer.
- Describe why a private key never has to travel to be useful.
- Generate a certificate authority and per-service certificates with openssl.
- Identify which generated files are secrets and which are safe to hand out.
What we are doing and what we are not
Lesson 4 ended with a requirement: a credential where the secret never leaves the machine that owns it.
This lesson builds those files and nothing else. Your Go and TypeScript code does not change at all. Lesson 6 switches on encryption with them, and Lesson 7 uses them to replace the API key completely.
That order is deliberate. Certificates confuse people mostly because they meet them already wired into a framework. Spend thirty minutes with the raw files first and the rest of the course gets much easier.
You need one extra tool
openssl. Check it with openssl version. It ships with macOS and almost every Linux distribution. On Windows, use WSL or Git Bash.
Start with something you already understand
Imagine a sealed letter from a king. Anyone can look at the wax seal. Nobody can make another one, because only the king owns the stamp that presses it.
Two things are true at once, and they are the whole idea:
- the seal proves the letter came from the king;
- looking at the seal teaches you nothing about how to make one.
A password fails this test badly. To prove you know the password, you have to say it — and now whoever heard it can say it too. The seal is better because checking it and making it are different abilities.
Certificates are that idea, in maths.
Two keys, and one rule
A key pair is two files created together at the same moment:
| File | Who has it | What it can do |
|---|---|---|
| Private key | One machine, forever | Make signatures |
| Public key | Anybody at all | Check signatures |
They are generated as a matched pair, and they cannot be made separately. The public key is derived from the private key; the reverse is not possible in any practical amount of time. That one-way relationship is what everything rests on.
The one rule
The private key makes signatures. The public key only checks them. Holding the public key does not let you forge anything.
Compare that with Lesson 3's API key, where one string did both jobs. Everyone who could check the key could also present it. Splitting that single ability into two is the entire upgrade.
What "signing" actually does
The word sounds vague, so here is the mechanism in three steps.
Step 1: shrink the message to a fingerprint. A hash function turns any amount of data into a short fixed-size value. The same input always gives the same output, and changing one single byte gives a completely different output.
"transfer 100 to alice" → 9f2b7c…
"transfer 900 to alice" → 4e10da… ← one character changed, unrecognisableStep 2: sign the fingerprint. The private key performs a mathematical operation on that fingerprint and produces a signature — a short blob of bytes.
Step 3: anybody checks it. Someone holding the public key hashes the message themselves, and asks the maths whether this signature matches this fingerprint and this public key. The answer is yes or no.
SIGNER (has private key) VERIFIER (has public key)
message message ─────► hash ──┐
│ │
hash signature ────► check ─┴─► yes / no
│ ▲
private key ──► signature ────────────┘Notice what never crosses the arrow: the private key. The verifier gets the message and the signature, and that is enough. This is why Lesson 4's whole list of weaknesses collapses.
A question worth asking now
"If the signature travels, why can an attacker not just reuse it?" Good instinct. The answer is that the signature only proves something about the exact data it covered. In Lesson 6 you will see that a TLS connection makes each side sign a value unique to that one conversation, so a captured signature is worthless on the next one.
What a certificate actually is
A signature proves "whoever made this holds the private key". It does not say who that is. You still need a name attached to a public key, and something making that pairing believable.
That is all a certificate does:
The definition to memorize
A certificate is a public key, plus a name, signed by someone you trust.
The "someone you trust" is a certificate authority, usually shortened to CA.
Anatomy of the file
Here is a certificate with the jargon labelled:
┌─────────────────────────────────────────────┐ │ Subject: CN=go-server │ ← the name being claimed │ Public key: 04:a3:1f:… │ ← the key that name owns │ Not before: 1 Aug 2026 │ ← validity window │ Not after: 30 Jul 2036 │ │ SAN: DNS:localhost, IP:127.0.0.1 │ ← addresses it may serve │ Key usage: digital signature │ ← what it may be used for │ Issuer: CN=secure-services-ca │ ← who vouched for all of it ├─────────────────────────────────────────────┤ │ Signature by the issuer over everything │ ← makes it tamper-evident │ above: 30:45:02:20:… │ └─────────────────────────────────────────────┘
Every field above the line is plain, readable data. The signature below the line covers all of it, which means changing a single character of the name invalidates the whole file. That is why a certificate can be handed to strangers safely: it is public information that nobody can edit.
The chain of trust
Why believe the issuer? Because you decided to, once, deliberately.
CA certificate (secure-services-ca)
signed by itself ── you install this one and choose to trust it
│
│ signs
▼
go-server certificate
│
a client checks: "was this signed by a CA I already trust?"
yes → accept no → refuseTrust has to start somewhere. It starts at a file you install on purpose, called a root or trust anchor. Everything else is believed because the root vouched for it.
Your browser ships with roughly 150 of these preinstalled, which is how it can trust a website it has never visited. For traffic inside your own systems you want none of those — you want exactly one issuer, yours. So you are about to create it.
Why this matters more than it looks
Every CA in your trust store can create a certificate for any name. Trusting 150 organizations means any one of them, or anyone who compromises one of them, can produce a certificate your service will believe. Inside your own network you know your peers in advance, so trusting one issuer you control is strictly stronger.
Three file types you will see
| Extension | Contains | Secret? |
|---|---|---|
.key | A private key | Yes |
.crt | A certificate: public key plus name plus signature | No |
.csr | A certificate signing request: asking a CA to sign a public key under a name | No |
A CSR is worth one extra sentence, because it is the step people find strange. When a service wants a certificate, it generates its own key pair, keeps the private half, and sends the CA a request containing the public half and the name it wants. The CA never sees the private key. It only signs the request.
All three are PEM files, which means base64 text wrapped in -----BEGIN ...----- markers. You can open them in a text editor. They will look like noise, and later in this lesson you will decode one properly.
Write the certificate script
Certificates are generated by a handful of long openssl commands. Nobody memorizes them. Put them in a script so the whole thing is one reproducible step.
Create gen-certs.sh in your secure-services folder.
Part 1 — the preamble
set -euo pipefail means "stop immediately if anything fails". Put it at the top of every bash script you write; without it a failed command is silently skipped and later steps run on missing files.
cd "$(dirname "$0")" moves to the folder the script lives in, so it behaves the same no matter where you run it from.
Part 2 — create the CA
Decoding the flags:
| Flag | Meaning |
|---|---|
-x509 | Produce a finished certificate, not a signing request |
-newkey ec ... P-256 | Generate a new elliptic-curve key: modern, small, fast |
-nodes | "No DES" — do not password-protect the key file |
-keyout ca.key | Where to write the private key |
-out ca.crt | Where to write the certificate |
-days 3650 | Valid for ten years |
-subj "/CN=..." | The name. CN stands for common name |
CA:TRUE | This certificate is allowed to sign others |
2>/dev/null hides openssl's progress chatter. Remove it if a command fails and you want to see why.
Part 3 — one certificate per service
Two lines in that extension file are worth remembering.
extendedKeyUsage=serverAuth means "this certificate may be used to prove a server". A certificate declares what it is allowed to be used for, and software enforces it. In Lesson 7 you will add one word here, and that one word is most of the lesson.
subjectAltName=DNS:localhost,IP:127.0.0.1 lists the addresses this certificate is valid for. A client that dialed 127.0.0.1 checks this list and refuses if its address is missing. This is the check that produces the famous "certificate is valid for X, not Y" error.
chmod 600 means "only my user account may read this file". Always do that with private keys.
Run it
Your repository now looks like this:
secure-services/
├── certs/
│ ├── ca.crt
│ ├── ca.key
│ ├── go.crt
│ ├── go.key
│ ├── ts.crt
│ └── ts.key
├── gen-certs.sh
├── go.mod
├── main.go
└── server.tsKeep the keys out of version control
Create a .gitignore now, before you commit anything:
certs/The whole folder is regenerated by one command, so nothing is lost, and no private key ever reaches your history.
Look inside a certificate
A certificate is not a magic blob. Read one:
Your dates will differ; the structure will not.
- subject is who this certificate is about.
go-serveris the name Lesson 7 reads and logs. - issuer is who vouched for it. Since we created the CA ourselves, only software we configure will accept it.
- notAfter is when it stops working. Ten years.
Remember this number
Ten years is a very long time for a credential that sits in a folder. If one of these leaks you will not find out, and it stays valid until 2036. Lesson 11 replaces the whole approach with certificates that live sixty seconds. For now, notice the problem and keep going.
Compare it with the CA's own certificate:
Subject and issuer are the same. The CA signed its own certificate, which is what "self-signed root" means. Trust has to start somewhere, and it starts at a file you decide to believe.
Which files are secret?
| File | Secret? | If it leaks |
|---|---|---|
ca.key | Yes | Anyone can mint certificates your services trust |
ca.crt | No | Harmless. It is public by design |
go.key, ts.key | Yes | Someone can impersonate that service |
go.crt, ts.crt | No | Harmless. It is sent to every client anyway |
The pattern
.key files are secrets. .crt files are not. Certificates are meant to be handed out — that is their entire job.
ca.key is the most valuable file on this list by a wide margin. A leaked service key impersonates one service. A leaked CA key mints new identities forever, and every service in the system accepts them.
Common errors
| Error | Meaning | Fix |
|---|---|---|
command not found: openssl | openssl is not installed | Install it, or use WSL / Git Bash on Windows |
permission denied: ./gen-certs.sh | The script is not executable | chmod +x gen-certs.sh |
unknown option -addext | Very old openssl (1.0.x) | Upgrade to openssl 1.1.1 or newer |
Empty or zero-byte .crt files | An earlier command failed silently | Remove 2>/dev/null and rerun to read the real error |
Lesson 5 checkpoint
You are ready for Lesson 6 when you can explain all of these without rereading:
- what a certificate contains, in one sentence;
- why a private key never has to travel across the network;
- what a certificate authority is, and why you made your own instead of using a public one;
- which of the six generated files must stay secret;
- what
subjectAltNameis checked against; - what
extendedKeyUsage=serverAuthcurrently permits, and what it does not.
Next you will hand these files to your servers. Traffic becomes encrypted, the API key stops crossing the network in readable form, and you will find that the receiving service still has no idea who is calling.