diff options
Diffstat (limited to 'eaaf_modules/eaaf_module_auth_sl20')
19 files changed, 2709 insertions, 0 deletions
| diff --git a/eaaf_modules/eaaf_module_auth_sl20/pom.xml b/eaaf_modules/eaaf_module_auth_sl20/pom.xml new file mode 100644 index 00000000..722b334a --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/pom.xml @@ -0,0 +1,115 @@ +<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.eaaf</groupId> +    <artifactId>eaaf_modules</artifactId> +    <version>1.x</version> +  </parent> +  <artifactId>eaaf_module_auth_sl20</artifactId> +  <version>${egiz.eaaf.version}</version> +  <name>Generic SL2.0 authentication</name> +   +   <licenses> +    <license> +      <name>European Union Public License, version 1.2 (EUPL-1.2)</name> +      <url>https://opensource.org/licenses/EUPL-1.2</url> +      <distribution>repo</distribution> +    </license> +  </licenses> + +  <developers> +    <developer> +      <name>Thomas Lenz</name> +      <email>thomas.lenz@egiz.gv.at</email> +      <organization>eGovernment Innovation Center (EGIZ)</organization> +      <organizationUrl>https://www.egiz.gv.at</organizationUrl> +    </developer> +  </developers> +   +  <dependencies> +  	<dependency> +		<groupId>at.gv.egiz.eaaf</groupId> +	  	<artifactId>eaaf-core</artifactId> +	  	<version>${egiz.eaaf.version}</version> +	</dependency> +  	<dependency> +    	<groupId>org.bitbucket.b_c</groupId> +    	<artifactId>jose4j</artifactId> +  	</dependency> +  	<dependency> +    	<groupId>com.fasterxml.jackson.core</groupId> +    	<artifactId>jackson-databind</artifactId> +	</dependency> +  	 +	<dependency> +    	<groupId>javax.servlet</groupId> +    	<artifactId>javax.servlet-api</artifactId> +    	<scope>provided</scope> +	</dependency>  	  	 +  </dependencies> +   +  <build> +   	<resources> +  		<resource> +  			<directory>src/main/resources</directory> +  		</resource> +  	</resources> +   +     <plugins> +      <plugin> +        <groupId>org.apache.maven.plugins</groupId> +        <artifactId>maven-compiler-plugin</artifactId> +        <version>3.8.0</version> +        <configuration> +          <source>${java.version}</source> +          <target>${java.version}</target> +        </configuration> +      </plugin> + +      <plugin> +        <groupId>org.apache.maven.plugins</groupId> +        <artifactId>maven-jar-plugin</artifactId> +        <version>3.1.1</version> +        <configuration> +        	<archive> +            	<manifest> +              		<addClasspath>true</addClasspath> +              		<addDefaultImplementationEntries>true</addDefaultImplementationEntries> +              		<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries> +            	</manifest> +          	</archive>             +        </configuration> +      </plugin> + +      <plugin> +        <groupId>org.apache.maven.plugins</groupId> +        <artifactId>maven-clean-plugin</artifactId> +        <version>3.1.0</version> +        <configuration> +          <filesets> +            <fileset> +              <directory>test-output</directory> +            </fileset> +          </filesets> +        </configuration> +      </plugin> +       +      <!-- enable co-existence of testng and junit --> +	  <plugin> +	  	<artifactId>maven-surefire-plugin</artifactId> +		<version>${surefire.version}</version> +		<configuration> +			<threadCount>1</threadCount>					 +		</configuration> +		<dependencies> +			<dependency> +				<groupId>org.apache.maven.surefire</groupId> +					<artifactId>surefire-junit47</artifactId> +					<version>${surefire.version}</version> +				</dependency> +			</dependencies> +	  </plugin> +  	</plugins> +  </build> +   +</project>
\ No newline at end of file diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/AbstractSL20AuthenticationModulImpl.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/AbstractSL20AuthenticationModulImpl.java new file mode 100644 index 00000000..b23df12e --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/AbstractSL20AuthenticationModulImpl.java @@ -0,0 +1,96 @@ +package at.gv.egiz.eaaf.modules.auth.sl20; + +import java.util.Arrays; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.api.idp.auth.modules.AuthModule; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.impl.idp.auth.AbstractAuthenticationManager; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20Constants; + + +/** + * @author tlenz + * + */ +public abstract class AbstractSL20AuthenticationModulImpl implements AuthModule {	 +	private static final Logger log = LoggerFactory.getLogger(AbstractSL20AuthenticationModulImpl.class); +	 +	private int priority = 3; +	public static final List<String> VDA_TYPE_IDS = Arrays.asList("1", "2", "3", "4"); +	 +	@Autowired(required=true) protected IConfiguration authConfig; +	@Autowired(required=true) private AbstractAuthenticationManager authManager; +	 +	@Override +	public int getPriority() {  +		return priority;  +	} + +	/** +	 * Sets the priority of this module. Default value is {@code 0}. +	 * @param priority The priority. +	 */ +	public void setPriority(int priority) { +		this.priority = priority; +	} + +	@PostConstruct +	protected void initalSL20Authentication() { +		//parameter to whiteList +		authManager.addHeaderNameToWhiteList(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE); +		authManager.addHeaderNameToWhiteList(SL20Constants.HTTP_HEADER_SL20_VDA_TYPE); +		 +	} +	 +	 +	/* (non-Javadoc) +	 * @see at.gv.egovernment.moa.id.auth.modules.AuthModule#selectProcess(at.gv.egovernment.moa.id.process.api.ExecutionContext) +	 */ +	@Override +	public String selectProcess(ExecutionContext context) { +		ISPConfiguration spConfig = (ISPConfiguration) context.get(EAAFConstants.PROCESSCONTEXT_SP_CONFIG); +		 +		String sl20ClientTypeHeader = (String) context.get(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE.toLowerCase()); +		String sl20VDATypeHeader = (String)  context.get(SL20Constants.HTTP_HEADER_SL20_VDA_TYPE.toLowerCase()); +				 +		if (spConfig != null &&  +				StringUtils.isNotEmpty(spConfig.getConfigurationValue(getConfigPropertyNameEnableModule())) && +					Boolean.valueOf(spConfig.getConfigurationValue(getConfigPropertyNameEnableModule()))) { +			log.debug("SL2.0 is enabled for " + spConfig.getUniqueIdentifier()); +			log.trace(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE +  ": " + sl20ClientTypeHeader);			 +			log.trace(SL20Constants.HTTP_HEADER_SL20_VDA_TYPE +  ": " + sl20VDATypeHeader); +			return "SL20Authentication"; +			 +		} else { +			log.trace("SL2.0 is NOT enabled for " + spConfig.getUniqueIdentifier()); +			return null; +			 +		} +						 +	} + +	/** +	 * Get the SP specific configuration-key that holds the enabled key for this authentication module  +	 *  +	 * @return configuration key for SP configuration +	 */ +	public abstract String getConfigPropertyNameEnableModule(); +	 +	/* (non-Javadoc) +	 * @see at.gv.egovernment.moa.id.auth.modules.AuthModule#getProcessDefinitions() +	 */ +	@Override +	public abstract String[] getProcessDefinitions(); + +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java new file mode 100644 index 00000000..2adb9172 --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java @@ -0,0 +1,54 @@ +package at.gv.egiz.eaaf.modules.auth.sl20; + +public class Constants { +	 +	public static final String CONFIG_PROP_PREFIX = "modules.sl20"; +	public static final String CONFIG_PROP_VDA_ENDPOINT_QUALeID = CONFIG_PROP_PREFIX + ".vda.urls.qualeID.endpoint."; +	public static final String CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT = "default"; +	public static final String CONFIG_PROP_VDA_AUTHBLOCK_TRANSFORMATION_ID = CONFIG_PROP_PREFIX + ".vda.authblock.transformation.id";	 +	public static final String CONFIG_PROP_SECURITY_KEYSTORE_PATH = CONFIG_PROP_PREFIX + ".security.keystore.path"; +	public static final String CONFIG_PROP_SECURITY_KEYSTORE_PASSWORD = CONFIG_PROP_PREFIX + ".security.keystore.password"; +	public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_ALIAS = CONFIG_PROP_PREFIX + ".security.sign.alias"; +	public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_PASSWORD = CONFIG_PROP_PREFIX + ".security.sign.password"; +	public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_ALIAS = CONFIG_PROP_PREFIX + ".security.encryption.alias";; +	public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_PASSWORD = CONFIG_PROP_PREFIX + ".security.encryption.password"; +	 +	public static final String CONFIG_PROP_VDA_ENDPOINT_QUALeID_LIST = CONFIG_PROP_VDA_ENDPOINT_QUALeID; +	public static final String CONFIG_PROP_SP_LIST = CONFIG_PROP_PREFIX + ".sp.entityIds."; +	 +	public static final String CONFIG_PROP_DISABLE_EID_VALIDATION = CONFIG_PROP_PREFIX + ".security.eID.validation.disable"; +	public static final String CONFIG_PROP_ENABLE_EID_ENCRYPTION = CONFIG_PROP_PREFIX + ".security.eID.encryption.enabled"; +	public static final String CONFIG_PROP_FORCE_EID_ENCRYPTION = CONFIG_PROP_PREFIX + ".security.eID.encryption.required"; +	public static final String CONFIG_PROP_FORCE_EID_SIGNED_RESULT = CONFIG_PROP_PREFIX + ".security.eID.signed.result.required"; +	 +	public static final String CONFIG_PROP_IPC_RETURN_URL = CONFIG_PROP_PREFIX + ".ipc.return.url"; +	 +	 +	public static final String CONFIG_PROP_SP_ENABLE_SL20_AUTHENTICATION = "auth.sl20.enabled"; +	 +	public static final String PENDING_REQ_STORAGE_PREFIX = "SL20_AUTH_"; +	 +	/** +	 * Only dummy data for development!!!!!! 	   +	 */ +	public static final String DUMMY_SIGNING_CERT =  +			"MIIC9zCCAd8CBFretWcwDQYJKoZIhvcNAQEOBQAwQDELMAkGA1UEBhMCQVQxDTAL\n" +  +			"BgNVBAoMBEVHSVoxIjAgBgNVBAMMGW93biBkdW1teSBtZXRhZGF0YSBzaWduZXIw\n" +  +			"HhcNMTgwNDI0MDQ0MTExWhcNMjEwMTE3MDQ0MTExWjBAMQswCQYDVQQGEwJBVDEN\n" +  +			"MAsGA1UECgwERUdJWjEiMCAGA1UEAwwZb3duIGR1bW15IG1ldGFkYXRhIHNpZ25l\n" +  +			"cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJvN3l1pjzlnmoW5trHH\n" +  +			"Rb1s60QtGNp2v1nfMg1R6h7SzygtmO869v5bqrVBBVGmujslr7W8cZ2DLmJoQx1N\n" +  +			"WwhccjXTHpNPw0B70qHGch2uRNkqkizSOlwth0Ll2DJtzxTolbajYdg+xppXScUq\n" +  +			"WNlNZndauPSnB2CESgNkaUou4x4YVSDInugAtLvdLx8rf2YcuidI6UIXxeSZr3VO\n" +  +			"Z12YtddzcJ+lwh7OX8B0UvLsdYjKjefjEudyuNBmVwLv4K2LsFhSqgE1CAzk3oCb\n" +  +			"V2A84klaWVPiXoBiOucyouvX781WVp1aCBp0QA8gpJH7/2wRsdPQ90tjMzM7dcgY\n" +  +			"LDkCAwEAATANBgkqhkiG9w0BAQ4FAAOCAQEAQuYRQcCNLDYU1ItliYz9f28+KDyU\n" +  +			"8WjF3NDZrlJbGSKQ4n7wkBfxdK3zprmpHadWDB+aZaPt/+voE2FduzPiLUDlpazN\n" +  +			"60JJ5/YHZ3q9MZvdoNg6rjkpioWatoj/smUkT6oUWL/gp8tH12fOd2oJygBqXMve\n" +  +			"3y3qVCghnjRaMYuXcScTZcjH9yebkTLygirtw34oGVb7t+HwbtcN65fUIBly6Rcl\n" +  +			"8NV3pwOKhXFKDAqXUpvhebL4+tWOqPdqfIfGaE6rELfTf3icGY3CQCzDz5Gp0Ptc\n" +  +			"TfQqm64xnhtAruXNJXWg2ptg+GuQgWnJUgQ8wLNMxw9XdeEwlQo5dL6xmg=="; +	 +	public static final String DUMMY_SIGNING_CERT_FINGERPRINT = "IwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJvN3l1pjzlnmoW"; + +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/EventCodes.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/EventCodes.java new file mode 100644 index 00000000..9cc574bd --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/EventCodes.java @@ -0,0 +1,14 @@ +package at.gv.egiz.eaaf.modules.auth.sl20; + +/** + * Set of event codes uses in Auth-Handler implementation + *  + * @author tlenz + * + */ +public class EventCodes { + +	public static final int AUTHPROCESS_SL20_SELECTED = 4111; +	public static final int AUTHPROCESS_SL20_ENDPOINT_URL = 4112; +	public static final int AUTHPROCESS_SL20_DATAURL_IP = 4113; +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/data/VerificationResult.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/data/VerificationResult.java new file mode 100644 index 00000000..0c625a9b --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/data/VerificationResult.java @@ -0,0 +1,39 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.data; + +import java.security.cert.X509Certificate; +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; + +public class VerificationResult { + +	private Boolean validSigned = null; +	private List<X509Certificate> certs = null; +	private JsonNode payload = null; +	 +	public VerificationResult(JsonNode payload) { +		this.payload = payload; +		 +	} +	 +	public VerificationResult(JsonNode string, List<X509Certificate> certs, boolean wasValidSigned) { +		this.payload = string; +		this.certs = certs; +		this.validSigned = wasValidSigned; +		 +	} +	 +	public Boolean isValidSigned() { +		return validSigned; +	} +	public List<X509Certificate> getCertChain() { +		return certs; +	} +	public JsonNode getPayload() { +		return payload; +	} +	 +	 +	 +	 +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20Exception.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20Exception.java new file mode 100644 index 00000000..b23b5ca3 --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20Exception.java @@ -0,0 +1,19 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.exceptions; + +import at.gv.egiz.eaaf.core.exceptions.EAAFAuthenticationException; + +public class SL20Exception extends EAAFAuthenticationException { + +	private static final long serialVersionUID = 1L; + +	public SL20Exception(String messageId, Object[] parameters) { +		super(messageId, parameters); + +	} +	 +	public SL20Exception(String messageId, Object[] parameters, Throwable wrapped) { +		super(messageId, parameters, wrapped); + +	} + +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20SecurityException.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20SecurityException.java new file mode 100644 index 00000000..eaf55ba3 --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20SecurityException.java @@ -0,0 +1,20 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.exceptions; + +public class SL20SecurityException extends SL20Exception { + +	private static final long serialVersionUID = 3281385988027147449L; + +	public SL20SecurityException(Object[] parameters) { +		super("sl20.05", parameters); +	} +	 +	public SL20SecurityException(String parameter) { +		super("sl20.05", new Object[] {parameter}); +	} +	 +	public SL20SecurityException(Object[] parameters, Throwable wrapped) { +		super("sl20.05", parameters, wrapped); + +	} + +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20eIDDataValidationException.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20eIDDataValidationException.java new file mode 100644 index 00000000..24df735a --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20eIDDataValidationException.java @@ -0,0 +1,16 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.exceptions; + +public class SL20eIDDataValidationException extends SL20Exception { +	private static final long serialVersionUID = 1L; + +	public SL20eIDDataValidationException(Object[] parameters) { +		super("sl20.07", parameters); +		 +	} +	 +	public SL20eIDDataValidationException(Object[] parameters, Throwable e) { +		super("sl20.07", parameters, e); +		 +	} + +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SLCommandoBuildException.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SLCommandoBuildException.java new file mode 100644 index 00000000..1f521ebc --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SLCommandoBuildException.java @@ -0,0 +1,17 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.exceptions; + +public class SLCommandoBuildException extends SL20Exception { + +	private static final long serialVersionUID = 1L; + +	 +	public SLCommandoBuildException(String msg) { +		super("sl20.01", new Object[]{msg}); +		 +	} +	 +	public SLCommandoBuildException(String msg, Throwable e) { +		super("sl20.01", new Object[]{msg}, e); +		 +	} +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SLCommandoParserException.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SLCommandoParserException.java new file mode 100644 index 00000000..60993e69 --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SLCommandoParserException.java @@ -0,0 +1,17 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.exceptions; + +public class SLCommandoParserException extends SL20Exception { + +	private static final long serialVersionUID = 1L; + +	 +	public SLCommandoParserException(String msg) { +		super("sl20.02", new Object[]{msg}); +		 +	} +	 +	public SLCommandoParserException(String msg, Throwable e) { +		super("sl20.02", new Object[]{msg}, e); +		 +	} +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractCreateQualeIDRequestTask.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractCreateQualeIDRequestTask.java new file mode 100644 index 00000000..337002c5 --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractCreateQualeIDRequestTask.java @@ -0,0 +1,221 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.tasks; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.message.BasicNameValuePair; +import org.jose4j.base64url.Base64Url; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.exceptions.EAAFAuthenticationException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import at.gv.egiz.eaaf.core.impl.utils.DataURLBuilder; +import at.gv.egiz.eaaf.core.impl.utils.HttpClientFactory; +import at.gv.egiz.eaaf.core.impl.utils.KeyValueUtils; +import at.gv.egiz.eaaf.core.impl.utils.Random; +import at.gv.egiz.eaaf.core.impl.utils.TransactionIDUtils; +import at.gv.egiz.eaaf.modules.auth.sl20.Constants; +import at.gv.egiz.eaaf.modules.auth.sl20.EventCodes; +import at.gv.egiz.eaaf.modules.auth.sl20.data.VerificationResult; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20Exception; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SLCommandoBuildException; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SLCommandoParserException; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.IJOSETools; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20Constants; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20HttpBindingUtils; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JSONBuilderUtils; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JSONExtractorUtils; + +public abstract class AbstractCreateQualeIDRequestTask extends AbstractAuthServletTask { +	private static final Logger log = LoggerFactory.getLogger(AbstractCreateQualeIDRequestTask.class); +	 +	@Autowired(required=true) private IJOSETools joseTools; +	@Autowired(required=true) private IConfiguration basicConfig; +	@Autowired(required=true) private HttpClientFactory httpClientFactory; +	 +	@Override  +	public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) +			throws TaskExecutionException { +			 +			log.debug("Starting SL2.0 authentication process .... "); +  +			revisionsLogger.logEvent(pendingReq, EventCodes.AUTHPROCESS_SL20_SELECTED, "sl20auth"); +			 +			try { +				//get service-provider configuration +				ISPConfiguration oaConfig = pendingReq.getServiceProviderConfiguration(); +				 +				//get basic configuration parameters +				String vdaQualeIDUrl = extractVDAURLForSpecificOA(oaConfig, executionContext);				 +				if (StringUtils.isEmpty(vdaQualeIDUrl)) { +					log.error("NO VDA URL for qualified eID (" + Constants.CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT + ")"); +					throw new SL20Exception("sl20.03", new Object[]{"NO VDA URL for qualified eID"}); +					 +				} +				 +				revisionsLogger.logEvent(pendingReq, EventCodes.AUTHPROCESS_SL20_ENDPOINT_URL, vdaQualeIDUrl); +						 +				//create SL2.0 command for qualified eID +				String signedQualeIDCommand = buildSignedQualifiedEIDCommand(); +						 +				//build request container +				String qualeIDReqId = Random.nextProcessReferenceValue(); +				ObjectNode sl20Req = SL20JSONBuilderUtils.createGenericRequest(qualeIDReqId, null, null, signedQualeIDCommand); +				 +				//build http POST request +				HttpPost httpReq = new HttpPost(new URIBuilder(vdaQualeIDUrl).build());								 +				List<NameValuePair> parameters = new ArrayList<NameValuePair>();; +				parameters.add(new BasicNameValuePair(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM, Base64Url.encode(sl20Req.toString().getBytes()))); +				httpReq.setEntity(new UrlEncodedFormEntity(parameters ));				 +				 +				//build http GET request +//				URIBuilder sl20ReqUri = new URIBuilder(vdaQualeIDUrl); +//				sl20ReqUri.addParameter(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM, Base64Url.encode(sl20Req.toString().getBytes())); +//				HttpGet httpReq = new HttpGet(sl20ReqUri.build()); +				 +				//set native client header +				httpReq.addHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE, SL20Constants.HTTP_HEADER_VALUE_NATIVE); + +				log.trace("Request VDA via SL20 with: " + Base64Url.encode(sl20Req.toString().getBytes())); +				 +				//request VDA +				HttpResponse httpResp = httpClientFactory.getHttpClient().execute(httpReq); +								 +				//parse response +				log.info("Receive response from VDA ... "); +				JsonNode sl20Resp = SL20JSONExtractorUtils.getSL20ContainerFromResponse(httpResp); +				VerificationResult respPayloadContainer = SL20JSONExtractorUtils.extractSL20PayLoad(sl20Resp, null, false); +				 +				if (respPayloadContainer.isValidSigned() == null) { +					log.debug("Receive unsigned payLoad from VDA"); +					 +				} +				 +				JsonNode respPayload = respPayloadContainer.getPayload(); +				if (respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).asText() +						.equals(SL20Constants.SL20_COMMAND_IDENTIFIER_REDIRECT)) { +					log.debug("Find 'redirect' command in VDA response ... ");									 +					JsonNode params = SL20JSONExtractorUtils.getJSONObjectValue(respPayload, SL20Constants.SL20_COMMAND_CONTAINER_PARAMS, true);					 +					String redirectURL = SL20JSONExtractorUtils.getStringValue(params, SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_URL, true);									 +					JsonNode command = SL20JSONExtractorUtils.getJSONObjectValue(params, SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_COMMAND, false); +					String signedCommand = SL20JSONExtractorUtils.getStringValue(params, SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_SIGNEDCOMMAND, false);					 + +					//create forward SL2.0 command +					ObjectNode sl20Forward = sl20Resp.deepCopy();					 +					SL20JSONBuilderUtils.addOnlyOnceOfTwo(sl20Forward,  +							SL20Constants.SL20_PAYLOAD, SL20Constants.SL20_SIGNEDPAYLOAD,  +							command.deepCopy(), signedCommand); +										 +					//store pending request +					pendingReq.setRawDataToTransaction(Constants.PENDING_REQ_STORAGE_PREFIX + SL20Constants.SL20_REQID,  +							qualeIDReqId); +					requestStoreage.storePendingRequest(pendingReq); + +					//forward SL2.0 command +					//TODO: maybe add SL2ClientType Header from execution context +					SL20HttpBindingUtils.writeIntoResponse(request, response, sl20Forward, redirectURL); +													 +				} else if (respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).asText() +						.equals(SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR)) {  +					JsonNode result = SL20JSONExtractorUtils.getJSONObjectValue(respPayload, SL20Constants.SL20_COMMAND_CONTAINER_RESULT, false); +					if (result  == null) +						result = SL20JSONExtractorUtils.getJSONObjectValue(respPayload, SL20Constants.SL20_COMMAND_CONTAINER_PARAMS, false); +					 +					String errorCode = SL20JSONExtractorUtils.getStringValue(result, SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORCODE, true); +					String errorMsg = SL20JSONExtractorUtils.getStringValue(result, SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORMESSAGE, true); +					 +					log.info("Receive SL2.0 error. Code:" + errorCode + " Msg:" + errorMsg); +					throw new SL20Exception("sl20.08", new Object[]{errorCode, errorMsg}); +					 +				} else { +					//TODO: update to add error handling +					log.warn("Received an unrecognized command: " + respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).asText()); +					throw new SLCommandoParserException("Received an unrecognized command: \" + respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).getAsString()"); +				} +				 +				 +			} catch (EAAFAuthenticationException  e) { +				throw new TaskExecutionException(pendingReq, "SL2.0 Authentication FAILED. Msg: " + e.getMessage(), e); +				 +			} catch (Exception e) { +				log.warn("SL2.0 Authentication FAILED with a generic error.", e); +				throw new TaskExecutionException(pendingReq, e.getMessage(), e); +				 +			} finally {				 +				TransactionIDUtils.removeTransactionId(); +				TransactionIDUtils.removeSessionId(); +				 +			} +			 +	} +			 +	/** +	 * Create a implementation specific qualified eID SL2.0 command +	 * @param oaConfig  +	 *  +	 * @return signed JWT token as serialized {@link String}  +	 * @throws CertificateEncodingException  +	 * @throws SLCommandoBuildException  +	 * @throws SL20Exception  +	 */ +	protected abstract String buildSignedQualifiedEIDCommand() throws CertificateEncodingException, SL20Exception; + +	 +	private String extractVDAURLForSpecificOA(ISPConfiguration oaConfig, ExecutionContext executionContext) {		 +		 +		//TODO: fully remove if not required any more +		//String spSpecificVDAEndpoints = oaConfig.getConfigurationValue(MOAIDConfigurationConstants.SERVICE_AUTH_SL20_ENDPOINTS);		 +		String spSpecificVDAEndpoints = null; +		 +		Map<String, String> endPointMap = authConfig.getBasicMOAIDConfigurationWithPrefix(Constants.CONFIG_PROP_VDA_ENDPOINT_QUALeID_LIST); +		if (StringUtils.isNotEmpty(spSpecificVDAEndpoints)) { +			endPointMap.putAll(KeyValueUtils.convertListToMap( +							KeyValueUtils.getListOfCSVValues( +								KeyValueUtils.normalizeCSVValueString(spSpecificVDAEndpoints)))); +			log.debug("Find OA specific SL2.0 endpoints. Updating endPoint list ... "); +		 +		}  +		 +		log.trace("Find #" + endPointMap.size() + " SL2.0 endpoints ... "); +		 +		//selection based on request Header +		String sl20VDATypeHeader = (String)  executionContext.get(SL20Constants.HTTP_HEADER_SL20_VDA_TYPE.toLowerCase()); +		if (StringUtils.isNotEmpty(sl20VDATypeHeader)) { +			String vdaURL = endPointMap.get(sl20VDATypeHeader); +			if (StringUtils.isNotEmpty(vdaURL)) +				return vdaURL.trim(); +			 +			else +				log.info("Can NOT find VDA with Id: " + sl20VDATypeHeader + ". Use default VDA"); +						 +	} +		 +		log.info("NO SP specific VDA endpoint found. Use default VDA"); +		return endPointMap.getOrDefault(Constants.CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT, +				Constants.CONFIG_PROP_VDA_ENDPOINT_QUALeID + Constants.CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT); +		 +	} + +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualeIDTask.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualeIDTask.java new file mode 100644 index 00000000..34a097bd --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualeIDTask.java @@ -0,0 +1,285 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.tasks; + +import java.io.IOException; +import java.io.StringWriter; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.entity.ContentType; +import org.jose4j.base64url.Base64Url; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.exceptions.EAAFAuthenticationException; +import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import at.gv.egiz.eaaf.core.impl.utils.DataURLBuilder; +import at.gv.egiz.eaaf.core.impl.utils.StreamUtils; +import at.gv.egiz.eaaf.core.impl.utils.TransactionIDUtils; +import at.gv.egiz.eaaf.modules.auth.sl20.Constants; +import at.gv.egiz.eaaf.modules.auth.sl20.EventCodes; +import at.gv.egiz.eaaf.modules.auth.sl20.data.VerificationResult; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20Exception; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20SecurityException; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SLCommandoParserException; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.IJOSETools; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.JsonMapper; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20Constants; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JSONBuilderUtils; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JSONExtractorUtils; + + +public abstract class AbstractReceiveQualeIDTask extends AbstractAuthServletTask { +	private static final Logger log = LoggerFactory.getLogger(AbstractReceiveQualeIDTask.class); +	 +	@Autowired(required=true) private IJOSETools joseTools; +	 +	@Override +	public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) +			throws TaskExecutionException {		 +		String sl20Result = null; +		  +		try { +			log.debug("Receiving SL2.0 response process .... "); +			JsonNode sl20ReqObj = null;			 +			try { +				//get SL2.0 command or result from HTTP request +				Map<String, String> reqParams = getParameters(request); +				sl20Result = reqParams.get(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM); +				 +				if (StringUtils.isEmpty(sl20Result)) { +					//Workaround for SIC Handy-Signature, because it sends result in InputStream +					String isReqInput = StreamUtils.readStream(request.getInputStream(), "UTF-8");					 +					if (StringUtils.isNotEmpty(isReqInput)) { +						log.info("Use SIC Handy-Signature work-around!"); +						sl20Result = isReqInput.substring("slcommand=".length()); +						 +					} else {					 +						log.info("NO SL2.0 commando or result FOUND."); +						throw new SL20Exception("sl20.04", null); +					} +				 +				} + +				log.trace("Received SL2.0 result: " + sl20Result);				 +				revisionsLogger.logEvent(pendingReq, EventCodes.AUTHPROCESS_SL20_DATAURL_IP, request.getRemoteAddr()); +			 +				//parse SL2.0 command/result into JSON			 +				try { +					sl20ReqObj = new JsonMapper().getMapper().readTree(Base64Url.decodeToUtf8String(sl20Result)); +				 +				} catch (JsonParseException  e) { +					log.warn("SL2.0 command or result is NOT valid JSON.", e); +					log.debug("SL2.0 msg: " + sl20Result); +					throw new SL20Exception("sl20.02", new Object[]{"SL2.0 command or result is NOT valid JSON."}, e); +				 +				} +			 +				//validate reqId with inResponseTo  +				String sl20ReqId = pendingReq.getRawData(Constants.PENDING_REQ_STORAGE_PREFIX + SL20Constants.SL20_REQID, String.class); +				String inRespTo = SL20JSONExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_INRESPTO, true); +				if (sl20ReqId == null || !sl20ReqId.equals(inRespTo)) { +					log.info("SL20 'reqId': " + sl20ReqId + " does NOT match to 'inResponseTo':" + inRespTo); +					throw new SL20SecurityException("SL20 'reqId': " + sl20ReqId + " does NOT match to 'inResponseTo':" + inRespTo); +				} +			 +			 +				//validate signature +				VerificationResult payLoadContainer = SL20JSONExtractorUtils.extractSL20PayLoad( +						sl20ReqObj, joseTools,  +						authConfig.getBasicMOAIDConfigurationBoolean(Constants.CONFIG_PROP_FORCE_EID_SIGNED_RESULT, true)); +				 +				if ( (payLoadContainer.isValidSigned() == null || !payLoadContainer.isValidSigned())) { +					if (authConfig.getBasicMOAIDConfigurationBoolean(Constants.CONFIG_PROP_FORCE_EID_SIGNED_RESULT, true)) { +						log.info("SL20 result from VDA was not valid signed"); +						throw new SL20SecurityException(new Object[]{"Signature on SL20 result NOT valid."}); +				 +					} else { +						log.warn("SL20 result from VDA is NOT valid signed, but signatures-verification is DISABLED by configuration!"); +						 +					} +				} +				 +				/*TODO validate certificate by using MOA-SPSS +				* currently, the certificate is validated in IJOSETools by using a pkcs12 or jks keystore +				*/ +				List<X509Certificate> sigCertChain = payLoadContainer.getCertChain(); +			 +			 +				//extract payloaf +				JsonNode payLoad = payLoadContainer.getPayload(); +			 +				 +				//handle SL2.0 response payLoad +				handleResponsePayLoad(payLoad); +								 +				 +			} catch (EAAFAuthenticationException  e) { +				log.warn("SL2.0 processing error:", e); +				if (sl20Result != null) +					log.debug("Received SL2.0 result: " + sl20Result); +				pendingReq.setRawDataToTransaction( +						Constants.PENDING_REQ_STORAGE_PREFIX + SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR,  +						new TaskExecutionException(pendingReq, "SL2.0 Authentication FAILED. Msg: " + e.getMessage(), e)); +				 +			} catch (Exception e) { +				log.warn("ERROR:", e); +				log.warn("SL2.0 Authentication FAILED with a generic error.", e); +				if (sl20Result != null) +					log.debug("Received SL2.0 result: " + sl20Result); +				pendingReq.setRawDataToTransaction( +						Constants.PENDING_REQ_STORAGE_PREFIX + SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR,  +						new TaskExecutionException(pendingReq, e.getMessage(), e)); +				 +			} finally { +				//store pending request +				requestStoreage.storePendingRequest(pendingReq); +								 +					//write SL2.0 response +					if (sl20ReqObj != null)				 +						buildResponse(request, response, sl20ReqObj); +					else  +						buildErrorResponse(request, response, "2000", "General transport Binding error");					 +					 +			} +		 +		} catch (Exception e) { +			//write internal server errror 500 according to SL2.0 specification, chapter https transport binding			 +			log.warn("Can NOT build SL2.0 response. Reason: " + e.getMessage(), e); +			if (sl20Result != null) +				log.debug("Received SL2.0 result: " + sl20Result); +			try { +				response.sendError(500, "Internal Server Error."); +				 +			} catch (IOException e1) { +				log.error("Can NOT send error message. SOMETHING IS REALY WRONG!", e); +				 +			}	 +			 +		} finally { +			TransactionIDUtils.removeTransactionId(); +			TransactionIDUtils.removeSessionId(); +			 +		} +	} + +	protected abstract void handleResponsePayLoad(JsonNode payLoad) throws SLCommandoParserException, SL20Exception, EAAFStorageException; + +	protected abstract String getResumeEndPoint(); +	 +	private void buildErrorResponse(HttpServletRequest request, HttpServletResponse response, String errorCode, String errorMsg) throws Exception {				 +		ObjectNode error = SL20JSONBuilderUtils.createErrorCommandResult(errorCode, errorMsg); +		ObjectNode respContainer = SL20JSONBuilderUtils.createGenericRequest( +				UUID.randomUUID().toString(),  +				null,  +				error ,  +				null); +			 +		log.debug("Client request containts 'native client' header ... ");	 +		log.trace("SL20 response to VDA: " + respContainer); +		StringWriter writer = new StringWriter(); +		writer.write(respContainer.toString());						 +		final byte[] content = writer.toString().getBytes("UTF-8"); +		response.setStatus(HttpServletResponse.SC_OK); +		response.setContentLength(content.length); +		response.setContentType(ContentType.APPLICATION_JSON.toString());						 +		response.getOutputStream().write(content); +				 +	} + +	private void buildResponse(HttpServletRequest request, HttpServletResponse response, JsonNode sl20ReqObj) throws IOException, SL20Exception {		 +				//create response  +				Map<String, String> reqParameters = new HashMap<String, String>(); +				reqParameters.put(EAAFConstants.PARAM_HTTP_TARGET_PENDINGREQUESTID, pendingReq.getPendingRequestId()); +				ObjectNode callReqParams = SL20JSONBuilderUtils.createCallCommandParameters( +						new DataURLBuilder().buildDataURL(pendingReq.getAuthURL(), getResumeEndPoint(), null),  +						SL20Constants.SL20_COMMAND_PARAM_GENERAL_CALL_METHOD_GET,  +						false,  +						reqParameters); +				ObjectNode callCommand = SL20JSONBuilderUtils.createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_CALL, callReqParams); +				 +				//build first redirect command for app +				ObjectNode redirectOneParams = SL20JSONBuilderUtils.createRedirectCommandParameters( +				generateICPRedirectURLForDebugging(),  +				callCommand, null, true); +				ObjectNode redirectOneCommand = SL20JSONBuilderUtils.createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_REDIRECT, redirectOneParams); +								 +				//build second redirect command for IDP +				ObjectNode redirectTwoParams = SL20JSONBuilderUtils.createRedirectCommandParameters( +						new DataURLBuilder().buildDataURL(pendingReq.getAuthURL(), getResumeEndPoint(), null),  +						redirectOneCommand, null, true); +				ObjectNode redirectTwoCommand = SL20JSONBuilderUtils.createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_REDIRECT, redirectTwoParams); +				 +				//build generic SL2.0 response container								 +				String transactionId = SL20JSONExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_TRANSACTIONID, false); +				ObjectNode respContainer = SL20JSONBuilderUtils.createGenericRequest( +						UUID.randomUUID().toString(),  +						transactionId,  +						redirectTwoCommand,  +						null);  +				 +		//workaround for A-Trust +				if (request.getHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE) != null &&  +				request.getHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE).equals(SL20Constants.HTTP_HEADER_VALUE_NATIVE) +					|| true) {					 +			log.debug("Client request containts 'native client' header ... "); +			log.trace("SL20 response to VDA: " + respContainer); +					StringWriter writer = new StringWriter(); +					writer.write(respContainer.toString());						 +					final byte[] content = writer.toString().getBytes("UTF-8"); +					response.setStatus(HttpServletResponse.SC_OK); +					response.setContentLength(content.length); +					response.setContentType(ContentType.APPLICATION_JSON.toString());						 +					response.getOutputStream().write(content); +					 +					 +				} else { +					log.info("SL2.0 DataURL communication needs http header: '" + SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE + "'"); +					throw new SL20Exception("sl20.06",  +							new Object[] {"SL2.0 DataURL communication needs http header: '" + SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE + "'"}); +					 +				} +			} + +	/** +	 * Generates a IPC redirect URL that is configured on IDP side +	 *  +	 * @return IPC ReturnURL, or null if no URL is configured +	 */ +	private String generateICPRedirectURLForDebugging() { +		final String PATTERN_PENDING_REQ_ID = "#PENDINGREQID#"; +			 +		String ipcRedirectURLConfig = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_IPC_RETURN_URL);				 +		if (StringUtils.isNotEmpty(ipcRedirectURLConfig)) {			 +			if (ipcRedirectURLConfig.contains(PATTERN_PENDING_REQ_ID)) { +				log.trace("Find 'pendingReqId' pattern in IPC redirect URL. Update url ... "); +				ipcRedirectURLConfig = ipcRedirectURLConfig.replaceAll( +						"#PENDINGREQID#",  +						EAAFConstants.PARAM_HTTP_TARGET_PENDINGREQUESTID + "=" + pendingReq.getPendingRequestId()); +			 +			} +			 +			return ipcRedirectURLConfig; +		} +	 +		return null; +		 +	} +	 +	 +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/IJOSETools.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/IJOSETools.java new file mode 100644 index 00000000..35e6de4f --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/IJOSETools.java @@ -0,0 +1,48 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.utils; + +import java.security.cert.X509Certificate; + +import com.fasterxml.jackson.databind.JsonNode; + +import at.gv.egiz.eaaf.modules.auth.sl20.data.VerificationResult; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20Exception; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SLCommandoBuildException; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SLCommandoParserException; + +public interface IJOSETools { + +	/** +	 * Create a JWS signature +	 *  +	 * @param payLoad Payload to sign +	 * @throws SLCommandoBuildException  +	 */ +	public String createSignature(String payLoad) throws SLCommandoBuildException; + +	/** +	 * Validates a JWS signature +	 *  +	 * @param serializedContent +	 * @return +	 * @throws SLCommandoParserException +	 * @throws SL20Exception  +	 */ +	public VerificationResult validateSignature(String serializedContent) throws SL20Exception; +	 +	/** +	 * Get the encryption certificate for SL2.0 End-to-End encryption +	 *  +	 * @return +	 */ +	public X509Certificate getEncryptionCertificate(); + +	/** +	 * Decrypt a serialized JWE token +	 *  +	 * @param compactSerialization Serialized JWE token +	 * @return decrypted payload +	 * @throws SL20Exception  +	 */ +	public JsonNode decryptPayload(String compactSerialization) throws SL20Exception; +	  +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonMapper.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonMapper.java new file mode 100644 index 00000000..959a696a --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonMapper.java @@ -0,0 +1,107 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.utils; + +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; + +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.NonNull; + +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.type.TypeFactory; + +public class JsonMapper { +	private static final Logger log = LoggerFactory.getLogger(JsonMapper.class); +	 +	private ObjectMapper mapper = new ObjectMapper(); +	 +	/** +	 * The default constructor where the default pretty printer is disabled. +	 */ +	public JsonMapper() { +		this(false); +		 +	} +	 +	/** +	 * The constructor. +	 * @param prettyPrint enables or disables the default pretty printer +	 */ +	public JsonMapper(@NonNull boolean prettyPrint) { +		log.trace("Initializing JSON object-mapper ... "); +		mapper.configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, true); +		mapper.configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, true); +		mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES , true); +		mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE); +		mapper.setVisibility(PropertyAccessor.GETTER, Visibility.PUBLIC_ONLY); +		mapper.setVisibility(PropertyAccessor.IS_GETTER, Visibility.PUBLIC_ONLY); +		if (prettyPrint) { +			mapper.enable(SerializationFeature.INDENT_OUTPUT); +		} +				 +		log.debug("JSON object-mapper initialized"); +		 +	} +	 +	 +	/** +	 * Get the internal mapper implemenation +	 *  +	 * @return +	 */ +	public ObjectMapper getMapper() { +		return mapper; +		 +	} +	 +	 +	/** +	 * Serialize an object to a JSON string. +	 * @param value the object to serialize +	 * @return a JSON string +	 * @throws JsonProcessingException thrown when an error occurs during serialization +	 */ +	public String serialize(Object value) throws JsonProcessingException { +		return mapper.writeValueAsString(value); +		 +	} + +	/** +	 * Deserialize a JSON string. +	 *  +	 * @param value the JSON string to deserialize +	 * @param clazz optional parameter that determines the type of the returned object. If not set, an {@link Object} is returned. +	 * @return the deserialized JSON string as an object of type {@code clazz} or {@link Object} +	 * @throws JsonParseException if the JSON string contains invalid content. +	 * @throws JsonMappingException if the input JSON structure does not match structure expected for result type +	 * @throws IOException if an I/O problem occurs (e.g. unexpected end-of-input) +	 */ +	public <T> Object deserialize(String value, Class<T> clazz) throws JsonParseException, JsonMappingException, IOException{ + +		ObjectMapper mapper = new ObjectMapper(); +		if (clazz != null) { +			JavaType javaType = TypeFactory.defaultInstance().constructType(clazz); +			return mapper.readValue(value, javaType); +			 +		} else +			return mapper.readValue(value, Object.class); +		 +	} +	 +	 +	 +	public <T> Object deserialize(String value, TypeReference<T> clazz) throws JsonParseException, JsonMappingException, IOException {		 +		return mapper.readValue(value, clazz); +						 +	} +	 +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java new file mode 100644 index 00000000..5eda95cc --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java @@ -0,0 +1,394 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.utils; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.security.Key; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.StringUtils; +import org.jose4j.jwa.AlgorithmConstraints; +import org.jose4j.jwa.AlgorithmConstraints.ConstraintType; +import org.jose4j.jwe.JsonWebEncryption; +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwx.JsonWebStructure; +import org.jose4j.keys.X509Util; +import org.jose4j.keys.resolvers.X509VerificationKeyResolver; +import org.jose4j.lang.JoseException; +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 com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonNode; + +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException; +import at.gv.egiz.eaaf.core.impl.utils.FileUtils; +import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; +import at.gv.egiz.eaaf.core.impl.utils.X509Utils; +import at.gv.egiz.eaaf.modules.auth.sl20.Constants; +import at.gv.egiz.eaaf.modules.auth.sl20.data.VerificationResult; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20Exception; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20SecurityException; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SLCommandoBuildException; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SLCommandoParserException; + +@Service +public class JsonSecurityUtils implements IJOSETools{ +	private static final Logger log = LoggerFactory.getLogger(JsonSecurityUtils.class); +	 +	@Autowired(required=true) IConfiguration authConfig; +	private Key signPrivKey = null; +	private X509Certificate[] signCertChain = null; +	 +	private Key encPrivKey = null; +	private X509Certificate[] encCertChain = null; +	 +	private List<X509Certificate> trustedCerts = new ArrayList<X509Certificate>(); +	 +	private static JsonMapper mapper = new JsonMapper(); +	 +	@PostConstruct +	protected void initalize() { +		log.info("Initialize SL2.0 authentication security constrains ... "); +		try { +			if (getKeyStoreFilePath() != null) { 			 +				KeyStore keyStore = KeyStoreUtils.loadKeyStore(getKeyStoreFilePath(),  +						getKeyStorePassword()); +				 +				//load signing key +				signPrivKey = keyStore.getKey(getSigningKeyAlias(), getSigningKeyPassword().toCharArray()); +				Certificate[] certChainSigning = keyStore.getCertificateChain(getSigningKeyAlias()); +				signCertChain = new X509Certificate[certChainSigning.length]; +				for (int i=0; i<certChainSigning.length; i++) { +					if (certChainSigning[i] instanceof X509Certificate) { +						signCertChain[i] = (X509Certificate)certChainSigning[i]; +					} else +						log.warn("NO X509 certificate for signing: " + certChainSigning[i].getType()); +					 +				} +				 +				//load encryption key +				try { +					encPrivKey = keyStore.getKey(getEncryptionKeyAlias(), getEncryptionKeyPassword().toCharArray()); +					if (encPrivKey != null) { +						Certificate[] certChainEncryption = keyStore.getCertificateChain(getEncryptionKeyAlias()); +						encCertChain = new X509Certificate[certChainEncryption.length]; +						for (int i=0; i<certChainEncryption.length; i++) { +							if (certChainEncryption[i] instanceof X509Certificate) { +								encCertChain[i] = (X509Certificate)certChainEncryption[i]; +							} else +								log.warn("NO X509 certificate for encryption: " + certChainEncryption[i].getType()); +						}									 +					} else +						log.info("No encryption key for SL2.0 found. End-to-End encryption is not used."); +					 +				} catch (Exception e) { +					log.warn("No encryption key for SL2.0 found. End-to-End encryption is not used. Reason: " + e.getMessage(), e); +			 +				} +				 +				//load trusted certificates +				Enumeration<String> aliases = keyStore.aliases(); +				while(aliases.hasMoreElements()) { +					String el = aliases.nextElement(); +					log.trace("Process TrustStoreEntry: " + el); +					if (keyStore.isCertificateEntry(el)) { +						Certificate cert = keyStore.getCertificate(el);  +						if (cert != null && cert instanceof X509Certificate) +							trustedCerts.add((X509Certificate) cert); +						else +							log.info("Can not process entry: " + el + ". Reason: " + cert.toString()); +						 +					} +				} +	 +				//some short validation +				if (signPrivKey == null || !(signPrivKey instanceof PrivateKey)) { +					log.info("Can NOT open privateKey for SL2.0 signing. KeyStore=" + getKeyStoreFilePath()); +					throw new SL20Exception("sl20.03", new Object[]{"Can NOT open private key for signing"}); +					 +				} +				 +				if (signCertChain == null || signCertChain.length == 0) { +					log.info("NO certificate for SL2.0 signing. KeyStore=" + getKeyStoreFilePath()); +					throw new SL20Exception("sl20.03", new Object[]{"NO certificate for SL2.0 signing"}); +					 +				} +				 +				log.info("SL2.0 authentication security constrains initialized."); +				 +			} else +				log.info("NO SL2.0 authentication security configuration. Initialization was skipped"); +							 +		} catch ( Exception e) { +			log.error("SL2.0 security constrains initialization FAILED.", e); +			 +		} +		 +	} +	 +	 +	@Override +	public String createSignature(String payLoad) throws SLCommandoBuildException { +		try { +			JsonWebSignature jws = new JsonWebSignature(); +			 +			//set payload +			jws.setPayload(payLoad); +		 +			//set basic header		 +			jws.setContentTypeHeaderValue(SL20Constants.SL20_CONTENTTYPE_SIGNED_COMMAND); +		 +			//set signing information +			jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); +			jws.setKey(signPrivKey); +			 +			//TODO: +			jws.setCertificateChainHeaderValue(signCertChain); +			jws.setX509CertSha256ThumbprintHeaderValue(signCertChain[0]); +		 +			return jws.getCompactSerialization(); +			 +		} catch (JoseException e) { +			log.warn("Can NOT sign SL2.0 command.", e); +			throw new SLCommandoBuildException("Can NOT sign SL2.0 command.", e); +			 +		} +		 +	} +	 +	@Override +	public VerificationResult validateSignature(String serializedContent) throws SL20Exception { +		try { +			JsonWebSignature jws = new JsonWebSignature(); +			//set payload +			jws.setCompactSerialization(serializedContent); +				 +			//set security constrains +			jws.setAlgorithmConstraints(new AlgorithmConstraints(ConstraintType.WHITELIST,    +					SL20Constants.SL20_ALGORITHM_WHITELIST_SIGNING.toArray(new String[SL20Constants.SL20_ALGORITHM_WHITELIST_SIGNING.size()]))); +			 +			//load signinc certs +			Key selectedKey = null; +			List<X509Certificate> x5cCerts = jws.getCertificateChainHeaderValue(); +			String x5t256 = jws.getX509CertSha256ThumbprintHeaderValue(); +			if (x5cCerts != null) { +				log.debug("Found x509 certificate in JOSE header ... "); +			log.trace("Sorting received X509 certificates ... "); +				List<X509Certificate> sortedX5cCerts  = X509Utils.sortCertificates(x5cCerts); +							 +				if (trustedCerts.contains(sortedX5cCerts.get(0))) { +					selectedKey = sortedX5cCerts.get(0).getPublicKey(); +					 +				} else { +					log.info("Can NOT find JOSE certificate in truststore."); +					log.debug("JOSE certificate: " + sortedX5cCerts.get(0).toString()); +					try { +						log.debug("Cert: " + Base64Utils.encodeToString(sortedX5cCerts.get(0).getEncoded())); +					} catch (CertificateEncodingException e) { +						e.printStackTrace(); +					} +					 +				} +				 +			} else if (StringUtils.isNotEmpty(x5t256)) { +				log.debug("Found x5t256 fingerprint in JOSE header .... "); +				X509VerificationKeyResolver x509VerificationKeyResolver = new X509VerificationKeyResolver(trustedCerts); +				selectedKey = x509VerificationKeyResolver.resolveKey(jws, Collections.<JsonWebStructure>emptyList()); +				 +			} else { +				log.info("Signed SL2.0 response contains NO signature certificate or NO certificate fingerprint"); +				throw new SLCommandoParserException("Signed SL2.0 response contains NO signature certificate or NO certificate fingerprint"); +				 +			} +					 +			if (selectedKey == null) { +				log.info("Can NOT select verification key for JWS. Signature verification FAILED."); +				throw new SLCommandoParserException("Can NOT select verification key for JWS. Signature verification FAILED"); +				 +			} +			 +			//set verification key +			jws.setKey(selectedKey); +			 +			//validate signature +			boolean valid = jws.verifySignature(); +			if (!valid) { +				log.info("JWS signature invalide. Stopping authentication process ..."); +				log.debug("Received JWS msg: " + serializedContent); +				throw new SL20SecurityException("JWS signature invalide."); +				 +			} +			 +			 +			//load payLoad +			log.debug("SL2.0 commando signature validation sucessfull"); +			JsonNode sl20Req = mapper.getMapper().readTree(jws.getPayload()); +			 +			return new VerificationResult(sl20Req, null, valid) ; +			 +		} catch (JoseException | JsonParseException e) { +			log.warn("SL2.0 commando signature validation FAILED", e); +			throw new SL20SecurityException(new Object[]{e.getMessage()}, e); +			 +		} catch (IOException e) { +			log.warn("Decrypted SL2.0 result can not be parsed.", e); +			throw new SLCommandoParserException("Decrypted SL2.0 result can not be parsed", e); +			 +		} +					 +	} +	 + +	@Override +	public JsonNode decryptPayload(String compactSerialization) throws SL20Exception { +		try {			 +			JsonWebEncryption receiverJwe = new JsonWebEncryption(); +		 +			//set security constrains +			receiverJwe.setAlgorithmConstraints( +					new AlgorithmConstraints(ConstraintType.WHITELIST, +							SL20Constants.SL20_ALGORITHM_WHITELIST_KEYENCRYPTION.toArray(new String[SL20Constants.SL20_ALGORITHM_WHITELIST_KEYENCRYPTION.size()]))); +			receiverJwe.setContentEncryptionAlgorithmConstraints( +					new AlgorithmConstraints(ConstraintType.WHITELIST, +							SL20Constants.SL20_ALGORITHM_WHITELIST_ENCRYPTION.toArray(new String[SL20Constants.SL20_ALGORITHM_WHITELIST_ENCRYPTION.size()]))); +		 +			//set payload +			receiverJwe.setCompactSerialization(compactSerialization); + +					 +			//validate key from header against key from config +			List<X509Certificate> x5cCerts = receiverJwe.getCertificateChainHeaderValue(); +			String x5t256 = receiverJwe.getX509CertSha256ThumbprintHeaderValue(); +			if (x5cCerts != null) { +				log.debug("Found x509 certificate in JOSE header ... "); +				log.trace("Sorting received X509 certificates ... "); +				List<X509Certificate> sortedX5cCerts  = X509Utils.sortCertificates(x5cCerts); +		 +				if (!sortedX5cCerts.get(0).equals(encCertChain[0])) { +					log.info("Certificate from JOSE header does NOT match encryption certificate"); +					log.debug("JOSE certificate: " + sortedX5cCerts.get(0).toString()); +					 +					try { +						log.debug("Cert: " + Base64Utils.encode(sortedX5cCerts.get(0).getEncoded())); +					} catch (CertificateEncodingException e) { +						e.printStackTrace(); +					} +					throw new SL20Exception("sl20.05", new Object[]{"Certificate from JOSE header does NOT match encryption certificate"}); +				} +				 +			} else if (StringUtils.isNotEmpty(x5t256)) { +				log.debug("Found x5t256 fingerprint in JOSE header .... "); +				String certFingerPrint = X509Util.x5tS256(encCertChain[0]); +				if (!certFingerPrint.equals(x5t256)) { +					log.info("X5t256 from JOSE header does NOT match encryption certificate"); +					log.debug("X5t256 from JOSE header: " + x5t256 + " Encrytption cert: " + certFingerPrint); +					throw new SL20Exception("sl20.05", new Object[]{"X5t256 from JOSE header does NOT match encryption certificate"}); +					 +				} +				 +			} else { +				log.info("Signed SL2.0 response contains NO signature certificate or NO certificate fingerprint"); +				throw new SLCommandoParserException("Signed SL2.0 response contains NO signature certificate or NO certificate fingerprint"); +				 +			} +						 +			//set key +			receiverJwe.setKey(encPrivKey); +			 +						 +			//decrypt payload			 +			return mapper.getMapper().readTree(receiverJwe.getPlaintextString()); +			 +		} catch (JoseException e) { +			log.warn("SL2.0 result decryption FAILED", e); +			throw new SL20SecurityException(new Object[]{e.getMessage()}, e); +			 +		} catch ( JsonParseException e) { +			log.warn("Decrypted SL2.0 result is NOT a valid JSON.", e); +			throw new SLCommandoParserException("Decrypted SL2.0 result is NOT a valid JSON.", e); +			 +		} catch (IOException e) { +			log.warn("Decrypted SL2.0 result can not be parsed.", e); +			throw new SLCommandoParserException("Decrypted SL2.0 result can not be parsed", e); +		} +		 +	} +	 +	 +	 +	@Override +	public X509Certificate getEncryptionCertificate() { +		//TODO: maybe update after SL2.0 update on encryption certificate parts +		if (encCertChain !=null && encCertChain.length > 0) +			return encCertChain[0]; +		else +			return null; +	} +	 +	private String getKeyStoreFilePath() throws EAAFConfigurationException, MalformedURLException { +		return FileUtils.makeAbsoluteURL( +					authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_PATH),  +					authConfig.getConfigurationRootDirectory()); +	} +	 +	private String getKeyStorePassword() { +		String value = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_PASSWORD); +		if (value != null) +			value = value.trim(); +		 +		return value; + +	} +	 +	private String getSigningKeyAlias() { +		String value = authConfig.getBasicConfiguration( +				Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_ALIAS).trim(); +		if (value != null) +			value = value.trim(); +		 +		return value; +	} +	 +	private String getSigningKeyPassword() { +		String value = authConfig.getBasicConfiguration( +				Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_PASSWORD).trim(); +		if (value != null) +			value = value.trim(); +		 +		return value; +	} + +	private String getEncryptionKeyAlias() { +		String value = authConfig.getBasicConfiguration( +				Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_ALIAS).trim(); +		if (value != null) +			value = value.trim(); +		 +		return value; +	} +	 +	private String getEncryptionKeyPassword() { +		String value = authConfig.getBasicConfiguration( +				Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_PASSWORD).trim(); +		if (value != null) +			value = value.trim(); +		 +		return value; +	} +	 +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20Constants.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20Constants.java new file mode 100644 index 00000000..b5b82174 --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20Constants.java @@ -0,0 +1,232 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.utils; + +import java.util.Arrays; +import java.util.List; + +import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers; +import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers; +import org.jose4j.jws.AlgorithmIdentifiers; + +public class SL20Constants { +	public static final int CURRENT_SL20_VERSION = 10; +	 +	//http binding parameters +	public static final String PARAM_SL20_REQ_COMMAND_PARAM = "slcommand"; +	public static final String PARAM_SL20_REQ_COMMAND_PARAM_OLD = "sl2command"; +	 +	public static final String PARAM_SL20_REQ_ICP_RETURN_URL_PARAM = "slIPCReturnUrl"; +	public static final String PARAM_SL20_REQ_TRANSACTIONID = "slTransactionID"; +	 +	public static final String HTTP_HEADER_SL20_CLIENT_TYPE = "SL2ClientType"; +	public static final String HTTP_HEADER_SL20_VDA_TYPE = "X-MOA-VDA"; +	public static final String HTTP_HEADER_VALUE_NATIVE = "nativeApp"; +	 +	 +	//******************************************************************************************* +	//JSON signing and encryption headers +	public static final String JSON_ALGORITHM = "alg"; +	public static final String JSON_CONTENTTYPE = "cty"; +	public static final String JSON_X509_CERTIFICATE = "x5c"; +	public static final String JSON_X509_FINGERPRINT = "x5t#S256"; +	public static final String JSON_ENCRYPTION_PAYLOAD = "enc"; +	 +	public static final String JSON_ALGORITHM_SIGNING_RS256 = AlgorithmIdentifiers.RSA_USING_SHA256; +	public static final String JSON_ALGORITHM_SIGNING_RS512 = AlgorithmIdentifiers.RSA_USING_SHA512; +	public static final String JSON_ALGORITHM_SIGNING_ES256 = AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256; +	public static final String JSON_ALGORITHM_SIGNING_ES512 = AlgorithmIdentifiers.ECDSA_USING_P521_CURVE_AND_SHA512; +	public static final String JSON_ALGORITHM_SIGNING_PS256 = AlgorithmIdentifiers.RSA_PSS_USING_SHA256; +	public static final String JSON_ALGORITHM_SIGNING_PS512 = AlgorithmIdentifiers.RSA_PSS_USING_SHA512; + +	public static final List<String> SL20_ALGORITHM_WHITELIST_SIGNING = Arrays.asList( +			JSON_ALGORITHM_SIGNING_RS256, +			JSON_ALGORITHM_SIGNING_RS512, +			JSON_ALGORITHM_SIGNING_ES256, +			JSON_ALGORITHM_SIGNING_ES512, +			JSON_ALGORITHM_SIGNING_PS256, +			JSON_ALGORITHM_SIGNING_PS512 +			); +	 +	public static final String JSON_ALGORITHM_ENC_KEY_RSAOAEP = KeyManagementAlgorithmIdentifiers.RSA_OAEP; +	public static final String JSON_ALGORITHM_ENC_KEY_RSAOAEP256 = KeyManagementAlgorithmIdentifiers.RSA_OAEP_256; +	 +	public static final List<String> SL20_ALGORITHM_WHITELIST_KEYENCRYPTION = Arrays.asList( +			JSON_ALGORITHM_ENC_KEY_RSAOAEP, +			JSON_ALGORITHM_ENC_KEY_RSAOAEP256 +			); +	 +	public static final String JSON_ALGORITHM_ENC_PAYLOAD_A128CBCHS256 = ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256; +	public static final String JSON_ALGORITHM_ENC_PAYLOAD_A256CBCHS512 = ContentEncryptionAlgorithmIdentifiers.AES_256_CBC_HMAC_SHA_512; +	public static final String JSON_ALGORITHM_ENC_PAYLOAD_A128GCM = ContentEncryptionAlgorithmIdentifiers.AES_128_GCM; +	public static final String JSON_ALGORITHM_ENC_PAYLOAD_A256GCM = ContentEncryptionAlgorithmIdentifiers.AES_256_GCM; +	 +	public static final List<String> SL20_ALGORITHM_WHITELIST_ENCRYPTION = Arrays.asList( +			JSON_ALGORITHM_ENC_PAYLOAD_A128CBCHS256, +			JSON_ALGORITHM_ENC_PAYLOAD_A256CBCHS512, +			JSON_ALGORITHM_ENC_PAYLOAD_A128GCM, +			JSON_ALGORITHM_ENC_PAYLOAD_A256GCM +		); +	 +	 +	//********************************************************************************************* +	//Object identifier for generic transport container +	public static final String SL20_CONTENTTYPE_SIGNED_COMMAND ="application/sl2.0;command"; +	public static final String SL20_CONTENTTYPE_ENCRYPTED_RESULT ="application/sl2.0;result"; +	 +	public static final String SL20_VERSION = "v"; +	public static final String SL20_REQID = "reqID"; +	public static final String SL20_RESPID = "respID"; +	public static final String SL20_INRESPTO = "inResponseTo"; +	public static final String SL20_TRANSACTIONID = "transactionID"; +	public static final String SL20_PAYLOAD = "payload"; +	public static final String SL20_SIGNEDPAYLOAD = "signedPayload"; +	 +	//Generic Object identifier for commands +	public static final String SL20_COMMAND_CONTAINER_NAME = "name"; +	public static final String SL20_COMMAND_CONTAINER_PARAMS = "params"; +	public static final String SL20_COMMAND_CONTAINER_RESULT = "result"; +	public static final String SL20_COMMAND_CONTAINER_ENCRYPTEDRESULT = "encryptedResult"; +		 +	//COMMAND Object identifier +	public static final String SL20_COMMAND_IDENTIFIER_REDIRECT = "redirect"; +	public static final String SL20_COMMAND_IDENTIFIER_CALL = "call"; +	public static final String SL20_COMMAND_IDENTIFIER_ERROR = "error"; +	public static final String SL20_COMMAND_IDENTIFIER_QUALIFIEDEID = "qualifiedeID"; +	//public static final String SL20_COMMAND_IDENTIFIER_QUALIFIEDSIG = "qualifiedSig"; +	 +	public static final String SL20_COMMAND_IDENTIFIER_GETCERTIFICATE = "getCertificate"; +	public static final String SL20_COMMAND_IDENTIFIER_CREATE_SIG_CADES = "createCAdES"; +	 +	 +	public static final String SL20_COMMAND_IDENTIFIER_BINDING_CREATE_KEY = "createBindingKey"; +	public static final String SL20_COMMAND_IDENTIFIER_BINDING_STORE_CERT = "storeBindingCert"; +	 +	public static final String SL20_COMMAND_IDENTIFIER_AUTH_IDANDPASSWORD = "idAndPassword"; +	public static final String SL20_COMMAND_IDENTIFIER_AUTH_JWSTOKENFACTOR = "jwsTokenAuth"; +	public static final String SL20_COMMAND_IDENTIFIER_AUTH_QRCODEFACTOR = "qrCodeFactor"; +	 +	//*****COMMAND parameter identifier****** +	//general Identifier +	public static final String SL20_COMMAND_PARAM_GENERAL_REQPARAMETER_VALUE = "value"; +	public static final String SL20_COMMAND_PARAM_GENERAL_REQPARAMETER_KEY = "key";	 +	public static final String SL20_COMMAND_PARAM_GENERAL_DATAURL = "dataUrl"; +	public static final String SL20_COMMAND_PARAM_GENERAL_RESPONSEENCRYPTIONCERTIFICATE = "x5cEnc"; +	public static final String SL20_COMMAND_PARAM_GENERAL_RESPONSEENCRYPTIONJWK = "jwkEnc"; +	 +	//Redirect command +	public static final String SL20_COMMAND_PARAM_GENERAL_REDIRECT_URL = "url"; +	public static final String SL20_COMMAND_PARAM_GENERAL_REDIRECT_COMMAND = "command"; +	public static final String SL20_COMMAND_PARAM_GENERAL_REDIRECT_SIGNEDCOMMAND = "signedCommand"; +	public static final String SL20_COMMAND_PARAM_GENERAL_REDIRECT_IPCREDIRECT = "IPCRedirect";		 +	 +	//Call command +	public static final String SL20_COMMAND_PARAM_GENERAL_CALL_URL = SL20_COMMAND_PARAM_GENERAL_REDIRECT_URL; +	public static final String SL20_COMMAND_PARAM_GENERAL_CALL_METHOD = "method"; +	public static final String SL20_COMMAND_PARAM_GENERAL_CALL_METHOD_GET = "get"; +	public static final String SL20_COMMAND_PARAM_GENERAL_CALL_METHOD_POST = "post"; +	public static final String SL20_COMMAND_PARAM_GENERAL_CALL_INCLUDETRANSACTIONID = "includeTransactionID";	 +	public static final String SL20_COMMAND_PARAM_GENERAL_CALL_REQPARAMETER = "reqParams"; + +	//error command +	public static final String SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORCODE = "errorCode"; +	public static final String SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORMESSAGE = "errorMessage"; +	 +	//qualified eID command +	public static final String SL20_COMMAND_PARAM_EID_AUTHBLOCKID = "authBlockTemplateID"; +	public static final String SL20_COMMAND_PARAM_EID_DATAURL = SL20_COMMAND_PARAM_GENERAL_DATAURL;  +	public static final String SL20_COMMAND_PARAM_EID_ATTRIBUTES = "attributes"; +	public static final String SL20_COMMAND_PARAM_EID_ATTRIBUTES_MANDATEREFVALUE = "MANDATE-REFERENCE-VALUE"; +	public static final String SL20_COMMAND_PARAM_EID_ATTRIBUTES_SPUNIQUEID = "SP-UNIQUEID"; +	public static final String SL20_COMMAND_PARAM_EID_ATTRIBUTES_SPFRIENDLYNAME = "SP-FRIENDLYNAME"; +	public static final String SL20_COMMAND_PARAM_EID_ATTRIBUTES_SPCOUNTRYCODE = "SP-COUNTRYCODE"; +	public static final String SL20_COMMAND_PARAM_EID_X5CENC = SL20_COMMAND_PARAM_GENERAL_RESPONSEENCRYPTIONCERTIFICATE; +	public static final String SL20_COMMAND_PARAM_EID_JWKCENC = SL20_COMMAND_PARAM_GENERAL_RESPONSEENCRYPTIONJWK; +	public static final String SL20_COMMAND_PARAM_EID_RESULT_IDL = "EID-IDENTITY-LINK"; +	public static final String SL20_COMMAND_PARAM_EID_RESULT_AUTHBLOCK = "EID-AUTH-BLOCK"; +	public static final String SL20_COMMAND_PARAM_EID_RESULT_CCSURL = "EID-CCS-URL"; +	public static final String SL20_COMMAND_PARAM_EID_RESULT_LOA = "EID-CITIZEN-QAA-LEVEL"; +	 +	//qualified Signature comamnd +//	public static final String SL20_COMMAND_PARAM_QUALSIG_DATAURL = SL20_COMMAND_PARAM_GENERAL_DATAURL; +//	public static final String SL20_COMMAND_PARAM_QUALSIG_X5CENC = SL20_COMMAND_PARAM_GENERAL_RESPONSEENCRYPTIONCERTIFICATE; +	 +	 +	//getCertificate +	public static final String SL20_COMMAND_PARAM_GETCERTIFICATE_KEYID = "keyId"; +	public static final String SL20_COMMAND_PARAM_GETCERTIFICATE_DATAURL = SL20_COMMAND_PARAM_GENERAL_DATAURL; +	public static final String SL20_COMMAND_PARAM_GETCERTIFICATE_X5CENC = SL20_COMMAND_PARAM_GENERAL_RESPONSEENCRYPTIONCERTIFICATE; +	public static final String SL20_COMMAND_PARAM_GETCERTIFICATE_JWKCENC = SL20_COMMAND_PARAM_GENERAL_RESPONSEENCRYPTIONJWK; +	public static final String SL20_COMMAND_PARAM_GETCERTIFICATE_RESULT_CERTIFICATE = "x5c"; +	 +	//createCAdES Signture +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_KEYID = "keyId";	 +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENT = "content"; +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_MIMETYPE = "mimeType"; +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_PADES_COMBATIBILTY = "padesComatibility"; +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_EXCLUDEBYTERANGE = "excludedByteRange"; +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_CADESLEVEL = "cadesLevel";	 +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_DATAURL = SL20_COMMAND_PARAM_GENERAL_DATAURL; +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_X5CENC = SL20_COMMAND_PARAM_GENERAL_RESPONSEENCRYPTIONCERTIFICATE; +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_JWKCENC = SL20_COMMAND_PARAM_GENERAL_RESPONSEENCRYPTIONJWK; +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_RESULT_SIGNATURE = "signature"; +	 +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_CADESLEVEL_BASIC = "cAdES"; +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_CADESLEVEL_T = "cAdES-T"; +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_CADESLEVEL_C = "cAdES-C"; +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_CADESLEVEL_X = "cAdES-X"; +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_CADESLEVEL_XL = "cAdES-X-L"; +	public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_CADESLEVEL_A = "cAdES-A"; +	 +	 +	 +	//create binding key command +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_KONTOID = "kontoID"; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_SN = "SN"; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_KEYLENGTH = "keyLength"; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_KEYALG = "keyAlg"; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_POLICIES = "policies"; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_DATAURL = SL20_COMMAND_PARAM_GENERAL_DATAURL; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_X5CVDATRUST = "x5cVdaTrust"; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_REQUESTUSERPASSWORD = "reqUserPassword";	 +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_X5CENC = SL20_COMMAND_PARAM_GENERAL_RESPONSEENCRYPTIONCERTIFICATE; +	 +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_KEYALG_RSA = "RSA"; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_KEYALG_SECPR256R1 = "secp256r1"; +	 +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_POLICIES_LIFETIME = "lifeTime"; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_POLICIES_USESECUREELEMENT = "useSecureElement"; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_POLICIES_KEYTIMEOUT = "keyTimeout"; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_POLICIES_NEEDUSERAUTH = "needUserAuth"; +	 +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_RESULT_APPID = "appID"; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_RESULT_CSR = "csr"; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_RESULT_KEYATTESTATIONZERTIFICATE = "attCert"; +	public static final String SL20_COMMAND_PARAM_BINDING_CREATE_RESULT_USERPASSWORD = "encodedPass"; +		 +	 +	//store binding certificate command +	public static final String SL20_COMMAND_PARAM_BINDING_STORE_CERTIFICATE = "x5c"; +	public static final String SL20_COMMAND_PARAM_BINDING_STORE_DATAURL = SL20_COMMAND_PARAM_GENERAL_DATAURL; +	public static final String SL20_COMMAND_PARAM_BINDING_STORE_RESULT_SUCESS = "success"; +	public static final String SL20_COMMAND_PARAM_BINDING_STORE_RESULT_SUCESS_VALUE = "OK"; +	 +	// Username and password authentication +	public static final String SL20_COMMAND_PARAM_AUTH_IDANDPASSWORD_KEYALG = "keyAlg"; +	public static final String SL20_COMMAND_PARAM_AUTH_IDANDPASSWORD_KEYALG_VALUE_PLAIN = "plain"; +	public static final String SL20_COMMAND_PARAM_AUTH_IDANDPASSWORD_KEYALG_VALUE_PBKDF2 = "PBKDF2"; +	public static final String SL20_COMMAND_PARAM_AUTH_IDANDPASSWORD_DATAURL = SL20_COMMAND_PARAM_GENERAL_DATAURL; +	public static final String SL20_COMMAND_PARAM_AUTH_IDANDPASSWORD_X5CENC = SL20_COMMAND_PARAM_GENERAL_RESPONSEENCRYPTIONCERTIFICATE;	 +	public static final String SL20_COMMAND_PARAM_AUTH_IDANDPASSWORD_RESULT_KONTOID = SL20_COMMAND_PARAM_BINDING_CREATE_KONTOID; +	public static final String SL20_COMMAND_PARAM_AUTH_IDANDPASSWORD_RESULT_USERPASSWORD = SL20_COMMAND_PARAM_BINDING_CREATE_RESULT_USERPASSWORD; +	 +	//JWS Token authentication +	public static final String SL20_COMMAND_PARAM_AUTH_JWSTOKEN_NONCE = "nonce"; +	public static final String SL20_COMMAND_PARAM_AUTH_JWSTOKEN_DISPLAYDATA = "displayData"; +	public static final String SL20_COMMAND_PARAM_AUTH_JWSTOKEN_DISPLAYURL = "displayUrl"; +	public static final String SL20_COMMAND_PARAM_AUTH_JWSTOKEN_DATAURL = SL20_COMMAND_PARAM_GENERAL_DATAURL;	 +	public static final String SL20_COMMAND_PARAM_AUTH_JWSTOKEN_RESULT_NONCE = SL20_COMMAND_PARAM_AUTH_JWSTOKEN_NONCE; +	 +	//QR-Code authentication +	public static final String SL20_COMMAND_PARAM_AUTH_QRCODE_QRCODE = "qrCode"; +	public static final String SL20_COMMAND_PARAM_AUTH_QRCODE_DATAURL = SL20_COMMAND_PARAM_GENERAL_DATAURL; +	 +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20HttpBindingUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20HttpBindingUtils.java new file mode 100644 index 00000000..78edf640 --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20HttpBindingUtils.java @@ -0,0 +1,46 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.utils; + +import java.io.IOException; +import java.io.StringWriter; +import java.net.URISyntaxException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ContentType; +import org.jose4j.base64url.Base64Url; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + +public class SL20HttpBindingUtils { +	private static final Logger log = LoggerFactory.getLogger(SL20HttpBindingUtils.class); +	 +	public static void writeIntoResponse(HttpServletRequest request, HttpServletResponse response, JsonNode sl20Forward, String redirectURL) throws IOException, URISyntaxException { +		//forward SL2.0 command +		if (request.getHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE) != null &&  +				request.getHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE).equals(SL20Constants.HTTP_HEADER_VALUE_NATIVE)) { +			log.debug("Client request containts 'native client' header ... ");												 +			StringWriter writer = new StringWriter(); +			writer.write(sl20Forward.toString());						 +			final byte[] content = writer.toString().getBytes("UTF-8"); +			response.setStatus(HttpServletResponse.SC_OK); +			response.setContentLength(content.length); +			response.setContentType(ContentType.APPLICATION_JSON.toString());						 +			response.getOutputStream().write(content); +											 +		} else { +			log.debug("Client request containts is no native client ... "); +			URIBuilder clientRedirectURI = new URIBuilder(redirectURL); +			clientRedirectURI.addParameter( +					SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM,  +					Base64Url.encode(sl20Forward.toString().getBytes())); +			response.setStatus(307); +			response.setHeader("Location", clientRedirectURI.build().toString()); +			 +		} +		 +	} +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JSONBuilderUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JSONBuilderUtils.java new file mode 100644 index 00000000..611bb339 --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JSONBuilderUtils.java @@ -0,0 +1,616 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.utils; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import at.gv.egiz.eaaf.modules.auth.sl20.Constants; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SLCommandoBuildException; + +public class SL20JSONBuilderUtils { + +	private static JsonMapper mapper = new JsonMapper(); +	 +	/** +	 * Create command request +	 * @param name +	 * @param params +	 * @throws SLCommandoBuildException +	 * @return +	 */ +	public static ObjectNode createCommand(String name, ObjectNode params) throws SLCommandoBuildException { +		 +		ObjectNode command = mapper.getMapper().createObjectNode(); +		addSingleStringElement(command, SL20Constants.SL20_COMMAND_CONTAINER_NAME, name, true); +		addSingleJSONElement(command, SL20Constants.SL20_COMMAND_CONTAINER_PARAMS, params, true);				 +		return command; +		 +	} +	 +	/** +	 * Create signed command request +	 *  +	 * @param name +	 * @param params +	 * @param signer +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static String createSignedCommand(String name, ObjectNode params, IJOSETools signer) throws SLCommandoBuildException { +		ObjectNode command = mapper.getMapper().createObjectNode();	 +		addSingleStringElement(command, SL20Constants.SL20_COMMAND_CONTAINER_NAME, name, true); +		addSingleJSONElement(command, SL20Constants.SL20_COMMAND_CONTAINER_PARAMS, params, true);		 +		return signer.createSignature(command.toString()); +				 +	} + + +	/** +	 * Create encrypted command result +	 *  +	 * @param result +	 * @param encrypter +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static String createEncryptedCommandoResult(ObjectNode result, JsonSecurityUtils encrypter) throws SLCommandoBuildException { +		//TODO: add real implementation +		//create header and footer +		String dummyHeader = createJsonEncryptionHeader(encrypter).toString(); +		String payLoad = result.toString(); +		String dummyFooter = createJsonSignedFooter(encrypter); +		 +		return Base64.getUrlEncoder().encodeToString(dummyHeader.getBytes()) + "." +			+ Base64.getUrlEncoder().encodeToString(payLoad.getBytes()) + "." +			+ Base64.getUrlEncoder().encodeToString(dummyFooter.getBytes()); +	 +	} +	 +	 +	/** +	 * Create command result +	 *  +	 * @param name +	 * @param result +	 * @param encryptedResult +	 * @throws SLCommandoBuildException +	 * @return +	 */ +	public static ObjectNode createCommandResponse(String name, ObjectNode result, String encryptedResult) throws SLCommandoBuildException { +		ObjectNode command = mapper.getMapper().createObjectNode(); +		addSingleStringElement(command, SL20Constants.SL20_COMMAND_CONTAINER_NAME, name, true);		 +		addOnlyOnceOfTwo(command,  +				SL20Constants.SL20_COMMAND_CONTAINER_RESULT, SL20Constants.SL20_COMMAND_CONTAINER_ENCRYPTEDRESULT,  +				result, encryptedResult);			 +		return command; +		 +	} +	 +	/** +	 * Create command result +	 *  +	 * @param name +	 * @param result +	 * @param encryptedResult +	 * @throws SLCommandoBuildException +	 * @return +	 */ +	public static String createSignedCommandResponse(String name, ObjectNode result, String encryptedResult, JsonSecurityUtils signer) throws SLCommandoBuildException { +		ObjectNode command = mapper.getMapper().createObjectNode(); +		addSingleStringElement(command, SL20Constants.SL20_COMMAND_CONTAINER_NAME, name, true);		 +		addOnlyOnceOfTwo(command,  +				SL20Constants.SL20_COMMAND_CONTAINER_RESULT, SL20Constants.SL20_COMMAND_CONTAINER_ENCRYPTEDRESULT,  +				result, encryptedResult);	 +		String encodedCommand = command.toString();  +		 +		//TODO: add real implementation +		//create header and footer +		String dummyHeader = createJsonSignedHeader(signer).toString(); +		String dummyFooter = createJsonSignedFooter(signer); +		 +		return Base64.getUrlEncoder().encodeToString(dummyHeader.getBytes()) + "." +				+ Base64.getUrlEncoder().encodeToString(encodedCommand.getBytes()) + "." +				+ Base64.getUrlEncoder().encodeToString(dummyFooter.getBytes()); +				 +	} +	 +	/** +	 * Create parameters for Redirect command +	 *   +	 * @param url +	 * @param command +	 * @param signedCommand +	 * @param ipcRedirect +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static ObjectNode createRedirectCommandParameters(String url, ObjectNode command, ObjectNode signedCommand, Boolean ipcRedirect) throws SLCommandoBuildException{ +		ObjectNode redirectReqParams = mapper.getMapper().createObjectNode(); +		addOnlyOnceOfTwo(redirectReqParams,  +				SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_COMMAND, SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_SIGNEDCOMMAND,  +				command, signedCommand);		 +		addSingleStringElement(redirectReqParams, SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_URL, url, false); +		addSingleBooleanElement(redirectReqParams, SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_IPCREDIRECT, ipcRedirect, false); +		return redirectReqParams; +		 +	} +	 +	/** +	 * Create parameters for Call command +	 *  +	 * @param url +	 * @param method +	 * @param includeTransactionId +	 * @param reqParameters +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static ObjectNode createCallCommandParameters(String url, String method, Boolean includeTransactionId, Map<String, String> reqParameters) throws SLCommandoBuildException { +		ObjectNode callReqParams = mapper.getMapper().createObjectNode(); +		addSingleStringElement(callReqParams, SL20Constants.SL20_COMMAND_PARAM_GENERAL_CALL_URL, url, true); +		addSingleStringElement(callReqParams, SL20Constants.SL20_COMMAND_PARAM_GENERAL_CALL_METHOD, method, true); +		addSingleBooleanElement(callReqParams, SL20Constants.SL20_COMMAND_PARAM_GENERAL_CALL_INCLUDETRANSACTIONID, includeTransactionId, false); +		addArrayOfStringElements(callReqParams, SL20Constants.SL20_COMMAND_PARAM_GENERAL_CALL_REQPARAMETER, reqParameters);		 +		return callReqParams; +		 +	} +	 +	/** +	 * Create result for Error command +	 *  +	 * @param errorCode +	 * @param errorMsg +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static ObjectNode createErrorCommandResult(String errorCode, String errorMsg) throws SLCommandoBuildException { +		ObjectNode result = mapper.getMapper().createObjectNode(); +		addSingleStringElement(result, SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORCODE, errorCode, true); +		addSingleStringElement(result, SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORMESSAGE, errorMsg, true); +		return result; +		 +	} +	 +	 +	/** +	 * Create parameters for qualifiedeID command +	 *  +	 * @param authBlockId +	 * @param dataUrl +	 * @param additionalReqParameters +	 * @param x5cEnc +	 * @return +	 * @throws CertificateEncodingException +	 * @throws SLCommandoBuildException +	 */ +	public static ObjectNode createQualifiedeIDCommandParameters(String authBlockId,  String dataUrl,  +			Map<String, String> additionalReqParameters, X509Certificate x5cEnc) throws CertificateEncodingException, SLCommandoBuildException { +		ObjectNode params = mapper.getMapper().createObjectNode(); +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_EID_AUTHBLOCKID, authBlockId, true); +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_EID_DATAURL, dataUrl, true); +		addArrayOfStringElements(params, SL20Constants.SL20_COMMAND_PARAM_EID_ATTRIBUTES, additionalReqParameters); +		addSingleCertificateElement(params, SL20Constants.SL20_COMMAND_PARAM_EID_X5CENC, x5cEnc, false);		 +		return params; + +	} +	 +	/** +	 * Create result for qualifiedeID command +	 *  +	 * @param idl +	 * @param authBlock +	 * @param ccsURL +	 * @param LoA +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static ObjectNode createQualifiedeIDCommandResult(byte[] idl, byte[] authBlock, String ccsURL, String LoA) throws SLCommandoBuildException { +		ObjectNode result = mapper.getMapper().createObjectNode(); +		addSingleByteElement(result, SL20Constants.SL20_COMMAND_PARAM_EID_RESULT_IDL, idl, true); +		addSingleByteElement(result, SL20Constants.SL20_COMMAND_PARAM_EID_RESULT_AUTHBLOCK, authBlock, true); +		addSingleStringElement(result, SL20Constants.SL20_COMMAND_PARAM_EID_RESULT_CCSURL, ccsURL, true); +		addSingleStringElement(result, SL20Constants.SL20_COMMAND_PARAM_EID_RESULT_LOA, LoA, true); +		return result; +		 +	} +	 +	 +	/** +	 * Create Binding-Key command parameters +	 *  +	 * @param kontoId +	 * @param subjectName +	 * @param keySize +	 * @param keyAlg +	 * @param policies +	 * @param dataUrl +	 * @param x5cVdaTrust +	 * @param reqUserPassword +	 * @param x5cEnc +	 * @return +	 * @throws SLCommandoBuildException +	 * @throws CertificateEncodingException +	 */ +	public static ObjectNode createBindingKeyCommandParams(String kontoId, String subjectName, int keySize, String keyAlg,  +			Map<String, String> policies, String dataUrl, X509Certificate x5cVdaTrust, Boolean reqUserPassword, X509Certificate x5cEnc) throws SLCommandoBuildException, CertificateEncodingException { +		ObjectNode params = mapper.getMapper().createObjectNode(); +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_BINDING_CREATE_KONTOID, kontoId, true); +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_BINDING_CREATE_SN, subjectName, true); +		addSingleNumberElement(params, SL20Constants.SL20_COMMAND_PARAM_BINDING_CREATE_KEYLENGTH, keySize, true); +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_BINDING_CREATE_KEYALG, keyAlg, true);		 +		addArrayOfStringElements(params, SL20Constants.SL20_COMMAND_PARAM_BINDING_CREATE_POLICIES, policies);		 +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_BINDING_CREATE_DATAURL, dataUrl, true); +		addSingleCertificateElement(params, SL20Constants.SL20_COMMAND_PARAM_BINDING_CREATE_X5CVDATRUST, x5cVdaTrust, false); +		addSingleBooleanElement(params, SL20Constants.SL20_COMMAND_PARAM_BINDING_CREATE_REQUESTUSERPASSWORD, reqUserPassword, false); +		addSingleCertificateElement(params, SL20Constants.SL20_COMMAND_PARAM_BINDING_CREATE_X5CENC, x5cEnc, false); +		return params; +		 +	} +	 +	/** +	 * Create Binding-Key command result +	 *  +	 * @param appId +	 * @param csr +	 * @param attCert +	 * @param password +	 * @return +	 * @throws SLCommandoBuildException +	 * @throws CertificateEncodingException +	 */ +	public static ObjectNode createBindingKeyCommandResult(String appId, byte[] csr, X509Certificate attCert, byte[] password) throws SLCommandoBuildException, CertificateEncodingException { +		ObjectNode result = mapper.getMapper().createObjectNode(); +		addSingleStringElement(result, SL20Constants.SL20_COMMAND_PARAM_BINDING_CREATE_RESULT_APPID, appId, true); +		addSingleByteElement(result, SL20Constants.SL20_COMMAND_PARAM_BINDING_CREATE_RESULT_CSR, csr, true); +		addSingleCertificateElement(result, SL20Constants.SL20_COMMAND_PARAM_BINDING_CREATE_RESULT_KEYATTESTATIONZERTIFICATE, attCert, false); +		addSingleByteElement(result, SL20Constants.SL20_COMMAND_PARAM_BINDING_CREATE_RESULT_USERPASSWORD, password, false);		 +		return result; +		 +	} +	 +	/** +	 * Create Store Binding-Certificate command parameters +	 *  +	 * @param cert +	 * @param dataUrl +	 * @return +	 * @throws CertificateEncodingException +	 * @throws SLCommandoBuildException +	 */ +	public static ObjectNode createStoreBindingCertCommandParams(X509Certificate cert, String dataUrl) throws CertificateEncodingException, SLCommandoBuildException { +		ObjectNode params = mapper.getMapper().createObjectNode(); +		addSingleCertificateElement(params, SL20Constants.SL20_COMMAND_PARAM_BINDING_STORE_CERTIFICATE, cert, true); +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_BINDING_STORE_DATAURL, dataUrl, true);		 +		return params; +		 +	} +	 +	/** +	 * Create Store Binding-Certificate command result +	 *  +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static ObjectNode createStoreBindingCertCommandSuccessResult() throws SLCommandoBuildException { +		ObjectNode result = mapper.getMapper().createObjectNode(); +		addSingleStringElement(result, SL20Constants.SL20_COMMAND_PARAM_BINDING_STORE_RESULT_SUCESS,  +				SL20Constants.SL20_COMMAND_PARAM_BINDING_STORE_RESULT_SUCESS_VALUE, true); +		return result; +		 +	} +	 +	 +	/** +	 * Create idAndPassword command parameters +	 *  +	 * @param keyAlg +	 * @param dataUrl +	 * @param x5cEnc +	 * @return +	 * @throws SLCommandoBuildException +	 * @throws CertificateEncodingException +	 */ +	public static ObjectNode createIdAndPasswordCommandParameters(String keyAlg, String dataUrl, X509Certificate x5cEnc) throws SLCommandoBuildException, CertificateEncodingException { +		ObjectNode params = mapper.getMapper().createObjectNode();		 +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_AUTH_IDANDPASSWORD_KEYALG, keyAlg, true); +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_AUTH_IDANDPASSWORD_DATAURL, dataUrl, true); +		addSingleCertificateElement(params, SL20Constants.SL20_COMMAND_PARAM_AUTH_IDANDPASSWORD_X5CENC, x5cEnc, false); +		return params; +		 +	} +	 +	/** +	 * Create idAndPassword command result +	 *  +	 * @param kontoId +	 * @param password +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static ObjectNode createIdAndPasswordCommandResult(String kontoId, byte[] password) throws SLCommandoBuildException { +		ObjectNode result = mapper.getMapper().createObjectNode(); +		addSingleStringElement(result, SL20Constants.SL20_COMMAND_PARAM_AUTH_IDANDPASSWORD_RESULT_KONTOID, kontoId, true); +		addSingleByteElement(result, SL20Constants.SL20_COMMAND_PARAM_AUTH_IDANDPASSWORD_RESULT_USERPASSWORD, password, true);		 +		return result; +		 +	} +	 +	/** +	 * Create JWS Token Authentication command +	 *  +	 * @param nonce +	 * @param dataUrl +	 * @param displayData +	 * @param displayUrl +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static ObjectNode createJwsTokenAuthCommandParams(String nonce, String dataUrl, List<String> displayData, List<String> displayUrl) throws SLCommandoBuildException { +		ObjectNode params = mapper.getMapper().createObjectNode(); +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_AUTH_JWSTOKEN_NONCE, nonce, true); +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_AUTH_JWSTOKEN_DATAURL, dataUrl, true); +		addArrayOfStrings(params, SL20Constants.SL20_COMMAND_PARAM_AUTH_JWSTOKEN_DISPLAYDATA, displayData); +		addArrayOfStrings(params, SL20Constants.SL20_COMMAND_PARAM_AUTH_JWSTOKEN_DISPLAYURL, displayUrl);		 +		return params; +		 +	} +	 +	/** +	 * Create JWS Token Authentication command result +	 *  +	 * @param nonce +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static ObjectNode createJwsTokenAuthCommandResult(String nonce) throws SLCommandoBuildException { +		ObjectNode result = mapper.getMapper().createObjectNode(); +		addSingleStringElement(result, SL20Constants.SL20_COMMAND_PARAM_AUTH_JWSTOKEN_RESULT_NONCE, nonce, true);		 +		return result; +		 +	} +	 +	 +	/** +	 * Create Generic Request Container +	 *  +	 * @param reqId +	 * @param transactionId +	 * @param payLoad +	 * @param signedPayload +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static ObjectNode createGenericRequest(String reqId, String transactionId, ObjectNode payLoad, String signedPayload) throws SLCommandoBuildException { +		ObjectNode req = mapper.getMapper().createObjectNode(); +		addSingleIntegerElement(req, SL20Constants.SL20_VERSION, SL20Constants.CURRENT_SL20_VERSION, true); +		addSingleStringElement(req, SL20Constants.SL20_REQID, reqId, true); +		addSingleStringElement(req, SL20Constants.SL20_TRANSACTIONID, transactionId, false);		 +		addOnlyOnceOfTwo(req, SL20Constants.SL20_PAYLOAD, SL20Constants.SL20_SIGNEDPAYLOAD,  +				payLoad, signedPayload);		 +		return req; +		 +	} +	 +	/** +	 * Create Generic Response Container +	 *  +	 * @param respId +	 * @param inResponseTo +	 * @param transactionId +	 * @param payLoad +	 * @param signedPayload +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static final ObjectNode createGenericResponse(String respId, String inResponseTo, String transactionId,  +			ObjectNode payLoad, String signedPayload) throws SLCommandoBuildException {		 +		ObjectNode req = mapper.getMapper().createObjectNode(); +		addSingleIntegerElement(req, SL20Constants.SL20_VERSION, SL20Constants.CURRENT_SL20_VERSION, true); +		addSingleStringElement(req, SL20Constants.SL20_RESPID, respId, true); +		addSingleStringElement(req, SL20Constants.SL20_INRESPTO, inResponseTo, true); +		addSingleStringElement(req, SL20Constants.SL20_TRANSACTIONID, transactionId, false);		 +		addOnlyOnceOfTwo(req, SL20Constants.SL20_PAYLOAD, SL20Constants.SL20_SIGNEDPAYLOAD,  +				payLoad, signedPayload);		 +		return req; +		 +	} +	 +	/** +	 * Add one element of two possible elements <br> +	 * This method adds either the first element or the second element to parent JSON, but never both.   +	 *  +	 * @param parent Parent JSON element +	 * @param firstKeyId first element Id +	 * @param secondKeyId second element Id +	 * @param first first element +	 * @param second second element +	 * @throws SLCommandoBuildException +	 */ +	public static void addOnlyOnceOfTwo(ObjectNode parent, String firstKeyId, String secondKeyId, ObjectNode first, String second) throws SLCommandoBuildException { +		if (first == null && (second == null  || second.isEmpty())) +			throw new SLCommandoBuildException(firstKeyId + " and " + secondKeyId + " is NULL"); +		 +		else if (first != null && second != null) +			throw new SLCommandoBuildException(firstKeyId + " and " + secondKeyId + " can not SET TWICE"); +		 +		else if (first != null) +			parent.set(firstKeyId, first); +		 +		else if (second != null && !second.isEmpty()) +			parent.put(secondKeyId, second); +		 +		else +			throw new SLCommandoBuildException("Internal build error"); +	} +	 +	 +	 +	//TODO!!!! +	private static ObjectNode createJsonSignedHeader(JsonSecurityUtils signer) throws SLCommandoBuildException { +		ObjectNode header = mapper.getMapper().createObjectNode(); +		addSingleStringElement(header, SL20Constants.JSON_ALGORITHM, SL20Constants.JSON_ALGORITHM_SIGNING_RS256, true); +		addSingleStringElement(header, SL20Constants.JSON_CONTENTTYPE, SL20Constants.SL20_CONTENTTYPE_SIGNED_COMMAND, true);		 +		addArrayOfStrings(header, SL20Constants.JSON_X509_CERTIFICATE, Arrays.asList(Constants.DUMMY_SIGNING_CERT)); +		 +		return header; +	} +	 +	//TODO!!!! +	private static ObjectNode createJsonEncryptionHeader(JsonSecurityUtils signer) throws SLCommandoBuildException { +		ObjectNode header = mapper.getMapper().createObjectNode(); +		addSingleStringElement(header, SL20Constants.JSON_ALGORITHM, SL20Constants.JSON_ALGORITHM_ENC_KEY_RSAOAEP, true); +		addSingleStringElement(header, SL20Constants.JSON_ENCRYPTION_PAYLOAD, SL20Constants.JSON_ALGORITHM_ENC_PAYLOAD_A128CBCHS256, true); +		addSingleStringElement(header, SL20Constants.JSON_CONTENTTYPE, SL20Constants.SL20_CONTENTTYPE_ENCRYPTED_RESULT, true);		 +		addSingleStringElement(header, SL20Constants.JSON_X509_FINGERPRINT, Constants.DUMMY_SIGNING_CERT_FINGERPRINT, true); +		 +		return header; +	} +	 +	//TODO!!!! +	private static String createJsonSignedFooter(JsonSecurityUtils signer) {		 +		return "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7\n" +  +				"  AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4\n" +  +				"  BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K\n" +  +				"  0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv\n" +  +				"  hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB\n" +  +				"  p0igcN_IoypGlUPQGe77Rw"; +	} + +	 +	 +	private static void addArrayOfStrings(ObjectNode parent, String keyId, List<String> values) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId);		 +		if (values != null) { +			ArrayNode callReqParamsArray = mapper.getMapper().createArrayNode(); +			parent.set(keyId, callReqParamsArray  ); +			for(String el : values) +				callReqParamsArray.add(el); +			 +		} +	} +	 +	 +	private static void addArrayOfStringElements(ObjectNode parent, String keyId, Map<String, String> keyValuePairs) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId);		 +		if (keyValuePairs != null) {			 +			ArrayNode callReqParamsArray = mapper.getMapper().createArrayNode(); +			parent.set(keyId, callReqParamsArray); +			 +			for(Entry<String, String> el : keyValuePairs.entrySet()) { +				ObjectNode callReqParams = mapper.getMapper().createObjectNode(); +				callReqParams.put(el.getKey(), el.getValue()); +				callReqParamsArray.add(callReqParams); +				 +			} +		} +	} +	 +	private static void addSingleCertificateElement(ObjectNode parent, String keyId, X509Certificate cert, boolean isRequired) throws CertificateEncodingException, SLCommandoBuildException { +		if (cert != null) +			addSingleByteElement(parent, keyId, cert.getEncoded(), isRequired); +		 +		else if (isRequired) +			throw new SLCommandoBuildException(keyId + " is marked as REQUIRED"); +		 +	} +	 +	 +	 +	private static void addSingleByteElement(ObjectNode parent, String keyId, byte[] value, boolean isRequired) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId); +		 +		if (isRequired && value == null) +			throw new SLCommandoBuildException(keyId + " has NULL value"); +		 +		else if (value != null) +			parent.put(keyId, Base64.getEncoder().encodeToString(value)); +		 +	} +	 +	private static void addSingleBooleanElement(ObjectNode parent, String keyId, Boolean value, boolean isRequired) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId); +		 +		if (isRequired && value == null) +			throw new SLCommandoBuildException(keyId + " has a NULL value"); +			 +		else if (value != null) +			parent.put(keyId, value); +		 +	} +	 +	private static void addSingleNumberElement(ObjectNode parent, String keyId, Integer value, boolean isRequired) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId); +		 +		if (isRequired && value == null) +			throw new SLCommandoBuildException(keyId + " has a NULL value"); +		 +		else if (value != null) +			parent.put(keyId, value);; +		 +	} +	 +	private static void addSingleStringElement(ObjectNode parent, String keyId, String value, boolean isRequired) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId); +		 +		if (isRequired && (value == null || value.isEmpty())) +			throw new SLCommandoBuildException(keyId + " has an empty value"); +		 +		else if (value != null && !value.isEmpty()) +			parent.put(keyId, value); +		 +	} +	 +	private static void addSingleIntegerElement(ObjectNode parent, String keyId, Integer value, boolean isRequired) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId); +		 +		if (isRequired && value == null) +			throw new SLCommandoBuildException(keyId + " has an empty value"); +		 +		else if (value != null) +			parent.put(keyId, value); +		 +	} +	 +	private static void addSingleJSONElement(ObjectNode parent, String keyId, ObjectNode element, boolean isRequired) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId); +		 +		if (isRequired && element == null) +			throw new SLCommandoBuildException("No commando name included"); +		 +		else if (element != null) +			parent.set(keyId, element); +				 +	} +		 +	private static void addOnlyOnceOfTwo(ObjectNode parent, String firstKeyId, String secondKeyId, ObjectNode first, ObjectNode second) throws SLCommandoBuildException { +		if (first == null && second == null) +			throw new SLCommandoBuildException(firstKeyId + " and " + secondKeyId + " is NULL"); +		 +		else if (first != null && second != null) +			throw new SLCommandoBuildException(firstKeyId + " and " + secondKeyId + " can not SET TWICE"); +		 +		else if (first != null) +			parent.set(firstKeyId, first); +		 +		else if (second != null) +			parent.set(secondKeyId, second); +		 +		else +			throw new SLCommandoBuildException("Internal build error"); +	} +	 +	private static void validateParentAndKey(ObjectNode parent, String keyId) throws SLCommandoBuildException { +		if (parent == null) +			throw new SLCommandoBuildException("NO parent JSON element"); +		 +		if (keyId == null || keyId.isEmpty()) +			throw new SLCommandoBuildException("NO JSON element identifier"); +	} +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JSONExtractorUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JSONExtractorUtils.java new file mode 100644 index 00000000..827b5970 --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JSONExtractorUtils.java @@ -0,0 +1,353 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.utils; + +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.utils.URIBuilder; +import org.apache.log4j.Logger; +import org.jose4j.base64url.Base64Url; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import at.gv.egiz.eaaf.modules.auth.sl20.data.VerificationResult; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20Exception; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SLCommandoParserException; + +public class SL20JSONExtractorUtils { +	private static final Logger log = Logger.getLogger(SL20JSONExtractorUtils.class); +	private static JsonMapper mapper = new JsonMapper(); +	 +	 +	/** +	 * Extract String value from JSON  +	 *  +	 * @param input +	 * @param keyID +	 * @param isRequired +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static String getStringValue(JsonNode input, String keyID, boolean isRequired) throws SLCommandoParserException { +		try { +			JsonNode internal = getAndCheck(input, keyID, isRequired); +		 +			if (internal != null) +				return internal.asText(); +			else +				return null; +			 +		} catch (SLCommandoParserException e) { +			throw e; +			 +		} catch (Exception e) { +			throw new SLCommandoParserException("Can not extract String value with keyId: " + keyID, e); +			 +		}		 +	} +	 +	/** +	 * Extract Boolean value from JSON  +	 *  +	 * @param input +	 * @param keyID +	 * @param isRequired +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static boolean getBooleanValue(ObjectNode input, String keyID, boolean isRequired, boolean defaultValue) throws SLCommandoParserException { +		try { +			JsonNode internal = getAndCheck(input, keyID, isRequired); +				 +			if (internal != null) +				return internal.asBoolean(); +			else +				return defaultValue; +			 +		} catch (SLCommandoParserException e) { +			throw e; +			 +		} catch (Exception e) { +			throw new SLCommandoParserException("Can not extract Boolean value with keyId: " + keyID, e); +			 +		}		 +	} +	 +	/** +	 * Extract JSONObject value from JSON  +	 *  +	 * @param input +	 * @param keyID +	 * @param isRequired +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static JsonNode getJSONObjectValue(JsonNode input, String keyID, boolean isRequired) throws SLCommandoParserException { +		try { +			JsonNode internal = getAndCheck(input, keyID, isRequired); +				 +			if (internal != null) +				return internal; +			else +				return null; +			 +		} catch (SLCommandoParserException e) { +			throw e; +			 +		} catch (Exception e) { +			throw new SLCommandoParserException("Can not extract Boolean value with keyId: " + keyID, e); +			 +		}		 +	} +	 +	/** +	 * Extract a List of String elements from a JSON element +	 *  +	 * @param input +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static List<String> getListOfStringElements(JsonNode input) throws SLCommandoParserException { +		List<String> result = new ArrayList<String>(); +		if (input != null) { +			if (input.isArray()) {			 +				Iterator<JsonNode> arrayIterator = input.iterator(); +				while(arrayIterator.hasNext()) { +					JsonNode next = arrayIterator.next(); +					if (next.isTextual()) +						result.add(next.asText());											 +				} +				 +			} else if (input.isTextual()) { +				result.add(input.asText()); +				 +			} else { +				log.warn("JSON Element IS NOT a JSON array or a JSON Primitive"); +				throw new SLCommandoParserException("JSON Element IS NOT a JSON array or a JSON Primitive"); +				 +			} +		} +		 +		return result; +	} +	 +	/** +	 * Extract Map of Key/Value pairs from a JSON Element +	 *  +	 * @param input parent JSON object +	 * @param keyID KeyId of the child that should be parsed +	 * @param isRequired +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static Map<String, String> getMapOfStringElements(JsonNode input, String keyID, boolean isRequired) throws SLCommandoParserException { +		JsonNode internal = getAndCheck(input, keyID, isRequired); +		return getMapOfStringElements(internal); +		 +	} +	 +	/** +	 * Extract Map of Key/Value pairs from a JSON Element  +	 *  +	 * @param input +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static Map<String, String> getMapOfStringElements(JsonNode input) throws SLCommandoParserException {		 +		Map<String, String> result = new HashMap<String, String>(); + +		if (input != null) { +			if (input.isArray()) {						 +				Iterator<JsonNode> arrayIterator = input.iterator(); +				while(arrayIterator.hasNext()) { +					JsonNode next = arrayIterator.next(); +					Iterator<Entry<String, JsonNode>> entry = next.fields(); +					entitySetToMap(result, entry); +										 +				} +				 +			} else if (input.isObject()) { +				Iterator<Entry<String, JsonNode>> objectKeys = input.fields(); +				entitySetToMap(result, objectKeys); +				 +			} else +				throw new SLCommandoParserException("JSON Element IS NOT a JSON array or a JSON object"); +			 +		} +		 +		return result; +	} +	 +	private static void entitySetToMap(Map<String, String> result, Iterator<Entry<String, JsonNode>> entry) { +		while (entry.hasNext()) { +			Entry<String, JsonNode> el = entry.next(); +			if (result.containsKey(el.getKey())) +				log.info("Attr. Map already contains Element with Key: " + el.getKey() + ". Overwrite element ... "); +			 +			result.put(el.getKey(), el.getValue().asText()); +		 +		} +		 +	} +	 +	 +	public static JsonNode extractSL20Result(JsonNode command, IJOSETools decrypter, boolean mustBeEncrypted) throws SL20Exception { +		JsonNode result = command.get(SL20Constants.SL20_COMMAND_CONTAINER_RESULT); +		JsonNode encryptedResult = command.get(SL20Constants.SL20_COMMAND_CONTAINER_ENCRYPTEDRESULT); +		 +		if (result == null && encryptedResult == null) +			throw new SLCommandoParserException("NO result OR encryptedResult FOUND.");		 +				 +		else if (encryptedResult == null && mustBeEncrypted) +			throw new SLCommandoParserException("result MUST be signed."); +				 +		else if (encryptedResult != null && encryptedResult.isTextual()) { +			try { +				return decrypter.decryptPayload(encryptedResult.asText()); +				 +			} catch (Exception e) { +				log.info("Can NOT decrypt SL20 result. Reason:" + e.getMessage()); +				if (!mustBeEncrypted) { +					log.warn("Decrypted results are disabled by configuration. Parse result in plain if it is possible"); + +					//dummy code +					try { +						String[] signedPayload = encryptedResult.toString().split("\\."); +						JsonNode payLoad = mapper.getMapper().readTree(new String(Base64.getUrlDecoder().decode(signedPayload[1]))); +						return payLoad; +						 +					} catch (Exception e1) { +						log.debug("DummyCode FAILED, Reason: " + e1.getMessage() + " Ignore it ..."); +						throw new SL20Exception(e.getMessage(), null, e); +						 +					} +					 +				} else +					throw e;	 +				 +			} + +		} else if (result != null) { +				return result; + +		} else +			throw new SLCommandoParserException("Internal build error"); +		 +		 +	} +		 +	/** +	 * Extract payLoad from generic transport container +	 *  +	 * @param container +	 * @param joseTools +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static VerificationResult extractSL20PayLoad(JsonNode container, IJOSETools joseTools, boolean mustBeSigned) throws SL20Exception { +		 +		JsonNode sl20Payload = container.get(SL20Constants.SL20_PAYLOAD); +		JsonNode sl20SignedPayload = container.get(SL20Constants.SL20_SIGNEDPAYLOAD); +		 +		if (mustBeSigned && joseTools == null) +			throw new SLCommandoParserException("'joseTools' MUST be set if 'mustBeSigned' is 'true'"); +					 +		if (sl20Payload == null && sl20SignedPayload == null) +			throw new SLCommandoParserException("NO payLoad OR signedPayload FOUND.");		 +		 +		else if (sl20SignedPayload == null && mustBeSigned) +			throw new SLCommandoParserException("payLoad MUST be signed."); + +		else if (joseTools != null && sl20SignedPayload != null && sl20SignedPayload.isTextual()) {	 +			return joseTools.validateSignature(sl20SignedPayload.asText()); +		 +		} else if (sl20Payload != null) +			return new VerificationResult(sl20Payload); +		 +		 else +			throw new SLCommandoParserException("Internal build error"); +			 +		 +	} +	 +	 +	/** +	 * Extract generic transport container from httpResponse +	 *  +	 * @param httpResp +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static JsonNode getSL20ContainerFromResponse(HttpResponse httpResp) throws SLCommandoParserException { +		try { +			JsonNode sl20Resp = null; +			if (httpResp.getStatusLine().getStatusCode() == 307) { +				Header[] locationHeader = httpResp.getHeaders("Location"); +				if (locationHeader == null) +					throw new SLCommandoParserException("Find Redirect statuscode but not Location header"); +			 +				String sl20RespString = new URIBuilder(locationHeader[0].getValue()).getQueryParams().get(0).getValue(); +				sl20Resp =  mapper.getMapper().readTree(Base64Url.encode((sl20RespString.getBytes()))); +			 +			} else if (httpResp.getStatusLine().getStatusCode() == 200) { +				if (!httpResp.getEntity().getContentType().getValue().startsWith("application/json")) +					throw new SLCommandoParserException("SL20 response with a wrong ContentType: " + httpResp.getEntity().getContentType().getValue());					 +				sl20Resp = parseSL20ResultFromResponse(httpResp.getEntity()); +		 +			} else if ( (httpResp.getStatusLine().getStatusCode() == 500) ||  +					(httpResp.getStatusLine().getStatusCode() == 401) ||  +					(httpResp.getStatusLine().getStatusCode() == 400) ) { +				log.info("SL20 response with http-code: " + httpResp.getStatusLine().getStatusCode()  +						+ ". Search for error message");				 +				sl20Resp = parseSL20ResultFromResponse(httpResp.getEntity()); +				 +				 +			} else +				throw new SLCommandoParserException("SL20 response with http-code: " + httpResp.getStatusLine().getStatusCode()); + +			log.info("Find JSON object in http response"); +			return sl20Resp; +			 +		} catch (Exception e) { +			throw new SLCommandoParserException("SL20 response parsing FAILED! Reason: " + e.getMessage(), e); +			 +		}		 +	} +	 +	private static JsonNode parseSL20ResultFromResponse(HttpEntity resp) throws Exception { +		if (resp != null && resp.getContent() != null) { +			JsonNode sl20Resp = mapper.getMapper().readTree(new InputStreamReader(resp.getContent())); +			 +			//TODO: check sl20Resp type like && sl20Resp.isJsonObject()			 +			if (sl20Resp != null) { +				return sl20Resp; +				 +			} else +				throw new SLCommandoParserException("SL2.0 can NOT parse to a JSON object"); +			 +			 +		} else +			throw new SLCommandoParserException("Can NOT find content in http response"); + 					 +	} +	 +	 +	private static JsonNode getAndCheck(JsonNode input, String keyID, boolean isRequired) throws SLCommandoParserException { +		JsonNode internal = input.get(keyID); +		 +		if (internal == null && isRequired)  +			throw new SLCommandoParserException("REQUIRED Element with keyId: " + keyID + " does not exist"); +		 +		return internal; +		 +	} +} | 
