From 9acf3c2e8aca9016daf76785747d838cdc5b0330 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Mon, 9 Jul 2018 10:11:25 +0200 Subject: add SL20 connecter-backend in a first beta version (getCertificate looks good, create signature is untested) --- .../gv/egiz/pdfas/web/config/WebConfiguration.java | 105 ++++++ .../at/gv/egiz/pdfas/web/helper/PdfAsHelper.java | 390 +++++++++++++++++---- .../gv/egiz/pdfas/web/servlets/DataURLServlet.java | 4 +- .../egiz/pdfas/web/servlets/ExternSignServlet.java | 8 +- .../gv/egiz/pdfas/web/servlets/JSONAPIServlet.java | 11 +- .../egiz/pdfas/web/servlets/SLDataURLServlet.java | 234 +++++++++++++ .../pdfas/web/servlets/UIEntryPointServlet.java | 11 +- .../gv/egiz/pdfas/web/sl20/JsonSecurityUtils.java | 381 ++++++++++++++++++++ .../at/gv/egiz/pdfas/web/sl20/KeyStoreUtils.java | 222 ++++++++++++ .../egiz/pdfas/web/sl20/SL20HttpBindingUtils.java | 47 +++ .../java/at/gv/egiz/pdfas/web/sl20/X509Utils.java | 62 ++++ pdf-as-web/src/main/webapp/WEB-INF/web.xml | 10 + pdf-as-web/src/main/webapp/index.jsp | 11 + 13 files changed, 1427 insertions(+), 69 deletions(-) create mode 100644 pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/SLDataURLServlet.java create mode 100644 pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/JsonSecurityUtils.java create mode 100644 pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/KeyStoreUtils.java create mode 100644 pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/SL20HttpBindingUtils.java create mode 100644 pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/X509Utils.java (limited to 'pdf-as-web/src/main') diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/config/WebConfiguration.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/config/WebConfiguration.java index d63f698f..c6b27eb3 100644 --- a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/config/WebConfiguration.java +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/config/WebConfiguration.java @@ -42,9 +42,12 @@ public class WebConfiguration implements IConfigurationConstants { public static final String LOCAL_BKU_ENABLED = "bku.sign.enabled"; public static final String ONLINE_BKU_ENABLED = "moc.sign.enabled"; public static final String MOBILE_BKU_ENABLED = "mobile.sign.enabled"; + public static final String SL20_BKU_ENABLED = "sl20.sign.enabled"; public static final String LOCAL_BKU_URL = "bku.local.url"; public static final String ONLINE_BKU_URL = "bku.online.url"; public static final String MOBILE_BKU_URL = "bku.mobile.url"; + public static final String SL20_BKU_URL = "sl20.mobile.url"; + public static final String ERROR_DETAILS = "error.showdetails"; public static final String PDF_AS_WORK_DIR = "pdfas.dir"; public static final String STATISTIC_BACKEND_LIST = "statistic.backends"; @@ -82,6 +85,23 @@ public class WebConfiguration implements IConfigurationConstants { public static final String KEYSTORE_DEFAULT_ALIAS = KEYSTORE_DEFAULT + "." + KEYSTORE_ALIAS; public static final String KEYSTORE_DEFAULT_KEY_PASS = KEYSTORE_DEFAULT + "." + KEYSTORE_KEY_PASS; + //SL20 stuff + public static final String SL20_PREFIX = "sl20"; + public static final String SL20_KEYSTORE_PREFIX = SL20_PREFIX + ".keystore"; + public static final String SL20_KEYSTORE_FILE = SL20_KEYSTORE_PREFIX + "." + "file"; + public static final String SL20_KEYSTORE_TYPE = SL20_KEYSTORE_PREFIX + "." + "type"; + public static final String SL20_KEYSTORE_PASS = SL20_KEYSTORE_PREFIX + "." + "pass"; + public static final String SL20_KEYSTORE_KEY_SIGN_ALIAS = SL20_KEYSTORE_PREFIX + "." + "sign.key.alias"; + public static final String SL20_KEYSTORE_KEY_SIGN_PASS = SL20_KEYSTORE_PREFIX + "." + "sign.key.pass"; + public static final String SL20_KEYSTORE_KEY_ENCRYPTION_ALIAS = SL20_KEYSTORE_PREFIX + "." + "enc.key.alias"; + public static final String SL20_KEYSTORE_KEY_ENCRYPTION_PASS = SL20_KEYSTORE_PREFIX + "." + "enc.key.pass"; + public static final String SL20_DEBUG_VALIDATION_DISABLED = SL20_PREFIX + ".debug.validation.disable"; + public static final String SL20_DEBUG_SIGNING_ENABLED = SL20_PREFIX + ".debug.signed.result.enabled"; + public static final String SL20_DEBUG_SIGNING_REQUIRED = SL20_PREFIX + ".debug.signed.result.required"; + public static final String SL20_DEBUG_ENCRYPTION_ENABLED = SL20_PREFIX + ".debug.encryption.enabled"; + public static final String SL20_DEBUG_ENCRYPTION_REQUIRED = SL20_PREFIX + ".debug.encryption.required"; + + public static final String WHITELIST_ENABLED = "whitelist.enabled"; public static final String WHITELIST_VALUE_PRE = "whitelist.url."; @@ -248,6 +268,20 @@ public class WebConfiguration implements IConfigurationConstants { return null; } + public static String getSecurityLayer20URL() { + if(getSL20Enabled()) { + String overwrite = properties.getProperty(SL20_SIGN_URL); + if(overwrite == null) { + overwrite = properties.getProperty(SL20_BKU_URL); + if(overwrite == null) { + overwrite = PdfAsHelper.getPdfAsConfig().getValue(SL20_SIGN_URL); + } + } + return overwrite; + } + return null; + } + public static String getPdfASDir() { return properties.getProperty(PDF_AS_WORK_DIR); } @@ -447,6 +481,16 @@ public class WebConfiguration implements IConfigurationConstants { return false; } + public static boolean getSL20Enabled() { + String value = properties.getProperty(SL20_BKU_ENABLED); + if (value != null) { + if (value.equals("true")) { + return true; + } + } + return false; + } + public static boolean getSoapSignEnabled() { String value = properties.getProperty(SOAP_SIGN_ENABLED); if (value != null) { @@ -598,5 +642,66 @@ public class WebConfiguration implements IConfigurationConstants { } return false; } + + public static String getSL20KeyStorePath() { + return properties.getProperty(SL20_KEYSTORE_FILE); + + } + + public static String getSL20KeyStoreType() { + return properties.getProperty(SL20_KEYSTORE_TYPE); + + } + + public static String getSL20KeyStorePassword() { + return properties.getProperty(SL20_KEYSTORE_PASS); + + } + + public static String getSL20KeySigningAlias() { + return properties.getProperty(SL20_KEYSTORE_KEY_SIGN_ALIAS); + + } + + public static String getSL20KeySigningPassword() { + return properties.getProperty(SL20_KEYSTORE_KEY_SIGN_PASS); + + } + + public static String getSL20KeyEncryptionAlias() { + return properties.getProperty(SL20_KEYSTORE_KEY_ENCRYPTION_ALIAS); + + } + + public static String getSL20KeyEncryptionPassword() { + return properties.getProperty(SL20_KEYSTORE_KEY_ENCRYPTION_PASS); + + } + + public static boolean isSL20ValidationDisabled( ) { + return Boolean.parseBoolean(properties.getProperty(SL20_DEBUG_VALIDATION_DISABLED, String.valueOf(false))); + + } + + public static boolean isSL20SigningEnabled( ) { + return Boolean.parseBoolean(properties.getProperty(SL20_DEBUG_SIGNING_ENABLED, String.valueOf(false))); + + } + + public static boolean isSL20SigningRequired( ) { + return Boolean.parseBoolean(properties.getProperty(SL20_DEBUG_SIGNING_REQUIRED, String.valueOf(false))); + + } + + public static boolean isSL20EncryptionEnabled( ) { + return Boolean.parseBoolean(properties.getProperty(SL20_DEBUG_ENCRYPTION_ENABLED, String.valueOf(false))); + + } + + public static boolean isSL20EncryptionRequired( ) { + return Boolean.parseBoolean(properties.getProperty(SL20_DEBUG_ENCRYPTION_REQUIRED, String.valueOf(false))); + + } + } diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/helper/PdfAsHelper.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/helper/PdfAsHelper.java index 3aad831d..4b776cb3 100644 --- a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/helper/PdfAsHelper.java +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/helper/PdfAsHelper.java @@ -30,12 +30,17 @@ import java.awt.image.RenderedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.StringWriter; import java.io.UnsupportedEncodingException; +import java.net.URL; import java.net.URLEncoder; import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.UUID; import javax.imageio.ImageIO; import javax.servlet.RequestDispatcher; @@ -51,9 +56,12 @@ import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.http.entity.ContentType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gson.JsonObject; + import at.gv.egiz.pdfas.api.ws.PDFASSignParameters; import at.gv.egiz.pdfas.api.ws.PDFASSignParameters.Connector; import at.gv.egiz.pdfas.api.ws.PDFASSignResponse; @@ -77,6 +85,8 @@ import at.gv.egiz.pdfas.sigs.pades.PAdESSignerKeystore; import at.gv.egiz.pdfas.web.config.WebConfiguration; import at.gv.egiz.pdfas.web.exception.PdfAsWebException; import at.gv.egiz.pdfas.web.servlets.UIEntryPointServlet; +import at.gv.egiz.pdfas.web.sl20.JsonSecurityUtils; +import at.gv.egiz.pdfas.web.sl20.SL20HttpBindingUtils; import at.gv.egiz.pdfas.web.stats.StatisticEvent; import at.gv.egiz.sl.schema.CreateCMSSignatureResponseType; import at.gv.egiz.sl.schema.InfoboxAssocArrayPairType; @@ -84,8 +94,16 @@ import at.gv.egiz.sl.schema.InfoboxReadRequestType; import at.gv.egiz.sl.schema.InfoboxReadResponseType; import at.gv.egiz.sl.schema.ObjectFactory; import at.gv.egiz.sl.util.BKUSLConnector; +import at.gv.egiz.sl.util.BaseSLConnector; import at.gv.egiz.sl.util.RequestPackage; import at.gv.egiz.sl.util.SLMarschaller; +import at.gv.egiz.sl20.SL20Connector; +import at.gv.egiz.sl20.data.VerificationResult; +import at.gv.egiz.sl20.exceptions.SL20Exception; +import at.gv.egiz.sl20.exceptions.SLCommandoParserException; +import at.gv.egiz.sl20.utils.SL20Constants; +import at.gv.egiz.sl20.utils.SL20JSONBuilderUtils; +import at.gv.egiz.sl20.utils.SL20JSONExtractorUtils; public class PdfAsHelper { @@ -105,6 +123,7 @@ public class PdfAsHelper { private static final String PDF_PROVIDE_PAGE = "/ProvidePDF"; private static final String PDF_PDFDATA_PAGE = "/PDFData"; private static final String PDF_DATAURL_PAGE = "/DataURL"; + private static final String PDF_SL20_DATAURL_PAGE = "/DataURLSL20"; private static final String PDF_USERENTRY_PAGE = "/userentry"; private static final String PDF_ERR_URL = "PDF_ERR_URL"; private static final String PDF_FILE_NAME = "PDF_FILE_NAME"; @@ -118,6 +137,7 @@ public class PdfAsHelper { private static final String SIGNATURE_ACTIVE = "SIGNATURE_ACTIVE"; private static final String VERIFICATION_RESULT = "VERIFICATION_RESULT"; private static final String QRCODE_CONTENT = "QR_CONT"; + public static final String PDF_SESSION_PREFIX = "PDF_SESSION_"; private static final Logger logger = LoggerFactory .getLogger(PdfAsHelper.class); @@ -707,6 +727,12 @@ public class PdfAsHelper { // conn.setBase64(true); signer = new PAdESSigner(conn); session.setAttribute(PDF_SL_CONNECTOR, conn); + + } else if (connector.equals("sl20")) { + SL20Connector conn = new SL20Connector(config); + signer = new PAdESSigner(conn); + session.setAttribute(PDF_SL_CONNECTOR, conn); + } else { throw new PdfAsWebException( "Invalid connector (bku | onlinebku | mobilebku | moa | jks)"); @@ -794,9 +820,15 @@ public class PdfAsHelper { // conn.setBase64(true); signer = new PAdESSigner(conn); session.setAttribute(PDF_SL_CONNECTOR, conn); + + } else if (connector.equals("sl20")) { + SL20Connector conn = new SL20Connector(config); + signer = new PAdESSigner(conn); + session.setAttribute(PDF_SL_CONNECTOR, conn); + } else { throw new PdfAsWebException( - "Invalid connector (bku | onlinebku | mobilebku | moa | jks)"); + "Invalid connector (bku | onlinebku | mobilebku | moa | jks | sl20)"); } signParameter.setPreprocessorArguments(preProcessor); signParameter.setPlainSigner(signer); @@ -839,7 +871,7 @@ public class PdfAsHelper { PdfAsHelper.process(request, response, context); } - private static byte[] getCertificate( + public static byte[] getCertificate( InfoboxReadResponseType infoboxReadResponseType) { byte[] data = null; if (infoboxReadResponseType.getAssocArrayData() != null) { @@ -898,7 +930,7 @@ public class PdfAsHelper { public static void injectCertificate(HttpServletRequest request, HttpServletResponse response, - InfoboxReadResponseType infoboxReadResponseType, + byte[] certificate, ServletContext context) throws Exception { HttpSession session = request.getSession(); @@ -910,7 +942,7 @@ public class PdfAsHelper { + session.getId()); } - statusRequest.setCertificate(getCertificate(infoboxReadResponseType)); + statusRequest.setCertificate(certificate); statusRequest = pdfAs.process(statusRequest); session.setAttribute(PDF_STATUS, statusRequest); @@ -919,7 +951,7 @@ public class PdfAsHelper { public static void injectSignature(HttpServletRequest request, HttpServletResponse response, - CreateCMSSignatureResponseType createCMSSignatureResponseType, + byte[] cmsSginature, ServletContext context) throws Exception { logger.debug("Got CMS Signature Response"); @@ -933,8 +965,7 @@ public class PdfAsHelper { + session.getId()); } - statusRequest.setSigature(createCMSSignatureResponseType - .getCMSSignature()); + statusRequest.setSigature(cmsSginature); statusRequest = pdfAs.process(statusRequest); session.setAttribute(PDF_STATUS, statusRequest); @@ -996,21 +1027,35 @@ public class PdfAsHelper { String connector = (String) session.getAttribute(PDF_SL_INTERACTIVE); + //load connector + BaseSLConnector slConnector = null; if (connector.equals("bku") || connector.equals("onlinebku") - || connector.equals("mobilebku")) { - BKUSLConnector bkuSLConnector = (BKUSLConnector) session + || connector.equals("mobilebku")) + slConnector = (BKUSLConnector) session .getAttribute(PDF_SL_CONNECTOR); - - if (statusRequest.needCertificate()) { - logger.debug("Needing Certificate from BKU"); - // build SL Request to read certificate - InfoboxReadRequestType readCertificateRequest = bkuSLConnector - .createInfoboxReadRequest(statusRequest - .getSignParameter()); - + + else if (connector.equals("sl20")) + slConnector = (SL20Connector) session + .getAttribute(PDF_SL_CONNECTOR); + + else + throw new PdfAsWebException("Invalid connector: " + connector); + + JsonSecurityUtils joseTools = JsonSecurityUtils.getInstance(); + if (!joseTools.isInitialized()) + joseTools = null; + + if (statusRequest.needCertificate()) { + logger.debug("Needing Certificate from BKU"); + // build SL Request to read certificate + InfoboxReadRequestType readCertificateRequest = slConnector + .createInfoboxReadRequest(statusRequest + .getSignParameter()); + + if (slConnector instanceof BKUSLConnector) { JAXBElement readRequest = of .createInfoboxReadRequest(readCertificateRequest); - + String url = generateDataURL(request, response); String slRequest = SLMarschaller.marshalToString(readRequest); String template = getTemplateSL(); @@ -1021,7 +1066,7 @@ public class PdfAsHelper { StringEscapeUtils.escapeHtml4(slRequest)); template = template.replace("##DataURL##", url); template = template.replace("##LOCALE##", locale); - + if (statusRequest.getSignParameter().getTransactionId() != null) { template = template.replace( "##ADDITIONAL##", @@ -1034,70 +1079,220 @@ public class PdfAsHelper { } else { template = template.replace("##ADDITIONAL##", ""); } - + response.getWriter().write(template); // TODO: set content type of response!! response.setContentType("text/html"); response.getWriter().close(); - } else if (statusRequest.needSignature()) { - logger.debug("Needing Signature from BKU"); - // build SL Request for cms signature - RequestPackage pack = bkuSLConnector.createCMSRequest( - statusRequest.getSignatureData(), - statusRequest.getSignatureDataByteRange(), - statusRequest.getSignParameter()); - + + } else if (slConnector instanceof SL20Connector) { + //generate request for getCertificate command + SL20Connector sl20Connector = (SL20Connector)slConnector; + + //use 'SecureSigningKeypair' per default + String keyId = SL20Connector.SecureSignatureKeypair; + + java.security.cert.X509Certificate x5cEnc = null; + if (WebConfiguration.isSL20EncryptionEnabled() && joseTools != null) + x5cEnc = joseTools.getEncryptionCertificate(); + JsonObject getCertParams = + SL20JSONBuilderUtils.createGetCertificateCommandParameters( + keyId, generateDataURLSL20(request, response), x5cEnc); + + JsonObject sl20Req = null; + String reqId = UUID.randomUUID().toString(); + if (WebConfiguration.isSL20SigningEnabled()) { + String signedCertCommand = SL20JSONBuilderUtils.createSignedCommand( + SL20Constants.SL20_COMMAND_IDENTIFIER_GETCERTIFICATE, getCertParams, joseTools); + sl20Req = SL20JSONBuilderUtils.createGenericRequest(reqId, null, null, signedCertCommand); + + } else { + JsonObject getCertCommand = SL20JSONBuilderUtils.createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_GETCERTIFICATE, getCertParams); + sl20Req = SL20JSONBuilderUtils.createGenericRequest(reqId, null, getCertCommand, null); + + } + + //send SL20 request via Backend connection + JsonObject sl20Resp = sl20Connector.sendSL20Request(sl20Req, null, generateBKUURL(connector)); + if (sl20Resp == null) { + logger.info("Receive NO responce from SL2.0 connection. Process stops ... "); + throw new SLCommandoParserException(); + + } + + VerificationResult respPayloadContainer = SL20JSONExtractorUtils.extractSL20PayLoad( + sl20Resp, joseTools, WebConfiguration.isSL20SigningRequired()); + + if (respPayloadContainer.isValidSigned() == null) + logger.debug("Receive unsigned payLoad from VDA"); + + JsonObject respPayload = respPayloadContainer.getPayload(); + if (respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).getAsString() + .equals(SL20Constants.SL20_COMMAND_IDENTIFIER_REDIRECT)) { + logger.debug("Find 'redirect' command in VDA response ... "); + JsonObject params = SL20JSONExtractorUtils.getJSONObjectValue(respPayload, SL20Constants.SL20_COMMAND_CONTAINER_PARAMS, true); + String redirectURL = SL20JSONExtractorUtils.getStringValue(params, SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_URL, true); + JsonObject command = SL20JSONExtractorUtils.getJSONObjectValue(params, SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_COMMAND, false); + String signedCommand = SL20JSONExtractorUtils.getStringValue(params, SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_SIGNEDCOMMAND, false); + + //create forward SL2.0 command + JsonObject sl20Forward = sl20Resp.deepCopy().getAsJsonObject(); + SL20JSONBuilderUtils.addOnlyOnceOfTwo(sl20Forward, + SL20Constants.SL20_PAYLOAD, SL20Constants.SL20_SIGNEDPAYLOAD, + command, signedCommand); + + //store requestId + + request.getSession(false).setAttribute(PDF_SESSION_PREFIX + SL20Constants.SL20_REQID, reqId); + + //forward SL2.0 command + SL20HttpBindingUtils.writeIntoResponse(request, response, sl20Forward, redirectURL); + + } else if (respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).getAsString() + .equals(SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR)) { + JsonObject result = SL20JSONExtractorUtils.getJSONObjectValue(respPayload, SL20Constants.SL20_COMMAND_CONTAINER_RESULT, false); + if (result == null) + result = SL20JSONExtractorUtils.getJSONObjectValue(respPayload, SL20Constants.SL20_COMMAND_CONTAINER_PARAMS, false); + + String errorCode = SL20JSONExtractorUtils.getStringValue(result, SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORCODE, true); + String errorMsg = SL20JSONExtractorUtils.getStringValue(result, SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORMESSAGE, true); + + logger.info("Receive SL2.0 error. Code:" + errorCode + " Msg:" + errorMsg); + throw new SL20Exception("sl20.08"); + + } else { + logger.warn("Received an unrecognized command: " + respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).getAsString()); + throw new SLCommandoParserException(); + + } + + } else + throw new PdfAsWebException("Invalid connector: " + slConnector.getClass().getName()); + + } else if (statusRequest.needSignature()) { + logger.debug("Needing Signature from BKU"); + // build SL Request for cms signature + RequestPackage pack = slConnector.createCMSRequest( + statusRequest.getSignatureData(), + statusRequest.getSignatureDataByteRange(), + statusRequest.getSignParameter()); + + if (slConnector instanceof BKUSLConnector) { String slRequest = SLMarschaller .marshalToString(of .createCreateCMSSignatureRequest(pack .getRequestType())); logger.trace("SL Request: " + slRequest); - + response.setContentType("text/xml"); response.getWriter().write(slRequest); response.getWriter().close(); + + } else if (slConnector instanceof SL20Connector) { + //convert byte range + List byteRanges = new ArrayList(); + for (int el : statusRequest.getSignatureDataByteRange()) + byteRanges.add(String.valueOf(el)); + + java.security.cert.X509Certificate x5cEnc = null; + if (WebConfiguration.isSL20EncryptionEnabled() && joseTools != null) + x5cEnc = joseTools.getEncryptionCertificate(); + + //set 'true' as default + boolean padesCompatibel = true; + if (pack.getRequestType().getPAdESFlag() != null) + padesCompatibel = pack.getRequestType().getPAdESFlag(); + + JsonObject createCAdESSigParams = + SL20JSONBuilderUtils.createCreateCAdESCommandParameters( + pack.getRequestType().getKeyboxIdentifier(), + statusRequest.getSignatureData(), + pack.getRequestType().getDataObject().getMetaInfo().getMimeType(), + padesCompatibel , + byteRanges, + SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_CADESLEVEL_BASIC, + generateDataURLSL20(request, response), + x5cEnc) ; + + JsonObject sl20CreateCAdES = null; + String reqId = UUID.randomUUID().toString(); + if (WebConfiguration.isSL20SigningEnabled()) { + String signedCertCommand = SL20JSONBuilderUtils.createSignedCommand( + SL20Constants.SL20_COMMAND_IDENTIFIER_CREATE_SIG_CADES, createCAdESSigParams, joseTools); + sl20CreateCAdES = SL20JSONBuilderUtils.createGenericRequest(reqId, null, null, signedCertCommand); + + } else { + JsonObject getCertCommand = SL20JSONBuilderUtils.createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_CREATE_SIG_CADES, createCAdESSigParams); + sl20CreateCAdES = SL20JSONBuilderUtils.createGenericRequest(UUID.randomUUID().toString(), null, getCertCommand, null); + + } + + request.getSession(false).setAttribute(PDF_SESSION_PREFIX + SL20Constants.SL20_REQID, reqId); + + //forward SL2.0 command + logger.trace("Write 'createCAdES' command to VDA: " + sl20CreateCAdES.toString()); + StringWriter writer = new StringWriter(); + writer.write(sl20CreateCAdES.toString()); + final byte[] content = writer.toString().getBytes("UTF-8"); + response.setStatus(HttpServletResponse.SC_OK); + response.setContentLength(content.length); + response.setContentType(ContentType.APPLICATION_JSON.toString()); + response.getOutputStream().write(content); + + } else + throw new PdfAsWebException("Invalid connector: " + slConnector.getClass().getName()); + + + + } else if (statusRequest.isReady()) { + // TODO: store pdf document redirect to Finish URL + logger.debug("Document ready!"); + + SignResult result = pdfAs.finishSign(statusRequest); + + ByteArrayOutputStream baos = (ByteArrayOutputStream) session + .getAttribute(PDF_OUTPUT); + baos.close(); + + PDFASVerificationResponse verResponse = new PDFASVerificationResponse(); + List verResults = PdfAsHelper.synchornousVerify( + baos.toByteArray(), -2, + PdfAsHelper.getVerificationLevel(request), null); + + if (verResults.size() != 1) { + throw new WebServiceException( + "Document verification failed!"); + } + VerifyResult verifyResult = verResults.get(0); - } else if (statusRequest.isReady()) { - // TODO: store pdf document redirect to Finish URL - logger.debug("Document ready!"); - - SignResult result = pdfAs.finishSign(statusRequest); - - ByteArrayOutputStream baos = (ByteArrayOutputStream) session - .getAttribute(PDF_OUTPUT); - baos.close(); - - PDFASVerificationResponse verResponse = new PDFASVerificationResponse(); - List verResults = PdfAsHelper.synchornousVerify( - baos.toByteArray(), -2, - PdfAsHelper.getVerificationLevel(request), null); + verResponse.setCertificateCode(verifyResult + .getCertificateCheck().getCode()); + verResponse.setValueCode(verifyResult.getValueCheckCode() + .getCode()); - if (verResults.size() != 1) { - throw new WebServiceException( - "Document verification failed!"); - } - VerifyResult verifyResult = verResults.get(0); + PdfAsHelper.setPDFASVerificationResponse(request, verResponse); + PdfAsHelper.setSignedPdf(request, response, baos.toByteArray()); - verResponse.setCertificateCode(verifyResult - .getCertificateCheck().getCode()); - verResponse.setValueCode(verifyResult.getValueCheckCode() - .getCode()); + String signerCert = Base64.encodeBase64String(result + .getSignerCertificate().getEncoded()); - PdfAsHelper.setPDFASVerificationResponse(request, verResponse); - PdfAsHelper.setSignedPdf(request, response, baos.toByteArray()); + PdfAsHelper.setSignerCertificate(request, signerCert); + + if (slConnector instanceof BKUSLConnector) { PdfAsHelper.gotoProvidePdf(context, request, response); - - String signerCert = Base64.encodeBase64String(result - .getSignerCertificate().getEncoded()); - - PdfAsHelper.setSignerCertificate(request, signerCert); - - } else { - throw new PdfAsWebException("Invalid state!"); - } + + } else if (slConnector instanceof SL20Connector) { + //TODO: add code to send SL20 redirect command to redirect the user from DataURL connection to App Front-End connection + String callUrl = generateProvideURL(request, response); + String transactionId = (String) request.getAttribute(PdfAsHelper.PDF_SESSION_PREFIX + SL20Constants.SL20_TRANSACTIONID); + buildSL20RedirectResponse(request, response, transactionId, callUrl); + + } else + throw new PdfAsWebException("Invalid connector: " + slConnector.getClass().getName()); + } else { - throw new PdfAsWebException("Invalid connector: " + connector); + throw new PdfAsWebException("Invalid state!"); } } @@ -1338,6 +1533,11 @@ public class PdfAsHelper { request.getSession(true); } + public static String generateDataURLSL20(HttpServletRequest request, + HttpServletResponse response) { + return generateURL(request, response, PDF_SL20_DATAURL_PAGE); + } + public static String generateDataURL(HttpServletRequest request, HttpServletResponse response) { return generateURL(request, response, PDF_DATAURL_PAGE); @@ -1385,6 +1585,8 @@ public class PdfAsHelper { return WebConfiguration.getOnlineBKUURL(); } else if (connector.equals("mobilebku")) { return WebConfiguration.getHandyBKUURL(); + } else if (connector.equals("sl20")) { + return WebConfiguration.getSecurityLayer20URL(); } return WebConfiguration.getLocalBKUURL(); } @@ -1542,4 +1744,64 @@ public class PdfAsHelper { public static String getSCMRevision() { return PdfAsFactory.getSCMRevision(); } + + public static void buildSL20RedirectResponse(HttpServletRequest request, HttpServletResponse response, String transactionId, String callURL) throws IOException, SL20Exception { + //create response + Map reqParameters = UrlParameterExtractor.splitQuery(new URL(callURL)); + + //extract URL without parameters + String url; + int paramIndex = callURL.indexOf("?"); + if (paramIndex == -1) + url = callURL; + else + url = callURL.substring(0, paramIndex); + + JsonObject callReqParams = SL20JSONBuilderUtils.createCallCommandParameters( + url, + SL20Constants.SL20_COMMAND_PARAM_GENERAL_CALL_METHOD_GET, + false, + reqParameters); + JsonObject callCommand = SL20JSONBuilderUtils.createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_CALL, callReqParams); + + //build first redirect command for app + JsonObject redirectOneParams = SL20JSONBuilderUtils.createRedirectCommandParameters( + null, + callCommand, null, true); + JsonObject redirectOneCommand = SL20JSONBuilderUtils.createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_REDIRECT, redirectOneParams); + + //build second redirect command for IDP + JsonObject redirectTwoParams = SL20JSONBuilderUtils.createRedirectCommandParameters( + callURL, + redirectOneCommand, null, false); + JsonObject redirectTwoCommand = SL20JSONBuilderUtils.createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_REDIRECT, redirectTwoParams); + + //build generic SL2.0 response container + JsonObject respContainer = SL20JSONBuilderUtils.createGenericRequest( + UUID.randomUUID().toString(), + transactionId, + redirectTwoCommand, + null); + + //workaround for A-Trust + if (request.getHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE) != null && + request.getHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE).equals(SL20Constants.HTTP_HEADER_VALUE_NATIVE) + || true) { + logger.debug("Client request containts 'native client' header ... "); + logger.trace("SL20 response to VDA: " + respContainer); + StringWriter writer = new StringWriter(); + writer.write(respContainer.toString()); + final byte[] content = writer.toString().getBytes("UTF-8"); + response.setStatus(HttpServletResponse.SC_OK); + response.setContentLength(content.length); + response.setContentType(ContentType.APPLICATION_JSON.toString()); + response.getOutputStream().write(content); + + + } else { + logger.info("SL2.0 DataURL communication needs http header: '" + SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE + "'"); + throw new SL20Exception("sl20.06"); + + } + } } diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/DataURLServlet.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/DataURLServlet.java index 45861953..50c3b063 100644 --- a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/DataURLServlet.java +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/DataURLServlet.java @@ -93,11 +93,11 @@ public class DataURLServlet extends HttpServlet { if(jaxbObject.getValue() instanceof InfoboxReadResponseType) { InfoboxReadResponseType infoboxReadResponseType = (InfoboxReadResponseType)jaxbObject.getValue(); logger.info("Got InfoboxReadResponseType"); - PdfAsHelper.injectCertificate(request, response, infoboxReadResponseType, getServletContext()); + PdfAsHelper.injectCertificate(request, response, PdfAsHelper.getCertificate(infoboxReadResponseType), getServletContext()); } else if(jaxbObject.getValue() instanceof CreateCMSSignatureResponseType) { CreateCMSSignatureResponseType createCMSSignatureResponseType = (CreateCMSSignatureResponseType)jaxbObject.getValue(); logger.info("Got CreateCMSSignatureResponseType"); - PdfAsHelper.injectSignature(request, response, createCMSSignatureResponseType, getServletContext()); + PdfAsHelper.injectSignature(request, response, createCMSSignatureResponseType.getCMSSignature(), getServletContext()); } else if(jaxbObject.getValue() instanceof ErrorResponseType) { ErrorResponseType errorResponseType = (ErrorResponseType)jaxbObject.getValue(); logger.warn("SecurityLayer: " + errorResponseType.getErrorCode() + " " + errorResponseType.getInfo()); diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/ExternSignServlet.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/ExternSignServlet.java index 3cea5247..1d2ab14e 100644 --- a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/ExternSignServlet.java +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/ExternSignServlet.java @@ -354,7 +354,8 @@ public class ExternSignServlet extends HttpServlet { logger.debug("Starting signature creation with: " + connector); //IPlainSigner signer; - if (connector.equals("bku") || connector.equals("onlinebku") || connector.equals("mobilebku")) { + if (connector.equals("bku") || connector.equals("onlinebku") || connector.equals("mobilebku") + || connector.equals("sl20")) { // start asynchronous signature creation if(connector.equals("bku")) { @@ -372,6 +373,11 @@ public class ExternSignServlet extends HttpServlet { throw new PdfAsWebException("Invalid connector bku is not supported"); } } + if (connector.equals("sl20")) { + if(WebConfiguration.getSecurityLayer20URL() == null) { + throw new PdfAsWebException("Invalid connector bku is not supported"); + } + } PdfAsHelper.setStatisticEvent(request, response, statisticEvent); diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/JSONAPIServlet.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/JSONAPIServlet.java index 0cee185a..13d874e8 100644 --- a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/JSONAPIServlet.java +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/JSONAPIServlet.java @@ -119,7 +119,9 @@ public class JSONAPIServlet extends HttpServlet { connectorEnum = PDFASSignParameters.Connector.MOBILEBKU; } else if(PDFASSignParameters.Connector.ONLINEBKU.equalsName(connector)) { connectorEnum = PDFASSignParameters.Connector.ONLINEBKU; - } + } else if(PDFASSignParameters.Connector.SECLAYER20.equalsName(connector)) { + connectorEnum = PDFASSignParameters.Connector.SECLAYER20; + } if(connectorEnum == null) { throw new ServletException( @@ -212,6 +214,13 @@ public class JSONAPIServlet extends HttpServlet { "Invalid connector mobilebku is not supported"); } } + + if (PDFASSignParameters.Connector.SECLAYER20.equals(connectorEnum)) { + if (WebConfiguration.getSecurityLayer20URL() == null) { + throw new PdfAsWebException( + "Invalid connector mobilebku is not supported"); + } + } PdfAsHelper.startSignatureJson(request, response, getServletContext(), diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/SLDataURLServlet.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/SLDataURLServlet.java new file mode 100644 index 00000000..7ddf0a55 --- /dev/null +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/SLDataURLServlet.java @@ -0,0 +1,234 @@ +package at.gv.egiz.pdfas.web.servlets; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.annotation.MultipartConfig; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.jose4j.base64url.Base64Url; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; + +import at.gv.egiz.pdfas.lib.util.StreamUtils; +import at.gv.egiz.pdfas.web.config.WebConfiguration; +import at.gv.egiz.pdfas.web.helper.PdfAsHelper; +import at.gv.egiz.pdfas.web.sl20.JsonSecurityUtils; +import at.gv.egiz.pdfas.web.sl20.X509Utils; +import at.gv.egiz.sl20.data.VerificationResult; +import at.gv.egiz.sl20.exceptions.SL20Exception; +import at.gv.egiz.sl20.exceptions.SL20SecurityException; +import at.gv.egiz.sl20.exceptions.SLCommandoParserException; +import at.gv.egiz.sl20.utils.SL20Constants; +import at.gv.egiz.sl20.utils.SL20JSONExtractorUtils; + +@MultipartConfig +public class SLDataURLServlet extends HttpServlet { + + private static final Logger logger = LoggerFactory + .getLogger(SLDataURLServlet.class); + + private static final long serialVersionUID = 1L; + + /** + * @see HttpServlet#HttpServlet() + */ + public SLDataURLServlet() { + super(); + } + + /** + * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse + * response) + */ + protected void doGet(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + this.process(request, response); + } + + /** + * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse + * response) + */ + protected void doPost(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + this.process(request, response); + } + + protected void process(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + + JsonObject sl20ReqObj = null; + try { + if(!PdfAsHelper.checkDataUrlAccess(request)) { + throw new Exception("No valid dataURL access"); + } + + PdfAsHelper.setFromDataUrl(request); + + String sl20Result = request.getParameter(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM); + if (StringUtils.isEmpty(sl20Result)) { + //Workaround for SIC Handy-Signature, because it sends result in InputStream + String isReqInput = StreamUtils.readStream(request.getInputStream(), "UTF-8"); + if (StringUtils.isNotEmpty(isReqInput)) { + logger.info("Use SIC Handy-Signature work-around!"); + sl20Result = isReqInput.substring("slcommand=".length()); + + } else { + logger.info("NO SL2.0 commando or result FOUND."); + throw new SL20Exception("sl20.04", null); + } + + } + + logger.trace("Received SL2.0 result: " + sl20Result); + + //parse SL2.0 command/result into JSON + try { + JsonParser jsonParser = new JsonParser(); + JsonElement sl20Req = jsonParser.parse(Base64Url.decodeToUtf8String(sl20Result)); + sl20ReqObj = sl20Req.getAsJsonObject(); + + } catch (JsonSyntaxException e) { + logger.warn("SL2.0 command or result is NOT valid JSON.", e); + logger.debug("SL2.0 msg: " + sl20Result); + throw new SL20Exception("sl20.02", e); + + } + + //extract transactionId + String transactionId = SL20JSONExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_TRANSACTIONID, false); + if (StringUtils.isNotEmpty(transactionId)) + request.setAttribute(PdfAsHelper.PDF_SESSION_PREFIX + SL20Constants.SL20_TRANSACTIONID, transactionId); + + + //validate reqId with inResponseTo + String sl20ReqId = (String) request.getSession(false).getAttribute(PdfAsHelper.PDF_SESSION_PREFIX + SL20Constants.SL20_REQID); + String inRespTo = SL20JSONExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_INRESPTO, true); + if (sl20ReqId == null || !sl20ReqId.equals(inRespTo)) { + logger.info("SL20 'reqId': " + sl20ReqId + " does NOT match to 'inResponseTo':" + inRespTo); + throw new SL20SecurityException("SL20 'reqId': " + sl20ReqId + " does NOT match to 'inResponseTo':" + inRespTo); + } + + JsonSecurityUtils joseTools = JsonSecurityUtils.getInstance(); + if (!joseTools.isInitialized()) + joseTools = null; + + //validate signature + VerificationResult payLoadContainer = SL20JSONExtractorUtils.extractSL20PayLoad(sl20ReqObj, joseTools, + WebConfiguration.isSL20SigningRequired()); + + if ( (payLoadContainer.isValidSigned() == null || !payLoadContainer.isValidSigned())) { + if (WebConfiguration.isSL20SigningRequired()) { + logger.info("SL20 result from VDA was not valid signed"); + throw new SL20SecurityException("Signature on SL20 result NOT valid."); + + } else { + logger.warn("SL20 result from VDA is NOT valid signed, but signatures-verification is DISABLED by configuration!"); + + } + } + + //extract payloaf + JsonObject payLoad = payLoadContainer.getPayload(); + + //check response type + if (SL20JSONExtractorUtils.getStringValue( + payLoad, SL20Constants.SL20_COMMAND_CONTAINER_NAME, true) + .equals(SL20Constants.SL20_COMMAND_IDENTIFIER_GETCERTIFICATE)) { + logger.debug("Find " + SL20Constants.SL20_COMMAND_IDENTIFIER_GETCERTIFICATE + " result .... "); + + JsonElement getCertificateResult = SL20JSONExtractorUtils.extractSL20Result( + payLoad, joseTools, + WebConfiguration.isSL20EncryptionRequired()); + + //extract certificates + List 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 certs = new ArrayList(); + for (String certB64 : certsB64) + certs.add(new iaik.x509.X509Certificate(Base64.getDecoder().decode(certB64))); + + List sortedCerts = X509Utils.sortCertificates(certs); + logger.debug("Sorting of certificate completed. Select end-user certificate ... "); + PdfAsHelper.injectCertificate(request, response, Base64.getDecoder().decode(sortedCerts.get(0).getEncoded()), getServletContext()); + + } + + } else if (SL20JSONExtractorUtils.getStringValue( + payLoad, SL20Constants.SL20_COMMAND_CONTAINER_NAME, true) + .equals(SL20Constants.SL20_COMMAND_IDENTIFIER_CREATE_SIG_CADES)) { + logger.debug("Find " + SL20Constants.SL20_COMMAND_IDENTIFIER_CREATE_SIG_CADES + " result .... "); + + JsonElement getCertificateResult = SL20JSONExtractorUtils.extractSL20Result( + payLoad, joseTools, + WebConfiguration.isSL20EncryptionRequired()); + + //extract CAdES signature + String cadesSigB64 = SL20JSONExtractorUtils.getStringValue( + getCertificateResult.getAsJsonObject(), + SL20Constants.SL20_COMMAND_PARAM_CREATE_SIG_CADES_RESULT_SIGNATURE, + true); + + if (StringUtils.isEmpty(cadesSigB64)) { + logger.warn("SL20 'createCAdES' result contains NO signature"); + throw new SLCommandoParserException(); + } + + PdfAsHelper.injectSignature(request, response, Base64.getDecoder().decode(cadesSigB64), getServletContext()); + + } else { + logger.info("SL20 response is NOT a " + SL20Constants.SL20_COMMAND_IDENTIFIER_QUALIFIEDEID + " result"); + throw new SLCommandoParserException(); + + } + + } catch (Exception e) { + logger.warn("Error in DataURL Servlet. " , e); + PdfAsHelper.setSessionException(request, response, e.getMessage(), + e); + + if (PdfAsHelper.getFromDataUrl(request)) { + String errorUrl = PdfAsHelper.generateErrorURL(request, response); + try { + String transactionId = null; + if (sl20ReqObj != null) + transactionId = SL20JSONExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_TRANSACTIONID, false); + + PdfAsHelper.buildSL20RedirectResponse(request, response, transactionId, errorUrl); + + } catch (SL20Exception e1) { + logger.error("SL20 error-handling FAILED", e); + response.sendError(500, "Internal Server Error."); + + } + + } else + PdfAsHelper.gotoError(getServletContext(), request, response); + } + } +} diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/UIEntryPointServlet.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/UIEntryPointServlet.java index e8ac3658..73f8299c 100644 --- a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/UIEntryPointServlet.java +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/servlets/UIEntryPointServlet.java @@ -131,7 +131,8 @@ public class UIEntryPointServlet extends HttpServlet { // IPlainSigner signer; if (connector.equals(Connector.BKU) || connector.equals(Connector.ONLINEBKU) - || connector.equals(Connector.MOBILEBKU)) { + || connector.equals(Connector.MOBILEBKU) + || connector.equals(Connector.SECLAYER20)) { // start asynchronous signature creation if (connector.equals(Connector.BKU)) { @@ -154,6 +155,14 @@ public class UIEntryPointServlet extends HttpServlet { "Invalid connector mobilebku is not supported"); } } + + if (connector.equals(Connector.SECLAYER20)) { + if (WebConfiguration.getSecurityLayer20URL() == null) { + throw new PdfAsWebException( + "Invalid connector mobilebku is not supported"); + } + } + Map 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 trustedCerts = new ArrayList(); + + 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 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 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 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.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 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 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 InputStream, and + * closes the InputStream. + * + * @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 String[], starting with "0". + * + * @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 String[], starting with "0". +// * All the files in the directory are considered to be certificates. +// * +// * @param keyStoreType key store type +// * @param certDirURLString file URL of directory containing certificate filenames +// * @return key store created +// * @throws IOException thrown while reading the certificates from file +// * @throws GeneralSecurityException thrown while creating the key store +// */ +// public static KeyStore createKeyStoreFromCertificateDirectory( +// String keyStoreType, +// String certDirURLString) +// throws IOException, GeneralSecurityException { +// +// URL certDirURL = new URL(certDirURLString); +// String certDirname = certDirURL.getFile(); +// File certDir = new File(certDirname); +// String[] certFilenames = certDir.list(); +// String separator = +// (certDirname.endsWith(File.separator) ? "" : File.separator); +// for (int i = 0; i < certFilenames.length; i++) { +// certFilenames[i] = certDirname + separator + certFilenames[i]; +// } +// return createKeyStore(keyStoreType, certFilenames); +// } + + /** + * Loads an X509 certificate from file. + * @param certFilename filename + * @return the certificate loaded + * @throws IOException thrown while reading the certificate from file + * @throws GeneralSecurityException thrown while creating the certificate + */ + private static Certificate loadCertificate(String certFilename) + throws IOException, GeneralSecurityException { + + FileInputStream in = new FileInputStream(certFilename); + Certificate cert = new X509Certificate(in); + in.close(); + return cert; + } + + + /** + * Loads a keyStore without knowing the keyStore type + * @param keyStorePath URL to the keyStore + * @param password Password protecting the keyStore + * @return keyStore loaded + * @throws KeyStoreException thrown if keyStore cannot be loaded + * @throws FileNotFoundException + * @throws IOException + */ + public static KeyStore loadKeyStore(String keyStorePath, String password) throws KeyStoreException, IOException{ + + //InputStream is = new FileInputStream(keyStorePath); + URL keystoreURL = new URL(keyStorePath); + InputStream in = keystoreURL.openStream(); + InputStream isBuffered = new BufferedInputStream(in); + return loadKeyStore(isBuffered, password); + + } + + /** + * Loads a keyStore without knowing the keyStore type + * @param in input stream + * @param password Password protecting the keyStore + * @return keyStore loaded + * @throws KeyStoreException thrown if keyStore cannot be loaded + * @throws FileNotFoundException + * @throws IOException + */ +public static KeyStore loadKeyStore(InputStream is, String password) throws KeyStoreException, IOException{ + is.mark(1024*1024); + KeyStore ks = null; + try { + try { + ks = loadKeyStore(KEYSTORE_TYPE_PKCS12, is, password); + } catch (IOException e2) { + is.reset(); + ks = loadKeyStore(KEYSTORE_TYPE_JKS, is, password); + } + } catch(Exception e) { + e.printStackTrace(); + //throw new KeyStoreException(e); + } + return ks; + + } + + + + +} diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/SL20HttpBindingUtils.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/SL20HttpBindingUtils.java new file mode 100644 index 00000000..f5d6ff55 --- /dev/null +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/SL20HttpBindingUtils.java @@ -0,0 +1,47 @@ +package at.gv.egiz.pdfas.web.sl20; + +import java.io.IOException; +import java.io.StringWriter; +import java.net.URISyntaxException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ContentType; +import org.jose4j.base64url.Base64Url; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonObject; + +import at.gv.egiz.sl20.utils.SL20Constants; + +public class SL20HttpBindingUtils { + private static final org.slf4j.Logger log = LoggerFactory.getLogger(SL20HttpBindingUtils.class); + + public static void writeIntoResponse(HttpServletRequest request, HttpServletResponse response, JsonObject sl20Forward, String redirectURL) throws IOException, URISyntaxException { + //forward SL2.0 command + if (request.getHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE) != null && + request.getHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE).equals(SL20Constants.HTTP_HEADER_VALUE_NATIVE)) { + log.debug("Client request containts 'native client' header ... "); + StringWriter writer = new StringWriter(); + writer.write(sl20Forward.toString()); + final byte[] content = writer.toString().getBytes("UTF-8"); + response.setStatus(HttpServletResponse.SC_OK); + response.setContentLength(content.length); + response.setContentType(ContentType.APPLICATION_JSON.toString()); + response.getOutputStream().write(content); + + } else { + log.debug("Client request containts is no native client ... "); + URIBuilder clientRedirectURI = new URIBuilder(redirectURL); + clientRedirectURI.addParameter( + SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM, + Base64Url.encode(sl20Forward.toString().getBytes())); + response.setStatus(307); + response.setHeader("Location", clientRedirectURI.build().toString()); + + } + + } +} diff --git a/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/X509Utils.java b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/X509Utils.java new file mode 100644 index 00000000..391b8271 --- /dev/null +++ b/pdf-as-web/src/main/java/at/gv/egiz/pdfas/web/sl20/X509Utils.java @@ -0,0 +1,62 @@ +package at.gv.egiz.pdfas.web.sl20; + +import java.security.cert.X509Certificate; +import java.util.List; + +import javax.security.auth.x500.X500Principal; + +public class X509Utils { + + /** + * Sorts the Certificate Chain by IssuerDN and SubjectDN. The [0]-Element should be the Hostname, + * the last Element should be the Root Certificate. + * + * @param certs + * The first element must be the correct one. + * @return sorted Certificate Chain + */ + public static List sortCertificates( + List 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 @@ at.gv.egiz.pdfas.web.servlets.DataURLServlet + + SLDataURLServlet + SLDataURLServlet + + at.gv.egiz.pdfas.web.servlets.SLDataURLServlet + VisBlockServlet VisBlockServlet @@ -184,6 +190,10 @@ DataURLServlet /DataURL + + SLDataURLServlet + /DataURLSL20 + VerifyServlet /Verify 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) { + %> + + + + <% + } + %> + <% if (WebConfiguration.getKeystoreDefaultEnabled()) { %> -- cgit v1.2.3