diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a09852..5ea783f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. 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..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 @@ -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: + * + *

+ * + *

The exception message should provide clear diagnostic information about the failure cause. + * * @since 1.0.0 */ public interface CommandProcessorApi { @@ -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. + * + *

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

+ * + *

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

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

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

+ * + *

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 } 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..f3884bd --- /dev/null +++ b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/KeyStorageType.java @@ -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). + * + *

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/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/ApduInterpreterSpi.java b/src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java index 4d627e4..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 @@ -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. * *

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

* + *

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

+ * + *

Error Handling Strategy

+ * + *

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

+ * + *

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
Security status not satisfied0x6982Authentication failed (PC/SC + * compliant)
INS not supported0x6D00Unknown instruction code
+ * + *

Exception Propagation

+ * + *

The following error conditions may result in 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

+ * + *

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

+ * + * @see CommandProcessorApi + * @see ApduInterpreterFactorySpi * @since 1.0.0 */ public interface ApduInterpreterSpi { diff --git a/src/main/uml/api_class_diagram.puml b/src/main/uml/api_class_diagram.puml index 9243dd7..17d1a26 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.+ (2026-01-14) end title ' == THEME == @@ -58,12 +58,19 @@ 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) +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) } package spi #C_GREY3 { +interface ApduInterpreterFactorySpi <> extends api.ApduInterpreterFactory { @@ -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 == diff --git a/src/main/uml/api_class_diagram.svg b/src/main/uml/api_class_diagram.svg index 3477e11..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.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.+ (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