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.nio.charset.StandardCharsets;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
|
@ -74,11 +75,17 @@ public class KSeFAuthManager {
|
|||
private String challenge = "";
|
||||
private String refNumber = null;
|
||||
private String authToken = null;
|
||||
private String accessToken = null;
|
||||
private String challengeTimestamp = null;
|
||||
private String symmetricCertBase64;
|
||||
private String ksefTokenCertBase64;
|
||||
private PublicKey symmetricPublicKey;
|
||||
private PublicKey ksefTokenPublicKey;
|
||||
private String sessionRefNumber;
|
||||
private String sessionValidUntil;
|
||||
|
||||
private byte[] sessionAesKey;
|
||||
private byte[] sessionIv;
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
|
|
@ -114,6 +121,10 @@ public class KSeFAuthManager {
|
|||
this.ksefToken = Optional.ofNullable(token);
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setKsefNip(String nip) {
|
||||
this.ksefNip = Optional.ofNullable(nip);
|
||||
}
|
||||
|
|
@ -122,6 +133,10 @@ public class KSeFAuthManager {
|
|||
this.ksefEndpoint = Optional.ofNullable(endpoint);
|
||||
}
|
||||
|
||||
public String getSessionRefNumber() {
|
||||
return sessionRefNumber;
|
||||
}
|
||||
|
||||
public void setDebug(boolean debug) {
|
||||
this.debug = debug;
|
||||
}
|
||||
|
|
@ -150,6 +165,10 @@ public class KSeFAuthManager {
|
|||
return authToken;
|
||||
}
|
||||
|
||||
public PublicKey getSymmetricPublicKey() {
|
||||
return symmetricPublicKey;
|
||||
}
|
||||
|
||||
public String getChallengeValue() {
|
||||
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
|
||||
*
|
||||
|
|
@ -369,7 +483,7 @@ public class KSeFAuthManager {
|
|||
|
||||
// Access Token extrahieren und speichern
|
||||
JsonObject accessTokenObj = jsonObject.getJsonObject("accessToken");
|
||||
this.authToken = accessTokenObj.getString("token");
|
||||
this.accessToken = accessTokenObj.getString("token");
|
||||
|
||||
logger.info("│ └── Access Token received");
|
||||
|
||||
|
|
@ -459,22 +573,6 @@ public class KSeFAuthManager {
|
|||
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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
package com.alexanderlogistics.ksef;
|
||||
|
||||
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.DisplayName;
|
||||
|
|
@ -15,43 +30,147 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class KSeFAuthManagerTest {
|
||||
|
||||
private KSeFAuthManager manager;
|
||||
private static Logger logger = Logger.getLogger(KSeFAuthManagerTest.class.getName());
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
manager = new KSeFAuthManager();
|
||||
private KSeFAuthManager manager;
|
||||
|
||||
// Test config
|
||||
manager.setKsefToken(Params.TOKEN);
|
||||
manager.setKsefNip(Params.NIP);
|
||||
manager.setKsefEndpoint("https://ksef-test.mf.gov.pl/api/v2");
|
||||
manager.setDebug(true);
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
manager = new KSeFAuthManager();
|
||||
|
||||
// init() ausführen
|
||||
manager.init();
|
||||
}
|
||||
// Test config
|
||||
manager.setKsefToken(Params.TOKEN);
|
||||
manager.setKsefNip(Params.NIP);
|
||||
manager.setKsefEndpoint("https://ksef-test.mf.gov.pl/api/v2");
|
||||
manager.setDebug(true);
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// 1) Challenge abholen
|
||||
// ------------------------------------------------------------
|
||||
@Test
|
||||
@Order(1)
|
||||
@DisplayName("POST /api/v2/auth/token/redeem")
|
||||
void testRedeemToken() throws Exception {
|
||||
// init() ausführen
|
||||
manager.init();
|
||||
}
|
||||
|
||||
manager.loadPublicKeyCertificates();
|
||||
manager.authChallenge();
|
||||
manager.authKSeFToken();
|
||||
manager.redeemToken();
|
||||
// ------------------------------------------------------------
|
||||
// 1) Challenge abholen
|
||||
// ------------------------------------------------------------
|
||||
@Test
|
||||
@Order(1)
|
||||
@DisplayName("POST /api/v2/auth/token/redeem")
|
||||
void testRedeemToken() throws Exception {
|
||||
|
||||
assertNotNull(
|
||||
manager.getRefNumber(),
|
||||
"RefNumber darf nicht NULL sein");
|
||||
manager.loadPublicKeyCertificates();
|
||||
manager.authChallenge();
|
||||
manager.authKSeFToken();
|
||||
manager.redeemToken();
|
||||
|
||||
assertNotNull(
|
||||
manager.getAuthToken(),
|
||||
"AuthToken darf nicht leer sein");
|
||||
assertNotNull(
|
||||
manager.getRefNumber(),
|
||||
"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