Skip to content

Commit 9ec422e

Browse files
committed
feat: add dlm-auth command for authenticated DLM state transitions
Implement authenticated DLM state transitions using HMAC-SHA256 as specified in R01AN5562. The dlm-auth command supports transitions that require cryptographic authentication: NSECSD to SSD, SSD to DPL, and DPL to LCK_DBG/LCK_BOOT/RMA_REQ. Add OpenSSL dependency for HMAC-SHA256 computation across all platforms: libssl-dev on Linux, openssl via brew on macOS, and openssl:x64-windows via vcpkg on Windows. Signed-off-by: Vincent Jardin <vjardin@free.fr>
1 parent b0e9de7 commit 9ec422e

File tree

7 files changed

+439
-34
lines changed

7 files changed

+439
-34
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
- name: Install dependencies
4242
run: |
4343
sudo apt-get update
44-
sudo apt-get install -y meson ninja-build libcmocka-dev help2man
44+
sudo apt-get install -y meson ninja-build libcmocka-dev libssl-dev help2man
4545
4646
- name: Configure
4747
run: meson setup build
@@ -67,7 +67,7 @@ jobs:
6767
- name: Install dependencies
6868
run: |
6969
sudo apt-get update
70-
sudo apt-get install -y meson ninja-build help2man
70+
sudo apt-get install -y meson ninja-build libssl-dev help2man
7171
7272
- name: Configure static build
7373
run: meson setup build-static -Dstatic=true -Dtests=false
@@ -93,7 +93,7 @@ jobs:
9393
- name: Install dependencies
9494
run: |
9595
sudo apt-get update
96-
sudo apt-get install -y meson ninja-build libcmocka-dev
96+
sudo apt-get install -y meson ninja-build libcmocka-dev libssl-dev
9797
9898
- name: Configure
9999
run: meson setup build
@@ -119,7 +119,7 @@ jobs:
119119
- name: Install dependencies
120120
run: |
121121
sudo apt-get update
122-
sudo apt-get install -y meson ninja-build
122+
sudo apt-get install -y meson ninja-build libssl-dev
123123
124124
- name: Configure static build
125125
run: meson setup build-static -Dstatic=true -Dtests=false
@@ -143,7 +143,7 @@ jobs:
143143
- uses: actions/checkout@v4
144144

145145
- name: Install dependencies
146-
run: brew install meson ninja
146+
run: brew install meson ninja openssl
147147

148148
- name: Configure
149149
run: meson setup build -Dtests=false
@@ -174,9 +174,9 @@ jobs:
174174
- name: Setup MSVC
175175
uses: ilammy/msvc-dev-cmd@v1
176176

177-
- name: Install cmocka via vcpkg
177+
- name: Install dependencies via vcpkg
178178
run: |
179-
vcpkg install cmocka:x64-windows
179+
vcpkg install cmocka:x64-windows openssl:x64-windows
180180
vcpkg integrate install
181181
182182
- name: Configure

meson.build

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ cc = meson.get_compiler('c')
4949
deps = []
5050
link_args = []
5151

52+
# OpenSSL for HMAC-SHA256 (dlm-auth command)
53+
openssl = dependency('openssl', required : false, static : get_option('static'))
54+
if openssl.found()
55+
deps += openssl
56+
add_project_arguments('-DHAVE_OPENSSL', language : 'c')
57+
endif
58+
5259
if host_machine.system() == 'windows'
5360
# Windows requires SetupAPI for USB device detection
5461
deps += cc.find_library('setupapi')

radfu.h2m

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,15 +199,46 @@ state, it cannot return to a less restricted state. The \fBinit\fR command
199199
can reset a device to SSD state only if the device supports it and is not
200200
in a locked state.
201201

