You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds `http_response::unauthorized(digest_challenge)` factory and a
dispatch-time switch on `body_kind::digest_challenge` that routes
through libmicrohttpd's `MHD_queue_auth_required_response3`, so the
authoritative `WWW-Authenticate: Digest ...` challenge with
nonce/opaque/algorithm/qop is written into the wire response. The
legacy `unauthorized("Digest", realm, body)` overload remains
source-compatible; its Doxygen now points new code at the new overload.
Key pieces:
* `digest_challenge` struct (`src/httpserver/http_response.hpp`,
HAVE_DAUTH-gated) — public RFC-7616 challenge-parameter container.
* `body_kind::digest_challenge` enumerator + `detail::digest_challenge_body`
subclass (`src/httpserver/body_kind.hpp`, `src/httpserver/detail/body.hpp`,
`src/detail/body.cpp`) — heap-allocated params inside a unique_ptr to
keep the body's inline footprint under the 64-byte SBO budget.
* `webserver_impl::queue_response_dispatching_kind` (`src/detail/webserver_request.cpp`) —
branches on `body_kind::digest_challenge` and calls
`MHD_queue_auth_required_response3` with the user-supplied params;
every other body kind goes through the standard `MHD_queue_response`
path.
* Per-`webserver_impl` opaque (`digest_opaque_`) seeded once at
construction from `std::random_device`, substituted when the
factory leaves the opaque field empty (RFC 7616 §5.10: opaque is an
identifier, not a secret).
* Validation: realm/opaque/domain/body fields are rejected with
`std::invalid_argument` on CR/LF/NUL (CWE-113 header injection).
DR-013 ("Digest auth simplified to static WWW-Authenticate challenge")
is marked Superseded by TASK-062 — the value-type/DR-005 constraint is
preserved by doing the kind-switch at dispatch time (the response
itself stays a movable value); only the dispatch path knows about
MHD_Connection. http-response.md updated to drop the
"non-RFC-compliant" sentence and point at the new overload.
Tests:
* New unit test `http_response_digest_factory_test.cpp` (12 tests):
status/kind/SBO/algorithm/opaque/domain round-trip + CR/LF/NUL
injection guard on each text field.
* New integ test `digest_challenge_format_test.cpp`:
spins up a webserver wired to digest_auth_random + nonce_nc_size,
hits with plain curl (no --digest), asserts the WWW-Authenticate
header carries every RFC 7616 §3.3-mandated token.
* `digest_resource` in `test/integ/authentication.cpp` rewritten to
emit the new `digest_challenge` body; `digest_auth` test flipped
from expecting 401/FAIL to 200/SUCCESS (AC1).
Acceptance criteria:
1. curl --digest negotiates: digest_auth test passes 200/SUCCESS.
2. New integ test pins WWW-Authenticate format against RFC 7616 §3.3.
3. Six placeholder integ tests now drive real nonce/opaque handshake
end-to-end (wrong-password arms still resolve to 401/FAIL but
through MHD_digest_auth_check3 validation).
4. libmicrohttpd's MD5/SHA-256 helpers remain the underlying primitive
(we delegate to MHD's nonce HMAC machinery; no crypto here).
5. Typecheck and lint gates pass (check-complexity, check-file-size,
check-warning-suppressions, check-duplication, check-hygiene).
6. Tests pass (digest_challenge_format and http_response_digest_factory
in isolation; the pre-existing `basic` cascade flake on
feature/v2.0 is unchanged).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: specs/architecture/04-components/http-response.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,7 +19,7 @@ The body subclasses (`detail::string_body`, `file_body`, `iovec_body`, `pipe_bod
19
19
20
20
**Interfaces:**
21
21
- Exposes (from PRD §3.5):
22
-
- Factories: `http_response::string(...)`, `::file(...)`, `::iovec(std::span<const httpserver::iovec_entry>)`, `::pipe(...)`, `::empty(...)`, `::deferred(...)`, `::unauthorized(scheme, realm, ...)`— all return `http_response` by value. **For the `"Digest"` scheme**, `unauthorized()` produces only a static `WWW-Authenticate: Digest realm="<realm>"` challenge; it does NOT set `nonce`, `opaque`, `algorithm`, or`qop`, which RFC 7616 §3.3 requires. The response provides no replay protection and will be rejected by strict RFC 7616 parsers. This is a known limitation of the value-typed response model (DR-013). Callers needing fully RFC-compliant Digest auth must call MHD APIs directly; use `"Basic"`for a compliant challenge.
22
+
- Factories: `http_response::string(...)`, `::file(...)`, `::iovec(std::span<const httpserver::iovec_entry>)`, `::pipe(...)`, `::empty(...)`, `::deferred(...)`, `::unauthorized(scheme, realm, ...)`, `::unauthorized(digest_challenge)`— all return `http_response` by value. The `unauthorized(digest_challenge)` overload (TASK-062, declared behind `HAVE_DAUTH`) produces a fully RFC 7616 §3.3-compliant `WWW-Authenticate: Digest …` challenge with `nonce`, `opaque`, `algorithm`, and`qop` parameters; the dispatch path detects `body_kind::digest_challenge` and routes through libmicrohttpd's `MHD_queue_auth_required_response3`, which drives the per-connection nonce state machine. The legacy `unauthorized("Digest", realm, body)` string overload remains for source compatibility and emits only `WWW-Authenticate: Digest realm="<realm>"`; for new code prefer the `digest_challenge` overload. See DR-013 (Superseded by TASK-062) for the supersession note.
23
23
- **`httpserver::iovec_entry`** is a library-defined POD declared in `<httpserver/iovec_entry.hpp>` (a dedicated public header installed alongside `http_response.hpp` and included by it): `struct iovec_entry { const void* base; std::size_t len; };`. It mirrors POSIX `struct iovec` exactly in layout but does not require `<sys/uio.h>` in any installed header. The internal dispatch path uses the user-supplied span to build a `struct iovec` array inside `iovec_body`. The implementation file `src/detail/body.cpp` carries `static_assert`s pinning the layout assumption: `static_assert(sizeof(iovec_entry) == sizeof(struct iovec))`, `static_assert(offsetof(iovec_entry, base) == offsetof(struct iovec, iov_base))`, `static_assert(offsetof(iovec_entry, len) == offsetof(struct iovec, iov_len))`. When the asserts hold, conversion is a `reinterpret_cast`; when they fail (a hypothetical platform with divergent layout), the build fails loudly at compile time and we fall back to memcpy. This keeps the public header free of system headers and makes the API uniformly available on platforms where `<sys/uio.h>` is not standard (e.g., MSVC builds).
24
24
- Fluent setters: `with_header`, `with_footer`, `with_cookie`, `with_status` — each has two ref-qualified overloads: `& → http_response&` (mutate-in-place on an lvalue) and `&& → http_response&&` (return the object by rvalue-reference for zero-copy rvalue factory chains, e.g. `http_response::string("body").with_header("X-Foo", "bar").with_status(201)`).
25
25
-`const` accessors: `get_header`, `get_footer`, `get_cookie` returning `string_view` (empty on miss; do not insert).
Copy file name to clipboardExpand all lines: specs/architecture/11-decisions/DR-013.md
+18-1Lines changed: 18 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
### DR-013: Digest auth simplified to static WWW-Authenticate challenge
2
2
3
-
**Status:**Accepted
3
+
**Status:**Superseded by TASK-062 (2026-06-04)
4
4
**Date:** 2026-05-04
5
5
**Context:** v1's `basic_auth_fail_response` and `digest_auth_fail_response` each produced their 401 challenges via dedicated response subclasses; `digest_auth_fail_response` in particular delegated to `MHD_queue_auth_required_response3`, which sets the full RFC 7616 nonce, opaque, algorithm, and qop fields and wires the challenge into libmicrohttpd's per-connection Digest state machine. v2.0 removes all `*_response` subclasses and the `get_raw_response` / `decorate_response` / `enqueue_response` virtuals in favour of the unified `http_response::unauthorized(scheme, realm, body)` factory. The factory returns an `http_response` by value with no MHD connection handle available, so it cannot call `MHD_queue_auth_required_response3` — that API requires a live connection pointer, which is incompatible with the value-typed response model.
Copy file name to clipboardExpand all lines: specs/tasks/M7-v2-cleanup/TASK-062.md
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,12 +8,12 @@
8
8
Make `http_response::unauthorized(...)` produce an RFC-7616-compliant `WWW-Authenticate: Digest …` challenge with `nonce`, `opaque`, `algorithm`, and `qop` parameters, and drive the matching nonce/opaque server-side state machine so strict clients negotiate a real Digest session. The current implementation (`src/httpserver/http_response.hpp:184-196`) is documented as a non-RFC-compliant stub that strict parsers reject.
9
9
10
10
**Action Items:**
11
-
-[] Audit current `http_response::unauthorized(...)` overloads and document the gap against RFC 7616 §3 (challenge format) and §3.4 (`Authorization` validation) in a short header comment.
12
-
-[] Add a nonce/opaque generator to `webserver_impl` (CSPRNG-backed, with replay/expiry tracking). Reuse the existing `dauth` plumbing where possible.
13
-
-[] Extend `http_response::unauthorized(...)` to accept (or auto-derive) `nonce`, `opaque`, `algorithm` (default `MD5`, support `SHA-256` and `SHA-256-sess`), `qop` (default `auth`).
14
-
-[] Wire the dispatch path to validate incoming `Authorization: Digest …` against the issued nonce/opaque pair and route to `dauth` handlers.
15
-
-[] Convert the six v2 digest placeholder integ tests (`test/integ/authentication.cpp:42-60, 245-613`) to drive the nonce/opaque state machine end-to-end. Coordinated with TASK-079 (test work).
16
-
-[] Update Doxygen on `unauthorized(...)` to remove the "non-RFC-compliant stub" disclaimer and add RFC references.
11
+
-[x] Audit current `http_response::unauthorized(...)` overloads and document the gap against RFC 7616 §3 (challenge format) and §3.4 (`Authorization` validation) in a short header comment.
12
+
-[x] Add a nonce/opaque generator to `webserver_impl` (CSPRNG-backed, with replay/expiry tracking). Reuse the existing `dauth` plumbing where possible. — *delegated to libmicrohttpd via `MHD_queue_auth_required_response3` (nonce HMAC keyed by `MHD_OPTION_DIGEST_AUTH_RANDOM`, replay window by `MHD_OPTION_NONCE_NC_SIZE`); opaque generated once at `webserver_impl` construction from `std::random_device`.*
13
+
-[x] Extend `http_response::unauthorized(...)` to accept (or auto-derive) `nonce`, `opaque`, `algorithm` (default `MD5`, support `SHA-256` and `SHA-256-sess`), `qop` (default `auth`). — *new `unauthorized(digest_challenge)` overload supports MD5 (default), SHA-256, SHA-512-256, qop="auth". SHA-256-sess deferred (out of v2 scope, see plan §7).*
14
+
-[x] Wire the dispatch path to validate incoming `Authorization: Digest …` against the issued nonce/opaque pair and route to `dauth` handlers. — *dispatch branches on `body_kind::digest_challenge` in `webserver_impl::queue_response_dispatching_kind`; existing `req.check_digest_auth(...)` validates the returning header.*
15
+
-[x] Convert the six v2 digest placeholder integ tests (`test/integ/authentication.cpp:42-60, 245-613`) to drive the nonce/opaque state machine end-to-end. Coordinated with TASK-079 (test work). — *`digest_resource` updated to emit the new `digest_challenge` body; all six tests now exercise the real handshake path end-to-end (the wrong-password tests remain 401/FAIL but now go through `MHD_digest_auth_check3` validation rather than failing because no nonce was issued).*
16
+
-[x] Update Doxygen on `unauthorized(...)` to remove the "non-RFC-compliant stub" disclaimer and add RFC references.
17
17
18
18
**Dependencies:**
19
19
- Blocked by: None
@@ -30,4 +30,4 @@ Make `http_response::unauthorized(...)` produce an RFC-7616-compliant `WWW-Authe
0 commit comments