mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2026-05-16 23:48:10 +02:00
Add init_hardcoded and init_clean_slate transmission to the open SSM. Four new states (OPEN_SEND/RECV_INIT_HARDCODED, OPEN_SEND/RECV_INIT_ CLEAN_SLATE) between GET_FW_INFO and UPLOAD_FWEXT, matching the python-validity send_init() flow. init_hardcoded is always sent; clean_slate only when fwext is not loaded. Skipped in emulation mode. Remove dead crt_hardcoded[] (420 bytes, G_GNUC_UNUSED) from validity_tls.c — this CA cert data now lives exclusively in validity_pair_constants.inc. Expose enrollment response parser for unit testing: - EnrollmentUpdateResult struct and ENROLLMENT_MAGIC_LEN moved to validity.h - parse_enrollment_update_response() and enrollment_update_result_clear() no longer static Remove in-tree doc/ directory — documentation lives in ../validity-artifacts/docs/. Tests: 9 new enrollment parser test cases, 0 regressions. Result: 41 OK, 0 Fail, 2 Skipped.
292 lines
10 KiB
C
292 lines
10 KiB
C
/*
|
|
* Unit tests for enrollment response parsing
|
|
*
|
|
* Copyright (C) 2024 libfprint contributors
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#include <glib.h>
|
|
#include <string.h>
|
|
|
|
#include "fpi-byte-utils.h"
|
|
#include "drivers/validity/validity.h"
|
|
|
|
/* ================================================================
|
|
* Helper: build a tagged block
|
|
* [tag:2LE][len:2LE][padding:MAGIC_LEN][payload:len]
|
|
* Total block size = 4 + MAGIC_LEN + len = MAGIC_LEN + len + 4
|
|
* Wait — re-read the parser:
|
|
* tag(2LE) | len(2LE) => block_size = MAGIC_LEN + len
|
|
* so the full block is [tag:2][len:2] + body[MAGIC_LEN + len]
|
|
* No — looking at the code: pos + 4 reads tag+len, then
|
|
* block_size = MAGIC_LEN + len, and the block starts at data[pos].
|
|
* Template: data[pos .. pos + block_size].
|
|
* Header: data[pos + MAGIC_LEN .. pos + MAGIC_LEN + len].
|
|
* Advance: pos += block_size.
|
|
*
|
|
* Actually re-reading more carefully:
|
|
* tag = data[pos], len = data[pos+2]
|
|
* block_size = MAGIC_LEN + len
|
|
* template = data[pos .. pos + block_size]
|
|
* So the 4 bytes of tag+len are INSIDE the block_size.
|
|
* MAGIC_LEN = 0x38 = 56 which is > 4, so tag+len fit inside.
|
|
*
|
|
* To build test data: write tag(2LE) at offset 0, len(2LE) at
|
|
* offset 2, then (MAGIC_LEN - 4) padding bytes, then len payload bytes.
|
|
* Total = MAGIC_LEN + len.
|
|
* ================================================================ */
|
|
static guint8 *
|
|
build_block (guint16 tag, const guint8 *payload, guint16 payload_len,
|
|
gsize *out_len)
|
|
{
|
|
gsize block_size = ENROLLMENT_MAGIC_LEN + payload_len;
|
|
guint8 *buf = g_malloc0 (block_size);
|
|
|
|
FP_WRITE_UINT16_LE (buf, tag);
|
|
FP_WRITE_UINT16_LE (buf + 2, payload_len);
|
|
|
|
if (payload && payload_len > 0)
|
|
memcpy (buf + ENROLLMENT_MAGIC_LEN, payload, payload_len);
|
|
|
|
*out_len = block_size;
|
|
return buf;
|
|
}
|
|
|
|
/* ================================================================
|
|
* T8.1: parse empty data — returns TRUE, all fields NULL
|
|
* ================================================================ */
|
|
static void
|
|
test_parse_empty (void)
|
|
{
|
|
EnrollmentUpdateResult result;
|
|
gboolean ok = parse_enrollment_update_response (NULL, 0, &result);
|
|
|
|
g_assert_true (ok);
|
|
g_assert_null (result.header);
|
|
g_assert_null (result.template_data);
|
|
g_assert_null (result.tid);
|
|
}
|
|
|
|
/* ================================================================
|
|
* T8.2: parse single template block (tag=0)
|
|
* ================================================================ */
|
|
static void
|
|
test_parse_template_block (void)
|
|
{
|
|
guint8 payload[] = { 0xDE, 0xAD, 0xBE, 0xEF };
|
|
gsize block_len;
|
|
g_autofree guint8 *data = build_block (0, payload, sizeof (payload),
|
|
&block_len);
|
|
|
|
EnrollmentUpdateResult result;
|
|
gboolean ok = parse_enrollment_update_response (data, block_len, &result);
|
|
|
|
g_assert_true (ok);
|
|
g_assert_nonnull (result.template_data);
|
|
g_assert_cmpuint (result.template_len, ==, block_len);
|
|
g_assert_null (result.header);
|
|
g_assert_null (result.tid);
|
|
|
|
enrollment_update_result_clear (&result);
|
|
}
|
|
|
|
/* ================================================================
|
|
* T8.3: parse header block (tag=1)
|
|
* ================================================================ */
|
|
static void
|
|
test_parse_header_block (void)
|
|
{
|
|
guint8 payload[] = { 0x01, 0x02, 0x03 };
|
|
gsize block_len;
|
|
g_autofree guint8 *data = build_block (1, payload, sizeof (payload),
|
|
&block_len);
|
|
|
|
EnrollmentUpdateResult result;
|
|
gboolean ok = parse_enrollment_update_response (data, block_len, &result);
|
|
|
|
g_assert_true (ok);
|
|
g_assert_nonnull (result.header);
|
|
g_assert_cmpuint (result.header_len, ==, sizeof (payload));
|
|
g_assert_cmpmem (result.header, result.header_len, payload, sizeof (payload));
|
|
g_assert_null (result.template_data);
|
|
g_assert_null (result.tid);
|
|
|
|
enrollment_update_result_clear (&result);
|
|
}
|
|
|
|
/* ================================================================
|
|
* T8.4: parse tid block (tag=3) — signals enrollment complete
|
|
* ================================================================ */
|
|
static void
|
|
test_parse_tid_block (void)
|
|
{
|
|
guint8 payload[] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
|
|
gsize block_len;
|
|
g_autofree guint8 *data = build_block (3, payload, sizeof (payload),
|
|
&block_len);
|
|
|
|
EnrollmentUpdateResult result;
|
|
gboolean ok = parse_enrollment_update_response (data, block_len, &result);
|
|
|
|
g_assert_true (ok);
|
|
g_assert_nonnull (result.tid);
|
|
g_assert_cmpuint (result.tid_len, ==, sizeof (payload));
|
|
g_assert_cmpmem (result.tid, result.tid_len, payload, sizeof (payload));
|
|
g_assert_null (result.template_data);
|
|
g_assert_null (result.header);
|
|
|
|
enrollment_update_result_clear (&result);
|
|
}
|
|
|
|
/* ================================================================
|
|
* T8.5: parse multiple blocks — template + header + tid
|
|
* ================================================================ */
|
|
static void
|
|
test_parse_multiple_blocks (void)
|
|
{
|
|
guint8 tmpl_payload[] = { 0x11, 0x22 };
|
|
guint8 hdr_payload[] = { 0x33, 0x44, 0x55 };
|
|
guint8 tid_payload[] = { 0x66 };
|
|
|
|
gsize tmpl_len, hdr_len, tid_len;
|
|
g_autofree guint8 *tmpl = build_block (0, tmpl_payload,
|
|
sizeof (tmpl_payload), &tmpl_len);
|
|
g_autofree guint8 *hdr = build_block (1, hdr_payload,
|
|
sizeof (hdr_payload), &hdr_len);
|
|
g_autofree guint8 *tid = build_block (3, tid_payload,
|
|
sizeof (tid_payload), &tid_len);
|
|
|
|
/* Concatenate all three blocks */
|
|
gsize total = tmpl_len + hdr_len + tid_len;
|
|
g_autofree guint8 *data = g_malloc (total);
|
|
memcpy (data, tmpl, tmpl_len);
|
|
memcpy (data + tmpl_len, hdr, hdr_len);
|
|
memcpy (data + tmpl_len + hdr_len, tid, tid_len);
|
|
|
|
EnrollmentUpdateResult result;
|
|
gboolean ok = parse_enrollment_update_response (data, total, &result);
|
|
|
|
g_assert_true (ok);
|
|
g_assert_nonnull (result.template_data);
|
|
g_assert_nonnull (result.header);
|
|
g_assert_nonnull (result.tid);
|
|
g_assert_cmpuint (result.template_len, ==, tmpl_len);
|
|
g_assert_cmpuint (result.header_len, ==, sizeof (hdr_payload));
|
|
g_assert_cmpuint (result.tid_len, ==, sizeof (tid_payload));
|
|
|
|
enrollment_update_result_clear (&result);
|
|
}
|
|
|
|
/* ================================================================
|
|
* T8.6: parse truncated data — stops before reading past buffer
|
|
* ================================================================ */
|
|
static void
|
|
test_parse_truncated (void)
|
|
{
|
|
guint8 payload[] = { 0xAA };
|
|
gsize block_len;
|
|
g_autofree guint8 *data = build_block (0, payload, sizeof (payload),
|
|
&block_len);
|
|
|
|
/* Pass data_len shorter than block_size so the block can't be read */
|
|
EnrollmentUpdateResult result;
|
|
gboolean ok = parse_enrollment_update_response (data, 10, &result);
|
|
|
|
g_assert_true (ok);
|
|
/* No fields should be populated since the block was truncated */
|
|
g_assert_null (result.template_data);
|
|
g_assert_null (result.header);
|
|
g_assert_null (result.tid);
|
|
}
|
|
|
|
/* ================================================================
|
|
* T8.7: parse unknown tag — silently skipped
|
|
* ================================================================ */
|
|
static void
|
|
test_parse_unknown_tag (void)
|
|
{
|
|
guint8 payload[] = { 0x99 };
|
|
gsize block_len;
|
|
g_autofree guint8 *data = build_block (42, payload, sizeof (payload),
|
|
&block_len);
|
|
|
|
EnrollmentUpdateResult result;
|
|
gboolean ok = parse_enrollment_update_response (data, block_len, &result);
|
|
|
|
g_assert_true (ok);
|
|
g_assert_null (result.template_data);
|
|
g_assert_null (result.header);
|
|
g_assert_null (result.tid);
|
|
}
|
|
|
|
/* ================================================================
|
|
* T8.8: result_clear — frees and zeroes
|
|
* ================================================================ */
|
|
static void
|
|
test_result_clear (void)
|
|
{
|
|
EnrollmentUpdateResult result;
|
|
result.header = g_malloc (10);
|
|
result.header_len = 10;
|
|
result.template_data = g_malloc (20);
|
|
result.template_len = 20;
|
|
result.tid = g_malloc (5);
|
|
result.tid_len = 5;
|
|
|
|
enrollment_update_result_clear (&result);
|
|
|
|
g_assert_null (result.header);
|
|
g_assert_null (result.template_data);
|
|
g_assert_null (result.tid);
|
|
g_assert_cmpuint (result.header_len, ==, 0);
|
|
g_assert_cmpuint (result.template_len, ==, 0);
|
|
g_assert_cmpuint (result.tid_len, ==, 0);
|
|
}
|
|
|
|
/* ================================================================
|
|
* T8.9: parse zero-length payload — tag present but no data
|
|
* ================================================================ */
|
|
static void
|
|
test_parse_zero_length_payload (void)
|
|
{
|
|
gsize block_len;
|
|
g_autofree guint8 *data = build_block (1, NULL, 0, &block_len);
|
|
|
|
EnrollmentUpdateResult result;
|
|
gboolean ok = parse_enrollment_update_response (data, block_len, &result);
|
|
|
|
g_assert_true (ok);
|
|
/* Tag 1 with len=0: header should be NULL (len > 0 check in parser) */
|
|
g_assert_null (result.header);
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
g_test_init (&argc, &argv, NULL);
|
|
|
|
g_test_add_func ("/validity/enroll/parse-empty",
|
|
test_parse_empty);
|
|
g_test_add_func ("/validity/enroll/parse-template-block",
|
|
test_parse_template_block);
|
|
g_test_add_func ("/validity/enroll/parse-header-block",
|
|
test_parse_header_block);
|
|
g_test_add_func ("/validity/enroll/parse-tid-block",
|
|
test_parse_tid_block);
|
|
g_test_add_func ("/validity/enroll/parse-multiple-blocks",
|
|
test_parse_multiple_blocks);
|
|
g_test_add_func ("/validity/enroll/parse-truncated",
|
|
test_parse_truncated);
|
|
g_test_add_func ("/validity/enroll/parse-unknown-tag",
|
|
test_parse_unknown_tag);
|
|
g_test_add_func ("/validity/enroll/result-clear",
|
|
test_result_clear);
|
|
g_test_add_func ("/validity/enroll/parse-zero-length-payload",
|
|
test_parse_zero_length_payload);
|
|
|
|
return g_test_run ();
|
|
}
|