From d6f4b34eae2e977cdd0339fb17302976fdae0574 Mon Sep 17 00:00:00 2001 From: Jakob Heher Date: Tue, 4 Oct 2022 15:02:43 +0200 Subject: QR code handling --- pdf-over-gui/pom.xml | 5 + .../asit/pdfover/gui/bku/MobileBKUConnector.java | 76 +++++++++- .../pdfover/gui/bku/OLDMobileBKUConnector.java | 12 +- .../asit/pdfover/gui/bku/mobile/ATrustParser.java | 59 ++++++-- .../gui/composites/MobileBKUQRComposite.java | 5 +- .../gui/workflow/states/MobileBKUState.java | 154 ++++++++++++++------- .../pdfover/gui/workflow/states/SigningState.java | 12 +- .../at/asit/pdfover/signer/pdfas/PdfAs4Signer.java | 14 +- 8 files changed, 259 insertions(+), 78 deletions(-) diff --git a/pdf-over-gui/pom.xml b/pdf-over-gui/pom.xml index ee0b41b2..4cb083e3 100644 --- a/pdf-over-gui/pom.xml +++ b/pdf-over-gui/pom.xml @@ -49,6 +49,11 @@ gson 2.9.0 + + org.json + json + 20220320 + org.jsoup jsoup diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/MobileBKUConnector.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/MobileBKUConnector.java index 6f6e5301..14971ce1 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/MobileBKUConnector.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/MobileBKUConnector.java @@ -14,6 +14,7 @@ import javax.annotation.Nonnull; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; @@ -25,9 +26,12 @@ import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.NoHttpResponseException; +import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.json.JSONObject; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -172,13 +176,19 @@ public class MobileBKUConnector implements BkuSlConnector { return post; } - private static @Nonnull ClassicHttpRequest buildFormSubmit(@Nonnull ATrustParser.HTMLResult html) { + private static @Nonnull ClassicHttpRequest buildFormSubmit(@Nonnull ATrustParser.HTMLResult html, @Nonnull String submitButtonId) { HttpPost post = new HttpPost(html.formTarget); var builder = MultipartEntityBuilder.create(); for (var pair : html.iterateFormOptions()) builder.addTextBody(pair.getKey(), pair.getValue()); - + if (html.submitButtons.containsKey(submitButtonId)) { + var submitInfo = html.submitButtons.get(submitButtonId); + builder.addTextBody(submitInfo.name, submitInfo.value); + } else { + log.warn("Attempted to use submit button #{} which does not exist. Omitting.", submitButtonId); + } + post.setEntity(builder.build()); return post; } @@ -189,11 +199,65 @@ public class MobileBKUConnector implements BkuSlConnector { */ private @Nonnull ClassicHttpRequest presentResponseToUserAndReturnNextRequest(@Nonnull ATrustParser.HTMLResult html) throws UserCancelledException { if (html.usernamePasswordBlock != null) { - while ((this.credentials.username == null) || (this.credentials.password == null)) { - this.state.getCredentialsFromUserTo(this.credentials, null); + try { + while ((this.credentials.username == null) || (this.credentials.password == null)) { + this.state.getCredentialsFromUserTo(this.credentials, null); // TODO error message + } + html.usernamePasswordBlock.setUsernamePassword(this.credentials.username, this.credentials.password); + return buildFormSubmit(html, "Button_Identification"); + } catch (UserCancelledException e) { + return buildFormSubmit(html, "Button_Cancel"); + } + } + if (html.qrCodeBlock != null) { + try (final CloseableHttpClient httpClient = HttpClients.custom().disableRedirectHandling().build()) { + final HttpGet request = new HttpGet(html.qrCodeBlock.qrCodeURI); + boolean[] done = new boolean[1]; + done[0] = false; + Thread longPollThread = new Thread(() -> { + log.debug("longPollThread hello"); + while (!done[0]) { + try (final CloseableHttpResponse response = httpClient.execute(new HttpGet(html.qrCodeBlock.pollingURI))) { + JSONObject jsonResponse = new JSONObject(EntityUtils.toString(response.getEntity())); + if (jsonResponse.getBoolean("Fin")) + state.signalQRScanned(); + else if (jsonResponse.getBoolean("Wait")) + { + log.debug("longPollThread continue..."); + continue; + } + else if (jsonResponse.getBoolean("Error")) + state.signalQRScanned(); /* will trigger reload and find error; this is the same thing a-trust does */ + else { + log.warn("Unknown long poll response:\n{}", jsonResponse.toString(2)); + break; + } + } catch (NoHttpResponseException e) { + continue; + } catch (IOException | ParseException | IllegalStateException e) { + log.warn("QR code long polling exception", e); + /* sleep so we don't hammer a-trust too hard in case this goes wrong */ + try { Thread.sleep(5000); } catch (InterruptedException e2) {} + } + } + log.debug("longPollThread goodbye"); + }); + try { + longPollThread.start(); + MobileBKUState.QRResult result = this.state.showQRCode(html.qrCodeBlock.referenceValue, html.qrCodeBlock.qrCodeURI, null); + switch (result) { + case UPDATE: return new HttpGet(html.htmlDocument.baseUri()); + case TO_FIDO2: throw new IllegalStateException(); + case TO_SMS: throw new IllegalStateException(); + } + } finally { + done[0] = true; + request.abort(); + try { longPollThread.join(1000); } catch (InterruptedException e) {} + } + } catch (IOException e) { + log.warn("closing CloseableHttpClient threw exception", e); } - html.usernamePasswordBlock.setUsernamePassword(this.credentials.username, this.credentials.password); - return buildFormSubmit(html); } throw new IllegalStateException("No top-level block is set? Something has gone terribly wrong."); } diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/OLDMobileBKUConnector.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/OLDMobileBKUConnector.java index a71f37df..9703e602 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/OLDMobileBKUConnector.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/OLDMobileBKUConnector.java @@ -15,6 +15,9 @@ */ package at.asit.pdfover.gui.bku; +import java.io.IOException; +import java.net.URISyntaxException; + // Imports import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,6 +28,7 @@ import at.asit.pdfover.gui.bku.OLDmobile.ATrustStatus; import at.asit.pdfover.gui.workflow.states.MobileBKUState; import at.asit.pdfover.signer.BkuSlConnector; import at.asit.pdfover.signer.SignatureException; +import at.asit.pdfover.signer.UserCancelledException; import at.asit.pdfover.signer.pdfas.PdfAs4SLRequest; import at.asit.pdfover.signer.pdfas.PdfAs4SigningState; @@ -51,7 +55,7 @@ public class OLDMobileBKUConnector implements BkuSlConnector { * @see at.asit.pdfover.signer.BkuSlConnector#handleSLRequest(java.lang.String) */ @Override - public String handleSLRequest(PdfAs4SLRequest request) throws SignatureException { + public String handleSLRequest(PdfAs4SLRequest request) throws SignatureException, UserCancelledException { PdfAs4SigningState signingState = this.state.getSigningState(); signingState.signatureRequest = request; @@ -120,7 +124,11 @@ public class OLDMobileBKUConnector implements BkuSlConnector { boolean enterTAN = true; String responseData = null; if (status.qrCodeURL != null) { - this.state.showQR(); + try { + this.state.OLDshowQR(); + } catch (IOException | URISyntaxException e) { + throw new SignatureException(e); + } if ("cancel".equals(this.state.status.errorMessage)) throw new SignatureException(new IllegalStateException()); if (status.qrCodeURL == null) { diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustParser.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustParser.java index f452202d..890ffad1 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustParser.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustParser.java @@ -33,9 +33,13 @@ public class ATrustParser { log.debug("Tested for element {} -- not found.", selector); throw new ComponentParseFailed(); } - protected @Nonnull String getAttributeEnsureNotNull(@Nonnull String selector, @Nonnull String attribute) throws ComponentParseFailed { + protected @Nonnull org.jsoup.nodes.Element getElementEnsureNotNull(@Nonnull String selector) throws ComponentParseFailed { var elm = this.htmlDocument.selectFirst(selector); if (elm == null) { log.warn("Expected element not found in response: {}", selector); throw new ComponentParseFailed(); } + return elm; + } + protected @Nonnull String getAttributeEnsureNotNull(@Nonnull String selector, @Nonnull String attribute) throws ComponentParseFailed { + var elm = getElementEnsureNotNull(selector); if (!elm.hasAttr(attribute)) { log.warn("Element {} is missing expected attribute '{}'.", selector, attribute); throw new ComponentParseFailed(); } return ISNOTNULL(elm.attr(attribute)); } @@ -55,6 +59,7 @@ public class ATrustParser { public static class UsernamePasswordBlock extends TopLevelFormBlock { private @Nonnull String usernameKey; private @Nonnull String passwordKey; + public @CheckForNull String errorMessage; public void setUsernamePassword(String username, String password) { formOptions.put(usernameKey, username); formOptions.put(passwordKey, password); @@ -65,22 +70,38 @@ public class ATrustParser { abortIfElementMissing("#handynummer"); this.usernameKey = getAttributeEnsureNotNull("#handynummer", "name"); this.passwordKey = getAttributeEnsureNotNull("#signaturpasswort", "name"); - - /* remove unused submit buttons */ - // TODO: we should generalize this somehow for input type="submit" - // TODO: do we maybe want to use the actual cancel button? - formOptions.remove(getAttributeEnsureNotNull("#Button_Cancel", "name")); - formOptions.remove(getAttributeEnsureNotNull("#Button_localBku", "name")); } } public static class QRCodeBlock extends TopLevelFormBlock { + public @Nonnull String referenceValue; public @Nonnull URI qrCodeURI; + public @Nonnull URI pollingURI; + public @Nullable String errorMessage; private QRCodeBlock(@Nonnull org.jsoup.nodes.Document htmlDocument, @Nonnull Map formOptions) throws ComponentParseFailed { super(htmlDocument, formOptions); abortIfElementMissing("#qrimage"); + + this.referenceValue = ISNOTNULL(getElementEnsureNotNull("#vergleichswert").ownText()); this.qrCodeURI = getURIAttributeEnsureNotNull("#qrimage", "abs:src"); + + var pollingScriptElm = getElementEnsureNotNull("#jsLongPoll script"); + String pollingScript = pollingScriptElm.data(); + int startIdx = pollingScript.indexOf("qrpoll(\""); + if (startIdx < 0) { log.warn("Failed to find 'qrpoll(\"' in jsLongPoll script:\n{}", pollingScript); throw new ComponentParseFailed(); } + startIdx += 8; + + int endIdx = pollingScript.indexOf("\");", startIdx); + if (endIdx < 0) { log.warn("Failed to find qrpoll terminator '\");' in jsLongPoll script:\n{}", pollingScript); throw new ComponentParseFailed(); } + + String pollingUriString = pollingScript.substring(startIdx, endIdx); + try { + this.pollingURI = ISNOTNULL(new URI(pollingScriptElm.baseUri()).resolve(pollingUriString)); + } catch (URISyntaxException e) { + log.warn("URI '{}' could not be parsed", pollingUriString); + throw new ComponentParseFailed(); + } } } @@ -104,6 +125,16 @@ public class ATrustParser { public final @Nonnull URI formTarget; public final @Nonnull Map formOptions = new HashMap<>(); + public static class NameValuePair { + public final @Nonnull String name; + public final @Nonnull String value; + public NameValuePair(@Nonnull String n, @Nonnull String v) { name = n; value = v; } + } + /** + * map: id -> (name, value) + */ + public final @Nonnull Map submitButtons = new HashMap<>(); + public @Nonnull Iterable> iterateFormOptions() { return ISNOTNULL(formOptions.entrySet()); } /* optional mode switch links (any number may or may not be null) */ @@ -182,9 +213,17 @@ public class ATrustParser { for (var input : mainForm.select("input")) { String name = input.attr("name"); - if (name.isEmpty()) - continue; - this.formOptions.put(name, input.attr("value")); + + /* special handling for submit inputs, they only get sent if they are "clicked" */ + if ("submit".equals(input.attr("type"))) { + if (name.isEmpty()) + this.submitButtons.put(input.attr("id"), null); + else + this.submitButtons.put(input.attr("id"), new NameValuePair(name, ISNOTNULL(input.attr("value")))); + } else { + if (!name.isEmpty()) + this.formOptions.put(name, input.attr("value")); + } } this.fido2Link = getHrefIfExists("#FidoButton"); diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUQRComposite.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUQRComposite.java index 1a1a10ac..3f1aa04d 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUQRComposite.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUQRComposite.java @@ -17,6 +17,7 @@ package at.asit.pdfover.gui.composites; // Imports import java.awt.Desktop; +import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.URI; @@ -222,13 +223,13 @@ public class MobileBKUQRComposite extends StateComposite { * @param qrcode * the qrcode to set */ - public void setQR(InputStream qrcode) { + public void setQR(byte[] qrcode) { if (qrcode == null) { setErrorMessage(Messages.getString("error.FailedToLoadQRCode")); return; } try { - this.currentQRImage = new ImageData(qrcode); + this.currentQRImage = new ImageData(new ByteArrayInputStream(qrcode)); } catch (SWTException e) { log.warn("Failed to load QR code", e); setErrorMessage(Messages.getString("error.FailedToLoadQRCode")); diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/MobileBKUState.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/MobileBKUState.java index 26be4626..9c3fc807 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/MobileBKUState.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/MobileBKUState.java @@ -15,8 +15,11 @@ */ package at.asit.pdfover.gui.workflow.states; +import java.io.IOException; import java.io.InputStream; import java.net.ConnectException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.UnknownHostException; import java.util.Timer; import java.util.TimerTask; @@ -31,6 +34,12 @@ import at.asit.pdfover.signer.SignatureException; import at.asit.pdfover.signer.UserCancelledException; import at.asit.pdfover.signer.pdfas.PdfAs4SigningState; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.slf4j.Logger; @@ -220,8 +229,7 @@ public class MobileBKUState extends State { } public void getCredentialsFromUserTo(@Nonnull UsernameAndPassword credentials, @Nullable String errorMessage) throws UserCancelledException { - boolean[] cancelState = new boolean[1]; - Display.getDefault().syncExec(() -> { + Display.getDefault().syncCall(() -> { MobileBKUEnterNumberComposite ui = this.getMobileBKUEnterNumberComposite(); if (!ui.userAck) { // We need number and password => show UI! @@ -252,10 +260,10 @@ public class MobileBKUState extends State { if (!(ui.userCancel && ui.isRememberPassword())) /* don't allow "remember" to be enabled via cancel button */ getStateMachine().configProvider.setRememberMobilePasswordPersistent(ui.isRememberPassword()); - cancelState[0] = ui.userCancel; - ui.userCancel = false; - if (cancelState[0]) - return; + if (ui.userCancel) { + ui.userCancel = false; + throw new UserCancelledException(); + } // user hit ok ui.userAck = false; @@ -266,9 +274,9 @@ public class MobileBKUState extends State { // show waiting composite getStateMachine().display(this.getWaitingComposite()); + + return true; /* dummy return for lambda type deduction */ }); - if (cancelState[0]) - throw new UserCancelledException(); } /** @@ -342,46 +350,45 @@ public class MobileBKUState extends State { }); } + public enum QRResult { + /* the user has pressed the FIDO2 button */ + TO_FIDO2, + /* the user has pressed the SMS button */ + TO_SMS, + /* signalQRScanned has been called; this indicates that we should refresh the page */ + UPDATE + }; + /** - * Show QR code + * start showing the QR code at the indicated URI + * this method will block until the QR code state completes + * (due to QR code being scanned, or the user pressing a button) + *

+ * it is the responsibility of the caller to perform AJAX long polling + * @return */ - public void showQR() { - final ATrustStatus status = this.status; - final ATrustHandler handler = this.handler; - - final Timer checkDone = new Timer(); - checkDone.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - // ping signature page to see if code has been scanned - try { - String resp = handler.getSignaturePage(); - if (handler.handleQRResponse(resp)) { - log.debug("Signature page response: " + resp); - getMobileBKUQRComposite().setDone(true); - Display display = getStateMachine(). - getMainShell().getDisplay(); - display.wake(); - checkDone.cancel(); - } - Display.getDefault().wake(); - } catch (Exception e) { - log.error("Error getting signature page", e); - } + public QRResult showQRCode(@Nonnull String referenceValue, @Nonnull URI qrCodeURI, @Nullable String errorMessage) throws UserCancelledException { + byte[] qrCode; + try (final CloseableHttpClient httpClient = HttpClients.createDefault()) { + try (final CloseableHttpResponse response = httpClient.execute(new HttpGet(qrCodeURI))) { + qrCode = EntityUtils.toByteArray(response.getEntity()); } - }, 0, 5000); - - Display.getDefault().syncExec(() -> { + } catch (IOException e) { + log.warn("Failed to load QR code."); + qrCode = null; + } + + final byte[] qrCodeCopy = qrCode; /* because java is silly */ + return Display.getDefault().syncCall(() -> { MobileBKUQRComposite qr = getMobileBKUQRComposite(); + qr.setUserCancel(false); + qr.setUserSMS(false); + qr.setDone(false); qr.setRefVal(status.refVal); qr.setSignatureData(status.signatureDataURL); qr.setErrorMessage(status.errorMessage); - InputStream qrcode = handler.getQRCode(); - if (qrcode == null) { - this.threadException = new Exception(Messages.getString("error.FailedToLoadQRCode")); - } - qr.setQR(qrcode); + qr.setQR(qrCodeCopy); getStateMachine().display(qr); Display display = getStateMachine().getMainShell().getDisplay(); @@ -391,28 +398,69 @@ public class MobileBKUState extends State { } } - checkDone.cancel(); + getStateMachine().display(this.getWaitingComposite()); if (qr.isUserCancel()) { - qr.setUserCancel(false); clearRememberedPassword(); - status.errorMessage = "cancel"; - return; + throw new UserCancelledException(); } - if (qr.isUserSMS()) { - qr.setUserSMS(false); - status.qrCodeURL = null; - } + if (qr.isUserSMS()) + return QRResult.TO_SMS; if (qr.isDone()) - qr.setDone(false); - - // show waiting composite - getStateMachine().display(this.getWaitingComposite()); + return QRResult.UPDATE; + + throw new RuntimeException("unexpected display wake"); }); } + /** + * indicate that the long polling operation completed + * (any ongoing showQRCode call will then return) + */ + public void signalQRScanned() { + getMobileBKUQRComposite().setDone(true); + Display display = getStateMachine(). + getMainShell().getDisplay(); + display.wake(); + } + + /** + * Show QR code + * @throws URISyntaxException + * @throws UserCancelledException + * @throws IOException + */ + public void OLDshowQR() throws IOException, UserCancelledException, URISyntaxException { + final ATrustStatus status = this.status; + final ATrustHandler handler = this.handler; + + final Timer checkDone = new Timer(); + checkDone.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + // ping signature page to see if code has been scanned + try { + String resp = handler.getSignaturePage(); + if (handler.handleQRResponse(resp)) { + log.debug("Signature page response: " + resp); + checkDone.cancel(); + signalQRScanned(); + } + Display.getDefault().wake(); + } catch (Exception e) { + log.error("Error getting signature page", e); + } + } + }, 0, 5000); + + QRResult result = showQRCode(status.refVal, new URI(status.baseURL).resolve(status.qrCodeURL), status.errorMessage); + checkDone.cancel(); + if (result == QRResult.TO_SMS) + status.qrCodeURL = null; + } + /** * This composite notifies the user to open the signature-app */ @@ -569,7 +617,7 @@ public class MobileBKUState extends State { public void run() { this.signingState = getStateMachine().status.signingState; - this.signingState.bkuConnector = new OLDMobileBKUConnector(this); + this.signingState.bkuConnector = new MobileBKUConnector(this); this.signingState.useBase64Request = false; if (this.threadException != null) { diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/SigningState.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/SigningState.java index b9bdc917..169aefb8 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/SigningState.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/SigningState.java @@ -30,6 +30,7 @@ import at.asit.pdfover.commons.Messages; import at.asit.pdfover.gui.workflow.StateMachine; import at.asit.pdfover.gui.workflow.Status; import at.asit.pdfover.signer.SignatureException; +import at.asit.pdfover.signer.UserCancelledException; import at.asit.pdfover.signer.pdfas.PdfAs4Signer; /** @@ -108,11 +109,14 @@ public class SigningState extends State { if (cause instanceof ConnectException) message += ": " + cause.getMessage(); if (cause instanceof IllegalStateException) { - // Dummy exception - don't display error, go back to BKU Selection - this.setNextState(new BKUSelectionState(getStateMachine())); - return; + // TODO legacy hack + this.threadException = new UserCancelledException(); } - + } + if (this.threadException instanceof UserCancelledException) { + // don't display error, go back to BKU Selection + this.setNextState(new BKUSelectionState(getStateMachine())); + return; } // if we have gotten to this point, this is an actual exception diff --git a/pdf-over-signer/src/main/java/at/asit/pdfover/signer/pdfas/PdfAs4Signer.java b/pdf-over-signer/src/main/java/at/asit/pdfover/signer/pdfas/PdfAs4Signer.java index 80a7e14d..c7be135f 100644 --- a/pdf-over-signer/src/main/java/at/asit/pdfover/signer/pdfas/PdfAs4Signer.java +++ b/pdf-over-signer/src/main/java/at/asit/pdfover/signer/pdfas/PdfAs4Signer.java @@ -11,8 +11,10 @@ import at.asit.pdfover.signer.ByteArrayDocumentSource; import at.asit.pdfover.signer.SignResult; import at.asit.pdfover.signer.SignatureException; import at.asit.pdfover.signer.SignaturePosition; +import at.asit.pdfover.signer.UserCancelledException; import at.gv.egiz.pdfas.common.exceptions.PDFASError; import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.common.exceptions.SLPdfAsException; import at.gv.egiz.pdfas.lib.api.ByteArrayDataSource; import at.gv.egiz.pdfas.lib.api.Configuration; import at.gv.egiz.pdfas.lib.api.IConfigurationConstants; @@ -104,7 +106,7 @@ public class PdfAs4Signer { } } - public static SignResult sign(PdfAs4SigningState state) throws SignatureException { + public static SignResult sign(PdfAs4SigningState state) throws SignatureException, UserCancelledException { try { if (state == null) { throw new SignatureException("Incorrect SigningState!"); @@ -154,6 +156,16 @@ public class PdfAs4Signer { return result; } } catch (PdfAsException | PDFASError e) { + { // TODO hack around pdf-as not handling this error code properly cf. #124 + Throwable rootCause = e; + while (rootCause.getCause() != null) + rootCause = rootCause.getCause(); + try { /* error code 6001 is user cancellation */ + if (((SLPdfAsException)rootCause).getMessage().startsWith("6001 :")) + throw new UserCancelledException(); + } catch (ClassCastException e2) { /* fall through to wrapped throw */} + } + throw new SignatureException(e); } } -- cgit v1.2.3