Commmit changes provided by Jakob on
https://github.com/andOTP/andOTP/files/6136564/motp_fixes.patch.txt
This commit is contained in:
parent
bde29f11cc
commit
8235a930d7
5 changed files with 84 additions and 64 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<EntryViewHolder>
|
|||
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<EntryViewHolder>
|
|||
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<EntryViewHolder>
|
|||
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<EntryViewHolder>
|
|||
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<EntryViewHolder>
|
|||
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();
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue