openaliro
Aliro reader: UWB/CCC core and ESP32-S3/C5/C6 port
Loading...
Searching...
No Matches
aliro_stepup.h
1// Aliro step-up (Access Document) phase: builds the mdoc DeviceRequest, unwraps and decrypts the
2// SessionData DeviceResponse, decodes the CBOR document per spec 7.2/8.4.2, and runs the six-step
3// Access Document verification of spec 7.4. Reference-completeness codec + verifier; the verdict is
4// logged and stored, never gates the unlock (the provisioned trust store remains the sole gate).
5/*
6 * Copyright (c) 2026 asxeem
7 * SPDX-License-Identifier: ISC
8 *
9 * aliro_stepup — the Aliro §8.4 step-up phase: the ISO 18013-5 (mdoc) document
10 * exchange the Reader MAY run in the standard phase to obtain an Access or
11 * Revocation Document. Three concerns, split across two translation units so the
12 * wire-facing decoder fuzzes with no crypto dependency:
13 *
14 * aliro_stepup_parse.c pure CBOR decode + DeviceResponse structural parse
15 * (Table 7-1/7-2/8-22). No crypto, no allocation; every
16 * field is a bounds-checked slice of the caller's buffer.
17 * aliro_stepup.c the DeviceRequest builder, the ENVELOPE / GET RESPONSE
18 * APDU codec, the SessionData seal/open (reusing the
19 * aliro_secchan AES-256-GCM channel under StepUpSK), and
20 * the §7.4 verifier (digest recompute + validity + the
21 * issuer-signature check via an injected ES256 verify).
22 *
23 * The ES256 primitive is passed in (aliro_stepup_verify_ctx.ecdsa_verify) so this
24 * module carries no elliptic-curve dependency: the target wires the PSA-backed
25 * aliro_ecdsa_p256_verify, the host KAT injects its own.
26 *
27 * Provenance: original. Structures from the Aliro v1.0 spec (§7, §8.4, §14.6)
28 * and ISO 18013-5; the code is original.
29 */
30#pragma once
31
32#include <stddef.h>
33#include <stdint.h>
34
35#include "aliro_crypto.h" /* struct aliro_secchan */
36
37#ifdef __cplusplus
38extern "C" {
39#endif
40
41/* ---- document types (§7.7) ---- */
42#define ALIRO_STEPUP_DOCTYPE_ACCESS "aliro-a"
43#define ALIRO_STEPUP_DOCTYPE_REVOCATION "aliro-r"
44
45/* ---- StepUpSK-derived session keys (§8.4.3): HKDF-SHA256(empty salt, IKM =
46 * StepUpSK, info = "SKReader"/"SKDevice", L = 32). StepUpSK is block[64..95]. */
47#define ALIRO_STEPUP_SK_OFFSET 64u
48int aliro_stepup_derive_keys(const uint8_t block[ALIRO_KEY_BLOCK_LEN],
49 uint8_t sk_reader[ALIRO_SESSION_KEY_LEN],
50 uint8_t sk_device[ALIRO_SESSION_KEY_LEN]);
51
52/* Initialise a SessionData secure channel: enc = SKReader (reader->device),
53 * dec = SKDevice (device->reader). Counters start at 1 (aliro_secchan_init). */
54void aliro_stepup_channel_init(struct aliro_secchan *sc,
55 const uint8_t sk_reader[ALIRO_SESSION_KEY_LEN],
56 const uint8_t sk_device[ALIRO_SESSION_KEY_LEN]);
57
58/* ---- requester (§8.4.2) ----
59 * Build the fixed Access-Document DeviceRequest CBOR (Table 8-21): docType
60 * "aliro-a", namespace "aliro-a", requesting the given element identifiers
61 * (intent-to-retain true). With elems=NULL/n_elems=0, requests element2+element4
62 * (the §14.6 example request). Returns 0 and sets *out_len, or -1 on overflow. */
63int aliro_stepup_build_device_request(const char *const *elems, size_t n_elems, uint8_t *out,
64 size_t cap, size_t *out_len);
65
66/* Seal a DeviceRequest into a SessionData message {"data": bstr(ct||tag)} and
67 * advance the channel. Returns 0 and sets *out_len, or -1. */
68int aliro_stepup_seal_sessiondata(struct aliro_secchan *sc, const uint8_t *plain, size_t plain_len,
69 uint8_t *out, size_t cap, size_t *out_len);
70
71/* Open a SessionData message: unwrap {"data": bstr}, AES-256-GCM-open under the
72 * channel, and write the plaintext DeviceResponse. Returns 0 and sets *out_len;
73 * <0 on a malformed wrapper or a GCM tag mismatch. */
74int aliro_stepup_open_sessiondata(struct aliro_secchan *sc, const uint8_t *sd, size_t sd_len,
75 uint8_t *out, size_t cap, size_t *out_len);
76
77/* ---- ENVELOPE / GET RESPONSE APDUs (§8.4.4) ----
78 * ENVELOPE carries the SessionData in the command data field. chaining=1 sets
79 * CLA 0x10 (more blocks follow). Result = "CLA C3 00 00 Lc <data> 00". */
80#define ALIRO_INS_ENVELOPE 0xC3u
81#define ALIRO_INS_GET_RESPONSE 0xC0u
82int aliro_stepup_build_envelope(const uint8_t *data, size_t data_len, int chaining, uint8_t *out,
83 size_t cap, size_t *out_len);
84/* GET RESPONSE for `le` bytes (from the 61XX status word): "00 C0 00 00 <le>". */
85int aliro_stepup_build_get_response(uint8_t le, uint8_t *out, size_t cap, size_t *out_len);
86
87/* ---- parsed DeviceResponse (slices into the caller's buffer) ---- */
88#define ALIRO_STEPUP_MAX_DIGESTS 24u
89#define ALIRO_STEPUP_MAX_ITEMS 16u
90#define ALIRO_STEPUP_ID_MAX 32u
91
97 uint64_t id;
98 uint8_t hash[32];
99};
100
106 uint64_t digest_id;
107 const uint8_t
108 *tagged; /* the 24(bstr(IssuerSignedItem)) bytes, hashed for the digest check */
109 size_t tagged_len;
110 char elem_id[ALIRO_STEPUP_ID_MAX];
111};
112
122 int have_document; /* 0 = DeviceResponse carried no documents (device declined) */
123 int status; /* DeviceResponse "3" */
124
125 char doc_type[ALIRO_STEPUP_ID_MAX]; /* Documents[].docType ("5") */
126 char name_space[ALIRO_STEPUP_ID_MAX]; /* the single issuerSigned namespace */
127
128 /* IssuerAuth COSE_Sign1 [protected, unprotected, payload, signature]. */
129 const uint8_t *protected_hdr; /* content of the protected bstr */
130 size_t protected_len;
131 const uint8_t *kid; /* unprotected label 4, or NULL */
132 size_t kid_len;
133 const uint8_t *x5chain; /* unprotected label 33, or NULL (raw item bytes) */
134 size_t x5chain_len;
135 const uint8_t *payload; /* content of the payload bstr = 24(bstr(MSO)) */
136 size_t payload_len;
137 const uint8_t *signature; /* 64-byte r||s */
138
139 /* MobileSecurityObject (Table 7-1). */
140 char digest_alg[ALIRO_STEPUP_ID_MAX]; /* "SHA-256" */
141 char mso_doc_type[ALIRO_STEPUP_ID_MAX];
142 struct aliro_stepup_digest digests[ALIRO_STEPUP_MAX_DIGESTS];
143 size_t n_digests;
144
145 /* validityInfo tdates as parsed epoch seconds (UTC). */
146 int have_signed, have_valid_from, have_valid_until;
147 int64_t signed_epoch, valid_from_epoch, valid_until_epoch;
148 int have_iteration;
149 uint64_t iteration;
150 int time_verification_required;
151
152 /* disclosed IssuerSignedItems. */
153 struct aliro_stepup_item items[ALIRO_STEPUP_MAX_ITEMS];
154 size_t n_items;
155};
156
157/* Structural decode of a plaintext DeviceResponse (Table 8-22). CRYPTO-FREE and
158 * bounds-checked: this is the fuzz surface. Returns 0 on a well-formed document
159 * (which may still be have_document=0), <0 on malformed CBOR / limits exceeded. */
160int aliro_stepup_parse_response(const uint8_t *buf, size_t len, struct aliro_stepup_doc *doc);
161
162/* ---- verifier (§7.4) ---- */
163struct aliro_stepup_issuer {
164 const uint8_t *kid; /* matched against IssuerAuth kid */
165 size_t kid_len;
166 uint8_t pub[65]; /* P-256 uncompressed issuer public key */
167};
168
175 const struct aliro_stepup_issuer *issuers;
176 size_t n_issuers;
177 int time_valid; /* reader holds a trusted clock (timesync) */
178 int64_t now_epoch; /* current UTC seconds; used only when time_valid */
179 uint64_t access_iteration; /* stored AccessIteration for this issuer (default 0) */
180 const char *expected_doctype;
181 /* ES256 over msg (hashing internal), pub = 65-byte point, sig = 64-byte r||s.
182 * Returns 0 on a valid signature. Target: aliro_ecdsa_p256_verify. */
183 int (*ecdsa_verify)(const uint8_t pub[65], const uint8_t *msg, size_t msg_len,
184 const uint8_t sig[64]);
185};
186
194 int valid; /* all applicable steps passed AND >=1 valid element */
195 int reject_step; /* 0 = accepted; else the first §7.4 step that failed (1..6) */
196
197 int issuer_key_found; /* step 1 */
198 int issuer_chain_validated; /* 0 for x5chain: EE key used, chain NOT validated (ref limit)
199 */
200 int sig_ok; /* step 2 */
201 int digests_ok; /* step 3: every disclosed item matched its valueDigest */
202 int doctype_ok; /* step 4 */
203 int time_ok; /* step 5 */
204 int iteration_ok; /* step 6 */
205 size_t valid_elements; /* disclosed items whose digest verified */
206};
207
208/* Run §7.4 over a parsed document. Never gates access; fills *verdict for the
209 * caller to log/store. Returns 0 if verdict->valid, <0 otherwise (same info as
210 * verdict->reject_step; the return is a convenience, not an access decision). */
211int aliro_stepup_verify(const struct aliro_stepup_doc *doc,
212 const struct aliro_stepup_verify_ctx *ctx,
213 struct aliro_stepup_verdict *verdict);
214
215/* Convenience: open the SessionData response, parse, and verify in one call.
216 * Fills *verdict; returns 0 on verdict->valid, <0 on any decrypt/parse/verify
217 * failure (the worker logs the verdict regardless of the return). scratch must
218 * hold the decrypted DeviceResponse (>= sd_len). */
219int aliro_stepup_run(struct aliro_secchan *sc, const uint8_t *sd_resp, size_t sd_len,
220 const struct aliro_stepup_verify_ctx *ctx, uint8_t *scratch,
221 size_t scratch_cap, struct aliro_stepup_doc *doc,
222 struct aliro_stepup_verdict *verdict);
223
224/* ---- ESP worker seam (implemented per-platform; see aliro_stepup_worker.c) ----
225 * Copies the collected SessionData response + keys + verify inputs and runs
226 * aliro_stepup_run() off the BLE-host task, so parse/verify never touches the
227 * auth segment or the ranging arm window. Returns 0 if queued. */
228struct aliro_stepup_job {
229 uint8_t sk_reader[ALIRO_SESSION_KEY_LEN];
230 uint8_t sk_device[ALIRO_SESSION_KEY_LEN];
231 uint8_t issuer_pub[65];
232 uint8_t issuer_kid[16];
233 size_t issuer_kid_len;
234 int have_issuer;
235 int time_valid;
236 int64_t now_epoch;
237 uint16_t conn_handle;
238 size_t sd_len;
239 uint8_t sd[2048]; /* SessionData response (x5chain-cert headroom) */
240};
241int aliro_stepup_worker_submit(const struct aliro_stepup_job *job);
242
243/* Copy out the most recent verdict the worker produced (for `aliro-stepup
244 * status`). Returns 1 and fills *verdict (+ *conn if non-NULL) when one exists,
245 * 0 otherwise. Implemented in the per-platform worker. */
246int aliro_stepup_worker_last(struct aliro_stepup_verdict *verdict, uint16_t *conn);
247
248#ifdef __cplusplus
249}
250#endif
Step-up credential element digest: SHA-256 hash of a disclosed IssuerSignedItem, with its digest_id f...
Definition aliro_stepup.h:96
Parsed Step-up access document (ISO/IEC 18013-5 mDoc): have_document (0 if device declined),...
Definition aliro_stepup.h:121
Single disclosed IssuerSignedItem from a Step-up document: digest_id (which digest to check against),...
Definition aliro_stepup.h:105
Verdict of Step-up document verification (ISO/IEC 18013-5 §7.4): valid (all passed steps + >=1 valid ...
Definition aliro_stepup.h:193
Context for Step-up document verification: issuers (trust store), time_valid/now_epoch (clock state),...
Definition aliro_stepup.h:174