implement Unlock by Samsung Fingerprint (Pass SDK)

This commit is contained in:
Philipp Crocoll 2016-01-10 20:27:26 +01:00
parent 41d6b3ac5e
commit b67f5538ce
14 changed files with 333 additions and 78 deletions

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "SamsungPass"]
path = SamsungPass
url = https://github.com/sraiteri/Xamarin-Samsung-Pass.git
[submodule "src/SamsungPass"]
path = src/SamsungPass
url = https://github.com/sraiteri/Xamarin-Samsung-Pass.git

View File

@ -47,6 +47,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AncientIconSet", "AncientIc
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FingerprintTest", "FingerprintTest\FingerprintTest.csproj", "{52C0A0E7-D625-44BE-948E-D98BC6C82F0F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SamsungPass", "SamsungPass\Xamarin.SamsungPass\SamsungPass\SamsungPass.csproj", "{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -524,6 +526,24 @@ Global
{52C0A0E7-D625-44BE-948E-D98BC6C82F0F}.ReleaseNoNet|Mixed Platforms.Deploy.0 = Release|Any CPU
{52C0A0E7-D625-44BE-948E-D98BC6C82F0F}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{52C0A0E7-D625-44BE-948E-D98BC6C82F0F}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Win32.ActiveCfg = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|x64.ActiveCfg = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Any CPU.Build.0 = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Win32.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|x64.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

1
src/SamsungPass Submodule

@ -0,0 +1 @@
Subproject commit 1495b1ad7914fa2be69e111b90039770d101aa3d

View File

@ -17,6 +17,12 @@ using Javax.Crypto.Spec;
namespace keepass2android
{
public interface IFingerprintAuthCallback
{
void OnFingerprintAuthSucceeded();
void OnFingerprintError(string toString);
}
public class FingerprintModule
{
public Context Context { get; set; }
@ -106,7 +112,7 @@ namespace keepass2android
}
}
public abstract class FingerprintCrypt: FingerprintManager.AuthenticationCallback
public abstract class FingerprintCrypt: FingerprintManager.AuthenticationCallback, IFingerprintIdentifier
{
protected const string FailedToInitCipher = "Failed to init Cipher";
public override void OnAuthenticationError(FingerprintState errorCode, ICharSequence errString)
@ -157,7 +163,9 @@ namespace keepass2android
}
public abstract bool InitCipher();
public abstract bool Init();
protected static string GetAlias(string keyId)
{
return "keepass2android." + keyId;
@ -175,6 +183,11 @@ namespace keepass2android
}
}
public void StartListening(Context ctx, IFingerprintAuthCallback callback)
{
StartListening(new FingerprintAuthCallbackAdapter(callback, ctx));
}
public void StartListening(FingerprintManager.AuthenticationCallback callback)
{
if (!IsFingerprintAuthAvailable)
@ -227,6 +240,13 @@ namespace keepass2android
}
}
public interface IFingerprintIdentifier
{
bool Init();
void StartListening(Context ctx, IFingerprintAuthCallback callback);
void StopListening();
}
public class FingerprintDecryption : FingerprintCrypt
{
private readonly Context _context;
@ -245,9 +265,9 @@ namespace keepass2android
_iv = Base64.Decode(PreferenceManager.GetDefaultSharedPreferences(context).GetString(GetIvPrefKey(prefKey), null), 0);
}
public override bool InitCipher()
public override bool Init()
{
Kp2aLog.Log("FP: InitCipher for Dec");
Kp2aLog.Log("FP: Init for Dec");
try
{
_keystore.Load(null);
@ -355,9 +375,9 @@ namespace keepass2android
}
}
public override bool InitCipher()
public override bool Init()
{
Kp2aLog.Log("FP: InitCipher for Enc ");
Kp2aLog.Log("FP: Init for Enc ");
try
{
_keystore.Load(null);

View File

@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using Com.Samsung.Android.Sdk;
using Com.Samsung.Android.Sdk.Pass;
using Java.Lang;
namespace keepass2android
{
class FingerprintSamsungIdentifier: IFingerprintIdentifier
{
SpassFingerprint _spassFingerprint;
Spass _spass;
public FingerprintSamsungIdentifier(Context context)
{
_spass = new Spass();
try
{
_spass.Initialize(context);
}
catch (SecurityException)
{
//"Did you add the permission to the AndroidManifest.xml?");
throw;
}
if (_spass.IsFeatureEnabled(Spass.DeviceFingerprint))
{
_spassFingerprint = new SpassFingerprint(context);
}
else
{
throw new RuntimeException("Fingerprint Featue not available.");
}
}
public bool Init()
{
try
{
return _spassFingerprint.HasRegisteredFinger;
}
catch (UnsupportedOperationException)
{
return false;
}
}
class IdentifyListener : Java.Lang.Object, IIdentifyListener
{
private readonly IFingerprintAuthCallback _callback;
private readonly Context _context;
private readonly FingerprintSamsungIdentifier _id;
public IdentifyListener(IFingerprintAuthCallback callback, Context context, FingerprintSamsungIdentifier id)
{
_callback = callback;
_context = context;
_id = id;
}
public void OnFinished (int responseCode)
{
_id.Listening = false;
if (responseCode == SpassFingerprint.StatusAuthentificationSuccess)
{
_callback.OnFingerprintAuthSucceeded();
}
else if (responseCode == SpassFingerprint.StatusAuthentificationPasswordSuccess)
{
_callback.OnFingerprintAuthSucceeded();
}
}
public void OnReady ()
{
}
public void OnStarted ()
{
}
}
internal bool Listening
{
get; set;
}
public void StartListening(Context ctx, IFingerprintAuthCallback callback)
{
if (Listening) return;
try
{
_spassFingerprint.StartIdentifyWithDialog(ctx, new IdentifyListener(callback, ctx, this), false);
Listening = true;
}
catch (SpassInvalidStateException m)
{
callback.OnFingerprintError(m.Message);
}
catch (IllegalStateException ex)
{
callback.OnFingerprintError(ex.Message);
}
}
public void StopListening()
{
try
{
_spassFingerprint.CancelIdentify();
Listening = false;
}
catch (IllegalStateException ise)
{
Kp2aLog.Log(ise.Message);
}
}
}
}

View File

@ -24,7 +24,7 @@ namespace keepass2android
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.KeyboardHidden,
Theme = "@style/MyTheme_ActionBar", MainLauncher = false)]
[IntentFilter(new[] { "kp2a.action.FingerprintSetupActivity" }, Categories = new[] { Intent.CategoryDefault })]
public class FingerprintSetupActivity : LockCloseActivity
public class FingerprintSetupActivity : LockCloseActivity, IFingerprintAuthCallback
{
private readonly ActivityDesign _activityDesign;
@ -119,8 +119,29 @@ namespace keepass2android
FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Gone;
if ((int)Build.VERSION.SdkInt >= 23)
RequestPermissions(new[] { Manifest.Permission.UseFingerprint }, FingerprintPermissionRequestCode);
if ((int) Build.VERSION.SdkInt >= 23)
RequestPermissions(new[] {Manifest.Permission.UseFingerprint}, FingerprintPermissionRequestCode);
else
{
try
{
//try to create a Samsung ID object
_samsungFingerprint = new FingerprintSamsungIdentifier(this);
if (!_samsungFingerprint.Init())
{
SetError(Resource.String.fingerprint_no_enrolled);
}
ShowRadioButtons();
}
catch (Exception)
{
_samsungFingerprint = null;
}
}
FindViewById(Resource.Id.container_fingerprint_unlock).Visibility = _samsungFingerprint == null
? ViewStates.Visible
: ViewStates.Gone;
}
string CurrentPreferenceKey
@ -181,18 +202,32 @@ namespace keepass2android
SetError(Resource.String.fingerprint_no_enrolled);
return;
}
FindViewById<TextView>(Resource.Id.tvFatalError).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Visible;
FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Gone;
ShowRadioButtons();
}
}
private void ShowRadioButtons()
{
FindViewById<TextView>(Resource.Id.tvFatalError).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Visible;
FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Gone;
}
private void ChangeUnlockMode(FingerprintUnlockMode oldMode, FingerprintUnlockMode newMode)
{
if (oldMode == newMode)
return;
if (_samsungFingerprint != null)
{
_unlockMode = newMode;
ISharedPreferencesEditor edit = PreferenceManager.GetDefaultSharedPreferences(this).Edit();
edit.PutString(App.Kp2a.GetDb().CurrentFingerprintModePrefKey, _unlockMode.ToString());
edit.Commit();
return;
}
if (newMode == FingerprintUnlockMode.Disabled)
{
_unlockMode = newMode;
@ -207,10 +242,10 @@ namespace keepass2android
_enc = new FingerprintEncryption(new FingerprintModule(this), CurrentPreferenceKey);
try
{
if (!_enc.InitCipher())
if (!_enc.Init())
throw new Exception("Failed to initialize cipher");
ResetErrorTextRunnable();
_enc.StartListening(new SetupCallback(this));
_enc.StartListening(new FingerprintAuthCallbackAdapter(this, this));
}
catch (Exception e)
{
@ -227,7 +262,10 @@ namespace keepass2android
static readonly long SUCCESS_DELAY_MILLIS = 1300;
private ImageView _fpIcon;
private TextView _fpTextView;
public void OnAuthSucceeded()
private FingerprintSamsungIdentifier _samsungFingerprint;
public void OnFingerprintAuthSucceeded()
{
_unlockMode = _desiredUnlockMode;
@ -248,6 +286,7 @@ namespace keepass2android
}
public void OnFingerprintError(string error)
{
_fpIcon.SetImageResource(Resource.Drawable.ic_fingerprint_error);
@ -270,7 +309,7 @@ namespace keepass2android
{
base.OnResume();
if (_enc != null)
_enc.StartListening(new SetupCallback(this));
_enc.StartListening(new FingerprintAuthCallbackAdapter(this, this));
}
protected override void OnPause()
@ -281,33 +320,4 @@ namespace keepass2android
}
}
internal class SetupCallback : FingerprintManager.AuthenticationCallback
{
private readonly FingerprintSetupActivity _fingerprintSetupActivity;
public SetupCallback(FingerprintSetupActivity fingerprintSetupActivity)
{
_fingerprintSetupActivity = fingerprintSetupActivity;
}
public override void OnAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)
{
_fingerprintSetupActivity.OnAuthSucceeded();
}
public override void OnAuthenticationError(FingerprintState errorCode, ICharSequence errString)
{
_fingerprintSetupActivity.OnFingerprintError(errString.ToString());
}
public override void OnAuthenticationHelp(FingerprintState helpCode, ICharSequence helpString)
{
_fingerprintSetupActivity.OnFingerprintError(helpString.ToString());
}
public override void OnAuthenticationFailed()
{
_fingerprintSetupActivity.OnFingerprintError(_fingerprintSetupActivity.Resources.GetString(Resource.String.fingerprint_not_recognized));
}
}
}

