Repository navigation
Signed device redemption A1: P-256 ECDSA verify + ES256 APNs provider token - #11588
gunbai-bot[bot] wants to merge 24 commits into
Conversation
…pp Attest, ECDSA P-256 The protocol between the roadmap server and the operator's phone for redeeming an approval with a biometric-gated device signature, modeled before any server route or Swift. iOS is the V1 realization; Android is modeled at every platform point (FCM, Keystore key attestation) and realized by nothing yet. - extdeps.crypto.signature: ECDSA P-256/SHA-256 interface (FIPS 186-5), one wire encoding per key and signature, sole_constructor evidence naming its message. - extdeps.apple.apns / secure_enclave / app_attest, extdeps.google.fcm, extdeps.android.key_attestation: cited upstream shapes. - gunbc.auth.approval_device_redemption: push is an opaque wake-up only; the app fetches the stored request, signs over a server challenge, the stored request text, the verb and the capability; server owns decided_at and derives the login; signature REQUIRED on the device route; RedemptionIdentityEvidence names the legacy arms by mechanism. - Server routes, verification primitives, ApprovalTarget, cutover and Android are declared frontiers; the existing /approve route and store are untouched while the Mt. Collins first boot runs on them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…icts, route paths Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…Ns provider token Two v1 host primitives over RustCrypto p256 0.13 (digest 0.10, the family sha2 0.10 and hmac 0.12 already use), registered exactly as hmac_sha256_hex was (40fb5b9): 04_method.dag signature + infer_method mirror (--required-regen first_generation_equal=true), dispatch roster, interpreter arm, std.primitives contract + surface name + roster, v1_interpreter_primitive_surface row. - p256_ecdsa_verify_b64url(key_point_b64url, signature_b64url, message) -> Bool?: 65-octet SEC 1 uncompressed point, 64-octet r||s, unpadded base64url; the message is SHA-256 hashed by the verifier. Absent on any non-admitted encoding. - extdeps.crypto.signature p256_ecdsa_verify: decodes both inputs, hands the DECODED octet lengths to signature_verification_from_implementation. Three new arms keep absence from reading as a mismatch: VerifyingKeyUndecodable, SignatureUndecodable, SignatureEncodingRefused (right lengths, not a point on the curve / scalar in range). - es256_jwt_sign(p8_pem_secret, key_id, team_id, issued_at_epoch) -> String?: header {alg ES256, kid}, claims {iss, iat}, JOSE r||s, RFC 6979 deterministic. - extdeps.apple.apns apns_provider_token -> ApnsProviderTokenMint (Signed | AuthKeyUnreadable | IssuedAtBeforeEpoch). - Witnesses (test.claim.p256_ecdsa_witness_test): RFC 7515 A.3's PUBLISHED ES256 signature verifies; tampered message, tampered signature and wrong key are SignatureInvalid; the compressed spelling of the right key is VerifyingKeyMalformed{33}; an off-curve point is SignatureEncodingRefused; the provider token equals an exact expected JWT (independently verified with openssl) and verifies under the RFC public key; unreadable key and negative iat refuse. Rust unit tests beside the arms. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Re review 67577 (REQUEST_CHANGES): I checked both findings against this PR's own diff (
Fixing them here would fork #11585's modeling onto a second branch. The findings are forwarded to that PR's owner, who is already revising these seams (e.g. One point does fall to this lane: — sent from swift-ibex-621 |
…ective framing, App Attest current checks - AdmittedDeviceRedemption and VerifiedDeviceEnrollment are sole_constructor; the decision commit consumes only the admitted value, and the login comes from the code's issuance. - Enrolment code, enrolment and revocation are generations of ONE CAS slot, so consuming the code is creating the enrolment; expired, reused, other-bytes and Android refuse. - Every signed/MAC'd message is length-framed (code-point counts): injective for any field content, where the unit-separator join was not (capability_text itself contains it). - capability_tag_hex names the tag's real encoding. - App Attest: AppIdPrefix (not team id), validation category and bundle version refusals, seam-minted VerifiedAttestation/VerifiedAssertion carrying key, receipt and client data; redemption joins the assertion to the enrolled attest key and the exact signed bytes. - APNs: top-level custom-data carrier; a 200 without apns-id is undecodable. - Reads that return capabilities are assertion-authenticated; residual stated. - std.measure Second/ByteSize for APNs ceilings and signature sizes (review 67564). - Unconsumed declarations removed or given named consumers; Android attestation corrected; iOS realization frontier added; stale approval_push references fixed. - gunbc.auth.approval_device_redemption_fixtures renders the cross-language vectors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…named in their frontiers Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… split wire contract into approval_device_wire Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Rebased onto the revised #11585 seams (merge commits da8e3a6 and 33575e6). The sizes on the three refusal arms I added are now — sent from swift-ibex-621 |
…mption_fixtures regen) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Re review 67600:
— sent from swift-ibex-621 |
|
Re review 67617:
Both edits are held with the PR until the ruling is relayed. — sent from swift-ibex-621 |
…_realization_frontier (review 67617) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…itable, not validated (review 67625) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Re review 67625:
— sent from swift-ibex-621 |
|
Re review 67641: this is the same admission finding as reviews 67600 and 67617. It's escalated for an operator ruling, and the PR is held for it. The ruling will be recorded on I checked the one new point, — sent from swift-ibex-621 |
- Admission takes the signing input plus seam-minted verdicts; SignedRedemption, EnrolmentRequest, PresentedEnrollmentEvidence and PushRegistration are wire bodies in approval_device_wire, with their deferred consumer stated once; RedemptionIdentityEvidence deleted. - Capability tag carried in its canonical base64url spelling and converted through capability_tag_hex (refusing a non-canonical spelling); witnesses start from issue_capability and refuse a hex tag placed in the base64url field. - App Attest assertion result renamed AssertionAuthentic and scoped to the checks it makes; counter and challenge left to the consumer, whose replay authority is stated; stored public key joined. - APNs: only Apple's documented priorities (10, 5); push type scoped to alert. - Read transcript vectors and a discriminating witness; enrolment store (code/enrolment/revocation as one CAS slot) with record round-trip witnesses. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ane A) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ks already carry them Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…expectation typed - vectors.json is gunbc.generated_artifact ApprovalDeviceVectorsArtifact (new RepoConsumer CrossLanguageClientTest), so the required generated-artifact phase and the refusing merge driver gate it; the module's own regen/agree are deleted as a second route. - device_store_write takes std.durable_compare_and_set CasExpectation; the Int decode and its absorbing else are gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…7674) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… owner (side-chat condition) The issuer takes no login: issue_enrolment_code writes approval_operator_login. No HTTP issuer, since the loopback dashboard is reachable by every on-host POSIX user; the enrolment POST only consumes an already-issued code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # dag/gunbc/auth/approval_device_redemption.dag
|
CI on 307a21d: — sent from swift-ibex-621 |
…fact (from CI heal bundle) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…6_jwt_sign; the JWS is assembled in .dag (review 67702) The host kernel is only the P-256 signature. The APNs provider token's header, claims, base64url segments and compact form are now assembled from extdeps.languages.json.emit, a new extdeps.auth.jws (RFC 7515) and extdeps.crypto.signature p256_ecdsa_sign, and the token is typed as extdeps.auth.jwt JwtCompactSerialization. The unreachable issued_at < 0 check goes with the old primitive. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Re review 67702: both findings verified and fixed (head 7da0a5e).
Because the emitter serializes Re-run: — sent from swift-ibex-621 |
# Conflicts: # .gitattributes # .github/workflows/witnesses.yml # dag/gunbc/generated_artifact.dag # dag/gunbc/generated_artifact_emit.dag
|
Moved to draft. It will not merge as v1 host builtins. The operator ruled on 2026-09-18, relayed by the lane owner, verbatim: "basically if you're asking if we can use the rust escape hatch the answer is no - i wuld just take the chance to model them up front - you're just kicking the can down the road". The crypto is being modeled in .dag instead: bitwise emulation, a multi-limb bignum, SHA-256 and P-256 ECDSA, then RFC 6979, P-384, DER/X.509, CBOR and App Attest. It sits behind the same seams ( — sent from swift-ibex-621 |
|
Operator direction now forbids this v1/RustCrypto escape-hatch route. Keep this PR draft and non-landable. The replacement program is #11628 atop the modeled numeric/bit substrate #11627 and pure codec work #11629: implement SHA/HMAC/P-256/ES256 in |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Closing: this implements the approach the operator REFUSED. These PRs add P-256/ES256 (and App Attest) as v1 HOST PRIMITIVES in src/v1/stage0/src/v1_interpreter.rs -- the Rust escape hatch. The operator's ruling was explicit: 'if you're asking if we can use the rust escape hatch the answer is no - i would just take the chance to model them up front - you're just kicking the can down the road.' The replacement is #11645, which implements the same primitives in .dag (std.bitwise, std.bignat, extdeps.crypto.sha2, extdeps.crypto.nist_p256, std.bounded_nat) and DELETED these interpreter arms, their registrations and the Cargo edges in the same change. The model half of this work already landed separately in #11585 (extdeps.apple.apns, secure_enclave, app_attest), so nothing here is unique except the retired realization. Leaving these open is a hazard: they are large (+2896 / +4410), they touch files main already carries, and a future reader could merge a refused approach that reintroduces host crypto. Closing rather than leaving stale. If any specific piece here turns out not to exist in #11645 or #11585, reopen with that piece named. |
Approval app, lane A, part 1 of 2: the ECDSA P-256 verifier and the ES256 APNs provider token. App Attest is part 2 and will be a separate PR.
Stacked on #11585 (it contains that PR's commit through a merge). The base stays
mainso that CI runs on it. To review only this lane, diff againstsession/wise-owl-628, which is the last commit, d51fd6a.What landed
Two v1 host primitives over RustCrypto
p2560.13, which uses the same digest 0.10 family thatsha2/hmacalready use. Each is registered the wayhmac_sha256_hexwas in 40fb5b9:04_method.dagsignature and its infer_method mirrorstd.primitivescontract, surface name and roster entryv1_interpreter_primitive_surfacerowp256_ecdsa_verify_b64url(key, sig, message) -> Bool?is bound asextdeps.crypto.signaturep256_ecdsa_verify. It passes the decoded octet lengths tosignature_verification_from_implementation. Absence from the primitive never reads as a mismatch, because three arms cover it:VerifyingKeyUndecodable,SignatureUndecodableandSignatureEncodingRefused. The last one covers input with the right lengths that is not a point on the curve or has a scalar out of range.es256_jwt_sign(p8, kid, team, iat) -> String?is modeled asextdeps.apple.apnsapns_provider_token -> ApnsProviderTokenMint.Witness evidence (all were run, and the results below were read)
test.claim.p256_ecdsa_witness_test: all 11 test fns passed. They were run through a localgunbc rundriver that ANDs them, and it exited 0.Some(true)and the binary rebuilt. Exactlya_tampered_message_is_invalid,a_tampered_signature_is_invalidandthe_wrong_key_is_invalidwent red.openssl dgst -sha256 -verifyagainst the RFC public key, and a one-byte change to the signing input fails.--required-regen:first_generation_equal=true. The compile-clean result for the whole tree is left to CI.🤖 Generated with Claude Code