aboutsummaryrefslogtreecommitdiff
path: root/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/SAMLVerificationEngineSP.java
diff options
context:
space:
mode:
Diffstat (limited to 'id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/SAMLVerificationEngineSP.java')
-rw-r--r--id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/SAMLVerificationEngineSP.java261
1 files changed, 261 insertions, 0 deletions
diff --git a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/SAMLVerificationEngineSP.java b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/SAMLVerificationEngineSP.java
new file mode 100644
index 000000000..385fe90fb
--- /dev/null
+++ b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/SAMLVerificationEngineSP.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2014 Federal Chancellery Austria
+ * MOA-ID has been developed in a cooperation between BRZ, the Federal
+ * Chancellery Austria - ICT staff unit, and Graz University of Technology.
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
+ * the European Commission - subsequent versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ * http://www.osor.eu/eupl/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licence is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and
+ * limitations under the Licence.
+ *
+ * This product combines work with different licenses. See the "NOTICE" text
+ * file for details on the various modules and licenses.
+ * The "NOTICE" text file is part of the distribution. Any derivative works
+ * that you distribute must include a readable copy of the "NOTICE" text file.
+ */
+package at.gv.egovernment.moa.id.protocols.pvp2x.verification;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.opensaml.common.binding.decoding.BasicURLComparator;
+import org.opensaml.saml2.core.Audience;
+import org.opensaml.saml2.core.AudienceRestriction;
+import org.opensaml.saml2.core.Conditions;
+import org.opensaml.saml2.core.EncryptedAssertion;
+import org.opensaml.saml2.core.Response;
+import org.opensaml.saml2.core.StatusCode;
+import org.opensaml.saml2.core.validator.AudienceRestrictionSchemaValidator;
+import org.opensaml.saml2.core.validator.AudienceSchemaValidator;
+import org.opensaml.saml2.encryption.Decrypter;
+import org.opensaml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver;
+import org.opensaml.xml.encryption.ChainingEncryptedKeyResolver;
+import org.opensaml.xml.encryption.DecryptionException;
+import org.opensaml.xml.encryption.InlineEncryptedKeyResolver;
+import org.opensaml.xml.encryption.SimpleRetrievalMethodEncryptedKeyResolver;
+import org.opensaml.xml.security.credential.Credential;
+import org.opensaml.xml.security.keyinfo.StaticKeyInfoCredentialResolver;
+import org.opensaml.xml.validation.ValidationException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import at.gv.egovernment.moa.id.commons.MOAIDAuthConstants;
+import at.gv.egovernment.moa.id.commons.api.AuthConfiguration;
+import at.gv.egovernment.moa.id.commons.api.exceptions.ConfigurationException;
+import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.AssertionValidationExeption;
+import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.SchemaValidationException;
+import at.gv.egovernment.moa.logging.Logger;
+
+/**
+ * @author tlenz
+ *
+ */
+@Service("SAMLVerificationEngineSP")
+public class SAMLVerificationEngineSP extends SAMLVerificationEngine {
+
+ @Autowired AuthConfiguration authConfig;
+
+ /**
+ * Validate a PVP response and all included assertions
+ *
+ * @param samlResp
+ * @param validateDestination
+ * @param assertionDecryption
+ * @param spEntityID
+ * @param loggerSPName
+ * @throws AssertionValidationExeption
+ */
+ public void validateAssertion(Response samlResp, boolean validateDestination, Credential assertionDecryption, String spEntityID, String loggerSPName) throws AssertionValidationExeption {
+ validateAssertion(samlResp, validateDestination, assertionDecryption, spEntityID, loggerSPName, true);
+
+ }
+
+
+ public void validateAssertion(Response samlResp, boolean validateDestination, Credential assertionDecryption, String spEntityID, String loggerSPName,
+ boolean validateDateTime) throws AssertionValidationExeption {
+ try {
+ if (samlResp.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS_URI)) {
+ List<org.opensaml.saml2.core.Assertion> saml2assertions = new ArrayList<org.opensaml.saml2.core.Assertion>();
+
+ //validate destination URL
+ List<String> allowedPublicURLPrefix = authConfig.getPublicURLPrefix();
+ boolean isValidDestination = false;
+ for (String allowedPreFix : allowedPublicURLPrefix) {
+ if (validateDestination && samlResp.getDestination().startsWith(
+ allowedPreFix)) {
+ isValidDestination = true;
+ break;
+
+ }
+ }
+ if (!isValidDestination && validateDestination) {
+ Logger.warn("PVP 2.1 assertion destination does not match to IDP URL");
+ throw new AssertionValidationExeption("sp.pvp2.07", new Object[]{loggerSPName, "'Destination' attribute is not valid"});
+
+ }
+
+ //validate response issueInstant
+ DateTime issueInstant = samlResp.getIssueInstant();
+ if (issueInstant == null) {
+ Logger.warn("PVP response does not include a 'IssueInstant' attribute");
+ throw new AssertionValidationExeption("sp.pvp2.07", new Object[]{loggerSPName, "'IssueInstant' attribute is not included"});
+
+ }
+ if (validateDateTime && issueInstant.minusMinutes(MOAIDAuthConstants.TIME_JITTER).isAfterNow()) {
+ Logger.warn("PVP response: IssueInstant DateTime is not valid anymore.");
+ throw new AssertionValidationExeption("sp.pvp2.07", new Object[]{loggerSPName, "'IssueInstant' Time is not valid any more"});
+
+ }
+
+
+ //check encrypted Assertions
+ List<EncryptedAssertion> encryAssertionList = samlResp.getEncryptedAssertions();
+ if (encryAssertionList != null && encryAssertionList.size() > 0) {
+ //decrypt assertions
+ Logger.debug("Found encryped assertion. Start decryption ...");
+
+ StaticKeyInfoCredentialResolver skicr =
+ new StaticKeyInfoCredentialResolver(assertionDecryption);
+
+ ChainingEncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver();
+ encryptedKeyResolver.getResolverChain().add( new InlineEncryptedKeyResolver() );
+ encryptedKeyResolver.getResolverChain().add( new EncryptedElementTypeEncryptedKeyResolver() );
+ encryptedKeyResolver.getResolverChain().add( new SimpleRetrievalMethodEncryptedKeyResolver() );
+
+ Decrypter samlDecrypter = new Decrypter(null, skicr, encryptedKeyResolver);
+
+ for (EncryptedAssertion encAssertion : encryAssertionList)
+ saml2assertions.add(samlDecrypter.decrypt(encAssertion));
+
+ Logger.debug("Assertion decryption finished. ");
+
+ } else {
+ saml2assertions.addAll(samlResp.getAssertions());
+
+ }
+
+ //validate all assertions
+ List<org.opensaml.saml2.core.Assertion> validatedassertions = new ArrayList<org.opensaml.saml2.core.Assertion>();
+ for (org.opensaml.saml2.core.Assertion saml2assertion : saml2assertions) {
+
+ boolean isAssertionValid = true;
+ try {
+ //schema validation
+ performSchemaValidation(saml2assertion.getDOM());
+
+
+ //validate DateTime conditions
+ Conditions conditions = saml2assertion.getConditions();
+ if (conditions != null) {
+ DateTime notbefore = conditions.getNotBefore().minusMinutes(5);
+ DateTime notafter = conditions.getNotOnOrAfter();
+ if (validateDateTime &&
+ (notbefore.isAfterNow() || notafter.isBeforeNow()) ) {
+ isAssertionValid = false;
+ Logger.info("Assertion:" + saml2assertion.getID()
+ + " is out of Date. "
+ + "{ Current : " + new DateTime()
+ + " NotBefore: " + notbefore
+ + " NotAfter : " + notafter
+ + " }");;
+
+ }
+
+ //validate audienceRestrictions are valid for this SP
+ List<AudienceRestriction> audienceRest = conditions.getAudienceRestrictions();
+ if (audienceRest == null || audienceRest.size() == 0) {
+ Logger.info("Assertion:" + saml2assertion.getID()
+ + " has not 'AudienceRestriction' element");
+ isAssertionValid = false;
+
+ } else {
+ for (AudienceRestriction el : audienceRest) {
+ el.registerValidator(new AudienceRestrictionSchemaValidator());
+ el.validate(false);
+
+ for (Audience audience : el.getAudiences()) {
+ audience.registerValidator(new AudienceSchemaValidator());
+ audience.validate(false);
+
+ if (!urlCompare(spEntityID, audience.getAudienceURI())) {
+ Logger.info("Assertion:" + saml2assertion.getID()
+ + " 'AudienceRestriction' is not valid.");
+ isAssertionValid = false;
+
+ }
+ }
+ }
+ }
+
+ } else {
+ Logger.info("Assertion:" + saml2assertion.getID()
+ + " contains not 'Conditions' element");
+ isAssertionValid = false;
+
+ }
+
+ //add assertion if it is valid
+ if (isAssertionValid) {
+ Logger.debug("Add valid Assertion:" + saml2assertion.getID());
+ validatedassertions.add(saml2assertion);
+
+ } else
+ Logger.warn("Remove non-valid Assertion:" + saml2assertion.getID());
+
+ } catch (SchemaValidationException e) {
+ isAssertionValid = false;
+ Logger.info("Assertion:" + saml2assertion.getID()
+ + " Schema validation FAILED. Msg:" + e.getMessage());
+
+ } catch (ValidationException e) {
+ isAssertionValid = false;
+ Logger.info("Assertion:" + saml2assertion.getID()
+ + " AudienceRestriction schema-validation FAILED. Msg:" + e.getMessage());
+
+ }
+ }
+
+ if (validatedassertions.isEmpty()) {
+ Logger.info("No valid PVP 2.1 assertion received.");
+ throw new AssertionValidationExeption("sp.pvp2.10", new Object[]{loggerSPName});
+
+ }
+
+ samlResp.getAssertions().clear();
+ samlResp.getEncryptedAssertions().clear();
+ samlResp.getAssertions().addAll(validatedassertions);
+
+ } else {
+ Logger.info("PVP 2.1 assertion includes an error. Receive errorcode "
+ + samlResp.getStatus().getStatusCode().getValue());
+ throw new AssertionValidationExeption("sp.pvp2.05",
+ new Object[]{loggerSPName,
+ samlResp.getIssuer().getValue(),
+ samlResp.getStatus().getStatusCode().getValue(),
+ samlResp.getStatus().getStatusMessage().getMessage()});
+
+ }
+
+ } catch (DecryptionException e) {
+ Logger.warn("Assertion decrypt FAILED.", e);
+ throw new AssertionValidationExeption("sp.pvp2.11", null, e);
+
+ } catch (ConfigurationException e) {
+ throw new AssertionValidationExeption("pvp.12", null, e);
+ }
+ }
+
+ protected static boolean urlCompare(String url1, String url2) {
+ BasicURLComparator comparator = new BasicURLComparator();
+ comparator.setCaseInsensitive(false);
+ return comparator.compare(url1, url2);
+ }
+}