read yubikey and log in

This commit is contained in:
Kyle Spearrin 2017-06-28 22:24:04 -04:00
parent d71bc775d5
commit 56075cb7d9
15 changed files with 262 additions and 101 deletions

View file

@ -860,6 +860,18 @@
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\share_tools.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\yubikey.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\yubikey.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\yubikey.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\yubikey.png" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets" Condition="Exists('..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

View file

@ -51,7 +51,7 @@ namespace Bit.Android
Console.WriteLine("A OnCreate");
Window.SetSoftInputMode(SoftInput.StateHidden);
Window.AddFlags(WindowManagerFlags.Secure);
//Window.AddFlags(WindowManagerFlags.Secure);
var appIdService = Resolver.Resolve<IAppIdService>();
var authService = Resolver.Resolve<IAuthService>();
@ -105,10 +105,10 @@ namespace Bit.Android
LaunchApp(args);
});
MessagingCenter.Subscribe<Xamarin.Forms.Application>(
Xamarin.Forms.Application.Current, "ListenYubiKeyOTP", (sender) =>
MessagingCenter.Subscribe<Xamarin.Forms.Application, bool>(
Xamarin.Forms.Application.Current, "ListenYubiKeyOTP", (sender, listen) =>
{
ListenYubiKey();
ListenYubiKey(listen);
});
}
@ -142,6 +142,7 @@ namespace Bit.Android
{
Console.WriteLine("A OnPause");
base.OnPause();
ListenYubiKey(false);
}
protected override void OnDestroy()
@ -176,6 +177,18 @@ namespace Bit.Android
// workaround for app compat bug
// ref https://bugzilla.xamarin.com/show_bug.cgi?id=36907
Task.Delay(10).Wait();
if(Utilities.NfcEnabled())
{
MessagingCenter.Send(Xamarin.Forms.Application.Current, "ResumeYubiKey");
}
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
Console.WriteLine("A OnNewIntent");
ParseYubiKey(intent.DataString);
}
public void RateApp()
@ -237,7 +250,15 @@ namespace Bit.Android
}
}
private void ListenYubiKey()
private void ListenYubiKey(bool listen)
{
if(!Utilities.NfcEnabled())
{
return;
}
var adapter = NfcAdapter.GetDefaultAdapter(this);
if(listen)
{
var intent = new Intent(this, Class);
intent.AddFlags(ActivityFlags.SingleTop);
@ -247,26 +268,30 @@ namespace Bit.Android
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
ndef.AddDataScheme("http");
ndef.AddDataScheme("https");
var filters = new IntentFilter[] { ndef };
// register for foreground dispatch so we'll receive tags according to our intent filters
var adapter = NfcAdapter.GetDefaultAdapter(this);
adapter.EnableForegroundDispatch(this, pendingIntent, new IntentFilter[] { ndef }, null);
var data = Intent.DataString;
if(data != null)
adapter.EnableForegroundDispatch(this, pendingIntent, filters, null);
}
else
{
adapter.DisableForegroundDispatch(this);
}
}
private void ParseYubiKey(string data)
{
if(data == null)
{
return;
}
var otpMatch = _otpPattern.Matcher(data);
if(otpMatch.Matches())
{
var otp = otpMatch.Group(1);
Console.WriteLine("Got OTP: " + otp);
MessagingCenter.Send(Xamarin.Forms.Application.Current, "GotYubiKeyOTP", otp);
}
else
{
Console.WriteLine("Data from ndef didn't match, it was: " + data);
}
}
}
}
}

View file

@ -2744,8 +2744,8 @@ namespace Bit.Android
// aapt resource value: 0x7f0200e4
public const int notification_sm = 2130837732;
// aapt resource value: 0x7f0200f3
public const int notification_template_icon_bg = 2130837747;
// aapt resource value: 0x7f0200f4
public const int notification_template_icon_bg = 2130837748;
// aapt resource value: 0x7f0200e5
public const int plus = 2130837733;
@ -2789,6 +2789,9 @@ namespace Bit.Android
// aapt resource value: 0x7f0200f2
public const int user = 2130837746;
// aapt resource value: 0x7f0200f3
public const int yubikey = 2130837747;
static Drawable()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View file

@ -1,5 +1,4 @@
using Android.App;
using Android.Nfc;
using Android.OS;
using Bit.App.Abstractions;
@ -42,14 +41,6 @@ namespace Bit.Android.Services
return 1f;
}
}
public bool NfcEnabled
{
get
{
var manager = (NfcManager)Application.Context.GetSystemService("nfc");
var adapter = manager.DefaultAdapter;
return adapter != null && adapter.IsEnabled;
}
}
public bool NfcEnabled => Utilities.NfcEnabled();
}
}

View file

