From 0a6c89cd9dd77e1cdc5c7e7dc01083098a85a62a Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sun, 3 Jan 2021 19:54:18 +0100 Subject: [PATCH] Show a clearer error message when encountering phonefactor:// URI's Sometimes users get confused when they're trying to scan a Microsoft Authenticator QR code and think the error occurs because of a bug in Aegis. --- .../aegis/otp/GoogleAuthInfo.java | 36 +++++++++---------- .../aegis/otp/GoogleAuthInfoException.java | 21 +++++++++-- .../aegis/ui/ScannerActivity.java | 4 ++- app/src/main/res/values/strings.xml | 1 + 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java b/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java index ba5a2c0e..97d31a25 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java +++ b/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java @@ -65,7 +65,7 @@ public class GoogleAuthInfo implements Serializable { public static GoogleAuthInfo parseUri(String s) throws GoogleAuthInfoException { Uri uri = Uri.parse(s); if (uri == null) { - throw new GoogleAuthInfoException(String.format("Bad URI format: %s", s)); + throw new GoogleAuthInfoException(uri, String.format("Bad URI format: %s", s)); } return GoogleAuthInfo.parseUri(uri); } @@ -73,27 +73,27 @@ public class GoogleAuthInfo implements Serializable { public static GoogleAuthInfo parseUri(Uri uri) throws GoogleAuthInfoException { String scheme = uri.getScheme(); if (scheme == null || !scheme.equals(SCHEME)) { - throw new GoogleAuthInfoException("Unsupported protocol"); + throw new GoogleAuthInfoException(uri, String.format("Unsupported protocol: %s", scheme)); } // 'secret' is a required parameter String encodedSecret = uri.getQueryParameter("secret"); if (encodedSecret == null) { - throw new GoogleAuthInfoException("Parameter 'secret' is not present"); + throw new GoogleAuthInfoException(uri, "Parameter 'secret' is not present"); } byte[] secret; try { secret = parseSecret(encodedSecret); } catch (EncodingException e) { - throw new GoogleAuthInfoException("Bad secret", e); + throw new GoogleAuthInfoException(uri, "Bad secret", e); } OtpInfo info; try { String type = uri.getHost(); if (type == null) { - throw new GoogleAuthInfoException(String.format("Host not present in URI: %s", uri.toString())); + throw new GoogleAuthInfoException(uri, String.format("Host not present in URI: %s", uri.toString())); } switch (type) { @@ -117,16 +117,16 @@ public class GoogleAuthInfo implements Serializable { HotpInfo hotpInfo = new HotpInfo(secret); String counter = uri.getQueryParameter("counter"); if (counter == null) { - throw new GoogleAuthInfoException("Parameter 'counter' is not present"); + throw new GoogleAuthInfoException(uri, "Parameter 'counter' is not present"); } hotpInfo.setCounter(Long.parseLong(counter)); info = hotpInfo; break; default: - throw new GoogleAuthInfoException(String.format("Unsupported OTP type: %s", type)); + throw new GoogleAuthInfoException(uri, String.format("Unsupported OTP type: %s", type)); } } catch (OtpInfoException | NumberFormatException e) { - throw new GoogleAuthInfoException(e); + throw new GoogleAuthInfoException(uri, e); } // provider info used to disambiguate accounts @@ -166,7 +166,7 @@ public class GoogleAuthInfo implements Serializable { info.setDigits(Integer.parseInt(digits)); } } catch (OtpInfoException | NumberFormatException e) { - throw new GoogleAuthInfoException(e); + throw new GoogleAuthInfoException(uri, e); } return new GoogleAuthInfo(info, accountName, issuer); @@ -183,7 +183,7 @@ public class GoogleAuthInfo implements Serializable { public static Export parseExportUri(String s) throws GoogleAuthInfoException { Uri uri = Uri.parse(s); if (uri == null) { - throw new GoogleAuthInfoException("Bad URI format"); + throw new GoogleAuthInfoException(uri, "Bad URI format"); } return GoogleAuthInfo.parseExportUri(uri); } @@ -191,17 +191,17 @@ public class GoogleAuthInfo implements Serializable { public static Export parseExportUri(Uri uri) throws GoogleAuthInfoException { String scheme = uri.getScheme(); if (scheme == null || !scheme.equals(SCHEME_EXPORT)) { - throw new GoogleAuthInfoException("Unsupported protocol"); + throw new GoogleAuthInfoException(uri, "Unsupported protocol"); } String host = uri.getHost(); if (host == null || !host.equals("offline")) { - throw new GoogleAuthInfoException("Unsupported host"); + throw new GoogleAuthInfoException(uri, "Unsupported host"); } String data = uri.getQueryParameter("data"); if (data == null) { - throw new GoogleAuthInfoException("Parameter 'data' is not set"); + throw new GoogleAuthInfoException(uri, "Parameter 'data' is not set"); } GoogleAuthProtos.MigrationPayload payload; @@ -209,7 +209,7 @@ public class GoogleAuthInfo implements Serializable { byte[] bytes = Base64.decode(data); payload = GoogleAuthProtos.MigrationPayload.parseFrom(bytes); } catch (EncodingException | InvalidProtocolBufferException e) { - throw new GoogleAuthInfoException(e); + throw new GoogleAuthInfoException(uri, e); } List infos = new ArrayList<>(); @@ -227,7 +227,7 @@ public class GoogleAuthInfo implements Serializable { digits = 8; break; default: - throw new GoogleAuthInfoException(String.format("Unsupported digits: %d", params.getDigits().ordinal())); + throw new GoogleAuthInfoException(uri, String.format("Unsupported digits: %d", params.getDigits().ordinal())); } String algo; @@ -244,7 +244,7 @@ public class GoogleAuthInfo implements Serializable { algo = "SHA512"; break; default: - throw new GoogleAuthInfoException(String.format("Unsupported hash algorithm: %d", params.getAlgorithm().ordinal())); + throw new GoogleAuthInfoException(uri, String.format("Unsupported hash algorithm: %d", params.getAlgorithm().ordinal())); } byte[] secret = params.getSecret().toByteArray(); @@ -258,10 +258,10 @@ public class GoogleAuthInfo implements Serializable { otp = new HotpInfo(secret, algo, digits, params.getCounter()); break; default: - throw new GoogleAuthInfoException(String.format("Unsupported algorithm: %d", params.getType().ordinal())); + throw new GoogleAuthInfoException(uri, String.format("Unsupported algorithm: %d", params.getType().ordinal())); } } catch (OtpInfoException e){ - throw new GoogleAuthInfoException(e); + throw new GoogleAuthInfoException(uri, e); } String name = params.getName(); diff --git a/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfoException.java b/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfoException.java index cd950244..c26cd3f5 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfoException.java +++ b/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfoException.java @@ -1,16 +1,30 @@ package com.beemdevelopment.aegis.otp; +import android.net.Uri; + public class GoogleAuthInfoException extends Exception { - public GoogleAuthInfoException(Throwable cause) { + private final Uri _uri; + + public GoogleAuthInfoException(Uri uri, Throwable cause) { super(cause); + _uri = uri; } - public GoogleAuthInfoException(String message) { + public GoogleAuthInfoException(Uri uri, String message) { super(message); + _uri = uri; } - public GoogleAuthInfoException(String message, Throwable cause) { + public GoogleAuthInfoException(Uri uri, String message, Throwable cause) { super(message, cause); + _uri = uri; + } + + /** + * Reports whether the scheme of the URI is phonefactor://. + */ + public boolean isPhoneFactor() { + return _uri != null && _uri.getScheme() != null && _uri.getScheme().equals("phonefactor"); } @Override @@ -19,6 +33,7 @@ public class GoogleAuthInfoException extends Exception { if (cause == null) { return super.getMessage(); } + return String.format("%s (%s)", super.getMessage(), cause.getMessage()); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/ScannerActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/ScannerActivity.java index 2067b255..a140a986 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/ScannerActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/ScannerActivity.java @@ -156,7 +156,9 @@ public class ScannerActivity extends AegisActivity implements QrCodeAnalyzer.Lis } } catch (GoogleAuthInfoException e) { e.printStackTrace(); - Dialogs.showErrorDialog(this, R.string.read_qr_error, e, ((dialog, which) -> bindPreview(_cameraProvider))); + Dialogs.showErrorDialog(this, + e.isPhoneFactor() ? R.string.read_qr_error_phonefactor : R.string.read_qr_error, + e, ((dialog, which) -> bindPreview(_cameraProvider))); _cameraProvider.unbindAll(); } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8cc7c302..3197996e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -192,6 +192,7 @@ An error occurred while trying to enable biometric unlock. Some devices have poor implementations of biometric authentication and it is likely that yours is one of them. Consider switching to a password-only configuration instead. No cameras available An error occurred while trying to read the QR code + Aegis is not compatible with Microsoft\'s proprietary 2FA algorithm. Please make sure to select \"Setup application without notifications\" when configuring 2FA in Office 365. Raw Unlocking the vault Unlocking the vault (repairing)