001package com.nimbusds.oauth2.sdk.assertions.jwt;
002
003
004import java.util.Set;
005
006import net.jcip.annotations.Immutable;
007
008import com.nimbusds.jwt.JWTClaimsSet;
009import com.nimbusds.jwt.proc.BadJWTException;
010import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
011
012import com.nimbusds.oauth2.sdk.id.Audience;
013
014
015/**
016 * JSON Web Token (JWT) bearer assertion claims set verifier for OAuth 2.0
017 * client authentication and authorisation grants.
018 *
019 * <p>Related specifications:
020 *
021 * <ul>
022 *     <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and
023 *         Authorization Grants (RFC 7523).
024 * </ul>
025 */
026@Immutable
027public class JWTAssertionClaimsSetVerifier extends DefaultJWTClaimsVerifier {
028
029
030        // Cache JWT exceptions for quick processing of bad claims sets
031
032
033        /**
034         * Missing JWT expiration claim.
035         */
036        private static BadJWTException MISSING_EXP_CLAIM_EXCEPTION =
037                new BadJWTException("Missing JWT expiration claim");
038
039
040        /**
041         * Missing JWT audience claim.
042         */
043        private static BadJWTException MISSING_AUD_CLAIM_EXCEPTION =
044                new BadJWTException("Missing JWT audience claim");
045
046
047        /**
048         * Missing JWT subject claim.
049         */
050        private static BadJWTException MISSING_SUB_CLAIM_EXCEPTION =
051                new BadJWTException("Missing JWT subject claim");
052
053
054        /**
055         * Missing JWT issuer claim.
056         */
057        private static BadJWTException MISSING_ISS_CLAIM_EXCEPTION =
058                new BadJWTException("Missing JWT issuer claim");
059
060
061        /**
062         * The expected audience.
063         */
064        private final Set<Audience> expectedAudience;
065
066
067        /**
068         * Cached unexpected JWT audience claim exception.
069         */
070        private final BadJWTException unexpectedAudClaimException;
071
072
073        /**
074         * Creates a new JWT bearer assertion claims set verifier.
075         *
076         * @param expectedAudience The permitted audience (aud) claim values.
077         *                         Must not be empty or {@code null}. Should
078         *                         typically contain the token endpoint URI and
079         *                         for OpenID provider it may also include the
080         *                         issuer URI.
081         */
082        public JWTAssertionClaimsSetVerifier(final Set<Audience> expectedAudience) {
083
084                if (expectedAudience == null || expectedAudience.isEmpty()) {
085                        throw new IllegalArgumentException("The expected audience set must not be null or empty");
086                }
087
088                this.expectedAudience = expectedAudience;
089
090                unexpectedAudClaimException = new BadJWTException("Invalid JWT audience claim, expected " + expectedAudience);
091        }
092
093
094        /**
095         * Returns the permitted audience values.
096         *
097         * @return The permitted audience (aud) claim values.
098         */
099        public Set<Audience> getExpectedAudience() {
100
101                return expectedAudience;
102        }
103
104
105        @Override
106        public void verify(final JWTClaimsSet claimsSet)
107                throws BadJWTException {
108
109                super.verify(claimsSet);
110
111                if (claimsSet.getExpirationTime() == null) {
112                        throw MISSING_EXP_CLAIM_EXCEPTION;
113                }
114
115                if (claimsSet.getAudience() == null || claimsSet.getAudience().isEmpty()) {
116                        throw MISSING_AUD_CLAIM_EXCEPTION;
117                }
118
119                boolean audMatch = false;
120
121                for (String aud: claimsSet.getAudience()) {
122
123                        if (aud == null || aud.isEmpty()) {
124                                continue; // skip
125                        }
126
127                        if (expectedAudience.contains(new Audience(aud))) {
128                                audMatch = true;
129                        }
130                }
131
132                if (! audMatch) {
133                        throw unexpectedAudClaimException;
134                }
135
136                if (claimsSet.getIssuer() == null) {
137                        throw MISSING_ISS_CLAIM_EXCEPTION;
138                }
139
140                if (claimsSet.getSubject() == null) {
141                        throw MISSING_SUB_CLAIM_EXCEPTION;
142                }
143        }
144}