View File

@ -64,8 +64,7 @@ namespace KeeChallenge
private byte[] GenerateChallenge()
{
CryptoRandom rand = CryptoRandom.Instance;
byte[] chal = CryptoRandom.Instance.GetRandomBytes(challengeLenBytes);
byte[] chal = CryptoRandom.Instance.GetRandomBytes(challengeLenBytes);
if (LT64)
{
chal[challengeLenBytes - 2] = (byte)~chal[challengeLenBytes - 1];

View File

@ -700,6 +700,7 @@ namespace keepass2android
private ActivityDesign _activityDesign;
private FingerprintDecryption _fingerprintDec;
private bool _fingerprintPermissionGranted;
private PasswordActivityBroadcastReceiver _intentReceiver;
internal class MyActionBarDrawerToggle : ActionBarDrawerToggle
@ -756,6 +757,11 @@ namespace keepass2android
{
_activityDesign.ApplyTheme();
base.OnCreate(savedInstanceState);
_intentReceiver = new PasswordActivityBroadcastReceiver(this);
IntentFilter filter = new IntentFilter();
filter.AddAction(Intent.ActionScreenOff);
RegisterReceiver(_intentReceiver, filter);
//use FlagSecure to make sure the last (revealed) character of the master password is not visible in recent apps
@ -1800,7 +1806,7 @@ namespace keepass2android
btn.Tag = GetString(Resource.String.fingerprint_unlock_hint);
if (_fingerprintDec.InitCipher())
if (_fingerprintDec.Init())
{
btn.SetImageResource(Resource.Drawable.ic_fp_40px);
_fingerprintDec.StartListening(new FingerprintAuthCallbackAdapter(this, this));
@ -2044,13 +2050,36 @@ namespace keepass2android
}
}
private class PasswordActivityBroadcastReceiver : BroadcastReceiver
{
readonly PasswordActivity _activity;
public PasswordActivityBroadcastReceiver(PasswordActivity activity)
{
_activity = activity;
}
public override void OnReceive(Context context, Intent intent)
{
switch (intent.Action)
{
case Intent.ActionScreenOff:
Kp2aLog.Log("bla");
_activity.OnScreenLocked();
break;
}
}
}
private void OnScreenLocked()
{
if (_fingerprintDec != null)
_fingerprintDec.StopListening();
}
}
public interface IFingerprintAuthCallback
{
void OnFingerprintAuthSucceeded();
void OnFingerprintError(string toString);
}
}

