Skip to content

Commit e93c645

Browse files
committed
v2.4.6
1 parent 4b72e7d commit e93c645

33 files changed

+3239
-103
lines changed

hwsecurity-fido/src/main/java/de/cotech/hw/fido/WebViewFidoBridge.java

Lines changed: 89 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@
2626

2727

2828
import java.io.IOException;
29+
import java.net.URI;
30+
import java.net.URISyntaxException;
2931
import java.nio.charset.Charset;
3032
import java.util.List;
3133

3234
import android.annotation.TargetApi;
3335
import android.content.Context;
3436
import android.graphics.Bitmap;
3537
import android.net.Uri;
38+
import android.os.Build;
3639
import android.os.Build.VERSION_CODES;
3740
import android.os.Handler;
3841
import android.os.Parcelable;
@@ -44,7 +47,9 @@
4447
import androidx.annotation.Nullable;
4548
import androidx.appcompat.app.AppCompatActivity;
4649
import androidx.fragment.app.FragmentManager;
50+
4751
import com.google.auto.value.AutoValue;
52+
4853
import de.cotech.hw.fido.internal.jsapi.U2fApiUtils;
4954
import de.cotech.hw.fido.internal.jsapi.U2fAuthenticateRequest;
5055
import de.cotech.hw.fido.internal.jsapi.U2fJsonParser;
@@ -60,39 +65,62 @@
6065
import de.cotech.hw.util.HwTimber;
6166

6267

