From 61c0fbc5eb9d24267d4ca24cdebe4566e959bd02 Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Thu, 29 Aug 2024 09:52:25 +0200 Subject: feat(utils): add RFC7636 (Outh2 PKCE) implementation --- .../gv/egiz/eaaf/core/impl/utils/Rfc7636Utils.java | 141 +++++++++++++++++++++ .../eaaf/core/test/utils/Rfc7636UtilsTest.java | 68 ++++++++++ 2 files changed, 209 insertions(+) create mode 100644 eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/Rfc7636Utils.java create mode 100644 eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/utils/Rfc7636UtilsTest.java (limited to 'eaaf_core_utils/src') diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/Rfc7636Utils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/Rfc7636Utils.java new file mode 100644 index 00000000..b213d932 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/Rfc7636Utils.java @@ -0,0 +1,141 @@ +package at.gv.egiz.eaaf.core.impl.utils; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; + +import lombok.Builder; +import lombok.Getter; + +/** + * Implements OAuth2 PKCE from RFC7636. + */ +public class Rfc7636Utils { + + private SecureRandom random; + private static Rfc7636Utils instance = null; + + public static final String PARAM_NAME_VERIFIER = "code_verifier"; + public static final String PARAM_NAME_CHALLENGE = "code_challenge"; + public static final String PARAM_NAME_METHOD = "code_challenge_method"; + + /** + * Get an instance of RFC7636 implementation for PKCE. + * + * @return {@link Rfc7636Utils} + * @throws NoSuchAlgorithmException In case of Secure-Random problems + */ + public static Rfc7636Utils getInstance() throws NoSuchAlgorithmException { + if (instance == null) { + instance = new Rfc7636Utils(); + + } + return instance; + + } + + /** + * Generates a new pair of PKCE information. + * + *
+ * Using S256 as method. + *
+ * + * @return PKCE data that can be directly used + * @throws NoSuchAlgorithmException In case of SHA-256 problem + */ + public PkceInfo generate() throws NoSuchAlgorithmException { + return generate(Method.S256); + + } + + /** + * Generates a new pair of PKCE information. + * + * @param method Challenge generation method + * @return PKCE data that can be directly used + * @throws NoSuchAlgorithmException In case of SHA-256 problem + */ + public PkceInfo generate(Method method) throws NoSuchAlgorithmException { + String value = generateNewRandomValue(); + return PkceInfo.builder() + .codeMethod(method) + .codeVerifier(value) + .codeChallenge(calculateChallenge(value, method)) + .build(); + + } + + /** + * Verify PKCE information. + * + * @param input Set of PKCE infos + * @returntrue
if infos a valid, otherwise false
+ * @throws NoSuchAlgorithmException In case of SHA-256 problem
+ */
+ public boolean verify(PkceInfo input) throws NoSuchAlgorithmException {
+ return input.codeChallenge.equals(
+ calculateChallenge(input.codeVerifier, input.codeMethod));
+
+ }
+
+ private String calculateChallenge(String value, Method method) throws NoSuchAlgorithmException {
+ if (Method.PLAIN.equals(method)) {
+ return value;
+
+ } else {
+ return encodeB64(MessageDigest.getInstance("SHA-256").digest(
+ value.getBytes(StandardCharsets.US_ASCII)));
+
+ }
+ }
+
+ private String generateNewRandomValue() {
+ byte[] values = new byte[20];
+ random.nextBytes(values);
+ return encodeB64(values);
+
+ }
+
+ private String encodeB64(byte[] input) {
+ return Base64.getUrlEncoder().withoutPadding().encodeToString(input);
+ }
+
+ private Rfc7636Utils() throws NoSuchAlgorithmException {
+ random = SecureRandom.getInstanceStrong();
+
+ }
+
+ public enum Method {
+ S256, PLAIN
+ }
+
+ /**
+ * Holds a fresh pair of PKCE information.
+ *
+ * @author tlenz
+ */
+ @Getter
+ @Builder(toBuilder = true)
+ public static class PkceInfo {
+
+ String codeVerifier;
+
+ String codeChallenge;
+
+ Method codeMethod;
+
+ /**
+ * Get PKCE method as String regarding to RFC7636.
+ *
+ * @return PKCE method
+ */
+ public String getMethod() {
+ return codeMethod.toString();
+
+ }
+
+ }
+}
diff --git a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/utils/Rfc7636UtilsTest.java b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/utils/Rfc7636UtilsTest.java
new file mode 100644
index 00000000..3bedf3d0
--- /dev/null
+++ b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/utils/Rfc7636UtilsTest.java
@@ -0,0 +1,68 @@
+package at.gv.egiz.eaaf.core.test.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+import at.gv.egiz.eaaf.core.impl.utils.Rfc7636Utils;
+import at.gv.egiz.eaaf.core.impl.utils.Rfc7636Utils.Method;
+import at.gv.egiz.eaaf.core.impl.utils.Rfc7636Utils.PkceInfo;
+import lombok.SneakyThrows;
+
+@RunWith(BlockJUnit4ClassRunner.class)
+public class Rfc7636UtilsTest {
+
+ @Test
+ @SneakyThrows
+ public void generateS256() {
+ PkceInfo infos = Rfc7636Utils.getInstance().generate();
+ assertNotNull(infos);
+ assertEquals("S256", infos.getMethod());
+ assertEquals(Method.S256, infos.getCodeMethod());
+ assertNotNull(infos.getCodeChallenge());
+ assertNotNull(infos.getCodeVerifier());
+ assertTrue(Rfc7636Utils.getInstance().verify(infos));
+ }
+
+ @Test
+ @SneakyThrows
+ public void generatePlain() {
+ PkceInfo infos = Rfc7636Utils.getInstance().generate(Method.PLAIN);
+ assertNotNull(infos);
+ assertEquals("PLAIN", infos.getMethod());
+ assertEquals(Method.PLAIN, infos.getCodeMethod());
+ assertTrue(Rfc7636Utils.getInstance().verify(infos));
+
+ }
+
+ @Test
+ @SneakyThrows
+ public void verifyInvalid() {
+ PkceInfo infos = PkceInfo.builder()
+ .codeMethod(Method.S256)
+ .codeVerifier(RandomStringUtils.randomAlphabetic(10))
+ .codeChallenge(RandomStringUtils.randomAlphabetic(10))
+ .build();
+ assertFalse(Rfc7636Utils.getInstance().verify(infos));
+
+ }
+
+ @Test
+ @SneakyThrows
+ public void verifyRfc7636TestVector() {
+ PkceInfo infos = PkceInfo.builder()
+ .codeMethod(Method.S256)
+ .codeVerifier("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk")
+ .codeChallenge("E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM")
+ .build();
+ assertTrue(Rfc7636Utils.getInstance().verify(infos));
+
+ }
+
+}
--
cgit v1.2.3