Skip to content

App Attest step 1: CBOR, COSE_Key and WebAuthn authenticator-data decoders in .dag, claimed over a real Apple-issued attestation - #11970

Merged
gunbai-bot[bot] merged 6 commits into
mainfrom
session/nimble-eagle-216
Sep 21, 2026
Merged

gunbai-bot[bot] merged 6 commits into
mainfrom
session/nimble-eagle-216

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Step 1 of the iOS approval-app plan (thread msg_8d2b7301; decision msg_54c92f2f: A terminal, B filed, proceed on 1/2/4 + P-384 authoring). The pure byte decoders the App Attest verifier needs, in .dag, each an independently cited upstream module (DESIGN §3, external upstream decomposition):

  • extdeps.standards.rfc_8949 — CBOR, strict: definite-length only (indefinite refuses at its head naming the major type), major 7 with info 25..27 (floats) refuses, reserved info 28..30 refuses, integers ≥ 2^53 refuse before the multiply (the 64-bit host Int would otherwise overflow loudly first — found by the witness), nesting bounded at 16, one-document-consumes-all, unique text-key member read (duplicate key refuses, as json_object_unique_member does).
  • extdeps.standards.rfc_9052 — COSE_Key, exactly the EC2/P-256/ES256 shape, rendered as the SEC 1 uncompressed point extdeps.crypto.nist_p256 takes; duplicated labels refuse.
  • extdeps.standards.webauthn_authenticator_data — WebAuthn L3 §6.1: whole structure (AT → attested credential incl. COSE key, ED → extensions, trailing refuses) and the 37-octet prefix.
  • extdeps.apple.app_attest_sample_ios_14_4 — a real attestation object + assertion Apple's service issued to a development build (veehaitch/devicecheck-appattest src/test/resources/ios-14.4.yaml @ cb26211f, Apache-2.0), transcribed as octets (SHA-256 of both in the module) with the corpus's published facts (team id, bundle id, key id, clientDataHash, counter). Apple publishes no vector; this is the external authority for what the service emits.

What is established, by execution

Over Apple's bytes (test.claim.app_attest_sample_decode_witness_test): fmt apple-appattest; x5c = [761, 583]-octet DER; 3703-octet receipt; authData 164 octets with AT, appattestdevelop AAGUID (step 7), counter 0 (step 6), credentialId == key id (step 8), rpIdHash == SHA256(app_attest_app_id(team, bundle)) (step 5, joins extdeps.crypto.sha2), SHA256(COSE key as SEC 1) == key id (step 4). Assertion: 37-octet prefix, counter 1, 70-octet DER signature.

Upstream fact the sample surfaced, now modeled: Apple's assertion authenticatorData is 37 octets with the AT flag (0x40) set and nothing following — it violates WebAuthn's own layout, so the full decoder refuses it as truncated at offset 37 and assertions are read by decode_authenticator_data_prefix. Both arms are claimed so the prefix reader cannot be silently swapped for the full one.

Chain facts read from the sample's DER (for step 3's sizing): leaf is P-256 signed ecdsa-with-SHA256 by a P-384 CA key; CA 1 → root is P-384 / ecdsa-with-SHA384 (root PEM fetched from apple.com confirms secp384r1). So P-384 arithmetic is on the path for both chain links; SHA-384 for one.

Receipt (exact head 4a45ee7, binary /cargo-target/release/claim_batch sha256 b763d4dd…e7cae1)

claim_batch --source-root dag --source-root src/v2 --hermetic \
  --entry dag/test/claim/cbor_rfc8949_witness_test.dag --functions <18> \
  --entry dag/test/claim/cose_rfc9052_witness_test.dag --functions <4> \
  --entry dag/test/claim/app_attest_sample_decode_witness_test.dag --functions <6>
PASS 28 / FAIL 0

Costs by [witness] line: CBOR/COSE rows 60–3.3k eval steps; sample rows 2.2k–449k (the largest is SHA-256 of the 65-octet point). A first cut carried the fixture as base64 and paid ~3.7M steps per row on the transport encoding — that is why the fixture is octets.

