diff --git a/implementation/jwt-auth/src/main/java/io/smallrye/jwt/auth/principal/KeyLocationResolver.java b/implementation/jwt-auth/src/main/java/io/smallrye/jwt/auth/principal/KeyLocationResolver.java index adea6859..6922bbed 100644 --- a/implementation/jwt-auth/src/main/java/io/smallrye/jwt/auth/principal/KeyLocationResolver.java +++ b/implementation/jwt-auth/src/main/java/io/smallrye/jwt/auth/principal/KeyLocationResolver.java @@ -16,6 +16,7 @@ */ package io.smallrye.jwt.auth.principal; +import java.io.IOException; import java.security.Key; import java.security.PublicKey; import java.security.cert.X509Certificate; @@ -26,6 +27,7 @@ import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwx.JsonWebStructure; import org.jose4j.keys.resolvers.VerificationKeyResolver; +import org.jose4j.lang.JoseException; import org.jose4j.lang.UnresolvableKeyException; import io.smallrye.jwt.KeyFormat; @@ -63,6 +65,14 @@ public Key resolveKey(JsonWebSignature jws, List nestingContex Key theKey = tryAsVerificationJwk(jws); if (theKey == null) { + try { + if (httpsJwks != null && httpsJwks.getJsonWebKeys() != null && jws != null + && jws.getKeyIdHeaderValue() != null) { + throw PrincipalMessages.msg.unmatchedTokenKidException(); + } + } catch (JoseException | IOException e) { + // ignore, if JWK is unavailable this was logged previously + } reportUnresolvableKeyException(authContextInfo.getPublicKeyContent(), authContextInfo.getPublicKeyLocation()); } return theKey; diff --git a/implementation/jwt-auth/src/main/java/io/smallrye/jwt/auth/principal/PrincipalMessages.java b/implementation/jwt-auth/src/main/java/io/smallrye/jwt/auth/principal/PrincipalMessages.java index 57944558..a75c74b5 100644 --- a/implementation/jwt-auth/src/main/java/io/smallrye/jwt/auth/principal/PrincipalMessages.java +++ b/implementation/jwt-auth/src/main/java/io/smallrye/jwt/auth/principal/PrincipalMessages.java @@ -80,4 +80,8 @@ interface PrincipalMessages { @Message(id = 7020, value = "Required key identifier is null") UnresolvableKeyException nullKeyIdentifier(); + + @Message(id = 7021, value = "JWK set does not contain provided token 'kid'") + UnmatchedTokenKidException unmatchedTokenKidException(); + } \ No newline at end of file diff --git a/implementation/jwt-auth/src/main/java/io/smallrye/jwt/auth/principal/UnmatchedTokenKidException.java b/implementation/jwt-auth/src/main/java/io/smallrye/jwt/auth/principal/UnmatchedTokenKidException.java new file mode 100644 index 00000000..49c14117 --- /dev/null +++ b/implementation/jwt-auth/src/main/java/io/smallrye/jwt/auth/principal/UnmatchedTokenKidException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2022 Red Hat, Inc, and individual contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.smallrye.jwt.auth.principal; + +import org.jose4j.lang.UnresolvableKeyException; + +public class UnmatchedTokenKidException extends UnresolvableKeyException { + + private static final long serialVersionUID = 1L; + + public UnmatchedTokenKidException(String message) { + super(message); + } + + public UnmatchedTokenKidException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/implementation/jwt-http-mechanism/src/main/java/io/smallrye/jwt/auth/mechanism/JWTHttpAuthenticationMechanism.java b/implementation/jwt-http-mechanism/src/main/java/io/smallrye/jwt/auth/mechanism/JWTHttpAuthenticationMechanism.java index a5e8afda..3f0de763 100644 --- a/implementation/jwt-http-mechanism/src/main/java/io/smallrye/jwt/auth/mechanism/JWTHttpAuthenticationMechanism.java +++ b/implementation/jwt-http-mechanism/src/main/java/io/smallrye/jwt/auth/mechanism/JWTHttpAuthenticationMechanism.java @@ -41,6 +41,7 @@ import io.smallrye.jwt.auth.principal.JWTAuthContextInfo; import io.smallrye.jwt.auth.principal.JWTParser; import io.smallrye.jwt.auth.principal.ParseException; +import io.smallrye.jwt.auth.principal.UnmatchedTokenKidException; /** * A JAX-RS HttpAuthenticationMechanism prototype @@ -97,7 +98,10 @@ public AuthenticationStatus validateRequest(HttpServletRequest request, MechanismLogging.log.success(); return httpMessageContext.notifyContainerAboutLogin(jwtPrincipal, groups); } catch (ParseException e) { - if (e.getCause() instanceof UnresolvableKeyException) { + if (e.getCause() instanceof UnmatchedTokenKidException) { + MechanismLogging.log.kidNotInJWkSet(); + return httpMessageContext.responseUnauthorized(); + } else if (e.getCause() instanceof UnresolvableKeyException) { MechanismLogging.log.noUsableKey(); return reportInternalError(httpMessageContext); } else { @@ -158,4 +162,4 @@ protected String getCookieValue(String cookieName) { return null; } } -} \ No newline at end of file +} diff --git a/implementation/jwt-http-mechanism/src/main/java/io/smallrye/jwt/auth/mechanism/MechanismLogging.java b/implementation/jwt-http-mechanism/src/main/java/io/smallrye/jwt/auth/mechanism/MechanismLogging.java index 9927179a..40d7bf72 100644 --- a/implementation/jwt-http-mechanism/src/main/java/io/smallrye/jwt/auth/mechanism/MechanismLogging.java +++ b/implementation/jwt-http-mechanism/src/main/java/io/smallrye/jwt/auth/mechanism/MechanismLogging.java @@ -26,4 +26,8 @@ interface MechanismLogging extends BasicLogger { @LogMessage(level = Logger.Level.DEBUG) @Message(id = 11003, value = "Failed to resolve the key. Either corrupt or unavailable.") void noUsableKey(); + + @LogMessage(level = Logger.Level.DEBUG) + @Message(id = 11004, value = "JWK set does not contain provided token 'kid'") + void kidNotInJWkSet(); }