feat(applet): accessDocumentVerified flag in CredentialStore (M2A.1)

Persistent boolean flag for the cached IssuerAuth verify result. Field
sits beside accessDocumentFinalized in writeTo/readFrom (FIELD_VERSION
bumped 1->2 -- schema change requires re-personalize). reset() clears.
Test pins the serialize/deserialize round-trip via the existing
RecordingSink/ReplaySource helpers.

Foundation for M2A.2 (CoseVerifier) and M2A.3 (verify-at-finalize wiring).
This commit is contained in:
michael
2026-06-17 13:57:51 -07:00
parent 4741ce7ba3
commit 531b1d3186
2 changed files with 28 additions and 2 deletions

View File

@@ -36,7 +36,7 @@ final class CredentialStore {
/** Stable field order for AMD-H Element serialization. Append-only —
* never reorder or remove without bumping the package version and
* writing an explicit migration step in the new ELF's onRestore. */
static final byte FIELD_VERSION = 1;
static final byte FIELD_VERSION = 2;
/** Publish-point read by AliroApplet/StepUpApplet via {@link #get()}.
* PersonalizationApplet owns the actual instance; this is just an alias
@@ -57,6 +57,7 @@ final class CredentialStore {
private final byte[] accessDocument;
private short accessDocumentLen;
private boolean accessDocumentFinalized;
private boolean accessDocumentVerified;
private boolean committed;
@@ -202,6 +203,14 @@ final class CredentialStore {
return accessDocumentFinalized;
}
void markAccessDocumentVerified() {
accessDocumentVerified = true;
}
boolean isAccessDocumentVerified() {
return accessDocumentVerified;
}
short getAccessDocumentLen() {
return accessDocumentLen;
}
@@ -223,6 +232,7 @@ final class CredentialStore {
sink.write(credentialPubKeySet);
sink.write(readerPubKeySet);
sink.write(accessDocumentFinalized);
sink.write(accessDocumentVerified);
sink.write(accessDocumentLen);
sink.write(credentialPrivKey);
sink.write(credentialPubKey);
@@ -246,6 +256,7 @@ final class CredentialStore {
s.credentialPubKeySet = src.readBoolean();
s.readerPubKeySet = src.readBoolean();
s.accessDocumentFinalized = src.readBoolean();
s.accessDocumentVerified = src.readBoolean();
s.accessDocumentLen = src.readShort();
byte[] a;
a = src.readByteArray();
@@ -307,6 +318,7 @@ final class CredentialStore {
readerPubKeySet = false;
accessDocumentLen = 0;
accessDocumentFinalized = false;
accessDocumentVerified = false;
committed = false;
}
}

View File

@@ -81,6 +81,20 @@ class CredentialStoreSerializationTest {
assertFalse(restored.hasAccessDocument());
}
@Test
void accessDocumentVerifiedRoundTripsThroughSerialize() {
CredentialStore s1 = CredentialStore.bootstrap();
s1.resetForTesting();
s1.markAccessDocumentVerified();
RecordingSink buf = new RecordingSink();
s1.writeTo(buf);
CredentialStore s2 = CredentialStore.readFrom(buf.toSource());
assertTrue(s2.isAccessDocumentVerified(),
"verified flag must survive serialize/deserialize round-trip");
}
/**
* Forges a payload with a bogus FIELD_VERSION byte and asserts that
* {@link CredentialStore#readFrom} rejects it with
@@ -91,7 +105,7 @@ class CredentialStoreSerializationTest {
@Test
void readFromRejectsWrongFieldVersion() {
RecordingSink buf = new RecordingSink();
buf.write((byte) 0x7F); // != FIELD_VERSION (== 1)
buf.write((byte) 0x7F); // != FIELD_VERSION
ISOException ex = assertThrows(ISOException.class,
() -> CredentialStore.readFrom(buf.toSource()));