From 759ac5f42c6aff901dbeede4fbf1a1d2e08cad0f Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Wed, 4 Dec 2019 19:43:32 +0100 Subject: common EGIZ code-style refactoring --- .../impl/verification/SamlVerificationEngine.java | 218 +++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/SamlVerificationEngine.java (limited to 'eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/SamlVerificationEngine.java') diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/SamlVerificationEngine.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/SamlVerificationEngine.java new file mode 100644 index 00000000..64eb5247 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/SamlVerificationEngine.java @@ -0,0 +1,218 @@ +/* + * Copyright 2017 Graz University of Technology EAAF-Core Components has been developed in a + * cooperation between EGIZ, A-SIT Plus, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 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: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * 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.egiz.eaaf.modules.pvp2.impl.verification; + +import javax.xml.namespace.QName; +import javax.xml.transform.dom.DOMSource; +import javax.xml.validation.Schema; +import javax.xml.validation.Validator; +import at.gv.egiz.eaaf.core.exceptions.InvalidProtocolRequestException; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvpMetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IRefreshableMetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.exception.SchemaValidationException; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.InboundMessage; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.PvpSProfileRequest; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.PvpSProfileResponse; +import org.apache.commons.lang3.StringUtils; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +@Service("SAMLVerificationEngine") +public class SamlVerificationEngine { + private static final Logger log = LoggerFactory.getLogger(SamlVerificationEngine.class); + + + @Autowired(required = true) + IPvpMetadataProvider metadataProvider; + + /** + * Verify signature of a signed SAML2 object. + * + * @param msg SAML2 message + * @param sigTrustEngine TrustEngine + * @throws org.opensaml.xml.security.SecurityException In case of invalid signature + * @throws Exception In case of a general error + */ + public void verify(final InboundMessage msg, final SignatureTrustEngine sigTrustEngine) + throws org.opensaml.xml.security.SecurityException, Exception { + try { + if (msg instanceof PvpSProfileRequest + && ((PvpSProfileRequest) msg).getSamlRequest() instanceof RequestAbstractType) { + verifyRequest(((RequestAbstractType) ((PvpSProfileRequest) msg).getSamlRequest()), + sigTrustEngine); + } else { + verifyIdpResponse(((PvpSProfileResponse) msg).getResponse(), sigTrustEngine); + } + + } catch (final InvalidProtocolRequestException e) { + if (StringUtils.isEmpty(msg.getEntityID())) { + throw e; + + } + log.debug( + "PVP2X message validation FAILED. Relead metadata for entityID: " + msg.getEntityID()); + + if (metadataProvider == null || !(metadataProvider instanceof IRefreshableMetadataProvider) + || !((IRefreshableMetadataProvider) metadataProvider) + .refreshMetadataProvider(msg.getEntityID())) { + throw e; + } else { + log.trace("PVP2X metadata reload finished. Check validate message again."); + + if (msg instanceof PvpSProfileRequest + && ((PvpSProfileRequest) msg).getSamlRequest() instanceof RequestAbstractType) { + verifyRequest(((RequestAbstractType) ((PvpSProfileRequest) msg).getSamlRequest()), + sigTrustEngine); + } else { + verifyIdpResponse(((PvpSProfileResponse) msg).getResponse(), sigTrustEngine); + } + + } + log.trace("Second PVP2X message validation finished"); + } + } + + public void verifySloResponse(final StatusResponseType samlObj, + final SignatureTrustEngine sigTrustEngine) throws InvalidProtocolRequestException { + verifyResponse(samlObj, sigTrustEngine, SPSSODescriptor.DEFAULT_ELEMENT_NAME); + + } + + public void verifyIdpResponse(final StatusResponseType samlObj, + final SignatureTrustEngine sigTrustEngine) throws InvalidProtocolRequestException { + verifyResponse(samlObj, sigTrustEngine, IDPSSODescriptor.DEFAULT_ELEMENT_NAME); + + } + + private void verifyResponse(final StatusResponseType samlObj, + final SignatureTrustEngine sigTrustEngine, final QName defaultElementName) + throws InvalidProtocolRequestException { + final SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); + try { + profileValidator.validate(samlObj.getSignature()); + performSchemaValidation(samlObj.getDOM()); + + } catch (final ValidationException e) { + log.warn("Signature is not conform to SAML signature profile", e); + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); + + } catch (final SchemaValidationException e) { + throw new InvalidProtocolRequestException("pvp2.22", new Object[] {e.getMessage()}); + + } + + final 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 (final org.opensaml.xml.security.SecurityException e) { + log.warn("PVP2x message signature validation FAILED.", e); + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); + } + } + + private void verifyRequest(final RequestAbstractType samlObj, + final SignatureTrustEngine sigTrustEngine) throws InvalidProtocolRequestException { + final SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); + try { + profileValidator.validate(samlObj.getSignature()); + performSchemaValidation(samlObj.getDOM()); + + } catch (final ValidationException e) { + log.warn("Signature is not conform to SAML signature profile", e); + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); + + } catch (final SchemaValidationException e) { + throw new InvalidProtocolRequestException("pvp2.22", new Object[] {e.getMessage()}); + + } + + final 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 (final org.opensaml.xml.security.SecurityException e) { + log.warn("PVP2x message signature validation FAILED.", e); + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); + } + } + + protected void performSchemaValidation(final Element source) throws SchemaValidationException { + + String err = null; + try { + final Schema test = SAMLSchemaBuilder.getSAML11Schema(); + final Validator val = test.newValidator(); + val.validate(new DOMSource(source)); + log.debug("Schema validation check done OK"); + return; + + } catch (final SAXException e) { + err = e.getMessage(); + if (log.isDebugEnabled() || log.isTraceEnabled()) { + log.warn("Schema validation FAILED with exception:", e); + } else { + log.warn("Schema validation FAILED with message: " + e.getMessage()); + } + + } catch (final Exception e) { + err = e.getMessage(); + if (log.isDebugEnabled() || log.isTraceEnabled()) { + log.warn("Schema validation FAILED with exception:", e); + } else { + log.warn("Schema validation FAILED with message: " + e.getMessage()); + } + + } + + throw new SchemaValidationException("pvp2.22", new Object[] {err}); + + } + +} -- cgit v1.2.3