diff --git a/CHANGELOG.md b/CHANGELOG.md index c5f92e8..8eb2633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [unreleased] +## [1.1.0] - 2026-02-06 +### Added +- **Mifare Classic support**: New product types `MIFARE_CLASSIC_1K` and `MIFARE_CLASSIC_4K` in + `ProductType` enum to support NXP Mifare Classic 1K (64 blocks) and 4K (256 blocks) cards. +- **Authentication capability**: New `ProductType.hasAuthentication()` method to indicate whether a + storage card requires authentication before read/write operations. +- **Mifare Classic key types**: New `MifareClassicKeyType` enum with `KEY_A` and `KEY_B` constants + for Mifare Classic authentication. +- **Authentication methods**: New authentication methods for Mifare Classic cards: + - `StorageCardSelectionExtension.prepareMifareClassicAuthenticate(int blockAddress, MifareClassicKeyType, byte[] key)` + - `StorageCardSelectionExtension.prepareMifareClassicAuthenticate(int blockAddress, MifareClassicKeyType, int keyNumber)` + - `StorageCardTransactionManager.prepareMifareClassicAuthenticate(int blockAddress, MifareClassicKeyType, byte[] key)` + - `StorageCardTransactionManager.prepareMifareClassicAuthenticate(int blockAddress, MifareClassicKeyType, int keyNumber)` +- **Authentication exception**: New `SCAuthenticationFailedException` for handling Mifare Classic + authentication failures. +- **ST25-specific system block methods**: New dedicated methods for ST25/SRT512 system block access: + - `StorageCardTransactionManager.prepareSt25ReadSystemBlock()` + - `StorageCardTransactionManager.prepareSt25WriteSystemBlock(byte[] data)` +### Changed +- **System block documentation**: Enhanced `StorageCard.getSystemBlock()` documentation to clarify + it is specific to ST25/SRT512 cards. +### Deprecated +- `StorageCardTransactionManager.prepareReadSystemBlock()` - Use `prepareSt25ReadSystemBlock()` + instead. +- `StorageCardTransactionManager.prepareWriteSystemBlock(byte[])` - Use + `prepareSt25WriteSystemBlock(byte[])` instead. + ## [1.0.0] - 2025-11-21 ### Changed - **Exception hierarchy refactoring**: `StorageCardException` transformed from abstract class to interface. @@ -70,7 +97,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.1.0] - 2025-06-18 This is the initial release. -[unreleased]: https://github.com/eclipse-keypop/keypop-storagecard-java-api/compare/1.0.0...HEAD +[unreleased]: https://github.com/eclipse-keypop/keypop-storagecard-java-api/compare/1.1.0...HEAD +[1.1.0]: https://github.com/eclipse-keypop/keypop-storagecard-java-api/compare/1.0.0...1.1.0 [1.0.0]: https://github.com/eclipse-keypop/keypop-storagecard-java-api/compare/0.3.0...1.0.0 [0.3.0]: https://github.com/eclipse-keypop/keypop-storagecard-java-api/compare/0.2.0...0.3.0 [0.2.0]: https://github.com/eclipse-keypop/keypop-storagecard-java-api/compare/0.1.0...0.2.0 diff --git a/gradle.properties b/gradle.properties index ed9ac83..12c5b87 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ group = org.eclipse.keypop title = Keypop Storage Card Java API description = API defining the needed interfaces to manage storage cards -version = 1.0.0-SNAPSHOT +version = 1.1.0-SNAPSHOT # Java Configuration javaSourceLevel = 1.8 diff --git a/src/main/java/org/eclipse/keypop/storagecard/MifareClassicKeyType.java b/src/main/java/org/eclipse/keypop/storagecard/MifareClassicKeyType.java new file mode 100644 index 0000000..7ac6df3 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/storagecard/MifareClassicKeyType.java @@ -0,0 +1,45 @@ +/* ************************************************************************************** + * 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.keypop.storagecard; + +/** + * Enumeration of the Mifare Classic key types used for authentication. + * + *
Mifare Classic cards support two types of keys per sector for access control: Key A and Key B. + * Each sector can be protected independently using these keys, allowing fine-grained access + * control. + * + * @since 1.1.0 + */ +public enum MifareClassicKeyType { + + /** + * Key A type. + * + *
This is the primary key used for authentication to a Mifare Classic sector. In most + * configurations, Key A has read/write permissions while Key B may have restricted permissions. + * + * @since 1.1.0 + */ + KEY_A, + + /** + * Key B type. + * + *
This is the secondary key used for authentication to a Mifare Classic sector. Key B is often + * used for restricted operations or read-only access, depending on the sector's access + * conditions. + * + * @since 1.1.0 + */ + KEY_B +} diff --git a/src/main/java/org/eclipse/keypop/storagecard/SCAuthenticationFailedException.java b/src/main/java/org/eclipse/keypop/storagecard/SCAuthenticationFailedException.java new file mode 100644 index 0000000..d2b446c --- /dev/null +++ b/src/main/java/org/eclipse/keypop/storagecard/SCAuthenticationFailedException.java @@ -0,0 +1,67 @@ +/* ************************************************************************************** + * 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.keypop.storagecard; + +import org.eclipse.keypop.reader.CardCommunicationException; +import org.eclipse.keypop.storagecard.card.StorageCard; + +/** + * Indicates that an authentication attempt on a {@link StorageCard} has failed. + * + *
This exception is thrown when authentication to a Mifare Classic sector fails, typically due + * to incorrect key data or key type. Authentication is required before reading from or writing to + * protected sectors on Mifare Classic cards. + * + * @since 1.1.0 + */ +public final class SCAuthenticationFailedException extends CardCommunicationException + implements StorageCardException { + + private final Integer blockAddress; + + /** + * Creates a new exception indicating an authentication failure during the execution of a storage + * card command. + * + * @param blockAddress The block address involved in the error, or {@code null} if not relevant. + * @param message The message describing the exception context. + * @since 1.1.0 + */ + public SCAuthenticationFailedException(Integer blockAddress, String message) { + super(message); + this.blockAddress = blockAddress; + } + + /** + * Creates a new exception indicating an authentication failure during the execution of a storage + * card command, with an underlying cause. + * + * @param blockAddress The block address involved in the error, or {@code null} if not relevant. + * @param message The message describing the exception context. + * @param cause The underlying cause of the exception. + * @since 1.1.0 + */ + public SCAuthenticationFailedException(Integer blockAddress, String message, Throwable cause) { + super(message, cause); + this.blockAddress = blockAddress; + } + + /** + * {@inheritDoc} + * + * @since 1.1.0 + */ + @Override + public Integer getBlockAddress() { + return blockAddress; + } +} diff --git a/src/main/java/org/eclipse/keypop/storagecard/StorageCardApiProperties.java b/src/main/java/org/eclipse/keypop/storagecard/StorageCardApiProperties.java index 853d3a8..e1f8912 100644 --- a/src/main/java/org/eclipse/keypop/storagecard/StorageCardApiProperties.java +++ b/src/main/java/org/eclipse/keypop/storagecard/StorageCardApiProperties.java @@ -23,7 +23,7 @@ public final class StorageCardApiProperties { * * @since 1.0.0 */ - public static final String VERSION = "1.0"; + public static final String VERSION = "1.1"; /** Private constructor */ private StorageCardApiProperties() {} diff --git a/src/main/java/org/eclipse/keypop/storagecard/card/ProductType.java b/src/main/java/org/eclipse/keypop/storagecard/card/ProductType.java index 6209af5..d090444 100644 --- a/src/main/java/org/eclipse/keypop/storagecard/card/ProductType.java +++ b/src/main/java/org/eclipse/keypop/storagecard/card/ProductType.java @@ -23,19 +23,34 @@ public enum ProductType { * * @since 1.0.0 */ - MIFARE_ULTRALIGHT(16, 4, false, true), + MIFARE_ULTRALIGHT(16, 4, false, true, false), + + /** + * NXP Mifare Classic 1K + * + * @since 1.1.0 + */ + MIFARE_CLASSIC_1K(64, 16, false, true, true), + + /** + * NXP Mifare Classic 4K + * + * @since 1.1.0 + */ + MIFARE_CLASSIC_4K(256, 16, false, true, true), /** * ST Microelectronics ST25 / SRT512 * * @since 1.0.0 */ - ST25_SRT512(16, 4, true, false); + ST25_SRT512(16, 4, true, false, false); private final int blockCount; private final int blockSize; private final boolean hasSystemBlock; private final boolean hasWriteAcknowledgment; + private final boolean hasAuthentication; /** * Constructor. @@ -45,13 +60,20 @@ public enum ProductType { * @param hasSystemBlock Whether this card type has an accessible system block. * @param hasWriteAcknowledgment Whether this card provides a reliable acknowledgment confirming * successful write operations. + * @param hasAuthentication Whether this card type requires authentication before read/write + * operations. */ ProductType( - int blockCount, int blockSize, boolean hasSystemBlock, boolean hasWriteAcknowledgment) { + int blockCount, + int blockSize, + boolean hasSystemBlock, + boolean hasWriteAcknowledgment, + boolean hasAuthentication) { this.blockCount = blockCount; this.blockSize = blockSize; this.hasSystemBlock = hasSystemBlock; this.hasWriteAcknowledgment = hasWriteAcknowledgment; + this.hasAuthentication = hasAuthentication; } /** @@ -108,4 +130,22 @@ public boolean hasSystemBlock() { public boolean hasWriteAcknowledgment() { return hasWriteAcknowledgment; } + + /** + * Indicates whether this storage card type requires authentication before read/write operations. + * + *
If this method returns {@code true}, the card requires successful authentication to a sector + * before read or write operations can be performed on blocks within that sector. For Mifare + * Classic cards, authentication must be performed using Key A or Key B with the appropriate + * authentication methods. + * + *
If this method returns {@code false}, the card allows direct read/write operations without + * prior authentication. + * + * @return {@code true} if the card requires authentication, {@code false} otherwise. + * @since 1.1.0 + */ + public boolean hasAuthentication() { + return hasAuthentication; + } } diff --git a/src/main/java/org/eclipse/keypop/storagecard/card/StorageCard.java b/src/main/java/org/eclipse/keypop/storagecard/card/StorageCard.java index 301d83d..a109c5b 100644 --- a/src/main/java/org/eclipse/keypop/storagecard/card/StorageCard.java +++ b/src/main/java/org/eclipse/keypop/storagecard/card/StorageCard.java @@ -40,10 +40,12 @@ public interface StorageCard extends SmartCard { * Retrieves the system block from the storage card when available. * *
The system block contains card-specific metadata and configuration data such as access - * control settings. Not all storage card types provide access to system blocks. + * control settings. This feature is specific to ST25/SRT512 cards which provide access to a + * system block at address 255. * - *
The system block must have been previously read using a the {{@link - * StorageCardTransactionManager#prepareReadSystemBlock()}) method. + *
The system block must have been previously read using the {@link + * StorageCardTransactionManager#prepareSt25ReadSystemBlock()} method (or the deprecated {@link + * StorageCardTransactionManager#prepareReadSystemBlock()} method). * * @return The system block data as a byte array, or null if the system block has not been read * yet. diff --git a/src/main/java/org/eclipse/keypop/storagecard/card/StorageCardSelectionExtension.java b/src/main/java/org/eclipse/keypop/storagecard/card/StorageCardSelectionExtension.java index 432b63f..ae7e25a 100644 --- a/src/main/java/org/eclipse/keypop/storagecard/card/StorageCardSelectionExtension.java +++ b/src/main/java/org/eclipse/keypop/storagecard/card/StorageCardSelectionExtension.java @@ -12,6 +12,7 @@ package org.eclipse.keypop.storagecard.card; import org.eclipse.keypop.reader.selection.spi.CardSelectionExtension; +import org.eclipse.keypop.storagecard.MifareClassicKeyType; /** * Extends the {@link CardSelectionExtension} interface of the "Keypop Reader API" to provide means @@ -54,4 +55,57 @@ public interface StorageCardSelectionExtension extends CardSelectionExtension { * @since 1.0.0 */ StorageCardSelectionExtension prepareReadBlocks(int fromBlockAddress, int toBlockAddress); + + /** + * Prepares a Mifare Classic authentication command using a provided key. + * + *
This method is specific to Mifare Classic cards and must be called before reading from or + * writing to protected sectors. The authentication applies to the entire sector containing the + * specified block address. + * + *
The key must be a 6-byte array representing the Mifare Classic key value. + * + *
When the key value is provided this way, it will be sent to the reader to be stored as a + * volatile key at index 0 (see Load Key command of the PC/SC standard). This volatile key is + * temporary and will be erased after usage, when the reader is powered off. + * + *
Security Note: This method transmits the key value over the communication + * channel between the application and the reader. For production environments and + * security-sensitive applications, it is recommended to use {@link + * #prepareMifareClassicAuthenticate(int, MifareClassicKeyType, int)} instead, which references a + * pre-stored key in the reader without transmitting the key value. + * + * @param blockAddress The address of any block within the sector to authenticate. + * @param mifareClassicKeyType The type of key to use (Key A or Key B). + * @param key The 6-byte key data for authentication. + * @return The current instance. + * @throws IllegalArgumentException If the block address is out of range, or if the key is null or + * not exactly 6 bytes long. + * @throws UnsupportedOperationException If the current card type does not support authentication. + * @since 1.1.0 + */ + StorageCardSelectionExtension prepareMifareClassicAuthenticate( + int blockAddress, MifareClassicKeyType mifareClassicKeyType, byte[] key); + + /** + * Prepares a Mifare Classic authentication command using a key stored in the reader. + * + *
This method is specific to Mifare Classic cards and must be called before reading from or + * writing to protected sectors. The authentication applies to the entire sector containing the + * specified block address. + * + *
The key is referenced by its storage index in the reader's key storage. This allows using + * pre-configured keys without transmitting them over the communication channel. + * + * @param blockAddress The address of any block within the sector to authenticate. + * @param mifareClassicKeyType The type of key to use (Key A or Key B). + * @param keyNumber The index of the key in the reader's key storage. + * @return The current instance. + * @throws IllegalArgumentException If the block address is out of range, or if the key number is + * invalid. + * @throws UnsupportedOperationException If the current card type does not support authentication. + * @since 1.1.0 + */ + StorageCardSelectionExtension prepareMifareClassicAuthenticate( + int blockAddress, MifareClassicKeyType mifareClassicKeyType, int keyNumber); } diff --git a/src/main/java/org/eclipse/keypop/storagecard/package-info.java b/src/main/java/org/eclipse/keypop/storagecard/package-info.java index e438fc5..4e9bf6e 100644 --- a/src/main/java/org/eclipse/keypop/storagecard/package-info.java +++ b/src/main/java/org/eclipse/keypop/storagecard/package-info.java @@ -1,2 +1,2 @@ -/** Contains the factories and builders to create the public elements of the extension. */ +/** Contains the factories, builders, and exceptions for the Storage Card API. */ package org.eclipse.keypop.storagecard; diff --git a/src/main/java/org/eclipse/keypop/storagecard/transaction/StorageCardTransactionManager.java b/src/main/java/org/eclipse/keypop/storagecard/transaction/StorageCardTransactionManager.java index c86c8d6..e7924ac 100644 --- a/src/main/java/org/eclipse/keypop/storagecard/transaction/StorageCardTransactionManager.java +++ b/src/main/java/org/eclipse/keypop/storagecard/transaction/StorageCardTransactionManager.java @@ -13,6 +13,7 @@ import org.eclipse.keypop.reader.ChannelControl; import org.eclipse.keypop.reader.transaction.spi.CardTransactionManager; +import org.eclipse.keypop.storagecard.MifareClassicKeyType; import org.eclipse.keypop.storagecard.card.ProductType; import org.eclipse.keypop.storagecard.card.StorageCard; @@ -62,7 +63,10 @@ public interface StorageCardTransactionManager * @throws UnsupportedOperationException If the current card type does not support system block * access. * @since 1.0.0 + * @deprecated Use {@link #prepareSt25ReadSystemBlock()} instead. This method will be removed in a + * future version. */ + @Deprecated StorageCardTransactionManager prepareReadSystemBlock(); /** @@ -88,9 +92,50 @@ public interface StorageCardTransactionManager * write access. * @see ProductType#getBlockSize() * @since 1.0.0 + * @deprecated Use {@link #prepareSt25WriteSystemBlock(byte[])} instead. This method will be + * removed in a future version. */ + @Deprecated StorageCardTransactionManager prepareWriteSystemBlock(byte[] data); + /** + * Prepares the reading of the system block from an ST25/SRT512 storage card. + * + *
This method is specific to ST25 and SRT512 card types which provide access to a system block + * at address 255 containing card-specific metadata and configuration data. + * + *
Once this command is processed, the result is available in {@link StorageCard}. + * + * @return The current instance. + * @throws UnsupportedOperationException If the current card type is not ST25/SRT512. + * @since 1.1.0 + */ + StorageCardTransactionManager prepareSt25ReadSystemBlock(); + + /** + * Prepares the writing of data to the system block of an ST25/SRT512 storage card. + * + *
This method is specific to ST25 and SRT512 card types which provide access to a system block + * at address 255 containing card-specific metadata and configuration data. + * + *
The data length must match the block size defined by the card's {@link ProductType}. + * + *
Important: After execution of this write command, the {@link StorageCard} + * memory image is not automatically updated. ST25/SRT512 cards do not provide + * reliable status codes to confirm successful write operations. To ensure data consistency, an + * explicit read operation must be performed after the write to refresh the memory image and + * verify the actual content stored on the card. + * + * @param data The data to be written to the system block. The length must match the card's block + * size. + * @return The current instance. + * @throws IllegalArgumentException If data is null or its length does not match the block size. + * @throws UnsupportedOperationException If the current card type is not ST25/SRT512. + * @see ProductType#getBlockSize() + * @since 1.1.0 + */ + StorageCardTransactionManager prepareSt25WriteSystemBlock(byte[] data); + /** * Prepares the reading of a specific block from the storage card. * @@ -149,4 +194,66 @@ public interface StorageCardTransactionManager * @since 1.0.0 */ StorageCardTransactionManager prepareWriteBlocks(int fromBlockAddress, byte[] data); + + /** + * Prepares a Mifare Classic authentication command using a provided key. + * + *
This method is specific to Mifare Classic cards and must be called before reading from or + * writing to protected sectors. The authentication applies to the entire sector containing the + * specified block address. + * + *
The key must be a 6-byte array representing the Mifare Classic key value. + * + *
When the key value is provided this way, it will be sent to the reader to be stored as a + * volatile key at index 0 (see Load Key command of the PC/SC standard). This volatile key is + * temporary and will be erased after usage, when the reader is powered off. + * + *
Security Note: This method transmits the key value over the communication + * channel between the application and the reader. For production environments and + * security-sensitive applications, it is recommended to use {@link + * #prepareMifareClassicAuthenticate(int, MifareClassicKeyType, int)} instead, which references a + * pre-stored key in the reader without transmitting the key value. + * + *
Once authenticated, subsequent read and write operations within the same sector can be + * performed without re-authentication, until the card is removed from the field or another sector + * is accessed. + * + * @param blockAddress The address of any block within the sector to authenticate. + * @param mifareClassicKeyType The type of key to use (Key A or Key B). + * @param key The 6-byte key data for authentication. + * @return The current instance. + * @throws IllegalArgumentException If the block address is out of range, or if the key is null or + * not exactly 6 bytes long. + * @throws UnsupportedOperationException If the current card type does not support authentication. + * @since 1.1.0 + */ + StorageCardTransactionManager prepareMifareClassicAuthenticate( + int blockAddress, MifareClassicKeyType mifareClassicKeyType, byte[] key); + + /** + * Prepares a Mifare Classic authentication command using a key stored in the reader. + * + *
This method is specific to Mifare Classic cards and must be called before reading from or + * writing to protected sectors. The authentication applies to the entire sector containing the + * specified block address. + * + *
The key is referenced by its storage index in the reader's key storage. This allows using + * pre-configured keys without transmitting them over the communication channel, providing + * enhanced security. + * + *
Once authenticated, subsequent read and write operations within the same sector can be + * performed without re-authentication, until the card is removed from the field or another sector + * is accessed. + * + * @param blockAddress The address of any block within the sector to authenticate. + * @param mifareClassicKeyType The type of key to use (Key A or Key B). + * @param keyNumber The index of the key in the reader's key storage. + * @return The current instance. + * @throws IllegalArgumentException If the block address is out of range, or if the key number is + * invalid. + * @throws UnsupportedOperationException If the current card type does not support authentication. + * @since 1.1.0 + */ + StorageCardTransactionManager prepareMifareClassicAuthenticate( + int blockAddress, MifareClassicKeyType mifareClassicKeyType, int keyNumber); }