diff options
8 files changed, 508 insertions, 4 deletions
diff --git a/pdf-as-web-statistic-api/src/main/java/at/gv/egiz/pdfas/web/stats/StatisticEvent.java b/pdf-as-web-statistic-api/src/main/java/at/gv/egiz/pdfas/web/stats/StatisticEvent.java index b1c5de11..aafbb99b 100644 --- a/pdf-as-web-statistic-api/src/main/java/at/gv/egiz/pdfas/web/stats/StatisticEvent.java +++ b/pdf-as-web-statistic-api/src/main/java/at/gv/egiz/pdfas/web/stats/StatisticEvent.java @@ -38,7 +38,8 @@ public class StatisticEvent { public enum Source { WEB("web"), - SOAP("soap"); + SOAP("soap"), + JSON("json"); private String name; diff --git a/pdf-as-web/build.gradle b/pdf-as-web/build.gradle index 49310e2f..a6ca37aa 100644 --- a/pdf-as-web/build.gradle +++ b/pdf-as-web/build.gradle @@ -51,7 +51,8 @@ dependencies { compile 'com.thetransactioncompany:cors-filter:2.3' compile 'ch.qos.logback:logback-classic:1.1.3' compile 'ch.qos.logback:logback-core:1.1.3' - + compile 'org.json:json:20160212' + providedCompile 'javax.servlet:javax.servlet-api:3.0.1' testCompile group: 'junit', name: 'junit', version: '4.+' } 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 4ef320a1..7fbed8d9 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 @@ -57,6 +57,7 @@ public class WebConfiguration implements IConfigurationConstants { public static final String SOAP_VERIFY_ENABLED = "soap.verify.enabled"; public static final String RELOAD_PASSWORD = "reload.pwd"; public static final String RELOAD_ENABLED = "reload.enabled"; + public static final String KEEP_SIGNED_DOCUMENT = "keep.signed"; public static final String MOA_LIST = "moal"; public static final String MOA_URL = "url"; @@ -299,7 +300,17 @@ public class WebConfiguration implements IConfigurationConstants { } return false; } - + + public static boolean isKeepSignedDocument() { + String value = properties.getProperty(KEEP_SIGNED_DOCUMENT); + if (value != null) { + if (value.equals("true")) { + return true; + } + } + return false; + } + public static boolean isMoaEnabled(String keyIdentifier) { String value = properties.getProperty(MOA_LIST + "." + keyIdentifier + ".enabled"); if (value != null) { diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/helper/JSONStartResponse.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/helper/JSONStartResponse.java new file mode 100644 index 00000000..98c59981 --- /dev/null +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/helper/JSONStartResponse.java @@ -0,0 +1,59 @@ +package at.gv.egiz.pdfas.web.helper; + +import at.gv.egiz.sl.util.SLMarschaller; + +/** + * Created by Andreas Fitzek on 6/23/16. + */ +public class JSONStartResponse { + + String url; + String slRequest; + String template; + String locale; + String bkuURL; + + public JSONStartResponse(String url, String slRequest, String template, String locale, String bkuURL) { + this.url = url; + this.slRequest = slRequest; + this.template = template; + this.bkuURL = bkuURL; + this.locale = locale; + } + + public String getBkuURL() { + return bkuURL; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getLocale() { + return locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + public String getTemplate() { + return template; + } + + public void setTemplate(String template) { + this.template = template; + } + + public String getSlRequest() { + return slRequest; + } + + public void setSlRequest(String slRequest) { + this.slRequest = slRequest; + } +} 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 c4db2e4f..691ab423 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 @@ -97,6 +97,7 @@ public class PdfAsHelper { private static final String PDF_SIGNER = "PDF_SIGNER"; private static final String PDF_SL_INTERACTIVE = "PDF_SL_INTERACTIVE"; private static final String PDF_SIGNED_DATA = "PDF_SIGNED_DATA"; + private static final String PDF_SIGNED_DATA_CREATED = "PDF_SIGNED_DATA_CREATED"; private static final String PDF_LOCALE = "PDF_LOCALE"; private static final String PDF_ERR_MESSAGE = "PDF_ERR_MESSAGE"; private static final String PDF_ERR_THROWABLE = "PDF_ERR_THROWABLE"; @@ -664,6 +665,93 @@ public class PdfAsHelper { return signResponse; } + public static void startSignatureJson(HttpServletRequest request, + HttpServletResponse response, ServletContext context, + byte[] pdfData, String connector, String position, + String transactionId, String profile, + Map<String, String> preProcessor, Map<String, String> overwrite) throws Exception { + + // TODO: Protect session so that only one PDF can be signed during one + // session + /* + * if(PdfAsHelper.isSignatureActive(request)) { throw new + * PdfAsException("Signature is active in this session"); } + * + * PdfAsHelper.setSignatureActive(request, true); + */ + + validatePdfSize(request, response, pdfData); + + HttpSession session = request.getSession(); + + logger.info("Starting signature in session: " + session.getId()); + + Configuration config = pdfAs.getConfiguration(); + session.setAttribute(PDF_CONFIG, config); + + ConfigurationOverwrite.overwriteConfiguration(overwrite, config); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + session.setAttribute(PDF_OUTPUT, baos); + + // Generate Sign Parameter + SignParameter signParameter = PdfAsFactory.createSignParameter(config, + new ByteArrayDataSource(pdfData), baos); + + logger.info("Setting TransactionID: " + transactionId); + + signParameter.setTransactionId(transactionId); + + IPlainSigner signer; + if (connector.equals("bku") || connector.equals("onlinebku") + || connector.equals("mobilebku")) { + BKUSLConnector conn = new BKUSLConnector(config); + // conn.setBase64(true); + signer = new PAdESSigner(conn); + session.setAttribute(PDF_SL_CONNECTOR, conn); + } else { + throw new PdfAsWebException( + "Invalid connector (bku | onlinebku | mobilebku | moa | jks)"); + } + signParameter.setPreprocessorArguments(preProcessor); + signParameter.setPlainSigner(signer); + session.setAttribute(PDF_SIGNER, signer); + session.setAttribute(PDF_SL_INTERACTIVE, connector); + + String qrCodeContent = PdfAsHelper.getQRCodeContent(request); + + if (qrCodeContent != null) { + if (profile == null) { + // get default Profile + profile = config.getValue("sig_obj.type.default"); + } + + if (profile == null) { + logger.warn("Failed to determine default profile! Using hard coded!"); + profile = "SIGNATURBLOCK_SMALL_DE"; + } + + ByteArrayOutputStream qrbaos = new ByteArrayOutputStream(); + try { + String key = "sig_obj." + profile + ".value.SIG_LABEL"; + QRCodeGenerator.generateQRCode(qrCodeContent, qrbaos, 200); + String value = Base64.encodeBase64String(qrbaos.toByteArray()); + config.setValue(key, value); + } finally { + IOUtils.closeQuietly(qrbaos); + } + } + + // set Signature Profile (null use default ...) + signParameter.setSignatureProfileId(profile); + + // set Signature Position + signParameter.setSignaturePosition(position); + + StatusRequest statusRequest = pdfAs.startSign(signParameter); + session.setAttribute(PDF_STATUS, statusRequest); + } + public static void startSignature(HttpServletRequest request, HttpServletResponse response, ServletContext context, byte[] pdfData, String connector, String position, @@ -861,6 +949,43 @@ public class PdfAsHelper { + session.getId()); } + public static JSONStartResponse startJsonProcess(HttpServletRequest request, + HttpServletResponse response, ServletContext context) + throws Exception { + HttpSession session = request.getSession(); + StatusRequest statusRequest = (StatusRequest) session + .getAttribute(PDF_STATUS); + // IPlainSigner plainSigner = (IPlainSigner) session + // .getAttribute(PDF_SIGNER); + + String connector = (String) session.getAttribute(PDF_SL_INTERACTIVE); + + if (connector.equals("bku") || connector.equals("onlinebku") + || connector.equals("mobilebku")) { + BKUSLConnector bkuSLConnector = (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()); + + JAXBElement<InfoboxReadRequestType> readRequest = of + .createInfoboxReadRequest(readCertificateRequest); + + String url = generateDataURL(request, response); + String slRequest = SLMarschaller.marshalToString(readRequest); + String template = getTemplateSL(); + String locale = getLocale(request, response); + String bkuURL = generateBKUURL(connector); + return new JSONStartResponse(url, slRequest, template, locale, bkuURL); + } + } + return null; + } + public static void process(HttpServletRequest request, HttpServletResponse response, ServletContext context) throws Exception { @@ -1018,6 +1143,26 @@ public class PdfAsHelper { return xml; } + public static boolean isSignedDataExpired(HttpServletRequest request, + HttpServletResponse response) { + HttpSession session = request.getSession(); + Object signedData = session.getAttribute(PDF_SIGNED_DATA_CREATED); + if (signedData == null) { + return true; + } + + if (signedData instanceof Long) { + long created = ((Long)signedData).longValue(); + long now = System.currentTimeMillis(); + + long validUntil = created + 300000; + + return validUntil > now; + } + logger.warn("PDF_SIGNED_DATA_CREATED in session is not a long type!"); + return true; + } + public static byte[] getSignedPdf(HttpServletRequest request, HttpServletResponse response) { HttpSession session = request.getSession(); @@ -1037,6 +1182,7 @@ public class PdfAsHelper { HttpServletResponse response, byte[] signedData) { HttpSession session = request.getSession(); session.setAttribute(PDF_SIGNED_DATA, signedData); + session.setAttribute(PDF_SIGNED_DATA_CREATED, Long.valueOf(System.currentTimeMillis())); } public static void setStatisticEvent(HttpServletRequest request, 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 new file mode 100644 index 00000000..1b9b4560 --- /dev/null +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/JSONAPIServlet.java @@ -0,0 +1,255 @@ +package at.gv.egiz.pdfas.web.servlets; + +import at.gv.egiz.pdfas.api.ws.PDFASSignParameters; +import at.gv.egiz.pdfas.api.ws.PDFASSignRequest; +import at.gv.egiz.pdfas.api.ws.PDFASSignResponse; +import at.gv.egiz.pdfas.api.ws.VerificationLevel; +import at.gv.egiz.pdfas.common.exceptions.PDFASError; +import at.gv.egiz.pdfas.lib.api.verify.VerifyParameter; +import at.gv.egiz.pdfas.lib.api.verify.VerifyResult; +import at.gv.egiz.pdfas.web.config.WebConfiguration; +import at.gv.egiz.pdfas.web.exception.PdfAsWebException; +import at.gv.egiz.pdfas.web.filter.UserAgentFilter; +import at.gv.egiz.pdfas.web.helper.DigestHelper; +import at.gv.egiz.pdfas.web.helper.JSONStartResponse; +import at.gv.egiz.pdfas.web.helper.PdfAsHelper; +import at.gv.egiz.pdfas.web.stats.StatisticEvent; +import at.gv.egiz.pdfas.web.stats.StatisticFrontend; +import at.gv.egiz.pdfas.web.store.RequestStore; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +import org.json.HTTP; +import org.json.JSONObject; +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.xml.ws.WebServiceException; +import java.io.BufferedReader; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * Created by Andreas Fitzek on 6/23/16. + */ +public class JSONAPIServlet extends HttpServlet { + + private static final String JSON_PROFILE = "profile"; + private static final String JSON_POSITION = "position"; + private static final String JSON_CONNECTOR = "connector"; + private static final String JSON_REQUEST_ID = "reqID"; + private static final String JSON_INPUT = "input"; + private static final String JSON_OUTPUT = "output"; + private static final String JSON_OUTPUT_SIG = "verifySignature"; + private static final String JSON_OUTPUT_CER = "verifyCertificate"; + private static final String JSON_DATAURL = "dataUrl"; + private static final String JSON_BKUURL = "bkuUrl"; + private static final String JSON_SLREQUEST = "slRequest"; + + private static final Logger logger = LoggerFactory.getLogger(JSONAPIServlet.class); + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + String jsonString = IOUtils.toString(req.getInputStream(), "UTF-8"); + + logger.debug("Reading json String {}", jsonString); + + JSONObject jsonObject = new JSONObject(jsonString); + + logger.debug("JSON parsed: {}", jsonObject.toString()); + + process(req, resp, jsonObject); + } + + protected void process(HttpServletRequest request, + HttpServletResponse response, + JSONObject jsonObject) throws ServletException, IOException { + + JSONObject jsonResponse = new JSONObject(); + + + + String profile = jsonObject.has(JSON_PROFILE) ? jsonObject.getString(JSON_PROFILE) : null; + String position = jsonObject.has(JSON_POSITION) ? jsonObject.getString(JSON_POSITION) : null; + String connector = jsonObject.getString(JSON_CONNECTOR); + String input = jsonObject.getString(JSON_INPUT); + String requestID = jsonObject.has(JSON_REQUEST_ID) ? jsonObject.getString(JSON_REQUEST_ID) : null; + + if(input == null) { + throw new ServletException( + "Invalid input value!"); + } + + byte[] inputDocument = Base64.decodeBase64(input); + + StatisticEvent statisticEvent = new StatisticEvent(); + statisticEvent.setSource(StatisticEvent.Source.JSON); + statisticEvent.setOperation(StatisticEvent.Operation.SIGN); + statisticEvent.setUserAgent(UserAgentFilter.getUserAgent()); + statisticEvent.setStartNow(); + + try { + + if(connector == null) { + throw new ServletException( + "Invalid connector value!"); + } + + PDFASSignParameters.Connector connectorEnum = null; + + if(PDFASSignParameters.Connector.MOA.equalsName(connector)) { + connectorEnum = PDFASSignParameters.Connector.MOA; + } else if(PDFASSignParameters.Connector.JKS.equalsName(connector)) { + connectorEnum = PDFASSignParameters.Connector.JKS; + } else if(PDFASSignParameters.Connector.BKU.equalsName(connector)) { + connectorEnum = PDFASSignParameters.Connector.BKU; + } else if(PDFASSignParameters.Connector.MOBILEBKU.equalsName(connector)) { + connectorEnum = PDFASSignParameters.Connector.MOBILEBKU; + } else if(PDFASSignParameters.Connector.ONLINEBKU.equalsName(connector)) { + connectorEnum = PDFASSignParameters.Connector.ONLINEBKU; + } + + if(connectorEnum == null) { + throw new ServletException( + "Invalid connector value!"); + } + + // TODO: check connector is enabled! + + statisticEvent.setFilesize(inputDocument.length); + statisticEvent.setProfileId(profile); + statisticEvent.setDevice(connector); + + PDFASSignParameters parameters = new PDFASSignParameters(); + parameters.setConnector(connectorEnum); + parameters.setPosition(position); + parameters.setProfile(profile); + + if (PDFASSignParameters.Connector.MOA.equals(connectorEnum) + || PDFASSignParameters.Connector.JKS.equals(connectorEnum)) { + // Plain server based signatures!! + PDFASSignResponse pdfasSignResponse = PdfAsHelper.synchornousServerSignature( + inputDocument, parameters); + + VerifyResult verifyResult = null; + + List<VerifyResult> verResults = PdfAsHelper + .synchornousVerify( + pdfasSignResponse.getSignedPDF(), + -1, + VerifyParameter.SignatureVerificationLevel.INTEGRITY_ONLY_VERIFICATION, + null); + + if (verResults.size() != 1) { + throw new ServletException( + "Document verification failed!"); + } + verifyResult = verResults.get(0); + + if(verifyResult.getValueCheckCode().getCode() == 0) { + statisticEvent.setStatus(StatisticEvent.Status.OK); + statisticEvent.setEndNow(); + statisticEvent.setTimestampNow(); + StatisticFrontend.getInstance().storeEvent(statisticEvent); + statisticEvent.setLogged(true); + } else { + statisticEvent.setStatus(StatisticEvent.Status.ERROR); + statisticEvent.setErrorCode(verifyResult.getValueCheckCode().getCode()); + statisticEvent.setEndNow(); + statisticEvent.setTimestampNow(); + StatisticFrontend.getInstance().storeEvent(statisticEvent); + statisticEvent.setLogged(true); + } + + jsonResponse.put(JSON_OUTPUT, Base64.encodeBase64String(pdfasSignResponse.getSignedPDF())); + jsonResponse.put(JSON_OUTPUT_SIG, verifyResult.getValueCheckCode().getCode()); + jsonResponse.put(JSON_OUTPUT_CER, verifyResult.getCertificateCheck().getCode()); + + } else { + + PdfAsHelper.setStatisticEvent(request, response, statisticEvent); + PdfAsHelper.setVerificationLevel(request, + VerifyParameter.SignatureVerificationLevel.INTEGRITY_ONLY_VERIFICATION); + + String pdfDataHash = DigestHelper.getHexEncodedHash(inputDocument); + + PdfAsHelper.setSignatureDataHash(request, pdfDataHash); + logger.debug("Storing signatures data hash: " + pdfDataHash); + + logger.debug("Starting signature creation with: " + connector); + + // start asynchronous signature creation + + if (PDFASSignParameters.Connector.BKU.equals(connectorEnum)) { + if (WebConfiguration.getLocalBKUURL() == null) { + throw new PdfAsWebException( + "Invalid connector bku is not supported"); + } + } + + if (PDFASSignParameters.Connector.ONLINEBKU.equals(connectorEnum)) { + if (WebConfiguration.getLocalBKUURL() == null) { + throw new PdfAsWebException( + "Invalid connector onlinebku is not supported"); + } + } + + if (PDFASSignParameters.Connector.MOBILEBKU.equals(connectorEnum)) { + if (WebConfiguration.getLocalBKUURL() == null) { + throw new PdfAsWebException( + "Invalid connector mobilebku is not supported"); + } + } + + + PdfAsHelper.startSignatureJson(request, response, getServletContext(), + inputDocument, connectorEnum.toString(), + position, + null, + profile, null, + null); + + JSONStartResponse jsonStartResponse = PdfAsHelper.startJsonProcess(request, response, getServletContext()); + + if(jsonStartResponse == null) { + throw new PdfAsWebException( + "Invalid configuration for json API"); + } + + jsonResponse.put(JSON_DATAURL, jsonStartResponse.getUrl()); + jsonResponse.put(JSON_BKUURL, jsonStartResponse.getBkuURL()); + jsonResponse.put(JSON_SLREQUEST, jsonStartResponse.getSlRequest()); + } + + response.setContentType("application/json"); + IOUtils.write(jsonResponse.toString(), response.getOutputStream(), "UTF-8"); + + } catch (Throwable e) { + + statisticEvent.setStatus(StatisticEvent.Status.ERROR); + statisticEvent.setException(e); + if(e instanceof PDFASError) { + statisticEvent.setErrorCode(((PDFASError)e).getCode()); + } + statisticEvent.setEndNow(); + statisticEvent.setTimestampNow(); + StatisticFrontend.getInstance().storeEvent(statisticEvent); + statisticEvent.setLogged(true); + + logger.warn("Error in JSON Service", e); + if (e.getCause() != null) { + throw new ServletException(e.getCause().getMessage()); + } else { + throw new ServletException(e.getMessage()); + } + } finally { + logger.debug("Done JSON Sign Request"); + } + } +} 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 4fce6860..cf8486b5 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 @@ -31,6 +31,7 @@ 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; @@ -85,6 +86,18 @@ public class PDFData extends HttpServlet { String plainPDFDigest = PdfAsParameterExtractor.getOrigDigest(request); if (signedData != null) { + + if(WebConfiguration.isKeepSignedDocument()) { + if(PdfAsHelper.isSignedDataExpired(request, response)) { + logger.debug("Destroying expired signed data in session : {}", request.getSession().getId()); + request.getSession().invalidate(); + PdfAsHelper.setSessionException(request, response, + "No signed pdf document available.", null); + PdfAsHelper.gotoError(getServletContext(), request, response); + return; + } + } + if (plainPDFDigest != null) { String signatureDataHash = PdfAsHelper .getSignatureDataHash(request); @@ -132,7 +145,12 @@ public class PDFData extends HttpServlet { os.close(); // When data is collected destroy session! - request.getSession().invalidate(); + if(!WebConfiguration.isKeepSignedDocument()) { + logger.debug("Destroying signed data in session : {}", request.getSession().getId()); + request.getSession().invalidate(); + } else { + logger.debug("Keeping signed data in session : {}", request.getSession().getId()); + } } else { PdfAsHelper.setSessionException(request, response, "No signed pdf document available.", null); 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 3e6b30bd..a7bb5f74 100644 --- a/pdf-as-web/src/main/webapp/WEB-INF/web.xml +++ b/pdf-as-web/src/main/webapp/WEB-INF/web.xml @@ -139,6 +139,13 @@ <description></description> <servlet-class>at.gv.egiz.pdfas.web.servlets.PlaceholderGeneratorServlet</servlet-class> </servlet> + <servlet> + <servlet-name>JSONAPIServlet</servlet-name> + <display-name>JSONAPIServlet</display-name> + <description></description> + <servlet-class>at.gv.egiz.pdfas.web.servlets.JSONAPIServlet</servlet-class> + </servlet> + <!-- Define mappings that are used by the servlet container to translate a particular request URI (context-relative) to a particular servlet. The @@ -206,6 +213,12 @@ <url-pattern>/placeholder</url-pattern> </servlet-mapping> + <servlet-mapping> + <servlet-name>JSONAPIServlet</servlet-name> + <url-pattern>/api/v1/sign</url-pattern> + </servlet-mapping> + + <!-- Define the default session timeout for your application, in minutes. From a servlet or JSP page, you can modify the timeout for a particular session dynamically by using HttpSession.getMaxInactiveInterval(). --> |