diff options
Diffstat (limited to 'eaaf_core_utils/src')
-rw-r--r-- | eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/Rfc7636Utils.java | 141 | ||||
-rw-r--r-- | eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/utils/Rfc7636UtilsTest.java | 68 |
2 files changed, 209 insertions, 0 deletions
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. + * + * <p> + * Using S256 as method. + * </p> + * + * @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 + * @return <code>true</code> if infos a valid, otherwise <code>false</code> + * @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)); + + } + +} |