Mutation controls (each restored by reverse edit, positives beside them):

  • webauthn_authenticator_data AT bit flags / 64 → flags / 32: the_sample_auth_data_carries_apples_facts FAIL, the_sample_attestation_decodes_to_apples_shape PASS.
  • rfc_8949 negative 0 - 1 - arg → 0 - arg: appendix_a_negative_decode FAIL, appendix_a_map_decode PASS.

Parse: v1_src_dag_parse clean on all seven files (the only errors it prints are pre-existing in dag/test/claim/fabric/fabric_storage_wire_witness_test.dag, on main). Bare-name sweep: six helper names collided with existing corpus declarations (ascii, entry, finish, refuses, decodes_to, well_formed) and were renamed before commit.

Consumption (§3c), stated honestly

rfc_8949 is consumed by webauthn_authenticator_data; all three decoders and the sample are consumed by the witnesses. Their production consumer — the fold that supplies attestation_verification_from_implementation / assertion_verification_from_implementation — is step 3 of the plan (declared frontier already on main: gunbc.auth.approval_device_redemption ecdsa_verification_realization_frontier, which names "CBOR decode, X.509 chain…" as the missing pieces; this PR lands the CBOR half). Step 2 (ASN.1 DER + RFC 5280 profile + pinned Apple root) follows as its own PR.

CI green is not evidence for these product-layer modules (required lanes do not resolve them); the receipt above is.

🤖 Generated with Claude Code

…thn authenticator-data decoders in .dag, claimed over a real Apple-issued attestation

The first of the pure byte decoders the App Attest verifier needs (extdeps.apple.app_attest
attestation_verification_from_implementation has no supplier on main). Three extdeps modules,
each cited: extdeps.standards.rfc_8949 (strict CBOR: definite-length only, floats and reserved
additional info refuse, integers beyond 2^53 refuse before the multiply, nesting bounded, unique
text-key member read), extdeps.standards.rfc_9052 (COSE_Key EC2 P-256 ES256 -> SEC 1 point),
extdeps.standards.webauthn_authenticator_data (the §6.1 layout, whole and 37-octet prefix).

The fixture is a real attestation object and assertion Apple issued to a development build
(veehaitch/devicecheck-appattest ios-14.4.yaml at cb26211f), transcribed as octets with its
published facts; over it the decoders establish Apple's steps 4, 5, 6, 7 and 8 from real bytes.
One upstream fact the sample surfaced: Apple's assertion authenticatorData is 37 octets with the
AT flag set and nothing following, so assertions are read as the prefix, never the full layout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title land iOS approval APP App Attest step 1: CBOR, COSE_Key and WebAuthn authenticator-data decoders in .dag, claimed over a real Apple-issued attestation Sep 21, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 21, 2026 15:25
@gunbai-bot
gunbai-bot Bot force-pushed the session/nimble-eagle-216 branch from a5530e9 to 4a45ee7 Compare September 21, 2026 15:45
…ther than re-matching CborValue (review 69585)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 21, 2026
…P-256 and P-384 as parameter rows, SHA-384, and the frontier retargeted to the decided shape

