diff --git a/app/build.gradle b/app/build.gradle index 51f48f5..af691c4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ android { def versionMajor = 2 def versionMinor = 0 - def versionPatch = 3 - def versionAdditive = 1 + def versionPatch = 4 + def versionAdditive = 0 versionCode versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionAdditive versionName "${versionMajor}.${versionMinor}.${versionPatch}b${versionAdditive}" @@ -51,6 +51,7 @@ android { sourceCompatibility 1.8 targetCompatibility 1.8 } + buildToolsVersion = '29.0.2' } dependencies { @@ -60,7 +61,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.core:core:1.2.0-rc01' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'com.google.android.material:material:1.2.0-alpha02' + implementation 'com.google.android.material:material:1.2.0-alpha03' implementation 'commons-codec:commons-codec:1.10' implementation 'androidx.preference:preference:1.1.0' implementation 'junit:junit:4.12' @@ -69,8 +70,8 @@ dependencies { implementation 'androidx.vectordrawable:vectordrawable:1.1.0' implementation 'com.google.code.gson:gson:2.8.5' - implementation 'com.google.firebase:firebase-core:17.2.1' - implementation 'com.google.firebase:firebase-messaging:20.0.1' + implementation 'com.google.firebase:firebase-core:17.2.2' + implementation 'com.google.firebase:firebase-messaging:20.1.0' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:1.10.19' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2b40024..fc9a3a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,7 +10,8 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning"> loadTokens() { + public ArrayList loadTokens() throws IOException, GeneralSecurityException { logprint("LOADING TOKEN"); ArrayList tokens = new ArrayList<>(); try { @@ -157,7 +159,7 @@ public ArrayList loadTokens() { * * @param tokens ArrayList of tokens to save */ - public void saveTokens(ArrayList tokens) { + public void saveTokens(ArrayList tokens) throws GeneralSecurityException, IOException { if (tokens == null) { return; } @@ -170,12 +172,8 @@ public void saveTokens(ArrayList tokens) { } } - try { - if (saveToFile(DATAFILE, tmp.toString().getBytes())) { - logprint("Tokenlist saved."); - } - } catch (InvalidKeyException e) { - e.printStackTrace(); + if (saveToFile(DATAFILE, tmp.toString().getBytes())) { + logprint("Tokenlist saved."); } } @@ -200,8 +198,7 @@ private Token makeTokenFromJSON(JSONObject o) throws JSONException { t.enrollment_credential = o.getString(ENROLLMENT_CRED); t.sslVerify = o.getBoolean(SSL_VERIFY); try { - t.rollout_expiration = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") - .parse(o.getString(ROLLOUT_EXPIRATION)); + t.rollout_expiration = dateFormat.parse(o.getString(ROLLOUT_EXPIRATION)); } catch (ParseException e) { e.printStackTrace(); } @@ -260,8 +257,7 @@ private JSONObject makeJSONfromToken(Token t) throws JSONException { // If the rollout is not finished yet, save the data necessary to complete it if (t.state.equals(UNFINISHED)) { o.put(URL, t.rollout_url); - o.put(ROLLOUT_EXPIRATION, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") - .format(t.rollout_expiration)); + o.put(ROLLOUT_EXPIRATION, dateFormat.format(t.rollout_expiration)); o.put(ENROLLMENT_CRED, t.enrollment_credential); o.put(SSL_VERIFY, t.sslVerify); } @@ -299,7 +295,7 @@ private JSONObject makeJSONfromToken(Token t) throws JSONException { return o; } - public void storePIPubkey(String key, String serial) throws GeneralSecurityException, IllegalArgumentException { + public void storePIPubkey(String key, String serial) throws GeneralSecurityException, IllegalArgumentException, IOException { byte[] keybytes = decodeBase64(key); PublicKey pubkey = PKCS1ToSubjectPublicKeyInfo.decodePKCS1PublicKey(keybytes); @@ -313,25 +309,18 @@ public void storePIPubkey(String key, String serial) throws GeneralSecurityExcep PublicKey pubkey = kf.generatePublic(keySpec); */ } - public PublicKey getPIPubkey(String serial) { + public PublicKey getPIPubkey(String serial) throws GeneralSecurityException, IOException { if (baseFilePath == null) return null; return getPIPubkey(baseFilePath, serial); } - PublicKey getPIPubkey(String filepath, String serial) { - try { - byte[] keybytes = loadDataFromFile(serial + "_" + PUBKEYFILE, filepath); - // build pubkey - if (keybytes == null) return null; - X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(keybytes); - KeyFactory kf = KeyFactory.getInstance("RSA"); - return kf.generatePublic(X509publicKey); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } catch (InvalidKeySpecException e) { - e.printStackTrace(); - } - return null; + PublicKey getPIPubkey(String filepath, String serial) throws GeneralSecurityException, IOException { + byte[] keybytes = loadDataFromFile(serial + "_" + PUBKEYFILE, filepath); + // build pubkey + if (keybytes == null) return null; + X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(keybytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePublic(X509publicKey); } /** @@ -341,7 +330,7 @@ PublicKey getPIPubkey(String filepath, String serial) { * @param fileName Name of the file to load * @return raw data as byte array, null if no baseFilePath is set or there is no file */ - private byte[] loadDataFromFile(String fileName) { + private byte[] loadDataFromFile(String fileName) throws IOException, GeneralSecurityException { if (baseFilePath == null) return null; return loadDataFromFile(fileName, baseFilePath); } @@ -354,20 +343,14 @@ private byte[] loadDataFromFile(String fileName) { * @param baseFilePath baseFilePath of the Context * @return raw data as byte array, null if there is no file */ - private byte[] loadDataFromFile(String fileName, String baseFilePath) { - try { - byte[] encryptedData = readFile(new File(baseFilePath + "/" + fileName)); - // decrypt - SecretKey encryptionKey = getSecretKey(new File(baseFilePath + "/" + KEYFILE)); - if (encryptedData == null) { - return null; - } - return decrypt(encryptionKey, encryptedData); - } catch (Exception e) { - // combine exceptions here, nothing would be done anyway - e.printStackTrace(); + private byte[] loadDataFromFile(String fileName, String baseFilePath) throws IOException, GeneralSecurityException { + byte[] encryptedData = readFile(new File(baseFilePath + "/" + fileName)); + // decrypt + SecretKey encryptionKey = getSecretKey(new File(baseFilePath + "/" + KEYFILE)); + if (encryptedData == null) { + return null; } - return null; + return decrypt(encryptionKey, encryptedData); } private void writeFile(File file, byte[] data) throws IOException { @@ -406,7 +389,7 @@ public void removePubkeyFor(String serial) { } } - public void storeFirebaseConfig(FirebaseInitConfig firebaseInitConfig) throws InvalidKeyException { + public void storeFirebaseConfig(FirebaseInitConfig firebaseInitConfig) throws GeneralSecurityException, IOException { logprint("Storing Firebase config..."); JSONObject o = new JSONObject(); try { @@ -425,29 +408,22 @@ public void storeFirebaseConfig(FirebaseInitConfig firebaseInitConfig) throws In /** * @return FirebaseInitConfig object or null if there is no config / error */ - public FirebaseInitConfig loadFirebaseConfig() { + public FirebaseInitConfig loadFirebaseConfig() throws IOException, GeneralSecurityException, JSONException { logprint("Loading Firebase config..."); - try { - byte[] data = loadDataFromFile(FB_CONFIG_FILE); - if (data == null) { - logprint("Firebase config not found!"); - return null; - } - - JSONObject o = new JSONObject(new String(data)); - String projID = o.getString(PROJECT_ID); - String appID = o.getString(APP_ID); - String api_key = o.getString(API_KEY); - String projNumber = o.getString(PROJECT_NUMBER); - - //logprint("Firebase config loaded."); - return new FirebaseInitConfig(projID, appID, api_key, projNumber); - - } catch (Exception e) { - e.printStackTrace(); - logprint("Missing parameter from config!"); + byte[] data = loadDataFromFile(FB_CONFIG_FILE); + if (data == null) { + logprint("Firebase config not found!"); return null; } + + JSONObject o = new JSONObject(new String(data)); + String projID = o.getString(PROJECT_ID); + String appID = o.getString(APP_ID); + String api_key = o.getString(API_KEY); + String projNumber = o.getString(PROJECT_NUMBER); + + //logprint("Firebase config loaded."); + return new FirebaseInitConfig(projID, appID, api_key, projNumber); } public void removeFirebaseConfig() { @@ -468,20 +444,11 @@ public void removeFirebaseConfig() { * @param data Data to save * @return true if successful, false if error */ - private boolean saveToFile(String fileName, String baseFilePath, byte[] data) throws InvalidKeyException { - try { - SecretKey key = getSecretKey(new File(baseFilePath + "/" + KEYFILE)); - data = encrypt(key, data); - writeFile(new File(baseFilePath + "/" + fileName), data); - return true; - } catch (InvalidKeyException e) { - throw e; - } catch (GeneralSecurityException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - return false; + private boolean saveToFile(String fileName, String baseFilePath, byte[] data) throws GeneralSecurityException, IOException { + SecretKey key = getSecretKey(new File(baseFilePath + "/" + KEYFILE)); + data = encrypt(key, data); + writeFile(new File(baseFilePath + "/" + fileName), data); + return true; } /** @@ -492,7 +459,7 @@ private boolean saveToFile(String fileName, String baseFilePath, byte[] data) th * @param data Data to save * @return true if successful, false if error */ - public boolean saveToFile(String fileName, byte[] data) throws InvalidKeyException { + public boolean saveToFile(String fileName, byte[] data) throws GeneralSecurityException, IOException { if (baseFilePath == null) return false; return saveToFile(fileName, baseFilePath, data); } @@ -644,9 +611,11 @@ public static byte[] hexStringToByteArray(String hex) { } public static void logprint(String msg) { - if (msg == null) - return; - Log.e(TAG, msg); + if (BuildConfig.DEBUG) { + if (msg == null) + return; + Log.e(TAG, msg); + } } public static String insertPeriodically(String text, int stepSize) { diff --git a/app/src/main/java/it/netknights/piauthenticator/viewcontroller/MainActivity.java b/app/src/main/java/it/netknights/piauthenticator/viewcontroller/MainActivity.java index b4fc48e..bf7abc3 100644 --- a/app/src/main/java/it/netknights/piauthenticator/viewcontroller/MainActivity.java +++ b/app/src/main/java/it/netknights/piauthenticator/viewcontroller/MainActivity.java @@ -150,13 +150,9 @@ protected void onCreate(Bundle savedInstanceState) { secretKeyWrapper = null; try { secretKeyWrapper = new SecretKeyWrapper(this); - } catch (GeneralSecurityException e) { + } catch (GeneralSecurityException | IllegalStateException | IOException e) { // TODO handle e e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (IllegalStateException e) { - // This exception occurs when the creation of the keypair fails, the app cannot save keys then and is unusable - makeDeviceNotSupportedDialog(); + makeDeviceNotSupportedDialog(e); } Util util = new Util(secretKeyWrapper, getFilesDir().getAbsolutePath()); @@ -166,7 +162,7 @@ protected void onCreate(Bundle savedInstanceState) { // this method checks if saving keys works, if not a dialog will appear informing the user // that the application cannot be used on the device - presenter.checkKeyStoreIsWorking(); + //presenter.checkKeyStoreIsWorking(); tokenlistadapter.setPresenterInterface(presenter); @@ -877,8 +873,14 @@ private void showAlertDialog(String title, String message, @Nullable String posi } @Override - public void makeDeviceNotSupportedDialog() { - makeAlertDialog(R.string.device_not_supported, R.string.device_not_supported_text, + public void makeDeviceNotSupportedDialog(Exception e) { + Throwable t = e.getCause(); + String cause = "Throwable is null!"; + if (t != null) { + cause = t.getLocalizedMessage(); + } + String message = getStringResource(R.string.device_not_supported_text) + "(Cause: " + cause + ")"; + makeAlertDialog(R.string.device_not_supported, message, R.string.device_not_supported_btn_text, false, (dialog, which) -> { this.finish(); // TODO this does not seem to be the best way to handle this diff --git a/app/src/main/java/it/netknights/piauthenticator/viewcontroller/TokenListAdapter.java b/app/src/main/java/it/netknights/piauthenticator/viewcontroller/TokenListAdapter.java index 7a4596b..bccc241 100644 --- a/app/src/main/java/it/netknights/piauthenticator/viewcontroller/TokenListAdapter.java +++ b/app/src/main/java/it/netknights/piauthenticator/viewcontroller/TokenListAdapter.java @@ -43,6 +43,8 @@ import androidx.core.widget.TextViewCompat; +import org.apache.commons.codec.binary.Base32; + import java.util.ArrayList; import it.netknights.piauthenticator.R; @@ -266,12 +268,10 @@ private void setupHOTP(final Token token, Button nextbtn, ProgressBar progressBa nextbtn.setVisibility(VISIBLE); nextbtn.setOnClickListener(v -> { - // disable button for some time after clicking nextbtn.setEnabled(false); new Handler().postDelayed(() -> nextbtn.setEnabled(true), 1000); presenterInterface.increaseHOTPCounter(token); - }); } diff --git a/app/src/main/res/layout/entry_push.xml b/app/src/main/res/layout/entry_push.xml index 08540fb..eca3714 100644 --- a/app/src/main/res/layout/entry_push.xml +++ b/app/src/main/res/layout/entry_push.xml @@ -1,6 +1,7 @@ +