diff options
author | Thomas Lenz <thomas.lenz@egiz.gv.at> | 2018-06-26 11:03:48 +0200 |
---|---|---|
committer | Thomas Lenz <thomas.lenz@egiz.gv.at> | 2018-06-26 11:03:48 +0200 |
commit | bee5dd259a4438d45ecd1bcc26dfba12875236d6 (patch) | |
tree | fe1cf7a35cd15dee5fb3c05de0341aa63bf743e0 /eaaf_modules | |
download | EAAF-Components-bee5dd259a4438d45ecd1bcc26dfba12875236d6.tar.gz EAAF-Components-bee5dd259a4438d45ecd1bcc26dfba12875236d6.tar.bz2 EAAF-Components-bee5dd259a4438d45ecd1bcc26dfba12875236d6.zip |
initial commit
Diffstat (limited to 'eaaf_modules')
88 files changed, 7575 insertions, 0 deletions
diff --git a/eaaf_modules/eaaf_module_pvp2_core/pom.xml b/eaaf_modules/eaaf_module_pvp2_core/pom.xml new file mode 100644 index 00000000..57d190eb --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/pom.xml @@ -0,0 +1,94 @@ +<?xml version="1.0"?> +<!-- + --> + +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>at.gv.egiz.eaaf</groupId> + <artifactId>eaaf_modules</artifactId> + <version>1.x</version> + </parent> + <artifactId>eaaf_module_pvp2_core</artifactId> + <version>${egiz.eaaf.version}</version> + <name>eaaf_module_pvp2_core</name> + <url>http://maven.apache.org</url> + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + <dependencies> + <dependency> + <groupId>at.gv.egiz.eaaf</groupId> + <artifactId>eaaf-core</artifactId> + <version>${egiz.eaaf.version}</version> + </dependency> + + <dependency> + <groupId>org.opensaml</groupId> + <artifactId>opensaml</artifactId> + </dependency> + <dependency> + <groupId>org.opensaml</groupId> + <artifactId>xmltooling</artifactId> + </dependency> + <dependency> + <groupId>org.opensaml</groupId> + <artifactId>openws</artifactId> + </dependency> + <dependency> + <groupId>org.apache.santuario</groupId> + <artifactId>xmlsec</artifactId> + </dependency> + <dependency> + <groupId>org.owasp.esapi</groupId> + <artifactId>esapi</artifactId> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <scope>provided</scope> + </dependency> + + <!-- Testing --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <finalName>eaaf_module_pvp2_core</finalName> + + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.7.0</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + + <!-- enable co-existence of testng and junit --> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <version>${surefire.version}</version> + <configuration> + <threadCount>1</threadCount> + <argLine>--add-modules java.xml.bind</argLine> + </configuration> + <dependencies> + <dependency> + <groupId>org.apache.maven.surefire</groupId> + <artifactId>surefire-junit47</artifactId> + <version>${surefire.version}</version> + </dependency> + </dependencies> + </plugin> + + </plugins> + </build> +</project> 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 new file mode 100644 index 00000000..cfb88e87 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/PVP2SProfileCoreSpringResourceProvider.java @@ -0,0 +1,30 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2; + +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 + public String getName() { + return "EAAF PVP2 S-Profile Core SpringResourceProvider"; + } + + @Override + public String[] getPackagesToScan() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Resource[] getResourcesToLoad() { + ClassPathResource sl20AuthConfig = new ClassPathResource("/eaaf_pvp.beans.xml", PVP2SProfileCoreSpringResourceProvider.class); + + return new Resource[] {sl20AuthConfig}; + } + +} 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 new file mode 100644 index 00000000..f5b7baa8 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/PVPConstants.java @@ -0,0 +1,107 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.opensaml.xml.encryption.EncryptionConstants; +import org.opensaml.xml.signature.SignatureConstants; + +import at.gv.egiz.eaaf.core.api.data.PVPAttributeDefinitions; +import at.gv.egiz.eaaf.core.impl.data.Trible; + +public interface PVPConstants extends PVPAttributeDefinitions { + + public static final String DEFAULT_SIGNING_METHODE = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; + public static final String DEFAULT_DIGESTMETHODE = SignatureConstants.ALGO_ID_DIGEST_SHA256; + public static final String DEFAULT_SYM_ENCRYPTION_METHODE = EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256; + public static final String DEFAULT_ASYM_ENCRYPTION_METHODE = EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSAOAEP; + + public static final String ENTITY_CATEGORY_ATTRIBITE = "http://macedir.org/entity-category"; + public static final String EGOVTOKEN = "http://www.ref.gv.at/ns/names/agiz/pvp/egovtoken"; + public static final String CITIZENTOKEN = "http://www.ref.gv.at/ns/names/agiz/pvp/citizentoken"; + + @Deprecated + public static final String STORK_ATTRIBUTE_PREFIX = "http://www.stork.gov.eu/"; + + public static final String REDIRECT = "Redirect"; + public static final String POST = "Post"; + public static final String SOAP = "Soap"; + public static final String METADATA = "Metadata"; + public static final String ATTRIBUTEQUERY = "AttributeQuery"; + public static final String SINGLELOGOUT = "SingleLogOut"; + + /** + * + * Get required PVP attributes for egovtoken + * First : PVP attribute name (OID) + * Second: FriendlyName + * Third: Required + * + */ + public static final List<Trible<String, String, Boolean>> EGOVTOKEN_PVP_ATTRIBUTES = + Collections.unmodifiableList(new ArrayList<Trible<String, String, Boolean>>() { + private static final long serialVersionUID = 1L; + { + //currently supported attributes + add(Trible.newInstance(PVP_VERSION_NAME, PVP_VERSION_FRIENDLY_NAME, true)); + add(Trible.newInstance(PRINCIPAL_NAME_NAME, PRINCIPAL_NAME_FRIENDLY_NAME, true)); + + //currently not supported attributes + add(Trible.newInstance(USERID_NAME, USERID_FRIENDLY_NAME, false)); + add(Trible.newInstance(GID_NAME, GID_FRIENDLY_NAME, false)); + add(Trible.newInstance(PARTICIPANT_ID_NAME, PARTICIPANT_ID_FRIENDLY_NAME, false)); + add(Trible.newInstance(OU_GV_OU_ID_NAME, OU_GV_OU_ID_FRIENDLY_NAME, false)); + add(Trible.newInstance(OU_NAME, OU_FRIENDLY_NAME, false)); + add(Trible.newInstance(SECCLASS_NAME, SECCLASS_FRIENDLY_NAME, false)); + + + } + }); + + /** + * + * Get required PVP attributes for citizenToken + * First : PVP attribute name (OID) + * Second: FriendlyName + * Third: Required + * + */ + public static final List<Trible<String, String, Boolean>> CITIZENTOKEN_PVP_ATTRIBUTES = + Collections.unmodifiableList(new ArrayList<Trible<String, String, Boolean>>() { + private static final long serialVersionUID = 1L; + { + //required attributes - eIDAS minimal-data set + add(Trible.newInstance(PVP_VERSION_NAME, PVP_VERSION_FRIENDLY_NAME, true)); + add(Trible.newInstance(PRINCIPAL_NAME_NAME, PRINCIPAL_NAME_FRIENDLY_NAME, true)); + add(Trible.newInstance(GIVEN_NAME_NAME, GIVEN_NAME_FRIENDLY_NAME, true)); + add(Trible.newInstance(BIRTHDATE_NAME, BIRTHDATE_FRIENDLY_NAME, true)); + add(Trible.newInstance(BPK_NAME, BPK_FRIENDLY_NAME, true)); + + + //not required attributes + add(Trible.newInstance(EID_CITIZEN_EIDAS_QAA_LEVEL_NAME, EID_CITIZEN_EIDAS_QAA_LEVEL_FRIENDLY_NAME, false)); + add(Trible.newInstance(EID_ISSUING_NATION_NAME, EID_ISSUING_NATION_FRIENDLY_NAME, false)); + add(Trible.newInstance(EID_SECTOR_FOR_IDENTIFIER_NAME, EID_SECTOR_FOR_IDENTIFIER_FRIENDLY_NAME, false)); + add(Trible.newInstance(MANDATE_TYPE_NAME, MANDATE_TYPE_FRIENDLY_NAME, false)); + add(Trible.newInstance(MANDATE_TYPE_OID_NAME, MANDATE_TYPE_OID_FRIENDLY_NAME, false)); + add(Trible.newInstance(MANDATE_LEG_PER_SOURCE_PIN_NAME, MANDATE_LEG_PER_SOURCE_PIN_FRIENDLY_NAME, false)); + add(Trible.newInstance(MANDATE_LEG_PER_SOURCE_PIN_TYPE_NAME, MANDATE_LEG_PER_SOURCE_PIN_TYPE_FRIENDLY_NAME, false)); + add(Trible.newInstance(MANDATE_NAT_PER_BPK_NAME, MANDATE_NAT_PER_BPK_FRIENDLY_NAME, false)); + add(Trible.newInstance(MANDATE_NAT_PER_GIVEN_NAME_NAME, MANDATE_NAT_PER_GIVEN_NAME_FRIENDLY_NAME, false)); + add(Trible.newInstance(MANDATE_NAT_PER_FAMILY_NAME_NAME, MANDATE_NAT_PER_FAMILY_NAME_FRIENDLY_NAME, false)); + add(Trible.newInstance(MANDATE_NAT_PER_BIRTHDATE_NAME, MANDATE_NAT_PER_BIRTHDATE_FRIENDLY_NAME, false)); + add(Trible.newInstance(MANDATE_LEG_PER_FULL_NAME_NAME, MANDATE_LEG_PER_FULL_NAME_FRIENDLY_NAME, false)); + add(Trible.newInstance(MANDATE_PROF_REP_OID_NAME, MANDATE_PROF_REP_OID_FRIENDLY_NAME, false)); + add(Trible.newInstance(MANDATE_PROF_REP_DESC_NAME, MANDATE_PROF_REP_DESC_FRIENDLY_NAME, false)); + add(Trible.newInstance(MANDATE_REFERENCE_VALUE_NAME, MANDATE_REFERENCE_VALUE_FRIENDLY_NAME, false)); + + + + } + }); + + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/PVPEventConstants.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/PVPEventConstants.java new file mode 100644 index 00000000..9b620a04 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/PVPEventConstants.java @@ -0,0 +1,11 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2; + +public class PVPEventConstants { + + //TODO!!! + public static final int AUTHPROTOCOL_PVP_METADATA = 3100; + public static final int AUTHPROTOCOL_PVP_REQUEST_AUTHREQUEST = 3101; + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/IPVP2BasicConfiguration.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/IPVP2BasicConfiguration.java new file mode 100644 index 00000000..28ccd7e0 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/IPVP2BasicConfiguration.java @@ -0,0 +1,26 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.api; + +import java.util.List; + +import org.opensaml.saml2.metadata.ContactPerson; +import org.opensaml.saml2.metadata.Organization; + +import at.gv.egiz.eaaf.core.exceptions.EAAFException; + +public interface IPVP2BasicConfiguration { + + public String getIDPEntityId(String authURL) throws EAAFException; + + public String getIDPSSOPostService(String authURL) throws EAAFException; + + public String getIDPSSORedirectService(String authURL) throws EAAFException; + + public Object getIDPSSOSOAPService(String extractAuthURLFromRequest) throws EAAFException; + + public List<ContactPerson> getIDPContacts() throws EAAFException; + + public Organization getIDPOrganisation() throws EAAFException; + +} 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 new file mode 100644 index 00000000..959ad747 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/binding/IDecoder.java @@ -0,0 +1,25 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.api.binding; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.opensaml.common.binding.decoding.URIComparator; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.ws.message.decoder.MessageDecodingException; +import org.opensaml.xml.security.SecurityException; + +import at.gv.egiz.eaaf.modules.pvp2.api.message.InboundMessageInterface; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + + +public interface IDecoder { + public InboundMessageInterface decode(HttpServletRequest req, + HttpServletResponse resp, MetadataProvider metadataProvider, boolean isSPEndPoint, URIComparator comparator) + throws MessageDecodingException, SecurityException, PVP2Exception; + + public boolean handleDecode(String action, HttpServletRequest req); + + public String getSAML2BindingName(); +} 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 new file mode 100644 index 00000000..a4475f20 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/binding/IEncoder.java @@ -0,0 +1,51 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.api.binding; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.opensaml.saml2.core.RequestAbstractType; +import org.opensaml.saml2.core.StatusResponseType; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.xml.security.SecurityException; +import org.opensaml.xml.security.credential.Credential; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + +public interface IEncoder { + + /** + * + * @param req The http request + * @param resp The http response + * @param request The SAML2 request object + * @param targetLocation URL, where the request should be transmit + * @param relayState token for session handling + * @param credentials Credential to sign the request object + * @param pendingReq Internal MOA-ID request object that contains session-state informations but never null + * @throws MessageEncodingException + * @throws SecurityException + * @throws PVP2Exception + */ + public void encodeRequest(HttpServletRequest req, + HttpServletResponse resp, RequestAbstractType request, String targetLocation, String relayState, Credential credentials, IRequest pendingReq) + throws MessageEncodingException, SecurityException, PVP2Exception; + + /** + * Encoder SAML Response + * @param req The http request + * @param resp The http response + * @param response The SAML2 repsonse object + * @param targetLocation URL, where the request should be transmit + * @param relayState token for session handling + * @param credentials Credential to sign the response object + * @param pendingReq Internal MOA-ID request object that contains session-state informations but never null + * @throws MessageEncodingException + * @throws SecurityException + */ + public void encodeRespone(HttpServletRequest req, + HttpServletResponse resp, StatusResponseType response, String targetLocation, String relayState, Credential credentials, IRequest pendingReq) + throws MessageEncodingException, SecurityException, PVP2Exception; +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/message/InboundMessageInterface.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/message/InboundMessageInterface.java new file mode 100644 index 00000000..00edb1bf --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/message/InboundMessageInterface.java @@ -0,0 +1,18 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.api.message; + +import org.w3c.dom.Element; + +/** + * @author tlenz + * + */ +public interface InboundMessageInterface { + + public String getRelayState(); + public String getEntityID(); + public boolean isVerified(); + public Element getInboundMessage(); + +} 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 new file mode 100644 index 00000000..218e5171 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IPVPMetadataBuilderConfiguration.java @@ -0,0 +1,218 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.api.metadata; + +import java.util.List; + +import org.opensaml.saml2.core.Attribute; +import org.opensaml.saml2.metadata.ContactPerson; +import org.opensaml.saml2.metadata.Organization; +import org.opensaml.saml2.metadata.RequestedAttribute; +import org.opensaml.xml.security.credential.Credential; + +import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; + +/** + * @author tlenz + * + */ +public interface IPVPMetadataBuilderConfiguration { + + + /** + * Defines a unique name for this PVP Service-provider, which is used for logging + * + * @return + */ + public String getSPNameForLogging(); + + /** + * Set metadata valid area + * + * @return valid until in hours [h] + */ + public int getMetadataValidUntil(); + + /** + * Build a SAML2 Entities element as metadata root element + * + * @return true, if the metadata should start with entities element + */ + public boolean buildEntitiesDescriptorAsRootElement(); + + /** + * + * + * @return true, if an IDP SSO-descriptor element should be generated + */ + public boolean buildIDPSSODescriptor(); + + /** + * + * + * @return true, if an SP SSO-descriptor element should be generated + */ + public boolean buildSPSSODescriptor(); + + /** + * Set the PVP entityID for this SAML2 metadata. + * The entityID must be an URL and must be start with the public-URL prefix of the server + * + * @return PVP entityID postfix as String + */ + public String getEntityID(); + + /** + * Set a friendlyName for this PVP entity + * + * @return + */ + public String getEntityFriendlyName(); + + /** + * Set the contact information for this metadata entity + * + * @return + */ + public List<ContactPerson> getContactPersonInformation(); + + /** + * Set organisation information for this metadata entity + * + * @return + */ + public Organization getOrgansiationInformation(); + + + /** + * Set the credential for metadata signing + * + * @return + * @throws CredentialsNotAvailableException + */ + public Credential getMetadataSigningCredentials() throws CredentialsNotAvailableException; + + /** + * Set the credential for request/response signing + * IDP metadata: this credential is used for SAML2 response signing + * SP metadata: this credential is used for SAML2 response signing + * + * @return + * @throws CredentialsNotAvailableException + */ + public Credential getRequestorResponseSigningCredentials() throws CredentialsNotAvailableException; + + /** + * Set the credential for response encryption + * + * @return + * @throws CredentialsNotAvailableException + */ + public Credential getEncryptionCredentials() throws CredentialsNotAvailableException; + + /** + * Set the IDP Post-Binding URL for WebSSO + * + * @return + */ + public String getIDPWebSSOPostBindingURL(); + + /** + * Set the IDP Redirect-Binding URL for WebSSO + * + * @return + */ + public String getIDPWebSSORedirectBindingURL(); + + /** + * Set the IDP Post-Binding URL for Single LogOut + * + * @return + */ + public String getIDPSLOPostBindingURL(); + + /** + * Set the IDP Redirect-Binding URL for Single LogOut + * + * @return + */ + public String getIDPSLORedirectBindingURL(); + + /** + * Set the SP Post-Binding URL for for the Assertion-Consumer Service + * + * @return + */ + public String getSPAssertionConsumerServicePostBindingURL(); + + /** + * Set the SP Redirect-Binding URL for the Assertion-Consumer Service + * + * @return + */ + public String getSPAssertionConsumerServiceRedirectBindingURL(); + + /** + * Set the SP Post-Binding URL for Single LogOut + * + * @return + */ + public String getSPSLOPostBindingURL(); + + /** + * Set the SP Redirect-Binding URL for Single LogOut + * + * @return + */ + public String getSPSLORedirectBindingURL(); + + /** + * Set the SP SOAP-Binding URL for Single LogOut + * + * @return + */ + public String getSPSLOSOAPBindingURL(); + + + /** + * Set all SAML2 attributes which could be provided by this IDP + * + * @return + */ + public List<Attribute> getIDPPossibleAttributes(); + + /** + * Set all nameID types which could be provided by this IDP + * + * @return a List of SAML2 nameID types + */ + public List<String> getIDPPossibleNameITTypes(); + + /** + * Set all SAML2 attributes which are required by the SP + * + * @return + */ + public List<RequestedAttribute> getSPRequiredAttributes(); + + /** + * Set all nameID types which allowed from the SP + * + * @return a List of SAML2 nameID types + */ + public List<String> getSPAllowedNameITTypes(); + + /** + * Set the 'wantAssertionSigned' attribute in SP metadata + * + * @return + */ + public boolean wantAssertionSigned(); + + /** + * Set the 'wantAuthnRequestSigned' attribute + * + * @return + */ + public boolean wantAuthnRequestSigned(); +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IPVPMetadataConfigurationFactory.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IPVPMetadataConfigurationFactory.java new file mode 100644 index 00000000..7492c0ff --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IPVPMetadataConfigurationFactory.java @@ -0,0 +1,11 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.api.metadata; + +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider; + +public interface IPVPMetadataConfigurationFactory { + + public IPVPMetadataBuilderConfiguration generateMetadataBuilderConfiguration(String authURL, AbstractCredentialProvider pvpIDPCredentials); + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IPVPMetadataProvider.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IPVPMetadataProvider.java new file mode 100644 index 00000000..4c721d45 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IPVPMetadataProvider.java @@ -0,0 +1,37 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.api.metadata; + +import java.util.List; + +import javax.xml.namespace.QName; + +import org.opensaml.saml2.metadata.EntitiesDescriptor; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.RoleDescriptor; +import org.opensaml.saml2.metadata.provider.MetadataFilter; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.xml.XMLObject; + +public interface IPVPMetadataProvider extends MetadataProvider { + + boolean requireValidMetadata(); + + void setRequireValidMetadata(boolean requireValidMetadata); + + MetadataFilter getMetadataFilter(); + + void setMetadataFilter(MetadataFilter newFilter) throws MetadataProviderException; + + XMLObject getMetadata() throws MetadataProviderException; + + EntitiesDescriptor getEntitiesDescriptor(String entitiesID) throws MetadataProviderException; + + EntityDescriptor getEntityDescriptor(String entityID) throws MetadataProviderException; + + List<RoleDescriptor> getRole(String entityID, QName roleName) throws MetadataProviderException; + + RoleDescriptor getRole(String entityID, QName roleName, String supportedProtocol) throws MetadataProviderException; + +}
\ No newline at end of file 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 new file mode 100644 index 00000000..07321e0c --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/metadata/IRefreshableMetadataProvider.java @@ -0,0 +1,18 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.api.metadata; + +/** + * @author tlenz + * + */ +public interface IRefreshableMetadataProvider { + + /** + * Refresh a entity or load a entity in a metadata provider + * + * @param entityID + * @return true, if refresh is success, otherwise false + */ + public boolean refreshMetadataProvider(String entityID); +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/validation/ISAMLValidator.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/validation/ISAMLValidator.java new file mode 100644 index 00000000..a13a0bac --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/api/validation/ISAMLValidator.java @@ -0,0 +1,11 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.api.validation; + +import org.opensaml.saml2.core.RequestAbstractType; + +import at.gv.egiz.eaaf.core.exceptions.EAAFException; + +public interface ISAMLValidator { + public void validateRequest(RequestAbstractType request) throws EAAFException; +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/AttributQueryException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/AttributQueryException.java new file mode 100644 index 00000000..5388d446 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/AttributQueryException.java @@ -0,0 +1,24 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +/** + * @author tlenz + * + */ +public class AttributQueryException extends PVP2Exception { + + /** + * + */ + private static final long serialVersionUID = -4302422507173728748L; + + public AttributQueryException(String messageId, Object[] parameters) { + super(messageId, parameters); + } + + public AttributQueryException(String messageId, Object[] parameters, Throwable e) { + super(messageId, parameters, e); + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/BindingNotSupportedException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/BindingNotSupportedException.java new file mode 100644 index 00000000..e230d490 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/BindingNotSupportedException.java @@ -0,0 +1,21 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +import org.opensaml.saml2.core.StatusCode; + +public class BindingNotSupportedException extends PVP2Exception { + + public BindingNotSupportedException(String binding) { + super("pvp2.11", new Object[] {binding}); + this.statusCodeValue = StatusCode.UNSUPPORTED_BINDING_URI; + } + + /** + * + */ + private static final long serialVersionUID = -7227603941387879360L; + + + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/CredentialsNotAvailableException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/CredentialsNotAvailableException.java new file mode 100644 index 00000000..f477b67b --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/CredentialsNotAvailableException.java @@ -0,0 +1,24 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +import at.gv.egiz.eaaf.core.exceptions.EAAFException; + +public class CredentialsNotAvailableException extends EAAFException { + + public CredentialsNotAvailableException(String messageId, + Object[] parameters) { + super(messageId, parameters, messageId); + } + + public CredentialsNotAvailableException(String messageId, + Object[] parameters, Throwable e) { + super(messageId, parameters, e.getMessage(), e); + } + + /** + * + */ + private static final long serialVersionUID = -2564476345552842599L; + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/InvalidDateFormatException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/InvalidDateFormatException.java new file mode 100644 index 00000000..1ea49a49 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/InvalidDateFormatException.java @@ -0,0 +1,19 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +import org.opensaml.saml2.core.StatusCode; + +public class InvalidDateFormatException extends PVP2Exception { + + public InvalidDateFormatException() { + super("pvp2.02", null); + this.statusCodeValue = StatusCode.REQUESTER_URI; + } + + /** + * + */ + private static final long serialVersionUID = -6867976890237846085L; + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/InvalidPVPRequestException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/InvalidPVPRequestException.java new file mode 100644 index 00000000..31050425 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/InvalidPVPRequestException.java @@ -0,0 +1,16 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +public class InvalidPVPRequestException extends PVP2Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public InvalidPVPRequestException(String messageId, Object[] parameters) { + super(messageId, parameters); + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/NameIDFormatNotSupportedException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/NameIDFormatNotSupportedException.java new file mode 100644 index 00000000..140ef179 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/NameIDFormatNotSupportedException.java @@ -0,0 +1,22 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +import org.opensaml.saml2.core.StatusCode; + +import at.gv.egiz.eaaf.core.exceptions.AuthnRequestValidatorException; + +public class NameIDFormatNotSupportedException extends AuthnRequestValidatorException { + + public NameIDFormatNotSupportedException(String nameIDFormat) { + super("pvp2.12", new Object[] {nameIDFormat}, "NameID format not supported"); + statusCodeValue = StatusCode.INVALID_NAMEID_POLICY_URI; + + } + + /** + * + */ + private static final long serialVersionUID = -2270762519437873336L; + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/NoMetadataInformationException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/NoMetadataInformationException.java new file mode 100644 index 00000000..bf528e5c --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/NoMetadataInformationException.java @@ -0,0 +1,19 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +import org.opensaml.saml2.core.StatusCode; + +public class NoMetadataInformationException extends PVP2Exception { + + public NoMetadataInformationException() { + super("pvp2.15", null); + this.statusCodeValue = StatusCode.UNKNOWN_PRINCIPAL_URI; + } + + /** + * + */ + private static final long serialVersionUID = -4608068445208032193L; + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/PVP2Exception.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/PVP2Exception.java new file mode 100644 index 00000000..889bda2e --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/PVP2Exception.java @@ -0,0 +1,42 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +import org.opensaml.saml2.core.StatusCode; + +import at.gv.egiz.eaaf.core.exceptions.EAAFException; + +public abstract class PVP2Exception extends EAAFException { + //TODO:!!!!! + + protected String statusCodeValue = StatusCode.RESPONDER_URI; + protected String statusMessageValue = null; + + public PVP2Exception(String messageId, Object[] parameters, + Throwable wrapped) { + super(messageId, parameters, wrapped.getMessage(), wrapped); + this.statusMessageValue = this.getMessage(); + } + + public PVP2Exception(String messageId, Object[] parameters) { + super(messageId, parameters, messageId); + this.statusMessageValue = this.getMessage(); + } + + + public String getStatusCodeValue() { + return (this.statusCodeValue); + } + + public String getStatusMessageValue() { + return (this.statusMessageValue); + } + + /** + * + */ + private static final long serialVersionUID = 7669537952484421069L; + + + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/PVP2MetadataException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/PVP2MetadataException.java new file mode 100644 index 00000000..f255a7e4 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/PVP2MetadataException.java @@ -0,0 +1,17 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +public class PVP2MetadataException extends PVP2Exception { + + private static final long serialVersionUID = 1L; + + public PVP2MetadataException(String messageId, Object[] parameters) { + super(messageId, parameters); + } + + public PVP2MetadataException(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/QAANotAllowedException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/QAANotAllowedException.java new file mode 100644 index 00000000..8b4e74d5 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/QAANotAllowedException.java @@ -0,0 +1,20 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +import org.opensaml.saml2.core.StatusCode; + + +public class QAANotAllowedException extends PVP2Exception { + + public QAANotAllowedException(String qaa_auth, String qaa_request) { + super("pvp2.17", new Object[] {qaa_auth, qaa_request}); + this.statusCodeValue = StatusCode.REQUESTER_URI; + } + + /** + * + */ + private static final long serialVersionUID = -3964192953884089323L; + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/QAANotSupportedException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/QAANotSupportedException.java new file mode 100644 index 00000000..9eb44a61 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/QAANotSupportedException.java @@ -0,0 +1,20 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +import org.opensaml.saml2.core.StatusCode; + + +public class QAANotSupportedException extends PVP2Exception { + + public QAANotSupportedException(String qaa) { + super("pvp2.05", new Object[] {qaa}); + this.statusCodeValue = StatusCode.REQUESTER_URI; + } + + /** + * + */ + private static final long serialVersionUID = -3964192953884089323L; + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SchemaValidationException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SchemaValidationException.java new file mode 100644 index 00000000..de728f84 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SchemaValidationException.java @@ -0,0 +1,32 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +/** + * @author tlenz + * + */ +public class SchemaValidationException extends PVP2Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * @param messageId + * @param parameters + */ + public SchemaValidationException(String messageId, Object[] parameters) { + super(messageId, parameters); + } + + /** + * @param messageId + * @param parameters + */ + public SchemaValidationException(String messageId, Object[] parameters, Throwable e) { + super(messageId, parameters, e); + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SignatureValidationException.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SignatureValidationException.java new file mode 100644 index 00000000..77d32f10 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/exception/SignatureValidationException.java @@ -0,0 +1,38 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.exception; + +import org.opensaml.saml2.metadata.provider.FilterException; + +/** + * @author tlenz + * + */ +public class SignatureValidationException extends FilterException { + + /** + * @param string + */ + public SignatureValidationException(String string) { + super(string); + + } + + /** + * @param e + */ + public SignatureValidationException(Exception e) { + super(e); + } + + /** + * @param string + * @param object + */ + public SignatureValidationException(String string, Exception e) { + super(string, e); + } + + private static final long serialVersionUID = 1L; + +} 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 new file mode 100644 index 00000000..4bcbed19 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/PostBinding.java @@ -0,0 +1,211 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.binding; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.common.SAMLObject; +import org.opensaml.common.binding.BasicSAMLMessageContext; +import org.opensaml.common.binding.decoding.URIComparator; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.binding.decoding.HTTPPostDecoder; +import org.opensaml.saml2.core.RequestAbstractType; +import org.opensaml.saml2.core.StatusResponseType; +import org.opensaml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.opensaml.saml2.metadata.SingleSignOnService; +import org.opensaml.saml2.metadata.impl.SingleSignOnServiceBuilder; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.ws.message.decoder.MessageDecodingException; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.ws.security.SecurityPolicyResolver; +import org.opensaml.ws.security.provider.BasicSecurityPolicy; +import org.opensaml.ws.security.provider.StaticSecurityPolicyResolver; +import org.opensaml.ws.transport.http.HttpServletRequestAdapter; +import org.opensaml.ws.transport.http.HttpServletResponseAdapter; +import org.opensaml.xml.parse.BasicParserPool; +import org.opensaml.xml.security.SecurityException; +import org.opensaml.xml.security.credential.Credential; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.gui.IGUIBuilderConfiguration; +import at.gv.egiz.eaaf.core.api.gui.IGUIBuilderConfigurationFactory; +import at.gv.egiz.eaaf.core.api.gui.IGUIFormBuilder; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.impl.gui.velocity.VelocityProvider; +import at.gv.egiz.eaaf.modules.pvp2.PVPConstants; +import at.gv.egiz.eaaf.modules.pvp2.api.binding.IDecoder; +import at.gv.egiz.eaaf.modules.pvp2.api.binding.IEncoder; +import at.gv.egiz.eaaf.modules.pvp2.api.message.InboundMessageInterface; +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.HTTPPostEncoderWithOwnTemplate; +import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.initialize.EAAFDefaultSAML2Bootstrap; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.TrustEngineFactory; +import at.gv.egiz.eaaf.modules.pvp2.impl.verification.PVPSignedRequestPolicyRule; + +@Service("PVPPOSTBinding") +public class PostBinding implements IDecoder, IEncoder { + private static final Logger log = LoggerFactory.getLogger(PostBinding.class); + + @Autowired(required=true) IConfiguration authConfig; + @Autowired(required=true) IGUIFormBuilder guiBuilder; + @Autowired(required=true) IGUIBuilderConfigurationFactory guiConfigFactory; + + public void encodeRequest(HttpServletRequest req, HttpServletResponse resp, + RequestAbstractType request, String targetLocation, String relayState, Credential credentials, IRequest pendingReq) + throws MessageEncodingException, SecurityException { + + try { + //load default PVP security configurations + EAAFDefaultSAML2Bootstrap.initializeDefaultPVPConfiguration(); + + //initialize POST binding encoder with template decoration + IGUIBuilderConfiguration guiConfig = guiConfigFactory.getSPSpecificSAML2PostConfiguration( + pendingReq, + "pvp_postbinding_template.html", + authConfig.getConfigurationRootDirectory()); + + HTTPPostEncoderWithOwnTemplate encoder = new HTTPPostEncoderWithOwnTemplate(guiConfig, guiBuilder, + VelocityProvider.getClassPathVelocityEngine()); + + //set OpenSAML2 process parameter into binding context dao + HttpServletResponseAdapter responseAdapter = new HttpServletResponseAdapter( + resp, true); + BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject> context = new BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject>(); + SingleSignOnService service = new SingleSignOnServiceBuilder().buildObject(); + service.setBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); + service.setLocation(targetLocation);; + + context.setOutboundSAMLMessageSigningCredential(credentials); + context.setPeerEntityEndpoint(service); + context.setOutboundSAMLMessage(request); + context.setOutboundMessageTransport(responseAdapter); + context.setRelayState(relayState); + + encoder.encode(context); + + } catch (Exception e) { + e.printStackTrace(); + throw new SecurityException(e); + } + } + + public void encodeRespone(HttpServletRequest req, HttpServletResponse resp, + StatusResponseType response, String targetLocation, String relayState, Credential credentials, IRequest pendingReq) + throws MessageEncodingException, SecurityException { + + try { + //load default PVP security configurations + EAAFDefaultSAML2Bootstrap.initializeDefaultPVPConfiguration(); + + log.debug("create SAML POSTBinding response"); + + //initialize POST binding encoder with template decoration + IGUIBuilderConfiguration guiConfig = guiConfigFactory.getSPSpecificSAML2PostConfiguration( + pendingReq, + "pvp_postbinding_template.html", + authConfig.getConfigurationRootDirectory()); + HTTPPostEncoderWithOwnTemplate encoder = new HTTPPostEncoderWithOwnTemplate(guiConfig, guiBuilder, + VelocityProvider.getClassPathVelocityEngine()); + + //set OpenSAML2 process parameter into binding context dao + HttpServletResponseAdapter responseAdapter = new HttpServletResponseAdapter( + resp, true); + BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject> context = new BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject>(); + SingleSignOnService service = new SingleSignOnServiceBuilder() + .buildObject(); + service.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); + service.setLocation(targetLocation); + context.setOutboundSAMLMessageSigningCredential(credentials); + context.setPeerEntityEndpoint(service); + // context.setOutboundMessage(authReq); + context.setOutboundSAMLMessage(response); + context.setOutboundMessageTransport(responseAdapter); + context.setRelayState(relayState); + + encoder.encode(context); + + } catch (Exception e) { + e.printStackTrace(); + throw new SecurityException(e); + } + } + + public InboundMessageInterface decode(HttpServletRequest req, + HttpServletResponse resp, MetadataProvider metadataProvider, boolean isSPEndPoint, URIComparator comparator) throws MessageDecodingException, + SecurityException { + + HTTPPostDecoder decode = new HTTPPostDecoder(new BasicParserPool()); + BasicSAMLMessageContext<SAMLObject, ?, ?> messageContext = new BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject>(); + 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); + + //set security policy context + BasicSecurityPolicy policy = new BasicSecurityPolicy(); + policy.getPolicyRules().add( + new PVPSignedRequestPolicyRule(metadataProvider, + TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider), + messageContext.getPeerEntityRole())); + SecurityPolicyResolver secResolver = new StaticSecurityPolicyResolver(policy); + messageContext.setSecurityPolicyResolver(secResolver); + + decode.decode(messageContext); + + InboundMessage msg = null; + if (messageContext.getInboundMessage() instanceof RequestAbstractType) { + RequestAbstractType inboundMessage = (RequestAbstractType) messageContext + .getInboundMessage(); + msg = new PVPSProfileRequest(inboundMessage, getSAML2BindingName()); + msg.setEntityID(inboundMessage.getIssuer().getValue()); + + } else if (messageContext.getInboundMessage() instanceof StatusResponseType){ + StatusResponseType inboundMessage = (StatusResponseType) messageContext.getInboundMessage(); + msg = new PVPSProfileResponse(inboundMessage); + msg.setEntityID(inboundMessage.getIssuer().getValue()); + + } else + //create empty container if request type is unknown + msg = new InboundMessage(); + + if (messageContext.getPeerEntityMetadata() != null) + msg.setEntityID(messageContext.getPeerEntityMetadata().getEntityID()); + + else { + if (StringUtils.isEmpty(msg.getEntityID())) + log.info("No Metadata found for OA with EntityID " + messageContext.getInboundMessageIssuer()); + } + + + msg.setVerified(true); + msg.setRelayState(messageContext.getRelayState()); + + return msg; + } + + public boolean handleDecode(String action, HttpServletRequest req) { + return (req.getMethod().equals("POST") && action.equals(PVPConstants.POST)); + } + + public String getSAML2BindingName() { + return SAMLConstants.SAML2_POST_BINDING_URI; + } +} 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 new file mode 100644 index 00000000..949cbe57 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/RedirectBinding.java @@ -0,0 +1,215 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.binding; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.common.SAMLObject; +import org.opensaml.common.binding.BasicSAMLMessageContext; +import org.opensaml.common.binding.decoding.URIComparator; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.binding.decoding.HTTPRedirectDeflateDecoder; +import org.opensaml.saml2.binding.encoding.HTTPRedirectDeflateEncoder; +import org.opensaml.saml2.binding.security.SAML2HTTPRedirectDeflateSignatureRule; +import org.opensaml.saml2.core.RequestAbstractType; +import org.opensaml.saml2.core.StatusResponseType; +import org.opensaml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.opensaml.saml2.metadata.SingleSignOnService; +import org.opensaml.saml2.metadata.impl.SingleSignOnServiceBuilder; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.ws.message.decoder.MessageDecodingException; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.ws.security.SecurityPolicyResolver; +import org.opensaml.ws.security.provider.BasicSecurityPolicy; +import org.opensaml.ws.security.provider.StaticSecurityPolicyResolver; +import org.opensaml.ws.transport.http.HttpServletRequestAdapter; +import org.opensaml.ws.transport.http.HttpServletResponseAdapter; +import org.opensaml.xml.parse.BasicParserPool; +import org.opensaml.xml.security.SecurityException; +import org.opensaml.xml.security.credential.Credential; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.modules.pvp2.PVPConstants; +import at.gv.egiz.eaaf.modules.pvp2.api.binding.IDecoder; +import at.gv.egiz.eaaf.modules.pvp2.api.binding.IEncoder; +import at.gv.egiz.eaaf.modules.pvp2.api.message.InboundMessageInterface; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IRefreshableMetadataProvider; +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.initialize.EAAFDefaultSAML2Bootstrap; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.TrustEngineFactory; +import at.gv.egiz.eaaf.modules.pvp2.impl.verification.PVPAuthRequestSignedRole; + +@Service("PVPRedirectBinding") +public class RedirectBinding implements IDecoder, IEncoder { + + private static final Logger log = LoggerFactory.getLogger(RedirectBinding.class); + + public void encodeRequest(HttpServletRequest req, HttpServletResponse resp, + RequestAbstractType request, String targetLocation, String relayState, Credential credentials, IRequest pendingReq) + throws MessageEncodingException, SecurityException { + + //load default PVP security configurations + EAAFDefaultSAML2Bootstrap.initializeDefaultPVPConfiguration(); + + log.debug("create SAML RedirectBinding response"); + + HTTPRedirectDeflateEncoder encoder = new HTTPRedirectDeflateEncoder(); + HttpServletResponseAdapter responseAdapter = new HttpServletResponseAdapter( + resp, true); + BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject> context = new BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject>(); + SingleSignOnService service = new SingleSignOnServiceBuilder() + .buildObject(); + service.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); + service.setLocation(targetLocation); + context.setOutboundSAMLMessageSigningCredential(credentials); + context.setPeerEntityEndpoint(service); + context.setOutboundSAMLMessage(request); + context.setOutboundMessageTransport(responseAdapter); + context.setRelayState(relayState); + + encoder.encode(context); + } + + public void encodeRespone(HttpServletRequest req, HttpServletResponse resp, + StatusResponseType response, String targetLocation, String relayState, + Credential credentials, IRequest pendingReq) throws MessageEncodingException, SecurityException { + + //load default PVP security configurations + EAAFDefaultSAML2Bootstrap.initializeDefaultPVPConfiguration(); + + log.debug("create SAML RedirectBinding response"); + + HTTPRedirectDeflateEncoder encoder = new HTTPRedirectDeflateEncoder(); + HttpServletResponseAdapter responseAdapter = new HttpServletResponseAdapter( + resp, true); + BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject> context = new BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject>(); + SingleSignOnService service = new SingleSignOnServiceBuilder() + .buildObject(); + service.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); + service.setLocation(targetLocation); + context.setOutboundSAMLMessageSigningCredential(credentials); + context.setPeerEntityEndpoint(service); + context.setOutboundSAMLMessage(response); + context.setOutboundMessageTransport(responseAdapter); + context.setRelayState(relayState); + + encoder.encode(context); + + } + + public InboundMessageInterface decode(HttpServletRequest req, + HttpServletResponse resp, MetadataProvider metadataProvider, boolean isSPEndPoint, URIComparator comparator) throws MessageDecodingException, + SecurityException { + + HTTPRedirectDeflateDecoder decode = new HTTPRedirectDeflateDecoder( + new BasicParserPool()); + + BasicSAMLMessageContext<SAMLObject, ?, ?> messageContext = new BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject>(); + 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); + + SAML2HTTPRedirectDeflateSignatureRule signatureRule = new SAML2HTTPRedirectDeflateSignatureRule( + TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider)); + PVPAuthRequestSignedRole signedRole = new PVPAuthRequestSignedRole(); + BasicSecurityPolicy policy = new BasicSecurityPolicy(); + policy.getPolicyRules().add(signedRole); + policy.getPolicyRules().add(signatureRule); + 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); + + try { + decode.decode(messageContext); + + //check signature + signatureRule.evaluate(messageContext); + + } catch (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) { + RequestAbstractType inboundMessage = (RequestAbstractType) messageContext + .getInboundMessage(); + msg = new PVPSProfileRequest(inboundMessage, getSAML2BindingName()); + + + } else if (messageContext.getInboundMessage() instanceof StatusResponseType){ + 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; + } + + public boolean handleDecode(String action, HttpServletRequest req) { + return ((action.equals(PVPConstants.REDIRECT) || action.equals(PVPConstants.SINGLELOGOUT)) + && req.getMethod().equals("GET")); + } + + public String getSAML2BindingName() { + return SAMLConstants.SAML2_REDIRECT_BINDING_URI; + } +} 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 new file mode 100644 index 00000000..2c2e0d77 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/binding/SoapBinding.java @@ -0,0 +1,148 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.binding; + +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.common.SAMLObject; +import org.opensaml.common.binding.BasicSAMLMessageContext; +import org.opensaml.common.binding.decoding.URIComparator; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.binding.encoding.HTTPSOAP11Encoder; +import org.opensaml.saml2.core.RequestAbstractType; +import org.opensaml.saml2.core.StatusResponseType; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.ws.message.decoder.MessageDecodingException; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.ws.soap.soap11.Envelope; +import org.opensaml.ws.soap.soap11.decoder.http.HTTPSOAP11Decoder; +import org.opensaml.ws.transport.http.HttpServletRequestAdapter; +import org.opensaml.ws.transport.http.HttpServletResponseAdapter; +import org.opensaml.xml.XMLObject; +import org.opensaml.xml.parse.BasicParserPool; +import org.opensaml.xml.security.SecurityException; +import org.opensaml.xml.security.credential.Credential; +import org.opensaml.xml.signature.SignableXMLObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.modules.pvp2.PVPConstants; +import at.gv.egiz.eaaf.modules.pvp2.api.binding.IDecoder; +import at.gv.egiz.eaaf.modules.pvp2.api.binding.IEncoder; +import at.gv.egiz.eaaf.modules.pvp2.api.message.InboundMessageInterface; +import at.gv.egiz.eaaf.modules.pvp2.exception.AttributQueryException; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.PVPSProfileRequest; +import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.initialize.EAAFDefaultSAML2Bootstrap; + +@Service("PVPSOAPBinding") +public class SoapBinding implements IDecoder, IEncoder { + + private static final Logger log = LoggerFactory.getLogger(SoapBinding.class); + public InboundMessageInterface decode(HttpServletRequest req, + HttpServletResponse resp, MetadataProvider metadataProvider, boolean isSPEndPoint, URIComparator comparator) throws MessageDecodingException, + SecurityException, PVP2Exception { + HTTPSOAP11Decoder soapDecoder = new HTTPSOAP11Decoder(new BasicParserPool()); + BasicSAMLMessageContext<SAMLObject, ?, ?> messageContext = + new BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject>(); + messageContext + .setInboundMessageTransport(new HttpServletRequestAdapter( + req)); + messageContext.setMetadataProvider(metadataProvider); + + //TODO: update in a futher version: + // requires a special SignedSOAPRequestPolicyRole because + // messageContext.getInboundMessage() is not directly signed + + //set security context +// BasicSecurityPolicy policy = new BasicSecurityPolicy(); +// policy.getPolicyRules().add( +// new MOAPVPSignedRequestPolicyRule( +// TrustEngineFactory.getSignatureKnownKeysTrustEngine(), +// SPSSODescriptor.DEFAULT_ELEMENT_NAME)); +// SecurityPolicyResolver resolver = new StaticSecurityPolicyResolver( +// policy); +// messageContext.setSecurityPolicyResolver(resolver); + + //decode message + soapDecoder.decode(messageContext); + + Envelope inboundMessage = (Envelope) messageContext + .getInboundMessage(); + + if (inboundMessage.getBody() != null) { + List<XMLObject> xmlElemList = inboundMessage.getBody().getUnknownXMLObjects(); + + if (!xmlElemList.isEmpty()) { + SignableXMLObject attrReq = (SignableXMLObject) xmlElemList.get(0); + PVPSProfileRequest request = new PVPSProfileRequest(attrReq, getSAML2BindingName()); + + if (messageContext.getPeerEntityMetadata() != null) + request.setEntityID(messageContext.getPeerEntityMetadata().getEntityID()); + + else if (attrReq instanceof RequestAbstractType) { + 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 (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); + } + + public boolean handleDecode(String action, HttpServletRequest req) { + return (req.getMethod().equals("POST") && + (action.equals(PVPConstants.SOAP) || action.equals(PVPConstants.ATTRIBUTEQUERY))); + } + + public void encodeRequest(HttpServletRequest req, HttpServletResponse resp, + RequestAbstractType request, String targetLocation, String relayState, Credential credentials, IRequest pendingReq) + throws MessageEncodingException, SecurityException, PVP2Exception { + + } + + public void encodeRespone(HttpServletRequest req, HttpServletResponse resp, + StatusResponseType response, String targetLocation, String relayState, Credential credentials, IRequest pendingReq) + throws MessageEncodingException, SecurityException, PVP2Exception { + + //load default PVP security configurations + EAAFDefaultSAML2Bootstrap.initializeDefaultPVPConfiguration(); + + HTTPSOAP11Encoder encoder = new HTTPSOAP11Encoder(); + HttpServletResponseAdapter responseAdapter = new HttpServletResponseAdapter( + resp, true); + BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject> context = new BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject>(); + context.setOutboundSAMLMessageSigningCredential(credentials); + context.setOutboundSAMLMessage(response); + context.setOutboundMessageTransport(responseAdapter); + + encoder.encode(context); + + } + + public String getSAML2BindingName() { + return SAMLConstants.SAML2_SOAP11_BINDING_URI; + } + +} 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 new file mode 100644 index 00000000..abc47e81 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/CitizenTokenBuilder.java @@ -0,0 +1,97 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.builder; + +import org.opensaml.saml2.core.Attribute; +import org.opensaml.saml2.core.AttributeValue; +import org.opensaml.xml.Configuration; +import org.opensaml.xml.XMLObject; +import org.opensaml.xml.schema.XSInteger; +import org.opensaml.xml.schema.XSString; +import org.opensaml.xml.schema.impl.XSIntegerBuilder; +import org.opensaml.xml.schema.impl.XSStringBuilder; + +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; + +public class CitizenTokenBuilder { + + public static XMLObject buildAttributeStringValue(String value) { + XSStringBuilder stringBuilder = (XSStringBuilder) Configuration.getBuilderFactory().getBuilder(XSString.TYPE_NAME); + XSString stringValue = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); + stringValue.setValue(value); + return stringValue; + } + + public static XMLObject buildAttributeIntegerValue(int value) { + XSIntegerBuilder integerBuilder = (XSIntegerBuilder) Configuration.getBuilderFactory().getBuilder(XSInteger.TYPE_NAME); + XSInteger integerValue = integerBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSInteger.TYPE_NAME); + integerValue.setValue(value); + return integerValue; + } + + public static Attribute buildStringAttribute(String friendlyName, + String name, String value) { + Attribute attribute = + SAML2Utils.createSAMLObject(Attribute.class); + attribute.setFriendlyName(friendlyName); + attribute.setName(name); + attribute.getAttributeValues().add(buildAttributeStringValue(value)); + return attribute; + } + + public static Attribute buildIntegerAttribute(String friendlyName, + String name, int value) { + Attribute attribute = + SAML2Utils.createSAMLObject(Attribute.class); + attribute.setFriendlyName(friendlyName); + attribute.setName(name); + attribute.getAttributeValues().add(buildAttributeIntegerValue(value)); + return attribute; + } + + public static Attribute buildPVPVersion(String value) { + return buildStringAttribute("PVP-VERSION", + "urn:oid:1.2.40.0.10.2.1.1.261.10", value); + } + + public static Attribute buildSecClass(int value) { + return buildIntegerAttribute("SECCLASS", + "", value); + } + + public static Attribute buildPrincipalName(String value) { + return buildStringAttribute("PRINCIPAL-NAME", + "urn:oid:1.2.40.0.10.2.1.1.261.20", value); + } + + public static Attribute buildGivenName(String value) { + return buildStringAttribute("GIVEN-NAME", + "urn:oid:2.5.4.42", value); + } + + public static Attribute buildBirthday(String value) { + return buildStringAttribute("BIRTHDATE", + "urn:oid:1.2.40.0.10.2.1.1.55", value); + } + + public static Attribute buildBPK(String value) { + return buildStringAttribute("BPK", + "urn:oid:1.2.40.0.10.2.1.1.149", value); + } + + public static Attribute buildEID_CITIZEN_QAALEVEL(int value) { + return buildIntegerAttribute("EID-CITIZEN-QAA-LEVEL", + "urn:oid:1.2.40.0.10.2.1.1.261.94", value); + } + + public static Attribute buildEID_ISSUING_NATION(String value) { + return buildStringAttribute("EID-ISSUING-NATION", + "urn:oid:1.2.40.0.10.2.1.1.261.32", value); + } + + public static Attribute buildEID_SECTOR_FOR_IDENTIFIER(String value) { + return buildStringAttribute("EID-SECTOR-FOR-IDENTIFIER", + "urn:oid:1.2.40.0.10.2.1.1.261.34", value); + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/PVPAttributeBuilder.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/PVPAttributeBuilder.java new file mode 100644 index 00000000..41623f3d --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/PVPAttributeBuilder.java @@ -0,0 +1,186 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.builder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.ServiceLoader; + +import org.opensaml.saml2.core.Attribute; +import org.opensaml.saml2.metadata.RequestedAttribute; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.eaaf.core.api.idp.IAttributeBuilder; +import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator; +import at.gv.egiz.eaaf.core.api.idp.IAuthData; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.exceptions.AttributeBuilderException; +import at.gv.egiz.eaaf.core.exceptions.InvalidDateFormatAttributeException; +import at.gv.egiz.eaaf.core.exceptions.UnavailableAttributeException; +import at.gv.egiz.eaaf.modules.pvp2.exception.InvalidDateFormatException; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; + +public class PVPAttributeBuilder { + + private static final Logger log = LoggerFactory.getLogger(PVPAttributeBuilder.class); + + private static IAttributeGenerator<Attribute> generator = new SamlAttributeGenerator(); + private static HashMap<String, IAttributeBuilder> builders; + + private static ServiceLoader<IAttributeBuilder> attributBuilderLoader = + ServiceLoader.load(IAttributeBuilder.class); + + private static void addBuilder(IAttributeBuilder builder) { + builders.put(builder.getName(), builder); + } + + static { + builders = new HashMap<String, IAttributeBuilder>(); + + log.info("Loading protocol attribut-builder modules:"); + if (attributBuilderLoader != null ) { + Iterator<IAttributeBuilder> moduleLoaderInterator = attributBuilderLoader.iterator(); + while (moduleLoaderInterator.hasNext()) { + try { + IAttributeBuilder modul = moduleLoaderInterator.next(); + log.info("Loading attribut-builder Modul Information: " + modul.getName()); + addBuilder(modul); + + } catch(Throwable e) { + log.error("Check configuration! " + "Some attribute-builder modul" + + " is not a valid IAttributeBuilder", e); + } + } + } + + log.info("Loading attribute-builder modules done"); + + } + + + /** + * Get a specific attribute builder + * + * @param name Attribute-builder friendly name + * + * @return Attribute-builder with this name or null if builder does not exists + */ + public static IAttributeBuilder getAttributeBuilder(String name) { + return builders.get(name); + + } + + public static Attribute buildAttribute(String name, ISPConfiguration oaParam, + IAuthData authData) throws PVP2Exception, AttributeBuilderException { + if (builders.containsKey(name)) { + try { + return builders.get(name).build(oaParam, authData, generator); + } + catch (AttributeBuilderException e) { + if (e instanceof UnavailableAttributeException) { + throw e; + + } else if (e instanceof InvalidDateFormatAttributeException) { + throw new InvalidDateFormatException(); + + } else { + throw new UnavailableAttributeException(name); + + } + } + } + return null; + } + + public static Attribute buildEmptyAttribute(String name) { + if (builders.containsKey(name)) { + return builders.get(name).buildEmpty(generator); + } + return null; + } + + public static Attribute buildAttribute(String name, String value) { + if (builders.containsKey(name)) { + return builders.get(name).buildEmpty(generator); + } + return null; + } + + + + public static List<Attribute> buildSupportedEmptyAttributes() { + List<Attribute> attributes = new ArrayList<Attribute>(); + Iterator<IAttributeBuilder> builderIt = builders.values().iterator(); + while (builderIt.hasNext()) { + IAttributeBuilder builder = builderIt.next(); + Attribute emptyAttribute = builder.buildEmpty(generator); + if (emptyAttribute != null) { + attributes.add(emptyAttribute); + } + } + return attributes; + } + + public static RequestedAttribute buildReqAttribute(String name, String friendlyName, boolean required) { + RequestedAttribute attribute = SAML2Utils.createSAMLObject(RequestedAttribute.class); + attribute.setIsRequired(required); + attribute.setName(name); + attribute.setFriendlyName(friendlyName); + attribute.setNameFormat(Attribute.URI_REFERENCE); + return attribute; + } + + /** + * Build a set of PVP Response-Attributes + * <br><br> + * <b>INFO:</b> If a specific attribute can not be build, a info is logged, but no execpetion is thrown. + * Therefore, the return List must not include all requested attributes. + * + * @param authData AuthenticationData <code>IAuthData</code> which is used to build the attribute values, but never <code>null</code> + * @param reqAttributenName List of PVP attribute names which are requested, but never <code>null</code> + * @return List of PVP attributes, but never <code>null</code> + */ + public static List<Attribute> buildSetOfResponseAttributes(IAuthData authData, + Collection<String> reqAttributenName) { + List<Attribute> attrList = new ArrayList<Attribute>(); + if (reqAttributenName != null) { + Iterator<String> it = reqAttributenName.iterator(); + while (it.hasNext()) { + String reqAttributName = it.next(); + try { + Attribute attr = PVPAttributeBuilder.buildAttribute( + reqAttributName, null, authData); + if (attr == null) { + log.info( + "Attribute generation failed! for " + + reqAttributName); + + } else { + attrList.add(attr); + + } + + } catch (PVP2Exception e) { + log.info( + "Attribute generation failed! for " + + reqAttributName); + + } catch (Exception e) { + log.warn( + "General Attribute generation failed! for " + + reqAttributName, e); + + } + } + } + + return attrList; + } + + +} 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 new file mode 100644 index 00000000..abfac305 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/PVPMetadataBuilder.java @@ -0,0 +1,425 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.builder; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.apache.commons.httpclient.auth.CredentialsNotAvailableException; +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.opensaml.Configuration; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.metadata.AssertionConsumerService; +import org.opensaml.saml2.metadata.AttributeConsumingService; +import org.opensaml.saml2.metadata.ContactPerson; +import org.opensaml.saml2.metadata.EntitiesDescriptor; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.saml2.metadata.KeyDescriptor; +import org.opensaml.saml2.metadata.LocalizedString; +import org.opensaml.saml2.metadata.NameIDFormat; +import org.opensaml.saml2.metadata.Organization; +import org.opensaml.saml2.metadata.RequestedAttribute; +import org.opensaml.saml2.metadata.RoleDescriptor; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.opensaml.saml2.metadata.ServiceName; +import org.opensaml.saml2.metadata.SingleLogoutService; +import org.opensaml.saml2.metadata.SingleSignOnService; +import org.opensaml.xml.io.Marshaller; +import org.opensaml.xml.io.MarshallingException; +import org.opensaml.xml.security.SecurityException; +import org.opensaml.xml.security.SecurityHelper; +import org.opensaml.xml.security.credential.Credential; +import org.opensaml.xml.security.credential.UsageType; +import org.opensaml.xml.security.keyinfo.KeyInfoGenerator; +import org.opensaml.xml.security.x509.X509KeyInfoGeneratorFactory; +import org.opensaml.xml.signature.Signature; +import org.opensaml.xml.signature.SignatureException; +import org.opensaml.xml.signature.Signer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.w3c.dom.Document; + +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPVPMetadataBuilderConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.initialize.EAAFDefaultSAML2Bootstrap; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; + +/** + * @author tlenz + * + */ + +@Service("PVPMetadataBuilder") +public class PVPMetadataBuilder { + + private static final Logger log = LoggerFactory.getLogger(PVPMetadataBuilder.class); + + X509KeyInfoGeneratorFactory keyInfoFactory = null; + + /** + * + */ + public PVPMetadataBuilder() { + keyInfoFactory = new X509KeyInfoGeneratorFactory(); + keyInfoFactory.setEmitEntityIDAsKeyName(true); + keyInfoFactory.setEmitEntityCertificate(true); + + } + + + /** + * + * Build PVP 2.1 conform SAML2 metadata + * + * @param config + * PVPMetadataBuilder configuration + * + * @return PVP metadata as XML String + * @throws SecurityException + * @throws ConfigurationException + * @throws CredentialsNotAvailableException + * @throws TransformerFactoryConfigurationError + * @throws MarshallingException + * @throws TransformerException + * @throws ParserConfigurationException + * @throws IOException + * @throws SignatureException + */ + public String buildPVPMetadata(IPVPMetadataBuilderConfiguration config) throws CredentialsNotAvailableException, EAAFException, SecurityException, TransformerFactoryConfigurationError, MarshallingException, TransformerException, ParserConfigurationException, IOException, SignatureException { + DateTime date = new DateTime(); + EntityDescriptor entityDescriptor = SAML2Utils + .createSAMLObject(EntityDescriptor.class); + + //set entityID + entityDescriptor.setEntityID(config.getEntityID()); + + //set contact and organisation information + List<ContactPerson> contactPersons = config.getContactPersonInformation(); + if (contactPersons != null) + entityDescriptor.getContactPersons().addAll(contactPersons); + + Organization organisation = config.getOrgansiationInformation(); + if (organisation != null) + entityDescriptor.setOrganization(organisation); + + //set IDP metadata + if (config.buildIDPSSODescriptor()) { + RoleDescriptor idpSSODesc = generateIDPMetadata(config); + if (idpSSODesc != null) + entityDescriptor.getRoleDescriptors().add(idpSSODesc); + + } + + //set SP metadata for interfederation + if (config.buildSPSSODescriptor()) { + RoleDescriptor spSSODesc = generateSPMetadata(config); + if (spSSODesc != null) + entityDescriptor.getRoleDescriptors().add(spSSODesc); + + } + + //set metadata signature parameters + Credential metadataSignCred = config.getMetadataSigningCredentials(); + Signature signature = AbstractCredentialProvider.getIDPSignature(metadataSignCred); + SecurityHelper.prepareSignatureParams(signature, metadataSignCred, null, null); + + //initialize XML document builder + DocumentBuilder builder; + DocumentBuilderFactory factory = DocumentBuilderFactory + .newInstance(); + + builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + + + //build entities descriptor + if (config.buildEntitiesDescriptorAsRootElement()) { + EntitiesDescriptor entitiesDescriptor = + SAML2Utils.createSAMLObject(EntitiesDescriptor.class); + entitiesDescriptor.setName(config.getEntityFriendlyName()); + entitiesDescriptor.setID(SAML2Utils.getSecureIdentifier()); + entitiesDescriptor.setValidUntil(date.plusHours(config.getMetadataValidUntil())); + entitiesDescriptor.getEntityDescriptors().add(entityDescriptor); + + //load default PVP security configurations + EAAFDefaultSAML2Bootstrap.initializeDefaultPVPConfiguration(); + entitiesDescriptor.setSignature(signature); + + + //marshall document + Marshaller out = Configuration.getMarshallerFactory() + .getMarshaller(entitiesDescriptor); + out.marshall(entitiesDescriptor, document); + + } else { + entityDescriptor.setValidUntil(date.plusHours(config.getMetadataValidUntil())); + entityDescriptor.setID(SAML2Utils.getSecureIdentifier()); + + entityDescriptor.setSignature(signature); + + + + //marshall document + Marshaller out = Configuration.getMarshallerFactory() + .getMarshaller(entityDescriptor); + out.marshall(entityDescriptor, document); + + } + + //sign metadata + Signer.signObject(signature); + + //transform metadata object to XML string + Transformer transformer = TransformerFactory.newInstance() + .newTransformer(); + + StringWriter sw = new StringWriter(); + StreamResult sr = new StreamResult(sw); + DOMSource source = new DOMSource(document); + transformer.transform(source, sr); + sw.close(); + + return sw.toString(); + } + + + private RoleDescriptor generateSPMetadata(IPVPMetadataBuilderConfiguration config) throws CredentialsNotAvailableException, SecurityException, EAAFException { + SPSSODescriptor spSSODescriptor = SAML2Utils.createSAMLObject(SPSSODescriptor.class); + spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); + spSSODescriptor.setAuthnRequestsSigned(config.wantAuthnRequestSigned()); + spSSODescriptor.setWantAssertionsSigned(config.wantAssertionSigned()); + + KeyInfoGenerator keyInfoGenerator = keyInfoFactory.newInstance(); + + //Set AuthRequest Signing certificate + Credential authcredential = config.getRequestorResponseSigningCredentials(); + if (authcredential == null) { + log.warn("SP Metadata generation FAILED! --> Builder has NO request signing-credential. "); + return null; + + } else { + KeyDescriptor signKeyDescriptor = SAML2Utils + .createSAMLObject(KeyDescriptor.class); + signKeyDescriptor.setUse(UsageType.SIGNING); + signKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(authcredential)); + spSSODescriptor.getKeyDescriptors().add(signKeyDescriptor); + + } + + //Set assertion encryption credentials + Credential authEncCredential = config.getEncryptionCredentials(); + + if (authEncCredential != null) { + KeyDescriptor encryKeyDescriptor = SAML2Utils + .createSAMLObject(KeyDescriptor.class); + encryKeyDescriptor.setUse(UsageType.ENCRYPTION); + encryKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(authEncCredential)); + spSSODescriptor.getKeyDescriptors().add(encryKeyDescriptor); + + } else { + log.warn("No Assertion Encryption-Key defined. This setting is not recommended!"); + + } + + //check nameID formates + if (config.getSPAllowedNameITTypes() == null || config.getSPAllowedNameITTypes().size() == 0) { + log.warn("SP Metadata generation FAILED! --> Builder has NO provideable SAML2 nameIDFormats. "); + return null; + + } else { + for (String format : config.getSPAllowedNameITTypes()) { + NameIDFormat nameIDFormat = SAML2Utils.createSAMLObject(NameIDFormat.class); + nameIDFormat.setFormat(format); + spSSODescriptor.getNameIDFormats().add(nameIDFormat); + + } + } + + + //add POST-Binding assertion consumer services + if (StringUtils.isNotEmpty(config.getSPAssertionConsumerServicePostBindingURL())) { + AssertionConsumerService postassertionConsumerService = SAML2Utils.createSAMLObject(AssertionConsumerService.class); + postassertionConsumerService.setIndex(0); + postassertionConsumerService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); + postassertionConsumerService.setLocation(config.getSPAssertionConsumerServicePostBindingURL()); + postassertionConsumerService.setIsDefault(true); + spSSODescriptor.getAssertionConsumerServices().add(postassertionConsumerService); + + } + + //add POST-Binding assertion consumer services + if (StringUtils.isNotEmpty(config.getSPAssertionConsumerServiceRedirectBindingURL())) { + AssertionConsumerService redirectassertionConsumerService = SAML2Utils.createSAMLObject(AssertionConsumerService.class); + redirectassertionConsumerService.setIndex(1); + redirectassertionConsumerService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); + redirectassertionConsumerService.setLocation(config.getSPAssertionConsumerServiceRedirectBindingURL()); + spSSODescriptor.getAssertionConsumerServices().add(redirectassertionConsumerService); + + } + + //validate WebSSO endpoints + if (spSSODescriptor.getAssertionConsumerServices().size() == 0) { + log.warn("SP Metadata generation FAILED! --> NO SAML2 AssertionConsumerService endpoint found. "); + return null; + + } + + //add POST-Binding SLO descriptor + if (StringUtils.isNotEmpty(config.getSPSLOPostBindingURL())) { + SingleLogoutService postSLOService = SAML2Utils.createSAMLObject(SingleLogoutService.class); + postSLOService.setLocation(config.getSPSLOPostBindingURL()); + postSLOService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); + spSSODescriptor.getSingleLogoutServices().add(postSLOService); + + } + + //add POST-Binding SLO descriptor + if (StringUtils.isNotEmpty(config.getSPSLORedirectBindingURL())) { + SingleLogoutService redirectSLOService = SAML2Utils.createSAMLObject(SingleLogoutService.class); + redirectSLOService.setLocation(config.getSPSLORedirectBindingURL()); + redirectSLOService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); + spSSODescriptor.getSingleLogoutServices().add(redirectSLOService); + + } + + //add POST-Binding SLO descriptor + if (StringUtils.isNotEmpty(config.getSPSLOSOAPBindingURL())) { + SingleLogoutService soapSLOService = SAML2Utils.createSAMLObject(SingleLogoutService.class); + soapSLOService.setLocation(config.getSPSLOSOAPBindingURL()); + soapSLOService.setBinding(SAMLConstants.SAML2_SOAP11_BINDING_URI); + spSSODescriptor.getSingleLogoutServices().add(soapSLOService); + + } + + + //add required attributes + List<RequestedAttribute> reqSPAttr = config.getSPRequiredAttributes(); + AttributeConsumingService attributeService = SAML2Utils.createSAMLObject(AttributeConsumingService.class); + + attributeService.setIndex(0); + attributeService.setIsDefault(true); + ServiceName serviceName = SAML2Utils.createSAMLObject(ServiceName.class); + serviceName.setName(new LocalizedString("Default Service", "en")); + attributeService.getNames().add(serviceName); + + if (reqSPAttr != null && reqSPAttr.size() > 0) { + log.debug("Add " + reqSPAttr.size() + " attributes to SP metadata"); + attributeService.getRequestAttributes().addAll(reqSPAttr); + + } else { + log.debug("SP metadata contains NO requested attributes."); + + } + + spSSODescriptor.getAttributeConsumingServices().add(attributeService); + + return spSSODescriptor; + } + + private IDPSSODescriptor generateIDPMetadata(IPVPMetadataBuilderConfiguration config) throws EAAFException, CredentialsNotAvailableException, SecurityException { + //check response signing credential + Credential responseSignCred = config.getRequestorResponseSigningCredentials(); + if (responseSignCred == null) { + log.warn("IDP Metadata generation FAILED! --> Builder has NO Response signing credential. "); + return null; + + } + + //check nameID formates + if (config.getIDPPossibleNameITTypes() == null || config.getIDPPossibleNameITTypes().size() == 0) { + log.warn("IDP Metadata generation FAILED! --> Builder has NO provideable SAML2 nameIDFormats. "); + return null; + + } + + // build SAML2 IDP-SSO descriptor element + IDPSSODescriptor idpSSODescriptor = SAML2Utils + .createSAMLObject(IDPSSODescriptor.class); + + idpSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); + + //set ass default value, because PVP 2.x specification defines this feature as MUST + idpSSODescriptor.setWantAuthnRequestsSigned(config.wantAuthnRequestSigned()); + + // add WebSSO descriptor for POST-Binding + if (StringUtils.isNotEmpty(config.getIDPWebSSOPostBindingURL())) { + SingleSignOnService postSingleSignOnService = SAML2Utils.createSAMLObject(SingleSignOnService.class); + postSingleSignOnService.setLocation(config.getIDPWebSSOPostBindingURL()); + postSingleSignOnService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); + idpSSODescriptor.getSingleSignOnServices().add(postSingleSignOnService); + + } + + // add WebSSO descriptor for Redirect-Binding + if (StringUtils.isNotEmpty(config.getIDPWebSSORedirectBindingURL())) { + SingleSignOnService postSingleSignOnService = SAML2Utils.createSAMLObject(SingleSignOnService.class); + postSingleSignOnService.setLocation(config.getIDPWebSSORedirectBindingURL()); + postSingleSignOnService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); + idpSSODescriptor.getSingleSignOnServices().add(postSingleSignOnService); + + } + + //add Single LogOut POST-Binding endpoing + if (StringUtils.isNotEmpty(config.getIDPSLOPostBindingURL())) { + SingleLogoutService postSLOService = SAML2Utils.createSAMLObject(SingleLogoutService.class); + postSLOService.setLocation(config.getIDPSLOPostBindingURL()); + postSLOService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); + idpSSODescriptor.getSingleLogoutServices().add(postSLOService); + + } + + //add Single LogOut Redirect-Binding endpoing + if (StringUtils.isNotEmpty(config.getIDPSLORedirectBindingURL())) { + SingleLogoutService redirectSLOService = SAML2Utils.createSAMLObject(SingleLogoutService.class); + redirectSLOService.setLocation(config.getIDPSLORedirectBindingURL()); + redirectSLOService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); + idpSSODescriptor.getSingleLogoutServices().add(redirectSLOService); + + } + + //validate WebSSO endpoints + if (idpSSODescriptor.getSingleSignOnServices().size() == 0) { + log.warn("IDP Metadata generation FAILED! --> NO SAML2 SingleSignOnService endpoint found. "); + return null; + + } + + //set assertion signing key + KeyDescriptor signKeyDescriptor = SAML2Utils + .createSAMLObject(KeyDescriptor.class); + signKeyDescriptor.setUse(UsageType.SIGNING); + KeyInfoGenerator keyInfoGenerator = keyInfoFactory.newInstance(); + signKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(config.getRequestorResponseSigningCredentials())); + idpSSODescriptor.getKeyDescriptors().add(signKeyDescriptor); + + //set IDP attribute set + idpSSODescriptor.getAttributes().addAll(config.getIDPPossibleAttributes()); + + //set providable nameID formats + for (String format : config.getIDPPossibleNameITTypes()) { + NameIDFormat nameIDFormat = SAML2Utils.createSAMLObject(NameIDFormat.class); + nameIDFormat.setFormat(format); + idpSSODescriptor.getNameIDFormats().add(nameIDFormat); + + } + + return idpSSODescriptor; + + } + +} 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 new file mode 100644 index 00000000..cb36f202 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/builder/SamlAttributeGenerator.java @@ -0,0 +1,68 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.builder; + +import org.opensaml.saml2.core.Attribute; +import org.opensaml.saml2.core.AttributeValue; +import org.opensaml.xml.Configuration; +import org.opensaml.xml.XMLObject; +import org.opensaml.xml.schema.XSInteger; +import org.opensaml.xml.schema.XSString; +import org.opensaml.xml.schema.impl.XSIntegerBuilder; +import org.opensaml.xml.schema.impl.XSStringBuilder; + +import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; + +public class SamlAttributeGenerator implements IAttributeGenerator<Attribute> { + + private XMLObject buildAttributeStringValue(String value) { + XSStringBuilder stringBuilder = (XSStringBuilder) Configuration.getBuilderFactory().getBuilder(XSString.TYPE_NAME); + XSString stringValue = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); + stringValue.setValue(value); + return stringValue; + } + + private XMLObject buildAttributeIntegerValue(int value) { + XSIntegerBuilder integerBuilder = (XSIntegerBuilder) Configuration.getBuilderFactory().getBuilder(XSInteger.TYPE_NAME); + XSInteger integerValue = integerBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSInteger.TYPE_NAME); + integerValue.setValue(value); + return integerValue; + } + + public Attribute buildStringAttribute(final String friendlyName, final String name, final String value) { + Attribute attribute = SAML2Utils.createSAMLObject(Attribute.class); + attribute.setFriendlyName(friendlyName); + attribute.setName(name); + attribute.setNameFormat(Attribute.URI_REFERENCE); + attribute.getAttributeValues().add(buildAttributeStringValue(value)); + return attribute; + } + + public Attribute buildIntegerAttribute(final String friendlyName, final String name, final int value) { + Attribute attribute = SAML2Utils.createSAMLObject(Attribute.class); + attribute.setFriendlyName(friendlyName); + attribute.setName(name); + attribute.setNameFormat(Attribute.URI_REFERENCE); + attribute.getAttributeValues().add(buildAttributeIntegerValue(value)); + return attribute; + } + + public Attribute buildEmptyAttribute(final String friendlyName, final String name) { + Attribute attribute = SAML2Utils.createSAMLObject(Attribute.class); + attribute.setFriendlyName(friendlyName); + attribute.setName(name); + attribute.setNameFormat(Attribute.URI_REFERENCE); + return attribute; + } + + public Attribute buildLongAttribute(String friendlyName, String name, long value) { + Attribute attribute = SAML2Utils.createSAMLObject(Attribute.class); + attribute.setFriendlyName(friendlyName); + attribute.setName(name); + attribute.setNameFormat(Attribute.URI_REFERENCE); + attribute.getAttributeValues().add(buildAttributeIntegerValue((int) value)); + return attribute; + } + +} 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 new file mode 100644 index 00000000..adc2b516 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/message/InboundMessage.java @@ -0,0 +1,99 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.message; + +import java.io.Serializable; + +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Element; + +import at.gv.egiz.eaaf.modules.pvp2.api.message.InboundMessageInterface; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPVPMetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.exception.NoMetadataInformationException; + +/** + * @author tlenz + * + */ +public class InboundMessage implements InboundMessageInterface, Serializable{ + private static final Logger log = LoggerFactory.getLogger(InboundMessage.class); + + private static final long serialVersionUID = 2395131650841669663L; + + private Element samlMessage = null; + private boolean verified = false; + private String entityID = null; + private String relayState = null; + + + public EntityDescriptor getEntityMetadata(IPVPMetadataProvider metadataProvider) throws NoMetadataInformationException { + try { + if (metadataProvider == null) + throw new NullPointerException("No PVP MetadataProvider found."); + + return metadataProvider.getEntityDescriptor(this.entityID); + + } catch (MetadataProviderException e) { + log.warn("No Metadata for EntitiyID " + entityID); + throw new NoMetadataInformationException(); + } + } + + /** + * @param entitiyID the entitiyID to set + */ + public void setEntityID(String entitiyID) { + this.entityID = entitiyID; + } + + public void setVerified(boolean verified) { + this.verified = verified; + } + + /** + * @param relayState the relayState to set + */ + public void setRelayState(String relayState) { + this.relayState = relayState; + } + + public void setSAMLMessage(Element msg) { + this.samlMessage = msg; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.messages.PVP21InboundMessage#getRelayState() + */ + @Override + public String getRelayState() { + return relayState; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.messages.PVP21InboundMessage#getEntityID() + */ + @Override + public String getEntityID() { + return entityID; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.messages.PVP21InboundMessage#isVerified() + */ + @Override + public boolean isVerified() { + return verified; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.messages.PVP21InboundMessage#getInboundMessage() + */ + @Override + public Element getInboundMessage() { + return samlMessage; + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/message/PVPSProfileRequest.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/message/PVPSProfileRequest.java new file mode 100644 index 00000000..0a9a0c5b --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/message/PVPSProfileRequest.java @@ -0,0 +1,45 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.message; + + +import org.opensaml.Configuration; +import org.opensaml.xml.io.Unmarshaller; +import org.opensaml.xml.io.UnmarshallerFactory; +import org.opensaml.xml.io.UnmarshallingException; +import org.opensaml.xml.signature.SignableXMLObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PVPSProfileRequest extends InboundMessage{ + private static final Logger log = LoggerFactory.getLogger(PVPSProfileRequest.class); + + private static final long serialVersionUID = 8613921176727607896L; + + private String binding = null; + + public PVPSProfileRequest(SignableXMLObject inboundMessage, String binding) { + setSAMLMessage(inboundMessage.getDOM()); + this.binding = binding; + + } + + public String getRequestBinding() { + return binding; + } + + public SignableXMLObject getSamlRequest() { + UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory(); + Unmarshaller unmashaller = unmarshallerFactory.getUnmarshaller(getInboundMessage()); + + try { + return (SignableXMLObject) unmashaller.unmarshall(getInboundMessage()); + + } catch (UnmarshallingException e) { + log.warn("AuthnRequest Unmarshaller error", e); + return null; + } + + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/message/PVPSProfileResponse.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/message/PVPSProfileResponse.java new file mode 100644 index 00000000..6102e1c8 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/message/PVPSProfileResponse.java @@ -0,0 +1,37 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.message; + +import org.opensaml.Configuration; +import org.opensaml.saml2.core.StatusResponseType; +import org.opensaml.xml.io.Unmarshaller; +import org.opensaml.xml.io.UnmarshallerFactory; +import org.opensaml.xml.io.UnmarshallingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PVPSProfileResponse extends InboundMessage { + + private static final Logger log = LoggerFactory.getLogger(PVPSProfileResponse.class); + + private static final long serialVersionUID = -1133012928130138501L; + + public PVPSProfileResponse(StatusResponseType response) { + setSAMLMessage(response.getDOM()); + } + + public StatusResponseType getResponse() { + UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory(); + Unmarshaller unmashaller = unmarshallerFactory.getUnmarshaller(getInboundMessage()); + + try { + return (StatusResponseType) unmashaller.unmarshall(getInboundMessage()); + + } catch (UnmarshallingException e) { + log.warn("AuthnResponse Unmarshaller error", e); + return null; + } + + } + +} 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 new file mode 100644 index 00000000..c3eaa9a3 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/AbstractChainingMetadataProvider.java @@ -0,0 +1,446 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.metadata; + +import java.io.IOException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Timer; + +import javax.xml.namespace.QName; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.saml2.metadata.EntitiesDescriptor; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.RoleDescriptor; +import org.opensaml.saml2.metadata.provider.ChainingMetadataProvider; +import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataFilter; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.saml2.metadata.provider.ObservableMetadataProvider; +import org.opensaml.xml.XMLObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.eaaf.core.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.IPVPMetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IRefreshableMetadataProvider; + +public abstract class AbstractChainingMetadataProvider extends SimpleMetadataProvider + implements ObservableMetadataProvider, IGarbageCollectorProcessing, + IRefreshableMetadataProvider, IDestroyableObject, IPVPMetadataProvider { + + private static final Logger log = LoggerFactory.getLogger(AbstractChainingMetadataProvider.class); + + private MetadataProvider internalProvider = null; + private static Object mutex = new Object(); + private Timer timer = null; + + + public AbstractChainingMetadataProvider() { + internalProvider = new ChainingMetadataProvider(); + + } + + public final Timer getTimer() { + return this.timer; + + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.config.auth.IGarbageCollectorProcessing#runGarbageCollector() + */ + @Override + public void runGarbageCollector() { + synchronized (mutex) { + /**add new Metadataprovider or remove Metadataprovider which are not in use any more.**/ + try { + log.trace("Check consistence of PVP2X metadata"); + addAndRemoveMetadataProvider(); + + } catch (EAAFConfigurationException e) { + log.error("Access to MOA-ID configuration FAILED.", e); + + } + } + + } + + public void fullyDestroy() { + internalDestroy(); + + } + + @Override + public synchronized boolean refreshMetadataProvider(String entityID) { + try { + //check if metadata provider is already loaded + try { + if (internalProvider.getEntityDescriptor(entityID) != null) + return true; + + } catch (MetadataProviderException e) {} + + + //reload metadata provider + String metadataURL = getMetadataURL(entityID); + if (StringUtils.isNotEmpty(metadataURL)) { + Map<String, HTTPMetadataProvider> actuallyLoadedProviders = getAllActuallyLoadedProviders(); + + // check if MetadataProvider is actually loaded + if (actuallyLoadedProviders.containsKey(metadataURL)) { + actuallyLoadedProviders.get(metadataURL).refresh(); + log.info("SAML2 metadata for service provider: " + + entityID + " is refreshed."); + return true; + + } else { + //load new Metadata Provider + if (timer == null) + timer = new Timer(true); + + ChainingMetadataProvider chainProvider = (ChainingMetadataProvider) internalProvider; + chainProvider.addMetadataProvider(createNewMetadataProvider(entityID)); + + emitChangeEvent(); + log.info("SAML2 metadata for service provider: " + + entityID + " is added."); + return true; + + } + + } else + log.debug("Can not refresh SAML2 metadata: NO SAML2 metadata URL for SP with Id: " + entityID); + + } catch (MetadataProviderException e) { + log.warn("Refresh SAML2 metadata for service provider: " + + entityID + " FAILED.", e); + + } catch (IOException e) { + log.warn("Refresh SAML2 metadata for service provider: " + + entityID + " FAILED.", e); + + } catch (EAAFConfigurationException e) { + log.warn("Refresh SAML2 metadata for service provider: " + + entityID + " FAILED.", e); + + } catch (CertificateException e) { + log.warn("Refresh SAML2 metadata for service provider: " + + entityID + " FAILED.", e); + + } + + return false; + + } + + public void internalDestroy() { + if (internalProvider != null && internalProvider instanceof ChainingMetadataProvider) { + log.info("Destrorying PVP-Authentication MetaDataProvider."); + ChainingMetadataProvider chainProvider = (ChainingMetadataProvider) internalProvider; + + List<MetadataProvider> providers = chainProvider.getProviders(); + for (MetadataProvider provider : providers) { + if (provider instanceof HTTPMetadataProvider) { + HTTPMetadataProvider httpprovider = (HTTPMetadataProvider) provider; + log.debug("Destroy HTTPMetadataProvider +" + httpprovider.getMetadataURI()); + httpprovider.destroy(); + + } else { + log.warn("MetadataProvider can not be destroyed."); + } + } + + internalProvider = new ChainingMetadataProvider(); + + if (timer != null) + timer.cancel(); + + } else { + log.warn("ReInitalize MOAMetaDataProvider is not possible! MOA-ID Instance has to be restarted manualy"); + } + } + + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#requireValidMetadata() + */ + @Override + public boolean requireValidMetadata() { + return internalProvider.requireValidMetadata(); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#setRequireValidMetadata(boolean) + */ + @Override + public void setRequireValidMetadata(boolean requireValidMetadata) { + internalProvider.setRequireValidMetadata(requireValidMetadata); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#getMetadataFilter() + */ + @Override + public MetadataFilter getMetadataFilter() { + return internalProvider.getMetadataFilter(); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#setMetadataFilter(org.opensaml.saml2.metadata.provider.MetadataFilter) + */ + @Override + public void setMetadataFilter(MetadataFilter newFilter) + throws MetadataProviderException { + internalProvider.setMetadataFilter(newFilter); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#getMetadata() + */ + @Override + public XMLObject getMetadata() throws MetadataProviderException { + return internalProvider.getMetadata(); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#getEntitiesDescriptor(java.lang.String) + */ + @Override + public EntitiesDescriptor getEntitiesDescriptor(String entitiesID) + throws MetadataProviderException { + EntitiesDescriptor entitiesDesc = null; + try { + entitiesDesc = internalProvider.getEntitiesDescriptor(entitiesID); + + if (entitiesDesc == null) { + log.debug("Can not find PVP metadata for entityID: " + entitiesID + + " Start refreshing process ..."); + if (refreshMetadataProvider(entitiesID)) + return internalProvider.getEntitiesDescriptor(entitiesID); + + } + + } catch (MetadataProviderException e) { + log.debug("Can not find PVP metadata for entityID: " + entitiesID + + " Start refreshing process ..."); + if (refreshMetadataProvider(entitiesID)) + return internalProvider.getEntitiesDescriptor(entitiesID); + + } + + return entitiesDesc; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#getEntityDescriptor(java.lang.String) + */ + @Override + public EntityDescriptor getEntityDescriptor(String entityID) + throws MetadataProviderException { + EntityDescriptor entityDesc = null; + try { + entityDesc = internalProvider.getEntityDescriptor(entityID); + if (entityDesc == null) { + log.debug("Can not find PVP metadata for entityID: " + entityID + + " Start refreshing process ..."); + if (refreshMetadataProvider(entityID)) + return internalProvider.getEntityDescriptor(entityID); + + } + + } catch (MetadataProviderException e) { + log.debug("Can not find PVP metadata for entityID: " + entityID + + " Start refreshing process ..."); + if (refreshMetadataProvider(entityID)) + return internalProvider.getEntityDescriptor(entityID); + + } + +// if (entityDesc != null) +// lastAccess.put(entityID, new Date()); + + return entityDesc; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#getRole(java.lang.String, javax.xml.namespace.QName) + */ + @Override + public List<RoleDescriptor> getRole(String entityID, QName roleName) + throws MetadataProviderException { + List<RoleDescriptor> result = internalProvider.getRole(entityID, roleName); + +// if (result != null) +// lastAccess.put(entityID, new Date()); + + return result; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#getRole(java.lang.String, javax.xml.namespace.QName, java.lang.String) + */ + @Override + public RoleDescriptor getRole(String entityID, QName roleName, + String supportedProtocol) throws MetadataProviderException { + RoleDescriptor result = internalProvider.getRole(entityID, roleName, supportedProtocol); + +// if (result != null) +// lastAccess.put(entityID, new Date()); + + return result; + } + + /* (non-Javadoc) + * @see org.opensaml.saml2.metadata.provider.ObservableMetadataProvider#getObservers() + */ + @Override + public List<Observer> getObservers() { + return ((ChainingMetadataProvider) internalProvider).getObservers(); + } + + + /** + * Get the URL to metadata for a specific entityID + * + * @param entityId + * @return + * @throws EAAFConfigurationException + */ + protected abstract String getMetadataURL(String entityId) throws EAAFConfigurationException; + + /** + * Creates a new implementation specific SAML2 metadata provider + * + * @param entityId + * @return + * @throws EAAFConfigurationException + * @throws IOException + * @throws CertificateException + * @throws ConfigurationException + */ + protected abstract MetadataProvider createNewMetadataProvider(String entityId) throws EAAFConfigurationException, IOException, CertificateException; + + /** + * Get a List of metadata URLs for all SAML2 SPs from configuration + * + * @throws EAAFConfigurationException + */ + protected abstract List<String> getAllMetadataURLsFromConfiguration() throws EAAFConfigurationException; + + + protected void emitChangeEvent() { + if ((getObservers() == null) || (getObservers().size() == 0)) { + return; + } + + List<Observer> tempObserverList = new ArrayList<Observer>(getObservers()); + for (ObservableMetadataProvider.Observer observer : tempObserverList) + if (observer != null) + observer.onEvent(this); + } + + private Map<String, HTTPMetadataProvider> getAllActuallyLoadedProviders() { + Map<String, HTTPMetadataProvider> loadedproviders = new HashMap<String, HTTPMetadataProvider>(); + ChainingMetadataProvider chainProvider = (ChainingMetadataProvider) internalProvider; + + //make a Map of all actually loaded HTTPMetadataProvider + List<MetadataProvider> providers = chainProvider.getProviders(); + for (MetadataProvider provider : providers) { + if (provider instanceof HTTPMetadataProvider) { + HTTPMetadataProvider httpprovider = (HTTPMetadataProvider) provider; + loadedproviders.put(httpprovider.getMetadataURI(), httpprovider); + + } + } + + return loadedproviders; + } + + private void addAndRemoveMetadataProvider() throws EAAFConfigurationException { + if (internalProvider != null && internalProvider instanceof ChainingMetadataProvider) { + log.info("Reload MOAMetaDataProvider."); + + /*OpenSAML ChainingMetadataProvider can not remove a MetadataProvider (UnsupportedOperationException) + *The ChainingMetadataProvider use internal a unmodifiableList to hold all registrated MetadataProviders.*/ + Map<String, MetadataProvider> providersinuse = new HashMap<String, MetadataProvider>(); + ChainingMetadataProvider chainProvider = (ChainingMetadataProvider) internalProvider; + + //get all actually loaded metadata providers + Map<String, HTTPMetadataProvider> loadedproviders = getAllActuallyLoadedProviders(); + + /* 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 + List<String> allMetadataURLs = getAllMetadataURLsFromConfiguration(); + + if (allMetadataURLs != null) { + Iterator<String> metadataURLInterator = allMetadataURLs.iterator(); + while (metadataURLInterator.hasNext()) { + String metadataurl = metadataURLInterator.next(); + try { + if (StringUtils.isNotEmpty(metadataurl)) { + if (loadedproviders.containsKey(metadataurl)) { + // SAML2 SP is actually loaded, to nothing + providersinuse.put(metadataurl, loadedproviders.get(metadataurl)); + loadedproviders.remove(metadataurl); + + } + } + } catch (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 + Collection<HTTPMetadataProvider> notusedproviders = loadedproviders.values(); + for (HTTPMetadataProvider provider : notusedproviders) { + String metadataurl = provider.getMetadataURI(); + try { + provider.destroy(); + + /*OpenSAML ChainingMetadataProvider can not remove a MetadataProvider (UnsupportedOperationException) + *The ChainingMetadataProvider use internal a unmodifiableList to hold all registrated MetadataProviders.*/ + //chainProvider.removeMetadataProvider(provider); + log.info("Remove not used MetadataProvider with MetadataURL " + metadataurl); + + } catch (Throwable e) { + log.error("HTTPMetadataProvider with URL " + metadataurl + + " can not be removed from the list of actually loaded Providers.", e); + + } + + } + + try { + chainProvider.setProviders(new ArrayList<MetadataProvider>(providersinuse.values())); + emitChangeEvent(); + + } catch (MetadataProviderException e) { + log.warn("ReInitalize AbstractMetaDataProvider is not possible! Service has to be restarted manualy", e); + + } + + } else + log.warn("ReInitalize AbstractMetaDataProvider is not possible! Service has to be restarted manualy"); + + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/MetadataFilterChain.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/MetadataFilterChain.java new file mode 100644 index 00000000..37204520 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/MetadataFilterChain.java @@ -0,0 +1,56 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.metadata; + +import java.util.ArrayList; +import java.util.List; + +import org.opensaml.saml2.metadata.provider.FilterException; +import org.opensaml.saml2.metadata.provider.MetadataFilter; +import org.opensaml.xml.XMLObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * @author tlenz + * + */ +public class MetadataFilterChain implements MetadataFilter { + private static final Logger log = LoggerFactory.getLogger(MetadataFilterChain.class); + + + private List<MetadataFilter> filters = new ArrayList<MetadataFilter>(); + + /** + * Return all actually used Metadata filters + * + * @return List of Metadata filters + */ + public List<MetadataFilter> getFilters() { + return filters; + } + + /** + * Add a new Metadata filter to filterchain + * + * @param filter + */ + public void addFilter(MetadataFilter filter) { + filters.add(filter); + } + + + /* (non-Javadoc) + * @see org.opensaml.saml2.metadata.provider.MetadataFilter#doFilter(org.opensaml.xml.XMLObject) + */ + @Override + public void doFilter(XMLObject arg0) throws FilterException { + for (MetadataFilter filter : filters) { + log.trace("Use EAAFMetadataFilter " + filter.getClass().getName()); + filter.doFilter(arg0); + } + + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/SimpleMetadataProvider.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/SimpleMetadataProvider.java new file mode 100644 index 00000000..0c6ffb49 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/SimpleMetadataProvider.java @@ -0,0 +1,212 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.metadata; + +import java.io.File; +import java.net.MalformedURLException; +import java.util.Timer; + +import javax.net.ssl.SSLHandshakeException; + +import org.apache.commons.httpclient.HttpClient; +import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider; +import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataFilter; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.xml.parse.ParserPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +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.exception.SchemaValidationException; +import at.gv.egiz.eaaf.modules.pvp2.exception.SignatureValidationException; + +/** + * @author tlenz + * + */ +public abstract class SimpleMetadataProvider implements MetadataProvider{ + private static final Logger log = LoggerFactory.getLogger(SimpleMetadataProvider.class); + + private static final String URI_PREFIX_HTTP = "http:"; + private static final String URI_PREFIX_HTTPS = "https:"; + private static final String URI_PREFIX_FILE = "file:"; + + + @Autowired + protected IConfiguration authConfig; + + + /** + * Create a single SAML2 metadata provider + * + * @param metadataLocation where the metadata should be loaded, but never null. If the location starts with http(s):, than a http + * based metadata provider is used. If the location starts with file:, than a filesystem based metadata provider is used + * @param filter Filters, which should be used to validate the metadata + * @param IdForLogging Id, which is used for Logging + * @param timer {@link Timer} which is used to schedule metadata refresh operations + * @param httpClient Apache commons 3.x http client + * + * @return SAML2 Metadata Provider, or null if the metadata provider can not initialized + */ + protected MetadataProvider createNewSimpleMetadataProvider(String metadataLocation, MetadataFilter filter, + String IdForLogging, Timer timer, ParserPool pool, HttpClient httpClient) { + if (metadataLocation.startsWith(URI_PREFIX_HTTP) || metadataLocation.startsWith(URI_PREFIX_HTTPS)) { + if (httpClient != null) + return createNewHTTPMetaDataProvider(metadataLocation, filter, IdForLogging, timer, pool, httpClient); + + else { + log.warn("Can not load http(s) based SAML2 metadata without a HTTP client"); + return null; + } + + } else { + String absoluteMetadataLocation; + try { + absoluteMetadataLocation = FileUtils.makeAbsoluteURL( + metadataLocation, + authConfig.getConfigurationRootDirectory()); + + if (absoluteMetadataLocation.startsWith(URI_PREFIX_FILE)) { + File metadataFile = new File(absoluteMetadataLocation); + if (metadataFile.exists()) + return createNewFileSystemMetaDataProvider(metadataFile, filter, IdForLogging, timer, pool); + + else { + log.warn("SAML2 metadata file: " + absoluteMetadataLocation + " not found or not exist"); + return null; + } + + } + + + } catch (MalformedURLException e) { + log.warn("SAML2 metadata URL is invalid: " + metadataLocation, e); + + } + + } + + log.warn("SAML2 metadata has an unsupported metadata location prefix: " + metadataLocation); + return null; + + } + + + /** + * Create a single SAML2 filesystem based metadata provider + * + * @param metadataFile File, where the metadata should be loaded + * @param filter Filters, which should be used to validate the metadata + * @param IdForLogging Id, which is used for Logging + * @param timer {@link Timer} which is used to schedule metadata refresh operations + * @param pool + * + * @return SAML2 Metadata Provider + */ + private MetadataProvider createNewFileSystemMetaDataProvider(File metadataFile, MetadataFilter filter, String IdForLogging, Timer timer, ParserPool pool) { + FilesystemMetadataProvider fileSystemProvider = null; + try { + fileSystemProvider = new FilesystemMetadataProvider(timer, metadataFile); + fileSystemProvider.setParserPool(pool); + fileSystemProvider.setRequireValidMetadata(true); + fileSystemProvider.setMinRefreshDelay(1000*60*15); //15 minutes + fileSystemProvider.setMaxRefreshDelay(1000*60*60*24); //24 hours + //httpProvider.setRefreshDelayFactor(0.1F); + + fileSystemProvider.setMetadataFilter(filter); + fileSystemProvider.initialize(); + + fileSystemProvider.setRequireValidMetadata(true); + + return fileSystemProvider; + + } catch (Exception e) { + log.warn( + "Failed to load Metadata file for " + + IdForLogging + "[ " + + "File: " + metadataFile.getAbsolutePath() + + " Msg: " + e.getMessage() + " ]", e); + + + log.warn("Can not initialize SAML2 metadata provider from filesystem: " + metadataFile.getAbsolutePath() + + " Reason: " + e.getMessage(), e); + + if (fileSystemProvider != null) + fileSystemProvider.destroy(); + + } + + return null; + + } + + + + /** + * Create a single SAML2 HTTP metadata provider + * + * @param metadataURL URL, where the metadata should be loaded + * @param filter Filters, which should be used to validate the metadata + * @param IdForLogging Id, which is used for Logging + * @param timer {@link Timer} which is used to schedule metadata refresh operations + * @param pool + * + * @return SAML2 Metadata Provider + */ + private MetadataProvider createNewHTTPMetaDataProvider(String metadataURL, MetadataFilter filter, String IdForLogging, Timer timer, ParserPool pool, HttpClient httpClient) { + HTTPMetadataProvider httpProvider = null; + try { + httpProvider = new HTTPMetadataProvider(timer, httpClient, + metadataURL); + httpProvider.setParserPool(pool); + httpProvider.setRequireValidMetadata(true); + httpProvider.setMinRefreshDelay(1000*60*15); //15 minutes + httpProvider.setMaxRefreshDelay(1000*60*60*24); //24 hours + //httpProvider.setRefreshDelayFactor(0.1F); + + httpProvider.setMetadataFilter(filter); + httpProvider.initialize(); + + httpProvider.setRequireValidMetadata(true); + + return httpProvider; + + } catch (Throwable e) { + if (e.getCause() != null && e.getCause().getCause() instanceof SSLHandshakeException) { + log.warn("SSL-Server certificate for metadata " + + metadataURL + " not trusted.", e); + + } if (e.getCause() != null && e.getCause().getCause() instanceof SignatureValidationException) { + log.warn("Signature verification for metadata" + + metadataURL + " FAILED.", e); + + } if (e.getCause() != null && e.getCause().getCause() instanceof SchemaValidationException) { + log.warn("Schema validation for metadata " + + metadataURL + " FAILED.", e); + } + + log.warn( + "Failed to load Metadata file for " + + IdForLogging + "[ " + + e.getMessage() + " ]", e); + + if (httpProvider != null) { + log.debug("Destroy failed Metadata provider"); + httpProvider.destroy(); + } + +// if (timer != null) { +// log.debug("Destroy Timer."); +// timer.cancel(); +// } + + + } + + return null; + } + +} 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 new file mode 100644 index 00000000..d7491a88 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/HTTPPostEncoderWithOwnTemplate.java @@ -0,0 +1,97 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.opensaml; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Writer; + +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import org.opensaml.common.binding.SAMLMessageContext; +import org.opensaml.saml2.binding.encoding.HTTPPostEncoder; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.ws.transport.http.HTTPOutTransport; +import org.opensaml.ws.transport.http.HTTPTransportUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.eaaf.core.api.gui.IGUIBuilderConfiguration; +import at.gv.egiz.eaaf.core.api.gui.IGUIFormBuilder; + +/** + * @author tlenz + * + */ +public class HTTPPostEncoderWithOwnTemplate extends HTTPPostEncoder { + private static final Logger log = LoggerFactory.getLogger(HTTPPostEncoderWithOwnTemplate.class); + + + private VelocityEngine velocityEngine; + private IGUIBuilderConfiguration guiConfig; + private IGUIFormBuilder guiBuilder; + + /** + * @param engine + * @param templateId + */ + public HTTPPostEncoderWithOwnTemplate(IGUIBuilderConfiguration guiConfig, IGUIFormBuilder guiBuilder, VelocityEngine engine) { + super(engine, null); + this.velocityEngine = engine; + this.guiConfig = guiConfig; + this.guiBuilder = guiBuilder; + + } + + /** + * Base64 and POST encodes the outbound message and writes it to the outbound transport. + * + * @param messageContext current message context + * @param endpointURL endpoint URL to which to encode message + * + * @throws MessageEncodingException thrown if there is a problem encoding the message + */ + protected void postEncode(SAMLMessageContext messageContext, String endpointURL) throws MessageEncodingException { + log.debug("Invoking Velocity template to create POST body"); + InputStream is = null; + try { + //build Velocity Context from GUI input paramters + VelocityContext context = guiBuilder.generateVelocityContextFromConfiguration(guiConfig); + + //load template + is = guiBuilder.getTemplateInputStream(guiConfig); + + //populate velocity context with SAML2 parameters + populateVelocityContext(context, messageContext, endpointURL); + + //populate transport parameter + HTTPOutTransport outTransport = (HTTPOutTransport) messageContext.getOutboundMessageTransport(); + HTTPTransportUtils.addNoCacheHeaders(outTransport); + HTTPTransportUtils.setUTF8Encoding(outTransport); + HTTPTransportUtils.setContentType(outTransport, "text/html"); + + //evaluate template and write content to response + Writer out = new OutputStreamWriter(outTransport.getOutgoingStream(), "UTF-8"); + velocityEngine.evaluate(context, out, "SAML2_POST_BINDING", new BufferedReader(new InputStreamReader(is))); + out.flush(); + + } catch (Exception e) { + log.error("Error invoking Velocity template", e); + throw new MessageEncodingException("Error creating output document", e); + + } finally { + if (is != null) { + try { + is.close(); + + } catch (IOException e) { + log.error("Can NOT close GUI-Template InputStream.", e); + } + } + + } + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/KeyStoreX509CredentialAdapter.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/KeyStoreX509CredentialAdapter.java new file mode 100644 index 00000000..854a291e --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/KeyStoreX509CredentialAdapter.java @@ -0,0 +1,32 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.opensaml; + +import java.security.KeyStore; + +import org.opensaml.xml.security.x509.X509Credential; + + +/** + * @author tlenz + * + */ +public class KeyStoreX509CredentialAdapter extends + org.opensaml.xml.security.x509.KeyStoreX509CredentialAdapter { + + /** + * @param store + * @param alias + * @param password + */ + public KeyStoreX509CredentialAdapter(KeyStore store, String alias, + char[] password) { + super(store, alias, password); + } + + public Class<? extends X509Credential> getCredentialType() { + return X509Credential.class; + } + + +} 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 new file mode 100644 index 00000000..8d0634fb --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/StringRedirectDeflateEncoder.java @@ -0,0 +1,57 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.opensaml; + +import org.opensaml.common.binding.SAMLMessageContext; +import org.opensaml.saml2.binding.encoding.HTTPRedirectDeflateEncoder; +import org.opensaml.ws.message.MessageContext; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.initialize.EAAFDefaultSAML2Bootstrap; + +/** + * @author tlenz + * + */ +public class StringRedirectDeflateEncoder extends HTTPRedirectDeflateEncoder { + private static final Logger log = LoggerFactory.getLogger(StringRedirectDeflateEncoder.class); + + private String redirectURL = null; + + public void encode(MessageContext messageContext) + throws MessageEncodingException { + if (!(messageContext instanceof SAMLMessageContext)) { + log.error("Invalid message context type, this encoder only support SAMLMessageContext"); + throw new MessageEncodingException( + "Invalid message context type, this encoder only support SAMLMessageContext"); + } + + //load default PVP security configurations + EAAFDefaultSAML2Bootstrap.initializeDefaultPVPConfiguration(); + + SAMLMessageContext samlMsgCtx = (SAMLMessageContext) messageContext; + + String endpointURL = getEndpointURL(samlMsgCtx).buildURL(); + + setResponseDestination(samlMsgCtx.getOutboundSAMLMessage(), endpointURL); + + removeSignature(samlMsgCtx); + + String encodedMessage = deflateAndBase64Encode(samlMsgCtx + .getOutboundSAMLMessage()); + + redirectURL = buildRedirectURL(samlMsgCtx, endpointURL, + encodedMessage); + } + + /** + * @return the redirectURL + */ + public String getRedirectURL() { + return redirectURL; + } + + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/initialize/EAAFDefaultSAML2Bootstrap.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/initialize/EAAFDefaultSAML2Bootstrap.java new file mode 100644 index 00000000..7b9bef88 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/initialize/EAAFDefaultSAML2Bootstrap.java @@ -0,0 +1,42 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.initialize; + +import org.opensaml.Configuration; +import org.opensaml.DefaultBootstrap; +import org.opensaml.xml.ConfigurationException; + +/** + * @author tlenz + * + */ +public class EAAFDefaultSAML2Bootstrap extends DefaultBootstrap { + + public static synchronized void bootstrap() throws ConfigurationException { + + initializeXMLSecurity(); + + initializeXMLTooling(); + + initializeArtifactBuilderFactories(); + + initializeGlobalSecurityConfiguration(); + + initializeParserPool(); + + initializeESAPI(); + + } + + public static void initializeDefaultPVPConfiguration() { + initializeGlobalSecurityConfiguration(); + + } + + /** + * Initializes the default global security configuration. + */ + protected static void initializeGlobalSecurityConfiguration() { + Configuration.setGlobalSecurityConfiguration(EAAFDefaultSecurityConfigurationBootstrap.buildDefaultConfig()); + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/initialize/EAAFDefaultSecurityConfigurationBootstrap.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/initialize/EAAFDefaultSecurityConfigurationBootstrap.java new file mode 100644 index 00000000..0008ac87 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/opensaml/initialize/EAAFDefaultSecurityConfigurationBootstrap.java @@ -0,0 +1,132 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.initialize; + +import org.opensaml.xml.encryption.EncryptionConstants; +import org.opensaml.xml.security.BasicSecurityConfiguration; +import org.opensaml.xml.security.DefaultSecurityConfigurationBootstrap; +import org.opensaml.xml.security.credential.BasicKeyInfoGeneratorFactory; +import org.opensaml.xml.security.keyinfo.KeyInfoGeneratorManager; +import org.opensaml.xml.security.keyinfo.NamedKeyInfoGeneratorManager; +import org.opensaml.xml.security.x509.X509KeyInfoGeneratorFactory; +import org.opensaml.xml.signature.SignatureConstants; + +/** + * @author tlenz + * + */ +public class EAAFDefaultSecurityConfigurationBootstrap extends + DefaultSecurityConfigurationBootstrap { + + public static BasicSecurityConfiguration buildDefaultConfig() { + BasicSecurityConfiguration config = new BasicSecurityConfiguration(); + + populateSignatureParams(config); + populateEncryptionParams(config); + populateKeyInfoCredentialResolverParams(config); + populateKeyInfoGeneratorManager(config); + populateKeyParams(config); + + return config; + } + + protected static void populateKeyInfoGeneratorManager( + BasicSecurityConfiguration config) { + NamedKeyInfoGeneratorManager namedManager = new NamedKeyInfoGeneratorManager(); + config.setKeyInfoGeneratorManager(namedManager); + + namedManager.setUseDefaultManager(true); + KeyInfoGeneratorManager defaultManager = namedManager + .getDefaultManager(); + + BasicKeyInfoGeneratorFactory basicFactory = new BasicKeyInfoGeneratorFactory(); + basicFactory.setEmitPublicKeyValue(true); + + X509KeyInfoGeneratorFactory x509Factory = new X509KeyInfoGeneratorFactory(); + x509Factory.setEmitEntityCertificate(true); + + defaultManager.registerFactory(basicFactory); + defaultManager.registerFactory(x509Factory); + } + + protected static void populateSignatureParams( + BasicSecurityConfiguration config) { + + //use SHA256 instead of SHA1 + config.registerSignatureAlgorithmURI("RSA", + SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); + + config.registerSignatureAlgorithmURI("DSA", + "http://www.w3.org/2000/09/xmldsig#dsa-sha1"); + + //use SHA256 instead of SHA1 + config.registerSignatureAlgorithmURI("EC", + SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA256); + + //use SHA256 instead of SHA1 + config.registerSignatureAlgorithmURI("AES", + SignatureConstants.ALGO_ID_MAC_HMAC_SHA256); + + + config.registerSignatureAlgorithmURI("DESede", + SignatureConstants.ALGO_ID_MAC_HMAC_SHA256); + + config.setSignatureCanonicalizationAlgorithm("http://www.w3.org/2001/10/xml-exc-c14n#"); + config.setSignatureHMACOutputLength(null); + + //use SHA256 instead of SHA1 + config.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256); + } + + protected static void populateEncryptionParams( + BasicSecurityConfiguration config) { + config.registerDataEncryptionAlgorithmURI("AES", Integer.valueOf(128), + "http://www.w3.org/2001/04/xmlenc#aes128-cbc"); + config.registerDataEncryptionAlgorithmURI("AES", Integer.valueOf(192), + "http://www.w3.org/2001/04/xmlenc#aes192-cbc"); + config.registerDataEncryptionAlgorithmURI("AES", Integer.valueOf(256), + "http://www.w3.org/2001/04/xmlenc#aes256-cbc"); + + //support GCM mode + config.registerDataEncryptionAlgorithmURI("AES", Integer.valueOf(128), + EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128_GCM); + + config.registerDataEncryptionAlgorithmURI("AES", Integer.valueOf(192), + EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES192_GCM); + + config.registerDataEncryptionAlgorithmURI("AES", Integer.valueOf(256), + EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256_GCM); + + + config.registerDataEncryptionAlgorithmURI("DESede", + Integer.valueOf(168), + "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"); + config.registerDataEncryptionAlgorithmURI("DESede", + Integer.valueOf(192), + "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"); + + config.registerKeyTransportEncryptionAlgorithmURI("RSA", null, "AES", + "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"); + + config.registerKeyTransportEncryptionAlgorithmURI("RSA", null, + "DESede", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"); + + config.registerKeyTransportEncryptionAlgorithmURI("AES", + Integer.valueOf(128), null, + "http://www.w3.org/2001/04/xmlenc#kw-aes128"); + config.registerKeyTransportEncryptionAlgorithmURI("AES", + Integer.valueOf(192), null, + "http://www.w3.org/2001/04/xmlenc#kw-aes192"); + config.registerKeyTransportEncryptionAlgorithmURI("AES", + Integer.valueOf(256), null, + "http://www.w3.org/2001/04/xmlenc#kw-aes256"); + config.registerKeyTransportEncryptionAlgorithmURI("DESede", + Integer.valueOf(168), null, + "http://www.w3.org/2001/04/xmlenc#kw-tripledes"); + config.registerKeyTransportEncryptionAlgorithmURI("DESede", + Integer.valueOf(192), null, + "http://www.w3.org/2001/04/xmlenc#kw-tripledes"); + + config.setAutoGeneratedDataEncryptionKeyAlgorithmURI("http://www.w3.org/2001/04/xmlenc#aes128-cbc"); + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/AbstractCredentialProvider.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/AbstractCredentialProvider.java new file mode 100644 index 00000000..40ad8a82 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/AbstractCredentialProvider.java @@ -0,0 +1,201 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.utils; + +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.RSAPrivateKey; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.xml.security.credential.Credential; +import org.opensaml.xml.security.credential.UsageType; +import org.opensaml.xml.security.x509.X509Credential; +import org.opensaml.xml.signature.Signature; +import org.opensaml.xml.signature.SignatureConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; +import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; +import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.KeyStoreX509CredentialAdapter; + +public abstract class AbstractCredentialProvider { + + private static final Logger log = LoggerFactory.getLogger(AbstractCredentialProvider.class); + + private KeyStore keyStore = null; + + /** + * Get a friendlyName for this keyStore implementation + * This friendlyName is used for logging + * + * @return keyStore friendlyName + */ + public abstract String getFriendlyName(); + + /** + * Get KeyStore + * + * @return URL to the keyStore + * @throws EAAFException + */ + public abstract String getKeyStoreFilePath() throws EAAFException; + + /** + * Get keyStore password + * + * @return Password of the keyStore + */ + public abstract String getKeyStorePassword(); + + /** + * Get alias of key for metadata signing + * + * @return key alias + */ + public abstract String getMetadataKeyAlias(); + + /** + * Get password of key for metadata signing + * + * @return key password + */ + public abstract String getMetadataKeyPassword(); + + /** + * Get alias of key for request/response signing + * + * @return key alias + */ + public abstract String getSignatureKeyAlias(); + + /** + * Get password of key for request/response signing + * + * @return key password + */ + public abstract String getSignatureKeyPassword(); + + /** + * Get alias of key for IDP response encryption + * + * @return key alias + */ + public abstract String getEncryptionKeyAlias(); + + /** + * Get password of key for IDP response encryption + * + * @return key password + */ + public abstract String getEncryptionKeyPassword(); + + + public X509Credential getIDPMetaDataSigningCredential() + throws CredentialsNotAvailableException { + try { + + if (keyStore == null) + keyStore = KeyStoreUtils.loadKeyStore(getKeyStoreFilePath(), + getKeyStorePassword()); + + KeyStoreX509CredentialAdapter credentials = new KeyStoreX509CredentialAdapter( + keyStore, getMetadataKeyAlias(), getMetadataKeyPassword().toCharArray()); + + credentials.setUsageType(UsageType.SIGNING); + if (credentials.getPrivateKey() == null && credentials.getSecretKey() == null) { + log.error(getFriendlyName() + " Metadata Signing credentials is not found or contains no PrivateKey."); + throw new CredentialsNotAvailableException("config.27", new Object[]{getFriendlyName() + " Assertion Signing credentials (Alias: " + + getMetadataKeyAlias() + ") is not found or contains no PrivateKey."}); + + } + return credentials; + } catch (Exception e) { + log.error("Failed to generate " + getFriendlyName() + " Metadata Signing credentials"); + e.printStackTrace(); + throw new CredentialsNotAvailableException("config.27", new Object[]{e.getMessage()}, e); + } + } + + public X509Credential getIDPAssertionSigningCredential() + throws CredentialsNotAvailableException { + try { + if (keyStore == null) + keyStore = KeyStoreUtils.loadKeyStore(getKeyStoreFilePath(), + getKeyStorePassword()); + + KeyStoreX509CredentialAdapter credentials = new KeyStoreX509CredentialAdapter( + keyStore, getSignatureKeyAlias(), getSignatureKeyPassword().toCharArray()); + + credentials.setUsageType(UsageType.SIGNING); + if (credentials.getPrivateKey() == null && credentials.getSecretKey() == null) { + log.error(getFriendlyName() + " Assertion Signing credentials is not found or contains no PrivateKey."); + throw new CredentialsNotAvailableException("config.27", new Object[]{getFriendlyName() + " Assertion Signing credentials (Alias: " + + getSignatureKeyAlias() + ") is not found or contains no PrivateKey."}); + + } + + return (X509Credential) credentials; + } catch (Exception e) { + log.error("Failed to generate " + getFriendlyName() + " Assertion Signing credentials"); + e.printStackTrace(); + throw new CredentialsNotAvailableException("config.27", new Object[]{e.getMessage()}, e); + } + } + + public X509Credential getIDPAssertionEncryptionCredential() + throws CredentialsNotAvailableException { + try { + if (keyStore == null) + keyStore = KeyStoreUtils.loadKeyStore(getKeyStoreFilePath(), + getKeyStorePassword()); + + //if no encryption key is configured return null + if (StringUtils.isEmpty(getEncryptionKeyAlias())) + return null; + + KeyStoreX509CredentialAdapter credentials = new KeyStoreX509CredentialAdapter( + keyStore, getEncryptionKeyAlias(), getEncryptionKeyPassword().toCharArray()); + + credentials.setUsageType(UsageType.ENCRYPTION); + + if (credentials.getPrivateKey() == null && credentials.getSecretKey() == null) { + log.error(getFriendlyName() + " Assertion Encryption credentials is not found or contains no PrivateKey."); + throw new CredentialsNotAvailableException("config.27", new Object[]{getFriendlyName() + " Assertion Encryption credentials (Alias: " + + getEncryptionKeyAlias() + ") is not found or contains no PrivateKey."}); + + } + + return (X509Credential) credentials; + + } catch (Exception e) { + log.error("Failed to generate " + getFriendlyName() + " Assertion Encryption credentials"); + e.printStackTrace(); + throw new CredentialsNotAvailableException("config.27", new Object[]{e.getMessage()}, e); + } + } + + public static Signature getIDPSignature(Credential credentials) { + PrivateKey privatekey = credentials.getPrivateKey(); + Signature signer = SAML2Utils.createSAMLObject(Signature.class); + + if (privatekey instanceof RSAPrivateKey) { + signer.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); + + } else if (privatekey instanceof ECPrivateKey) { + signer.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA256); + + } else { + log.warn("Could NOT evaluate the Private-Key type from " + credentials.getEntityId() + " credential."); + + + } + + signer.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + signer.setSigningCredential(credentials); + return signer; + + } +} 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 new file mode 100644 index 00000000..707f12e2 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/QAALevelVerifier.java @@ -0,0 +1,41 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.utils; + +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; + +/** + * @author tlenz + * + */ +public class QAALevelVerifier { + + private static final Logger log = LoggerFactory.getLogger(QAALevelVerifier.class); + + public static void verifyQAALevel(String qaaAuth, String qaaRequest) throws QAANotAllowedException { + + if (EAAFConstants.EIDAS_QAA_LOW.equals(qaaRequest) && + (EAAFConstants.EIDAS_QAA_LOW.equals(qaaAuth) || + EAAFConstants.EIDAS_QAA_SUBSTANTIAL.equals(qaaAuth) || + EAAFConstants.EIDAS_QAA_HIGH.equals(qaaAuth)) + ) + log.debug("Requesed LoA fits LoA from authentication. Continue auth process ... "); + + else if (EAAFConstants.EIDAS_QAA_SUBSTANTIAL.equals(qaaRequest) && + (EAAFConstants.EIDAS_QAA_SUBSTANTIAL.equals(qaaAuth) || + EAAFConstants.EIDAS_QAA_HIGH.equals(qaaAuth)) + ) + log.debug("Requesed LoA fits LoA from authentication. Continue auth process ... "); + + else if (EAAFConstants.EIDAS_QAA_HIGH.equals(qaaRequest) && EAAFConstants.EIDAS_QAA_HIGH.equals(qaaAuth)) + log.debug("Requesed LoA fits LoA from authentication. Continue auth process ... "); + + else + throw new QAANotAllowedException(qaaAuth, qaaRequest); + + } +} 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 new file mode 100644 index 00000000..1da3fea3 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/SAML2Utils.java @@ -0,0 +1,125 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.utils; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.util.List; + +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.opensaml.Configuration; +import org.opensaml.common.impl.SecureRandomIdentifierGenerator; +import org.opensaml.saml2.core.Status; +import org.opensaml.saml2.core.StatusCode; +import org.opensaml.saml2.metadata.AssertionConsumerService; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.opensaml.ws.soap.soap11.Body; +import org.opensaml.ws.soap.soap11.Envelope; +import org.opensaml.xml.XMLObject; +import org.opensaml.xml.XMLObjectBuilderFactory; +import org.opensaml.xml.io.Marshaller; +import org.opensaml.xml.io.MarshallingException; +import org.w3c.dom.Document; + +import at.gv.egiz.eaaf.core.impl.utils.Random; + +public class SAML2Utils { + + public static <T> T createSAMLObject(final Class<T> clazz) { + try { + XMLObjectBuilderFactory builderFactory = Configuration + .getBuilderFactory(); + + QName defaultElementName = (QName) clazz.getDeclaredField( + "DEFAULT_ELEMENT_NAME").get(null); + @SuppressWarnings("unchecked") + T object = (T) builderFactory.getBuilder(defaultElementName) + .buildObject(defaultElementName); + return object; + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } + + public static String getSecureIdentifier() { + return "_".concat(Random.nextHexRandom16()); + + /*Bug-Fix: There are open problems with RandomNumberGenerator via Java SPI and Java JDK 8.121 + * Generation of a 16bit Random identifier FAILES with an Caused by: java.lang.ArrayIndexOutOfBoundsException + * Caused by: java.lang.ArrayIndexOutOfBoundsException + at iaik.security.random.o.engineNextBytes(Unknown Source) + at iaik.security.random.SecRandomSpi.engineNextBytes(Unknown Source) + at java.security.SecureRandom.nextBytes(SecureRandom.java:468) + at org.opensaml.common.impl.SecureRandomIdentifierGenerator.generateIdentifier(SecureRandomIdentifierGenerator.java:62) + at org.opensaml.common.impl.SecureRandomIdentifierGenerator.generateIdentifier(SecureRandomIdentifierGenerator.java:56) + at at.gv.egovernment.moa.id.protocols.pvp2x.utils.SAML2Utils.getSecureIdentifier(SAML2Utils.java:69) + */ + //return idGenerator.generateIdentifier(); + } + + private static SecureRandomIdentifierGenerator idGenerator; + + private static DocumentBuilder builder; + static { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + try { + builder = factory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + idGenerator = new SecureRandomIdentifierGenerator(); + } catch(NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + public static Document asDOMDocument(XMLObject object) throws IOException, + MarshallingException, TransformerException { + Document document = builder.newDocument(); + Marshaller out = Configuration.getMarshallerFactory().getMarshaller( + object); + out.marshall(object, document); + return document; + } + + public static Status getSuccessStatus() { + Status status = SAML2Utils.createSAMLObject(Status.class); + StatusCode statusCode = SAML2Utils.createSAMLObject(StatusCode.class); + statusCode.setValue(StatusCode.SUCCESS_URI); + status.setStatusCode(statusCode); + return status; + } + + public static int getDefaultAssertionConsumerServiceIndex(SPSSODescriptor spSSODescriptor) { + + List<AssertionConsumerService> assertionConsumerList = spSSODescriptor.getAssertionConsumerServices(); + + for (AssertionConsumerService el : assertionConsumerList) { + if (el.isDefault()) + return el.getIndex(); + + } + + return 0; + } + + public static Envelope buildSOAP11Envelope(XMLObject payload) { + XMLObjectBuilderFactory bf = Configuration.getBuilderFactory(); + Envelope envelope = (Envelope) bf.getBuilder(Envelope.DEFAULT_ELEMENT_NAME).buildObject(Envelope.DEFAULT_ELEMENT_NAME); + Body body = (Body) bf.getBuilder(Body.DEFAULT_ELEMENT_NAME).buildObject(Body.DEFAULT_ELEMENT_NAME); + + body.getUnknownXMLObjects().add(payload); + envelope.setBody(body); + + return envelope; + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/EAAFURICompare.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/EAAFURICompare.java new file mode 100644 index 00000000..4d0963cb --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/EAAFURICompare.java @@ -0,0 +1,36 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.validation; + +import org.opensaml.common.binding.decoding.URIComparator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class EAAFURICompare implements URIComparator { + private static final Logger log = LoggerFactory.getLogger(EAAFURICompare.class); + + private String serviceURL = ""; + + /** + * + * + * @param serviceURL public URL of the PVP S-Profile endpoint + */ + public EAAFURICompare(String serviceURL) { + this.serviceURL = serviceURL; + } + + public boolean compare(String uri1, String uri2) { + if (this.serviceURL.equals(uri1)) + return true; + + else { + log.warn("PVP request destination-endpoint: " + uri1 + + " does not match to IDP endpoint:" + serviceURL); + return false; + + } + } + +} 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 new file mode 100644 index 00000000..529f1ab6 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/TrustEngineFactory.java @@ -0,0 +1,41 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.validation; + +import java.util.ArrayList; +import java.util.List; + +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.security.MetadataCredentialResolver; +import org.opensaml.xml.security.keyinfo.BasicProviderKeyInfoCredentialResolver; +import org.opensaml.xml.security.keyinfo.KeyInfoCredentialResolver; +import org.opensaml.xml.security.keyinfo.KeyInfoProvider; +import org.opensaml.xml.security.keyinfo.provider.DSAKeyValueProvider; +import org.opensaml.xml.security.keyinfo.provider.InlineX509DataProvider; +import org.opensaml.xml.security.keyinfo.provider.RSAKeyValueProvider; +import org.opensaml.xml.signature.SignatureTrustEngine; +import org.opensaml.xml.signature.impl.ExplicitKeySignatureTrustEngine; + +public class TrustEngineFactory { + + public static SignatureTrustEngine getSignatureKnownKeysTrustEngine(MetadataProvider provider) { + MetadataCredentialResolver resolver; + + resolver = new MetadataCredentialResolver(provider); + + List<KeyInfoProvider> keyInfoProvider = new ArrayList<KeyInfoProvider>(); + keyInfoProvider.add(new DSAKeyValueProvider()); + keyInfoProvider.add(new RSAKeyValueProvider()); + keyInfoProvider.add(new InlineX509DataProvider()); + + KeyInfoCredentialResolver keyInfoResolver = new BasicProviderKeyInfoCredentialResolver( + keyInfoProvider); + + ExplicitKeySignatureTrustEngine engine = new ExplicitKeySignatureTrustEngine( + resolver, keyInfoResolver); + + return engine; + + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/AbstractMetadataSignatureFilter.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/AbstractMetadataSignatureFilter.java new file mode 100644 index 00000000..286c1999 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/AbstractMetadataSignatureFilter.java @@ -0,0 +1,128 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.opensaml.saml2.metadata.EntitiesDescriptor; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.provider.MetadataFilter; +import org.opensaml.xml.XMLObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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.SignatureValidationException; + +public abstract class AbstractMetadataSignatureFilter implements MetadataFilter { + private static final Logger log = LoggerFactory.getLogger(AbstractMetadataSignatureFilter.class); + + public void doFilter(XMLObject metadata) throws SignatureValidationException { + try { + if (metadata instanceof EntitiesDescriptor) { + EntitiesDescriptor entitiesDescriptor = (EntitiesDescriptor) metadata; + if(entitiesDescriptor.getSignature() == null) { + throw new PVP2MetadataException("Root element of metadata file has to be signed", null); + } + processEntitiesDescriptor(entitiesDescriptor); + + + if (entitiesDescriptor.getEntityDescriptors().size() == 0) { + throw new PVP2MetadataException("No valid entity in metadata " + + entitiesDescriptor.getName() + ". Metadata is not loaded.", null); + } + + + } else if (metadata instanceof EntityDescriptor) { + EntityDescriptor entityDescriptor = (EntityDescriptor) metadata; + processEntityDescriptorr(entityDescriptor); + + } else { + throw new PVP2MetadataException("Invalid Metadata file Root element is no EntitiesDescriptor", null); + } + + + + log.info("Metadata signature policy check done OK"); + } catch (EAAFException e) { + log.warn("Metadata signature policy check FAILED.", e); + throw new SignatureValidationException(e); + } + } + + /** + * Signature verification of a SAML2 EntityDescriptor element + * + * @param desc + * @throws PVP2MetadataException if the signature is not valid or can not verified + */ + protected abstract void verify(EntityDescriptor desc) throws PVP2MetadataException; + + /** + * Signature verification of a SAML2 EntitiesDescriptor element + * + * @param desc + * @throws PVP2MetadataException if the signature is not valid or can not verified + */ + protected abstract void verify(EntitiesDescriptor desc) throws PVP2MetadataException; + + /** + * Verify a EntityDescriptor element of an EntitiesDescriptor + * + * @param entity EntityDescriptor to verify + * @param desc Full EntitiesDescriptor that contains the EntityDescriptor + * @throws PVP2MetadataException + */ + protected abstract void verify(EntityDescriptor entity, EntitiesDescriptor desc) throws PVP2MetadataException; + + + private void processEntityDescriptorr(EntityDescriptor desc) throws EAAFException { + verify(desc); + + } + + private void processEntitiesDescriptor(EntitiesDescriptor desc) throws EAAFException { + Iterator<EntitiesDescriptor> entID = desc.getEntitiesDescriptors().iterator(); + + if(desc.getSignature() != null) { + verify(desc); + + } + + while(entID.hasNext()) { + processEntitiesDescriptor(entID.next()); + } + + Iterator<EntityDescriptor> entIT = desc.getEntityDescriptors().iterator(); + List<EntityDescriptor> verifiedEntIT = new ArrayList<EntityDescriptor>(); + + //check every Entity + while(entIT.hasNext()) { + EntityDescriptor entity = entIT.next(); + log.debug("Validate metadata for entityID: " + entity.getEntityID() + " ..... "); + try { + verify(entity, desc); + + //add entity to verified entity-list + verifiedEntIT.add(entity); + log.debug("Metadata for entityID: " + entity.getEntityID() + " valid"); + + + } catch (Exception e) { + //remove entity of signature can not be verified. + log.info("Entity " + entity.getEntityID() + " is removed from metadata " + + desc.getName() + ". Entity verification error: " + e.getMessage()); + + } + + } + + //set only verified entity elements + desc.getEntityDescriptors().clear(); + desc.getEntityDescriptors().addAll(verifiedEntIT); + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/PVPEntityCategoryFilter.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/PVPEntityCategoryFilter.java new file mode 100644 index 00000000..e29fb145 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/PVPEntityCategoryFilter.java @@ -0,0 +1,211 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata; + +import java.util.ArrayList; +import java.util.List; + +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.common.Extensions; +import org.opensaml.saml2.core.Attribute; +import org.opensaml.saml2.metadata.AttributeConsumingService; +import org.opensaml.saml2.metadata.EntitiesDescriptor; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.LocalizedString; +import org.opensaml.saml2.metadata.RequestedAttribute; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.opensaml.saml2.metadata.ServiceName; +import org.opensaml.saml2.metadata.provider.FilterException; +import org.opensaml.saml2.metadata.provider.MetadataFilter; +import org.opensaml.samlext.saml2mdattr.EntityAttributes; +import org.opensaml.xml.XMLObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.eaaf.core.impl.data.Trible; +import at.gv.egiz.eaaf.modules.pvp2.PVPConstants; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2MetadataException; +import at.gv.egiz.eaaf.modules.pvp2.impl.builder.PVPAttributeBuilder; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; + +/** + * @author tlenz + * + */ +public class PVPEntityCategoryFilter implements MetadataFilter { + private static final Logger log = LoggerFactory.getLogger(PVPEntityCategoryFilter.class); + + private boolean isUsed = false; + + /** + * Filter to map PVP EntityCategories into a set of single PVP attributes + * + * @param isUsed if true PVP EntityCategories are mapped, otherwise they are ignored + * + */ + public PVPEntityCategoryFilter(boolean isUsed) { + this.isUsed = isUsed; + } + + + /* (non-Javadoc) + * @see org.opensaml.saml2.metadata.provider.MetadataFilter#doFilter(org.opensaml.xml.XMLObject) + */ + @Override + public void doFilter(XMLObject metadata) throws FilterException { + + if (isUsed) { + log.trace("Map PVP EntityCategory to single PVP Attributes ... "); + String entityId = null; + try { + if (metadata instanceof EntitiesDescriptor) { + log.trace("Find EnitiesDescriptor ... "); + EntitiesDescriptor entitiesDesc = (EntitiesDescriptor) metadata; + if (entitiesDesc.getEntityDescriptors() != null) { + for (EntityDescriptor el : entitiesDesc.getEntityDescriptors()) + resolveEntityCategoriesToAttributes(el); + + } + + } else if (metadata instanceof EntityDescriptor) { + log.trace("Find EntityDescriptor"); + resolveEntityCategoriesToAttributes((EntityDescriptor)metadata); + + + } else + throw new PVP2MetadataException("Invalid Metadata file Root element is no Entities- or EntityDescriptor", null); + + + + } catch (Exception e) { + log.warn("SAML2 Metadata processing FAILED: Can not resolve EntityCategories for metadata: " + entityId, e); + + } + + } else + log.trace("Filter to map PVP EntityCategory to single PVP Attributes is deactivated"); + + } + + private void resolveEntityCategoriesToAttributes(EntityDescriptor metadata) { + log.debug("Resolving EntityCategorie for Entity: " + metadata.getEntityID() + " ..."); + Extensions extensions = metadata.getExtensions(); + if (extensions != null) { + List<XMLObject> listOfExt = extensions.getUnknownXMLObjects(); + if (listOfExt != null && !listOfExt.isEmpty()) { + log.trace("Find #" + listOfExt.size() + " 'Extension' elements "); + for (XMLObject el : listOfExt) { + log.trace("Find ExtensionElement: " + el.getElementQName().toString()); + if (el instanceof EntityAttributes) { + EntityAttributes entityAttrElem = (EntityAttributes)el; + if (entityAttrElem.getAttributes() != null) { + log.trace("Find EntityAttributes. Start attribute processing ..."); + for (Attribute entityAttr : entityAttrElem.getAttributes()) { + if (entityAttr.getName().equals(PVPConstants.ENTITY_CATEGORY_ATTRIBITE)) { + if (!entityAttr.getAttributeValues().isEmpty()) { + String entityAttrValue = entityAttr.getAttributeValues().get(0).getDOM().getTextContent(); + if (PVPConstants.EGOVTOKEN.equals(entityAttrValue)) { + log.debug("Find 'EGOVTOKEN' EntityAttribute. Adding single pvp attributes ... "); + addAttributesToEntityDescriptor(metadata, + buildAttributeList(PVPConstants.EGOVTOKEN_PVP_ATTRIBUTES), + entityAttrValue); + + + } else if (PVPConstants.CITIZENTOKEN.equals(entityAttrValue)) { + log.debug("Find 'CITIZENTOKEN' EntityAttribute. Adding single pvp attributes ... "); + addAttributesToEntityDescriptor(metadata, + buildAttributeList(PVPConstants.CITIZENTOKEN_PVP_ATTRIBUTES), + entityAttrValue); + + } else + log.info("EntityAttributeValue: " + entityAttrValue + " is UNKNOWN!"); + + } else + log.info("EntityAttribute: No attribute value"); + + } else + log.info("EntityAttribute: " + entityAttr.getName() + " is NOT supported"); + + } + + } else + log.info("Can NOT resolve EntityAttributes! Reason: Only EntityAttributes are supported!"); + + } + } + + } else + log.trace("'Extension' element is 'null' or empty"); + + } else + log.trace("No 'Extension' element found"); + + } + + /** + * @param metadata + * @param attrList + */ + private void addAttributesToEntityDescriptor(EntityDescriptor metadata, List<RequestedAttribute> attrList, String entityAttr) { + SPSSODescriptor spSSODesc = metadata.getSPSSODescriptor(SAMLConstants.SAML20P_NS); + if (spSSODesc != null) { + if (spSSODesc.getAttributeConsumingServices() == null || + spSSODesc.getAttributeConsumingServices().isEmpty()) { + log.trace("No 'AttributeConsumingServices' found. Added it ..."); + + AttributeConsumingService attributeService = SAML2Utils.createSAMLObject(AttributeConsumingService.class); + attributeService.setIndex(0); + attributeService.setIsDefault(true); + ServiceName serviceName = SAML2Utils.createSAMLObject(ServiceName.class); + serviceName.setName(new LocalizedString("Default Service", "en")); + attributeService.getNames().add(serviceName); + + if (attrList != null && !attrList.isEmpty()) { + attributeService.getRequestAttributes().addAll(attrList); + log.info("Add " + attrList.size() + " attributes for 'EntityAttribute': " + entityAttr); + + } + + spSSODesc.getAttributeConsumingServices().add(attributeService); + + } else { + log.debug("Find 'AttributeConsumingServices'. Starting updating process ... "); + for (AttributeConsumingService el : spSSODesc.getAttributeConsumingServices()) { + log.debug("Update 'AttributeConsumingService' with Index: " + el.getIndex()); + + //load currently requested attributes + List<String> currentlyReqAttr = new ArrayList<String>(); + for (RequestedAttribute reqAttr : el.getRequestAttributes()) + currentlyReqAttr.add(reqAttr.getName()); + + + //check against EntityAttribute List + for (RequestedAttribute entityAttrListEl : attrList) { + if (!currentlyReqAttr.contains(entityAttrListEl.getName())) { + el.getRequestAttributes().add(entityAttrListEl); + + } else + log.debug("'AttributeConsumingService' already contains attr: " + entityAttrListEl.getName()); + + } + + } + + } + + } else + log.info("Can ONLY add 'EntityAttributes' to 'SPSSODescriptor'"); + + } + + private List<RequestedAttribute> buildAttributeList(List<Trible<String, String, Boolean>> attrSet) { + List<RequestedAttribute> requestedAttributes = new ArrayList<RequestedAttribute>(); + for (Trible<String, String, Boolean> el : attrSet) + requestedAttributes.add(PVPAttributeBuilder.buildReqAttribute(el.getFirst(), el.getSecond(), el.getThird())); + + return requestedAttributes; + + + } + +} 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 new file mode 100644 index 00000000..a7dddd32 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/SchemaValidationFilter.java @@ -0,0 +1,81 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata; + +import javax.xml.transform.dom.DOMSource; +import javax.xml.validation.Schema; +import javax.xml.validation.Validator; + +import org.opensaml.common.xml.SAMLSchemaBuilder; +import org.opensaml.saml2.metadata.provider.FilterException; +import org.opensaml.saml2.metadata.provider.MetadataFilter; +import org.opensaml.xml.XMLObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +import at.gv.egiz.eaaf.modules.pvp2.exception.SchemaValidationException; + +/** + * @author tlenz + * + */ +public class SchemaValidationFilter implements MetadataFilter { + private static final Logger log = LoggerFactory.getLogger(SchemaValidationFilter.class); + private boolean isActive = true; + + public SchemaValidationFilter() { + } + + /** + * + */ + public SchemaValidationFilter(boolean useSchemaValidation) { + this.isActive = useSchemaValidation; + } + + + /* (non-Javadoc) + * @see org.opensaml.saml2.metadata.provider.MetadataFilter#doFilter(org.opensaml.xml.XMLObject) + */ + @Override + public void doFilter(XMLObject arg0) throws FilterException { + + String errString = null; + + if (isActive) { + try { + Schema test = SAMLSchemaBuilder.getSAML11Schema(); + Validator val = test.newValidator(); + DOMSource source = new DOMSource(arg0.getDOM()); + val.validate(source); + log.info("Metadata Schema validation check done OK"); + return; + + } catch (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 (Exception 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(); + + } + + throw new FilterException( + new SchemaValidationException("Metadata Schema validation FAILED with message: "+ errString, null)); + + } 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/verification/AbstractRequestSignedSecurityPolicyRule.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/AbstractRequestSignedSecurityPolicyRule.java new file mode 100644 index 00000000..32615d64 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/AbstractRequestSignedSecurityPolicyRule.java @@ -0,0 +1,171 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.verification; + +import javax.xml.namespace.QName; +import javax.xml.transform.dom.DOMSource; +import javax.xml.validation.Schema; +import javax.xml.validation.Validator; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.common.SignableSAMLObject; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.common.xml.SAMLSchemaBuilder; +import org.opensaml.security.MetadataCriteria; +import org.opensaml.security.SAMLSignatureProfileValidator; +import org.opensaml.ws.message.MessageContext; +import org.opensaml.ws.security.SecurityPolicyException; +import org.opensaml.ws.security.SecurityPolicyRule; +import org.opensaml.xml.XMLObject; +import org.opensaml.xml.security.CriteriaSet; +import org.opensaml.xml.security.credential.UsageType; +import org.opensaml.xml.security.criteria.EntityIDCriteria; +import org.opensaml.xml.security.criteria.UsageCriteria; +import org.opensaml.xml.signature.SignatureTrustEngine; +import org.opensaml.xml.validation.ValidationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import at.gv.egiz.eaaf.modules.pvp2.exception.SchemaValidationException; + +/** + * @author tlenz + * + */ +public abstract class AbstractRequestSignedSecurityPolicyRule implements SecurityPolicyRule { + + private static final Logger log = LoggerFactory.getLogger(AbstractRequestSignedSecurityPolicyRule.class); + + + private SignatureTrustEngine trustEngine = null; + private QName peerEntityRole = null; + /** + * @param peerEntityRole + * + */ + public AbstractRequestSignedSecurityPolicyRule(SignatureTrustEngine trustEngine, QName peerEntityRole) { + this.trustEngine = trustEngine; + this.peerEntityRole = peerEntityRole; + + } + + + /** + * Reload the PVP metadata for a given entity + * + * @param entityID for which the metadata should be refreshed. + * @return true if the refresh was successful, otherwise false + */ + protected abstract boolean refreshMetadataProvider(String entityID); + + + protected abstract SignableSAMLObject getSignedSAMLObject(XMLObject inboundData); + + /* (non-Javadoc) + * @see org.opensaml.ws.security.SecurityPolicyRule#evaluate(org.opensaml.ws.message.MessageContext) + */ + @Override + public void evaluate(MessageContext context) throws SecurityPolicyException { + try { + verifySignature(context); + + } catch (SecurityPolicyException e) { + if (StringUtils.isEmpty(context.getInboundMessageIssuer())) { + throw e; + + } + log.debug("PVP2X message validation FAILED. Reload metadata for entityID: " + context.getInboundMessageIssuer()); + if (!refreshMetadataProvider(context.getInboundMessageIssuer())) + throw e; + + else { + log.trace("PVP2X metadata reload finished. Check validate message again."); + verifySignature(context); + + } + log.trace("Second PVP2X message validation finished"); + + } + + + } + + private void verifySignature(MessageContext context) throws SecurityPolicyException { + SignableSAMLObject samlObj = getSignedSAMLObject(context.getInboundMessage()); + if (samlObj != null && samlObj.getSignature() != null) { + + SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); + try { + profileValidator.validate(samlObj.getSignature()); + performSchemaValidation(samlObj.getDOM()); + + } catch (ValidationException e) { + log.warn("Signature is not conform to SAML signature profile", e); + throw new SecurityPolicyException("Signature is not conform to SAML signature profile"); + + } catch (SchemaValidationException e) { + log.warn("Signature is not conform to SAML signature profile", e); + throw new SecurityPolicyException("Signature is not conform to SAML signature profile"); + + } + + + + CriteriaSet criteriaSet = new CriteriaSet(); + criteriaSet.add( new EntityIDCriteria(context.getInboundMessageIssuer()) ); + criteriaSet.add( new MetadataCriteria(peerEntityRole, SAMLConstants.SAML20P_NS) ); + criteriaSet.add( new UsageCriteria(UsageType.SIGNING) ); + + try { + if (!trustEngine.validate(samlObj.getSignature(), criteriaSet)) { + throw new SecurityPolicyException("Signature validation FAILED."); + + } + log.debug("PVP message signature valid."); + + } catch (org.opensaml.xml.security.SecurityException e) { + log.info("PVP2x message signature validation FAILED. Message:" + e.getMessage()); + throw new SecurityPolicyException("Signature validation FAILED."); + + } + + } else { + throw new SecurityPolicyException("PVP Message is not signed."); + + } + + } + + private void performSchemaValidation(Element source) throws SchemaValidationException { + + String err = null; + try { + Schema test = SAMLSchemaBuilder.getSAML11Schema(); + Validator val = test.newValidator(); + val.validate(new DOMSource(source)); + log.debug("Schema validation check done OK"); + return; + + } catch (SAXException e) { + err = e.getMessage(); + if (log.isDebugEnabled() || log.isTraceEnabled()) + log.warn("Schema validation FAILED with exception:", e); + else + log.warn("Schema validation FAILED with message: "+ e.getMessage()); + + } catch (Exception e) { + err = e.getMessage(); + if (log.isDebugEnabled() || log.isTraceEnabled()) + log.warn("Schema validation FAILED with exception:", e); + else + log.warn("Schema validation FAILED with message: "+ e.getMessage()); + + } + + throw new SchemaValidationException("pvp2.22", new Object[]{err}); + + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/AuthnRequestValidator.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/AuthnRequestValidator.java new file mode 100644 index 00000000..86c7f309 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/AuthnRequestValidator.java @@ -0,0 +1,45 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.verification; + +import org.opensaml.saml2.core.AuthnRequest; +import org.opensaml.saml2.core.NameID; +import org.opensaml.saml2.core.NameIDPolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.eaaf.core.exceptions.AuthnRequestValidatorException; +import at.gv.egiz.eaaf.modules.pvp2.exception.NameIDFormatNotSupportedException; + + +/** + * @author tlenz + * + */ +public class AuthnRequestValidator { + private static final Logger log = LoggerFactory.getLogger(AuthnRequestValidator.class); + + public static void validate(AuthnRequest req) throws AuthnRequestValidatorException{ + + //validate NameIDPolicy + NameIDPolicy nameIDPolicy = req.getNameIDPolicy(); + if (nameIDPolicy != null) { + String nameIDFormat = nameIDPolicy.getFormat(); + if (nameIDFormat != null) { + if ( !(NameID.TRANSIENT.equals(nameIDFormat) || + NameID.PERSISTENT.equals(nameIDFormat) || + NameID.UNSPECIFIED.equals(nameIDFormat)) ) { + + throw new NameIDFormatNotSupportedException(nameIDFormat); + + } + + } else + log.trace("Find NameIDPolicy, but NameIDFormat is 'null'"); + } else + log.trace("AuthnRequest includes no 'NameIDPolicy'"); + + + + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/PVPAuthRequestSignedRole.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/PVPAuthRequestSignedRole.java new file mode 100644 index 00000000..458d28c2 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/PVPAuthRequestSignedRole.java @@ -0,0 +1,29 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.verification; + +import org.opensaml.common.binding.SAMLMessageContext; +import org.opensaml.saml2.binding.security.SAML2AuthnRequestsSignedRule; +import org.opensaml.ws.transport.http.HTTPInTransport; +import org.opensaml.xml.util.DatatypeHelper; + +/** + * @author tlenz + * + */ +public class PVPAuthRequestSignedRole extends SAML2AuthnRequestsSignedRule { + + @Override + protected boolean isMessageSigned(SAMLMessageContext messageContext) { + // This handles HTTP-Redirect and HTTP-POST-SimpleSign bindings. + HTTPInTransport inTransport = (HTTPInTransport) messageContext.getInboundMessageTransport(); + String sigParam = inTransport.getParameterValue("Signature"); + boolean isSigned = !DatatypeHelper.isEmpty(sigParam); + + String sigAlgParam = inTransport.getParameterValue("SigAlg"); + boolean isSigAlgExists = !DatatypeHelper.isEmpty(sigAlgParam); + + return isSigned && isSigAlgExists; + + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/PVPSignedRequestPolicyRule.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/PVPSignedRequestPolicyRule.java new file mode 100644 index 00000000..af6c864e --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/PVPSignedRequestPolicyRule.java @@ -0,0 +1,60 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.verification; + +import javax.xml.namespace.QName; + +import org.opensaml.common.SignableSAMLObject; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.xml.XMLObject; +import org.opensaml.xml.signature.SignatureTrustEngine; + +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IRefreshableMetadataProvider; + +/** + * @author tlenz + * + */ +public class PVPSignedRequestPolicyRule extends + AbstractRequestSignedSecurityPolicyRule { + + private IRefreshableMetadataProvider metadataProvider = null; + + /** + * @param metadataProvider + * @param trustEngine + * @param peerEntityRole + */ + public PVPSignedRequestPolicyRule(MetadataProvider metadataProvider, SignatureTrustEngine trustEngine, + QName peerEntityRole) { + super(trustEngine, peerEntityRole); + if (metadataProvider instanceof IRefreshableMetadataProvider) + this.metadataProvider = (IRefreshableMetadataProvider) metadataProvider; + + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.validation.AbstractRequestSignedSecurityPolicyRule#refreshMetadataProvider(java.lang.String) + */ + @Override + protected boolean refreshMetadataProvider(String entityID) { + if (metadataProvider != null) + return metadataProvider.refreshMetadataProvider(entityID); + + return false; + + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.protocols.pvp2x.validation.AbstractRequestSignedSecurityPolicyRule#getSignedSAMLObject(org.opensaml.xml.XMLObject) + */ + @Override + protected SignableSAMLObject getSignedSAMLObject(XMLObject inboundData) { + if (inboundData instanceof SignableSAMLObject) + return (SignableSAMLObject) inboundData; + + else + return null; + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/SAMLVerificationEngine.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/SAMLVerificationEngine.java new file mode 100644 index 00000000..fe147ea7 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/verification/SAMLVerificationEngine.java @@ -0,0 +1,183 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.impl.verification; + +import javax.xml.namespace.QName; +import javax.xml.transform.dom.DOMSource; +import javax.xml.validation.Schema; +import javax.xml.validation.Validator; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.common.xml.SAMLSchemaBuilder; +import org.opensaml.saml2.core.RequestAbstractType; +import org.opensaml.saml2.core.StatusResponseType; +import org.opensaml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.opensaml.security.MetadataCriteria; +import org.opensaml.security.SAMLSignatureProfileValidator; +import org.opensaml.xml.security.CriteriaSet; +import org.opensaml.xml.security.credential.UsageType; +import org.opensaml.xml.security.criteria.EntityIDCriteria; +import org.opensaml.xml.security.criteria.UsageCriteria; +import org.opensaml.xml.signature.SignatureTrustEngine; +import org.opensaml.xml.validation.ValidationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import at.gv.egiz.eaaf.core.exceptions.InvalidProtocolRequestException; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPVPMetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IRefreshableMetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.exception.SchemaValidationException; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.InboundMessage; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.PVPSProfileRequest; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.PVPSProfileResponse; + +@Service("SAMLVerificationEngine") +public class SAMLVerificationEngine { + private static final Logger log = LoggerFactory.getLogger(SAMLVerificationEngine.class); + + + @Autowired(required=true) IPVPMetadataProvider metadataProvider; + + public void verify(InboundMessage msg, SignatureTrustEngine sigTrustEngine ) throws org.opensaml.xml.security.SecurityException, Exception { + try { + if (msg instanceof PVPSProfileRequest && + ((PVPSProfileRequest)msg).getSamlRequest() instanceof RequestAbstractType) + verifyRequest(((RequestAbstractType)((PVPSProfileRequest)msg).getSamlRequest()), sigTrustEngine); + + else + verifyIDPResponse(((PVPSProfileResponse)msg).getResponse(), sigTrustEngine); + + } catch (InvalidProtocolRequestException e) { + if (StringUtils.isEmpty(msg.getEntityID())) { + throw e; + + } + log.debug("PVP2X message validation FAILED. Relead metadata for entityID: " + msg.getEntityID()); + + if (metadataProvider == null || + !(metadataProvider instanceof IRefreshableMetadataProvider) || + !((IRefreshableMetadataProvider)metadataProvider).refreshMetadataProvider(msg.getEntityID())) + throw e; + + else { + log.trace("PVP2X metadata reload finished. Check validate message again."); + + if (msg instanceof PVPSProfileRequest && + ((PVPSProfileRequest)msg).getSamlRequest() instanceof RequestAbstractType) + verifyRequest(((RequestAbstractType)((PVPSProfileRequest)msg).getSamlRequest()), sigTrustEngine); + + else + verifyIDPResponse(((PVPSProfileResponse)msg).getResponse(), sigTrustEngine); + + } + log.trace("Second PVP2X message validation finished"); + } + } + + public void verifySLOResponse(StatusResponseType samlObj, SignatureTrustEngine sigTrustEngine ) throws InvalidProtocolRequestException { + verifyResponse(samlObj, sigTrustEngine, SPSSODescriptor.DEFAULT_ELEMENT_NAME); + + } + + public void verifyIDPResponse(StatusResponseType samlObj, SignatureTrustEngine sigTrustEngine) throws InvalidProtocolRequestException{ + verifyResponse(samlObj, sigTrustEngine, IDPSSODescriptor.DEFAULT_ELEMENT_NAME); + + } + + private void verifyResponse(StatusResponseType samlObj, SignatureTrustEngine sigTrustEngine, QName defaultElementName) throws InvalidProtocolRequestException{ + SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); + try { + profileValidator.validate(samlObj.getSignature()); + performSchemaValidation(samlObj.getDOM()); + + } catch (ValidationException e) { + log.warn("Signature is not conform to SAML signature profile", e); + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}, "Signature is not conform to SAML signature profile"); + + } catch (SchemaValidationException e) { + throw new InvalidProtocolRequestException("pvp2.22", new Object[] {e.getMessage()}, "SAML response does not fit XML scheme"); + + } + + CriteriaSet criteriaSet = new CriteriaSet(); + criteriaSet.add( new EntityIDCriteria(samlObj.getIssuer().getValue()) ); + criteriaSet.add( new MetadataCriteria(defaultElementName, SAMLConstants.SAML20P_NS) ); + criteriaSet.add( new UsageCriteria(UsageType.SIGNING) ); + + try { + if (!sigTrustEngine.validate(samlObj.getSignature(), criteriaSet)) { + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}, "Signature verification FAILED on SAML response"); + } + } catch (org.opensaml.xml.security.SecurityException e) { + log.warn("PVP2x message signature validation FAILED.", e); + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}, "Signature verification FAILED on SAML response"); + } + } + + private void verifyRequest(RequestAbstractType samlObj, SignatureTrustEngine sigTrustEngine ) throws InvalidProtocolRequestException { + SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); + try { + profileValidator.validate(samlObj.getSignature()); + performSchemaValidation(samlObj.getDOM()); + + } catch (ValidationException e) { + log.warn("Signature is not conform to SAML signature profile", e); + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}, "Scheme validation FAILED on SAML request"); + + } catch (SchemaValidationException e) { + throw new InvalidProtocolRequestException("pvp2.22", new Object[] {e.getMessage()}, "Scheme verification FAILED on SAML request"); + + } + + CriteriaSet criteriaSet = new CriteriaSet(); + criteriaSet.add( new EntityIDCriteria(samlObj.getIssuer().getValue()) ); + criteriaSet.add( new MetadataCriteria(SPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS) ); + criteriaSet.add( new UsageCriteria(UsageType.SIGNING) ); + + try { + if (!sigTrustEngine.validate(samlObj.getSignature(), criteriaSet)) { + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}, "Signature verification FAILED on SAML request"); + } + } catch (org.opensaml.xml.security.SecurityException e) { + log.warn("PVP2x message signature validation FAILED.", e); + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}, "Signature verification FAILED on SAML request"); + } + } + + protected void performSchemaValidation(Element source) throws SchemaValidationException { + + String err = null; + try { + Schema test = SAMLSchemaBuilder.getSAML11Schema(); + Validator val = test.newValidator(); + val.validate(new DOMSource(source)); + log.debug("Schema validation check done OK"); + return; + + } catch (SAXException e) { + err = e.getMessage(); + if (log.isDebugEnabled() || log.isTraceEnabled()) + log.warn("Schema validation FAILED with exception:", e); + else + log.warn("Schema validation FAILED with message: "+ e.getMessage()); + + } catch (Exception e) { + err = e.getMessage(); + if (log.isDebugEnabled() || log.isTraceEnabled()) + log.warn("Schema validation FAILED with exception:", e); + else + log.warn("Schema validation FAILED with message: "+ e.getMessage()); + + } + + throw new SchemaValidationException("pvp2.22", new Object[]{err}); + + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/resources/META-INF/services/at.gv.egiz.components.spring.api.SpringResourceProvider b/eaaf_modules/eaaf_module_pvp2_core/src/main/resources/META-INF/services/at.gv.egiz.components.spring.api.SpringResourceProvider new file mode 100644 index 00000000..9c60d724 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/resources/META-INF/services/at.gv.egiz.components.spring.api.SpringResourceProvider @@ -0,0 +1 @@ +at.gv.egiz.eaaf.modules.pvp2.PVP2SProfileCoreSpringResourceProvider
\ No newline at end of file diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/resources/eaaf_pvp.beans.xml b/eaaf_modules/eaaf_module_pvp2_core/src/main/resources/eaaf_pvp.beans.xml new file mode 100644 index 00000000..2c64b5f1 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/resources/eaaf_pvp.beans.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + --> + +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:context="http://www.springframework.org/schema/context" + xmlns:tx="http://www.springframework.org/schema/tx" + xmlns:aop="http://www.springframework.org/schema/aop" + xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> + + <bean id="PVPMetadataBuilder" + class="at.gv.egiz.eaaf.modules.pvp2.impl.builder.PVPMetadataBuilder" /> + + <bean id="PVPPOSTBinding" + class="at.gv.egiz.eaaf.modules.pvp2.impl.binding.PostBinding" /> + + <bean id="PVPRedirectBinding" + class="at.gv.egiz.eaaf.modules.pvp2.impl.binding.RedirectBinding" /> + + <bean id="PVPSOAPBinding" + class="at.gv.egiz.eaaf.modules.pvp2.impl.binding.SoapBinding" /> + + <bean id="SAMLVerificationEngine" + class="at.gv.egiz.eaaf.modules.pvp2.impl.verification.SAMLVerificationEngine" /> + +</beans>
\ No newline at end of file diff --git a/eaaf_modules/eaaf_module_pvp2_idp/pom.xml b/eaaf_modules/eaaf_module_pvp2_idp/pom.xml new file mode 100644 index 00000000..90b38119 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/pom.xml @@ -0,0 +1,71 @@ +<?xml version="1.0"?> +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>at.gv.egiz.eaaf</groupId> + <artifactId>eaaf_modules</artifactId> + <version>1.x</version> + </parent> + <artifactId>eaaf_module_pvp2_idp</artifactId> + <version>${egiz.eaaf.version}</version> + <name>eaaf_module_pvp2_core</name> + <url>http://maven.apache.org</url> + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + <dependencies> + <dependency> + <groupId>at.gv.egiz.eaaf</groupId> + <artifactId>eaaf_module_pvp2_core</artifactId> + <version>${egiz.eaaf.version}</version> + </dependency> + + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <scope>provided</scope> + </dependency> + + <!-- Testing --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <finalName>eaaf_module_pvp2_idp</finalName> + + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.7.0</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + + <!-- enable co-existence of testng and junit --> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <version>${surefire.version}</version> + <configuration> + <threadCount>1</threadCount> + <argLine>--add-modules java.xml.bind</argLine> + </configuration> + <dependencies> + <dependency> + <groupId>org.apache.maven.surefire</groupId> + <artifactId>surefire-junit47</artifactId> + <version>${surefire.version}</version> + </dependency> + </dependencies> + </plugin> + + </plugins> + </build> +</project> diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/META-INF/MANIFEST.MF b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 00000000..254272e1 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/PVP2SProfileIDPSpringResourceProvider.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/PVP2SProfileIDPSpringResourceProvider.java new file mode 100644 index 00000000..7a9ac92b --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/PVP2SProfileIDPSpringResourceProvider.java @@ -0,0 +1,30 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import at.gv.egiz.components.spring.api.SpringResourceProvider; + +public class PVP2SProfileIDPSpringResourceProvider implements SpringResourceProvider { + + @Override + public String getName() { + return "EAAF PVP2 S-Profile IDP SpringResourceProvider"; + } + + @Override + public String[] getPackagesToScan() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Resource[] getResourcesToLoad() { + ClassPathResource sl20AuthConfig = new ClassPathResource("/eaaf_pvp_idp.beans.xml", PVP2SProfileIDPSpringResourceProvider.class); + + return new Resource[] {sl20AuthConfig}; + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/api/builder/ISubjectNameIdGenerator.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/api/builder/ISubjectNameIdGenerator.java new file mode 100644 index 00000000..ac999ffc --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/api/builder/ISubjectNameIdGenerator.java @@ -0,0 +1,21 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.api.builder; + +import at.gv.egiz.eaaf.core.api.idp.IAuthData; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + +public interface ISubjectNameIdGenerator { + + /** + * Generates a SAML2 subjectNameId from authentication data + * + * @param authData Authentication data for the current pending request + * @param spConfig Service provider configuration + * @return Pair of subjectNameId and NameIdFormat + * @throws PVP2Exception + */ + public Pair<String, String> generateSubjectNameId(IAuthData authData, ISPConfiguration spConfig) throws PVP2Exception; +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/InvalidAssertionConsumerServiceException.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/InvalidAssertionConsumerServiceException.java new file mode 100644 index 00000000..d9ffa2f2 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/InvalidAssertionConsumerServiceException.java @@ -0,0 +1,30 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.exception; + +import org.opensaml.saml2.core.StatusCode; + +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + +public class InvalidAssertionConsumerServiceException extends PVP2Exception { + + public InvalidAssertionConsumerServiceException(int idx) { + super("pvp2.00", new Object[]{idx}); + this.statusCodeValue = StatusCode.REQUESTER_URI; + } + + /** + * + */ + public InvalidAssertionConsumerServiceException(String wrongURL) { + super("pvp2.23", new Object[]{wrongURL}); + this.statusCodeValue = StatusCode.REQUESTER_URI; + + } + + /** + * + */ + private static final long serialVersionUID = 7861790149343943091L; + +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/InvalidAssertionEncryptionException.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/InvalidAssertionEncryptionException.java new file mode 100644 index 00000000..d0b6feb9 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/InvalidAssertionEncryptionException.java @@ -0,0 +1,18 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.exception; + +import org.opensaml.saml2.core.StatusCode; + +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + +public class InvalidAssertionEncryptionException extends PVP2Exception { + + private static final long serialVersionUID = 6513388841485355549L; + + public InvalidAssertionEncryptionException() { + super("pvp2.16", new Object[]{}); + this.statusCodeValue = StatusCode.RESPONDER_URI; + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/RequestDeniedException.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/RequestDeniedException.java new file mode 100644 index 00000000..5abd6dbe --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/RequestDeniedException.java @@ -0,0 +1,21 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.exception; + +import org.opensaml.saml2.core.StatusCode; + +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + +public class RequestDeniedException extends PVP2Exception { + + public RequestDeniedException() { + super("pvp2.14", null); + this.statusCodeValue = StatusCode.REQUEST_DENIED_URI; + } + + /** + * + */ + private static final long serialVersionUID = 4415896615794730553L; + +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/ResponderErrorException.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/ResponderErrorException.java new file mode 100644 index 00000000..f7145458 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/ResponderErrorException.java @@ -0,0 +1,26 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.exception; + +import org.opensaml.saml2.core.StatusCode; + +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + +public class ResponderErrorException extends PVP2Exception { + + /** + * + */ + private static final long serialVersionUID = -425416760138285446L; + + public ResponderErrorException(String messageId, Object[] parameters, + Throwable wrapped) { + super(messageId, parameters, wrapped); + this.statusCodeValue = StatusCode.RESPONDER_URI; + } + + public ResponderErrorException(String messageId, Object[] parameters) { + super(messageId, parameters); + this.statusCodeValue = StatusCode.RESPONDER_URI; + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/SAMLRequestNotSignedException.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/SAMLRequestNotSignedException.java new file mode 100644 index 00000000..364fdbf0 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/SAMLRequestNotSignedException.java @@ -0,0 +1,26 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.exception; + +import org.opensaml.saml2.core.StatusCode; + +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + +public class SAMLRequestNotSignedException extends PVP2Exception { + + public SAMLRequestNotSignedException() { + super("pvp2.07", null); + this.statusCodeValue = StatusCode.REQUESTER_URI; + } + + public SAMLRequestNotSignedException(Throwable e) { + super("pvp2.07", null, e); + this.statusCodeValue = StatusCode.REQUESTER_URI; + } + + /** + * + */ + private static final long serialVersionUID = 1L; + +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/SAMLRequestNotSupported.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/SAMLRequestNotSupported.java new file mode 100644 index 00000000..b370a7be --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/SAMLRequestNotSupported.java @@ -0,0 +1,22 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.exception; + +import org.opensaml.saml2.core.StatusCode; + +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + + +public class SAMLRequestNotSupported extends PVP2Exception { + + public SAMLRequestNotSupported() { + super("pvp2.09", null); + this.statusCodeValue = StatusCode.REQUEST_UNSUPPORTED_URI; + } + + /** + * + */ + private static final long serialVersionUID = 1244883178458802767L; + +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/UnprovideableAttributeException.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/UnprovideableAttributeException.java new file mode 100644 index 00000000..5dea922c --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/exception/UnprovideableAttributeException.java @@ -0,0 +1,19 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.exception; + +import org.opensaml.saml2.core.StatusCode; + +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + +public class UnprovideableAttributeException extends PVP2Exception { + /** + * + */ + private static final long serialVersionUID = 3972197758163647157L; + + public UnprovideableAttributeException(String attributeName) { + super("pvp2.10", new Object[] {attributeName}); + this.statusCodeValue = StatusCode.UNKNOWN_ATTR_PROFILE_URI; + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/AbstractPVP2XProtocol.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/AbstractPVP2XProtocol.java new file mode 100644 index 00000000..93ffa789 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/AbstractPVP2XProtocol.java @@ -0,0 +1,537 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.impl; + +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.core.AuthnRequest; +import org.opensaml.saml2.core.Issuer; +import org.opensaml.saml2.core.NameID; +import org.opensaml.saml2.core.Response; +import org.opensaml.saml2.core.Status; +import org.opensaml.saml2.core.StatusCode; +import org.opensaml.saml2.core.StatusMessage; +import org.opensaml.saml2.core.impl.AuthnRequestImpl; +import org.opensaml.saml2.metadata.AssertionConsumerService; +import org.opensaml.saml2.metadata.AttributeConsumingService; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.opensaml.ws.security.SecurityPolicyException; +import org.opensaml.xml.security.x509.X509Credential; +import org.opensaml.xml.signature.SignableXMLObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import at.gv.egiz.components.eventlog.api.EventConstants; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.api.idp.IModulInfo; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger; +import at.gv.egiz.eaaf.core.exceptions.AuthnRequestValidatorException; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.exceptions.InvalidProtocolRequestException; +import at.gv.egiz.eaaf.core.exceptions.NoPassivAuthenticationException; +import at.gv.egiz.eaaf.core.exceptions.SLOException; +import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractAuthProtocolModulController; +import at.gv.egiz.eaaf.modules.pvp2.PVPEventConstants; +import at.gv.egiz.eaaf.modules.pvp2.api.IPVP2BasicConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.api.binding.IEncoder; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPVPMetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.exception.InvalidPVPRequestException; +import at.gv.egiz.eaaf.modules.pvp2.exception.NameIDFormatNotSupportedException; +import at.gv.egiz.eaaf.modules.pvp2.exception.NoMetadataInformationException; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; +import at.gv.egiz.eaaf.modules.pvp2.idp.exception.InvalidAssertionConsumerServiceException; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.PostBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.RedirectBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.SoapBinding; +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.utils.AbstractCredentialProvider; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.EAAFURICompare; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.TrustEngineFactory; +import at.gv.egiz.eaaf.modules.pvp2.impl.verification.AuthnRequestValidator; +import at.gv.egiz.eaaf.modules.pvp2.impl.verification.SAMLVerificationEngine; + +public abstract class AbstractPVP2XProtocol extends AbstractAuthProtocolModulController implements IModulInfo { + private static final Logger log = LoggerFactory.getLogger(AbstractPVP2XProtocol.class); + + @Autowired(required=true) protected IPVP2BasicConfiguration pvpBasicConfiguration; + @Autowired(required=true) protected IPVPMetadataProvider metadataProvider; + @Autowired(required=true) protected SAMLVerificationEngine samlVerificationEngine; + + private AbstractCredentialProvider pvpIDPCredentials; + + /** + * Sets a specific credential provider for PVP S-Profile IDP component. + * @param pvpIDPCredentials credential provider + */ + public void setPvpIDPCredentials(AbstractCredentialProvider pvpIDPCredentials) { + this.pvpIDPCredentials = pvpIDPCredentials; + + } + + public boolean generateErrorMessage(Throwable e, + HttpServletRequest request, HttpServletResponse response, + IRequest protocolRequest) throws Throwable { + + if(protocolRequest == null) { + throw e; + } + + if(!(protocolRequest instanceof PVPSProfilePendingRequest) ) { + throw e; + } + PVPSProfilePendingRequest pvpRequest = (PVPSProfilePendingRequest)protocolRequest; + + Response samlResponse = + SAML2Utils.createSAMLObject(Response.class); + Status status = SAML2Utils.createSAMLObject(Status.class); + StatusCode statusCode = SAML2Utils.createSAMLObject(StatusCode.class); + StatusMessage statusMessage = SAML2Utils.createSAMLObject(StatusMessage.class); + + String moaError = null; + + if(e instanceof NoPassivAuthenticationException) { + statusCode.setValue(StatusCode.NO_PASSIVE_URI); + statusMessage.setMessage(StringEscapeUtils.escapeXml(e.getLocalizedMessage())); + + } else if (e instanceof NameIDFormatNotSupportedException) { + statusCode.setValue(StatusCode.INVALID_NAMEID_POLICY_URI); + statusMessage.setMessage(StringEscapeUtils.escapeXml(e.getLocalizedMessage())); + + } else if (e instanceof SLOException) { + //SLOExecpetions only occurs if session information is lost + return false; + + } else if(e instanceof PVP2Exception) { + PVP2Exception ex = (PVP2Exception) e; + statusCode.setValue(ex.getStatusCodeValue()); + String statusMessageValue = ex.getStatusMessageValue(); + if(statusMessageValue != null) { + statusMessage.setMessage(StringEscapeUtils.escapeXml(statusMessageValue)); + } + moaError = statusMessager.mapInternalErrorToExternalError(ex.getErrorId()); + + } else { + statusCode.setValue(StatusCode.RESPONDER_URI); + statusMessage.setMessage(StringEscapeUtils.escapeXml(e.getLocalizedMessage())); + moaError = statusMessager.getResponseErrorCode(e); + } + + + if (StringUtils.isNotEmpty(moaError)) { + StatusCode moaStatusCode = SAML2Utils.createSAMLObject(StatusCode.class); + moaStatusCode.setValue(moaError); + statusCode.setStatusCode(moaStatusCode); + } + + status.setStatusCode(statusCode); + if(statusMessage.getMessage() != null) { + status.setStatusMessage(statusMessage); + } + samlResponse.setStatus(status); + String remoteSessionID = SAML2Utils.getSecureIdentifier(); + samlResponse.setID(remoteSessionID); + + samlResponse.setIssueInstant(new DateTime()); + Issuer nissuer = SAML2Utils.createSAMLObject(Issuer.class); + nissuer.setValue(pvpBasicConfiguration.getIDPEntityId(pvpRequest.getAuthURL())); + nissuer.setFormat(NameID.ENTITY); + samlResponse.setIssuer(nissuer); + + IEncoder encoder = null; + + if(pvpRequest.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) { + encoder = applicationContext.getBean("PVPRedirectBinding", RedirectBinding.class); + + } else if(pvpRequest.getBinding().equals(SAMLConstants.SAML2_POST_BINDING_URI)) { + encoder = applicationContext.getBean("PVPPOSTBinding", PostBinding.class); + + } else if (pvpRequest.getBinding().equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) { + encoder = applicationContext.getBean("PVPSOAPBinding", SoapBinding.class); + } + + if(encoder == null) { + // default to redirect binding + encoder = new RedirectBinding(); + } + + String relayState = null; + if (pvpRequest.getRequest() != null) + relayState = pvpRequest.getRequest().getRelayState(); + + X509Credential signCred = pvpIDPCredentials.getIDPAssertionSigningCredential(); + + encoder.encodeRespone(request, response, samlResponse, pvpRequest.getConsumerURL(), + relayState, signCred, protocolRequest); + return true; + } + + public boolean validate(HttpServletRequest request, + HttpServletResponse response, IRequest pending) { + + return true; + } + + protected void pvpMetadataRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { + //create pendingRequest object + PVPSProfilePendingRequest pendingReq = applicationContext.getBean(PVPSProfilePendingRequest.class); + pendingReq.initialize(req, authConfig); + pendingReq.setModule(getName()); + + revisionsLogger.logEvent( + pendingReq.getUniqueSessionIdentifier(), + pendingReq.getUniqueTransactionIdentifier(), + EventConstants.TRANSACTION_IP, + req.getRemoteAddr()); + + MetadataAction metadataAction = applicationContext.getBean(MetadataAction.class); + metadataAction.processRequest(pendingReq, + req, resp, null); + + } + + protected void PVPIDPPostRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { + PVPSProfilePendingRequest pendingReq = null; + + try { + //create pendingRequest object + pendingReq = applicationContext.getBean(PVPSProfilePendingRequest.class); + pendingReq.initialize(req, authConfig); + pendingReq.setModule(getName()); + + revisionsLogger.logEvent(EventConstants.SESSION_CREATED, pendingReq.getUniqueSessionIdentifier()); + revisionsLogger.logEvent(EventConstants.TRANSACTION_CREATED, pendingReq.getUniqueTransactionIdentifier()); + revisionsLogger.logEvent( + pendingReq.getUniqueSessionIdentifier(), + pendingReq.getUniqueTransactionIdentifier(), + EventConstants.TRANSACTION_IP, + req.getRemoteAddr()); + + //get POST-Binding decoder implementation + InboundMessage msg = (InboundMessage) new PostBinding().decode( + req, resp, metadataProvider, false, + new EAAFURICompare(pvpBasicConfiguration.getIDPSSOPostService(pendingReq.getAuthURL()))); + pendingReq.setRequest(msg); + + //preProcess Message + preProcess(req, resp, pendingReq); + + } catch (SecurityPolicyException e) { + String samlRequest = req.getParameter("SAMLRequest"); + log.warn("Receive INVALID protocol request: " + samlRequest, e); + + //write revision log entries + if (pendingReq != null) + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); + + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}, e.getMessage()); + + } catch (SecurityException e) { + String samlRequest = req.getParameter("SAMLRequest"); + log.warn("Receive INVALID protocol request: " + samlRequest, e); + + //write revision log entries + if (pendingReq != null) + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); + + throw new InvalidProtocolRequestException("pvp2.22", new Object[] {e.getMessage()}, e.getMessage()); + + } catch (EAAFException e) { + + //write revision log entries + if (pendingReq != null) + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); + + throw e; + + } catch (Throwable e) { + String samlRequest = req.getParameter("SAMLRequest"); + log.warn("Receive INVALID protocol request: " + samlRequest, e); + + //write revision log entries + if (pendingReq != null) + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); + + throw new EAAFException("pvp2.24", new Object[] {e.getMessage()}, e.getMessage(), e); + } + } + + protected void PVPIDPRedirecttRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { + PVPSProfilePendingRequest pendingReq = null; + try { + //create pendingRequest object + pendingReq = applicationContext.getBean(PVPSProfilePendingRequest.class); + pendingReq.initialize(req, authConfig); + pendingReq.setModule(getName()); + + revisionsLogger.logEvent(EventConstants.SESSION_CREATED, pendingReq.getUniqueSessionIdentifier()); + revisionsLogger.logEvent(EventConstants.TRANSACTION_CREATED, pendingReq.getUniqueTransactionIdentifier()); + revisionsLogger.logEvent( + pendingReq.getUniqueSessionIdentifier(), + pendingReq.getUniqueTransactionIdentifier(), + EventConstants.TRANSACTION_IP, + req.getRemoteAddr()); + + //get POST-Binding decoder implementation + InboundMessage msg = (InboundMessage) new RedirectBinding().decode( + req, resp, metadataProvider, false, + new EAAFURICompare(pvpBasicConfiguration.getIDPSSORedirectService(pendingReq.getAuthURL()))); + pendingReq.setRequest(msg); + + //preProcess Message + preProcess(req, resp, pendingReq); + + } catch (SecurityPolicyException e) { + String samlRequest = req.getParameter("SAMLRequest"); + log.warn("Receive INVALID protocol request: " + samlRequest, e); + + //write revision log entries + if (pendingReq != null) + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); + + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}, e.getMessage()); + + } catch (SecurityException e) { + String samlRequest = req.getParameter("SAMLRequest"); + log.warn("Receive INVALID protocol request: " + samlRequest, e); + + //write revision log entries + if (pendingReq != null) + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); + + throw new InvalidProtocolRequestException("pvp2.22", new Object[] {e.getMessage()}, e.getMessage()); + + } catch (EAAFException e) { + String samlRequest = req.getParameter("SAMLRequest"); + log.info("Receive INVALID protocol request: " + samlRequest); + + //write revision log entries + if (pendingReq != null) + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); + + throw e; + + } catch (Throwable e) { + String samlRequest = req.getParameter("SAMLRequest"); + log.warn("Receive INVALID protocol request: " + samlRequest, e); + + //write revision log entries + if (pendingReq != null) + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); + + throw new EAAFException("pvp2.24", new Object[] {e.getMessage()}, e.getMessage(), e); + } + } + + + + /** + * + * + * @param request + * @param response + * @param msg + * @return true if preprocess can handle this request type, otherwise false + * @throws Throwable + */ + abstract protected boolean childPreProcess(HttpServletRequest request, + HttpServletResponse response, PVPSProfilePendingRequest pendingReq) throws Throwable; + + protected void preProcess(HttpServletRequest request, + HttpServletResponse response, PVPSProfilePendingRequest pendingReq) throws Throwable { + + InboundMessage msg = pendingReq.getRequest(); + + if (StringUtils.isEmpty(msg.getEntityID())) { + throw new InvalidProtocolRequestException("pvp2.20", new Object[] {}, "EntityId is null or empty"); + + } + + if(!msg.isVerified()) { + samlVerificationEngine.verify(msg, + TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider)); + msg.setVerified(true); + + } + + + if (msg instanceof PVPSProfileRequest && + ((PVPSProfileRequest)msg).getSamlRequest() instanceof AuthnRequest) + preProcessAuthRequest(request, response, pendingReq); + + else if (childPreProcess(request, response, pendingReq)) + log.debug("Find protocol handler in child implementation"); + + else { + log.error("Receive unsupported PVP21 message"); + throw new InvalidPVPRequestException("Unsupported PVP21 message", new Object[] {}); + } + + revisionsLogger.logEvent(pendingReq, IRevisionLogger.AUTHPROTOCOL_TYPE, getAuthProtocolIdentifier()); + + //switch to session authentication + performAuthentication(request, response, pendingReq); + } + + + /** + * PreProcess Authn request + * @param request + * @param response + * @param pendingReq + * @throws Throwable + */ + private void preProcessAuthRequest(HttpServletRequest request, + HttpServletResponse response, PVPSProfilePendingRequest pendingReq) throws Throwable { + + PVPSProfileRequest moaRequest = ((PVPSProfileRequest)pendingReq.getRequest()); + SignableXMLObject samlReq = moaRequest.getSamlRequest(); + + if(!(samlReq instanceof AuthnRequest)) { + throw new InvalidPVPRequestException("Unsupported request", new Object[] {}); + } + + EntityDescriptor metadata = moaRequest.getEntityMetadata(metadataProvider); + if(metadata == null) { + throw new NoMetadataInformationException(); + } + SPSSODescriptor spSSODescriptor = metadata.getSPSSODescriptor(SAMLConstants.SAML20P_NS); + + AuthnRequest authnRequest = (AuthnRequest)samlReq; + + if (authnRequest.getIssueInstant() == null) { + log.warn("Unsupported request: No IssueInstant Attribute found."); + throw new AuthnRequestValidatorException("Unsupported request: No IssueInstant Attribute found.", new Object[] {}, + "Unsupported request: No IssueInstant Attribute found", pendingReq); + + } + + if (authnRequest.getIssueInstant().minusMinutes(EAAFConstants.ALLOWED_TIME_JITTER).isAfterNow()) { + log.warn("Unsupported request: No IssueInstant DateTime is not valid anymore."); + throw new AuthnRequestValidatorException("Unsupported request: No IssueInstant DateTime is not valid anymore.", new Object[] {}, + "Unsupported request: No IssueInstant DateTime is not valid anymore.", pendingReq); + + } + + //parse AssertionConsumerService + AssertionConsumerService consumerService = null; + if (StringUtils.isNotEmpty(authnRequest.getAssertionConsumerServiceURL()) && + StringUtils.isNotEmpty(authnRequest.getProtocolBinding())) { + //use AssertionConsumerServiceURL from request + + //check requested AssertionConsumingService URL against metadata + List<AssertionConsumerService> metadataAssertionServiceList = spSSODescriptor.getAssertionConsumerServices(); + for (AssertionConsumerService service : metadataAssertionServiceList) { + if (authnRequest.getProtocolBinding().equals(service.getBinding()) + && authnRequest.getAssertionConsumerServiceURL().equals(service.getLocation())) { + consumerService = SAML2Utils.createSAMLObject(AssertionConsumerService.class); + consumerService.setBinding(authnRequest.getProtocolBinding()); + consumerService.setLocation(authnRequest.getAssertionConsumerServiceURL()); + log.debug("Requested AssertionConsumerServiceURL is valid."); + } + } + + if (consumerService == null) { + throw new InvalidAssertionConsumerServiceException(authnRequest.getAssertionConsumerServiceURL()); + + } + + + } else { + //use AssertionConsumerServiceIndex and select consumerService from metadata + Integer aIdx = authnRequest.getAssertionConsumerServiceIndex(); + int assertionidx = 0; + + if(aIdx != null) { + assertionidx = aIdx.intValue(); + + } else { + assertionidx = SAML2Utils.getDefaultAssertionConsumerServiceIndex(spSSODescriptor); + + } + consumerService = spSSODescriptor.getAssertionConsumerServices().get(assertionidx); + + if (consumerService == null) { + throw new InvalidAssertionConsumerServiceException(aIdx); + + } + } + + + //select AttributeConsumingService from request + AttributeConsumingService attributeConsumer = null; + Integer aIdx = authnRequest.getAttributeConsumingServiceIndex(); + int attributeIdx = 0; + + if(aIdx != null) { + attributeIdx = aIdx.intValue(); + } + + if (spSSODescriptor.getAttributeConsumingServices() != null && + spSSODescriptor.getAttributeConsumingServices().size() > 0) { + attributeConsumer = spSSODescriptor.getAttributeConsumingServices().get(attributeIdx); + } + + //validate AuthnRequest + AuthnRequestImpl authReq = (AuthnRequestImpl) samlReq; + AuthnRequestValidator.validate(authReq); + +// String useMandate = request.getParameter(PARAM_USEMANDATE); +// if(useMandate != null) { +// if(useMandate.equals("true") && attributeConsumer != null) { +// if(!CheckMandateAttributes.canHandleMandate(attributeConsumer)) { +// throw new MandateAttributesNotHandleAbleException(); +// } +// } +// } + + String oaURL = moaRequest.getEntityMetadata(metadataProvider).getEntityID(); + oaURL = StringEscapeUtils.escapeHtml(oaURL); + ISPConfiguration oa = authConfig.getServiceProviderConfiguration(oaURL); + + log.info("Dispatch PVP2 AuthnRequest: OAURL=" + oaURL + " Binding=" + consumerService.getBinding()); + + pendingReq.setSPEntityId(oaURL); + pendingReq.setOnlineApplicationConfiguration(oa); + pendingReq.setBinding(consumerService.getBinding()); + pendingReq.setRequest(moaRequest); + pendingReq.setConsumerURL(consumerService.getLocation()); + + //parse AuthRequest + pendingReq.setPassiv(authReq.isPassive()); + pendingReq.setForce(authReq.isForceAuthn()); + + //AuthnRequest needs authentication + pendingReq.setNeedAuthentication(true); + + //set protocol action, which should be executed after authentication + pendingReq.setAction(AuthenticationAction.class.getName()); + + //write revisionslog entry + revisionsLogger.logEvent(pendingReq, PVPEventConstants.AUTHPROTOCOL_PVP_REQUEST_AUTHREQUEST); + + } + + @PostConstruct + private void verifyInitialization() { + if (pvpIDPCredentials == null) { + log.error("No SAML2 credentialProvider injected!"); + throw new RuntimeException("No SAML2 credentialProvider injected!"); + + } + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/AuthenticationAction.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/AuthenticationAction.java new file mode 100644 index 00000000..adcff465 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/AuthenticationAction.java @@ -0,0 +1,154 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.impl; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.joda.time.DateTime; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.core.Assertion; +import org.opensaml.saml2.core.AuthnRequest; +import org.opensaml.saml2.core.Response; +import org.opensaml.saml2.metadata.AssertionConsumerService; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.xml.security.SecurityException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.idp.IAction; +import at.gv.egiz.eaaf.core.api.idp.IAuthData; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.slo.SLOInformationInterface; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.impl.data.SLOInformationImpl; +import at.gv.egiz.eaaf.modules.pvp2.api.IPVP2BasicConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.api.binding.IEncoder; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPVPMetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.exception.BindingNotSupportedException; +import at.gv.egiz.eaaf.modules.pvp2.idp.exception.ResponderErrorException; +import at.gv.egiz.eaaf.modules.pvp2.idp.impl.builder.AuthResponseBuilder; +import at.gv.egiz.eaaf.modules.pvp2.idp.impl.builder.PVP2AssertionBuilder; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.PostBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.RedirectBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.PVPSProfileRequest; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; + +@Service("PVPAuthenticationRequestAction") +public class AuthenticationAction implements IAction { + private static final Logger log = LoggerFactory.getLogger(AuthenticationAction.class); + + private static final String CONFIG_PROPERTY_PVP2_ENABLE_ENCRYPTION = "protocols.pvp2.assertion.encryption.active"; + + @Autowired(required=true) private IPVPMetadataProvider metadataProvider; + @Autowired(required=true) ApplicationContext springContext; + @Autowired(required=true) IConfiguration authConfig; + @Autowired(required=true) PVP2AssertionBuilder assertionBuilder; + @Autowired(required=true) IPVP2BasicConfiguration pvpBasicConfiguration; + + private AbstractCredentialProvider pvpIDPCredentials; + + /** + * Sets a specific credential provider for PVP S-Profile IDP component. + * @param pvpIDPCredentials credential provider + */ + public void setPvpIDPCredentials(AbstractCredentialProvider pvpIDPCredentials) { + this.pvpIDPCredentials = pvpIDPCredentials; + + } + + public SLOInformationInterface processRequest(IRequest req, HttpServletRequest httpReq, + HttpServletResponse httpResp, IAuthData authData) throws ResponderErrorException { + PVPSProfilePendingRequest pvpRequest = (PVPSProfilePendingRequest) req; + try { + //get basic information + PVPSProfileRequest moaRequest = (PVPSProfileRequest) pvpRequest.getRequest(); + AuthnRequest authnRequest = (AuthnRequest) moaRequest.getSamlRequest(); + EntityDescriptor peerEntity = moaRequest.getEntityMetadata(metadataProvider); + + AssertionConsumerService consumerService = + SAML2Utils.createSAMLObject(AssertionConsumerService.class); + consumerService.setBinding(pvpRequest.getBinding()); + consumerService.setLocation(pvpRequest.getConsumerURL()); + + DateTime date = new DateTime(); + SLOInformationImpl sloInformation = new SLOInformationImpl(); + String issuerEntityID = pvpBasicConfiguration.getIDPEntityId(pvpRequest.getAuthURL()); + + //build Assertion + Assertion assertion = assertionBuilder.buildAssertion(issuerEntityID, pvpRequest, authnRequest, authData, + peerEntity, date, consumerService, sloInformation); + + Response authResponse = AuthResponseBuilder.buildResponse( + metadataProvider, issuerEntityID, authnRequest, + date, assertion, authConfig.getBasicMOAIDConfigurationBoolean( + CONFIG_PROPERTY_PVP2_ENABLE_ENCRYPTION, true)); + + IEncoder binding = null; + + if (consumerService.getBinding().equals( + SAMLConstants.SAML2_REDIRECT_BINDING_URI)) { + binding = springContext.getBean("PVPRedirectBinding", RedirectBinding.class); + + } else if (consumerService.getBinding().equals( + SAMLConstants.SAML2_POST_BINDING_URI)) { + binding = springContext.getBean("PVPPOSTBinding", PostBinding.class); + + } + + if (binding == null) { + throw new BindingNotSupportedException(consumerService.getBinding()); + } + + binding.encodeRespone(httpReq, httpResp, authResponse, + consumerService.getLocation(), moaRequest.getRelayState(), + pvpIDPCredentials.getIDPAssertionSigningCredential(), req); + + //set protocol type + sloInformation.setProtocolType(req.requestedModule()); + sloInformation.setSpEntityID(req.getServiceProviderConfiguration().getUniqueIdentifier()); + return sloInformation; + + } catch (MessageEncodingException e) { + log.error("Message Encoding exception", e); + throw new ResponderErrorException("pvp2.01", null, e); + + } catch (SecurityException e) { + log.error("Security exception", e); + throw new ResponderErrorException("pvp2.01", null, e); + + } catch (EAAFException e) { + log.error("Response generation error", e); + throw new ResponderErrorException("pvp2.01", null, e); + + } + + } + + public boolean needAuthentication(IRequest req, HttpServletRequest httpReq, + HttpServletResponse httpResp) { + return true; + } + + public String getDefaultActionName() { + return "PVPAuthenticationRequestAction"; + + } + + @PostConstruct + private void verifyInitialization() { + if (pvpIDPCredentials == null) { + log.error("No SAML2 credentialProvider injected!"); + throw new RuntimeException("No SAML2 credentialProvider injected!"); + + } + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/MetadataAction.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/MetadataAction.java new file mode 100644 index 00000000..fa871597 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/MetadataAction.java @@ -0,0 +1,99 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.impl; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.idp.IAction; +import at.gv.egiz.eaaf.core.api.idp.IAuthData; +import at.gv.egiz.eaaf.core.api.idp.slo.SLOInformationInterface; +import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger; +import at.gv.egiz.eaaf.modules.pvp2.PVPEventConstants; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPVPMetadataBuilderConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPVPMetadataConfigurationFactory; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2MetadataException; +import at.gv.egiz.eaaf.modules.pvp2.impl.builder.PVPMetadataBuilder; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider; + +@Service("pvpMetadataService") +public class MetadataAction implements IAction { + + private static final Logger log = LoggerFactory.getLogger(MetadataAction.class); + + @Autowired private IRevisionLogger revisionsLogger; + @Autowired private PVPMetadataBuilder metadatabuilder; + @Autowired private IPVPMetadataConfigurationFactory configFactory; + + private AbstractCredentialProvider pvpIDPCredentials; + + /** + * Sets a specific credential provider for PVP S-Profile IDP component. + * @param pvpIDPCredentials credential provider + */ + public void setPvpIDPCredentials(AbstractCredentialProvider pvpIDPCredentials) { + this.pvpIDPCredentials = pvpIDPCredentials; + + } + + public SLOInformationInterface processRequest(IRequest req, HttpServletRequest httpReq, + HttpServletResponse httpResp, IAuthData authData) throws PVP2MetadataException { + try { + revisionsLogger.logEvent(req, PVPEventConstants.AUTHPROTOCOL_PVP_METADATA); + + //build metadata + IPVPMetadataBuilderConfiguration metadataConfig = + configFactory.generateMetadataBuilderConfiguration( + req.getAuthURLWithOutSlash(), + pvpIDPCredentials); + + ; + + String metadataXML = metadatabuilder.buildPVPMetadata(metadataConfig); + log.debug("METADATA: " + metadataXML); + + byte[] content = metadataXML.getBytes("UTF-8"); + httpResp.setStatus(HttpServletResponse.SC_OK); + httpResp.setContentLength(content.length); + httpResp.setContentType(MediaType.APPLICATION_XML_VALUE); + httpResp.getOutputStream().write(content); + return null; + + } catch (Exception e) { + log.error("Failed to generate metadata", e); + throw new PVP2MetadataException("pvp2.13", null); + } + } + + public boolean needAuthentication(IRequest req, HttpServletRequest httpReq, + HttpServletResponse httpResp) { + return false; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.moduls.IAction#getDefaultActionName() + */ + @Override + public String getDefaultActionName() { + return "IDP - PVP Metadata action"; + } + + @PostConstruct + private void verifyInitialization() { + if (pvpIDPCredentials == null) { + log.error("No SAML2 credentialProvider injected!"); + throw new RuntimeException("No SAML2 credentialProvider injected!"); + + } + } + + +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/PVPSProfilePendingRequest.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/PVPSProfilePendingRequest.java new file mode 100644 index 00000000..06c64b84 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/PVPSProfilePendingRequest.java @@ -0,0 +1,103 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.impl; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.InboundMessage; + +@Component("PVPSProfilePendingRequest") +@Scope(value = BeanDefinition.SCOPE_PROTOTYPE) +public class PVPSProfilePendingRequest extends RequestImpl { + private static final long serialVersionUID = 4889919265919638188L; + + InboundMessage request; + String binding; + String consumerURL; + + public InboundMessage getRequest() { + return request; + } + + public void setRequest(InboundMessage request) { + this.request = request; + } + + public String getBinding() { + return binding; + } + + public void setBinding(String binding) { + this.binding = binding; + } + + public String getConsumerURL() { + return consumerURL; + } + + public void setConsumerURL(String consumerURL) { + this.consumerURL = consumerURL; + + } + +// /* (non-Javadoc) +// * @see at.gv.egovernment.moa.id.moduls.RequestImpl#getRequestedAttributes() +// */ +// @Override +// public Collection<String> getRequestedAttributes(MetadataProvider metadataProvider) { +// +// Map<String, String> reqAttr = new HashMap<String, String>(); +// for (String el : PVP2XProtocol.DEFAULTREQUESTEDATTRFORINTERFEDERATION) +// reqAttr.put(el, ""); +// +// try { +// SPSSODescriptor spSSODescriptor = getRequest().getEntityMetadata(metadataProvider).getSPSSODescriptor(SAMLConstants.SAML20P_NS); +// if (spSSODescriptor.getAttributeConsumingServices() != null && +// spSSODescriptor.getAttributeConsumingServices().size() > 0) { +// +// Integer aIdx = null; +// if (getRequest() instanceof MOARequest && +// ((MOARequest)getRequest()).getSamlRequest() instanceof AuthnRequestImpl) { +// AuthnRequestImpl authnRequest = (AuthnRequestImpl)((MOARequest)getRequest()).getSamlRequest(); +// aIdx = authnRequest.getAttributeConsumingServiceIndex(); +// +// } else { +// Logger.error("MOARequest is NOT of type AuthnRequest"); +// } +// +// int idx = 0; +// +// AttributeConsumingService attributeConsumingService = null; +// +// if (aIdx != null) { +// idx = aIdx.intValue(); +// attributeConsumingService = spSSODescriptor +// .getAttributeConsumingServices().get(idx); +// +// } else { +// List<AttributeConsumingService> attrConsumingServiceList = spSSODescriptor.getAttributeConsumingServices(); +// for (AttributeConsumingService el : attrConsumingServiceList) { +// if (el.isDefault()) +// attributeConsumingService = el; +// } +// } +// +// for ( RequestedAttribute attr : attributeConsumingService.getRequestAttributes()) +// reqAttr.put(attr.getName(), ""); +// } +// +// //return attributQueryBuilder.buildSAML2AttributeList(this.getOnlineApplicationConfiguration(), reqAttr.keySet().iterator()); +// return reqAttr.keySet(); +// +// } catch (NoMetadataInformationException e) { +// Logger.warn("NO metadata found for Entity " + getRequest().getEntityID()); +// return null; +// +// } +// +// } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/builder/AuthResponseBuilder.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/builder/AuthResponseBuilder.java new file mode 100644 index 00000000..34a28f72 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/builder/AuthResponseBuilder.java @@ -0,0 +1,129 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.impl.builder; + +import java.util.ArrayList; +import java.util.List; + +import org.joda.time.DateTime; +import org.opensaml.Configuration; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.core.Assertion; +import org.opensaml.saml2.core.EncryptedAssertion; +import org.opensaml.saml2.core.Issuer; +import org.opensaml.saml2.core.NameID; +import org.opensaml.saml2.core.RequestAbstractType; +import org.opensaml.saml2.core.Response; +import org.opensaml.saml2.encryption.Encrypter; +import org.opensaml.saml2.encryption.Encrypter.KeyPlacement; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.security.MetadataCredentialResolver; +import org.opensaml.security.MetadataCriteria; +import org.opensaml.xml.encryption.EncryptionException; +import org.opensaml.xml.encryption.EncryptionParameters; +import org.opensaml.xml.encryption.KeyEncryptionParameters; +import org.opensaml.xml.security.CriteriaSet; +import org.opensaml.xml.security.SecurityException; +import org.opensaml.xml.security.credential.UsageType; +import org.opensaml.xml.security.criteria.EntityIDCriteria; +import org.opensaml.xml.security.criteria.UsageCriteria; +import org.opensaml.xml.security.keyinfo.KeyInfoGeneratorFactory; +import org.opensaml.xml.security.x509.X509Credential; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.eaaf.modules.pvp2.PVPConstants; +import at.gv.egiz.eaaf.modules.pvp2.idp.exception.InvalidAssertionEncryptionException; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; + +/** + * @author tlenz + * + */ +public class AuthResponseBuilder { + + private static final Logger log = LoggerFactory.getLogger(AuthResponseBuilder.class); + + public static Response buildResponse(MetadataProvider metadataProvider, String issuerEntityID, RequestAbstractType req, DateTime date, Assertion assertion, boolean enableEncryption) throws InvalidAssertionEncryptionException { + Response authResponse = SAML2Utils.createSAMLObject(Response.class); + + Issuer nissuer = SAML2Utils.createSAMLObject(Issuer.class); + + nissuer.setValue(issuerEntityID); + nissuer.setFormat(NameID.ENTITY); + authResponse.setIssuer(nissuer); + authResponse.setInResponseTo(req.getID()); + + //set responseID + String remoteSessionID = SAML2Utils.getSecureIdentifier(); + authResponse.setID(remoteSessionID); + + + //SAML2 response required IssueInstant + authResponse.setIssueInstant(date); + + authResponse.setStatus(SAML2Utils.getSuccessStatus()); + + //check, if metadata includes an encryption key + MetadataCredentialResolver mdCredResolver = + new MetadataCredentialResolver(metadataProvider); + + CriteriaSet criteriaSet = new CriteriaSet(); + criteriaSet.add( new EntityIDCriteria(req.getIssuer().getValue()) ); + criteriaSet.add( new MetadataCriteria(SPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS) ); + criteriaSet.add( new UsageCriteria(UsageType.ENCRYPTION) ); + + X509Credential encryptionCredentials = null; + try { + encryptionCredentials = (X509Credential) mdCredResolver.resolveSingle(criteriaSet); + + } catch (SecurityException e2) { + log.warn("Can not extract the Assertion Encryption-Key from metadata", e2); + throw new InvalidAssertionEncryptionException(); + + } + + if (encryptionCredentials != null && enableEncryption) { + //encrypt SAML2 assertion + + try { + + EncryptionParameters dataEncParams = new EncryptionParameters(); + dataEncParams.setAlgorithm(PVPConstants.DEFAULT_SYM_ENCRYPTION_METHODE); + + List<KeyEncryptionParameters> keyEncParamList = new ArrayList<KeyEncryptionParameters>(); + KeyEncryptionParameters keyEncParam = new KeyEncryptionParameters(); + + keyEncParam.setEncryptionCredential(encryptionCredentials); + keyEncParam.setAlgorithm(PVPConstants.DEFAULT_ASYM_ENCRYPTION_METHODE); + KeyInfoGeneratorFactory kigf = Configuration.getGlobalSecurityConfiguration() + .getKeyInfoGeneratorManager().getDefaultManager() + .getFactory(encryptionCredentials); + keyEncParam.setKeyInfoGenerator(kigf.newInstance()); + keyEncParamList.add(keyEncParam); + + Encrypter samlEncrypter = new Encrypter(dataEncParams, keyEncParamList); + //samlEncrypter.setKeyPlacement(KeyPlacement.INLINE); + samlEncrypter.setKeyPlacement(KeyPlacement.PEER); + + EncryptedAssertion encryptAssertion = null; + + encryptAssertion = samlEncrypter.encrypt(assertion); + + authResponse.getEncryptedAssertions().add(encryptAssertion); + + } catch (EncryptionException e1) { + log.warn("Can not encrypt the PVP2 assertion", e1); + throw new InvalidAssertionEncryptionException(); + + } + + } else { + authResponse.getAssertions().add(assertion); + + } + + return authResponse; + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/builder/PVP2AssertionBuilder.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/builder/PVP2AssertionBuilder.java new file mode 100644 index 00000000..7369da15 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/builder/PVP2AssertionBuilder.java @@ -0,0 +1,448 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.impl.builder; + +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.core.Assertion; +import org.opensaml.saml2.core.Attribute; +import org.opensaml.saml2.core.AttributeQuery; +import org.opensaml.saml2.core.AttributeStatement; +import org.opensaml.saml2.core.Audience; +import org.opensaml.saml2.core.AudienceRestriction; +import org.opensaml.saml2.core.AuthnContext; +import org.opensaml.saml2.core.AuthnContextClassRef; +import org.opensaml.saml2.core.AuthnRequest; +import org.opensaml.saml2.core.AuthnStatement; +import org.opensaml.saml2.core.Conditions; +import org.opensaml.saml2.core.Issuer; +import org.opensaml.saml2.core.NameID; +import org.opensaml.saml2.core.RequestedAuthnContext; +import org.opensaml.saml2.core.Subject; +import org.opensaml.saml2.core.SubjectConfirmation; +import org.opensaml.saml2.core.SubjectConfirmationData; +import org.opensaml.saml2.core.impl.AuthnRequestImpl; +import org.opensaml.saml2.metadata.AssertionConsumerService; +import org.opensaml.saml2.metadata.AttributeConsumingService; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.NameIDFormat; +import org.opensaml.saml2.metadata.RequestedAttribute; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.Base64Utils; + +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.api.data.ILoALevelMapper; +import at.gv.egiz.eaaf.core.api.idp.IAuthData; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.api.idp.slo.SLOInformationInterface; +import at.gv.egiz.eaaf.core.exceptions.UnavailableAttributeException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl; +import at.gv.egiz.eaaf.core.impl.utils.Random; +import at.gv.egiz.eaaf.modules.pvp2.PVPConstants; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; +import at.gv.egiz.eaaf.modules.pvp2.exception.QAANotSupportedException; +import at.gv.egiz.eaaf.modules.pvp2.idp.api.builder.ISubjectNameIdGenerator; +import at.gv.egiz.eaaf.modules.pvp2.idp.exception.ResponderErrorException; +import at.gv.egiz.eaaf.modules.pvp2.idp.exception.UnprovideableAttributeException; +import at.gv.egiz.eaaf.modules.pvp2.idp.impl.PVPSProfilePendingRequest; +import at.gv.egiz.eaaf.modules.pvp2.impl.builder.PVPAttributeBuilder; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.QAALevelVerifier; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; + +@Service("PVP2AssertionBuilder") +public class PVP2AssertionBuilder implements PVPConstants { + + private static final Logger log = LoggerFactory.getLogger(PVP2AssertionBuilder.class); + @Autowired private ILoALevelMapper loaLevelMapper; + @Autowired private ISubjectNameIdGenerator subjectNameIdGenerator; + + + /** + * Build a PVP assertion as response for a SAML2 AttributeQuery request + * + * @param issuerEntityID EnitiyID, which should be used for this IDP response + * @param attrQuery AttributeQuery request from Service-Provider + * @param attrList List of PVP response attributes + * @param now Current time + * @param validTo ValidTo time of the assertion + * @param qaaLevel QAA level of the authentication + * @param sessionIndex SAML2 SessionIndex, which should be included * + * @return PVP 2.1 Assertion + * @throws PVP2Exception + */ + public Assertion buildAssertion(String issuerEntityID, AttributeQuery attrQuery, + List<Attribute> attrList, DateTime now, DateTime validTo, String qaaLevel, String sessionIndex) throws PVP2Exception { + + AuthnContextClassRef authnContextClassRef = SAML2Utils.createSAMLObject(AuthnContextClassRef.class); + authnContextClassRef.setAuthnContextClassRef(qaaLevel); + + NameID subjectNameID = SAML2Utils.createSAMLObject(NameID.class); + subjectNameID.setFormat(attrQuery.getSubject().getNameID().getFormat()); + subjectNameID.setValue(attrQuery.getSubject().getNameID().getValue()); + + SubjectConfirmationData subjectConfirmationData = null; + + return buildGenericAssertion(issuerEntityID, attrQuery.getIssuer().getValue(), now, + authnContextClassRef, attrList, subjectNameID, subjectConfirmationData, sessionIndex, + validTo); + } + + + /** + * Build a PVP 2.1 assertion as response of a SAML2 AuthnRequest + * + * @param issuerEntityID EnitiyID, which should be used for this IDP response + * @param pendingReq Current processed pendingRequest DAO + * @param authnRequest Current processed PVP AuthnRequest + * @param authData AuthenticationData of the user, which is already authenticated + * @param peerEntity SAML2 EntityDescriptor of the service-provider, which receives the response + * @param date TimeStamp + * @param assertionConsumerService SAML2 endpoint of the service-provider, which should be used + * @param sloInformation Single LogOut information DAO + * @return + * @throws PVP2Exception + */ + public Assertion buildAssertion(String issuerEntityID, PVPSProfilePendingRequest pendingReq, AuthnRequest authnRequest, + IAuthData authData, EntityDescriptor peerEntity, DateTime date, + AssertionConsumerService assertionConsumerService, SLOInformationInterface sloInformation) + throws PVP2Exception { + + RequestedAuthnContext reqAuthnContext = authnRequest + .getRequestedAuthnContext(); + + AuthnContextClassRef authnContextClassRef = SAML2Utils + .createSAMLObject(AuthnContextClassRef.class); + + ISPConfiguration oaParam = pendingReq.getServiceProviderConfiguration(); + + if (reqAuthnContext == null) { + authnContextClassRef.setAuthnContextClassRef(authData.getEIDASQAALevel()); + + } else { + + boolean eIDAS_qaa_found = false; + + List<AuthnContextClassRef> reqAuthnContextClassRefIt = reqAuthnContext + .getAuthnContextClassRefs(); + + if (reqAuthnContextClassRefIt.size() == 0) { + QAALevelVerifier.verifyQAALevel(authData.getEIDASQAALevel(), EAAFConstants.EIDAS_QAA_HIGH); + + eIDAS_qaa_found = true; + authnContextClassRef.setAuthnContextClassRef(EAAFConstants.EIDAS_QAA_HIGH); + + } else { + for (AuthnContextClassRef authnClassRef : reqAuthnContextClassRefIt) { + String qaa_uri = authnClassRef.getAuthnContextClassRef(); + + if (!qaa_uri.trim().startsWith(EAAFConstants.EIDAS_QAA_PREFIX)) { + if (loaLevelMapper != null) { + log.debug("Find no eIDAS LoA. Start mapping process ... " ); + qaa_uri = loaLevelMapper.mapToeIDASLoA(qaa_uri.trim()); + + } else + log.debug("AuthnRequest contains no eIDAS LoA. NO LoA mapper FOUND, ignore " + + "'" + qaa_uri.trim() + "'"); + + } + + if (qaa_uri.trim().equals(EAAFConstants.EIDAS_QAA_HIGH) + || qaa_uri.trim().equals(EAAFConstants.EIDAS_QAA_SUBSTANTIAL) + || qaa_uri.trim().equals(EAAFConstants.EIDAS_QAA_LOW)) { + + if (authData.isForeigner()) { + QAALevelVerifier.verifyQAALevel(authData.getEIDASQAALevel(), oaParam.getMinimumLevelOfAssurence()); + + eIDAS_qaa_found = true; + authnContextClassRef.setAuthnContextClassRef(authData.getEIDASQAALevel()); + + } else { + + QAALevelVerifier.verifyQAALevel(authData.getEIDASQAALevel(), + qaa_uri.trim()); + + eIDAS_qaa_found = true; + authnContextClassRef.setAuthnContextClassRef(authData.getEIDASQAALevel()); + + } + break; + } + } + } + + if (!eIDAS_qaa_found) + throw new QAANotSupportedException(EAAFConstants.EIDAS_QAA_HIGH); + + } + + + + SPSSODescriptor spSSODescriptor = peerEntity + .getSPSSODescriptor(SAMLConstants.SAML20P_NS); + + //add Attributes to Assertion + List<Attribute> attrList = new ArrayList<Attribute>(); + if (spSSODescriptor.getAttributeConsumingServices() != null && + spSSODescriptor.getAttributeConsumingServices().size() > 0) { + + Integer aIdx = authnRequest.getAttributeConsumingServiceIndex(); + int idx = 0; + + AttributeConsumingService attributeConsumingService = null; + if (aIdx != null) { + idx = aIdx.intValue(); + attributeConsumingService = spSSODescriptor + .getAttributeConsumingServices().get(idx); + + } else { + List<AttributeConsumingService> attrConsumingServiceList = spSSODescriptor.getAttributeConsumingServices(); + for (AttributeConsumingService el : attrConsumingServiceList) { + if (el.isDefault()) + attributeConsumingService = el; + } + } + + /* + * TODO: maybe use first AttributeConsumingService if no is selected + * in request or on service is marked as default + * + */ + if (attributeConsumingService == null ) { + List<AttributeConsumingService> attrConsumingServiceList = spSSODescriptor.getAttributeConsumingServices(); + if (attrConsumingServiceList != null && !attrConsumingServiceList.isEmpty()) + attributeConsumingService = attrConsumingServiceList.get(0); + + } + + + if (attributeConsumingService != null) { + Iterator<RequestedAttribute> it = attributeConsumingService + .getRequestAttributes().iterator(); + while (it.hasNext()) { + RequestedAttribute reqAttribut = it.next(); + try { + Attribute attr = PVPAttributeBuilder.buildAttribute( + reqAttribut.getName(), oaParam, authData); + if (attr == null) { + if (reqAttribut.isRequired()) { + throw new UnprovideableAttributeException( + reqAttribut.getName()); + } + } else { + attrList.add(attr); + } + + } catch (UnavailableAttributeException e) { + log.info( + "Attribute generation for " + + reqAttribut.getFriendlyName() + " not possible."); + if (reqAttribut.isRequired()) { + throw new UnprovideableAttributeException( + reqAttribut.getName()); + } + + + } catch (PVP2Exception e) { + log.info( + "Attribute generation failed! for " + + reqAttribut.getFriendlyName()); + if (reqAttribut.isRequired()) { + throw new UnprovideableAttributeException( + reqAttribut.getName()); + } + + } catch (Exception e) { + log.warn( + "General Attribute generation failed! for " + + reqAttribut.getFriendlyName(), e); + if (reqAttribut.isRequired()) { + throw new UnprovideableAttributeException( + reqAttribut.getName()); + } + + } + } + } + } + + //generate subjectNameId + NameID subjectNameID = SAML2Utils.createSAMLObject(NameID.class); + Pair<String, String> subjectNameIdPair = subjectNameIdGenerator.generateSubjectNameId(authData, oaParam); + subjectNameID.setValue(subjectNameIdPair.getFirst()); + subjectNameID.setNameQualifier(subjectNameIdPair.getSecond()); + + //get NameIDFormat from request + String nameIDFormat = NameID.TRANSIENT; + AuthnRequest authnReq = (AuthnRequestImpl) authnRequest; + if (authnReq.getNameIDPolicy() != null && + StringUtils.isNotEmpty(authnReq.getNameIDPolicy().getFormat())) { + nameIDFormat = authnReq.getNameIDPolicy().getFormat(); + + } else { + //get NameIDFormat from metadata + List<NameIDFormat> metadataNameIDFormats = spSSODescriptor.getNameIDFormats(); + + if (metadataNameIDFormats != null) { + + for (NameIDFormat el : metadataNameIDFormats) { + if (NameID.PERSISTENT.equals(el.getFormat())) { + nameIDFormat = NameID.PERSISTENT; + break; + + } else if (NameID.TRANSIENT.equals(el.getFormat()) || + NameID.UNSPECIFIED.equals(el.getFormat())) + break; + + } + } + } + + if (NameID.TRANSIENT.equals(nameIDFormat) || NameID.UNSPECIFIED.equals(nameIDFormat)) { + String random = Random.nextHexRandom32(); + String nameID = subjectNameID.getValue(); + + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] hash = md.digest((nameID + random).getBytes("ISO-8859-1")); + subjectNameID.setValue(Base64Utils.encodeToString(hash)); + subjectNameID.setNameQualifier(null); + subjectNameID.setFormat(NameID.TRANSIENT); + + } catch (Exception e) { + log.warn("PVP2 subjectNameID error", e); + throw new ResponderErrorException("pvp2.13", null, e); + } + + } else + subjectNameID.setFormat(nameIDFormat); + + + String sessionIndex = null; + + //if request is a reauthentication and NameIDFormat match reuse old session information + if (StringUtils.isNotEmpty(authData.getNameID()) && + StringUtils.isNotEmpty(authData.getNameIDFormat()) && + nameIDFormat.equals(authData.getNameIDFormat())) { + subjectNameID.setValue(authData.getNameID()); + sessionIndex = authData.getSessionIndex(); + + } + + // + if (StringUtils.isEmpty(sessionIndex)) + sessionIndex = SAML2Utils.getSecureIdentifier(); + + SubjectConfirmationData subjectConfirmationData = SAML2Utils + .createSAMLObject(SubjectConfirmationData.class); + subjectConfirmationData.setInResponseTo(authnRequest.getID()); + subjectConfirmationData.setNotOnOrAfter(new DateTime(authData.getSsoSessionValidTo().getTime())); +// subjectConfirmationData.setNotBefore(date); + + //set 'recipient' attribute in subjectConformationData + subjectConfirmationData.setRecipient(assertionConsumerService.getLocation()); + + //set IP address of the user machine as 'Address' attribute in subjectConformationData + String usersIPAddress = pendingReq.getGenericData( + RequestImpl.DATAID_REQUESTER_IP_ADDRESS, String.class); + if (StringUtils.isNotEmpty(usersIPAddress)) + subjectConfirmationData.setAddress(usersIPAddress); + + //set SLO information + sloInformation.setUserNameIdentifier(subjectNameID.getValue()); + sloInformation.setNameIDFormat(subjectNameID.getFormat()); + sloInformation.setSessionIndex(sessionIndex); + + return buildGenericAssertion(issuerEntityID, peerEntity.getEntityID(), date, authnContextClassRef, attrList, subjectNameID, subjectConfirmationData, sessionIndex, subjectConfirmationData.getNotOnOrAfter()); + } + + /** + * + * @param issuer IDP EntityID + * @param entityID Service Provider EntityID + * @param date + * @param authnContextClassRef + * @param attrList + * @param subjectNameID + * @param subjectConfirmationData + * @param sessionIndex + * @param isValidTo + * @return + * @throws ConfigurationException + */ + + public Assertion buildGenericAssertion(String issuer, String entityID, DateTime date, + AuthnContextClassRef authnContextClassRef, List<Attribute> attrList, + NameID subjectNameID, SubjectConfirmationData subjectConfirmationData, + String sessionIndex, DateTime isValidTo) throws ResponderErrorException { + Assertion assertion = SAML2Utils.createSAMLObject(Assertion.class); + + AuthnContext authnContext = SAML2Utils + .createSAMLObject(AuthnContext.class); + authnContext.setAuthnContextClassRef(authnContextClassRef); + + AuthnStatement authnStatement = SAML2Utils + .createSAMLObject(AuthnStatement.class); + + authnStatement.setAuthnInstant(date); + authnStatement.setSessionIndex(sessionIndex); + authnStatement.setAuthnContext(authnContext); + + assertion.getAuthnStatements().add(authnStatement); + + AttributeStatement attributeStatement = SAML2Utils + .createSAMLObject(AttributeStatement.class); + attributeStatement.getAttributes().addAll(attrList); + if (attributeStatement.getAttributes().size() > 0) { + assertion.getAttributeStatements().add(attributeStatement); + } + + Subject subject = SAML2Utils.createSAMLObject(Subject.class); + subject.setNameID(subjectNameID); + + SubjectConfirmation subjectConfirmation = SAML2Utils + .createSAMLObject(SubjectConfirmation.class); + subjectConfirmation.setMethod(SubjectConfirmation.METHOD_BEARER); + subjectConfirmation.setSubjectConfirmationData(subjectConfirmationData); + + subject.getSubjectConfirmations().add(subjectConfirmation); + + Conditions conditions = SAML2Utils.createSAMLObject(Conditions.class); + AudienceRestriction audienceRestriction = SAML2Utils + .createSAMLObject(AudienceRestriction.class); + Audience audience = SAML2Utils.createSAMLObject(Audience.class); + + audience.setAudienceURI(entityID); + audienceRestriction.getAudiences().add(audience); + conditions.setNotBefore(date); + conditions.setNotOnOrAfter(isValidTo); + + conditions.getAudienceRestrictions().add(audienceRestriction); + + assertion.setConditions(conditions); + + Issuer issuerObj = SAML2Utils.createSAMLObject(Issuer.class); + + if (issuer.endsWith("/")) + issuer = issuer.substring(0, issuer.length()-1); + issuerObj.setValue(issuer); + issuerObj.setFormat(NameID.ENTITY); + + assertion.setIssuer(issuerObj); + assertion.setSubject(subject); + assertion.setID(SAML2Utils.getSecureIdentifier()); + assertion.setIssueInstant(date); + + return assertion; + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/resources/META-INF/services/at.gv.egiz.components.spring.api.SpringResourceProvider b/eaaf_modules/eaaf_module_pvp2_idp/src/main/resources/META-INF/services/at.gv.egiz.components.spring.api.SpringResourceProvider new file mode 100644 index 00000000..cda12a62 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/resources/META-INF/services/at.gv.egiz.components.spring.api.SpringResourceProvider @@ -0,0 +1 @@ +at.gv.egiz.eaaf.modules.pvp2.idp.PVP2SProfileIDPSpringResourceProvider
\ No newline at end of file diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/resources/eaaf_pvp_idp.beans.xml b/eaaf_modules/eaaf_module_pvp2_idp/src/main/resources/eaaf_pvp_idp.beans.xml new file mode 100644 index 00000000..a54482e9 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/resources/eaaf_pvp_idp.beans.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:context="http://www.springframework.org/schema/context" + xmlns:tx="http://www.springframework.org/schema/tx" + xmlns:aop="http://www.springframework.org/schema/aop" + xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> + + <bean id="PVP2AssertionBuilder" + class="at.gv.egiz.eaaf.modules.pvp2.idp.impl.builder.PVP2AssertionBuilder" /> + + <bean id="PVPSProfilePendingRequest" + class="at.gv.egiz.eaaf.modules.pvp2.idp.impl.PVPSProfilePendingRequest" + scope="prototype"/> + +</beans>
\ No newline at end of file diff --git a/eaaf_modules/eaaf_module_pvp2_sp/pom.xml b/eaaf_modules/eaaf_module_pvp2_sp/pom.xml new file mode 100644 index 00000000..0ec3895d --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_sp/pom.xml @@ -0,0 +1,71 @@ +<?xml version="1.0"?> +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>at.gv.egiz.eaaf</groupId> + <artifactId>eaaf_modules</artifactId> + <version>1.x</version> + </parent> + <artifactId>eaaf_module_pvp2_sp</artifactId> + <version>${egiz.eaaf.version}</version> + <name>eaaf_module_pvp2_sp</name> + <url>http://maven.apache.org</url> + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + <dependencies> + <dependency> + <groupId>at.gv.egiz.eaaf</groupId> + <artifactId>eaaf_module_pvp2_core</artifactId> + <version>${egiz.eaaf.version}</version> + </dependency> + + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <finalName>eaaf_module_pvp2_sp</finalName> + + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.7.0</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + + <!-- enable co-existence of testng and junit --> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <version>${surefire.version}</version> + <configuration> + <threadCount>1</threadCount> + <argLine>--add-modules java.xml.bind</argLine> + </configuration> + <dependencies> + <dependency> + <groupId>org.apache.maven.surefire</groupId> + <artifactId>surefire-junit47</artifactId> + <version>${surefire.version}</version> + </dependency> + </dependencies> + </plugin> + + </plugins> + </build> + +</project> diff --git a/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/META-INF/MANIFEST.MF b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 00000000..254272e1 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/api/IPVPAuthnRequestBuilderConfiguruation.java b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/api/IPVPAuthnRequestBuilderConfiguruation.java new file mode 100644 index 00000000..b0439775 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/api/IPVPAuthnRequestBuilderConfiguruation.java @@ -0,0 +1,142 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.sp.api; + +import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.xml.security.credential.Credential; +import org.w3c.dom.Element; + +/** + * @author tlenz + * + */ +public interface IPVPAuthnRequestBuilderConfiguruation { + + /** + * Defines a unique name for this PVP Service-provider, which is used for logging + * + * @return + */ + public String getSPNameForLogging(); + + /** + * If true, the SAML2 isPassive flag is set in the AuthnRequest + * + * @return + */ + public Boolean isPassivRequest(); + + /** + * Define the ID of the AssertionConsumerService, + * which defines the required attributes in service-provider metadata. + * + * @return + */ + public Integer getAssertionConsumerServiceId(); + + /** + * Define the SAML2 EntityID of the service provider. + * + * @return + */ + public String getSPEntityID(); + + /** + * Define the SAML2 NameIDPolicy + * + * @return Service-Provider EntityID, but never null + */ + public String getNameIDPolicyFormat(); + + /** + * Define the AuthnContextClassRefernece of this request + * + * Example: + * http://www.ref.gv.at/ns/names/agiz/pvp/secclass/0-3 + * http://www.stork.gov.eu/1.0/citizenQAALevel/4 + * + * + * @return + */ + public String getAuthnContextClassRef(); + + /** + * Define the AuthnContextComparison model, which should be used + * + * @return + */ + public AuthnContextComparisonTypeEnumeration getAuthnContextComparison(); + + + /** + * Define the credential, which should be used to sign the AuthnRequest + * + * @return + */ + public Credential getAuthnRequestSigningCredential(); + + + /** + * Define the SAML2 EntityDescriptor of the IDP, which should receive the AuthnRequest + * + * @return Credential, but never null. + */ + public EntityDescriptor getIDPEntityDescriptor(); + + /** + * Set the SAML2 NameIDPolicy allow-creation flag + * + * @return EntityDescriptor, but never null. + */ + public boolean getNameIDPolicyAllowCreation(); + + + /** + * Set the requested SubjectNameID + * + * @return SubjectNameID, or null if no SubjectNameID should be used + */ + public String getSubjectNameID(); + + /** + * Define the qualifier of the <code>SubjectNameID</code> + * <br><br> + * Like: 'urn:publicid:gv.at:cdid+BF' + * + * @return qualifier, or null if no qualifier should be set + */ + public String getSubjectNameIDQualifier(); + + /** + * Define the format of the subjectNameID, which is included in authn-request + * + * + * @return nameIDFormat, of SAML2 'transient' if nothing is defined + */ + public String getSubjectNameIDFormat(); + + /** + * Define a SP specific SAML2 requestID + * + * @return requestID, or null if the requestID should be generated automatically + */ + public String getRequestID(); + + /** + * Defines the 'method' attribute in 'SubjectConformation' element + * + * @return method, or null if no method should set + */ + public String getSubjectConformationMethode(); + + /** + * Define the information, which should be added as 'subjectConformationDate' + * in 'SubjectConformation' element + * + * @return subjectConformation information or null if no subjectConformation should be set + */ + public Element getSubjectConformationDate(); + + +} diff --git a/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/exception/AssertionAttributeExtractorExeption.java b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/exception/AssertionAttributeExtractorExeption.java new file mode 100644 index 00000000..dccb0b22 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/exception/AssertionAttributeExtractorExeption.java @@ -0,0 +1,32 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.sp.exception; + +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + +/** + * @author tlenz + * + */ +public class AssertionAttributeExtractorExeption extends PVP2Exception { + + /** + * + */ + private static final long serialVersionUID = -6459000942830951492L; + + public AssertionAttributeExtractorExeption(String attributeName) { + super("Parse PVP2.1 assertion FAILED: Attribute " + attributeName + + " can not extract.", null); + } + + public AssertionAttributeExtractorExeption(String messageId, + Object[] parameters) { + super(messageId, parameters); + } + + public AssertionAttributeExtractorExeption() { + super("Parse PVP2.1 assertion FAILED. Interfederation not possible", null); + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/exception/AssertionValidationExeption.java b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/exception/AssertionValidationExeption.java new file mode 100644 index 00000000..aec71501 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/exception/AssertionValidationExeption.java @@ -0,0 +1,29 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.sp.exception; + +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + +/** + * @author tlenz + * + */ +public class AssertionValidationExeption extends PVP2Exception { + + private static final long serialVersionUID = -3987805399122286259L; + + public AssertionValidationExeption(String messageId, Object[] parameters) { + super(messageId, parameters); + } + + /** + * @param string + * @param object + * @param e + */ + public AssertionValidationExeption(String string, Object[] parameters, + Throwable e) { + super(string, parameters, e); + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/exception/AuthnRequestBuildException.java b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/exception/AuthnRequestBuildException.java new file mode 100644 index 00000000..bdb158b6 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/exception/AuthnRequestBuildException.java @@ -0,0 +1,29 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.sp.exception; + +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + +/** + * @author tlenz + * + */ +public class AuthnRequestBuildException extends PVP2Exception { + + /** + * + */ + private static final long serialVersionUID = -1375451065455859354L; + + /** + * @param messageId + * @param parameters + */ + public AuthnRequestBuildException(String messageId, Object[] parameters) { + super(messageId, parameters); + } + + public AuthnRequestBuildException(String messageId, Object[] parameters, Throwable e) { + super(messageId, parameters, e); + } +} diff --git a/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/exception/AuthnResponseValidationException.java b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/exception/AuthnResponseValidationException.java new file mode 100644 index 00000000..6f11f1c2 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/exception/AuthnResponseValidationException.java @@ -0,0 +1,30 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.sp.exception; + +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; + +/** + * @author tlenz + * + */ +public class AuthnResponseValidationException extends PVP2Exception { + + /** + * + */ + private static final long serialVersionUID = 8023812861029406575L; + + /** + * @param messageId + * @param parameters + */ + public AuthnResponseValidationException(String messageId, Object[] parameters) { + super(messageId, parameters); + } + + public AuthnResponseValidationException(String messageId, Object[] parameters, Throwable e) { + super(messageId, parameters, e); + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/PVPAuthnRequestBuilder.java b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/PVPAuthnRequestBuilder.java new file mode 100644 index 00000000..9b02dc77 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/PVPAuthnRequestBuilder.java @@ -0,0 +1,205 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.sp.impl; + +import java.security.NoSuchAlgorithmException; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.opensaml.common.impl.SecureRandomIdentifierGenerator; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.core.AuthnContextClassRef; +import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration; +import org.opensaml.saml2.core.AuthnRequest; +import org.opensaml.saml2.core.Issuer; +import org.opensaml.saml2.core.NameID; +import org.opensaml.saml2.core.NameIDPolicy; +import org.opensaml.saml2.core.NameIDType; +import org.opensaml.saml2.core.RequestedAuthnContext; +import org.opensaml.saml2.core.Subject; +import org.opensaml.saml2.core.SubjectConfirmation; +import org.opensaml.saml2.core.SubjectConfirmationData; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.SingleSignOnService; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.xml.security.SecurityException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.modules.pvp2.api.binding.IEncoder; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.PostBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.RedirectBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; +import at.gv.egiz.eaaf.modules.pvp2.sp.api.IPVPAuthnRequestBuilderConfiguruation; +import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AuthnRequestBuildException; + +/** + * @author tlenz + * + */ +@Service("PVPAuthnRequestBuilder") +public class PVPAuthnRequestBuilder { + private static final Logger log = LoggerFactory.getLogger(PVPAuthnRequestBuilder.class); + + + @Autowired(required=true) ApplicationContext springContext; + + + /** + * Build a PVP2.x specific authentication request + * + * @param pendingReq Currently processed pendingRequest + * @param config AuthnRequest builder configuration, never null + * @param idpEntity SAML2 EntityDescriptor of the IDP, which receive this AuthnRequest, never null + * @param httpResp + * @throws NoSuchAlgorithmException + * @throws SecurityException + * @throws PVP2Exception + * @throws MessageEncodingException + */ + public void buildAuthnRequest(IRequest pendingReq, IPVPAuthnRequestBuilderConfiguruation config, + HttpServletResponse httpResp) throws NoSuchAlgorithmException, MessageEncodingException, PVP2Exception, SecurityException { + //get IDP Entity element from config + EntityDescriptor idpEntity = config.getIDPEntityDescriptor(); + + AuthnRequest authReq = SAML2Utils + .createSAMLObject(AuthnRequest.class); + + //select SingleSignOn Service endpoint from IDP metadata + SingleSignOnService endpoint = null; + for (SingleSignOnService sss : + idpEntity.getIDPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleSignOnServices()) { + + // use POST binding as default if it exists + if (sss.getBinding().equals(SAMLConstants.SAML2_POST_BINDING_URI)) { + endpoint = sss; + + } else if ( sss.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI) + && endpoint == null ) + endpoint = sss; + + } + + if (endpoint == null) { + log.warn("Building AuthnRequest FAILED: > Requested IDP " + idpEntity.getEntityID() + + " does not support POST or Redirect Binding."); + throw new AuthnRequestBuildException("sp.pvp2.00", new Object[]{config.getSPNameForLogging(), idpEntity.getEntityID()}); + + } else + authReq.setDestination(endpoint.getLocation()); + + + //set basic AuthnRequest information + String reqID = config.getRequestID(); + if (StringUtils.isNotEmpty(reqID)) + authReq.setID(reqID); + + else { + SecureRandomIdentifierGenerator gen = new SecureRandomIdentifierGenerator(); + authReq.setID(gen.generateIdentifier()); + + } + + authReq.setIssueInstant(new DateTime()); + + //set isPassive flag + if (config.isPassivRequest() == null) + authReq.setIsPassive(false); + else + authReq.setIsPassive(config.isPassivRequest()); + + //set EntityID of the service provider + Issuer issuer = SAML2Utils.createSAMLObject(Issuer.class); + issuer.setFormat(NameIDType.ENTITY); + issuer.setValue(config.getSPEntityID()); + authReq.setIssuer(issuer); + + //set AssertionConsumerService ID + if (config.getAssertionConsumerServiceId() != null) + authReq.setAssertionConsumerServiceIndex(config.getAssertionConsumerServiceId()); + + //set NameIDPolicy + if (config.getNameIDPolicyFormat() != null) { + NameIDPolicy policy = SAML2Utils.createSAMLObject(NameIDPolicy.class); + policy.setAllowCreate(config.getNameIDPolicyAllowCreation()); + policy.setFormat(config.getNameIDPolicyFormat()); + authReq.setNameIDPolicy(policy); + } + + //set requested QAA level + if (config.getAuthnContextClassRef() != null) { + RequestedAuthnContext reqAuthContext = SAML2Utils.createSAMLObject(RequestedAuthnContext.class); + AuthnContextClassRef authnClassRef = SAML2Utils.createSAMLObject(AuthnContextClassRef.class); + + authnClassRef.setAuthnContextClassRef(config.getAuthnContextClassRef()); + + if (config.getAuthnContextComparison() == null) + reqAuthContext.setComparison(AuthnContextComparisonTypeEnumeration.MINIMUM); + else + reqAuthContext.setComparison(config.getAuthnContextComparison()); + + reqAuthContext.getAuthnContextClassRefs().add(authnClassRef); + authReq.setRequestedAuthnContext(reqAuthContext); + } + + //set request Subject element + if (StringUtils.isNotEmpty(config.getSubjectNameID())) { + Subject reqSubject = SAML2Utils.createSAMLObject(Subject.class); + NameID subjectNameID = SAML2Utils.createSAMLObject(NameID.class); + + subjectNameID.setValue(config.getSubjectNameID()); + if (StringUtils.isNotEmpty(config.getSubjectNameIDQualifier())) + subjectNameID.setNameQualifier(config.getSubjectNameIDQualifier()); + + if (StringUtils.isNotEmpty(config.getSubjectNameIDFormat())) + subjectNameID.setFormat(config.getSubjectNameIDFormat()); + else + subjectNameID.setFormat(NameID.TRANSIENT); + + reqSubject.setNameID(subjectNameID); + + if (config.getSubjectConformationDate() != null) { + SubjectConfirmation subjectConformation = SAML2Utils.createSAMLObject(SubjectConfirmation.class); + SubjectConfirmationData subjectConformDate = SAML2Utils.createSAMLObject(SubjectConfirmationData.class); + subjectConformation.setSubjectConfirmationData(subjectConformDate); + reqSubject.getSubjectConfirmations().add(subjectConformation ); + + if (config.getSubjectConformationMethode() != null) + subjectConformation.setMethod(config.getSubjectConformationMethode()); + + subjectConformDate.setDOM(config.getSubjectConformationDate()); + + } + + authReq.setSubject(reqSubject ); + + } + + //TODO: implement requested attributes + //maybe: config.getRequestedAttributes(); + + //select message encoder + IEncoder binding = null; + if (endpoint.getBinding().equals( + SAMLConstants.SAML2_REDIRECT_BINDING_URI)) { + binding = springContext.getBean("PVPRedirectBinding", RedirectBinding.class); + + } else if (endpoint.getBinding().equals( + SAMLConstants.SAML2_POST_BINDING_URI)) { + binding = springContext.getBean("PVPPOSTBinding", PostBinding.class); + + } + + //encode message + binding.encodeRequest(null, httpResp, authReq, + endpoint.getLocation(), pendingReq.getPendingRequestId(), config.getAuthnRequestSigningCredential(), pendingReq); + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/utils/AssertionAttributeExtractor.java b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/utils/AssertionAttributeExtractor.java new file mode 100644 index 00000000..1674e3fd --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/utils/AssertionAttributeExtractor.java @@ -0,0 +1,275 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.sp.impl.utils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.saml2.core.Assertion; +import org.opensaml.saml2.core.Attribute; +import org.opensaml.saml2.core.AttributeStatement; +import org.opensaml.saml2.core.AuthnContextClassRef; +import org.opensaml.saml2.core.AuthnStatement; +import org.opensaml.saml2.core.Response; +import org.opensaml.saml2.core.StatusResponseType; +import org.opensaml.saml2.core.Subject; +import org.opensaml.xml.XMLObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.eaaf.modules.pvp2.PVPConstants; +import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AssertionAttributeExtractorExeption; + +public class AssertionAttributeExtractor { + + private static final Logger log = LoggerFactory.getLogger(AssertionAttributeExtractor.class); + + private Assertion assertion = null; + private Map<String, List<String>> attributs = new HashMap<String, List<String>>(); + //private PersonalAttributeList storkAttributes = new PersonalAttributeList(); + + private final List<String> minimalMDSAttributeNamesList = Arrays.asList( + PVPConstants.PRINCIPAL_NAME_NAME, + PVPConstants.GIVEN_NAME_NAME, + PVPConstants.BIRTHDATE_NAME, + PVPConstants.BPK_NAME); + + private final List<String> minimalIDLAttributeNamesList = Arrays.asList( + PVPConstants.EID_IDENTITY_LINK_NAME, + PVPConstants.EID_SOURCE_PIN_NAME, + PVPConstants.EID_SOURCE_PIN_TYPE_NAME); + + /** + * Parse the SAML2 Response element and extracts included information + * <br><br> + * <b>INFO:</b> Actually, only the first SAML2 Assertion of the SAML2 Response is used! + * + * @param samlResponse SAML2 Response + * @throws AssertionAttributeExtractorExeption + */ + public AssertionAttributeExtractor(StatusResponseType samlResponse) throws AssertionAttributeExtractorExeption { + if (samlResponse != null && samlResponse instanceof Response) { + List<Assertion> assertions = ((Response) samlResponse).getAssertions(); + if (assertions.size() == 0) + throw new AssertionAttributeExtractorExeption("Assertion"); + + else if (assertions.size() > 1) + log.warn("Found more then ONE PVP2.1 assertions. Only the First is used."); + + assertion = assertions.get(0); + + if (assertion.getAttributeStatements() != null && + assertion.getAttributeStatements().size() > 0) { + AttributeStatement attrStat = assertion.getAttributeStatements().get(0); + for (Attribute attr : attrStat.getAttributes()) { + if (attr.getName().startsWith(PVPConstants.STORK_ATTRIBUTE_PREFIX)) { + List<String> storkAttrValues = new ArrayList<String>(); + for (XMLObject el : attr.getAttributeValues()) + storkAttrValues.add(el.getDOM().getTextContent()); + +// PersonalAttribute storkAttr = new PersonalAttribute(attr.getName(), +// false, storkAttrValues , "Available"); +// storkAttributes.put(attr.getName(), storkAttr ); + + } else { + List<String> attrList = new ArrayList<String>(); + for (XMLObject el : attr.getAttributeValues()) + attrList.add(el.getDOM().getTextContent()); + + attributs.put(attr.getName(), attrList); + + } + } + + } + + } else + throw new AssertionAttributeExtractorExeption(); + } + + /** + * Get all SAML2 attributes from first SAML2 AttributeStatement element + * + * @return List of SAML2 Attributes + */ + public List<Attribute> getAllResponseAttributesFromFirstAttributeStatement() { + return assertion.getAttributeStatements().get(0).getAttributes(); + + } + + /** + * Get all SAML2 attributes of specific SAML2 AttributeStatement element + * + * @param attrStatementID List ID of the AttributeStatement element + * @return List of SAML2 Attributes + */ + public List<Attribute> getAllResponseAttributes(int attrStatementID) { + return assertion.getAttributeStatements().get(attrStatementID).getAttributes(); + + } + + /** + * check attributes from assertion with minimal required attribute list + * @return + */ + public boolean containsAllRequiredAttributes() { + return containsAllRequiredAttributes(minimalMDSAttributeNamesList) + || containsAllRequiredAttributes(minimalIDLAttributeNamesList); + + } + + /** + * check attributes from assertion with attributeNameList + * bPK or enc_bPK are always needed + * + * @param List of attributes which are required + * + * @return + */ + public boolean containsAllRequiredAttributes(Collection<String> attributeNameList) { + + //first check if a bPK or an encrypted bPK is available + boolean flag = true; + for (String attr : attributeNameList) { + if (!attributs.containsKey(attr)) { + flag = false; + log.debug("Assertion contains no Attribute " + attr); + + } + + } + + if (flag) + return flag; + + else { + log.debug("Assertion contains no all minimum attributes from: " + attributeNameList.toString()); + return false; + + } + } + + public boolean containsAttribute(String attributeName) { + return attributs.containsKey(attributeName); + + } + + public String getSingleAttributeValue(String attributeName) { + if (attributs.containsKey(attributeName) && attributs.get(attributeName).size() > 0) + return attributs.get(attributeName).get(0); + else + return null; + + } + + public List<String> getAttributeValues(String attributeName) { + return attributs.get(attributeName); + + } + + /** + * Return all include PVP attribute names + * + * @return + */ + public Set<String> getAllIncludeAttributeNames() { + return attributs.keySet(); + + } + +// public PersonalAttributeList getSTORKAttributes() { +// return storkAttributes; +// } + + + public String getNameID() throws AssertionAttributeExtractorExeption { + if (assertion.getSubject() != null) { + Subject subject = assertion.getSubject(); + + if (subject.getNameID() != null) { + if (StringUtils.isNotEmpty(subject.getNameID().getValue())) + return subject.getNameID().getValue(); + + else + log.error("SAML2 NameID Element is empty."); + } + } + + throw new AssertionAttributeExtractorExeption("nameID"); + } + + public String getSessionIndex() throws AssertionAttributeExtractorExeption { + AuthnStatement authn = getAuthnStatement(); + + if (StringUtils.isNotEmpty(authn.getSessionIndex())) + return authn.getSessionIndex(); + + else + throw new AssertionAttributeExtractorExeption("SessionIndex"); + } + + /** + * @return + * @throws AssertionAttributeExtractorExeption + */ + public String getQAALevel() throws AssertionAttributeExtractorExeption { + AuthnStatement authn = getAuthnStatement(); + if (authn.getAuthnContext() != null && authn.getAuthnContext().getAuthnContextClassRef() != null) { + AuthnContextClassRef qaaClass = authn.getAuthnContext().getAuthnContextClassRef(); + + if (StringUtils.isNotEmpty(qaaClass.getAuthnContextClassRef())) + return qaaClass.getAuthnContextClassRef(); + + else + throw new AssertionAttributeExtractorExeption("AuthnContextClassRef (QAALevel)"); + } + + throw new AssertionAttributeExtractorExeption("AuthnContextClassRef"); + } + + public Assertion getFullAssertion() { + return assertion; + } + + + /** + * Get the Assertion validTo period + * + * Primarily, the 'SessionNotOnOrAfter' attribute in the SAML2 'AuthnStatment' element is used. + * If this is empty, this method returns value of SAML 'Conditions' element. + * + * @return Date, until this SAML2 assertion is valid + */ + public Date getAssertionNotOnOrAfter() { + if (getFullAssertion().getAuthnStatements() != null + && getFullAssertion().getAuthnStatements().size() > 0) { + for (AuthnStatement el : getFullAssertion().getAuthnStatements()) { + if (el.getSessionNotOnOrAfter() != null) + return (el.getSessionNotOnOrAfter().toDate()); + } + + } + + return getFullAssertion().getConditions().getNotOnOrAfter().toDate(); + + } + + private AuthnStatement getAuthnStatement() throws AssertionAttributeExtractorExeption { + List<AuthnStatement> authnList = assertion.getAuthnStatements(); + if (authnList.size() == 0) + throw new AssertionAttributeExtractorExeption("AuthnStatement"); + + else if (authnList.size() > 1) + log.warn("Found more then ONE AuthnStatements in PVP2.1 assertions. Only the First is used."); + + return authnList.get(0); + } + +} diff --git a/eaaf_modules/pom.xml b/eaaf_modules/pom.xml new file mode 100644 index 00000000..68bc60aa --- /dev/null +++ b/eaaf_modules/pom.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>at.gv.egiz</groupId> + <artifactId>eaaf</artifactId> + <version>1.x</version> + </parent> + + <groupId>at.gv.egiz.eaaf</groupId> + <artifactId>eaaf_modules</artifactId> + <packaging>pom</packaging> + + <name>Modules for EAAF components</name> + + <modules> + <!--module>authmodule-eIDAS-v2</module--> + <module>eaaf_module_pvp2_core</module> + <module>eaaf_module_pvp2_idp</module> + <module>eaaf_module_pvp2_sp</module> + </modules> + +</project>
\ No newline at end of file |