/*******************************************************************************
* Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria
* PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
* joint initiative of the Federal Chancellery Austria 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.ws;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.jws.WebService;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.soap.MTOM;
import org.apache.commons.lang3.StringUtils;
import at.gv.egiz.pdfas.api.processing.CoreSignParams;
import at.gv.egiz.pdfas.api.processing.DocumentToSign;
import at.gv.egiz.pdfas.api.processing.PdfasSignRequest;
import at.gv.egiz.pdfas.api.processing.PdfasSignResponse;
import at.gv.egiz.pdfas.api.processing.SignedDocument;
import at.gv.egiz.pdfas.api.ws.PDFASBulkSignRequest;
import at.gv.egiz.pdfas.api.ws.PDFASBulkSignResponse;
import at.gv.egiz.pdfas.api.ws.PDFASSignParameters.Connector;
import at.gv.egiz.pdfas.api.ws.PDFASSignRequest;
import at.gv.egiz.pdfas.api.ws.PDFASSignResponse;
import at.gv.egiz.pdfas.api.ws.PDFASSigning;
import at.gv.egiz.pdfas.api.ws.PdfasGetMultipleRequest;
import at.gv.egiz.pdfas.api.ws.PdfasSignMultipleRequest;
import at.gv.egiz.pdfas.api.ws.PdfasSignMultipleResponse;
import at.gv.egiz.pdfas.api.ws.PdfasSignedDocument;
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.SignatureVerificationLevel;
import at.gv.egiz.pdfas.lib.api.verify.VerifyResult;
import at.gv.egiz.pdfas.web.config.WebConfiguration;
import at.gv.egiz.pdfas.web.filter.UserAgentFilter;
import at.gv.egiz.pdfas.web.helper.PdfAsHelper;
import at.gv.egiz.pdfas.web.stats.StatisticEvent;
import at.gv.egiz.pdfas.web.stats.StatisticEvent.Operation;
import at.gv.egiz.pdfas.web.stats.StatisticEvent.Source;
import at.gv.egiz.pdfas.web.stats.StatisticEvent.Status;
import at.gv.egiz.pdfas.web.stats.StatisticFrontend;
import at.gv.egiz.pdfas.web.store.RequestStore;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@MTOM
@WebService(endpointInterface = "at.gv.egiz.pdfas.api.ws.PDFASSigning")
public class PDFASSigningImpl implements PDFASSigning {
/*
* public byte[] signPDFDokument(byte[] inputDocument, PDFASSignParameters
* parameters) { checkSoapSignEnabled(); try { return
* PdfAsHelper.synchornousServerSignature(inputDocument, parameters); } catch
* (Throwable e) { logger.error("Server Signature failed.", e); if
* (WebConfiguration.isShowErrorDetails()) { throw new
* WebServiceException("Server Signature failed.", e); } else { throw new
* WebServiceException("Server Signature failed."); } } }
*/
@Override
public PDFASSignResponse signPDFDokument(PDFASSignRequest request) {
log.debug("Starting SOAP Sign Request");
checkSoapSignEnabled();
if (request == null) {
log.warn("SOAP Sign Request is null!");
return null;
}
// map request into internal data-structure
final PdfasSignRequest internalReq = buildOperationRequest(request);
final StatisticEvent statisticEvent = new StatisticEvent();
statisticEvent.setSource(Source.SOAP);
statisticEvent.setOperation(Operation.SIGN);
statisticEvent.setUserAgent(UserAgentFilter.getUserAgent());
statisticEvent.setProfileId(request.getParameters().getProfile());
statisticEvent.setDevice(request.getParameters().getConnector().toString());
statisticEvent.setStartNow();
PDFASSignResponse response = new PDFASSignResponse();
try {
if (request.getParameters().getConnector() == null) {
throw new WebServiceException(
"Invalid connector value!");
}
if (request.getParameters().getConnector().equals(Connector.MOA)
|| request.getParameters().getConnector()
.equals(Connector.JKS)) {
// perform technical signing process
final PdfasSignResponse internalResp = PdfAsHelper.synchronousServerSignature(internalReq);
// validate signatures
internalResp.getSignedPdfs().forEach(el -> validatePdfSignature(el, internalReq, statisticEvent));
// must be done later, because we should verify signed documents before
response = buildResponseFromInternalResult(internalResp, internalReq.getRequestID());
} else {
// Signatures with user interaction!!
final String id = RequestStore.getInstance().createNewStoreEntry(internalReq, statisticEvent);
if (id == null) {
throw new WebServiceException("Failed to store request");
}
final String userEntryURL = PdfAsHelper.generateUserEntryURL(id);
log.debug("Generated request store: " + id);
log.debug("Generated UI URL: " + userEntryURL);
if (userEntryURL == null) {
throw new WebServiceException(
"Failed to generate User Entry URL");
}
response.setRedirectUrl(userEntryURL);
}
} catch (final Throwable e) {
statisticEvent.setStatus(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);
log.warn("Error in Soap Service", e);
if (e.getCause() != null) {
response.setError(e.getCause().getMessage());
} else {
response.setError(e.getMessage());
}
} finally {
log.debug("Done SOAP Sign Request");
}
response.setRequestID(request.getRequestID());
return response;
}
@Override
public PDFASBulkSignResponse signPDFDokument(PDFASBulkSignRequest request) {
log.debug("Starting SOAP BulkSign Request");
checkSoapSignEnabled();
final List responses = new ArrayList<>();
if (request.getSignRequests() != null) {
for (int i = 0; i < request.getSignRequests().size(); i++) {
final PDFASSignResponse response = signPDFDokument(request
.getSignRequests().get(i));
if (response != null) {
responses.add(response);
}
}
final PDFASBulkSignResponse response = new PDFASBulkSignResponse();
response.setSignResponses(responses);
log.debug("Done SOAP Sign Request");
return response;
}
log.warn("Server Signature failed. [PDFASBulkSignRequest is NULL]");
if (WebConfiguration.isShowErrorDetails()) {
throw new WebServiceException("PDFASBulkSignRequest is NULL");
} else {
throw new WebServiceException("Server Signature failed.");
}
}
@Override
public PdfasSignMultipleResponse signPDFDokument(PdfasSignMultipleRequest request) {
log.debug("Starting SOAP Bulk-Sign Request");
checkSoapSignEnabled();
if (request == null) {
log.warn("SOAP Sign Request is null!");
return null;
}
// map request into internal data-structure
final PdfasSignRequest internalReq = buildOperationRequest(request);
final StatisticEvent statisticEvent = new StatisticEvent();
statisticEvent.setSource(Source.SOAP);
statisticEvent.setOperation(Operation.SIGNBULK);
statisticEvent.setUserAgent(UserAgentFilter.getUserAgent());
statisticEvent.setDevice(internalReq.getCoreParams().getConnector().toString());
statisticEvent.setStartNow();
PdfasSignMultipleResponse response = new PdfasSignMultipleResponse();
try {
if (internalReq.getCoreParams().getConnector() == null) {
throw new WebServiceException(
"Invalid connector value!");
}
if (internalReq.getCoreParams().getConnector().equals(Connector.MOA)
|| internalReq.getCoreParams().getConnector()
.equals(Connector.JKS)) {
// perform technical signing process
final PdfasSignResponse internalResp = PdfAsHelper.synchronousServerSignature(internalReq);
// validate signatures
internalResp.getSignedPdfs().forEach(el -> validatePdfSignature(el, internalReq, statisticEvent));
// must be done later, because we should verify signed documents before
response = buildMultiResponseFromInternalResult(internalResp, internalReq.getRequestID(), internalReq
.getCoreParams().getTransactionId());
} else {
// Signatures with user interaction!!
final String id = RequestStore.getInstance().createNewStoreEntry(internalReq, statisticEvent);
if (id == null) {
throw new WebServiceException("Failed to store request");
}
final String userEntryURL = PdfAsHelper.generateUserEntryURL(id);
log.debug("Generated request store: " + id);
log.debug("Generated UI URL: " + userEntryURL);
if (userEntryURL == null) {
throw new WebServiceException(
"Failed to generate User Entry URL");
}
response.setRedirectUrl(userEntryURL);
response.setRequestID(request.getRequestID());
}
} catch (final Throwable e) {
statisticEvent.setStatus(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);
log.warn("Error in Soap Service", e);
if (e.getCause() != null) {
response.setError(e.getCause().getMessage());
} else {
response.setError(e.getMessage());
}
} finally {
log.debug("Done SOAP Sign Request");
}
return response;
}
@Override
public PdfasSignMultipleResponse getSignedDokument(PdfasGetMultipleRequest request) {
log.debug("Starting SOAP Get-Signed Request");
checkSoapSignEnabled();
if (request == null) {
log.warn("SOAP Get-Signed Request is null!");
return null;
}
final PdfasSignMultipleResponse response = new PdfasSignMultipleResponse();
try {
if (StringUtils.isEmpty(request.getToken())) {
log.warn("SOAP Get-Signed Request contains NO token");
throw new WebServiceException("SOAP Get-Signed Request contains NO token");
}
final PdfasSignResponse result = RequestStore.getInstance().fetchStoreResponse(request.getToken());
if (result != null) {
response.setRequestID(result.getRequestId());
response.setTransactionId(result.getTransactionId());
response.setOutput(result.getSignedPdfs().stream()
.map(el -> {
PdfasSignedDocument out = new PdfasSignedDocument();
out.setFileName(el.getFileName());
out.setOutputData(el.getOutputData());
out.setVerificationResponse(el.getVerificationResponse());
return out;
})
.collect(Collectors.toList()));
} else {
log.warn("SOAP Get-Signed Request token is unknown or expired");
throw new WebServiceException("SOAP Get-Signed Request token is unknown or expired");
}
} catch (final Throwable e) {
log.warn("Error in Soap Service", e);
if (e.getCause() != null) {
response.setError(e.getCause().getMessage());
} else {
response.setError(e.getMessage());
}
} finally {
log.debug("Done SOAP Sign Request");
}
return response;
}
private PdfasSignRequest buildOperationRequest(PdfasSignMultipleRequest request) {
final PdfasSignRequest data = new PdfasSignRequest();
data.setRequestID(request.getRequestID());
data.setVerificationLevel(request.getVerificationLevel());
final CoreSignParams coreParams = new CoreSignParams();
coreParams.setSignatureBlockParameters(request.getSignatureBlockParameters());
coreParams.setConnector(request.getConnector());
coreParams.setKeyIdentifier(request.getKeyIdentifier());
coreParams.setOverrides(
request.getOverrides() != null ? request.getOverrides().getMap() : null);
coreParams.setPreprocessor(
request.getPreprocessor() != null ? request.getPreprocessor().getMap() : null);
coreParams.setInvokeErrorUrl(request.getInvokeErrorUrl());
coreParams.setInvokeTarget(request.getInvokeTarget());
coreParams.setInvokeUrl(request.getInvokeUrl());
coreParams.setTransactionId(request.getTransactionId());
data.setCoreParams(coreParams);
request.getInput().forEach(el -> {
final DocumentToSign document = new DocumentToSign();
document.setInputData(el.getInputData());
document.setPosition(el.getPosition());
document.setProfile(el.getProfile());
document.setQrCodeContent(el.getQrCodeContent());
document.setFileName(el.getFileName());
data.addDocumentToSign(document);
});
return data;
}
private PdfasSignMultipleResponse buildMultiResponseFromInternalResult(PdfasSignResponse internalResp,
String reqId, String transactionId) {
final PdfasSignMultipleResponse resp = new PdfasSignMultipleResponse();
resp.setRequestID(reqId);
resp.setTransactionId(transactionId);
resp.setOutput(
internalResp.getSignedPdfs().stream()
.map(el -> {
final PdfasSignedDocument out = new PdfasSignedDocument();
out.setFileName(el.getFileName());
out.setOutputData(el.getOutputData());
out.setVerificationResponse(el.getVerificationResponse());
return out;
})
.collect(Collectors.toList()));
return resp;
}
private void checkSoapSignEnabled() {
if (!WebConfiguration.getSoapSignEnabled()) {
throw new WebServiceException("Service disabled!");
}
}
private PdfasSignRequest buildOperationRequest(PDFASSignRequest request) {
final PdfasSignRequest data = new PdfasSignRequest();
data.setRequestID(request.getRequestID());
data.setVerificationLevel(request.getVerificationLevel());
final CoreSignParams coreParams = new CoreSignParams();
coreParams.setSignatureBlockParameters(request.getSignatureBlockParameters());
coreParams.setConnector(request.getParameters().getConnector());
coreParams.setKeyIdentifier(request.getParameters().getKeyIdentifier());
coreParams.setOverrides(
request.getParameters().getOverrides() != null ? request.getParameters().getOverrides().getMap()
: null);
coreParams.setPreprocessor(
request.getParameters().getPreprocessor() != null ? request.getParameters().getPreprocessor().getMap()
: null);
coreParams.setInvokeErrorUrl(request.getParameters().getInvokeErrorURL());
coreParams.setInvokeTarget(request.getParameters().getInvokeTarget());
coreParams.setInvokeUrl(request.getParameters().getInvokeURL());
coreParams.setTransactionId(request.getParameters().getTransactionId());
data.setCoreParams(coreParams);
final DocumentToSign document = new DocumentToSign();
document.setInputData(request.getInputData());
document.setPosition(request.getParameters().getPosition());
document.setProfile(request.getParameters().getProfile());
document.setQrCodeContent(request.getParameters().getQRCodeContent());
data.addDocumentToSign(document);
return data;
}
private PDFASSignResponse buildResponseFromInternalResult(PdfasSignResponse internalResp, String reqId) {
final PDFASSignResponse resp = new PDFASSignResponse();
resp.setRequestID(reqId);
resp.setSignedPDF(internalResp.getSignedPdfs().get(0).getOutputData());
resp.setVerificationResponse(internalResp.getSignedPdfs().get(0).getVerificationResponse());
return resp;
}
@SneakyThrows
private void validatePdfSignature(SignedDocument el, PdfasSignRequest request,
StatisticEvent statisticEvent) {
Map preProcessor = null;
if (request.getCoreParams().getPreprocessor() != null) {
preProcessor = request.getCoreParams().getPreprocessor();
}
VerifyResult verifyResult = null;
if (request.getVerificationLevel() != null &&
request.getVerificationLevel().equals(
VerificationLevel.FULL_CERT_PATH)) {
final List verResults = PdfAsHelper
.synchronousVerify(
el.getOutputData(),
-1,
SignatureVerificationLevel.FULL_VERIFICATION,
preProcessor);
if (verResults.size() < 1) {
throw new WebServiceException(
"Document verification failed! " + verResults.size());
}
verifyResult = verResults.get(verResults.size() - 1);
} else {
final List verResults = PdfAsHelper
.synchronousVerify(
el.getOutputData(),
-1,
SignatureVerificationLevel.INTEGRITY_ONLY_VERIFICATION,
preProcessor);
if (verResults.size() < 1) {
throw new WebServiceException(
"Document verification failed! " + verResults.size());
}
verifyResult = verResults.get(verResults.size() - 1);
}
if (verifyResult.getValueCheckCode().getCode() == 0) {
statisticEvent.setStatus(Status.OK);
statisticEvent.setEndNow();
statisticEvent.setTimestampNow();
statisticEvent.setFilesize(el.getOutputData().length);
StatisticFrontend.getInstance().storeEvent(statisticEvent);
statisticEvent.setLogged(true);
} else {
statisticEvent.setStatus(Status.ERROR);
statisticEvent.setErrorCode(verifyResult.getValueCheckCode().getCode());
statisticEvent.setEndNow();
statisticEvent.setTimestampNow();
statisticEvent.setFilesize(el.getOutputData().length);
StatisticFrontend.getInstance().storeEvent(statisticEvent);
statisticEvent.setLogged(true);
}
el.getVerificationResponse().setCertificateCode(
verifyResult.getCertificateCheck().getCode());
el.getVerificationResponse().setValueCode(
verifyResult.getValueCheckCode().getCode());
}
}