001package com.nimbusds.oauth2.sdk.assertions.saml2; 002 003 004import java.util.Date; 005import java.util.Set; 006 007import com.nimbusds.jwt.proc.ClockSkewAware; 008import com.nimbusds.jwt.util.DateUtils; 009import com.nimbusds.oauth2.sdk.id.Audience; 010import net.jcip.annotations.Immutable; 011import org.apache.commons.collections4.CollectionUtils; 012 013 014/** 015 * SAML 2.0 bearer assertion details verifier for OAuth 2.0 client 016 * authentication and authorisation grants. Intended for initial validation of 017 * SAML 2.0 assertions: 018 * 019 * <ul> 020 * <li>Audience check 021 * <li>Expiration time check 022 * <li>Not-before time check (is set) 023 * </ul> 024 * 025 * <p>Related specifications: 026 * 027 * <ul> 028 * <li>Security Assertion Markup Language (SAML) 2.0 Profile for OAuth 2.0 029 * Client Authentication and Authorization Grants (RFC 7522). 030 * </ul> 031 */ 032@Immutable 033public class SAML2AssertionDetailsVerifier implements ClockSkewAware { 034 035 036 /** 037 * The default maximum acceptable clock skew, in seconds (60). 038 */ 039 public static final int DEFAULT_MAX_CLOCK_SKEW_SECONDS = 60; 040 041 042 // Cache SAML exceptions to speed up processing 043 044 045 /** 046 * Expired SAML 2.0 assertion exception. 047 */ 048 private static final BadSAML2AssertionException EXPIRED_SAML2_ASSERTION_EXCEPTION = 049 new BadSAML2AssertionException("Expired SAML 2.0 assertion"); 050 051 052 /** 053 * SAML 2.0 assertion before use time. 054 */ 055 private static final BadSAML2AssertionException SAML2_ASSERTION_BEFORE_USE_EXCEPTION = 056 new BadSAML2AssertionException("SAML 2.0 assertion before use time"); 057 058 059 /** 060 * The expected audience. 061 */ 062 private final Set<Audience> expectedAudience; 063 064 065 /** 066 * Cached unexpected SAML 2.0 audience exception. 067 */ 068 private final BadSAML2AssertionException unexpectedAudienceException; 069 070 071 /** 072 * The maximum acceptable clock skew, in seconds. 073 */ 074 private int maxClockSkewSeconds = DEFAULT_MAX_CLOCK_SKEW_SECONDS; 075 076 077 /** 078 * Creates a new SAML 2.0 bearer assertion details verifier. 079 * 080 * @param expectedAudience The expected audience values. Must not be 081 * empty or {@code null}. Should typically 082 * contain the token endpoint URI and for 083 * OpenID provider it may also include the 084 * issuer URI. 085 */ 086 public SAML2AssertionDetailsVerifier(final Set<Audience> expectedAudience) { 087 if (CollectionUtils.isEmpty(expectedAudience)) { 088 throw new IllegalArgumentException("The expected audience set must not be null or empty"); 089 } 090 091 this.expectedAudience = expectedAudience; 092 093 unexpectedAudienceException = new BadSAML2AssertionException("Invalid SAML 2.0 audience, expected " + expectedAudience); 094 } 095 096 097 /** 098 * Returns the expected audience values. 099 * 100 * @return The expected audience values. 101 */ 102 public Set<Audience> getExpectedAudience() { 103 return expectedAudience; 104 } 105 106 107 @Override 108 public int getMaxClockSkew() { 109 return maxClockSkewSeconds; 110 } 111 112 113 @Override 114 public void setMaxClockSkew(int maxClockSkewSeconds) { 115 this.maxClockSkewSeconds = maxClockSkewSeconds; 116 } 117 118 119 /** 120 * Verifies the specified SAML 2.0 bearer assertion details. 121 * 122 * @param assertionDetails The SAML 2.0 bearer assertion details. Must 123 * not be {@code null}. 124 * 125 * @throws BadSAML2AssertionException If verification didn't pass 126 * successfully. 127 */ 128 public void verify(final SAML2AssertionDetails assertionDetails) 129 throws BadSAML2AssertionException { 130 131 // Check audience 132 if (! Audience.matchesAny(expectedAudience, assertionDetails.getAudience())) { 133 throw unexpectedAudienceException; 134 } 135 136 // Check expiration 137 final Date now = new Date(); 138 139 if (! DateUtils.isAfter(assertionDetails.getExpirationTime(), now, maxClockSkewSeconds)) { 140 throw EXPIRED_SAML2_ASSERTION_EXCEPTION; 141 } 142 143 // Check optional not before use time 144 if (assertionDetails.getNotBeforeTime() != null) { 145 if (! DateUtils.isBefore(assertionDetails.getNotBeforeTime(), now, maxClockSkewSeconds)) 146 throw SAML2_ASSERTION_BEFORE_USE_EXCEPTION; 147 } 148 } 149}