keepass2android/src/keepass2android/NfcOtpActivity.cs

115 lines
3.3 KiB
C#

using System;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Nfc;
using Android.OS;
using Android.Widget;
using Java.Util;
using Java.Util.Regex;
using Keepass2android.Yubiclip.Scancode;
namespace keepass2android
{
[Activity(Label = "@string/app_name",
ConfigurationChanges = ConfigChanges.Orientation |
ConfigChanges.KeyboardHidden,
NoHistory = true,
ExcludeFromRecents = true,
Theme = "@android:style/Theme.Dialog")]
[IntentFilter(new[] { "android.nfc.action.NDEF_DISCOVERED" },
Label = "@string/app_name",
Categories = new[] { Intent.CategoryDefault },
DataHost = "my.yubico.com",
DataPathPrefix = "/neo",
DataScheme = "https")]
public class NfcOtpActivity : Activity
{
private String GetOtpFromIntent(Intent intent)
{
String data = intent.DataString;
Matcher matcher = OtpPattern.Matcher(data);
if (matcher.Matches())
{
String otp = matcher.Group(1);
return otp;
}
else
{
IParcelable[] raw = Intent.GetParcelableArrayExtra(NfcAdapter.ExtraNdefMessages);
byte[] bytes = ((NdefMessage) raw[0]).ToByteArray();
bytes = Arrays.CopyOfRange(bytes, DATA_OFFSET, bytes.Length);
String layout = "US";
KeyboardLayout kbd = KeyboardLayout.ForName(layout);
String otp = kbd.FromScanCodes(bytes);
return otp;
}
return null;
}
//private static readonly Java.Util.Regex.Pattern OtpPattern = Java.Util.Regex.Pattern.Compile("^https://my\\.yubico\\.com/neo/(.+)$");
private static readonly Java.Util.Regex.Pattern OtpPattern = Java.Util.Regex.Pattern.Compile("^https://my\\.yubico\\.com/neo/([a-zA-Z0-9!]+)$");
private const int DATA_OFFSET = 23;
private ActivityDesign _design;
public NfcOtpActivity()
{
_design = new ActivityDesign(this);
}
protected override void OnCreate(Bundle bundle)
{
_design.ApplyTheme();
base.OnCreate(bundle);
Intent i = new Intent(this, typeof (PasswordActivity));
i.SetAction(Intents.StartWithOtp);
//things to consider:
// PasswordActivity should be resumed if currently active -> this is why single top is used and why PasswordActivity is started
// If PasswordActivity is not open already, it may be the wrong place to send our OTP to because maybe the user first needs to select
// a file (which might require UI action like entering credentials, all of which is handled in FileSelectActivity)
// FileSelectActivity is not on the back stack, it finishes itself.
// -> PasswordActivity needs to handle this and return to FSA.
i.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
try
{
string otp = GetOtpFromIntent(Intent);
if (otp == null)
throw new Exception("Otp must not be null!");
i.PutExtra(Intents.OtpExtraKey, otp);
}
catch (Exception e)
{
Kp2aLog.Log(e.ToString());
Toast.MakeText(this, "No Yubikey OTP found!", ToastLength.Long).Show();
Finish();
return;
}
if (App.Kp2a.GetDb().Loaded)
{
Toast.MakeText(this, GetString(Resource.String.otp_discarded_because_db_open), ToastLength.Long).Show();
}
else
{
StartActivity(i);
}
Finish();
}
protected override void OnResume()
{
base.OnResume();
_design.ReapplyTheme();
}
}
}