63-
@TargetApi(VERSION_CODES.LOLLIPOP)
68+
/**
69+
* If you are using a WebView for your login flow, you can use this WebViewFidoBridge
70+
* for extending the WebView's Javascript API with the official FIDO U2F APIs.
71+
* <p>
72+
* Currently supported:
73+
* - 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
74+
* <p>
75+
* Note: Currently only compatible and tested with Android SDK >= 19 due to evaluateJavascript() calls.
76+
*/
77+
@TargetApi(VERSION_CODES.KITKAT)
6478
public class WebViewFidoBridge {
6579
private static final String FIDO_BRIDGE_INTERFACE = "fidobridgejava";
6680
private static final String ASSETS_BRIDGE_JS = "fidobridge.js";
6781

6882
private final Context context;
6983
private final FragmentManager fragmentManager;
7084
private final WebView webView;
85+
private final FidoDialogOptions.Builder optionsBuilder;
7186

7287
private String currentLoadedHost;
7388
private boolean loadingNewPage;
7489

75-
90+
@SuppressWarnings("unused") // public API
7691
public static WebViewFidoBridge createInstanceForWebView(AppCompatActivity activity, WebView webView) {
77-
return createInstanceForWebView(activity.getApplicationContext(), activity.getSupportFragmentManager(), webView);
92+
return createInstanceForWebView(activity.getApplicationContext(), activity.getSupportFragmentManager(), webView, null);
7893
}
7994

80-
// TODO should this be public API?
81-
private static WebViewFidoBridge createInstanceForWebView(
82-
Context context, FragmentManager fragmentManager, WebView webView) {
95+
/**
96+
* Same as createInstanceForWebView, but allows to set FidoDialogOptions.
97+
* <p>
98+
* Note: Timeout and Title will be overwritten.
99+
*/
100+
@SuppressWarnings("unused") // public API
101+
public static WebViewFidoBridge createInstanceForWebView(AppCompatActivity activity, WebView webView, FidoDialogOptions.Builder optionsBuilder) {
102+
return createInstanceForWebView(activity.getApplicationContext(), activity.getSupportFragmentManager(), webView, optionsBuilder);
103+
}
104+
105+
public static WebViewFidoBridge createInstanceForWebView(Context context, FragmentManager fragmentManager, WebView webView) {
106+
return createInstanceForWebView(context, fragmentManager, webView, null);
107+
}
108+
109+
@SuppressWarnings("WeakerAccess") // public API
110+
public static WebViewFidoBridge createInstanceForWebView(Context context, FragmentManager fragmentManager, WebView webView, FidoDialogOptions.Builder optionsBuilder) {
83111
Context applicationContext = context.getApplicationContext();
84112

85-
WebViewFidoBridge webViewFidoBridge = new WebViewFidoBridge(applicationContext, fragmentManager, webView);
113+
WebViewFidoBridge webViewFidoBridge = new WebViewFidoBridge(applicationContext, fragmentManager, webView, optionsBuilder);
86114
webViewFidoBridge.addJavascriptInterfaceToWebView();
87115

88116
return webViewFidoBridge;
89117
}
90118

91-
92-
private WebViewFidoBridge(Context context, FragmentManager fragmentManager, WebView webView) {
119+
private WebViewFidoBridge(Context context, FragmentManager fragmentManager, WebView webView, FidoDialogOptions.Builder optionsBuilder) {
93120
this.context = context;
94121
this.fragmentManager = fragmentManager;
95122
this.webView = webView;
123+
this.optionsBuilder = optionsBuilder;
96124
}
97125

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

112140
// region delegate
113141

114-
@SuppressWarnings("unused") // parity with WebViewClient.shouldInterceptRequest
142+
/**
143+
* Call this in your WebViewClient.shouldInterceptRequest(WebView view, WebResourceRequest request)
144+
*/
145+
@TargetApi(VERSION_CODES.LOLLIPOP)
146+
@SuppressWarnings("unused")
147+
// parity with WebViewClient.shouldInterceptRequest(WebView view, WebResourceRequest request)
115148
public void delegateShouldInterceptRequest(WebView view, WebResourceRequest request) {
116-
HwTimber.d("shouldInterceptRequest %s", request.getUrl());
149+
HwTimber.d("shouldInterceptRequest(WebView view, WebResourceRequest request) %s", request.getUrl());
150+
injectOnInterceptRequest();
151+
}
117152

118-
if (loadingNewPage) {
119-
loadingNewPage = false;
120-
HwTimber.d("Scheduling fido bridge injection!");
121-
Handler handler = new Handler(context.getMainLooper());
122-
handler.postAtFrontOfQueue(this::injectJavascriptFidoBridge);
123-
}
153+
/**
154+
* Call this in your WebViewClient.shouldInterceptRequest(WebView view, String url)
155+
*/
156+
@TargetApi(VERSION_CODES.KITKAT)
157+
@SuppressWarnings("unused")
158+
// parity with WebViewClient.shouldInterceptRequest(WebView view, String url)
159+
public void delegateShouldInterceptRequest(WebView view, String url) {
160+
HwTimber.d("shouldInterceptRequest(WebView view, String url): %s", url);
161+
injectOnInterceptRequest();
124162
}
125163

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

183+
private void injectOnInterceptRequest() {
184+
if (loadingNewPage) {
185+
loadingNewPage = false;
186+
HwTimber.d("Scheduling fido bridge injection!");
187+
Handler handler = new Handler(context.getMainLooper());
188+
handler.postAtFrontOfQueue(this::injectJavascriptFidoBridge);
189+
}
190+
}
191+
145192
private void injectJavascriptFidoBridge() {
146193
try {
147194
String jsContent = AndroidUtils.loadTextFromAssets(context, ASSETS_BRIDGE_JS, Charset.defaultCharset());
@@ -179,11 +226,15 @@ private void handleRegisterRequest(String requestJson) {
179226
}
180227

181228
private void showRegisterFragment(RequestData requestData, String appId, String challenge,
182-
Long timeoutSeconds) {
229+
Long timeoutSeconds) {
183230
FidoRegisterRequest registerRequest = FidoRegisterRequest.create(
184231
appId, getCurrentFacetId(), challenge, requestData);
185-
FidoDialogOptions fidoDialogOptions = getFidoDialogOptions(timeoutSeconds);
186-
FidoDialogFragment fidoDialogFragment = FidoDialogFragment.newInstance(registerRequest, fidoDialogOptions);
232+
233+
FidoDialogOptions.Builder opsBuilder = optionsBuilder != null ? optionsBuilder : FidoDialogOptions.builder();
234+
opsBuilder.setTimeoutSeconds(timeoutSeconds);
235+
opsBuilder.setTitle(context.getString(R.string.hwsecurity_title_default_register_app_id, getDisplayAppId(appId)));
236+
237+
FidoDialogFragment fidoDialogFragment = FidoDialogFragment.newInstance(registerRequest, opsBuilder.build());
187238
fidoDialogFragment.setFidoRegisterCallback(fidoRegisterCallback);
188239
fidoDialogFragment.show(fragmentManager);
189240
}
@@ -199,15 +250,12 @@ public void onFidoRegisterResponse(@NonNull FidoRegisterResponse registerRespons
199250

200251
@Override
201252
public void onFidoRegisterCancel(@NonNull FidoRegisterRequest fidoRegisterRequest) {
202-
// Google's Authenticator does not return any error code when the user closes the activity
203-
// but we do
204-
HwTimber.d("onRegisterCancel");
253+
// Google's Authenticator does not return error codes when the user closes the activity, but we do
205254
handleError(fidoRegisterRequest.getCustomData(), ErrorCode.OTHER_ERROR);
206255
}
207256

208257
@Override
209258
public void onFidoRegisterTimeout(@NonNull FidoRegisterRequest fidoRegisterRequest) {
210-
HwTimber.d("onRegisterTimeout");
211259
handleError(fidoRegisterRequest.getCustomData(), ErrorCode.TIMEOUT);
212260
}
213261
};
@@ -244,16 +292,19 @@ private void showSignFragment(
244292
Long timeoutSeconds) {
245293
FidoAuthenticateRequest authenticateRequest = FidoAuthenticateRequest.create(
246294
appId, getCurrentFacetId(), challenge, keyHandles, requestData);
247-
FidoDialogOptions fidoDialogOptions = getFidoDialogOptions(timeoutSeconds);
248-
FidoDialogFragment fidoDialogFragment = FidoDialogFragment.newInstance(authenticateRequest, fidoDialogOptions);
295+
296+
FidoDialogOptions.Builder opsBuilder = optionsBuilder != null ? optionsBuilder : FidoDialogOptions.builder();
297+
opsBuilder.setTimeoutSeconds(timeoutSeconds);
298+
opsBuilder.setTitle(context.getString(R.string.hwsecurity_title_default_authenticate_app_id, getDisplayAppId(appId)));
299+
300+
FidoDialogFragment fidoDialogFragment = FidoDialogFragment.newInstance(authenticateRequest, opsBuilder.build());
249301
fidoDialogFragment.setFidoAuthenticateCallback(fidoAuthenticateCallback);
250302
fidoDialogFragment.show(fragmentManager);
251303
}
252304

253305
private OnFidoAuthenticateCallback fidoAuthenticateCallback = new OnFidoAuthenticateCallback() {
254306
@Override
255307
public void onFidoAuthenticateResponse(@NonNull FidoAuthenticateResponse authenticateResponse) {
256-
HwTimber.d("onAuthenticateResponse");
257308
U2fResponse u2fResponse = U2fResponse.createAuthenticateResponse(
258309
authenticateResponse.<RequestData>getCustomData().getRequestId(),
259310
authenticateResponse.getClientData(),
@@ -264,15 +315,12 @@ public void onFidoAuthenticateResponse(@NonNull FidoAuthenticateResponse authent
264315

265316
@Override
266317
public void onFidoAuthenticateCancel(@NonNull FidoAuthenticateRequest fidoAuthenticateRequest) {
267-
// Google's Authenticator does not return any error code when the user closes the activity
268-
// but we do
269-
HwTimber.d("onAuthenticateCancel");
318+
// Google's Authenticator does not return error codes when the user closes the activity, but we do
270319
handleError(fidoAuthenticateRequest.getCustomData(), ErrorCode.OTHER_ERROR);
271320
}
272321

273322
@Override
274323
public void onFidoAuthenticateTimeout(@NonNull FidoAuthenticateRequest fidoAuthenticateRequest) {
275-
HwTimber.d("onAuthenticateTimeout");
276324
handleError(fidoAuthenticateRequest.getCustomData(), ErrorCode.TIMEOUT);
277325
}
278326
};
@@ -285,6 +333,15 @@ private String getCurrentFacetId() {
285333
return "https://" + currentLoadedHost;
286334
}
287335

336+
private String getDisplayAppId(String appId) {
337+
try {
338+
URI appIdUri = new URI(appId);
339+
return appIdUri.getHost();
340+
} catch (URISyntaxException e) {
341+
throw new IllegalStateException("Invalid URI used for appId");
342+
}
343+
}
344+
288345
private void checkAppIdForFacet(String appId) throws IOException {
289346
Uri appIdUri = Uri.parse(appId);
290347
String appIdHost = appIdUri.getHost();
@@ -293,13 +350,6 @@ private void checkAppIdForFacet(String appId) throws IOException {
293350
}
294351
}
295352

296-
private FidoDialogOptions getFidoDialogOptions(Long timeoutSeconds) {
297-
return FidoDialogOptions.builder()
298-
// .setTitle(getString(R.string.fido_authenticate, getDisplayAppId(u2fAuthenticateRequest.appId)))
299-
.setTimeoutSeconds(timeoutSeconds)
300-
.build();
301-
}
302-
303353
private void handleError(RequestData requestData, ErrorCode errorCode) {
304354
U2fResponse u2fResponse = U2fResponse.createErrorResponse(
305355
requestData.getType(), requestData.getRequestId(), errorCode);
@@ -320,6 +370,7 @@ public static RequestData create(String type, Long requestId) {
320370
}
321371

322372
abstract String getType();
373+
323374
@Nullable
324375
abstract Long getRequestId();
325376
}

hwsecurity-fido/src/main/java/de/cotech/hw/fido/internal/AnimatedVectorDrawableHelper.java renamed to hwsecurity-fido/src/main/java/de/cotech/hw/fido/internal/utils/AnimatedVectorDrawableHelper.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
1-
package de.cotech.hw.fido.internal;
1+
/*
2+
* Copyright (C) 2018-2019 Confidential Technologies GmbH
3+
*
4+
* You can purchase a commercial license at https://hwsecurity.dev.
5+
* Buying such a license is mandatory as soon as you develop commercial
6+
* activities involving this program without disclosing the source code
7+
* of your own applications.
8+
*
9+
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU General Public License as published by
13+
* the Free Software Foundation, either version 3 of the License, or
14+
* (at your option) any later version.
15+
*
16+
* This program is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
* GNU General Public License for more details.
20+
*
21+
* You should have received a copy of the GNU General Public License
22+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
23+
*/
24+
25+
package de.cotech.hw.fido.internal.utils;
226

327
import android.content.Context;
428
import android.graphics.drawable.Animatable;
@@ -7,6 +31,7 @@
731
import android.os.Handler;
832
import android.os.Looper;
933
import android.widget.ImageView;
34+
1035
import androidx.annotation.NonNull;
1136
import androidx.annotation.RestrictTo;
1237
import androidx.core.view.ViewCompat;

0 commit comments

Comments
 (0)