@ -3,11 +3,19 @@ using Android.App;
using Android.Content;
using Java.Security;
using System.IO;
using Android.Nfc;
namespace Bit.Android
{
public static class Utilities
{
public static bool NfcEnabled()
{
var manager = (NfcManager)Application.Context.GetSystemService("nfc");
var adapter = manager.DefaultAdapter;
return adapter != null && adapter.IsEnabled;
}
public static void SendCrashEmail(Exception e, bool includeSecurityProviders = true)
{
SendCrashEmail(e.Message + "\n\n" + e.StackTrace, includeSecurityProviders);

View file

@ -13,14 +13,17 @@ using Bit.App.Enums;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FFImageLoading.Forms;
namespace Bit.App.Pages
{
public class LoginTwoFactorPage : ExtendedContentPage
{
private DateTime? _lastAction;
private IAuthService _authService;
private IUserDialogs _userDialogs;
private ISyncService _syncService;
private IDeviceInfoService _deviceInfoService;
private IGoogleAnalyticsService _googleAnalyticsService;
private IPushNotification _pushNotification;
private readonly string _email;
@ -33,6 +36,8 @@ namespace Bit.App.Pages
public LoginTwoFactorPage(string email, FullLoginResult result, TwoFactorProviderType? type = null)
: base(updateActivity: false)
{
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
_email = email;
_result = result;
_masterPasswordHash = result.MasterPasswordHash;
@ -47,26 +52,54 @@ namespace Bit.App.Pages
_pushNotification = Resolver.Resolve<IPushNotification>();
Init();
SubscribeYubiKey(true);
}
public FormEntryCell TokenCell { get; set; }
public ExtendedSwitchCell RememberCell { get; set; }
public HybridWebView WebView { get; set; }
private void Init()
{
var scrollView = new ScrollView();
if(!_providerType.HasValue)
var anotherMethodButton = new ExtendedButton
{
var noProviderLabel = new Label
Text = "Use another two-step login method",
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
Margin = new Thickness(15, 0, 15, 25),
Command = new Command(() => AnotherMethodAsync()),
Uppercase = false,
BackgroundColor = Color.Transparent
};
var instruction = new Label
{
Text = "No provider.",
LineBreakMode = LineBreakMode.WordWrap,
Margin = new Thickness(15),
HorizontalTextAlignment = TextAlignment.Center
};
scrollView.Content = noProviderLabel;
RememberCell = new ExtendedSwitchCell
{
Text = "Remember me",
On = false
};
if(!_providerType.HasValue)
{
instruction.Text = "No providers available.";
var layout = new StackLayout
{
Children = { instruction, anotherMethodButton },
Spacing = 0
};
scrollView.Content = layout;
Title = "Login Unavailable";
Content = scrollView;
}
else if(_providerType.Value == TwoFactorProviderType.Authenticator ||
_providerType.Value == TwoFactorProviderType.Email)
@ -88,54 +121,12 @@ namespace Bit.App.Pages
TokenCell.Entry.Keyboard = Keyboard.Numeric;
TokenCell.Entry.ReturnType = ReturnType.Go;
RememberCell = new ExtendedSwitchCell
{
Text = "Remember me",
On = false
};
var table = new ExtendedTableView
{
Intent = TableIntent.Settings,
EnableScrolling = false,
HasUnevenRows = true,
EnableSelection = true,
NoFooter = true,
NoHeader = true,
VerticalOptions = LayoutOptions.Start,
Root = new TableRoot
{
var table = new TwoFactorTable(
new TableSection(" ")
{
TokenCell,
RememberCell
}
}
};
if(Device.RuntimePlatform == Device.iOS)
{
table.RowHeight = -1;
table.EstimatedRowHeight = 70;
}
var instruction = new Label
{
Text = AppResources.EnterVerificationCode,
LineBreakMode = LineBreakMode.WordWrap,
Margin = new Thickness(15),
HorizontalTextAlignment = TextAlignment.Center
};
var anotherMethodButton = new ExtendedButton
{
Text = "Use another two-step login method",
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
Margin = new Thickness(15, 0, 15, 25),
Command = new Command(() => AnotherMethodAsync()),
Uppercase = false,
BackgroundColor = Color.Transparent
};
});
var layout = new StackLayout
{
@ -189,25 +180,57 @@ namespace Bit.App.Pages
var host = WebUtility.UrlEncode(duoParams["Host"].ToString());
var req = WebUtility.UrlEncode(duoParams["Signature"].ToString());
WebView = new HybridWebView
var webView = new HybridWebView
{
Uri = $"http://192.168.1.6:4001/duo-mobile.html?host={host}&request={req}",
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand
};
WebView.RegisterAction(async (sig) =>
webView.RegisterAction(async (sig) =>
{
await LogInAsync(sig, false);
});
Title = "Duo";
Content = WebView;
Content = webView;
}
else if(_providerType == TwoFactorProviderType.YubiKey)
{
instruction.Text = "Hold your YubiKey NEO against the back of the device to continue.";
var image = new CachedImage
{
Source = "yubikey",
VerticalOptions = LayoutOptions.Start,
HorizontalOptions = LayoutOptions.Center,
WidthRequest = 266,
HeightRequest = 160,
Margin = new Thickness(0, 0, 0, 25)
};
var table = new TwoFactorTable(
new TableSection(" ")
{
RememberCell
});
var layout = new StackLayout
{
Children = { instruction, image, table, anotherMethodButton },
Spacing = 0
};
scrollView.Content = layout;
Title = "YubiKey";
Content = scrollView;
}
}
protected override void OnAppearing()
{
base.OnAppearing();
ListenYubiKey(true);
if(TokenCell != null)
{
@ -220,6 +243,7 @@ namespace Bit.App.Pages
protected override void OnDisappearing()
{
base.OnDisappearing();
ListenYubiKey(false);
if(TokenCell != null)
{
@ -251,6 +275,12 @@ namespace Bit.App.Pages
private async Task LogInAsync(string token, bool remember)
{
if(_lastAction.LastActionWasRecent())
{
return;
}
_lastAction = DateTime.UtcNow;
if(string.IsNullOrWhiteSpace(token))
{
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
@ -264,6 +294,7 @@ namespace Bit.App.Pages
_userDialogs.HideLoading();
if(!response.Success)
{
ListenYubiKey(true);
await DisplayAlert(AppResources.AnErrorHasOccurred, response.ErrorMessage, AppResources.Ok);
return;
}
@ -299,7 +330,7 @@ namespace Bit.App.Pages
switch(p.Key)
{
case TwoFactorProviderType.Authenticator:
if(provider == TwoFactorProviderType.Duo)
if(provider == TwoFactorProviderType.Duo || provider == TwoFactorProviderType.YubiKey)
{
continue;
}
@ -311,6 +342,16 @@ namespace Bit.App.Pages
}
break;
case TwoFactorProviderType.Duo:
if(provider == TwoFactorProviderType.YubiKey)
{
continue;
}
break;
case TwoFactorProviderType.YubiKey:
if(!_deviceInfoService.NfcEnabled)
{
continue;
}
break;
default:
continue;
@ -322,5 +363,76 @@ namespace Bit.App.Pages
return provider;
}
private void ListenYubiKey(bool listen)
{
if(_providerType == TwoFactorProviderType.YubiKey)
{
MessagingCenter.Send(Application.Current, "ListenYubiKeyOTP", listen);
}
}
private void SubscribeYubiKey(bool subscribe)
{
if(_providerType != TwoFactorProviderType.YubiKey)
{
return;
}
MessagingCenter.Unsubscribe<Application, string>(Application.Current, "GotYubiKeyOTP");
MessagingCenter.Unsubscribe<Application>(Application.Current, "ResumeYubiKey");
if(!subscribe)
{
return;
}
MessagingCenter.Subscribe<Application, string>(Application.Current, "GotYubiKeyOTP", async (sender, otp) =>
{
MessagingCenter.Unsubscribe<Application, string>(Application.Current, "GotYubiKeyOTP");
if(_providerType == TwoFactorProviderType.YubiKey)
{
await LogInAsync(otp, RememberCell.On);
}
});
SubscribeYubiKeyResume();
}
private void SubscribeYubiKeyResume()
{
MessagingCenter.Subscribe<Application>(Application.Current, "ResumeYubiKey", (sender) =>
{
MessagingCenter.Unsubscribe<Application>(Application.Current, "ResumeYubiKey");
if(_providerType == TwoFactorProviderType.YubiKey)
{
MessagingCenter.Send(Application.Current, "ListenYubiKeyOTP", true);
SubscribeYubiKeyResume();
}
});
}
public class TwoFactorTable : ExtendedTableView
{
public TwoFactorTable(TableSection section)
{
Intent = TableIntent.Settings;
EnableScrolling = false;
HasUnevenRows = true;
EnableSelection = true;
NoFooter = true;
NoHeader = true;
VerticalOptions = LayoutOptions.Start;
Root = Root = new TableRoot
{
section
};
if(Device.RuntimePlatform == Device.iOS)
{
RowHeight = -1;
EstimatedRowHeight = 70;
}
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

View file

@ -716,6 +716,15 @@
<ItemGroup>
<BundleResource Include="Resources\share_tools%403x.png" />
</ItemGroup>
<ItemGroup>
<BundleResource Include="Resources\yubikey.png" />
</ItemGroup>
<ItemGroup>
<BundleResource Include="Resources\yubikey%402x.png" />
</ItemGroup>
<ItemGroup>
<BundleResource Include="Resources\yubikey%403x.png" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>

View file

@ -57,6 +57,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="Mono.Android.Export" />
<Reference Include="mscorlib" />
<Reference Include="PCLCrypto, Version=2.0.0.0, Culture=neutral, PublicKeyToken=d4421c8a4786956c, processorArchitecture=MSIL">
<HintPath>..\..\packages\PCLCrypto.2.0.147\lib\MonoAndroid23\PCLCrypto.dll</HintPath>