From c0efefe030c11d853df8e02456c595a06b1aa073 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Mon, 1 Dec 2025 16:58:10 +0100 Subject: [PATCH 01/14] feat: update API diagram to version 1.1 with new methods for key loading and authentication (Mifare Classic) --- src/main/uml/api_class_diagram.puml | 5 ++++- src/main/uml/api_class_diagram.svg | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/uml/api_class_diagram.puml b/src/main/uml/api_class_diagram.puml index 9243dd7..34cff42 100644 --- a/src/main/uml/api_class_diagram.puml +++ b/src/main/uml/api_class_diagram.puml @@ -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.+ (2025-12-01) end title ' == THEME == @@ -64,6 +64,9 @@ package "org.eclipse.keyple.core.plugin.storagecard" as api #C_GREY1 { +byte[] getUID() +byte[] readBlock(int blockAddress, int length) +void writeBlock(int blockAddress, byte[] data) + + +void loadKey(boolean isVolatileMemory, byte keyNumber, byte[] key) + +void generalAuthenticate(int blockAddress, int keyType, int keyNumber) } package spi #C_GREY3 { +interface ApduInterpreterFactorySpi <> extends api.ApduInterpreterFactory { diff --git a/src/main/uml/api_class_diagram.svg b/src/main/uml/api_class_diagram.svg index 3477e11..4e981bf 100644 --- a/src/main/uml/api_class_diagram.svg +++ b/src/main/uml/api_class_diagram.svg @@ -1 +1 @@ -Keyple - keyple-plugin-storagecard-java-api - 1.0.+ (2025-02-21)Keyple - keyple-plugin-storagecard-java-api - 1.0.+ (2025-02-21)org.eclipse.keyple.core.plugin.storagecardinternalspifinalPluginStorageCardApiPropertiesfinalString VERSIONApduInterpreterFactoryCommandProcessorApibyte[] transmitIsoApdu(byte[] apdu) byte[] getUID()byte[] readBlock(int blockAddress, int length)void writeBlock(int blockAddress, byte[] data)ApduInterpreterFactorySpiApduInterpreterSpi createApduInterpreter()ApduInterpreterSpivoid setCommandProcessor(CommandProcessorApi CommandProcessorApi) byte[] processApdu(byte[] apdu)createuse \ No newline at end of file +Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2025-12-01)Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2025-12-01)org.eclipse.keyple.core.plugin.storagecardinternalspifinalPluginStorageCardApiPropertiesfinalString VERSIONApduInterpreterFactoryCommandProcessorApibyte[] transmitIsoApdu(byte[] apdu) byte[] getUID()byte[] readBlock(int blockAddress, int length)void writeBlock(int blockAddress, byte[] data) void loadKey(boolean isVolatileMemory, byte keyNumber, byte[] key)void generalAuthenticate(int blockAddress, int keyType, int keyNumber)ApduInterpreterFactorySpiApduInterpreterSpi createApduInterpreter()ApduInterpreterSpivoid setCommandProcessor(CommandProcessorApi CommandProcessorApi) byte[] processApdu(byte[] apdu)createuse \ No newline at end of file From 7c8e9bdb617b566239aedb9d07ec510663e74cf5 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Mon, 12 Jan 2026 09:10:20 +0100 Subject: [PATCH 02/14] feat: introduce key loading and authentication methods --- CHANGELOG.md | 3 + gradle.properties | 2 +- .../PluginStorageCardApiProperties.java | 2 +- .../internal/CommandProcessorApi.java | 83 +++++++++++++++++++ 4 files changed, 88 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a09852..26cb558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ 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 +- Added `loadKey` method to `CommandProcessorApi` interface for loading card-specific authentication keys into reader memory (volatile or non-volatile). +- Added `generalAuthenticate` method to `CommandProcessorApi` interface for performing authentication to contactless cards using previously loaded keys. ## [1.0.0] - 2025-07-08 This is the initial release. diff --git a/gradle.properties b/gradle.properties index d51898b..4a99b9c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/PluginStorageCardApiProperties.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/PluginStorageCardApiProperties.java index 00e76ad..35a481f 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/PluginStorageCardApiProperties.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/PluginStorageCardApiProperties.java @@ -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() {} diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java index 200fe5a..ff15779 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java @@ -84,4 +84,87 @@ 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. + * + *

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. + * + *

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. + * + *

The key structure and length depend on the card type. For example: + * + *

    + *
  • Mifare cards: 6-byte keys (Type A or Type B) + *
  • Other contactless cards: card-specific key formats + *
+ * + *

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. + * + *

