From 5b99ebf53322a2c270c62d70a553d4af91f7cd7e Mon Sep 17 00:00:00 2001 From: David Kral Date: Wed, 22 Feb 2023 14:31:41 +0100 Subject: [PATCH 1/5] JWT-Auth upgrade to 2.1 version Signed-off-by: David Kral --- dependencies/pom.xml | 2 +- .../jwt/auth/JwtAuthProvider.java | 66 ++++++++++- .../io/helidon/security/jwt/EncryptedJwt.java | 105 ++++++++++++++---- .../java/io/helidon/security/jwt/Jwt.java | 33 +++++- 4 files changed, 178 insertions(+), 28 deletions(-) diff --git a/dependencies/pom.xml b/dependencies/pom.xml index ffc9f917d2c..7dd676084df 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -109,7 +109,7 @@ 4.0 2.0 4.0 - 2.0 + 2.1 4.0 3.0 3.0 diff --git a/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java b/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java index 7ebd31cd674..ae2cd16aecd 100644 --- a/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java +++ b/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java @@ -32,6 +32,7 @@ import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Base64; @@ -116,6 +117,8 @@ public class JwtAuthProvider extends SynchronousProvider implements Authenticati * Configuration key for expected audiences of incoming tokens. Used for validation of JWT. */ public static final String CONFIG_EXPECTED_AUDIENCES = "mp.jwt.verify.audiences"; + private static final String CONFIG_EXPECTED_MAX_TOKEN_AGE = "mp.jwt.verify.token.age"; + private static final String CONFIG_CLOCK_SKEW = "mp.jwt.verify.clock.skew"; /** * Configuration of Cookie property name which contains JWT token. * @@ -147,7 +150,10 @@ public class JwtAuthProvider extends SynchronousProvider implements Authenticati private final Map targetToJwtConfig = new IdentityHashMap<>(); private final String expectedIssuer; private final String cookiePrefix; + private final String decryptionKeyAlgorithm; private final boolean useCookie; + private final Duration expectedMaxTokenAge; + private final Duration clockSkew; private JwtAuthProvider(Builder builder) { this.optional = builder.optional; @@ -167,6 +173,9 @@ private JwtAuthProvider(Builder builder) { this.useCookie = builder.useCookie; this.decryptionKeys = builder.decryptionKeys; this.defaultDecryptionJwk = builder.defaultDecryptionJwk; + this.decryptionKeyAlgorithm = builder.decryptionKeyAlgorithm; + this.expectedMaxTokenAge = builder.expectedMaxTokenAge; + this.clockSkew = builder.clockSkew; if (null == atnTokenHandler) { defaultTokenHandler = TokenHandler.builder() @@ -252,7 +261,14 @@ AuthenticationResponse authenticate(ProviderRequest providerRequest, LoginConfig throw new JwtException("Header \"cty\" (content type) must be set to \"JWT\" " + "for encrypted tokens"); } - signedJwt = encryptedJwt.decrypt(decryptionKeys.get(), defaultDecryptionJwk.get()); + List> validators = new LinkedList<>(); + EncryptedJwt.addKekValidator(validators, decryptionKeyAlgorithm, true); + Errors errors = encryptedJwt.validate(validators); + if (errors.isValid()) { + signedJwt = encryptedJwt.decrypt(decryptionKeys.get(), defaultDecryptionJwk.get()); + } else { + return AuthenticationResponse.failed(errors.toString()); + } } else { signedJwt = SignedJwt.parseToken(token); } @@ -278,7 +294,13 @@ AuthenticationResponse authenticate(ProviderRequest providerRequest, LoginConfig } // validate user principal is present Jwt.addUserPrincipalValidator(validators); - validators.add(Jwt.ExpirationValidator.create(true)); + validators.add(Jwt.ExpirationValidator.create(Instant.now(), + (int) clockSkew.getSeconds(), + ChronoUnit.SECONDS, + true)); + if (expectedMaxTokenAge != null) { + Jwt.addMaxTokenAgeValidator(validators, expectedMaxTokenAge, clockSkew,true); + } Errors validate = jwt.validate(validators); @@ -579,6 +601,7 @@ public static class Builder implements io.helidon.common.Builder audiences) { return this; } + /** + * Maximal expected token age. If this value is set, {@code iat} claim needs to be present in the JWT. + * + * @param expectedMaxTokenAge expected maximal token age in seconds + * @return updated builder instance + */ + public Builder expectedMaxTokenAge(int expectedMaxTokenAge) { + this.expectedMaxTokenAge = Duration.ofSeconds(expectedMaxTokenAge); + return this; + } + /** * Private key to decryption of encrypted claims. * @@ -1205,6 +1245,17 @@ public Builder decryptKeyLocation(String decryptKeyLocation) { return this; } + /** + * Required decryption key algorithm. + * + * @param decryptionKeyAlgorithm required decryption key algorithm + * @return updated builder instance + */ + public Builder decryptKeyAlgorithm(String decryptionKeyAlgorithm) { + this.decryptionKeyAlgorithm = decryptionKeyAlgorithm; + return this; + } + /** * Whether to load JWK verification keys on server startup * Default value is {@code false}. @@ -1217,6 +1268,17 @@ public Builder loadOnStartup(boolean loadOnStartup) { return this; } + /** + * Clock skew to be accounted for in token expiration and max age validations. + * + * @param clockSkew clock skew + * @return updated builder instance + */ + public Builder clockSkew(int clockSkew) { + this.clockSkew = Duration.ofSeconds(clockSkew); + return this; + } + private void verifyKeys(Config config) { config.get("jwk.resource").as(Resource::create).ifPresent(this::verifyJwk); } diff --git a/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java b/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java index 7d949399d6f..4679f390262 100644 --- a/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java +++ b/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022 Oracle and/or its affiliates. + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,10 +23,15 @@ import java.security.PublicKey; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; import java.util.Arrays; import java.util.Base64; +import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -36,6 +41,8 @@ import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; import javax.crypto.spec.SecretKeySpec; import io.helidon.common.Errors; @@ -54,7 +61,6 @@ */ public final class EncryptedJwt { - private static final Map RSA_ALGORITHMS; private static final Map CONTENT_ENCRYPTION; private static final Pattern JWE_PATTERN = Pattern @@ -63,10 +69,6 @@ public final class EncryptedJwt { private static final Base64.Encoder URL_ENCODER = Base64.getUrlEncoder().withoutPadding(); static { - RSA_ALGORITHMS = Map.of(SupportedAlgorithm.RSA_OAEP, "RSA/ECB/OAEPWithSHA-1AndMGF1Padding", - SupportedAlgorithm.RSA_OAEP_256, "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", - SupportedAlgorithm.RSA1_5, "RSA/ECB/PKCS1Padding"); - CONTENT_ENCRYPTION = Map.of(SupportedEncryption.A128GCM, new AesGcmAlgorithm(128), SupportedEncryption.A192GCM, new AesGcmAlgorithm(192), SupportedEncryption.A256GCM, new AesGcmAlgorithm(256), @@ -173,7 +175,7 @@ public static EncryptedJwt parseToken(String token) { * * * @param header parsed JWT header - * @param token String with the token + * @param token String with the token * @return Encrypted jwt parts * @throws RuntimeException in case of invalid content, see {@link Errors.ErrorMessagesException} */ @@ -193,6 +195,32 @@ public static EncryptedJwt parseToken(JwtHeaders header, String token) { } } + /** + * Add validator of kek algorithm to the collection of validators. + * + * @param validators collection of validators + * @param expectedKekAlg audience key encryption key algorithm + * @param mandatory whether the alg field is mandatory in the token + */ + public static void addKekValidator(Collection> validators, String expectedKekAlg, boolean mandatory) { + validators.add((encryptedJwt, collector) -> { + Optional kekAlgorithm = encryptedJwt.header.algorithm(); + if (kekAlgorithm.isPresent()) { + if (expectedKekAlg == null) { + return; //any kek is allowed + } else if (kekAlgorithm.get().equals(expectedKekAlg)) { + return; + } + collector.fatal(encryptedJwt, "Key encryption key algorithm must be equal to " + expectedKekAlg + + ", yet it is: " + kekAlgorithm.get()); + } else { + if (mandatory) { + collector.fatal(encryptedJwt, "Key encryption key algorithm is expected to be present for encrypted JWT"); + } + } + }); + } + private static EncryptedJwt parse(String token, Errors.Collector collector, JwtHeaders header, @@ -221,11 +249,15 @@ private static byte[] encryptRsa(String algorithm, PublicKey publicKey, byte[] u } } - private static byte[] decryptRsa(String algorithm, PrivateKey privateKey, byte[] encryptedKey) { + private static byte[] decryptRsa(SupportedAlgorithm supportedAlgorithm, PrivateKey privateKey, byte[] encryptedKey) { try { - Cipher rsaCipher = Cipher.getInstance(algorithm); - rsaCipher.init(Cipher.DECRYPT_MODE, privateKey); - return rsaCipher.doFinal(encryptedKey); + Cipher rsaCipher = Cipher.getInstance(supportedAlgorithm.cipherName()); + if (supportedAlgorithm.parameterSpec() == null) { + rsaCipher.init(Cipher.UNWRAP_MODE, privateKey); + } else { + rsaCipher.init(Cipher.UNWRAP_MODE, privateKey, supportedAlgorithm.parameterSpec()); + } + return rsaCipher.unwrap(encryptedKey, "AES", Cipher.SECRET_KEY).getEncoded(); } catch (Exception e) { throw new JwtException("Exception during rsa key decryption occurred.", e); } @@ -255,7 +287,7 @@ private static byte[] decodeBytes(String base64, Errors.Collector collector, Str * * Selected {@link Jwk} needs to have private key set. * - * @param jwkKeys jwk keys + * @param jwkKeys jwk keys * @return empty optional if any error has occurred or SignedJwt instance if the decryption and validation was successful */ public SignedJwt decrypt(JwkKeys jwkKeys) { @@ -268,7 +300,7 @@ public SignedJwt decrypt(JwkKeys jwkKeys) { * * Provided {@link Jwk} needs to have private key set. * - * @param jwk jwk keys + * @param jwk jwk keys * @return empty optional if any error has occurred or SignedJwt instance if the decryption and validation was successful */ public SignedJwt decrypt(Jwk jwk) { @@ -313,11 +345,10 @@ public SignedJwt decrypt(JwkKeys jwkKeys, Jwk defaultJwk) { if (enc == null) { errors.fatal("Content encryption algorithm not set."); } - + SupportedAlgorithm supportedAlgorithm = null; if (alg != null) { try { - SupportedAlgorithm supportedAlgorithm = SupportedAlgorithm.getValue(alg); - algorithm = RSA_ALGORITHMS.get(supportedAlgorithm); + supportedAlgorithm = SupportedAlgorithm.getValue(alg); } catch (IllegalArgumentException e) { errors.fatal("Value of the claim alg not supported. alg: " + alg); } @@ -345,7 +376,7 @@ public SignedJwt decrypt(JwkKeys jwkKeys, Jwk defaultJwk) { errors.collect().checkValid(); - byte[] decryptedKey = decryptRsa(algorithm, privateKey, encryptedKey); + byte[] decryptedKey = decryptRsa(supportedAlgorithm, privateKey, encryptedKey); //Base64 headers are used as an aad. This aad has to be in US_ASCII encoding. EncryptionParts encryptionParts = new EncryptionParts(decryptedKey, iv, @@ -417,6 +448,12 @@ public byte[] encryptedPayload() { return Arrays.copyOf(encryptedPayload, encryptedPayload.length); } + public Errors validate(List> validators) { + Errors.Collector collector = Errors.collector(); + validators.forEach(it -> it.validate(this, collector)); + return collector.collect(); + } + /** * Encrypted JWT builder. */ @@ -511,7 +548,7 @@ public EncryptedJwt build() { JwtHeaders headers = headersBuilder.build(); StringBuilder tokenBuilder = new StringBuilder(); String headersBase64 = encode(headers.headerJson().toString()); - String rsaCipherType = RSA_ALGORITHMS.get(algorithm); + String rsaCipherType = algorithm.cipherName(); AesAlgorithm contentEncryption = CONTENT_ENCRYPTION.get(encryption); //Base64 headers are used as an aad. This aad has to be in US_ASCII encoding. EncryptionParts encryptionParts = contentEncryption.encrypt(jwt.tokenContent().getBytes(StandardCharsets.UTF_8), @@ -544,20 +581,28 @@ public enum SupportedAlgorithm { /** * RSA-OAEP declares that RSA/ECB/OAEPWithSHA-1AndMGF1Padding cipher will be used for content key encryption. */ - RSA_OAEP("RSA-OAEP"), + RSA_OAEP("RSA-OAEP", + "RSA/ECB/OAEPWithSHA-1AndMGF1Padding", + new OAEPParameterSpec("SHA-1", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT)), /** * RSA-OAEP-256 declares that RSA/ECB/OAEPWithSHA-256AndMGF1Padding cipher will be used for content key encryption. */ - RSA_OAEP_256("RSA-OAEP-256"), + RSA_OAEP_256("RSA-OAEP-256", + "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", + new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT)), /** * RSA1_5 declares that RSA/ECB/PKCS1Padding cipher will be used for content key encryption. */ - RSA1_5("RSA1_5"); + RSA1_5("RSA1_5", "RSA/ECB/PKCS1Padding", null); private final String algorithmName; + private final String cipherName; + private final AlgorithmParameterSpec parameterSpec; - SupportedAlgorithm(String algorithmName) { + SupportedAlgorithm(String algorithmName, String cipherName, AlgorithmParameterSpec parameterSpec) { this.algorithmName = algorithmName; + this.cipherName = cipherName; + this.parameterSpec = parameterSpec; } @Override @@ -565,9 +610,21 @@ public String toString() { return algorithmName; } - static SupportedAlgorithm getValue(String value) { + String cipherName() { + return cipherName; + } + + String algorithmName() { + return algorithmName; + } + + AlgorithmParameterSpec parameterSpec() { + return parameterSpec; + } + + static SupportedAlgorithm getValue(String algorithmName) { for (SupportedAlgorithm v : values()) { - if (v.algorithmName.equalsIgnoreCase(value)) { + if (v.algorithmName.equalsIgnoreCase(algorithmName)) { return v; } } diff --git a/security/jwt/src/main/java/io/helidon/security/jwt/Jwt.java b/security/jwt/src/main/java/io/helidon/security/jwt/Jwt.java index 8ef8950f250..881679f3a54 100644 --- a/security/jwt/src/main/java/io/helidon/security/jwt/Jwt.java +++ b/security/jwt/src/main/java/io/helidon/security/jwt/Jwt.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022 Oracle and/or its affiliates. + * Copyright (c) 2018, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package io.helidon.security.jwt; import java.net.URI; +import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; @@ -426,6 +427,36 @@ public static void addAudienceValidator(Collection> validators, S }); } + /** + * Add validator of max token age to the collection of validators. + * + * @param validators collection of validators + * @param expectedMaxTokenAge max token age since issue time + * @param clockSkew clock skew + * @param iatRequired whether to fail if iat clam is present + */ + public static void addMaxTokenAgeValidator(Collection> validators, + Duration expectedMaxTokenAge, + Duration clockSkew, + boolean iatRequired) { + validators.add((jwt, collector) -> { + Optional maybeIssueTime = jwt.issueTime(); + if (maybeIssueTime.isPresent()) { + Instant now = Instant.now(); + Instant issueTime = maybeIssueTime.get().minus(clockSkew); + Instant maxValidTime = issueTime.plus(expectedMaxTokenAge).plus(clockSkew); + if (issueTime.isBefore(now) && maxValidTime.isAfter(now)) { + return; + } + collector.fatal(jwt, "Current time need to be between " + issueTime + + " and " + maxValidTime + ", but was " + now); + } else if (iatRequired) { + collector.fatal(jwt, "Claim iat is required to be present in JWT when validating token max allowed age."); + } + + }); + } + /** * Get a builder to create a JWT. * From d4a674f25faf197e87904c9fa57556aa61d1df91 Mon Sep 17 00:00:00 2001 From: David Kral Date: Wed, 22 Feb 2023 14:39:55 +0100 Subject: [PATCH 2/5] checkstyle fixes Signed-off-by: David Kral --- .../io/helidon/microprofile/jwt/auth/JwtAuthProvider.java | 2 +- .../main/java/io/helidon/security/jwt/EncryptedJwt.java | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java b/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java index ae2cd16aecd..5620a1a1bd5 100644 --- a/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java +++ b/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java @@ -299,7 +299,7 @@ AuthenticationResponse authenticate(ProviderRequest providerRequest, LoginConfig ChronoUnit.SECONDS, true)); if (expectedMaxTokenAge != null) { - Jwt.addMaxTokenAgeValidator(validators, expectedMaxTokenAge, clockSkew,true); + Jwt.addMaxTokenAgeValidator(validators, expectedMaxTokenAge, clockSkew, true); } Errors validate = jwt.validate(validators); diff --git a/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java b/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java index 4679f390262..e854bfffef0 100644 --- a/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java +++ b/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java @@ -31,7 +31,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -448,6 +447,12 @@ public byte[] encryptedPayload() { return Arrays.copyOf(encryptedPayload, encryptedPayload.length); } + /** + * Validate this Encrypted JWT against provided validators. + * + * @param validators Validators to validate with. + * @return errors instance to check if valid and access error messages + */ public Errors validate(List> validators) { Errors.Collector collector = Errors.collector(); validators.forEach(it -> it.validate(this, collector)); From 22c6526d6f41223e002c0200c208e075b5191393 Mon Sep 17 00:00:00 2001 From: David Kral Date: Wed, 22 Feb 2023 15:06:49 +0100 Subject: [PATCH 3/5] encryption changed to key wrapping Signed-off-by: David Kral --- .../io/helidon/security/jwt/EncryptedJwt.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java b/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java index e854bfffef0..d2940f57f92 100644 --- a/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java +++ b/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java @@ -238,17 +238,21 @@ private static EncryptedJwt parse(String token, return new EncryptedJwt(token, header, iv, encryptedKey, authTag, encryptedPayload); } - private static byte[] encryptRsa(String algorithm, PublicKey publicKey, byte[] unencryptedKey) { + private static byte[] wrapRsa(SupportedAlgorithm supportedAlgorithm, PublicKey publicKey, byte[] unencryptedKey) { try { - Cipher rsaCipher = Cipher.getInstance(algorithm); - rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey); - return rsaCipher.doFinal(unencryptedKey); + Cipher rsaCipher = Cipher.getInstance(supportedAlgorithm.cipherName()); + if (supportedAlgorithm.parameterSpec() == null) { + rsaCipher.init(Cipher.WRAP_MODE, publicKey); + } else { + rsaCipher.init(Cipher.WRAP_MODE, publicKey, supportedAlgorithm.parameterSpec()); + } + return rsaCipher.wrap(new SecretKeySpec(unencryptedKey, "AES")); } catch (Exception e) { throw new JwtException("Exception during rsa key decryption occurred.", e); } } - private static byte[] decryptRsa(SupportedAlgorithm supportedAlgorithm, PrivateKey privateKey, byte[] encryptedKey) { + private static byte[] unwrapRsa(SupportedAlgorithm supportedAlgorithm, PrivateKey privateKey, byte[] encryptedKey) { try { Cipher rsaCipher = Cipher.getInstance(supportedAlgorithm.cipherName()); if (supportedAlgorithm.parameterSpec() == null) { @@ -375,7 +379,7 @@ public SignedJwt decrypt(JwkKeys jwkKeys, Jwk defaultJwk) { errors.collect().checkValid(); - byte[] decryptedKey = decryptRsa(supportedAlgorithm, privateKey, encryptedKey); + byte[] decryptedKey = unwrapRsa(supportedAlgorithm, privateKey, encryptedKey); //Base64 headers are used as an aad. This aad has to be in US_ASCII encoding. EncryptionParts encryptionParts = new EncryptionParts(decryptedKey, iv, @@ -553,14 +557,13 @@ public EncryptedJwt build() { JwtHeaders headers = headersBuilder.build(); StringBuilder tokenBuilder = new StringBuilder(); String headersBase64 = encode(headers.headerJson().toString()); - String rsaCipherType = algorithm.cipherName(); AesAlgorithm contentEncryption = CONTENT_ENCRYPTION.get(encryption); //Base64 headers are used as an aad. This aad has to be in US_ASCII encoding. EncryptionParts encryptionParts = contentEncryption.encrypt(jwt.tokenContent().getBytes(StandardCharsets.UTF_8), headersBase64.getBytes(StandardCharsets.US_ASCII)); byte[] aesKey = encryptionParts.key(); - byte[] encryptedAesKey = encryptRsa(rsaCipherType, publicKey, aesKey); + byte[] encryptedAesKey = wrapRsa(algorithm, publicKey, aesKey); String token = tokenBuilder.append(headersBase64).append(".") .append(encode(encryptedAesKey)).append(".") .append(encode(encryptionParts.iv())).append(".") From 77336d309559841cea096e124d8b2509a940946b Mon Sep 17 00:00:00 2001 From: David Kral Date: Wed, 1 Mar 2023 12:43:08 +0100 Subject: [PATCH 4/5] suggestions implemented Signed-off-by: David Kral --- .../io/helidon/microprofile/jwt/auth/JwtAuthProvider.java | 5 +++-- .../src/main/java/io/helidon/security/jwt/EncryptedJwt.java | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java b/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java index 5620a1a1bd5..202745be0dc 100644 --- a/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java +++ b/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java @@ -117,6 +117,7 @@ public class JwtAuthProvider extends SynchronousProvider implements Authenticati * Configuration key for expected audiences of incoming tokens. Used for validation of JWT. */ public static final String CONFIG_EXPECTED_AUDIENCES = "mp.jwt.verify.audiences"; + private static final String CONFIG_EXPECTED_MAX_TOKEN_AGE = "mp.jwt.verify.token.age"; private static final String CONFIG_CLOCK_SKEW = "mp.jwt.verify.clock.skew"; /** @@ -1246,9 +1247,9 @@ public Builder decryptKeyLocation(String decryptKeyLocation) { } /** - * Required decryption key algorithm. + * Expected decryption key algorithm. * - * @param decryptionKeyAlgorithm required decryption key algorithm + * @param decryptionKeyAlgorithm expected decryption key algorithm * @return updated builder instance */ public Builder decryptKeyAlgorithm(String decryptionKeyAlgorithm) { diff --git a/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java b/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java index d2940f57f92..bc8f5d643a3 100644 --- a/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java +++ b/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java @@ -205,9 +205,8 @@ public static void addKekValidator(Collection> validator validators.add((encryptedJwt, collector) -> { Optional kekAlgorithm = encryptedJwt.header.algorithm(); if (kekAlgorithm.isPresent()) { - if (expectedKekAlg == null) { - return; //any kek is allowed - } else if (kekAlgorithm.get().equals(expectedKekAlg)) { + //if null, any kek alg is allowed + if (expectedKekAlg == null || kekAlgorithm.get().equals(expectedKekAlg)) { return; } collector.fatal(encryptedJwt, "Key encryption key algorithm must be equal to " + expectedKekAlg From a4a0a8f81f47c437dacde25fb60fa2d8e5eee12a Mon Sep 17 00:00:00 2001 From: David Kral Date: Tue, 7 Mar 2023 13:08:05 +0100 Subject: [PATCH 5/5] changes implemented Signed-off-by: David Kral --- .../io/helidon/microprofile/jwt/auth/JwtAuthProvider.java | 5 ++--- .../src/main/java/io/helidon/security/jwt/EncryptedJwt.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java b/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java index 202745be0dc..897ea3630e9 100644 --- a/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java +++ b/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java @@ -101,9 +101,6 @@ * Provider that provides JWT authentication. */ public class JwtAuthProvider extends SynchronousProvider implements AuthenticationProvider, OutboundSecurityProvider { - private static final System.Logger LOGGER = System.getLogger(JwtAuthProvider.class.getName()); - - private static final JsonReaderFactory JSON = Json.createReaderFactory(Collections.emptyMap()); /** * Configure this for outbound requests to override user to use. @@ -132,6 +129,8 @@ public class JwtAuthProvider extends SynchronousProvider implements Authenticati * Default value is {@link Http.Header#AUTHORIZATION}. */ private static final String CONFIG_JWT_HEADER = "mp.jwt.token.header"; + private static final System.Logger LOGGER = System.getLogger(JwtAuthProvider.class.getName()); + private static final JsonReaderFactory JSON = Json.createReaderFactory(Collections.emptyMap()); private final boolean optional; private final boolean authenticate; diff --git a/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java b/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java index bc8f5d643a3..2806c58f599 100644 --- a/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java +++ b/security/jwt/src/main/java/io/helidon/security/jwt/EncryptedJwt.java @@ -174,7 +174,7 @@ public static EncryptedJwt parseToken(String token) { * * * @param header parsed JWT header - * @param token String with the token + * @param token String with the token * @return Encrypted jwt parts * @throws RuntimeException in case of invalid content, see {@link Errors.ErrorMessagesException} */