From e7610325ee2f1d1f4e97e1e7a9b212e692836b5a Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Tue, 4 Feb 2020 17:37:34 +0100 Subject: first stable version that uses OpenSAML 3.x --- .../Pvp2SProfileCoreSpringResourceProvider.java | 4 +- .../at/gv/egiz/eaaf/modules/pvp2/PvpConstants.java | 48 +-- .../eaaf/modules/pvp2/api/binding/IDecoder.java | 3 +- .../eaaf/modules/pvp2/api/binding/IEncoder.java | 2 +- .../pvp2/api/credential/EaafX509Credential.java | 1 - .../metadata/IPvpMetadataBuilderConfiguration.java | 6 +- .../api/metadata/IRefreshableMetadataProvider.java | 2 +- .../api/validation/IAuthnRequestPostProcessor.java | 14 +- .../pvp2/exception/Pvp2InternalErrorException.java | 12 + .../exception/SamlMessageValidationException.java | 12 + .../exception/SamlMetadataSignatureException.java | 26 +- .../modules/pvp2/impl/binding/AbstractBinding.java | 127 +++++++- .../modules/pvp2/impl/binding/PostBinding.java | 147 +++++---- .../modules/pvp2/impl/binding/RedirectBinding.java | 234 +++------------ .../modules/pvp2/impl/binding/SoapBinding.java | 104 +++---- .../pvp2/impl/builder/CitizenTokenBuilder.java | 3 +- .../pvp2/impl/builder/PvpMetadataBuilder.java | 53 ++-- .../pvp2/impl/builder/SamlAttributeGenerator.java | 3 +- .../pvp2/impl/logging/PvpModuleMessageSource.java | 1 - .../modules/pvp2/impl/message/InboundMessage.java | 11 +- .../metadata/AbstractChainingMetadataProvider.java | 334 ++++++++++----------- .../impl/metadata/PvpMetadataResolverAdapter.java | 39 ++- .../impl/metadata/PvpMetadataResolverFactory.java | 293 +++++++++--------- .../pvp2/impl/opensaml/EaafHttpPostDecoder.java | 60 ++-- .../opensaml/EaafHttpRedirectDeflateDecoder.java | 37 ++- .../EaafKeyStoreX509CredentialAdapter.java | 20 +- .../opensaml/HttpPostEncoderWithOwnTemplate.java | 1 - .../impl/opensaml/OpenSaml3ResourceAdapter.java | 11 +- .../opensaml/StringRedirectDeflateEncoder.java | 10 +- .../initialize/EaafOpenSaml3xInitializer.java | 8 +- .../modules/pvp2/impl/utils/QaaLevelVerifier.java | 6 +- .../eaaf/modules/pvp2/impl/utils/Saml2Utils.java | 80 ++--- .../modules/pvp2/impl/utils/SamlHttpUtils.java | 33 ++ .../pvp2/impl/validation/TrustEngineFactory.java | 47 ++- .../metadata/SchemaValidationFilter.java | 19 +- .../SimpleMetadataSignatureVerificationFilter.java | 146 +++++++++ .../EaafMessageContextInitializationHandler.java | 41 +++ ...ttpRedirectDeflateSignatureSecurityHandler.java | 107 +++++++ ...ProtocolMessageXmlSignatureSecurityHandler.java | 75 +++++ .../verification/PvpSamlMessageHandlerChain.java | 2 +- .../impl/verification/SamlVerificationEngine.java | 7 +- 41 files changed, 1329 insertions(+), 860 deletions(-) create mode 100644 eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/Pvp2InternalErrorException.java create mode 100644 eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SamlMessageValidationException.java create mode 100644 eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/SamlHttpUtils.java create mode 100644 eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/SimpleMetadataSignatureVerificationFilter.java create mode 100644 eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/EaafSaml2HttpRedirectDeflateSignatureSecurityHandler.java create mode 100644 eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/EaafSamlProtocolMessageXmlSignatureSecurityHandler.java (limited to 'eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf') diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/Pvp2SProfileCoreSpringResourceProvider.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/Pvp2SProfileCoreSpringResourceProvider.java index 2779ee1d..232e4ae9 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/Pvp2SProfileCoreSpringResourceProvider.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/Pvp2SProfileCoreSpringResourceProvider.java @@ -19,11 +19,11 @@ package at.gv.egiz.eaaf.modules.pvp2; +import at.gv.egiz.components.spring.api.SpringResourceProvider; + import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; -import at.gv.egiz.components.spring.api.SpringResourceProvider; - public class Pvp2SProfileCoreSpringResourceProvider implements SpringResourceProvider { @Override diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/PvpConstants.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/PvpConstants.java index b1ac8e75..69b94255 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/PvpConstants.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/PvpConstants.java @@ -36,11 +36,15 @@ import org.opensaml.xmlsec.signature.support.SignatureConstants; import com.google.common.collect.ImmutableMap; public interface PvpConstants extends PvpAttributeDefinitions { - //module configuration parameters + // module configuration parameters String CONFIG_PROP_SEC_SIGNING_RSA_ALG = "pvp2.security.alg.signing.rsa"; String CONFIG_PROP_SEC_SIGNING_EC_ALG = "pvp2.security.alg.signing.ec"; + String CONFIG_PROP_SEC_ENCRYPTION_DATA = "pvp2.security.alg.enc.data"; + String CONFIG_PROP_SEC_ENCRYPTION_KEY_RSA_ALG = "pvp2.security.alg.enc.key.rsa"; + String CONFIG_PROP_SEC_ENCRYPTION_KEY_EC_ALG = "pvp2.security.alg.enc.key.ec"; + String CONFIG_PROPERTY_PVP2_ENABLE_ENCRYPTION = "pvp2.assertion.encryption.active"; - //Default values + // Default values String DEFAULT_SIGNING_METHODE_RSA = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; String DEFAULT_SIGNING_METHODE_EC = @@ -49,12 +53,13 @@ public interface PvpConstants extends PvpAttributeDefinitions { String DEFAULT_DIGESTMETHODE = SignatureConstants.ALGO_ID_DIGEST_SHA256; String DEFAULT_SYM_ENCRYPTION_METHODE = - EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256; - String DEFAULT_ASYM_ENCRYPTION_METHODE = + EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128_GCM; + String DEFAULT_ASYM_ENCRYPTION_METHODE_RSA = EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSAOAEP; + String DEFAULT_ASYM_ENCRYPTION_METHODE_EC = + EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH; - - //PVP entity categories + // PVP entity categories String ENTITY_CATEGORY_ATTRIBITE = "http://macedir.org/entity-category"; String EGOVTOKEN = "http://www.ref.gv.at/ns/names/agiz/pvp/egovtoken"; String CITIZENTOKEN = "http://www.ref.gv.at/ns/names/agiz/pvp/citizentoken"; @@ -152,19 +157,22 @@ public interface PvpConstants extends PvpAttributeDefinitions { ImmutableMap SIGNATURE_TO_DIGEST_ALGORITHM_MAP = ImmutableMap.builder() - .put(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256, SignatureConstants.ALGO_ID_DIGEST_SHA256) - .put(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA384, SignatureConstants.ALGO_ID_DIGEST_SHA384) - .put(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512, SignatureConstants.ALGO_ID_DIGEST_SHA512) - .put(SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA256, SignatureConstants.ALGO_ID_DIGEST_SHA256) - .put(SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA384, SignatureConstants.ALGO_ID_DIGEST_SHA384) - .put(SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA512, SignatureConstants.ALGO_ID_DIGEST_SHA512) - .put(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256_MGF1, SignatureConstants.ALGO_ID_DIGEST_SHA256) - .put(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA384_MGF1, SignatureConstants.ALGO_ID_DIGEST_SHA384) - .put(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA512_MGF1, SignatureConstants.ALGO_ID_DIGEST_SHA512) - .put(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA3_256_MGF1, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA3_256) - .put(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA3_384_MGF1, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA3_384) - .put(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA3_512_MGF1, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA3_512) - - .build(); + .put(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256, SignatureConstants.ALGO_ID_DIGEST_SHA256) + .put(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA384, SignatureConstants.ALGO_ID_DIGEST_SHA384) + .put(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512, SignatureConstants.ALGO_ID_DIGEST_SHA512) + .put(SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA256, SignatureConstants.ALGO_ID_DIGEST_SHA256) + .put(SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA384, SignatureConstants.ALGO_ID_DIGEST_SHA384) + .put(SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA512, SignatureConstants.ALGO_ID_DIGEST_SHA512) + .put(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256_MGF1, SignatureConstants.ALGO_ID_DIGEST_SHA256) + .put(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA384_MGF1, SignatureConstants.ALGO_ID_DIGEST_SHA384) + .put(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA512_MGF1, SignatureConstants.ALGO_ID_DIGEST_SHA512) + .put(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA3_256_MGF1, + MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA3_256) + .put(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA3_384_MGF1, + MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA3_384) + .put(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA3_512_MGF1, + MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA3_512) + + .build(); } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/binding/IDecoder.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/binding/IDecoder.java index e8da499c..83bfee84 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/binding/IDecoder.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/binding/IDecoder.java @@ -21,6 +21,7 @@ package at.gv.egiz.eaaf.modules.pvp2.api.binding; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.xml.namespace.QName; import at.gv.egiz.eaaf.modules.pvp2.api.message.InboundMessageInterface; import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; @@ -30,7 +31,7 @@ import net.shibboleth.utilities.java.support.net.URIComparator; public interface IDecoder { InboundMessageInterface decode(HttpServletRequest req, HttpServletResponse resp, - IPvp2MetadataProvider metadataProvider, boolean isSpEndPoint, URIComparator comparator) + IPvp2MetadataProvider metadataProvider, QName peerEntityRole, URIComparator comparator) throws Pvp2Exception; boolean handleDecode(String action, HttpServletRequest req); diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/binding/IEncoder.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/binding/IEncoder.java index 691d6574..5a8bc4fb 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/binding/IEncoder.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/binding/IEncoder.java @@ -60,7 +60,7 @@ public interface IEncoder { * @param credentials Credential to sign the response object * @param pendingReq Internal MOA-ID request object that contains * session-state informations but never null - * @throws SecurityException In case of an error + * @throws SecurityException In case of an error */ void encodeResponse(HttpServletRequest req, HttpServletResponse resp, StatusResponseType response, String targetLocation, String relayState, EaafX509Credential credentials, diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/credential/EaafX509Credential.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/credential/EaafX509Credential.java index 568b617d..ce6451c0 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/credential/EaafX509Credential.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/credential/EaafX509Credential.java @@ -4,7 +4,6 @@ import javax.annotation.Nonnull; import org.opensaml.security.x509.X509Credential; - public interface EaafX509Credential extends X509Credential { /** diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IPvpMetadataBuilderConfiguration.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IPvpMetadataBuilderConfiguration.java index 128d4c2f..3d9125fe 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IPvpMetadataBuilderConfiguration.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IPvpMetadataBuilderConfiguration.java @@ -22,15 +22,15 @@ package at.gv.egiz.eaaf.modules.pvp2.api.metadata; import java.util.Collection; import java.util.List; +import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; +import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; + import org.opensaml.saml.saml2.core.Attribute; import org.opensaml.saml.saml2.metadata.ContactPerson; import org.opensaml.saml.saml2.metadata.Organization; import org.opensaml.saml.saml2.metadata.RequestedAttribute; import org.opensaml.security.credential.Credential; -import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; -import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; - /** * PVP Metadata builder configuration. * diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IRefreshableMetadataProvider.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IRefreshableMetadataProvider.java index 39536771..cc492345 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IRefreshableMetadataProvider.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IRefreshableMetadataProvider.java @@ -27,7 +27,7 @@ import org.opensaml.saml.metadata.resolver.RefreshableMetadataResolver; * @author tlenz * */ -public interface IRefreshableMetadataProvider extends RefreshableMetadataResolver{ +public interface IRefreshableMetadataProvider extends RefreshableMetadataResolver { /** * Refresh a entity or load a entity in a metadata provider. diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/validation/IAuthnRequestPostProcessor.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/validation/IAuthnRequestPostProcessor.java index 2e84413e..9f7a5980 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/validation/IAuthnRequestPostProcessor.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/validation/IAuthnRequestPostProcessor.java @@ -31,7 +31,8 @@ import org.opensaml.saml.saml2.metadata.SPSSODescriptor; * SAML2 Authn. request post-processor. * *

- * Implementations of this interface are executed before user authentication starts. + * Implementations of this interface are executed before user authentication + * starts. *

* * @author tlenz @@ -42,12 +43,13 @@ public interface IAuthnRequestPostProcessor { /** * Authn. request post-processor * - * @param httpReq http request - * @param pendingReq current pending request - * @param authReq received SAML2 authentication request + * @param httpReq http request + * @param pendingReq current pending request + * @param authReq received SAML2 authentication request * @param spSsoDescriptor Metadata descriptor of the requested SP - * @throws AuthnRequestValidatorException In case of a validation error, - * if post processor implements additional validation + * @throws AuthnRequestValidatorException In case of a validation error, if post + * processor implements additional + * validation */ void process(HttpServletRequest httpReq, IRequest pendingReq, AuthnRequest authReq, SPSSODescriptor spSsoDescriptor) throws AuthnRequestValidatorException; diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/Pvp2InternalErrorException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/Pvp2InternalErrorException.java new file mode 100644 index 00000000..0b69897b --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/Pvp2InternalErrorException.java @@ -0,0 +1,12 @@ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +public class Pvp2InternalErrorException extends Pvp2Exception { + + private static final long serialVersionUID = 496637421176810375L; + + public Pvp2InternalErrorException(Throwable wrapped) { + super("internal.pvp.98", new Object[] { wrapped.getMessage() }, wrapped); + + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SamlMessageValidationException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SamlMessageValidationException.java new file mode 100644 index 00000000..774d0927 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SamlMessageValidationException.java @@ -0,0 +1,12 @@ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +public class SamlMessageValidationException extends Pvp2Exception { + + private static final long serialVersionUID = 2545822499416501014L; + + public SamlMessageValidationException(String messageId, Object[] parameters, Throwable wrapped) { + super(messageId, parameters, wrapped); + + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SamlMetadataSignatureException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SamlMetadataSignatureException.java index 711fa41f..9ef3a673 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SamlMetadataSignatureException.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SamlMetadataSignatureException.java @@ -21,17 +21,33 @@ package at.gv.egiz.eaaf.modules.pvp2.exception; import org.opensaml.saml.saml2.core.StatusCode; -public class SamlMetadataSignatureException extends Pvp2Exception { +public class SamlMetadataSignatureException extends Pvp2MetadataException { private static final long serialVersionUID = 1L; - public SamlMetadataSignatureException() { - super("pvp2.25", null); + /** + * In case of a SAML2 metadata-signature verification error. + * + * @param metadataUrl Path metadata that should be loaded + * @param reason Details on error + * + */ + public SamlMetadataSignatureException(String metadataUrl, String reason) { + super("internal.pvp.07", new Object[] { metadataUrl, reason }); this.statusCodeValue = StatusCode.REQUESTER; + } - public SamlMetadataSignatureException(final Throwable e) { - super("pvp2.25", null, e); + /** + * In case of a SAML2 metadata-signature verification error. + * + * @param metadataUrl Path metadata that should be loaded + * @param reason Details on error + * @param e Error + */ + public SamlMetadataSignatureException(String metadataUrl, String reason, final Throwable e) { + super("internal.pvp.07", new Object[] { metadataUrl, reason }, e); this.statusCodeValue = StatusCode.REQUESTER; + } } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/AbstractBinding.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/AbstractBinding.java index ae108c35..3543d85a 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/AbstractBinding.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/AbstractBinding.java @@ -1,15 +1,29 @@ package at.gv.egiz.eaaf.modules.pvp2.impl.binding; +import javax.xml.namespace.QName; + import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; +import at.gv.egiz.eaaf.modules.pvp2.api.message.InboundMessageInterface; import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2Exception; +import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2InternalErrorException; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlBindingException; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlMessageValidationException; import at.gv.egiz.eaaf.modules.pvp2.exception.SamlSigningException; +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 at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; import at.gv.egiz.eaaf.modules.pvp2.impl.validation.TrustEngineFactory; +import at.gv.egiz.eaaf.modules.pvp2.impl.verification.PvpSamlMessageHandlerChain; import org.opensaml.core.config.ConfigurationService; import org.opensaml.messaging.context.BaseContext; import org.opensaml.messaging.context.MessageContext; +import org.opensaml.messaging.decoder.MessageDecodingException; +import org.opensaml.messaging.decoder.servlet.HttpServletRequestMessageDecoder; +import org.opensaml.messaging.handler.MessageHandlerException; import org.opensaml.saml.common.SAMLObject; import org.opensaml.saml.common.SignableSAMLObject; import org.opensaml.saml.common.binding.SAMLBindingSupport; @@ -19,6 +33,8 @@ import org.opensaml.saml.common.messaging.context.SAMLMessageInfoContext; import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext; import org.opensaml.saml.common.messaging.context.SAMLProtocolContext; import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.core.RequestAbstractType; +import org.opensaml.saml.saml2.core.StatusResponseType; import org.opensaml.saml.saml2.metadata.SingleSignOnService; import org.opensaml.saml.saml2.metadata.impl.SingleSignOnServiceBuilder; import org.opensaml.xmlsec.SignatureSigningParameters; @@ -28,21 +44,62 @@ import org.opensaml.xmlsec.context.SecurityParametersContext; import org.opensaml.xmlsec.signature.support.SignatureConstants; import org.springframework.beans.factory.annotation.Autowired; +import com.google.common.base.Optional; +import com.google.common.base.Predicates; +import com.google.common.base.Throwables; +import com.google.common.collect.FluentIterable; +import lombok.extern.slf4j.Slf4j; +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; + /** * Abstract Binding implements common code for SAML2 binding implementations. * * @author tlenz * */ +@Slf4j public abstract class AbstractBinding { - @Autowired protected IConfiguration basicConfig; + @Autowired + protected IConfiguration basicConfig; public abstract String getSaml2BindingName(); + protected MessageContext internalMessageDecode( + HttpServletRequestMessageDecoder decoder, + String binding) throws Pvp2Exception { + try { + decoder.initialize(); + decoder.decode(); + + } catch (final ComponentInitializationException e) { + log.warn("Internal initialization error. Reason: {}", e.getMessage()); + throw new Pvp2InternalErrorException(e); + + } catch (final MessageDecodingException e) { + final Optional pvpException = FluentIterable.from( + Throwables.getCausalChain(e)).filter( + Predicates.instanceOf(Pvp2Exception.class)).first(); + + if (pvpException.isPresent()) { + throw (Pvp2Exception) pvpException.get(); + + } else { + throw new SamlBindingException("internal.pvp.95", + new Object[] { binding, "decoding", e.getMessage() }, + e); + + } + + } + + return decoder.getMessageContext(); + + } + protected MessageContext buildBasicMessageContext( SAMLMessageEncoder encoder, SignableSAMLObject response) { - final MessageContext messageContext = new MessageContext(); + final MessageContext messageContext = new MessageContext<>(); messageContext.setMessage(response); encoder.setMessageContext(messageContext); return messageContext; @@ -63,7 +120,7 @@ public abstract class AbstractBinding { signingParams.setSignatureReferenceDigestMethod( Saml2Utils.getDigestAlgorithm(signingParams.getSignatureAlgorithm())); - signingParams.setKeyInfoGenerator(Saml2Utils.getKeyInfoGenerator(credentials, false)); + signingParams.setKeyInfoGenerator(Saml2Utils.getKeyInfoGenerator(credentials, true)); return securityParamContext; @@ -83,16 +140,16 @@ public abstract class AbstractBinding { } protected void injectInboundMessageContexts(MessageContext messageContext, - IPvp2MetadataProvider metadataProvider) { - messageContext.addSubcontext(new SAMLPeerEntityContext()); + IPvp2MetadataProvider metadataProvider, QName peerEntityRole) throws Pvp2InternalErrorException { + final SAMLPeerEntityContext peerEntityContext = new SAMLPeerEntityContext(); + peerEntityContext.setRole(peerEntityRole); + messageContext.addSubcontext(peerEntityContext); messageContext.addSubcontext(new SAMLMessageInfoContext()); - final SAMLProtocolContext protocolContext = new SAMLProtocolContext(); protocolContext.setProtocol(SAMLConstants.SAML20P_NS); messageContext.addSubcontext(protocolContext); - final SecurityParametersContext securityParameterContext = new SecurityParametersContext(); final SignatureValidationParameters sigValParameters = new SignatureValidationParameters(); securityParameterContext.setSignatureValidationParameters(sigValParameters); @@ -100,9 +157,63 @@ public abstract class AbstractBinding { sigValParameters.setBlacklistedAlgorithms( ConfigurationService.get(SignatureValidationConfiguration.class) - .getBlacklistedAlgorithms()); + .getBlacklistedAlgorithms()); sigValParameters.setSignatureTrustEngine( TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider)); } + + protected void performMessageValidation(PvpSamlMessageHandlerChain messageValidatorChain, + MessageContext messageContext) throws Pvp2Exception { + try { + messageValidatorChain.initialize(); + messageValidatorChain.invoke(messageContext); + + } catch (final ComponentInitializationException e) { + log.warn("Internal initialization error. Reason: {}", e.getMessage()); + throw new Pvp2InternalErrorException(e); + + } catch (final MessageHandlerException e) { + log.info("SAML message validation error. Reason: {}", e.getMessage()); + final Optional pvpException = FluentIterable.from( + Throwables.getCausalChain(e)).filter( + Predicates.instanceOf(Pvp2Exception.class)).first(); + + if (pvpException.isPresent()) { + throw (Pvp2Exception) pvpException.get(); + + } else { + throw new SamlMessageValidationException("internal.pvp.11", + new Object[] { e.getMessage() }, e); + + } + } + } + + protected InboundMessageInterface performMessageDecodePostProcessing( + MessageContext messageContext, boolean isVerified) { + InboundMessage msg = null; + if (messageContext.getMessage() instanceof RequestAbstractType) { + final RequestAbstractType inboundMessage = + (RequestAbstractType) messageContext.getMessage(); + msg = new PvpSProfileRequest(inboundMessage, getSaml2BindingName()); + msg.setEntityID(inboundMessage.getIssuer().getValue()); + + } else if (messageContext.getMessage() instanceof StatusResponseType) { + final StatusResponseType inboundMessage = + (StatusResponseType) messageContext.getMessage(); + msg = new PvpSProfileResponse(inboundMessage); + msg.setEntityID(inboundMessage.getIssuer().getValue()); + + } else { + // create empty container if request type is unknown + msg = new InboundMessage(); + + } + + msg.setVerified(isVerified); + msg.setRelayState(SAMLBindingSupport.getRelayState(messageContext)); + + return msg; + } } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/PostBinding.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/PostBinding.java index 6f39392d..c679de20 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/PostBinding.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/PostBinding.java @@ -21,6 +21,7 @@ package at.gv.egiz.eaaf.modules.pvp2.impl.binding; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.xml.namespace.QName; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.gui.IGuiBuilderConfigurationFactory; @@ -36,16 +37,17 @@ import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; import at.gv.egiz.eaaf.modules.pvp2.exception.InvalidPvpRequestException; import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2Exception; import at.gv.egiz.eaaf.modules.pvp2.exception.SamlBindingException; -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 at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.EaafHttpPostDecoder; import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.HttpPostEncoderWithOwnTemplate; +import at.gv.egiz.eaaf.modules.pvp2.impl.verification.EaafSamlProtocolMessageXmlSignatureSecurityHandler; +import at.gv.egiz.eaaf.modules.pvp2.impl.verification.PvpSamlMessageHandlerChain; import org.opensaml.messaging.context.MessageContext; -import org.opensaml.messaging.decoder.MessageDecodingException; import org.opensaml.saml.common.SAMLObject; import org.opensaml.saml.common.binding.SAMLBindingSupport; +import org.opensaml.saml.common.binding.impl.CheckMessageVersionHandler; +import org.opensaml.saml.common.binding.security.impl.MessageLifetimeSecurityHandler; +import org.opensaml.saml.common.binding.security.impl.ReceivedEndpointSecurityHandler; import org.opensaml.saml.common.messaging.SAMLMessageSecuritySupport; import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.core.RequestAbstractType; @@ -53,7 +55,6 @@ import org.opensaml.saml.saml2.core.StatusResponseType; import org.springframework.beans.factory.annotation.Autowired; import lombok.extern.slf4j.Slf4j; -import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.net.URIComparator; @Slf4j @@ -78,35 +79,34 @@ public class PostBinding extends AbstractBinding implements IDecoder, IEncoder { guiConfigFactory.getSpSpecificSaml2PostConfiguration(pendingReq, "pvp_postbinding_template.html", authConfig.getConfigurationRootDirectory()); - final HttpPostEncoderWithOwnTemplate encoder - = new HttpPostEncoderWithOwnTemplate(guiConfig, guiBuilder); + final HttpPostEncoderWithOwnTemplate encoder = new HttpPostEncoderWithOwnTemplate(guiConfig, + guiBuilder); encoder.setHttpServletResponse(httpResp); - //inject message context + // inject message context final MessageContext messageContext = buildBasicMessageContext(encoder, request); - //inject signing context + // inject signing context messageContext.addSubcontext(injectSigningInfos(credentials)); - //set endpoint url + // set endpoint url messageContext.addSubcontext(injectEndpointInfos(request, targetLocation)); - - //set relayState of exists + // set relayState of exists SAMLBindingSupport.setRelayState(messageContext, relayState); - //sign SAML2 message + // sign SAML2 message SAMLMessageSecuritySupport.signMessage(messageContext); - //encode message + // encode message encoder.initialize(); encoder.encode(); } catch (final Exception e) { log.warn("Can not encode SAML2 Post-Binding request", e); throw new SamlBindingException("internal.pvp.95", - new Object[] {PvpConstants.POST, "encoding", e.getMessage()}, + new Object[] { PvpConstants.POST, "encoding", e.getMessage() }, e); } @@ -116,7 +116,7 @@ public class PostBinding extends AbstractBinding implements IDecoder, IEncoder { public void encodeResponse(final HttpServletRequest httpReq, final HttpServletResponse httpResp, final StatusResponseType response, final String targetLocation, final String relayState, final EaafX509Credential credentials, final IRequest pendingReq) - throws Pvp2Exception { + throws Pvp2Exception { try { log.debug("create SAML POSTBinding response"); @@ -130,99 +130,62 @@ public class PostBinding extends AbstractBinding implements IDecoder, IEncoder { encoder.setHttpServletResponse(httpResp); - //inject message context + // inject message context final MessageContext messageContext = buildBasicMessageContext(encoder, response); - //inject signing context + // inject signing context messageContext.addSubcontext(injectSigningInfos(credentials)); - //set endpoint url + // set endpoint url messageContext.addSubcontext(injectEndpointInfos(response, targetLocation)); - //set relayState of exists + // set relayState of exists SAMLBindingSupport.setRelayState(messageContext, relayState); - //sign SAML2 message + // sign SAML2 message SAMLMessageSecuritySupport.signMessage(messageContext); - //encode message + // encode message encoder.initialize(); encoder.encode(); } catch (final Exception e) { log.warn("Can not encode SAML2 Post-Binding response", e); throw new SamlBindingException("internal.pvp.95", - new Object[] {PvpConstants.POST, "encoding", e.getMessage()}, + new Object[] { PvpConstants.POST, "encoding", e.getMessage() }, e); } } - - - - @Override public InboundMessageInterface decode(final HttpServletRequest req, final HttpServletResponse resp, final IPvp2MetadataProvider metadataProvider, - final boolean isSpEndPoint, final URIComparator comparator) + QName peerEntityRole, final URIComparator comparator) throws Pvp2Exception { - //TODO: check, if we should re-implement HTTPPostDecoder to collect the last http parameter!!! - final EaafHttpPostDecoder decode = new EaafHttpPostDecoder(); - decode.setHttpServletRequest(req); - - //decode request - try { - decode.initialize(); - decode.decode(); - - } catch (MessageDecodingException | ComponentInitializationException e) { - throw new SamlBindingException("internal.pvp.95", - new Object[] {PvpConstants.POST, "decoding", e.getMessage()}, - e); - } - - final MessageContext messageContext = decode.getMessageContext(); + final EaafHttpPostDecoder decode = new EaafHttpPostDecoder(req); + final MessageContext messageContext = internalMessageDecode(decode, PvpConstants.POST); - if (SAMLBindingSupport.isSigningCapableBinding(messageContext)) { + // check if PVP2 AuthnRequest is signed + if (!SAMLBindingSupport.isMessageSigned(messageContext)) { log.info("SAML Post-Binding message contains no signature. Message will be rejected"); throw new InvalidPvpRequestException("internal.pvp.02", null); } - //inject informations into message context that are required for further processing - injectInboundMessageContexts(messageContext, metadataProvider); + // inject informations into message context that are required for further + // processing + injectInboundMessageContexts(messageContext, metadataProvider, peerEntityRole); + final PvpSamlMessageHandlerChain messageValidatorChain = + buildMessageValidationChain(req, comparator, metadataProvider); + log.trace("Message validation (Signature, ...) on binding-level starts ... "); + performMessageValidation(messageValidatorChain, messageContext); - //TODO: add sig validation!!! + log.trace("Message validation successful"); + return performMessageDecodePostProcessing(messageContext, true); - - - InboundMessage msg = null; - - if (messageContext.getMessage() instanceof RequestAbstractType) { - final RequestAbstractType inboundMessage = - (RequestAbstractType) messageContext.getMessage(); - msg = new PvpSProfileRequest(inboundMessage, getSaml2BindingName()); - msg.setEntityID(inboundMessage.getIssuer().getValue()); - - } else if (messageContext.getMessage() instanceof StatusResponseType) { - final StatusResponseType inboundMessage = - (StatusResponseType) messageContext.getMessage(); - msg = new PvpSProfileResponse(inboundMessage); - msg.setEntityID(inboundMessage.getIssuer().getValue()); - - } else { - // create empty container if request type is unknown - msg = new InboundMessage(); - - } - - msg.setVerified(false); - msg.setRelayState(SAMLBindingSupport.getRelayState(messageContext)); - - return msg; } @Override @@ -234,5 +197,41 @@ public class PostBinding extends AbstractBinding implements IDecoder, IEncoder { @Override public String getSaml2BindingName() { return SAMLConstants.SAML2_POST_BINDING_URI; + + } + + private PvpSamlMessageHandlerChain buildMessageValidationChain(HttpServletRequest req, + URIComparator comparator, IPvp2MetadataProvider metadataProvider) { + final PvpSamlMessageHandlerChain messageValidatorChain = new PvpSamlMessageHandlerChain(); + + final ReceivedEndpointSecurityHandler endpointSecurityHandler = new ReceivedEndpointSecurityHandler(); + endpointSecurityHandler.setHttpServletRequest(req); + endpointSecurityHandler.setURIComparator(comparator); + + messageValidatorChain.addHandler(new CheckMessageVersionHandler()); + messageValidatorChain.addHandler(endpointSecurityHandler); + messageValidatorChain.addHandler( + new EaafSamlProtocolMessageXmlSignatureSecurityHandler(metadataProvider)); + messageValidatorChain.addHandler(new MessageLifetimeSecurityHandler()); + + /* + * TODO: maybe we add it in a later version Because: - AuthnRequest replay + * should not be a problem on IDP side - Response replay will be not possible, + * because EAAF PVP implements countermeasure based on one-time tokens for each + * request + * + */ + // final MessageReplaySecurityHandler replaySecurityHandler = new + // MessageReplaySecurityHandler(); + // final StorageService replayCacheStorage = null; + // final ReplayCache replayCache = new ReplayCache(); + // replayCache.setId("Message replay cache"); + // replayCache.setStrict(true); + // replayCache.setStorage(replayCacheStorage); + // replaySecurityHandler.setReplayCache(replayCache ); + // messageValidatorChain.addHandler(replaySecurityHandler); + + return messageValidatorChain; + } } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/RedirectBinding.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/RedirectBinding.java index 5f74053d..f62f8a11 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/RedirectBinding.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/RedirectBinding.java @@ -21,6 +21,7 @@ package at.gv.egiz.eaaf.modules.pvp2.impl.binding; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.xml.namespace.QName; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.modules.pvp2.PvpConstants; @@ -32,15 +33,11 @@ import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; import at.gv.egiz.eaaf.modules.pvp2.exception.InvalidPvpRequestException; import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2Exception; import at.gv.egiz.eaaf.modules.pvp2.exception.SamlBindingException; -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 at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.EaafHttpRedirectDeflateDecoder; +import at.gv.egiz.eaaf.modules.pvp2.impl.verification.EaafSaml2HttpRedirectDeflateSignatureSecurityHandler; import at.gv.egiz.eaaf.modules.pvp2.impl.verification.PvpSamlMessageHandlerChain; import org.opensaml.messaging.context.MessageContext; -import org.opensaml.messaging.decoder.MessageDecodingException; -import org.opensaml.messaging.handler.MessageHandlerException; import org.opensaml.saml.common.SAMLObject; import org.opensaml.saml.common.binding.SAMLBindingSupport; import org.opensaml.saml.common.binding.impl.CheckMessageVersionHandler; @@ -48,13 +45,11 @@ import org.opensaml.saml.common.binding.security.impl.MessageLifetimeSecurityHan import org.opensaml.saml.common.messaging.context.SAMLBindingContext; import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.binding.encoding.impl.HTTPRedirectDeflateEncoder; -import org.opensaml.saml.saml2.binding.security.impl.SAML2HTTPRedirectDeflateSignatureSecurityHandler; import org.opensaml.saml.saml2.core.RequestAbstractType; import org.opensaml.saml.saml2.core.StatusResponseType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.net.URIComparator; public class RedirectBinding extends AbstractBinding implements IDecoder, IEncoder { @@ -137,26 +132,11 @@ public class RedirectBinding extends AbstractBinding implements IDecoder, IEncod @Override public InboundMessageInterface decode(final HttpServletRequest req, final HttpServletResponse resp, final IPvp2MetadataProvider metadataProvider, - final boolean isSpEndPoint, final URIComparator comparator) + QName peerEntityRole, final URIComparator comparator) throws Pvp2Exception { - // TODO: implement one flat decoder to get SAML2 request/response parametes as - // same as in SAML2HTTPRedirectDeflateSignatureSecurityHandler - final EaafHttpRedirectDeflateDecoder decode = new EaafHttpRedirectDeflateDecoder(); - decode.setHttpServletRequest(req); - - // decode request - try { - decode.initialize(); - decode.decode(); - - } catch (MessageDecodingException | ComponentInitializationException e) { - throw new SamlBindingException("internal.pvp.95", - new Object[] { PvpConstants.REDIRECT, "decoding", e.getMessage() }, - e); - } - - final MessageContext messageContext = decode.getMessageContext(); + final EaafHttpRedirectDeflateDecoder decode = new EaafHttpRedirectDeflateDecoder(req); + final MessageContext messageContext = internalMessageDecode(decode, PvpConstants.REDIRECT); final SAMLBindingContext bindingContext = messageContext.getSubcontext(SAMLBindingContext.class, true); if (!bindingContext.hasBindingSignature()) { @@ -165,171 +145,18 @@ public class RedirectBinding extends AbstractBinding implements IDecoder, IEncod } - //inject informations into message context that are required for further processing - injectInboundMessageContexts(messageContext, metadataProvider); + // inject informations into message context that are required for further + // processing + injectInboundMessageContexts(messageContext, metadataProvider, peerEntityRole); + final PvpSamlMessageHandlerChain messageValidatorChain = + buildMessageValidationChain(req, metadataProvider); - log.trace("Signature validation on binding-level starts ... "); - final PvpSamlMessageHandlerChain messageValidatorChain = new PvpSamlMessageHandlerChain(); - final SAML2HTTPRedirectDeflateSignatureSecurityHandler redirectBindingSignaturHandler = - new SAML2HTTPRedirectDeflateSignatureSecurityHandler(); - redirectBindingSignaturHandler.setHttpServletRequest(req); - - messageValidatorChain.addHandler(new CheckMessageVersionHandler()); - messageValidatorChain.addHandler(redirectBindingSignaturHandler); - messageValidatorChain.addHandler(new MessageLifetimeSecurityHandler()); - - - /*TODO: maybe we add it in a later version - * Because: - * - AuthnRequest replay should not be a problem on IDP side - * - Response replay will be not possible, because EAAF PVP implements - * countermeasure based on one-time tokens for each request - * - */ - //final MessageReplaySecurityHandler replaySecurityHandler = new MessageReplaySecurityHandler(); - //final StorageService replayCacheStorage = null; - //final ReplayCache replayCache = new ReplayCache(); - //replayCache.setId("Message replay cache"); - //replayCache.setStrict(true); - //replayCache.setStorage(replayCacheStorage); - //replaySecurityHandler.setReplayCache(replayCache ); - //messageValidatorChain.addHandler(replaySecurityHandler); + log.trace("Message validation (Signature, ...) on binding-level starts ... "); + performMessageValidation(messageValidatorChain, messageContext); + log.trace("Message validation successful"); + return performMessageDecodePostProcessing(messageContext, true); - try { - messageValidatorChain.initialize(); - messageValidatorChain.invoke(messageContext); - - } catch (final ComponentInitializationException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - - } catch (final MessageHandlerException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - InboundMessage msg = null; - - if (messageContext.getMessage() instanceof RequestAbstractType) { - final RequestAbstractType inboundMessage = - (RequestAbstractType) messageContext.getMessage(); - msg = new PvpSProfileRequest(inboundMessage, getSaml2BindingName()); - msg.setEntityID(inboundMessage.getIssuer().getValue()); - - } else if (messageContext.getMessage() instanceof StatusResponseType) { - final StatusResponseType inboundMessage = - (StatusResponseType) messageContext.getMessage(); - msg = new PvpSProfileResponse(inboundMessage); - msg.setEntityID(inboundMessage.getIssuer().getValue()); - - } else { - // create empty container if request type is unknown - msg = new InboundMessage(); - - } - - msg.setVerified(false); - msg.setRelayState(SAMLBindingSupport.getRelayState(messageContext)); - - return msg; - -// final BasicSAMLMessageContext messageContext = -// new BasicSAMLMessageContext<>(); -// messageContext.setInboundMessageTransport(new HttpServletRequestAdapter(req)); -// -// // set metadata descriptor type -// if (isSpEndPoint) { -// messageContext.setPeerEntityRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME); -// decode.setURIComparator(comparator); -// -// } else { -// messageContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME); -// decode.setURIComparator(comparator); -// } -// -// messageContext.setMetadataProvider(metadataProvider); -// -// final SAML2HTTPRedirectDeflateSignatureRule signatureRule = -// new SAML2HTTPRedirectDeflateSignatureRule( -// TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider)); -// final PvpAuthRequestSignedRole signedRole = new PvpAuthRequestSignedRole(); -// final BasicSecurityPolicy policy = new BasicSecurityPolicy(); -// policy.getPolicyRules().add(signedRole); -// policy.getPolicyRules().add(signatureRule); -// final SecurityPolicyResolver resolver = new StaticSecurityPolicyResolver(policy); -// messageContext.setSecurityPolicyResolver(resolver); -// -// // set metadata descriptor type -// if (isSpEndPoint) { -// messageContext.setPeerEntityRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME); -// } else { -// messageContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME); -// } -// -// SAML2AuthnRequestsSignedSecurityHandler -// -// try { -// decode.decode(messageContext); -// -// // check signature -// signatureRule.evaluate(messageContext); -// -// } catch (final SecurityException e) { -// if (StringUtils.isEmpty(messageContext.getInboundMessageIssuer())) { -// throw e; -// -// } -// -// if (metadataProvider instanceof IRefreshableMetadataProvider) { -// log.debug("PVP2X message validation FAILED. Reload metadata for entityID: " -// + messageContext.getInboundMessageIssuer()); -// if (!((IRefreshableMetadataProvider) metadataProvider) -// .refreshMetadataProvider(messageContext.getInboundMessageIssuer())) { -// throw e; -// } else { -// log.trace("PVP2X metadata reload finished. Check validate message again."); -// decode.decode(messageContext); -// -// // check signature -// signatureRule.evaluate(messageContext); -// -// } -// log.trace("Second PVP2X message validation finished"); -// -// } else { -// throw e; -// -// } -// } -// -// InboundMessage msg = null; -// if (messageContext.getInboundMessage() instanceof RequestAbstractType) { -// final RequestAbstractType inboundMessage = -// (RequestAbstractType) messageContext.getInboundMessage(); -// msg = new PvpSProfileRequest(inboundMessage, getSaml2BindingName()); -// -// } else if (messageContext.getInboundMessage() instanceof StatusResponseType) { -// final StatusResponseType inboundMessage = -// (StatusResponseType) messageContext.getInboundMessage(); -// msg = new PvpSProfileResponse(inboundMessage); -// -// } else { -// // create empty container if request type is unknown -// msg = new InboundMessage(); -// } -// -// if (messageContext.getPeerEntityMetadata() != null) { -// msg.setEntityID(messageContext.getPeerEntityMetadata().getEntityID()); -// } else { -// log.info( -// "No Metadata found for OA with EntityID " + messageContext.getInboundMessageIssuer()); -// } -// -// msg.setVerified(true); -// msg.setRelayState(messageContext.getRelayState()); -// -// return msg; } @Override @@ -341,5 +168,38 @@ public class RedirectBinding extends AbstractBinding implements IDecoder, IEncod @Override public String getSaml2BindingName() { return SAMLConstants.SAML2_REDIRECT_BINDING_URI; + + } + + private PvpSamlMessageHandlerChain buildMessageValidationChain(HttpServletRequest req, + IPvp2MetadataProvider metadataProvider) { + final PvpSamlMessageHandlerChain messageValidatorChain = new PvpSamlMessageHandlerChain(); + final EaafSaml2HttpRedirectDeflateSignatureSecurityHandler redirectBindingSignaturHandler = + new EaafSaml2HttpRedirectDeflateSignatureSecurityHandler(metadataProvider); + redirectBindingSignaturHandler.setHttpServletRequest(req); + + messageValidatorChain.addHandler(new CheckMessageVersionHandler()); + messageValidatorChain.addHandler(redirectBindingSignaturHandler); + messageValidatorChain.addHandler(new MessageLifetimeSecurityHandler()); + + /* + * TODO: maybe we add it in a later version Because: - AuthnRequest replay + * should not be a problem on IDP side - Response replay will be not possible, + * because EAAF PVP implements countermeasure based on one-time tokens for each + * request + * + */ + // final MessageReplaySecurityHandler replaySecurityHandler = new + // MessageReplaySecurityHandler(); + // final StorageService replayCacheStorage = null; + // final ReplayCache replayCache = new ReplayCache(); + // replayCache.setId("Message replay cache"); + // replayCache.setStrict(true); + // replayCache.setStorage(replayCacheStorage); + // replaySecurityHandler.setReplayCache(replayCache ); + // messageValidatorChain.addHandler(replaySecurityHandler); + + return messageValidatorChain; + } } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/SoapBinding.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/SoapBinding.java index e0df2d2a..49e93f0a 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/SoapBinding.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/SoapBinding.java @@ -21,6 +21,7 @@ package at.gv.egiz.eaaf.modules.pvp2.impl.binding; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.xml.namespace.QName; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.modules.pvp2.PvpConstants; @@ -29,27 +30,29 @@ import at.gv.egiz.eaaf.modules.pvp2.api.binding.IEncoder; import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; import at.gv.egiz.eaaf.modules.pvp2.api.message.InboundMessageInterface; import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; -import at.gv.egiz.eaaf.modules.pvp2.exception.AttributQueryException; +import at.gv.egiz.eaaf.modules.pvp2.exception.InvalidPvpRequestException; import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2Exception; +import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2InternalErrorException; import at.gv.egiz.eaaf.modules.pvp2.exception.SamlBindingException; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; import at.gv.egiz.eaaf.modules.pvp2.impl.verification.EaafMessageContextInitializationHandler; +import at.gv.egiz.eaaf.modules.pvp2.impl.verification.EaafSamlProtocolMessageXmlSignatureSecurityHandler; import at.gv.egiz.eaaf.modules.pvp2.impl.verification.PvpSamlMessageHandlerChain; import org.opensaml.messaging.context.MessageContext; -import org.opensaml.messaging.decoder.MessageDecodingException; import org.opensaml.saml.common.SAMLObject; import org.opensaml.saml.common.binding.SAMLBindingSupport; import org.opensaml.saml.common.binding.impl.CheckMessageVersionHandler; import org.opensaml.saml.common.binding.impl.SAMLProtocolAndRoleHandler; import org.opensaml.saml.common.binding.impl.SAMLSOAPDecoderBodyHandler; import org.opensaml.saml.common.binding.security.impl.MessageLifetimeSecurityHandler; -import org.opensaml.saml.common.binding.security.impl.SAMLProtocolMessageXMLSignatureSecurityHandler; import org.opensaml.saml.common.messaging.SAMLMessageSecuritySupport; import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.binding.decoding.impl.HTTPSOAP11Decoder; import org.opensaml.saml.saml2.binding.encoding.impl.HTTPSOAP11Encoder; import org.opensaml.saml.saml2.core.RequestAbstractType; import org.opensaml.saml.saml2.core.StatusResponseType; +import org.opensaml.soap.messaging.context.SOAP11Context; import lombok.extern.slf4j.Slf4j; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; @@ -61,72 +64,55 @@ public class SoapBinding extends AbstractBinding implements IDecoder, IEncoder { @Override public InboundMessageInterface decode(final HttpServletRequest req, final HttpServletResponse resp, final IPvp2MetadataProvider metadataProvider, - final boolean isSpEndPoint, final URIComparator comparator) + QName peerEntityRole, final URIComparator comparator) throws Pvp2Exception { - try { - final HTTPSOAP11Decoder soapDecoder = new HTTPSOAP11Decoder(); + final HTTPSOAP11Decoder soapDecoder = new HTTPSOAP11Decoder(); + soapDecoder.setHttpServletRequest(req); + + injectMessageHandlerChain(soapDecoder, metadataProvider, peerEntityRole); + + final MessageContext messageContext = + internalMessageDecode(soapDecoder, PvpConstants.SOAP); + + // check if PVP2 AuthnRequest is signed + if (!SAMLBindingSupport.isMessageSigned(messageContext)) { + log.info("SAML Post-Binding message contains no signature. Message will be rejected"); + throw new InvalidPvpRequestException("internal.pvp.02", null); + + } + + return performMessageDecodePostProcessing(messageContext, true); + } + private void injectMessageHandlerChain(HTTPSOAP11Decoder soapDecoder, + IPvp2MetadataProvider metadataProvider, QName peerEntityRole) throws Pvp2InternalErrorException { + try { final PvpSamlMessageHandlerChain messageValidatorChain = new PvpSamlMessageHandlerChain(); - soapDecoder.setBodyHandler(messageValidatorChain); + messageValidatorChain.addHandler(new EaafMessageContextInitializationHandler(metadataProvider)); + messageValidatorChain.addHandler(new SAMLSOAPDecoderBodyHandler()); - final SAMLProtocolMessageXMLSignatureSecurityHandler msgSignatureValidationHandler = - new SAMLProtocolMessageXMLSignatureSecurityHandler(); + final SAMLProtocolAndRoleHandler samlProtocolHandler = new SAMLProtocolAndRoleHandler(); + samlProtocolHandler.setProtocol(SAMLConstants.SAML20P_NS); + samlProtocolHandler.setRole(peerEntityRole); + messageValidatorChain.addHandler(samlProtocolHandler); - messageValidatorChain.addHandler(new EaafMessageContextInitializationHandler()); messageValidatorChain.addHandler(new CheckMessageVersionHandler()); - messageValidatorChain.addHandler(new SAMLProtocolAndRoleHandler()); - messageValidatorChain.addHandler(msgSignatureValidationHandler); + messageValidatorChain.addHandler( + new EaafSamlProtocolMessageXmlSignatureSecurityHandler(metadataProvider)); messageValidatorChain.addHandler(new MessageLifetimeSecurityHandler()); - messageValidatorChain.addHandler(new SAMLSOAPDecoderBodyHandler()); - // decode message - soapDecoder.initialize(); - soapDecoder.decode(); + messageValidatorChain.initialize(); - final MessageContext messageContext = soapDecoder.getMessageContext(); - messageContext.getMessage(); + soapDecoder.setBodyHandler(messageValidatorChain); + + } catch (final ComponentInitializationException e) { + log.warn("Internal initialization error. Reason: {}", e.getMessage()); + throw new Pvp2InternalErrorException(e); - } catch (MessageDecodingException | ComponentInitializationException e) { - throw new SamlBindingException("internal.pvp.95", - new Object[] { PvpConstants.POST, "decoding", e.getMessage() }, - e); } -// final Envelope inboundMessage = (Envelope) messageContext.getMessage(); -// -// if (inboundMessage.getBody() != null) { -// final List xmlElemList = inboundMessage.getBody().getUnknownXMLObjects(); -// -// if (!xmlElemList.isEmpty()) { -// final SignableXMLObject attrReq = (SignableXMLObject) xmlElemList.get(0); -// final PvpSProfileRequest request = new PvpSProfileRequest(attrReq, getSaml2BindingName()); -// -// if (messageContext.getPeerEntityMetadata() != null) { -// request.setEntityID(messageContext.getPeerEntityMetadata().getEntityID()); -// -// } else if (attrReq instanceof RequestAbstractType) { -// final RequestAbstractType attributeRequest = (RequestAbstractType) attrReq; -// try { -// if (StringUtils.isNotEmpty(attributeRequest.getIssuer().getValue()) -// && metadataProvider.getRole(attributeRequest.getIssuer().getValue(), -// SPSSODescriptor.DEFAULT_ELEMENT_NAME) != null) { -// request.setEntityID(attributeRequest.getIssuer().getValue()); -// } -// -// } catch (final Exception e) { -// log.warn("No Metadata found with EntityID " + attributeRequest.getIssuer().getValue()); -// } -// } -// -// request.setVerified(false); -// return request; -// -// } -// } - - log.error("Receive empty PVP 2.1 attributequery request."); - throw new AttributQueryException("Receive empty PVP 2.1 attributequery request.", null); + } @Override @@ -157,6 +143,11 @@ public class SoapBinding extends AbstractBinding implements IDecoder, IEncoder { // inject message context final MessageContext messageContext = buildBasicMessageContext(encoder, response); + //inject SOAP enveloped + final SOAP11Context soap11Context = new SOAP11Context(); + soap11Context.setEnvelope(Saml2Utils.buildSoap11Envelope(response)); + messageContext.addSubcontext(soap11Context); + // inject signing context messageContext.addSubcontext(injectSigningInfos(credentials)); @@ -172,6 +163,7 @@ public class SoapBinding extends AbstractBinding implements IDecoder, IEncoder { // encode message encoder.initialize(); encoder.encode(); + } catch (final Exception e) { log.warn("Can not encode SAML2 SOAP-Binding response", e); throw new SamlBindingException("internal.pvp.95", diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/CitizenTokenBuilder.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/CitizenTokenBuilder.java index 1667a07d..bf201803 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/CitizenTokenBuilder.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/CitizenTokenBuilder.java @@ -61,7 +61,8 @@ public class CitizenTokenBuilder { */ public static XMLObject buildAttributeIntegerValue(final int value) { final XSIntegerBuilder integerBuilder = - (XSIntegerBuilder) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(XSInteger.TYPE_NAME); + (XSIntegerBuilder) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder( + XSInteger.TYPE_NAME); final XSInteger integerValue = integerBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSInteger.TYPE_NAME); integerValue.setValue(value); diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/PvpMetadataBuilder.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/PvpMetadataBuilder.java index d5893d4a..92922e09 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/PvpMetadataBuilder.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/PvpMetadataBuilder.java @@ -20,6 +20,7 @@ package at.gv.egiz.eaaf.modules.pvp2.impl.builder; import java.io.IOException; +import java.text.MessageFormat; import java.util.Collection; import java.util.List; @@ -28,6 +29,13 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactoryConfigurationError; +import at.gv.egiz.eaaf.core.exceptions.EaafBuilderException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvpMetadataBuilderConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; + import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.opensaml.core.xml.io.MarshallingException; @@ -57,14 +65,8 @@ import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; import org.opensaml.xmlsec.signature.support.SignatureException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; import org.w3c.dom.Element; -import at.gv.egiz.eaaf.core.exceptions.EaafException; -import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; -import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvpMetadataBuilderConfiguration; -import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; -import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; import net.shibboleth.utilities.java.support.xml.SerializeSupport; /** @@ -74,9 +76,10 @@ import net.shibboleth.utilities.java.support.xml.SerializeSupport; * */ -@Service("PVPMetadataBuilder") public class PvpMetadataBuilder { + private static final String ERROR_ROLE_DESCR = "Can not build {0}"; + private static final Logger log = LoggerFactory.getLogger(PvpMetadataBuilder.class); X509KeyInfoGeneratorFactory keyInfoFactory = null; @@ -133,6 +136,12 @@ public class PvpMetadataBuilder { final RoleDescriptor idpSsoDesc = generateIdpMetadata(config); if (idpSsoDesc != null) { entityDescriptor.getRoleDescriptors().add(idpSsoDesc); + + } else { + final String msg = MessageFormat.format(ERROR_ROLE_DESCR, + IDPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME); + throw new EaafBuilderException("internal.pvp.13", new Object[] { msg }, msg); + } } @@ -142,12 +151,17 @@ public class PvpMetadataBuilder { final RoleDescriptor spSsoDesc = generateSpMetadata(config); if (spSsoDesc != null) { entityDescriptor.getRoleDescriptors().add(spSsoDesc); + + } else { + final String msg = MessageFormat.format(ERROR_ROLE_DESCR, SPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME); + throw new EaafBuilderException("internal.pvp.13", new Object[] { msg }, msg); + } } - + SignableSAMLObject metadataToSign; - + // build entities descriptor if (config.buildEntitiesDescriptorAsRootElement()) { final EntitiesDescriptor entitiesDescriptor = @@ -157,24 +171,24 @@ public class PvpMetadataBuilder { entitiesDescriptor.setValidUntil(date.plusHours(config.getMetadataValidUntil())); entitiesDescriptor.getEntityDescriptors().add(entityDescriptor); metadataToSign = entitiesDescriptor; - + } else { entityDescriptor.setValidUntil(date.plusHours(config.getMetadataValidUntil())); entityDescriptor.setID(Saml2Utils.getSecureIdentifier()); metadataToSign = entityDescriptor; - + } // sign metadata final EaafX509Credential metadataSignCred = config.getMetadataSigningCredentials(); - SignableSAMLObject signedMetadata = Saml2Utils.signSamlObject(metadataToSign, metadataSignCred, true); - - + final SignableSAMLObject signedMetadata = Saml2Utils.signSamlObject(metadataToSign, metadataSignCred, + true); + // Serialize metadata - final Element document =XMLObjectSupport.marshall(signedMetadata); - String serializedMetadata = SerializeSupport.nodeToString(document); + final Element document = XMLObjectSupport.marshall(signedMetadata); + final String serializedMetadata = SerializeSupport.nodeToString(document); return serializedMetadata; - + } private RoleDescriptor generateSpMetadata(final IPvpMetadataBuilderConfiguration config) @@ -402,7 +416,10 @@ public class PvpMetadataBuilder { idpSsoDescriptor.getKeyDescriptors().add(signKeyDescriptor); // set IDP attribute set - idpSsoDescriptor.getAttributes().addAll(config.getIdpPossibleAttributes()); + if (config.getIdpPossibleAttributes() != null) { + idpSsoDescriptor.getAttributes().addAll(config.getIdpPossibleAttributes()); + + } // set providable nameID formats for (final String format : config.getIdpPossibleNameIdTypes()) { diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/SamlAttributeGenerator.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/SamlAttributeGenerator.java index d37d6724..5c44af24 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/SamlAttributeGenerator.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/SamlAttributeGenerator.java @@ -44,7 +44,8 @@ public class SamlAttributeGenerator implements IAttributeGenerator { private XMLObject buildAttributeIntegerValue(final int value) { final XSIntegerBuilder integerBuilder = - (XSIntegerBuilder) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(XSInteger.TYPE_NAME); + (XSIntegerBuilder) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder( + XSInteger.TYPE_NAME); final XSInteger integerValue = integerBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSInteger.TYPE_NAME); integerValue.setValue(value); diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/logging/PvpModuleMessageSource.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/logging/PvpModuleMessageSource.java index 961f29cb..227ff30e 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/logging/PvpModuleMessageSource.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/logging/PvpModuleMessageSource.java @@ -5,7 +5,6 @@ import java.util.List; import at.gv.egiz.eaaf.core.api.logging.IMessageSourceLocation; - public class PvpModuleMessageSource implements IMessageSourceLocation { @Override diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/message/InboundMessage.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/message/InboundMessage.java index da958d5b..0ffa3789 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/message/InboundMessage.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/message/InboundMessage.java @@ -25,16 +25,17 @@ import java.io.Serializable; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; +import at.gv.egiz.eaaf.core.impl.utils.DomUtils; +import at.gv.egiz.eaaf.modules.pvp2.api.message.InboundMessageInterface; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.exception.NoMetadataInformationException; + import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import org.xml.sax.SAXException; -import at.gv.egiz.eaaf.core.impl.utils.DomUtils; -import at.gv.egiz.eaaf.modules.pvp2.api.message.InboundMessageInterface; -import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; -import at.gv.egiz.eaaf.modules.pvp2.exception.NoMetadataInformationException; import net.shibboleth.utilities.java.support.resolver.ResolverException; public class InboundMessage implements InboundMessageInterface, Serializable { @@ -68,7 +69,7 @@ public class InboundMessage implements InboundMessageInterface, Serializable { } catch (final ResolverException e) { log.warn("No Metadata for EntitiyID " + entityID); throw new NoMetadataInformationException(); - + } } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/AbstractChainingMetadataProvider.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/AbstractChainingMetadataProvider.java index 3fc675e9..8a20b932 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/AbstractChainingMetadataProvider.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/AbstractChainingMetadataProvider.java @@ -27,12 +27,17 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.naming.ConfigurationException; +import at.gv.egiz.components.spring.api.IDestroyableObject; +import at.gv.egiz.eaaf.core.api.IGarbageCollectorProcessing; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IRefreshableMetadataProvider; + import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.opensaml.core.criterion.EntityIdCriterion; @@ -43,11 +48,6 @@ import org.opensaml.saml.metadata.resolver.filter.MetadataFilter; import org.opensaml.saml.metadata.resolver.impl.AbstractMetadataResolver; import org.opensaml.saml.saml2.metadata.EntityDescriptor; -import at.gv.egiz.components.spring.api.IDestroyableObject; -import at.gv.egiz.eaaf.core.api.IGarbageCollectorProcessing; -import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; -import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; -import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IRefreshableMetadataProvider; import lombok.extern.slf4j.Slf4j; import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements; import net.shibboleth.utilities.java.support.component.IdentifiedComponent; @@ -55,10 +55,13 @@ import net.shibboleth.utilities.java.support.resolver.CriteriaSet; import net.shibboleth.utilities.java.support.resolver.ResolverException; @Slf4j -public abstract class AbstractChainingMetadataProvider implements IGarbageCollectorProcessing, IRefreshableMetadataProvider, +public abstract class AbstractChainingMetadataProvider implements IGarbageCollectorProcessing, + IRefreshableMetadataProvider, IDestroyableObject, IPvp2MetadataProvider, ClearableMetadataResolver { - - @Nonnull @NonnullElements private final List internalResolvers; + + @Nonnull + @NonnullElements + private final List internalResolvers; private DateTime lastRefeshTimestamp; private boolean lastRefeshSuccessful; private static Object mutex = new Object(); @@ -72,7 +75,6 @@ public abstract class AbstractChainingMetadataProvider implements IGarbageCollec } - /* * (non-Javadoc) * @@ -104,8 +106,6 @@ public abstract class AbstractChainingMetadataProvider implements IGarbageCollec } - - @Override public synchronized boolean refreshMetadataProvider(final String entityId) { try { @@ -130,7 +130,7 @@ public abstract class AbstractChainingMetadataProvider implements IGarbageCollec // check if MetadataProvider is actually loaded final MetadataResolver loadedResover = actuallyLoadedResolver.get(metadataUrl); if (loadedResover instanceof RefreshableMetadataResolver) { - ((RefreshableMetadataResolver)loadedResover).refresh(); + ((RefreshableMetadataResolver) loadedResover).refresh(); log.info("SAML2 metadata for service provider: " + entityId + " is refreshed."); return true; @@ -162,30 +162,29 @@ public abstract class AbstractChainingMetadataProvider implements IGarbageCollec * */ public void internalDestroy() { - log.info("Destroying chained metadata resolvers ..."); + log.info("Destroying chained metadata resolvers ..."); - for (final MetadataResolver resolver : internalResolvers) { - destroyMetadataResolver(resolver); - } + for (final MetadataResolver resolver : internalResolvers) { + destroyMetadataResolver(resolver); + } - internalResolvers.clear(); + internalResolvers.clear(); } - /** {@inheritDoc} */ - @Override + @Override public final MetadataFilter getMetadataFilter() { - log.warn("{} does NOT support {}", AbstractChainingMetadataProvider.class.getName(), - MetadataFilter.class.getName()); - return null; + log.warn("{} does NOT support {}", AbstractChainingMetadataProvider.class.getName(), + MetadataFilter.class.getName()); + return null; } - /** {@inheritDoc} */ - @Override + @Override public final void setMetadataFilter(final MetadataFilter newFilter) { log.warn("{} does NOT support {}", AbstractChainingMetadataProvider.class.getName(), MetadataFilter.class.getName()); - throw new UnsupportedOperationException("Metadata filters are not supported on AbstractChainingMetadataProvider"); + throw new UnsupportedOperationException( + "Metadata filters are not supported on AbstractChainingMetadataProvider"); } /* @@ -221,17 +220,17 @@ public abstract class AbstractChainingMetadataProvider implements IGarbageCollec } @Override - @Nullable + @Nullable public final EntityDescriptor resolveSingle(@Nullable final CriteriaSet criteria) throws ResolverException { for (final MetadataResolver resolver : internalResolvers) { try { - final EntityDescriptor descriptors = resolver.resolveSingle(criteria); - if (descriptors != null) { - return descriptors; - } + final EntityDescriptor descriptors = resolver.resolveSingle(criteria); + if (descriptors != null) { + return descriptors; + } } catch (final ResolverException e) { - continue; + continue; } @@ -242,87 +241,90 @@ public abstract class AbstractChainingMetadataProvider implements IGarbageCollec } @Override - @Nonnull - public final Iterable resolve(@Nullable final CriteriaSet criteria) throws ResolverException { - for (final MetadataResolver resolver : internalResolvers) { - try { - final Iterable descriptors = resolver.resolve(criteria); - if (descriptors != null && descriptors.iterator().hasNext()) { - return descriptors; - - } - - } catch (final ResolverException e) { - continue; - - } + @Nonnull + public final Iterable resolve(@Nullable final CriteriaSet criteria) + throws ResolverException { + for (final MetadataResolver resolver : internalResolvers) { + try { + final Iterable descriptors = resolver.resolve(criteria); + if (descriptors != null && descriptors.iterator().hasNext()) { + return descriptors; + + } + + } catch (final ResolverException e) { + continue; + } + } - return Collections.emptyList(); + return Collections.emptyList(); } - + @Override public final void clear() throws ResolverException { - for (final MetadataResolver resolver : internalResolvers) { - if (resolver instanceof ClearableMetadataResolver) { - ((ClearableMetadataResolver) resolver).clear(); - } + for (final MetadataResolver resolver : internalResolvers) { + if (resolver instanceof ClearableMetadataResolver) { + ((ClearableMetadataResolver) resolver).clear(); } + } } @Override public final void clear(String entityID) throws ResolverException { - for (final MetadataResolver resolver : internalResolvers) { - if (resolver instanceof ClearableMetadataResolver) { - ((ClearableMetadataResolver) resolver).clear(entityID); - } + for (final MetadataResolver resolver : internalResolvers) { + if (resolver instanceof ClearableMetadataResolver) { + ((ClearableMetadataResolver) resolver).clear(entityID); } + } } - @Override final public void refresh() throws ResolverException { - this.lastRefeshSuccessful = false; - for (final MetadataResolver resolver : internalResolvers) { - if (resolver instanceof RefreshableMetadataResolver) { - ((RefreshableMetadataResolver) resolver).refresh(); - - } + @Override + public final void refresh() throws ResolverException { + this.lastRefeshSuccessful = false; + for (final MetadataResolver resolver : internalResolvers) { + if (resolver instanceof RefreshableMetadataResolver) { + ((RefreshableMetadataResolver) resolver).refresh(); + } - - this.lastRefeshTimestamp = DateTime.now(); - this.lastRefeshSuccessful = true; + } + + this.lastRefeshTimestamp = DateTime.now(); + this.lastRefeshSuccessful = true; } @Override - @Nullable public DateTime getLastUpdate() { - DateTime ret = null; - for (final MetadataResolver resolver : internalResolvers) { - if (resolver instanceof RefreshableMetadataResolver) { - final DateTime lastUpdate = ((RefreshableMetadataResolver) resolver).getLastUpdate(); - if (ret == null || ret.isBefore(lastUpdate)) { - ret = lastUpdate; - } - } + @Nullable + public DateTime getLastUpdate() { + DateTime ret = null; + for (final MetadataResolver resolver : internalResolvers) { + if (resolver instanceof RefreshableMetadataResolver) { + final DateTime lastUpdate = ((RefreshableMetadataResolver) resolver).getLastUpdate(); + if (ret == null || ret.isBefore(lastUpdate)) { + ret = lastUpdate; + } } - - return ret; + } + + return ret; } @Override - @Nullable final public DateTime getLastRefresh() { - DateTime ret = null; - for (final MetadataResolver resolver : internalResolvers) { - if (resolver instanceof RefreshableMetadataResolver) { - final DateTime lastRefresh = ((RefreshableMetadataResolver) resolver).getLastRefresh(); - if (ret == null || ret.isBefore(lastRefresh)) { - ret = lastRefresh; - } - } + @Nullable + public final DateTime getLastRefresh() { + DateTime ret = null; + for (final MetadataResolver resolver : internalResolvers) { + if (resolver instanceof RefreshableMetadataResolver) { + final DateTime lastRefresh = ((RefreshableMetadataResolver) resolver).getLastRefresh(); + if (ret == null || ret.isBefore(lastRefresh)) { + ret = lastRefresh; + } } - - return ret; + } + + return ret; } - - + /** * Get the URL to metadata for a specific entityID. * @@ -354,103 +356,92 @@ public abstract class AbstractChainingMetadataProvider implements IGarbageCollec protected abstract List getAllMetadataUrlsFromConfiguration() throws EaafConfigurationException; - /** * Get a Id for this metadata provider. - * + * * @return */ @Nonnull protected abstract String getMetadataProviderId(); - + protected final MetadataResolver getMetadataResolver() { log.warn("{} does NOT support 'getMetadataResolver'", AbstractChainingMetadataProvider.class.getName()); return null; - + } - + private Map getAllActuallyLoadedResolvers() { final Map loadedproviders = new HashMap<>(); // make a Map of all actually loaded HTTPMetadataProvider for (final MetadataResolver resolver : internalResolvers) { - if (resolver instanceof IdentifiedComponent) { - loadedproviders.put(((IdentifiedComponent) resolver).getId(), resolver); - - } else { - final String uuid = UUID.randomUUID().toString(); - loadedproviders.put(uuid, resolver); - log.debug("MetadatenResolver is not of type: {}. Mark it with id: {}", - IdentifiedComponent.class.getSimpleName(), uuid); + loadedproviders.put(((IdentifiedComponent) resolver).getId(), resolver); - } } return loadedproviders; } private void addAndRemoveMetadataProvider() throws EaafConfigurationException { - log.info("EAAF chaining metadata resolver starting internal managment task .... "); + log.info("EAAF chaining metadata resolver starting internal managment task .... "); + + /* + * OpenSAML ChainingMetadataProvider can not remove a MetadataProvider + * (UnsupportedOperationException) The ChainingMetadataProvider use internal a + * unmodifiableList to hold all registrated MetadataProviders. + */ + final Map providersinuse = new HashMap<>(); + + // get all actually loaded metadata providers + final Map loadedproviders = getAllActuallyLoadedResolvers(); + + /* + * TODO: maybe add metadata provider destroy after timeout. But could be a + * problem if one Metadataprovider load an EntitiesDescriptor with more the + * multiple EntityDescriptors. If one of this EntityDesciptors are expired the + * full EntitiesDescriptor is removed. + * + * Timeout requires a better solution in this case! + */ + + // load all SAML2 SPs form configuration and + // compare actually loaded Providers with configured SAML2 SPs + final List allMetadataUrls = getAllMetadataUrlsFromConfiguration(); + + final Iterator metadataUrlInterator = allMetadataUrls.iterator(); + while (metadataUrlInterator.hasNext()) { + final String metadataurl = metadataUrlInterator.next(); + try { + if (StringUtils.isNotEmpty(metadataurl) + && loadedproviders.containsKey(metadataurl)) { + // SAML2 SP is actually loaded, to nothing + providersinuse.put(metadataurl, loadedproviders.get(metadataurl)); + loadedproviders.remove(metadataurl); + + } + } catch (final Throwable e) { + log.error("Failed to add Metadata (unhandled reason: " + e.getMessage(), e); + + } + } + + // remove all actually loaded MetadataProviders with are not in ConfigurationDB + // any more + final Collection notusedproviders = loadedproviders.values(); + for (final MetadataResolver resolver : notusedproviders) { + log.info("Remove not used MetadataProvider with MetadataURL " + resolver.getId()); + destroyMetadataResolver(resolver); + internalResolvers.remove(resolver); /* * OpenSAML ChainingMetadataProvider can not remove a MetadataProvider * (UnsupportedOperationException) The ChainingMetadataProvider use internal a * unmodifiableList to hold all registrated MetadataProviders. */ - final Map providersinuse = new HashMap<>(); + // chainProvider.removeMetadataProvider(provider); - // get all actually loaded metadata providers - final Map loadedproviders = getAllActuallyLoadedResolvers(); - - /* - * TODO: maybe add metadata provider destroy after timeout. But could be a - * problem if one Metadataprovider load an EntitiesDescriptor with more the - * multiple EntityDescriptors. If one of this EntityDesciptors are expired the - * full EntitiesDescriptor is removed. - * - * Timeout requires a better solution in this case! - */ - - // load all SAML2 SPs form configuration and - // compare actually loaded Providers with configured SAML2 SPs - final List allMetadataUrls = getAllMetadataUrlsFromConfiguration(); - - final Iterator metadataUrlInterator = allMetadataUrls.iterator(); - while (metadataUrlInterator.hasNext()) { - final String metadataurl = metadataUrlInterator.next(); - try { - if (StringUtils.isNotEmpty(metadataurl) - && loadedproviders.containsKey(metadataurl)) { - // SAML2 SP is actually loaded, to nothing - providersinuse.put(metadataurl, loadedproviders.get(metadataurl)); - loadedproviders.remove(metadataurl); - - } - } catch (final Throwable e) { - log.error("Failed to add Metadata (unhandled reason: " + e.getMessage(), e); - - } - } - - - // remove all actually loaded MetadataProviders with are not in ConfigurationDB - // any more - final Collection notusedproviders = loadedproviders.values(); - for (final MetadataResolver resolver : notusedproviders) { - log.info("Remove not used MetadataProvider with MetadataURL " + resolver.getId()); - destroyMetadataResolver(resolver); - internalResolvers.remove(resolver); - - /* - * OpenSAML ChainingMetadataProvider can not remove a MetadataProvider - * (UnsupportedOperationException) The ChainingMetadataProvider use internal a - * unmodifiableList to hold all registrated MetadataProviders. - */ - // chainProvider.removeMetadataProvider(provider); - - - } + } } @@ -474,39 +465,34 @@ public abstract class AbstractChainingMetadataProvider implements IGarbageCollec } } - - @Override public DateTime getLastSuccessfulRefresh() { return this.lastRefeshTimestamp; - - } + } @Override public Boolean wasLastRefreshSuccess() { return this.lastRefeshSuccessful; - - } - - - /** {@inheritDoc} */ - @Override public boolean isRequireValidMetadata() { - log.warn("Attempt to access unsupported requireValidMetadata property on ChainingMetadataResolver"); - return false; } - /** {@inheritDoc} */ - @Override public void setRequireValidMetadata(final boolean requireValidMetadata) { - throw new UnsupportedOperationException("Setting requireValidMetadata is not supported on chaining resolver"); + @Override + public boolean isRequireValidMetadata() { + log.warn("Attempt to access unsupported requireValidMetadata property on ChainingMetadataResolver"); + return false; } + @Override + public void setRequireValidMetadata(final boolean requireValidMetadata) { + throw new UnsupportedOperationException( + "Setting requireValidMetadata is not supported on chaining resolver"); + } @Override public String getId() { return getMetadataProviderId(); - + } - + } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/PvpMetadataResolverAdapter.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/PvpMetadataResolverAdapter.java index bd2b79cb..d2b861dc 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/PvpMetadataResolverAdapter.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/PvpMetadataResolverAdapter.java @@ -1,18 +1,22 @@ package at.gv.egiz.eaaf.modules.pvp2.impl.metadata; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IRefreshableMetadataProvider; + import org.joda.time.DateTime; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.saml.metadata.resolver.ExtendedRefreshableMetadataResolver; import org.opensaml.saml.metadata.resolver.filter.MetadataFilter; import org.opensaml.saml.saml2.metadata.EntityDescriptor; -import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; +import lombok.extern.slf4j.Slf4j; import net.shibboleth.utilities.java.support.resolver.CriteriaSet; import net.shibboleth.utilities.java.support.resolver.ResolverException; -public class PvpMetadataResolverAdapter implements IPvp2MetadataProvider { - - private ExtendedRefreshableMetadataResolver internalProvider; +@Slf4j +public class PvpMetadataResolverAdapter implements IPvp2MetadataProvider, IRefreshableMetadataProvider { + + private final ExtendedRefreshableMetadataResolver internalProvider; public PvpMetadataResolverAdapter(ExtendedRefreshableMetadataResolver provider) { this.internalProvider = provider; @@ -27,7 +31,7 @@ public class PvpMetadataResolverAdapter implements IPvp2MetadataProvider { @Override public DateTime getLastRefresh() { return internalProvider.getLastRefresh(); - + } @Override @@ -38,7 +42,7 @@ public class PvpMetadataResolverAdapter implements IPvp2MetadataProvider { @Override public boolean isRequireValidMetadata() { return internalProvider.isRequireValidMetadata(); - + } @Override @@ -50,12 +54,12 @@ public class PvpMetadataResolverAdapter implements IPvp2MetadataProvider { @Override public MetadataFilter getMetadataFilter() { return internalProvider.getMetadataFilter(); - + } @Override public void setMetadataFilter(MetadataFilter newFilter) { - internalProvider.setMetadataFilter(newFilter); + internalProvider.setMetadataFilter(newFilter); } @@ -67,7 +71,7 @@ public class PvpMetadataResolverAdapter implements IPvp2MetadataProvider { @Override public EntityDescriptor resolveSingle(CriteriaSet criteria) throws ResolverException { return internalProvider.resolveSingle(criteria); - + } @Override @@ -80,7 +84,7 @@ public class PvpMetadataResolverAdapter implements IPvp2MetadataProvider { final CriteriaSet criteria = new CriteriaSet(); criteria.add(new EntityIdCriterion(entityId)); return internalProvider.resolveSingle(criteria); - + } @Override @@ -93,4 +97,19 @@ public class PvpMetadataResolverAdapter implements IPvp2MetadataProvider { return internalProvider.wasLastRefreshSuccess(); } + @Override + public boolean refreshMetadataProvider(String entityID) { + try { + log.trace("Refeshing metadata-provider: {} ... ", getId()); + internalProvider.refresh(); + return true; + + } catch (final ResolverException e) { + log.warn("Refreshing of metadata-provider: {} failed. Reason: {}", + getId(), e.getMessage()); + return false; + + } + } + } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/PvpMetadataResolverFactory.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/PvpMetadataResolverFactory.java index f548bc7b..0b505e56 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/PvpMetadataResolverFactory.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/PvpMetadataResolverFactory.java @@ -8,23 +8,31 @@ import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.net.ssl.SSLHandshakeException; +import at.gv.egiz.components.spring.api.IDestroyableObject; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.impl.utils.FileUtils; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2MetadataException; +import at.gv.egiz.eaaf.modules.pvp2.exception.SchemaValidationException; +import at.gv.egiz.eaaf.modules.pvp2.exception.SignatureValidationException; +import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.OpenSaml3ResourceAdapter; + import org.apache.http.client.HttpClient; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; import org.opensaml.saml.metadata.resolver.ExtendedRefreshableMetadataResolver; import org.opensaml.saml.metadata.resolver.filter.MetadataFilter; +import org.opensaml.saml.metadata.resolver.impl.AbstractReloadingMetadataResolver; import org.opensaml.saml.metadata.resolver.impl.HTTPMetadataResolver; import org.opensaml.saml.metadata.resolver.impl.ResourceBackedMetadataResolver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ResourceLoader; -import at.gv.egiz.components.spring.api.IDestroyableObject; -import at.gv.egiz.eaaf.core.api.idp.IConfiguration; -import at.gv.egiz.eaaf.core.impl.utils.FileUtils; -import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; -import at.gv.egiz.eaaf.modules.pvp2.exception.SchemaValidationException; -import at.gv.egiz.eaaf.modules.pvp2.exception.SignatureValidationException; -import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.OpenSaml3ResourceAdapter; +import com.google.common.base.Predicates; +import com.google.common.base.Throwables; +import com.google.common.collect.FluentIterable; import lombok.extern.slf4j.Slf4j; +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import net.shibboleth.utilities.java.support.resolver.ResolverException; import net.shibboleth.utilities.java.support.resource.Resource; import net.shibboleth.utilities.java.support.xml.ParserPool; @@ -34,13 +42,18 @@ public class PvpMetadataResolverFactory implements IDestroyableObject { private static final String URI_PREFIX_HTTP = "http:"; private static final String URI_PREFIX_HTTPS = "https:"; + private static final String NOT_SUCCESS = "Maybe metadata was expired"; + private Timer timer = null; - - @Autowired private IConfiguration authConfig; - @Autowired private ResourceLoader resourceLoader; - + + @Autowired + private IConfiguration authConfig; + @Autowired + private ResourceLoader resourceLoader; + /** - * Create a single SAML2 metadata provider by using the default OpenSAML3 parser-pool. + * Create a single SAML2 metadata provider by using the default OpenSAML3 + * parser-pool. * * @param metadataLocation where the metadata should be loaded, but never null. * If the location starts with http(s):, than a http @@ -54,17 +67,18 @@ public class PvpMetadataResolverFactory implements IDestroyableObject { * * @return SAML2 Metadata Provider, or null if the metadata provider can not * initialized + * @throws Pvp2MetadataException In case of an initialization error */ - @Nullable + @Nullable public IPvp2MetadataProvider createMetadataProvider(@Nonnull final String metadataLocation, @Nullable final MetadataFilter filter, @Nonnull final String idForLogging, - @Nullable final HttpClient httpClient) { - return createMetadataProvider(metadataLocation, filter, idForLogging, - XMLObjectProviderRegistrySupport.getParserPool(), - httpClient); - + @Nullable final HttpClient httpClient) throws Pvp2MetadataException { + return createMetadataProvider(metadataLocation, filter, idForLogging, + XMLObjectProviderRegistrySupport.getParserPool(), + httpClient); + } - + /** * Create a single SAML2 metadata provider. * @@ -80,57 +94,74 @@ public class PvpMetadataResolverFactory implements IDestroyableObject { * * @return SAML2 Metadata Provider, or null if the metadata provider can not * initialized + * @throws Pvp2MetadataException In case of an initialization error */ - @Nullable + @Nullable public IPvp2MetadataProvider createMetadataProvider(@Nonnull final String metadataLocation, @Nullable final MetadataFilter filter, @Nonnull final String idForLogging, - @Nullable final ParserPool pool, @Nullable final HttpClient httpClient) { - + @Nullable final ParserPool pool, @Nullable final HttpClient httpClient) throws Pvp2MetadataException { + ExtendedRefreshableMetadataResolver internalProvider = null; - - if (metadataLocation.startsWith(URI_PREFIX_HTTP) - || metadataLocation.startsWith(URI_PREFIX_HTTPS)) { - if (httpClient != null) { - internalProvider = createNewHttpMetaDataProvider(metadataLocation, filter, idForLogging, timer, pool, - httpClient); + + try { + if (metadataLocation.startsWith(URI_PREFIX_HTTP) + || metadataLocation.startsWith(URI_PREFIX_HTTPS)) { + internalProvider = createNewHttpMetaDataProvider(metadataLocation, filter, + idForLogging, timer, pool, httpClient); + } else { - log.warn("Can not load http(s) based SAML2 metadata without a HTTP client"); - + final String absoluteMetadataLocation = + FileUtils.makeAbsoluteUrl(metadataLocation, authConfig.getConfigurationRootDirectory()); + final org.springframework.core.io.Resource resource = + resourceLoader.getResource(absoluteMetadataLocation); + + if (resource.exists()) { + internalProvider = createNewFileSystemMetaDataProvider( + new OpenSaml3ResourceAdapter(resource), + filter, idForLogging, timer, + pool); + + } else { + log.warn( + "SAML2 metadata file: {} not found or not exist", absoluteMetadataLocation); + throw new Pvp2MetadataException("internal.pvp.05", + new Object[] { absoluteMetadataLocation, "File NOT found or exist." }); + + } } - } else { - String absoluteMetadataLocation; - try { - absoluteMetadataLocation = - FileUtils.makeAbsoluteUrl(metadataLocation, authConfig.getConfigurationRootDirectory()); + } catch (final ComponentInitializationException e) { + log.warn("Failed to load Metadata file for {} [ {} ]", + idForLogging, e.getMessage()); + checkResolverInitializationError(e, metadataLocation); - org.springframework.core.io.Resource resource = resourceLoader.getResource(absoluteMetadataLocation); - if (resource.exists()) { - internalProvider = createNewFileSystemMetaDataProvider( - new OpenSaml3ResourceAdapter(resource), - filter, idForLogging, timer, - pool); - } else { - log.warn( - "SAML2 metadata file: " + absoluteMetadataLocation + " not found or not exist"); - - } + } catch (final Exception e) { + throw new Pvp2MetadataException("internal.pvp.09", new Object[] { metadataLocation, e.getMessage() }); + } - } catch (final IOException e) { - log.warn("SAML2 metadata URL is invalid: " + metadataLocation, e); + if (!internalProvider.wasLastRefreshSuccess()) { + log.info("Metadata loading from source: {} failed. {}", metadataLocation, NOT_SUCCESS); + throw new Pvp2MetadataException("internal.pvp.09", new Object[] { metadataLocation, NOT_SUCCESS }); - } } - if (internalProvider != null) { - return new PvpMetadataResolverAdapter(internalProvider); - - } else { - log.warn("SAML2 metadata has an unsupported metadata location prefix: " + metadataLocation); - return null; - + return new PvpMetadataResolverAdapter(internalProvider); + + } + + @Override + public void fullyDestroy() { + if (timer != null) { + log.info("Stopping timer-thread for PVP metadata resolver ... "); + timer.cancel(); } + } + + @PostConstruct + private void initialize() { + log.info("Initializing timer-thread for PVP metadata resolver ... "); + timer = new Timer("PVP metadata-resolver refresh"); } @@ -142,55 +173,26 @@ public class PvpMetadataResolverFactory implements IDestroyableObject { * @param idForLogging Id, which is used for Logging * @param timer {@link Timer} which is used to schedule metadata refresh * operations - * @param pool + * @param pool SAML2 parser pool that should be used * * @return SAML2 Metadata Provider - * @throws IOException + * @throws IOException In case of a metadata resource error + * @throws ComponentInitializationException In case of a metadata resolver + * initialization error */ private ExtendedRefreshableMetadataResolver createNewFileSystemMetaDataProvider(final Resource metadataFile, final MetadataFilter filter, final String idForLogging, final Timer timer, - final ParserPool pool) throws IOException { + final ParserPool pool) throws IOException, ComponentInitializationException { ResourceBackedMetadataResolver fileSystemResolver = null; - try { - //fileSystemResolver = new FilesystemMetadataResolver(timer, metadataFile); - - fileSystemResolver = new ResourceBackedMetadataResolver(timer, metadataFile); - - if (pool != null) { - fileSystemResolver.setParserPool(pool); - - } else { - fileSystemResolver.setParserPool( - XMLObjectProviderRegistrySupport.getParserPool()); - - } - fileSystemResolver.setRequireValidMetadata(true); - fileSystemResolver.setMinRefreshDelay(1000 * 60 * 15); // 15 minutes - fileSystemResolver.setMaxRefreshDelay(1000 * 60 * 60 * 24); // 24 hours - - fileSystemResolver.setMetadataFilter(filter); - fileSystemResolver.initialize(); - fileSystemResolver.setId(metadataFile.getURI().toASCIIString()); - - fileSystemResolver.setRequireValidMetadata(true); - - return fileSystemResolver; - - } catch (final Exception e) { - log.warn("Failed to load Metadata file for " + idForLogging + "[ " + "File: " - + metadataFile.getURI().toASCIIString() + " Msg: " + e.getMessage() + " ]", e); - - log.warn("Can not initialize SAML2 metadata provider from filesystem: " - + metadataFile.getURI().toASCIIString() + " Reason: " + e.getMessage(), e); + fileSystemResolver = new ResourceBackedMetadataResolver(timer, metadataFile); + injectMetadataResolverConfiguration(fileSystemResolver, filter, pool); + fileSystemResolver.setId(metadataFile.getURI().toASCIIString()); + fileSystemResolver.initialize(); - if (fileSystemResolver != null) { - fileSystemResolver.destroy(); + log.trace("Set-up metadata-resolver with ID: {} as: {}", + idForLogging, fileSystemResolver.getClass().getSimpleName()); - } - - } - - return null; + return fileSystemResolver; } @@ -202,70 +204,75 @@ public class PvpMetadataResolverFactory implements IDestroyableObject { * @param idForLogging Id, which is used for Logging * @param timer {@link Timer} which is used to schedule metadata refresh * operations - * @param pool - * + * @param pool SAML2 parser pool that should be used * @return SAML2 Metadata Provider + * @throws ComponentInitializationException In case of a metadata resolver + * initialization error + * @throws ResolverException In case of an internal OpenSAML + * resolver error */ private ExtendedRefreshableMetadataResolver createNewHttpMetaDataProvider(final String metadataUrl, final MetadataFilter filter, final String idForLogging, final Timer timer, - final ParserPool pool, final HttpClient httpClient) { + final ParserPool pool, final HttpClient httpClient) throws ComponentInitializationException, + ResolverException { HTTPMetadataResolver httpMetadataResolver = null; - try { - httpMetadataResolver = new HTTPMetadataResolver(timer, httpClient, metadataUrl); - httpMetadataResolver.setParserPool(pool); - httpMetadataResolver.setRequireValidMetadata(true); - httpMetadataResolver.setMinRefreshDelay(1000 * 60 * 15); // 15 minutes - httpMetadataResolver.setMaxRefreshDelay(1000 * 60 * 60 * 24); // 24 hours - // httpProvider.setRefreshDelayFactor(0.1F); + httpMetadataResolver = new HTTPMetadataResolver(timer, httpClient, metadataUrl); + injectMetadataResolverConfiguration(httpMetadataResolver, filter, pool); + httpMetadataResolver.setId(metadataUrl); + httpMetadataResolver.initialize(); - httpMetadataResolver.setMetadataFilter(filter); - httpMetadataResolver.setId(metadataUrl); - httpMetadataResolver.initialize(); + log.trace("Set-up metadata-resolver with ID: {} as: {}", + idForLogging, httpMetadataResolver.getClass().getSimpleName()); - httpMetadataResolver.setRequireValidMetadata(true); + return httpMetadataResolver; - return httpMetadataResolver; + } - } catch (final Throwable e) { - if (e.getCause() != null && e.getCause().getCause() instanceof SSLHandshakeException) { - log.warn("SSL-Server certificate for metadata " + metadataUrl + " not trusted.", e); + private void injectMetadataResolverConfiguration(AbstractReloadingMetadataResolver resolver, + final MetadataFilter filter, final ParserPool pool) { + if (pool != null) { + resolver.setParserPool(pool); - } - if (e.getCause() != null && e.getCause().getCause() instanceof SignatureValidationException) { - log.warn("Signature verification for metadata" + metadataUrl + " FAILED.", e); + } else { + resolver.setParserPool( + XMLObjectProviderRegistrySupport.getParserPool()); - } - if (e.getCause() != null && e.getCause().getCause() instanceof SchemaValidationException) { - log.warn("Schema validation for metadata " + metadataUrl + " FAILED.", e); - } + } + + resolver.setRequireValidMetadata(true); + resolver.setMinRefreshDelay(1000 * 60 * 15); // 15 minutes + resolver.setMaxRefreshDelay(1000 * 60 * 60 * 24); // 24 hours + resolver.setMetadataFilter(filter); + + } - log.warn("Failed to load Metadata file for " + idForLogging + "[ " + e.getMessage() + " ]", + private void checkResolverInitializationError(ComponentInitializationException e, String metadataLocation) + throws Pvp2MetadataException { + if (FluentIterable.from(Throwables.getCausalChain(e)).filter( + Predicates.instanceOf(SSLHandshakeException.class)).first().isPresent()) { + log.info("SSL-Server certificate for metadata: {} not trusted.", metadataLocation, null, e); + throw new Pvp2MetadataException("internal.pvp.06", new Object[] { metadataLocation, e.getMessage() }, e); - if (httpMetadataResolver != null) { - log.debug("Destroy failed Metadata provider"); - httpMetadataResolver.destroy(); + } else if (FluentIterable.from(Throwables.getCausalChain(e)).filter( + Predicates.instanceOf(SignatureValidationException.class)).first().isPresent()) { + log.info("Signature verification for metadata: {} FAILED.", metadataLocation, null, e); + throw new Pvp2MetadataException("internal.pvp.07", new Object[] { metadataLocation, e.getMessage() }, + e); - } + } else if (FluentIterable.from(Throwables.getCausalChain(e)).filter( + Predicates.instanceOf(SchemaValidationException.class)).first().isPresent()) { + log.info("Schema validation for metadata: {} FAILED.", metadataLocation, null, e); + throw new Pvp2MetadataException("internal.pvp.08", new Object[] { metadataLocation, e.getMessage() }, + e); + + } else { + log.info("Generic initialization error for metadata: {}", metadataLocation, null, e); + throw new Pvp2MetadataException("internal.pvp.09", new Object[] { metadataLocation, e.getMessage() }, + e); } - return null; } - @Override - public void fullyDestroy() { - if (timer != null) { - log.info("Stopping timer-thread for PVP metadata resolver ... "); - timer.cancel(); - } - } - - @PostConstruct - private void initialize() { - log.info("Initializing timer-thread for PVP metadata resolver ... "); - timer = new Timer("PVP metadata-resolver refresh"); - - } - } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/EaafHttpPostDecoder.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/EaafHttpPostDecoder.java index d23affba..fdd44b9a 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/EaafHttpPostDecoder.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/EaafHttpPostDecoder.java @@ -2,18 +2,18 @@ package at.gv.egiz.eaaf.modules.pvp2.impl.opensaml; import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.io.UnsupportedEncodingException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SamlHttpUtils; + import org.opensaml.core.xml.XMLObject; import org.opensaml.messaging.decoder.MessageDecodingException; import org.opensaml.saml.saml2.binding.decoding.impl.HTTPPostDecoder; import com.google.common.base.Strings; - -import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; import lombok.extern.slf4j.Slf4j; import net.shibboleth.utilities.java.support.codec.Base64Support; @@ -27,14 +27,22 @@ import net.shibboleth.utilities.java.support.codec.Base64Support; @Slf4j public class EaafHttpPostDecoder extends HTTPPostDecoder { + private static final String SAML_REQ_PARAM_NAME = "SAMLRequest"; + private static final String SAML_RESP_PARAM_NAME = "SAMLResponse"; + + public EaafHttpPostDecoder(HttpServletRequest req) { + setHttpServletRequest(req); + } + @Override protected InputStream getBase64DecodedMessage(final HttpServletRequest request) throws MessageDecodingException { log.debug("Getting Base64 encoded message from request"); - String encodedMessage = getLastParameterFromRequest(request, "SAMLRequest"); + String encodedMessage = SamlHttpUtils.getLastParameterFromRequest(request, SAML_REQ_PARAM_NAME); if (Strings.isNullOrEmpty(encodedMessage)) { - encodedMessage = getLastParameterFromRequest(request, "SAMLResponse"); + encodedMessage = SamlHttpUtils.getLastParameterFromRequest(request, SAML_RESP_PARAM_NAME); + } if (Strings.isNullOrEmpty(encodedMessage)) { @@ -43,14 +51,17 @@ public class EaafHttpPostDecoder extends HTTPPostDecoder { throw new MessageDecodingException("No SAML message present in request"); } - log.trace("Base64 decoding SAML message:\n{}", encodedMessage); + log.trace("Base64 decoding SAML message: {}", encodedMessage); final byte[] decodedBytes = Base64Support.decode(encodedMessage); - if (decodedBytes == null) { - log.info("Unable to Base64 decode SAML message"); - throw new MessageDecodingException("Unable to Base64 decode SAML message"); + + try { + log.trace("Decoded SAML message: {}", new String(decodedBytes, "UTF-8")); + + } catch (final UnsupportedEncodingException e) { + log.warn("Logging of incomming message failed", e); + } - log.trace("Decoded SAML message:\n{}", new String(decodedBytes)); return new ByteArrayInputStream(decodedBytes); } @@ -61,31 +72,8 @@ public class EaafHttpPostDecoder extends HTTPPostDecoder { */ @Override protected XMLObject unmarshallMessage(final InputStream messageStream) throws MessageDecodingException { - return Saml2Utils.unmarshallMessage(messageStream); - - } - - /** - * Always read the last parameter with this name from request to get a strict - * deterministic behavior.
- *
- * If more than one parameters with the same name exists, this method - * always select the last parameter value. - * - * @param request Incoming http request - * @param paramName Name of the http parameter - * @return the last parameter value with this name, or null if the - * parameter not exists - */ - @Nullable - private String getLastParameterFromRequest(@Nonnull HttpServletRequest request, @Nonnull String paramName) { - final String[] values = request.getParameterValues(paramName); - if (values != null && values.length > 0) { - return values[values.length - 1]; - - } - - return null; + return Saml2Utils.unmarshallMessage(messageStream); } + } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/EaafHttpRedirectDeflateDecoder.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/EaafHttpRedirectDeflateDecoder.java index 16d73296..c5174f02 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/EaafHttpRedirectDeflateDecoder.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/EaafHttpRedirectDeflateDecoder.java @@ -4,6 +4,9 @@ import java.io.InputStream; import javax.servlet.http.HttpServletRequest; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SamlHttpUtils; + import org.opensaml.core.xml.XMLObject; import org.opensaml.messaging.context.MessageContext; import org.opensaml.messaging.decoder.MessageDecodingException; @@ -13,10 +16,7 @@ import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.binding.decoding.impl.HTTPRedirectDeflateDecoder; import com.google.common.base.Strings; - -import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; import lombok.extern.slf4j.Slf4j; -import net.shibboleth.utilities.java.support.net.URISupport; import net.shibboleth.utilities.java.support.primitive.StringSupport; /** @@ -29,6 +29,14 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport; @Slf4j public class EaafHttpRedirectDeflateDecoder extends HTTPRedirectDeflateDecoder { + private static final String SAML_REQ_PARAM_NAME = "SAMLRequest"; + private static final String SAML_RESP_PARAM_NAME = "SAMLResponse"; + + public EaafHttpRedirectDeflateDecoder(HttpServletRequest req) { + setHttpServletRequest(req); + + } + @Override protected void doDecode() throws MessageDecodingException { final MessageContext messageContext = new MessageContext<>(); @@ -52,16 +60,19 @@ public class EaafHttpRedirectDeflateDecoder extends HTTPRedirectDeflateDecoder { // implement parameter extraction as same as in // SAML2HTTPRedirectDeflateSignatureSecurityHandler.java - final String queryString = getHttpServletRequest().getQueryString(); - if (!Strings.isNullOrEmpty(URISupport.getRawQueryStringParameter(queryString, "SAMLRequest"))) { - samlMessageIns = decodeMessage(URISupport.getRawQueryStringParameter(queryString, "SAMLRequest")); - } else if (!Strings.isNullOrEmpty(URISupport.getRawQueryStringParameter(queryString, "SAMLResponse"))) { - samlMessageIns = decodeMessage(URISupport.getRawQueryStringParameter(queryString, "SAMLResponse")); + final String samlReq = SamlHttpUtils.getLastParameterFromRequest(request, SAML_REQ_PARAM_NAME); + final String samlResp = SamlHttpUtils.getLastParameterFromRequest(request, SAML_RESP_PARAM_NAME); + if (!Strings.isNullOrEmpty(samlReq)) { + samlMessageIns = decodeMessage(samlReq); + + } else if (!Strings.isNullOrEmpty(samlResp)) { + samlMessageIns = decodeMessage(samlResp); + } else { throw new MessageDecodingException( "No SAMLRequest or SAMLResponse query path parameter, invalid SAML 2 HTTP Redirect message"); } - + final SAMLObject samlMessage = (SAMLObject) unmarshallMessage(samlMessageIns); messageContext.setMessage(samlMessage); log.debug("Decoded SAML message"); @@ -69,9 +80,9 @@ public class EaafHttpRedirectDeflateDecoder extends HTTPRedirectDeflateDecoder { populateBindingContext(messageContext); setMessageContext(messageContext); - + } - + /** * EAAF specific unmarshaller perform XML schema validation before unmarshalling * the SAML message. @@ -79,8 +90,8 @@ public class EaafHttpRedirectDeflateDecoder extends HTTPRedirectDeflateDecoder { */ @Override protected XMLObject unmarshallMessage(final InputStream messageStream) throws MessageDecodingException { - return Saml2Utils.unmarshallMessage(messageStream); - + return Saml2Utils.unmarshallMessage(messageStream); + } } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/EaafKeyStoreX509CredentialAdapter.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/EaafKeyStoreX509CredentialAdapter.java index 7c433c1c..6d81700a 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/EaafKeyStoreX509CredentialAdapter.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/EaafKeyStoreX509CredentialAdapter.java @@ -50,21 +50,24 @@ public class EaafKeyStoreX509CredentialAdapter extends KeyStoreX509CredentialAda /** * Get an OpenSAML2 keystore. * - * @param store Java KeyStore - * @param alias Key alias - * @param password key Password - * @param keyStoreFriendlyName Friendlyname of this keystore for logging purposes - * @throws CredentialsNotAvailableException In case of an initialization exception + * @param store Java KeyStore + * @param alias Key alias + * @param password key Password + * @param keyStoreFriendlyName Friendlyname of this keystore for logging + * purposes + * @throws CredentialsNotAvailableException In case of an initialization + * exception */ public EaafKeyStoreX509CredentialAdapter(@Nonnull final KeyStore store, @Nonnull final String alias, - @Nullable final char[] password, @Nonnull String keyStoreFriendlyName) throws CredentialsNotAvailableException { + @Nullable final char[] password, @Nonnull String keyStoreFriendlyName) + throws CredentialsNotAvailableException { super(store, alias, password); if (getPrivateKey() == null && getSecretKey() == null) { log.error("KeyStore: {} Key with alias: {} not found or contains no PrivateKey.", keyStoreFriendlyName, alias); throw new CredentialsNotAvailableException("internal.pvp.00", - new Object[] { keyStoreFriendlyName, alias}); + new Object[] { keyStoreFriendlyName, alias }); } @@ -74,7 +77,8 @@ public class EaafKeyStoreX509CredentialAdapter extends KeyStoreX509CredentialAda PvpConstants.DEFAULT_SIGNING_METHODE_EC)); } catch (final SamlSigningException e) { - throw new CredentialsNotAvailableException("internal.pvp.01", new Object[] {keyStoreFriendlyName, alias}, e); + throw new CredentialsNotAvailableException("internal.pvp.01", new Object[] { keyStoreFriendlyName, + alias }, e); } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/HttpPostEncoderWithOwnTemplate.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/HttpPostEncoderWithOwnTemplate.java index 3650e617..fa77b73c 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/HttpPostEncoderWithOwnTemplate.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/HttpPostEncoderWithOwnTemplate.java @@ -19,7 +19,6 @@ package at.gv.egiz.eaaf.modules.pvp2.impl.opensaml; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/OpenSaml3ResourceAdapter.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/OpenSaml3ResourceAdapter.java index 2e45aea2..f474267f 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/OpenSaml3ResourceAdapter.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/OpenSaml3ResourceAdapter.java @@ -9,22 +9,23 @@ import java.net.URL; import net.shibboleth.utilities.java.support.resource.Resource; /** - * Adapter that connects a Spring {@link org.springframework.core.io.Resource} to a {@link Resource}. - * + * Adapter that connects a Spring {@link org.springframework.core.io.Resource} + * to a {@link Resource}. + * * @author tlenz * */ public class OpenSaml3ResourceAdapter implements Resource { - private org.springframework.core.io.Resource internalResource; + private final org.springframework.core.io.Resource internalResource; public OpenSaml3ResourceAdapter(org.springframework.core.io.Resource resource) { this.internalResource = resource; } - + @Override public boolean exists() { - return internalResource.exists(); + return internalResource.exists(); } @Override diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/StringRedirectDeflateEncoder.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/StringRedirectDeflateEncoder.java index bd450518..38735fb8 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/StringRedirectDeflateEncoder.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/StringRedirectDeflateEncoder.java @@ -23,8 +23,8 @@ import org.opensaml.messaging.context.MessageContext; import org.opensaml.messaging.encoder.MessageEncodingException; import org.opensaml.saml.common.SAMLObject; import org.opensaml.saml.saml2.binding.encoding.impl.HTTPRedirectDeflateEncoder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import lombok.extern.slf4j.Slf4j; /** * Create deflate encoded SAML2 redirect-binding informations. @@ -32,9 +32,9 @@ import org.slf4j.LoggerFactory; * @author tlenz * */ -public class StringRedirectDeflateEncoder extends HTTPRedirectDeflateEncoder { - private static final Logger log = LoggerFactory.getLogger(StringRedirectDeflateEncoder.class); +@Slf4j +public class StringRedirectDeflateEncoder extends HTTPRedirectDeflateEncoder { private String redirectUrl = null; @Override @@ -50,6 +50,8 @@ public class StringRedirectDeflateEncoder extends HTTPRedirectDeflateEncoder { redirectUrl = buildRedirectURL(messageContext, endpointUrl, encodedMessage); + log.trace("SAML2 redirect-binding URL was generated as: {}", redirectUrl); + } /** diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/initialize/EaafOpenSaml3xInitializer.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/initialize/EaafOpenSaml3xInitializer.java index 42d4d736..5c6d861d 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/initialize/EaafOpenSaml3xInitializer.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/initialize/EaafOpenSaml3xInitializer.java @@ -60,10 +60,12 @@ public class EaafOpenSaml3xInitializer extends InitializationService { /** * EAAF specific OpenSAML3.x initialization. * - * @throws InitializationException In case of an error - * @throws ComponentInitializationException + * @throws InitializationException In case of an error + * @throws ComponentInitializationException In case of an OpenSAML3 + * initialization error */ - public static synchronized void eaafInitialize() throws InitializationException, ComponentInitializationException { + public static synchronized void eaafInitialize() throws InitializationException, + ComponentInitializationException { log.debug("Initializing OpenSAML 3.x ... "); initialize(); diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/QaaLevelVerifier.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/QaaLevelVerifier.java index 31ffd5a7..ca6f29e4 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/QaaLevelVerifier.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/QaaLevelVerifier.java @@ -21,13 +21,13 @@ package at.gv.egiz.eaaf.modules.pvp2.impl.utils; import java.util.List; +import at.gv.egiz.eaaf.core.api.data.EaafConstants; +import at.gv.egiz.eaaf.modules.pvp2.exception.QaaNotAllowedException; + import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import at.gv.egiz.eaaf.core.api.data.EaafConstants; -import at.gv.egiz.eaaf.modules.pvp2.exception.QaaNotAllowedException; - /** * EAAF LoA Level verifier checks if requested LoA matchs to LoA of * authentication. diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/Saml2Utils.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/Saml2Utils.java index 763c07f6..dc7e9338 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/Saml2Utils.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/Saml2Utils.java @@ -37,6 +37,14 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.validation.Schema; import javax.xml.validation.Validator; +import at.gv.egiz.eaaf.core.impl.utils.DomUtils; +import at.gv.egiz.eaaf.core.impl.utils.Random; +import at.gv.egiz.eaaf.modules.pvp2.PvpConstants; +import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; +import at.gv.egiz.eaaf.modules.pvp2.api.reqattr.EaafRequestedAttribute; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlSigningException; +import at.gv.egiz.eaaf.modules.pvp2.exception.SchemaValidationException; + import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.opensaml.core.xml.XMLObject; @@ -82,13 +90,6 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; -import at.gv.egiz.eaaf.core.impl.utils.DomUtils; -import at.gv.egiz.eaaf.core.impl.utils.Random; -import at.gv.egiz.eaaf.modules.pvp2.PvpConstants; -import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; -import at.gv.egiz.eaaf.modules.pvp2.api.reqattr.EaafRequestedAttribute; -import at.gv.egiz.eaaf.modules.pvp2.exception.SamlSigningException; -import at.gv.egiz.eaaf.modules.pvp2.exception.SchemaValidationException; import net.shibboleth.utilities.java.support.xml.QNameSupport; import net.shibboleth.utilities.java.support.xml.SerializeSupport; @@ -114,13 +115,14 @@ public class Saml2Utils { } /** - * Sign a OpenSAML 3.x object with a {@link X509Credential}. - *
- *

This method used {@link PvpConstants.DEFAULT_SIGNING_METHODE_RSA} - * or {@link PvpConstants.DEFAULT_SIGNING_METHODE_EC} as algorithm

+ * Sign a OpenSAML 3.x object with a {@link X509Credential}.
+ *

+ * This method used {@link PvpConstants.DEFAULT_SIGNING_METHODE_RSA} or + * {@link PvpConstants.DEFAULT_SIGNING_METHODE_EC} as algorithm + *

* - * @param {@link SignableXMLObject} - * @param toSign object that should be signed + * @param {@link SignableXMLObject} + * @param toSign object that should be signed * @param signingCredential Credentials that should be used for signing * @param injectCertificate true, if certificate should be part of the signature * @return Signed object @@ -157,18 +159,20 @@ public class Saml2Utils { } catch (final SignatureException | MarshallingException | SecurityException e) { throw new SamlSigningException("internal.pvp.96", - new Object[] {signingCredential.getEntityId(), e.getMessage()}, e); + new Object[] { signingCredential.getEntityId(), e.getMessage() }, e); } } /** - * SAML2 message unmarshaller that performs schema validation before unmarshall the message. - * + * SAML2 message unmarshaller that performs schema validation before unmarshall + * the message. + * * @param messageStream SAML2 message that shoulld be unmarshalled * @return OpenSAML XML object - * @throws MessageDecodingException In case of a schema-validation or unmarshalling error + * @throws MessageDecodingException In case of a schema-validation or + * unmarshalling error */ public static XMLObject unmarshallMessage(final InputStream messageStream) throws MessageDecodingException { try { @@ -201,22 +205,24 @@ public class Saml2Utils { } catch (ParserConfigurationException | SAXException e) { log.warn("Message schema-validation failed."); - throw new MessageDecodingException("Message schema-validation failed.", + throw new MessageDecodingException("Message schema-validation failed.", new SchemaValidationException("internal.pvp.03", new Object[] { e.getMessage() }, e)); } catch (final IOException e) { log.error("Error read message from input stream", e); throw new MessageDecodingException("Error read message from input stream", e); - + } } - + /** * Select signature algorithm for a given credential. * - * @param credentials {@link X509Credential} that will be used for signing - * @param rsaSigAlgorithm RSA based signing algorithm that should be used in case of RSA credential - * @param ecSigAlgorithm EC based signing algorithm that should be used in case of RSA credential + * @param credentials {@link X509Credential} that will be used for signing + * @param rsaSigAlgorithm RSA based signing algorithm that should be used in + * case of RSA credential + * @param ecSigAlgorithm EC based signing algorithm that should be used in case + * of RSA credential * @return either the rsaSigAlgorithm or the ecSigAlgorithm * @throws SamlSigningException In case of an unsupported credential */ @@ -233,7 +239,7 @@ public class Saml2Utils { log.warn("Could NOT evaluate the Private-Key type from " + credentials.getEntityId() + " credential."); throw new SamlSigningException("internal.pvp.97", - new Object[] {credentials.getEntityId(), privatekey.getClass().getName()}); + new Object[] { credentials.getEntityId(), privatekey.getClass().getName() }); } } @@ -263,14 +269,16 @@ public class Saml2Utils { } /** - * Get a {@link KeyInfoGenerator} that injects key information into XML signature. + * Get a {@link KeyInfoGenerator} that injects key information into XML + * signature. * - * @param credential @link X509Credential} that will be used for signing - * @param injectCertificate Set true if the certificate should be added to KeyInfo + * @param credential @link X509Credential} that will be used for signing + * @param injectCertificate Set true if the certificate should be + * added to KeyInfo * @return Generator for a XML signature key-information */ public static KeyInfoGenerator getKeyInfoGenerator(X509Credential credential, boolean injectCertificate) { - //OpenSAML3 only support RSA and DSA for direct key injection + // OpenSAML3 only support RSA and DSA for direct key injection KeyInfoGeneratorFactory keyInfoGenFac = null; if (injectCertificate || credential.getPublicKey() instanceof ECPublicKey) { final SignatureSigningConfiguration secConfiguration = SecurityConfigurationSupport @@ -280,7 +288,7 @@ public class Saml2Utils { keyInfoGenFac = keyInfoGenManager.getFactory(credential); } else { - keyInfoGenFac = createKeyInfoWithoutCertificate(credential); + keyInfoGenFac = createKeyInfoWithoutCertificate(); } @@ -288,7 +296,6 @@ public class Saml2Utils { } - /** * Create a SAML2 object. * @@ -462,19 +469,20 @@ public class Saml2Utils { .buildObject(Signature.DEFAULT_ELEMENT_NAME); signature.setSigningCredential(signingCredential); signature.setSignatureAlgorithm(usedSigAlg); - final KeyInfo keyInfo = getKeyInfoGenerator(signingCredential, injectCertificate).generate(signingCredential); + final KeyInfo keyInfo = getKeyInfoGenerator(signingCredential, injectCertificate).generate( + signingCredential); signature.setKeyInfo(keyInfo); signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); return signature; } - private static KeyInfoGeneratorFactory createKeyInfoWithoutCertificate(X509Credential credential) { + private static KeyInfoGeneratorFactory createKeyInfoWithoutCertificate() { final KeyInfoGeneratorFactory keyInfoGenFac = new BasicKeyInfoGeneratorFactory(); - ((BasicKeyInfoGeneratorFactory)keyInfoGenFac).setEmitPublicKeyValue(true); - ((BasicKeyInfoGeneratorFactory)keyInfoGenFac).setEmitEntityIDAsKeyName(true); - ((BasicKeyInfoGeneratorFactory)keyInfoGenFac).setEmitKeyNames(true); - ((BasicKeyInfoGeneratorFactory)keyInfoGenFac).setEmitPublicDEREncodedKeyValue(true); + ((BasicKeyInfoGeneratorFactory) keyInfoGenFac).setEmitPublicKeyValue(true); + ((BasicKeyInfoGeneratorFactory) keyInfoGenFac).setEmitEntityIDAsKeyName(true); + ((BasicKeyInfoGeneratorFactory) keyInfoGenFac).setEmitKeyNames(true); + ((BasicKeyInfoGeneratorFactory) keyInfoGenFac).setEmitPublicDEREncodedKeyValue(true); return keyInfoGenFac; } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/SamlHttpUtils.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/SamlHttpUtils.java new file mode 100644 index 00000000..2e02bf22 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/SamlHttpUtils.java @@ -0,0 +1,33 @@ +package at.gv.egiz.eaaf.modules.pvp2.impl.utils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +public class SamlHttpUtils { + + /** + * Always read the last parameter with this name from request to get a strict + * deterministic behavior.
+ *
+ * If more than one parameters with the same name exists, this method + * always select the last parameter value. + * + * @param request Incoming http request + * @param paramName Name of the http parameter + * @return the last parameter value with this name, or null if the + * parameter not exists + */ + @Nullable + public static String getLastParameterFromRequest(@Nonnull HttpServletRequest request, + @Nonnull String paramName) { + final String[] values = request.getParameterValues(paramName); + if (values != null && values.length > 0) { + return values[values.length - 1]; + + } + + return null; + + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/TrustEngineFactory.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/TrustEngineFactory.java index 1591198c..f0758706 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/TrustEngineFactory.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/TrustEngineFactory.java @@ -23,9 +23,11 @@ import java.util.ArrayList; import java.util.List; import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2InternalErrorException; import org.opensaml.saml.metadata.resolver.impl.PredicateRoleDescriptorResolver; import org.opensaml.saml.security.impl.MetadataCredentialResolver; +import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; import org.opensaml.xmlsec.keyinfo.impl.BasicProviderKeyInfoCredentialResolver; import org.opensaml.xmlsec.keyinfo.impl.KeyInfoProvider; import org.opensaml.xmlsec.keyinfo.impl.provider.DSAKeyValueProvider; @@ -34,29 +36,50 @@ import org.opensaml.xmlsec.keyinfo.impl.provider.RSAKeyValueProvider; import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; +import lombok.extern.slf4j.Slf4j; +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; + +@Slf4j public class TrustEngineFactory { /** * Get OpenSAML2 TrustEngine. * * @param mdResolver Metadata provider - * @return + * @return TrustEngine for SAML2 message validation + * @throws Pvp2InternalErrorException In case of a TrustEngine initialization + * error */ public static SignatureTrustEngine getSignatureKnownKeysTrustEngine( - final IPvp2MetadataProvider mdResolver) { - final MetadataCredentialResolver resolver = new MetadataCredentialResolver(); - resolver.setRoleDescriptorResolver(new PredicateRoleDescriptorResolver(mdResolver)); + final IPvp2MetadataProvider mdResolver) throws Pvp2InternalErrorException { + try { + final List keyInfoProvider = new ArrayList<>(); + keyInfoProvider.add(new DSAKeyValueProvider()); + keyInfoProvider.add(new RSAKeyValueProvider()); + keyInfoProvider.add(new InlineX509DataProvider()); + final KeyInfoCredentialResolver keyInfoCredentialResolver = new BasicProviderKeyInfoCredentialResolver( + keyInfoProvider); + + final PredicateRoleDescriptorResolver roleDescriptorResolver = new PredicateRoleDescriptorResolver( + mdResolver); + roleDescriptorResolver.setRequireValidMetadata(true); + roleDescriptorResolver.initialize(); + + final MetadataCredentialResolver resolver = new MetadataCredentialResolver(); + resolver.setRoleDescriptorResolver(roleDescriptorResolver); + resolver.setKeyInfoCredentialResolver(keyInfoCredentialResolver); + resolver.initialize(); + + final ExplicitKeySignatureTrustEngine engine = + new ExplicitKeySignatureTrustEngine(resolver, keyInfoCredentialResolver); - final List keyInfoProvider = new ArrayList<>(); - keyInfoProvider.add(new DSAKeyValueProvider()); - keyInfoProvider.add(new RSAKeyValueProvider()); - keyInfoProvider.add(new InlineX509DataProvider()); + return engine; - final ExplicitKeySignatureTrustEngine engine = - new ExplicitKeySignatureTrustEngine(resolver, - new BasicProviderKeyInfoCredentialResolver(keyInfoProvider)); + } catch (final ComponentInitializationException e) { + log.warn("Initialization of SignatureTrustEngine FAILED.", e); + throw new Pvp2InternalErrorException(e); - return engine; + } } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/SchemaValidationFilter.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/SchemaValidationFilter.java index 73a11c49..1994eba0 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/SchemaValidationFilter.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/SchemaValidationFilter.java @@ -32,7 +32,6 @@ import org.opensaml.saml.metadata.resolver.filter.FilterException; import org.opensaml.saml.metadata.resolver.filter.MetadataFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.xml.sax.SAXException; public class SchemaValidationFilter implements MetadataFilter { private static final Logger log = LoggerFactory.getLogger(SchemaValidationFilter.class); @@ -58,8 +57,6 @@ public class SchemaValidationFilter implements MetadataFilter { @Override public XMLObject filter(final XMLObject arg0) throws FilterException { - String errString = null; - if (isActive) { try { final Schema test = schemaBuilder.getSAMLSchema(); @@ -68,15 +65,6 @@ public class SchemaValidationFilter implements MetadataFilter { val.validate(source); log.info("Metadata Schema validation check done OK"); - } catch (final SAXException e) { - if (log.isDebugEnabled() || log.isTraceEnabled()) { - log.warn("Metadata Schema validation FAILED with exception:", e); - } else { - log.warn("Metadata Schema validation FAILED with message: " + e.getMessage()); - } - - errString = e.getMessage(); - } catch (final Exception e) { if (log.isDebugEnabled() || log.isTraceEnabled()) { log.warn("Metadata Schema validation FAILED with exception:", e); @@ -84,13 +72,10 @@ public class SchemaValidationFilter implements MetadataFilter { log.warn("Metadata Schema validation FAILED with message: " + e.getMessage()); } - errString = e.getMessage(); - + throw new FilterException(new SchemaValidationException("internal.pvp.03", + new Object[] { e.getMessage() }, e)); } - throw new FilterException(new SchemaValidationException("pvp2.26", - new Object[] { "Metadata Schema validation FAILED with message: " + errString })); - } else { log.info("Metadata Schema validation check is DEACTIVATED!"); diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/SimpleMetadataSignatureVerificationFilter.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/SimpleMetadataSignatureVerificationFilter.java new file mode 100644 index 00000000..ef09e5c4 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/SimpleMetadataSignatureVerificationFilter.java @@ -0,0 +1,146 @@ +/* + * Copyright 2018 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, 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 "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.validation.metadata; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nonnull; + +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2MetadataException; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlMetadataSignatureException; + +import org.opensaml.saml.common.SignableSAMLObject; +import org.opensaml.saml.saml2.metadata.EntitiesDescriptor; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; +import org.opensaml.security.x509.BasicX509Credential; +import org.opensaml.xmlsec.signature.support.SignatureException; +import org.opensaml.xmlsec.signature.support.SignatureValidator; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SimpleMetadataSignatureVerificationFilter extends AbstractMetadataSignatureFilter { + + private final String metadataUrl; + private final List trustedCredential = new ArrayList<>(); + + private static final String ERROR_07 = "internal.pvp.07"; + private static final String ERROR_12 = "internal.pvp.12"; + private static final String ERROR_MSG_ENTITIESDESC = "EntitiesDescritors are NOT supported"; + private static final String ERROR_MSG_SIGNOTVALID = "Signature not valid or no trusted certificate found"; + + /** + * SAML2 metadata-signature verification-filter that uses a simple {@link List} + * of trusted {@link BasicX509Credential} as truststore.
+ *

+ * This filter only validates {@link EntityDescriptor} elements.
+ * SAML2 metadata with {@link EntitiesDescriptor} are not supported. + *

+ * + * @param credentials Trust X509 certificates + * @param metadataUrl Metadata URL for logging purposes + */ + public SimpleMetadataSignatureVerificationFilter(@Nonnull List credentials, + @Nonnull String metadataUrl) { + this.metadataUrl = metadataUrl; + this.trustedCredential.addAll(credentials); + + } + + @Override + protected void verify(EntityDescriptor desc) throws Pvp2MetadataException { + try { + internalVerify(desc); + + } catch (final EaafException e) { + log.info("Metadata verification FAILED for: {} Reason: {}", metadataUrl, e.getMessage()); + throw new Pvp2MetadataException(ERROR_07, + new Object[] { metadataUrl, e.getMessage() }, e); + + } + } + + @Override + protected void verify(EntitiesDescriptor desc) throws Pvp2MetadataException { + throw new Pvp2MetadataException(ERROR_07, + new Object[] { metadataUrl, ERROR_MSG_ENTITIESDESC }); + + } + + @Override + protected void verify(EntityDescriptor entity, EntitiesDescriptor desc) throws Pvp2MetadataException { + throw new Pvp2MetadataException(ERROR_07, + new Object[] { metadataUrl, ERROR_MSG_ENTITIESDESC }); + + } + + private void internalVerify(SignableSAMLObject signedElement) + throws EaafException { + // check if signature exists + if (signedElement.getSignature() == null) { + throw new Pvp2MetadataException(ERROR_12, + new Object[] { metadataUrl }); + + } + + // perform general signature validation + try { + final SAMLSignatureProfileValidator sigValidator = new SAMLSignatureProfileValidator(); + sigValidator.validate(signedElement.getSignature()); + + } catch (final SignatureException e) { + log.error("Failed to validate Signature", e); + throw new Pvp2MetadataException(ERROR_07, + new Object[] { metadataUrl, e.getMessage() }, e); + + } + + // perform cryptographic signature verification + boolean isTrusted = false; + for (final BasicX509Credential cred : trustedCredential) { + log.trace("Validating signature with credential: {} ... ", + cred.getEntityCertificate().getSubjectDN()); + try { + SignatureValidator.validate(signedElement.getSignature(), cred); + isTrusted = true; + + } catch (final SignatureException e) { + log.debug("Failed to verfiy Signature with cert: {} Reason: {}", + cred.getEntityCertificate().getSubjectDN(), e.getMessage()); + + } + } + + if (!isTrusted) { + log.info("PVP2 metadata: " + metadataUrl + " are NOT trusted!"); + throw new SamlMetadataSignatureException(metadataUrl, ERROR_MSG_SIGNOTVALID); + + } + + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/EaafMessageContextInitializationHandler.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/EaafMessageContextInitializationHandler.java index 2672bef2..aba0a68b 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/EaafMessageContextInitializationHandler.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/EaafMessageContextInitializationHandler.java @@ -1,23 +1,64 @@ package at.gv.egiz.eaaf.modules.pvp2.impl.verification; +import javax.annotation.Nonnull; + +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2InternalErrorException; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.TrustEngineFactory; + +import org.opensaml.core.config.ConfigurationService; import org.opensaml.messaging.context.MessageContext; import org.opensaml.messaging.handler.AbstractMessageHandler; import org.opensaml.messaging.handler.MessageHandlerException; import org.opensaml.saml.common.SAMLObject; import org.opensaml.saml.common.messaging.context.SAMLMessageInfoContext; import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext; +import org.opensaml.xmlsec.SignatureValidationConfiguration; +import org.opensaml.xmlsec.SignatureValidationParameters; +import org.opensaml.xmlsec.context.SecurityParametersContext; +import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; import lombok.extern.slf4j.Slf4j; +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; @Slf4j public class EaafMessageContextInitializationHandler extends AbstractMessageHandler { + private final IPvp2MetadataProvider internalMetadataProvider; + private SignatureTrustEngine trustEngine; + + public EaafMessageContextInitializationHandler(@Nonnull IPvp2MetadataProvider metadataProvider) { + internalMetadataProvider = metadataProvider; + } + + @Override + protected void doInitialize() throws ComponentInitializationException { + try { + trustEngine = TrustEngineFactory.getSignatureKnownKeysTrustEngine(internalMetadataProvider); + + } catch (final Pvp2InternalErrorException e) { + throw new ComponentInitializationException("TrustEngine injection FAILED", e); + + } + } + + @Override protected void doInvoke(MessageContext messageContext) throws MessageHandlerException { log.trace("Injecting sub-context to SAML2 message ... "); messageContext.addSubcontext(new SAMLPeerEntityContext()); messageContext.addSubcontext(new SAMLMessageInfoContext()); + + final SecurityParametersContext securityParameterContext = new SecurityParametersContext(); + final SignatureValidationParameters sigValParameters = new SignatureValidationParameters(); + securityParameterContext.setSignatureValidationParameters(sigValParameters); + messageContext.addSubcontext(securityParameterContext); + + sigValParameters.setBlacklistedAlgorithms( + ConfigurationService.get(SignatureValidationConfiguration.class) + .getBlacklistedAlgorithms()); + sigValParameters.setSignatureTrustEngine(trustEngine); } } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/EaafSaml2HttpRedirectDeflateSignatureSecurityHandler.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/EaafSaml2HttpRedirectDeflateSignatureSecurityHandler.java new file mode 100644 index 00000000..204229ee --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/EaafSaml2HttpRedirectDeflateSignatureSecurityHandler.java @@ -0,0 +1,107 @@ +package at.gv.egiz.eaaf.modules.pvp2.impl.verification; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IRefreshableMetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlSigningException; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SamlHttpUtils; + +import org.opensaml.messaging.context.MessageContext; +import org.opensaml.messaging.handler.MessageHandlerException; +import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext; +import org.opensaml.saml.saml2.binding.security.impl.SAML2HTTPRedirectDeflateSignatureSecurityHandler; + +import com.google.common.base.Strings; +import lombok.extern.slf4j.Slf4j; +import net.shibboleth.utilities.java.support.codec.Base64Support; + +/** + * Always extracts the last http parameter with a specific name from request, if + * more than one with the same name exists. + * + * @author tlenz + * + */ +@Slf4j +public class EaafSaml2HttpRedirectDeflateSignatureSecurityHandler extends + SAML2HTTPRedirectDeflateSignatureSecurityHandler { + + public static final String HTTP_REDIRECT_SIGALG = "SigAlg"; + public static final String HTTP_REDIRECT_SIGNATURE = "Signature"; + + private IRefreshableMetadataProvider refreshableMetadataProvider = null; + + /** + * Signature verification handler that reloads SAML2 metadata if signature + * verification fails. + * + * @param metadataProvider Metadata provider implementation. Refreshing is only + * possible, if that provider implements + * {@link IRefreshableMetadataProvider} + */ + public EaafSaml2HttpRedirectDeflateSignatureSecurityHandler( + @Nullable IPvp2MetadataProvider metadataProvider) { + if (metadataProvider != null) { + if (metadataProvider instanceof IRefreshableMetadataProvider) { + refreshableMetadataProvider = (IRefreshableMetadataProvider) metadataProvider; + + } else { + log.trace("Refreshing is not supported by {} metadata-provider", + metadataProvider.getClass().getSimpleName()); + + } + } + } + + @Override + protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException { + try { + super.doInvoke(messageContext); + + } catch (final MessageHandlerException e) { + if (refreshableMetadataProvider != null) { + + log.debug("Starting metadata refresh process ... "); + if (refreshableMetadataProvider.refreshMetadataProvider( + messageContext.getSubcontext(SAMLPeerEntityContext.class).getEntityId())) { + log.trace("Refreshing successful. Restarting message evaluation ... "); + + try { + super.doInvoke(messageContext); + return; + + } catch (final MessageHandlerException e1) { + log.debug("Signature validation fails twice with second error: {}", e.getMessage()); + + } + } + } + + log.info("Signature validation of SAML message failed. Reason: {}", e.getMessage()); + throw new MessageHandlerException( + new SamlSigningException("internal.pvp.10", new Object[] { e.getMessage() }, e)); + } + } + + @Override + @Nullable + protected byte[] getSignature() throws MessageHandlerException { + final String signature = SamlHttpUtils.getLastParameterFromRequest( + getHttpServletRequest(), HTTP_REDIRECT_SIGNATURE); + + if (Strings.isNullOrEmpty(signature)) { + return null; + + } + return Base64Support.decode(signature); + } + + @Override + @Nullable + protected String getSignatureAlgorithm() throws MessageHandlerException { + return SamlHttpUtils.getLastParameterFromRequest(getHttpServletRequest(), HTTP_REDIRECT_SIGALG); + + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/EaafSamlProtocolMessageXmlSignatureSecurityHandler.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/EaafSamlProtocolMessageXmlSignatureSecurityHandler.java new file mode 100644 index 00000000..9f6bc864 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/EaafSamlProtocolMessageXmlSignatureSecurityHandler.java @@ -0,0 +1,75 @@ +package at.gv.egiz.eaaf.modules.pvp2.impl.verification; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IRefreshableMetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlSigningException; + +import org.opensaml.messaging.context.MessageContext; +import org.opensaml.messaging.handler.MessageHandlerException; +import org.opensaml.saml.common.binding.security.impl.SAMLProtocolMessageXMLSignatureSecurityHandler; +import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class EaafSamlProtocolMessageXmlSignatureSecurityHandler extends + SAMLProtocolMessageXMLSignatureSecurityHandler { + + private IRefreshableMetadataProvider refreshableMetadataProvider = null; + + /** + * Signature verification handler that reloads SAML2 metadata if signature + * verification fails. + * + * @param metadataProvider Metadata provider implementation. Refreshing is only + * possible, if that provider implements + * {@link IRefreshableMetadataProvider} + */ + public EaafSamlProtocolMessageXmlSignatureSecurityHandler( + @Nullable IPvp2MetadataProvider metadataProvider) { + if (metadataProvider != null) { + if (metadataProvider instanceof IRefreshableMetadataProvider) { + refreshableMetadataProvider = (IRefreshableMetadataProvider) metadataProvider; + + } else { + log.trace("Refreshing is not supported by {} metadata-provider", + metadataProvider.getClass().getSimpleName()); + + } + } + } + + @Override + public void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException { + try { + super.doInvoke(messageContext); + + } catch (final MessageHandlerException e) { + if (refreshableMetadataProvider != null) { + + log.debug("Starting metadata refresh process ... "); + if (refreshableMetadataProvider.refreshMetadataProvider( + messageContext.getSubcontext(SAMLPeerEntityContext.class).getEntityId())) { + log.trace("Refreshing successful. Restarting message evaluation ... "); + + try { + super.doInvoke(messageContext); + return; + + } catch (final MessageHandlerException e1) { + log.debug("Signature validation fails twice with second error: {}", e.getMessage()); + + } + } + } + + log.info("Signature validation of SAML message failed. Reason: {}", e.getMessage()); + throw new MessageHandlerException( + new SamlSigningException("internal.pvp.10", new Object[] { e.getMessage() }, e)); + } + + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/PvpSamlMessageHandlerChain.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/PvpSamlMessageHandlerChain.java index e43d0423..a1365023 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/PvpSamlMessageHandlerChain.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/PvpSamlMessageHandlerChain.java @@ -24,12 +24,12 @@ public class PvpSamlMessageHandlerChain implements MessageHandlerChain handler : getHandlers()) { log.trace("Initializing SAML message handler: {}", handler.getClass().getName()); handler.invoke(messageContext); } - } @Override 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 index 658dfe16..2e26de7f 100644 --- 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 @@ -68,9 +68,10 @@ public class SamlVerificationEngine { * * @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 + * @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 SecurityException, Exception { -- cgit v1.2.3