Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
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: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Documentation of recommended status words for ApduInterpreterSpi implementations
- Exception handling guidelines for CommandProcessorApi implementations
- Added `KeyStorageType` enum to specify memory type (VOLATILE or NON_VOLATILE).
- Added `loadKey` method to `CommandProcessorApi` interface using `KeyStorageType` for loading card-specific
authentication keys into reader memory.
- Added `generalAuthenticate` method to `CommandProcessorApi` interface for performing authentication to contactless
cards using previously loaded keys.
### Changed
- Enhanced JavaDoc with comprehensive error handling information
- Added status word reference table following ISO 7816-4 and PC/SC standards

## [1.0.0] - 2025-07-08
This is the initial release.
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
group = org.eclipse.keyple
title = Keyple Plugin Storage Card API
description = API dedicated to standardize communication between Keyple plugins and APDU interpreters for storage card processing
version = 1.0.1-SNAPSHOT
version = 1.1.0-SNAPSHOT

# Java Configuration
javaSourceLevel = 1.8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public final class PluginStorageCardApiProperties {
*
* @since 1.0.0
*/
public static final String VERSION = "1.0";
public static final String VERSION = "1.1";

/** Private constructor */
private PluginStorageCardApiProperties() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@
*
* <p>To be implemented by the plugin.
*
* <h3>Exception Handling</h3>
*
* <p>All methods may throw exceptions to indicate error conditions. Implementations should
* distinguish between:
*
* <ul>
* <li>{@link IllegalArgumentException} - For invalid parameters that can be validated before
* hardware access
* <li>{@link Exception} - For technical errors during hardware operations (communication
* failures, authentication failures, etc.)
* </ul>
*
* <p>The exception message should provide clear diagnostic information about the failure cause.
*
* @since 1.0.0
*/
public interface CommandProcessorApi {
Expand Down Expand Up @@ -84,4 +98,90 @@ public interface CommandProcessorApi {
* @since 1.0.0
*/
void writeBlock(int blockAddress, byte[] data) throws Exception;

/**
* Loads a card-specific authentication key into the reader's memory.
*
* <p>This method stores a card key in either volatile (RAM) or non-volatile (EEPROM) memory of
* the card reader. The key can be subsequently used by the {@link #generalAuthenticate(int, int,
* int)} method to authenticate to the card. This command can be used for all kinds of contactless
* cards.
*
* <p>Volatile memory provides temporary storage that is cleared when the reader loses power,
* while non-volatile memory persists across power cycles. The availability of non-volatile memory
* depends on the specific reader hardware.
*
* <p>The key structure and length depend on the card type. For example:
*
* <ul>
* <li>Mifare cards: 6-byte keys (Type A or Type B)
* <li>Other contactless cards: card-specific key formats
* </ul>
*
* <p>The key number parameter identifies the storage location in the reader's memory. The valid
* range and meaning of key numbers depend on the reader implementation and whether volatile or
* non-volatile memory is used. Consult the reader documentation for specific key number
* assignments.
*
* <p>Note that keys stored in memory cannot be read back for security reasons. Once loaded, they
* can only be used for authentication operations.
*
* @param keyStorageType The type of memory to store the key in (VOLATILE or NON_VOLATILE).
* @param keyNumber The key index identifying the storage location. Valid ranges depend on the
* reader implementation and memory type.
* @param key A byte array containing the card-specific key value. Must not be null. The required
* length depends on the card type and authentication algorithm.
* @throws IllegalArgumentException if {@code keyStorageType} or {@code key} is null, if the key
* length is not valid for the card type, or if {@code keyNumber} is not in the valid range
* for the reader and memory type.
* @throws Exception if the load operation fails, if the specified memory type is not available on
* the reader hardware, or if a communication error occurs.
* @see #generalAuthenticate(int, int, int)
* @since 1.1.0
*/
void loadKey(KeyStorageType keyStorageType, int keyNumber, byte[] key)
throws Exception; // NOSONAR

/**
* Performs authentication to a contactless card using a previously loaded key.
*
* <p>This method authenticates to a specific memory location on the card using a key that was
* previously loaded into the reader's memory via the {@link #loadKey(KeyStorageType, int,
* byte[])} method. Successful authentication is typically required before performing read or
* write operations on protected memory areas.
*
* <p>The authentication process establishes a secure session between the reader and the card. The
* block address represents the block number or starting byte number of the card to be
* authenticated, depending on the card type.
*
* <p>The key type parameter is card-specific and indicates which type of key to use for
* authentication. Examples:
*
* <ul>
* <li>Mifare cards: 0x60 (KEY_A) or 0x61 (KEY_B)
* <li>Other contactless cards: card-specific key type values
* </ul>
*
* <p>The key number parameter references a key previously loaded via {@link
* #loadKey(KeyStorageType, int, byte[])} and identifies which stored key to use for this
* authentication operation.
*
* @param blockAddress The block number or starting byte number on the card where authentication
* is to be performed. Valid range depends on the card type and memory structure.
* @param keyType The type of key to use for authentication. The valid values are card-specific
* (e.g., 0x60 or 0x61 for Mifare cards).
* @param keyNumber The index of the previously loaded key to use for authentication. Must
* reference a key that was loaded via {@link #loadKey(KeyStorageType, int, byte[])}.
* @return {@code true} if authentication succeeded, {@code false} if authentication failed (e.g.,
* incorrect key).
* @throws IllegalArgumentException if {@code blockAddress} is out of valid range for the card
* type, if {@code keyType} is not supported by the card, or if {@code keyNumber} does not
* reference a valid loaded key.
* @throws Exception if the referenced key was not previously loaded, if the card does not support
* authentication, or if a communication error occurs.
* @see #loadKey(KeyStorageType, int, byte[])
* @since 1.1.0
*/
boolean generalAuthenticate(int blockAddress, int keyType, int keyNumber)
throws Exception; // NOSONAR
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* **************************************************************************************
* Copyright (c) 2026 Calypso Networks Association https://calypsonet.org/
*
* See the NOTICE file(s) distributed with this work for additional information
* regarding copyright ownership.
*
* This program and the accompanying materials are made available under the terms of the
* MIT License which is available at https://opensource.org/licenses/MIT
*
* SPDX-License-Identifier: MIT
************************************************************************************** */
package org.eclipse.keyple.core.plugin.storagecard.internal;

/**
* Enumeration defining the memory types available for key storage in the reader.
*
* @since 1.1.0
*/
public enum KeyStorageType {
/**
* Volatile memory (RAM).
*
* <p>Keys stored here are lost when the reader is powered off or reset. This is generally faster
* and supports unlimited write cycles.
*
* @since 1.1.0
*/
VOLATILE,

/**
* Non-volatile memory (EEPROM/Flash).
*
* <p>Keys stored here persist across power cycles. Note that non-volatile memory typically has a
* limited number of write cycles.
*
* @since 1.1.0
*/
NON_VOLATILE
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*
* @since 1.0.0
*/
@FunctionalInterface
public interface ApduInterpreterFactorySpi extends ApduInterpreterFactory {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,96 @@
package org.eclipse.keyple.core.plugin.storagecard.internal.spi;

import org.eclipse.keyple.core.plugin.storagecard.internal.CommandProcessorApi;
import org.eclipse.keyple.core.plugin.storagecard.internal.KeyStorageType;

/**
* Interface defining an APDU interpreter for processing commands sent to a card.
* Interface defining an APDU interpreter for processing commands sent to storage cards.
*
* <p>Its implementation is provided by {@link ApduInterpreterFactorySpi}.
*
* <p>Upon calling {@code processApdu}, implementations determine how to invoke the appropriate
* methods of {@link CommandProcessorApi} based on the card type.
* methods of {@link CommandProcessorApi} based on the APDU class byte (CLA) and instruction (INS).
*
* <p>For standard ISO 7816-4 APDUs, the interpreter directly calls {@link
* CommandProcessorApi#transmitIsoApdu(byte[])}. For storage-type cards, the interpreter translates
* the APDU into specific read/write operations using {@link CommandProcessorApi#getUID()}, {@link
* CommandProcessorApi#readBlock(int, int)} and {@link CommandProcessorApi#writeBlock(int, byte[])}.
* <h3>APDU Processing Strategy</h3>
*
* <p>The interpreter follows a two-pathway processing model:
*
* <ul>
* <li><strong>Standard ISO 7816-4 APDUs</strong> (CLA != 0xFF): Directly transmitted via {@link
* CommandProcessorApi#transmitIsoApdu(byte[])} without interpretation.
* <li><strong>Storage Card APDUs</strong> (CLA = 0xFF): Interpreted and translated into
* CommandProcessorApi method calls based on the instruction byte:
* <ul>
* <li><code>INS 0xCA</code> → {@link CommandProcessorApi#getUID()} - Get card UID
* <li><code>INS 0xB0</code> → {@link CommandProcessorApi#readBlock(int, int)} - Read binary
* data
* <li><code>INS 0xD6</code> → {@link CommandProcessorApi#writeBlock(int, byte[])} - Write
* binary data
* <li><code>INS 0x82</code> → {@link CommandProcessorApi#loadKey(KeyStorageType, int,
* byte[])} - Load authentication key
* <li><code>INS 0x86</code> → {@link CommandProcessorApi#generalAuthenticate(int, int,
* int)} - Authenticate with key
* </ul>
* </ul>
*
* <h3>Error Handling Strategy</h3>
*
* <p>This interface follows a <strong>trust-based validation</strong> approach:
*
* <ul>
* <li><strong>Validation</strong> is performed upstream by the card extension that generates the
* APDUs. Since this extension is part of the same ecosystem, its output is trusted.
* <li><strong>{@link CommandProcessorApi}</strong> does not perform parameter validation and
* trusts the incoming data to be correct.
* <li><strong>Exceptions propagate</strong> to the caller unless they represent normal protocol
* conditions (e.g., authentication failure, unsupported instruction).
* <li><strong>Status words</strong> are returned only for protocol-level conditions, not
* validation errors.
* </ul>
*
* <h3>Status Words Usage</h3>
*
* <p>Implementations return ISO 7816-4 / PC/SC compliant status words for the following conditions:
*
* <table border="1">
* <caption>Status Words returned by implementations</caption>
* <tr><th>Status Word</th><th>Code</th><th>Usage</th></tr>
* <tr><td>Success</td><td>0x9000</td><td>Successful execution</td></tr>
* <tr><td>Security status not satisfied</td><td>0x6982</td><td>Authentication failed (PC/SC
* compliant)</td></tr>
* <tr><td>INS not supported</td><td>0x6D00</td><td>Unknown instruction code</td></tr>
* </table>
*
* <h3>Exception Propagation</h3>
*
* <p>The following error conditions may result in exceptions:
*
* <ul>
* <li><strong>Hardware/communication errors</strong> (card not responding, transmission failure)
* - thrown by {@link CommandProcessorApi}
* <li><strong>Protocol-level errors</strong> that cannot be represented by status words - may be
* thrown by implementations
* </ul>
*
* <p><strong>Note:</strong> Parameter validation (P1/P2 values, data lengths, key numbers, etc.) is
* the responsibility of the card extension generating the APDUs, not this interface or its
* implementations.
*
* <h3>PC/SC Compliance</h3>
*
* <p>Storage card commands (CLA=0xFF) follow the PC/SC v2.01.09 specification:
*
* <ul>
* <li>LOAD KEY: <code>FF 82 [P1] [P2] 06 [6-byte key]</code>
* <li>GENERAL AUTHENTICATE: <code>FF 86 00 00 05 [version][addr-MSB][addr-LSB][key-type][key-num]
* </code>
* <li>GET DATA (UID): <code>FF CA 00 00 [Le]</code>
* <li>READ BINARY: <code>FF B0 [P1] [P2] [Le]</code>
* <li>UPDATE BINARY: <code>FF D6 [P1] [P2] [Lc] [data]</code>
* </ul>
*
* @see CommandProcessorApi
* @see ApduInterpreterFactorySpi
* @since 1.0.0
*/
public interface ApduInterpreterSpi {
Expand Down
10 changes: 9 additions & 1 deletion src/main/uml/api_class_diagram.puml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@startuml
title
Keyple - keyple-plugin-storagecard-java-api - 1.0.+ (2025-02-21)
Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2026-01-14)
end title

' == THEME ==
Expand Down Expand Up @@ -58,12 +58,19 @@ package "org.eclipse.keyple.core.plugin.storagecard" as api #C_GREY1 {
+interface ApduInterpreterFactory <<red>> {
}
package "internal" as internal #C_GREY2 {
+enum "<color:blue>KeyStorageType" as KeyStorageType <<green>> {
<color:blue>VOLATILE
<color:blue>NON_VOLATILE
}
+interface CommandProcessorApi {
+byte[] transmitIsoApdu(byte[] apdu)

+byte[] getUID()
+byte[] readBlock(int blockAddress, int length)
+void writeBlock(int blockAddress, byte[] data)

+<color:blue>void loadKey(KeyStorageType keyStorageType, int keyNumber, byte[] key)
+<color:blue>boolean generalAuthenticate(int blockAddress, int keyType, int keyNumber)
}
package spi #C_GREY3 {
+interface ApduInterpreterFactorySpi <<red>> extends api.ApduInterpreterFactory {
Expand All @@ -81,6 +88,7 @@ package "org.eclipse.keyple.core.plugin.storagecard" as api #C_GREY1 {
' Associations
ApduInterpreterFactorySpi --> ApduInterpreterSpi : create >
ApduInterpreterSpi --> CommandProcessorApi : use >
CommandProcessorApi --> KeyStorageType : use >

' == LAYOUT ==

Expand Down
2 changes: 1 addition & 1 deletion src/main/uml/api_class_diagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading