/* * Copyright (C) 2026 KofTwentyTwo */ package com.kof22.agentadmin.bootstrap; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.time.Instant; import java.util.Arrays; import java.util.Base64; import java.util.concurrent.TimeUnit; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsServer; /******************************************************************************* * Real HTTPS issuer for native-host tests; trusts only its temporary synthetic certificate. ******************************************************************************/ final class NativeVisualIdentity implements AutoCloseable { private final HttpsServer server; private final KeyPair key; private final SSLSocketFactory previous; final String issuer; NativeVisualIdentity(Path temporaryDirectory) throws Exception { Path storePath = temporaryDirectory.resolve("synthetic-issuer.p12"); Path log = temporaryDirectory.resolve("keytool.log"); var keytool = new ProcessBuilder( Path.of(System.getProperty("java.home"), "bin", "keytool").toString(), "-genkeypair", "-alias", "issuer", "-keyalg", "RSA", "-keysize", "2048", "-storetype", "PKCS12", "-keystore", storePath.toString(), "-storepass", "synthetic-only", "-dname", "CN=localhost", "-ext", "SAN=dns:localhost,ip:127.0.0.1", "-validity", "2", "-noprompt") .redirectErrorStream(true) .redirectOutput(log.toFile()) .start(); if(!keytool.waitFor(15, TimeUnit.SECONDS)) { keytool.destroyForcibly(); throw new IllegalStateException("Synthetic issuer certificate generation timed out"); } if(keytool.exitValue() != 0) { throw new IllegalStateException("Synthetic issuer certificate generation failed"); } var store = KeyStore.getInstance("PKCS12"); try(var input = Files.newInputStream(storePath)) { store.load(input, "synthetic-only".toCharArray()); } var managers = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); managers.init(store, "synthetic-only".toCharArray()); var certificates = KeyStore.getInstance(KeyStore.getDefaultType()); try(var input = Files.newInputStream(Path.of(System.getProperty("java.home"), "lib", "security", "cacerts"))) { certificates.load(input, "changeit".toCharArray()); } certificates.setCertificateEntry("carl-synthetic-issuer", store.getCertificate("issuer")); Path trustPath = temporaryDirectory.resolve("synthetic-trust.p12"); Files.createFile(trustPath, java.nio.file.attribute.PosixFilePermissions.asFileAttribute( java.nio.file.attribute.PosixFilePermissions.fromString("rw-------"))); try(var output = Files.newOutputStream(trustPath)) { certificates.store(output, "synthetic-only".toCharArray()); } var trust = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trust.init(certificates); var tls = SSLContext.getInstance("TLS"); tls.init(managers.getKeyManagers(), trust.getTrustManagers(), null); var generator = KeyPairGenerator.getInstance("RSA"); generator.initialize(2048); key = generator.generateKeyPair(); server = HttpsServer.create(new InetSocketAddress("127.0.0.1", 0), 0); server.setHttpsConfigurator(new HttpsConfigurator(tls)); issuer = "https://localhost:" + server.getAddress().getPort() + "/"; var publicKey = (RSAPublicKey) key.getPublic(); byte[] jwks = ("{\"keys\":[{\"kty\":\"RSA\",\"kid\":\"native-synthetic\",\"alg\":\"RS256\"," + "\"use\":\"sig\",\"n\":\"" + unsigned(publicKey.getModulus().toByteArray()) + "\",\"e\":\"" + unsigned(publicKey.getPublicExponent().toByteArray()) + "\"}]}") .getBytes(StandardCharsets.UTF_8); server.createContext( "/.well-known/jwks.json", exchange -> { exchange.getResponseHeaders().set("Content-Type", "application/json"); exchange.sendResponseHeaders(200, jwks.length); try(var output = exchange.getResponseBody()) { output.write(jwks); } }); previous = HttpsURLConnection.getDefaultSSLSocketFactory(); HttpsURLConnection.setDefaultSSLSocketFactory(tls.getSocketFactory()); addBrowserEndpoints(); server.start(); } String token(String subject) { return token(subject, Instant.now().plusSeconds(300), "native-admin"); } String token(String subject, Instant expiry, String audience) { return JWT.create() .withKeyId("native-synthetic") .withIssuer(issuer) .withAudience(audience) .withSubject(subject) .withExpiresAt(expiry) .sign(Algorithm.RSA256((RSAPublicKey) key.getPublic(), (RSAPrivateKey) key.getPrivate())); } private final java.util.Map> codes = new java.util.concurrent.ConcurrentHashMap<>(); private static java.util.Map parameters(String text) { var values = new java.util.HashMap(); if(text != null) { for(String part : text.split("&")) { var pair = part.split("=", 2); values.put(java.net.URLDecoder.decode(pair[0], StandardCharsets.UTF_8), pair.length == 2 ? java.net.URLDecoder.decode(pair[1], StandardCharsets.UTF_8) : ""); } } return values; } private static void reply(com.sun.net.httpserver.HttpExchange exchange, int code, String type, String body) throws java.io.IOException { byte[] bytes = body.getBytes(StandardCharsets.UTF_8); exchange.getResponseHeaders().set("Content-Type", type); exchange.getResponseHeaders().set("Cache-Control", "no-store"); exchange.sendResponseHeaders(code, bytes.length); try(var out = exchange.getResponseBody()) { out.write(bytes); } } private void addBrowserEndpoints() { var json = new com.fasterxml.jackson.databind.ObjectMapper(); server.createContext("/authorize", exchange -> { var p = parameters(exchange.getRequestURI().getRawQuery()); if(!"POST".equals(exchange.getRequestMethod()) && !"none".equals(p.get("prompt"))) { reply(exchange, 200, "text/html; charset=UTF-8", "

Sign in to Carl AI

This preview uses fictional household data. Choose a synthetic family member.

"); return; } String subject = "alice"; String cookie = exchange.getRequestHeaders().getFirst("Cookie"); if(cookie != null && java.util.Arrays.stream(cookie.split(";")).anyMatch(value -> value.trim().equals("syntheticOperator=bob"))) { subject = "bob"; } if("POST".equals(exchange.getRequestMethod())) { subject = parameters(new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8)).getOrDefault("subject", "alice"); if(!java.util.Set.of("alice", "bob").contains(subject)) { reply(exchange, 400, "text/plain", "Declared synthetic actor required"); return; } exchange.getResponseHeaders().add("Set-Cookie", "syntheticOperator=" + subject + "; Path=/; HttpOnly; Secure; SameSite=Lax"); } p.put("syntheticSubject", subject); var code = java.util.UUID.randomUUID().toString(); codes.put(code, p); var result = java.util.Map.of("code", code, "state", p.getOrDefault("state", "")); if("web_message".equals(p.get("response_mode"))) { var redirect = java.net.URI.create(p.get("redirect_uri")); var origin = redirect.getScheme() + "://" + redirect.getRawAuthority(); reply(exchange, 200, "text/html; charset=UTF-8", ""); } else { exchange.getResponseHeaders().set("Location", p.get("redirect_uri") + "?code=" + code + "&state=" + java.net.URLEncoder.encode(p.getOrDefault("state", ""), StandardCharsets.UTF_8)); exchange.sendResponseHeaders(302, -1); exchange.close(); } }); server.createContext("/oauth/token", exchange -> { String origin = exchange.getRequestHeaders().getFirst("Origin"); if(origin != null && (origin.startsWith("https://127.0.0.1:") || origin.startsWith("https://localhost:"))) { exchange.getResponseHeaders().set("Access-Control-Allow-Origin", origin); exchange.getResponseHeaders().set("Access-Control-Allow-Headers", "content-type,auth0-client"); } if("OPTIONS".equals(exchange.getRequestMethod())) { exchange.sendResponseHeaders(204, -1); exchange.close(); return; } String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); java.util.Map p; if(body.startsWith("{")) { p = json.readValue(body, new com.fasterxml.jackson.core.type.TypeReference>() { }); } else { p = parameters(body); } var auth = codes.remove(p.get("code")); if(auth == null) { reply(exchange, 400, "application/json", "{\"error\":\"invalid_grant\"}"); return; } try { String challenge = java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(java.security.MessageDigest.getInstance("SHA-256").digest(p.getOrDefault("code_verifier", "").getBytes(StandardCharsets.UTF_8))); if(!challenge.equals(auth.get("code_challenge"))) { reply(exchange, 400, "application/json", "{\"error\":\"invalid_grant\"}"); return; } } catch(java.security.NoSuchAlgorithmException e) { throw new IllegalStateException(e); } String subject = auth.get("syntheticSubject"); String label = subject.equals("bob") ? "Bob (synthetic)" : "Alice (synthetic)"; var id = JWT.create().withKeyId("native-synthetic").withIssuer(issuer).withAudience("synthetic-client") .withSubject(subject).withIssuedAt(Instant.now()).withExpiresAt(Instant.now().plusSeconds(1800)) .withClaim("nonce", auth.get("nonce")).withClaim("name", label).withClaim("email", subject + "@example.invalid") .sign(Algorithm.RSA256((RSAPublicKey) key.getPublic(), (RSAPrivateKey) key.getPrivate())); reply(exchange, 200, "application/json", json.writeValueAsString(java.util.Map.of("access_token", token(subject, Instant.now().plusSeconds(1800), "native-admin"), "id_token", id, "token_type", "Bearer", "expires_in", 1800, "scope", auth.getOrDefault("scope", "openid profile email")))); }); server.createContext("/v2/logout", exchange -> { var p = parameters(exchange.getRequestURI().getRawQuery()); exchange.getResponseHeaders().add("Set-Cookie", "syntheticOperator=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax"); exchange.getResponseHeaders().set("Location", p.getOrDefault("returnTo", "/")); exchange.sendResponseHeaders(302, -1); exchange.close(); }); server.createContext("/userinfo", exchange -> { String authorization = exchange.getRequestHeaders().getFirst("Authorization"); try { if(authorization == null || !authorization.startsWith("Bearer ")) { throw new IllegalArgumentException("Synthetic bearer required"); } String subject = JWT.require(Algorithm.RSA256((RSAPublicKey) key.getPublic(), (RSAPrivateKey) key.getPrivate())).withIssuer(issuer).withAudience("native-admin").build().verify(authorization.substring(7)).getSubject(); if(!java.util.Set.of("alice", "bob").contains(subject)) { throw new IllegalArgumentException("Declared synthetic actor required"); } reply(exchange, 200, "application/json", json.writeValueAsString(java.util.Map.of("sub", subject, "name", subject.equals("bob") ? "Bob (synthetic)" : "Alice (synthetic)"))); } catch(RuntimeException invalid) { reply(exchange, 401, "application/json", "{\"error\":\"invalid_token\"}"); } }); } private static String unsigned(byte[] bytes) { return Base64.getUrlEncoder() .withoutPadding() .encodeToString(Arrays.copyOfRange(bytes, bytes[0] == 0 ? 1 : 0, bytes.length)); } @Override public void close() { server.stop(0); HttpsURLConnection.setDefaultSSLSocketFactory(previous); } }