202-
.SS Examples
202+
.SS DLM State Transition Examples
203+
204+
Forward transition (locking):
205+
.nf
206+
$ radfu dlm-transit dpl
207+
DLM state transition: SSD (0x02) -> DPL (0x04)
208+
DLM transit complete: SSD -> DPL
209+
.fi
210+
211+
Authenticated regression (unlocking without erase):
212+
.nf
213+
$ radfu dlm-auth nsecsd hex:000102030405060708090A0B0C0D0E0F
214+
DLM state transition: DPL (0x04) -> NSECSD (0x03)
215+
Authenticating with NONSECDBG_KEY...
216+
Received challenge: 1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D
217+
Computed response: 8F7E6D5C4B3A2918F7E6D5C4B3A29180...
218+
DLM authentication successful: DPL -> NSECSD
219+
.fi
220+
221+
Factory reset (erases flash):
222+
.nf
223+
$ radfu init
224+
DLM state transition: DPL (0x04) -> SSD (0x02)
225+
Initializing device (factory reset)...
226+
WARNING: This will erase all flash areas and reset boundaries!
227+
Initialize complete: DPL -> SSD
228+
.fi
229+
230+
.SS Command Summary
203231
.nf
204232
radfu dlm # Show current DLM state
205233
radfu dlm-transit ssd # Transition to SSD (from CM only)
206234
radfu dlm-transit nsecsd # Transition to NSECSD
207235
radfu dlm-transit dpl # Transition to Deployed
208236
radfu dlm-transit lck_dbg # Lock debug (IRREVERSIBLE)
209237
radfu dlm-transit lck_boot # Lock boot interface (IRREVERSIBLE)
210-
radfu init # Factory reset to SSD (if supported)
238+
radfu dlm-auth ssd file:key # Regress NSECSD -> SSD (SECDBG_KEY)
239+
radfu dlm-auth nsecsd hex:... # Regress DPL -> NSECSD (NONSECDBG_KEY)
240+
radfu dlm-auth rma_req file:k # Enter RMA (erases flash!)
241+
radfu init # Factory reset to SSD (erases flash)
211242
.fi
212243

213244
[examples]

src/main.c

