|  | @@ -25,13 +25,31 @@ import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMap
 | 
	
		
			
				|  |  |  import org.springframework.security.saml2.Saml2Exception;
 | 
	
		
			
				|  |  |  import org.springframework.security.saml2.credentials.Saml2X509Credential;
 | 
	
		
			
				|  |  |  import org.springframework.util.Assert;
 | 
	
		
			
				|  |  | +import org.springframework.util.StringUtils;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +import java.security.cert.X509Certificate;
 | 
	
		
			
				|  |  | +import java.time.Duration;
 | 
	
		
			
				|  |  | +import java.util.ArrayList;
 | 
	
		
			
				|  |  | +import java.util.Collection;
 | 
	
		
			
				|  |  | +import java.util.Collections;
 | 
	
		
			
				|  |  | +import java.util.HashMap;
 | 
	
		
			
				|  |  | +import java.util.HashSet;
 | 
	
		
			
				|  |  | +import java.util.LinkedList;
 | 
	
		
			
				|  |  | +import java.util.List;
 | 
	
		
			
				|  |  | +import java.util.Map;
 | 
	
		
			
				|  |  | +import java.util.Set;
 | 
	
		
			
				|  |  | +import javax.annotation.Nonnull;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
 | 
	
		
			
				|  |  |  import org.apache.commons.logging.Log;
 | 
	
		
			
				|  |  |  import org.apache.commons.logging.LogFactory;
 | 
	
		
			
				|  |  | -import org.opensaml.saml.common.SignableSAMLObject;
 | 
	
		
			
				|  |  | -import org.opensaml.saml.common.assertion.AssertionValidationException;
 | 
	
		
			
				|  |  | +import org.opensaml.core.criterion.EntityIdCriterion;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  import org.opensaml.saml.common.assertion.ValidationContext;
 | 
	
		
			
				|  |  |  import org.opensaml.saml.common.assertion.ValidationResult;
 | 
	
		
			
				|  |  | +import org.opensaml.saml.common.xml.SAMLConstants;
 | 
	
		
			
				|  |  | +import org.opensaml.saml.criterion.ProtocolCriterion;
 | 
	
		
			
				|  |  | +import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion;
 | 
	
		
			
				|  |  |  import org.opensaml.saml.saml2.assertion.ConditionValidator;
 | 
	
		
			
				|  |  |  import org.opensaml.saml.saml2.assertion.SAML20AssertionValidator;
 | 
	
		
			
				|  |  |  import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters;
 | 
	
	
		
			
				|  | @@ -51,40 +69,36 @@ import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
 | 
	
		
			
				|  |  |  import org.opensaml.security.credential.Credential;
 | 
	
		
			
				|  |  |  import org.opensaml.security.credential.CredentialResolver;
 | 
	
		
			
				|  |  |  import org.opensaml.security.credential.CredentialSupport;
 | 
	
		
			
				|  |  | +import org.opensaml.security.credential.UsageType;
 | 
	
		
			
				|  |  | +import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion;
 | 
	
		
			
				|  |  | +import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion;
 | 
	
		
			
				|  |  |  import org.opensaml.security.credential.impl.CollectionCredentialResolver;
 | 
	
		
			
				|  |  | +import org.opensaml.security.criteria.UsageCriterion;
 | 
	
		
			
				|  |  | +import org.opensaml.security.x509.BasicX509Credential;
 | 
	
		
			
				|  |  |  import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
 | 
	
		
			
				|  |  |  import org.opensaml.xmlsec.encryption.support.DecryptionException;
 | 
	
		
			
				|  |  |  import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
 | 
	
		
			
				|  |  |  import org.opensaml.xmlsec.keyinfo.impl.StaticKeyInfoCredentialResolver;
 | 
	
		
			
				|  |  | -import org.opensaml.xmlsec.signature.support.SignatureException;
 | 
	
		
			
				|  |  |  import org.opensaml.xmlsec.signature.support.SignaturePrevalidator;
 | 
	
		
			
				|  |  |  import org.opensaml.xmlsec.signature.support.SignatureTrustEngine;
 | 
	
		
			
				|  |  | -import org.opensaml.xmlsec.signature.support.SignatureValidator;
 | 
	
		
			
				|  |  |  import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -import java.security.cert.X509Certificate;
 | 
	
		
			
				|  |  | -import java.time.Duration;
 | 
	
		
			
				|  |  | -import java.util.Collection;
 | 
	
		
			
				|  |  | -import java.util.Collections;
 | 
	
		
			
				|  |  | -import java.util.HashMap;
 | 
	
		
			
				|  |  | -import java.util.HashSet;
 | 
	
		
			
				|  |  | -import java.util.LinkedList;
 | 
	
		
			
				|  |  | -import java.util.List;
 | 
	
		
			
				|  |  | -import java.util.Map;
 | 
	
		
			
				|  |  | -import java.util.Set;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -import static java.lang.String.format;
 | 
	
		
			
				|  |  |  import static java.util.Collections.singleton;
 | 
	
		
			
				|  |  |  import static java.util.Collections.singletonList;
 | 
	
		
			
				|  |  | +import static org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.CLOCK_SKEW;
 | 
	
		
			
				|  |  | +import static org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.COND_VALID_AUDIENCES;
 | 
	
		
			
				|  |  | +import static org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.SIGNATURE_REQUIRED;
 | 
	
		
			
				|  |  |  import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.DECRYPTION_ERROR;
 | 
	
		
			
				|  |  | +import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR;
 | 
	
		
			
				|  |  | +import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.INVALID_ASSERTION;
 | 
	
		
			
				|  |  |  import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.INVALID_DESTINATION;
 | 
	
		
			
				|  |  |  import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.INVALID_ISSUER;
 | 
	
		
			
				|  |  | +import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.INVALID_SIGNATURE;
 | 
	
		
			
				|  |  |  import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.MALFORMED_RESPONSE_DATA;
 | 
	
		
			
				|  |  |  import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.SUBJECT_NOT_FOUND;
 | 
	
		
			
				|  |  |  import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.UNKNOWN_RESPONSE_CLASS;
 | 
	
		
			
				|  |  |  import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.USERNAME_NOT_FOUND;
 | 
	
		
			
				|  |  |  import static org.springframework.util.Assert.notNull;
 | 
	
		
			
				|  |  | -import static org.springframework.util.StringUtils.hasText;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  |   * Implementation of {@link AuthenticationProvider} for SAML authentications when receiving a
 | 
	
	
		
			
				|  | @@ -126,6 +140,20 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	private static Log logger = LogFactory.getLog(OpenSamlAuthenticationProvider.class);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +	private final List<ConditionValidator> conditions = Collections.singletonList(new AudienceRestrictionConditionValidator());
 | 
	
		
			
				|  |  | +	private final SubjectConfirmationValidator subjectConfirmationValidator = new BearerSubjectConfirmationValidator() {
 | 
	
		
			
				|  |  | +		@Nonnull
 | 
	
		
			
				|  |  | +		@Override
 | 
	
		
			
				|  |  | +		protected ValidationResult validateAddress(@Nonnull SubjectConfirmation confirmation,
 | 
	
		
			
				|  |  | +				@Nonnull Assertion assertion, @Nonnull ValidationContext context) {
 | 
	
		
			
				|  |  | +			// skipping address validation - gh-7514
 | 
	
		
			
				|  |  | +			return ValidationResult.VALID;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	};
 | 
	
		
			
				|  |  | +	private final List<SubjectConfirmationValidator> subjects = Collections.singletonList(this.subjectConfirmationValidator);
 | 
	
		
			
				|  |  | +	private final List<StatementValidator> statements = Collections.emptyList();
 | 
	
		
			
				|  |  | +	private final SignaturePrevalidator signaturePrevalidator = new SAMLSignatureProfileValidator();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	private final OpenSamlImplementation saml = OpenSamlImplementation.getInstance();
 | 
	
		
			
				|  |  |  	private Converter<Assertion, Collection<? extends GrantedAuthority>> authoritiesExtractor =
 | 
	
		
			
				|  |  |  			(a -> singletonList(new SimpleGrantedAuthority("ROLE_USER")));
 | 
	
	
		
			
				|  | @@ -174,17 +202,17 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 | 
	
		
			
				|  |  |  	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 | 
	
		
			
				|  |  |  		try {
 | 
	
		
			
				|  |  |  			Saml2AuthenticationToken token = (Saml2AuthenticationToken) authentication;
 | 
	
		
			
				|  |  | -			Response samlResponse = getSaml2Response(token);
 | 
	
		
			
				|  |  | -			Assertion assertion = validateSaml2Response(token, token.getRecipientUri(), samlResponse);
 | 
	
		
			
				|  |  | +			Response response = parse(token.getSaml2Response());
 | 
	
		
			
				|  |  | +			List<Assertion> validAssertions = validateResponse(token, response);
 | 
	
		
			
				|  |  | +			Assertion assertion = validAssertions.get(0);
 | 
	
		
			
				|  |  |  			String username = getUsername(token, assertion);
 | 
	
		
			
				|  |  |  			return new Saml2Authentication(
 | 
	
		
			
				|  |  |  					new SimpleSaml2AuthenticatedPrincipal(username), token.getSaml2Response(),
 | 
	
		
			
				|  |  | -					this.authoritiesMapper.mapAuthorities(getAssertionAuthorities(assertion))
 | 
	
		
			
				|  |  | -			);
 | 
	
		
			
				|  |  | +					this.authoritiesMapper.mapAuthorities(getAssertionAuthorities(assertion)));
 | 
	
		
			
				|  |  |  		} catch (Saml2AuthenticationException e) {
 | 
	
		
			
				|  |  |  			throw e;
 | 
	
		
			
				|  |  |  		} catch (Exception e) {
 | 
	
		
			
				|  |  | -			throw authException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, e.getMessage(), e);
 | 
	
		
			
				|  |  | +			throw authException(INTERNAL_VALIDATION_ERROR, e.getMessage(), e);
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -200,241 +228,187 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 | 
	
		
			
				|  |  |  		return this.authoritiesExtractor.convert(assertion);
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	private String getUsername(Saml2AuthenticationToken token, Assertion assertion) throws Saml2AuthenticationException {
 | 
	
		
			
				|  |  | -		String username = null;
 | 
	
		
			
				|  |  | -		Subject subject = assertion.getSubject();
 | 
	
		
			
				|  |  | -		if (subject == null) {
 | 
	
		
			
				|  |  | -			throw authException(SUBJECT_NOT_FOUND, "Assertion [" + assertion.getID() + "] is missing a subject");
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		if (subject.getNameID() != null) {
 | 
	
		
			
				|  |  | -			username = subject.getNameID().getValue();
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		else if (subject.getEncryptedID() != null) {
 | 
	
		
			
				|  |  | -			NameID nameId = decrypt(token, subject.getEncryptedID());
 | 
	
		
			
				|  |  | -			username = nameId.getValue();
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		if (username == null) {
 | 
	
		
			
				|  |  | -			throw authException(USERNAME_NOT_FOUND, "Assertion [" + assertion.getID() + "] is missing a user identifier");
 | 
	
		
			
				|  |  | +	private Response parse(String response) throws Saml2Exception, Saml2AuthenticationException {
 | 
	
		
			
				|  |  | +		try {
 | 
	
		
			
				|  |  | +			Object result = this.saml.resolve(response);
 | 
	
		
			
				|  |  | +			if (result instanceof Response) {
 | 
	
		
			
				|  |  | +				return (Response) result;
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +			else {
 | 
	
		
			
				|  |  | +				throw authException(UNKNOWN_RESPONSE_CLASS, "Invalid response class:" + result.getClass().getName());
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +		} catch (Saml2Exception x) {
 | 
	
		
			
				|  |  | +			throw authException(MALFORMED_RESPONSE_DATA, x.getMessage(), x);
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  | -		return username;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	private Assertion validateSaml2Response(Saml2AuthenticationToken token,
 | 
	
		
			
				|  |  | -											String recipient,
 | 
	
		
			
				|  |  | -											Response samlResponse) throws Saml2AuthenticationException {
 | 
	
		
			
				|  |  | -		//optional validation if the response contains a destination
 | 
	
		
			
				|  |  | -		if (hasText(samlResponse.getDestination()) && !recipient.equals(samlResponse.getDestination())) {
 | 
	
		
			
				|  |  | -			throw authException(INVALID_DESTINATION, "Invalid SAML response destination: " + samlResponse.getDestination());
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | +	private List<Assertion> validateResponse(Saml2AuthenticationToken token, Response response)
 | 
	
		
			
				|  |  | +			throws Saml2AuthenticationException {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		String issuer = samlResponse.getIssuer().getValue();
 | 
	
		
			
				|  |  | +		List<Assertion> validAssertions = new ArrayList<>();
 | 
	
		
			
				|  |  | +		String issuer = response.getIssuer().getValue();
 | 
	
		
			
				|  |  |  		if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  |  			logger.debug("Validating SAML response from " + issuer);
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  | -		if (!hasText(issuer) || (!issuer.equals(token.getIdpEntityId()))) {
 | 
	
		
			
				|  |  | -			String message = String.format("Response issuer '%s' doesn't match '%s'", issuer, token.getIdpEntityId());
 | 
	
		
			
				|  |  | -			throw authException(INVALID_ISSUER, message);
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		Saml2AuthenticationException lastValidationError = null;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		boolean responseSigned = hasValidSignature(samlResponse, token);
 | 
	
		
			
				|  |  | -		for (Assertion a : samlResponse.getAssertions()) {
 | 
	
		
			
				|  |  | -			if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | -				logger.debug("Checking plain assertion validity " + a);
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -			try {
 | 
	
		
			
				|  |  | -				validateAssertion(recipient, a, token, !responseSigned);
 | 
	
		
			
				|  |  | -				return a;
 | 
	
		
			
				|  |  | -			} catch (Saml2AuthenticationException e) {
 | 
	
		
			
				|  |  | -				lastValidationError = e;
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | +		List<Assertion> assertions = new ArrayList<>(response.getAssertions());
 | 
	
		
			
				|  |  | +		for (EncryptedAssertion encryptedAssertion : response.getEncryptedAssertions()) {
 | 
	
		
			
				|  |  | +			Assertion assertion = decrypt(token, encryptedAssertion);
 | 
	
		
			
				|  |  | +			assertions.add(assertion);
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  | -		for (EncryptedAssertion ea : samlResponse.getEncryptedAssertions()) {
 | 
	
		
			
				|  |  | -			if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | -				logger.debug("Checking encrypted assertion validity " + ea);
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -			try {
 | 
	
		
			
				|  |  | -				Assertion a = decrypt(token, ea);
 | 
	
		
			
				|  |  | -				validateAssertion(recipient, a, token, !responseSigned);
 | 
	
		
			
				|  |  | -				return a;
 | 
	
		
			
				|  |  | -			} catch (Saml2AuthenticationException e) {
 | 
	
		
			
				|  |  | -				lastValidationError = e;
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		if (lastValidationError != null) {
 | 
	
		
			
				|  |  | -			throw lastValidationError;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		else {
 | 
	
		
			
				|  |  | +		if (assertions.isEmpty()) {
 | 
	
		
			
				|  |  |  			throw authException(MALFORMED_RESPONSE_DATA, "No assertions found in response.");
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	private boolean hasValidSignature(SignableSAMLObject samlObject, Saml2AuthenticationToken token) {
 | 
	
		
			
				|  |  | -		if (!samlObject.isSigned()) {
 | 
	
		
			
				|  |  | -			if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | -				logger.debug("SAML object is not signed, no signatures found");
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -			return false;
 | 
	
		
			
				|  |  | +		if (!isSigned(response, assertions)) {
 | 
	
		
			
				|  |  | +			throw authException(INVALID_SIGNATURE, "Either the response or one of the assertions is unsigned. " +
 | 
	
		
			
				|  |  | +					"Please either sign the response or all of the assertions.");
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		List<X509Certificate> verificationKeys = getVerificationCertificates(token);
 | 
	
		
			
				|  |  | -		if (verificationKeys.isEmpty()) {
 | 
	
		
			
				|  |  | -			return false;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | +		SignatureTrustEngine signatureTrustEngine = buildSignatureTrustEngine(token);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		for (X509Certificate certificate : verificationKeys) {
 | 
	
		
			
				|  |  | -			Credential credential = getVerificationCredential(certificate);
 | 
	
		
			
				|  |  | +		Map<String, Saml2AuthenticationException> validationExceptions = new HashMap<>();
 | 
	
		
			
				|  |  | +		if (response.isSigned()) {
 | 
	
		
			
				|  |  | +			SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
 | 
	
		
			
				|  |  |  			try {
 | 
	
		
			
				|  |  | -				SignatureValidator.validate(samlObject.getSignature(), credential);
 | 
	
		
			
				|  |  | -				if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | -					logger.debug("Valid signature found in SAML object:"+samlObject.getClass().getName());
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				return true;
 | 
	
		
			
				|  |  | +				profileValidator.validate(response.getSignature());
 | 
	
		
			
				|  |  | +			} catch (Exception e) {
 | 
	
		
			
				|  |  | +				validationExceptions.put(INVALID_SIGNATURE, authException(INVALID_SIGNATURE,
 | 
	
		
			
				|  |  | +						"Invalid signature for SAML Response [" + response.getID() + "]", e));
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  | -			catch (SignatureException ignored) {
 | 
	
		
			
				|  |  | -				if (logger.isTraceEnabled()) {
 | 
	
		
			
				|  |  | -					logger.trace("Signature validation failed with cert:"+certificate.toString(), ignored);
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				else if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | -					logger.debug("Signature validation failed with cert:"+certificate.toString());
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			try {
 | 
	
		
			
				|  |  | +				CriteriaSet criteriaSet = new CriteriaSet();
 | 
	
		
			
				|  |  | +				criteriaSet.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer)));
 | 
	
		
			
				|  |  | +				criteriaSet.add(new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)));
 | 
	
		
			
				|  |  | +				criteriaSet.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING)));
 | 
	
		
			
				|  |  | +				if (!signatureTrustEngine.validate(response.getSignature(), criteriaSet)) {
 | 
	
		
			
				|  |  | +					validationExceptions.put(INVALID_SIGNATURE, authException(INVALID_SIGNATURE,
 | 
	
		
			
				|  |  | +							"Invalid signature for SAML Response [" + response.getID() + "]"));
 | 
	
		
			
				|  |  |  				}
 | 
	
		
			
				|  |  | +			} catch (Exception e) {
 | 
	
		
			
				|  |  | +				validationExceptions.put(INVALID_SIGNATURE, authException(INVALID_SIGNATURE,
 | 
	
		
			
				|  |  | +						"Invalid signature for SAML Response [" + response.getID() + "]", e));
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  | -		return false;
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	private void validateAssertion(String recipient, Assertion a, Saml2AuthenticationToken token, boolean signatureRequired) {
 | 
	
		
			
				|  |  | -		SAML20AssertionValidator validator = getAssertionValidator(token);
 | 
	
		
			
				|  |  | -		Map<String, Object> validationParams = new HashMap<>();
 | 
	
		
			
				|  |  | -		validationParams.put(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, false);
 | 
	
		
			
				|  |  | -		validationParams.put(
 | 
	
		
			
				|  |  | -				SAML2AssertionValidationParameters.CLOCK_SKEW,
 | 
	
		
			
				|  |  | -				this.responseTimeValidationSkew.toMillis()
 | 
	
		
			
				|  |  | -		);
 | 
	
		
			
				|  |  | -		validationParams.put(
 | 
	
		
			
				|  |  | -				SAML2AssertionValidationParameters.COND_VALID_AUDIENCES,
 | 
	
		
			
				|  |  | -				singleton(token.getLocalSpEntityId())
 | 
	
		
			
				|  |  | -		);
 | 
	
		
			
				|  |  | -		if (hasText(recipient)) {
 | 
	
		
			
				|  |  | -			validationParams.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, singleton(recipient));
 | 
	
		
			
				|  |  | +		String destination = response.getDestination();
 | 
	
		
			
				|  |  | +		if (StringUtils.hasText(destination) && !destination.equals(token.getRecipientUri())) {
 | 
	
		
			
				|  |  | +			String message = "Invalid destination [" + destination + "] for SAML response [" + response.getID() + "]";
 | 
	
		
			
				|  |  | +			validationExceptions.put(INVALID_DESTINATION, authException(INVALID_DESTINATION, message));
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		if (signatureRequired && !hasValidSignature(a, token)) {
 | 
	
		
			
				|  |  | -			if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | -				logger.debug(format("Assertion [%s] does not a valid signature.", a.getID()));
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -			throw authException(Saml2ErrorCodes.INVALID_SIGNATURE, "Assertion doesn't have a valid signature.");
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		//ensure that OpenSAML doesn't attempt signature validation, already performed
 | 
	
		
			
				|  |  | -		a.setSignature(null);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		//ensure that we don't validate IP addresses as part of our validation gh-7514
 | 
	
		
			
				|  |  | -		if (a.getSubject() != null) {
 | 
	
		
			
				|  |  | -			for (SubjectConfirmation sc : a.getSubject().getSubjectConfirmations()) {
 | 
	
		
			
				|  |  | -				if (sc.getSubjectConfirmationData() != null) {
 | 
	
		
			
				|  |  | -					sc.getSubjectConfirmationData().setAddress(null);
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | +		if (!StringUtils.hasText(issuer) || !issuer.equals(token.getIdpEntityId())) {
 | 
	
		
			
				|  |  | +			String message = String.format("Invalid issuer [%s] for SAML response [%s]", issuer, response.getID());
 | 
	
		
			
				|  |  | +			validationExceptions.put(INVALID_ISSUER, authException(INVALID_ISSUER, message));
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		//remainder of assertion validation
 | 
	
		
			
				|  |  | -		ValidationContext vctx = new ValidationContext(validationParams);
 | 
	
		
			
				|  |  | -		try {
 | 
	
		
			
				|  |  | -			ValidationResult result = validator.validate(a, vctx);
 | 
	
		
			
				|  |  | -			boolean valid = result.equals(ValidationResult.VALID);
 | 
	
		
			
				|  |  | -			if (!valid) {
 | 
	
		
			
				|  |  | -				if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | -					logger.debug(format("Failed to validate assertion from %s", token.getIdpEntityId()));
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				throw authException(Saml2ErrorCodes.INVALID_ASSERTION, vctx.getValidationFailureMessage());
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | +		SAML20AssertionValidator validator = buildSamlAssertionValidator(signatureTrustEngine);
 | 
	
		
			
				|  |  | +		ValidationContext context = buildValidationContext(token, response);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | +			logger.debug("Validating " + assertions.size() + " assertions");
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  | -		catch (AssertionValidationException e) {
 | 
	
		
			
				|  |  | -			if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | -				logger.debug("Failed to validate assertion:", e);
 | 
	
		
			
				|  |  | +		for (Assertion assertion : assertions) {
 | 
	
		
			
				|  |  | +			if (logger.isTraceEnabled()) {
 | 
	
		
			
				|  |  | +				logger.trace("Validating assertion " + assertion.getID());
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +			try {
 | 
	
		
			
				|  |  | +				validAssertions.add(validateAssertion(assertion, validator, context));
 | 
	
		
			
				|  |  | +			} catch (Exception e) {
 | 
	
		
			
				|  |  | +				String message = String.format("Invalid assertion [%s] for SAML response [%s]", assertion.getID(), response.getID());
 | 
	
		
			
				|  |  | +				validationExceptions.put(INVALID_ASSERTION, authException(INVALID_ASSERTION, message, e));
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  | -			throw authException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, e.getMessage(), e);
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	private Response getSaml2Response(Saml2AuthenticationToken token) throws Saml2Exception, Saml2AuthenticationException {
 | 
	
		
			
				|  |  | -		try {
 | 
	
		
			
				|  |  | -			Object result = this.saml.resolve(token.getSaml2Response());
 | 
	
		
			
				|  |  | -			if (result instanceof Response) {
 | 
	
		
			
				|  |  | -				return (Response) result;
 | 
	
		
			
				|  |  | +		if (validationExceptions.isEmpty()) {
 | 
	
		
			
				|  |  | +			if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | +				logger.debug("Successfully validated SAML Response [" + response.getID() + "]");
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  | -			else {
 | 
	
		
			
				|  |  | -				throw authException(UNKNOWN_RESPONSE_CLASS, "Invalid response class:" + result.getClass().getName());
 | 
	
		
			
				|  |  | +		} else {
 | 
	
		
			
				|  |  | +			if (logger.isTraceEnabled()) {
 | 
	
		
			
				|  |  | +				logger.debug("Found " + validationExceptions.size() + " validation errors in SAML response [" + response.getID() + "]: " +
 | 
	
		
			
				|  |  | +						validationExceptions.values());
 | 
	
		
			
				|  |  | +			} else if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | +				logger.debug("Found " + validationExceptions.size() + " validation errors in SAML response [" + response.getID() + "]");
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  | -		} catch (Saml2Exception x) {
 | 
	
		
			
				|  |  | -			throw authException(MALFORMED_RESPONSE_DATA, x.getMessage(), x);
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | +		if (!validationExceptions.isEmpty()) {
 | 
	
		
			
				|  |  | +			throw validationExceptions.values().iterator().next();
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		if (validAssertions.isEmpty()) {
 | 
	
		
			
				|  |  | +			throw authException(MALFORMED_RESPONSE_DATA, "No valid assertions found in response.");
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	private Saml2Error validationError(String code, String description) {
 | 
	
		
			
				|  |  | -		return new Saml2Error(
 | 
	
		
			
				|  |  | -				code,
 | 
	
		
			
				|  |  | -				description
 | 
	
		
			
				|  |  | -		);
 | 
	
		
			
				|  |  | +		return validAssertions;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	private Saml2AuthenticationException authException(String code, String description) throws Saml2AuthenticationException {
 | 
	
		
			
				|  |  | -		return new Saml2AuthenticationException(
 | 
	
		
			
				|  |  | -				validationError(code, description)
 | 
	
		
			
				|  |  | -		);
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | +	private boolean isSigned(Response samlResponse, List<Assertion> assertions) {
 | 
	
		
			
				|  |  | +		if (samlResponse.isSigned()) {
 | 
	
		
			
				|  |  | +			return true;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +		for (Assertion assertion : assertions) {
 | 
	
		
			
				|  |  | +			if (!assertion.isSigned()) {
 | 
	
		
			
				|  |  | +				return false;
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	private Saml2AuthenticationException authException(String code, String description, Exception cause) throws Saml2AuthenticationException {
 | 
	
		
			
				|  |  | -		return new Saml2AuthenticationException(
 | 
	
		
			
				|  |  | -				validationError(code, description),
 | 
	
		
			
				|  |  | -				cause
 | 
	
		
			
				|  |  | -		);
 | 
	
		
			
				|  |  | +		return true;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	private SAML20AssertionValidator getAssertionValidator(Saml2AuthenticationToken provider) {
 | 
	
		
			
				|  |  | -		List<ConditionValidator> conditions = Collections.singletonList(new AudienceRestrictionConditionValidator());
 | 
	
		
			
				|  |  | -		BearerSubjectConfirmationValidator subjectConfirmationValidator = new BearerSubjectConfirmationValidator();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		List<SubjectConfirmationValidator> subjects = Collections.singletonList(subjectConfirmationValidator);
 | 
	
		
			
				|  |  | -		List<StatementValidator> statements = Collections.emptyList();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +	private SignatureTrustEngine buildSignatureTrustEngine(Saml2AuthenticationToken token) {
 | 
	
		
			
				|  |  |  		Set<Credential> credentials = new HashSet<>();
 | 
	
		
			
				|  |  | -		for (X509Certificate key : getVerificationCertificates(provider)) {
 | 
	
		
			
				|  |  | -			Credential cred = getVerificationCredential(key);
 | 
	
		
			
				|  |  | +		for (X509Certificate key : getVerificationCertificates(token)) {
 | 
	
		
			
				|  |  | +			BasicX509Credential cred = new BasicX509Credential(key);
 | 
	
		
			
				|  |  | +			cred.setUsageType(UsageType.SIGNING);
 | 
	
		
			
				|  |  | +			cred.setEntityId(token.getIdpEntityId());
 | 
	
		
			
				|  |  |  			credentials.add(cred);
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  		CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials);
 | 
	
		
			
				|  |  | -		SignatureTrustEngine signatureTrustEngine = new ExplicitKeySignatureTrustEngine(
 | 
	
		
			
				|  |  | +		return new ExplicitKeySignatureTrustEngine(
 | 
	
		
			
				|  |  |  				credentialsResolver,
 | 
	
		
			
				|  |  |  				DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()
 | 
	
		
			
				|  |  |  		);
 | 
	
		
			
				|  |  | -		SignaturePrevalidator signaturePrevalidator = new SAMLSignatureProfileValidator();
 | 
	
		
			
				|  |  | -		return new SAML20AssertionValidator(
 | 
	
		
			
				|  |  | -				conditions,
 | 
	
		
			
				|  |  | -				subjects,
 | 
	
		
			
				|  |  | -				statements,
 | 
	
		
			
				|  |  | -				signatureTrustEngine,
 | 
	
		
			
				|  |  | -				signaturePrevalidator
 | 
	
		
			
				|  |  | -		);
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	private Credential getVerificationCredential(X509Certificate certificate) {
 | 
	
		
			
				|  |  | -		return CredentialSupport.getSimpleCredential(certificate, null);
 | 
	
		
			
				|  |  | +	private ValidationContext buildValidationContext(Saml2AuthenticationToken token, Response response) {
 | 
	
		
			
				|  |  | +		Map<String, Object> validationParams = new HashMap<>();
 | 
	
		
			
				|  |  | +		validationParams.put(SIGNATURE_REQUIRED, !response.isSigned());
 | 
	
		
			
				|  |  | +		validationParams.put(CLOCK_SKEW, this.responseTimeValidationSkew.toMillis());
 | 
	
		
			
				|  |  | +		validationParams.put(COND_VALID_AUDIENCES, singleton(token.getLocalSpEntityId()));
 | 
	
		
			
				|  |  | +		if (StringUtils.hasText(token.getRecipientUri())) {
 | 
	
		
			
				|  |  | +			validationParams.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, singleton(token.getRecipientUri()));
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		return new ValidationContext(validationParams);
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	private Decrypter getDecrypter(Saml2X509Credential key) {
 | 
	
		
			
				|  |  | -		Credential credential = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey());
 | 
	
		
			
				|  |  | -		KeyInfoCredentialResolver resolver = new StaticKeyInfoCredentialResolver(credential);
 | 
	
		
			
				|  |  | -		Decrypter decrypter = new Decrypter(null, resolver, this.saml.getEncryptedKeyResolver());
 | 
	
		
			
				|  |  | -		decrypter.setRootInNewDocument(true);
 | 
	
		
			
				|  |  | -		return decrypter;
 | 
	
		
			
				|  |  | +	private SAML20AssertionValidator buildSamlAssertionValidator(SignatureTrustEngine signatureTrustEngine) {
 | 
	
		
			
				|  |  | +		return new SAML20AssertionValidator(
 | 
	
		
			
				|  |  | +				this.conditions, this.subjects, this.statements, signatureTrustEngine, this.signaturePrevalidator);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private Assertion validateAssertion(Assertion assertion,
 | 
	
		
			
				|  |  | +			SAML20AssertionValidator validator, ValidationContext context) {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		ValidationResult result;
 | 
	
		
			
				|  |  | +		try {
 | 
	
		
			
				|  |  | +			result = validator.validate(assertion, context);
 | 
	
		
			
				|  |  | +		} catch (Exception e) {
 | 
	
		
			
				|  |  | +			throw new Saml2Exception("An error occurred while validation the assertion", e);
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		if (result != ValidationResult.VALID) {
 | 
	
		
			
				|  |  | +			throw new Saml2Exception("An error occurred while validating the assertion: " +
 | 
	
		
			
				|  |  | +					context.getValidationFailureMessage());
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		return assertion;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	private Assertion decrypt(Saml2AuthenticationToken token, EncryptedAssertion assertion)
 | 
	
		
			
				|  |  |  			throws Saml2AuthenticationException {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  		Saml2AuthenticationException last = null;
 | 
	
		
			
				|  |  |  		List<Saml2X509Credential> decryptionCredentials = getDecryptionCredentials(token);
 | 
	
		
			
				|  |  |  		if (decryptionCredentials.isEmpty()) {
 | 
	
	
		
			
				|  | @@ -452,22 +426,12 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 | 
	
		
			
				|  |  |  		throw last;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	private NameID decrypt(Saml2AuthenticationToken token, EncryptedID assertion) throws Saml2AuthenticationException {
 | 
	
		
			
				|  |  | -		Saml2AuthenticationException last = null;
 | 
	
		
			
				|  |  | -		List<Saml2X509Credential> decryptionCredentials = getDecryptionCredentials(token);
 | 
	
		
			
				|  |  | -		if (decryptionCredentials.isEmpty()) {
 | 
	
		
			
				|  |  | -			throw authException(DECRYPTION_ERROR, "No valid decryption credentials found.");
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		for (Saml2X509Credential key : decryptionCredentials) {
 | 
	
		
			
				|  |  | -			Decrypter decrypter = getDecrypter(key);
 | 
	
		
			
				|  |  | -			try {
 | 
	
		
			
				|  |  | -				return (NameID) decrypter.decrypt(assertion);
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -			catch (DecryptionException e) {
 | 
	
		
			
				|  |  | -				last = authException(DECRYPTION_ERROR, e.getMessage(), e);
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		throw last;
 | 
	
		
			
				|  |  | +	private Decrypter getDecrypter(Saml2X509Credential key) {
 | 
	
		
			
				|  |  | +		Credential credential = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey());
 | 
	
		
			
				|  |  | +		KeyInfoCredentialResolver resolver = new StaticKeyInfoCredentialResolver(credential);
 | 
	
		
			
				|  |  | +		Decrypter decrypter = new Decrypter(null, resolver, this.saml.getEncryptedKeyResolver());
 | 
	
		
			
				|  |  | +		decrypter.setRootInNewDocument(true);
 | 
	
		
			
				|  |  | +		return decrypter;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	private List<Saml2X509Credential> getDecryptionCredentials(Saml2AuthenticationToken token) {
 | 
	
	
		
			
				|  | @@ -489,4 +453,61 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  		return result;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private String getUsername(Saml2AuthenticationToken token, Assertion assertion)
 | 
	
		
			
				|  |  | +			throws Saml2AuthenticationException {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		String username = null;
 | 
	
		
			
				|  |  | +		Subject subject = assertion.getSubject();
 | 
	
		
			
				|  |  | +		if (subject == null) {
 | 
	
		
			
				|  |  | +			throw authException(SUBJECT_NOT_FOUND, "Assertion [" + assertion.getID() + "] is missing a subject");
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		if (subject.getNameID() != null) {
 | 
	
		
			
				|  |  | +			username = subject.getNameID().getValue();
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		else if (subject.getEncryptedID() != null) {
 | 
	
		
			
				|  |  | +			NameID nameId = decrypt(token, subject.getEncryptedID());
 | 
	
		
			
				|  |  | +			username = nameId.getValue();
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		if (username == null) {
 | 
	
		
			
				|  |  | +			throw authException(USERNAME_NOT_FOUND, "Assertion [" + assertion.getID() + "] is missing a user identifier");
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		return username;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private NameID decrypt(Saml2AuthenticationToken token, EncryptedID assertion)
 | 
	
		
			
				|  |  | +			throws Saml2AuthenticationException {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		Saml2AuthenticationException last = null;
 | 
	
		
			
				|  |  | +		List<Saml2X509Credential> decryptionCredentials = getDecryptionCredentials(token);
 | 
	
		
			
				|  |  | +		if (decryptionCredentials.isEmpty()) {
 | 
	
		
			
				|  |  | +			throw authException(DECRYPTION_ERROR, "No valid decryption credentials found.");
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		for (Saml2X509Credential key : decryptionCredentials) {
 | 
	
		
			
				|  |  | +			Decrypter decrypter = getDecrypter(key);
 | 
	
		
			
				|  |  | +			try {
 | 
	
		
			
				|  |  | +				return (NameID) decrypter.decrypt(assertion);
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +			catch (DecryptionException e) {
 | 
	
		
			
				|  |  | +				last = authException(DECRYPTION_ERROR, e.getMessage(), e);
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		throw last;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private Saml2Error validationError(String code, String description) {
 | 
	
		
			
				|  |  | +		return new Saml2Error(code, description);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private Saml2AuthenticationException authException(String code, String description)
 | 
	
		
			
				|  |  | +			throws Saml2AuthenticationException {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		return new Saml2AuthenticationException(validationError(code, description));
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private Saml2AuthenticationException authException(String code, String description, Exception cause)
 | 
	
		
			
				|  |  | +			throws Saml2AuthenticationException {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		return new Saml2AuthenticationException(validationError(code, description), cause);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  }
 |