From 7af595b2cc9850e2df570b625896496ef516e863 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 14 Sep 2026 15:13:44 +0200 Subject: [PATCH] Specify candidate signed time samples and trust lifecycle Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a09cbc-910e-77a0-b336-3130194350b3 --- specs/protected-header-v0.1.schema.json | 23 ++++ specs/railiance-clock-v0.1.md | 3 + specs/sample-profile-v0.1.md | 148 ++++++++++++++++++++++ specs/time-request-v0.1.schema.json | 29 +++++ specs/time-sample-v0.1.schema.json | 63 +++++++++ specs/unsigned-fixtures.json | 26 ++++ workplans/RCLK-WP-0002-contract-review.md | 7 + 7 files changed, 299 insertions(+) create mode 100644 specs/protected-header-v0.1.schema.json create mode 100644 specs/sample-profile-v0.1.md create mode 100644 specs/time-request-v0.1.schema.json create mode 100644 specs/time-sample-v0.1.schema.json create mode 100644 specs/unsigned-fixtures.json diff --git a/specs/protected-header-v0.1.schema.json b/specs/protected-header-v0.1.schema.json new file mode 100644 index 0000000..42a33da --- /dev/null +++ b/specs/protected-header-v0.1.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "protected-header-v0.1 review candidate v0.1", + "type": "object", + "additionalProperties": false, + "required": [ + "alg", + "kid", + "typ" + ], + "properties": { + "alg": { + "const": "ES256" + }, + "kid": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}(?![\\s\\S])" + }, + "typ": { + "const": "railiance-clock-sample-v0.1" + } + } +} diff --git a/specs/railiance-clock-v0.1.md b/specs/railiance-clock-v0.1.md index e117fbc..b5711a8 100644 --- a/specs/railiance-clock-v0.1.md +++ b/specs/railiance-clock-v0.1.md @@ -198,3 +198,6 @@ dependencies, not placeholders to bypass while shipping. - Authenticated NTP option for infrastructure review (not v0.1 wire protocol): https://www.rfc-editor.org/rfc/rfc8915 - Corporate domain time: https://learn.microsoft.com/en-us/windows-server/networking/windows-time-service/how-the-windows-time-service-works - Internal ownership links: ../docs/ecosystem.md. + +Detailed wire/trust review candidate: [sample profile](sample-profile-v0.1.md). +This supplies proposed field/encoding choices; owner admission remains RCLK-WP-0002. diff --git a/specs/sample-profile-v0.1.md b/specs/sample-profile-v0.1.md new file mode 100644 index 0000000..7e3295f --- /dev/null +++ b/specs/sample-profile-v0.1.md @@ -0,0 +1,148 @@ +# Time sample profile — review candidate v0.1 + +Status: proposed under RCLK-WP-0002-T02/T03, 2026-09-14. No trust owner has +adopted this profile. It refines the base specification for review; conflicting +or unresolved requirements prevent implementation admission. + +## Transport and signature candidate + +HTTPS POST `/v1/time-samples` accepts the request schema, at most 1 KiB of UTF-8 +JSON, and returns at most 8 KiB of ASCII JWS Compact Serialization with +`Content-Type: application/jose`. No redirects, compression or detached payloads. +Set `Cache-Control: no-store` on responses. HTTP failure, wrong media type, +oversize or an unsigned error never establishes time. No bearer credential is +needed by this candidate sample request; endpoint access/exposure remains an +owner decision. TLS validation must already work through an admitted path. + +Choose JWS Compact Serialization as the candidate envelope: its protected header +and exact payload bytes are covered by the signature. Verify the original encoded +segments; never parse and reserialize before signature verification. +[Envelope standard: RFC 7515](https://www.rfc-editor.org/rfc/rfc7515). + +Candidate algorithm is `ES256`, restricted to P-256 keys and SHA-256, with the +JWS 64-byte R||S signature encoding. Use a maintained JOSE/crypto library, never +handwritten ECDSA. Owner/library compatibility review is still required. +[Algorithm definition: RFC 7518 §3.4](https://www.rfc-editor.org/rfc/rfc7518#section-3.4). + +Protected header contains exactly `alg`, `kid`, and +`typ: railiance-clock-sample-v0.1`. This typ is an application discriminator, +not a registered media type. Reject all other headers, including embedded keys, +remote key URLs, `crit`, and alternate payload encoding. No algorithm negotiation, +`none`, MAC profile, or token/key reuse. `kid` selects only within the locally +admitted authority/environment trust binding, never a global key search. + +JSON is UTF-8 without BOM, duplicate member names, nonfinite numbers or trailing +content. Enforce these at decoding, before a JSON Schema validator loses lexical +information. Limit nesting to 4. Base64url segments are unpadded, canonical and +nonempty; decoding and re-encoding must match. Fixed signatures decode to 64 bytes. +The signature binds bytes, so JSON member ordering is not a security ambiguity; +no additional JSON canonicalization scheme is required. Unknown fields/versions +are refused rather than interpreted through a newer schema silently. + +## Fields, units and correlation + +See the adjacent request, sample and protected-header schemas. The sample schema +applies to the decoded authenticated payload, not to the JWS envelope. + +- `profile` fixes wire semantics; `authority_id` and `environment` must equal + locally configured expectations and the outstanding request. +- `nonce` is 32 random bytes encoded as 43 base64url characters. The requester + generates it with a cryptographic RNG. Schema length alone cannot prove entropy + or canonical encoding; the decoder must also verify those requirements. +- `epoch` is an opaque continuity identifier. A UUID-shaped label is not a + monotonic generation, a freshness proof, or rollback authorization. +- `policy_id` names an immutable owner-adopted policy revision. Unknown revisions + are refused. The policy fixes leap handling, quality/error model, timer model, + trust freshness and local lifetime/RTT limits. +- `receive_unix_ns` and `send_unix_ns` are decimal strings of nonnegative signed + 64-bit POSIX nanoseconds since 1970-01-01T00:00:00Z. `error_ns` is a nonnegative + bound covering server send-time error, including acquisition resolution and + health observation aging. `max_age_ns` is a positive lifetime ceiling measured + from client request send, not from eventual response receipt. +- `quality` is `bounded` for candidate usable samples. No unhealthy sample with + a fabricated numeric bound is permitted. Diagnostics are a separate response. + A signed `bounded` assertion still requires the locally admitted policy/model. + +All decimal values must be <= 9223372036854775807, with no sign, exponent, decimal +point or leading zeros. Schema checks lexical shape only; implementations must +check numerical limits, send >= receive, and checked arithmetic separately. +There is no JWT `exp` evaluated against a misaligned local wall clock. + +POSIX nanoseconds do not encode a distinct leap second. This profile therefore +cannot claim protected use during a leap ambiguity: the admitted policy must +specify detection, exclusion and reacquisition behavior before deployment. +Ordinary RFC3339 display is derived from the accepted interval; display precision +is not an accuracy claim. Local timezone display conversion is separate. + +## Acceptance sequence and lifetime + +1. Before sending, require admitted transport/key/policy context and a supported + clock domain. Store nonce, request fields and monotonic send instant. Bound + outstanding requests and local timeout; limits come from local policy. +2. At receipt, capture monotonic receive time, reject domain/suspend changes and + expired requests, enforce envelope/JSON limits, select the pinned key and verify + signature/type/algorithm. Do not trust payload values until verification. +3. Validate the authenticated payload schema and semantic limits; match nonce, + authority/environment/profile, policy and continuity. Atomically retire the + nonce before installing the mapping. Only one response wins. Failed/expired + exchanges retire their nonce and retry with a new one. Resource exhaustion + may deny service but must never result in accepting a weaker sample. +4. Obtain conservative real elapsed bounds [Rmin,Rmax] from the reviewed timer + model. Accept the server-send error bound e only under admitted health policy. + At receipt use L=s3-e and U=s3+e+Rmax, as in the base specification. Checked + arithmetic must reject underflow/overflow. Server processing is not subtracted. +5. Age limits count conservative elapsed time since request send, including transit, + verification and any later processing. The smallest local, signed, trust and + policy lifetime limit applies. A slow response cannot restart the age budget. + Require the upper bound on age to be strictly below the applicable limit. +6. At each read, propagate from the receipt anchor by reviewed elapsed bounds + [Dmin,Dmax]: [L+Dmin,U+Dmax]. Check interval width, age, trust, continuity and + lifecycle before returning usable. The API does not execute a protected action; + consumers still check validity and action binding at the side effect. + +Arithmetic example, synthetic only: s3=1000000000 ns, e=2000000 ns and +Rmax=20000000 ns yield [998000000,1022000000]. A later elapsed interval +[9990000,10010000] yields [1007990000,1032010000]. These numbers establish no +production oscillator, RTT, uncertainty or freshness threshold. + +No protected holdover is admitted initially. Repeated reads of a live mapping +still require a proven bounded timer/lifecycle model and its finite age policy. +The schema alone cannot prove any of these operational assumptions. + +## Trust, epoch and restart rules + +The trust owner must supply an authoritative binding of authority/environment, +key ID, algorithm/public key, policy revision and validity/revocation state. +A signed sample cannot supply its own trust admission. Protected verification +requires independently admitted current trust evidence; if its freshness/validity +cannot be established with the available bootstrap context, return untrusted. +No use of the sample being evaluated to bootstrap its own key validity. + +Owner review must name the actual trust delivery/revocation mechanism and prove +its operation with wrong OS time. Until that exists, only isolated fixtures are +possible. HTTPS that cannot validate remains a bootstrap failure; never disable +certificate checks to obtain the first sample. + +Rotation requires the new key binding to be admitted before use. Revoke all +cached mappings signed by a revoked key; an old nonce/signature or code rollback +cannot restore it. Require a fresh exchange after process restart or epoch change. +Persisted rollback floors must have owner-approved integrity/recovery semantics. +If a new interval is wholly older than a trustworthy prior lower bound, refuse; +an overlapping interval does not prove continuity. Missing/corrupted persistent +state, restored VM snapshots or unknown epoch changes require reviewed recovery, +not automatic floor reset. The concrete storage/recovery design is still open. + +## Review evidence still required + +T02: signing library/custody compatibility; native trust bootstrap/revocation and +rotation; authoritative key bindings; epoch/floor recovery; leap policy adoption. +T03: measured server error model; Linux/Windows/WSL timer and suspend detection; +verified age/width thresholds; delayed/asymmetric path and arithmetic tests. +T04: consumer-specific validity boundaries, atomic consume/check-to-use and audit +attribution, including wrong-principal/tenant negatives. + +Schema fixtures test structural rejection only. They are unsigned, synthetic and +not a cryptographic, native transport, lifecycle or UTC-quality acceptance proof. +Future conformance must include duplicate JSON keys, noncanonical base64url, +wrong JWS key/algorithm/header, bad signature, replay, unknown policy, numerical +overflow, stale trust, delayed replies and suspend during verification. diff --git a/specs/time-request-v0.1.schema.json b/specs/time-request-v0.1.schema.json new file mode 100644 index 0000000..df9e982 --- /dev/null +++ b/specs/time-request-v0.1.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "time-request-v0.1 review candidate v0.1", + "type": "object", + "additionalProperties": false, + "required": [ + "profile", + "authority_id", + "environment", + "nonce" + ], + "properties": { + "profile": { + "const": "railiance-clock-v0.1" + }, + "authority_id": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}(?![\\s\\S])" + }, + "environment": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}(?![\\s\\S])" + }, + "nonce": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]{43}(?![\\s\\S])" + } + } +} diff --git a/specs/time-sample-v0.1.schema.json b/specs/time-sample-v0.1.schema.json new file mode 100644 index 0000000..1451860 --- /dev/null +++ b/specs/time-sample-v0.1.schema.json @@ -0,0 +1,63 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "time-sample-v0.1 review candidate v0.1", + "type": "object", + "additionalProperties": false, + "required": [ + "profile", + "authority_id", + "environment", + "nonce", + "epoch", + "policy_id", + "receive_unix_ns", + "send_unix_ns", + "error_ns", + "max_age_ns", + "quality" + ], + "properties": { + "profile": { + "const": "railiance-clock-v0.1" + }, + "authority_id": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}(?![\\s\\S])" + }, + "environment": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}(?![\\s\\S])" + }, + "nonce": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]{43}(?![\\s\\S])" + }, + "epoch": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}(?![\\s\\S])" + }, + "policy_id": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}(?![\\s\\S])" + }, + "receive_unix_ns": { + "type": "string", + "pattern": "^(0|[1-9][0-9]{0,18})(?![\\s\\S])" + }, + "send_unix_ns": { + "type": "string", + "pattern": "^(0|[1-9][0-9]{0,18})(?![\\s\\S])" + }, + "error_ns": { + "type": "string", + "pattern": "^(0|[1-9][0-9]{0,18})(?![\\s\\S])" + }, + "max_age_ns": { + "type": "string", + "pattern": "^[1-9][0-9]{0,18}(?![\\s\\S])" + }, + "quality": { + "const": "bounded" + } + } +} diff --git a/specs/unsigned-fixtures.json b/specs/unsigned-fixtures.json new file mode 100644 index 0000000..4accc3c --- /dev/null +++ b/specs/unsigned-fixtures.json @@ -0,0 +1,26 @@ +{ + "request": { + "profile": "railiance-clock-v0.1", + "authority_id": "fixture-authority", + "environment": "isolated-test", + "nonce": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "header": { + "alg": "ES256", + "kid": "fixture-key", + "typ": "railiance-clock-sample-v0.1" + }, + "sample": { + "profile": "railiance-clock-v0.1", + "authority_id": "fixture-authority", + "environment": "isolated-test", + "nonce": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "epoch": "fixture-epoch", + "policy_id": "fixture-policy-1", + "receive_unix_ns": "999000000", + "send_unix_ns": "1000000000", + "error_ns": "2000000", + "max_age_ns": "100000000", + "quality": "bounded" + } +} diff --git a/workplans/RCLK-WP-0002-contract-review.md b/workplans/RCLK-WP-0002-contract-review.md index 01c0423..49f75c5 100644 --- a/workplans/RCLK-WP-0002-contract-review.md +++ b/workplans/RCLK-WP-0002-contract-review.md @@ -64,3 +64,10 @@ Review started 2026-09-14: `docs/identity-and-utc-review.md` pins the IAM/layer source baseline, separates actors/workloads/permissions/Ansible artifacts, and identifies source-selection, leap and error-bound gates. Owner assent and measured policy remain pending; no review task is complete from the proposal alone. + +Sample/trust candidate added: `specs/sample-profile-v0.1.md`, three closed +JSON schemas and unsigned fixtures. Schema validation: three positive fixtures +and twenty structural negatives; no crypto/lifecycle/UTC acceptance claimed. +Trust delivery, measured timer/error policy, leap and rollback recovery remain +owner review gates. Custodian response 03014307-20b6-47d3-81f4-7f12fcaed014 +confirms discovery only and does not close admission tasks.