Skip to content

Commit 41fd3b7

Browse files
feat: add support for storage card security management (#6)
1 parent 9288aab commit 41fd3b7

9 files changed

Lines changed: 249 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [1.1.0] - 2026-02-06
10+
### Added
11+
- Documentation of recommended status words for ApduInterpreterSpi implementations
12+
- Exception handling guidelines for CommandProcessorApi implementations
13+
- Added `KeyStorageType` enum to specify memory type (VOLATILE or NON_VOLATILE).
14+
- Added `loadKey` method to `CommandProcessorApi` interface using `KeyStorageType` for loading card-specific
15+
authentication keys into reader memory.
16+
- Added `generalAuthenticate` method to `CommandProcessorApi` interface for performing authentication to contactless
17+
cards using previously loaded keys.
18+
### Changed
19+
- Enhanced JavaDoc with comprehensive error handling information
20+
- Added status word reference table following ISO 7816-4 and PC/SC standards
21+
922
## [1.0.0] - 2025-07-08
1023
This is the initial release.
1124

12-
[unreleased]: https://github.com/eclipse-keyple/keyple-plugin-storagecard-java-api/compare/1.0.0...HEAD
25+
[unreleased]: https://github.com/eclipse-keyple/keyple-plugin-storagecard-java-api/compare/1.1.0...HEAD
26+
[1.1.0]: https://github.com/eclipse-keyple/keyple-plugin-storagecard-java-api/compare/1.0.0...1.1.0
1327
[1.0.0]: https://github.com/eclipse-keyple/keyple-plugin-storagecard-java-api/releases/tag/1.0.0

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
group = org.eclipse.keyple
33
title = Keyple Plugin Storage Card API
44
description = API dedicated to standardize communication between Keyple plugins and APDU interpreters for storage card processing
5-
version = 1.0.1-SNAPSHOT
5+
version = 1.1.0-SNAPSHOT
66

77
# Java Configuration
88
javaSourceLevel = 1.8

src/main/java/org/eclipse/keyple/core/plugin/storagecard/PluginStorageCardApiProperties.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public final class PluginStorageCardApiProperties {
2222
*
2323
* @since 1.0.0
2424
*/
25-
public static final String VERSION = "1.0";
25+
public static final String VERSION = "1.1";
2626

2727
/** Private constructor */
2828
private PluginStorageCardApiProperties() {}

src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/CommandProcessorApi.java

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@
1616
*
1717
* <p>To be implemented by the plugin.
1818
*
19+
* <h3>Exception Handling</h3>
20+
*
21+
* <p>All methods may throw exceptions to indicate error conditions. Implementations should
22+
* distinguish between:
23+
*
24+
* <ul>
25+
* <li>{@link IllegalArgumentException} - For invalid parameters that can be validated before
26+
* hardware access
27+
* <li>{@link Exception} - For technical errors during hardware operations (communication
28+
* failures, authentication failures, etc.)
29+
* </ul>
30+
*
31+
* <p>The exception message should provide clear diagnostic information about the failure cause.
32+
*
1933
* @since 1.0.0
2034
*/
2135
public interface CommandProcessorApi {
@@ -84,4 +98,90 @@ public interface CommandProcessorApi {
8498
* @since 1.0.0
8599
*/
86100
void writeBlock(int blockAddress, byte[] data) throws Exception;
101+
102+
/**
103+
* Loads a card-specific authentication key into the reader's memory.
104+
*
105+
* <p>This method stores a card key in either volatile (RAM) or non-volatile (EEPROM) memory of
106+
* the card reader. The key can be subsequently used by the {@link #generalAuthenticate(int, int,
107+
* int)} method to authenticate to the card. This command can be used for all kinds of contactless
108+
* cards.
109+
*
110+
* <p>Volatile memory provides temporary storage that is cleared when the reader loses power,
111+
* while non-volatile memory persists across power cycles. The availability of non-volatile memory
112+
* depends on the specific reader hardware.
113+
*
114+
* <p>The key structure and length depend on the card type. For example:
115+
*
116+
* <ul>
117+
* <li>Mifare cards: 6-byte keys (Type A or Type B)
118+
* <li>Other contactless cards: card-specific key formats
119+
* </ul>
120+
*
121+
* <p>The key number parameter identifies the storage location in the reader's memory. The valid
122+
* range and meaning of key numbers depend on the reader implementation and whether volatile or
123+
* non-volatile memory is used. Consult the reader documentation for specific key number
124+
* assignments.
125+
*
126+
* <p>Note that keys stored in memory cannot be read back for security reasons. Once loaded, they
127+
* can only be used for authentication operations.
128+
*
129+
* @param keyStorageType The type of memory to store the key in (VOLATILE or NON_VOLATILE).
130+
* @param keyNumber The key index identifying the storage location. Valid ranges depend on the
131+
* reader implementation and memory type.
132+
* @param key A byte array containing the card-specific key value. Must not be null. The required
133+
* length depends on the card type and authentication algorithm.
134+
* @throws IllegalArgumentException if {@code keyStorageType} or {@code key} is null, if the key
135+
* length is not valid for the card type, or if {@code keyNumber} is not in the valid range
136+
* for the reader and memory type.
137+
* @throws Exception if the load operation fails, if the specified memory type is not available on
138+
* the reader hardware, or if a communication error occurs.
139+
* @see #generalAuthenticate(int, int, int)
140+
* @since 1.1.0
141+
*/
142+
void loadKey(KeyStorageType keyStorageType, int keyNumber, byte[] key)
143+
throws Exception; // NOSONAR
144+
145+
/**
146+
* Performs authentication to a contactless card using a previously loaded key.
147+
*
148+
* <p>This method authenticates to a specific memory location on the card using a key that was
149+
* previously loaded into the reader's memory via the {@link #loadKey(KeyStorageType, int,
150+
* byte[])} method. Successful authentication is typically required before performing read or
151+
* write operations on protected memory areas.
152+
*
153+
* <p>The authentication process establishes a secure session between the reader and the card. The
154+
* block address represents the block number or starting byte number of the card to be
155+
* authenticated, depending on the card type.
156+
*
157+
* <p>The key type parameter is card-specific and indicates which type of key to use for
158+
* authentication. Examples:
159+
*
160+
* <ul>
161+
* <li>Mifare cards: 0x60 (KEY_A) or 0x61 (KEY_B)
162+
* <li>Other contactless cards: card-specific key type values
163+
* </ul>
164+
*
165+
* <p>The key number parameter references a key previously loaded via {@link
166+
* #loadKey(KeyStorageType, int, byte[])} and identifies which stored key to use for this
167+
* authentication operation.
168+
*
169+
* @param blockAddress The block number or starting byte number on the card where authentication
170+
* is to be performed. Valid range depends on the card type and memory structure.
171+
* @param keyType The type of key to use for authentication. The valid values are card-specific
172+
* (e.g., 0x60 or 0x61 for Mifare cards).
173+
* @param keyNumber The index of the previously loaded key to use for authentication. Must
174+
* reference a key that was loaded via {@link #loadKey(KeyStorageType, int, byte[])}.
175+
* @return {@code true} if authentication succeeded, {@code false} if authentication failed (e.g.,
176+
* incorrect key).
177+
* @throws IllegalArgumentException if {@code blockAddress} is out of valid range for the card
178+
* type, if {@code keyType} is not supported by the card, or if {@code keyNumber} does not
179+
* reference a valid loaded key.
180+
* @throws Exception if the referenced key was not previously loaded, if the card does not support
181+
* authentication, or if a communication error occurs.
182+
* @see #loadKey(KeyStorageType, int, byte[])
183+
* @since 1.1.0
184+
*/
185+
boolean generalAuthenticate(int blockAddress, int keyType, int keyNumber)
186+
throws Exception; // NOSONAR
87187
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/* **************************************************************************************
2+
* Copyright (c) 2026 Calypso Networks Association https://calypsonet.org/
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional information
5+
* regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the terms of the
8+
* MIT License which is available at https://opensource.org/licenses/MIT
9+
*
10+
* SPDX-License-Identifier: MIT
11+
************************************************************************************** */
12+
package org.eclipse.keyple.core.plugin.storagecard.internal;
13+
14+
/**
15+
* Enumeration defining the memory types available for key storage in the reader.
16+
*
17+
* @since 1.1.0
18+
*/
19+
public enum KeyStorageType {
20+
/**
21+
* Volatile memory (RAM).
22+
*
23+
* <p>Keys stored here are lost when the reader is powered off or reset. This is generally faster
24+
* and supports unlimited write cycles.
25+
*
26+
* @since 1.1.0
27+
*/
28+
VOLATILE,
29+
30+
/**
31+
* Non-volatile memory (EEPROM/Flash).
32+
*
33+
* <p>Keys stored here persist across power cycles. Note that non-volatile memory typically has a
34+
* limited number of write cycles.
35+
*
36+
* @since 1.1.0
37+
*/
38+
NON_VOLATILE
39+
}

src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterFactorySpi.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*
2121
* @since 1.0.0
2222
*/
23+
@FunctionalInterface
2324
public interface ApduInterpreterFactorySpi extends ApduInterpreterFactory {
2425

2526
/**

src/main/java/org/eclipse/keyple/core/plugin/storagecard/internal/spi/ApduInterpreterSpi.java

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,96 @@
1212
package org.eclipse.keyple.core.plugin.storagecard.internal.spi;
1313

1414
import org.eclipse.keyple.core.plugin.storagecard.internal.CommandProcessorApi;
15+
import org.eclipse.keyple.core.plugin.storagecard.internal.KeyStorageType;
1516

1617
/**
17-
* Interface defining an APDU interpreter for processing commands sent to a card.
18+
* Interface defining an APDU interpreter for processing commands sent to storage cards.
1819
*
1920
* <p>Its implementation is provided by {@link ApduInterpreterFactorySpi}.
2021
*
2122
* <p>Upon calling {@code processApdu}, implementations determine how to invoke the appropriate
22-
* methods of {@link CommandProcessorApi} based on the card type.
23+
* methods of {@link CommandProcessorApi} based on the APDU class byte (CLA) and instruction (INS).
2324
*
24-
* <p>For standard ISO 7816-4 APDUs, the interpreter directly calls {@link
25-
* CommandProcessorApi#transmitIsoApdu(byte[])}. For storage-type cards, the interpreter translates
26-
* the APDU into specific read/write operations using {@link CommandProcessorApi#getUID()}, {@link
27-
* CommandProcessorApi#readBlock(int, int)} and {@link CommandProcessorApi#writeBlock(int, byte[])}.
25+
* <h3>APDU Processing Strategy</h3>
2826
*
27+
* <p>The interpreter follows a two-pathway processing model:
28+
*
29+
* <ul>
30+
* <li><strong>Standard ISO 7816-4 APDUs</strong> (CLA != 0xFF): Directly transmitted via {@link
31+
* CommandProcessorApi#transmitIsoApdu(byte[])} without interpretation.
32+
* <li><strong>Storage Card APDUs</strong> (CLA = 0xFF): Interpreted and translated into
33+
* CommandProcessorApi method calls based on the instruction byte:
34+
* <ul>
35+
* <li><code>INS 0xCA</code> → {@link CommandProcessorApi#getUID()} - Get card UID
36+
* <li><code>INS 0xB0</code> → {@link CommandProcessorApi#readBlock(int, int)} - Read binary
37+
* data
38+
* <li><code>INS 0xD6</code> → {@link CommandProcessorApi#writeBlock(int, byte[])} - Write
39+
* binary data
40+
* <li><code>INS 0x82</code> → {@link CommandProcessorApi#loadKey(KeyStorageType, int,
41+
* byte[])} - Load authentication key
42+
* <li><code>INS 0x86</code> → {@link CommandProcessorApi#generalAuthenticate(int, int,
43+
* int)} - Authenticate with key
44+
* </ul>
45+
* </ul>
46+
*
47+
* <h3>Error Handling Strategy</h3>
48+
*
49+
* <p>This interface follows a <strong>trust-based validation</strong> approach:
50+
*
51+
* <ul>
52+
* <li><strong>Validation</strong> is performed upstream by the card extension that generates the
53+
* APDUs. Since this extension is part of the same ecosystem, its output is trusted.
54+
* <li><strong>{@link CommandProcessorApi}</strong> does not perform parameter validation and
55+
* trusts the incoming data to be correct.
56+
* <li><strong>Exceptions propagate</strong> to the caller unless they represent normal protocol
57+
* conditions (e.g., authentication failure, unsupported instruction).
58+
* <li><strong>Status words</strong> are returned only for protocol-level conditions, not
59+
* validation errors.
60+
* </ul>
61+
*
62+
* <h3>Status Words Usage</h3>
63+
*
64+
* <p>Implementations return ISO 7816-4 / PC/SC compliant status words for the following conditions:
65+
*
66+
* <table border="1">
67+
* <caption>Status Words returned by implementations</caption>
68+
* <tr><th>Status Word</th><th>Code</th><th>Usage</th></tr>
69+
* <tr><td>Success</td><td>0x9000</td><td>Successful execution</td></tr>
70+
* <tr><td>Security status not satisfied</td><td>0x6982</td><td>Authentication failed (PC/SC
71+
* compliant)</td></tr>
72+
* <tr><td>INS not supported</td><td>0x6D00</td><td>Unknown instruction code</td></tr>
73+
* </table>
74+
*
75+
* <h3>Exception Propagation</h3>
76+
*
77+
* <p>The following error conditions may result in exceptions:
78+
*
79+
* <ul>
80+
* <li><strong>Hardware/communication errors</strong> (card not responding, transmission failure)
81+
* - thrown by {@link CommandProcessorApi}
82+
* <li><strong>Protocol-level errors</strong> that cannot be represented by status words - may be
83+
* thrown by implementations
84+
* </ul>
85+
*
86+
* <p><strong>Note:</strong> Parameter validation (P1/P2 values, data lengths, key numbers, etc.) is
87+
* the responsibility of the card extension generating the APDUs, not this interface or its
88+
* implementations.
89+
*
90+
* <h3>PC/SC Compliance</h3>
91+
*
92+
* <p>Storage card commands (CLA=0xFF) follow the PC/SC v2.01.09 specification:
93+
*
94+
* <ul>
95+
* <li>LOAD KEY: <code>FF 82 [P1] [P2] 06 [6-byte key]</code>
96+
* <li>GENERAL AUTHENTICATE: <code>FF 86 00 00 05 [version][addr-MSB][addr-LSB][key-type][key-num]
97+
* </code>
98+
* <li>GET DATA (UID): <code>FF CA 00 00 [Le]</code>
99+
* <li>READ BINARY: <code>FF B0 [P1] [P2] [Le]</code>
100+
* <li>UPDATE BINARY: <code>FF D6 [P1] [P2] [Lc] [data]</code>
101+
* </ul>
102+
*
103+
* @see CommandProcessorApi
104+
* @see ApduInterpreterFactorySpi
29105
* @since 1.0.0
30106
*/
31107
public interface ApduInterpreterSpi {

src/main/uml/api_class_diagram.puml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@startuml
22
title
3-
Keyple - keyple-plugin-storagecard-java-api - 1.0.+ (2025-02-21)
3+
Keyple - keyple-plugin-storagecard-java-api - 1.1.+ (2026-01-14)
44
end title
55

66
' == THEME ==
@@ -58,12 +58,19 @@ package "org.eclipse.keyple.core.plugin.storagecard" as api #C_GREY1 {
5858
+interface ApduInterpreterFactory <<red>> {
5959
}
6060
package "internal" as internal #C_GREY2 {
61+
+enum "<color:blue>KeyStorageType" as KeyStorageType <<green>> {
62+
<color:blue>VOLATILE
63+
<color:blue>NON_VOLATILE
64+
}
6165
+interface CommandProcessorApi {
6266
+byte[] transmitIsoApdu(byte[] apdu)
6367

6468
+byte[] getUID()
6569
+byte[] readBlock(int blockAddress, int length)
6670
+void writeBlock(int blockAddress, byte[] data)
71+
72+
+<color:blue>void loadKey(KeyStorageType keyStorageType, int keyNumber, byte[] key)
73+
+<color:blue>boolean generalAuthenticate(int blockAddress, int keyType, int keyNumber)
6774
}
6875
package spi #C_GREY3 {
6976
+interface ApduInterpreterFactorySpi <<red>> extends api.ApduInterpreterFactory {
@@ -81,6 +88,7 @@ package "org.eclipse.keyple.core.plugin.storagecard" as api #C_GREY1 {
8188
' Associations
8289
ApduInterpreterFactorySpi --> ApduInterpreterSpi : create >
8390
ApduInterpreterSpi --> CommandProcessorApi : use >
91+
CommandProcessorApi --> KeyStorageType : use >
8492

8593
' == LAYOUT ==
8694

src/main/uml/api_class_diagram.svg

Lines changed: 1 addition & 1 deletion
Loading

0 commit comments

Comments
 (0)