/******************************************************************************* * 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 javax.xml.namespace.QName; import javax.xml.transform.dom.DOMSource; import javax.xml.validation.Schema; import javax.xml.validation.Validator; import org.opensaml.common.xml.SAMLConstants; import org.opensaml.common.xml.SAMLSchemaBuilder; import org.opensaml.saml2.core.RequestAbstractType; import org.opensaml.saml2.core.StatusResponseType; 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.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.signature.SignatureTrustEngine; import org.opensaml.xml.validation.ValidationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; 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.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.metadata.MOAMetadataProvider; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.MiscUtil; @Service("SAMLVerificationEngine") public class SAMLVerificationEngine { @Autowired(required=true) MOAMetadataProvider metadataProvider; public void verify(InboundMessage msg, SignatureTrustEngine sigTrustEngine ) throws org.opensaml.xml.security.SecurityException, Exception { try { if (msg instanceof MOARequest && ((MOARequest)msg).getSamlRequest() instanceof RequestAbstractType) verifyRequest(((RequestAbstractType)((MOARequest)msg).getSamlRequest()), sigTrustEngine); else verifyIDPResponse(((MOAResponse)msg).getResponse(), sigTrustEngine); } catch (InvalidProtocolRequestException e) { if (MiscUtil.isEmpty(msg.getEntityID())) { throw e; } Logger.debug("PVP2X message validation FAILED. Relead metadata for entityID: " + msg.getEntityID()); if (metadataProvider == null || !metadataProvider.refreshMetadataProvider(msg.getEntityID())) throw e; else { Logger.trace("PVP2X metadata reload finished. Check validate message again."); if (msg instanceof MOARequest && ((MOARequest)msg).getSamlRequest() instanceof RequestAbstractType) verifyRequest(((RequestAbstractType)((MOARequest)msg).getSamlRequest()), sigTrustEngine); else verifyIDPResponse(((MOAResponse)msg).getResponse(), sigTrustEngine); } Logger.trace("Second PVP2X message validation finished"); } } public void verifyIDPResponse(StatusResponseType samlObj, SignatureTrustEngine sigTrustEngine) throws InvalidProtocolRequestException{ verifyResponse(samlObj, sigTrustEngine, IDPSSODescriptor.DEFAULT_ELEMENT_NAME); } public void verifySLOResponse(StatusResponseType samlObj, SignatureTrustEngine sigTrustEngine ) throws InvalidProtocolRequestException { verifyResponse(samlObj, sigTrustEngine, SPSSODescriptor.DEFAULT_ELEMENT_NAME); } private void verifyResponse(StatusResponseType samlObj, SignatureTrustEngine sigTrustEngine, QName defaultElementName) throws InvalidProtocolRequestException{ 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(defaultElementName, SAMLConstants.SAML20P_NS) ); criteriaSet.add( new UsageCriteria(UsageType.SIGNING) ); try { if (!sigTrustEngine.validate(samlObj.getSignature(), criteriaSet)) { throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); } } catch (org.opensaml.xml.security.SecurityException e) { Logger.warn("PVP2x message signature validation FAILED.", e); throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); } } public void verifyRequest(RequestAbstractType samlObj, SignatureTrustEngine sigTrustEngine ) throws InvalidProtocolRequestException { 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 (org.opensaml.xml.security.SecurityException e) { Logger.warn("PVP2x message signature validation FAILED.", e); throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); } } protected 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}); } }