at.gv.egovernment.moa.id.protocols.pvp2x.verification.SAMLVerificationEngine.java Source code

Java tutorial

Introduction

Here is the source code for at.gv.egovernment.moa.id.protocols.pvp2x.verification.SAMLVerificationEngine.java

Source

/*******************************************************************************
 * 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 javax.xml.transform.dom.DOMSource;
import javax.xml.validation.Schema;
import javax.xml.validation.Validator;

import org.joda.time.DateTime;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.common.xml.SAMLSchemaBuilder;
import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.EncryptedAssertion;
import org.opensaml.saml2.core.RequestAbstractType;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.saml2.core.StatusResponseType;
import org.opensaml.saml2.core.validator.AuthnRequestSchemaValidator;
import org.opensaml.saml2.encryption.Decrypter;
import org.opensaml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver;
import org.opensaml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml2.metadata.SPSSODescriptor;
import org.opensaml.security.MetadataCriteria;
import org.opensaml.security.SAMLSignatureProfileValidator;
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.CriteriaSet;
import org.opensaml.xml.security.credential.UsageType;
import org.opensaml.xml.security.criteria.EntityIDCriteria;
import org.opensaml.xml.security.criteria.UsageCriteria;
import org.opensaml.xml.security.keyinfo.StaticKeyInfoCredentialResolver;
import org.opensaml.xml.security.x509.X509Credential;
import org.opensaml.xml.signature.SignatureTrustEngine;
import org.opensaml.xml.validation.ValidationException;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import at.gv.egovernment.moa.id.auth.exception.InvalidProtocolRequestException;
import at.gv.egovernment.moa.id.config.ConfigurationException;
import at.gv.egovernment.moa.id.protocols.pvp2x.config.PVPConfiguration;
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.id.protocols.pvp2x.messages.InboundMessage;
import at.gv.egovernment.moa.id.protocols.pvp2x.messages.MOARequest;
import at.gv.egovernment.moa.id.protocols.pvp2x.messages.MOAResponse;
import at.gv.egovernment.moa.id.protocols.pvp2x.signer.CredentialProvider;
import at.gv.egovernment.moa.id.protocols.pvp2x.signer.CredentialsNotAvailableException;
import at.gv.egovernment.moa.logging.Logger;

public class SAMLVerificationEngine {

    public void verify(InboundMessage msg, SignatureTrustEngine sigTrustEngine)
            throws org.opensaml.xml.security.SecurityException, Exception {
        if (msg instanceof MOARequest && ((MOARequest) msg).getSamlRequest() instanceof RequestAbstractType)
            verifyRequest(((RequestAbstractType) ((MOARequest) msg).getSamlRequest()), sigTrustEngine);

        else
            verifyResponse(((MOAResponse) msg).getResponse(), sigTrustEngine);

    }

    public void verifyResponse(StatusResponseType samlObj, SignatureTrustEngine sigTrustEngine)
            throws org.opensaml.xml.security.SecurityException, Exception {
        SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
        try {
            profileValidator.validate(samlObj.getSignature());
            performSchemaValidation(samlObj.getDOM());

        } catch (ValidationException e) {
            Logger.warn("Signature is not conform to SAML signature profile", e);
            throw new InvalidProtocolRequestException("pvp2.21", new Object[] {});

        } catch (SchemaValidationException e) {
            throw new InvalidProtocolRequestException("pvp2.22", new Object[] { e.getMessage() });

        }

        CriteriaSet criteriaSet = new CriteriaSet();
        criteriaSet.add(new EntityIDCriteria(samlObj.getIssuer().getValue()));
        criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS));
        criteriaSet.add(new UsageCriteria(UsageType.SIGNING));

        try {
            if (!sigTrustEngine.validate(samlObj.getSignature(), criteriaSet)) {
                throw new InvalidProtocolRequestException("pvp2.21", new Object[] {});
            }
        } catch (SecurityException e) {
            e.printStackTrace();
            throw new InvalidProtocolRequestException("pvp2.21", new Object[] {});
        }
    }

    public void verifyRequest(RequestAbstractType samlObj, SignatureTrustEngine sigTrustEngine)
            throws org.opensaml.xml.security.SecurityException, Exception {
        SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
        try {
            profileValidator.validate(samlObj.getSignature());
            performSchemaValidation(samlObj.getDOM());

        } catch (ValidationException e) {
            Logger.warn("Signature is not conform to SAML signature profile", e);
            throw new InvalidProtocolRequestException("pvp2.21", new Object[] {});

        } catch (SchemaValidationException e) {
            throw new InvalidProtocolRequestException("pvp2.22", new Object[] { e.getMessage() });

        }

        CriteriaSet criteriaSet = new CriteriaSet();
        criteriaSet.add(new EntityIDCriteria(samlObj.getIssuer().getValue()));
        criteriaSet.add(new MetadataCriteria(SPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS));
        criteriaSet.add(new UsageCriteria(UsageType.SIGNING));

        try {
            if (!sigTrustEngine.validate(samlObj.getSignature(), criteriaSet)) {
                throw new InvalidProtocolRequestException("pvp2.21", new Object[] {});
            }
        } catch (SecurityException e) {
            e.printStackTrace();
            throw new InvalidProtocolRequestException("pvp2.21", new Object[] {});
        }
    }

    public static void validateAssertion(Response samlResp, boolean validateDestination)
            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>();

                if (validateDestination && !samlResp.getDestination()
                        .startsWith(PVPConfiguration.getInstance().getIDPPublicPath())) {
                    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);

                }

                //check encrypted Assertion
                List<EncryptedAssertion> encryAssertionList = samlResp.getEncryptedAssertions();
                if (encryAssertionList != null && encryAssertionList.size() > 0) {
                    //decrypt assertions

                    Logger.debug("Found encryped assertion. Start decryption ...");

                    X509Credential authDecCredential = CredentialProvider.getIDPAssertionEncryptionCredential();

                    StaticKeyInfoCredentialResolver skicr = new StaticKeyInfoCredentialResolver(authDecCredential);

                    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());

                }

                List<org.opensaml.saml2.core.Assertion> validatedassertions = new ArrayList<org.opensaml.saml2.core.Assertion>();
                for (org.opensaml.saml2.core.Assertion saml2assertion : saml2assertions) {

                    try {
                        performSchemaValidation(saml2assertion.getDOM());

                        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 + " }");
                            ;

                        } else {
                            validatedassertions.add(saml2assertion);

                        }

                    } catch (SchemaValidationException e) {

                    }
                }

                if (validatedassertions.isEmpty()) {
                    Logger.info("No valid PVP 2.1 assertion received.");
                    throw new AssertionValidationExeption("No valid PVP 2.1 assertion received.", null);
                }

                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("PVP 2.1 assertion includes an error. Receive errorcode "
                        + samlResp.getStatus().getStatusCode().getValue(), null);
            }

        } catch (CredentialsNotAvailableException e) {
            Logger.warn("Assertion decrypt FAILED - No Credentials", e);
            throw new AssertionValidationExeption("Assertion decrypt FAILED - No Credentials", null, e);

        } catch (DecryptionException e) {
            Logger.warn("Assertion decrypt FAILED.", e);
            throw new AssertionValidationExeption("Assertion decrypt FAILED.", null, e);

        } catch (ConfigurationException e) {
            throw new AssertionValidationExeption("pvp.12", null, e);
        }
    }

    private static void performSchemaValidation(Element source) throws SchemaValidationException {

        String err = null;
        try {
            Schema test = SAMLSchemaBuilder.getSAML11Schema();
            Validator val = test.newValidator();
            val.validate(new DOMSource(source));
            Logger.debug("Schema validation check done OK");
            return;

        } catch (SAXException e) {
            err = e.getMessage();
            if (Logger.isDebugEnabled() || Logger.isTraceEnabled())
                Logger.warn("Schema validation FAILED with exception:", e);
            else
                Logger.warn("Schema validation FAILED with message: " + e.getMessage());

        } catch (Exception e) {
            err = e.getMessage();
            if (Logger.isDebugEnabled() || Logger.isTraceEnabled())
                Logger.warn("Schema validation FAILED with exception:", e);
            else
                Logger.warn("Schema validation FAILED with message: " + e.getMessage());

        }

        throw new SchemaValidationException("pvp2.22", new Object[] { err });

    }

}