2
0

Fixed UTF-8 issue, click on A4.4, add notification and expiry.

[FIXED]     A Java UTF-8 encoding issue.
[FIXED]     Android 4.4 wasn't triggering onClick on TextViews.
[ADDED]     A notification when the password is copied.
[ADDED]     Expire the password from the clipboard after 20 seconds.
[UPDATED]   -underscore variant of slf4j-android to make tags settable with setprops
This commit is contained in:
Maarten Billemont 2015-04-18 14:48:27 -04:00
parent e126a55912
commit 8faf6b48dd
6 changed files with 53 additions and 15 deletions

View File

@ -58,8 +58,11 @@ public class MasterKeyV0 extends MasterKey {
logger.trc( "key scope: %s", mpKeyScope ); logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) ); logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
CharBuffer mpChars = CharBuffer.wrap( masterPassword ); ByteBuffer mpBytesBuf = MP_charset.encode( CharBuffer.wrap( masterPassword ) );
byte[] mpBytes = MP_charset.encode( mpChars ).array(); byte[] mpBytes = new byte[mpBytesBuf.remaining()];
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
Arrays.fill( mpBytesBuf.array(), (byte) 0 );
try { try {
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen ); return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
} }

View File

@ -4,6 +4,7 @@ import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt; import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Arrays; import java.util.Arrays;
@ -42,8 +43,11 @@ public class MasterKeyV3 extends MasterKeyV2 {
logger.trc( "key scope: %s", mpKeyScope ); logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) ); logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
CharBuffer mpChars = CharBuffer.wrap( masterPassword ); ByteBuffer mpBytesBuf = MP_charset.encode( CharBuffer.wrap( masterPassword ) );
byte[] mpBytes = MP_charset.encode( mpChars ).array(); byte[] mpBytes = new byte[mpBytesBuf.remaining()];
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
Arrays.fill( mpBytesBuf.array(), (byte)0 );
try { try {
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen ); return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
} }

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lyndir.masterpassword" package="com.lyndir.masterpassword"
android:versionCode="1" android:versionCode="2002"
android:versionName="2.2"> android:versionName="2.2">
<uses-sdk <uses-sdk

View File

@ -115,6 +115,7 @@
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-android</artifactId> <artifactId>slf4j-android</artifactId>
<version>1.7.13-underscore</version>
</dependency> </dependency>
<!-- clone https://github.com/mosabua/maven-android-sdk-deployer.git <!-- clone https://github.com/mosabua/maven-android-sdk-deployer.git

View File

@ -85,7 +85,7 @@
android:layout_gravity="center" android:layout_gravity="center"
android:orientation="vertical"> android:orientation="vertical">
<TextView <Button
android:id="@id/sitePasswordField" android:id="@id/sitePasswordField"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -2,10 +2,11 @@ package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf; import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import android.app.Activity; import android.app.*;
import android.content.*; import android.content.*;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.graphics.Paint; import android.graphics.Paint;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.*; import android.text.*;
import android.text.method.PasswordTransformationMethod; import android.text.method.PasswordTransformationMethod;
@ -18,7 +19,7 @@ import com.google.common.base.Throwables;
import com.google.common.util.concurrent.*; import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ConversionUtils; import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import java.util.Arrays; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -26,7 +27,9 @@ import javax.annotation.Nullable;
public class EmergencyActivity extends Activity { public class EmergencyActivity extends Activity {
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( EmergencyActivity.class ); private static final Logger logger = Logger.get( EmergencyActivity.class );
private static final ClipData EMPTY_CLIP = new ClipData( new ClipDescription( "", new String[0] ), new ClipData.Item( "" ) );
private static final int PASSWORD_NOTIFICATION = 0;
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() ); private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
private final ValueChangedListener updateMasterKey = new ValueChangedListener() { private final ValueChangedListener updateMasterKey = new ValueChangedListener() {
@ -66,7 +69,7 @@ public class EmergencyActivity extends Activity {
Spinner siteVersionField; Spinner siteVersionField;
@InjectView(R.id.sitePasswordField) @InjectView(R.id.sitePasswordField)
TextView sitePasswordField; Button sitePasswordField;
@InjectView(R.id.sitePasswordTip) @InjectView(R.id.sitePasswordTip)
TextView sitePasswordTip; TextView sitePasswordTip;
@ -306,13 +309,40 @@ public class EmergencyActivity extends Activity {
} }
public void copySitePassword(View view) { public void copySitePassword(View view) {
if (TextUtils.isEmpty( sitePassword )) final String currentSitePassword = this.sitePassword;
if (TextUtils.isEmpty( currentSitePassword ))
return; return;
ClipDescription description = new ClipDescription( strf( "Password for %s", siteNameField.getText() ), final ClipboardManager clipboardManager = (ClipboardManager) getSystemService( CLIPBOARD_SERVICE );
new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN } ); final NotificationManager notificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
ClipData clipData = new ClipData( description, new ClipData.Item( sitePassword ) );
((ClipboardManager) getSystemService( CLIPBOARD_SERVICE )).setPrimaryClip( clipData ); String title = strf( "Password for %s", siteNameField.getText() );
ClipDescription description = new ClipDescription( title, new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN } );
clipboardManager.setPrimaryClip( new ClipData( description, new ClipData.Item( currentSitePassword ) ) );
Notification.Builder notificationBuilder = new Notification.Builder( this ).setContentTitle( title )
.setContentText( "Paste the password into your app." )
.setSmallIcon( R.drawable.icon )
.setAutoCancel( true );
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
notificationBuilder.setVisibility( Notification.VISIBILITY_SECRET )
.setCategory( Notification.CATEGORY_RECOMMENDATION )
.setLocalOnly( true );
notificationManager.notify( PASSWORD_NOTIFICATION, notificationBuilder.build() );
final Timer timer = new Timer();
timer.schedule( new TimerTask() {
@Override
public void run() {
ClipData clip = clipboardManager.getPrimaryClip();
for (int i = 0; i < clip.getItemCount(); ++i)
if (currentSitePassword.equals( clip.getItemAt( i ).coerceToText( EmergencyActivity.this ) )) {
clipboardManager.setPrimaryClip( EMPTY_CLIP );
break;
}
notificationManager.cancel( PASSWORD_NOTIFICATION );
timer.cancel();
}
}, 20000 );
Intent startMain = new Intent( Intent.ACTION_MAIN ); Intent startMain = new Intent( Intent.ACTION_MAIN );
startMain.addCategory( Intent.CATEGORY_HOME ); startMain.addCategory( Intent.CATEGORY_HOME );