diff options
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.java | 261 |
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); + } +} |