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