refactor(applet): extract StepUpSession from StepUpApplet (M2C.1)

Move the four StepUp session-state fields (StepUpSKDevice, StepUpSKReader,
stepup_reader_counter, stepup_device_counter) and the IV-stamp +
counter-advance math out of StepUpApplet into a dedicated StepUpSession
holder. Adds a unit-testable surface for the spec §8.3.1.6/8/9 IV layout
and §8.4.3 counter-advance behaviour (4 new tests pinning reader/device IV
layout, carry across byte 2, and the 32-bit BE wrap at 0xFFFFFFFF).

Behaviour-preserving: full StepUpAppletTest stays green with no
test-assertion changes (the test-only getters keep their signatures and
just delegate to session.skDevice / session.skReader).

StepUpApplet shrinks 444 -> 394 LOC. Full suite: 138 tests, 0 failures,
3 errors (pre-existing GCM trio).
This commit is contained in:
michael
2026-06-17 16:10:14 -07:00
parent 047503e8c5
commit 4fb87b6200
3 changed files with 254 additions and 78 deletions

View File

@@ -0,0 +1,103 @@
package com.dangerousthings.aliro;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
/**
* Tests for {@link StepUpSession} — the session-state holder extracted out
* of {@link StepUpApplet} for the Step-Up AES-256-GCM session (spec §8.4.3
* + §8.3.1.6/8/9). Covers IV layout, counter advance, and 32-bit wrap.
*
* <p>Note: jcardsim is initialised lazily by {@link StepUpSession}'s
* {@code makeTransientByteArray} calls. The {@link CardSimulator} static
* initialiser does that wiring; instantiating one here is sufficient.
*/
class StepUpSessionTest {
private static StepUpSession freshSession() {
// CardSimulator's static init installs the jcardsim runtime that
// StepUpSession needs for makeTransientByteArray. Construct one and
// discard — only the static-init side-effect matters.
new com.licel.jcardsim.smartcardio.CardSimulator();
StepUpSession s = new StepUpSession();
s.reset();
return s;
}
/**
* Spec §8.3.1.8/9 reader-side IV layout: 8-byte zero prefix +
* 4-byte big-endian stepup_reader_counter. After {@link StepUpSession#reset()}
* the counter is {@code 0x00000001} per §8.4.3 + mdoc [6] §9.1.1.5.
*/
@Test
void readerIv_counter1_returnsAllZerosThen0001() {
StepUpSession s = freshSession();
byte[] iv = new byte[12];
s.readerIv(iv, (short) 0);
assertArrayEquals(
new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
iv,
"fresh-session reader IV = 00*8 || 00 00 00 01");
}
/**
* Advance reader counter 255 times from 1 -> 256 (= 0x00000100). Pins
* carry propagation from the low byte into byte 2 of the counter — the
* IV trailing 4 bytes must read {@code 00 00 01 00}.
*/
@Test
void readerIv_counter256_handlesCarryIntoByte2() {
StepUpSession s = freshSession();
// 1 -> 2 -> ... -> 256: 255 advances.
for (int i = 0; i < 255; i++) {
s.advanceReaderCounter();
}
byte[] iv = new byte[12];
s.readerIv(iv, (short) 0);
assertArrayEquals(
new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
iv,
"after 255 advances reader IV trails 00 00 01 00 (BE 256)");
}
/**
* Spec §8.3.1.6 device-side IV layout: 7-byte zero prefix + 0x01 +
* 4-byte big-endian stepup_device_counter. After reset the device
* counter is {@code 0x00000001} per §8.4.3.
*/
@Test
void deviceIv_counter1_returnsZeros_then01_then0001() {
StepUpSession s = freshSession();
byte[] iv = new byte[12];
s.deviceIv(iv, (short) 0);
assertArrayEquals(
new byte[] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1 },
iv,
"fresh-session device IV = 00*7 || 01 || 00 00 00 01");
}
/**
* Spec §8.3.3.5.4 says the counter SHALL never reach the limit, but the
* applet has no explicit overflow guard (YAGNI: the protocol's wire-speed
* ceiling makes this unreachable in practice). Lock the 32-bit BE wrap
* behaviour here so a future "let's add a check" doesn't silently change
* the math layer.
*/
@Test
void incrementCounterWrapsAt0xFFFFFFFF() {
StepUpSession s = freshSession();
// Force the reader counter to 0xFFFFFFFF, then advance.
s.readerCounter[0] = (byte) 0xFF;
s.readerCounter[1] = (byte) 0xFF;
s.readerCounter[2] = (byte) 0xFF;
s.readerCounter[3] = (byte) 0xFF;
s.advanceReaderCounter();
byte[] iv = new byte[12];
s.readerIv(iv, (short) 0);
assertArrayEquals(
new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
iv,
"0xFFFFFFFF + 1 wraps to 0x00000000 (mod 2^32)");
}
}