create own YubiChallenge activity

This commit is contained in:
Philipp Crocoll
2018-05-28 10:39:09 +02:00
parent 9e80013e28
commit a1d6347db3
9 changed files with 242 additions and 32 deletions

View File

@@ -33,7 +33,7 @@ namespace keepass2android
}
}
var chalIntent = Activity.TryGetYubichallengeIntentOrPrompt(challenge64, true);
var chalIntent = Activity.GetYubichallengeIntent(challenge64);
if (chalIntent == null)
throw new Exception("YubiChallenge not installed.");

View File

@@ -218,14 +218,18 @@ namespace keepass2android
}
//update the Entry output in the App database and notify the CopyToClipboard service
App.Kp2a.GetDb().LastOpenedEntry.OutputStrings.Set(key, new ProtectedString(isProtected, value));
Intent updateKeyboardIntent = new Intent(this, typeof(CopyToClipboardService));
updateKeyboardIntent.SetAction(Intents.UpdateKeyboard);
updateKeyboardIntent.PutExtra(KeyEntry, Entry.Uuid.ToHexString());
StartService(updateKeyboardIntent);
//notify plugins
NotifyPluginsOnModification(Strings.PrefixString+key);
if (App.Kp2a.GetDb()?.LastOpenedEntry != null)
{
App.Kp2a.GetDb().LastOpenedEntry.OutputStrings.Set(key, new ProtectedString(isProtected, value));
Intent updateKeyboardIntent = new Intent(this, typeof(CopyToClipboardService));
updateKeyboardIntent.SetAction(Intents.UpdateKeyboard);
updateKeyboardIntent.PutExtra(KeyEntry, Entry.Uuid.ToHexString());
StartService(updateKeyboardIntent);
//notify plugins
NotifyPluginsOnModification(Strings.PrefixString + key);
}
}
private void AddPluginAction(string pluginPackage, string fieldId, string popupItemId, string displayText, int iconId, Bundle bundleExtra)

View File

@@ -123,27 +123,12 @@ namespace keepass2android
}
public Intent TryGetYubichallengeIntentOrPrompt(byte[] challenge, bool promptToInstall)
public Intent GetYubichallengeIntent(byte[] challenge)
{
Intent chalIntent = new Intent("com.yubichallenge.NFCActivity.CHALLENGE");
Intent chalIntent = new Intent(this, typeof(YubiChallengeActivity));
chalIntent.PutExtra("challenge", challenge);
chalIntent.PutExtra("slot", 2);
IList<ResolveInfo> activities = PackageManager.QueryIntentActivities(chalIntent, 0);
bool isIntentSafe = activities.Count > 0;
if (isIntentSafe)
{
return chalIntent;
}
if (promptToInstall)
{
AlertDialog.Builder b = new AlertDialog.Builder(this);
b.SetMessage(Resource.String.YubiChallengeNotInstalled);
b.SetPositiveButton(Android.Resource.String.Ok,
delegate { Util.GotoUrl(this, GetString(Resource.String.MarketURL) + "com.yubichallenge"); });
b.SetNegativeButton(Resource.String.cancel, delegate { });
b.Create().Show();
}
return null;
return chalIntent;
}
}
}

View File