Note that keys stored in memory cannot be read back for security reasons. Once loaded, they + * can only be used for authentication operations. + * + * @param isVolatileMemory {@code true} to store the key in volatile memory (RAM), {@code false} + * to store in non-volatile memory (EEPROM). + * @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 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(boolean isVolatileMemory, int keyNumber, byte[] key) throws Exception; + + /** + * Performs authentication to a contactless card using a previously loaded key. + * + *

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(boolean, int, byte[])} + * method. Successful authentication is typically required before performing read or write + * operations on protected memory areas. + * + *

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. + * + *

The key type parameter is card-specific and indicates which type of key to use for + * authentication. Examples: + * + *

    + *
  • Mifare cards: 0x60 (KEY_A) or 0x61 (KEY_B) + *
  • Other contactless cards: card-specific key type values + *
+ * + *

The key number parameter references a key previously loaded via {@link #loadKey(boolean, + * 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(boolean, int, byte[])}. + * @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 authentication fails (incorrect key or access denied), if the + * referenced key was not previously loaded, if the card does not support authentication, or + * if a communication error occurs. + * @see #loadKey(boolean, int, byte[]) + * @since 1.1.0 + */ + void generalAuthenticate(int blockAddress, int keyType, int keyNumber) throws Exception; } From 490da95ae3b0a458482e49f5fd09bf82f90c24cf Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Mon, 12 Jan 2026 16:38:51 +0100 Subject: [PATCH 03/14] feat: add KeyProviderSpi interface for dynamic key retrieval and update API diagram --- CHANGELOG.md | 11 +++- .../spi/ApduInterpreterFactorySpi.java | 1 + .../internal/spi/KeyProviderSpi.java | 64 +++++++++++++++++++ src/main/uml/api_class_diagram.puml | 3 + 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/KeyProviderSpi.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 26cb558..2c41a07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Changelog + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), @@ -6,11 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Added `loadKey` method to `CommandProcessorApi` interface for loading card-specific authentication keys into reader memory (volatile or non-volatile). -- Added `generalAuthenticate` method to `CommandProcessorApi` interface for performing authentication to contactless cards using previously loaded keys. +- Added `loadKey` method to `CommandProcessorApi` interface for loading card-specific authentication keys into reader + memory (volatile or non-volatile). +- Added `generalAuthenticate` method to `CommandProcessorApi` interface for performing authentication to contactless + cards using previously loaded keys. +- Added `KeyProviderSpi` SPI interface for dynamic key retrieval from secure external storage (e.g., HSM, KeyStore, secure + cloud), enabling the "External Vault" security pattern. ## [1.0.0] - 2025-07-08 + This is the initial release. [unreleased]: https://github.com/eclipse-keyple/keyple-plugin-storagecard-java-api/compare/1.0.0...HEAD + [1.0.0]: https://github.com/eclipse-keyple/keyple-plugin-storagecard-java-api/releases/tag/1.0.0 diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterFactorySpi.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterFactorySpi.java index b7ef840..cf49eae 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterFactorySpi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterFactorySpi.java @@ -20,6 +20,7 @@ * * @since 1.0.0 */ +@FunctionalInterface public interface ApduInterpreterFactorySpi extends ApduInterpreterFactory { /** diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/KeyProviderSpi.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/KeyProviderSpi.java new file mode 100644 index 0000000..086bb66 --- /dev/null +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/KeyProviderSpi.java @@ -0,0 +1,64 @@ +/* ************************************************************************************** + * Copyright (c) 2025 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.spi; + +/** + * Interface for providing authentication keys dynamically to plugins during card operations. + * + *

This interface enables the "External Vault" security pattern, where cryptographic keys are + * stored securely by the application (e.g., in a Hardware Security Module, KeyStore, secure cloud + * service, or encrypted file) and provided to the plugin strictly on-demand, rather than being + * pre-loaded into reader memory. + * + *

This approach offers several security benefits: + * + *

    + *
  • Keys remain in secure storage and are only retrieved when needed for authentication + *
  • Reduces the attack surface by minimizing the time keys spend in plugin/reader memory + *
  • Allows centralized key management across multiple readers and plugins + *
  • Supports dynamic key rotation and revocation + *
+ * + *

The plugin will typically call this interface when performing authentication operations (e.g., + * via {@code generalAuthenticate}) if a key has not been previously loaded into the reader's memory + * via {@code loadKey}. + * + *

Security Consideration: The returned key byte array should be cleared (zeroed + * out) by the plugin after use to prevent keys from lingering in memory. + * + *

