From 928e6d23cbec5f1aabe335a8492cd9d4b2542422 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 2 Aug 2024 10:01:27 +0200 Subject: [PATCH] working on key management Signed-off-by: Stephan Richter --- de.srsoftware.oidc.api/build.gradle | 1 + .../de/srsoftware/oidc/api/Constants.java | 2 + .../de/srsoftware/oidc/api/KeyManager.java | 15 ++++ .../de/srsoftware/oidc/api/KeyStorage.java | 13 ++++ .../de/srsoftware/oidc/api/PathHandler.java | 8 +-- .../de/srsoftware/oidc/app/Application.java | 31 +++++---- .../oidc/backend/ClientController.java | 11 +-- .../oidc/backend/KeyStoreController.java | 20 ++++++ .../oidc/backend/RotatingKeyManager.java | 39 +++++++++++ .../oidc/backend/TokenController.java | 68 +++++++++++-------- .../oidc/backend/UserController.java | 4 +- .../oidc/backend/WellKnownController.java | 7 +- .../build.gradle | 2 + .../datastore/file/PlaintextKeyStore.java | 58 ++++++++++++++++ .../de/srsoftware/oidc/web/StaticPages.java | 3 +- .../src/main/resources/de/clients.html | 2 +- .../src/main/resources/en/authorization.js | 1 + .../src/main/resources/en/clients.html | 2 +- 18 files changed, 227 insertions(+), 60 deletions(-) create mode 100644 de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/KeyManager.java create mode 100644 de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/KeyStorage.java create mode 100644 de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/KeyStoreController.java create mode 100644 de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/RotatingKeyManager.java create mode 100644 de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/PlaintextKeyStore.java diff --git a/de.srsoftware.oidc.api/build.gradle b/de.srsoftware.oidc.api/build.gradle index ffc0a3c..83f182c 100644 --- a/de.srsoftware.oidc.api/build.gradle +++ b/de.srsoftware.oidc.api/build.gradle @@ -12,6 +12,7 @@ dependencies { testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation 'org.junit.jupiter:junit-jupiter' implementation 'org.json:json:20240303' + implementation 'org.bitbucket.b_c:jose4j:0.9.6' } test { diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java index 6c20c55..8d7259e 100644 --- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java @@ -17,8 +17,10 @@ public class Constants { public static final String GRANT_TYPE = "grant_type"; public static final String ID_TOKEN = "id_token"; public static final String NAME = "name"; + public static final String OPENID = "openid"; public static final String REDIRECT_URI = "redirect_uri"; public static final String REDIRECT_URIS = "redirect_uris"; + public static final String SCOPE = "scope"; public static final String SECRET = "secret"; public static final String STATE = "state"; public static final String TOKEN_TYPE = "token_type"; diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/KeyManager.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/KeyManager.java new file mode 100644 index 0000000..e380854 --- /dev/null +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/KeyManager.java @@ -0,0 +1,15 @@ +/* © SRSoftware 2024 */ +package de.srsoftware.oidc.api; + +import java.io.IOException; +import org.jose4j.jwk.PublicJsonWebKey; + + +public interface KeyManager { + public class KeyCreationException extends Exception { + public KeyCreationException(Exception cause) { + super(cause); + } + } + public PublicJsonWebKey getKey() throws KeyCreationException, IOException; +} diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/KeyStorage.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/KeyStorage.java new file mode 100644 index 0000000..c5f79be --- /dev/null +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/KeyStorage.java @@ -0,0 +1,13 @@ +/* © SRSoftware 2024 */ +package de.srsoftware.oidc.api; + +import java.io.IOException; +import java.util.List; +import org.jose4j.jwk.PublicJsonWebKey; + +public interface KeyStorage { + public KeyStorage drop(String keyId); + public List listKeys(); + public PublicJsonWebKey load(String keyId) throws IOException, KeyManager.KeyCreationException; + public KeyStorage store(PublicJsonWebKey jsonWebKey) throws IOException; +} diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/PathHandler.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/PathHandler.java index f3ce0b1..48b216e 100644 --- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/PathHandler.java +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/PathHandler.java @@ -43,15 +43,15 @@ public abstract class PathHandler implements HttpHandler { } public boolean doDelete(String path, HttpExchange ex) throws IOException { - return false; + return notFound(ex); } public boolean doGet(String path, HttpExchange ex) throws IOException { - return false; + return notFound(ex); } public boolean doPost(String path, HttpExchange ex) throws IOException { - return false; + return notFound(ex); } @Override @@ -112,7 +112,7 @@ public abstract class PathHandler implements HttpHandler { } public static boolean notFound(HttpExchange ex) throws IOException { - LOG.log(WARNING, "not implemented"); + LOG.log(ERROR, "not implemented"); return sendEmptyResponse(HTTP_NOT_FOUND, ex); } diff --git a/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java b/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java index 29004ce..94cedb3 100644 --- a/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java +++ b/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java @@ -12,12 +12,12 @@ import static java.lang.System.getenv; import com.sun.net.httpserver.HttpServer; import de.srsoftware.logging.ColorLogger; +import de.srsoftware.oidc.api.KeyManager; +import de.srsoftware.oidc.api.KeyStorage; import de.srsoftware.oidc.api.User; -import de.srsoftware.oidc.backend.ClientController; -import de.srsoftware.oidc.backend.TokenController; -import de.srsoftware.oidc.backend.UserController; -import de.srsoftware.oidc.backend.WellKnownController; +import de.srsoftware.oidc.backend.*; import de.srsoftware.oidc.datastore.file.FileStore; +import de.srsoftware.oidc.datastore.file.PlaintextKeyStore; import de.srsoftware.oidc.datastore.file.UuidHasher; import de.srsoftware.oidc.web.Forward; import de.srsoftware.oidc.web.StaticPages; @@ -33,6 +33,7 @@ public class Application { public static final String FIRST_USER = "admin"; public static final String FIRST_USER_PASS = "admin"; public static final String FIRST_UUID = UUID.randomUUID().toString(); + public static final String JWKS = "/api/jwks"; public static final String ROOT = "/"; public static final String STATIC_PATH = "/web"; @@ -43,20 +44,24 @@ public class Application { private static System.Logger LOG = new ColorLogger("Application").setLogLevel(DEBUG); public static void main(String[] args) throws Exception { - var argMap = map(args); - Optional basePath = argMap.get(BASE_PATH) instanceof Path p ? Optional.of(p) : Optional.empty(); - var storageFile = (argMap.get(CONFIG_PATH) instanceof Path p ? p : configDir(APP_NAME).resolve("config.json")).toFile(); - var passwordHasher = new UuidHasher(); - var firstHash = passwordHasher.hash(FIRST_USER_PASS, FIRST_UUID); - var firstUser = new User(FIRST_USER, firstHash, FIRST_USER, "%s@internal".formatted(FIRST_USER), FIRST_UUID).add(MANAGE_CLIENTS); - FileStore fileStore = new FileStore(storageFile, passwordHasher).init(firstUser); - HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); + var argMap = map(args); + Optional basePath = argMap.get(BASE_PATH) instanceof Path p ? Optional.of(p) : Optional.empty(); + var storageFile = (argMap.get(CONFIG_PATH) instanceof Path p ? p : configDir(APP_NAME).resolve("config.json")).toFile(); + var keyDir = storageFile.getParentFile().toPath().resolve("keys"); + var passwordHasher = new UuidHasher(); + var firstHash = passwordHasher.hash(FIRST_USER_PASS, FIRST_UUID); + var firstUser = new User(FIRST_USER, firstHash, FIRST_USER, "%s@internal".formatted(FIRST_USER), FIRST_UUID).add(MANAGE_CLIENTS); + KeyStorage keyStore = new PlaintextKeyStore(keyDir); + KeyManager keyManager = new RotatingKeyManager(keyStore); + FileStore fileStore = new FileStore(storageFile, passwordHasher).init(firstUser); + HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); new StaticPages(basePath).bindPath(STATIC_PATH, FAVICON).on(server); new Forward(INDEX).bindPath(ROOT).on(server); new WellKnownController().bindPath(WELL_KNOWN).on(server); new UserController(fileStore, fileStore).bindPath(API_USER).on(server); - new TokenController(fileStore, fileStore, fileStore).bindPath(API_TOKEN).on(server); + new TokenController(fileStore, fileStore, keyManager, fileStore).bindPath(API_TOKEN).on(server); new ClientController(fileStore, fileStore, fileStore).bindPath(API_CLIENT).on(server); + new KeyStoreController(keyStore).bindPath(JWKS).on(server); // server.setExecutor(Executors.newCachedThreadPool()); server.setExecutor(Executors.newSingleThreadExecutor()); server.start(); diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java index 1785475..69536d2 100644 --- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java +++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java @@ -11,6 +11,7 @@ import de.srsoftware.oidc.api.*; import java.io.IOException; import java.time.Duration; import java.time.Instant; +import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.UUID; @@ -29,8 +30,11 @@ public class ClientController extends Controller { private boolean authorize(HttpExchange ex, Session session) throws IOException { - var user = session.user(); - var json = json(ex); + var user = session.user(); + var json = json(ex); + var scope = json.getString(SCOPE); + if (!Arrays.asList(scope.split(" ")).contains(OPENID)) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "openid scope missing in request")); + var clientId = json.getString(CLIENT_ID); var redirect = json.getString(REDIRECT_URI); var optClient = clients.getClient(clientId); @@ -74,8 +78,7 @@ public class ClientController extends Controller { case "/": return deleteClient(ex, session); } - LOG.log(ERROR, "not implemented"); - return sendEmptyResponse(HTTP_NOT_FOUND, ex); + return notFound(ex); } diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/KeyStoreController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/KeyStoreController.java new file mode 100644 index 0000000..b0f0ccb --- /dev/null +++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/KeyStoreController.java @@ -0,0 +1,20 @@ +/* © SRSoftware 2024 */ +package de.srsoftware.oidc.backend; + +import com.sun.net.httpserver.HttpExchange; +import de.srsoftware.oidc.api.KeyStorage; +import de.srsoftware.oidc.api.PathHandler; +import java.io.IOException; + +public class KeyStoreController extends PathHandler { + private final KeyStorage keyStore; + + public KeyStoreController(KeyStorage keyStorage) { + keyStore = keyStorage; + } + + @Override + public boolean doGet(String path, HttpExchange ex) throws IOException { + return super.doGet(path, ex); + } +} diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/RotatingKeyManager.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/RotatingKeyManager.java new file mode 100644 index 0000000..b5ff0da --- /dev/null +++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/RotatingKeyManager.java @@ -0,0 +1,39 @@ +/* © SRSoftware 2024 */ +package de.srsoftware.oidc.backend; + +import static org.jose4j.jws.AlgorithmIdentifiers.RSA_USING_SHA256; + +import de.srsoftware.oidc.api.KeyManager; +import de.srsoftware.oidc.api.KeyStorage; +import java.io.IOException; +import java.util.UUID; +import org.jose4j.jwk.PublicJsonWebKey; +import org.jose4j.jwk.RsaJwkGenerator; +import org.jose4j.lang.JoseException; + +public class RotatingKeyManager implements KeyManager { + private static final System.Logger LOG = System.getLogger(RotatingKeyManager.class.getSimpleName()); + private final KeyStorage store; + + public RotatingKeyManager(KeyStorage keyStore) { + store = keyStore; + } + + @Override + public PublicJsonWebKey getKey() throws KeyCreationException, IOException { + var list = store.listKeys(); + return list.isEmpty() ? createNewKey() : store.load(list.get(0)); + } + + private PublicJsonWebKey createNewKey() throws KeyCreationException, IOException { + try { + var key = RsaJwkGenerator.generateJwk(2048); + key.setAlgorithm(RSA_USING_SHA256); + key.setKeyId(UUID.randomUUID().toString()); + store.store(key); + return key; + } catch (JoseException e) { + throw new KeyCreationException(e); + } + } +} diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java index e5549f8..0e58b48 100644 --- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java +++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java @@ -12,10 +12,10 @@ import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; -import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jwk.PublicJsonWebKey; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; -import org.jose4j.keys.HmacKey; +import org.jose4j.jwt.MalformedClaimException; import org.jose4j.lang.JoseException; import org.json.JSONObject; @@ -23,11 +23,13 @@ public class TokenController extends PathHandler { private final ClientService clients; private final AuthorizationService authorizations; private final UserService users; + private final KeyManager keyManager; - public TokenController(AuthorizationService authorizationService, ClientService clientService, UserService userService) { - authorizations = authorizationService; - clients = clientService; - users = userService; + public TokenController(AuthorizationService authorizationService, ClientService clientService, KeyManager keyManager, UserService userService) { + authorizations = authorizationService; + clients = clientService; + this.keyManager = keyManager; + users = userService; } private Map deserialize(String body) { @@ -45,7 +47,9 @@ public class TokenController extends PathHandler { } private boolean provideToken(HttpExchange ex) throws IOException { - var map = deserialize(body(ex)); + var map = deserialize(body(ex)); + // TODO: check data, → https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint + var grantType = map.get(GRANT_TYPE); if (!AUTH_CODE.equals(grantType)) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "unknown grant type", GRANT_TYPE, grantType)); @@ -66,8 +70,8 @@ public class TokenController extends PathHandler { var uri = URLDecoder.decode(map.get(REDIRECT_URI), StandardCharsets.UTF_8); if (!client.redirectUris().contains(uri)) sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "unknown redirect uri", REDIRECT_URI, uri)); - var secretFromClient = map.get(CLIENT_SECRET); - if (!client.secret().equals(secretFromClient)) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "client secret mismatch")); + var secretFromClient = URLDecoder.decode(map.get(CLIENT_SECRET)); + if (secretFromClient != null && !client.secret().equals(secretFromClient)) return sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "client secret mismatch")); String jwToken = createJWT(client, user.get()); ex.getResponseHeaders().add("Cache-Control", "no-store"); @@ -76,42 +80,52 @@ public class TokenController extends PathHandler { response.put(TOKEN_TYPE, BEARER); response.put(EXPIRES_IN, 3600); response.put(ID_TOKEN, jwToken); + LOG.log(DEBUG, jwToken); return sendContent(ex, response); } private String createJWT(Client client, User user) { try { - byte[] secretBytes = client.secret().getBytes(StandardCharsets.UTF_8); - HmacKey hmacKey = new HmacKey(secretBytes); + PublicJsonWebKey key = keyManager.getKey(); - JwtClaims claims = getJwtClaims(user); + JwtClaims claims = getJwtClaims(user, client); // A JWT is a JWS and/or a JWE with JSON claims as the payload. // In this example it is a JWS so we create a JsonWebSignature object. JsonWebSignature jws = new JsonWebSignature(); - if (secretBytes.length * 8 < 256) { - LOG.log(WARNING, "Using secret with less than 256 bits! You will go to hell for this!"); - jws.setDoKeyValidation(false); // TODO: this is dangerous! Better: enforce key length of 256bits! - } + jws.setHeader("typ", "JWT"); jws.setPayload(claims.toJson()); - jws.setKey(hmacKey); - jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256); + jws.setKey(key.getPrivateKey()); + jws.setKeyIdHeaderValue(key.getKeyId()); + jws.setAlgorithmHeaderValue(key.getAlgorithm()); return jws.getCompactSerialization(); - } catch (JoseException e) { + } catch (JoseException | KeyManager.KeyCreationException | IOException e) { throw new RuntimeException(e); } } - private static JwtClaims getJwtClaims(User user) { + private static JwtClaims getJwtClaims(User user, Client client) { JwtClaims claims = new JwtClaims(); - claims.setIssuer(APP_NAME); // who creates the token and signs it - claims.setExpirationTimeMinutesInTheFuture(10); // time when the token will expire (10 minutes from now) - claims.setGeneratedJwtId(); // a unique identifier for the token - claims.setIssuedAtToNow(); // when the token was issued/created (now) - claims.setNotBeforeMinutesInThePast(2); // time before which the token is not yet valid (2 minutes ago) - claims.setSubject(user.uuid()); // the subject/principal is whom the token is about - claims.setClaim("email", user.email()); // additional claims/attributes about the subject can be added + claims.setAudience(client.id(), "test"); + claims.setClaim("client_id", client.id()); + claims.setClaim("email", user.email()); // additional claims/attributes about the subject can be added + claims.setExpirationTimeMinutesInTheFuture(10); // time when the token will expire (10 minutes from now) + claims.setIssuedAtToNow(); // when the token was issued/created (now) + claims.setIssuer("https://lightoidc.srsoftware.de"); // who creates the token and signs it + claims.setGeneratedJwtId(); // a unique identifier for the token + claims.setSubject(user.uuid()); // the subject/principal is whom the token is about + + // die nachfolgenden Claims sind nur Spielerei, ich habe versucht, das System mit Umbrella zum Laufen zu bekommen + claims.setClaim("scope", "openid"); + claims.setStringListClaim("amr", "pwd"); + claims.setClaim("at_hash", Base64.getEncoder().encodeToString("Test".getBytes(StandardCharsets.UTF_8))); + claims.setClaim("azp", client.id()); + claims.setClaim("email_verified", true); + try { + claims.setClaim("rat", claims.getIssuedAt().getValue()); + } catch (MalformedClaimException e) { + } return claims; } } diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java index aa928e2..fc3c2da 100644 --- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java +++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java @@ -2,7 +2,6 @@ package de.srsoftware.oidc.backend; import static de.srsoftware.oidc.api.User.*; -import static java.lang.System.Logger.Level.WARNING; import static java.net.HttpURLConnection.*; import com.sun.net.httpserver.HttpExchange; @@ -31,8 +30,7 @@ public class UserController extends Controller { return logout(ex, session); } - LOG.log(WARNING, "not implemented"); - return sendEmptyResponse(HTTP_NOT_FOUND, ex); + return notFound(ex); } diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/WellKnownController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/WellKnownController.java index 17b5ee0..8174b6c 100644 --- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/WellKnownController.java +++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/WellKnownController.java @@ -1,8 +1,6 @@ /* © SRSoftware 2024 */ package de.srsoftware.oidc.backend; -import static java.lang.System.Logger.Level.WARNING; -import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import com.sun.net.httpserver.HttpExchange; import de.srsoftware.oidc.api.PathHandler; @@ -16,12 +14,11 @@ public class WellKnownController extends PathHandler { case "/openid-configuration": return openidConfig(ex); } - LOG.log(WARNING, "not implemented"); - return sendEmptyResponse(HTTP_NOT_FOUND, ex); + return notFound(ex); } private boolean openidConfig(HttpExchange ex) throws IOException { var host = hostname(ex); - return sendContent(ex, Map.of("token_endpoint", host + "/api/token", "authorization_endpoint", host + "/web/authorization.html", "userinfo_endpoint", host + "/api/userinfo", "jwks_uri", host + "/api/jwks")); + return sendContent(ex, Map.of("token_endpoint", host + "/api/token", "authorization_endpoint", host + "/web/authorization.html", "userinfo_endpoint", host + "/api/userinfo", "jwks_uri", host + "/api/jwks", "issuer", "https://lightoidc.srsoftware.de")); } } diff --git a/de.srsoftware.oidc.datastore.file/build.gradle b/de.srsoftware.oidc.datastore.file/build.gradle index cbc152a..7b3942d 100644 --- a/de.srsoftware.oidc.datastore.file/build.gradle +++ b/de.srsoftware.oidc.datastore.file/build.gradle @@ -14,6 +14,8 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter' implementation project(':de.srsoftware.oidc.api') implementation 'org.json:json:20240303' + implementation 'org.bitbucket.b_c:jose4j:0.9.6' + } test { diff --git a/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/PlaintextKeyStore.java b/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/PlaintextKeyStore.java new file mode 100644 index 0000000..86772b4 --- /dev/null +++ b/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/PlaintextKeyStore.java @@ -0,0 +1,58 @@ +/* © SRSoftware 2024 */ +package de.srsoftware.oidc.datastore.file; + +import static java.lang.System.Logger.Level.ERROR; + +import de.srsoftware.oidc.api.KeyManager; +import de.srsoftware.oidc.api.KeyStorage; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import org.jose4j.jwk.PublicJsonWebKey; +import org.jose4j.lang.JoseException; + +public class PlaintextKeyStore implements KeyStorage { + public static System.Logger LOG = System.getLogger(PlaintextKeyStore.class.getSimpleName()); + + private final Path dir; + + public PlaintextKeyStore(Path storageDir) { + this.dir = storageDir; + storageDir.toFile().mkdirs(); + } + @Override + public KeyStorage drop(String keyId) { + return null; + } + + @Override + public List listKeys() { + try { + return Files.list(dir).map(Path::toString).filter(filename -> filename.endsWith(".key")).map(filename -> filename.substring(0, filename.length() - 4)).toList(); + } catch (IOException e) { + LOG.log(ERROR, "Failed to list files in {0}:", dir, e); + return List.of(); + } + } + + @Override + public PublicJsonWebKey load(String keyId) throws IOException, KeyManager.KeyCreationException { + var json = Files.readString(filename(keyId)); + try { + return PublicJsonWebKey.Factory.newPublicJwk(json); + } catch (JoseException e) { + throw new KeyManager.KeyCreationException(e); + } + } + + @Override + public KeyStorage store(PublicJsonWebKey jsonWebKey) throws IOException { + Files.writeString(filename(jsonWebKey.getKeyId()), jsonWebKey.toJson()); + return this; + } + + private Path filename(String keyId) { + return dir.resolve("%s.key".formatted(keyId)); + } +} diff --git a/de.srsoftware.oidc.web/src/main/java/de/srsoftware/oidc/web/StaticPages.java b/de.srsoftware.oidc.web/src/main/java/de/srsoftware/oidc/web/StaticPages.java index c0abd79..fc6d895 100644 --- a/de.srsoftware.oidc.web/src/main/java/de/srsoftware/oidc/web/StaticPages.java +++ b/de.srsoftware.oidc.web/src/main/java/de/srsoftware/oidc/web/StaticPages.java @@ -2,7 +2,6 @@ package de.srsoftware.oidc.web; import static java.lang.System.Logger.Level.*; -import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import com.sun.net.httpserver.HttpExchange; import de.srsoftware.oidc.api.PathHandler; @@ -43,7 +42,7 @@ public class StaticPages extends PathHandler { return sendContent(ex, response.content); } catch (FileNotFoundException fnf) { LOG.log(WARNING, "Loaded {0} for language {1}…failed.", relativePath, lang); - return sendEmptyResponse(HTTP_NOT_FOUND, ex); + return notFound(ex); } } diff --git a/de.srsoftware.oidc.web/src/main/resources/de/clients.html b/de.srsoftware.oidc.web/src/main/resources/de/clients.html index 92bbb70..d7f7a4d 100644 --- a/de.srsoftware.oidc.web/src/main/resources/de/clients.html +++ b/de.srsoftware.oidc.web/src/main/resources/de/clients.html @@ -25,7 +25,7 @@ - + diff --git a/de.srsoftware.oidc.web/src/main/resources/en/authorization.js b/de.srsoftware.oidc.web/src/main/resources/en/authorization.js index 920c40e..40689df 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/authorization.js +++ b/de.srsoftware.oidc.web/src/main/resources/en/authorization.js @@ -13,6 +13,7 @@ async function handleResponse(response){ if (!json.confirmed){ showConfirmationDialog(json.name); } else { + console.log('redirecting to '+json.redirect_uri+'?code='+json.code+'&state='+json.state+'&scope=openid'); redirect(json.redirect_uri+'?code='+json.code+'&state='+json.state+'&scope=openid'); } return; diff --git a/de.srsoftware.oidc.web/src/main/resources/en/clients.html b/de.srsoftware.oidc.web/src/main/resources/en/clients.html index 296ebef..9887835 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/clients.html +++ b/de.srsoftware.oidc.web/src/main/resources/en/clients.html @@ -25,7 +25,7 @@ - +