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