From f39ab43fc0120b7fa97028d40acd7851de8d4a99 Mon Sep 17 00:00:00 2001 From: Jakob Heher Date: Thu, 24 Nov 2022 14:14:37 +0100 Subject: Repository moved to GitHub: https://github.com/a-sit/pdf-over --- .../gui/workflow/states/MobileBKUState.java | 668 --------------------- 1 file changed, 668 deletions(-) delete mode 100644 pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/MobileBKUState.java (limited to 'pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/MobileBKUState.java') 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 deleted file mode 100644 index d858c067..00000000 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/MobileBKUState.java +++ /dev/null @@ -1,668 +0,0 @@ -/* - * Copyright 2012 by A-SIT, Secure Information Technology Center Austria - * - * 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://joinup.ec.europa.eu/software/page/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. - */ -package at.asit.pdfover.gui.workflow.states; - -import java.io.IOException; -import java.net.ConnectException; -import java.net.URI; -import java.net.UnknownHostException; - -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -// Imports -import at.asit.pdfover.signer.UserCancelledException; -import at.asit.pdfover.signer.pdfas.PdfAs4SigningState; -import at.asit.webauthn.PublicKeyCredential; -import at.asit.webauthn.responsefields.AuthenticatorAssertionResponse; - -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.io.entity.EntityUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Display; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import at.asit.pdfover.gui.MainWindow.Buttons; -import at.asit.pdfover.gui.MainWindowBehavior; -import at.asit.pdfover.gui.bku.MobileBKUConnector; -import at.asit.pdfover.gui.composites.WaitingComposite; -import at.asit.pdfover.gui.composites.mobilebku.MobileBKUEnterNumberComposite; -import at.asit.pdfover.gui.composites.mobilebku.MobileBKUEnterTANComposite; -import at.asit.pdfover.gui.composites.mobilebku.MobileBKUFido2Composite; -import at.asit.pdfover.gui.composites.mobilebku.MobileBKUFingerprintComposite; -import at.asit.pdfover.gui.composites.mobilebku.MobileBKUQRComposite; -import at.asit.pdfover.gui.composites.mobilebku.WaitingForAppComposite; -import at.asit.pdfover.gui.controls.Dialog.BUTTONS; -import at.asit.pdfover.gui.controls.Dialog.ICON; -import at.asit.pdfover.gui.controls.Dialog; -import at.asit.pdfover.gui.controls.ErrorDialog; -import at.asit.pdfover.commons.Messages; -import at.asit.pdfover.gui.workflow.StateMachine; - -import static at.asit.pdfover.commons.Constants.ISNOTNULL; - -/** - * Logical state for performing the BKU Request to the A-Trust Mobile BKU - */ -public class MobileBKUState extends State { - static final Logger log = LoggerFactory.getLogger(MobileBKUState.class); - - PdfAs4SigningState signingState; - - public Exception threadException = null; - - public MobileBKUState(StateMachine stateMachine) { - super(stateMachine); - } - - MobileBKUEnterTANComposite mobileBKUEnterTANComposite = null; - - WaitingForAppComposite waitingForAppComposite = null; - WaitingForAppComposite getWaitingForAppComposite() { - if (this.waitingForAppComposite == null) { - this.waitingForAppComposite = getStateMachine() - .createComposite(WaitingForAppComposite.class, SWT.RESIZE, this); - } - - return this.waitingForAppComposite; - } - - WaitingComposite waitingComposite = null; - WaitingComposite getWaitingComposite() { - if (this.waitingComposite == null) { - this.waitingComposite = getStateMachine() - .createComposite(WaitingComposite.class, SWT.RESIZE, this); - } - - return this.waitingComposite; - } - - MobileBKUEnterTANComposite getMobileBKUEnterTANComposite() { - if (this.mobileBKUEnterTANComposite == null) { - this.mobileBKUEnterTANComposite = getStateMachine() - .createComposite(MobileBKUEnterTANComposite.class, SWT.RESIZE, this); - } - - return this.mobileBKUEnterTANComposite; - } - - MobileBKUQRComposite mobileBKUQRComposite = null; - MobileBKUQRComposite getMobileBKUQRComposite() { - if (this.mobileBKUQRComposite == null) { - this.mobileBKUQRComposite = getStateMachine() - .createComposite(MobileBKUQRComposite.class, SWT.RESIZE, this); - } - - return this.mobileBKUQRComposite; - } - - MobileBKUEnterNumberComposite mobileBKUEnterNumberComposite = null; - MobileBKUEnterNumberComposite getMobileBKUEnterNumberComposite() { - if (this.mobileBKUEnterNumberComposite == null) { - this.mobileBKUEnterNumberComposite = getStateMachine() - .createComposite(MobileBKUEnterNumberComposite.class, SWT.RESIZE, this); - } - - return this.mobileBKUEnterNumberComposite; - } - - MobileBKUFingerprintComposite mobileBKUFingerprintComposite = null; - MobileBKUFingerprintComposite getMobileBKUFingerprintComposite() { - if (this.mobileBKUFingerprintComposite == null) { - this.mobileBKUFingerprintComposite = getStateMachine() - .createComposite(MobileBKUFingerprintComposite.class, SWT.RESIZE, this); - } - - return this.mobileBKUFingerprintComposite; - } - - MobileBKUFido2Composite mobileBKUFido2Composite = null; - MobileBKUFido2Composite getMobileBKUFido2Composite() { - if (this.mobileBKUFido2Composite == null) { - this.mobileBKUFido2Composite = getStateMachine() - .createComposite(MobileBKUFido2Composite.class, SWT.RESIZE, this); - } - - return this.mobileBKUFido2Composite; - } - - /** - * @return the signingState - */ - public PdfAs4SigningState getSigningState() { - return this.signingState; - } - - /** - * Display an error message - * - * @param e - * the exception - */ - public void displayError(Exception e) { - String message = null; - if (e instanceof UnknownHostException) - { - log.error("Failed to resolve hostname", e); - message = Messages.formatString("error.CouldNotResolveHostname", e.getMessage()); - } else if (e instanceof ConnectException) { - log.error("Failed to connect", e); - message = Messages.formatString("error.FailedToConnect", e.getMessage()); - } else { - message = Messages.getString("error.Unexpected"); - log.error(message, e); - String errormsg = e.getLocalizedMessage(); - if (errormsg != null && !errormsg.isEmpty()) - message += ": " + errormsg; - } - displayError(message); - } - - /** - * Display an error message - * - * @param message - * the error message - */ - public void displayError(final String message) { - log.error(message); - Display.getDefault().syncExec(() -> { - ErrorDialog error = new ErrorDialog(getStateMachine().getMainShell(), message, BUTTONS.OK); - error.open(); - }); - } - - public void showInformationMessage(final @Nonnull String message) throws UserCancelledException { - Display.getDefault().syncCall(() -> { - Dialog dialog = new Dialog(getStateMachine().getMainShell(), Messages.getString("common.info"), message, BUTTONS.OK, ICON.INFORMATION); - int result = dialog.open(); - if (result == SWT.CANCEL) - throw new UserCancelledException(); - return true; /* dummy return to keep java happy */ - }); - } - - /** - * Show an error message to the user with "retry" or "cancel" as options - * returns normally on "retry", throws UserCancelledException on "cancel" - */ - public void showRecoverableError(final @Nonnull String errorMessage) throws UserCancelledException { - Display.getDefault().syncCall(() -> { - ErrorDialog error = new ErrorDialog(getStateMachine().getMainShell(), errorMessage, BUTTONS.RETRY_CANCEL); - int result = error.open(); - if (result == SWT.CANCEL) - throw new UserCancelledException(); - return true; /* dummy return */ - }); - } - - /** - * Show an error message to the user with only an "ok" option; - * throws UserCancelledException afterwards - */ - public void showUnrecoverableError(final @Nonnull String errorMessage) throws UserCancelledException { - Display.getDefault().syncCall(() -> { - ErrorDialog error = new ErrorDialog(getStateMachine().getMainShell(), errorMessage, BUTTONS.OK); - error.open(); - throw new UserCancelledException(); - }); - } - - public static class UsernameAndPassword { - public @CheckForNull String username; - public @CheckForNull String password; - public UsernameAndPassword() {} - public UsernameAndPassword(@Nullable String u, @Nullable String p) { this.username = u; this.password = p; } - } - public @Nonnull UsernameAndPassword getRememberedCredentials() { - UsernameAndPassword r = new UsernameAndPassword(); - storeRememberedCredentialsTo(r); - return r; - } - public void storeRememberedCredentialsTo(@Nonnull UsernameAndPassword output) { - output.username = getStateMachine().configProvider.getDefaultMobileNumber(); - output.password = getStateMachine().configProvider.getDefaultMobilePassword(); - } - - public void rememberCredentialsIfNecessary(@Nullable String username, @Nullable String password) { - if (getStateMachine().configProvider.getRememberMobilePassword()) - { - getStateMachine().configProvider.setDefaultMobileNumberPersistent(username); - getStateMachine().configProvider.setDefaultMobilePasswordOverlay(password); - } - } - public void rememberCredentialsIfNecessary(@Nonnull UsernameAndPassword credentials) { - rememberCredentialsIfNecessary(credentials.username, credentials.password); - } - - public void clearRememberedPassword() { - getStateMachine().configProvider.setDefaultMobilePasswordOverlay(null); - } - - public @Nonnull UsernameAndPassword getCredentialsFromUser(@Nullable String currentUsername, @Nullable String errorMessage) throws UserCancelledException { - UsernameAndPassword r = new UsernameAndPassword(currentUsername, null); - getCredentialsFromUserTo(r, errorMessage); - return r; - } - - private void updateRememberPasswordSetting(boolean enabled, boolean allowEnabling) { - final var config = getStateMachine().configProvider; - if (enabled == config.getRememberMobilePassword()) /* nothing to do here */ - return; - if (enabled && !allowEnabling) /* do not allow "cancel" to set the remember checkbox */ - return; - config.setRememberMobilePasswordPersistent(enabled); - if (!enabled) { /* clear remembered info */ - config.setDefaultMobileNumberPersistent(null); - config.setDefaultMobilePasswordOverlay(null); - } - } - - public void getCredentialsFromUserTo(@Nonnull UsernameAndPassword credentials, @Nullable String errorMessage) throws UserCancelledException { - Display.getDefault().syncCall(() -> { - MobileBKUEnterNumberComposite ui = this.getMobileBKUEnterNumberComposite(); - - if (!ui.userAck) { // We need number and password => show UI! - - if (errorMessage != null) - ui.setErrorMessage(errorMessage); - else - ui.setErrorMessage(Messages.getString("mobileBKU.aTrustDisclaimer")); - - if ((ui.getMobileNumber() == null) || ui.getMobileNumber().isEmpty()) { - // set possible phone number - ui.setMobileNumber(credentials.username); - } - - ui.setRememberPassword(getStateMachine().configProvider.getRememberMobilePassword()); - - ui.enableButton(); - getStateMachine().display(ui); - - Display display = getStateMachine().getMainShell().getDisplay(); - while (!ui.userAck && !ui.userCancel) { - if (!display.readAndDispatch()) { - display.sleep(); - } - } - } - - updateRememberPasswordSetting(ui.isRememberPassword(), !ui.userCancel); - - if (ui.userCancel) { - ui.userCancel = false; - throw new UserCancelledException(); - } - - // user hit ok - ui.userAck = false; - - // get number and password from UI - credentials.username = ui.getMobileNumber(); - credentials.password = ui.getMobilePassword(); - - // show waiting composite - getStateMachine().display(this.getWaitingComposite()); - - return true; /* dummy return for lambda type deduction */ - }); - } - - public static class SMSTanResult { - public static enum ResultType { TO_FIDO2, SMSTAN }; - public final @Nonnull ResultType type; - public final @CheckForNull String smsTan; - - private SMSTanResult(@Nullable String smsTan) { this.type = ResultType.SMSTAN; this.smsTan = smsTan; } - private SMSTanResult(@Nonnull ResultType type) { this.type = type; this.smsTan = null; } - } - - public @Nonnull SMSTanResult getSMSTanFromUser(final @Nonnull String referenceValue, final @Nullable URI signatureDataURI, final boolean showFido2, final @Nullable String errorMessage) throws UserCancelledException { - return ISNOTNULL(Display.getDefault().syncCall(() -> { - MobileBKUEnterTANComposite tan = getMobileBKUEnterTANComposite(); - - tan.reset(); - tan.setRefVal(referenceValue); - tan.setSignatureDataURI(signatureDataURI); - tan.setErrorMessage(errorMessage); - tan.setFIDO2Enabled(showFido2); - getStateMachine().display(tan); - - Display display = getStateMachine().getMainShell().getDisplay(); - while (!tan.isDone()) { - if (!display.readAndDispatch()) { - display.sleep(); - } - } - getStateMachine().display(getWaitingComposite()); - - if (tan.isUserCancel()) - throw new UserCancelledException(); - - if (tan.isUserFido2()) - return new SMSTanResult(SMSTanResult.ResultType.TO_FIDO2); - - return new SMSTanResult(tan.getTan()); - })); - } - - /** - * start showing the QR code at the indicated URI - * this method will return immediately */ - public void showQRCode(final @Nonnull String referenceValue, @Nonnull URI qrCodeURI, @Nullable URI signatureDataURI, final boolean showSmsTan, final boolean showFido2, final @Nullable String errorMessage) { - byte[] qrCode; - try (final CloseableHttpClient httpClient = HttpClients.createDefault()) { - try (final CloseableHttpResponse response = httpClient.execute(new HttpGet(qrCodeURI))) { - qrCode = EntityUtils.toByteArray(response.getEntity()); - } - } catch (IOException e) { - log.warn("Failed to load QR code."); - qrCode = null; - } - - final byte[] qrCodeCopy = qrCode; /* because java is silly */ - Display.getDefault().syncExec(() -> { - MobileBKUQRComposite qr = getMobileBKUQRComposite(); - qr.reset(); - - qr.setRefVal(referenceValue); - qr.setSignatureDataURI(signatureDataURI); - qr.setErrorMessage(errorMessage); - qr.setQR(qrCodeCopy); - qr.setSMSEnabled(showSmsTan); - qr.setFIDO2Enabled(showFido2); - getStateMachine().display(qr); - }); - } - - 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 - }; - - public @Nonnull QRResult waitForQRCodeResult() throws UserCancelledException { - return ISNOTNULL(Display.getDefault().syncCall(() -> { - MobileBKUQRComposite qr = getMobileBKUQRComposite(); - - Display display = getStateMachine().getMainShell().getDisplay(); - while (!qr.isDone()) { - if (!display.readAndDispatch()) { - display.sleep(); - } - } - - getStateMachine().display(this.getWaitingComposite()); - - if (qr.wasCancelClicked()) { - clearRememberedPassword(); - throw new UserCancelledException(); - } - - if (qr.wasSMSClicked()) - return QRResult.TO_SMS; - - if (qr.wasFIDO2Clicked()) - return QRResult.TO_FIDO2; - - return QRResult.UPDATE; - })); - } - - /** - * indicate that the long polling operation completed - * (any ongoing waitForQRCodeResult call will then return) - */ - public void signalQRScanned() { - getMobileBKUQRComposite().signalPollingDone(); - } - - /** - * start showing the "waiting for app" screen - * this method will return immediately */ - public void showWaitingForAppOpen(final @Nonnull String referenceValue, @Nullable URI signatureDataURI, final boolean showSmsTan, final boolean showFido2) { - Display.getDefault().syncExec(() -> { - WaitingForAppComposite wfa = getWaitingForAppComposite(); - wfa.reset(); - - // TODO composite does not currently support: refval, signature data - wfa.setSMSEnabled(showSmsTan); - wfa.setFIDO2Enabled(showFido2); - getStateMachine().display(wfa); - }); - } - - public enum AppOpenResult { - /* the user has pressed the FIDO2 button */ - TO_FIDO2, - /* the user has pressed the SMS button */ - TO_SMS, - /* signalAppOpened has been called; this indicates that we should refresh the page */ - UPDATE - }; - - public @Nonnull AppOpenResult waitForAppOpen() throws UserCancelledException { - return ISNOTNULL(Display.getDefault().syncCall(() -> { - WaitingForAppComposite wfa = getWaitingForAppComposite(); - - Display display = wfa.getDisplay(); - while (!wfa.isDone()) { - if (!display.readAndDispatch()) - display.sleep(); - } - - getStateMachine().display(this.getWaitingComposite()); - - if (wfa.wasCancelClicked()) { - clearRememberedPassword(); - throw new UserCancelledException(); - } - - if (wfa.wasSMSClicked()) - return AppOpenResult.TO_SMS; - - if (wfa.wasFIDO2Clicked()) - return AppOpenResult.TO_FIDO2; - - return AppOpenResult.UPDATE; - })); - } - - /** - * indicate that the long polling operation completed - * (any ongoing waitForAppOpen call will then return) - */ - public void signalAppOpened() { - getWaitingForAppComposite().signalPollingDone(); - } - - public void showWaitingForAppBiometry(final @Nonnull String referenceValue, @Nullable URI signatureDataURI, final boolean showSmsTan, final boolean showFido2) { - Display.getDefault().syncExec(() -> { - MobileBKUFingerprintComposite bio = getMobileBKUFingerprintComposite(); - bio.reset(); - - bio.setRefVal(referenceValue); - bio.signatureDataURI = signatureDataURI; - bio.setErrorMessage(null); // TODO - bio.setSMSEnabled(showSmsTan); - bio.setFIDO2Enabled(showFido2); - getStateMachine().display(bio); - }); - } - - // TODO can we maybe deduplicate the various waiting screens' logic? - - public enum AppBiometryResult { - /* the user has pressed the FIDO2 button */ - TO_FIDO2, - /* the user has pressed the SMS button */ - TO_SMS, - /* signalAppBiometryDone has been called; this indicates that we should refresh the page */ - UPDATE - }; - - public @Nonnull AppBiometryResult waitForAppBiometry() throws UserCancelledException { - return ISNOTNULL(Display.getDefault().syncCall(() -> { - MobileBKUFingerprintComposite bio = getMobileBKUFingerprintComposite(); - - Display display = bio.getDisplay(); - while (!bio.isDone()) { - if (!display.readAndDispatch()) - display.sleep(); - } - - getStateMachine().display(this.getWaitingComposite()); - - if (bio.wasCancelClicked()) { - clearRememberedPassword(); - throw new UserCancelledException(); - } - - if (bio.wasSMSClicked()) - return AppBiometryResult.TO_SMS; - - if (bio.wasFIDO2Clicked()) - return AppBiometryResult.TO_FIDO2; - - return AppBiometryResult.UPDATE; - })); - } - - public void signalAppBiometryDone() { - getMobileBKUFingerprintComposite().signalPollingDone(); - } - - public static class FIDO2Result { - public static enum ResultType { TO_SMS, CREDENTIAL }; - public final @Nonnull ResultType type; - public final @Nullable PublicKeyCredential credential; - - private FIDO2Result(@Nonnull ResultType type) { this.type = type; this.credential = null; } - private FIDO2Result(@Nonnull PublicKeyCredential cred) { this.type = ResultType.CREDENTIAL; this.credential = cred; } - } - - /** - * prompts user for fido2 auth and blocks until result is available - * @param fido2Options JSON data from A-Trust - * @return - * @throws UserCancelledException - */ - public @Nonnull FIDO2Result promptUserForFIDO2Auth(final @Nonnull String fido2Options, @Nullable URI signatureDataURI, final boolean showSmsTan) throws UserCancelledException { - return ISNOTNULL(Display.getDefault().syncCall(() -> { - MobileBKUFido2Composite fido2 = getMobileBKUFido2Composite(); - fido2.initialize(fido2Options); - fido2.setSMSEnabled(showSmsTan); - fido2.setSignatureDataURI(signatureDataURI); - - getStateMachine().display(fido2); - - Display display = fido2.getDisplay(); - while (!fido2.isDone()) { - if (!display.readAndDispatch()) - display.sleep(); - } - - getStateMachine().display(this.getWaitingComposite()); - - if (fido2.wasUserCancelClicked()) - throw new UserCancelledException(); - - if (fido2.wasUserSMSClicked()) - return new FIDO2Result(FIDO2Result.ResultType.TO_SMS); - - return new FIDO2Result(ISNOTNULL(fido2.getResultingCredential())); - })); - } - - /* - * (non-Javadoc) - * - * @see - * at.asit.pdfover.gui.workflow.WorkflowState#update(at.asit.pdfover.gui - * .workflow.Workflow) - */ - @Override - public void run() { - this.signingState = getStateMachine().status.signingState; - - this.signingState.bkuConnector = new MobileBKUConnector(this); - this.signingState.useBase64Request = false; - - if (this.threadException != null) { - displayError(this.threadException); - return; - } - - getStateMachine().display( - this.getWaitingComposite()); - - this.setNextState(new SigningState(getStateMachine())); - } - - /* - * (non-Javadoc) - * - * @see at.asit.pdfover.gui.workflow.states.State#cleanUp() - */ - @Override - public void cleanUp() { - if (this.mobileBKUEnterNumberComposite != null) - this.mobileBKUEnterNumberComposite.dispose(); - if (this.mobileBKUEnterTANComposite != null) - this.mobileBKUEnterTANComposite.dispose(); - if (this.waitingComposite != null) - this.waitingComposite.dispose(); - if (this.waitingForAppComposite != null) - this.waitingForAppComposite.dispose(); - } - - /* - * (non-Javadoc) - * - * @see at.asit.pdfover.gui.workflow.states.State#setMainWindowBehavior() - */ - @Override - public void updateMainWindowBehavior() { - MainWindowBehavior behavior = getStateMachine().status.behavior; - behavior.reset(); - behavior.setActive(Buttons.OPEN, true); - behavior.setActive(Buttons.POSITION, true); - behavior.setActive(Buttons.SIGN, true); - behavior.setEnabled(Buttons.OPEN, true); - behavior.setEnabled(Buttons.POSITION, true); - //behavior.setEnabled(Buttons.SIGN, true); - } - - @Override - public String toString() { - return this.getClass().getName(); - } - - /** - * invoke state machine update in main thread - */ - public void invokeUpdate() { - getStateMachine().invokeUpdate(); - } -} -- cgit v1.2.3