View File

@ -99,4 +99,6 @@
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="keepass2android.keepass2android_debug.permission.KP2aInternalFileBrowsing" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<!-- Samsung Pass permission -->
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
</manifest>

View File

@ -102,4 +102,6 @@
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="keepass2android.keepass2android.permission.KP2aInternalFileBrowsing" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<!-- Samsung Pass permission -->
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
</manifest>

View File

@ -80,4 +80,6 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<!-- Samsung Pass permission -->
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
</manifest>

View File

@ -43,7 +43,7 @@ namespace keepass2android
private QuickUnlockBroadcastReceiver _intentReceiver;
private ActivityDesign _design;
private bool _fingerprintPermissionGranted;
private FingerprintDecryption _fingerprintDec;
private IFingerprintIdentifier _fingerprintIdentifier;
private int _quickUnlockLength;
private const int FingerprintPermissionRequestCode = 0;
@ -139,8 +139,12 @@ namespace keepass2android
filter.AddAction(Intents.DatabaseLocked);
RegisterReceiver(_intentReceiver, filter);
if ((int)Build.VERSION.SdkInt >= 23)
RequestPermissions(new[] { Manifest.Permission.UseFingerprint }, FingerprintPermissionRequestCode);
if ((int) Build.VERSION.SdkInt >= 23)
RequestPermissions(new[] {Manifest.Permission.UseFingerprint}, FingerprintPermissionRequestCode);
else
{
}
}
@ -176,7 +180,7 @@ namespace keepass2android
public void OnFingerprintAuthSucceeded()
{
_fingerprintDec.StopListening();
_fingerprintIdentifier.StopListening();
var btn = FindViewById<ImageButton>(Resource.Id.fingerprintbtn);
btn.SetImageResource(Resource.Drawable.ic_fingerprint_success);
@ -207,23 +211,42 @@ namespace keepass2android
return;
}
FingerprintModule fpModule = new FingerprintModule(this);
_fingerprintDec = new FingerprintDecryption(fpModule, App.Kp2a.GetDb().CurrentFingerprintPrefKey, this,
App.Kp2a.GetDb().CurrentFingerprintPrefKey);
if (_fingerprintPermissionGranted)
{
FingerprintModule fpModule = new FingerprintModule(this);
_fingerprintIdentifier = new FingerprintDecryption(fpModule, App.Kp2a.GetDb().CurrentFingerprintPrefKey, this,
App.Kp2a.GetDb().CurrentFingerprintPrefKey);
}
else
{
try
{
_fingerprintIdentifier = new FingerprintSamsungIdentifier(this);
btn.Click += (sender, args) =>
{
if (_fingerprintIdentifier.Init())
_fingerprintIdentifier.StartListening(this, this);
};
}
catch (Exception)
{
FindViewById<ImageButton>(Resource.Id.fingerprintbtn).Visibility = ViewStates.Gone;
return;
}
}
btn.Tag = GetString(Resource.String.fingerprint_unlock_hint);
if (_fingerprintDec.InitCipher())
if (_fingerprintIdentifier.Init())
{
btn.SetImageResource(Resource.Drawable.ic_fp_40px);
_fingerprintDec.StartListening(new FingerprintAuthCallbackAdapter(this, this));
_fingerprintIdentifier.StartListening(this, this);
}
else
{
//key invalidated permanently
btn.SetImageResource(Resource.Drawable.ic_fingerprint_error);
btn.Tag = GetString(Resource.String.fingerprint_unlock_failed);
_fingerprintDec = null;
_fingerprintIdentifier = null;
ClearFingerprintUnlockData();
}
@ -233,7 +256,7 @@ namespace keepass2android
btn.SetImageResource(Resource.Drawable.ic_fingerprint_error);
btn.Tag = "Error initializing Fingerprint Unlock: " + e;
_fingerprintDec = null;
_fingerprintIdentifier = null;
}
@ -294,23 +317,17 @@ namespace keepass2android
}, 50);
if (_fingerprintPermissionGranted)
{
InitFingerprintUnlock();
}
else
{
FindViewById<ImageButton>(Resource.Id.fingerprintbtn).Visibility = ViewStates.Gone;
}
InitFingerprintUnlock();
}
protected override void OnPause()
{
base.OnPause();
if (_fingerprintDec != null)
if (_fingerprintIdentifier != null)
{
_fingerprintDec.StopListening();
_fingerprintIdentifier.StopListening();
}
}

