neuer Test Code - bessere Key Verwaltung
This commit is contained in:
parent
1bce7db64e
commit
6c4e72fe82
6 changed files with 555 additions and 413 deletions
|
|
@ -1,22 +1,16 @@
|
||||||
package com.alexanderlogistics.ksef.api;
|
package com.alexanderlogistics.ksef.api;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.net.http.HttpResponse;
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
|
@ -60,27 +54,22 @@ public class KSeFAPIService {
|
||||||
DocumentService documentService;
|
DocumentService documentService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method uploads an KSeF Invoice document (XML).
|
* This method uploads a KSeF Invoice document (XML).
|
||||||
* The response of the uplaod is a 'referenceNumber' which is stored into the
|
* The response of the upload is a 'referenceNumber' which is stored into the
|
||||||
* item ksef.referenceNumber
|
* item ksef.referenceNumber
|
||||||
*
|
*
|
||||||
*
|
* @param workitem workitem containing the invoice
|
||||||
* @param companyName
|
* @param fileName name of the XML file to upload
|
||||||
* @param contactType
|
* @return referenceNumber from KSeF
|
||||||
* @param accessToken
|
|
||||||
* @return
|
|
||||||
* @throws PluginException
|
* @throws PluginException
|
||||||
*/
|
*/
|
||||||
public String uploadInvoice(ItemCollection workitem, String fileName)
|
public String uploadInvoice(ItemCollection workitem, String fileName) throws PluginException {
|
||||||
throws PluginException {
|
|
||||||
String referenceNumber = null;
|
// First open an interactive session (reuses existing if valid)
|
||||||
|
kseFAuthManager.openSession();
|
||||||
logger.info("├── 📤 Upload Invoice...");
|
logger.info("├── 📤 Upload Invoice...");
|
||||||
|
|
||||||
// First open an interactive Session. The KSeFAuthManager automatically reuses
|
// Validate session
|
||||||
// an existing session
|
|
||||||
|
|
||||||
kseFAuthManager.openSession();
|
|
||||||
|
|
||||||
if (kseFAuthManager.getAccessToken() == null) {
|
if (kseFAuthManager.getAccessToken() == null) {
|
||||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
"API Error - missing AccessToken!");
|
"API Error - missing AccessToken!");
|
||||||
|
|
@ -89,80 +78,54 @@ public class KSeFAPIService {
|
||||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
"API Error - missing SessionRefNumber!");
|
"API Error - missing SessionRefNumber!");
|
||||||
}
|
}
|
||||||
|
if (kseFAuthManager.getSessionEncryption() == null) {
|
||||||
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
|
"API Error - missing Session Encryption!");
|
||||||
|
}
|
||||||
|
|
||||||
// load invoice
|
// Load invoice XML
|
||||||
FileData fileData = workitem.getFileData(fileName);
|
FileData fileData = workitem.getFileData(fileName);
|
||||||
|
|
||||||
byte[] invoiceXml = fileData.getContent();
|
byte[] invoiceXml = fileData.getContent();
|
||||||
|
|
||||||
logger.info("│ ├── XML Invoice loaded - " + invoiceXml.length + " bytes");
|
logger.info("│ ├── XML Invoice loaded - " + invoiceXml.length + " bytes");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// ---------- 3) AES Key generieren ----------
|
// Get the session encryption keys (already sent to KSeF during session
|
||||||
logger.info("│ ├── Generate AES Key...");
|
// creation!)
|
||||||
SecureRandom random = new SecureRandom();
|
SessionEncryption encryption = kseFAuthManager.getSessionEncryption();
|
||||||
|
|
||||||
byte[] aesKeyBytes = new byte[32]; // 256-bit AES key
|
// Encrypt invoice with session keys
|
||||||
random.nextBytes(aesKeyBytes);
|
byte[] encryptedInvoice = encryptInvoiceWithSessionKeys(invoiceXml, encryption);
|
||||||
|
|
||||||
byte[] ivBytes = new byte[16]; // 128-bit IV
|
// Calculate hashes
|
||||||
random.nextBytes(ivBytes);
|
String invoiceHash = calculateSHA256Hash(invoiceXml);
|
||||||
|
String encryptedInvoiceHash = calculateSHA256Hash(encryptedInvoice);
|
||||||
|
|
||||||
SecretKeySpec aesKey = new SecretKeySpec(aesKeyBytes, "AES");
|
// Build JSON request body
|
||||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
String jsonBody = String.format(
|
||||||
|
"{" +
|
||||||
// ---------- 4) XML per AES-256-CBC verschlüsseln ----------
|
"\"invoiceHash\": \"%s\"," +
|
||||||
javax.crypto.Cipher aesCipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
|
"\"invoiceSize\": %d," +
|
||||||
aesCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, iv);
|
"\"encryptedInvoiceHash\": \"%s\"," +
|
||||||
|
"\"encryptedInvoiceSize\": %d," +
|
||||||
byte[] encryptedInvoiceXml = aesCipher.doFinal(invoiceXml);
|
"\"encryptedInvoiceContent\": \"%s\"," +
|
||||||
|
"\"offlineMode\": false" +
|
||||||
// ---------- 5) AES Key per RSA verschlüsseln ----------
|
|
||||||
PublicKey ksefPublicKey = kseFAuthManager.getSymmetricPublicKey();
|
|
||||||
|
|
||||||
javax.crypto.Cipher rsaCipher = javax.crypto.Cipher
|
|
||||||
.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
|
||||||
rsaCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, ksefPublicKey);
|
|
||||||
|
|
||||||
byte[] encryptedAesKey = rsaCipher.doFinal(aesKeyBytes);
|
|
||||||
|
|
||||||
// ---------- 6) SHA-256 Hash des Klartext-XML berechnen ----------
|
|
||||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
|
||||||
|
|
||||||
// hash origin invoice
|
|
||||||
byte[] xmlHashBytes = sha256.digest(invoiceXml);
|
|
||||||
String invoiceHash = Base64.getEncoder().encodeToString(xmlHashBytes);
|
|
||||||
|
|
||||||
// hash encrypted invoice
|
|
||||||
xmlHashBytes = sha256.digest(encryptedInvoiceXml);
|
|
||||||
String encryptedInvoiceHash = Base64.getEncoder().encodeToString(xmlHashBytes);
|
|
||||||
|
|
||||||
// ---------- 7) Request-Body bauen ----------
|
|
||||||
// Struktur gemäß KSeF-Doku
|
|
||||||
String jsonBody = String.format("{ " +
|
|
||||||
"\"invoiceHash\": \"%s\", " +
|
|
||||||
"\"invoiceSize\": \"%s\", " +
|
|
||||||
"\"encryptedInvoiceHash\": \"%s\", " +
|
|
||||||
"\"encryptedInvoiceSize\": \"%s\", " +
|
|
||||||
"\"encryptedInvoiceContent\": \"%s\", " +
|
|
||||||
"\"offlineMode\": false " +
|
|
||||||
"}",
|
"}",
|
||||||
invoiceHash,
|
invoiceHash,
|
||||||
invoiceXml.length,
|
invoiceXml.length,
|
||||||
encryptedInvoiceHash,
|
encryptedInvoiceHash,
|
||||||
encryptedInvoiceXml.length,
|
encryptedInvoice.length,
|
||||||
Base64.getEncoder().encodeToString(encryptedInvoiceXml));
|
Base64.getEncoder().encodeToString(encryptedInvoice));
|
||||||
|
|
||||||
// ---------- 8) HTTP POST vorbereiten ----------
|
// Send upload request
|
||||||
// String uri = manager.getBaseURI() + "/sessions/online/" +
|
|
||||||
// manager.getRefNumber() + "/invoices";
|
|
||||||
String uri = kseFAuthManager.getBaseURI() + "/sessions/online/"
|
String uri = kseFAuthManager.getBaseURI() + "/sessions/online/"
|
||||||
+ kseFAuthManager.getSessionRefNumber()
|
+ kseFAuthManager.getSessionRefNumber() + "/invoices";
|
||||||
+ "/invoices";
|
|
||||||
logger.info("│ ├── Endpoint: " + uri);
|
logger.info("│ ├── POST: " + uri);
|
||||||
|
|
||||||
if (kseFAuthManager.isDebug()) {
|
if (kseFAuthManager.isDebug()) {
|
||||||
logger.info("Request: " + jsonBody);
|
logger.info("│ ├── Request Body: " + jsonBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create(uri))
|
.uri(URI.create(uri))
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
|
|
@ -170,53 +133,94 @@ public class KSeFAPIService {
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// ---------- 9) Call ausführen ----------
|
HttpResponse<String> response = kseFAuthManager.getHttpClient().send(
|
||||||
var response = kseFAuthManager.getHttpClient().send(request,
|
request, HttpResponse.BodyHandlers.ofString());
|
||||||
java.net.http.HttpResponse.BodyHandlers.ofString());
|
|
||||||
|
|
||||||
System.out.println("Upload Response:");
|
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||||
System.out.println(response.statusCode());
|
|
||||||
System.out.println(response.body());
|
|
||||||
|
|
||||||
|
// if (kseFAuthManager.isDebug()) {
|
||||||
|
logger.info("│ ├── Response Body: " + response.body());
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (response.statusCode() != 202) {
|
||||||
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
|
"Invoice upload failed with status: " + response.statusCode() +
|
||||||
|
" - " + response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse response and get reference number
|
||||||
|
String referenceNumber = null;
|
||||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||||
// Annahme: response.body() ist ein String mit JSON-Inhalt
|
|
||||||
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
||||||
referenceNumber = jsonObject.getString("referenceNumber");
|
referenceNumber = jsonObject.getString("referenceNumber");
|
||||||
|
|
||||||
if (kseFAuthManager.isDebug()) {
|
logger.info("│ ├── Reference Number: " + referenceNumber);
|
||||||
logger.info("│ ├── referenceNumber: " + referenceNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.severe("├── ⚠️ Error parsing JSON response" + e.getMessage());
|
|
||||||
|
|
||||||
kseFAuthManager.deleteCurrentSession();
|
|
||||||
|
|
||||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
"Error parsing JSON response: " + e.getMessage());
|
"Error parsing upload response: " + e.getMessage());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.statusCode() == 202) {
|
// Store reference number in file metadata
|
||||||
logger.info("├── ✅ Upload successful ");
|
List<Object> textlist = new ArrayList<>();
|
||||||
} else {
|
|
||||||
logger.info("├── ⚠️ Upload failed! ");
|
|
||||||
}
|
|
||||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IOException | InterruptedException
|
|
||||||
| InvalidKeyException | IllegalBlockSizeException | BadPaddingException
|
|
||||||
| InvalidAlgorithmParameterException e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
// finally set the referenceNumber
|
|
||||||
List<Object> textlist = new ArrayList<Object>();
|
|
||||||
textlist.add(referenceNumber);
|
textlist.add(referenceNumber);
|
||||||
fileData.setAttribute("ksef.referenceNumber", textlist);
|
fileData.setAttribute("ksef.referenceNumber", textlist);
|
||||||
|
|
||||||
kseFAuthManager.closeSession();
|
logger.info("├── ✅ Upload successful - Reference: " + referenceNumber);
|
||||||
kseFAuthManager.deleteCurrentSession();
|
|
||||||
|
|
||||||
return referenceNumber;
|
return referenceNumber;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.severe("├── ⚠️ Invoice upload failed: " + e.getMessage());
|
||||||
|
|
||||||
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
|
"Invoice upload failed: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
kseFAuthManager.closeSession();
|
||||||
|
kseFAuthManager.deleteCurrentSession();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt invoice XML with the session encryption keys
|
||||||
|
*
|
||||||
|
* @param invoiceXml the invoice XML content
|
||||||
|
* @param encryption the session encryption containing AES key and IV
|
||||||
|
* @return encrypted invoice bytes
|
||||||
|
* @throws Exception if encryption fails
|
||||||
|
*/
|
||||||
|
private byte[] encryptInvoiceWithSessionKeys(byte[] invoiceXml, SessionEncryption encryption)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
logger.info("│ ├── Encrypting invoice with session keys...");
|
||||||
|
|
||||||
|
// Use the SAME keys that were sent during session creation!
|
||||||
|
SecretKeySpec aesKey = new SecretKeySpec(encryption.getAesKeyBytes(), "AES");
|
||||||
|
IvParameterSpec iv = new IvParameterSpec(encryption.getInitializationVector());
|
||||||
|
|
||||||
|
// Encrypt with AES-256-CBC
|
||||||
|
Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, iv);
|
||||||
|
|
||||||
|
byte[] encryptedData = aesCipher.doFinal(invoiceXml);
|
||||||
|
|
||||||
|
logger.info("│ ├── Invoice encrypted - " + encryptedData.length + " bytes");
|
||||||
|
|
||||||
|
return encryptedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate SHA-256 hash of data
|
||||||
|
*
|
||||||
|
* @param data byte array to hash
|
||||||
|
* @return Base64-encoded hash
|
||||||
|
* @throws NoSuchAlgorithmException if SHA-256 is not available
|
||||||
|
*/
|
||||||
|
private String calculateSHA256Hash(byte[] data) throws NoSuchAlgorithmException {
|
||||||
|
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] hashBytes = sha256.digest(data);
|
||||||
|
return Base64.getEncoder().encodeToString(hashBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.spec.MGF1ParameterSpec;
|
import java.security.spec.MGF1ParameterSpec;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
@ -83,10 +82,10 @@ public class KSeFAuthManager {
|
||||||
private String baseURI = "";
|
private String baseURI = "";
|
||||||
|
|
||||||
// public keys
|
// public keys
|
||||||
private String symmetricCertBase64;
|
private String sessionEncryptionCertBase64;
|
||||||
private String ksefTokenCertBase64;
|
private String tokenEncryptionCertBase64;
|
||||||
private PublicKey symmetricPublicKey;
|
private PublicKey sessionEncryptionPublicKey;
|
||||||
private PublicKey ksefTokenPublicKey;
|
private PublicKey tokenEncryptionPublicKey;
|
||||||
|
|
||||||
// Auth information
|
// Auth information
|
||||||
private String challenge = "";
|
private String challenge = "";
|
||||||
|
|
@ -95,11 +94,10 @@ public class KSeFAuthManager {
|
||||||
private String challengeTimestamp = null;
|
private String challengeTimestamp = null;
|
||||||
|
|
||||||
// Session information
|
// Session information
|
||||||
|
private SessionEncryption encryption = null;
|
||||||
private String accessToken = null;
|
private String accessToken = null;
|
||||||
private String sessionRefNumber;
|
private String sessionRefNumber;
|
||||||
private String sessionValidUntil;
|
private String sessionValidUntil;
|
||||||
private byte[] sessionAesKey;
|
|
||||||
private byte[] sessionIv;
|
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
void init() {
|
void init() {
|
||||||
|
|
@ -142,6 +140,10 @@ public class KSeFAuthManager {
|
||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SessionEncryption getSessionEncryption() {
|
||||||
|
return encryption;
|
||||||
|
}
|
||||||
|
|
||||||
public void setKsefNip(String nip) {
|
public void setKsefNip(String nip) {
|
||||||
this.ksefNip = Optional.ofNullable(nip);
|
this.ksefNip = Optional.ofNullable(nip);
|
||||||
}
|
}
|
||||||
|
|
@ -170,20 +172,8 @@ public class KSeFAuthManager {
|
||||||
return authToken;
|
return authToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PublicKey getSymmetricPublicKey() {
|
public PublicKey getSessionEncryptionPublicKey() {
|
||||||
return symmetricPublicKey;
|
return sessionEncryptionPublicKey;
|
||||||
}
|
|
||||||
|
|
||||||
public String getChallengeValue() {
|
|
||||||
return challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getSessionAesKey() {
|
|
||||||
return sessionAesKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getSessionIv() {
|
|
||||||
return sessionIv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -200,12 +190,12 @@ public class KSeFAuthManager {
|
||||||
this.loadPublicKeyCertificates();
|
this.loadPublicKeyCertificates();
|
||||||
this.authChallenge();
|
this.authChallenge();
|
||||||
this.authKSeFToken();
|
this.authKSeFToken();
|
||||||
this.waitSomeTime(3000);
|
// this.waitSomeTime(3000);
|
||||||
|
|
||||||
// Jetzt Status abwarten
|
// Jetzt Status abwarten
|
||||||
this.waitForAuthStatus();
|
this.waitForAuthStatus();
|
||||||
|
|
||||||
waitSomeTime(5000);
|
// waitSomeTime(5000);
|
||||||
this.redeemToken();
|
this.redeemToken();
|
||||||
|
|
||||||
this.openInteractiveSession();
|
this.openInteractiveSession();
|
||||||
|
|
@ -308,30 +298,30 @@ public class KSeFAuthManager {
|
||||||
var usages = entry.getJsonArray("usage");
|
var usages = entry.getJsonArray("usage");
|
||||||
|
|
||||||
if (usages.stream().anyMatch(v -> v.toString().contains("KsefTokenEncryption"))) {
|
if (usages.stream().anyMatch(v -> v.toString().contains("KsefTokenEncryption"))) {
|
||||||
ksefTokenCertBase64 = entry.getString("certificate");
|
tokenEncryptionCertBase64 = entry.getString("certificate");
|
||||||
if (debug) {
|
if (debug) {
|
||||||
logger.info("│ ├── ✓ KsefTokenEncryption certificate found.");
|
logger.info("│ ├── ✓ KsefTokenEncryption certificate found.");
|
||||||
} // Public Key extrahieren
|
} // Public Key extrahieren
|
||||||
ksefTokenPublicKey = extractPublicKeyFromCertificate(ksefTokenCertBase64);
|
tokenEncryptionPublicKey = extractPublicKeyFromCertificate(tokenEncryptionCertBase64);
|
||||||
logger.info("│ ├── ✓ RSA PublicKey successfully extracted.");
|
logger.info("│ ├── ✓ RSA PublicKey successfully extracted.");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usages.stream().anyMatch(v -> v.toString().contains("SymmetricKeyEncryption"))) {
|
if (usages.stream().anyMatch(v -> v.toString().contains("SymmetricKeyEncryption"))) {
|
||||||
symmetricCertBase64 = entry.getString("certificate");
|
sessionEncryptionCertBase64 = entry.getString("certificate");
|
||||||
if (debug) {
|
if (debug) {
|
||||||
logger.info("│ ├── ✓ SymmetricKeyEncryption certificate found.");
|
logger.info("│ ├── ✓ SymmetricKeyEncryption certificate found.");
|
||||||
}
|
}
|
||||||
// Public Key extrahieren
|
// Public Key extrahieren
|
||||||
symmetricPublicKey = extractPublicKeyFromCertificate(symmetricCertBase64);
|
sessionEncryptionPublicKey = extractPublicKeyFromCertificate(sessionEncryptionCertBase64);
|
||||||
logger.info("│ ├── ✓ Symetric PublicKey successfully extracted.");
|
logger.info("│ ├── ✓ Symetric PublicKey successfully extracted.");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ksefTokenPublicKey == null) {
|
if (tokenEncryptionPublicKey == null) {
|
||||||
logger.warning("│ ├── ⚠️ No certificate with usage 'ksefTokenPublicKey' found!");
|
logger.warning("│ ├── ⚠️ No certificate with usage 'ksefTokenPublicKey' found!");
|
||||||
}
|
}
|
||||||
if (symmetricPublicKey == null) {
|
if (sessionEncryptionPublicKey == null) {
|
||||||
logger.warning("│ ├── ⚠️ No certificate with usage 'symmetricPublicKey' found!");
|
logger.warning("│ ├── ⚠️ No certificate with usage 'symmetricPublicKey' found!");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
@ -595,27 +585,16 @@ public class KSeFAuthManager {
|
||||||
String uri = baseURI + "/sessions/online";
|
String uri = baseURI + "/sessions/online";
|
||||||
logger.info("│ ├── POST: " + uri);
|
logger.info("│ ├── POST: " + uri);
|
||||||
|
|
||||||
HttpResponse<String> response = null;
|
|
||||||
try {
|
try {
|
||||||
// --- AES Schlüssel erzeugen ---
|
// Generate encryption data using helper
|
||||||
SecureRandom rnd = new SecureRandom();
|
encryption = KSeFEncryptionHelper
|
||||||
byte[] aesKeyBytes = new byte[32]; // 256-bit AES key
|
.createSessionEncryption(this.sessionEncryptionPublicKey);
|
||||||
rnd.nextBytes(aesKeyBytes);
|
|
||||||
byte[] ivBytes = new byte[16]; // 128-bit IV
|
|
||||||
rnd.nextBytes(ivBytes);
|
|
||||||
|
|
||||||
// --- AES Key mit RSA encrypten ---
|
|
||||||
Cipher rsa;
|
|
||||||
|
|
||||||
rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
|
|
||||||
|
|
||||||
rsa.init(Cipher.ENCRYPT_MODE, this.symmetricPublicKey);
|
|
||||||
byte[] encryptedAesKey = rsa.doFinal(aesKeyBytes);
|
|
||||||
|
|
||||||
|
// Build JSON payload
|
||||||
String jsonPayload = String.format(
|
String jsonPayload = String.format(
|
||||||
"{" +
|
"{" +
|
||||||
"\"formCode\": {" +
|
"\"formCode\": {" +
|
||||||
" \"systemCode\": \"FA (3)\"," +
|
" \"systemCode\": \"FA (2)\"," +
|
||||||
" \"schemaVersion\": \"1-0E\"," +
|
" \"schemaVersion\": \"1-0E\"," +
|
||||||
" \"value\": \"FA\"" +
|
" \"value\": \"FA\"" +
|
||||||
"}," +
|
"}," +
|
||||||
|
|
@ -624,28 +603,27 @@ public class KSeFAuthManager {
|
||||||
" \"initializationVector\": \"%s\"" +
|
" \"initializationVector\": \"%s\"" +
|
||||||
"}" +
|
"}" +
|
||||||
"}",
|
"}",
|
||||||
Base64.getEncoder().encodeToString(encryptedAesKey),
|
encryption.getEncryptedAesKeyBase64(), encryption.getIvBase64());
|
||||||
Base64.getEncoder().encodeToString(ivBytes));
|
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
logger.info("│ ├── Payload: " + jsonPayload);
|
logger.info("│ ├── Payload: " + jsonPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send HTTP request
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create(uri))
|
.uri(URI.create(uri))
|
||||||
// .header("Accept-Charset", "UTF-8")
|
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response = httpClient.send(request,
|
||||||
|
HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||||
if (debug) {
|
if (debug) {
|
||||||
|
logger.info("│ ├── Response Body: " + response.body());
|
||||||
logger.info("Response: " + response.body());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.statusCode() != 201) {
|
if (response.statusCode() != 201) {
|
||||||
|
|
@ -653,41 +631,31 @@ public class KSeFAuthManager {
|
||||||
"Failed to open interactive session: " + response.statusCode());
|
"Failed to open interactive session: " + response.statusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse JSON
|
// Parse and store session data
|
||||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||||
// Annahme: response.body() ist ein String mit JSON-Inhalt
|
|
||||||
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
||||||
|
|
||||||
sessionRefNumber = jsonObject.getString("referenceNumber");
|
sessionRefNumber = jsonObject.getString("referenceNumber");
|
||||||
sessionValidUntil = jsonObject.getString("validUntil");
|
sessionValidUntil = jsonObject.getString("validUntil");
|
||||||
if (true) {
|
|
||||||
logger.info("│ ├── Session ReferenceNumber = " + sessionRefNumber);
|
logger.info("│ ├── Session ReferenceNumber = " + sessionRefNumber);
|
||||||
logger.info("│ └── Session ValidUntil = " + sessionValidUntil);
|
logger.info("│ └── Session ValidUntil = " + sessionValidUntil);
|
||||||
}
|
|
||||||
|
|
||||||
// IMPORTANT: Store the AES key - this key is mandatory to upload a invoice
|
|
||||||
this.sessionAesKey = aesKeyBytes;
|
|
||||||
this.sessionIv = ivBytes;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.severe("├── ⚠️ Error parsing JSON response" + e.getMessage());
|
logger.severe("├── ⚠️ Error parsing JSON response: " + e.getMessage());
|
||||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
"Error parsing JSON response: " + e.getMessage());
|
"Error parsing JSON response: " + e.getMessage());
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IOException | InterruptedException
|
|
||||||
| InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
|
// WICHTIG: Warte kurz, damit die Session vollständig initialisiert wird
|
||||||
|
// logger.info("│ ├── Waiting for session to be ready...");
|
||||||
|
// waitSomeTime(30000); // 2 Sekunden warten
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
logger.severe("├── ⚠️ API Error: " + e.getMessage());
|
logger.severe("├── ⚠️ API Error: " + e.getMessage());
|
||||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
"API Error openInteractiveSession: " + e.getMessage());
|
"API Error openInteractiveSession: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// logger.info("│ ├── Sleep 5 sec...");
|
|
||||||
// try {
|
|
||||||
// Thread.sleep(5000);
|
|
||||||
// } catch (InterruptedException e) {
|
|
||||||
// Thread.currentThread().interrupt();
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -704,7 +672,7 @@ public class KSeFAuthManager {
|
||||||
*/
|
*/
|
||||||
public String getEncryptedToken() throws PluginException {
|
public String getEncryptedToken() throws PluginException {
|
||||||
String result = null;
|
String result = null;
|
||||||
if (ksefTokenPublicKey == null) {
|
if (tokenEncryptionPublicKey == null) {
|
||||||
throw new IllegalStateException("Public Key not loaded! Call loadPublicKeyCertificates() first.");
|
throw new IllegalStateException("Public Key not loaded! Call loadPublicKeyCertificates() first.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -737,7 +705,7 @@ public class KSeFAuthManager {
|
||||||
"MGF1",
|
"MGF1",
|
||||||
MGF1ParameterSpec.SHA256,
|
MGF1ParameterSpec.SHA256,
|
||||||
PSource.PSpecified.DEFAULT);
|
PSource.PSpecified.DEFAULT);
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, ksefTokenPublicKey, oaepParams);
|
cipher.init(Cipher.ENCRYPT_MODE, tokenEncryptionPublicKey, oaepParams);
|
||||||
|
|
||||||
byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
|
byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
|
@ -766,6 +734,7 @@ public class KSeFAuthManager {
|
||||||
* This method verifies the actual status of the authentication process for the
|
* This method verifies the actual status of the authentication process for the
|
||||||
* current auth token (this is the token received from the start of the auth
|
* current auth token (this is the token received from the start of the auth
|
||||||
* procedure)
|
* procedure)
|
||||||
|
* The method returns the aut status code (not the HTTP Response code!)
|
||||||
*
|
*
|
||||||
* https://ksef-test.mf.gov.pl/docs/v2/index.html#tag/Uzyskiwanie-dostepu/paths/~1api~1v2~1auth~1%7BreferenceNumber%7D/get
|
* https://ksef-test.mf.gov.pl/docs/v2/index.html#tag/Uzyskiwanie-dostepu/paths/~1api~1v2~1auth~1%7BreferenceNumber%7D/get
|
||||||
*
|
*
|
||||||
|
|
@ -776,7 +745,7 @@ public class KSeFAuthManager {
|
||||||
logger.info("├── ↩️ KSeF API check auth status...");
|
logger.info("├── ↩️ KSeF API check auth status...");
|
||||||
String uri = baseURI + "/auth/" + authRefNumber;
|
String uri = baseURI + "/auth/" + authRefNumber;
|
||||||
int httpResponse = -1;
|
int httpResponse = -1;
|
||||||
|
int statusCode = -1;
|
||||||
logger.info("│ ├── GET: " + uri);
|
logger.info("│ ├── GET: " + uri);
|
||||||
HttpResponse<String> response = null;
|
HttpResponse<String> response = null;
|
||||||
try {
|
try {
|
||||||
|
|
@ -800,8 +769,19 @@ public class KSeFAuthManager {
|
||||||
|
|
||||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||||
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
||||||
|
|
||||||
|
// Status Code aus dem status-Objekt extrahieren
|
||||||
|
if (jsonObject.containsKey("status")) {
|
||||||
|
JsonObject statusObject = jsonObject.getJsonObject("status");
|
||||||
|
if (statusObject.containsKey("code")) {
|
||||||
|
statusCode = statusObject.getInt("code");
|
||||||
|
logger.info("│ ├── Status Code: " + statusCode);
|
||||||
|
logger.info("│ ├── Status Description: " + statusObject.getString("description"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Boolean isRedeemed = jsonObject.containsKey("isTokenRedeemed") ? jsonObject.getBoolean("isTokenRedeemed")
|
Boolean isRedeemed = jsonObject.containsKey("isTokenRedeemed") ? jsonObject.getBoolean("isTokenRedeemed")
|
||||||
: null;
|
: false;
|
||||||
|
|
||||||
String lastTokenRefreshDate = jsonObject.containsKey("lastTokenRefreshDate")
|
String lastTokenRefreshDate = jsonObject.containsKey("lastTokenRefreshDate")
|
||||||
? jsonObject.getString("lastTokenRefreshDate")
|
? jsonObject.getString("lastTokenRefreshDate")
|
||||||
|
|
@ -824,7 +804,7 @@ public class KSeFAuthManager {
|
||||||
"Error parsing JSON response: " + e.getMessage());
|
"Error parsing JSON response: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return httpResponse;
|
return statusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
package com.alexanderlogistics.ksef.api;
|
||||||
|
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for KSeF encryption operations.
|
||||||
|
* Handles AES-256 symmetric key generation and RSA encryption.
|
||||||
|
*/
|
||||||
|
public class KSeFEncryptionHelper {
|
||||||
|
|
||||||
|
private static final String AES_ALGORITHM = "AES";
|
||||||
|
private static final String RSA_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
|
||||||
|
private static final int AES_KEY_SIZE = 256;
|
||||||
|
private static final int IV_SIZE = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a new AES-256 symmetric key for session encryption
|
||||||
|
*
|
||||||
|
* @return SecretKey instance containing the generated AES-256 key
|
||||||
|
* @throws NoSuchAlgorithmException if AES algorithm is not available
|
||||||
|
*/
|
||||||
|
public static SecretKey generateAesKey() throws NoSuchAlgorithmException {
|
||||||
|
KeyGenerator keyGen = KeyGenerator.getInstance(AES_ALGORITHM);
|
||||||
|
keyGen.init(AES_KEY_SIZE, new SecureRandom());
|
||||||
|
return keyGen.generateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random initialization vector for AES encryption
|
||||||
|
*
|
||||||
|
* @return byte array containing 16 random bytes
|
||||||
|
*/
|
||||||
|
public static byte[] generateInitializationVector() {
|
||||||
|
byte[] iv = new byte[IV_SIZE];
|
||||||
|
new SecureRandom().nextBytes(iv);
|
||||||
|
return iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt a symmetric AES key with KSeF's RSA public key
|
||||||
|
*
|
||||||
|
* @param aesKey the AES symmetric key to encrypt
|
||||||
|
* @param ksefPublicKey KSeF's RSA public key
|
||||||
|
* @return Base64-encoded encrypted AES key
|
||||||
|
* @throws NoSuchAlgorithmException if RSA algorithm is not available
|
||||||
|
* @throws NoSuchPaddingException if padding scheme is not available
|
||||||
|
* @throws InvalidKeyException if the public key is invalid
|
||||||
|
* @throws IllegalBlockSizeException if the key size is invalid
|
||||||
|
* @throws BadPaddingException if padding is incorrect
|
||||||
|
*/
|
||||||
|
public static String encryptAesKey(SecretKey aesKey, PublicKey ksefPublicKey)
|
||||||
|
throws NoSuchAlgorithmException, NoSuchPaddingException,
|
||||||
|
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
||||||
|
|
||||||
|
Cipher rsaCipher = Cipher.getInstance(RSA_TRANSFORMATION);
|
||||||
|
rsaCipher.init(Cipher.ENCRYPT_MODE, ksefPublicKey);
|
||||||
|
|
||||||
|
byte[] encryptedKey = rsaCipher.doFinal(aesKey.getEncoded());
|
||||||
|
return Base64.getEncoder().encodeToString(encryptedKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode byte array to Base64 string
|
||||||
|
*
|
||||||
|
* @param data byte array to encode
|
||||||
|
* @return Base64-encoded string
|
||||||
|
*/
|
||||||
|
public static String toBase64(byte[] data) {
|
||||||
|
return Base64.getEncoder().encodeToString(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode Base64 string to byte array
|
||||||
|
*
|
||||||
|
* @param base64 Base64-encoded string
|
||||||
|
* @return decoded byte array
|
||||||
|
*/
|
||||||
|
public static byte[] fromBase64(String base64) {
|
||||||
|
return Base64.getDecoder().decode(base64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreate SecretKey from raw bytes (useful when loading from storage)
|
||||||
|
*
|
||||||
|
* @param keyBytes raw AES key bytes
|
||||||
|
* @return SecretKey instance
|
||||||
|
*/
|
||||||
|
public static SecretKey bytesToAesKey(byte[] keyBytes) {
|
||||||
|
return new SecretKeySpec(keyBytes, AES_ALGORITHM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate complete session encryption data
|
||||||
|
* Creates AES key, IV, and encrypts the AES key with KSeF's public key
|
||||||
|
*
|
||||||
|
* @param ksefPublicKey KSeF's RSA public key
|
||||||
|
* @return SessionEncryption containing all encryption data
|
||||||
|
* @throws NoSuchAlgorithmException if algorithm is not available
|
||||||
|
* @throws NoSuchPaddingException if padding scheme is not available
|
||||||
|
* @throws InvalidKeyException if the public key is invalid
|
||||||
|
* @throws IllegalBlockSizeException if the key size is invalid
|
||||||
|
* @throws BadPaddingException if padding is incorrect
|
||||||
|
*/
|
||||||
|
public static SessionEncryption createSessionEncryption(PublicKey ksefPublicKey)
|
||||||
|
throws NoSuchAlgorithmException, NoSuchPaddingException,
|
||||||
|
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
||||||
|
|
||||||
|
// Generate AES key and IV
|
||||||
|
SecretKey aesKey = generateAesKey();
|
||||||
|
byte[] iv = generateInitializationVector();
|
||||||
|
|
||||||
|
// Encrypt AES key with KSeF's public key
|
||||||
|
String encryptedAesKey = encryptAesKey(aesKey, ksefPublicKey);
|
||||||
|
|
||||||
|
return new SessionEncryption(aesKey, iv, encryptedAesKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.alexanderlogistics.ksef.api;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container class for session encryption data
|
||||||
|
*/
|
||||||
|
public class SessionEncryption {
|
||||||
|
private final SecretKey aesKey;
|
||||||
|
private final byte[] initializationVector;
|
||||||
|
private final String encryptedAesKeyBase64;
|
||||||
|
|
||||||
|
public SessionEncryption(SecretKey aesKey, byte[] iv, String encryptedAesKeyBase64) {
|
||||||
|
this.aesKey = aesKey;
|
||||||
|
this.initializationVector = iv;
|
||||||
|
this.encryptedAesKeyBase64 = encryptedAesKeyBase64;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecretKey getAesKey() {
|
||||||
|
return aesKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getAesKeyBytes() {
|
||||||
|
return aesKey.getEncoded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getInitializationVector() {
|
||||||
|
return initializationVector;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIvBase64() {
|
||||||
|
return Base64.getEncoder().encodeToString(initializationVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEncryptedAesKeyBase64() {
|
||||||
|
return encryptedAesKeyBase64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,18 +14,13 @@ import org.imixs.workflow.FileData;
|
||||||
import org.imixs.workflow.ItemCollection;
|
import org.imixs.workflow.ItemCollection;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.MethodOrderer;
|
|
||||||
import org.junit.jupiter.api.Order;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
|
||||||
|
|
||||||
import com.alexanderlogistics.TestLoggerConfig;
|
import com.alexanderlogistics.TestLoggerConfig;
|
||||||
|
|
||||||
//@Disabled
|
//@Disabled
|
||||||
@ExtendWith(MockitoExtension.class)
|
//@ExtendWith(MockitoExtension.class)
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
// @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
public class KSeFAPIServiceTest {
|
public class KSeFAPIServiceTest {
|
||||||
|
|
||||||
private static Logger logger = Logger.getLogger(KSeFAPIServiceTest.class.getName());
|
private static Logger logger = Logger.getLogger(KSeFAPIServiceTest.class.getName());
|
||||||
|
|
@ -49,16 +44,6 @@ public class KSeFAPIServiceTest {
|
||||||
// init() ausführen
|
// init() ausführen
|
||||||
manager.init();
|
manager.init();
|
||||||
|
|
||||||
// Open Session
|
|
||||||
// manager.openSession();
|
|
||||||
|
|
||||||
// manager.checkTokenStatus();
|
|
||||||
// manager.loadPublicKeyCertificates();
|
|
||||||
// manager.authChallenge();
|
|
||||||
// manager.authKSeFToken();
|
|
||||||
// manager.redeemToken();
|
|
||||||
// manager.openInteractiveSession();
|
|
||||||
|
|
||||||
// Create API Service and inject manager
|
// Create API Service and inject manager
|
||||||
apiService = new KSeFAPIService();
|
apiService = new KSeFAPIService();
|
||||||
apiService.kseFAuthManager = manager;
|
apiService.kseFAuthManager = manager;
|
||||||
|
|
@ -66,7 +51,6 @@ public class KSeFAPIServiceTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(4)
|
|
||||||
@DisplayName("Upload Invoice XML (AES-256 + RSA)")
|
@DisplayName("Upload Invoice XML (AES-256 + RSA)")
|
||||||
public void testUploadInvoice() throws Exception {
|
public void testUploadInvoice() throws Exception {
|
||||||
logger.info("==> Test: Upload Invoice XML");
|
logger.info("==> Test: Upload Invoice XML");
|
||||||
|
|
@ -74,6 +58,7 @@ public class KSeFAPIServiceTest {
|
||||||
ItemCollection workitem = createWorkitem();
|
ItemCollection workitem = createWorkitem();
|
||||||
|
|
||||||
// Execute upload
|
// Execute upload
|
||||||
|
|
||||||
String referenceNumber = apiService.uploadInvoice(workitem, "example-invoice-01.xml");
|
String referenceNumber = apiService.uploadInvoice(workitem, "example-invoice-01.xml");
|
||||||
|
|
||||||
// Verify result
|
// Verify result
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
|
@ -100,13 +99,18 @@ public class KSeFAuthManagerTest {
|
||||||
byte[] invoiceXml = Files.readAllBytes(xmlPath);
|
byte[] invoiceXml = Files.readAllBytes(xmlPath);
|
||||||
|
|
||||||
// ---------- 3) AES Key generieren ----------
|
// ---------- 3) AES Key generieren ----------
|
||||||
SecureRandom random = new SecureRandom();
|
// SecureRandom random = new SecureRandom();
|
||||||
|
// byte[] aesKeyBytes = new byte[32]; // 256-bit AES key
|
||||||
|
// random.nextBytes(aesKeyBytes);
|
||||||
|
// byte[] ivBytes = new byte[16]; // 128-bit IV
|
||||||
|
// random.nextBytes(ivBytes);
|
||||||
|
// SecretKeySpec aesKey = new SecretKeySpec(aesKeyBytes, "AES");
|
||||||
|
// IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
||||||
|
|
||||||
byte[] aesKeyBytes = new byte[32]; // 256-bit AES key
|
// Statt neue Keys zu generieren:
|
||||||
random.nextBytes(aesKeyBytes);
|
SessionEncryption encryption = manager.getSessionEncryption();
|
||||||
|
byte[] aesKeyBytes = encryption.getAesKeyBytes();
|
||||||
byte[] ivBytes = new byte[16]; // 128-bit IV
|
byte[] ivBytes = encryption.getInitializationVector();
|
||||||
random.nextBytes(ivBytes);
|
|
||||||
|
|
||||||
SecretKeySpec aesKey = new SecretKeySpec(aesKeyBytes, "AES");
|
SecretKeySpec aesKey = new SecretKeySpec(aesKeyBytes, "AES");
|
||||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
||||||
|
|
@ -118,7 +122,7 @@ public class KSeFAuthManagerTest {
|
||||||
byte[] encryptedInvoiceXml = aesCipher.doFinal(invoiceXml);
|
byte[] encryptedInvoiceXml = aesCipher.doFinal(invoiceXml);
|
||||||
|
|
||||||
// ---------- 5) AES Key per RSA verschlüsseln ----------
|
// ---------- 5) AES Key per RSA verschlüsseln ----------
|
||||||
PublicKey ksefPublicKey = manager.getSymmetricPublicKey();
|
PublicKey ksefPublicKey = manager.getSessionEncryptionPublicKey();
|
||||||
|
|
||||||
javax.crypto.Cipher rsaCipher = javax.crypto.Cipher
|
javax.crypto.Cipher rsaCipher = javax.crypto.Cipher
|
||||||
.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue