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.jarm;
019
020
021import java.util.Date;
022import java.util.List;
023
024import com.nimbusds.jose.proc.SecurityContext;
025import com.nimbusds.jwt.JWTClaimsSet;
026import com.nimbusds.jwt.proc.BadJWTException;
027import com.nimbusds.jwt.proc.ClockSkewAware;
028import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
029import com.nimbusds.jwt.util.DateUtils;
030import com.nimbusds.oauth2.sdk.id.ClientID;
031import com.nimbusds.oauth2.sdk.id.Issuer;
032import com.nimbusds.openid.connect.sdk.validators.BadJWTExceptions;
033import net.jcip.annotations.ThreadSafe;
034
035
036/**
037 * JSON Web Token (JWT) encoded authorisation response claims verifier.
038 *
039 * <p>Related specifications:
040 *
041 * <ul>
042 *     <li>Financial-grade API: JWT Secured Authorization Response Mode for
043 *         OAuth 2.0 (JARM).
044 * </ul>
045 */
046@ThreadSafe
047public class JARMClaimsVerifier implements JWTClaimsSetVerifier, ClockSkewAware {
048        
049        
050        /**
051         * The expected Authorisation Server.
052         */
053        private final Issuer expectedIssuer;
054        
055        
056        /**
057         * The requesting client (for the JWT audience).
058         */
059        private final ClientID expectedClientID;
060        
061        
062        /**
063         * The maximum acceptable clock skew, in seconds.
064         */
065        private int maxClockSkew;
066        
067        
068        /**
069         * Creates a new ID token claims verifier.
070         *
071         * @param issuer       The expected Authorisation Server. Must not be
072         *                     {@code null}.
073         * @param clientID     The client ID. Must not be {@code null}.
074         * @param maxClockSkew The maximum acceptable clock skew (absolute
075         *                     value), in seconds. Must be zero (no clock skew)
076         *                     or positive integer.
077         */
078        public JARMClaimsVerifier(final Issuer issuer,
079                                  final ClientID clientID,
080                                  final int maxClockSkew) {
081                
082                if (issuer == null) {
083                        throw new IllegalArgumentException("The expected ID token issuer must not be null");
084                }
085                this.expectedIssuer = issuer;
086                
087                if (clientID == null) {
088                        throw new IllegalArgumentException("The client ID must not be null");
089                }
090                this.expectedClientID = clientID;
091                
092                setMaxClockSkew(maxClockSkew);
093        }
094        
095        
096        /**
097         * Returns the expected Authorisation Server.
098         *
099         * @return The Authorisation Server issuer.
100         */
101        public Issuer getExpectedIssuer() {
102                
103                return expectedIssuer;
104        }
105        
106        
107        /**
108         * Returns the client ID for verifying the JWT audience.
109         *
110         * @return The client ID.
111         */
112        public ClientID getClientID() {
113                
114                return expectedClientID;
115        }
116        
117        
118        @Override
119        public int getMaxClockSkew() {
120                
121                return maxClockSkew;
122        }
123        
124        
125        @Override
126        public void setMaxClockSkew(final int maxClockSkew) {
127                if (maxClockSkew < 0) {
128                        throw new IllegalArgumentException("The max clock skew must be zero or positive");
129                }
130                this.maxClockSkew = maxClockSkew;
131        }
132        
133        
134        @Override
135        public void verify(final JWTClaimsSet claimsSet, final SecurityContext ctx)
136                throws BadJWTException {
137                
138                // See http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
139                
140                final String tokenIssuer = claimsSet.getIssuer();
141                
142                if (tokenIssuer == null) {
143                        throw BadJWTExceptions.MISSING_ISS_CLAIM_EXCEPTION;
144                }
145                
146                if (! expectedIssuer.getValue().equals(tokenIssuer)) {
147                        throw new BadJWTException("Unexpected JWT issuer: " + tokenIssuer);
148                }
149                
150                final List<String> tokenAudience = claimsSet.getAudience();
151                
152                if (tokenAudience == null || tokenAudience.isEmpty()) {
153                        throw BadJWTExceptions.MISSING_AUD_CLAIM_EXCEPTION;
154                }
155                
156                if (! tokenAudience.contains(expectedClientID.getValue())) {
157                        throw new BadJWTException("Unexpected JWT audience: " + tokenAudience);
158                }
159                
160                final Date exp = claimsSet.getExpirationTime();
161                
162                if (exp == null) {
163                        throw BadJWTExceptions.MISSING_EXP_CLAIM_EXCEPTION;
164                }
165                
166                final Date nowRef = new Date();
167                
168                // Expiration must be after current time, given acceptable clock skew
169                if (! DateUtils.isAfter(exp, nowRef, maxClockSkew)) {
170                        throw BadJWTExceptions.EXPIRED_EXCEPTION;
171                }
172        }
173}