summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJérémy Zurcher <jeremy@asynk.ch>2017-03-30 09:16:28 +0200
committerJérémy Zurcher <jeremy@asynk.ch>2017-03-30 09:16:28 +0200
commitac2a06489202c6f1de3bc43078059e608ce87e20 (patch)
tree432272cac5783c62715d83b928f4e22e9afe0990
parentb611f66803dee20970dc2f386783f507a411465a (diff)
downloadshare-ac2a06489202c6f1de3bc43078059e608ce87e20.zip
share-ac2a06489202c6f1de3bc43078059e608ce87e20.tar.gz
java : vaadin : add U2F support ;)
-rw-r--r--java/vaadin/build.xml7
-rw-r--r--java/vaadin/ivy.xml1
-rw-r--r--java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_down.pngbin0 -> 3326 bytes
-rw-r--r--java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_up.pngbin0 -> 3226 bytes
-rw-r--r--java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-128.pngbin0 -> 2505 bytes
-rw-r--r--java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-24.pngbin0 -> 732 bytes
-rw-r--r--java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-32.pngbin0 -> 901 bytes
-rw-r--r--java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-64.pngbin0 -> 1882 bytes
-rw-r--r--java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-lock.pngbin0 -> 2614 bytes
-rw-r--r--java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo-48.pngbin0 -> 1791 bytes
-rw-r--r--java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo.pngbin0 -> 10109 bytes
-rw-r--r--java/vaadin/src/main/java/ch/asynk/security/U2fConnector.java164
-rw-r--r--java/vaadin/src/main/java/ch/asynk/security/U2fService.java12
-rw-r--r--java/vaadin/src/main/java/ch/asynk/security/U2fServiceImpl.java43
-rw-r--r--java/vaadin/src/main/java/ch/asynk/security/u2f-api.js748
-rw-r--r--java/vaadin/src/main/java/ch/asynk/security/u2f_connector.js30
-rw-r--r--java/vaadin/src/main/java/ch/asynk/ui/Icons.java9
-rw-r--r--java/vaadin/src/main/java/ch/asynk/ui/ViewMain.java135
-rw-r--r--java/vaadin/src/run/java/ch/asynk/Main.java10
19 files changed, 1150 insertions, 9 deletions
diff --git a/java/vaadin/build.xml b/java/vaadin/build.xml
index 1090e68..712034f 100644
--- a/java/vaadin/build.xml
+++ b/java/vaadin/build.xml
@@ -2,7 +2,7 @@
<project xmlns:ivy="antlib:org.apache.ivy.ant"
name="My Vaadin Hello World"
basedir="."
- default="war">
+ default="compile.all">
<target name="configure">
<property file="${basedir}/src/main/resources/Application.properties"/>
@@ -114,6 +114,11 @@
<compilerarg value="-Xmaxerrs"/>
<compilerarg value="10"/>
</javac>
+ <copy todir="${classes.dir}">
+ <fileset dir="${src.dir}">
+ <include name="**/*.js"/>
+ </fileset>
+ </copy>
</target>
<target name="compile.all" description="compile everything" depends="compile.app,compile.themes"/>
diff --git a/java/vaadin/ivy.xml b/java/vaadin/ivy.xml
index b1a4b3b..81ca70d 100644
--- a/java/vaadin/ivy.xml
+++ b/java/vaadin/ivy.xml
@@ -22,5 +22,6 @@
<dependency org="org.codehaus.janino" name="janino" rev="3.0.6"/>
<dependency org="com.yahoo.platform.yui" name="yuicompressor" rev="2.4.8" conf="themes->default"/>
<dependency org="org.eclipse.jetty" name="jetty-webapp" rev="9.4.2.v20170220" conf="run->default"/>
+ <dependency org="com.yubico" name="u2flib-server-core" rev="0.16.0"/>
</dependencies>
</ivy-module>
diff --git a/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_down.png b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_down.png
new file mode 100644
index 0000000..7e1709f
--- /dev/null
+++ b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_down.png
Binary files differ
diff --git a/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_up.png b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_up.png
new file mode 100644
index 0000000..cc442d5
--- /dev/null
+++ b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_up.png
Binary files differ
diff --git a/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-128.png b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-128.png
new file mode 100644
index 0000000..52d0761
--- /dev/null
+++ b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-128.png
Binary files differ
diff --git a/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-24.png b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-24.png
new file mode 100644
index 0000000..bd5594a
--- /dev/null
+++ b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-24.png
Binary files differ
diff --git a/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-32.png b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-32.png
new file mode 100644
index 0000000..78a15c4
--- /dev/null
+++ b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-32.png
Binary files differ
diff --git a/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-64.png b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-64.png
new file mode 100644
index 0000000..c89c931
--- /dev/null
+++ b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-64.png
Binary files differ
diff --git a/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-lock.png b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-lock.png
new file mode 100644
index 0000000..7823ff2
--- /dev/null
+++ b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-lock.png
Binary files differ
diff --git a/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo-48.png b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo-48.png
new file mode 100644
index 0000000..3641de1
--- /dev/null
+++ b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo-48.png
Binary files differ
diff --git a/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo.png b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo.png
new file mode 100644
index 0000000..a18234b
--- /dev/null
+++ b/java/vaadin/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo.png
Binary files differ
diff --git a/java/vaadin/src/main/java/ch/asynk/security/U2fConnector.java b/java/vaadin/src/main/java/ch/asynk/security/U2fConnector.java
new file mode 100644
index 0000000..24b406d
--- /dev/null
+++ b/java/vaadin/src/main/java/ch/asynk/security/U2fConnector.java
@@ -0,0 +1,164 @@
+package ch.asynk.security;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.vaadin.annotations.JavaScript;
+import com.vaadin.server.AbstractJavaScriptExtension;
+import com.vaadin.ui.UI;
+import com.vaadin.ui.JavaScriptFunction;
+import com.yubico.u2f.U2F;
+import com.yubico.u2f.data.DeviceRegistration;
+import com.yubico.u2f.data.messages.AuthenticateRequestData;
+import com.yubico.u2f.data.messages.AuthenticateResponse;
+import com.yubico.u2f.data.messages.RegisterRequestData;
+import com.yubico.u2f.data.messages.RegisterResponse;
+import com.yubico.u2f.exceptions.DeviceCompromisedException;
+import com.yubico.u2f.exceptions.NoEligibleDevicesException;
+import elemental.json.JsonArray;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@JavaScript({"u2f-api.js", "u2f_connector.js"})
+public class U2fConnector extends AbstractJavaScriptExtension
+{
+ private static final long serialVersionUID = 1L;
+
+ private static final Logger logger = LoggerFactory.getLogger(U2fConnector.class);
+
+ // appId MUST match the URL - https://developers.yubico.com/U2F/App_ID.html
+ private final String appId = "https://localhost:8666";
+
+ private final U2F u2f = new U2F();
+ // private final U2F u2f = U2F.withoutAppIdValidation();
+
+ // https://developers.yubico.com/U2F/Libraries/Client_error_codes.html
+ private static String U2F_ERRORS[] = {"","OTHER_ERROR","BAD_REQUEST","CONFIGURATION_UNSUPPORTED","DEVICE_INELIGIBLE","TIMEOUT","DEVICE_COMPROMISED"};
+ public String getErrorMsg(int i) { return U2F_ERRORS[i]; }
+
+ private final U2fService u2fService = new U2fServiceImpl();
+
+ class Request
+ {
+ String userId;
+ String data;
+ U2fListener listener;
+ public Request(String userId, String data, U2fListener listener)
+ {
+ this.userId = userId;
+ this.data = data;
+ this.listener = listener;
+ }
+ }
+
+ private final Map<String, Request> requests = new HashMap<>();
+
+ public enum U2fAction {
+ REGISTRATION_PENDING,
+ REGISTRATION_SUCCESS,
+ REGISTRATION_FAILURE,
+ AUTHENTICATION_PENDING,
+ AUTHENTICATION_FAILURE,
+ AUTHENTICATION_SUCCESS
+ };
+
+ public interface U2fListener
+ {
+ public void u2fCallback(U2fAction action, String msg);
+ }
+
+ public U2fConnector() {
+ extend(UI.getCurrent());
+ addFunction("onRegisterResponse", new JavaScriptFunction() {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public void call(final JsonArray arguments) {
+ onRegisterResponse(arguments);
+ }
+ });
+ addFunction("onAuthenticateResponse", new JavaScriptFunction() {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public void call(final JsonArray arguments) {
+ onAuthenticateResponse(arguments);
+ }
+ });
+ }
+
+ public void sendRegisterRequest(final String userId, final U2fListener listener)
+ {
+ final RegisterRequestData registerRequestData = u2f.startRegistration(appId, u2fService.getDeviceRegistrations(userId));
+ final String registerRequestDataJson = registerRequestData.toJson();
+ final String registerRequestDataId = registerRequestData.getRequestId();
+ requests.put(registerRequestDataId, new Request(userId, registerRequestDataJson, listener));
+ callFunction("register", registerRequestDataJson);
+ listener.u2fCallback(U2fAction.REGISTRATION_PENDING, null);
+ logger.debug(String.format("register[%s] : %s", userId, registerRequestDataJson));
+ }
+
+ public void onRegisterResponse(JsonArray arguments)
+ {
+ if (arguments.get(0) instanceof elemental.json.impl.JreJsonNumber) {
+ int errorCode = (int) arguments.getNumber(0);
+ // FIXME why does it not work ??
+ // final RegisterRequestData registerRequestData = RegisterRequestData.fromJson(arguments.getString(1));
+ final String registerRequestDataJson = arguments.getObject(1).toString();
+ final RegisterRequestData registerRequestData = RegisterRequestData.fromJson(registerRequestDataJson);
+ final Request request = requests.remove(registerRequestData.getRequestId());
+ request.listener.u2fCallback(U2fAction.REGISTRATION_FAILURE, getErrorMsg(errorCode));
+ logger.debug(String.format("failure[%d] : %s", errorCode, registerRequestDataJson));
+ } else {
+ final String registerResponseJson = arguments.getString(0);
+ final RegisterResponse registerResponse = RegisterResponse.fromJson(registerResponseJson);
+ final Request request = requests.remove(registerResponse.getRequestId());
+ final RegisterRequestData registerRequestData = RegisterRequestData.fromJson(request.data);
+ u2fService.addDeviceRegistration(request.userId, u2f.finishRegistration(registerRequestData, registerResponse));
+ request.listener.u2fCallback(U2fAction.REGISTRATION_SUCCESS, null);
+ logger.debug(String.format("success : %s", registerResponseJson));
+ }
+ }
+
+ public void startAuthentication(final String userId, final U2fListener listener)
+ {
+ try {
+ final AuthenticateRequestData authenticateRequestData = u2f.startAuthentication(appId, u2fService.getDeviceRegistrations(userId));
+ final String authenticateRequestDataJson = authenticateRequestData.toJson();
+ final String authenticateRequestDataId = authenticateRequestData.getRequestId();
+ requests.put(authenticateRequestDataId, new Request(userId, authenticateRequestDataJson, listener));
+ callFunction("authenticate", authenticateRequestDataJson, userId);
+ listener.u2fCallback(U2fAction.AUTHENTICATION_PENDING, null);
+ logger.debug(String.format("authenticate[%s] : %s", userId, authenticateRequestDataJson));
+ } catch (NoEligibleDevicesException e) {
+ listener.u2fCallback(U2fAction.AUTHENTICATION_FAILURE, getErrorMsg(4));
+ }
+ }
+
+ public void onAuthenticateResponse(JsonArray arguments)
+ {
+ if (arguments.get(0) instanceof elemental.json.impl.JreJsonNumber) {
+ int errorCode = (int) arguments.getNumber(0);
+ // FIXME why does it not work ??
+ // final AuthenticateRequestData authenticateRequestData = AuthenticateRequestData.fromJson(arguments.getString(1));
+ final String authenticateRequestDataJson = arguments.getObject(1).toString();
+ final AuthenticateRequestData authenticateRequestData = AuthenticateRequestData.fromJson(authenticateRequestDataJson);
+ final Request request = requests.remove(authenticateRequestData.getRequestId());
+ request.listener.u2fCallback(U2fAction.AUTHENTICATION_FAILURE, getErrorMsg(errorCode));
+ logger.debug(String.format("failure[%d] : %s", errorCode, authenticateRequestDataJson));
+ } else {
+ final String authenticateResponseJson = arguments.getString(0);
+ final AuthenticateResponse authenticateResponse = AuthenticateResponse.fromJson(authenticateResponseJson);
+ final Request request = requests.remove(authenticateResponse.getRequestId());
+ final AuthenticateRequestData authenticateRequestData = AuthenticateRequestData.fromJson(request.data);
+ DeviceRegistration registration = null;
+ try {
+ registration = u2f.finishAuthentication(authenticateRequestData, authenticateResponse, u2fService.getDeviceRegistrations(request.userId));
+ } catch (final DeviceCompromisedException e) {
+ request.listener.u2fCallback(U2fAction.AUTHENTICATION_FAILURE, getErrorMsg(6));
+ logger.error(String.format("device compromised: %s", request.data));
+ }
+ request.listener.u2fCallback(U2fAction.AUTHENTICATION_SUCCESS, null);
+ logger.debug(String.format("success : %s", authenticateResponseJson));
+ }
+ }
+}
diff --git a/java/vaadin/src/main/java/ch/asynk/security/U2fService.java b/java/vaadin/src/main/java/ch/asynk/security/U2fService.java
new file mode 100644
index 0000000..9bce94e
--- /dev/null
+++ b/java/vaadin/src/main/java/ch/asynk/security/U2fService.java
@@ -0,0 +1,12 @@
+package ch.asynk.security;
+
+import java.util.List;
+
+import com.yubico.u2f.data.DeviceRegistration;
+
+public interface U2fService
+{
+ public boolean hasDeviceRegistrations(final String userId);
+ public List<DeviceRegistration> getDeviceRegistrations(final String userId);
+ public void addDeviceRegistration(final String userId, final DeviceRegistration deviceRegistration);
+}
diff --git a/java/vaadin/src/main/java/ch/asynk/security/U2fServiceImpl.java b/java/vaadin/src/main/java/ch/asynk/security/U2fServiceImpl.java
new file mode 100644
index 0000000..f8e785f
--- /dev/null
+++ b/java/vaadin/src/main/java/ch/asynk/security/U2fServiceImpl.java
@@ -0,0 +1,43 @@
+package ch.asynk.security;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.yubico.u2f.data.DeviceRegistration;
+
+public class U2fServiceImpl implements U2fService
+{
+ private HashMap<String, List<DeviceRegistration>> store;
+
+ public U2fServiceImpl()
+ {
+ store = new HashMap<String, List<DeviceRegistration>>();
+ }
+
+ public boolean hasDeviceRegistrations(final String userId)
+ {
+ return store.containsKey(userId);
+ }
+
+ public List<DeviceRegistration> getDeviceRegistrations(final String userId)
+ {
+ return get(userId);
+ }
+
+ public void addDeviceRegistration(final String userId, final DeviceRegistration deviceRegistration)
+ {
+ List<DeviceRegistration> registrations = get(userId);
+ registrations.add(deviceRegistration);
+ store.put(userId, registrations);
+ }
+
+ private List<DeviceRegistration> get(final String userId)
+ {
+ List<DeviceRegistration> registrations = store.get(userId);
+ if (registrations == null)
+ registrations = new ArrayList<DeviceRegistration>();
+ return registrations;
+ }
+}
diff --git a/java/vaadin/src/main/java/ch/asynk/security/u2f-api.js b/java/vaadin/src/main/java/ch/asynk/security/u2f-api.js
new file mode 100644
index 0000000..9244d14
--- /dev/null
+++ b/java/vaadin/src/main/java/ch/asynk/security/u2f-api.js
@@ -0,0 +1,748 @@
+//Copyright 2014-2015 Google Inc. All rights reserved.
+
+//Use of this source code is governed by a BSD-style
+//license that can be found in the LICENSE file or at
+//https://developers.google.com/open-source/licenses/bsd
+
+/**
+ * @fileoverview The U2F api.
+ */
+'use strict';
+
+
+/**
+ * Namespace for the U2F api.
+ * @type {Object}
+ */
+var u2f = u2f || {};
+
+/**
+ * FIDO U2F Javascript API Version
+ * @number
+ */
+var js_api_version;
+
+/**
+ * The U2F extension id
+ * @const {string}
+ */
+// The Chrome packaged app extension ID.
+// Uncomment this if you want to deploy a server instance that uses
+// the package Chrome app and does not require installing the U2F Chrome extension.
+ u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
+// The U2F Chrome extension ID.
+// Uncomment this if you want to deploy a server instance that uses
+// the U2F Chrome extension to authenticate.
+// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
+
+
+/**
+ * Message types for messsages to/from the extension
+ * @const
+ * @enum {string}
+ */
+u2f.MessageTypes = {
+ 'U2F_REGISTER_REQUEST': 'u2f_register_request',
+ 'U2F_REGISTER_RESPONSE': 'u2f_register_response',
+ 'U2F_SIGN_REQUEST': 'u2f_sign_request',
+ 'U2F_SIGN_RESPONSE': 'u2f_sign_response',
+ 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
+ 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response'
+};
+
+
+/**
+ * Response status codes
+ * @const
+ * @enum {number}
+ */
+u2f.ErrorCodes = {
+ 'OK': 0,
+ 'OTHER_ERROR': 1,
+ 'BAD_REQUEST': 2,
+ 'CONFIGURATION_UNSUPPORTED': 3,
+ 'DEVICE_INELIGIBLE': 4,
+ 'TIMEOUT': 5
+};
+
+
+/**
+ * A message for registration requests
+ * @typedef {{
+ * type: u2f.MessageTypes,
+ * appId: ?string,
+ * timeoutSeconds: ?number,
+ * requestId: ?number
+ * }}
+ */
+u2f.U2fRequest;
+
+
+/**
+ * A message for registration responses
+ * @typedef {{
+ * type: u2f.MessageTypes,
+ * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
+ * requestId: ?number
+ * }}
+ */
+u2f.U2fResponse;
+
+
+/**
+ * An error object for responses
+ * @typedef {{
+ * errorCode: u2f.ErrorCodes,
+ * errorMessage: ?string
+ * }}
+ */
+u2f.Error;
+
+/**
+ * Data object for a single sign request.
+ * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}}
+ */
+u2f.Transport;
+
+
+/**
+ * Data object for a single sign request.
+ * @typedef {Array<u2f.Transport>}
+ */
+u2f.Transports;
+
+/**
+ * Data object for a single sign request.
+ * @typedef {{
+ * version: string,
+ * challenge: string,
+ * keyHandle: string,
+ * appId: string
+ * }}
+ */
+u2f.SignRequest;
+
+
+/**
+ * Data object for a sign response.
+ * @typedef {{
+ * keyHandle: string,
+ * signatureData: string,
+ * clientData: string
+ * }}
+ */
+u2f.SignResponse;
+
+
+/**
+ * Data object for a registration request.
+ * @typedef {{
+ * version: string,
+ * challenge: string
+ * }}
+ */
+u2f.RegisterRequest;
+
+
+/**
+ * Data object for a registration response.
+ * @typedef {{
+ * version: string,
+ * keyHandle: string,
+ * transports: Transports,
+ * appId: string
+ * }}
+ */
+u2f.RegisterResponse;
+
+
+/**
+ * Data object for a registered key.
+ * @typedef {{
+ * version: string,
+ * keyHandle: string,
+ * transports: ?Transports,
+ * appId: ?string
+ * }}
+ */
+u2f.RegisteredKey;
+
+
+/**
+ * Data object for a get API register response.
+ * @typedef {{
+ * js_api_version: number
+ * }}
+ */
+u2f.GetJsApiVersionResponse;
+
+
+//Low level MessagePort API support
+
+/**
+ * Sets up a MessagePort to the U2F extension using the
+ * available mechanisms.
+ * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
+ */
+u2f.getMessagePort = function(callback) {
+ if (typeof chrome != 'undefined' && chrome.runtime) {
+ // The actual message here does not matter, but we need to get a reply
+ // for the callback to run. Thus, send an empty signature request
+ // in order to get a failure response.
+ var msg = {
+ type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+ signRequests: []
+ };
+ chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
+ if (!chrome.runtime.lastError) {
+ // We are on a whitelisted origin and can talk directly
+ // with the extension.
+ u2f.getChromeRuntimePort_(callback);
+ } else {
+ // chrome.runtime was available, but we couldn't message
+ // the extension directly, use iframe
+ u2f.getIframePort_(callback);
+ }
+ });
+ } else if (u2f.isAndroidChrome_()) {
+ u2f.getAuthenticatorPort_(callback);
+ } else if (u2f.isIosChrome_()) {
+ u2f.getIosPort_(callback);
+ } else {
+ // chrome.runtime was not available at all, which is normal
+ // when this origin doesn't have access to any extensions.
+ u2f.getIframePort_(callback);
+ }
+};
+
+/**
+ * Detect chrome running on android based on the browser's useragent.
+ * @private
+ */
+u2f.isAndroidChrome_ = function() {
+ var userAgent = navigator.userAgent;
+ return userAgent.indexOf('Chrome') != -1 &&
+ userAgent.indexOf('Android') != -1;
+};
+
+/**
+ * Detect chrome running on iOS based on the browser's platform.
+ * @private
+ */
+u2f.isIosChrome_ = function() {
+ return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
+};
+
+/**
+ * Connects directly to the extension via chrome.runtime.connect.
+ * @param {function(u2f.WrappedChromeRuntimePort_)} callback
+ * @private
+ */
+u2f.getChromeRuntimePort_ = function(callback) {
+ var port = chrome.runtime.connect(u2f.EXTENSION_ID,
+ {'includeTlsChannelId': true});
+ setTimeout(function() {
+ callback(new u2f.WrappedChromeRuntimePort_(port));
+ }, 0);
+};
+
+/**
+ * Return a 'port' abstraction to the Authenticator app.
+ * @param {function(u2f.WrappedAuthenticatorPort_)} callback
+ * @private
+ */
+u2f.getAuthenticatorPort_ = function(callback) {
+ setTimeout(function() {
+ callback(new u2f.WrappedAuthenticatorPort_());
+ }, 0);
+};
+
+/**
+ * Return a 'port' abstraction to the iOS client app.
+ * @param {function(u2f.WrappedIosPort_)} callback
+ * @private
+ */
+u2f.getIosPort_ = function(callback) {
+ setTimeout(function() {
+ callback(new u2f.WrappedIosPort_());
+ }, 0);
+};
+
+/**
+ * A wrapper for chrome.runtime.Port that is compatible with MessagePort.
+ * @param {Port} port
+ * @constructor
+ * @private
+ */
+u2f.WrappedChromeRuntimePort_ = function(port) {
+ this.port_ = port;
+};
+
+/**
+ * Format and return a sign request compliant with the JS API version supported by the extension.
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {number} timeoutSeconds
+ * @param {number} reqId
+ * @return {Object}
+ */
+u2f.formatSignRequest_ =
+ function(appId, challenge, registeredKeys, timeoutSeconds, reqId) {
+ if (js_api_version === undefined || js_api_version < 1.1) {
+ // Adapt request to the 1.0 JS API
+ var signRequests = [];
+ for (var i = 0; i < registeredKeys.length; i++) {
+ signRequests[i] = {
+ version: registeredKeys[i].version,
+ challenge: challenge,
+ keyHandle: registeredKeys[i].keyHandle,
+ appId: appId
+ };
+ }
+ return {
+ type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+ signRequests: signRequests,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+ }
+ // JS 1.1 API
+ return {
+ type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+ appId: appId,
+ challenge: challenge,
+ registeredKeys: registeredKeys,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+};
+
+/**
+ * Format and return a register request compliant with the JS API version supported by the extension..
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {Array<u2f.RegisterRequest>} signRequests
+ * @param {number} timeoutSeconds
+ * @param {number} reqId
+ * @return {Object}
+ */
+u2f.formatRegisterRequest_ =
+ function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
+ if (js_api_version === undefined || js_api_version < 1.1) {
+ // Adapt request to the 1.0 JS API
+ for (var i = 0; i < registerRequests.length; i++) {
+ registerRequests[i].appId = appId;
+ }
+ var signRequests = [];
+ for (var i = 0; i < registeredKeys.length; i++) {
+ signRequests[i] = {
+ version: registeredKeys[i].version,
+ challenge: registerRequests[0],
+ keyHandle: registeredKeys[i].keyHandle,
+ appId: appId
+ };
+ }
+ return {
+ type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
+ signRequests: signRequests,
+ registerRequests: registerRequests,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+ }
+ // JS 1.1 API
+ return {
+ type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
+ appId: appId,
+ registerRequests: registerRequests,
+ registeredKeys: registeredKeys,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+};
+
+
+/**
+ * Posts a message on the underlying channel.
+ * @param {Object} message
+ */
+u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
+ this.port_.postMessage(message);
+};
+
+
+/**
+ * Emulates the HTML 5 addEventListener interface. Works only for the
+ * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
+ function(eventName, handler) {
+ var name = eventName.toLowerCase();
+ if (name == 'message' || name == 'onmessage') {
+ this.port_.onMessage.addListener(function(message) {
+ // Emulate a minimal MessageEvent object
+ handler({'data': message});
+ });
+ } else {
+ console.error('WrappedChromeRuntimePort only supports onMessage');
+ }
+};
+
+/**
+ * Wrap the Authenticator app with a MessagePort interface.
+ * @constructor
+ * @private
+ */
+u2f.WrappedAuthenticatorPort_ = function() {
+ this.requestId_ = -1;
+ this.requestObject_ = null;
+}
+
+/**
+ * Launch the Authenticator intent.
+ * @param {Object} message
+ */
+u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
+ var intentUrl =
+ u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
+ ';S.request=' + encodeURIComponent(JSON.stringify(message)) +
+ ';end';
+ document.location = intentUrl;
+};
+
+/**
+ * Tells what type of port this is.
+ * @return {String} port type
+ */
+u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() {
+ return "WrappedAuthenticatorPort_";
+};
+
+
+/**
+ * Emulates the HTML 5 addEventListener interface.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) {
+ var name = eventName.toLowerCase();
+ if (name == 'message') {
+ var self = this;
+ /* Register a callback to that executes when
+ * chrome injects the response. */
+ window.addEventListener(
+ 'message', self.onRequestUpdate_.bind(self, handler), false);
+ } else {
+ console.error('WrappedAuthenticatorPort only supports message');
+ }
+};
+
+/**
+ * Callback invoked when a response is received from the Authenticator.
+ * @param function({data: Object}) callback
+ * @param {Object} message message Object
+ */
+u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
+ function(callback, message) {
+ var messageObject = JSON.parse(message.data);
+ var intentUrl = messageObject['intentURL'];
+
+ var errorCode = messageObject['errorCode'];
+ var responseObject = null;
+ if (messageObject.hasOwnProperty('data')) {
+ responseObject = /** @type {Object} */ (
+ JSON.parse(messageObject['data']));
+ }
+
+ callback({'data': responseObject});
+};
+
+/**
+ * Base URL for intents to Authenticator.
+ * @const
+ * @private
+ */
+u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
+ 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
+
+/**
+ * Wrap the iOS client app with a MessagePort interface.
+ * @constructor
+ * @private
+ */
+u2f.WrappedIosPort_ = function() {};
+
+/**
+ * Launch the iOS client app request
+ * @param {Object} message
+ */
+u2f.WrappedIosPort_.prototype.postMessage = function(message) {
+ var str = JSON.stringify(message);
+ var url = "u2f://auth?" + encodeURI(str);
+ location.replace(url);
+};
+
+/**
+ * Tells what type of port this is.
+ * @return {String} port type
+ */
+u2f.WrappedIosPort_.prototype.getPortType = function() {
+ return "WrappedIosPort_";
+};
+
+/**
+ * Emulates the HTML 5 addEventListener interface.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) {
+ var name = eventName.toLowerCase();
+ if (name !== 'message') {
+ console.error('WrappedIosPort only supports message');
+ }
+};
+
+/**
+ * Sets up an embedded trampoline iframe, sourced from the extension.
+ * @param {function(MessagePort)} callback
+ * @private
+ */
+u2f.getIframePort_ = function(callback) {
+ // Create the iframe
+ var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
+ var iframe = document.createElement('iframe');
+ iframe.src = iframeOrigin + '/u2f-comms.html';
+ iframe.setAttribute('style', 'display:none');
+ document.body.appendChild(iframe);
+
+ var channel = new MessageChannel();
+ var ready = function(message) {
+ if (message.data == 'ready') {
+ channel.port1.removeEventListener('message', ready);
+ callback(channel.port1);
+ } else {
+ console.error('First event on iframe port was not "ready"');
+ }
+ };
+ channel.port1.addEventListener('message', ready);
+ channel.port1.start();
+
+ iframe.addEventListener('load', function() {
+ // Deliver the port to the iframe and initialize
+ iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
+ });
+};
+
+
+//High-level JS API
+
+/**
+ * Default extension response timeout in seconds.
+ * @const
+ */
+u2f.EXTENSION_TIMEOUT_SEC = 30;
+
+/**
+ * A singleton instance for a MessagePort to the extension.
+ * @type {MessagePort|u2f.WrappedChromeRuntimePort_}
+ * @private
+ */
+u2f.port_ = null;
+
+/**
+ * Callbacks waiting for a port
+ * @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>}
+ * @private
+ */
+u2f.waitingForPort_ = [];
+
+/**
+ * A counter for requestIds.
+ * @type {number}
+ * @private
+ */
+u2f.reqCounter_ = 0;
+
+/**
+ * A map from requestIds to client callbacks
+ * @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse))
+ * |function((u2f.Error|u2f.SignResponse)))>}
+ * @private
+ */
+u2f.callbackMap_ = {};
+
+/**
+ * Creates or retrieves the MessagePort singleton to use.
+ * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
+ * @private
+ */
+u2f.getPortSingleton_ = function(callback) {
+ if (u2f.port_) {
+ callback(u2f.port_);
+ } else {
+ if (u2f.waitingForPort_.length == 0) {
+ u2f.getMessagePort(function(port) {
+ u2f.port_ = port;
+ u2f.port_.addEventListener('message',
+ /** @type {function(Event)} */ (u2f.responseHandler_));
+
+ // Careful, here be async callbacks. Maybe.
+ while (u2f.waitingForPort_.length)
+ u2f.waitingForPort_.shift()(u2f.port_);
+ });
+ }
+ u2f.waitingForPort_.push(callback);
+ }
+};
+
+/**
+ * Handles response messages from the extension.
+ * @param {MessageEvent.<u2f.Response>} message
+ * @private
+ */
+u2f.responseHandler_ = function(message) {
+ var response = message.data;
+ var reqId = response['requestId'];
+ if (!reqId || !u2f.callbackMap_[reqId]) {
+ console.error('Unknown or missing requestId in response.');
+ return;
+ }
+ var cb = u2f.callbackMap_[reqId];
+ delete u2f.callbackMap_[reqId];
+ cb(response['responseData']);
+};
+
+/**
+ * Dispatches an array of sign requests to available U2F tokens.
+ * If the JS API version supported by the extension is unknown, it first sends a
+ * message to the extension to find out the supported API version and then it sends
+ * the sign request.
+ * @param {string=} appId
+ * @param {string=} challenge
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.SignResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
+ if (js_api_version === undefined) {
+ // Send a message to get the extension to JS API version, then send the actual sign request.
+ u2f.getApiVersion(
+ function (response) {
+ js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
+ console.log("Extension JS API Version: ", js_api_version);
+ u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
+ });
+ } else {
+ // We know the JS API version. Send the actual sign request in the supported API version.
+ u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
+ }
+};
+
+/**
+ * Dispatches an array of sign requests to available U2F tokens.
+ * @param {string=} appId
+ * @param {string=} challenge
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.SignResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+ var reqId = ++u2f.reqCounter_;
+ u2f.callbackMap_[reqId] = callback;
+ var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+ opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+ var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
+ port.postMessage(req);
+ });
+};
+
+/**
+ * Dispatches register requests to available U2F tokens. An array of sign
+ * requests identifies already registered tokens.
+ * If the JS API version supported by the extension is unknown, it first sends a
+ * message to the extension to find out the supported API version and then it sends
+ * the register request.
+ * @param {string=} appId
+ * @param {Array<u2f.RegisterRequest>} registerRequests
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.RegisterResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
+ if (js_api_version === undefined) {
+ // Send a message to get the extension to JS API version, then send the actual register request.
+ u2f.getApiVersion(
+ function (response) {
+ js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version'];
+ console.log("Extension JS API Version: ", js_api_version);
+ u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
+ callback, opt_timeoutSeconds);
+ });
+ } else {
+ // We know the JS API version. Send the actual register request in the supported API version.
+ u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
+ callback, opt_timeoutSeconds);
+ }
+};
+
+/**
+ * Dispatches register requests to available U2F tokens. An array of sign
+ * requests identifies already registered tokens.
+ * @param {string=} appId
+ * @param {Array<u2f.RegisterRequest>} registerRequests
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.RegisterResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+ var reqId = ++u2f.reqCounter_;
+ u2f.callbackMap_[reqId] = callback;
+ var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+ opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+ var req = u2f.formatRegisterRequest_(
+ appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
+ port.postMessage(req);
+ });
+};
+
+
+/**
+ * Dispatches a message to the extension to find out the supported
+ * JS API version.
+ * If the user is on a mobile phone and is thus using Google Authenticator instead
+ * of the Chrome extension, don't send the request and simply return 0.
+ * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.getApiVersion = function(callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+ // If we are using Android Google Authenticator or iOS client app,
+ // do not fire an intent to ask which JS API version to use.
+ if (port.getPortType) {
+ var apiVersion;
+ switch (port.getPortType()) {
+ case 'WrappedIosPort_':
+ case 'WrappedAuthenticatorPort_':
+ apiVersion = 1.1;
+ break;
+
+ default:
+ apiVersion = 0;
+ break;
+ }
+ callback({ 'js_api_version': apiVersion });
+ return;
+ }
+ var reqId = ++u2f.reqCounter_;
+ u2f.callbackMap_[reqId] = callback;
+ var req = {
+ type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
+ timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
+ opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
+ requestId: reqId
+ };
+ port.postMessage(req);
+ });
+};
diff --git a/java/vaadin/src/main/java/ch/asynk/security/u2f_connector.js b/java/vaadin/src/main/java/ch/asynk/security/u2f_connector.js
new file mode 100644
index 0000000..8723cb3
--- /dev/null
+++ b/java/vaadin/src/main/java/ch/asynk/security/u2f_connector.js
@@ -0,0 +1,30 @@
+window.ch_asynk_security_U2fConnector = function() {
+
+ var connector = this;
+
+ this.register = function(requestJson) {
+ var request = JSON.parse(requestJson);
+ // alert(JSON.stringify(request));
+ setTimeout(function() {
+ u2f.register(request.registerRequests, request.authenticateRequests, function(data) {
+ if(data.errorCode)
+ connector.onRegisterResponse(data.errorCode, request);
+ else
+ connector.onRegisterResponse(JSON.stringify(data));
+ });
+ }, 500);
+ }
+
+ this.authenticate = function(requestJson) {
+ var request = JSON.parse(requestJson);
+ //alert(JSON.stringify(request));
+ setTimeout(function() {
+ u2f.sign(request.authenticateRequests, function(data) {
+ if(data.errorCode)
+ connector.onAuthenticateResponse(data.errorCode, request);
+ else
+ connector.onAuthenticateResponse(JSON.stringify(data));
+ });
+ }, 500);
+ }
+}
diff --git a/java/vaadin/src/main/java/ch/asynk/ui/Icons.java b/java/vaadin/src/main/java/ch/asynk/ui/Icons.java
index 98bfb5f..3cc96bc 100644
--- a/java/vaadin/src/main/java/ch/asynk/ui/Icons.java
+++ b/java/vaadin/src/main/java/ch/asynk/ui/Icons.java
@@ -6,4 +6,13 @@ public class Icons
{
public static final ThemeResource help = new ThemeResource("icons/help.png");
public static final ThemeResource home = new ThemeResource("icons/home.png");
+ public static final ThemeResource u2f128 = new ThemeResource("icons/u2f-128.png");
+ public static final ThemeResource u2f64 = new ThemeResource("icons/u2f-64.png");
+ public static final ThemeResource u2f32 = new ThemeResource("icons/u2f-32.png");
+ public static final ThemeResource u2f24 = new ThemeResource("icons/u2f-24.png");
+ public static final ThemeResource u2flogo = new ThemeResource("icons/u2f-logo.png");
+ public static final ThemeResource u2flogo48 = new ThemeResource("icons/u2f-logo-48.png");
+ public static final ThemeResource u2flock = new ThemeResource("icons/u2f-lock.png");
+ public static final ThemeResource thumbUp = new ThemeResource("icons/thumb_up.png");
+ public static final ThemeResource thumbDown = new ThemeResource("icons/thumb_down.png");
}
diff --git a/java/vaadin/src/main/java/ch/asynk/ui/ViewMain.java b/java/vaadin/src/main/java/ch/asynk/ui/ViewMain.java
index 489e328..66ef64f 100644
--- a/java/vaadin/src/main/java/ch/asynk/ui/ViewMain.java
+++ b/java/vaadin/src/main/java/ch/asynk/ui/ViewMain.java
@@ -1,20 +1,43 @@
package ch.asynk.ui;
+import com.vaadin.data.validator.RegexpValidator;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
+import com.vaadin.server.Page;
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Image;
import com.vaadin.ui.Label;
+import com.vaadin.ui.Notification;
+import com.vaadin.ui.Panel;
import com.vaadin.ui.TabSheet;
+import com.vaadin.ui.TextField;
+import com.vaadin.ui.UI;
+import com.vaadin.ui.GridLayout;
import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.Window;
-public class ViewMain extends TabSheet implements View
+import ch.asynk.security.U2fConnector;
+
+public class ViewMain extends TabSheet implements View, U2fConnector.U2fListener
{
private static final long serialVersionUID = 1L;
+ private static final String U2F_TITLE = "FIDO U2F (Universal 2nd factor)";
+
+ private final Window u2fWindow;
+ private final U2fConnector u2fConnector;
+
+ private enum Action { U2F_REGISTER, U2F_AUTHENTICATE }
+
public ViewMain()
{
+ u2fWindow = u2fWindow();
+ u2fConnector = new U2fConnector();
addStyleName("framed");
- addTab(content("0"), "Tab 0", Icons.home, 0);
- addTab(content("1"), "Tab 1");
+ addTab(u2fComponent("register", Action.U2F_REGISTER), "Register", Icons.u2flogo48);
+ addTab(u2fComponent("authenticate", Action.U2F_AUTHENTICATE), "Authenticate", Icons.u2f32);
setSelectedTab(2);
}
@@ -29,4 +52,110 @@ public class ViewMain extends TabSheet implements View
ly.addComponent(new Label(" Hi, this is " + s));
return ly;
}
+
+ private Component u2fComponent(final String title, Action action)
+ {
+ final TextField tf = new TextField();
+ tf.setRequired(true);
+ tf.setInputPrompt("username");
+ tf.addValidator(new RegexpValidator("^(?=.{6,20}$)[a-zA-Z][a-zA-Z0-9#@.]+[a-zA-Z]$", "invalid username"));
+
+ final ViewMain instance = this;
+ final Button bt = new Button(title.toLowerCase());
+ bt.addClickListener(new Button.ClickListener() {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public void buttonClick(Button.ClickEvent event) {
+ try {
+ tf.validate();
+ String userId = tf.getValue();
+ tf.setValue("");
+ if (action == Action.U2F_REGISTER)
+ u2fConnector.sendRegisterRequest(userId, instance);
+ else if (action == Action.U2F_AUTHENTICATE)
+ u2fConnector.startAuthentication(userId, instance);
+ } catch (com.yubico.u2f.exceptions.U2fBadConfigurationException e) {
+ Notification.show(U2F_TITLE, e.getMessage(), Notification.Type.ERROR_MESSAGE);
+ } catch (Exception e) {
+ Notification.show("invalid user name");
+ }
+ }
+ });
+
+ Panel panel = new Panel(title);
+ panel.setWidth("450px");
+ final GridLayout ly = new GridLayout(2,2);
+ ly.setMargin(true);
+ ly.setSpacing(true);
+ panel.setContent(ly);
+ ly.addComponent(new Image(null, Icons.u2flock), 0, 0, 0, 1);
+ ly.addComponent(tf, 1, 0);
+ ly.addComponent(bt,1,1);
+ ly.setComponentAlignment(bt, Alignment.MIDDLE_CENTER);
+
+ VerticalLayout vl = new VerticalLayout();
+ vl.setMargin(true);
+ vl.setSizeFull();
+ vl.addComponent(panel);
+ vl.setComponentAlignment(panel, Alignment.MIDDLE_CENTER);
+ return vl;
+ }
+
+ public void u2fCallback(U2fConnector.U2fAction action, String msg)
+ {
+ Notification n = null;
+ switch (action) {
+ case REGISTRATION_PENDING:
+ case AUTHENTICATION_PENDING:
+ UI.getCurrent().addWindow(u2fWindow);
+ break;
+ case REGISTRATION_SUCCESS:
+ msg = "Your token has been successfully registred.";
+ break;
+ case REGISTRATION_FAILURE:
+ msg = "Registration failed with error : " + msg;
+ break;
+ case AUTHENTICATION_SUCCESS:
+ msg = "You have been successfully authenticated.";
+ break;
+ case AUTHENTICATION_FAILURE:
+ msg = "Authentication failed with error : " + msg;
+ break;
+ }
+ switch (action) {
+ case REGISTRATION_SUCCESS:
+ case AUTHENTICATION_SUCCESS:
+ n = new Notification(U2F_TITLE, msg, Notification.Type.HUMANIZED_MESSAGE);
+ n.setIcon(Icons.thumbUp);
+ break;
+ case REGISTRATION_FAILURE:
+ case AUTHENTICATION_FAILURE:
+ n = new Notification(U2F_TITLE, msg, Notification.Type.ERROR_MESSAGE);
+ n.setIcon(Icons.thumbDown);
+ break;
+ }
+ if (n != null) {
+ n.setDelayMsec(5000);
+ u2fWindow.close();
+ n.show(Page.getCurrent());
+ }
+ }
+
+ private Window u2fWindow()
+ {
+ Window w = new Window(U2F_TITLE);
+ final VerticalLayout vy = new VerticalLayout();
+ vy.setMargin(true);
+ vy.setSpacing(true);
+ vy.addComponent(new Label("Insert your u2f token and click on it."));
+ vy.addComponent(new Image(null, Icons.u2flock));
+ w.setModal(true);
+ w.setClosable(false);
+ w.setContent(vy);
+ w.setResizable(false);
+ w.setWidth("280px");
+ w.setHeight("220px");
+ w.center();
+ return w;
+ }
}
diff --git a/java/vaadin/src/run/java/ch/asynk/Main.java b/java/vaadin/src/run/java/ch/asynk/Main.java
index 222fccb..005ac9c 100644
--- a/java/vaadin/src/run/java/ch/asynk/Main.java
+++ b/java/vaadin/src/run/java/ch/asynk/Main.java
@@ -84,7 +84,7 @@ class HttpVaadinJettyServer extends VaadinJettyServer
@Override
protected void configure(int port)
{
- System.out.println("http://127.0.0.1:" + port + "/hello");
+ System.out.println("http://localhost:" + port + "/hello");
addConnector(httpConnector(port));
}
}
@@ -96,7 +96,7 @@ class HttpsVaadinJettyServer extends VaadinJettyServer
@Override
protected void configure(int port)
{
- System.out.println("https://127.0.0.1:" + port + "/hello");
+ System.out.println("https://localhost:" + port + "/hello");
addConnector(httpsConnector(port));
}
}
@@ -107,8 +107,8 @@ class HttpHttpsVaadinJettyServer extends HttpsVaadinJettyServer
@Override
protected void configure(int port)
{
- System.out.println("http://127.0.0.1:" + port + "/hello");
- System.out.println("https://127.0.0.1:" + (port + 1) + "/hello");
+ System.out.println("http://localhost:" + port + "/hello");
+ System.out.println("https://localhost:" + (port + 1) + "/hello");
this.setConnectors(new Connector[] { httpConnector(port), httpsConnector(port + 1) });
}
}
@@ -121,6 +121,6 @@ public class Main
String webRoot = System.getProperty("WEBROOT");
if (webRoot == null) webRoot = "./src/main/WebContent";
- new HttpVaadinJettyServer(port, webRoot).start();
+ new HttpsVaadinJettyServer(port, webRoot).start();
}
}