Password Attacks & Cryptography · intermediate · ~12 min
**What you will learn** - Explain what a *digital signature* is and how a private key signs while the matching public key verifies, proving both authenticity and integrity — and why that is the opposite direction from encryption-for-secrecy. - Read a real X.509 certificate and identify its key fields: subject, issuer, validity dates, Subject Alternative Names (SANs), public key, and the CA signature. - Trace a certificate *chain of trust* from a leaf certificate up to a trusted root CA, and perform the four checks a client makes before trusting a server. - Walk through a simplified TLS 1.3 handshake and explain how it combines asymmetric authentication with a fast symmetric session key, and what *forward secrecy* buys you. - Detect and clearly report common TLS/PKI weaknesses (expired, self-signed, or hostname-mismatched certificates; weak protocol versions; weak ciphers; missing HSTS) — and, for each, apply a secure fix and *verify* it. - Inspect a certificate safely with `openssl` against an authorized localhost lab and interpret the output without falling for the myth that a lock icon means "safe."
Security objective. The asset this lesson protects is data in transit — passwords, session cookies, tokens, and payment or health data traveling across a network you do not control. The threat is anyone sitting on the network path (a hostile Wi-Fi access point, a compromised router, a malicious ISP) who wants to read that data (breaking confidentiality), change it silently (breaking integrity), or impersonate the server (breaking authentication). By the end you will be able to detect and prevent the everyday TLS misconfigurations that hand those capabilities to an attacker.
Every time you see the lock icon in a browser, TLS (Transport Layer Security, the modern successor to SSL) is doing three jobs at once: confidentiality (nobody on the network can read your traffic), integrity (nobody can silently modify it), and server authentication (you are really talking to the host you typed, not an impostor). TLS is the backbone of HTTPS, secure email, VPNs, and most machine-to-machine APIs.
This lesson builds directly on the prerequisite, Symmetric and asymmetric encryption. There you learned that symmetric crypto is fast but needs a shared secret, while asymmetric crypto (public/private key pairs) solves key distribution but is slow. TLS is exactly where those two ideas finally cooperate: asymmetric keys authenticate the server and agree on a secret, then symmetric keys encrypt the bulk traffic at high speed. TLS is the real-world payoff for everything in that prerequisite.
To get there we layer three concepts:
Once these click, the security work becomes concrete: read a certificate, judge whether a TLS configuration is safe, and explain why a finding like "expired certificate" or "TLS 1.0 enabled" matters to an engineer who has to fix it.
Authorization and ethics. Everything here is defensive. Only inspect or test TLS on systems you own or are explicitly authorized to assess — your own server, a localhost service, a lab VM, or a CTF target. Reading a certificate a server already presents publicly is fine; probing third-party hosts you do not own is not.
TLS protects the data that matters most: passwords, session cookies, payment details, health records, and API tokens. If TLS is misconfigured, all of that can be read or tampered with by anyone positioned on the network path.
The same building blocks reappear everywhere once you know them:
For defenders and testers, TLS and certificate misconfigurations are among the most routine findings in any authorized assessment: expired or self-signed certificates, hostnames that do not match, downgraded protocol versions, weak ciphers, and missing HTTP Strict Transport Security (HSTS). These are not exotic — they appear constantly, they are easy to detect, and they directly weaken confidentiality and authentication. Understanding why each is a problem, and being able to prove the fix works, is what separates copy-pasting a scanner result from writing a finding an engineer can act on. And remember: a clean scanner run does not prove a system is secure — it only proves the scanner did not flag anything it knows how to test.
Definition. A digital signature is a value computed from (a) a hash of the data and (b) a private key, such that anyone holding the matching public key can confirm the data is authentic and unmodified.
Plain language. Hashing turns any message into a short, fixed-size fingerprint. Signing scrambles that fingerprint with a key only the signer has. Verifying re-hashes the message and checks that the signature matches the fingerprint using the public key. Because only the private-key holder could have produced a signature that verifies, a valid signature proves who signed (authenticity) and that the data did not change (integrity).
How it works internally. Sign: signature = sign(privkey, hash(message)). Verify: recompute hash(message) and check verify(pubkey, signature, hash) == true. Note the direction: the private key signs, the public key verifies. This is the opposite of encryption-for-secrecy (where the public key encrypts and the private key decrypts), which is a frequent point of confusion.
When to use / not use. Use signatures whenever you need to prove origin and integrity but do not need secrecy — software updates, certificates, tokens, signed logs. Do not confuse a signature with encryption: a signed message is still readable by everyone. And a signature only proves the data matches a key; it says nothing about whether you should trust that key — that is what certificates are for.
Pitfall. A valid signature on the wrong key is worthless. If an attacker can get you to accept their public key as legitimate, every signature they make will verify. Trust in the key is the hard part.
SIGN (private key) VERIFY (public key)
----------------- -------------------
message --hash--> H message --hash--> H'
H --sign(priv)--> signature verify(pub, signature) --> H
H == H' ? -> trusted / rejected
Knowledge check. Which key signs and which key verifies? Why does that direction give you authenticity rather than secrecy?
Definition. A certificate is a signed document (format: X.509) that binds a public key to an identity (one or more hostnames) and is signed by a Certificate Authority (CA).
Plain language. A bare public key is anonymous — you have no idea whose it is. A certificate is a public key plus a vouching statement: "This key belongs to example.com, and I, the CA, attest to that." Because the CA signs the certificate, anyone who trusts the CA can trust the binding.
Structure. Key fields you will actually read:
| Field | Meaning |
|---|---|
| Subject | Who the certificate is for (the hostname / organization) |
| Subject Alternative Names (SANs) | The list of hostnames the cert is valid for (browsers use this, not the legacy CN) |
| Issuer | Who signed it (the CA) |
| Validity (Not Before / Not After) | The date window the cert is valid in |
| Public Key | The server's public key |
| Signature | The CA's signature over all of the above |
When to use / not use. Public-facing servers should use CA-issued certificates so any client trusts them automatically. Self-signed certificates are fine for internal/lab use where you control the clients, but a public site with a self-signed cert will (correctly) trigger a browser warning.
Pitfall. Relying on the legacy Common Name (CN) field for the hostname. Modern clients match the hostname against the SAN list. A cert with the right CN but missing SAN will be rejected by current browsers.
Knowledge check. A certificate's Subject says example.com but its SAN list contains only www.example.com. You visit https://example.com. Will it validate? Which of the four checks fails, and what asset does that check protect?
Definition. A chain of trust is the sequence of certificates linking the server's leaf certificate to a root CA your system already trusts, where each certificate is signed by the one above it.
Plain language. CAs do not sign every website with their precious root key. The root signs intermediate CAs, and intermediates sign the leaf certificates websites use. Your operating system or browser ships with a built-in trust store of root certificates. Validation means: follow the signatures up the chain until you reach a root you already trust.
Trust store (preinstalled roots)
|
[ Root CA ] <- self-signed, trusted by your OS
| signs
[ Intermediate CA ] <- signed by Root
| signs
[ Leaf: CN/SAN = example.com ] <- the server presents this (+ intermediate)
|
public key for example.com
The four validation checks. Before a client trusts the server, it confirms all of:
When to use / not use. Servers must send the leaf and any intermediate certificates so the client can build the chain. A common production outage is forgetting to bundle the intermediate: it works in your browser (which cached the intermediate) but fails for fresh clients.
Pitfall. Treating a trusted chain as proof the site is safe. It only proves you are talking to the holder of that hostname's private key over an encrypted channel. A phishing site can have a perfectly valid certificate for its own look-alike domain.
Knowledge check (find-the-bug). A server admin says "the cert works fine in my browser but mobile apps and curl say untrusted." The chain validates locally. What configuration mistake is most likely, and which log or command would confirm it?
Definition. The handshake is the opening exchange of a TLS connection that authenticates the server, agrees on cryptographic parameters, and derives a shared symmetric session key.
Plain language (TLS 1.3, simplified). The client offers the versions and ciphers it supports plus its key-exchange material. The server replies with its choice, its certificate, a signature proving it holds the matching private key, and its own key-exchange material. Both sides combine the exchanged material (an ephemeral Diffie-Hellman exchange) into the same secret, derive a symmetric session key, and from then on encrypt everything symmetrically.
Client Server
| --- ClientHello (versions, ciphers, key share) ---> |
| <-- ServerHello (chosen params, key share) -------- |
| <-- Certificate (+ chain) ------------------------- |
| <-- CertificateVerify (signature) ----------------- |
| <-- Finished -------------------------------------- |
| --- Finished (now both derive session key) -------> |
| ===== application data, symmetric encryption ====== |
How it works internally. Asymmetric crypto does the authentication (validate the certificate) and the key agreement (Diffie-Hellman). Once both sides share a secret, symmetric crypto (e.g. AES-GCM or ChaCha20-Poly1305) encrypts the bulk traffic because it is far faster. This is exactly the symmetric/asymmetric trade-off from the prerequisite, resolved in one protocol.
Forward secrecy. Because TLS 1.3 uses ephemeral Diffie-Hellman keys discarded after the session, recording the traffic today and stealing the server's private key tomorrow does not let an attacker decrypt past sessions. That property is forward secrecy, and it is a major reason older RSA key-exchange modes were removed.
When to use / not use. Always negotiate the highest mutually supported version (prefer TLS 1.3, accept TLS 1.2). Disable SSLv3, TLS 1.0, and TLS 1.1 — they have known weaknesses and are deprecated.
Pitfall. Assuming "TLS is on" equals "TLS is safe." A server can speak TLS while still allowing a downgrade to a weak version or cipher; an active attacker may push the connection toward the weakest option both sides accept.
Knowledge check (explain in your own words). Why does TLS bother with asymmetric crypto at all if symmetric encryption is faster — why not use symmetric for everything? And why must this experimentation only happen against a host you are authorized to test?
TLS itself is a wire protocol, not source syntax, so the practical "syntax" you work with is the openssl command line for inspecting certificates and connections. The two commands you will use most:
# Show the certificate a server presents, then print the parsed certificate.
# -servername sends SNI so virtual hosts return the right cert.
openssl s_client -connect localhost:8443 -servername localhost </dev/null \
| openssl x509 -noout -text
# Read a certificate file directly and show only the fields that matter most.
openssl x509 -in server.crt -noout -subject -issuer -dates -ext subjectAltName
Annotated breakdown of the key flags:
s_client act as a TLS client and connect
-connect H:P target host and port
-servername H send SNI (Server Name Indication) so the server picks the right cert
</dev/null close stdin immediately so the command exits after the handshake
x509 X.509 certificate tool
-noout do not re-print the raw base64 certificate
-text print all fields in human-readable form
-subject/-issuer/-dates print just those fields
-ext subjectAltName print just the SAN extension (the hostnames)
One more you will reach for when checking the chain and the verify result:
openssl s_client -connect localhost:8443 -servername localhost </dev/null 2>/dev/null \
| grep -E "Verify return code|Protocol|Cipher"
# 'Verify return code: 0 (ok)' means the chain validated against your trust store.
TLS (Transport Layer Security) secures most network traffic. It combines several cryptographic primitives into one trusted channel.
Signing means encrypting a hash of the data with a private key. Anyone can then verify it with the matching public key.
A valid signature proves two things:
This is how software updates, JWTs, and certificates are trusted.
How do you trust a server's public key? With a certificate.
A certificate binds a public key to an identity (a hostname). It is signed by a Certificate Authority (CA) — a trusted third party.
Your system trusts a set of root CAs. A server's certificate chains up to one of them.
The browser checks that:
Certificate Transparency logs every issued certificate publicly. This is useful for recon, as covered earlier.
So TLS gives you three guarantees from the networking track: confidentiality, integrity, and server authentication.
These are standard, real findings.
Below is a complete, reproducible local lab that generates a self-signed certificate, starts a local HTTPS server, and inspects the certificate with openssl. Everything runs on localhost only — no external hosts are touched. Treat this as the INSECURE-to-SECURE-to-VERIFY shape: the self-signed cert plus any weak config is the insecure baseline; the hardening block is the fix; the checks at the end are the verification.
Authorization checklist before you run any lab:
localhost), or a system you have written permission to test.trap does this).#!/usr/bin/env bash
# tls-lab.sh - generate a local cert, serve HTTPS on localhost, inspect the cert.
# Authorized-lab use only: this binds to localhost and uses a throwaway self-signed cert.
set -euo pipefail
WORKDIR="$(mktemp -d)" # isolated temp dir; cleaned up at the end
trap 'rm -rf "$WORKDIR"' EXIT # ensure cleanup even on error
cd "$WORKDIR"
# 1) Create a private key + self-signed cert valid for 1 day, with a SAN for localhost.
# -nodes: do not encrypt the key file (fine for a throwaway lab key, NEVER in prod).
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout server.key -out server.crt \
-days 1 -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost"
chmod 600 server.key # least privilege: only the owner may read the key
# 2) Inspect the certificate we just made: identity, issuer, validity, and SANs.
echo "=== Certificate summary ==="
openssl x509 -in server.crt -noout -subject -issuer -dates -ext subjectAltName
# 3) Start a local HTTPS server in the background using the cert.
# -no_ssl3 -no_tls1 -no_tls1_1 hardens it to TLS 1.2+ (the SECURE baseline).
openssl s_server -accept 8443 -cert server.crt -key server.key -www \
-no_ssl3 -no_tls1 -no_tls1_1 >/dev/null 2>&1 &
SERVER_PID=$!
trap 'kill "$SERVER_PID" 2>/dev/null; rm -rf "$WORKDIR"' EXIT
sleep 1 # give the server a moment to bind
# 4) VERIFY (accepts good): a normal client negotiates a modern protocol + cipher.
echo "=== Negotiated connection (expect TLS 1.3 + AEAD) ==="
openssl s_client -connect localhost:8443 -servername localhost </dev/null 2>/dev/null \
| grep -E "Protocol|Cipher"
# 5) VERIFY (rejects bad): forcing a deprecated version must fail the handshake.
echo "=== Forced TLS 1.1 (expect handshake failure) ==="
if openssl s_client -connect localhost:8443 -tls1_1 </dev/null >/dev/null 2>&1; then
echo "UNEXPECTED: weak TLS 1.1 was accepted"
else
echo "OK: TLS 1.1 rejected as intended"
fi
What it does. It builds a fresh RSA key and a self-signed certificate whose SAN is localhost, prints the certificate's identity fields, starts an openssl HTTPS server on port 8443 restricted to TLS 1.2+, connects as a client to confirm a modern protocol/cipher is negotiated (good input accepted), and finally forces TLS 1.1 to confirm the server refuses it (bad input rejected).
Expected output (values vary by OpenSSL version).
=== Certificate summary ===
subject=CN = localhost
issuer=CN = localhost
notBefore=...
notAfter=...
X509v3 Subject Alternative Name:
DNS:localhost
=== Negotiated connection (expect TLS 1.3 + AEAD) ===
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
=== Forced TLS 1.1 (expect handshake failure) ===
OK: TLS 1.1 rejected as intended
subject and issuer are identical — the signature of a self-signed certificate: nobody else vouches for it, so a normal browser would warn. The negotiated cipher is a modern AEAD suite, which is what you want to see, and the forced TLS 1.1 handshake fails, proving the hardening works.
Lab cleanup / reset. The two trap ... EXIT lines kill the background server and delete the temp directory (key + cert included) automatically when the script ends, even on error. If you ran the openssl s_server command by hand instead, stop it with kill %1 (or find it via pgrep -f 's_server') and delete the working directory. Verify nothing is left listening with lsof -i :8443 (should return nothing).
Key edge cases. If port 8443 is already in use the server fails to bind (pick another port). If you connect with a hostname not in the SAN list (say 127.0.0.1 instead of localhost), strict clients reject the certificate even though the connection is otherwise valid — that is the hostname-match check doing its job.
Walking through tls-lab.sh in execution order:
| Step | Line | What happens | State after |
|---|---|---|---|
| 1 | set -euo pipefail |
Abort on any error, unset variable, or failed pipe | Script is fail-fast |
| 2 | WORKDIR=$(mktemp -d) + trap ... EXIT |
Make an isolated temp directory and schedule its deletion | All artifacts live in one disposable dir |
| 3 | openssl req -x509 -newkey rsa:2048 -nodes ... |
Generate a 2048-bit RSA key (server.key) and a self-signed X.509 cert (server.crt) with CN=localhost and a localhost SAN |
A usable key/cert pair exists on disk |
| 4 | chmod 600 server.key |
Restrict the private key to owner-read/write only | Key is not world-readable (least privilege) |
| 5 | openssl x509 -in server.crt -noout ... |
Parse the cert and print subject, issuer, dates, and SAN | You can see subject == issuer (self-signed) |
| 6 | openssl s_server -accept 8443 ... -no_tls1 -no_tls1_1 & |
Start a TLS 1.2+ server in the background on port 8443 | A live HTTPS endpoint on localhost; SERVER_PID holds its PID |
| 7 | sleep 1 |
Wait for the socket to bind | Server is ready to accept |
| 8 | `s_client ... | grep -E "Protocol | Cipher"` |
| 9 | s_client -tls1_1 ... inside if |
Force a deprecated version; expect failure | Non-zero exit -> prints "OK: TLS 1.1 rejected" |
| 10 | trap fires on EXIT |
Kill the server and remove the temp dir | No leftover process or files |
What is happening cryptographically during step 8: the client sends a ClientHello with its supported versions and a Diffie-Hellman key share; the server replies with its choice, sends server.crt, and signs the handshake transcript with server.key to prove it owns the certificate's private key; both sides combine their DH shares into the same secret and derive the symmetric session key. The Protocol/Cipher lines you see are the result of that negotiation. The client does not reject the self-signed cert here because s_client does not enforce the trust store by default — a real browser would, which is exactly why self-signed certs warn.
Step 9 is the mitigation-verification half: by forcing -tls1_1, you ask the server to speak a protocol you deliberately disabled. A failed handshake is the success signal — it proves an attacker cannot downgrade the connection to a weak version even if they wanted to.
Mistake 1 - Confusing signing with encryption.
Wrong mental model: "Sign with the public key, verify with the private key."
Why it is wrong: that is backwards. The private key signs (only the owner can do it); the public key verifies (anyone can). Getting this backwards makes every later concept — certificates, JWTs, the handshake — incoherent.
Corrected: signature = sign(PRIVATE, hash) and verify(PUBLIC, signature). To remember it: the secret thing (private key) does the secret-only action (creating a signature). Recognize/prevent: if your explanation lets anyone create a valid signature, you have swapped the keys.
Mistake 2 - "There's a lock icon, so the site is safe."
Wrong: assuming a valid certificate means the site is trustworthy.
Why it is wrong: a valid certificate only proves you have an encrypted channel to the holder of that hostname's private key. A phishing site paypa1-secure.com can get a perfectly valid certificate for its own domain. TLS authenticates the server, not the intent.
Corrected: treat the certificate as proof of who you are talking to and that nobody can eavesdrop, then separately verify the hostname is the one you actually meant. Recognize/prevent: always read the actual domain, not the padlock.
Mistake 3 - "I decoded the JWT / the scanner passed, so it's verified/secure."
Wrong: pasting a token into a decoder to read its claims and calling it "verified," or treating a clean automated scan as proof of security.
Why it is wrong: decoding a JWT is just base64 — it does not check the signature at all; a forged token decodes identically to a real one. Likewise, a scanner only reports what it knows how to test; passing it does not prove the system is secure, and nothing is ever "completely secure."
Corrected: verify the JWT signature with the issuer's public key (and check exp, iss, aud); treat scanner output as a starting point, then manually confirm. Recognize/prevent: if no public key or secret was involved in your check, you did not verify anything.
Mistake 4 - Forgetting the intermediate certificate.
Wrong (server config): install only the leaf certificate.
Why it is wrong: clients need the full chain to reach a trusted root. Your browser may have cached the intermediate from another site and validate anyway, hiding the bug — but fresh clients (mobile apps, curl, other machines) fail with "unable to get local issuer certificate."
Corrected: configure the server to send the leaf plus the intermediate bundle. Recognize/prevent: test from a clean client — openssl s_client -connect host:443 and confirm Verify return code: 0 (ok).
Mistake 5 - Leaving weak protocols/ciphers enabled to "support old clients."
Wrong: enabling TLS 1.0/1.1 or export/RC4 ciphers for compatibility.
Why it is wrong: an active attacker can negotiate the connection down to the weakest option both sides accept, defeating the strong options entirely.
Corrected: restrict to TLS 1.2+ (prefer 1.3) and modern AEAD ciphers. Recognize/prevent: verify with openssl s_client -tls1_1 ... returning a handshake failure.
How to recognize these overall. Browser warnings ("Not secure", "NET::ERR_CERT_*"), a non-zero Verify return code from s_client, and scanner findings like "TLS 1.0 supported" or "missing HSTS header" all point at one of the above.
Common openssl s_client results and what they mean:
Verify return code: 0 (ok) — chain validated successfully.Verify return code: 21 (unable to verify the first certificate) or 20 (unable to get local issuer certificate) — the server is not sending the intermediate certificate, or the root is not in your trust store. Bundle the intermediate on the server.Verify return code: 10 (certificate has expired) — the current time is outside the validity window. Check notAfter and the server clock.handshake failure / no protocols available — you forced a version (e.g. -tls1_1) the server rejects. That is often the desired result when confirming weak protocols are disabled.Concrete debugging steps when HTTPS "doesn't work":
openssl s_client -connect host:port -servername host </dev/null and read the Verify return code line.openssl x509 -noout -subject -issuer -dates -ext subjectAltName. Check the hostname is in the SAN and the dates are current.Certificate chain block at the top of s_client output — you should see leaf -> intermediate -> (root).grep -E "Protocol|Cipher". Unexpectedly old? The server allows downgrades.-servername and watch it return the wrong (default) certificate — proof that SNI matters.Questions to ask when it fails:
This topic is itself a security control, so the "safety notes" are about deploying and assessing TLS defensively, and about what to log when you do.
Threat model (what TLS defends, and where it does not).
entry point: the network path (Wi-Fi, ISP, routers)
|
Client <==[ TLS channel ]==> Server
^ trust boundary 1: ^ trust boundary 2:
| client trust store | server private key + cert config
|
ASSETS protected: confidentiality, integrity, and server identity of
data in transit (passwords, cookies, tokens, payment data)
NOT protected by TLS: data at rest, application bugs (XSS/SQLi),
a malicious/look-alike server with its own valid cert, a stolen
private key (though forward secrecy limits damage to future sessions).
Defensive practices (beginner rules first):
Strict-Transport-Security) so browsers refuse to downgrade to HTTP.chmod 600), never commit it to source control, rotate if exposed.Insecure vs secure config.
Insecure config:
WARNING: intentionally vulnerable — use only in a local, isolated, authorized lab. Do not deploy.
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # TLS 1.0/1.1 are deprecated and downgradable
ssl_ciphers RC4:HIGH:!aNULL; # RC4 is broken
# (no Strict-Transport-Security header)
Why unsafe: an active network attacker can force the connection down to TLS 1.0 + RC4, weakening confidentiality; without HSTS, a victim's first HTTP request can be stripped to plaintext before any redirect.
Secure fix:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
Mitigation verification (prove the fix rejects bad and accepts good):
openssl s_client -connect localhost:8443 -tls1_1 </dev/null # expect: handshake failure (bad rejected)
openssl s_client -connect localhost:8443 -tls1_3 </dev/null # expect: Protocol TLSv1.3 (good accepted)
curl -sI https://localhost:8443 | grep -i strict-transport # expect: HSTS header present
Detection and logging. For each TLS connection or verification event, log: a timestamp, the source (client IP / identifier), the resource or hostname (SNI) requested, the result (handshake success/failure), the security decision (negotiated version + cipher, and the certificate verify code), and a correlation id to tie related events together. Alert on unusual downgrades (a client that suddenly negotiates TLS 1.0), spikes in expired-cert or verify-failure errors, and unexpected certificate changes for monitored hosts. Watch Certificate Transparency logs for certificates issued for your domains that you did not request.
Never log private keys, session keys, full request/response bodies (which may hold passwords or tokens), session cookies, or full PANs. Logging those turns your log store into the very secret an attacker wants.
False positives arise naturally: a legitimate client behind an old corporate proxy may negotiate a lower version; a monitoring probe may trip "repeated connection" alerts; a planned certificate rotation looks like an "unexpected certificate change." Tune thresholds and allowlist known scanners so real abuse is not buried in noise.
Concrete authorized real-world uses:
A typical authorized engagement: you are asked to assess your employer's public web endpoints. You inspect each certificate (SAN, dates, issuer, chain), check the negotiated protocols and ciphers against the company baseline, confirm HSTS is present, and write findings for anything weak — always with a fix and a retest step.
Professional best-practice habits.
Beginner rules:
Advanced habits:
includeSubDomains and consider preloading.Reporting a TLS finding. When you report one, use a finding template: title (e.g. "TLS 1.0 enabled on login endpoint"), severity (justified by exploitability, access required, and impact — not everything is critical; a downgrade needs an active on-path attacker, so it is usually medium, while a private-key exposure is critical), affected component, preconditions, safe reproduction (the openssl s_client command against the authorized host), evidence (the negotiated version/cipher output), impact, likelihood, remediation (the secure config), and retest (the verification commands that must now fail for weak options and succeed for strong ones).
Beginner 1 — Read a self-signed certificate.
Objective: generate a self-signed certificate for localhost and print its key fields.
Requirements: use openssl req -x509 -newkey rsa:2048 -nodes ... to create server.crt, then print subject, issuer, validity dates, and SAN.
Expected observation: subject and issuer are identical (self-signed).
Constraints: localhost/lab only; delete the key and cert when done.
Hints: add the SAN with -addext "subjectAltName=DNS:localhost"; inspect with openssl x509 -noout -text.
Concepts: certificate fields, SAN, self-signed certs.
Beginner 2 — Label the four validation checks.
Objective: for your certificate, list each of the four checks a client performs and state, for that cert, whether it passes.
Requirements: use your cert from task 1; reason about signature chain, validity dates, hostname match (for localhost), and revocation.
Expected output: a short table (check / pass-fail / why).
Hints: a self-signed cert chains only to itself, so it fails the "trusted root" check on a normal client.
Concepts: chain of trust, the four checks.
Intermediate 1 — Inspect a live, authorized endpoint.
Objective: run the lab server from the lesson and capture the negotiated protocol and cipher.
Requirements: start openssl s_server on localhost, connect with s_client -servername localhost, and extract the Protocol and Cipher lines.
Expected output: a TLS 1.3 protocol line and an AEAD cipher.
Constraints: localhost only; stop the server and clean up afterward.
Hints: pipe s_client through grep -E "Protocol|Cipher".
Concepts: handshake, version/cipher negotiation.
Intermediate 2 — Trigger and explain a hostname mismatch.
Objective: connect to your localhost cert using 127.0.0.1 as the name and observe/explain the verification difference.
Requirements: compare -servername localhost vs connecting by IP; describe why strict clients would reject the IP case and which of the four checks fails.
Expected output: an explanation tied to the SAN list.
Constraints: localhost/lab only.
Hints: the SAN only contains DNS:localhost, not the IP.
Concepts: hostname-match check, SAN.
Challenge — Build a weak-then-hardened config, verify, and report.
Objective: write two server configs — one intentionally weak (label it with the exact required WARNING line), one hardened — a verification script that proves the hardened one rejects weak protocols and the weak one accepts them, and a one-paragraph finding using the reporting template.
Requirements: hardened config restricts to TLS 1.2/1.3 + AEAD ciphers and sets HSTS; the script uses openssl s_client -tls1_1 (expect failure on hardened) and curl -sI ... | grep -i strict-transport (expect header). The finding must include a justified severity (not automatically critical) and a retest step.
Constraints: localhost/lab only; never deploy the weak config; clean up both configs and any running server.
Defensive conclusion: end by remediating (apply the hardened config) and verifying (the forced-weak handshake now fails).
Hints: a forced-version handshake that fails is the success signal for the hardened case.
Concepts: protocol/cipher hardening, downgrade resistance, HSTS, mitigation verification, finding template.
openssl s_client -connect host:port -servername host </dev/null | openssl x509 -noout -text; read the Verify return code to judge the chain.