Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/org/openfips201/applet/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
116 changes: 115 additions & 1 deletion src/org/openfips201/applet/PIV.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 6 additions & 1 deletion src/org/openfips201/applet/PIVAPDU.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
120 changes: 118 additions & 2 deletions src/org/openfips201/applet/PIVDataStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
}