From 5dafacf23bf2d79bd6f000cf153c0a95d6caa9cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric?= Date: Wed, 3 Dec 2025 15:36:05 +0100 Subject: [PATCH 1/3] First acs and acq --- src/main/java/acq/acq/src/Main.java | 47 +++++++++++++++++++++++-- src/main/java/acs/acs/src/Main.java | 54 +++++++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/main/java/acq/acq/src/Main.java b/src/main/java/acq/acq/src/Main.java index 626968a..cb11032 100644 --- a/src/main/java/acq/acq/src/Main.java +++ b/src/main/java/acq/acq/src/Main.java @@ -1,7 +1,50 @@ package acq.acq.src; +// File: AcqClient.java +import javax.net.ssl.*; +import java.io.*; +import java.security.KeyStore; public class Main { - public static void main(String[] args) { - + public static void main(String[] args) throws Exception { + String host = "localhost"; + int port = 8443; + + // If client needs to present cert (mTLS) + String keystorePath = "assets/certs/acq.p12"; + char[] keystorePass = "hepl".toCharArray(); + + // Truststore to trust the server's CA + String truststorePath = "assets/certs/acq-trust.jks"; + char[] truststorePass = "heplhepl".toCharArray(); + + KeyStore ks = KeyStore.getInstance("PKCS12"); + try (FileInputStream fis = new FileInputStream(keystorePath)) { + ks.load(fis, keystorePass); + } + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, keystorePass); + + KeyStore ts = KeyStore.getInstance("JKS"); + try (FileInputStream fis = new FileInputStream(truststorePath)) { + ts.load(fis, truststorePass); + } + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ts); + + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + SSLSocketFactory factory = ctx.getSocketFactory(); + try (SSLSocket socket = (SSLSocket) factory.createSocket(host, port)) { + socket.startHandshake(); + BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + out.write("Hello ACS\n"); + out.flush(); + + String resp = in.readLine(); + System.out.println("Response from ACS: " + resp); + } } } diff --git a/src/main/java/acs/acs/src/Main.java b/src/main/java/acs/acs/src/Main.java index b72340c..649bce0 100644 --- a/src/main/java/acs/acs/src/Main.java +++ b/src/main/java/acs/acs/src/Main.java @@ -1,7 +1,57 @@ package acs.acs.src; +// File: AcsServer.java +import javax.net.ssl.*; +import java.io.*; +import java.security.KeyStore; + public class Main { - public static void main(String[] args) { - + public static void main(String[] args) throws Exception { + int port = 8443; + // Keystore containing server private key + cert (PKCS12 or JKS) + String keystorePath = "assets/certs/acs.p12"; + char[] keystorePass = "hepl".toCharArray(); + + // Truststore (to verify client if mutual TLS) + String truststorePath = "assets/certs/acs-trust.jks"; + char[] truststorePass = "heplhepl".toCharArray(); + + KeyStore ks = KeyStore.getInstance("PKCS12"); + try (FileInputStream fis = new FileInputStream(keystorePath)) { + ks.load(fis, keystorePass); + } + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, keystorePass); + + KeyStore ts = KeyStore.getInstance("JKS"); + try (FileInputStream fis = new FileInputStream(truststorePath)) { + ts.load(fis, truststorePass); + } + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ts); + + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + SSLServerSocketFactory ssf = ctx.getServerSocketFactory(); + SSLServerSocket serverSocket = (SSLServerSocket) ssf.createServerSocket(port); + // si vous voulez mTLS : + serverSocket.setNeedClientAuth(true); + + System.out.println("ACS listening on port " + port); + while (true) { + try (SSLSocket socket = (SSLSocket) serverSocket.accept()) { + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); + + String line = in.readLine(); // simple single-line message + System.out.println("Received from ACQ: " + line); + + out.write("ACK from ACS\n"); + out.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } } } From b1ca58bd1ad5cf99dc213e0c3a9b793e99b92a42 Mon Sep 17 00:00:00 2001 From: Matthias Guillitte Date: Wed, 3 Dec 2025 15:51:04 +0100 Subject: [PATCH 2/3] ExternalApp : Premier jet # Conflicts: # src/main/java/externalApp/externalApp/src/Main.java --- .../java/common/common/src/ports/Ports.java | 9 +++ .../externalApp/externalApp/src/Main.java | 70 ++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/main/java/common/common/src/ports/Ports.java diff --git a/src/main/java/common/common/src/ports/Ports.java b/src/main/java/common/common/src/ports/Ports.java new file mode 100644 index 0000000..896e47f --- /dev/null +++ b/src/main/java/common/common/src/ports/Ports.java @@ -0,0 +1,9 @@ +package common.common.src.ports; + +public class Ports { + /** + * Port d'écoute du service ACS pour la communication avec l'application externe. + */ + public static int PORT_AUTH = 8786; + public static String ACS_HOST = "127.0.0.1"; +} diff --git a/src/main/java/externalApp/externalApp/src/Main.java b/src/main/java/externalApp/externalApp/src/Main.java index 749d097..aa907b7 100644 --- a/src/main/java/externalApp/externalApp/src/Main.java +++ b/src/main/java/externalApp/externalApp/src/Main.java @@ -1,9 +1,75 @@ package externalApp.externalApp.src; +import common.common.src.ports.Ports; import common.common.src.logger.Logger; +import org.gradle.internal.impldep.com.fasterxml.jackson.databind.ObjectMapper; +import org.gradle.internal.impldep.com.fasterxml.jackson.databind.node.ObjectNode; + +import javax.net.ssl.HttpsURLConnection; +import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.cert.CertificateException; +import java.util.Base64; public class Main { - public static void main(String[] args) { - Logger.displayInfo("Hello World"); + public static void main(String[] args) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, URISyntaxException { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + System.out.println("Quel est la date d'expiration de la carte de crédit ? (MM/AA)"); + String expirationDate = br.readLine(); + Logger.displayInfo("Date d'expiration saisie : " + expirationDate); + System.out.println("Numéro de la carte de crédit : "); + String cardNumber = br.readLine(); + Logger.displayInfo("Numéro de carte saisi : " + cardNumber); + + // Construction du JSON + ObjectMapper mapper = new ObjectMapper(); + ObjectNode json = mapper.createObjectNode(); + json.put("expirationDate", expirationDate); + json.put("cardNumber", cardNumber); + String jsonString = mapper.writeValueAsString(json); + + // Signer le JSON + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + char[] password = /* TODO */.toCharArray(); + try (FileInputStream pkFile = new FileInputStream("ma_cle.p12")) { + keyStore.load(pkFile, password); + } catch (CertificateException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + PrivateKey privateKey = (PrivateKey) keyStore.getKey(/* TODO */, password); + Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initSign(privateKey); + signature.update(jsonString.getBytes(StandardCharsets.UTF_8)); + byte[] signedBytes = signature.sign(); + + String signatureBase64 = Base64.getEncoder().encodeToString(signedBytes); + + // Construction du JSON final avec la signature + ObjectNode finalJson = mapper.createObjectNode(); + finalJson.put("data", jsonString); + finalJson.put("signature", signatureBase64); + + String finalPayload = mapper.writeValueAsString(finalJson); + + // Envoi à l'ACS + Logger.displayInfo("Envoi des informations au service d'authentification sur le port " + Ports.PORT_AUTH + " à l'hôte " + Ports.ACS_HOST); + URL url = new URI("https://" + Ports.ACS_HOST + ":" + Ports.PORT_AUTH).toURL(); + HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); + + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/json"); + con.setDoOutput(true); + + try (OutputStream os = con.getOutputStream()) { + os.write(finalPayload.getBytes(StandardCharsets.UTF_8)); + Logger.displaySent("Payload final envoyé : " + finalPayload); + } + + int responseCode = con.getResponseCode(); + Logger.displayReceived("Code de réponse reçu : " + responseCode); } } From 6a605643449bb77a3c26f427be29cd9e1b19470b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric?= Date: Wed, 3 Dec 2025 15:52:44 +0100 Subject: [PATCH 3/3] externalApp csr, key, store ... --- assets/certs/ca.cert.srl | 2 +- assets/certs/externalApp.cert.pem | 21 +++++++++++++++++++++ assets/certs/externalApp.csr.pem | 16 ++++++++++++++++ assets/certs/externalApp.jks | Bin 0 -> 3142 bytes assets/certs/externalApp.key.pem | 28 ++++++++++++++++++++++++++++ assets/certs/externalApp.p12 | Bin 0 -> 3666 bytes 6 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 assets/certs/externalApp.cert.pem create mode 100644 assets/certs/externalApp.csr.pem create mode 100644 assets/certs/externalApp.jks create mode 100644 assets/certs/externalApp.key.pem create mode 100644 assets/certs/externalApp.p12 diff --git a/assets/certs/ca.cert.srl b/assets/certs/ca.cert.srl index 9384156..58eff74 100644 --- a/assets/certs/ca.cert.srl +++ b/assets/certs/ca.cert.srl @@ -1 +1 @@ -40DC6F16EBBDE5724552A319CD26F7C16B02ACEE +40DC6F16EBBDE5724552A319CD26F7C16B02ACEF diff --git a/assets/certs/externalApp.cert.pem b/assets/certs/externalApp.cert.pem new file mode 100644 index 0000000..f7af7ae --- /dev/null +++ b/assets/certs/externalApp.cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZTCCAk2gAwIBAgIUQNxvFuu95XJFUqMZzSb3wWsCrO8wDQYJKoZIhvcNAQEL +BQAwUDELMAkGA1UEBhMCQkUxDjAMBgNVBAgMBUxpZWdlMQ4wDAYDVQQHDAVMaWVn +ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTI1MTIwMzE0 +NDI1MFoXDTI2MTIwMzE0NDI1MFowRTELMAkGA1UEBhMCQmUxEzARBgNVBAgMClNv +bWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJBRa7imhWweQo0qV1TImqcCX2Rw +IhYIeoVtPKxtwkjgcb6Wj9iHtj3wbfGzGoMH92H4TXHVjgIukSJyUH5RjaMu1f0Z +d9EepZCGLr443NRJwwjf5HSCauiCk7dODe3AzDWiolNoPNDaud6AB+dGKn0Shkzv +pG2MiwAZrUDZijsTYuscRmh9fHMvgVYkSex56xTcXdCQuF0bfvZ1zBwfKwsY/j3Z +6dJRe+5aqCk2clbNjoIqkHNjVOZJKW+Ymi+mN+SjKkDIcW08b6tD3RhCFkuTR6/Q +Oymq+Gy/2hLUIFZZALU4J4s3V6oIUnBi3GMR2hN7Lnd04FDxUR4A7Z00i0UCAwEA +AaNCMEAwHQYDVR0OBBYEFP3mtVOR6kok58eA0hVNKRyf9NCwMB8GA1UdIwQYMBaA +FCvWbKgANSI8XWEaWtWAKUtiMNprMA0GCSqGSIb3DQEBCwUAA4IBAQBMAkUL2pQP +s4i+jtLoHenpz2smTs47jr5plxC7WcMnSwNcu7juu3GvogCB3ee8HCmBPFZ4k2a8 +wZ4gJVOsW1Lstji1dgpJ8bcM2ToXAOnU15+3ym2G1hBoWpp30OjUIDteYF/zqXAP +YloYExUkwVZmxW9IZJR+CXU2wvtc6mR3U6KnyR5Pd/ksTZv11bsxtOw9G4afdGrl +x74wtWsRBebvRDZotlObGRTFXrfhvdaY1+ZT4sepR5bCrkg0PjJvEz74PJWotcPb +ezcuReeo+xfaVuSl/yuCcQCOfOWhVLnLrqUvl08oRNnoQ098E83yXZFKEvSWsRyW +8Opes4zwY9RZ +-----END CERTIFICATE----- diff --git a/assets/certs/externalApp.csr.pem b/assets/certs/externalApp.csr.pem new file mode 100644 index 0000000..0be1b9d --- /dev/null +++ b/assets/certs/externalApp.csr.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICijCCAXICAQAwRTELMAkGA1UEBhMCQmUxEzARBgNVBAgMClNvbWUtU3RhdGUx +ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAJBRa7imhWweQo0qV1TImqcCX2RwIhYIeoVtPKxt +wkjgcb6Wj9iHtj3wbfGzGoMH92H4TXHVjgIukSJyUH5RjaMu1f0Zd9EepZCGLr44 +3NRJwwjf5HSCauiCk7dODe3AzDWiolNoPNDaud6AB+dGKn0ShkzvpG2MiwAZrUDZ +ijsTYuscRmh9fHMvgVYkSex56xTcXdCQuF0bfvZ1zBwfKwsY/j3Z6dJRe+5aqCk2 +clbNjoIqkHNjVOZJKW+Ymi+mN+SjKkDIcW08b6tD3RhCFkuTR6/QOymq+Gy/2hLU +IFZZALU4J4s3V6oIUnBi3GMR2hN7Lnd04FDxUR4A7Z00i0UCAwEAAaAAMA0GCSqG +SIb3DQEBCwUAA4IBAQCBQmHUuJakHI0190O5mZgNsSe/Ml+YJfd1NeQksm/1gOwu +Id+MUdWuVpwAY3W6j8iYZN7IFPmEtlpH77z/CVoAGztPUpp8e3rYGclJOdVNVTuD +jyieLxXUwFmHBh/fAmcoLpt2/V4ltNaT17h3cCkv4A0PwCV9IURv6S5zzO0UnWmB +PHfzGpfN+JtwrDn3MJ4g4v3CjXu9lhL3Hn9fDhEh3pCEHlWZyDMFVg/7ubyVqzAN +FTOfh9laU+8fBy8UqQkznRarfkx7Ce8REnbabKf3sdHxNkO5SzpQJnFBuLT0IiGH +R1M5nA1hwMvJkWVBGVorHDx6vaTAjkcjd7JGaMS2 +-----END CERTIFICATE REQUEST----- diff --git a/assets/certs/externalApp.jks b/assets/certs/externalApp.jks new file mode 100644 index 0000000000000000000000000000000000000000..6bc6d1fcb8df6b69d3db31f93ec39830313a3dfa GIT binary patch literal 3142 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