From 47cc55d87fa94299e8b11b937fcf47a5281261c8 Mon Sep 17 00:00:00 2001 From: Dusan Jakub Date: Fri, 15 Sep 2023 17:12:38 +0200 Subject: [PATCH] PKCE --- pom.xml | 5 ++++ .../java/com/ysoft/geecon/OAuthResource.java | 3 ++ .../java/com/ysoft/geecon/dto/AuthParams.java | 30 +++++++++++++++++++ .../geecon/dto/AuthorizationSession.java | 11 +++++++ src/main/java/com/ysoft/geecon/dto/Pkce.java | 18 +++++++++++ .../com/ysoft/geecon/dto/TokenParams.java | 15 +++++++--- .../ysoft/geecon/error/OAuthException.java | 1 + src/main/resources/application.properties | 2 ++ .../java/com/ysoft/geecon/dto/PkceTest.java | 13 ++++++++ 9 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/ysoft/geecon/dto/Pkce.java create mode 100644 src/test/java/com/ysoft/geecon/dto/PkceTest.java diff --git a/pom.xml b/pom.xml index fba2dad..6b79bb1 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,11 @@ commons-lang3 3.4 + + commons-codec + commons-codec + 1.16.0 + diff --git a/src/main/java/com/ysoft/geecon/OAuthResource.java b/src/main/java/com/ysoft/geecon/OAuthResource.java index 812de81..6695cd5 100644 --- a/src/main/java/com/ysoft/geecon/OAuthResource.java +++ b/src/main/java/com/ysoft/geecon/OAuthResource.java @@ -103,6 +103,9 @@ public class OAuthResource { validateClient(params); var session = sessionsRepo.redeemAuthorizationCode(params.getCode()) .orElseThrow(() -> new OAuthException("Invalid code")); + if (!session.validateCodeChallenge(params.getCodeVerifier())) { + throw new OAuthException("Invalid code verifier"); + } String idToken = null; diff --git a/src/main/java/com/ysoft/geecon/dto/AuthParams.java b/src/main/java/com/ysoft/geecon/dto/AuthParams.java index bcc9f74..3c1c630 100644 --- a/src/main/java/com/ysoft/geecon/dto/AuthParams.java +++ b/src/main/java/com/ysoft/geecon/dto/AuthParams.java @@ -24,6 +24,12 @@ public class AuthParams { String scope; @RestQuery("state") String state; + @RestQuery("code_challenge_method") + String codeChallengeMethod; + @RestQuery("code_challenge") + String codeChallenge; + @RestQuery("nonce") + String nonce; public String getLoginHint() { return loginHint; @@ -76,4 +82,28 @@ public class AuthParams { public void setState(String state) { this.state = state; } + + public String getCodeChallengeMethod() { + return codeChallengeMethod; + } + + public void setCodeChallengeMethod(String codeChallengeMethod) { + this.codeChallengeMethod = codeChallengeMethod; + } + + public String getCodeChallenge() { + return codeChallenge; + } + + public void setCodeChallenge(String codeChallenge) { + this.codeChallenge = codeChallenge; + } + + public String getNonce() { + return nonce; + } + + public void setNonce(String nonce) { + this.nonce = nonce; + } } diff --git a/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java b/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java index 5b55f85..d9a28fa 100644 --- a/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java +++ b/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java @@ -36,4 +36,15 @@ public record AuthorizationSession(AuthParams params, public String scope() { return acceptedScopes == null ? null : String.join(" ", acceptedScopes); } + + public boolean validateCodeChallenge(String codeVerifier) { + if (params.codeChallengeMethod == null) { + return true; + } + if (codeVerifier == null) { + return false; + } + return Pkce.validate(params.codeChallengeMethod, params.codeChallenge, codeVerifier); + } + } diff --git a/src/main/java/com/ysoft/geecon/dto/Pkce.java b/src/main/java/com/ysoft/geecon/dto/Pkce.java new file mode 100644 index 0000000..a1946d1 --- /dev/null +++ b/src/main/java/com/ysoft/geecon/dto/Pkce.java @@ -0,0 +1,18 @@ +package com.ysoft.geecon.dto; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.digest.DigestUtils; + +public class Pkce { + static boolean validate(String challengeMethod, String codeChallenge, String codeVerifier) { + return switch (challengeMethod) { + case "plain" -> codeVerifier.equals(codeChallenge); + case "S256" -> codeChallenge.equals(s256(codeVerifier)); + default -> false; + }; + } + + static String s256(String codeVerifier) { + return Base64.encodeBase64URLSafeString(DigestUtils.sha256(codeVerifier)); + } +} diff --git a/src/main/java/com/ysoft/geecon/dto/TokenParams.java b/src/main/java/com/ysoft/geecon/dto/TokenParams.java index a1a2e04..0b5f8b6 100644 --- a/src/main/java/com/ysoft/geecon/dto/TokenParams.java +++ b/src/main/java/com/ysoft/geecon/dto/TokenParams.java @@ -1,10 +1,6 @@ package com.ysoft.geecon.dto; import jakarta.ws.rs.FormParam; -import org.jboss.resteasy.reactive.RestQuery; - -import java.util.Arrays; -import java.util.List; public class TokenParams { @@ -23,6 +19,9 @@ public class TokenParams { @FormParam("code") private String code; + @FormParam("code_verifier") + private String codeVerifier; + public String getGrantType() { return grantType; } @@ -62,4 +61,12 @@ public class TokenParams { public void setCode(String code) { this.code = code; } + + public String getCodeVerifier() { + return codeVerifier; + } + + public void setCodeVerifier(String codeVerifier) { + this.codeVerifier = codeVerifier; + } } diff --git a/src/main/java/com/ysoft/geecon/error/OAuthException.java b/src/main/java/com/ysoft/geecon/error/OAuthException.java index 7e17808..7e5779d 100644 --- a/src/main/java/com/ysoft/geecon/error/OAuthException.java +++ b/src/main/java/com/ysoft/geecon/error/OAuthException.java @@ -1,6 +1,7 @@ package com.ysoft.geecon.error; public class OAuthException extends RuntimeException { + // https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-09.html#name-error-response-2 public OAuthException(String message) { super(message); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e69de29..d6c772b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -0,0 +1,2 @@ +quarkus.http.cors=true +quarkus.http.cors.origins=/.*/ \ No newline at end of file diff --git a/src/test/java/com/ysoft/geecon/dto/PkceTest.java b/src/test/java/com/ysoft/geecon/dto/PkceTest.java new file mode 100644 index 0000000..f6bed2e --- /dev/null +++ b/src/test/java/com/ysoft/geecon/dto/PkceTest.java @@ -0,0 +1,13 @@ +package com.ysoft.geecon.dto; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PkceTest { + + @Test + void validate() { + assertTrue(Pkce.validate("S256", "W2ap_IuB0HJkMIkuxaXV2l8_Gx5mVmMStG_HQrAnQxA", "7AmAEXcl2Km9LQMwtUhif7GQ97HZy9RT72KZBwmxBRI")); + } +} \ No newline at end of file