extdeps.crypto.nist_prime_curve carries the a = -3 Jacobian group law, Shamir double-scalar
ladder, partial public-key validation and ECDSA verification over a SUPPLIED digest, written
once (the formulas #11645 landed for P-256, moved not rewritten). extdeps.crypto.nist_p256 is
now its parameter row plus the SHA-256-bound verifier the device signature uses; the new
extdeps.crypto.nist_p384 is the second row, consumed by Apple's chain step, whose two links are
a P-384 key signing with SHA-256 and a P-384 key signing with SHA-384. extdeps.crypto.sha2 gains
SHA-384 (the SHA-512 computation with its own initial state, truncated) over std.bitwise Word64,
its constants derived from the definition rather than transcribed.

P-256 semantics preserved by execution: the RFC 6979 sample signature verifies and the
different-message control is false through the family fold (88 s / 86 s, 56M / 55M eval steps,
peak 15.5 GiB), and n.G = infinity holds. P-384 established on real bytes: Apple's CA 1 and root
keys are on the curve, a neighbouring point is not; the two chain-link verifications are enrolled
and NOT executed here (at 17 limbs they exceed this container's memory), the same disposition as
#11645's eight P-256 rows: a dedicated native gunbc test row is their executing evidence.

gunbc.auth.approval_device_redemption ecdsa_verification_realization_frontier no longer names
the host-Rust primitive the operator refused; it names the .dag readers landed by #11970/#11975/
this change, the verifier folds still owed, and the execution route decision (natively emitted
handler once MachineWidth<N> reification lands; interpreter cost is the v1-performance row).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ture's ByteSize authority; the two guard-shadowed zero defaults (CBOR argument octets, authenticator-data octets) now refuse as located truncations

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Review 69598 — both findings taken, in ff9a1e3: the COSE coordinate width is now derived from extdeps.crypto.signature signature_suite_public_key_size(EcdsaP256Sha256) ((65 − 1) / 2 as a ByteSize, refusal carries declared/expected ByteSize), and the two guard-shadowed zero defaults are gone: cbor_big_endian_value returns a CborArgument with a located CborArgumentTruncated { at } arm mapped to CborTruncated, and webauthn_authenticator_data reads through octet_at: Int? / big_endian_at: Int? with truncation refusals at the flags, counter and credentialId-length reads (the full decoder now shares the prefix decoder). All 28 step-1 witnesses re-run green at ff9a1e3.

— sent from nimble-eagle-216

…rived offsets (no bare 32/33), the truncation refusal carries ByteSize, and cose_integer_at answers Int? rather than a fabricated 0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Review 69674 — all three taken, in 199f273: the §6.1 widths are ByteSize rows (authenticator_data_rp_id_hash_size, _flags_size, _sign_count_size, _aaguid_size, _credential_id_length_size, and the derived _prefix_size), the flags and counter offsets are derived from them (no literal 32/33 anywhere), AuthenticatorDataTruncated carries needed/available as ByteSize; cose_integer_at returns Int? and its three consumers refuse CoseKeyLabelNotAnInteger on Absent. 28/28 re-run green at 199f273.

— sent from nimble-eagle-216

…ively (no in-band Int a caller could name a seventh of); CborTruncated carries needed/available as ByteSize; the two sample rows step 1 does not read are deleted and the clientDataHash row names its consumer (step 2's nonce claim)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Review 69692 — all three taken, in 2fdea11: AuthenticatorFlagBit is a six-arm sum with its §6.1 divisor per arm and flag_bit_set matches exhaustively (the else { flags / 128 } arm no longer exists); CborTruncated { needed: ByteSize, available: ByteSize } with the four truncation claims reading through byte_size_count; sample_client_data_b64 and sample_assertion_challenge_b64 deleted, sample_client_data_hash_b64 annotated with its named consumer (step 2's nonce claim in #11975). 28/28 green at 2fdea11.

— sent from nimble-eagle-216

…eyCoordinateNotAByteString (no zero-length measurement); the two TrailingOctets arms carry ByteSize; cbor_unsigned_value names its consumer (the verifier's category reader, #11989)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Review 69721 — all three taken, in 87539e5: CoseKeyCoordinateNotAByteString { label } is its own arm (the witness asserts it for a non-bstr y; a zero-length coordinate is now the only way to see declared: 0); CborTrailingOctets and AuthenticatorDataTrailingOctets carry consumed/total as ByteSize; cbor_unsigned_value is annotated with its named consumer — the verifier's validation_category_refusal in #11989 — since step 1 has no reader of it (the unused byte_size_count import is consumed by the same edit). 28/28 green at 87539e5.

— sent from nimble-eagle-216

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Sep 21, 2026
Merged via the queue into main with commit 5b10549 Sep 21, 2026
4 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/nimble-eagle-216 branch September 21, 2026 22:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants