From e4d5e0f70e9e797962e6662fc426045db5669d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20M=C5=82odawski?= Date: Sat, 2 Nov 2024 17:26:56 +0100 Subject: [PATCH] Add files via upload --- pom.xml | 9 +- .../security/example/PKCS11Example.java | 212 +++++++++++++++-- .../security/pkcs11/PKCS11Crypto.java | 216 ++++++++++-------- .../security/pkcs11/PKCS11RSACrypto.java | 161 +++++++++++++ .../security/pkcs11/PKCS11CryptoTest.java | 169 ++++---------- .../pkcs11/PKCS11DeviceManagerTest.java | 1 - .../security/pkcs11/PKCS11ManagerTest.java | 4 +- .../security/pkcs11/PKCS11RSACryptoTest.java | 153 +++++++++++++ .../security/pkcs11/PKCS11UtilsTest.java | 2 +- 9 files changed, 681 insertions(+), 246 deletions(-) create mode 100644 src/main/java/pl/mlodawski/security/pkcs11/PKCS11RSACrypto.java create mode 100644 src/test/java/pl/mlodawski/security/pkcs11/PKCS11RSACryptoTest.java diff --git a/pom.xml b/pom.xml index c42de55..c55ad57 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ pl.mlodawski.security credentials-support - 1.1 + 1.2 21 @@ -37,6 +37,13 @@ https://github.com/SimpleMethod/PKCS11-Java-Wrapper + + + github + GitHub SimpleMethod Apache Maven Packages + https://maven.pkg.github.com/SimpleMethod/PKCS11-Java-Wrapper + + diff --git a/src/main/java/pl/mlodawski/security/example/PKCS11Example.java b/src/main/java/pl/mlodawski/security/example/PKCS11Example.java index 967d32b..f4911b6 100644 --- a/src/main/java/pl/mlodawski/security/example/PKCS11Example.java +++ b/src/main/java/pl/mlodawski/security/example/PKCS11Example.java @@ -3,12 +3,12 @@ import pl.mlodawski.security.pkcs11.*; import pl.mlodawski.security.pkcs11.model.*; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; -import java.util.Base64; -import java.util.Map; -import java.util.Scanner; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; class PKCS11 { private final Path PKCS11_WRAPPER_PATH; @@ -76,15 +76,24 @@ public void onDeviceError(PKCS11Device device, Exception error) { signMessage(manager, session); break; case 3: - encryptDecryptData(manager, session); + signFile(manager, session); break; case 4: - listSupportedAlgorithms(manager, session); + verifyFileSignature(manager, session); break; case 5: + encryptDecryptData(manager, session); + break; + case 6: + encryptDecryptFile(manager, session); + break; + case 7: + listSupportedAlgorithms(manager, session); + break; + case 8: selectedDevice = null; return; - case 6: + case 9: session.close(); selectedDevice = null; PIN = null; @@ -96,14 +105,14 @@ public void onDeviceError(PKCS11Device device, Exception error) { System.out.println("Invalid choice. Please try again."); } - if (choice == 6) { - break; - } +// if (choice == 6) { +// break; +// } } } catch (Exception e) { System.out.println("Session error: " + e.getMessage()); selectedDevice = null; - PIN = null; + PIN = null; } } catch (Exception e) { System.out.println("An error occurred: " + e.getMessage()); @@ -115,6 +124,156 @@ public void onDeviceError(PKCS11Device device, Exception error) { } } + private void signFile(PKCS11Manager manager, PKCS11Session session) { + try { + KeyCertificatePair selectedPair = selectCertificateKeyPair(manager, session); + + System.out.print("Enter path to file to sign: "); + Scanner scanner = new Scanner(System.in); + String filePath = scanner.nextLine(); + + Path path = Paths.get(filePath); + if (!Files.exists(path)) { + System.out.println("File does not exist: " + filePath); + return; + } + + byte[] fileContent = Files.readAllBytes(path); + PKCS11Signer signer = new PKCS11Signer(); + byte[] signature = signer.signMessage(manager.getPkcs11(), + session.getSession(), + selectedPair.getKeyHandle(), + fileContent); + + // Save signature to file + String signatureFilePath = filePath + ".sig"; + Files.write(Paths.get(signatureFilePath), signature); + + System.out.println("File signed successfully. Signature saved to: " + signatureFilePath); + System.out.println("Signature (Base64): " + Base64.getEncoder().encodeToString(signature)); + + // Verify signature immediately + boolean isSignatureValid = signer.verifySignature(fileContent, signature, selectedPair.getCertificate()); + System.out.println("Signature verification: " + (isSignatureValid ? "Valid" : "Invalid")); + } catch (Exception e) { + System.out.println("Error during file signing: " + e.getMessage()); + throw new RuntimeException(e); + } + } + + private void verifyFileSignature(PKCS11Manager manager, PKCS11Session session) { + try { + KeyCertificatePair selectedPair = selectCertificateKeyPair(manager, session); + Scanner scanner = new Scanner(System.in); + + System.out.print("Enter path to file to verify: "); + String filePath = scanner.nextLine(); + + System.out.print("Enter path to signature file: "); + String signatureFilePath = scanner.nextLine(); + + if (!Files.exists(Paths.get(filePath))) { + System.out.println("File does not exist: " + filePath); + return; + } + if (!Files.exists(Paths.get(signatureFilePath))) { + System.out.println("Signature file does not exist: " + signatureFilePath); + return; + } + + byte[] fileContent = Files.readAllBytes(Paths.get(filePath)); + byte[] signature = Files.readAllBytes(Paths.get(signatureFilePath)); + + PKCS11Signer signer = new PKCS11Signer(); + boolean isSignatureValid = signer.verifySignature(fileContent, signature, selectedPair.getCertificate()); + + System.out.println("Signature verification result: " + (isSignatureValid ? "Valid" : "Invalid")); + } catch (Exception e) { + System.out.println("Error during signature verification: " + e.getMessage()); + } + } + + private void encryptDecryptFile(PKCS11Manager manager, PKCS11Session session) { + try { + KeyCertificatePair selectedPair = selectCertificateKeyPair(manager, session); + Scanner scanner = new Scanner(System.in); + + System.out.print("Enter path to file to encrypt: "); + String filePath = scanner.nextLine(); + + if (!Files.exists(Paths.get(filePath))) { + System.out.println("File does not exist: " + filePath); + return; + } + + byte[] fileContent = Files.readAllBytes(Paths.get(filePath)); + + // Encrypt file using hybrid encryption + PKCS11Crypto crypto = new PKCS11Crypto(); + byte[][] encryptedPackage = crypto.encryptData(fileContent, selectedPair.getCertificate()); + + // Save encrypted components + String encryptedKeyPath = filePath + ".key.enc"; + String encryptedIVPath = filePath + ".iv"; + String encryptedDataPath = filePath + ".data.enc"; + + Files.write(Paths.get(encryptedKeyPath), encryptedPackage[0]); + Files.write(Paths.get(encryptedIVPath), encryptedPackage[1]); + Files.write(Paths.get(encryptedDataPath), encryptedPackage[2]); + + System.out.println("File encrypted successfully."); + System.out.println("Encrypted key saved to: " + encryptedKeyPath); + System.out.println("IV saved to: " + encryptedIVPath); + System.out.println("Encrypted data saved to: " + encryptedDataPath); + + System.out.println("\nDo you want to decrypt the file? (y/n)"); + String answer = scanner.nextLine().toLowerCase(); + if (!answer.equals("y")) { + return; + } + + // Decrypt file + byte[][] decryptPackage = new byte[][]{ + Files.readAllBytes(Paths.get(encryptedKeyPath)), + Files.readAllBytes(Paths.get(encryptedIVPath)), + Files.readAllBytes(Paths.get(encryptedDataPath)) + }; + + byte[] decryptedData = crypto.decryptData( + manager.getPkcs11(), + session.getSession(), + selectedPair.getKeyHandle(), + decryptPackage + ); + + // Save decrypted file + String decryptedFilePath = filePath + ".dec"; + Files.write(Paths.get(decryptedFilePath), decryptedData); + System.out.println("File decrypted successfully. Saved to: " + decryptedFilePath); + + // Calculate and display checksums + String originalChecksum = getFileChecksum(fileContent); + String decryptedChecksum = getFileChecksum(decryptedData); + + System.out.println("\nFile integrity verification:"); + System.out.println("Original file SHA-256: " + originalChecksum); + System.out.println("Decrypted file SHA-256: " + decryptedChecksum); + + if (Arrays.equals(fileContent, decryptedData)) { + System.out.println("File integrity verified: Original and decrypted files match."); + } else { + System.out.println("Warning: Decrypted file does not match original!"); + } + } catch (Exception e) { + System.out.println("Error during file encryption/decryption: " + e.getMessage()); + } + } + + private String getFileChecksum(byte[] fileData) throws NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(fileData); + return Base64.getEncoder().encodeToString(hash); + } private boolean handleDeviceChange(PKCS11Manager manager) { int maxRetries = 3; @@ -130,7 +289,6 @@ private boolean handleDeviceChange(PKCS11Manager manager) { continue; } - if (!getPINFromUser()) { retryCount++; continue; @@ -223,10 +381,13 @@ private void displayMenu() { System.out.println("Current device: " + selectedDevice.getLabel()); System.out.println("1. List Available Certificates"); System.out.println("2. Sign a Message"); - System.out.println("3. Encrypt and Decrypt Data"); - System.out.println("4. List Supported Algorithms"); - System.out.println("5. Exit"); - System.out.println("6. Change Device"); + System.out.println("3. Sign a File"); + System.out.println("4. Verify File Signature"); + System.out.println("5. Encrypt and Decrypt Data"); + System.out.println("6. Encrypt and Decrypt File"); + System.out.println("7. List Supported Algorithms"); + System.out.println("8. Exit"); + System.out.println("9. Change Device"); System.out.print("Enter your choice: "); } @@ -294,12 +455,20 @@ private void encryptDecryptData(PKCS11Manager manager, PKCS11Session session) { Scanner scanner = new Scanner(System.in); String dataToEncrypt = scanner.nextLine(); - PKCS11Crypto decryptor = new PKCS11Crypto(); + PKCS11Crypto crypto = new PKCS11Crypto(); - byte[] encryptedData = decryptor.encryptData(dataToEncrypt.getBytes(), selectedPair.getCertificate()); + // Encrypt data + byte[][] encryptedPackage = crypto.encryptData(dataToEncrypt.getBytes(), selectedPair.getCertificate()); System.out.println("Data encrypted successfully."); + System.out.println("Encrypted data (Base64): " + Base64.getEncoder().encodeToString(encryptedPackage[2])); - byte[] decryptedData = decryptor.decryptData(manager.getPkcs11(), session.getSession(), selectedPair.getKeyHandle(), encryptedData); + // Decrypt data + byte[] decryptedData = crypto.decryptData( + manager.getPkcs11(), + session.getSession(), + selectedPair.getKeyHandle(), + encryptedPackage + ); System.out.println("Decrypted data: " + new String(decryptedData)); if (dataToEncrypt.equals(new String(decryptedData))) { @@ -309,10 +478,8 @@ private void encryptDecryptData(PKCS11Manager manager, PKCS11Session session) { } } catch (IllegalArgumentException e) { System.out.println("Invalid input: " + e.getMessage()); - throw e; } catch (Exception e) { System.out.println("Error during encryption/decryption: " + e.getMessage()); - throw e; } } @@ -339,6 +506,7 @@ private void listSupportedAlgorithms(PKCS11Manager manager, PKCS11Session sessio } } + public class PKCS11Example { public static void main(String[] args) { String userDir = System.getProperty("user.dir"); @@ -347,4 +515,4 @@ public static void main(String[] args) { ); example.run(); } -} +} \ No newline at end of file diff --git a/src/main/java/pl/mlodawski/security/pkcs11/PKCS11Crypto.java b/src/main/java/pl/mlodawski/security/pkcs11/PKCS11Crypto.java index ec9be4f..c7a1317 100644 --- a/src/main/java/pl/mlodawski/security/pkcs11/PKCS11Crypto.java +++ b/src/main/java/pl/mlodawski/security/pkcs11/PKCS11Crypto.java @@ -13,62 +13,54 @@ import com.sun.jna.ptr.NativeLongByReference; import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import java.security.cert.X509Certificate; +/** + * The {@code PKCS11Crypto} class provides methods for encrypting and decrypting data using + * a combination of AES and RSA algorithms. It leverages PKCS#11 for RSA decryption in hardware + * security modules (HSMs). + * + * This class uses the following transformations: + * - AES: "AES/CBC/PKCS5Padding" + * - RSA: "RSA/ECB/PKCS1Padding" + */ @Slf4j public class PKCS11Crypto { + private static final int AES_KEY_SIZE = 256; + private static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding"; + private static final String RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding"; + /** - * Initializes the cryptology process with the specified PKCS11 object, session, and private key handle. + * Encrypts the given data using AES encryption and then encrypts the AES key with RSA. * - * @param pkcs11 the PKCS11 object used for decryption - * @param session the session used for decryption - * @param privateKeyHandle the handle to the private key used for decryption - * @throws IllegalArgumentException if any of the parameters is null - * @throws RuntimeException if the decryption initialization fails + * @param dataToEncrypt the data to be encrypted + * @param certificate the X509 certificate containing the public key for RSA encryption + * @return a byte array containing the encrypted AES key, IV, and the encrypted data + * @throws EncryptionException if any error occurs during the encryption process */ - private void initCrypto(Pkcs11 pkcs11, NativeLong session, NativeLong privateKeyHandle) { - if (pkcs11 == null) { - throw new IllegalArgumentException("pkcs11 cannot be null"); - } - if (session == null) { - throw new IllegalArgumentException("session cannot be null"); - } - if (privateKeyHandle == null) { - throw new IllegalArgumentException("privateKeyHandle cannot be null"); - } + public byte[][] encryptData(byte[] dataToEncrypt, X509Certificate certificate) { + validateEncryptInput(dataToEncrypt, certificate); try { - CK_MECHANISM mechanism = new CK_MECHANISM(); - mechanism.mechanism = new NativeLong(Pkcs11Constants.CKM_RSA_PKCS); - pkcs11.C_DecryptInit(session, mechanism, privateKeyHandle); - } catch (Exception e) { - log.error("Crypto initialization failed", e); - throw new CryptoInitializationException("Crypto initialization failed", e); - } - } + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(AES_KEY_SIZE); + SecretKey aesKey = keyGen.generateKey(); - /** - * Encrypts the given data using RSA algorithm with the provided X509 certificate. - * - * @param dataToEncrypt the data to be encrypted - * @param certificate the X509 certificate used for encryption - * @return the encrypted data - * @throws IllegalArgumentException if dataToEncrypt is null or empty, or if certificate is null - * @throws RuntimeException if encryption fails - */ - public byte[] encryptData(byte[] dataToEncrypt, X509Certificate certificate) { - if (dataToEncrypt == null || dataToEncrypt.length == 0) { - throw new IllegalArgumentException("dataToEncrypt cannot be null or empty"); - } - if (certificate == null) { - throw new IllegalArgumentException("certificate cannot be null"); - } + Cipher aesCipher = Cipher.getInstance(AES_TRANSFORMATION); + aesCipher.init(Cipher.ENCRYPT_MODE, aesKey); + byte[] iv = aesCipher.getIV(); - try { - Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", new BouncyCastleProvider()); - cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey()); - return cipher.doFinal(dataToEncrypt); + byte[] encryptedData = aesCipher.doFinal(dataToEncrypt); + + Cipher rsaCipher = Cipher.getInstance(RSA_TRANSFORMATION, new BouncyCastleProvider()); + rsaCipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey()); + byte[] encryptedKey = rsaCipher.doFinal(aesKey.getEncoded()); + + return new byte[][]{encryptedKey, iv, encryptedData}; } catch (Exception e) { log.error("Encryption failed", e); throw new EncryptionException("Encryption failed", e); @@ -76,76 +68,58 @@ public byte[] encryptData(byte[] dataToEncrypt, X509Certificate certificate) { } /** - * Decrypts the given encrypted data using the specified private key. + * Decrypts the provided encrypted package using the specified PKCS#11 session and private key. * - * @param pkcs11 the Pkcs11 instance used for decryption - * @param session the native long value representing the session - * @param privateKeyHandle the native long value representing the private key handle - * @param encryptedData the byte array of encrypted data to be decrypted - * @return the decrypted data as a byte array - * @throws IllegalArgumentException if any of the input parameters are null or invalid - * @throws RuntimeException if decryption fails + * @param pkcs11 the PKCS#11 instance to use for decryption. + * @param session the active PKCS#11 session. + * @param privateKeyHandle the handle of the private key to use for decryption. + * @param encryptedPackage a 2D array containing the encrypted components: the encrypted AES key, the IV, and the encrypted data. + * @return the decrypted data as a byte array. + * @throws DecryptionException if the decryption process fails. */ - public byte[] decryptData(Pkcs11 pkcs11, NativeLong session, NativeLong privateKeyHandle, byte[] encryptedData) { - if (pkcs11 == null) { - throw new IllegalArgumentException("pkcs11 cannot be null"); - } - if (session == null) { - throw new IllegalArgumentException("session cannot be null"); - } - if (privateKeyHandle == null) { - throw new IllegalArgumentException("privateKeyHandle cannot be null"); - } - if (encryptedData == null || encryptedData.length == 0) { - throw new InvalidInputException("encryptedData cannot be null or empty"); - } + public byte[] decryptData(Pkcs11 pkcs11, NativeLong session, NativeLong privateKeyHandle, byte[][] encryptedPackage) { + validateDecryptInput(pkcs11, session, privateKeyHandle, encryptedPackage); + + byte[] encryptedKey = encryptedPackage[0]; + byte[] iv = encryptedPackage[1]; + byte[] encryptedData = encryptedPackage[2]; try { - initCrypto(pkcs11, session, privateKeyHandle); - return decrypt(pkcs11, session, encryptedData); - } catch (CryptoInitializationException e) { - log.error("Crypto initialization failed", e); - throw e; + CK_MECHANISM mechanism = new CK_MECHANISM(); + mechanism.mechanism = new NativeLong(Pkcs11Constants.CKM_RSA_PKCS); + NativeLong rv = pkcs11.C_DecryptInit(session, mechanism, privateKeyHandle); + checkResult(rv, "Failed to initialize decryption"); + + byte[] aesKeyBytes = decrypt(pkcs11, session, encryptedKey); + SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES"); + + Cipher aesCipher = Cipher.getInstance(AES_TRANSFORMATION); + aesCipher.init(Cipher.DECRYPT_MODE, aesKey, new javax.crypto.spec.IvParameterSpec(iv)); + return aesCipher.doFinal(encryptedData); } catch (Exception e) { log.error("Decryption failed", e); throw new DecryptionException("Decryption failed", e); } } - /** - * Decrypts the given encrypted data using PKCS11. + * Decrypts the provided encrypted data using the given PKCS#11 session. * - * @param pkcs11 the PKCS11 object used for encryption - * @param session the session ID - * @param encryptedData the data to decrypt - * @return the decrypted data - * @throws IllegalArgumentException if pkcs11, session, or encryptedData is null/empty - * @throws RuntimeException if decryption fails + * @param pkcs11 the PKCS#11 interface to perform cryptographic operations + * @param session the session handle used for decryption operations + * @param encryptedData the data to be decrypted + * @return the decrypted data as a byte array + * @throws DecryptionException if the decryption process fails */ - public byte[] decrypt(Pkcs11 pkcs11, NativeLong session, byte[] encryptedData) { - if (pkcs11 == null) { - throw new IllegalArgumentException("pkcs11 cannot be null"); - } - if (session == null) { - throw new IllegalArgumentException("session cannot be null"); - } - if (encryptedData == null || encryptedData.length == 0) { - throw new IllegalArgumentException("encryptedData cannot be null or empty"); - } - + private byte[] decrypt(Pkcs11 pkcs11, NativeLong session, byte[] encryptedData) { try { NativeLongByReference decryptedDataLen = new NativeLongByReference(); NativeLong result = pkcs11.C_Decrypt(session, encryptedData, new NativeLong(encryptedData.length), null, decryptedDataLen); - if (!result.equals(new NativeLong(Pkcs11Constants.CKR_OK))) { - throw new DecryptionException("Decryption failed with error code: " + result, null); - } + checkResult(result, "Decryption failed with error code"); byte[] decryptedData = new byte[decryptedDataLen.getValue().intValue()]; result = pkcs11.C_Decrypt(session, encryptedData, new NativeLong(encryptedData.length), decryptedData, decryptedDataLen); - if (!result.equals(new NativeLong(Pkcs11Constants.CKR_OK))) { - throw new DecryptionException("Decryption failed with error code: " + result, null); - } + checkResult(result, "Decryption failed with error code"); return decryptedData; } catch (Exception e) { @@ -154,4 +128,56 @@ public byte[] decrypt(Pkcs11 pkcs11, NativeLong session, byte[] encryptedData) { } } + /** + * Helper method to check the result from PKCS#11 calls. + * + * @param result the result code returned from the PKCS#11 call. + * @param errorMessage the error message to include in the exception if the result is not CKR_OK. + * @throws DecryptionException if the result is not CKR_OK. + */ + private void checkResult(NativeLong result, String errorMessage) { + if (!result.equals(new NativeLong(Pkcs11Constants.CKR_OK))) { + throw new DecryptionException(errorMessage + ": " + result, null); + } + } + + /** + * Validates the input parameters for the encryption process. + * + * @param dataToEncrypt the data to be encrypted, which must not be null or empty + * @param certificate the X509Certificate to be used for encryption, which must not be null and must contain a public key + * @throws InvalidInputException if any of the input parameters are invalid + */ + private void validateEncryptInput(byte[] dataToEncrypt, X509Certificate certificate) { + if (dataToEncrypt == null || dataToEncrypt.length == 0) { + throw new InvalidInputException("dataToEncrypt cannot be null or empty"); + } + if (certificate == null || certificate.getPublicKey() == null) { + throw new InvalidInputException("certificate or its public key cannot be null"); + } + } + + /** + * Validates the input for decryption. + * + * @param pkcs11 the instance of Pkcs11, cannot be null + * @param session the session handle, cannot be null + * @param privateKeyHandle the handle of the private key, cannot be null + * @param encryptedPackage the encrypted data package, must be an array of three non-null byte arrays + * @throws InvalidInputException if any of the input parameters are invalid + */ + private void validateDecryptInput(Pkcs11 pkcs11, NativeLong session, NativeLong privateKeyHandle, byte[][] encryptedPackage) { + if (pkcs11 == null) { + throw new InvalidInputException("pkcs11 instance cannot be null"); + } + if (session == null || privateKeyHandle == null) { + throw new InvalidInputException("session and privateKeyHandle cannot be null"); + } + if (encryptedPackage == null || encryptedPackage.length != 3) { + throw new InvalidInputException("encryptedPackage format is invalid"); + } + if (encryptedPackage[0] == null || encryptedPackage[1] == null || encryptedPackage[2] == null) { + throw new InvalidInputException("encryptedPackage elements cannot be null"); + } + } } \ No newline at end of file diff --git a/src/main/java/pl/mlodawski/security/pkcs11/PKCS11RSACrypto.java b/src/main/java/pl/mlodawski/security/pkcs11/PKCS11RSACrypto.java new file mode 100644 index 0000000..dda6be8 --- /dev/null +++ b/src/main/java/pl/mlodawski/security/pkcs11/PKCS11RSACrypto.java @@ -0,0 +1,161 @@ +package pl.mlodawski.security.pkcs11; + +import com.sun.jna.NativeLong; +import com.sun.jna.ptr.NativeLongByReference; +import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import pl.mlodawski.security.pkcs11.exceptions.CryptoInitializationException; +import pl.mlodawski.security.pkcs11.exceptions.DecryptionException; +import pl.mlodawski.security.pkcs11.exceptions.EncryptionException; +import pl.mlodawski.security.pkcs11.exceptions.InvalidInputException; +import ru.rutoken.pkcs11jna.CK_MECHANISM; +import ru.rutoken.pkcs11jna.Pkcs11; +import ru.rutoken.pkcs11jna.Pkcs11Constants; + +import javax.crypto.Cipher; +import java.security.cert.X509Certificate; + +/** + * PKCS11RSACrypto is a class that provides encryption and decryption methods + * using RSA algorithm and PKCS#11 standard. + */ +@Slf4j +public class PKCS11RSACrypto { + + /** + * Initializes the cryptology process with the specified PKCS11 object, session, and private key handle. + * + * @param pkcs11 the PKCS11 object used for decryption + * @param session the session used for decryption + * @param privateKeyHandle the handle to the private key used for decryption + * @throws IllegalArgumentException if any of the parameters is null + * @throws RuntimeException if the decryption initialization fails + */ + private void initCrypto(Pkcs11 pkcs11, NativeLong session, NativeLong privateKeyHandle) { + if (pkcs11 == null) { + throw new IllegalArgumentException("pkcs11 cannot be null"); + } + if (session == null) { + throw new IllegalArgumentException("session cannot be null"); + } + if (privateKeyHandle == null) { + throw new IllegalArgumentException("privateKeyHandle cannot be null"); + } + + try { + CK_MECHANISM mechanism = new CK_MECHANISM(); + mechanism.mechanism = new NativeLong(Pkcs11Constants.CKM_RSA_PKCS); + pkcs11.C_DecryptInit(session, mechanism, privateKeyHandle); + } catch (Exception e) { + log.error("Crypto initialization failed", e); + throw new CryptoInitializationException("Crypto initialization failed", e); + } + } + + /** + * Encrypts the given data using RSA algorithm with the provided X509 certificate. + * + * @param dataToEncrypt the data to be encrypted + * @param certificate the X509 certificate used for encryption + * @return the encrypted data + * @throws IllegalArgumentException if dataToEncrypt is null or empty, or if certificate is null + * @throws RuntimeException if encryption fails + */ + public byte[] encryptData(byte[] dataToEncrypt, X509Certificate certificate) { + if (dataToEncrypt == null || dataToEncrypt.length == 0) { + throw new IllegalArgumentException("dataToEncrypt cannot be null or empty"); + } + if (certificate == null) { + throw new IllegalArgumentException("certificate cannot be null"); + } + + try { + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", new BouncyCastleProvider()); + cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey()); + return cipher.doFinal(dataToEncrypt); + } catch (Exception e) { + log.error("Encryption failed", e); + throw new EncryptionException("Encryption failed", e); + } + } + + /** + * Decrypts the given encrypted data using the specified private key. + * + * @param pkcs11 the Pkcs11 instance used for decryption + * @param session the native long value representing the session + * @param privateKeyHandle the native long value representing the private key handle + * @param encryptedData the byte array of encrypted data to be decrypted + * @return the decrypted data as a byte array + * @throws IllegalArgumentException if any of the input parameters are null or invalid + * @throws RuntimeException if decryption fails + */ + public byte[] decryptData(Pkcs11 pkcs11, NativeLong session, NativeLong privateKeyHandle, byte[] encryptedData) { + if (pkcs11 == null) { + throw new IllegalArgumentException("pkcs11 cannot be null"); + } + if (session == null) { + throw new IllegalArgumentException("session cannot be null"); + } + if (privateKeyHandle == null) { + throw new IllegalArgumentException("privateKeyHandle cannot be null"); + } + if (encryptedData == null || encryptedData.length == 0) { + throw new InvalidInputException("encryptedData cannot be null or empty"); + } + + try { + initCrypto(pkcs11, session, privateKeyHandle); + return decrypt(pkcs11, session, encryptedData); + } catch (CryptoInitializationException e) { + log.error("Crypto initialization failed", e); + throw e; + } catch (Exception e) { + log.error("Decryption failed", e); + throw new DecryptionException("Decryption failed", e); + } + } + + + /** + * Decrypts the given encrypted data using PKCS11. + * + * @param pkcs11 the PKCS11 object used for encryption + * @param session the session ID + * @param encryptedData the data to decrypt + * @return the decrypted data + * @throws IllegalArgumentException if pkcs11, session, or encryptedData is null/empty + * @throws RuntimeException if decryption fails + */ + public byte[] decrypt(Pkcs11 pkcs11, NativeLong session, byte[] encryptedData) { + if (pkcs11 == null) { + throw new IllegalArgumentException("pkcs11 cannot be null"); + } + if (session == null) { + throw new IllegalArgumentException("session cannot be null"); + } + if (encryptedData == null || encryptedData.length == 0) { + throw new IllegalArgumentException("encryptedData cannot be null or empty"); + } + + try { + NativeLongByReference decryptedDataLen = new NativeLongByReference(); + NativeLong result = pkcs11.C_Decrypt(session, encryptedData, new NativeLong(encryptedData.length), null, decryptedDataLen); + if (!result.equals(new NativeLong(Pkcs11Constants.CKR_OK))) { + throw new DecryptionException("Decryption failed with error code: " + result, null); + } + + byte[] decryptedData = new byte[decryptedDataLen.getValue().intValue()]; + result = pkcs11.C_Decrypt(session, encryptedData, new NativeLong(encryptedData.length), decryptedData, decryptedDataLen); + if (!result.equals(new NativeLong(Pkcs11Constants.CKR_OK))) { + throw new DecryptionException("Decryption failed with error code: " + result, null); + } + + return decryptedData; + } catch (Exception e) { + log.error("Decryption failed", e); + throw new DecryptionException("Decryption failed", e); + } + } + +} \ No newline at end of file diff --git a/src/test/java/pl/mlodawski/security/pkcs11/PKCS11CryptoTest.java b/src/test/java/pl/mlodawski/security/pkcs11/PKCS11CryptoTest.java index 768332a..b8ebc03 100644 --- a/src/test/java/pl/mlodawski/security/pkcs11/PKCS11CryptoTest.java +++ b/src/test/java/pl/mlodawski/security/pkcs11/PKCS11CryptoTest.java @@ -1,158 +1,79 @@ package pl.mlodawski.security.pkcs11; -import org.junit.jupiter.api.BeforeEach; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import pl.mlodawski.security.pkcs11.exceptions.CryptoInitializationException; -import pl.mlodawski.security.pkcs11.exceptions.DecryptionException; import pl.mlodawski.security.pkcs11.exceptions.EncryptionException; import pl.mlodawski.security.pkcs11.exceptions.InvalidInputException; -import ru.rutoken.pkcs11jna.CK_MECHANISM; -import ru.rutoken.pkcs11jna.Pkcs11; -import ru.rutoken.pkcs11jna.Pkcs11Constants; -import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.PublicKey; import java.security.cert.X509Certificate; -import com.sun.jna.NativeLong; -import com.sun.jna.ptr.NativeLongByReference; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; +public class PKCS11CryptoTest { + private static final String ENCRYPTION_FAILED_MESSAGE = "Encryption failed"; -@ExtendWith(MockitoExtension.class) -class PKCS11CryptoTest { - @Mock - private Pkcs11 pkcs11Mock; - - @Mock - private X509Certificate certificateMock; - - private PKCS11Crypto pkcs11Crypto; - private KeyPair keyPair; - private NativeLong session; - private NativeLong privateKeyHandle; - - @BeforeEach - void setUp() throws Exception { - pkcs11Crypto = new PKCS11Crypto(); - session = new NativeLong(1L); - privateKeyHandle = new NativeLong(2L); - - // Generate a real RSA key pair for testing - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(2048); - keyPair = keyPairGenerator.generateKeyPair(); - - // We'll set up the certificate mock in individual tests where it's needed - } @Test - void encryptData_validInput_shouldEncryptSuccessfully() throws Exception { - when(certificateMock.getPublicKey()).thenReturn(keyPair.getPublic()); - byte[] dataToEncrypt = "Hello, World!".getBytes(); + public void encryptData_InvalidInput_ThrowsException() { + PKCS11Crypto pkcs11Crypto = new PKCS11Crypto(); - byte[] encryptedData = pkcs11Crypto.encryptData(dataToEncrypt, certificateMock); + assertThrows( + InvalidInputException.class, + () -> pkcs11Crypto.encryptData(null, mock(X509Certificate.class)) + ); - assertNotNull(encryptedData); - assertNotEquals(dataToEncrypt, encryptedData); - } + X509Certificate certificate = mock(X509Certificate.class); + when(certificate.getPublicKey()).thenReturn(null); - @Test - void encryptData_nullData_shouldThrowIllegalArgumentException() { - assertThrows(IllegalArgumentException.class, () -> pkcs11Crypto.encryptData(null, certificateMock)); + assertThrows( + InvalidInputException.class, + () -> pkcs11Crypto.encryptData(new byte[1], certificate) + ); } @Test - void encryptData_emptyData_shouldThrowIllegalArgumentException() { - assertThrows(IllegalArgumentException.class, () -> pkcs11Crypto.encryptData(new byte[0], certificateMock)); - } + public void encryptData_EncryptionFails_ThrowsException() throws Exception { + PKCS11Crypto pkcs11Crypto = new PKCS11Crypto(); - @Test - void encryptData_nullCertificate_shouldThrowIllegalArgumentException() { - assertThrows(IllegalArgumentException.class, () -> pkcs11Crypto.encryptData("Test".getBytes(), null)); - } + X509Certificate certificate = mock(X509Certificate.class); + PublicKey publicKey = mock(PublicKey.class); - @Test - void encryptData_invalidCertificate_shouldThrowEncryptionException() { - when(certificateMock.getPublicKey()).thenThrow(new RuntimeException("Invalid certificate")); - assertThrows(EncryptionException.class, () -> pkcs11Crypto.encryptData("Test".getBytes(), certificateMock)); - } + when(certificate.getPublicKey()).thenReturn(publicKey); - @Test - void decryptData_validInput_shouldDecryptSuccessfully() throws Exception { - byte[] originalData = "Hello, World!".getBytes(); - byte[] encryptedData = encryptWithRealKey(originalData); - - // Mock PKCS11 behavior - when(pkcs11Mock.C_Decrypt(eq(session), any(), any(), isNull(), any())).thenAnswer(invocation -> { - NativeLongByReference lengthRef = invocation.getArgument(4); - lengthRef.setValue(new NativeLong(originalData.length)); - return new NativeLong(0); - }); - - when(pkcs11Mock.C_Decrypt(eq(session), any(), any(), any(byte[].class), any())).thenAnswer(invocation -> { - byte[] outputBuffer = invocation.getArgument(3); - System.arraycopy(originalData, 0, outputBuffer, 0, originalData.length); - return new NativeLong(0); - }); - - byte[] decryptedData = pkcs11Crypto.decryptData(pkcs11Mock, session, privateKeyHandle, encryptedData); - - assertArrayEquals(originalData, decryptedData); - } + KeyGenerator keyGen = mock(KeyGenerator.class); + SecretKey secretKey = mock(SecretKey.class); - @Test - void decryptData_nullPkcs11_shouldThrowIllegalArgumentException() { - assertThrows(IllegalArgumentException.class, () -> pkcs11Crypto.decryptData(null, session, privateKeyHandle, new byte[]{1})); - } - - @Test - void decryptData_nullSession_shouldThrowIllegalArgumentException() { - assertThrows(IllegalArgumentException.class, () -> pkcs11Crypto.decryptData(pkcs11Mock, null, privateKeyHandle, new byte[]{1})); - } + when(keyGen.generateKey()).thenReturn(secretKey); + when(keyGen.generateKey()).thenThrow(new RuntimeException(ENCRYPTION_FAILED_MESSAGE)); - @Test - void decryptData_nullPrivateKeyHandle_shouldThrowIllegalArgumentException() { - assertThrows(IllegalArgumentException.class, () -> pkcs11Crypto.decryptData(pkcs11Mock, session, null, new byte[]{1})); + assertThrows( + EncryptionException.class, + () -> pkcs11Crypto.encryptData(new byte[1], certificate), + ENCRYPTION_FAILED_MESSAGE + ); } @Test - void decryptData_nullEncryptedData_shouldThrowInvalidInputException() { - assertThrows(InvalidInputException.class, () -> pkcs11Crypto.decryptData(pkcs11Mock, session, privateKeyHandle, null)); - } + public void encryptData_ValidInput_Success() throws Exception { + PKCS11Crypto pkcs11Crypto = new PKCS11Crypto(); - @Test - void decryptData_emptyEncryptedData_shouldThrowInvalidInputException() { - assertThrows(InvalidInputException.class, () -> pkcs11Crypto.decryptData(pkcs11Mock, session, privateKeyHandle, new byte[0])); - } + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + KeyPair keyPair = keyGen.generateKeyPair(); + PublicKey publicKey = keyPair.getPublic(); - @Test - void decryptData_initializationFailure_shouldThrowCryptoInitializationException() { - doThrow(new RuntimeException("Initialization failed")).when(pkcs11Mock).C_DecryptInit(any(), any(), any()); - - assertThrows(CryptoInitializationException.class, () -> - pkcs11Crypto.decryptData(pkcs11Mock, session, privateKeyHandle, new byte[]{1})); - } - - @Test - void decryptData_decryptionFailure_shouldThrowDecryptionException() { - when(pkcs11Mock.C_Decrypt(any(), any(), any(), any(), any())).thenReturn(new NativeLong(1)); - - assertThrows(DecryptionException.class, () -> - pkcs11Crypto.decrypt(pkcs11Mock, session, new byte[]{1})); - } + X509Certificate certificate = mock(X509Certificate.class); + when(certificate.getPublicKey()).thenReturn(publicKey); - // Helper method to encrypt data with the real public key - private byte[] encryptWithRealKey(byte[] data) throws Exception { - Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); - cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic()); - return cipher.doFinal(data); + byte[] dataToEncrypt = new byte[] { 0x01 }; + pkcs11Crypto.encryptData(dataToEncrypt, certificate); } } \ No newline at end of file diff --git a/src/test/java/pl/mlodawski/security/pkcs11/PKCS11DeviceManagerTest.java b/src/test/java/pl/mlodawski/security/pkcs11/PKCS11DeviceManagerTest.java index 9cc3f84..9bc2456 100644 --- a/src/test/java/pl/mlodawski/security/pkcs11/PKCS11DeviceManagerTest.java +++ b/src/test/java/pl/mlodawski/security/pkcs11/PKCS11DeviceManagerTest.java @@ -35,7 +35,6 @@ class PKCS11DeviceManagerTest { @BeforeEach void setUp() { - // Podstawowe mockowanie dla inicjalizacji lenient().when(pkcs11Mock.C_GetSlotList(eq(TOKEN_PRESENT), any(), any(NativeLongByReference.class))) .thenAnswer(inv -> { NativeLongByReference count = inv.getArgument(2); diff --git a/src/test/java/pl/mlodawski/security/pkcs11/PKCS11ManagerTest.java b/src/test/java/pl/mlodawski/security/pkcs11/PKCS11ManagerTest.java index c43c1a2..b7c935c 100644 --- a/src/test/java/pl/mlodawski/security/pkcs11/PKCS11ManagerTest.java +++ b/src/test/java/pl/mlodawski/security/pkcs11/PKCS11ManagerTest.java @@ -36,11 +36,11 @@ class PKCS11ManagerTest { @BeforeEach void setUp() { libraryPathMock = mock(Path.class); - // Mockowanie podstawowych wywołań PKCS11 + when(pkcs11Mock.C_GetSlotList(anyByte(), any(), any(NativeLongByReference.class))) .thenAnswer(invocation -> { NativeLongByReference count = invocation.getArgument(2); - count.setValue(new NativeLong(1)); // Symulujemy jeden slot + count.setValue(new NativeLong(1)); return new NativeLong(Pkcs11Constants.CKR_OK); }); diff --git a/src/test/java/pl/mlodawski/security/pkcs11/PKCS11RSACryptoTest.java b/src/test/java/pl/mlodawski/security/pkcs11/PKCS11RSACryptoTest.java new file mode 100644 index 0000000..5a71d82 --- /dev/null +++ b/src/test/java/pl/mlodawski/security/pkcs11/PKCS11RSACryptoTest.java @@ -0,0 +1,153 @@ +package pl.mlodawski.security.pkcs11; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import pl.mlodawski.security.pkcs11.exceptions.CryptoInitializationException; +import pl.mlodawski.security.pkcs11.exceptions.DecryptionException; +import pl.mlodawski.security.pkcs11.exceptions.EncryptionException; +import pl.mlodawski.security.pkcs11.exceptions.InvalidInputException; +import ru.rutoken.pkcs11jna.Pkcs11; + +import javax.crypto.Cipher; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.cert.X509Certificate; + +import com.sun.jna.NativeLong; +import com.sun.jna.ptr.NativeLongByReference; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class PKCS11RSACryptoTest { + + @Mock + private Pkcs11 pkcs11Mock; + + @Mock + private X509Certificate certificateMock; + + private PKCS11RSACrypto pkcs11Crypto; + private KeyPair keyPair; + private NativeLong session; + private NativeLong privateKeyHandle; + + @BeforeEach + void setUp() throws Exception { + pkcs11Crypto = new PKCS11RSACrypto(); + session = new NativeLong(1L); + privateKeyHandle = new NativeLong(2L); + + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + keyPair = keyPairGenerator.generateKeyPair(); + + } + + @Test + void encryptData_validInput_shouldEncryptSuccessfully() throws Exception { + when(certificateMock.getPublicKey()).thenReturn(keyPair.getPublic()); + byte[] dataToEncrypt = "Hello, World!".getBytes(); + + byte[] encryptedData = pkcs11Crypto.encryptData(dataToEncrypt, certificateMock); + + assertNotNull(encryptedData); + assertNotEquals(dataToEncrypt, encryptedData); + } + + @Test + void encryptData_nullData_shouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> pkcs11Crypto.encryptData(null, certificateMock)); + } + + @Test + void encryptData_emptyData_shouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> pkcs11Crypto.encryptData(new byte[0], certificateMock)); + } + + @Test + void encryptData_nullCertificate_shouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> pkcs11Crypto.encryptData("Test".getBytes(), null)); + } + + @Test + void encryptData_invalidCertificate_shouldThrowEncryptionException() { + when(certificateMock.getPublicKey()).thenThrow(new RuntimeException("Invalid certificate")); + assertThrows(EncryptionException.class, () -> pkcs11Crypto.encryptData("Test".getBytes(), certificateMock)); + } + + @Test + void decryptData_validInput_shouldDecryptSuccessfully() throws Exception { + byte[] originalData = "Hello, World!".getBytes(); + byte[] encryptedData = encryptWithRealKey(originalData); + + // Mock PKCS11 behavior + when(pkcs11Mock.C_Decrypt(eq(session), any(), any(), isNull(), any())).thenAnswer(invocation -> { + NativeLongByReference lengthRef = invocation.getArgument(4); + lengthRef.setValue(new NativeLong(originalData.length)); + return new NativeLong(0); + }); + + when(pkcs11Mock.C_Decrypt(eq(session), any(), any(), any(byte[].class), any())).thenAnswer(invocation -> { + byte[] outputBuffer = invocation.getArgument(3); + System.arraycopy(originalData, 0, outputBuffer, 0, originalData.length); + return new NativeLong(0); + }); + + byte[] decryptedData = pkcs11Crypto.decryptData(pkcs11Mock, session, privateKeyHandle, encryptedData); + + assertArrayEquals(originalData, decryptedData); + } + + @Test + void decryptData_nullPkcs11_shouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> pkcs11Crypto.decryptData(null, session, privateKeyHandle, new byte[]{1})); + } + + @Test + void decryptData_nullSession_shouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> pkcs11Crypto.decryptData(pkcs11Mock, null, privateKeyHandle, new byte[]{1})); + } + + @Test + void decryptData_nullPrivateKeyHandle_shouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> pkcs11Crypto.decryptData(pkcs11Mock, session, null, new byte[]{1})); + } + + @Test + void decryptData_nullEncryptedData_shouldThrowInvalidInputException() { + assertThrows(InvalidInputException.class, () -> pkcs11Crypto.decryptData(pkcs11Mock, session, privateKeyHandle, null)); + } + + @Test + void decryptData_emptyEncryptedData_shouldThrowInvalidInputException() { + assertThrows(InvalidInputException.class, () -> pkcs11Crypto.decryptData(pkcs11Mock, session, privateKeyHandle, new byte[0])); + } + + @Test + void decryptData_initializationFailure_shouldThrowCryptoInitializationException() { + doThrow(new RuntimeException("Initialization failed")).when(pkcs11Mock).C_DecryptInit(any(), any(), any()); + + assertThrows(CryptoInitializationException.class, () -> + pkcs11Crypto.decryptData(pkcs11Mock, session, privateKeyHandle, new byte[]{1})); + } + + @Test + void decryptData_decryptionFailure_shouldThrowDecryptionException() { + when(pkcs11Mock.C_Decrypt(any(), any(), any(), any(), any())).thenReturn(new NativeLong(1)); + + assertThrows(DecryptionException.class, () -> + pkcs11Crypto.decrypt(pkcs11Mock, session, new byte[]{1})); + } + + private byte[] encryptWithRealKey(byte[] data) throws Exception { + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic()); + return cipher.doFinal(data); + } +} \ No newline at end of file diff --git a/src/test/java/pl/mlodawski/security/pkcs11/PKCS11UtilsTest.java b/src/test/java/pl/mlodawski/security/pkcs11/PKCS11UtilsTest.java index ce32cc6..5af35af 100644 --- a/src/test/java/pl/mlodawski/security/pkcs11/PKCS11UtilsTest.java +++ b/src/test/java/pl/mlodawski/security/pkcs11/PKCS11UtilsTest.java @@ -86,7 +86,7 @@ void findAllPrivateKeys_nullSession_shouldThrowIllegalArgumentException() { @Test void getCKA_ID_validInput_shouldReturnCKA_ID() { - // Mock behavior for C_GetAttributeValue + when(pkcs11Mock.C_GetAttributeValue(any(NativeLong.class), any(NativeLong.class), any(CK_ATTRIBUTE[].class), any(NativeLong.class))) .thenAnswer(invocation -> { CK_ATTRIBUTE[] template = invocation.getArgument(2);