test authManager
This commit is contained in:
parent
5ead936783
commit
26b08eb507
3 changed files with 270 additions and 56 deletions
|
|
@ -7,6 +7,7 @@ import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
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;
|
||||||
|
|
@ -74,11 +75,17 @@ public class KSeFAuthManager {
|
||||||
private String challenge = "";
|
private String challenge = "";
|
||||||
private String refNumber = null;
|
private String refNumber = null;
|
||||||
private String authToken = null;
|
private String authToken = null;
|
||||||
|
private String accessToken = null;
|
||||||
private String challengeTimestamp = null;
|
private String challengeTimestamp = null;
|
||||||
private String symmetricCertBase64;
|
private String symmetricCertBase64;
|
||||||
private String ksefTokenCertBase64;
|
private String ksefTokenCertBase64;
|
||||||
private PublicKey symmetricPublicKey;
|
private PublicKey symmetricPublicKey;
|
||||||
private PublicKey ksefTokenPublicKey;
|
private PublicKey ksefTokenPublicKey;
|
||||||
|
private String sessionRefNumber;
|
||||||
|
private String sessionValidUntil;
|
||||||
|
|
||||||
|
private byte[] sessionAesKey;
|
||||||
|
private byte[] sessionIv;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
void init() {
|
void init() {
|
||||||
|
|
@ -114,6 +121,10 @@ public class KSeFAuthManager {
|
||||||
this.ksefToken = Optional.ofNullable(token);
|
this.ksefToken = Optional.ofNullable(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAccessToken() {
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
public void setKsefNip(String nip) {
|
public void setKsefNip(String nip) {
|
||||||
this.ksefNip = Optional.ofNullable(nip);
|
this.ksefNip = Optional.ofNullable(nip);
|
||||||
}
|
}
|
||||||
|
|
@ -122,6 +133,10 @@ public class KSeFAuthManager {
|
||||||
this.ksefEndpoint = Optional.ofNullable(endpoint);
|
this.ksefEndpoint = Optional.ofNullable(endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSessionRefNumber() {
|
||||||
|
return sessionRefNumber;
|
||||||
|
}
|
||||||
|
|
||||||
public void setDebug(boolean debug) {
|
public void setDebug(boolean debug) {
|
||||||
this.debug = debug;
|
this.debug = debug;
|
||||||
}
|
}
|
||||||
|
|
@ -150,6 +165,10 @@ public class KSeFAuthManager {
|
||||||
return authToken;
|
return authToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PublicKey getSymmetricPublicKey() {
|
||||||
|
return symmetricPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
public String getChallengeValue() {
|
public String getChallengeValue() {
|
||||||
return challenge;
|
return challenge;
|
||||||
}
|
}
|
||||||
|
|
@ -273,6 +292,101 @@ public class KSeFAuthManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper Method to open an interactive session
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@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());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verschlüsselt den KSeF-Token mit RSA-OAEP (SHA-256) für die Authentifizierung
|
* Verschlüsselt den KSeF-Token mit RSA-OAEP (SHA-256) für die Authentifizierung
|
||||||
*
|
*
|
||||||
|
|
@ -369,7 +483,7 @@ public class KSeFAuthManager {
|
||||||
|
|
||||||
// Access Token extrahieren und speichern
|
// Access Token extrahieren und speichern
|
||||||
JsonObject accessTokenObj = jsonObject.getJsonObject("accessToken");
|
JsonObject accessTokenObj = jsonObject.getJsonObject("accessToken");
|
||||||
this.authToken = accessTokenObj.getString("token");
|
this.accessToken = accessTokenObj.getString("token");
|
||||||
|
|
||||||
logger.info("│ └── Access Token received");
|
logger.info("│ └── Access Token received");
|
||||||
|
|
||||||
|
|
@ -459,22 +573,6 @@ public class KSeFAuthManager {
|
||||||
return certificate.getPublicKey();
|
return certificate.getPublicKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper Method to open an interactive session
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
@Lock(LockType.WRITE)
|
|
||||||
public void openInteractiveSession() throws Exception {
|
|
||||||
|
|
||||||
logger.info("├── KSeF API open interactive session...");
|
|
||||||
String uri = baseURI + "/sessions/online";
|
|
||||||
logger.info("│ ├── Endpoint: " + uri);
|
|
||||||
|
|
||||||
// ............
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper Method to analyze the auth status
|
* Helper Method to analyze the auth status
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,21 @@
|
||||||
package com.alexanderlogistics.ksef;
|
package com.alexanderlogistics.ksef;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
|
@ -15,43 +30,147 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
public class KSeFAuthManagerTest {
|
public class KSeFAuthManagerTest {
|
||||||
|
|
||||||
private KSeFAuthManager manager;
|
private static Logger logger = Logger.getLogger(KSeFAuthManagerTest.class.getName());
|
||||||
|
|
||||||
@BeforeEach
|
private KSeFAuthManager manager;
|
||||||
void setup() throws Exception {
|
|
||||||
manager = new KSeFAuthManager();
|
|
||||||
|
|
||||||
// Test config
|
@BeforeEach
|
||||||
manager.setKsefToken(Params.TOKEN);
|
void setup() throws Exception {
|
||||||
manager.setKsefNip(Params.NIP);
|
manager = new KSeFAuthManager();
|
||||||
manager.setKsefEndpoint("https://ksef-test.mf.gov.pl/api/v2");
|
|
||||||
manager.setDebug(true);
|
|
||||||
|
|
||||||
// init() ausführen
|
// Test config
|
||||||
manager.init();
|
manager.setKsefToken(Params.TOKEN);
|
||||||
}
|
manager.setKsefNip(Params.NIP);
|
||||||
|
manager.setKsefEndpoint("https://ksef-test.mf.gov.pl/api/v2");
|
||||||
|
manager.setDebug(true);
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// init() ausführen
|
||||||
// 1) Challenge abholen
|
manager.init();
|
||||||
// ------------------------------------------------------------
|
}
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
@DisplayName("POST /api/v2/auth/token/redeem")
|
|
||||||
void testRedeemToken() throws Exception {
|
|
||||||
|
|
||||||
manager.loadPublicKeyCertificates();
|
// ------------------------------------------------------------
|
||||||
manager.authChallenge();
|
// 1) Challenge abholen
|
||||||
manager.authKSeFToken();
|
// ------------------------------------------------------------
|
||||||
manager.redeemToken();
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
@DisplayName("POST /api/v2/auth/token/redeem")
|
||||||
|
void testRedeemToken() throws Exception {
|
||||||
|
|
||||||
assertNotNull(
|
manager.loadPublicKeyCertificates();
|
||||||
manager.getRefNumber(),
|
manager.authChallenge();
|
||||||
"RefNumber darf nicht NULL sein");
|
manager.authKSeFToken();
|
||||||
|
manager.redeemToken();
|
||||||
|
|
||||||
assertNotNull(
|
assertNotNull(
|
||||||
manager.getAuthToken(),
|
manager.getRefNumber(),
|
||||||
"AuthToken darf nicht leer sein");
|
"RefNumber darf nicht NULL sein");
|
||||||
|
|
||||||
}
|
assertNotNull(
|
||||||
|
manager.getAuthToken(),
|
||||||
|
"AuthToken darf nicht leer sein");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(4)
|
||||||
|
@DisplayName("Upload Invoice XML (AES-256 + RSA)")
|
||||||
|
public void testUploadInvoice() throws Exception {
|
||||||
|
|
||||||
|
// ---------- 1) Vorbereitung ----------
|
||||||
|
// API initialisieren
|
||||||
|
manager.loadPublicKeyCertificates();
|
||||||
|
manager.authChallenge();
|
||||||
|
manager.authKSeFToken();
|
||||||
|
manager.redeemToken();
|
||||||
|
|
||||||
|
manager.openInteractiveSession();
|
||||||
|
|
||||||
|
assertNotNull(manager.getAccessToken(), "AccessToken fehlt");
|
||||||
|
// assertNotNull(manager.getRefNumber(), "ReferenceNumber fehlt");
|
||||||
|
assertNotNull(manager.getSessionRefNumber(), "SessionReferenceNumber fehlt");
|
||||||
|
|
||||||
|
// ---------- 2) XML laden ----------
|
||||||
|
Path xmlPath = Paths.get("src/test/resources/ksef/example-invoice-01.xml");
|
||||||
|
byte[] invoiceXml = Files.readAllBytes(xmlPath);
|
||||||
|
|
||||||
|
// ---------- 3) AES Key generieren ----------
|
||||||
|
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 = 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 = manager.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 = manager.getBaseURI() + "/sessions/online/" + manager.getSessionRefNumber() + "/invoices";
|
||||||
|
logger.info("│ ├── Endpoint: " + uri);
|
||||||
|
logger.info("Request: " + jsonBody);
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(uri))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Authorization", "Bearer " + manager.getAccessToken())
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// ---------- 9) Call ausführen ----------
|
||||||
|
var response = manager.getHttpClient().send(request,
|
||||||
|
java.net.http.HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
System.out.println("Upload Response:");
|
||||||
|
System.out.println(response.statusCode());
|
||||||
|
System.out.println(response.body());
|
||||||
|
|
||||||
|
// ---------- 10) Basic Assertions ----------
|
||||||
|
assertTrue(response.statusCode() == 202,
|
||||||
|
"Upload muss erfolgreich sein");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue