diff options
30 files changed, 3130 insertions, 72 deletions
| diff --git a/build.gradle b/build.gradle index cb63098e..7c4bf510 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ task wrapper(type: Wrapper) {  allprojects {  	apply plugin: 'com.github.ben-manes.versions'  	repositories {  mavenCentral()  } -	version = '4.1.3' +	version = '4.1.4-Snapshot'  }  configurations { diff --git a/pdf-as-common/src/main/java/at/gv/egiz/pdfas/api/ws/PDFASSignParameters.java b/pdf-as-common/src/main/java/at/gv/egiz/pdfas/api/ws/PDFASSignParameters.java index 35c057a3..94906112 100644 --- a/pdf-as-common/src/main/java/at/gv/egiz/pdfas/api/ws/PDFASSignParameters.java +++ b/pdf-as-common/src/main/java/at/gv/egiz/pdfas/api/ws/PDFASSignParameters.java @@ -52,7 +52,10 @@ public class PDFASSignParameters implements Serializable {  		@XmlEnumValue("mobilebku")  		MOBILEBKU("mobilebku"),  		@XmlEnumValue("onlinebku") -		ONLINEBKU("onlinebku"); +		ONLINEBKU("onlinebku"), +		@XmlEnumValue("sl20") +		SECLAYER20("sl20"); +		  		private final String name;        diff --git a/pdf-as-lib/build.gradle b/pdf-as-lib/build.gradle index d6e813ac..bdb96949 100644 --- a/pdf-as-lib/build.gradle +++ b/pdf-as-lib/build.gradle @@ -61,6 +61,9 @@ dependencies {      compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.59' +	compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5' +	compile group: 'org.bitbucket.b_c', name: 'jose4j', version: '0.6.3' +  	compile group: 'commons-io', name: 'commons-io', version: '2.4'  	compile 'org.apache.commons:commons-collections4:4.0'  	compile group: 'ognl', name: 'ognl', version: '3.0.8' diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/api/IConfigurationConstants.java b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/api/IConfigurationConstants.java index e9e7e0f4..f043ff21 100644 --- a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/api/IConfigurationConstants.java +++ b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/api/IConfigurationConstants.java @@ -91,6 +91,7 @@ public interface IConfigurationConstants {  	public static final String MOC_SIGN_URL = "moc.sign.url";  	public static final String MOBILE_SIGN_URL = "mobile.sign.url"; +	public static final String SL20_SIGN_URL = "sl20.sign.url";  	public static final String REGISTER_PROVIDER = "registerSecurityProvider"; diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/util/StreamUtils.java b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/util/StreamUtils.java new file mode 100644 index 00000000..1590bef7 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/util/StreamUtils.java @@ -0,0 +1,168 @@ + +package at.gv.egiz.pdfas.lib.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; + +public class StreamUtils { +   +  /** +   * Compare the contents of two <code>InputStream</code>s. +   *  +   * @param is1 The 1st <code>InputStream</code> to compare. +   * @param is2 The 2nd <code>InputStream</code> to compare. +   * @return boolean <code>true</code>, if both streams contain the exactly the +   * same content, <code>false</code> otherwise. +   * @throws IOException An error occurred reading one of the streams. +   */ +  public static boolean compareStreams(InputStream is1, InputStream is2)  +    throws IOException { +       +    byte[] buf1 = new byte[256]; +    byte[] buf2 = new byte[256]; +    int length1; +    int length2; +   +    try { +      while (true) { +        length1 = is1.read(buf1); +        length2 = is2.read(buf2); +         +        if (length1 != length2) { +          return false; +        } +        if (length1 <= 0) { +          return true; +        } +        if (!compareBytes(buf1, buf2, length1)) { +          return false; +        } +      } +    } catch (IOException e) { +      throw e; +    } finally { +      // close both streams +      try { +        is1.close(); +        is2.close(); +      } catch (IOException e) { +        // ignore this +      } +    } +  } +   +  /** +   * Compare two byte arrays, up to a given maximum length. +   *  +   * @param b1 1st byte array to compare. +   * @param b2 2nd byte array to compare. +   * @param length The maximum number of bytes to compare. +   * @return <code>true</code>, if the byte arrays are equal, <code>false</code> +   * otherwise. +   */ +  private static boolean compareBytes(byte[] b1, byte[] b2, int length) { +    if (b1.length != b2.length) { +      return false; +    } +   +    for (int i = 0; i < b1.length && i < length; i++) { +      if (b1[i] != b2[i]) { +        return false; +      } +    } +   +    return true; +  } + +  /** +   * Reads a byte array from a stream. +   * @param in The <code>InputStream</code> to read. +   * @return The bytes contained in the given <code>InputStream</code>. +   * @throws IOException on any exception thrown +   */ +  public static byte[] readStream(InputStream in) throws IOException { + +    ByteArrayOutputStream out = new ByteArrayOutputStream(); +    copyStream(in, out, null); +		   +		/*   +    ByteArrayOutputStream out = new ByteArrayOutputStream(); +    int b; +    while ((b = in.read()) >= 0) +      out.write(b); +     +    */ +    in.close(); +    return out.toByteArray(); +  } + +  /** +   * Reads a <code>String</code> from a stream, using given encoding. +   * @param in The <code>InputStream</code> to read. +   * @param encoding The character encoding to use for converting the bytes +   * of the <code>InputStream</code> into a <code>String</code>. +   * @return The content of the given <code>InputStream</code> converted into +   * a <code>String</code>. +   * @throws IOException on any exception thrown +   */ +  public static String readStream(InputStream in, String encoding) throws IOException { +    ByteArrayOutputStream out = new ByteArrayOutputStream(); +    copyStream(in, out, null); + +    /* +    ByteArrayOutputStream out = new ByteArrayOutputStream(); +    int b; +    while ((b = in.read()) >= 0) +      out.write(b); +      */ +    in.close(); +    return out.toString(encoding); +  } +   +  /** +   * Reads all data (until EOF is reached) from the given source to the  +   * destination stream. If the destination stream is null, all data is dropped. +   * It uses the given buffer to read data and forward it. If the buffer is  +   * null, this method allocates a buffer. +   * +   * @param source The stream providing the data. +   * @param destination The stream that takes the data. If this is null, all +   *                    data from source will be read and discarded. +   * @param buffer The buffer to use for forwarding. If it is null, the method +   *               allocates a buffer. +   * @exception IOException If reading from the source or writing to the  +   *                        destination fails. +   */ +  private static void copyStream(InputStream source, OutputStream destination, byte[] buffer) throws IOException { +    if (source == null) { +      throw new NullPointerException("Argument \"source\" must not be null."); +    } +    if (buffer == null) { +      buffer = new byte[8192]; +    } +     +    if (destination != null) { +      int bytesRead; +      while ((bytesRead = source.read(buffer)) >= 0) { +        destination.write(buffer, 0, bytesRead); +      } +    } else { +      while (source.read(buffer) >= 0); +    }     +  } +   +  /** +   * Gets the stack trace of the <code>Throwable</code> passed in as a string. +   * @param t The <code>Throwable</code>. +   * @return a String representing the stack trace of the <code>Throwable</code>. +   */ +  public static String getStackTraceAsString(Throwable t) +  { +    ByteArrayOutputStream stackTraceBIS = new ByteArrayOutputStream(); +    t.printStackTrace(new PrintStream(stackTraceBIS)); +    return new String(stackTraceBIS.toByteArray()); +  } +} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/sl/schema/CreateCMSSignatureRequestType.java b/pdf-as-lib/src/main/java/at/gv/egiz/sl/schema/CreateCMSSignatureRequestType.java index 5d565d9d..3046b109 100644 --- a/pdf-as-lib/src/main/java/at/gv/egiz/sl/schema/CreateCMSSignatureRequestType.java +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl/schema/CreateCMSSignatureRequestType.java @@ -31,7 +31,7 @@  package at.gv.egiz.sl.schema; -import com.sun.org.apache.xpath.internal.operations.Bool; +//import com.sun.org.apache.xpath.internal.operations.Bool;  import javax.xml.bind.annotation.XmlAccessType;  import javax.xml.bind.annotation.XmlAccessorType; diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/sl20/SL20Connector.java b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/SL20Connector.java new file mode 100644 index 00000000..a82771bd --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/SL20Connector.java @@ -0,0 +1,98 @@ +package at.gv.egiz.sl20; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.codec.binary.Base64; +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.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicNameValuePair; +import org.jose4j.base64url.Base64Url; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonObject; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.lib.api.Configuration; +import at.gv.egiz.pdfas.lib.api.sign.SignParameter; +import at.gv.egiz.sl.schema.CreateCMSSignatureResponseType; +import at.gv.egiz.sl.schema.InfoboxReadRequestType; +import at.gv.egiz.sl.schema.InfoboxReadResponseType; +import at.gv.egiz.sl.util.BaseSLConnector; +import at.gv.egiz.sl.util.RequestPackage; +import at.gv.egiz.sl20.exceptions.SLCommandoParserException; +import at.gv.egiz.sl20.utils.SL20Constants; +import at.gv.egiz.sl20.utils.SL20JSONExtractorUtils; + +public class SL20Connector extends BaseSLConnector { +	private static final Logger log = LoggerFactory.getLogger(SL20Connector.class); +	 +	private String bkuUrl; +	 +	public SL20Connector(Configuration config) { +		this.bkuUrl = config.getValue(CONFIG_BKU_URL); +		 +	} + +	public JsonObject sendSL20Request(JsonObject sl20Req, SignParameter parameter, String vdaURL) {					 +		try { +			log.trace("Request VDA via SL20 with: " + org.bouncycastle.util.encoders.Base64.toBase64String(sl20Req.toString().getBytes())); +			//build http client +			CloseableHttpClient httpClient = buildHttpClient(); +			 +			//build http POST request +			HttpPost httpReq = new HttpPost(new URIBuilder(vdaURL).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 ));				 +			 +			//set native client header +			httpReq.addHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE, SL20Constants.HTTP_HEADER_VALUE_NATIVE); +			 +			//request VDA +			log.trace("Requesting VDA ... "); +			HttpResponse httpResp = httpClient.execute(httpReq);			 +			log.debug("Response from VDA received "); +			 +			return SL20JSONExtractorUtils.getSL20ContainerFromResponse(httpResp); +						 +		} catch (URISyntaxException | IOException e) { +			log.warn("Can NOT build SL20 http requst. Reason:" + e.getMessage(), e); +			return null; +			 +		} catch (SLCommandoParserException e) { +			log.warn("Can NOT parse SL20 response from VDA: Reason: " + e.getMessage(), e);			 +			return null; +			 +		}								 +		 +	} +	 +	@Override +	public InfoboxReadResponseType sendInfoboxReadRequest(InfoboxReadRequestType request, SignParameter parameter) +			throws PdfAsException { +		log.error("'sendInfoboxReadRequest' is NOT supported by " + SL20Connector.class.getName()); +		return null; +	} + +	@Override +	public CreateCMSSignatureResponseType sendCMSRequest(RequestPackage pack, SignParameter parameter) +			throws PdfAsException { +		log.error("'sendCMSReques' is NOT supported by " + SL20Connector.class.getName()); +		return null; +	} +	 +	private CloseableHttpClient buildHttpClient() { +		HttpClientBuilder builder = HttpClientBuilder.create(); +		return builder.build(); +	} + +} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/sl20/data/VerificationResult.java b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/data/VerificationResult.java new file mode 100644 index 00000000..acb0977e --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/data/VerificationResult.java @@ -0,0 +1,39 @@ +package at.gv.egiz.sl20.data; + +import java.security.cert.X509Certificate; +import java.util.List; + +import com.google.gson.JsonObject; + +public class VerificationResult { + +	private Boolean validSigned = null; +	private List<X509Certificate> certs = null; +	private JsonObject payload = null; +	 +	public VerificationResult(JsonObject payload) { +		this.payload = payload; +		 +	} +	 +	public VerificationResult(JsonObject 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 JsonObject getPayload() { +		return payload; +	} +	 +	 +	 +	 +} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/sl20/exceptions/SL20Exception.java b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/exceptions/SL20Exception.java new file mode 100644 index 00000000..108081bf --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/exceptions/SL20Exception.java @@ -0,0 +1,19 @@ +package at.gv.egiz.sl20.exceptions; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; + +public class SL20Exception extends PdfAsException { + +	private static final long serialVersionUID = 1L; + +	public SL20Exception(String messageId) { +		super(messageId); + +	} +	 +	public SL20Exception(String messageId, Throwable wrapped) { +		super(messageId, wrapped); + +	} + +} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/sl20/exceptions/SL20SecurityException.java b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/exceptions/SL20SecurityException.java new file mode 100644 index 00000000..e1562b14 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/exceptions/SL20SecurityException.java @@ -0,0 +1,20 @@ +package at.gv.egiz.sl20.exceptions; + +public class SL20SecurityException extends SL20Exception { + +	private static final long serialVersionUID = 3281385988027147449L; +	 +	public SL20SecurityException() { +		super("sl20.05"); +	} +	 +	public SL20SecurityException(Throwable wrapped) { +		super("sl20.05", wrapped); + +	} + +	public SL20SecurityException(String string) { +		super(string); +	} + +} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/sl20/exceptions/SLCommandoBuildException.java b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/exceptions/SLCommandoBuildException.java new file mode 100644 index 00000000..31ec2451 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/exceptions/SLCommandoBuildException.java @@ -0,0 +1,17 @@ +package at.gv.egiz.sl20.exceptions; + +public class SLCommandoBuildException extends SL20Exception { + +	private static final long serialVersionUID = 1L; + +	 +	public SLCommandoBuildException() { +		super("sl20.01"); +		 +	} +	 +	public SLCommandoBuildException(Throwable e) { +		super("sl20.01", e); +		 +	} +} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/sl20/exceptions/SLCommandoParserException.java b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/exceptions/SLCommandoParserException.java new file mode 100644 index 00000000..6c49ca66 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/exceptions/SLCommandoParserException.java @@ -0,0 +1,17 @@ +package at.gv.egiz.sl20.exceptions; + +public class SLCommandoParserException extends SL20Exception { + +	private static final long serialVersionUID = 1L; + +	 +	public SLCommandoParserException() { +		super("sl20.02"); +		 +	} +	 +	public SLCommandoParserException(Throwable e) { +		super("sl20.02", e); +		 +	} +} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/IJOSETools.java b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/IJOSETools.java new file mode 100644 index 00000000..a0f369d8 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/IJOSETools.java @@ -0,0 +1,54 @@ +package at.gv.egiz.sl20.utils; + +import java.security.cert.X509Certificate; + +import com.google.gson.JsonElement; + +import at.gv.egiz.sl20.data.VerificationResult; +import at.gv.egiz.sl20.exceptions.SL20Exception; +import at.gv.egiz.sl20.exceptions.SLCommandoBuildException; + +public interface IJOSETools { + +	/** +	 * Check if the JOSE tools are initialized +	 *  +	 * @return +	 */ +	public boolean isInitialized(); +	 +	/** +	 * 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 JsonElement decryptPayload(String compactSerialization) throws SL20Exception; +	  +} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/SL20Constants.java b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/SL20Constants.java new file mode 100644 index 00000000..59c3079d --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/SL20Constants.java @@ -0,0 +1,232 @@ +package at.gv.egiz.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/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/SL20JSONBuilderUtils.java b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/SL20JSONBuilderUtils.java new file mode 100644 index 00000000..40edb74b --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/SL20JSONBuilderUtils.java @@ -0,0 +1,604 @@ +package at.gv.egiz.sl20.utils; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.codec.binary.Base64; +import org.bouncycastle.util.encoders.Base64Encoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import at.gv.egiz.sl20.exceptions.SLCommandoBuildException; + +public class SL20JSONBuilderUtils { +	private static final Logger log = LoggerFactory.getLogger(SL20JSONBuilderUtils.class); +	 +	/** +	 * Create command request +	 * @param name +	 * @param params +	 * @throws SLCommandoBuildException +	 * @return +	 */ +	public static JsonObject createCommand(String name, JsonElement params) throws SLCommandoBuildException { +		JsonObject command = new JsonObject();		 +		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, JsonElement params, IJOSETools signer) throws SLCommandoBuildException { +		JsonObject command = new JsonObject();		 +		addSingleStringElement(command, SL20Constants.SL20_COMMAND_CONTAINER_NAME, name, true); +		addSingleJSONElement(command, SL20Constants.SL20_COMMAND_CONTAINER_PARAMS, params, true);		 +		return signer.createSignature(command.toString()); +				 +	} +	 +	/** +	 * Create command result +	 *  +	 * @param name +	 * @param result +	 * @param encryptedResult +	 * @throws SLCommandoBuildException +	 * @return +	 */ +	public static JsonObject createCommandResponse(String name, JsonElement result, String encryptedResult) throws SLCommandoBuildException { +		JsonObject command = new JsonObject(); +		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, JsonElement result, String encryptedResult, IJOSETools signer) throws SLCommandoBuildException { +		JsonObject command = new JsonObject(); +		addSingleStringElement(command, SL20Constants.SL20_COMMAND_CONTAINER_NAME, name, true);		 +		addOnlyOnceOfTwo(command,  +				SL20Constants.SL20_COMMAND_CONTAINER_RESULT, SL20Constants.SL20_COMMAND_CONTAINER_ENCRYPTEDRESULT,  +				result, encryptedResult);			 +		return signer.createSignature(command.toString()); +						 +	} +	 +	/** +	 * Create parameters for Redirect command +	 *   +	 * @param url +	 * @param command +	 * @param signedCommand +	 * @param ipcRedirect +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static JsonObject createRedirectCommandParameters(String url, JsonElement command, JsonElement signedCommand, Boolean ipcRedirect) throws SLCommandoBuildException{ +		JsonObject redirectReqParams = new JsonObject(); +		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 JsonObject createCallCommandParameters(String url, String method, Boolean includeTransactionId, Map<String, String> reqParameters) throws SLCommandoBuildException { +		JsonObject callReqParams = new JsonObject(); +		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 JsonObject createErrorCommandResult(String errorCode, String errorMsg) throws SLCommandoBuildException { +		JsonObject result = new JsonObject(); +		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 JsonObject createQualifiedeIDCommandParameters(String authBlockId,  String dataUrl,  +			Map<String, String> additionalReqParameters, X509Certificate x5cEnc) throws CertificateEncodingException, SLCommandoBuildException { +		JsonObject params = new JsonObject(); +		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; + +	} +	 +	public static JsonObject createGetCertificateCommandParameters(String keyId,  String dataUrl,  +			X509Certificate x5cEnc) throws CertificateEncodingException, SLCommandoBuildException {		 +		JsonObject params = new JsonObject(); +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_GETCERTIFICATE_KEYID, keyId, true); +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_GETCERTIFICATE_DATAURL, dataUrl, true); +		addSingleCertificateElement(params, SL20Constants.SL20_COMMAND_PARAM_GETCERTIFICATE_X5CENC, x5cEnc, false);		 +		return params; +		 +	} +		 +	public static JsonObject createCreateCAdESCommandParameters(String keyId, +			byte[] content, String mimeType, boolean padesCompatiblem, List<String> byteRanges, String cadesLevel,			 +			String dataUrl, X509Certificate x5cEnc) throws CertificateEncodingException, SLCommandoBuildException {		 +		JsonObject params = new JsonObject(); +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_KEYID, keyId, true);		 +		addSingleByteElement(params, SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENT, content, true);			 +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_MIMETYPE, mimeType, true);		 +		addSingleBooleanElement(params, SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_PADES_COMBATIBILTY, padesCompatiblem, false);		 +		addArrayOfStrings(params, SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_EXCLUDEBYTERANGE, byteRanges);		 +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CADESLEVEL, cadesLevel, false);		 +		addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_GETCERTIFICATE_DATAURL, dataUrl, true); +		addSingleCertificateElement(params, SL20Constants.SL20_COMMAND_PARAM_GETCERTIFICATE_X5CENC, x5cEnc, false);		 +		return params; +		 +	} +	 +	/** +	 * Create result for qualifiedeID command +	 *  +	 * @param idl +	 * @param authBlock +	 * @param ccsURL +	 * @param LoA +	 * @return +	 * @throws SLCommandoBuildException +	 */ +	public static JsonObject createQualifiedeIDCommandResult(byte[] idl, byte[] authBlock, String ccsURL, String LoA) throws SLCommandoBuildException { +		JsonObject result = new JsonObject(); +		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 JsonObject createBindingKeyCommandParams(String kontoId, String subjectName, int keySize, String keyAlg,  +			Map<String, String> policies, String dataUrl, X509Certificate x5cVdaTrust, Boolean reqUserPassword, X509Certificate x5cEnc) throws SLCommandoBuildException, CertificateEncodingException { +		JsonObject params = new JsonObject(); +		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 JsonObject createBindingKeyCommandResult(String appId, byte[] csr, X509Certificate attCert, byte[] password) throws SLCommandoBuildException, CertificateEncodingException { +		JsonObject result = new JsonObject(); +		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 JsonObject createStoreBindingCertCommandParams(X509Certificate cert, String dataUrl) throws CertificateEncodingException, SLCommandoBuildException { +		JsonObject params = new JsonObject(); +		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 JsonObject createStoreBindingCertCommandSuccessResult() throws SLCommandoBuildException { +		JsonObject result = new JsonObject(); +		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 JsonObject createIdAndPasswordCommandParameters(String keyAlg, String dataUrl, X509Certificate x5cEnc) throws SLCommandoBuildException, CertificateEncodingException { +		JsonObject params = new JsonObject();		 +		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 JsonObject createIdAndPasswordCommandResult(String kontoId, byte[] password) throws SLCommandoBuildException { +		JsonObject result = new JsonObject(); +		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 JsonObject createJwsTokenAuthCommandParams(String nonce, String dataUrl, List<String> displayData, List<String> displayUrl) throws SLCommandoBuildException { +		JsonObject params = new JsonObject(); +		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 JsonObject createJwsTokenAuthCommandResult(String nonce) throws SLCommandoBuildException { +		JsonObject result = new JsonObject(); +		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 JsonObject createGenericRequest(String reqId, String transactionId, JsonElement payLoad, String signedPayload) throws SLCommandoBuildException { +		JsonObject req = new JsonObject(); +		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 JsonObject createGenericResponse(String respId, String inResponseTo, String transactionId,  +			JsonElement payLoad, String signedPayload) throws SLCommandoBuildException { +		 +		JsonObject req = new JsonObject(); +		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(JsonObject parent, String firstKeyId, String secondKeyId, JsonElement first, String second) throws SLCommandoBuildException { +		if (first == null && (second == null  || second.isEmpty())) { +			log.warn(firstKeyId + " and " + secondKeyId + " is NULL"); +			throw new SLCommandoBuildException(); +		 +		} else if (first != null && second != null) { +			log.warn(firstKeyId + " and " + secondKeyId + " can not SET TWICE"); +			throw new SLCommandoBuildException(); +		 +		} else if (first != null) +			parent.add(firstKeyId, first); +		 +		else if (second != null && !second.isEmpty()) +			parent.addProperty(secondKeyId, second); +		 +		else { +			log.warn("Internal build error"); +			throw new SLCommandoBuildException(); +			 +		} +	} +	 +	private static void addArrayOfStrings(JsonObject parent, String keyId, List<String> values) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId);		 +		if (values != null) { +			JsonArray callReqParamsArray = new JsonArray(); +			parent.add(keyId, callReqParamsArray  ); +			for(String el : values) +				callReqParamsArray.add(el); +			 +		} +	} +	 +	 +	private static void addArrayOfStringElements(JsonObject parent, String keyId, Map<String, String> keyValuePairs) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId);		 +		if (keyValuePairs != null) {			 +			JsonArray callReqParamsArray = new JsonArray(); +			parent.add(keyId, callReqParamsArray  ); +			 +			for(Entry<String, String> el : keyValuePairs.entrySet()) { +				JsonObject callReqParams = new JsonObject(); +				//callReqParams.addProperty(SL20Constants.SL20_COMMAND_PARAM_GENERAL_REQPARAMETER_KEY, el.getKey()); +				//callReqParams.addProperty(SL20Constants.SL20_COMMAND_PARAM_GENERAL_REQPARAMETER_VALUE, el.getValue()); +				callReqParams.addProperty(el.getKey(), el.getValue()); +				callReqParamsArray.add(callReqParams); +				 +			} +		} +	} +	 +	private static void addSingleCertificateElement(JsonObject parent, String keyId, X509Certificate cert, boolean isRequired) throws CertificateEncodingException, SLCommandoBuildException { +		if (cert != null) +			addSingleByteElement(parent, keyId, cert.getEncoded(), isRequired); +		 +		else if (isRequired) { +			log.warn(keyId + " is marked as REQUIRED"); +			throw new SLCommandoBuildException(); +			 +		} +		 +	} +	 +	 +	 +	private static void addSingleByteElement(JsonObject parent, String keyId, byte[] value, boolean isRequired) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId); +		 +		if (isRequired && value == null) { +			log.warn(keyId + " has NULL value"); +			throw new SLCommandoBuildException(); +		 +		} else if (value != null) +			parent.addProperty(keyId, org.bouncycastle.util.encoders.Base64.toBase64String(value)); +		 +	} +	 +	private static void addSingleBooleanElement(JsonObject parent, String keyId, Boolean value, boolean isRequired) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId); +		 +		if (isRequired && value == null) { +			log.warn(keyId + " has a NULL value"); +			throw new SLCommandoBuildException(); +			 +		} else if (value != null) +			parent.addProperty(keyId, value); +		 +	} +	 +	private static void addSingleNumberElement(JsonObject parent, String keyId, Integer value, boolean isRequired) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId); +		 +		if (isRequired && value == null) { +			log.warn(keyId + " has a NULL value"); +			throw new SLCommandoBuildException(); +		 +		} else if (value != null) +			parent.addProperty(keyId, value);; +		 +	} +	 +	private static void addSingleStringElement(JsonObject parent, String keyId, String value, boolean isRequired) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId); +		 +		if (isRequired && (value == null || value.isEmpty())) { +			log.warn(keyId + " has an empty value"); +			throw new SLCommandoBuildException(); +		 +		} else if (value != null && !value.isEmpty()) +			parent.addProperty(keyId, value); +		 +	} +	 +	private static void addSingleIntegerElement(JsonObject parent, String keyId, Integer value, boolean isRequired) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId); +		 +		if (isRequired && value == null) { +			log.warn(keyId + " has an empty value"); +			throw new SLCommandoBuildException(); +		 +		} else if (value != null) +			parent.addProperty(keyId, value); +		 +	} +	 +	private static void addSingleJSONElement(JsonObject parent, String keyId, JsonElement element, boolean isRequired) throws SLCommandoBuildException { +		validateParentAndKey(parent, keyId); +		 +		if (isRequired && element == null) { +			log.warn("No commando name included"); +			throw new SLCommandoBuildException(); +		 +		} else if (element != null) +			parent.add(keyId, element); +		 +	} +		 +	private static void addOnlyOnceOfTwo(JsonObject parent, String firstKeyId, String secondKeyId, JsonElement first, JsonElement second) throws SLCommandoBuildException { +		if (first == null && second == null) { +			log.warn(firstKeyId + " and " + secondKeyId + " is NULL"); +			throw new SLCommandoBuildException(); +		 +		} else if (first != null && second != null) { +			log.warn(firstKeyId + " and " + secondKeyId + " can not SET TWICE"); +			throw new SLCommandoBuildException(); +		 +		} else if (first != null) +			parent.add(firstKeyId, first); +		 +		else if (second != null) +			parent.add(secondKeyId, second); +		 +		else { +			log.warn("Internal build error"); +			throw new SLCommandoBuildException(); +			 +		} +	} +	 +	private static void validateParentAndKey(JsonObject parent, String keyId) throws SLCommandoBuildException { +		if (parent == null) { +			log.warn("NO parent JSON element"); +			throw new SLCommandoBuildException(); +			 +		} +		if (keyId == null || keyId.isEmpty()) { +			log.warn("NO JSON element identifier"); +			throw new SLCommandoBuildException(); +			 +		} +	} +} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/SL20JSONExtractorUtils.java b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/SL20JSONExtractorUtils.java new file mode 100644 index 00000000..5a438e16 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/SL20JSONExtractorUtils.java @@ -0,0 +1,422 @@ +package at.gv.egiz.sl20.utils; + +import java.io.InputStreamReader; +import java.util.ArrayList; +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.jose4j.base64url.Base64Url; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import at.gv.egiz.sl20.data.VerificationResult; +import at.gv.egiz.sl20.exceptions.SL20Exception; +import at.gv.egiz.sl20.exceptions.SLCommandoParserException; + +public class SL20JSONExtractorUtils { +	private static final Logger log = LoggerFactory.getLogger(SL20JSONExtractorUtils.class); +	 +	/** +	 * Extract String value from JSON  +	 *  +	 * @param input +	 * @param keyID +	 * @param isRequired +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static String getStringValue(JsonObject input, String keyID, boolean isRequired) throws SLCommandoParserException { +		try { +			JsonElement internal = getAndCheck(input, keyID, isRequired); +		 +			if (internal != null) +				return internal.getAsString(); +			else +				return null; +			 +		} catch (SLCommandoParserException e) { +			throw e; +			 +		} catch (Exception e) { +			log.warn("Can not extract String value with keyId: " + keyID); +			throw new SLCommandoParserException(e); +			 +		}		 +	} +	 +	/** +	 * Extract Boolean value from JSON  +	 *  +	 * @param input +	 * @param keyID +	 * @param isRequired +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static boolean getBooleanValue(JsonObject input, String keyID, boolean isRequired, boolean defaultValue) throws SLCommandoParserException { +		try { +			JsonElement internal = getAndCheck(input, keyID, isRequired); +				 +			if (internal != null) +				return internal.getAsBoolean(); +			else +				return defaultValue; +			 +		} catch (SLCommandoParserException e) { +			throw e; +			 +		} catch (Exception e) { +			log.warn("Can not extract Boolean value with keyId: " + keyID); +			throw new SLCommandoParserException(e); +			 +		}		 +	} +	 +	/** +	 * Extract JSONObject value from JSON  +	 *  +	 * @param input +	 * @param keyID +	 * @param isRequired +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static JsonObject getJSONObjectValue(JsonObject input, String keyID, boolean isRequired) throws SLCommandoParserException { +		try { +			JsonElement internal = getAndCheck(input, keyID, isRequired); +				 +			if (internal != null) +				return internal.getAsJsonObject(); +			else +				return null; +			 +		} catch (SLCommandoParserException e) { +			throw e; +			 +		} catch (Exception e) { +			log.warn("Can not extract Boolean value with keyId: \" + keyID"); +			throw new SLCommandoParserException(e); +			 +		}		 +	} +	 +	/** +	 * 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(JsonObject input, String keyID, boolean isRequired) throws SLCommandoParserException { +		JsonElement internal = getAndCheck(input, keyID, isRequired); +		return getMapOfStringElements(internal); +		 +	} +	 +	/** +	 * Extract a List of String elements from a JSON element +	 *  +	 * @param input +	 * @param isRequired  +	 * @param keyID  +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static List<String> getListOfStringElements(JsonObject input, String keyID, boolean isRequired) throws SLCommandoParserException { +		JsonElement internal = getAndCheck(input, keyID, isRequired); + +		List<String> result = new ArrayList<String>(); +		if (internal != null) { +			if (internal.isJsonArray()) {			 +				Iterator<JsonElement> arrayIterator = internal.getAsJsonArray().iterator(); +				while(arrayIterator.hasNext()) { +					JsonElement next = arrayIterator.next(); +					if (next.isJsonPrimitive()) +						result.add(next.getAsString());											 +				} +				 +			} else if (internal.isJsonPrimitive()) { +				result.add(internal.getAsString()); +				 +			} else { +				log.warn("JSON Element IS NOT a JSON array or a JSON Primitive"); +				throw new SLCommandoParserException(); +			} +		} +		 +		return result; +	} +	 +	 +	/** +	 * Extract Map of Key/Value pairs from a JSON Element  +	 *  +	 * @param input +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static Map<String, String> getMapOfStringElements(JsonElement input) throws SLCommandoParserException {		 +		Map<String, String> result = new HashMap<String, String>(); +				 +		if (input != null) { +			if (input.isJsonArray()) {			 +				Iterator<JsonElement> arrayIterator = input.getAsJsonArray().iterator(); +				while(arrayIterator.hasNext()) { +					JsonElement next = arrayIterator.next();				 +					Iterator<Entry<String, JsonElement>> entry = next.getAsJsonObject().entrySet().iterator(); +					entitySetToMap(result, entry); +					 +				} +				 +			} else if (input.isJsonObject()) { +				Iterator<Entry<String, JsonElement>> objectKeys = input.getAsJsonObject().entrySet().iterator(); +				entitySetToMap(result, objectKeys); +				 +			} else { +				log.warn("JSON Element IS NOT a JSON array or a JSON object"); +				throw new SLCommandoParserException(); +			} +			 +		} +		 +		return result; +	} +	 +	private static void entitySetToMap(Map<String, String> result, Iterator<Entry<String, JsonElement>> entry) { +		while (entry.hasNext()) { +			Entry<String, JsonElement> 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().getAsString()); +		 +		} +		 +	} +	 +	 +	public static JsonElement extractSL20Result(JsonObject command, IJOSETools decrypter, boolean mustBeEncrypted) throws SL20Exception { +		JsonElement result = command.get(SL20Constants.SL20_COMMAND_CONTAINER_RESULT); +		JsonElement encryptedResult = command.get(SL20Constants.SL20_COMMAND_CONTAINER_ENCRYPTEDRESULT); +		 +		if (result == null && encryptedResult == null) { +			log.warn("NO result OR encryptedResult FOUND."); +			throw new SLCommandoParserException();		 +				 +		} else if (encryptedResult == null && mustBeEncrypted) { +			log.warn("result MUST be signed."); +			throw new SLCommandoParserException(); +				 +		} else if (encryptedResult != null && encryptedResult.isJsonPrimitive()) { +			try { +				return decrypter.decryptPayload(encryptedResult.getAsString()); +				 +			} 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("\\."); +						JsonElement payLoad = new JsonParser().parse(new String(Base64Url.decodeToUtf8String(signedPayload[1]))); +						return payLoad; +						 +					} catch (Exception e1) { +						log.debug("DummyCode FAILED, Reason: " + e1.getMessage() + " Ignore it ..."); +						throw new SL20Exception(e.getMessage(), e); +						 +					} +					 +				} else +					throw e;	 +				 +			} + +		} else if (result != null) { +				return result; + +		} else { +			log.error("Internal build error"); +			throw new SLCommandoParserException(); +			 +		} +		 +		 +	} +		 +	/** +	 * Extract payLoad from generic transport container +	 *  +	 * @param container +	 * @param joseTools +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static VerificationResult extractSL20PayLoad(JsonObject container, IJOSETools joseTools, boolean mustBeSigned) throws SL20Exception { +		 +		JsonElement sl20Payload = container.get(SL20Constants.SL20_PAYLOAD); +		JsonElement sl20SignedPayload = container.get(SL20Constants.SL20_SIGNEDPAYLOAD); +		 +		if (mustBeSigned && joseTools == null) { +			log.warn("'joseTools' MUST be set if 'mustBeSigned' is 'true'"); +			throw new SLCommandoParserException(); +			 +		}	 +		 +		if (sl20Payload == null && sl20SignedPayload == null) { +			log.warn("NO payLoad OR signedPayload FOUND."); +			throw new SLCommandoParserException();		 +		 +		} else if (sl20SignedPayload == null && mustBeSigned) { +			log.warn("payLoad MUST be signed."); +			throw new SLCommandoParserException(); + +		} else if (joseTools != null && sl20SignedPayload != null && sl20SignedPayload.isJsonPrimitive()) {	 +			try { +				return joseTools.validateSignature(sl20SignedPayload.getAsString()); +				 +			} catch (SL20Exception e) { +				if (!mustBeSigned && sl20Payload == null) { +					log.debug("Signature verification FAILED with reason: " + e.getMessage()  +						+ " but response MUST NOT be signed by configuration" +						+ " Starting backup process ... "); +					String[] split = sl20SignedPayload.getAsString().split("\\."); +					if (split.length == 3) { +						JsonElement payLoad = new JsonParser().parse(new String(Base64Url.decodeToUtf8String(split[1]))); +						log.info("Signature verification FAILED with reason: " + e.getMessage() + " Use plain result as it is"); +						return new VerificationResult(payLoad.getAsJsonObject()); +						 +					}										 +				} +			 +				throw e; +				 +			} +		 +		} else if (sl20Payload != null) +			return new VerificationResult(sl20Payload.getAsJsonObject()); +		 +		else if (joseTools == null && !mustBeSigned && sl20SignedPayload != null && sl20SignedPayload.isJsonPrimitive())  { +			log.info("Received signed SL20 response, but verification IS NOT required and NOT CONFIGURATED. Skip signature verification ... "); +			String[] split = sl20SignedPayload.getAsString().split("\\."); +			if (split.length == 3) { +				JsonElement payLoad = new JsonParser().parse(new String(Base64Url.decodeToUtf8String(split[1]))); +				return new VerificationResult(payLoad.getAsJsonObject()); +				 +			} else { +				log.warn("Can NOT skip signature verification, because signed result has an unsupported format!"); +				throw new SLCommandoParserException(); +				 +			} +			 +		} else { +			log.warn("Internal build error"); +			throw new SLCommandoParserException(); +			 +		 } +			 +		 +	} +	 +	 +	/** +	 * Extract generic transport container from httpResponse +	 *  +	 * @param httpResp +	 * @return +	 * @throws SLCommandoParserException +	 */ +	public static JsonObject getSL20ContainerFromResponse(HttpResponse httpResp) throws SLCommandoParserException { +		try { +			JsonObject sl20Resp = null; +			if (httpResp.getStatusLine().getStatusCode() == 307) { +				Header[] locationHeader = httpResp.getHeaders("Location"); +				if (locationHeader == null) { +					log.warn("Find Redirect statuscode but not Location header"); +					throw new SLCommandoParserException(); +			 +				} +				String sl20RespString = new URIBuilder(locationHeader[0].getValue()).getQueryParams().get(0).getValue(); +				sl20Resp = new JsonParser().parse(Base64Url.encode((sl20RespString.getBytes()))).getAsJsonObject(); +			 +			} else if (httpResp.getStatusLine().getStatusCode() == 200) { +				if (!httpResp.getEntity().getContentType().getValue().startsWith("application/json")) { +					log.warn("SL20 response with a wrong ContentType: " + httpResp.getEntity().getContentType().getValue()); +					throw new SLCommandoParserException(); +					 +				}				 +				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 { +				log.warn("SL20 response with http-code: " + httpResp.getStatusLine().getStatusCode()); +				throw new SLCommandoParserException(); +				 +			} + +			log.info("Find JSON object in http response"); +			return sl20Resp; +			 +		} catch (Exception e) { +			log.warn("SL20 response parsing FAILED! Reason: " + e.getMessage(), e); +			throw new SLCommandoParserException(e); +			 +		}		 +	} +	 +	private static JsonObject parseSL20ResultFromResponse(HttpEntity resp) throws Exception { +		if (resp != null && resp.getContent() != null) {			 +			JsonElement sl20Resp = new JsonParser().parse(new InputStreamReader(resp.getContent())); +			if (sl20Resp != null && sl20Resp.isJsonObject()) { +				return sl20Resp.getAsJsonObject(); +				 +			} else { +				log.warn("SL2.0 can NOT parse to a JSON object"); +				throw new SLCommandoParserException(); +				 +			} +			 +			 +		} else { +			log.warn("Can NOT find content in http response"); +			throw new SLCommandoParserException(); +			 +		} + 					 +	} +	 +	 +	private static JsonElement getAndCheck(JsonObject input, String keyID, boolean isRequired) throws SLCommandoParserException { +		JsonElement internal = input.get(keyID); +		 +		if (internal == null && isRequired) { +			log.warn("REQUIRED Element with keyId: " + keyID + " does not exist"); +			throw new SLCommandoParserException(); +			 +		} +		 +		return internal; +		 +	} +} diff --git a/pdf-as-moa/src/main/java/META-INF/MANIFEST.MF b/pdf-as-moa/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 00000000..254272e1 --- /dev/null +++ b/pdf-as-moa/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path:  + diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/config/WebConfiguration.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/config/WebConfiguration.java index d63f698f..c6b27eb3 100644 --- a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/config/WebConfiguration.java +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/config/WebConfiguration.java @@ -42,9 +42,12 @@ public class WebConfiguration implements IConfigurationConstants {  	public static final String LOCAL_BKU_ENABLED = "bku.sign.enabled";  	public static final String ONLINE_BKU_ENABLED = "moc.sign.enabled";  	public static final String MOBILE_BKU_ENABLED = "mobile.sign.enabled"; +	public static final String SL20_BKU_ENABLED = "sl20.sign.enabled";  	public static final String LOCAL_BKU_URL = "bku.local.url";  	public static final String ONLINE_BKU_URL = "bku.online.url";  	public static final String MOBILE_BKU_URL = "bku.mobile.url"; +	public static final String SL20_BKU_URL = "sl20.mobile.url"; +	  	public static final String ERROR_DETAILS = "error.showdetails";  	public static final String PDF_AS_WORK_DIR = "pdfas.dir";  	public static final String STATISTIC_BACKEND_LIST = "statistic.backends"; @@ -82,6 +85,23 @@ public class WebConfiguration implements IConfigurationConstants {  	public static final String KEYSTORE_DEFAULT_ALIAS = KEYSTORE_DEFAULT + "." + KEYSTORE_ALIAS;  	public static final String KEYSTORE_DEFAULT_KEY_PASS = KEYSTORE_DEFAULT + "." + KEYSTORE_KEY_PASS; +	//SL20 stuff +	public static final String SL20_PREFIX = "sl20"; +	public static final String SL20_KEYSTORE_PREFIX = SL20_PREFIX + ".keystore";  +	public static final String SL20_KEYSTORE_FILE = SL20_KEYSTORE_PREFIX + "." + "file"; +	public static final String SL20_KEYSTORE_TYPE = SL20_KEYSTORE_PREFIX + "." + "type"; +	public static final String SL20_KEYSTORE_PASS = SL20_KEYSTORE_PREFIX + "." + "pass"; +	public static final String SL20_KEYSTORE_KEY_SIGN_ALIAS = SL20_KEYSTORE_PREFIX + "." + "sign.key.alias"; +	public static final String SL20_KEYSTORE_KEY_SIGN_PASS = SL20_KEYSTORE_PREFIX + "." + "sign.key.pass"; +	public static final String SL20_KEYSTORE_KEY_ENCRYPTION_ALIAS = SL20_KEYSTORE_PREFIX + "." + "enc.key.alias"; +	public static final String SL20_KEYSTORE_KEY_ENCRYPTION_PASS = SL20_KEYSTORE_PREFIX + "." + "enc.key.pass"; +	public static final String SL20_DEBUG_VALIDATION_DISABLED = SL20_PREFIX + ".debug.validation.disable"; +	public static final String SL20_DEBUG_SIGNING_ENABLED = SL20_PREFIX + ".debug.signed.result.enabled"; +	public static final String SL20_DEBUG_SIGNING_REQUIRED = SL20_PREFIX + ".debug.signed.result.required"; +	public static final String SL20_DEBUG_ENCRYPTION_ENABLED = SL20_PREFIX + ".debug.encryption.enabled"; +	public static final String SL20_DEBUG_ENCRYPTION_REQUIRED = SL20_PREFIX + ".debug.encryption.required"; +	 +	  	public static final String WHITELIST_ENABLED = "whitelist.enabled";  	public static final String WHITELIST_VALUE_PRE = "whitelist.url."; @@ -248,6 +268,20 @@ public class WebConfiguration implements IConfigurationConstants {  		return null;  	} +	public static String getSecurityLayer20URL() { +		if(getSL20Enabled()) { +			String overwrite = properties.getProperty(SL20_SIGN_URL); +			if(overwrite == null) { +				overwrite = properties.getProperty(SL20_BKU_URL); +				if(overwrite == null) { +					overwrite = PdfAsHelper.getPdfAsConfig().getValue(SL20_SIGN_URL); +				} +			} +			return overwrite; +		} +		return null; +	} +	  	public static String getPdfASDir() {  		return properties.getProperty(PDF_AS_WORK_DIR);  	} @@ -447,6 +481,16 @@ public class WebConfiguration implements IConfigurationConstants {  		return false;  	} +	public static boolean getSL20Enabled() { +		String value = properties.getProperty(SL20_BKU_ENABLED); +		if (value != null) { +			if (value.equals("true")) { +				return true; +			} +		} +		return false; +	} +	  	public static boolean getSoapSignEnabled() {  		String value = properties.getProperty(SOAP_SIGN_ENABLED);  		if (value != null) { @@ -598,5 +642,66 @@ public class WebConfiguration implements IConfigurationConstants {  		}  		return false;  	} +	 +	public static String getSL20KeyStorePath() { +		return properties.getProperty(SL20_KEYSTORE_FILE); +		 +	} +	 +	public static String getSL20KeyStoreType() { +		return properties.getProperty(SL20_KEYSTORE_TYPE); +		 +	} +	 +	public static String getSL20KeyStorePassword() { +		return properties.getProperty(SL20_KEYSTORE_PASS); +		 +	} +	 +	public static String getSL20KeySigningAlias() { +		return properties.getProperty(SL20_KEYSTORE_KEY_SIGN_ALIAS); +		 +	} +	 +	public static String getSL20KeySigningPassword() { +		return properties.getProperty(SL20_KEYSTORE_KEY_SIGN_PASS); +		 +	} +	 +	public static String getSL20KeyEncryptionAlias() { +		return properties.getProperty(SL20_KEYSTORE_KEY_ENCRYPTION_ALIAS); +		 +	} +	 +	public static String getSL20KeyEncryptionPassword() { +		return properties.getProperty(SL20_KEYSTORE_KEY_ENCRYPTION_PASS); +		 +	} +	 +	public static boolean isSL20ValidationDisabled( ) { +		return Boolean.parseBoolean(properties.getProperty(SL20_DEBUG_VALIDATION_DISABLED, String.valueOf(false))); +		 +	} +	 +	public static boolean isSL20SigningEnabled( ) { +		return Boolean.parseBoolean(properties.getProperty(SL20_DEBUG_SIGNING_ENABLED, String.valueOf(false))); +		 +	} +	 +	public static boolean isSL20SigningRequired( ) { +		return Boolean.parseBoolean(properties.getProperty(SL20_DEBUG_SIGNING_REQUIRED, String.valueOf(false))); +		 +	} +	 +	public static boolean isSL20EncryptionEnabled( ) { +		return Boolean.parseBoolean(properties.getProperty(SL20_DEBUG_ENCRYPTION_ENABLED, String.valueOf(false))); +		 +	} +	 +	public static boolean isSL20EncryptionRequired( ) { +		return Boolean.parseBoolean(properties.getProperty(SL20_DEBUG_ENCRYPTION_REQUIRED, String.valueOf(false))); +		 +	} +	  } diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/helper/PdfAsHelper.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/helper/PdfAsHelper.java index 3aad831d..4b776cb3 100644 --- a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/helper/PdfAsHelper.java +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/helper/PdfAsHelper.java @@ -30,12 +30,17 @@ import java.awt.image.RenderedImage;  import java.io.ByteArrayOutputStream;  import java.io.File;  import java.io.IOException; +import java.io.StringWriter;  import java.io.UnsupportedEncodingException; +import java.net.URL;  import java.net.URLEncoder;  import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.HashMap;  import java.util.Iterator;  import java.util.List;  import java.util.Map; +import java.util.UUID;  import javax.imageio.ImageIO;  import javax.servlet.RequestDispatcher; @@ -51,9 +56,12 @@ import org.apache.commons.codec.binary.Base64;  import org.apache.commons.io.FileUtils;  import org.apache.commons.io.IOUtils;  import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.http.entity.ContentType;  import org.slf4j.Logger;  import org.slf4j.LoggerFactory; +import com.google.gson.JsonObject; +  import at.gv.egiz.pdfas.api.ws.PDFASSignParameters;  import at.gv.egiz.pdfas.api.ws.PDFASSignParameters.Connector;  import at.gv.egiz.pdfas.api.ws.PDFASSignResponse; @@ -77,6 +85,8 @@ import at.gv.egiz.pdfas.sigs.pades.PAdESSignerKeystore;  import at.gv.egiz.pdfas.web.config.WebConfiguration;  import at.gv.egiz.pdfas.web.exception.PdfAsWebException;  import at.gv.egiz.pdfas.web.servlets.UIEntryPointServlet; +import at.gv.egiz.pdfas.web.sl20.JsonSecurityUtils; +import at.gv.egiz.pdfas.web.sl20.SL20HttpBindingUtils;  import at.gv.egiz.pdfas.web.stats.StatisticEvent;  import at.gv.egiz.sl.schema.CreateCMSSignatureResponseType;  import at.gv.egiz.sl.schema.InfoboxAssocArrayPairType; @@ -84,8 +94,16 @@ import at.gv.egiz.sl.schema.InfoboxReadRequestType;  import at.gv.egiz.sl.schema.InfoboxReadResponseType;  import at.gv.egiz.sl.schema.ObjectFactory;  import at.gv.egiz.sl.util.BKUSLConnector; +import at.gv.egiz.sl.util.BaseSLConnector;  import at.gv.egiz.sl.util.RequestPackage;  import at.gv.egiz.sl.util.SLMarschaller; +import at.gv.egiz.sl20.SL20Connector; +import at.gv.egiz.sl20.data.VerificationResult; +import at.gv.egiz.sl20.exceptions.SL20Exception; +import at.gv.egiz.sl20.exceptions.SLCommandoParserException; +import at.gv.egiz.sl20.utils.SL20Constants; +import at.gv.egiz.sl20.utils.SL20JSONBuilderUtils; +import at.gv.egiz.sl20.utils.SL20JSONExtractorUtils;  public class PdfAsHelper { @@ -105,6 +123,7 @@ public class PdfAsHelper {  	private static final String PDF_PROVIDE_PAGE = "/ProvidePDF";  	private static final String PDF_PDFDATA_PAGE = "/PDFData";  	private static final String PDF_DATAURL_PAGE = "/DataURL"; +	private static final String PDF_SL20_DATAURL_PAGE = "/DataURLSL20";  	private static final String PDF_USERENTRY_PAGE = "/userentry";  	private static final String PDF_ERR_URL = "PDF_ERR_URL";  	private static final String PDF_FILE_NAME = "PDF_FILE_NAME"; @@ -118,6 +137,7 @@ public class PdfAsHelper {  	private static final String SIGNATURE_ACTIVE = "SIGNATURE_ACTIVE";  	private static final String VERIFICATION_RESULT = "VERIFICATION_RESULT";  	private static final String QRCODE_CONTENT = "QR_CONT"; +	public static final String PDF_SESSION_PREFIX = "PDF_SESSION_";  	private static final Logger logger = LoggerFactory  			.getLogger(PdfAsHelper.class); @@ -707,6 +727,12 @@ public class PdfAsHelper {  			// conn.setBase64(true);  			signer = new PAdESSigner(conn);  			session.setAttribute(PDF_SL_CONNECTOR, conn); +			 +		} else if (connector.equals("sl20")) { +			SL20Connector conn = new SL20Connector(config); +			signer = new PAdESSigner(conn); +			session.setAttribute(PDF_SL_CONNECTOR, conn); +			  		} else {  			throw new PdfAsWebException(  					"Invalid connector (bku | onlinebku | mobilebku | moa | jks)"); @@ -794,9 +820,15 @@ public class PdfAsHelper {  			// conn.setBase64(true);  			signer = new PAdESSigner(conn);  			session.setAttribute(PDF_SL_CONNECTOR, conn); + +		} else if (connector.equals("sl20")) { +			SL20Connector conn = new SL20Connector(config); +			signer = new PAdESSigner(conn); +			session.setAttribute(PDF_SL_CONNECTOR, conn); +			  		} else {  			throw new PdfAsWebException( -					"Invalid connector (bku | onlinebku | mobilebku | moa | jks)"); +					"Invalid connector (bku | onlinebku | mobilebku | moa | jks | sl20)");  		}  		signParameter.setPreprocessorArguments(preProcessor);  		signParameter.setPlainSigner(signer); @@ -839,7 +871,7 @@ public class PdfAsHelper {  		PdfAsHelper.process(request, response, context);  	} -	private static byte[] getCertificate( +	public static byte[] getCertificate(  			InfoboxReadResponseType infoboxReadResponseType) {  		byte[] data = null;  		if (infoboxReadResponseType.getAssocArrayData() != null) { @@ -898,7 +930,7 @@ public class PdfAsHelper {  	public static void injectCertificate(HttpServletRequest request,  			HttpServletResponse response, -			InfoboxReadResponseType infoboxReadResponseType, +			byte[] certificate,  			ServletContext context) throws Exception {  		HttpSession session = request.getSession(); @@ -910,7 +942,7 @@ public class PdfAsHelper {  					+ session.getId());  		} -		statusRequest.setCertificate(getCertificate(infoboxReadResponseType)); +		statusRequest.setCertificate(certificate);  		statusRequest = pdfAs.process(statusRequest);  		session.setAttribute(PDF_STATUS, statusRequest); @@ -919,7 +951,7 @@ public class PdfAsHelper {  	public static void injectSignature(HttpServletRequest request,  			HttpServletResponse response, -			CreateCMSSignatureResponseType createCMSSignatureResponseType, +			byte[] cmsSginature,  			ServletContext context) throws Exception {  		logger.debug("Got CMS Signature Response"); @@ -933,8 +965,7 @@ public class PdfAsHelper {  					+ session.getId());  		} -		statusRequest.setSigature(createCMSSignatureResponseType -				.getCMSSignature()); +		statusRequest.setSigature(cmsSginature);  		statusRequest = pdfAs.process(statusRequest);  		session.setAttribute(PDF_STATUS, statusRequest); @@ -996,21 +1027,35 @@ public class PdfAsHelper {  		String connector = (String) session.getAttribute(PDF_SL_INTERACTIVE); +		//load connector +		BaseSLConnector slConnector = null;  		if (connector.equals("bku") || connector.equals("onlinebku") -				|| connector.equals("mobilebku")) { -			BKUSLConnector bkuSLConnector = (BKUSLConnector) session +				|| connector.equals("mobilebku")) +			slConnector = (BKUSLConnector) session  					.getAttribute(PDF_SL_CONNECTOR); - -			if (statusRequest.needCertificate()) { -				logger.debug("Needing Certificate from BKU"); -				// build SL Request to read certificate -				InfoboxReadRequestType readCertificateRequest = bkuSLConnector -						.createInfoboxReadRequest(statusRequest -								.getSignParameter()); - +		 +		else if (connector.equals("sl20")) +			slConnector = (SL20Connector) session +					.getAttribute(PDF_SL_CONNECTOR); +		 +		else +			throw new PdfAsWebException("Invalid connector: " + connector); +		 +		JsonSecurityUtils joseTools = JsonSecurityUtils.getInstance(); +		if (!joseTools.isInitialized()) +			joseTools = null; +		 +		if (statusRequest.needCertificate()) { +			logger.debug("Needing Certificate from BKU"); +			// build SL Request to read certificate +			InfoboxReadRequestType readCertificateRequest = slConnector +					.createInfoboxReadRequest(statusRequest +							.getSignParameter()); + +			if (slConnector instanceof BKUSLConnector) {  				JAXBElement<InfoboxReadRequestType> readRequest = of  						.createInfoboxReadRequest(readCertificateRequest); - +	  				String url = generateDataURL(request, response);  				String slRequest = SLMarschaller.marshalToString(readRequest);  				String template = getTemplateSL(); @@ -1021,7 +1066,7 @@ public class PdfAsHelper {  						StringEscapeUtils.escapeHtml4(slRequest));  				template = template.replace("##DataURL##", url);  				template = template.replace("##LOCALE##", locale); - +	  				if (statusRequest.getSignParameter().getTransactionId() != null) {  					template = template.replace(  							"##ADDITIONAL##", @@ -1034,70 +1079,220 @@ public class PdfAsHelper {  				} else {  					template = template.replace("##ADDITIONAL##", "");  				} - +	  				response.getWriter().write(template);  				// TODO: set content type of response!!  				response.setContentType("text/html");  				response.getWriter().close(); -			} else if (statusRequest.needSignature()) { -				logger.debug("Needing Signature from BKU"); -				// build SL Request for cms signature -				RequestPackage pack = bkuSLConnector.createCMSRequest( -						statusRequest.getSignatureData(), -						statusRequest.getSignatureDataByteRange(), -						statusRequest.getSignParameter()); - +				 +			} else if (slConnector instanceof SL20Connector) { +				//generate request for getCertificate command  +				SL20Connector sl20Connector = (SL20Connector)slConnector; +				 +				//use 'SecureSigningKeypair' per default +				String keyId = SL20Connector.SecureSignatureKeypair; +				 +				java.security.cert.X509Certificate x5cEnc = null; +				if (WebConfiguration.isSL20EncryptionEnabled() && joseTools != null) +					x5cEnc = joseTools.getEncryptionCertificate(); +				JsonObject getCertParams =  +						SL20JSONBuilderUtils.createGetCertificateCommandParameters( +								keyId, generateDataURLSL20(request, response), x5cEnc); +				 +				JsonObject sl20Req = null; +				String reqId = UUID.randomUUID().toString(); +				if (WebConfiguration.isSL20SigningEnabled()) { +					String signedCertCommand = SL20JSONBuilderUtils.createSignedCommand( +							SL20Constants.SL20_COMMAND_IDENTIFIER_GETCERTIFICATE, getCertParams, joseTools); +					sl20Req = SL20JSONBuilderUtils.createGenericRequest(reqId, null, null, signedCertCommand); +					 +				} else { +					JsonObject getCertCommand = SL20JSONBuilderUtils.createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_GETCERTIFICATE, getCertParams); +					sl20Req = SL20JSONBuilderUtils.createGenericRequest(reqId, null, getCertCommand, null); +					 +				}	 +								 +				//send SL20 request via Backend connection +				JsonObject sl20Resp = sl20Connector.sendSL20Request(sl20Req, null, generateBKUURL(connector)); +				if (sl20Resp == null) { +					logger.info("Receive NO responce from SL2.0 connection. Process stops ... "); +					throw new SLCommandoParserException(); +					 +				} +				 +				VerificationResult respPayloadContainer = SL20JSONExtractorUtils.extractSL20PayLoad( +						sl20Resp, joseTools, WebConfiguration.isSL20SigningRequired()); +				 +				if (respPayloadContainer.isValidSigned() == null) +					logger.debug("Receive unsigned payLoad from VDA"); +					 +				JsonObject respPayload = respPayloadContainer.getPayload(); +				if (respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).getAsString() +						.equals(SL20Constants.SL20_COMMAND_IDENTIFIER_REDIRECT)) { +					logger.debug("Find 'redirect' command in VDA response ... ");									 +					JsonObject params = SL20JSONExtractorUtils.getJSONObjectValue(respPayload, SL20Constants.SL20_COMMAND_CONTAINER_PARAMS, true);					 +					String redirectURL = SL20JSONExtractorUtils.getStringValue(params, SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_URL, true);									 +					JsonObject 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 +					JsonObject sl20Forward = sl20Resp.deepCopy().getAsJsonObject(); +					SL20JSONBuilderUtils.addOnlyOnceOfTwo(sl20Forward,  +							SL20Constants.SL20_PAYLOAD, SL20Constants.SL20_SIGNEDPAYLOAD,  +							command, signedCommand); +										 +					//store requestId +					 +					request.getSession(false).setAttribute(PDF_SESSION_PREFIX + SL20Constants.SL20_REQID, reqId); + +					//forward SL2.0 command +					SL20HttpBindingUtils.writeIntoResponse(request, response, sl20Forward, redirectURL); +													 +				} else if (respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).getAsString() +						.equals(SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR)) {  +					JsonObject 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); +					 +					logger.info("Receive SL2.0 error. Code:" + errorCode + " Msg:" + errorMsg); +					throw new SL20Exception("sl20.08"); +					 +				} else { +					logger.warn("Received an unrecognized command: " + respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).getAsString()); +					throw new SLCommandoParserException(); +					 +				} +				 +			} else +				throw new PdfAsWebException("Invalid connector: " + slConnector.getClass().getName()); +			 +		} else if (statusRequest.needSignature()) { +			logger.debug("Needing Signature from BKU"); +			// build SL Request for cms signature +			RequestPackage pack = slConnector.createCMSRequest( +					statusRequest.getSignatureData(), +					statusRequest.getSignatureDataByteRange(), +					statusRequest.getSignParameter()); + +			if (slConnector instanceof BKUSLConnector) {						  				String slRequest = SLMarschaller  						.marshalToString(of  								.createCreateCMSSignatureRequest(pack  										.getRequestType()));  				logger.trace("SL Request: " + slRequest); - +				  				response.setContentType("text/xml");  				response.getWriter().write(slRequest);  				response.getWriter().close(); +				 +			} else if (slConnector instanceof SL20Connector) {				 +				//convert byte range +				List<String> byteRanges = new ArrayList<String>(); +				for (int el : statusRequest.getSignatureDataByteRange()) +					byteRanges.add(String.valueOf(el)); +				 +				java.security.cert.X509Certificate x5cEnc = null; +				if (WebConfiguration.isSL20EncryptionEnabled() && joseTools != null) +					x5cEnc = joseTools.getEncryptionCertificate(); + +				//set 'true' as default +				boolean padesCompatibel = true; +				if (pack.getRequestType().getPAdESFlag() != null) +					padesCompatibel = pack.getRequestType().getPAdESFlag(); +				 +				JsonObject createCAdESSigParams =  +						SL20JSONBuilderUtils.createCreateCAdESCommandParameters( +								pack.getRequestType().getKeyboxIdentifier(),  +								statusRequest.getSignatureData(),  +								pack.getRequestType().getDataObject().getMetaInfo().getMimeType(),  +								padesCompatibel ,  +								byteRanges,  +								SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CADESLEVEL_BASIC,  +								generateDataURLSL20(request, response),  +								x5cEnc) ; +				 +				JsonObject sl20CreateCAdES = null; +				String reqId = UUID.randomUUID().toString(); +				if (WebConfiguration.isSL20SigningEnabled()) { +					String signedCertCommand = SL20JSONBuilderUtils.createSignedCommand( +							SL20Constants.SL20_COMMAND_IDENTIFIER_CREATE_SIG_CADES, createCAdESSigParams, joseTools); +					sl20CreateCAdES = SL20JSONBuilderUtils.createGenericRequest(reqId, null, null, signedCertCommand); +					 +				} else { +					JsonObject getCertCommand = SL20JSONBuilderUtils.createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_CREATE_SIG_CADES, createCAdESSigParams); +					sl20CreateCAdES = SL20JSONBuilderUtils.createGenericRequest(UUID.randomUUID().toString(), null, getCertCommand, null); +					 +				}	 +				 +				request.getSession(false).setAttribute(PDF_SESSION_PREFIX + SL20Constants.SL20_REQID, reqId); + +				//forward SL2.0 command +				logger.trace("Write 'createCAdES' command to VDA: " + sl20CreateCAdES.toString()); +				StringWriter writer = new StringWriter(); +				writer.write(sl20CreateCAdES.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 +				throw new PdfAsWebException("Invalid connector: " + slConnector.getClass().getName()); +				 +				 + +		} else if (statusRequest.isReady()) { +			// TODO: store pdf document redirect to Finish URL +			logger.debug("Document ready!"); + +			SignResult result = pdfAs.finishSign(statusRequest); + +			ByteArrayOutputStream baos = (ByteArrayOutputStream) session +					.getAttribute(PDF_OUTPUT); +			baos.close(); + +			PDFASVerificationResponse verResponse = new PDFASVerificationResponse(); +			List<VerifyResult> verResults = PdfAsHelper.synchornousVerify( +					baos.toByteArray(), -2, +					PdfAsHelper.getVerificationLevel(request), null); + +			if (verResults.size() != 1) { +				throw new WebServiceException( +						"Document verification failed!"); +			} +			VerifyResult verifyResult = verResults.get(0); -			} else if (statusRequest.isReady()) { -				// TODO: store pdf document redirect to Finish URL -				logger.debug("Document ready!"); - -				SignResult result = pdfAs.finishSign(statusRequest); - -				ByteArrayOutputStream baos = (ByteArrayOutputStream) session -						.getAttribute(PDF_OUTPUT); -				baos.close(); - -				PDFASVerificationResponse verResponse = new PDFASVerificationResponse(); -				List<VerifyResult> verResults = PdfAsHelper.synchornousVerify( -						baos.toByteArray(), -2, -						PdfAsHelper.getVerificationLevel(request), null); +			verResponse.setCertificateCode(verifyResult +					.getCertificateCheck().getCode()); +			verResponse.setValueCode(verifyResult.getValueCheckCode() +					.getCode()); -				if (verResults.size() != 1) { -					throw new WebServiceException( -							"Document verification failed!"); -				} -				VerifyResult verifyResult = verResults.get(0); +			PdfAsHelper.setPDFASVerificationResponse(request, verResponse); +			PdfAsHelper.setSignedPdf(request, response, baos.toByteArray()); -				verResponse.setCertificateCode(verifyResult -						.getCertificateCheck().getCode()); -				verResponse.setValueCode(verifyResult.getValueCheckCode() -						.getCode()); +			String signerCert = Base64.encodeBase64String(result +					.getSignerCertificate().getEncoded()); -				PdfAsHelper.setPDFASVerificationResponse(request, verResponse); -				PdfAsHelper.setSignedPdf(request, response, baos.toByteArray()); +			PdfAsHelper.setSignerCertificate(request, signerCert); +			 +			if (slConnector instanceof BKUSLConnector) {  				PdfAsHelper.gotoProvidePdf(context, request, response); - -				String signerCert = Base64.encodeBase64String(result -						.getSignerCertificate().getEncoded()); - -				PdfAsHelper.setSignerCertificate(request, signerCert); - -			} else { -				throw new PdfAsWebException("Invalid state!"); -			} +				 +			} else if (slConnector instanceof SL20Connector) { +				//TODO: add code to send SL20 redirect command to redirect the user from DataURL connection to App Front-End connection +				String callUrl = generateProvideURL(request, response); +				String transactionId = (String) request.getAttribute(PdfAsHelper.PDF_SESSION_PREFIX + SL20Constants.SL20_TRANSACTIONID); +				buildSL20RedirectResponse(request, response, transactionId, callUrl); +				 +			} else +				throw new PdfAsWebException("Invalid connector: " + slConnector.getClass().getName()); +			  		} else { -			throw new PdfAsWebException("Invalid connector: " + connector); +			throw new PdfAsWebException("Invalid state!");  		}  	} @@ -1338,6 +1533,11 @@ public class PdfAsHelper {  		request.getSession(true);  	} +	public static String generateDataURLSL20(HttpServletRequest request, +			HttpServletResponse response) { +		return generateURL(request, response, PDF_SL20_DATAURL_PAGE); +	} +	  	public static String generateDataURL(HttpServletRequest request,  			HttpServletResponse response) {  		return generateURL(request, response, PDF_DATAURL_PAGE); @@ -1385,6 +1585,8 @@ public class PdfAsHelper {  			return WebConfiguration.getOnlineBKUURL();  		} else if (connector.equals("mobilebku")) {  			return WebConfiguration.getHandyBKUURL(); +		} else if (connector.equals("sl20")) { +			return WebConfiguration.getSecurityLayer20URL();  		}  		return WebConfiguration.getLocalBKUURL();  	} @@ -1542,4 +1744,64 @@ public class PdfAsHelper {  	public static String getSCMRevision() {  		return PdfAsFactory.getSCMRevision();  	} +	 +	public static void buildSL20RedirectResponse(HttpServletRequest request, HttpServletResponse response, String transactionId, String callURL) throws IOException, SL20Exception {		 +		//create response  +		Map<String, String> reqParameters = UrlParameterExtractor.splitQuery(new URL(callURL)); +		 +		//extract URL without parameters +		String url; +		int paramIndex = callURL.indexOf("?"); +		if (paramIndex == -1) +			url = callURL; +		else +			url = callURL.substring(0, paramIndex); +		 +		JsonObject callReqParams = SL20JSONBuilderUtils.createCallCommandParameters( +				url,  +				SL20Constants.SL20_COMMAND_PARAM_GENERAL_CALL_METHOD_GET,  +				false,  +				reqParameters); +		JsonObject callCommand = SL20JSONBuilderUtils.createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_CALL, callReqParams); +		 +		//build first redirect command for app +		JsonObject redirectOneParams = SL20JSONBuilderUtils.createRedirectCommandParameters( +				null,  +				callCommand, null, true); +		JsonObject redirectOneCommand = SL20JSONBuilderUtils.createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_REDIRECT, redirectOneParams); +						 +		//build second redirect command for IDP +		JsonObject redirectTwoParams = SL20JSONBuilderUtils.createRedirectCommandParameters( +				callURL,  +				redirectOneCommand, null, false); +		JsonObject redirectTwoCommand = SL20JSONBuilderUtils.createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_REDIRECT, redirectTwoParams); +		 +		//build generic SL2.0 response container								 +		JsonObject 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) {					 +			logger.debug("Client request containts 'native client' header ... "); +			logger.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 { +			logger.info("SL2.0 DataURL communication needs http header: '" + SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE + "'"); +			throw new SL20Exception("sl20.06"); +			 +		} +	}  } diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/DataURLServlet.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/DataURLServlet.java index 45861953..50c3b063 100644 --- a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/DataURLServlet.java +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/DataURLServlet.java @@ -93,11 +93,11 @@ public class DataURLServlet extends HttpServlet {  			if(jaxbObject.getValue() instanceof InfoboxReadResponseType) {  				InfoboxReadResponseType infoboxReadResponseType = (InfoboxReadResponseType)jaxbObject.getValue();  				logger.info("Got InfoboxReadResponseType"); -				PdfAsHelper.injectCertificate(request, response, infoboxReadResponseType, getServletContext()); +				PdfAsHelper.injectCertificate(request, response, PdfAsHelper.getCertificate(infoboxReadResponseType), getServletContext());  			} else if(jaxbObject.getValue() instanceof CreateCMSSignatureResponseType) {  				CreateCMSSignatureResponseType createCMSSignatureResponseType = (CreateCMSSignatureResponseType)jaxbObject.getValue();  				logger.info("Got CreateCMSSignatureResponseType"); -				PdfAsHelper.injectSignature(request, response, createCMSSignatureResponseType, getServletContext()); +				PdfAsHelper.injectSignature(request, response, createCMSSignatureResponseType.getCMSSignature(), getServletContext());  			} else if(jaxbObject.getValue() instanceof ErrorResponseType) {  				ErrorResponseType errorResponseType = (ErrorResponseType)jaxbObject.getValue();  				logger.warn("SecurityLayer: " + errorResponseType.getErrorCode() + " " + errorResponseType.getInfo()); diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/ExternSignServlet.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/ExternSignServlet.java index 3cea5247..1d2ab14e 100644 --- a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/ExternSignServlet.java +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/ExternSignServlet.java @@ -354,7 +354,8 @@ public class ExternSignServlet extends HttpServlet {  		logger.debug("Starting signature creation with: " + connector);  		//IPlainSigner signer; -		if (connector.equals("bku") || connector.equals("onlinebku") || connector.equals("mobilebku")) { +		if (connector.equals("bku") || connector.equals("onlinebku") || connector.equals("mobilebku") +				|| connector.equals("sl20")) {  			// start asynchronous signature creation  			if(connector.equals("bku")) { @@ -372,6 +373,11 @@ public class ExternSignServlet extends HttpServlet {  					throw new PdfAsWebException("Invalid connector bku is not supported");  				}  			} +			if (connector.equals("sl20")) { +				if(WebConfiguration.getSecurityLayer20URL() == null) { +					throw new PdfAsWebException("Invalid connector bku is not supported"); +				} +			}  			PdfAsHelper.setStatisticEvent(request, response, statisticEvent); diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/JSONAPIServlet.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/JSONAPIServlet.java index 0cee185a..13d874e8 100644 --- a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/JSONAPIServlet.java +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/JSONAPIServlet.java @@ -119,7 +119,9 @@ public class JSONAPIServlet extends HttpServlet {                  connectorEnum = PDFASSignParameters.Connector.MOBILEBKU;              } else if(PDFASSignParameters.Connector.ONLINEBKU.equalsName(connector)) {                  connectorEnum = PDFASSignParameters.Connector.ONLINEBKU; -            } +            } else if(PDFASSignParameters.Connector.SECLAYER20.equalsName(connector)) { +                connectorEnum = PDFASSignParameters.Connector.SECLAYER20; +            }               if(connectorEnum == null) {                  throw new ServletException( @@ -212,6 +214,13 @@ public class JSONAPIServlet extends HttpServlet {                                      "Invalid connector mobilebku is not supported");                          }                      } +                     +                    if (PDFASSignParameters.Connector.SECLAYER20.equals(connectorEnum)) { +                        if (WebConfiguration.getSecurityLayer20URL() == null) { +                            throw new PdfAsWebException( +                                    "Invalid connector mobilebku is not supported"); +                        } +                    }                      PdfAsHelper.startSignatureJson(request, response, getServletContext(), diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/SLDataURLServlet.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/SLDataURLServlet.java new file mode 100644 index 00000000..7ddf0a55 --- /dev/null +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/SLDataURLServlet.java @@ -0,0 +1,234 @@ +package at.gv.egiz.pdfas.web.servlets; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.annotation.MultipartConfig; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.jose4j.base64url.Base64Url; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; + +import at.gv.egiz.pdfas.lib.util.StreamUtils; +import at.gv.egiz.pdfas.web.config.WebConfiguration; +import at.gv.egiz.pdfas.web.helper.PdfAsHelper; +import at.gv.egiz.pdfas.web.sl20.JsonSecurityUtils; +import at.gv.egiz.pdfas.web.sl20.X509Utils; +import at.gv.egiz.sl20.data.VerificationResult; +import at.gv.egiz.sl20.exceptions.SL20Exception; +import at.gv.egiz.sl20.exceptions.SL20SecurityException; +import at.gv.egiz.sl20.exceptions.SLCommandoParserException; +import at.gv.egiz.sl20.utils.SL20Constants; +import at.gv.egiz.sl20.utils.SL20JSONExtractorUtils; + +@MultipartConfig +public class SLDataURLServlet extends HttpServlet { + +	private static final Logger logger = LoggerFactory +			.getLogger(SLDataURLServlet.class); +	 +	private static final long serialVersionUID = 1L; + +	/** +	 * @see HttpServlet#HttpServlet() +	 */ +	public SLDataURLServlet() { +		super(); +	} + +	/** +	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse +	 *      response) +	 */ +	protected void doGet(HttpServletRequest request, +			HttpServletResponse response) throws ServletException, IOException { +		this.process(request, response); +	} + +	/** +	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse +	 *      response) +	 */ +	protected void doPost(HttpServletRequest request, +			HttpServletResponse response) throws ServletException, IOException { +		this.process(request, response); +	} + +	protected void process(HttpServletRequest request, +			HttpServletResponse response) throws ServletException, IOException { +		 +		JsonObject sl20ReqObj = null;	 +		try { +			if(!PdfAsHelper.checkDataUrlAccess(request)) { +				throw new Exception("No valid dataURL access"); +			} +			 +			PdfAsHelper.setFromDataUrl(request); +						 +			String sl20Result = request.getParameter(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)) { +					logger.info("Use SIC Handy-Signature work-around!"); +					sl20Result = isReqInput.substring("slcommand=".length()); +					 +				} else {					 +					logger.info("NO SL2.0 commando or result FOUND."); +					throw new SL20Exception("sl20.04", null); +				} +				 +			} + +			logger.trace("Received SL2.0 result: " + sl20Result);		 +			 +			//parse SL2.0 command/result into JSON				 +			try { +				JsonParser jsonParser = new JsonParser(); +				JsonElement sl20Req = jsonParser.parse(Base64Url.decodeToUtf8String(sl20Result)); +				sl20ReqObj = sl20Req.getAsJsonObject(); +				 +			} catch (JsonSyntaxException e) { +				logger.warn("SL2.0 command or result is NOT valid JSON.", e); +				logger.debug("SL2.0 msg: " + sl20Result); +				throw new SL20Exception("sl20.02", e); +				 +			} +			 +			//extract transactionId +			String transactionId = SL20JSONExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_TRANSACTIONID, false); +			if (StringUtils.isNotEmpty(transactionId)) +					request.setAttribute(PdfAsHelper.PDF_SESSION_PREFIX + SL20Constants.SL20_TRANSACTIONID, transactionId); +			 +			 +			//validate reqId with inResponseTo  +			String sl20ReqId = (String) request.getSession(false).getAttribute(PdfAsHelper.PDF_SESSION_PREFIX + SL20Constants.SL20_REQID); +			String inRespTo = SL20JSONExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_INRESPTO, true); +			if (sl20ReqId == null || !sl20ReqId.equals(inRespTo)) { +				logger.info("SL20 'reqId': " + sl20ReqId + " does NOT match to 'inResponseTo':" + inRespTo); +				throw new SL20SecurityException("SL20 'reqId': " + sl20ReqId + " does NOT match to 'inResponseTo':" + inRespTo); +			} +			 +			JsonSecurityUtils joseTools = JsonSecurityUtils.getInstance(); +			if (!joseTools.isInitialized()) +				joseTools = null; +			 +			//validate signature +			VerificationResult payLoadContainer = SL20JSONExtractorUtils.extractSL20PayLoad(sl20ReqObj, joseTools,  +					WebConfiguration.isSL20SigningRequired()); +			 +			if ( (payLoadContainer.isValidSigned() == null || !payLoadContainer.isValidSigned())) { +				if (WebConfiguration.isSL20SigningRequired()) { +					logger.info("SL20 result from VDA was not valid signed"); +					throw new SL20SecurityException("Signature on SL20 result NOT valid."); +					 +				} else { +					logger.warn("SL20 result from VDA is NOT valid signed, but signatures-verification is DISABLED by configuration!"); +					 +				}				 +			} +						 +			//extract payloaf +			JsonObject payLoad = payLoadContainer.getPayload(); +			 +			//check response type +			if (SL20JSONExtractorUtils.getStringValue( +					payLoad, SL20Constants.SL20_COMMAND_CONTAINER_NAME, true) +						.equals(SL20Constants.SL20_COMMAND_IDENTIFIER_GETCERTIFICATE)) { +				logger.debug("Find " + SL20Constants.SL20_COMMAND_IDENTIFIER_GETCERTIFICATE + " result .... "); +								 +				JsonElement getCertificateResult = SL20JSONExtractorUtils.extractSL20Result( +						payLoad, joseTools,  +						WebConfiguration.isSL20EncryptionRequired()); + +				//extract certificates												 +				List<String> certsB64 = SL20JSONExtractorUtils.getListOfStringElements(getCertificateResult.getAsJsonObject(),  +						SL20Constants.SL20_COMMAND_PARAM_GETCERTIFICATE_RESULT_CERTIFICATE,  +						true); +				 +				if (certsB64.isEmpty()) { +					logger.warn("SL20 'getCertificate' result contains NO certificate"); +					throw new SLCommandoParserException(); +					 +				} else if (certsB64.size() == 1) { +					logger.debug("SL20 'getCertificate' result contains only one certificate"); +					PdfAsHelper.injectCertificate(request, response, Base64.getDecoder().decode(certsB64.get(0)), getServletContext());	 +					 +				} else { +					logger.debug("SL20 'getCertificate' result contains more than one certificate. Certificates must be sorted ... "); +					List<X509Certificate> certs = new ArrayList<X509Certificate>(); +					for (String certB64 : certsB64) +						certs.add(new iaik.x509.X509Certificate(Base64.getDecoder().decode(certB64))); +												 +					List<X509Certificate> sortedCerts = X509Utils.sortCertificates(certs); +					logger.debug("Sorting of certificate completed. Select end-user certificate ... "); +					PdfAsHelper.injectCertificate(request, response, Base64.getDecoder().decode(sortedCerts.get(0).getEncoded()), getServletContext()); +					 +				} +				 +			} else if (SL20JSONExtractorUtils.getStringValue( +					payLoad, SL20Constants.SL20_COMMAND_CONTAINER_NAME, true) +					.equals(SL20Constants.SL20_COMMAND_IDENTIFIER_CREATE_SIG_CADES)) { +				logger.debug("Find " + SL20Constants.SL20_COMMAND_IDENTIFIER_CREATE_SIG_CADES + " result .... "); +				 +				JsonElement getCertificateResult = SL20JSONExtractorUtils.extractSL20Result( +						payLoad, joseTools,  +						WebConfiguration.isSL20EncryptionRequired()); + +				//extract CAdES signature +				String cadesSigB64 = SL20JSONExtractorUtils.getStringValue( +						getCertificateResult.getAsJsonObject(),  +						SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_RESULT_SIGNATURE,  +						true); +				 +				if (StringUtils.isEmpty(cadesSigB64)) { +					logger.warn("SL20 'createCAdES' result contains NO signature"); +					throw new SLCommandoParserException(); +				} +				 +				PdfAsHelper.injectSignature(request, response, Base64.getDecoder().decode(cadesSigB64), getServletContext()); +				 +			} else { +				logger.info("SL20 response is NOT a " + SL20Constants.SL20_COMMAND_IDENTIFIER_QUALIFIEDEID + " result"); +				throw new SLCommandoParserException(); +				 +			}	 +			 +		} catch (Exception e) { +			logger.warn("Error in DataURL Servlet. " , e); +			PdfAsHelper.setSessionException(request, response, e.getMessage(), +					e); +			 +			if (PdfAsHelper.getFromDataUrl(request)) { +				String errorUrl = PdfAsHelper.generateErrorURL(request, response); +				try { +					String transactionId = null; +					if (sl20ReqObj != null) +						transactionId = SL20JSONExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_TRANSACTIONID, false); +					 +					PdfAsHelper.buildSL20RedirectResponse(request, response, transactionId, errorUrl); +					 +				} catch (SL20Exception e1) { +					logger.error("SL20 error-handling FAILED", e); +					response.sendError(500, "Internal Server Error."); +					 +				} +				 +			} else			 +				PdfAsHelper.gotoError(getServletContext(), request, response); +		} +	} +} diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/UIEntryPointServlet.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/UIEntryPointServlet.java index e8ac3658..73f8299c 100644 --- a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/UIEntryPointServlet.java +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/UIEntryPointServlet.java @@ -131,7 +131,8 @@ public class UIEntryPointServlet extends HttpServlet {  			// IPlainSigner signer;  			if (connector.equals(Connector.BKU)  					|| connector.equals(Connector.ONLINEBKU) -					|| connector.equals(Connector.MOBILEBKU)) { +					|| connector.equals(Connector.MOBILEBKU) +					|| connector.equals(Connector.SECLAYER20)) {  				// start asynchronous signature creation  				if (connector.equals(Connector.BKU)) { @@ -154,6 +155,14 @@ public class UIEntryPointServlet extends HttpServlet {  								"Invalid connector mobilebku is not supported");  					}  				} +				 +				if (connector.equals(Connector.SECLAYER20)) { +					if (WebConfiguration.getSecurityLayer20URL() == null) { +						throw new PdfAsWebException( +								"Invalid connector mobilebku is not supported"); +					} +				} +				  				Map<String, String> map = null;  				if (pdfAsRequest.getParameters().getPreprocessor() != null) {  					map = pdfAsRequest.getParameters().getPreprocessor() diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/JsonSecurityUtils.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/JsonSecurityUtils.java new file mode 100644 index 00000000..141808de --- /dev/null +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/JsonSecurityUtils.java @@ -0,0 +1,381 @@ +package at.gv.egiz.pdfas.web.sl20; + +import java.io.IOException; +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.Base64; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.util.encoders.Base64Encoder; +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 com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; + +import at.gv.egiz.pdfas.web.config.WebConfiguration; +import at.gv.egiz.sl20.data.VerificationResult; +import at.gv.egiz.sl20.exceptions.SL20Exception; +import at.gv.egiz.sl20.exceptions.SL20SecurityException; +import at.gv.egiz.sl20.exceptions.SLCommandoBuildException; +import at.gv.egiz.sl20.exceptions.SLCommandoParserException; +import at.gv.egiz.sl20.utils.IJOSETools; +import at.gv.egiz.sl20.utils.SL20Constants; + +public class JsonSecurityUtils implements IJOSETools{ + +	private static final Logger logger = LoggerFactory.getLogger(JsonSecurityUtils.class); +	 +	private Key signPrivKey = null; +	private X509Certificate[] signCertChain = null;	 +	private Key encPrivKey = null; +	private X509Certificate[] encCertChain = null;	 +	private List<X509Certificate> trustedCerts = new ArrayList<X509Certificate>(); +	 +	private boolean isInitialized = false; +	 +	private static JsonSecurityUtils instance = null; +		 +	public static JsonSecurityUtils getInstance() { +		if (instance == null) { +			instance = new JsonSecurityUtils(); +			instance.initalize(); +		} +		 +		return instance; +	} +	 +	private JsonSecurityUtils() { +		 +		 +	} +	 +	protected synchronized void initalize() { +		logger.info("Initialize SL2.0 authentication security constrains ... "); +		try { +			String keyStorePath = getKeyStoreFilePath(); +			 +			if (StringUtils.isNotEmpty(keyStorePath)) { +				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 +						logger.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 +								logger.warn("NO X509 certificate for encryption: " + certChainEncryption[i].getType()); +						}									 +					} else +						logger.info("No encryption key for SL2.0 found. End-to-End encryption is not used."); +					 +				} catch (Exception e) { +					logger.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(); +					logger.trace("Process TrustStoreEntry: " + el); +					if (keyStore.isCertificateEntry(el)) { +						Certificate cert = keyStore.getCertificate(el);  +						if (cert != null && cert instanceof X509Certificate) +							trustedCerts.add((X509Certificate) cert); +						else +							logger.info("Can not process entry: " + el + ". Reason: " + cert.toString()); +						 +					} +				} +	 +				//some short validation +				if (signPrivKey == null || !(signPrivKey instanceof PrivateKey)) { +					logger.info("Can NOT open privateKey for SL2.0 signing. KeyStore=" + getKeyStoreFilePath()); +					throw new SL20Exception("sl20.03"); +					 +				} +				 +				if (signCertChain == null || signCertChain.length == 0) { +					logger.info("NO certificate for SL2.0 signing. KeyStore=" + getKeyStoreFilePath()); +					throw new SL20Exception("sl20.03"); +					 +				} +				 +				isInitialized = true; +				logger.info("SL2.0 authentication security constrains initialized."); +				 +			} else +				logger.info("SL2.0 security constrains not configurated!"); +			 +		} catch ( Exception e) { +			logger.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) { +			logger.warn("Can NOT sign SL2.0 command.", e); +			throw new SLCommandoBuildException(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) { +				logger.debug("Found x509 certificate in JOSE header ... "); +				logger.trace("Sorting received X509 certificates ... "); +				List<X509Certificate> sortedX5cCerts  = X509Utils.sortCertificates(x5cCerts); +				 +				if (trustedCerts.contains(sortedX5cCerts.get(0))) { +					selectedKey = sortedX5cCerts.get(0).getPublicKey(); +					 +				} else { +					logger.info("Can NOT find JOSE certificate in truststore."); +					logger.debug("JOSE certificate: " + sortedX5cCerts.get(0).toString()); +					try {						 +						logger.debug("Cert: " + Base64.getEncoder().encodeToString(sortedX5cCerts.get(0).getEncoded())); +						 +					} catch (CertificateEncodingException e) { +						e.printStackTrace(); +						 +					} +					 +				} +				 +			} else if (StringUtils.isNotEmpty(x5t256)) { +				logger.debug("Found x5t256 fingerprint in JOSE header .... "); +				X509VerificationKeyResolver x509VerificationKeyResolver = new X509VerificationKeyResolver(trustedCerts); +				selectedKey = x509VerificationKeyResolver.resolveKey(jws, Collections.<JsonWebStructure>emptyList()); +				 +			} else { +				logger.info("Signed SL2.0 response contains NO signature certificate or NO certificate fingerprint"); +				throw new SLCommandoParserException(); +				 +			} +					 +			if (selectedKey == null) { +				logger.info("Can NOT select verification key for JWS. Signature verification FAILED."); +				throw new SLCommandoParserException(); +				 +			} +			 +			//set verification key +			jws.setKey(selectedKey); +			 +			//validate signature +			boolean valid = jws.verifySignature(); +			if (!valid) { +				logger.info("JWS signature invalide. Stopping authentication process ..."); +				logger.debug("Received JWS msg: " + serializedContent); +				throw new SL20SecurityException(); +				 +			} +				 +			 +			//load payLoad +			logger.debug("SL2.0 commando signature validation sucessfull"); +			JsonElement sl20Req = new JsonParser().parse(jws.getPayload()); +			 +			return new VerificationResult(sl20Req.getAsJsonObject(), null, valid) ; +			 +		} catch (JoseException e) { +			logger.warn("SL2.0 commando signature validation FAILED", e); +			throw new SL20SecurityException(e); +			 +		} +					 +	} +	 + +	@Override +	public JsonElement 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) { +				logger.debug("Found x509 certificate in JOSE header ... "); +				logger.trace("Sorting received X509 certificates ... "); +				List<X509Certificate> sortedX5cCerts  = X509Utils.sortCertificates(x5cCerts); +				 +				if (!sortedX5cCerts.get(0).equals(encCertChain[0])) { +					logger.info("Certificate from JOSE header does NOT match encryption certificate"); +					logger.debug("JOSE certificate: " + sortedX5cCerts.get(0).toString()); +					 +					try { +						logger.debug("Cert: " + Base64.getEncoder().encodeToString(sortedX5cCerts.get(0).getEncoded())); +					} catch (CertificateEncodingException e) { +						e.printStackTrace(); +					} +					throw new SL20Exception("sl20.05"); +				} +				 +			} else if (StringUtils.isNotEmpty(x5t256)) { +				logger.debug("Found x5t256 fingerprint in JOSE header .... "); +				String certFingerPrint = X509Util.x5tS256(encCertChain[0]); +				if (!certFingerPrint.equals(x5t256)) { +					logger.info("X5t256 from JOSE header does NOT match encryption certificate"); +					logger.debug("X5t256 from JOSE header: " + x5t256 + " Encrytption cert: " + certFingerPrint); +					throw new SL20Exception("sl20.05"); +					 +				} +				 +			} else { +				logger.info("Signed SL2.0 response contains NO signature certificate or NO certificate fingerprint"); +				throw new SLCommandoParserException(); +				 +			} +						 +			//set key +			receiverJwe.setKey(encPrivKey); +			 +						 +			//decrypt payload			 +			return new JsonParser().parse(receiverJwe.getPlaintextString()); +			 +		} catch (JoseException e) { +			logger.warn("SL2.0 result decryption FAILED", e); +			throw new SL20SecurityException(e); +			 +		} catch ( JsonSyntaxException e) { +			logger.warn("Decrypted SL2.0 result is NOT a valid JSON.", e); +			throw new SLCommandoParserException(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; +	} +	 +	@Override +	public boolean isInitialized() { +		return isInitialized; +		 +	} +	 +	private String getKeyStoreFilePath() { +		return WebConfiguration.getSL20KeyStorePath(); +	} +	 +	private String getKeyStorePassword() { +		return WebConfiguration.getSL20KeyStorePassword(); + +	} +	 +	private String getSigningKeyAlias() { +		return WebConfiguration.getSL20KeySigningAlias(); +	} +	 +	private String getSigningKeyPassword() { +		return WebConfiguration.getSL20KeySigningPassword(); +		 +	} + +	private String getEncryptionKeyAlias() { +		return WebConfiguration.getSL20KeyEncryptionAlias(); +	} +	 +	private String getEncryptionKeyPassword() { +		return WebConfiguration.getSL20KeyEncryptionPassword(); +	} +	 +} diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/KeyStoreUtils.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/KeyStoreUtils.java new file mode 100644 index 00000000..c7472a22 --- /dev/null +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/KeyStoreUtils.java @@ -0,0 +1,222 @@ +/* + * Copyright 2003 Federal Chancellery Austria + * MOA-ID has been developed in a cooperation between BRZ, the Federal + * Chancellery Austria - ICT staff unit, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * http://www.osor.eu/eupl/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + + +package at.gv.egiz.pdfas.web.sl20; + +import iaik.x509.X509Certificate; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.cert.Certificate; + +/** + * Utility for creating and loading key stores. + *  + * @author Paul Ivancsics + * @version $Id$ + */ +public class KeyStoreUtils { +	 +	/** +	 * JAVA KeyStore +	 */ +	private static final String KEYSTORE_TYPE_JKS = "JKS"; +	 +	/** +	 * PKCS12 KeyStore +	 */ +	private static final String KEYSTORE_TYPE_PKCS12 = "PKCS12"; +	 +	 + +  /** +   * Loads a key store from file. +   *  +   * @param keystoreType key store type +   * @param urlString URL of key store +   * @param password password protecting the key store +   * @return key store loaded +   * @throws IOException thrown while reading the key store from file +   * @throws GeneralSecurityException thrown while creating the key store +   */ +  public static KeyStore loadKeyStore( +    String keystoreType, +    String urlString, +    String password) +    throws IOException, GeneralSecurityException { + +    URL keystoreURL = new URL(urlString); +    InputStream in = keystoreURL.openStream(); +    return loadKeyStore(keystoreType, in, password); +  } +  /** +   * Loads a key store from an <code>InputStream</code>, and +   * closes the <code>InputStream</code>. +   *  +   * @param keystoreType key store type +   * @param in input stream +   * @param password password protecting the key store +   * @return key store loaded +   * @throws IOException thrown while reading the key store from the stream +   * @throws GeneralSecurityException thrown while creating the key store +   */ +  public static KeyStore loadKeyStore( +    String keystoreType, +    InputStream in, +    String password) +    throws IOException, GeneralSecurityException { + +    char[] chPassword = null; +    if (password != null) +      chPassword = password.toCharArray(); +    KeyStore ks = KeyStore.getInstance(keystoreType); +    ks.load(in, chPassword); +    in.close(); +    return ks; +  } +  /** +   * Creates a key store from X509 certificate files, aliasing them with +   * the index in the <code>String[]</code>, starting with <code>"0"</code>. +   *  +   * @param keyStoreType key store type +   * @param certFilenames certificate filenames +   * @return key store created +   * @throws IOException thrown while reading the certificates from file +   * @throws GeneralSecurityException thrown while creating the key store +   */ +  public static KeyStore createKeyStore( +    String keyStoreType, +    String[] certFilenames) +    throws IOException, GeneralSecurityException { + +    KeyStore ks = KeyStore.getInstance(keyStoreType); +    ks.load(null, null); +    for (int i = 0; i < certFilenames.length; i++) { +      Certificate cert = loadCertificate(certFilenames[i]); +      ks.setCertificateEntry("" + i, cert); +    } +    return ks; +  } +//  /** +//   * Creates a key store from a directory containg X509 certificate files,  +//   * aliasing them with the index in the <code>String[]</code>, starting with <code>"0"</code>. +//   * All the files in the directory are considered to be certificates. +//   *  +//   * @param keyStoreType key store type +//   * @param certDirURLString file URL of directory containing certificate filenames +//   * @return key store created +//   * @throws IOException thrown while reading the certificates from file +//   * @throws GeneralSecurityException thrown while creating the key store +//   */ +//  public static KeyStore createKeyStoreFromCertificateDirectory( +//    String keyStoreType, +//    String certDirURLString) +//    throws IOException, GeneralSecurityException { +// +//    URL certDirURL = new URL(certDirURLString); +//    String certDirname = certDirURL.getFile(); +//    File certDir = new File(certDirname); +//    String[] certFilenames = certDir.list(); +//    String separator = +//      (certDirname.endsWith(File.separator) ? "" : File.separator); +//    for (int i = 0; i < certFilenames.length; i++) { +//      certFilenames[i] = certDirname + separator + certFilenames[i]; +//    } +//    return createKeyStore(keyStoreType, certFilenames); +//  } + +  /** +   * Loads an X509 certificate from file. +   * @param certFilename filename +   * @return the certificate loaded +   * @throws IOException thrown while reading the certificate from file +   * @throws GeneralSecurityException thrown while creating the certificate +   */ +  private static Certificate loadCertificate(String certFilename) +    throws IOException, GeneralSecurityException { + +    FileInputStream in = new FileInputStream(certFilename); +    Certificate cert = new X509Certificate(in); +    in.close(); +    return cert; +  } +   +  +	/** +	 * Loads a keyStore without knowing the keyStore type +	 * @param keyStorePath URL to the keyStore +	 * @param password Password protecting the keyStore +	 * @return keyStore loaded +	 * @throws KeyStoreException thrown if keyStore cannot be loaded +	 * @throws FileNotFoundException  +	 * @throws IOException  +	 */ +  public static KeyStore loadKeyStore(String keyStorePath, String password) throws KeyStoreException, IOException{ +		 +		//InputStream is = new FileInputStream(keyStorePath); +	  	URL keystoreURL = new URL(keyStorePath); +	    InputStream in = keystoreURL.openStream(); +		InputStream isBuffered = new BufferedInputStream(in);				 +		return loadKeyStore(isBuffered, password); +		 +	} +	 +	/** +	 * Loads a keyStore without knowing the keyStore type +	 * @param in input stream +	 * @param password Password protecting the keyStore +	 * @return keyStore loaded +	 * @throws KeyStoreException thrown if keyStore cannot be loaded +	 * @throws FileNotFoundException  +	 * @throws IOException  +	 */ +public static KeyStore loadKeyStore(InputStream is, String password) throws KeyStoreException, IOException{		 +		is.mark(1024*1024); +		KeyStore ks = null; +		try { +			try {				 +				ks = loadKeyStore(KEYSTORE_TYPE_PKCS12, is, password); +			} catch (IOException e2) { +				is.reset();				 +				ks = loadKeyStore(KEYSTORE_TYPE_JKS, is, password); +			} +		} catch(Exception e) {			 +			e.printStackTrace(); +			//throw new KeyStoreException(e); +		} +		return ks;	 +						 +	} +   +	 + + +} diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/SL20HttpBindingUtils.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/SL20HttpBindingUtils.java new file mode 100644 index 00000000..f5d6ff55 --- /dev/null +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/SL20HttpBindingUtils.java @@ -0,0 +1,47 @@ +package at.gv.egiz.pdfas.web.sl20; + +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.LoggerFactory; + +import com.google.gson.JsonObject; + +import at.gv.egiz.sl20.utils.SL20Constants; + +public class SL20HttpBindingUtils { +	private static final org.slf4j.Logger log = LoggerFactory.getLogger(SL20HttpBindingUtils.class); +	 +	public static void writeIntoResponse(HttpServletRequest request, HttpServletResponse response, JsonObject 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/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/X509Utils.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/X509Utils.java new file mode 100644 index 00000000..391b8271 --- /dev/null +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/X509Utils.java @@ -0,0 +1,62 @@ +package at.gv.egiz.pdfas.web.sl20; + +import java.security.cert.X509Certificate; +import java.util.List; + +import javax.security.auth.x500.X500Principal; + +public class X509Utils { + +	  /** +	   * Sorts the Certificate Chain by IssuerDN and SubjectDN. The [0]-Element should be the Hostname, +	   * the last Element should be the Root Certificate. +	   *  +	   * @param certs +	   *          The first element must be the correct one. +	   * @return sorted Certificate Chain +	   */ +	  public static List<X509Certificate> sortCertificates( +		      List<X509Certificate> certs) +		  { +		    int length = certs.size(); +		    if (certs.size() <= 1) +		    { +		      return certs; +		    } + +		    for (X509Certificate cert : certs) +		    { +		      if (cert == null) +		      { +		        throw new NullPointerException(); +		      } +		    } + +		    for (int i = 0; i < length; i++) +		    { +		      boolean found = false; +		      X500Principal issuer = certs.get(i).getIssuerX500Principal(); +		      for (int j = i + 1; j < length; j++) +		      { +		        X500Principal subject = certs.get(j).getSubjectX500Principal(); +		        if (issuer.equals(subject)) +		        { +		          // sorting necessary? +		          if (i + 1 != j) +		          { +		            X509Certificate tmp = certs.get(i + 1); +		            certs.set(i + 1, certs.get(j)); +		            certs.set(j, tmp); +		          } +		          found = true; +		        } +		      } +		      if (!found) +		      { +		        break; +		      } +		    } + +		    return certs; +		} +} diff --git a/pdf-as-web/src/main/webapp/WEB-INF/web.xml b/pdf-as-web/src/main/webapp/WEB-INF/web.xml index a7bb5f74..9b69c983 100644 --- a/pdf-as-web/src/main/webapp/WEB-INF/web.xml +++ b/pdf-as-web/src/main/webapp/WEB-INF/web.xml @@ -85,6 +85,12 @@  		<description></description>  		<servlet-class>at.gv.egiz.pdfas.web.servlets.DataURLServlet</servlet-class>  	</servlet> + 	<servlet> +		<servlet-name>SLDataURLServlet</servlet-name> +		<display-name>SLDataURLServlet</display-name> +		<description></description> +		<servlet-class>at.gv.egiz.pdfas.web.servlets.SLDataURLServlet</servlet-class> +	</servlet>  	<servlet>  		<servlet-name>VisBlockServlet</servlet-name>  		<display-name>VisBlockServlet</display-name> @@ -185,6 +191,10 @@  		<url-pattern>/DataURL</url-pattern>  	</servlet-mapping>  	<servlet-mapping> +		<servlet-name>SLDataURLServlet</servlet-name> +		<url-pattern>/DataURLSL20</url-pattern> +	</servlet-mapping> +	<servlet-mapping>  		<servlet-name>VerifyServlet</servlet-name>  		<url-pattern>/Verify</url-pattern>  	</servlet-mapping> diff --git a/pdf-as-web/src/main/webapp/index.jsp b/pdf-as-web/src/main/webapp/index.jsp index de41028b..c07b2cc0 100644 --- a/pdf-as-web/src/main/webapp/index.jsp +++ b/pdf-as-web/src/main/webapp/index.jsp @@ -37,6 +37,17 @@  		<%  			}  		%> +		 +		<% +			if (WebConfiguration.getSecurityLayer20URL() != null) { +		%> +		<button type="submit" value="sl20" name="connector" id="sl20backend">SL2.0 Interface</button> +		<label for="placeholder_web_id">Placeholder ID</label> +          <input type="text" id="placeholder_web_id" name="placeholder_web_id"> +		<% +			} +		%> +		  		<%  			if (WebConfiguration.getKeystoreDefaultEnabled()) {  		%> | 