@@ -625,7 +625,7 @@ namespace keepass2android
protected override void HandleSuccess()
{
var chalIntent = Activity.TryGetYubichallengeIntentOrPrompt(Activity._chalInfo.Challenge, true);
var chalIntent = Activity.GetYubichallengeIntent(Activity._chalInfo.Challenge);
if (chalIntent != null)
{

View File

@@ -139,6 +139,7 @@
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W" android:value="426.0dip" />
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:value="360.0dip" />
</application>
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />

View File

@@ -864,7 +864,6 @@ Erstes öffentliches Release</string>
<item>Kennwort + OTP Secret (Recovery-Modus)</item>
<item>Passwort + Challenge-Response</item>
<item>Passwort + Challenge-Response-Secret (Recovery-Modus)</item>
<item>Password + Challenge-Response for KeepassXC database</item>
</string-array>
<string-array name="AcceptAllServerCertificates_options">
<item>Fehler bei Zertifikatsvalidierung ignorieren</item>

View File

@@ -554,7 +554,6 @@
<string name="init_otp">Load OTP auxiliary file…</string>
<string name="otp_explanation">Enter the next One-time-passwords (OTPs). Swipe your Yubikey NEO at the back of your device to enter via NFC.</string>
<string name="otp_hint">OTP %1$d</string>
<string name="YubiChallengeNotInstalled">Could not find an app that can handle the challenge. Please install Yubichallenge from Google Play.</string>
<string name="CouldntLoadOtpAuxFile">Could not load auxiliary OTP file!</string>
<string name="CouldntLoadOtpAuxFile_Hint">Please use the OtpKeyProv plugin in KeePass 2.x (PC) to configure your database for use with OTPs!</string>
<string name="otp_discarded_because_no_db">Please select database first. OTP is discarded for security reasons.</string>
@@ -567,6 +566,19 @@
<string name="ErrorUpdatingOtpAuxFile">Error updating OTP auxiliary file!</string>
<string name="SavingOtpAuxFile">Saving auxiliary OTP file…</string>
<string name="yubichallenge_title_activity_nfc">NFC Challenge-Response</string>
<string name="yubichallenge_challenging">Challenging</string>
<string name="yubichallenge_swipe">Please swipe your YubiKey NEO</string>
<string name="yubichallenge_swipe_and_hold">Please swipe and hold your YubiKey NEO</string>
<string name="yubichallenge_challenge_failed">Failed getting response, is the YubiKey and/or YubiChallenge configured correctly?</string>
<string name="yubichallenge_lost_tag">YubiKey NEO lost during operation</string>
<string name="yubichallenge_tag_error">Error communicating with YubiKey.</string>
<string name="yubichallenge_nfc_off">NFC seems to be off, turn on?</string>
<string name="yubichallenge_no_nfc">This device seems to not support NFC</string>
<string name="yubichallenge_response_error">YubiKey Error</string>
<string name="yubichallenge_bad_resp">The response from the YubiKey is incorrect</string>
<string name="bad_resp">The challenge response is incorrect.</string>
<string name="CouldntLoadChalAuxFile">Could not load auxiliary challenge file!</string>
<string name="CouldntLoadChalAuxFile_Hint">Please use the KeeChallenge plugin in KeePass 2.x (PC) to configure your database for use with challenge-response!</string>
@@ -986,7 +998,7 @@
* Improved determination of lock-state in some situations\n
* Database files are loaded to memory while you are typing your password for increased loading speed (see settings)\n
* Entries can be added to root group\n
* Bug fixes (resolving reference fields, problems with keyboard on Italian an Chinese devices)
* Bug fixes (resolving reference fields, problems with keyboard on Italian and Chinese devices)
</string>
<string name="ChangeLog_0_8_4">
<b>Version 0.8.4</b>\n
@@ -1086,7 +1098,6 @@ Initial public release
<item>Password + OTP secret (recovery mode)</item>
<item>Password + Challenge-Response</item>
<item>Password + Challenge-Response secret (recovery mode)</item>
<item>Password + Challenge-Response for KeepassXC database</item>
</string-array>
<string-array name="AcceptAllServerCertificates_options">
<item>Ignore certificate validation failures</item>

View File

@@ -0,0 +1,209 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.Nfc;
using Android.Nfc.Tech;
using Android.OS;
using Android.Preferences;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Java.IO;
namespace keepass2android
{
[Activity(Label = "@string/yubichallenge_title_activity_nfc")]
[IntentFilter(new string[] { "android.nfc.action.NDEF_DISCOVERED" }, Categories = new string[]{Android.Content.Intent.CategoryDefault})]
public class YubiChallengeActivity : Activity
{
private byte[] challenge;
private int slot;
private const byte SLOT_CHAL_HMAC1 = 0x30;
private const byte SLOT_CHAL_HMAC2 = 0x38;
private const byte CHAL_BYTES = 0x40; // 64
private const byte RESP_BYTES = 20;
private static byte[] selectCommand = { 0x00, (byte) 0xA4, 0x04,
0x00, 0x07, (byte) 0xA0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x00 };
private static byte[] chalCommand = { 0x00, 0x01, SLOT_CHAL_HMAC2,
0x00, CHAL_BYTES };
private AlertDialog swipeDialog;
private const int PERMISSIONS_REQUEST_NFC = 901234845;
protected override void OnResume()
{
base.OnResume();
challenge = Intent.GetByteArrayExtra("challenge");
slot = PreferenceManager.GetDefaultSharedPreferences(this).GetInt("pref_Slot", 2);
if (challenge == null) return;
else if (challenge.Length != CHAL_BYTES) return;
else ChallengeYubiKey();
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
Tag tag = intent.GetParcelableExtra(NfcAdapter.ExtraTag) as Tag;
SetResult(Result.Canceled, intent);
if (tag != null)
{
IsoDep isoTag = IsoDep.Get(tag);
try
{
isoTag.Connect();
byte[] resp = isoTag.Transceive(selectCommand);
int length = resp.Length;
if (resp[length - 2] == (byte)0x90 && resp[length - 1] == 0x00)
DoChallengeYubiKey(isoTag, slot, challenge);
else
{
Toast.MakeText(this, Resource.String.yubichallenge_tag_error, ToastLength.Long)
.Show();
}
isoTag.Close();
}
catch (TagLostException e)
{
Toast.MakeText(this, Resource.String.yubichallenge_lost_tag, ToastLength.Long)
.Show();
}
catch (IOException e)
{
Toast.MakeText(this, GetString(Resource.String.yubichallenge_tag_error) + e.Message,
ToastLength.Long).Show();
}
}
else SetResult(Result.Canceled, intent);
Finish();
}
protected override void OnPause()
{
base.OnPause();
if (swipeDialog != null)
{
swipeDialog.Dismiss();
swipeDialog = null;
}
DisableDispatch();
}
private void DisableDispatch()
{
NfcAdapter adapter = NfcAdapter.GetDefaultAdapter(this);
if (adapter != null)
{
adapter.DisableForegroundDispatch(this);
}
}
private void DoChallengeYubiKey(IsoDep isoTag, int slot, byte[] challenge)
{
if (challenge == null || challenge.Length != CHAL_BYTES)
return;
byte[] apdu = new byte[chalCommand.Length + CHAL_BYTES];
chalCommand.CopyTo(apdu, 0);
if (slot == 1)
apdu[2] = SLOT_CHAL_HMAC1;
challenge.CopyTo(apdu, chalCommand.Length);
byte[] respApdu = isoTag.Transceive(apdu);
if (respApdu.Length == 22 && respApdu[20] == (byte) 0x90
&& respApdu[21] == 0x00)
{
// Get the secret
byte[] resp = new byte[RESP_BYTES];
Array.Copy(respApdu, 0, resp, 0, RESP_BYTES);
Intent data = Intent;
data.PutExtra("response", resp);
SetResult(Result.Ok, data);
}
else
{
Toast.MakeText(this, Resource.String.yubichallenge_challenge_failed, ToastLength.Long)
.Show();
}
}
private void ChallengeYubiKey()
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetTitle(Resource.String.yubichallenge_challenging);
builder.SetMessage(Resource.String.yubichallenge_swipe);
builder.SetNegativeButton(Android.Resource.String.Cancel, (obj, args) => Finish());
builder.SetCancelable(false);
swipeDialog = builder.Show();
EnableDispatch();
}
public override void OnBackPressed()
{
base.OnBackPressed();
Finish();
}
private void EnableDispatch()
{
Intent intent = Intent;
intent.AddFlags(ActivityFlags.SingleTop);
PendingIntent tagIntent = PendingIntent.GetActivity(
this, 0, intent, 0);
IntentFilter iso = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
NfcAdapter adapter = NfcAdapter.GetDefaultAdapter(this);
if (adapter == null)
{
Toast.MakeText(this, Resource.String.yubichallenge_no_nfc, ToastLength.Long).Show();
return;
}
if (adapter.IsEnabled)
{
// register for foreground dispatch so we'll receive tags according to our intent filters
adapter.EnableForegroundDispatch(
this, tagIntent, new IntentFilter[] { iso },
new String[][] { new String[] { Java.Lang.Class.FromType(typeof(IsoDep)).Name }
}
);
} else {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.SetTitle(Resource.String.yubichallenge_nfc_off);
dialog.SetPositiveButton(Android.Resource.String.Yes, (sender, args) =>
{
Intent settings = new Intent(Android.Provider.Settings.ActionNfcSettings);
StartActivity(settings);
((Dialog)sender).Dismiss();
});
dialog.SetNegativeButton(Android.Resource.String.No, (sender, args) =>
{
((Dialog)sender).Dismiss();
});
dialog.Show();
}
}
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetResult(Result.Canceled);
// Create your application here
}
}
}

View File

@@ -307,6 +307,7 @@
<Compile Include="AttachmentContentProvider.cs" />
<Compile Include="app\AppTask.cs" />
<Compile Include="views\TextWithHelp.cs" />
<Compile Include="YubiChallengeActivity.cs" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\xml\searchable.xml">