001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk.assertions.jwt;
019
020
021import java.util.Set;
022
023import com.nimbusds.jwt.JWTClaimsSet;
024import com.nimbusds.jwt.proc.BadJWTException;
025import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
026import com.nimbusds.oauth2.sdk.id.Audience;
027import net.jcip.annotations.Immutable;
028import org.apache.commons.collections4.CollectionUtils;
029
030
031/**
032 * JSON Web Token (JWT) bearer assertion details (claims set) verifier for
033 * OAuth 2.0 client authentication and authorisation grants. Intended for
034 * initial validation of JWT assertions:
035 *
036 * <ul>
037 *     <li>Audience check
038 *     <li>Expiration time check
039 *     <li>Not-before time check (is set)
040 *     <li>Subject and issuer presence check
041 * </ul>
042 *
043 * <p>Related specifications:
044 *
045 * <ul>
046 *     <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and
047 *         Authorization Grants (RFC 7523).
048 * </ul>
049 */
050@Immutable
051public class JWTAssertionDetailsVerifier extends DefaultJWTClaimsVerifier {
052
053
054        // Cache JWT exceptions for quick processing of bad claims sets
055
056
057        /**
058         * Missing JWT expiration claim.
059         */
060        private static final BadJWTException MISSING_EXP_CLAIM_EXCEPTION =
061                new BadJWTException("Missing JWT expiration claim");
062
063
064        /**
065         * Missing JWT audience claim.
066         */
067        private static final BadJWTException MISSING_AUD_CLAIM_EXCEPTION =
068                new BadJWTException("Missing JWT audience claim");
069
070
071        /**
072         * Missing JWT subject claim.
073         */
074        private static final BadJWTException MISSING_SUB_CLAIM_EXCEPTION =
075                new BadJWTException("Missing JWT subject claim");
076
077
078        /**
079         * Missing JWT issuer claim.
080         */
081        private static final BadJWTException MISSING_ISS_CLAIM_EXCEPTION =
082                new BadJWTException("Missing JWT issuer claim");
083
084
085        /**
086         * The expected audience.
087         */
088        private final Set<Audience> expectedAudience;
089
090
091        /**
092         * Cached unexpected JWT audience claim exception.
093         */
094        private final BadJWTException unexpectedAudClaimException;
095
096
097        /**
098         * Creates a new JWT bearer assertion details (claims set) verifier.
099         *
100         * @param expectedAudience The expected audience (aud) claim values.
101         *                         Must not be empty or {@code null}. Should
102         *                         typically contain the token endpoint URI and
103         *                         for OpenID provider it may also include the
104         *                         issuer URI.
105         */
106        public JWTAssertionDetailsVerifier(final Set<Audience> expectedAudience) {
107
108                if (CollectionUtils.isEmpty(expectedAudience)) {
109                        throw new IllegalArgumentException("The expected audience set must not be null or empty");
110                }
111
112                this.expectedAudience = expectedAudience;
113
114                unexpectedAudClaimException = new BadJWTException("Invalid JWT audience claim, expected " + expectedAudience);
115        }
116
117
118        /**
119         * Returns the expected audience values.
120         *
121         * @return The expected audience (aud) claim values.
122         */
123        public Set<Audience> getExpectedAudience() {
124
125                return expectedAudience;
126        }
127
128
129        @Override
130        public void verify(final JWTClaimsSet claimsSet)
131                throws BadJWTException {
132
133                super.verify(claimsSet);
134
135                if (claimsSet.getExpirationTime() == null) {
136                        throw MISSING_EXP_CLAIM_EXCEPTION;
137                }
138
139                if (claimsSet.getAudience() == null || claimsSet.getAudience().isEmpty()) {
140                        throw MISSING_AUD_CLAIM_EXCEPTION;
141                }
142
143                boolean audMatch = false;
144
145                for (String aud: claimsSet.getAudience()) {
146
147                        if (aud == null || aud.isEmpty()) {
148                                continue; // skip
149                        }
150
151                        if (expectedAudience.contains(new Audience(aud))) {
152                                audMatch = true;
153                        }
154                }
155
156                if (! audMatch) {
157                        throw unexpectedAudClaimException;
158                }
159
160                if (claimsSet.getIssuer() == null) {
161                        throw MISSING_ISS_CLAIM_EXCEPTION;
162                }
163
164                if (claimsSet.getSubject() == null) {
165                        throw MISSING_SUB_CLAIM_EXCEPTION;
166                }
167        }
168}