diff --git a/doc/KSeF/KSEF_CLIENT.md b/doc/KSeF/KSEF_CLIENT.md
new file mode 100644
index 0000000..878c219
--- /dev/null
+++ b/doc/KSeF/KSEF_CLIENT.md
@@ -0,0 +1,76 @@
+# ksef-client
+
+Um die ksef-client Library nutzen zu können muss man eine relativ komplexe prozedur durchlaufen um Zugrifff darauf zu erhalen:
+
+## 1. Personal Access Token (PAT) erstellen
+
+Du brauchst einen GitHub-Token, damit dein Projekt das Paket aus GitHub Packages herunterladen darf.
+
+So machst du das:
+
+1. Klicke oben rechts auf GitHub auf dein Profil.
+2. Gehe zu Settings.
+3. Links unten: Developer settings.
+4. Dann: Personal access tokens.
+5. Dann: Tokens (classic).
+6. Klicke: Generate new token (classic).
+
+**Beim Erstellen:**
+
+- Vergib irgendeinen Namen (z. B. „ksef-client access“).
+- Wähle nur ein einziges Häkchen:
+
+👉 read:packages
+
+- Token generieren → Token kopieren! Du siehst ihn nur einmal.
+
+## 2. Maven für GitHub Packages konfigurieren
+
+Damit Maven die Pakete laden kann, musst du GitHub Packages in der Datei `~/.m2/settings.xml` eintragen.
+
+👉 Inhalt, den du einfügen musst:
+
+```xml
+
+
+
+ github-cirfmf
+ rsoika
+ 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
+
+
+
+
+
+```
+
+## 3. Repository in deiner pom.xml eintragen
+
+Füge das in deine pom.xml ein:
+
+```
+
+
+ github-cirfmf
+ https://maven.pkg.github.com/CIRFMF/ksef-client-java
+
+
+
+```
+
+## 4. Abhängigkeit hinzufügen
+
+Auch in die pom.xml:
+
+```xml
+ ..
+
+ pl.akmf.ksef-sdk
+ ksef-client
+ 3.0.4
+
+...
+```
diff --git a/office-alexander-logistics-app/pom.xml b/office-alexander-logistics-app/pom.xml
index 83af5bb..ffbc567 100644
--- a/office-alexander-logistics-app/pom.xml
+++ b/office-alexander-logistics-app/pom.xml
@@ -400,12 +400,18 @@
- io.alapierre.ksef-sdk
+ pl.akmf.ksef-sdk
ksef-client
- 2.1.8
+ 3.0.4
test
-
+
+
+
+ github-cirfmf
+ https://maven.pkg.github.com/CIRFMF/ksef-client-java
+
+
\ No newline at end of file
diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ksef/KSeFAPIService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ksef/KSeFAPIService.java
index 61737c2..822d99b 100644
--- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ksef/KSeFAPIService.java
+++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ksef/KSeFAPIService.java
@@ -7,7 +7,11 @@ import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.util.ArrayList;
import java.util.Base64;
+import java.util.List;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
@@ -18,6 +22,7 @@ import javax.crypto.spec.SecretKeySpec;
import org.imixs.workflow.FileData;
import org.imixs.workflow.ItemCollection;
+import org.imixs.workflow.engine.DocumentService;
import org.imixs.workflow.exceptions.PluginException;
import jakarta.annotation.PostConstruct;
@@ -36,172 +41,185 @@ import jakarta.json.bind.JsonbBuilder;
*
*/
@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
- "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
- "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
+ "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
+ "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
@RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
- "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
- "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
+ "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
+ "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
@Singleton
@RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS")
public class KSeFAPIService {
- private static Logger logger = Logger.getLogger(KSeFAPIService.class.getName());
+ private static Logger logger = Logger.getLogger(KSeFAPIService.class.getName());
- public static final String ERROR_API = "API_ERROR";
+ public static final String ERROR_API = "API_ERROR";
- @Inject
- KSeFAuthManager kseFAuthManager;
+ @Inject
+ KSeFAuthManager kseFAuthManager;
- @PostConstruct
- void init() {
+ @Inject
+ DocumentService documentService;
- }
+ @PostConstruct
+ void init() {
- /**
- * This method uploads a KSeF Invoice document (XML).
- * The response of the upload is a 'referenceNumber' which is stored into the
- * item ksef.referenceNumber
- *
- * @param workitem - the workitem containing the invoice file
- * @param fileName - the name of the invoice file to upload
- * @return referenceNumber - the KSeF reference number for the uploaded invoice
- * @throws PluginException
- */
- public String uploadInvoice(ItemCollection workitem, String fileName)
- throws PluginException {
- String referenceNumber = null;
- logger.info("├── Upload Invoice...");
-
- // First open an interactive Session. The KSeFAuthManager automatically reuses
- // an existing session
- kseFAuthManager.openSession();
-
- if (kseFAuthManager.getAccessToken() == null) {
- throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
- "API Error - missing AccessToken!");
- }
- if (kseFAuthManager.getSessionRefNumber() == null) {
- throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
- "API Error - missing SessionRefNumber!");
}
- // Get session encryption keys
- byte[] aesKeyBytes = kseFAuthManager.getSessionAesKey();
- byte[] ivBytes = kseFAuthManager.getSessionIv();
+ /**
+ * This method uploads an KSeF Invoice document (XML).
+ * The response of the uplaod is a 'referenceNumber' which is stored into the
+ * item ksef.referenceNumber
+ *
+ *
+ * @param companyName
+ * @param contactType
+ * @param accessToken
+ * @return
+ * @throws PluginException
+ */
+ public String uploadInvoice(ItemCollection workitem, String fileName)
+ throws PluginException {
+ String referenceNumber = null;
+ logger.info("├── Upload Invoice...");
- if (aesKeyBytes == null || ivBytes == null) {
- throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
- "API Error - missing session encryption keys!");
+ // First open an interactive Session. The KSeFAuthManager automatically reuses
+ // an existing session
+
+ kseFAuthManager.openSession();
+
+ if (kseFAuthManager.getAccessToken() == null) {
+ throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
+ "API Error - missing AccessToken!");
+ }
+ if (kseFAuthManager.getSessionRefNumber() == null) {
+ throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
+ "API Error - missing SessionRefNumber!");
+ }
+
+ // load invoice
+ FileData fileData = workitem.getFileData(fileName);
+
+ byte[] invoiceXml = fileData.getContent();
+
+ logger.info("│ ├── XML Invoice loaded - " + invoiceXml.length + " bytes");
+ try {
+ // ---------- 3) AES Key generieren ----------
+ logger.info("│ ├── Generate AES Key...");
+ 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);
+
+ // ---------- 4) XML per AES-256-CBC verschlüsseln ----------
+ javax.crypto.Cipher aesCipher;
+
+ aesCipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
+
+ aesCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, iv);
+
+ byte[] encryptedInvoiceXml = aesCipher.doFinal(invoiceXml);
+
+ // ---------- 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,
+ invoiceXml.length,
+ encryptedInvoiceHash,
+ encryptedInvoiceXml.length,
+ Base64.getEncoder().encodeToString(encryptedInvoiceXml));
+
+ // ---------- 8) HTTP POST vorbereiten ----------
+ // String uri = manager.getBaseURI() + "/sessions/online/" +
+ // manager.getRefNumber() + "/invoices";
+ String uri = kseFAuthManager.getBaseURI() + "/sessions/online/"
+ + kseFAuthManager.getSessionRefNumber()
+ + "/invoices";
+ logger.info("│ ├── Endpoint: " + uri);
+
+ if (kseFAuthManager.isDebug()) {
+ logger.info("Request: " + jsonBody);
+ }
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(uri))
+ .header("Content-Type", "application/json")
+ .header("Authorization", "Bearer " + kseFAuthManager.getAccessToken())
+ .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
+ .build();
+
+ // ---------- 9) Call ausführen ----------
+ var response = kseFAuthManager.getHttpClient().send(request,
+ java.net.http.HttpResponse.BodyHandlers.ofString());
+
+ System.out.println("Upload Response:");
+ System.out.println(response.statusCode());
+ System.out.println(response.body());
+
+ try (Jsonb jsonb = JsonbBuilder.create()) {
+ // Annahme: response.body() ist ein String mit JSON-Inhalt
+ JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
+ referenceNumber = jsonObject.getString("referenceNumber");
+
+ if (kseFAuthManager.isDebug()) {
+ logger.info("│ ├── referenceNumber: " + referenceNumber);
+ }
+
+ } catch (Exception e) {
+ logger.severe("├── ⚠️ Error parsing JSON response" + e.getMessage());
+ throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
+ "Error parsing JSON response: " + e.getMessage());
+
+ }
+
+ if (response.statusCode() == 202) {
+ logger.info("├── ✅ Upload successful ");
+ } 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