Lines changed: 140 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,16 @@ usage(int status) {
3535
" crc Calculate CRC-32 of flash region\n"
3636
" dlm Show Device Lifecycle Management state\n"
3737
" dlm-transit <state> Transition DLM state (ssd/nsecsd/dpl/lck_dbg/lck_boot)\n"
38+
" dlm-auth <state> <key> Authenticated DLM transition (ssd/nsecsd/rma_req)\n"
39+
" Key format: file:<path> or hex:<32_hex_chars>\n"
3840
" boundary Show secure/non-secure boundary settings\n"
3941
" boundary-set Set TrustZone boundaries (requires --cfs1/--cfs2/--dfs/--srs1/--srs2)\n"
4042
" param Show device parameter (initialization command)\n"
4143
" param-set <enable|disable> Enable/disable initialization command\n"
4244
" init Initialize device (factory reset to SSD state)\n"
4345
" osis Show OSIS (ID code protection) status\n"
44-
" key-set <idx> <file> Inject wrapped key from file at index\n"
45-
" key-verify <idx> Verify key at index\n"
46+
" key-set <type> <file> Inject wrapped DLM key (secdbg|nonsecdbg|rma)\n"
47+
" key-verify <type> Verify DLM key (secdbg|nonsecdbg|rma)\n"
4648
" ukey-set <idx> <file> Inject user wrapped key from file at index\n"
4749
" ukey-verify <idx> Verify user key at index\n"
4850
"\n"
@@ -130,6 +132,7 @@ enum command {
130132
CMD_CRC,
131133
CMD_DLM,
132134
CMD_DLM_TRANSIT,
135+
CMD_DLM_AUTH,
133136
CMD_BOUNDARY,
134137
CMD_BOUNDARY_SET,
135138
CMD_PARAM,
@@ -142,6 +145,114 @@ enum command {
142145
CMD_UKEY_VERIFY,
143146
};
144147

148+
/* DLM key types (KYTY) per R01AN5562 */
149+
#define KYTY_SECDBG 0x01
150+
#define KYTY_NONSECDBG 0x02
151+
#define KYTY_RMA 0x03
152+
153+
/* DLM authentication key length (16 bytes / 128 bits) */
154+
#define DLM_AUTH_KEY_LEN 16
155+
156+
/*
157+
* Parse DLM authentication key from hex string
158+
* Returns: 0 on success, -1 on error
159+
*/
160+
static int
161+
parse_hex_key(const char *str, uint8_t *key) {
162+
size_t len = strlen(str);
163+
164+
/* Accept with or without 0x prefix */
165+
if (len >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
166+
str += 2;
167+
len -= 2;
168+
}
169+
170+
if (len != DLM_AUTH_KEY_LEN * 2) {
171+
warnx("authentication key must be %d hex bytes (%d hex characters)",
172+
DLM_AUTH_KEY_LEN,
173+
DLM_AUTH_KEY_LEN * 2);
174+
return -1;
175+
}
176+
177+
for (int i = 0; i < DLM_AUTH_KEY_LEN; i++) {
178+
unsigned int byte;
179+
if (sscanf(str + i * 2, "%2x", &byte) != 1) {
180+
warnx("invalid hex character in key at position %d", i * 2);
181+
return -1;
182+
}
183+
key[i] = (uint8_t)byte;
184+
}
185+
186+
return 0;
187+
}
188+
189+
/*
190+
* Load DLM authentication key from file (binary, 16 bytes)
191+
* Returns: 0 on success, -1 on error
192+
*/
193+
static int
194+
load_key_from_file(const char *filename, uint8_t *key) {
195+
FILE *f = fopen(filename, "rb");
196+
if (f == NULL) {
197+
warn("failed to open key file: %s", filename);
198+
return -1;
199+
}
200+
201+
size_t n = fread(key, 1, DLM_AUTH_KEY_LEN, f);
202+
fclose(f);
203+
204+
if (n != DLM_AUTH_KEY_LEN) {
205+
warnx("key file must be %d bytes (got %zu)", DLM_AUTH_KEY_LEN, n);
206+
return -1;
207+
}
208+
209+
return 0;
210+
}
211+
212+
/*
213+
* Parse authentication key from string
214+
* Format: file:<filename> - read 16-byte binary key from file
215+
* hex:<value> - parse 32-char hex string (with/without 0x)
216+
* Returns: 0 on success, -1 on error
217+
*/
218+
static int
219+
parse_auth_key(const char *str, uint8_t *key) {
220+
if (strncmp(str, "file:", 5) == 0) {
221+
return load_key_from_file(str + 5, key);
222+
} else if (strncmp(str, "hex:", 4) == 0) {
223+
return parse_hex_key(str + 4, key);
224+
} else {
225+
warnx("invalid key format: %s", str);
226+
warnx("use: file:<filename> for binary key file");
227+
warnx(" hex:<hex_value> for hex string (32 chars)");
228+
return -1;
229+
}
230+
}
231+
232+
/*
233+
* Parse key type from string: accepts numeric (1,2,3) or keywords
234+
* Keywords: secdbg, nonsecdbg, rma (case-insensitive)
235+
* Returns key type on success, 0 on error
236+
*/
237+
static uint8_t
238+
parse_key_type(const char *str) {
239+
if (strcasecmp(str, "secdbg") == 0)
240+
return KYTY_SECDBG;
241+
if (strcasecmp(str, "nonsecdbg") == 0)
242+
return KYTY_NONSECDBG;
243+
if (strcasecmp(str, "rma") == 0)
244+
return KYTY_RMA;
245+
246+
/* Try numeric */
247+
char *endptr;
248+
unsigned long val = strtoul(str, &endptr, 0);
249+
if (*endptr != '\0' || val < 1 || val > 3) {
250+
warnx("invalid key type: %s (use secdbg, nonsecdbg, rma, or 1-3)", str);
251+
return 0;
252+
}
253+
return (uint8_t)val;
254+
}
255+
145256
/* Long-only options use values >= 256 */
146257
#define OPT_CFS1 256
147258
#define OPT_CFS2 257
@@ -185,6 +296,7 @@ main(int argc, char *argv[]) {
185296
uint8_t param_value = 0;
186297
uint8_t key_index = 0;
187298
const char *key_file = NULL;
299+
uint8_t auth_key[DLM_AUTH_KEY_LEN];
188300
ra_boundary_t bnd = { 0 };
189301
bool bnd_cfs1_set = false, bnd_cfs2_set = false, bnd_dfs_set = false;
190302
bool bnd_srs1_set = false, bnd_srs2_set = false;
@@ -304,6 +416,21 @@ main(int argc, char *argv[]) {
304416
dest_dlm = DLM_STATE_LCK_BOOT;
305417
else
306418
errx(EXIT_FAILURE, "unknown DLM state: %s (use ssd/nsecsd/dpl/lck_dbg/lck_boot)", state);
419+
} else if (strcmp(command, "dlm-auth") == 0) {
420+
cmd = CMD_DLM_AUTH;
421+
if (optind + 1 >= argc)
422+
errx(EXIT_FAILURE, "dlm-auth requires <state> and <key> arguments");
423+
const char *state = argv[optind];
424+
if (strcasecmp(state, "ssd") == 0)
425+
dest_dlm = DLM_STATE_SSD;
426+
else if (strcasecmp(state, "nsecsd") == 0)
427+
dest_dlm = DLM_STATE_NSECSD;
428+
else if (strcasecmp(state, "rma_req") == 0)
429+
dest_dlm = DLM_STATE_RMA_REQ;
430+
else
431+
errx(EXIT_FAILURE, "dlm-auth: invalid target state: %s (use ssd/nsecsd/rma_req)", state);
432+
if (parse_auth_key(argv[optind + 1], auth_key) < 0)
433+
errx(EXIT_FAILURE, "dlm-auth: invalid key format");
307434
} else if (strcmp(command, "boundary") == 0) {
308435
cmd = CMD_BOUNDARY;
309436
} else if (strcmp(command, "boundary-set") == 0) {
@@ -330,14 +457,18 @@ main(int argc, char *argv[]) {
330457
} else if (strcmp(command, "key-set") == 0) {
331458
cmd = CMD_KEY_SET;
332459
if (optind + 1 >= argc)
333-
errx(EXIT_FAILURE, "key-set requires index and file arguments");
334-
key_index = (uint8_t)strtoul(argv[optind], NULL, 10);
460+
errx(EXIT_FAILURE, "key-set requires type and file arguments");
461+
key_index = parse_key_type(argv[optind]);
462+
if (key_index == 0)
463+
errx(EXIT_FAILURE, "key-set: invalid key type");
335464
key_file = argv[optind + 1];
336465
} else if (strcmp(command, "key-verify") == 0) {
337466
cmd = CMD_KEY_VERIFY;
338467
if (optind >= argc)
339-
errx(EXIT_FAILURE, "key-verify requires index argument");
340-
key_index = (uint8_t)strtoul(argv[optind], NULL, 10);
468+
errx(EXIT_FAILURE, "key-verify requires type argument");
469+
key_index = parse_key_type(argv[optind]);
470+
if (key_index == 0)
471+
errx(EXIT_FAILURE, "key-verify: invalid key type");
341472
} else if (strcmp(command, "ukey-set") == 0) {
342473
cmd = CMD_UKEY_SET;
343474
if (optind + 1 >= argc)
@@ -451,6 +582,9 @@ main(int argc, char *argv[]) {
451582
case CMD_DLM_TRANSIT:
452583
ret = ra_dlm_transit(&dev, dest_dlm);
453584
break;
585+
case CMD_DLM_AUTH:
586+
ret = ra_dlm_auth(&dev, dest_dlm, auth_key);
587+
break;
454588
case CMD_BOUNDARY:
455589
ret = ra_get_boundary(&dev, NULL);
456590
break;

0 commit comments

Comments
 (0)