openaliro
Aliro reader: UWB/CCC core and ESP32-S3/C5/C6 port
Loading...
Searching...
No Matches
aliro_apdu.h
1// APDU framing and parsing for the Aliro Access Protocol: builds outbound command APDUs via a
2// TLV writer and parses the AUTH0/AUTH1 response APDUs exchanged during the reader-device
3// handshake.
4/*
5 * Copyright (c) 2026 asxeem
6 * SPDX-License-Identifier: ISC
7 *
8 * aliro_apdu — the Aliro credential-auth wire codec: single-byte-tag BER-TLV
9 * plus the AUTH0/AUTH1 command builders, the ECDSA authentication-data
10 * transcript, the AUTH0/AUTH1 response parsers, the EXCHANGE command, and the
11 * 4-byte L2CAP envelope. Pure byte manipulation, no crypto and no platform
12 * dependency, so it is host-KAT verifiable against the recovered layouts.
13 *
14 * Provenance: original. Byte layouts from the project's reverse-engineering
15 * notes; the code is original.
16 */
17#pragma once
18
19#include <stddef.h>
20#include <stdint.h>
21
22#ifdef __cplusplus
23extern "C" {
24#endif
25
26/* ---- BLE ProtocolType (envelope byte 0) ---- */
27#define ALIRO_PROTO_ACCESS 0x00u /* Access Protocol: payload is an ISO7816 APDU */
28#define ALIRO_PROTO_NOTIFICATION 0x02u /* Notification: Initiate-AP / Event */
29
30/* ---- Access-Protocol opcode (envelope byte 1, directional) ---- */
31#define ALIRO_AP_OP_COMMAND 0x00u /* reader -> device APDU command */
32#define ALIRO_AP_OP_RESPONSE 0x01u /* device -> reader APDU response (<tlv> SW1 SW2) */
33
34/* ---- Notification opcode (envelope byte 1 when type == NOTIFICATION) ---- */
35#define ALIRO_NOTIF_EVENT 0x00u /* Event, e.g. GeneralError [01 01 <code>] */
36#define ALIRO_NOTIF_INITIATE_AP 0x05u /* Initiate Access Protocol (phone's first msg) */
37
38/* ---- APDU instruction bytes (INS in "80 INS 00 00 Lc <tlv> Le") ----
39 * NOT BLE opcodes: every AP command frames as type=ACCESS, opcode=AP_OP_COMMAND,
40 * and carries the ISO7816 APDU whose INS selects the command. */
41#define ALIRO_INS_AUTH0 0x80u
42#define ALIRO_INS_AUTH1 0x81u
43#define ALIRO_INS_EXCHANGE 0xC9u
44
45/* ---- TLV tags ---- */
46#define ALIRO_TAG_EXP_PHASE 0x41u /* AUTH0: ExpeditedPhaseType; AUTH1: AccessCredentialType */
47#define ALIRO_TAG_USER_POL 0x42u /* AUTH0: UserAuthenticationPolicy */
48#define ALIRO_TAG_VERSION 0x5Cu /* AUTH0: protocol version u16 BE */
49#define ALIRO_TAG_READER_EPH 0x87u /* reader ephemeral pubkey (65) / transcript pubX (32) */
50#define ALIRO_TAG_TXID 0x4Cu /* transaction identifier (16) */
51#define ALIRO_TAG_READER_ID 0x4Du /* reader identifier (32) */
52#define ALIRO_TAG_SIG 0x9Eu /* ECDSA signature r|s (64) */
53#define ALIRO_TAG_DEVICE_PUBX \
54 0x86u /* transcript device pubX (32) / AUTH0Resp device eph pub (65) */
55#define ALIRO_TAG_USAGE 0x93u /* transcript usage domain separator (4) */
56#define ALIRO_TAG_DEVICE_PUB 0x5Au /* AUTH1Resp device public key (65) */
57#define ALIRO_TAG_STATUS 0x97u /* EXCHANGE ReaderStatus (u16 BE) */
58#define ALIRO_TAG_URSK_READY 0x98u /* EXCHANGE URSK-ready trigger (zero length) */
59
60/* ---- BER-TLV writer ---- */
61struct aliro_tlv_w {
62 uint8_t *buf;
63 size_t cap;
64 size_t len;
65 int err;
66};
67
68void aliro_tlv_w_init(struct aliro_tlv_w *w, uint8_t *buf, size_t cap);
69void aliro_tlv_put(struct aliro_tlv_w *w, uint8_t tag, const uint8_t *val, size_t len);
70void aliro_tlv_put_u8(struct aliro_tlv_w *w, uint8_t tag, uint8_t v);
71void aliro_tlv_put_u16(struct aliro_tlv_w *w, uint8_t tag, uint16_t v); /* big-endian */
72void aliro_tlv_put_empty(struct aliro_tlv_w *w, uint8_t tag); /* zero length */
73int aliro_tlv_w_finish(struct aliro_tlv_w *w, size_t *out_len); /* 0 ok, -1 overflow */
74
75/* Find the first item with tag; returns 0 and sets val/len, or -1 if absent. */
76int aliro_tlv_find(const uint8_t *buf, size_t buf_len, uint8_t tag, const uint8_t **val,
77 size_t *val_len);
78
79/* ---- command builders (out receives the raw APDU payload, no envelope) ---- */
80int aliro_apdu_build_auth0(uint8_t exp_phase, uint8_t user_policy, uint16_t version,
81 const uint8_t reader_eph_pub[65], const uint8_t txid[16],
82 const uint8_t reader_id[32], uint8_t *out, size_t cap, size_t *out_len);
83int aliro_apdu_build_auth1(uint8_t cred_type, const uint8_t sig[64], uint8_t *out, size_t cap,
84 size_t *out_len);
85
86/* The ECDSA transcript that is signed (reader) / verified (device). which:
87 * 1 = reader authenticates itself (kReaderUsage); 0 = verify user device
88 * (kUserDeviceUsage). Spans: device pubX (0x86) then reader-eph pubX (0x87). */
89#define ALIRO_AUTH_READER 1
90#define ALIRO_AUTH_DEVICE 0
91int aliro_apdu_build_authdata(int which, const uint8_t reader_id[32], const uint8_t device_pubx[32],
92 const uint8_t reader_eph_pubx[32], const uint8_t txid[16],
93 uint8_t *out, size_t cap, size_t *out_len);
94
95/* EXCHANGE command plaintext (sealed by the caller before framing). */
96int aliro_apdu_build_exchange(int have_status, uint16_t reader_status, int ursk_ready, uint8_t *out,
97 size_t cap, size_t *out_len);
98
99/* Wrap a command TLV in an ISO7816 short-form APDU: "80 <ins> 00 00 Lc <tlv> Le"
100 * (Le = 0x00 => up to 256 response bytes). ins is one of ALIRO_INS_*. The result
101 * is the AP command payload to frame with type=ACCESS, opcode=AP_OP_COMMAND. */
102int aliro_apdu_wrap(uint8_t ins, const uint8_t *tlv, size_t tlv_len, uint8_t *out, size_t cap,
103 size_t *out_len);
104
105/* ---- response parsers ---- */
106
107/* Strip the trailing 2-byte ISO7816 status word from an APDU response body: sets
108 * *sw (0x9000 = OK) and shrinks *len by 2. Returns -1 if fewer than 2 bytes. */
109int aliro_apdu_strip_sw(const uint8_t *buf, size_t *len, uint16_t *sw);
110
111/* Fields parsed from an AUTH0Response APDU: the device's mandatory ephemeral
112 * public key, plus the optional cryptogram sent when the device recognises the
113 * reader and offers the fast path. */
114struct aliro_auth0_response {
115 uint8_t device_eph_pub[65]; /* tag 0x86, mandatory */
116 int have_cryptogram; /* tag 0x9D present */
117 uint8_t cryptogram[64];
118};
119int aliro_apdu_parse_auth0_response(const uint8_t *buf, size_t len, struct aliro_auth0_response *r);
120
121/* Fields parsed from an AUTH1Response APDU: the device's mandatory signature
122 * over the transcript, plus the device public key and signaling bitmap it sends
123 * when the standard (non-fast) path is taken. */
124struct aliro_auth1_response {
125 int have_device_pub; /* tag 0x5A */
126 uint8_t device_pub[65];
127 uint8_t device_sig[64]; /* tag 0x9E, mandatory */
128 uint16_t signaling; /* 2-byte signaling bitmap */
129};
130int aliro_apdu_parse_auth1_response(const uint8_t *buf, size_t len, struct aliro_auth1_response *r);
131
132/* ---- 4-byte L2CAP envelope: [type&0x3F][opcode][len_be16][payload] ---- */
133#define ALIRO_ENVELOPE_HDR 4u
134int aliro_ble_frame(uint8_t type, uint8_t opcode, const uint8_t *payload, size_t plen, uint8_t *out,
135 size_t cap, size_t *out_len);
136int aliro_ble_unframe(const uint8_t *buf, size_t len, uint8_t *type, uint8_t *opcode,
137 const uint8_t **payload, size_t *plen);
138
139#ifdef __cplusplus
140}
141#endif