From a6cadad81df2b44a99ca452ea1737abf1fa7d3e8 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Thu, 10 Mar 2016 12:31:38 +0100 Subject: add additional PVP response validation --- .../id/protocols/pvp2x/PVPTargetConfiguration.java | 3 + .../builder/assertion/PVP2AssertionBuilder.java | 1 + .../verification/SAMLVerificationEngineSP.java | 142 +++++++++++++++++---- .../resources/properties/id_messages_de.properties | 2 + .../protocol_response_statuscodes_de.properties | 2 + 5 files changed, 125 insertions(+), 25 deletions(-) (limited to 'id/server/idserverlib') diff --git a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/PVPTargetConfiguration.java b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/PVPTargetConfiguration.java index b8ced1198..e7f2a7d4b 100644 --- a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/PVPTargetConfiguration.java +++ b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/PVPTargetConfiguration.java @@ -50,8 +50,11 @@ public class PVPTargetConfiguration extends RequestImpl { public static final String DATAID_INTERFEDERATION_NAMEID = "federatedNameID"; public static final String DATAID_INTERFEDERATION_QAALEVEL = "federatedQAALevel"; + public static final String DATAID_INTERFEDERATION_REQUESTID = "authnReqID"; + private static final long serialVersionUID = 4889919265919638188L; + InboundMessage request; String binding; String consumerURL; diff --git a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/builder/assertion/PVP2AssertionBuilder.java b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/builder/assertion/PVP2AssertionBuilder.java index 7a7044ebf..3aa05bb2d 100644 --- a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/builder/assertion/PVP2AssertionBuilder.java +++ b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/builder/assertion/PVP2AssertionBuilder.java @@ -435,6 +435,7 @@ public class PVP2AssertionBuilder implements PVPConstants { .createSAMLObject(SubjectConfirmationData.class); subjectConfirmationData.setInResponseTo(authnRequest.getID()); subjectConfirmationData.setNotOnOrAfter(new DateTime(authData.getSsoSessionValidTo().getTime())); + subjectConfirmationData.setNotBefore(date); subjectConfirmationData.setRecipient(assertionConsumerService.getLocation()); 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 index cd80d8c24..1e13da179 100644 --- 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 @@ -26,10 +26,15 @@ 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; @@ -38,9 +43,11 @@ 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.auth.MOAIDAuthConstants; import at.gv.egovernment.moa.id.config.ConfigurationException; import at.gv.egovernment.moa.id.config.auth.AuthConfiguration; import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.AssertionValidationExeption; @@ -56,7 +63,17 @@ public class SAMLVerificationEngineSP extends SAMLVerificationEngine { @Autowired AuthConfiguration authConfig; - public void validateAssertion(Response samlResp, boolean validateDestination, Credential assertionDecryption) throws AssertionValidationExeption { + /** + * 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 { try { if (samlResp.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS_URI)) { List saml2assertions = new ArrayList(); @@ -74,15 +91,28 @@ public class SAMLVerificationEngineSP extends SAMLVerificationEngine { } if (!isValidDestination && validateDestination) { Logger.warn("PVP 2.1 assertion destination does not match to IDP URL"); - throw new AssertionValidationExeption("PVP 2.1 assertion destination does not match to IDP URL", null); + throw new AssertionValidationExeption("sp.pvp2.07", new Object[]{loggerSPName, "'Destination' attribute is not valid"}); } - //check encrypted Assertion + //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 (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 encryAssertionList = samlResp.getEncryptedAssertions(); if (encryAssertionList != null && encryAssertionList.size() > 0) { - //decrypt assertions - + //decrypt assertions Logger.debug("Found encryped assertion. Start decryption ..."); StaticKeyInfoCredentialResolver skicr = @@ -93,13 +123,10 @@ public class SAMLVerificationEngineSP extends SAMLVerificationEngine { encryptedKeyResolver.getResolverChain().add( new EncryptedElementTypeEncryptedKeyResolver() ); encryptedKeyResolver.getResolverChain().add( new SimpleRetrievalMethodEncryptedKeyResolver() ); - Decrypter samlDecrypter = - new Decrypter(null, skicr, encryptedKeyResolver); + Decrypter samlDecrypter = new Decrypter(null, skicr, encryptedKeyResolver); - for (EncryptedAssertion encAssertion : encryAssertionList) { + for (EncryptedAssertion encAssertion : encryAssertionList) saml2assertions.add(samlDecrypter.decrypt(encAssertion)); - - } Logger.debug("Assertion decryption finished. "); @@ -108,35 +135,90 @@ public class SAMLVerificationEngineSP extends SAMLVerificationEngine { } + //validate all assertions List validatedassertions = new ArrayList(); for (org.opensaml.saml2.core.Assertion saml2assertion : saml2assertions) { - + + boolean isAssertionValid = true; try { + //schema validation performSchemaValidation(saml2assertion.getDOM()); - + + + //validate DateTime conditions Conditions conditions = saml2assertion.getConditions(); - DateTime notbefore = conditions.getNotBefore().minusMinutes(5); - DateTime notafter = conditions.getNotOnOrAfter(); - if ( notbefore.isAfterNow() || notafter.isBeforeNow() ) { - Logger.warn("PVP2 Assertion is out of Date. " - + "{ Current : " + new DateTime() - + " NotBefore: " + notbefore - + " NotAfter : " + notafter - + " }");; + if (conditions != null) { + DateTime notbefore = conditions.getNotBefore().minusMinutes(5); + DateTime notafter = conditions.getNotOnOrAfter(); + if ( 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 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("No valid PVP 2.1 assertion received.", null); + throw new AssertionValidationExeption("sp.pvp2.10", new Object[]{loggerSPName}); + } samlResp.getAssertions().clear(); @@ -146,16 +228,26 @@ public class SAMLVerificationEngineSP extends SAMLVerificationEngine { } else { Logger.info("PVP 2.1 assertion includes an error. Receive errorcode " + samlResp.getStatus().getStatusCode().getValue()); - throw new AssertionValidationExeption("PVP 2.1 assertion includes an error. Receive errorcode " - + samlResp.getStatus().getStatusCode().getValue(), null); + 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("Assertion decrypt FAILED.", null, 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); + } } diff --git a/id/server/idserverlib/src/main/resources/resources/properties/id_messages_de.properties b/id/server/idserverlib/src/main/resources/resources/properties/id_messages_de.properties index a53d7e920..34ef9c1d0 100644 --- a/id/server/idserverlib/src/main/resources/resources/properties/id_messages_de.properties +++ b/id/server/idserverlib/src/main/resources/resources/properties/id_messages_de.properties @@ -292,6 +292,8 @@ sp.pvp2.06=Receive invalid PVP Response from {0}. Assertion does not contain all sp.pvp2.07=Receive invalid PVP Response from {0}. Attribute {1} is not valid. sp.pvp2.08=Receive invalid PVP Response from {0}. Response issuer {1} is not valid or allowed. sp.pvp2.09=Receive invalid PVP Response from {0} {1}. StatusCodes:{2} {3} Msg:{4} +sp.pvp2.10=Receive invalid PVP Response from {0}. No valid assertion included. +sp.pvp2.11=Receive invalid PVP Response from {0}. Assertion decryption FAILED. oauth20.01=Fehlerhafte redirect url oauth20.02=Fehlender oder ung\u00FCltiger Parameter "{0}" diff --git a/id/server/idserverlib/src/main/resources/resources/properties/protocol_response_statuscodes_de.properties b/id/server/idserverlib/src/main/resources/resources/properties/protocol_response_statuscodes_de.properties index a81540e2b..27070cc84 100644 --- a/id/server/idserverlib/src/main/resources/resources/properties/protocol_response_statuscodes_de.properties +++ b/id/server/idserverlib/src/main/resources/resources/properties/protocol_response_statuscodes_de.properties @@ -111,6 +111,8 @@ sp.pvp2.06=TODO sp.pvp2.07=TODO sp.pvp2.08=TODO sp.pvp2.09=TODO +sp.pvp2.10=TODO +sp.pvp2.11=TODO validator.00=1102 validator.01=1102 -- cgit v1.2.3