diff options
Diffstat (limited to 'connector/src/main/java/at/asitplus/eidas/specific/connector/verification')
2 files changed, 345 insertions, 0 deletions
diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java new file mode 100644 index 00000000..607f42df --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java @@ -0,0 +1,203 @@ +package at.asitplus.eidas.specific.connector.verification; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.saml2.core.AuthnContextClassRef; +import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration; +import org.opensaml.saml2.core.AuthnRequest; +import org.opensaml.saml2.core.NameID; +import org.opensaml.saml2.core.NameIDPolicy; +import org.opensaml.saml2.core.RequestedAuthnContext; +import org.opensaml.saml2.core.Scoping; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.opensaml.xml.XMLObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; +import at.asitplus.eidas.specific.connector.config.ServiceProviderConfiguration; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.data.PVPAttributeDefinitions; +import at.gv.egiz.eaaf.core.exceptions.AuthnRequestValidatorException; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException; +import at.gv.egiz.eaaf.modules.pvp2.api.reqattr.EAAFRequestedAttribute; +import at.gv.egiz.eaaf.modules.pvp2.api.reqattr.EAAFRequestedAttributes; +import at.gv.egiz.eaaf.modules.pvp2.api.validation.IAuthnRequestValidator; +import at.gv.egiz.eaaf.modules.pvp2.exception.NameIDFormatNotSupportedException; + +public class AuthnRequestValidator implements IAuthnRequestValidator { + + private static final Logger log = LoggerFactory.getLogger(AuthnRequestValidator.class); + + @Override + public void validate(HttpServletRequest httpReq, IRequest pendingReq, AuthnRequest authnReq, + SPSSODescriptor spSSODescriptor) throws AuthnRequestValidatorException { + try { + //validate NameIDPolicy + NameIDPolicy nameIDPolicy = authnReq.getNameIDPolicy(); + if (nameIDPolicy != null) { + String nameIDFormat = nameIDPolicy.getFormat(); + if (nameIDFormat != null) { + if ( !(NameID.TRANSIENT.equals(nameIDFormat) || + NameID.PERSISTENT.equals(nameIDFormat)) ) { + + throw new NameIDFormatNotSupportedException(nameIDFormat); + + } + + } else + log.trace("Find NameIDPolicy, but NameIDFormat is 'null'"); + } else + log.trace("AuthnRequest includes no 'NameIDPolicy'"); + + + //post-process RequesterId + String spEntityId = extractScopeRequsterId(authnReq); + if (StringUtils.isEmpty(spEntityId)) { + log.info("NO service-provider entityID in Authn. request. Stop authn. process ... "); + throw new AuthnRequestValidatorException("pvp2.22", + new Object[] {"NO relaying-party entityID in Authn. request"}, pendingReq); + + } else + pendingReq.setRawDataToTransaction(MSeIDASNodeConstants.DATA_REQUESTERID, spEntityId); + + + //post-process ProviderName + String providerName = authnReq.getProviderName(); + if (StringUtils.isEmpty(providerName)) + log.info("Authn. request contains NO SP friendlyName"); + else + pendingReq.setRawDataToTransaction(MSeIDASNodeConstants.DATA_PROVIDERNAME, spEntityId); + + //post-process requested LoA + List<String> reqLoA = extractLoA(authnReq); + pendingReq.getServiceProviderConfiguration(ServiceProviderConfiguration.class).setRequiredLoA(reqLoA); + + //post-process requested LoA comparison-level + String reqLoAComperison = extractComparisonLevel(authnReq); + pendingReq.getServiceProviderConfiguration(ServiceProviderConfiguration.class).setLoAMachtingMode(reqLoAComperison); + + //validate and process requested attributes + boolean sectorDetected = false; + List<XMLObject> requestedAttributes = authnReq.getExtensions().getUnknownXMLObjects(); + for (XMLObject reqAttrObj : requestedAttributes) { + if (reqAttrObj instanceof EAAFRequestedAttributes) { + EAAFRequestedAttributes reqAttr = (EAAFRequestedAttributes)reqAttrObj; + if (reqAttr.getAttributes() != null && reqAttr.getAttributes().size() != 0 ) { + for (EAAFRequestedAttribute el : reqAttr.getAttributes()) { + log.trace("Processing req. attribute '" + el.getName() + "' ... "); + if (el.getName().equals(PVPAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME)) { + if (el.getAttributeValues() != null && el.getAttributeValues().size() == 1) { + String sectorId = el.getAttributeValues().get(0).getDOM().getTextContent(); + ServiceProviderConfiguration spConfig = pendingReq.getServiceProviderConfiguration(ServiceProviderConfiguration.class); + + try { + spConfig.setbPKTargetIdentifier(sectorId); + sectorDetected = true; + + } catch (EAAFException e) { + log.info("Requested sector: " + sectorId + " DOES NOT match to allowed sectors for SP: " + spConfig.getUniqueIdentifier()); + } + + } else + log.info("Req. attribute '" + el.getName() + "' contains NO or MORE THEN ONE attribute-values. Ignore full req. attribute"); + + } else + log.debug("Ignore req. attribute: " + el.getName()); + + } + + } else + log.debug("No requested Attributes in Authn. Request"); + + } else + log.info("Ignore unknown requested attribute: " + reqAttrObj.getElementQName().toString()); + + } + + if (!sectorDetected) { + log.info("Authn.Req validation FAILED. Reason: Contains NO or NO VALID target-sector information."); + throw new AuthnRequestValidatorException("pvp2.22", new Object[] {"NO or NO VALID target-sector information"}); + + } + + } catch (EAAFStorageException e) { + log.info("Can NOT store Authn. Req. data into pendingRequest." , e); + throw new AuthnRequestValidatorException("internal.02", null, e); + + } + + } + + private String extractComparisonLevel(AuthnRequest authnReq) { + if (authnReq.getRequestedAuthnContext() != null) { + RequestedAuthnContext authContext = authnReq.getRequestedAuthnContext(); + return authContext.getComparison().toString(); + + } + + return null; + } + + private List<String> extractLoA(AuthnRequest authnReq) throws AuthnRequestValidatorException { + List<String> result = new ArrayList<String>(); + if (authnReq.getRequestedAuthnContext() != null) { + RequestedAuthnContext authContext = authnReq.getRequestedAuthnContext(); + if (authContext.getComparison().equals(AuthnContextComparisonTypeEnumeration.MINIMUM)) { + if (authContext.getAuthnContextClassRefs().isEmpty()) { + log.debug("Authn. Req. contains no requested LoA"); + + } else if (authContext.getAuthnContextClassRefs().size() > 1) { + log.info("Authn. Req. contains MORE THAN ONE requested LoA, but " + + AuthnContextComparisonTypeEnumeration.MINIMUM + " allows only one" ); + throw new AuthnRequestValidatorException("pvp2.22", + new Object[] {"Authn. Req. contains MORE THAN ONE requested LoA, but " + + AuthnContextComparisonTypeEnumeration.MINIMUM + " allows only one"}); + + } else + result.add(authContext.getAuthnContextClassRefs().get(0).getAuthnContextClassRef()); + + } else if (authContext.getComparison().equals(AuthnContextComparisonTypeEnumeration.EXACT)) { + for (AuthnContextClassRef el : authContext.getAuthnContextClassRefs()) + result.add(el.getAuthnContextClassRef()); + + } else { + log.info("Currently only '" + AuthnContextComparisonTypeEnumeration.MINIMUM + "' and '" + + AuthnContextComparisonTypeEnumeration.EXACT + "' are supported"); + throw new AuthnRequestValidatorException("pvp2.22", + new Object[] {"Currently only '" + AuthnContextComparisonTypeEnumeration.MINIMUM + "' and '" + + AuthnContextComparisonTypeEnumeration.EXACT + "' are supported"}); + + } + + } + + return result; + } + + private String extractScopeRequsterId(AuthnRequest authnReq) { + if (authnReq.getScoping() != null) { + Scoping scoping = authnReq.getScoping(); + if (scoping.getRequesterIDs() != null && + scoping.getRequesterIDs().size() > 0) { + if (scoping.getRequesterIDs().size() == 1) + return scoping.getRequesterIDs().get(0).getRequesterID(); + + else { + log.info("Authn. request contains more than on RequesterIDs! Only use first one"); + return scoping.getRequesterIDs().get(0).getRequesterID(); + + } + } + } + + return null; + } + + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/MetadataSignatureVerificationFilter.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/MetadataSignatureVerificationFilter.java new file mode 100644 index 00000000..67d2d59b --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/MetadataSignatureVerificationFilter.java @@ -0,0 +1,142 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.verification; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import org.opensaml.common.SignableSAMLObject; +import org.opensaml.saml2.metadata.EntitiesDescriptor; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.security.SAMLSignatureProfileValidator; +import org.opensaml.xml.security.x509.BasicX509Credential; +import org.opensaml.xml.signature.SignatureValidator; +import org.opensaml.xml.validation.ValidationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2MetadataException; +import at.gv.egiz.eaaf.modules.pvp2.idp.exception.SAMLRequestNotSignedException; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata.AbstractMetadataSignatureFilter; + +public class MetadataSignatureVerificationFilter extends AbstractMetadataSignatureFilter{ + private static final Logger log = LoggerFactory.getLogger(MetadataSignatureVerificationFilter.class); + + private String metadataURL; + private List<BasicX509Credential> trustedCredential = new ArrayList<BasicX509Credential>(); + + public MetadataSignatureVerificationFilter(String trustStorePath, String trustStorePassword, String metadataURL) + throws PVP2MetadataException { + this.metadataURL = metadataURL; + + log.trace("Initialize metadata signature-verification filter with truststore: " + trustStorePath + " ... "); + try { + KeyStore keyStore = KeyStoreUtils.loadKeyStore(trustStorePath, trustStorePassword); + if (keyStore != null) { + //load trusted certificates + Enumeration<String> aliases = keyStore.aliases(); + while(aliases.hasMoreElements()) { + String el = aliases.nextElement(); + log.trace("Process TrustStoreEntry: " + el); + if (keyStore.isCertificateEntry(el)) { + Certificate cert = keyStore.getCertificate(el); + if (cert != null && cert instanceof X509Certificate) { + BasicX509Credential trustedCert = new BasicX509Credential(); + trustedCert.setEntityCertificate((X509Certificate) cert); + this.trustedCredential.add(trustedCert); + log.debug("Add cert: " + ((X509Certificate) cert).getSubjectDN() + " as trusted for metadata: " + metadataURL); + + } else + log.info("Can not process entry: " + el + ". Reason: " + cert.toString()); + + } + } + + + } else + throw new PVP2MetadataException("pvp2.26", + new Object[] {"Can not open trustStore: " + trustStorePath + " for metadata: " + metadataURL}); + + } catch (KeyStoreException | IOException e) { + log.warn("Can not open trustStore: " + trustStorePath + " for metadata: " + metadataURL + " Reason: " + e.getMessage(), e); + throw new PVP2MetadataException("pvp2.26", + new Object[] {"Can not open trustStore: " + trustStorePath + " for metadata"}, e); + + } + + + } + + + @Override + protected void verify(EntityDescriptor desc) throws PVP2MetadataException { + try { + internalVerify(desc); + + } catch (EAAFException e) { + log.info("Metadata verification FAILED for: " + metadataURL + " Reason: " +e.getMessage()); + throw new PVP2MetadataException("pvp2.26", + new Object[] {"Metadata verification FAILED for: " + metadataURL + " Reason: " +e.getMessage()}, e); + + } + } + + @Override + protected void verify(EntitiesDescriptor desc) throws PVP2MetadataException { + throw new PVP2MetadataException("pvp2.26", + new Object[] {"EntitiesDescritors are NOT supported"}); + + } + + @Override + protected void verify(EntityDescriptor entity, EntitiesDescriptor desc) throws PVP2MetadataException { + throw new PVP2MetadataException("pvp2.26", + new Object[] {"EntitiesDescritors are NOT supported"}); + + } + + private void internalVerify(SignableSAMLObject signedElement) + throws EAAFException { + if (signedElement.getSignature() == null) { + throw new SAMLRequestNotSignedException(); + } + + try { + SAMLSignatureProfileValidator sigValidator = new SAMLSignatureProfileValidator(); + sigValidator.validate(signedElement.getSignature()); + } catch (ValidationException e) { + log.error("Failed to validate Signature", e); + throw new SAMLRequestNotSignedException(e); + } + + boolean isTrusted = false; + for (BasicX509Credential cred : trustedCredential) { + SignatureValidator sigValidator = new SignatureValidator(cred); + try { + sigValidator.validate(signedElement.getSignature()); + isTrusted = true; + + } catch (ValidationException e) { + log.info("Failed to verfiy Signature with cert: " + cred.getEntityCertificate().getSubjectDN() + + " Reason: " + e.getMessage()); + + } + } + + if (!isTrusted) { + log.warn("PVP2 metadata: " + metadataURL + " are NOT trusted!"); + throw new SAMLRequestNotSignedException(); + + } + + } + +} |