Skip to content

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.

  1. Plain words, real ideas. Simple language, never dumbed-down concepts. Explain jargon on first use; collect terms in the glossary.
  2. No hype. No marketing adjectives, no "revolutionary," no promises about what something will do for the reader.
  3. Honest about limits. Say what the thing can't do. State uncertainty instead of smoothing it over. Validation notes include what remains unverified.
  4. No invented numbers. Benchmarks, defaults, and specs are either measured or qualified with "depends on your setup." If it wasn't tested, say so.
  5. Checkable. Commands are meant to be run, with "Check:" blocks showing what success looks like. Sources are linked, not asserted.
  6. Respect the reader's setup. Call out Windows/Mac/Linux differences. Don't assume a GPU, a budget, or prior experience.
  7. Safety isn't skipped. Real risks get stated plainly, not buried.
  8. Scoped. Each guide says what it covers and points elsewhere for the rest. No guide pretends to be the whole subject.
  9. 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 with openssl ca -name CA_root, pathlen:0;
  • a server certificate for server.example.test (SANs DNS: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.test only), 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 (including OPENSSLDIR), 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 -modulus vs rsa -modulus agreeing 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), the BEGIN PRIVATE KEY (PKCS#8) and BEGIN ENCRYPTED PRIVATE KEY headers, pkey -text for both keys (RSA head, EC in full), the encrypted key's three-prompt refusal ending "Could not read key", -passin ... -check answering "Key is valid", and -pubout producing the matching BEGIN PUBLIC KEY block.
  • Ch. 6: CSR creation with -subj, CSR read-back, SANs attached both ways — -addext on the command line and the shipped server-req.cnf, whose resulting CSR was read back with req -text | grep "Subject Alternative Name"; the self-signed certificate's subject==issuer, and both verification results: error 18 against the default store, OK against -CAfile itself.
  • Ch. 7: the whole openssl ca flow against the shipped ca.cnf: the signing transcript ("Signature ok", "Database updated"), the intermediate's subject/issuer, and openssl verify -CAfile root -untrusted inter server.cert.pem answering OK.
  • Ch. 8: live handshakes over loopback against openssl s_server -www on port 8443: the depth walk (2/1/0, each verify return:1), the presented chain block, New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384, Verify return code: 0 (ok), an HTTP GET answered HTTP/1.0 200 ok; the TLS 1.2 pin (Protocol: TLSv1.2, cipher ECDHE-RSA-AES256-GCM-SHA384); the forced mismatch — client -tls1_3 against the 1.2-only server — producing tlsv1 alert protocol version ... SSL alert number 70, with the misleading Verify return code: 0 (ok) preserved in the text as the trap it is; and openssl ciphers -v head.
  • Ch. 9: dgst -sha256 agreeing with sha256sum to the digit on the same file; dgst -sha256 -hmac and openssl mac agreeing on the same bytes (both quoted); the mac command's real refusal without -macopt digest: ("MAC parameter error" — reproduced, then run correctly); enc -aes-256-cbc round-trip, the verbatim *** WARNING : deprecated key derivation used. without -pbkdf2, silence with it, the Salted__ file header via od, and the wrong-password bad decrypt with 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 cmp on SHA-256 fingerprints (SAME-CERT); file identifying the DER; bundle assembly by cat and counted by grep "^-----BEGIN"; PKCS#12 export and -info (the full PBES2/PBKDF2/AES-256-CBC report, three certificate bags, shrouded keybag) plus a -clcerts -nokeys extraction; crl2pkcs7 -nocrl producing a .p7b whose two certificates openssl pkcs7 lists 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_chain to 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 -CAfile throughout), and the existence of ACME/Let's Encrypt automation, which the guide points to without operating.
  • One non-lab capture: chapter 8's s_client session against example.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, the SHA2-256(...) prefix in dgst output, and PKCS#12 defaults); the chapters say to check openssl version first, 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.