Writing Criteria
Every guide in this repo is written to the same standard. If you're adding or editing a guide — human or AI — hold it to these.
- Plain words, real ideas. Simple language, never dumbed-down concepts. Explain jargon on first use; collect terms in the glossary.
- No hype. No marketing adjectives, no "revolutionary," no promises about what something will do for the reader.
- Honest about limits. Say what the thing can't do. State uncertainty instead of smoothing it over. Validation notes include what remains unverified.
- No invented numbers. Benchmarks, defaults, and specs are either measured or qualified with "depends on your setup." If it wasn't tested, say so.
- Checkable. Commands are meant to be run, with "Check:" blocks showing what success looks like. Sources are linked, not asserted.
- Respect the reader's setup. Call out Windows/Mac/Linux differences. Don't assume a GPU, a budget, or prior experience.
- Safety isn't skipped. Real risks get stated plainly, not buried.
- Scoped. Each guide says what it covers and points elsewhere for the rest. No guide pretends to be the whole subject.
- Structure over prose. Tables, before/after examples, symptom → fix. Scannable beats essay.
Verification notes for this guide
Standard 3 and 4 applied to this guide,
specifically. This guide's subject is unusually
cooperative: everything it teaches runs on one
machine with no network and no privileges, so
the ledger below is short on excuses. OpenSSL
3.0.13 (the Ubuntu build) was on the build
machine, and every quoted output is a capture
from it. Working transcripts are kept with the
build (openssl-lab/transcripts/).
The lab. A complete miniature PKI was
generated for the guide and is regenerated by
the shipped examples/lab-setup.sh, which was
itself run fresh, end to end, in an empty
directory after being written, and reproduced
every result below (fresh keys, so fingerprints
differ between runs; the verification outcomes
are identical):
- a root CA (self-signed via
req -x509,-days 3650) and an intermediate CA signed by it withopenssl ca -name CA_root,pathlen:0; - a server certificate for
server.example.test(SANsDNS:server.example.test,IP:127.0.0.1) signed by the intermediate; - an expired twin, minted with
openssl ca -startdate 20240101000000Z -enddate 20250101000000Z— the method chapter 7 describes; its printed dates (notBefore=Jan 1 2024,notAfter=Jan 1 2025 GMT) are captured, not asserted; - a wrong-hostname certificate
(
other.example.testonly), and a self-signed certificate for the server name (req -x509 ... -addext); - chain bundles, a DER conversion, and a PKCS#12 export, all below.
Verified by running, per chapter:
- Ch. 3:
openssl version(3.0.13),version -a(includingOPENSSLDIR),help,list -commands,list -digest-algorithms(head),list -providers(default provider active). - Ch. 4: on the lab server certificate:
-subject,-issuer,-dates,-serial(1000),-fingerprint -sha256,-ext subjectAltName,-text(head, quoted trimmed), and the modulus match —x509 -modulusvsrsa -modulusagreeing to the digit on the same key pair. The root's subject==issuer is captured from the root certificate itself. - Ch. 5: RSA 2048 and EC P-256 generation
(
genpkey ... -pkeyopt), theBEGIN PRIVATE KEY(PKCS#8) andBEGIN ENCRYPTED PRIVATE KEYheaders,pkey -textfor both keys (RSA head, EC in full), the encrypted key's three-prompt refusal ending "Could not read key",-passin ... -checkanswering "Key is valid", and-puboutproducing the matchingBEGIN PUBLIC KEYblock. - Ch. 6: CSR creation with
-subj, CSR read-back, SANs attached both ways —-addexton the command line and the shippedserver-req.cnf, whose resulting CSR was read back withreq -text | grep "Subject Alternative Name"; the self-signed certificate's subject==issuer, and both verification results: error 18 against the default store,OKagainst-CAfileitself. - Ch. 7: the whole
openssl caflow against the shippedca.cnf: the signing transcript ("Signature ok", "Database updated"), the intermediate's subject/issuer, andopenssl verify -CAfile root -untrusted inter server.cert.pemansweringOK. - Ch. 8: live handshakes over loopback
against
openssl s_server -wwwon port 8443: the depth walk (2/1/0, eachverify return:1), the presented chain block,New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384,Verify return code: 0 (ok), an HTTPGETansweredHTTP/1.0 200 ok; the TLS 1.2 pin (Protocol: TLSv1.2, cipherECDHE-RSA-AES256-GCM-SHA384); the forced mismatch — client-tls1_3against the 1.2-only server — producingtlsv1 alert protocol version ... SSL alert number 70, with the misleadingVerify return code: 0 (ok)preserved in the text as the trap it is; andopenssl ciphers -vhead. - Ch. 9:
dgst -sha256agreeing withsha256sumto the digit on the same file;dgst -sha256 -hmacandopenssl macagreeing on the same bytes (both quoted); themaccommand's real refusal without-macopt digest:("MAC parameter error" — reproduced, then run correctly);enc -aes-256-cbcround-trip, the verbatim*** WARNING : deprecated key derivation used.without-pbkdf2, silence with it, theSalted__file header viaod, and the wrong-passwordbad decryptwith its exit code 1;rand -hex/-base64; base64 encode/decode round-trip. - Ch. 10: PEM→DER and DER→PEM with the
round-trip proven identical by
cmpon SHA-256 fingerprints (SAME-CERT);fileidentifying the DER; bundle assembly bycatand counted bygrep "^-----BEGIN"; PKCS#12 export and-info(the full PBES2/PBKDF2/AES-256-CBC report, three certificate bags, shrouded keybag) plus a-clcerts -nokeysextraction;crl2pkcs7 -nocrlproducing a.p7bwhose two certificatesopenssl pkcs7lists back. - Ch. 11: every failure produced for real,
not paraphrased, most of them twice (once
under
verify, once in a live handshake with-verify_return_error): error 20 (no issuer reachable, and the incomplete- chain variant — leaf served without-cert_chainto a client holding the root), error 18 (self-signed, both ways), error 19 (full chain presented to a client trusting nothing), error 10 (expired, both ways), error 62 (hostname mismatch under-verify_hostname, both ways — and the same certificate passing when asked for its own name).
Documented-only (not run):
- Nothing in the chapters' command surface
is documented-only. The lab, the failures,
and the live TLS sessions all ran on the
build machine, over loopback, with OpenSSL
3.0.13. Two framing statements rest on
general knowledge and are phrased as such
in the text: that browsers and operating
systems ship trust stores by decision (the
mechanism is demonstrated locally with
-CAfilethroughout), and the existence of ACME/Let's Encrypt automation, which the guide points to without operating. - One non-lab capture: chapter 8's
s_clientsession againstexample.com(outbound TLS from the build machine) — real, labelled in the text, and the guide does not depend on it. - Version drift: outputs are 3.0.13's.
OpenSSL 1.1.1 systems — still common — will
differ in some wording (notably
list, theSHA2-256(...)prefix indgstoutput, and PKCS#12 defaults); the chapters say to checkopenssl versionfirst, and no 1.1.1 machine was available to capture the differences.
Specimen discipline, for the record: every
name in the guide is example.test /
"Example Lab" stock, every key was generated
for the page, the one password used
(correct-horse) protects nothing real and
is called a specimen wherever it appears,
and the lab directory is disposable by
design.