verbesserte auth methodik
This commit is contained in:
parent
9f6d86d328
commit
5ead936783
2 changed files with 153 additions and 324 deletions
|
|
@ -7,13 +7,17 @@ import java.net.http.HttpRequest;
|
|||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.OAEPParameterSpec;
|
||||
import javax.crypto.spec.PSource;
|
||||
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.imixs.workflow.exceptions.InvalidAccessException;
|
||||
|
|
@ -70,19 +74,11 @@ public class KSeFAuthManager {
|
|||
private String challenge = "";
|
||||
private String refNumber = null;
|
||||
private String authToken = null;
|
||||
private String accessToken = null;
|
||||
private String refreshToken = null;
|
||||
private String symmetricCertBase64; // der Zertifikat-String aus der API
|
||||
private PublicKey symmetricPublicKey; // der extrahierte RSA Public Key
|
||||
private String sessionRefNumber = null;
|
||||
private String sessionValidUntil = null;
|
||||
private String ksefTokenEncryptionCertBase64 = null;
|
||||
private PublicKey ksefTokenEncryptionPublicKey;
|
||||
private String challengeTimestamp = null;
|
||||
|
||||
private byte[] sessionAesKey;
|
||||
|
||||
private byte[] sessionIv;
|
||||
private String symmetricCertBase64;
|
||||
private String ksefTokenCertBase64;
|
||||
private PublicKey symmetricPublicKey;
|
||||
private PublicKey ksefTokenPublicKey;
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
|
|
@ -135,13 +131,12 @@ public class KSeFAuthManager {
|
|||
*
|
||||
* @return
|
||||
*/
|
||||
// public String getEncodedToken() {
|
||||
public String getEncodedToken() {
|
||||
|
||||
// String result =
|
||||
// java.util.Base64.getEncoder().encodeToString(ksefToken.orElse("").getBytes());
|
||||
String result = java.util.Base64.getEncoder().encodeToString(ksefToken.orElse("").getBytes());
|
||||
|
||||
// return result;
|
||||
// }
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getBaseURI() {
|
||||
return baseURI;
|
||||
|
|
@ -155,14 +150,6 @@ public class KSeFAuthManager {
|
|||
return authToken;
|
||||
}
|
||||
|
||||
public PublicKey getSymmetricPublicKey() {
|
||||
return symmetricPublicKey;
|
||||
}
|
||||
|
||||
public String getSymmetricCertBase64() {
|
||||
return symmetricCertBase64;
|
||||
}
|
||||
|
||||
public String getChallengeValue() {
|
||||
return challenge;
|
||||
}
|
||||
|
|
@ -174,7 +161,7 @@ public class KSeFAuthManager {
|
|||
*/
|
||||
// @Lock(LockType.READ)
|
||||
@Lock(LockType.WRITE)
|
||||
public void getChallenge() throws PluginException {
|
||||
public void authChallenge() throws PluginException {
|
||||
logger.info("├── KSeF API Challenge...");
|
||||
String uri = baseURI + "/auth/challenge";
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
|
|
@ -230,30 +217,10 @@ public class KSeFAuthManager {
|
|||
* @throws Exception
|
||||
*/
|
||||
@Lock(LockType.WRITE)
|
||||
public void generateAuthToken() throws Exception {
|
||||
public void authKSeFToken() throws Exception {
|
||||
logger.info("├── KSeF API auth ksef-token...");
|
||||
String uri = baseURI + "/auth/ksef-token";
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
|
||||
if (ksefTokenEncryptionPublicKey == null) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_CONFIG,
|
||||
"Missing public key for KSeF token encryption (KsefTokenEncryption).");
|
||||
}
|
||||
|
||||
// token|timestamp (aus challenge)
|
||||
String token = ksefToken.orElseThrow(() -> new PluginException(
|
||||
KSeFAuthManager.class.getSimpleName(), ERROR_CONFIG, "Missing KSeF API token"));
|
||||
|
||||
String tokenWithTimestamp = token + "|" + challengeTimestamp;
|
||||
|
||||
logger.info("│ ├── token|timestamp = " + tokenWithTimestamp);
|
||||
|
||||
// Verschlüsseln mit RSA-OAEP SHA-256
|
||||
String encryptedToken = encryptWithPublicKey(tokenWithTimestamp, ksefTokenEncryptionPublicKey);
|
||||
|
||||
logger.info("│ ├── Encrypted token (Base64): " + encryptedToken);
|
||||
|
||||
// JSON payload zusammenbauen
|
||||
String jsonPayload = String.format(
|
||||
"{" +
|
||||
"\"challenge\": \"%s\"," +
|
||||
|
|
@ -263,9 +230,7 @@ public class KSeFAuthManager {
|
|||
"}," +
|
||||
"\"encryptedToken\": \"%s\"" +
|
||||
"}",
|
||||
challenge,
|
||||
ksefNip.get(),
|
||||
encryptedToken);
|
||||
challenge, ksefNip.get(), getEncryptedToken());
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(uri))
|
||||
|
|
@ -275,84 +240,146 @@ public class KSeFAuthManager {
|
|||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
if (debug) {
|
||||
logger.info("Response: " + response.body());
|
||||
}
|
||||
|
||||
// Prüfen, dass wir eine API-Antwort bekommen
|
||||
if (response.statusCode() >= 500) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_CONFIG,
|
||||
"Unable to access KSeF API: response=" + response.statusCode());
|
||||
}
|
||||
|
||||
// JSON parsen und Werte speichern
|
||||
if (response.statusCode() == 200 || response.statusCode() == 202) {
|
||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
||||
|
||||
// Reference Number speichern
|
||||
refNumber = jsonObject.getString("referenceNumber");
|
||||
logger.info("Extracted referenceNumber: " + refNumber);
|
||||
|
||||
// Authentication Token speichern
|
||||
JsonObject authTokenObject = jsonObject.getJsonObject("authenticationToken");
|
||||
authToken = authTokenObject.getString("token");
|
||||
logger.info("Extracted authenticationToken: " + authToken);
|
||||
|
||||
} 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verschlüsselt den KSeF-Token mit RSA-OAEP (SHA-256) für die Authentifizierung
|
||||
*
|
||||
* Format: token|timestamp_in_milliseconds
|
||||
*
|
||||
* @return Base64-kodierter verschlüsselter Token
|
||||
* @throws Exception
|
||||
*/
|
||||
public String getEncryptedToken() throws Exception {
|
||||
|
||||
if (ksefTokenPublicKey == null) {
|
||||
throw new IllegalStateException("Public Key not loaded! Call loadPublicKeyCertificates() first.");
|
||||
}
|
||||
|
||||
if (challengeTimestamp == null || challengeTimestamp.isEmpty()) {
|
||||
throw new IllegalStateException("Challenge timestamp is missing! Call authChallenge() first.");
|
||||
}
|
||||
|
||||
// 1. Timestamp parsen - KSeF liefert zu viele Dezimalstellen
|
||||
// Format: "2025-11-14T14:18:35.606507+00:00"
|
||||
// Flexibler Parser der verschiedene Dezimalstellen akzeptiert
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
||||
Instant instant = Instant.from(formatter.parse(challengeTimestamp));
|
||||
|
||||
long timestampMillis = instant.toEpochMilli();
|
||||
|
||||
logger.info("│ ├── Timestamp (ms): " + timestampMillis);
|
||||
|
||||
// 2. Format erstellen: token|timestamp
|
||||
String plaintext = ksefToken.get() + "|" + timestampMillis;
|
||||
|
||||
if (debug) {
|
||||
logger.info("│ ├── Plaintext format: [token]|[" + timestampMillis + "]");
|
||||
}
|
||||
|
||||
// 3. RSA-OAEP Verschlüsselung mit SHA-256
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
OAEPParameterSpec oaepParams = new OAEPParameterSpec(
|
||||
"SHA-256",
|
||||
"MGF1",
|
||||
MGF1ParameterSpec.SHA256,
|
||||
PSource.PSpecified.DEFAULT);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, ksefTokenPublicKey, oaepParams);
|
||||
|
||||
byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 4. Base64 kodieren
|
||||
String result = Base64.getEncoder().encodeToString(encrypted);
|
||||
|
||||
logger.info("│ ├── ✓ Token encrypted with RSA-OAEP");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redeems the KSeF token and returns a session token
|
||||
*
|
||||
* POST /auth/token/redeem
|
||||
*
|
||||
* @return JsonObject with referenceNumber and sessionToken
|
||||
* @throws Exception
|
||||
*/
|
||||
@Lock(LockType.WRITE)
|
||||
public void redeemAuthToken() throws Exception {
|
||||
logger.info("├── KSeF API redeem authentication token...");
|
||||
public JsonObject redeemToken() throws Exception {
|
||||
|
||||
logger.info("├── KSeF API redeem token...");
|
||||
String uri = baseURI + "/auth/token/redeem";
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(uri))
|
||||
.header("Accept", "application/json")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer " + authToken) // authToken aus generateAuthToken()
|
||||
.POST(HttpRequest.BodyPublishers.noBody()) // Payload leer, API erwartet nur Bearer im Header
|
||||
.header("Authorization", "Bearer " + authToken)
|
||||
.POST(HttpRequest.BodyPublishers.noBody())
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
HttpResponse<String> response = httpClient.send(request,
|
||||
HttpResponse.BodyHandlers.ofString());
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
|
||||
if (debug) {
|
||||
logger.info("Response: " + response.body());
|
||||
logger.info("│ └── Response: " + response.body());
|
||||
}
|
||||
|
||||
// Prüfen auf Fehler
|
||||
if (response.statusCode() >= 500) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_CONFIG,
|
||||
"Unable to access KSeF API: response=" + response.statusCode());
|
||||
if (response.statusCode() >= 400) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(),
|
||||
ERROR_API,
|
||||
"Token redeem failed: " + response.statusCode()
|
||||
+ " - " + response.body());
|
||||
}
|
||||
|
||||
// JSON parsen und AccessToken + RefreshToken speichern
|
||||
if (response.statusCode() == 200 || response.statusCode() == 201) {
|
||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
||||
|
||||
JsonObject accessTokenObj = jsonObject.getJsonObject("accessToken");
|
||||
accessToken = accessTokenObj.getString("token"); // finaler Bearer für alle weiteren API-Calls
|
||||
logger.info("Extracted accessToken: " + accessToken);
|
||||
// Access Token extrahieren und speichern
|
||||
JsonObject accessTokenObj = jsonObject.getJsonObject("accessToken");
|
||||
this.authToken = accessTokenObj.getString("token");
|
||||
|
||||
JsonObject refreshTokenObj = jsonObject.getJsonObject("refreshToken");
|
||||
refreshToken = refreshTokenObj.getString("token"); // optional zum späteren Refresh
|
||||
if (debug) {
|
||||
logger.info("Extracted refreshToken: " + refreshToken);
|
||||
}
|
||||
logger.info("│ └── Access Token received");
|
||||
|
||||
} 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());
|
||||
}
|
||||
} else {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"Failed to redeem authentication token: " + response.statusCode());
|
||||
return jsonObject;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.severe("├── ⚠️ Error parsing JSON response: " + e.getMessage());
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(),
|
||||
ERROR_API,
|
||||
"Error parsing response: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -362,7 +389,7 @@ public class KSeFAuthManager {
|
|||
* @throws Exception
|
||||
*/
|
||||
@Lock(LockType.WRITE)
|
||||
public void loadKsefPublicKeys() throws Exception {
|
||||
public void loadPublicKeyCertificates() throws Exception {
|
||||
|
||||
logger.info("├── KSeF API load public-key-certificates...");
|
||||
String uri = baseURI + "/security/public-key-certificates";
|
||||
|
|
@ -383,81 +410,53 @@ public class KSeFAuthManager {
|
|||
|
||||
if (response.statusCode() != 200) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"Unexpected response: " + response.statusCode());
|
||||
"Unexpected response from KSeF certificates endpoint: " + response.statusCode());
|
||||
}
|
||||
|
||||
// JSON parsen
|
||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||
|
||||
// WICHTIG: JsonArray lesen – NICHT JsonObject[]
|
||||
jakarta.json.JsonArray certArray = jsonb.fromJson(response.body(), jakarta.json.JsonArray.class);
|
||||
|
||||
for (var entryValue : certArray) {
|
||||
|
||||
JsonObject entry = entryValue.asJsonObject();
|
||||
var certArray = jsonb.fromJson(response.body(), JsonObject[].class);
|
||||
for (JsonObject entry : certArray) {
|
||||
// usage = ["KsefTokenEncryption"] oder ["SymmetricKeyEncryption"]
|
||||
var usages = entry.getJsonArray("usage");
|
||||
|
||||
if (usages != null &&
|
||||
usages.stream().anyMatch(v -> v.toString().contains("KsefTokenEncryption"))) {
|
||||
if (usages.stream().anyMatch(v -> v.toString().contains("KsefTokenEncryption"))) {
|
||||
ksefTokenCertBase64 = entry.getString("certificate");
|
||||
logger.info("│ ├── KsefTokenEncryption certificate found.");
|
||||
// Public Key extrahieren
|
||||
ksefTokenPublicKey = extractPublicKeyFromCertificate(ksefTokenCertBase64);
|
||||
logger.info("│ ├── ✓ RSA PublicKey successfully extracted.");
|
||||
|
||||
ksefTokenEncryptionCertBase64 = entry.getString("certificate");
|
||||
logger.info("│ ├── Found certificate with usage = KsefTokenEncryption");
|
||||
}
|
||||
|
||||
ksefTokenEncryptionPublicKey = extractPublicKeyFromCertificate(ksefTokenEncryptionCertBase64);
|
||||
if (usages.stream().anyMatch(v -> v.toString().contains("SymmetricKeyEncryption"))) {
|
||||
symmetricCertBase64 = entry.getString("certificate");
|
||||
logger.info("│ ├── SymmetricKeyEncryption certificate found.");
|
||||
// Public Key extrahieren
|
||||
symmetricPublicKey = extractPublicKeyFromCertificate(symmetricCertBase64);
|
||||
logger.info("│ ├── ✓ RSA PublicKey successfully extracted.");
|
||||
|
||||
logger.info("│ ├── ✓ RSA PublicKey for KSeF Token Encryption extracted.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"No certificate with usage = KsefTokenEncryption found");
|
||||
|
||||
if (ksefTokenPublicKey == null) {
|
||||
logger.warning("│ ├── ⚠️ No certificate with usage 'ksefTokenPublicKey' found!");
|
||||
}
|
||||
if (symmetricPublicKey == null) {
|
||||
logger.warning("│ ├── ⚠️ No certificate with usage 'symmetricPublicKey' found!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"Error parsing certificates JSON: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Lock(LockType.WRITE)
|
||||
public void loadSymmetricKeyPublicKey() throws Exception {
|
||||
|
||||
logger.info("├── KSeF API load symmetric public key...");
|
||||
String uri = baseURI + "/security/public-key";
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(uri))
|
||||
.header("Accept", "application/json")
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
if (debug) {
|
||||
logger.info("Response: " + response.body());
|
||||
}
|
||||
|
||||
if (response.statusCode() != 200) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"Unexpected response: " + response.statusCode());
|
||||
}
|
||||
|
||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||
|
||||
JsonObject json = jsonb.fromJson(response.body(), JsonObject.class);
|
||||
|
||||
symmetricCertBase64 = json.getString("certificate");
|
||||
logger.info("│ ├── Symmetric key certificate loaded");
|
||||
|
||||
symmetricPublicKey = extractPublicKeyFromCertificate(symmetricCertBase64);
|
||||
|
||||
logger.info("│ ├── ✓ RSA PublicKey for session encryption extracted.");
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"Error parsing public key JSON: " + e.getMessage());
|
||||
}
|
||||
private PublicKey extractPublicKeyFromCertificate(String base64Cert) throws Exception {
|
||||
byte[] der = java.util.Base64.getDecoder().decode(base64Cert);
|
||||
java.security.cert.CertificateFactory factory = java.security.cert.CertificateFactory.getInstance("X.509");
|
||||
var certificate = (java.security.cert.X509Certificate) factory
|
||||
.generateCertificate(new java.io.ByteArrayInputStream(der));
|
||||
return certificate.getPublicKey();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -468,90 +467,11 @@ public class KSeFAuthManager {
|
|||
@Lock(LockType.WRITE)
|
||||
public void openInteractiveSession() throws Exception {
|
||||
|
||||
if (accessToken == null) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"Error - missing access token!");
|
||||
}
|
||||
|
||||
logger.info("├── KSeF API open interactive session...");
|
||||
String uri = baseURI + "/sessions/online";
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
|
||||
// --- AES Schlüssel erzeugen ---
|
||||
SecureRandom rnd = new SecureRandom();
|
||||
byte[] aesKeyBytes = new byte[32]; // 256-bit AES key
|
||||
rnd.nextBytes(aesKeyBytes);
|
||||
byte[] ivBytes = new byte[16]; // 128-bit IV
|
||||
rnd.nextBytes(ivBytes);
|
||||
|
||||
// --- AES Key mit RSA encrypten ---
|
||||
// Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
|
||||
rsa.init(Cipher.ENCRYPT_MODE, this.symmetricPublicKey);
|
||||
byte[] encryptedAesKey = rsa.doFinal(aesKeyBytes);
|
||||
|
||||
String jsonPayload = String.format(
|
||||
"{" +
|
||||
"\"formCode\": {" +
|
||||
" \"systemCode\": \"FA (3)\"," +
|
||||
" \"schemaVersion\": \"1-0E\"," +
|
||||
" \"value\": \"FA\"" +
|
||||
"}," +
|
||||
"\"encryption\": {" +
|
||||
" \"encryptedSymmetricKey\": \"%s\"," +
|
||||
" \"initializationVector\": \"%s\"" +
|
||||
"}" +
|
||||
"}",
|
||||
Base64.getEncoder().encodeToString(encryptedAesKey),
|
||||
Base64.getEncoder().encodeToString(ivBytes));
|
||||
|
||||
if (debug) {
|
||||
logger.info("│ ├── Payload: " + jsonPayload);
|
||||
}
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(uri))
|
||||
// .header("Accept-Charset", "UTF-8")
|
||||
.header("Accept", "application/json")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer " + accessToken)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
if (debug) {
|
||||
logger.info("Response: " + response.body());
|
||||
}
|
||||
|
||||
if (response.statusCode() != 201) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"Failed to open interactive session: " + response.statusCode());
|
||||
}
|
||||
|
||||
// JSON parsen
|
||||
// Session RefNumber aus Response extrahieren und speichern
|
||||
|
||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||
// Annahme: response.body() ist ein String mit JSON-Inhalt
|
||||
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
||||
sessionRefNumber = jsonObject.getString("referenceNumber");
|
||||
sessionValidUntil = jsonObject.getString("validUntil");
|
||||
if (debug) {
|
||||
logger.info("│ ├── Session ReferenceNumber = " + sessionRefNumber);
|
||||
logger.info("│ └── Session ValidUntil = " + sessionValidUntil);
|
||||
}
|
||||
|
||||
// --- WICHTIG: AES Schlüssel speichern, wir brauchen ihn beim Upload ---
|
||||
this.sessionAesKey = aesKeyBytes;
|
||||
this.sessionIv = ivBytes;
|
||||
} 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());
|
||||
|
||||
}
|
||||
// ............
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -568,9 +488,6 @@ public class KSeFAuthManager {
|
|||
|
||||
logger.info("├── KSeF API check authentication status...");
|
||||
|
||||
logger.info("Using public key algorithm: " + symmetricPublicKey.getAlgorithm());
|
||||
logger.info("Key format: " + symmetricPublicKey.getFormat());
|
||||
|
||||
String uri = baseURI + "/auth/" + refNumber;
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
|
||||
|
|
@ -605,24 +522,4 @@ public class KSeFAuthManager {
|
|||
}
|
||||
}
|
||||
|
||||
private PublicKey extractPublicKeyFromCertificate(String base64Cert) throws Exception {
|
||||
|
||||
byte[] der = java.util.Base64.getDecoder().decode(base64Cert);
|
||||
|
||||
java.security.cert.CertificateFactory factory = java.security.cert.CertificateFactory.getInstance("X.509");
|
||||
|
||||
var certificate = (java.security.cert.X509Certificate) factory
|
||||
.generateCertificate(new java.io.ByteArrayInputStream(der));
|
||||
|
||||
return certificate.getPublicKey();
|
||||
}
|
||||
|
||||
private String encryptWithPublicKey(String data, PublicKey publicKey) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getEncoder().encodeToString(encryptedBytes);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.alexanderlogistics.ksef;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
|
@ -37,88 +36,21 @@ public class KSeFAuthManagerTest {
|
|||
// ------------------------------------------------------------
|
||||
@Test
|
||||
@Order(1)
|
||||
@DisplayName("GET /auth/challenge should return a challenge")
|
||||
void testChallenge() throws Exception {
|
||||
@DisplayName("POST /api/v2/auth/token/redeem")
|
||||
void testRedeemToken() throws Exception {
|
||||
|
||||
manager.getChallenge();
|
||||
manager.loadPublicKeyCertificates();
|
||||
manager.authChallenge();
|
||||
manager.authKSeFToken();
|
||||
manager.redeemToken();
|
||||
|
||||
assertNotNull(
|
||||
manager.getChallengeValue(),
|
||||
"Challenge darf nicht NULL sein");
|
||||
manager.getRefNumber(),
|
||||
"RefNumber darf nicht NULL sein");
|
||||
|
||||
assertFalse(
|
||||
manager.getChallengeValue().isBlank(),
|
||||
"Challenge darf nicht leer sein");
|
||||
|
||||
System.out.println("Challenge = " + manager.getChallengeValue());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// 2) Zertifikate holen (nur Public Keys)
|
||||
// ------------------------------------------------------------
|
||||
@Test
|
||||
@Order(2)
|
||||
@DisplayName("GET /security/public-key-certificates should return symmetric key certificate")
|
||||
void testPublicKeys() throws Exception {
|
||||
|
||||
manager.getKsefPublicKeys();
|
||||
|
||||
assertNotNull(manager.getSymmetricPublicKey(), "Symmetric RSA PublicKey muss vorhanden sein");
|
||||
assertNotNull(manager.getSymmetricCertBase64(), "Base64 Zertifikat muss vorhanden sein");
|
||||
|
||||
System.out.println("Symmetric PublicKey OK");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// 3) KSeF Token Request
|
||||
// ------------------------------------------------------------
|
||||
@Test
|
||||
@Order(3)
|
||||
@DisplayName("POST /auth/ksef-token should return a token")
|
||||
void testKsefToken() throws Exception {
|
||||
|
||||
// Challenge muss vorher geholt worden sein
|
||||
manager.getChallenge();
|
||||
|
||||
// Token verschlüsseln
|
||||
manager.generateAuthToken();
|
||||
|
||||
// Token-Call durchführen
|
||||
String authToken = manager.getAuthToken();
|
||||
assertNotNull(authToken, "auth token darf nicht NULL sein");
|
||||
|
||||
String refNumber = manager.getRefNumber();
|
||||
assertNotNull(refNumber, "refNumber darf nicht NULL sein");
|
||||
|
||||
System.out.println("Received KSeF Token OK");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
@DisplayName("Upload Invoice XML (AES-256 + RSA)")
|
||||
public void testOpenInteractiveSession() throws Exception {
|
||||
|
||||
// ---------- 1) Vorbereitung ----------
|
||||
// API initialisieren
|
||||
manager.loadKsefPublicKeys();
|
||||
manager.getChallenge();
|
||||
manager.generateAuthToken(); // erzeugt authToken + refNumber
|
||||
manager.getAuthStatus();
|
||||
manager.redeemAuthToken();
|
||||
|
||||
assertNotNull(manager.getAuthToken(), "AccessToken fehlt");
|
||||
assertNotNull(manager.getRefNumber(), "ReferenceNumber fehlt");
|
||||
|
||||
manager.openInteractiveSession();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5)
|
||||
@DisplayName("Upload Invoice XML (AES-256 + RSA)")
|
||||
public void testUploadInvoice() throws Exception {
|
||||
|
||||
// ---------- 1) Vorbereitung ----------
|
||||
// API initialisieren
|
||||
assertNotNull(
|
||||
manager.getAuthToken(),
|
||||
"AuthToken darf nicht leer sein");
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue