diff options
author | emusic <emina.music@egiz.gv.at> | 2018-11-22 13:14:28 +0100 |
---|---|---|
committer | emusic <emina.music@egiz.gv.at> | 2018-11-22 13:14:28 +0100 |
commit | f76f559c8c1437a152a4f51b441e4cebe25430f1 (patch) | |
tree | 8930f89863cdc7e564f6a97ac777c133384a8730 | |
parent | cd6ff625d48a63e024b05ed1a253be3551e41599 (diff) | |
parent | c87c82009fb07e285d6c9acfd4686efc2cbf21c9 (diff) | |
download | pdf-as-4-f76f559c8c1437a152a4f51b441e4cebe25430f1.tar.gz pdf-as-4-f76f559c8c1437a152a4f51b441e4cebe25430f1.tar.bz2 pdf-as-4-f76f559c8c1437a152a4f51b441e4cebe25430f1.zip |
Merge branch 'SL20_development'
# Conflicts:
# build.gradle
# pdf-as-cli/src/main/java/at/gv/egiz/pdfas/cli/Main.java
# pdf-as-lib/src/configuration/cfg/advancedconfig.properties
# pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/api/IConfigurationConstants.java
# pdf-as-lib/src/main/resources/config/config.zip
# pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/config/WebConfiguration.java
# pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/PDFData.java
39 files changed, 3381 insertions, 87 deletions
@@ -47,4 +47,6 @@ local.properties # PDT-specific .buildpath -/PDF-AS/.nb-gradle/
\ No newline at end of file +/PDF-AS/.nb-gradle/ + +.idea/*
\ No newline at end of file diff --git a/.idea/libraries/Gradle__com_google_code_gson_gson_2_8_5.xml b/.idea/libraries/Gradle__com_google_code_gson_gson_2_8_5.xml new file mode 100644 index 00000000..c3e23cab --- /dev/null +++ b/.idea/libraries/Gradle__com_google_code_gson_gson_2_8_5.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="Gradle: com.google.code.gson:gson:2.8.5"> + <CLASSES> + <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.8.5/f645ed69d595b24d4cf8b3fbb64cc505bede8829/gson-2.8.5.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.8.5/c5b4c491aecb72e7c32a78da0b5c6b9cda8dee0f/gson-2.8.5-sources.jar!/" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/.idea/libraries/Gradle__org_bitbucket_b_c_jose4j_0_6_3.xml b/.idea/libraries/Gradle__org_bitbucket_b_c_jose4j_0_6_3.xml new file mode 100644 index 00000000..9da71daf --- /dev/null +++ b/.idea/libraries/Gradle__org_bitbucket_b_c_jose4j_0_6_3.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="Gradle: org.bitbucket.b_c:jose4j:0.6.3"> + <CLASSES> + <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.bitbucket.b_c/jose4j/0.6.3/5bf092aa7be6fe8894d11f8d2040a2b3b401a14/jose4j-0.6.3.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.bitbucket.b_c/jose4j/0.6.3/2ed192025755ff7d79193325ca1793ba22dc10b0/jose4j-0.6.3-sources.jar!/" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/.idea/libraries/Gradle__org_slf4j_slf4j_api_1_7_18.xml b/.idea/libraries/Gradle__org_slf4j_slf4j_api_1_7_18.xml deleted file mode 100644 index 9214ea8c..00000000 --- a/.idea/libraries/Gradle__org_slf4j_slf4j_api_1_7_18.xml +++ /dev/null @@ -1,11 +0,0 @@ -<component name="libraryTable"> - <library name="Gradle: org.slf4j:slf4j-api:1.7.18"> - <CLASSES> - <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.18/b631d286463ced7cc42ee2171fe3beaed2836823/slf4j-api-1.7.18.jar!/" /> - </CLASSES> - <JAVADOC /> - <SOURCES> - <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.18/a84644a9e9c6ed158b0754aa4af53dc3b7844524/slf4j-api-1.7.18-sources.jar!/" /> - </SOURCES> - </library> -</component>
\ No newline at end of file diff --git a/.idea/libraries/Gradle__org_slf4j_slf4j_api_1_7_21.xml b/.idea/libraries/Gradle__org_slf4j_slf4j_api_1_7_21.xml new file mode 100644 index 00000000..12bea8a3 --- /dev/null +++ b/.idea/libraries/Gradle__org_slf4j_slf4j_api_1_7_21.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="Gradle: org.slf4j:slf4j-api:1.7.21"> + <CLASSES> + <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.21/139535a69a4239db087de9bab0bee568bf8e0b70/slf4j-api-1.7.21.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.21/f285ac123f201fb4b028bac556928d7cf527ef48/slf4j-api-1.7.21-sources.jar!/" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/pdf-as-cli/src/main/java/at/gv/egiz/pdfas/cli/Main.java b/pdf-as-cli/src/main/java/at/gv/egiz/pdfas/cli/Main.java index cf764d47..f384a140 100644 --- a/pdf-as-cli/src/main/java/at/gv/egiz/pdfas/cli/Main.java +++ b/pdf-as-cli/src/main/java/at/gv/egiz/pdfas/cli/Main.java @@ -23,6 +23,27 @@ ******************************************************************************/ package at.gv.egiz.pdfas.cli; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.UUID; + +import javax.activation.DataSource; + +import at.gv.egiz.pdfas.lib.api.*; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.GnuParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.slf4j.LoggerFactory; +import org.slf4j.Logger; + import at.gv.egiz.pdfas.common.exceptions.PDFASError; import at.gv.egiz.pdfas.common.utils.StreamUtils; import at.gv.egiz.pdfas.lib.api.ByteArrayDataSource; 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/configuration/cfg/advancedconfig.properties b/pdf-as-lib/src/configuration/cfg/advancedconfig.properties index c004757c..e2d39c1c 100644 --- a/pdf-as-lib/src/configuration/cfg/advancedconfig.properties +++ b/pdf-as-lib/src/configuration/cfg/advancedconfig.properties @@ -105,6 +105,3 @@ default.verifier.01=at.gv.egiz.pdfas.sigs.pades.PAdESVerifier #sigblock.placement.bgcolor.detection.enabled=true
#sigblock.placement.debug.file=/home/user/temp/debugImg.png
#runtime.backend=PDFBOX_2_BACKEND
-
-
-
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 d1b947b0..a9b8bb2f 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 @@ -88,6 +88,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..3088a564 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/SL20Connector.java @@ -0,0 +1,100 @@ +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 "); + + JsonObject sl20Resp = SL20JSONExtractorUtils.getSL20ContainerFromResponse(httpResp); + log.trace("SL20 command: " + sl20Resp.toString()); + return sl20Resp; + + } 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..fdefa1d9 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/SL20Constants.java @@ -0,0 +1,235 @@ +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_CONTENTURL = "contentUrl"; + public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENTMODE = "contentMode"; + 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"; + + public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENTMODE_DETACHED = "detached"; + public static final String SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENTMODE_ENVELOPING = "enveloping"; + + //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..7cbb7800 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/SL20JSONBuilderUtils.java @@ -0,0 +1,636 @@ +package at.gv.egiz.sl20.utils; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +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 contentUrl, String contentMode, String mimeType, boolean padesCompatiblem, List<JsonElement> 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); + + if (content != null && contentUrl != null) { + log.warn(SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENT + " and " + + SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENTURL + " can not SET TWICE"); + throw new SLCommandoBuildException(); + + } + + if (content != null) + addSingleByteElement(params, SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENT, content, true); + + else if (contentUrl != null ) + addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENTURL, contentUrl, true); + + else { + log.warn(SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENT + " and " + + SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENTURL + " is NULL"); + throw new SLCommandoBuildException(); + + } + + addSingleStringElement(params, SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENTMODE, contentMode, 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); + addArrayOfElements(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 addArrayOfElements(JsonObject parent, String keyId, List<JsonElement> values) throws SLCommandoBuildException { + validateParentAndKey(parent, keyId); + if (values != null) { + JsonArray callReqParamsArray = new JsonArray(); + parent.add(keyId, callReqParamsArray ); + for(JsonElement el : values) + callReqParamsArray.add(el); + + } + + } + + 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..5fbce83b --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/sl20/utils/SL20JSONExtractorUtils.java @@ -0,0 +1,416 @@ +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/configuration/pdf-as-web.properties b/pdf-as-web/src/main/configuration/pdf-as-web.properties index 10ef26a1..4cc59a47 100644 --- a/pdf-as-web/src/main/configuration/pdf-as-web.properties +++ b/pdf-as-web/src/main/configuration/pdf-as-web.properties @@ -70,3 +70,19 @@ request.store=at.gv.egiz.pdfas.web.store.InMemoryRequestStore #hibernate.props.hibernate.show_sql=true #hibernate.props.hibernate.hbm2ddl.auto=update +#Security layer 2.0 config + +#sl20.sign.enabled=true +#sl20.mobile.url=http://localhost:7080/vda/services/getCertificate +sl20.keystore.file= +sl20.keystore.pass= +sl20.keystore.sign.key.alias= +sl20.keystore.sign.key.pass= +sl20.keystore.enc.key.alias= +sl20.keystore.enc.key.pass= +sl20.debug.validation.disable=true +sl20.debug.signed.result.enabled=false +sl20.debug.signed.result.required=false +sl20.debug.encryption.enabled=false +sl20.debug.encryption.required=false +sl20.transfermode.filesize=20000000
\ No newline at end of file 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 13ac5ccd..1fffb17d 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) { @@ -587,4 +631,65 @@ public class WebConfiguration implements IConfigurationConstants { return ivalue; } + 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..8aeda417 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,14 +56,20 @@ 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.JsonArray; +import com.google.gson.JsonElement; +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; import at.gv.egiz.pdfas.api.ws.PDFASVerificationResponse; import at.gv.egiz.pdfas.common.exceptions.PDFASError; +import at.gv.egiz.pdfas.common.utils.PDFUtils; import at.gv.egiz.pdfas.lib.api.ByteArrayDataSource; import at.gv.egiz.pdfas.lib.api.Configuration; import at.gv.egiz.pdfas.lib.api.IConfigurationConstants; @@ -77,6 +88,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 +97,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 { @@ -104,7 +125,9 @@ public class PdfAsHelper { private static final String PDF_ERROR_PAGE = "/ErrorPage"; private static final String PDF_PROVIDE_PAGE = "/ProvidePDF"; private static final String PDF_PDFDATA_PAGE = "/PDFData"; + private static final String PDF_PDFDATAURL_PAGE = "/PDFURLData"; 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 +141,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 +731,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 +824,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 +875,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 +934,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 +946,7 @@ public class PdfAsHelper { + session.getId()); } - statusRequest.setCertificate(getCertificate(infoboxReadResponseType)); + statusRequest.setCertificate(certificate); statusRequest = pdfAs.process(statusRequest); session.setAttribute(PDF_STATUS, statusRequest); @@ -919,7 +955,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 +969,7 @@ public class PdfAsHelper { + session.getId()); } - statusRequest.setSigature(createCMSSignatureResponseType - .getCMSSignature()); + statusRequest.setSigature(cmsSginature); statusRequest = pdfAs.process(statusRequest); session.setAttribute(PDF_STATUS, statusRequest); @@ -996,21 +1031,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 +1070,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 +1083,255 @@ 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 (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!"); + + } else if (slConnector instanceof SL20Connector) { + //convert byte range + + int[] exclude_range = PDFUtils.buildExcludeRange(statusRequest.getSignatureDataByteRange()); + logger.info("Exclude Byte Range: " + exclude_range[0] + " " + exclude_range[1]); + + List<JsonElement> byteRanges = new ArrayList<JsonElement>(); + if (statusRequest.getSignatureDataByteRange().length % 2 != 0) { + logger.warn("ByteRange is not a set of pairs. Something is maybe suspect"); + } - VerifyResult verifyResult = verResults.get(0); - - verResponse.setCertificateCode(verifyResult - .getCertificateCheck().getCode()); - verResponse.setValueCode(verifyResult.getValueCheckCode() - .getCode()); + + for (int i=0; i<exclude_range.length/2; i++) { + JsonArray el = new JsonArray(); + el.add(exclude_range[2*i]); + el.add(exclude_range[2*i + 1]); + byteRanges.add(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(); + + byte[] data = PDFUtils.blackOutSignature(statusRequest.getSignatureData(), + statusRequest.getSignatureDataByteRange()); + JsonObject createCAdESSigParams; + if(data.length>20000000) { + createCAdESSigParams = + SL20JSONBuilderUtils.createCreateCAdESCommandParameters( + pack.getRequestType().getKeyboxIdentifier(), + null, + generateNSPdfURL(request, response), + SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENTMODE_DETACHED, + pack.getRequestType().getDataObject().getMetaInfo().getMimeType(), + padesCompatibel, + byteRanges, + SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CADESLEVEL_BASIC, + generateDataURLSL20(request, response), + x5cEnc); + } else + { + createCAdESSigParams = + SL20JSONBuilderUtils.createCreateCAdESCommandParameters( + pack.getRequestType().getKeyboxIdentifier(), + data, + null, + SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CONTENTMODE_DETACHED, + 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(reqId, 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); - PdfAsHelper.setPDFASVerificationResponse(request, verResponse); - PdfAsHelper.setSignedPdf(request, response, baos.toByteArray()); - PdfAsHelper.gotoProvidePdf(context, request, response); + 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); + String signerCert = Base64.encodeBase64String(result + .getSignerCertificate().getEncoded()); - } else { - throw new PdfAsWebException("Invalid state!"); - } + PdfAsHelper.setSignerCertificate(request, signerCert); + + if (slConnector instanceof BKUSLConnector) { + PdfAsHelper.gotoProvidePdf(context, request, response); + + } 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 +1572,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); @@ -1358,6 +1597,12 @@ public class PdfAsHelper { return generateURL(request, response, PDF_PDFDATA_PAGE); } + public static String generateNSPdfURL(HttpServletRequest request, + HttpServletResponse response) { + return generateURL(request, response, PDF_PDFDATAURL_PAGE); + } + + public static String generateUserEntryURL(String storeId) { String publicURL = WebConfiguration.getPublicURL(); if (publicURL == null) { @@ -1385,6 +1630,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 +1789,66 @@ 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); + + logger.trace("SL2.0 command: " + respContainer.toString()); + + //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/PDFData.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/PDFData.java index 2cf2f8ff..ec7e7f2b 100644 --- a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/PDFData.java +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/PDFData.java @@ -23,8 +23,18 @@ ******************************************************************************/ package at.gv.egiz.pdfas.web.servlets; -import at.gv.egiz.pdfas.api.ws.PDFASVerificationResponse; +import java.io.*; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import at.gv.egiz.pdfas.web.config.WebConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.pdfas.api.ws.PDFASVerificationResponse; import at.gv.egiz.pdfas.web.helper.PdfAsHelper; import at.gv.egiz.pdfas.web.helper.PdfAsParameterExtractor; import at.gv.egiz.pdfas.web.stats.StatisticEvent; diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/PDFURLData.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/PDFURLData.java new file mode 100644 index 00000000..d4112cad --- /dev/null +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/PDFURLData.java @@ -0,0 +1,87 @@ +package at.gv.egiz.pdfas.web.servlets; + +import at.gv.egiz.pdfas.common.exceptions.PDFIOException; +import at.gv.egiz.pdfas.common.utils.PDFUtils; +import at.gv.egiz.pdfas.lib.api.StatusRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.io.OutputStream; + +public class PDFURLData extends HttpServlet { + + private static final long serialVersionUID = 1L; + private static final String PDF_STATUS = "PDF_STATUS"; + + + private static final Logger logger = LoggerFactory.getLogger(PDFData.class); + + /** + * @see HttpServlet#HttpServlet() + */ + public PDFURLData() { + super(); + } + + /** + * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse + * response) + */ + protected void doGet(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + try { + this.process(request, response); + } catch (PDFIOException e) { + response.sendError(500, "file cannot be transfered"); + } + } + + /** + * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse + * response) + */ + protected void doPost(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + try { + this.process(request, response); + } catch (PDFIOException e) { + response.sendError(500, "internal server error"); } + } + + protected void process(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException, PDFIOException { + + HttpSession session = request.getSession(); + StatusRequest statusRequest = (StatusRequest) session + .getAttribute(PDF_STATUS); + + if(statusRequest!=null) + { + byte[] nonSignedData = statusRequest.getSignatureData(); + + if (nonSignedData != null) { + + byte[] blackoutnonSignedData = PDFUtils.blackOutSignature(nonSignedData, statusRequest.getSignatureDataByteRange()); + + response.setContentType("application/pdf"); + OutputStream os = response.getOutputStream(); + os.write(blackoutnonSignedData); + os.close(); + logger.debug("pdf file transfer finished"); + + } else { + logger.info("no pdf document is found"); + response.sendError(500, "no signed data found"); + } + } else { + logger.info("no session found"); + response.sendError(500, "no signed data found"); + } + } +}
\ No newline at end of file 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..31f5a2ef --- /dev/null +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/SLDataURLServlet.java @@ -0,0 +1,236 @@ +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 command: " + 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(); + + logger.trace("SL2.0 payLoad on DataURL: " + payLoad.toString()); + + //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, 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..e43ebfcf --- /dev/null +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/SL20HttpBindingUtils.java @@ -0,0 +1,48 @@ +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 + log.trace("SL20 command: " + sl20Forward.toString()); + 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()) { %> |