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}