From 8235a930d73b17b8690cfe695de72da04b0aae04 Mon Sep 17 00:00:00 2001 From: Santiago Garcia Mantinan <43165226+mantinan@users.noreply.github.com> Date: Mon, 15 Mar 2021 11:12:36 +0100 Subject: [PATCH] Commmit changes provided by Jakob on https://github.com/andOTP/andOTP/files/6136564/motp_fixes.patch.txt --- .../flocke/andotp/Database/Entry.java | 33 +++++++---- .../andotp/Dialogs/ManualEntryDialog.java | 25 +++++++- .../andotp/Utilities/TokenCalculator.java | 24 ++++---- .../andotp/View/EntriesCardAdapter.java | 57 +++++++++---------- .../flocke/andotp/View/EntryViewHolder.java | 9 --- 5 files changed, 84 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Database/Entry.java b/app/src/main/java/org/shadowice/flocke/andotp/Database/Entry.java index 5427e683..63b52dc7 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Database/Entry.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Database/Entry.java @@ -26,6 +26,7 @@ package org.shadowice.flocke.andotp.Database; import android.net.Uri; import org.apache.commons.codec.binary.Base32; +import org.apache.commons.codec.binary.Hex; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -173,7 +174,7 @@ public class Entry { this.label = label; if(type == OTPType.MOTP) { this.secret = secret.getBytes(); - }else{ + } else { this.secret = new Base32().decode(secret.toUpperCase()); } @@ -311,14 +312,22 @@ public class Entry { case STEAM: type = "steam"; break; + case MOTP: + type = "motp"; + break; default: return null; } Uri.Builder builder = new Uri.Builder() .scheme("otpauth") .authority(type) - .appendPath(this.label) - .appendQueryParameter("secret", new Base32().encodeAsString(this.secret)); + .appendPath(this.label); + + if (this.type == OTPType.MOTP) + builder.appendQueryParameter("secret", new String(this.secret)); + else + builder.appendQueryParameter("secret", new Base32().encodeAsString(this.secret)); + if (this.issuer != null) { builder.appendQueryParameter("issuer", this.issuer); } @@ -361,7 +370,10 @@ public class Entry { } public String getSecretEncoded() { - return new String(new Base32().encode(secret)); + if (type == OTPType.MOTP) + return new String(secret); + else + return new String(new Base32().encode(secret)); } public void setSecret(byte[] secret) { @@ -478,10 +490,6 @@ public class Entry { public void setListId(long newId) { listId = newId; } - - public boolean updateOTP() { - return updateOTP(false); - } public boolean updateOTP(boolean updateNow) { if (type == OTPType.TOTP || type == OTPType.STEAM) { @@ -512,7 +520,7 @@ public class Entry { if (currentPin.isEmpty()) { currentOTP = MOTP_NO_PIN_CODE; } else { - currentOTP = TokenCalculator.MOTP(currentPin, new String(this.secret)); + currentOTP = TokenCalculator.MOTP(currentPin, new String(this.secret), time); } last_update = counter; setColor(COLOR_DEFAULT); @@ -589,9 +597,12 @@ public class Entry { return color; } - public static boolean validateSecret(String secret) { + public static boolean validateSecret(String secret, OTPType type) { try { - new Base32().decode(secret.toUpperCase()); + if (type == OTPType.MOTP) + Hex.decodeHex(secret); + else + new Base32().decode(secret.toUpperCase()); } catch (Exception e) { return false; } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Dialogs/ManualEntryDialog.java b/app/src/main/java/org/shadowice/flocke/andotp/Dialogs/ManualEntryDialog.java index 89f0a640..41bd5beb 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Dialogs/ManualEntryDialog.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Dialogs/ManualEntryDialog.java @@ -202,13 +202,13 @@ public class ManualEntryDialog { positiveButton.setOnClickListener(view -> { //Replace spaces with empty characters String secret = secretInput.getText().toString().replaceAll("\\s+",""); + Entry.OTPType type = (Entry.OTPType) typeInput.getSelectedItem(); - if (!Entry.validateSecret(secret)) { + if (!Entry.validateSecret(secret, type)) { secretInput.setError(callingActivity.getString(R.string.error_invalid_secret)); return; } - Entry.OTPType type = (Entry.OTPType) typeInput.getSelectedItem(); TokenCalculator.HashAlgorithm algorithm = (TokenCalculator.HashAlgorithm) algorithmInput.getSelectedItem(); int digits = Integer.parseInt(digitsInput.getText().toString()); @@ -259,6 +259,27 @@ public class ManualEntryDialog { if (updateCallback != null) updateCallback.onUpdate(); } + } else if (type == Entry.OTPType.MOTP) { + if (isNewEntry) { + Entry newEntry; + + newEntry = new Entry(type, secret, issuer, label, tagsAdapter.getActiveTags()); + newEntry.updateOTP(false); + newEntry.setLastUsed(System.currentTimeMillis()); + + adapter.addEntry(newEntry); + } else { + oldEntry.setIssuer(issuer, true); + oldEntry.setLabel(label); + oldEntry.setTags(tagsAdapter.getActiveTags()); + + oldEntry.updateOTP(false); + + if (updateCallback != null) + updateCallback.onUpdate(); + } + + callingActivity.refreshTags(); } dialog.dismiss(); diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/TokenCalculator.java b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/TokenCalculator.java index 083f9ac6..7e157c29 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/TokenCalculator.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/TokenCalculator.java @@ -23,11 +23,12 @@ package org.shadowice.flocke.andotp.Utilities; +import org.apache.commons.codec.binary.Hex; + import java.nio.ByteBuffer; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Date; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; @@ -118,28 +119,25 @@ public class TokenCalculator { return r; } - public static String MOTP(String PIN, String secret) + public static String MOTP(String PIN, String secret, long epoch) { - Date dateNow = new Date(); - String epoch = "" + (dateNow.getTime()); - epoch = epoch.substring(0, epoch.length() - 4); - String otp = epoch + secret + PIN; + String epochText = String.valueOf(epoch / 10); + String hashText = epochText + secret + PIN; + String otp = ""; + try { // Create MD5 Hash MessageDigest digest = MessageDigest.getInstance("MD5"); - digest.update(otp.getBytes()); - byte messageDigest[] = digest.digest(); + digest.update(hashText.getBytes()); + byte[] messageDigest = digest.digest(); // Create Hex String - StringBuffer hexString = new StringBuffer(); - for (int i = 0; i < messageDigest.length; i++) - hexString.append(Integer.toHexString(0xFF & messageDigest[i])); - + String hexString = Hex.encodeHexString(messageDigest); otp = hexString.substring(0, 6); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); - otp=""; } + return otp; } } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/View/EntriesCardAdapter.java b/app/src/main/java/org/shadowice/flocke/andotp/View/EntriesCardAdapter.java index 0808304b..ec4f4039 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/View/EntriesCardAdapter.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/View/EntriesCardAdapter.java @@ -39,11 +39,10 @@ import android.text.TextUtils; import android.text.TextWatcher; import android.text.method.PasswordTransformationMethod; import android.view.LayoutInflater; -import android.view.Menu; import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.Filter; @@ -312,15 +311,21 @@ public class EntriesCardAdapter extends RecyclerView.Adapter public void onCardSingleClicked(final int position, final String text) { switch (settings.getTapSingle()) { case REVEAL: + establishPinIfNeeded(position); cardTapToRevealHandler(position); break; case COPY: + establishPinIfNeeded(position); copyHandler(position, text, false); break; case COPY_BACKGROUND: + establishPinIfNeeded(position); copyHandler(position, text, true); break; default: + // If tap-to-reveal is disabled a single tab still needs to establish the PIN + if (!settings.getTapToReveal()) + establishPinIfNeeded(position); break; } } @@ -329,12 +334,15 @@ public class EntriesCardAdapter extends RecyclerView.Adapter public void onCardDoubleClicked(final int position, final String text) { switch (settings.getTapDouble()) { case REVEAL: + establishPinIfNeeded(position); cardTapToRevealHandler(position); break; case COPY: + establishPinIfNeeded(position); copyHandler(position, text, false); break; case COPY_BACKGROUND: + establishPinIfNeeded(position); copyHandler(position, text, true); break; default: @@ -351,19 +359,18 @@ public class EntriesCardAdapter extends RecyclerView.Adapter public void onCounterLongPressed(int position) { setCounter(position); } - - @Override - public void onItemClickListener(int position) { - final Entry entry = displayedEntries.get(position); - if(entry.getType() == Entry.OTPType.MOTP && entry.getPin().isEmpty()){ - establishPIN(position); - } - } }); return viewHolder; } + private void establishPinIfNeeded(int position) { + final Entry entry = displayedEntries.get(position); + + if (entry.getType() == Entry.OTPType.MOTP && entry.getPin().isEmpty()) + establishPIN(position); + } + private void copyHandler(final int position, final String text, final boolean dropToBackground) { Tools.copyToClipboard(context, text); updateLastUsedAndFrequency(position, getRealIndex(position)); @@ -620,7 +627,7 @@ public class EntriesCardAdapter extends RecyclerView.Adapter input.setSingleLine(); input.requestFocus(); input.setTransformationMethod(new PasswordTransformationMethod()); - UIHelper.showKeyboard(context,input,true); + UIHelper.showKeyboard(context, input, true); FrameLayout container = new FrameLayout(context); container.setPaddingRelative(marginMedium, marginSmall, marginMedium, 0); @@ -629,26 +636,18 @@ public class EntriesCardAdapter extends RecyclerView.Adapter builder.setTitle(R.string.dialog_title_pin) .setCancelable(false) .setView(container) - .setPositiveButton(R.string.button_accept, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - int realIndex = getRealIndex(pos); - String newPin = input.getEditableText().toString(); + .setPositiveButton(R.string.button_accept, (dialogInterface, i) -> { + int realIndex = getRealIndex(pos); + String newPin = input.getEditableText().toString(); - displayedEntries.get(pos).setPin(newPin); - Entry e = entries.get(realIndex); - e.setPin(newPin); - e.updateOTP(true); - notifyDataSetChanged(); - UIHelper.hideKeyboard(context,input); - } - }) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - UIHelper.hideKeyboard(context,input); - } + displayedEntries.get(pos).setPin(newPin); + Entry e = entries.getEntry(realIndex); + e.setPin(newPin); + e.updateOTP(true); + notifyItemChanged(pos); + UIHelper.hideKeyboard(context, input); }) + .setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> UIHelper.hideKeyboard(context, input)) .create() .show(); } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/View/EntryViewHolder.java b/app/src/main/java/org/shadowice/flocke/andotp/View/EntryViewHolder.java index f06d4e3a..ef578348 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/View/EntryViewHolder.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/View/EntryViewHolder.java @@ -151,14 +151,6 @@ public class EntryViewHolder extends RecyclerView.ViewHolder ); } }); - - itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (callback != null) - callback.onItemClickListener(getAdapterPosition()); - } - }); } @FunctionalInterface @@ -362,7 +354,6 @@ public class EntryViewHolder extends RecyclerView.ViewHolder void onCounterClicked(int position); void onCounterLongPressed(int position); - void onItemClickListener(int position); } /** * Updates the color of OTP to red (if expiring) or default color (if new OTP)