To be implemented by the application. + * + * @since 1.1.0 + */ +@FunctionalInterface +public interface KeyProviderSpi { + + /** + * Retrieves the authentication key associated with the specified key index. + * + *

This method is called by the plugin when a key is required for authentication but has not + * been previously loaded into the reader's memory. The key index corresponds to the logical key + * number used in authentication commands. + * + *

The key structure and length depend on the card type and authentication algorithm. + * + *

If the requested key is not available or cannot be retrieved, this method should return + * {@code null}. The plugin will then fail the authentication operation with an appropriate error. + * + * @param keyIndex The index of the key requested by the authentication operation. The meaning and + * valid range of key indices depend on the specific plugin and card type implementation. + * @return A byte array containing the raw key bytes, or {@code null} if the key is not available, + * not found, or access is denied. + * @since 1.1.0 + */ + byte[] getKey(int keyIndex); +} diff --git a/src/main/uml/api_class_diagram.puml b/src/main/uml/api_class_diagram.puml index 34cff42..52c1188 100644 --- a/src/main/uml/api_class_diagram.puml +++ b/src/main/uml/api_class_diagram.puml @@ -77,6 +77,9 @@ package "org.eclipse.keyple.core.plugin.storagecard" as api #C_GREY1 { +byte[] processApdu(byte[] apdu) } + +interface KeyProviderSpi <> { + +byte[] getKey(int keyIndex) + } } } } From 031f94b7320f57e63b3ea544d64c58712a3740be Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Mon, 12 Jan 2026 16:42:30 +0100 Subject: [PATCH 04/14] docs: document status words and exception handling in API interfaces --- .../internal/CommandProcessorApi.java | 14 +++++++++++++ .../internal/spi/ApduInterpreterSpi.java | 21 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java index ff15779..7b2ca96 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java @@ -16,6 +16,20 @@ * *

To be implemented by the plugin. * + *

Exception Handling

+ * + *

All methods may throw exceptions to indicate error conditions. Implementations should + * distinguish between: + * + *

    + *
  • {@link IllegalArgumentException} - For invalid parameters that can be validated before + * hardware access + *
  • {@link Exception} - For technical errors during hardware operations (communication + * failures, authentication failures, etc.) + *
+ * + *

