Skip to content

Commit b94dd42

Browse files
committed
docs: Design spec for OWASP ASVS V12 self-assessment and CI gate
1 parent 932d7a4 commit b94dd42

1 file changed

Lines changed: 346 additions & 0 deletions

File tree

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
# OWASP ASVS V12 self-assessment + re-affirmation gate — design
2+
3+
## Goal
4+
5+
Make the library's TLS posture explicit and durable against silent drift.
6+
Three deliverables, one branch:
7+
8+
1. A self-assessed mapping of the library against **OWASP ASVS 5.0
9+
Level 2, chapter V12 (Secure Communication)**, committed at
10+
`docs/security/owasp-asvs.md`. Verbatim control text, per-control
11+
status, evidence linked to file paths.
12+
2. A README badge that names the standard and links to the checklist.
13+
3. A CI gate that fails any PR which changes TLS code without
14+
re-affirming the checklist (a dated audit-log entry).
15+
16+
The branch also extracts `TlsHttpsConfigurator` from a nested class on
17+
`OpenApiServer` into its own file under
18+
`com.retailsvc.http.internal`, so the gate's file-scope predicate is
19+
precise.
20+
21+
## Non-goals (v1)
22+
23+
- **No external authority involvement.** The badge says "self-assessed
24+
against ASVS 5.0 L2"; there is no OWASP certification body. Wording
25+
must never say "Compliant" or "Certified".
26+
- **No new TLS features.** The ASVS L3 controls (OCSP stapling, ECH)
27+
are noted in the checklist as "Future" but not implemented here.
28+
- **No cipher allowlist.** V12.1.2 stays at Partial — JDK defaults are
29+
used. Curating an allowlist is a follow-up.
30+
- **No outbound/client-side coverage.** V12.3 (service-to-service) is
31+
marked N/A — the library is server-side termination only.
32+
- **No mTLS.** V12.1.3 is N/A — same reason.
33+
- **No automated re-affirmation.** The CI gate enforces *presence* of
34+
an audit-log line; the line itself is written by the contributor.
35+
Tempting alternative (auto-bump dates) defeats the "stop and think"
36+
purpose.
37+
38+
## Public surface
39+
40+
This branch changes **no public API**. The TlsHttpsConfigurator
41+
extraction is internal-package only.
42+
43+
## Deliverable 1: `docs/security/owasp-asvs.md`
44+
45+
### File header
46+
47+
The file's first lines define scope and the immutable wording rule:
48+
49+
```markdown
50+
# OWASP ASVS 5.0 Level 2 — self-assessment
51+
52+
**Standard:** OWASP Application Security Verification Standard, version
53+
5.0.0, chapter V12 (Secure Communication).
54+
**Level:** 2 (typical baseline; consumers needing L3 must layer
55+
additional controls).
56+
**Scope:** Server-side TLS termination via `Builder.https(...)`.
57+
Outbound / service-to-service controls (V12.3) are N/A — the library
58+
makes no outbound TLS connections on the consumer's behalf.
59+
60+
**Wording rules.** This document is a self-assessment; it does NOT
61+
claim certification or compliance. The README badge says "ASVS 5.0
62+
Level 2" only — never "Certified", never "Compliant". OWASP does not
63+
issue conformance certifications.
64+
65+
**Re-affirmation rule.** Any change to TLS-related code (see
66+
`.github/scripts/asvs-gate.sh` for the exact predicate) MUST append a
67+
dated entry to the [Audit log](#audit-log) in the same PR. CI
68+
enforces this. The contributor writes the line — automated bumps are
69+
not accepted.
70+
71+
**Status legend.**
72+
- ✅ Implemented — the library satisfies this control end-to-end
73+
- 🤝 Delegated — consumer must satisfy; see Evidence for guidance
74+
- ⛔ N/A — out of scope for a server-side TLS termination library
75+
- 📋 Future — accepted as a gap; tracked for a follow-up release
76+
```
77+
78+
### Control table
79+
80+
Verbatim ASVS 5.0 V12 controls applicable to this library's scope:
81+
82+
| ID | Control (verbatim, ASVS 5.0) | Level | Status | Evidence |
83+
|---|---|---|---|---|
84+
| 12.1.1 | "Verify that only the latest recommended versions of the TLS protocol are enabled, such as TLS 1.2 and TLS 1.3." | L1 | ✅ Implemented | `TlsHttpsConfigurator` pins `setProtocols({"TLSv1.3","TLSv1.2"})`; verified end-to-end by `OpenApiServerHttpsIT#negotiatesTls13` |
85+
| 12.1.2 | "Verify that only recommended cipher suites are enabled, with the strongest cipher suites set as preferred." | L2 | 📋 Future | We rely on JDK 25 defaults (which exclude RC4, 3DES, EXPORT, NULL, anonymous suites by default). A curated allowlist with explicit preference order is tracked as a follow-up. |
86+
| 12.1.3 | "Verify that the application validates that mTLS client certificates are trusted before using the certificate identity." | L2 | ⛔ N/A | mTLS is not supported in v1. `TlsHttpsConfigurator` explicitly sets `setNeedClientAuth(false)` and `setWantClientAuth(false)`. If mTLS lands later, this row flips to Implemented + new evidence. |
87+
| 12.1.4 | "Verify that proper certification revocation, such as Online Certificate Status Protocol (OCSP) Stapling, is enabled." | L3 | 📋 Future | Out of scope for L2 baseline. Documented so L3-targeting consumers know the gap. |
88+
| 12.1.5 | "Verify that Encrypted Client Hello (ECH) is enabled in the application's TLS settings to prevent exposure of sensitive metadata." | L3 | 📋 Future | Out of scope for L2 baseline. JDK 25 has no stable ECH API. |
89+
| 12.2.1 | "Verify that TLS is used for all connectivity between a client and external facing, HTTP-based services, and does not fall back to insecure communications." | L1 | ✅ Implemented | When `.https(...)` is configured, the server binds `HttpsServer` only; no plaintext fallback listener is created. Mixed-mode (HTTP + HTTPS) is a documented non-goal — operators run two `OpenApiServer` instances if they need both. |
90+
| 12.2.2 | "Verify that external facing services use publicly trusted TLS certificates." | L1 | 🤝 Delegated | The library accepts whatever cert chain the consumer supplies. For production deployments, consumers MUST point `.https(certChain, privateKey)` at a chain signed by a publicly-trusted CA (Let's Encrypt is the recommended source, documented in README §HTTPS). |
91+
| 12.3.1–12.3.5 | (Service-to-service / outbound) | L2 | ⛔ N/A | The library does not initiate outbound TLS on the consumer's behalf. Consumers' outbound HTTP clients are their own responsibility. |
92+
93+
### Per-control supplementary notes
94+
95+
Each 🤝-Delegated row also gets a short prose paragraph after the
96+
table containing a code snippet showing how the consumer satisfies it.
97+
Example for 12.2.2:
98+
99+
> **12.2.2 satisfaction guidance.** Operators should point
100+
> `.https(certChain, privateKey)` at a chain issued by a publicly
101+
> trusted CA. The recommended workflow is certbot / Let's Encrypt
102+
> with the PEM files mounted from a secret manager (see README
103+
> §HTTPS for the full deployment pattern). The library performs no
104+
> chain validation on its own certificate — the server merely
105+
> presents the chain — so it is on the operator to ensure
106+
> publicly-trusted issuance.
107+
108+
Future-status rows do NOT get supplementary code. They get a
109+
one-sentence "accepted gap" note and a link target (filled in when a
110+
follow-up issue is opened; the link can be a literal placeholder
111+
`<issue TBD>` only if no issue exists yet — the gate does NOT
112+
require live links).
113+
114+
### Audit log section
115+
116+
```markdown
117+
## Audit log
118+
119+
- **2026-05-21** — Initial ASVS 5.0 Level 2 mapping for V12 controls (commit `<this commit SHA>`). All listed controls accepted as Implemented / Delegated / N/A / Future as tabulated above.
120+
```
121+
122+
The format is rigid and the gate parses it: `^- \*\*\d{4}-\d{2}-\d{2}\*\* — `. The bash regex used by the gate is `^[+]- \*\*[0-9]{4}-[0-9]{2}-[0-9]{2}\*\* — `.
123+
124+
## Deliverable 2: Refactor `TlsHttpsConfigurator` out of `OpenApiServer`
125+
126+
First commit on this branch. Pure refactor; tests stay green; no API
127+
change.
128+
129+
**Create:** `src/main/java/com/retailsvc/http/internal/TlsHttpsConfigurator.java`
130+
131+
```java
132+
package com.retailsvc.http.internal;
133+
134+
import com.sun.net.httpserver.HttpsConfigurator;
135+
import com.sun.net.httpserver.HttpsParameters;
136+
import javax.net.ssl.SSLContext;
137+
import javax.net.ssl.SSLParameters;
138+
139+
/**
140+
* Pins HTTPS to TLS 1.2 and 1.3 only, regardless of operator-level {@code java.security}
141+
* overrides, and explicitly leaves client-cert auth off (no mTLS in v1).
142+
*/
143+
public final class TlsHttpsConfigurator extends HttpsConfigurator {
144+
public TlsHttpsConfigurator(SSLContext context) {
145+
super(context);
146+
}
147+
148+
@Override
149+
public void configure(HttpsParameters params) {
150+
SSLParameters sslParams = getSSLContext().getDefaultSSLParameters();
151+
sslParams.setProtocols(new String[] {"TLSv1.3", "TLSv1.2"});
152+
sslParams.setNeedClientAuth(false);
153+
sslParams.setWantClientAuth(false);
154+
params.setSSLParameters(sslParams);
155+
}
156+
}
157+
```
158+
159+
**Modify:** `src/main/java/com/retailsvc/http/OpenApiServer.java`
160+
161+
- Delete the nested `private static final class TlsHttpsConfigurator`
162+
and its imports of `SSLParameters` / `HttpsParameters`.
163+
- Add `import com.retailsvc.http.internal.TlsHttpsConfigurator;`.
164+
- The single call site `new TlsHttpsConfigurator(sslContext)` is
165+
unchanged.
166+
167+
Visibility note: the constructor was implicitly private (nested in a
168+
public class with no modifier). After extraction it becomes `public`
169+
inside the `internal` package — same effective access for the only
170+
caller (`OpenApiServer`), which is in the parent package. The
171+
`internal` package is excluded from the published Javadoc and is not
172+
part of the API contract; this matches the existing precedent
173+
(`PemSslContext`, `Router`, `DispatchHandler`).
174+
175+
## Deliverable 3: CI gate
176+
177+
**Create:** `.github/workflows/asvs-gate.yml`
178+
179+
```yaml
180+
name: OWASP ASVS gate
181+
182+
on:
183+
pull_request:
184+
branches: [master]
185+
types: [opened, synchronize, reopened]
186+
187+
permissions:
188+
contents: read
189+
pull-requests: read
190+
191+
jobs:
192+
asvs-checklist:
193+
runs-on: ubuntu-latest
194+
steps:
195+
- uses: actions/checkout@v6
196+
with:
197+
fetch-depth: 0
198+
199+
- name: Enforce ASVS re-affirmation on TLS code changes
200+
env:
201+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
202+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
203+
run: .github/scripts/asvs-gate.sh
204+
```
205+
206+
**Create:** `.github/scripts/asvs-gate.sh` (bash, mode 0755).
207+
208+
Logic:
209+
210+
1. `git diff --name-only "$BASE_SHA" "$HEAD_SHA"` → changed file list.
211+
2. **TLS-relevance predicate** (OR-ed):
212+
- Path match: any changed file matches the regex
213+
`^src/main/java/com/retailsvc/http/internal/.*(Ssl|Tls|Https).*\.java$`.
214+
- Import diff: `git diff -U0 "$BASE_SHA" "$HEAD_SHA" -- 'src/main/java/**/*.java'`
215+
produces any line matching
216+
`^[+-]import (javax\.net\.ssl\.|com\.sun\.net\.httpserver\.Https)`.
217+
3. If predicate is false → `echo "ASVS gate: no TLS-relevant changes"; exit 0`.
218+
4. If predicate is true and `docs/security/owasp-asvs.md` is NOT in
219+
the changed file list → fail with the message in the brainstorming
220+
section. Print the triggering files.
221+
5. If predicate is true and the file IS changed, check the diff of
222+
`docs/security/owasp-asvs.md` for at least one added line matching
223+
`^[+]- \*\*[0-9]{4}-[0-9]{2}-[0-9]{2}\*\*`. If none → fail with a
224+
message explaining the audit-log line format and showing an example.
225+
6. On success → `echo "ASVS gate: TLS-relevant changes re-affirmed in
226+
docs/security/owasp-asvs.md"; exit 0`.
227+
228+
### Failure message format
229+
230+
Each failure path prints, to stderr:
231+
232+
```
233+
::error title=ASVS V12 gate::TLS-relevant code changed but docs/security/owasp-asvs.md was not updated.
234+
235+
Triggered by changes to:
236+
<one path per line>
237+
238+
Required action:
239+
1. Open docs/security/owasp-asvs.md
240+
2. Confirm each ASVS 5.0 L2 control still holds (update Status / Evidence rows if not)
241+
3. Append a dated line to ## Audit log, e.g.:
242+
- **2026-06-12** — Re-affirmed after change to PemSslContext.java (this PR); all controls hold
243+
244+
This gate exists so TLS changes can't silently drift away from the documented controls.
245+
See docs/security/owasp-asvs.md for the policy.
246+
```
247+
248+
The `::error::` annotation makes it surface in the PR Files-changed
249+
view next to the failing check, not just in the run log.
250+
251+
### Script testability
252+
253+
`asvs-gate.sh` runs cleanly outside CI for local pre-push validation:
254+
255+
```bash
256+
BASE_SHA=$(git merge-base origin/master HEAD) HEAD_SHA=HEAD .github/scripts/asvs-gate.sh
257+
```
258+
259+
Document this invocation in a short header comment at the top of the
260+
script. No bats / shunit2 framework dependency — the logic is shallow
261+
enough that one shell-level smoke test (run on the OWASP branch
262+
itself, which triggers all paths) is sufficient confidence.
263+
264+
## Deliverable 4: README badge
265+
266+
**Modify:** `README.md`.
267+
268+
In the existing badge block (between the Sonar/coverage badges and the
269+
workflow badge), insert one line:
270+
271+
```markdown
272+
[![OWASP ASVS](https://img.shields.io/badge/OWASP_ASVS_5.0-Level_2_V12-blueviolet)](docs/security/owasp-asvs.md)
273+
```
274+
275+
Hover text and the link target both point at the local file
276+
`docs/security/owasp-asvs.md`. Relative path so it works on GitHub
277+
web, raw clones, and IDE preview.
278+
279+
No other README changes in this branch. The HTTPS section already
280+
exists from the previous branch; references to ASVS within it are a
281+
nice-to-have but not required (and would couple the two docs more
282+
than necessary).
283+
284+
## File layout summary
285+
286+
```
287+
.github/
288+
scripts/
289+
asvs-gate.sh [new, executable]
290+
workflows/
291+
asvs-gate.yml [new]
292+
docs/
293+
security/
294+
owasp-asvs.md [new]
295+
README.md [modified — one badge line]
296+
src/main/java/com/retailsvc/http/
297+
OpenApiServer.java [modified — nested class extracted out]
298+
internal/
299+
TlsHttpsConfigurator.java [new]
300+
```
301+
302+
## Order of commits
303+
304+
1. `refactor: Extract TlsHttpsConfigurator to internal package`
305+
2. `chore: Add OWASP ASVS 5.0 L2 self-assessment for V12`
306+
3. `ci: Add ASVS re-affirmation gate for TLS code changes`
307+
4. `docs: Add OWASP ASVS badge to README`
308+
309+
Order matters: the refactor lands first so the gate (commit 3) can be
310+
authored against the new file layout. The badge lands last so the
311+
linked file already exists.
312+
313+
## Acceptance criteria
314+
315+
- `docs/security/owasp-asvs.md` exists, with verbatim ASVS 5.0 V12
316+
control text, statuses, evidence, audit log with an initial dated
317+
entry.
318+
- `src/main/java/com/retailsvc/http/internal/TlsHttpsConfigurator.java`
319+
exists; `OpenApiServer.java` no longer contains a nested
320+
`TlsHttpsConfigurator`; full test suite (`mvn verify`) is green.
321+
- `.github/workflows/asvs-gate.yml` runs on PR open / sync / reopen.
322+
- `.github/scripts/asvs-gate.sh` is executable, passes its own
323+
re-affirmation when run against the OWASP branch (which by
324+
definition both modifies a file in the import-grep predicate set
325+
*and* updates `docs/security/owasp-asvs.md`).
326+
- README badge renders and the link resolves on the GitHub web UI.
327+
- The OWASP branch passes its own gate when targeted at master.
328+
329+
## Out-of-scope follow-ups (post-v1)
330+
331+
- **Cipher suite allowlist** — flip 12.1.2 to Implemented; curated
332+
list of TLS 1.3 + TLS 1.2 suites with forward secrecy preferred.
333+
- **mTLS support** — flip 12.1.3 to Implemented; new builder method
334+
`.requireClientCert(TrustManager)`.
335+
- **OCSP stapling** — flip 12.1.4 (L3) to Implemented; requires
336+
`SSLEngine` customisation that JDK `HttpsServer` doesn't expose
337+
cleanly, likely needs a backend adapter (Jetty / Helidon Níma).
338+
- **L3 baseline** — once the L3 controls above land, document a
339+
parallel L3 self-assessment column or a separate L3-targeted
340+
checklist file.
341+
- **Cross-version mapping** — if auditors ask for ASVS 4.0.3 mapping,
342+
add a second column mapping each 5.0 control back to its 4.0.3
343+
predecessor.
344+
- **Multi-chapter coverage** — V11 (Cryptography) and V13
345+
(Configuration) have controls the library partially touches; out of
346+
scope for this branch which targets V12 only.

0 commit comments

Comments
 (0)