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.openid.connect.sdk.validators;
019
020
021import java.util.List;
022import java.util.Map;
023
024import net.jcip.annotations.ThreadSafe;
025
026import com.nimbusds.jose.proc.SecurityContext;
027import com.nimbusds.jwt.JWTClaimsSet;
028import com.nimbusds.jwt.proc.BadJWTException;
029import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
030import com.nimbusds.oauth2.sdk.id.ClientID;
031import com.nimbusds.oauth2.sdk.id.Issuer;
032import com.nimbusds.openid.connect.sdk.claims.LogoutTokenClaimsSet;
033
034
035/**
036 * ID token claims verifier.
037 *
038 * <p>Related specifications:
039 *
040 * <ul>
041 *     <li>OpenID Connect Back-Channel Logout 1.0, section 2.6 (draft 07).
042 * </ul>
043 */
044@ThreadSafe
045public class LogoutTokenClaimsVerifier implements JWTClaimsSetVerifier {
046        
047        
048        /**
049         * The expected logout token issuer.
050         */
051        private final Issuer expectedIssuer;
052
053
054        /**
055         * The requesting client.
056         */
057        private final ClientID expectedClientID;
058        
059        
060        /**
061         * Creates a new logout token claims verifier.
062         *
063         * @param issuer   The expected ID token issuer. Must not be
064         *                 {@code null}.
065         * @param clientID The client ID. Must not be {@code null}. or positive
066         *                 integer.
067         */
068        public LogoutTokenClaimsVerifier(final Issuer issuer,
069                                         final ClientID clientID) {
070                
071                if (issuer == null) {
072                        throw new IllegalArgumentException("The expected ID token issuer must not be null");
073                }
074                this.expectedIssuer = issuer;
075                
076                if (clientID == null) {
077                        throw new IllegalArgumentException("The client ID must not be null");
078                }
079                this.expectedClientID = clientID;
080        }
081        
082        
083        /**
084         * Returns the expected ID token issuer.
085         *
086         * @return The ID token issuer.
087         */
088        public Issuer getExpectedIssuer() {
089                
090                return expectedIssuer;
091        }
092        
093        
094        /**
095         * Returns the client ID for verifying the ID token audience.
096         *
097         * @return The client ID.
098         */
099        public ClientID getClientID() {
100                
101                return expectedClientID;
102        }
103        
104        
105        @Override
106        public void verify(final JWTClaimsSet claimsSet, final SecurityContext ctx)
107                throws BadJWTException {
108                
109                // See http://openid.net/specs/openid-connect-backchannel-1_0-ID1.html#Validation
110                
111                // Check event type
112                try {
113                        Map<String, Object> events = claimsSet.getJSONObjectClaim("events");
114                        
115                        if (events == null) {
116                                throw new BadJWTException("Missing JWT events (events) claim");
117                        }
118                        
119                        if (com.nimbusds.jose.util.JSONObjectUtils.getJSONObject(events, LogoutTokenClaimsSet.EVENT_TYPE) == null) {
120                                throw new BadJWTException("Missing event type, required " + LogoutTokenClaimsSet.EVENT_TYPE);
121                        }
122                        
123                } catch (java.text.ParseException e) {
124                        throw new BadJWTException("Invalid JWT events (events) claim", e);
125                }
126                
127                
128                // Check required claims, match them with expected where needed
129                
130                final String tokenIssuer = claimsSet.getIssuer();
131                
132                if (tokenIssuer == null) {
133                        throw BadJWTExceptions.MISSING_ISS_CLAIM_EXCEPTION;
134                }
135                
136                if (! getExpectedIssuer().getValue().equals(tokenIssuer)) {
137                        throw new BadJWTException("Unexpected JWT issuer: " + tokenIssuer);
138                }
139                
140                final List<String> tokenAudience = claimsSet.getAudience();
141                
142                if (tokenAudience == null || tokenAudience.isEmpty()) {
143                        throw BadJWTExceptions.MISSING_AUD_CLAIM_EXCEPTION;
144                }
145                
146                if (! tokenAudience.contains(expectedClientID.getValue())) {
147                        throw new BadJWTException("Unexpected JWT audience: " + tokenAudience);
148                }
149                
150                
151                if (claimsSet.getIssueTime() == null) {
152                        throw BadJWTExceptions.MISSING_IAT_CLAIM_EXCEPTION;
153                }
154                
155                if (claimsSet.getJWTID() == null) {
156                        throw new BadJWTException("Missing JWT ID (jti) claim");
157                }
158                
159                
160                // Either sub or sid must be present
161                try {
162                        if (claimsSet.getSubject() == null && claimsSet.getStringClaim("sid") == null) {
163                                throw new BadJWTException("Missing subject (sub) and / or session ID (sid) claim(s)");
164                        }
165                        
166                } catch (java.text.ParseException e) {
167                        throw new BadJWTException("Invalid session ID (sid) claim");
168                }
169                
170                // Nonce illegal
171                if (claimsSet.getClaim("nonce") != null) {
172                        throw new BadJWTException("Found illegal nonce (nonce) claim");
173                }
174        }
175}