From 8d7da581d28455aff7c95e3de840571e5db32fe6 Mon Sep 17 00:00:00 2001 From: dilucide Date: Sun, 23 Aug 2026 21:39:15 +0200 Subject: [PATCH 1/3] Bugfix: GET DATA invoked for the BITG object returned the ID_DATA_BITG_MSB||ID_DATA_BITG_MSB instead of ID_DATA_BITG_MSG||ID_DATA_BITG_LSB. --- src/org/openfips201/applet/PIV.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/openfips201/applet/PIV.java b/src/org/openfips201/applet/PIV.java index 0885d0b1..0006f003 100644 --- a/src/org/openfips201/applet/PIV.java +++ b/src/org/openfips201/applet/PIV.java @@ -264,7 +264,7 @@ void getData(PIVAPDU pApdu) throws ISOException { // Special - Handle the 2-byte BITG case case Constants.ID_DATA_BITG: buffer[0] = Constants.ID_DATA_BITG_MSB; - buffer[1] = Constants.ID_DATA_BITG_MSB; + buffer[1] = Constants.ID_DATA_BITG_LSB; buffer[2] = 0; length = 3; break; From 95e2dda42e8961d04b88d40edaa6d96fba6c82ea Mon Sep 17 00:00:00 2001 From: dilucide Date: Sun, 23 Aug 2026 21:48:10 +0200 Subject: [PATCH 2/3] Bugfix: For such SCP channels not having R-MAC or R-ENC modes enabled, isResponseWrapped() will return false, causing erroneous truncation of response data, f. i. with GENERATE ASYMMETRIC KEYPAIR. --- src/org/openfips201/applet/PIVAPDU.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/org/openfips201/applet/PIVAPDU.java b/src/org/openfips201/applet/PIVAPDU.java index d48aeed8..66dbca81 100644 --- a/src/org/openfips201/applet/PIVAPDU.java +++ b/src/org/openfips201/applet/PIVAPDU.java @@ -773,7 +773,12 @@ void processOutgoing(APDU apdu) throws ISOException { apdu.setOutgoingLength(outLength); apdu.sendBytes(Constants.ZERO_SHORT, outLength); } else { - // Same as plaintext + short remaining = (short)(context[CONTEXT_REMAINING] - inLength); + if (remaining > 0) { + status = ISO7816.SW_BYTES_REMAINING_00; + status |= (remaining > (short)0xff) ? (short)0xff : remaining; + } + apdu.setOutgoingLength(outLength); if (outLength > 0) { apdu.sendBytesLong(data, context[CONTEXT_OFFSET], outLength); From baf5cb0e8c18548315dbaeed409eb4f388fe259b Mon Sep 17 00:00:00 2001 From: dilucide Date: Sun, 23 Aug 2026 22:13:17 +0200 Subject: [PATCH 3/3] Add object deletion --- src/org/openfips201/applet/Constants.java | 11 +- src/org/openfips201/applet/PIV.java | 114 ++++++++++++++++++ src/org/openfips201/applet/PIVDataStore.java | 120 ++++++++++++++++++- 3 files changed, 242 insertions(+), 3 deletions(-) diff --git a/src/org/openfips201/applet/Constants.java b/src/org/openfips201/applet/Constants.java index e40ebdb1..06724572 100644 --- a/src/org/openfips201/applet/Constants.java +++ b/src/org/openfips201/applet/Constants.java @@ -266,11 +266,20 @@ private Constants() { // Tag for updating a configuration operation. static final byte TAG_OP_UPDATE_CONFIG = (byte) 0x68; + // Tag for deleting a data object (container) operation. + static final byte TAG_OP_DELETE_OBJECT = (byte) 0x69; + + // Tag for deleting a verifier (PIN/PUK) operation. + static final byte TAG_OP_DELETE_PIN = (byte) 0x6A; + + // Tag for deleting a key operation. + static final byte TAG_OP_DELETE_KEY = (byte) 0x6B; + // Tag for securing the applet (non-constructed tag). static final byte TAG_OP_SECURE_APPLET = (byte) 0x5F; // Tag for bulk requests. - static final byte TAG_OP_BULK_REQUEST = (byte) 0x6A; + static final byte TAG_OP_BULK_REQUEST = (byte) 0x7E; //////////////////////////////////////////////////////////////////////////////// // Custom Error Constants diff --git a/src/org/openfips201/applet/PIV.java b/src/org/openfips201/applet/PIV.java index 0006f003..cd250ac6 100644 --- a/src/org/openfips201/applet/PIV.java +++ b/src/org/openfips201/applet/PIV.java @@ -2197,6 +2197,105 @@ private void processCreateObjectRequest(byte operation, TLVReader reader) { } } + /** + * Deletes a data object (container) identified by its 1-3 byte identifier. + * + * @throws ISOException SW_REFERENCE_NOT_FOUND if no container matches the identifier + */ + private void processDeleteObjectRequest(TLVReader reader) { + // PRE-CONDITION 1 - The 'ID' tag MUST be present + if (!reader.match(Constants.TAG_OBJECT_ID)) { + ISOException.throwIt(Constants.SW_PUT_DATA_ID_MISSING); + return; + } + + // PRE-CONDITION 2 - The 'ID' tag MUST have length between 1 and 3 + short tagLength = reader.getLength(); + if (tagLength < Constants.OBJECT_ID_MIN_LENGTH || tagLength > Constants.OBJECT_ID_MAX_LENGTH) { + ISOException.throwIt(Constants.SW_PUT_DATA_ID_INVALID_LENGTH); + return; + } + + int id = PIVContainer.parseId(reader.getData(), reader.getDataOffset(), tagLength); + reader.moveNext(); + + // PRE-CONDITION 3 - The referenced object MUST exist + if (!dataStore.removeContainer(id)) { + ISOException.throwIt(Constants.SW_REFERENCE_NOT_FOUND); + } + } + + /** + * Deletes a verifier (PIN/PUK) identified by its single-byte reference. + * + * @throws ISOException SW_REFERENCE_NOT_FOUND if no verifier matches the identifier + */ + private void processDeletePinRequest(TLVReader reader) { + // PRE-CONDITION 1 - The 'ID' tag MUST be present + if (!reader.match(Constants.TAG_OBJECT_ID)) { + ISOException.throwIt(Constants.SW_PUT_DATA_ID_MISSING); + return; + } + + // PRE-CONDITION 2 - The 'ID' tag MUST be length 1 + if (reader.getLength() != (short) 1) { + ISOException.throwIt(Constants.SW_PUT_DATA_ID_INVALID_LENGTH); + return; + } + + byte id = reader.toByte(); + reader.moveNext(); + + // PRE-CONDITION 3 - The referenced verifier MUST exist + if (!dataStore.removeVerifier(id)) { + ISOException.throwIt(Constants.SW_REFERENCE_NOT_FOUND); + } + } + + /** + * Deletes one or more keys identified by a single-byte reference. The 'KEY MECHANISM' tag is + * optional: when supplied, only the key matching both the id and mechanism is removed; when + * omitted, every key sharing the id is removed (a reference may hold multiple mechanisms). + * + * @throws ISOException SW_REFERENCE_NOT_FOUND if no matching key exists + */ + private void processDeleteKeyRequest(TLVReader reader) { + // PRE-CONDITION 1 - The 'ID' tag MUST be present + if (!reader.match(Constants.TAG_OBJECT_ID)) { + ISOException.throwIt(Constants.SW_PUT_DATA_ID_MISSING); + return; + } + + // PRE-CONDITION 2 - The 'ID' tag MUST be length 1 + if (reader.getLength() != (short) 1) { + ISOException.throwIt(Constants.SW_PUT_DATA_ID_INVALID_LENGTH); + return; + } + + byte id = reader.toByte(); + reader.moveNext(); + + boolean found; + if (reader.match(Constants.TAG_KEY_MECHANISM)) { + // PRE-CONDITION 3 - If present, the 'KEY MECHANISM' tag MUST be length 1 + if (reader.getLength() != (short) 1) { + ISOException.throwIt(Constants.SW_PUT_DATA_KEY_MECHANISM_INVALID); + return; + } + + byte mechanism = reader.toByte(); + reader.moveNext(); + found = dataStore.removeKey(id, mechanism); + } else { + found = dataStore.removeKeysById(id); + } + + // PRE-CONDITION 4 - At least one matching key MUST have existed + if (!found) { + ISOException.throwIt(Constants.SW_REFERENCE_NOT_FOUND); + } + } + /** * This is the administrative equivalent for the PUT DATA card and is intended for use by Card * Management Systems to generate the on-card file-system. @@ -2269,6 +2368,21 @@ void putDataAdmin(PIVAPDU pApdu) throws ISOException { processCreateObjectRequest(operation, reader); break; + // Delete a data object (container) + case Constants.TAG_OP_DELETE_OBJECT: + processDeleteObjectRequest(reader); + break; + + // Delete a verifier + case Constants.TAG_OP_DELETE_PIN: + processDeletePinRequest(reader); + break; + + // Delete a key + case Constants.TAG_OP_DELETE_KEY: + processDeleteKeyRequest(reader); + break; + // Update one or more configuration parameters case Constants.TAG_OP_UPDATE_CONFIG: try { diff --git a/src/org/openfips201/applet/PIVDataStore.java b/src/org/openfips201/applet/PIVDataStore.java index 759e0712..84db67c7 100644 --- a/src/org/openfips201/applet/PIVDataStore.java +++ b/src/org/openfips201/applet/PIVDataStore.java @@ -65,6 +65,33 @@ void addContainer(PIVContainer container) { } } + /** + * Unlinks and erases the container matching {@code id}. Unlinking is performed before erasure so + * that a tear can never leave a cleared object reachable from the store. + * + * @return true if a container was found and removed + */ + boolean removeContainer(int id) { + PIVObject prev = null; + PIVObject current = firstContainer; + while (current != null) { + if (current.id == id) { + if (prev == null) { + firstContainer = (PIVContainer) current.nextObject; + } else { + prev.nextObject = current.nextObject; + } + current.clear(); + current.nextObject = null; + Platform.requestObjectDeletion(); + return true; + } + prev = current; + current = current.nextObject; + } + return false; + } + PIVKey getKey(byte id) { if (firstKey == null) { return null; @@ -102,7 +129,68 @@ void addKey(PIVKey key) { } else { firstKey.last().nextObject = key; } - } + } + + /** + * Unlinks and erases the single key matching both {@code id} and {@code mechanism}. + * + * @return true if a key was found and removed + */ + boolean removeKey(byte id, byte mechanism) { + int target = id & 0xFF; + PIVObject prev = null; + PIVObject current = firstKey; + while (current != null) { + if (current.id == target && ((PIVKey) current).getMechanism() == mechanism) { + if (prev == null) { + firstKey = (PIVKey) current.nextObject; + } else { + prev.nextObject = current.nextObject; + } + current.clear(); + current.nextObject = null; + Platform.requestObjectDeletion(); + return true; + } + prev = current; + current = current.nextObject; + } + return false; + } + + /** + * Unlinks and erases every key matching {@code id}, regardless of mechanism. This caters for the + * case where a single reference holds multiple keys (e.g. a PKI and a symmetric key at 9E). + * + * @return true if at least one key was found and removed + */ + boolean removeKeysById(byte id) { + int target = id & 0xFF; + boolean removed = false; + PIVObject prev = null; + PIVObject current = firstKey; + while (current != null) { + if (current.id == target) { + PIVObject next = current.nextObject; + if (prev == null) { + firstKey = (PIVKey) next; + } else { + prev.nextObject = next; + } + current.clear(); + current.nextObject = null; + removed = true; + current = next; // prev is unchanged, the removed node is gone + } else { + prev = current; + current = current.nextObject; + } + } + if (removed) { + Platform.requestObjectDeletion(); + } + return removed; + } PIVVerifier getVerifier(byte id) { if (firstVerifier == null) { @@ -174,7 +262,35 @@ void addVerifier(PIVVerifier verifier) { firstVerifier = verifier; } else { firstVerifier.last().nextObject = verifier; - } + } + } + + /** + * Unlinks and erases the verifier matching {@code id}. Note that clear() only resets the OwnerPIN + * state (the underlying object cannot be zeroised), so the node itself is released for reclamation. + * + * @return true if a verifier was found and removed + */ + boolean removeVerifier(byte id) { + int target = id & 0xFF; + PIVObject prev = null; + PIVObject current = firstVerifier; + while (current != null) { + if (current.id == target) { + if (prev == null) { + firstVerifier = (PIVVerifier) current.nextObject; + } else { + prev.nextObject = current.nextObject; + } + current.clear(); + current.nextObject = null; + Platform.requestObjectDeletion(); + return true; + } + prev = current; + current = current.nextObject; + } + return false; } }