The exception message should provide clear diagnostic information about the failure cause. + * * @since 1.0.0 */ public interface CommandProcessorApi { diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java index 4d627e4..7aec4dc 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java @@ -26,6 +26,27 @@ * the APDU into specific read/write operations using {@link CommandProcessorApi#getUID()}, {@link * CommandProcessorApi#readBlock(int, int)} and {@link CommandProcessorApi#writeBlock(int, byte[])}. * + *

Status Words

+ * + *

While this method declares {@code throws Exception} for interface compatibility, + * implementations are recommended to convert all errors to ISO 7816-4 compliant + * status words for APDU protocol conformity: + * + * + * + * + * + * + * + * + * + * + *
Status WordCodeUsage
Success0x9000Successful execution
Wrong length0x6700Invalid data length
Incorrect P1-P20x6A86Invalid parameters
Conditions not satisfied0x6985Preconditions not met
Authentication failed0x6300Wrong authentication key
Technical problem0x6581Hardware/communication error
INS not supported0x6D00Unknown instruction
+ * + *

Implementations that convert exceptions to status words should log the full exception details + * (including stack traces) for diagnostic purposes while returning appropriate status words to the + * client. + * * @since 1.0.0 */ public interface ApduInterpreterSpi { From bf1b721de6b7626bc01ddf7e00dfcd0673ffdfbd Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Tue, 13 Jan 2026 09:29:35 +0100 Subject: [PATCH 05/14] feat: remove KeyProviderSpi as key retrieval will be internal to the plugin --- CHANGELOG.md | 9 ++- .../internal/spi/KeyProviderSpi.java | 64 ------------------- src/main/uml/api_class_diagram.puml | 3 - 3 files changed, 7 insertions(+), 69 deletions(-) delete mode 100644 src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/KeyProviderSpi.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c41a07..02035b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Documentation of recommended status words for ApduInterpreterSpi implementations +- Exception handling guidelines for CommandProcessorApi implementations - Added `loadKey` method to `CommandProcessorApi` interface for loading card-specific authentication keys into reader memory (volatile or non-volatile). - Added `generalAuthenticate` method to `CommandProcessorApi` interface for performing authentication to contactless cards using previously loaded keys. -- Added `KeyProviderSpi` SPI interface for dynamic key retrieval from secure external storage (e.g., HSM, KeyStore, secure - cloud), enabling the "External Vault" security pattern. + +### Changed +- Enhanced JavaDoc with comprehensive error handling information +- Added status word reference table following ISO 7816-4 standard + ## [1.0.0] - 2025-07-08 diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/KeyProviderSpi.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/KeyProviderSpi.java deleted file mode 100644 index 086bb66..0000000 --- a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/KeyProviderSpi.java +++ /dev/null @@ -1,64 +0,0 @@ -/* ************************************************************************************** - * Copyright (c) 2025 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.spi; - -/** - * Interface for providing authentication keys dynamically to plugins during card operations. - * - *

This interface enables the "External Vault" security pattern, where cryptographic keys are - * stored securely by the application (e.g., in a Hardware Security Module, KeyStore, secure cloud - * service, or encrypted file) and provided to the plugin strictly on-demand, rather than being - * pre-loaded into reader memory. - * - *

This approach offers several security benefits: - * - *

    - *
  • Keys remain in secure storage and are only retrieved when needed for authentication - *
  • Reduces the attack surface by minimizing the time keys spend in plugin/reader memory - *
  • Allows centralized key management across multiple readers and plugins - *
  • Supports dynamic key rotation and revocation - *
- * - *

The plugin will typically call this interface when performing authentication operations (e.g., - * via {@code generalAuthenticate}) if a key has not been previously loaded into the reader's memory - * via {@code loadKey}. - * - *

Security Consideration: The returned key byte array should be cleared (zeroed - * out) by the plugin after use to prevent keys from lingering in memory. - * - *

To be implemented by the application. - * - * @since 1.1.0 - */ -@FunctionalInterface -public interface KeyProviderSpi { - - /** - * Retrieves the authentication key associated with the specified key index. - * - *

This method is called by the plugin when a key is required for authentication but has not - * been previously loaded into the reader's memory. The key index corresponds to the logical key - * number used in authentication commands. - * - *

The key structure and length depend on the card type and authentication algorithm. - * - *

If the requested key is not available or cannot be retrieved, this method should return - * {@code null}. The plugin will then fail the authentication operation with an appropriate error. - * - * @param keyIndex The index of the key requested by the authentication operation. The meaning and - * valid range of key indices depend on the specific plugin and card type implementation. - * @return A byte array containing the raw key bytes, or {@code null} if the key is not available, - * not found, or access is denied. - * @since 1.1.0 - */ - byte[] getKey(int keyIndex); -} diff --git a/src/main/uml/api_class_diagram.puml b/src/main/uml/api_class_diagram.puml index 52c1188..34cff42 100644 --- a/src/main/uml/api_class_diagram.puml +++ b/src/main/uml/api_class_diagram.puml @@ -77,9 +77,6 @@ package "org.eclipse.keyple.core.plugin.storagecard" as api #C_GREY1 { +byte[] processApdu(byte[] apdu) } - +interface KeyProviderSpi <> { - +byte[] getKey(int keyIndex) - } } } } From aab36d907693bc9ff777cec7e995cb89c4e3b819 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Tue, 13 Jan 2026 09:31:54 +0100 Subject: [PATCH 06/14] docs: remove empty lines in CHANGELOG.md --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02035b8..f2a26b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,12 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 memory (volatile or non-volatile). - 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 standard - ## [1.0.0] - 2025-07-08 This is the initial release. From 24a3ad728d30fec52b7dc0e0187a278ff2e50766 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Tue, 13 Jan 2026 11:01:55 +0100 Subject: [PATCH 07/14] docs: update status word reference description --- CHANGELOG.md | 2 +- .../plugin/storagecard/internal/spi/ApduInterpreterSpi.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2a26b6..fe12e19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 cards using previously loaded keys. ### Changed - Enhanced JavaDoc with comprehensive error handling information -- Added status word reference table following ISO 7816-4 standard +- Added status word reference table following ISO 7816-4 and PC/SC standards ## [1.0.0] - 2025-07-08 diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java index 7aec4dc..bdaf973 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java @@ -38,6 +38,8 @@ * Wrong length0x6700Invalid data length * Incorrect P1-P20x6A86Invalid parameters * Conditions not satisfied0x6985Preconditions not met + * Key type not known0x6986Invalid key type (PC/SC) + * Key number not valid0x6988Invalid key number (PC/SC) * Authentication failed0x6300Wrong authentication key * Technical problem0x6581Hardware/communication error * INS not supported0x6D00Unknown instruction From 97eb61ff027fe7e20ee21dd94320de4d8058b0af Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Wed, 14 Jan 2026 13:46:12 +0100 Subject: [PATCH 08/14] refactor: replace boolean isVolatileMemory with KeyStorageType enum in loadKey --- CHANGELOG.md | 5 ++- .../internal/CommandProcessorApi.java | 26 ++++++------- .../storagecard/internal/KeyStorageType.java | 39 +++++++++++++++++++ src/main/uml/api_class_diagram.puml | 9 ++++- src/main/uml/api_class_diagram.svg | 2 +- 5 files changed, 63 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/KeyStorageType.java diff --git a/CHANGELOG.md b/CHANGELOG.md index fe12e19..9948802 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Documentation of recommended status words for ApduInterpreterSpi implementations - Exception handling guidelines for CommandProcessorApi implementations -- Added `loadKey` method to `CommandProcessorApi` interface for loading card-specific authentication keys into reader - memory (volatile or non-volatile). +- 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 diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java index 7b2ca96..9aa6d77 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java @@ -126,29 +126,28 @@ public interface CommandProcessorApi { *

Note that keys stored in memory cannot be read back for security reasons. Once loaded, they * can only be used for authentication operations. * - * @param isVolatileMemory {@code true} to store the key in volatile memory (RAM), {@code false} - * to store in non-volatile memory (EEPROM). + * @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 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 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(boolean isVolatileMemory, int keyNumber, byte[] key) throws Exception; + void loadKey(KeyStorageType keyStorageType, int keyNumber, byte[] key) throws Exception; /** * Performs authentication to a contactless card using a previously loaded key. * *

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(boolean, int, byte[])} - * method. Successful authentication is typically required before performing read or write - * operations on protected memory areas. + * 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. * *

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 @@ -162,22 +161,23 @@ public interface CommandProcessorApi { *

  • Other contactless cards: card-specific key type values * * - *

    The key number parameter references a key previously loaded via {@link #loadKey(boolean, - * int, byte[])} and identifies which stored key to use for this authentication operation. + *

    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(boolean, int, byte[])}. + * reference a key that was loaded via {@link #loadKey(KeyStorageType, int, byte[])}. * @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 authentication fails (incorrect key or access denied), if the * referenced key was not previously loaded, if the card does not support authentication, or * if a communication error occurs. - * @see #loadKey(boolean, int, byte[]) + * @see #loadKey(KeyStorageType, int, byte[]) * @since 1.1.0 */ void generalAuthenticate(int blockAddress, int keyType, int keyNumber) throws Exception; diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/KeyStorageType.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/KeyStorageType.java new file mode 100644 index 0000000..122b3b8 --- /dev/null +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/KeyStorageType.java @@ -0,0 +1,39 @@ +/* ************************************************************************************** + * Copyright (c) 2025 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). + * + *

    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). + * + *

    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 +} diff --git a/src/main/uml/api_class_diagram.puml b/src/main/uml/api_class_diagram.puml index 34cff42..8cb17d9 100644 --- a/src/main/uml/api_class_diagram.puml +++ b/src/main/uml/api_class_diagram.puml @@ -1,6 +1,6 @@ @startuml title - Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2025-12-01) + Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2026-01-14) end title ' == THEME == @@ -58,6 +58,10 @@ package "org.eclipse.keyple.core.plugin.storagecard" as api #C_GREY1 { +interface ApduInterpreterFactory <> { } package "internal" as internal #C_GREY2 { + +enum "KeyStorageType" as KeyStorageType <> { + VOLATILE + NON_VOLATILE + } +interface CommandProcessorApi { +byte[] transmitIsoApdu(byte[] apdu) @@ -65,7 +69,7 @@ package "org.eclipse.keyple.core.plugin.storagecard" as api #C_GREY1 { +byte[] readBlock(int blockAddress, int length) +void writeBlock(int blockAddress, byte[] data) - +void loadKey(boolean isVolatileMemory, byte keyNumber, byte[] key) + +void loadKey(KeyStorageType keyStorageType, int keyNumber, byte[] key) +void generalAuthenticate(int blockAddress, int keyType, int keyNumber) } package spi #C_GREY3 { @@ -84,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 == diff --git a/src/main/uml/api_class_diagram.svg b/src/main/uml/api_class_diagram.svg index 4e981bf..ed707d2 100644 --- a/src/main/uml/api_class_diagram.svg +++ b/src/main/uml/api_class_diagram.svg @@ -1 +1 @@ -Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2025-12-01)Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2025-12-01)org.eclipse.keyple.core.plugin.storagecardinternalspifinalPluginStorageCardApiPropertiesfinalString VERSIONApduInterpreterFactoryCommandProcessorApibyte[] transmitIsoApdu(byte[] apdu) byte[] getUID()byte[] readBlock(int blockAddress, int length)void writeBlock(int blockAddress, byte[] data) void loadKey(boolean isVolatileMemory, byte keyNumber, byte[] key)void generalAuthenticate(int blockAddress, int keyType, int keyNumber)ApduInterpreterFactorySpiApduInterpreterSpi createApduInterpreter()ApduInterpreterSpivoid setCommandProcessor(CommandProcessorApi CommandProcessorApi) byte[] processApdu(byte[] apdu)createuse \ No newline at end of file +Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2026-01-14)Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2026-01-14)org.eclipse.keyple.core.plugin.storagecardinternalspifinalPluginStorageCardApiPropertiesfinalString VERSIONApduInterpreterFactoryKeyStorageTypeVOLATILENON_VOLATILECommandProcessorApibyte[] transmitIsoApdu(byte[] apdu) byte[] getUID()byte[] readBlock(int blockAddress, int length)void writeBlock(int blockAddress, byte[] data) void loadKey(KeyStorageType keyStorageType, int keyNumber, byte[] key)void generalAuthenticate(int blockAddress, int keyType, int keyNumber)ApduInterpreterFactorySpiApduInterpreterSpi createApduInterpreter()ApduInterpreterSpivoid setCommandProcessor(CommandProcessorApi CommandProcessorApi) byte[] processApdu(byte[] apdu)createuseuse \ No newline at end of file From 3e78a46ae401eba50bcddf6f98a320eb413068e2 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Wed, 14 Jan 2026 14:37:24 +0100 Subject: [PATCH 09/14] docs: update copyright year and remove extra lines in CHANGELOG.md --- CHANGELOG.md | 3 --- .../core/plugin/storagecard/internal/KeyStorageType.java | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9948802..5ea783f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,4 @@ # Changelog - All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), @@ -19,9 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added status word reference table following ISO 7816-4 and PC/SC standards ## [1.0.0] - 2025-07-08 - This is the initial release. [unreleased]: https://github.com/eclipse-keyple/keyple-plugin-storagecard-java-api/compare/1.0.0...HEAD - [1.0.0]: https://github.com/eclipse-keyple/keyple-plugin-storagecard-java-api/releases/tag/1.0.0 diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/KeyStorageType.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/KeyStorageType.java index 122b3b8..f3884bd 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/KeyStorageType.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/KeyStorageType.java @@ -1,5 +1,5 @@ /* ************************************************************************************** - * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ + * Copyright (c) 2026 Calypso Networks Association https://calypsonet.org/ * * See the NOTICE file(s) distributed with this work for additional information * regarding copyright ownership. From e031eb1b24ce6f9fa9e33bf477a76f4a3064b977 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Wed, 14 Jan 2026 15:02:54 +0100 Subject: [PATCH 10/14] refactor: add NOSONAR to suppress false-positive issues in loadKey and generalAuthenticate methods --- .../plugin/storagecard/internal/CommandProcessorApi.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java index 9aa6d77..72f8eca 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java @@ -139,7 +139,8 @@ public interface CommandProcessorApi { * @see #generalAuthenticate(int, int, int) * @since 1.1.0 */ - void loadKey(KeyStorageType keyStorageType, int keyNumber, byte[] key) throws Exception; + void loadKey(KeyStorageType keyStorageType, int keyNumber, byte[] key) + throws Exception; // NOSONAR /** * Performs authentication to a contactless card using a previously loaded key. @@ -180,5 +181,6 @@ public interface CommandProcessorApi { * @see #loadKey(KeyStorageType, int, byte[]) * @since 1.1.0 */ - void generalAuthenticate(int blockAddress, int keyType, int keyNumber) throws Exception; + void generalAuthenticate(int blockAddress, int keyType, int keyNumber) + throws Exception; // NOSONAR } From bbf0e43d3827e2d6398fa3de60b7ca5e68d059eb Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Wed, 14 Jan 2026 16:10:28 +0100 Subject: [PATCH 11/14] refactor: change generalAuthenticate return type to boolean --- .../plugin/storagecard/internal/CommandProcessorApi.java | 9 +++++---- src/main/uml/api_class_diagram.puml | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java index 72f8eca..1c1f97b 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java @@ -172,15 +172,16 @@ void loadKey(KeyStorageType keyStorageType, int keyNumber, byte[] key) * (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 authentication fails (incorrect key or access denied), if the - * referenced key was not previously loaded, if the card does not support authentication, or - * if a communication error occurs. + * @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 */ - void generalAuthenticate(int blockAddress, int keyType, int keyNumber) + boolean generalAuthenticate(int blockAddress, int keyType, int keyNumber) throws Exception; // NOSONAR } diff --git a/src/main/uml/api_class_diagram.puml b/src/main/uml/api_class_diagram.puml index 8cb17d9..17d1a26 100644 --- a/src/main/uml/api_class_diagram.puml +++ b/src/main/uml/api_class_diagram.puml @@ -70,7 +70,7 @@ package "org.eclipse.keyple.core.plugin.storagecard" as api #C_GREY1 { +void writeBlock(int blockAddress, byte[] data) +void loadKey(KeyStorageType keyStorageType, int keyNumber, byte[] key) - +void generalAuthenticate(int blockAddress, int keyType, int keyNumber) + +boolean generalAuthenticate(int blockAddress, int keyType, int keyNumber) } package spi #C_GREY3 { +interface ApduInterpreterFactorySpi <> extends api.ApduInterpreterFactory { From c2e82f0b6f070eea062f021e2cda6d15559b81d4 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Wed, 14 Jan 2026 16:19:40 +0100 Subject: [PATCH 12/14] update: modify API class diagram to reflect the latest class structure --- src/main/uml/api_class_diagram.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/uml/api_class_diagram.svg b/src/main/uml/api_class_diagram.svg index ed707d2..b2a0e0c 100644 --- a/src/main/uml/api_class_diagram.svg +++ b/src/main/uml/api_class_diagram.svg @@ -1 +1 @@ -Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2026-01-14)Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2026-01-14)org.eclipse.keyple.core.plugin.storagecardinternalspifinalPluginStorageCardApiPropertiesfinalString VERSIONApduInterpreterFactoryKeyStorageTypeVOLATILENON_VOLATILECommandProcessorApibyte[] transmitIsoApdu(byte[] apdu) byte[] getUID()byte[] readBlock(int blockAddress, int length)void writeBlock(int blockAddress, byte[] data) void loadKey(KeyStorageType keyStorageType, int keyNumber, byte[] key)void generalAuthenticate(int blockAddress, int keyType, int keyNumber)ApduInterpreterFactorySpiApduInterpreterSpi createApduInterpreter()ApduInterpreterSpivoid setCommandProcessor(CommandProcessorApi CommandProcessorApi) byte[] processApdu(byte[] apdu)createuseuse \ No newline at end of file +Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2026-01-14)Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2026-01-14)org.eclipse.keyple.core.plugin.storagecardinternalspifinalPluginStorageCardApiPropertiesfinalString VERSIONApduInterpreterFactoryKeyStorageTypeVOLATILENON_VOLATILECommandProcessorApibyte[] transmitIsoApdu(byte[] apdu) byte[] getUID()byte[] readBlock(int blockAddress, int length)void writeBlock(int blockAddress, byte[] data) void loadKey(KeyStorageType keyStorageType, int keyNumber, byte[] key)boolean generalAuthenticate(int blockAddress, int keyType, int keyNumber)ApduInterpreterFactorySpiApduInterpreterSpi createApduInterpreter()ApduInterpreterSpivoid setCommandProcessor(CommandProcessorApi CommandProcessorApi) byte[] processApdu(byte[] apdu)createuseuse \ No newline at end of file From 5c3c6cfc5a8b88a3f1c43ba2ce28ec07e2b12676 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Wed, 14 Jan 2026 17:08:54 +0100 Subject: [PATCH 13/14] docs: enhance APDU interpreter interface documentation with detailed processing strategy, error handling, and PC/SC compliance --- .../internal/spi/ApduInterpreterSpi.java | 93 ++++++++++++++----- 1 file changed, 72 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java index bdaf973..7f02830 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java @@ -12,43 +12,94 @@ 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. * *

    Its implementation is provided by {@link ApduInterpreterFactorySpi}. * *

    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). * - *

    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[])}. + *

    APDU Processing Strategy

    * - *

    Status Words

    + *

    The interpreter follows a two-pathway processing model: * - *

    While this method declares {@code throws Exception} for interface compatibility, - * implementations are recommended to convert all errors to ISO 7816-4 compliant - * status words for APDU protocol conformity: + *

      + *
    • Standard ISO 7816-4 APDUs (CLA != 0xFF): Directly transmitted via {@link + * CommandProcessorApi#transmitIsoApdu(byte[])} without interpretation. + *
    • Storage Card APDUs (CLA = 0xFF): Interpreted and translated into + * CommandProcessorApi method calls based on the instruction byte: + *
        + *
      • INS 0xCA → {@link CommandProcessorApi#getUID()} - Get card UID + *
      • INS 0xB0 → {@link CommandProcessorApi#readBlock(int, int)} - Read binary + * data + *
      • INS 0xD6 → {@link CommandProcessorApi#writeBlock(int, byte[])} - Write + * binary data + *
      • INS 0x82 → {@link CommandProcessorApi#loadKey(KeyStorageType, int, + * byte[])} - Load authentication key + *
      • INS 0x86 → {@link CommandProcessorApi#generalAuthenticate(int, int, + * int)} - Authenticate with key + *
      + *
    + * + *

    Error Handling Strategy

    + * + *

    This interface uses a delegation-based validation approach: + * + *

      + *
    • Validation is delegated to {@link CommandProcessorApi}, which throws typed + * exceptions for invalid parameters, lengths, or security violations. + *
    • Exceptions propagate to the caller unless they represent normal protocol + * conditions (e.g., authentication failure, unsupported instruction). + *
    • Status words are returned only for protocol-level conditions, not + * validation errors. + *
    + * + *

    Status Words Usage

    + * + *

    Implementations return ISO 7816-4 / PC/SC compliant status words for the following conditions: * * + * * * - * - * - * - * - * - * - * - * + * + * *
    Status Words returned by implementations
    Status WordCodeUsage
    Success0x9000Successful execution
    Wrong length0x6700Invalid data length
    Incorrect P1-P20x6A86Invalid parameters
    Conditions not satisfied0x6985Preconditions not met
    Key type not known0x6986Invalid key type (PC/SC)
    Key number not valid0x6988Invalid key number (PC/SC)
    Authentication failed0x6300Wrong authentication key
    Technical problem0x6581Hardware/communication error
    INS not supported0x6D00Unknown instruction
    Security status not satisfied0x6982Authentication failed (PC/SC + * compliant)
    INS not supported0x6D00Unknown instruction code
    * - *

    Implementations that convert exceptions to status words should log the full exception details - * (including stack traces) for diagnostic purposes while returning appropriate status words to the - * client. + *

    Exception Propagation

    + * + *

    The following error conditions result in exceptions thrown by {@link CommandProcessorApi}: + * + *

      + *
    • Invalid parameters (P1/P2 out of range, invalid key number) + *
    • Invalid data length (wrong key length, APDU length mismatch) + *
    • Invalid data format (wrong version byte, invalid key type) + *
    • Hardware/communication errors (card not responding, transmission failure) + *
    + * + *

    Callers should catch and handle these exceptions appropriately for their context (e.g., + * converting to HTTP status codes, logging for diagnostics, or wrapping in domain-specific + * exceptions). + * + *

    PC/SC Compliance

    + * + *

    Storage card commands (CLA=0xFF) follow the PC/SC v2.01.09 specification: + * + *

      + *
    • LOAD KEY: FF 82 [P1] [P2] 06 [6-byte key] + *
    • GENERAL AUTHENTICATE: FF 86 00 00 05 [version][addr-MSB][addr-LSB][key-type][key-num] + * + *
    • GET DATA (UID): FF CA 00 00 [Le] + *
    • READ BINARY: FF B0 [P1] [P2] [Le] + *
    • UPDATE BINARY: FF D6 [P1] [P2] [Lc] [data] + *
    * + * @see CommandProcessorApi + * @see ApduInterpreterFactorySpi * @since 1.0.0 */ public interface ApduInterpreterSpi { From 774ea8c9257a1fe23fc5a41129b509c31035e9e2 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Thu, 15 Jan 2026 10:16:56 +0100 Subject: [PATCH 14/14] docs: clarify APDU interpreter validation responsibilities and exception propagation strategy --- .../internal/spi/ApduInterpreterSpi.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java index 7f02830..0182968 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java @@ -46,11 +46,13 @@ * *

    Error Handling Strategy

    * - *

    This interface uses a delegation-based validation approach: + *

    This interface follows a trust-based validation approach: * *

      - *
    • Validation is delegated to {@link CommandProcessorApi}, which throws typed - * exceptions for invalid parameters, lengths, or security violations. + *
    • Validation is performed upstream by the card extension that generates the + * APDUs. Since this extension is part of the same ecosystem, its output is trusted. + *
    • {@link CommandProcessorApi} does not perform parameter validation and + * trusts the incoming data to be correct. *
    • Exceptions propagate to the caller unless they represent normal protocol * conditions (e.g., authentication failure, unsupported instruction). *
    • Status words are returned only for protocol-level conditions, not @@ -72,18 +74,18 @@ * *

      Exception Propagation

      * - *

      The following error conditions result in exceptions thrown by {@link CommandProcessorApi}: + *

      The following error conditions may result in exceptions: * *

        - *
      • Invalid parameters (P1/P2 out of range, invalid key number) - *
      • Invalid data length (wrong key length, APDU length mismatch) - *
      • Invalid data format (wrong version byte, invalid key type) *
      • Hardware/communication errors (card not responding, transmission failure) + * - thrown by {@link CommandProcessorApi} + *
      • Protocol-level errors that cannot be represented by status words - may be + * thrown by implementations *
      * - *

      Callers should catch and handle these exceptions appropriately for their context (e.g., - * converting to HTTP status codes, logging for diagnostics, or wrapping in domain-specific - * exceptions). + *

      Note: 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. * *

      PC/SC Compliance

      *