From fd24a56578a323715b844b610ba91a3bfd400342 Mon Sep 17 00:00:00 2001 From: Jakob Heher Date: Thu, 29 Sep 2022 13:36:57 +0200 Subject: fido2 proof of concept --- pdf-over-gui/pom.xml | 5 ++ .../asit/pdfover/gui/bku/MobileBKUConnector.java | 55 +++++++++++++ .../asit/pdfover/gui/bku/mobile/ATrustHandler.java | 92 ++++++++++++++++++---- .../asit/pdfover/gui/bku/mobile/ATrustStatus.java | 7 ++ .../gui/composites/MobileBKUQRComposite.java | 9 ++- 5 files changed, 150 insertions(+), 18 deletions(-) diff --git a/pdf-over-gui/pom.xml b/pdf-over-gui/pom.xml index 671f2967..b856e881 100644 --- a/pdf-over-gui/pom.xml +++ b/pdf-over-gui/pom.xml @@ -65,6 +65,11 @@ ${project.parent.version} compile + + at.a-sit + webauthn-java + 0.0.1-SNAPSHOT + 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 34b53173..dae4d007 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 @@ -15,10 +15,15 @@ */ package at.asit.pdfover.gui.bku; +import java.util.Base64; + // Imports import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; + import at.asit.pdfover.gui.bku.mobile.ATrustHandler; import at.asit.pdfover.gui.bku.mobile.ATrustStatus; import at.asit.pdfover.gui.bku.mobile.MobileBKUHandler; @@ -29,6 +34,11 @@ import at.asit.pdfover.signator.SLRequest; import at.asit.pdfover.signator.SLResponse; import at.asit.pdfover.signator.SignatureException; import at.asit.pdfover.signer.pdfas.PdfAs4SigningState; +import at.asit.webauthn.PublicKeyCredential; +import at.asit.webauthn.PublicKeyCredentialRequestOptions; +import at.asit.webauthn.WebAuthN; +import at.asit.webauthn.exceptions.WebAuthNOperationFailed; +import at.asit.webauthn.responsefields.AuthenticatorAssertionResponse; /** * @@ -125,6 +135,51 @@ public class MobileBKUConnector implements BkuSlConnector { if (status instanceof ATrustStatus) { ATrustStatus aStatus = (ATrustStatus) status; ATrustHandler aHandler = (ATrustHandler) handler; + if (aStatus.fido2OptionAvailable && (aStatus.fido2FormOptions == null)) { + try { + handler.handleCredentialsResponse(aHandler.postFIDO2Request()); + } catch (Exception ex) { + log.error("Error in PostCredentialsThread", ex); + this.state.threadException = ex; + throw new SignatureException(ex); + } + } + if (aStatus.fido2FormOptions != null) { + log.info("Fido2 credentials GET!"); + if (WebAuthN.isAvailable()) + { + log.info("Authenticating with WebAuthn!"); + enterTAN = false; + try { + PublicKeyCredential credential = + PublicKeyCredentialRequestOptions.FromJSONString(aStatus.fido2FormOptions.get(aStatus.fido2OptionsKey)).get("https://service.a-trust.at"); + + Base64.Encoder base64 = Base64.getEncoder(); + JsonObject aTrustCredential = new JsonObject(); + aTrustCredential.addProperty("id", credential.id); + aTrustCredential.addProperty("rawId", base64.encodeToString(credential.rawId)); + aTrustCredential.addProperty("type", credential.type); + aTrustCredential.add("extensions", new JsonObject()); // TODO fix getClientExtensionResults() in library + + JsonObject aTrustCredentialResponse = new JsonObject(); + aTrustCredential.add("response", aTrustCredentialResponse); + aTrustCredentialResponse.addProperty("authenticatorData", base64.encodeToString(credential.response.authenticatorData)); + aTrustCredentialResponse.addProperty("clientDataJson", base64.encodeToString(credential.response.clientDataJSON)); + aTrustCredentialResponse.addProperty("signature", base64.encodeToString(credential.response.signature)); + if (credential.response.userHandle != null) + aTrustCredentialResponse.addProperty("userHandle", base64.encodeToString(credential.response.userHandle)); + else + aTrustCredentialResponse.add("userHandle", JsonNull.INSTANCE); + + aStatus.fido2FormOptions.put(aStatus.fido2ResultKey, aTrustCredential.toString()); + handler.handleTANResponse(aHandler.postFIDO2Result()); // TODO dedicated response + } catch (WebAuthNOperationFailed e) { + log.error("WebAuthN failed", e); + } catch (Exception e) { + log.error("generic failure", e); + } + } + } if (aStatus.qrCodeURL != null) { this.state.showQR(); if ("cancel".equals(this.state.status.errorMessage)) diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustHandler.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustHandler.java index 18f93b22..5e4975e3 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustHandler.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustHandler.java @@ -21,16 +21,22 @@ import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.util.HashMap; +import java.util.Map; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringEscapeUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.program.Program; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -149,7 +155,7 @@ public class ATrustHandler extends MobileBKUHandler { * @see at.asit.pdfover.gui.workflow.states.mobilebku.MobileBKUHandler#handleCredentialsResponse(java.lang.String) */ @Override - public void handleCredentialsResponse(String responseData) throws Exception { + public void handleCredentialsResponse(final String responseData) throws Exception { ATrustStatus status = getStatus(); String viewState = status.viewState; String eventValidation = status.eventValidation; @@ -160,6 +166,8 @@ public class ATrustHandler extends MobileBKUHandler { status.errorMessage = null; + final Document responseDocument = Jsoup.parse(responseData); + if (responseData.contains("ExpiresInfo.aspx?sid=")) { // Certificate expiration interstitial - skip if (!expiryNoticeDisplayed) { @@ -196,8 +204,8 @@ public class ATrustHandler extends MobileBKUHandler { post.addParameter("__EVENTVALIDATION", t_eventValidation); post.addParameter("Button_Next", "Weiter"); - responseData = executePost(client, post); - log.trace("Response from mobile BKU: " + responseData); + handleCredentialsResponse(executePost(client, post)); + return; } else if (responseData.contains("tanAppInfo.aspx?sid=")) { // App info interstitial - skip log.info("Skipping tan app interstitial"); @@ -216,8 +224,8 @@ public class ATrustHandler extends MobileBKUHandler { post.addParameter("__EVENTVALIDATION", t_eventValidation); post.addParameter("NextBtn", "Weiter"); - responseData = executePost(client, post); - log.trace("Response from mobile BKU: " + responseData); + handleCredentialsResponse(executePost(client, post)); + return; } if (responseData.contains("signature.aspx?sid=")) { @@ -227,7 +235,12 @@ public class ATrustHandler extends MobileBKUHandler { sessionID = MobileBKUHelper.extractSubstring(responseData, "signature.aspx?sid=", "\""); viewState = MobileBKUHelper.extractValueFromTagWithParam(responseData, "", "id", "__VIEWSTATE", "value"); eventValidation = MobileBKUHelper.extractValueFromTagWithParam(responseData, "", "id", "__EVENTVALIDATION", "value"); - refVal = MobileBKUHelper.extractSubstring(responseData, "id='vergleichswert'>Vergleichswert:", ""); + try { + refVal = MobileBKUHelper.extractSubstring(responseData, "id='vergleichswert'>Vergleichswert:", ""); + } catch (Exception e) { + refVal = null; + log.debug("No reference value"); + } signatureDataURL = status.baseURL + "/ShowSigobj.aspx" + MobileBKUHelper.extractSubstring(responseData, "ShowSigobj.aspx", "'"); try { @@ -253,17 +266,27 @@ public class ATrustHandler extends MobileBKUHandler { }catch (Exception e) { log.debug("No text_tan tag"); } - try { - String webauthnLink = MobileBKUHelper.extractValueFromTagWithParam(responseData, "a", "id", "FidoButton", "href"); - log.info("Webauthn link: {}", webauthnLink); - } catch (Exception e) { - log.info("No webauthnLink"); - } - try { - String webauthnData = MobileBKUHelper.extractValueFromTagWithParam(responseData, "input", "id", "credentialOptions", "value"); - log.info("Fido credential options: {}", webauthnData); - } catch (Exception e) { - log.info("No webauthnData"); + + status.fido2OptionAvailable = (responseDocument.selectFirst("#FidoButton") != null); + { + Element fidoBlock = responseDocument.selectFirst("#fidoBlock"); + + if (fidoBlock != null) { + Map options = new HashMap<>(); + for (Element field : fidoBlock.select("input")) + { + if (!field.hasAttr("name")) + continue; + options.put(field.attr("name"), field.attr("value")); + if ("credentialOptions".equals(field.attr("id"))) + status.fido2OptionsKey = field.attr("name"); + if ("credentialResult".equals(field.attr("id"))) + status.fido2ResultKey = field.attr("name"); + } + log.info("Fido credential options: {}", options); + status.fido2FormOptions = options; + status.qrCodeURL = null; + } } } else if (responseData.contains("sl:InfoboxReadResponse")) { @@ -341,6 +364,7 @@ public class ATrustHandler extends MobileBKUHandler { return executePost(client, post); } + /* (non-Javadoc) * @see at.asit.pdfover.gui.workflow.states.mobilebku.MobileBKUHandler#handleTANResponse(java.lang.String) */ @@ -401,6 +425,40 @@ public class ATrustHandler extends MobileBKUHandler { return executeGet(client, get); } + /** + * Cancel QR process, request FIDO2 authentication + * @return the response + * @throws IOException Error during posting + */ + + public String postFIDO2Request() throws IOException { + ATrustStatus status = getStatus(); + + MobileBKUHelper.registerTrustedSocketFactory(); + HttpClient client = MobileBKUHelper.getHttpClient(status); + GetMethod get = new GetMethod(status.baseURL + "/usefido.aspx?sid=" + status.sessionID); + get.getParams().setContentCharset("utf-8"); + + return executeGet(client, get); + } + + public String postFIDO2Result() throws IOException { + ATrustStatus status = getStatus(); + + MobileBKUHelper.registerTrustedSocketFactory(); + HttpClient client = MobileBKUHelper.getHttpClient(status); + + PostMethod post = new PostMethod(status.baseURL + "/signature.aspx?sid=" + status.sessionID); + post.getParams().setContentCharset("utf-8"); + post.addParameter("__VIEWSTATE", status.viewState); + post.addParameter("__VIEWSTATEGENERATOR", status.viewStateGenerator); + post.addParameter("__EVENTVALIDATION", status.eventValidation); + for (Map.Entry entry : status.fido2FormOptions.entrySet()) + post.addParameter(entry.getKey(), entry.getValue()); + + return executePost(client, post); + } + /** * Get the QR code image * @return the QR code image as a String diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustStatus.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustStatus.java index b61b3a8b..6258b4ce 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustStatus.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustStatus.java @@ -15,6 +15,8 @@ */ package at.asit.pdfover.gui.bku.mobile; +import java.util.Map; + // Imports import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +49,11 @@ public class ATrustStatus extends MobileBKUStatus { public String dynAttrSignButton; public boolean isSMSTan = false; + public boolean fido2OptionAvailable = false; + public String fido2OptionsKey; + public String fido2ResultKey; + public Map fido2FormOptions; + /** * Constructor * @param provider the ConfigProvider 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 121d8c71..1a1a10ac 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 @@ -21,6 +21,7 @@ import java.io.InputStream; import java.net.URI; import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.SelectionAdapter; @@ -226,7 +227,13 @@ public class MobileBKUQRComposite extends StateComposite { setErrorMessage(Messages.getString("error.FailedToLoadQRCode")); return; } - this.currentQRImage = new ImageData(qrcode); + try { + this.currentQRImage = new ImageData(qrcode); + } catch (SWTException e) { + log.warn("Failed to load QR code", e); + setErrorMessage(Messages.getString("error.FailedToLoadQRCode")); + return; + } updateQRImage(); } -- cgit v1.2.3