Skip to content

Commit

Permalink
v2.4.6
Browse files Browse the repository at this point in the history
  • Loading branch information
dschuermann committed Aug 14, 2019
1 parent 4b72e7d commit e93c645
Show file tree
Hide file tree
Showing 33 changed files with 3,239 additions and 103 deletions.
127 changes: 89 additions & 38 deletions hwsecurity-fido/src/main/java/de/cotech/hw/fido/WebViewFidoBridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@


import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.List;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
import android.os.Parcelable;
Expand All @@ -44,7 +47,9 @@
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;

import com.google.auto.value.AutoValue;

import de.cotech.hw.fido.internal.jsapi.U2fApiUtils;
import de.cotech.hw.fido.internal.jsapi.U2fAuthenticateRequest;
import de.cotech.hw.fido.internal.jsapi.U2fJsonParser;
Expand All @@ -60,39 +65,62 @@
import de.cotech.hw.util.HwTimber;


@TargetApi(VERSION_CODES.LOLLIPOP)
/**
* If you are using a WebView for your login flow, you can use this WebViewFidoBridge
* for extending the WebView's Javascript API with the official FIDO U2F APIs.
* <p>
* Currently supported:
* - High level API of U2F v1.1, https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-javascript-api-v1.2-ps-20170411.html
* <p>
* Note: Currently only compatible and tested with Android SDK >= 19 due to evaluateJavascript() calls.
*/
@TargetApi(VERSION_CODES.KITKAT)
public class WebViewFidoBridge {
private static final String FIDO_BRIDGE_INTERFACE = "fidobridgejava";
private static final String ASSETS_BRIDGE_JS = "fidobridge.js";

private final Context context;
private final FragmentManager fragmentManager;
private final WebView webView;
private final FidoDialogOptions.Builder optionsBuilder;

private String currentLoadedHost;
private boolean loadingNewPage;


@SuppressWarnings("unused") // public API
public static WebViewFidoBridge createInstanceForWebView(AppCompatActivity activity, WebView webView) {
return createInstanceForWebView(activity.getApplicationContext(), activity.getSupportFragmentManager(), webView);
return createInstanceForWebView(activity.getApplicationContext(), activity.getSupportFragmentManager(), webView, null);
}

// TODO should this be public API?
private static WebViewFidoBridge createInstanceForWebView(
Context context, FragmentManager fragmentManager, WebView webView) {
/**
* Same as createInstanceForWebView, but allows to set FidoDialogOptions.
* <p>
* Note: Timeout and Title will be overwritten.
*/
@SuppressWarnings("unused") // public API
public static WebViewFidoBridge createInstanceForWebView(AppCompatActivity activity, WebView webView, FidoDialogOptions.Builder optionsBuilder) {
return createInstanceForWebView(activity.getApplicationContext(), activity.getSupportFragmentManager(), webView, optionsBuilder);
}

public static WebViewFidoBridge createInstanceForWebView(Context context, FragmentManager fragmentManager, WebView webView) {
return createInstanceForWebView(context, fragmentManager, webView, null);
}

@SuppressWarnings("WeakerAccess") // public API
public static WebViewFidoBridge createInstanceForWebView(Context context, FragmentManager fragmentManager, WebView webView, FidoDialogOptions.Builder optionsBuilder) {
Context applicationContext = context.getApplicationContext();

WebViewFidoBridge webViewFidoBridge = new WebViewFidoBridge(applicationContext, fragmentManager, webView);
WebViewFidoBridge webViewFidoBridge = new WebViewFidoBridge(applicationContext, fragmentManager, webView, optionsBuilder);
webViewFidoBridge.addJavascriptInterfaceToWebView();

return webViewFidoBridge;
}


private WebViewFidoBridge(Context context, FragmentManager fragmentManager, WebView webView) {
private WebViewFidoBridge(Context context, FragmentManager fragmentManager, WebView webView, FidoDialogOptions.Builder optionsBuilder) {
this.context = context;
this.fragmentManager = fragmentManager;
this.webView = webView;
this.optionsBuilder = optionsBuilder;
}

private void addJavascriptInterfaceToWebView() {
Expand All @@ -111,16 +139,26 @@ public void sign(String requestJson) {

// region delegate

@SuppressWarnings("unused") // parity with WebViewClient.shouldInterceptRequest
/**
* Call this in your WebViewClient.shouldInterceptRequest(WebView view, WebResourceRequest request)
*/
@TargetApi(VERSION_CODES.LOLLIPOP)
@SuppressWarnings("unused")
// parity with WebViewClient.shouldInterceptRequest(WebView view, WebResourceRequest request)
public void delegateShouldInterceptRequest(WebView view, WebResourceRequest request) {
HwTimber.d("shouldInterceptRequest %s", request.getUrl());
HwTimber.d("shouldInterceptRequest(WebView view, WebResourceRequest request) %s", request.getUrl());
injectOnInterceptRequest();
}

if (loadingNewPage) {
loadingNewPage = false;
HwTimber.d("Scheduling fido bridge injection!");
Handler handler = new Handler(context.getMainLooper());
handler.postAtFrontOfQueue(this::injectJavascriptFidoBridge);
}
/**
* Call this in your WebViewClient.shouldInterceptRequest(WebView view, String url)
*/
@TargetApi(VERSION_CODES.KITKAT)
@SuppressWarnings("unused")
// parity with WebViewClient.shouldInterceptRequest(WebView view, String url)
public void delegateShouldInterceptRequest(WebView view, String url) {
HwTimber.d("shouldInterceptRequest(WebView view, String url): %s", url);
injectOnInterceptRequest();
}

@SuppressWarnings("unused") // parity with WebViewClient.onPageStarted
Expand All @@ -142,6 +180,15 @@ public void delegateOnPageStarted(WebView view, String url, Bitmap favicon) {
this.loadingNewPage = true;
}

private void injectOnInterceptRequest() {
if (loadingNewPage) {
loadingNewPage = false;
HwTimber.d("Scheduling fido bridge injection!");
Handler handler = new Handler(context.getMainLooper());
handler.postAtFrontOfQueue(this::injectJavascriptFidoBridge);
}
}

private void injectJavascriptFidoBridge() {
try {
String jsContent = AndroidUtils.loadTextFromAssets(context, ASSETS_BRIDGE_JS, Charset.defaultCharset());
Expand Down Expand Up @@ -179,11 +226,15 @@ private void handleRegisterRequest(String requestJson) {
}

private void showRegisterFragment(RequestData requestData, String appId, String challenge,
Long timeoutSeconds) {
Long timeoutSeconds) {
FidoRegisterRequest registerRequest = FidoRegisterRequest.create(
appId, getCurrentFacetId(), challenge, requestData);
FidoDialogOptions fidoDialogOptions = getFidoDialogOptions(timeoutSeconds);
FidoDialogFragment fidoDialogFragment = FidoDialogFragment.newInstance(registerRequest, fidoDialogOptions);

FidoDialogOptions.Builder opsBuilder = optionsBuilder != null ? optionsBuilder : FidoDialogOptions.builder();
opsBuilder.setTimeoutSeconds(timeoutSeconds);
opsBuilder.setTitle(context.getString(R.string.hwsecurity_title_default_register_app_id, getDisplayAppId(appId)));

FidoDialogFragment fidoDialogFragment = FidoDialogFragment.newInstance(registerRequest, opsBuilder.build());
fidoDialogFragment.setFidoRegisterCallback(fidoRegisterCallback);
fidoDialogFragment.show(fragmentManager);
}
Expand All @@ -199,15 +250,12 @@ public void onFidoRegisterResponse(@NonNull FidoRegisterResponse registerRespons

@Override
public void onFidoRegisterCancel(@NonNull FidoRegisterRequest fidoRegisterRequest) {
// Google's Authenticator does not return any error code when the user closes the activity
// but we do
HwTimber.d("onRegisterCancel");
// Google's Authenticator does not return error codes when the user closes the activity, but we do
handleError(fidoRegisterRequest.getCustomData(), ErrorCode.OTHER_ERROR);
}

@Override
public void onFidoRegisterTimeout(@NonNull FidoRegisterRequest fidoRegisterRequest) {
HwTimber.d("onRegisterTimeout");
handleError(fidoRegisterRequest.getCustomData(), ErrorCode.TIMEOUT);
}
};
Expand Down Expand Up @@ -244,16 +292,19 @@ private void showSignFragment(
Long timeoutSeconds) {
FidoAuthenticateRequest authenticateRequest = FidoAuthenticateRequest.create(
appId, getCurrentFacetId(), challenge, keyHandles, requestData);
FidoDialogOptions fidoDialogOptions = getFidoDialogOptions(timeoutSeconds);
FidoDialogFragment fidoDialogFragment = FidoDialogFragment.newInstance(authenticateRequest, fidoDialogOptions);

FidoDialogOptions.Builder opsBuilder = optionsBuilder != null ? optionsBuilder : FidoDialogOptions.builder();
opsBuilder.setTimeoutSeconds(timeoutSeconds);
opsBuilder.setTitle(context.getString(R.string.hwsecurity_title_default_authenticate_app_id, getDisplayAppId(appId)));

FidoDialogFragment fidoDialogFragment = FidoDialogFragment.newInstance(authenticateRequest, opsBuilder.build());
fidoDialogFragment.setFidoAuthenticateCallback(fidoAuthenticateCallback);
fidoDialogFragment.show(fragmentManager);
}

private OnFidoAuthenticateCallback fidoAuthenticateCallback = new OnFidoAuthenticateCallback() {
@Override
public void onFidoAuthenticateResponse(@NonNull FidoAuthenticateResponse authenticateResponse) {
HwTimber.d("onAuthenticateResponse");
U2fResponse u2fResponse = U2fResponse.createAuthenticateResponse(
authenticateResponse.<RequestData>getCustomData().getRequestId(),
authenticateResponse.getClientData(),
Expand All @@ -264,15 +315,12 @@ public void onFidoAuthenticateResponse(@NonNull FidoAuthenticateResponse authent

@Override
public void onFidoAuthenticateCancel(@NonNull FidoAuthenticateRequest fidoAuthenticateRequest) {
// Google's Authenticator does not return any error code when the user closes the activity
// but we do
HwTimber.d("onAuthenticateCancel");
// Google's Authenticator does not return error codes when the user closes the activity, but we do
handleError(fidoAuthenticateRequest.getCustomData(), ErrorCode.OTHER_ERROR);
}

@Override
public void onFidoAuthenticateTimeout(@NonNull FidoAuthenticateRequest fidoAuthenticateRequest) {
HwTimber.d("onAuthenticateTimeout");
handleError(fidoAuthenticateRequest.getCustomData(), ErrorCode.TIMEOUT);
}
};
Expand All @@ -285,6 +333,15 @@ private String getCurrentFacetId() {
return "https://" + currentLoadedHost;
}

private String getDisplayAppId(String appId) {
try {
URI appIdUri = new URI(appId);
return appIdUri.getHost();
} catch (URISyntaxException e) {
throw new IllegalStateException("Invalid URI used for appId");
}
}

private void checkAppIdForFacet(String appId) throws IOException {
Uri appIdUri = Uri.parse(appId);
String appIdHost = appIdUri.getHost();
Expand All @@ -293,13 +350,6 @@ private void checkAppIdForFacet(String appId) throws IOException {
}
}

private FidoDialogOptions getFidoDialogOptions(Long timeoutSeconds) {
return FidoDialogOptions.builder()
// .setTitle(getString(R.string.fido_authenticate, getDisplayAppId(u2fAuthenticateRequest.appId)))
.setTimeoutSeconds(timeoutSeconds)
.build();
}

private void handleError(RequestData requestData, ErrorCode errorCode) {
U2fResponse u2fResponse = U2fResponse.createErrorResponse(
requestData.getType(), requestData.getRequestId(), errorCode);
Expand All @@ -320,6 +370,7 @@ public static RequestData create(String type, Long requestId) {
}

abstract String getType();

@Nullable
abstract Long getRequestId();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,28 @@
package de.cotech.hw.fido.internal;
/*
* Copyright (C) 2018-2019 Confidential Technologies GmbH
*
* You can purchase a commercial license at https://hwsecurity.dev.
* Buying such a license is mandatory as soon as you develop commercial
* activities involving this program without disclosing the source code
* of your own applications.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package de.cotech.hw.fido.internal.utils;

import android.content.Context;
import android.graphics.drawable.Animatable;
Expand All @@ -7,6 +31,7 @@
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;

import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.core.view.ViewCompat;
Expand Down
Loading

0 comments on commit e93c645

Please sign in to comment.