View File

@ -50,6 +50,7 @@
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:id="@+id/container_fingerprint_unlock"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<RadioButton

View File

@ -136,6 +136,7 @@
<Compile Include="EntryActivityClasses\ToggleVisibilityPopupMenuItem.cs" />
<Compile Include="EntryActivityClasses\WriteBinaryToFilePopupItem.cs" />
<Compile Include="FingerprintModule.cs" />
<Compile Include="FingerprintSamsungIdentifier.cs" />
<Compile Include="FingerprintSetupActivity.cs" />
<Compile Include="ExportDatabaseActivity.cs" />
<Compile Include="fileselect\FileChooserFileProvider.cs" />
@ -756,6 +757,10 @@
<Project>{3DA3911E-36DE-465E-8F15-F1991B6437E5}</Project>
<Name>PluginSdkBinding</Name>
</ProjectReference>
<ProjectReference Include="..\SamsungPass\Xamarin.SamsungPass\SamsungPass\SamsungPass.csproj">
<Project>{3a4b8e88-fa9b-4663-bcda-21c12e3af98a}</Project>
<Name>SamsungPass</Name>
</ProjectReference>
<ProjectReference Include="..\TwofishCipher\TwofishCipher.csproj">
<Project>{5CF675A5-9BEE-4720-BED9-D5BF14A2EBF9}</Project>
<Name>TwofishCipher</Name>