main: Added YubiKey

- Added a reference to the Yubico.YubiKey package in FireWallet.csproj

MainForm.cs: Added Yubikey login functionality
- Added code to use a connected Yubikey to encrypt and decrypt account passwords for login.

MainForm.Designer.cs: Added button for Yubikey settings
- Created a button for users to access the settings for using their Yubikey.
This commit is contained in:
Nathan Woodburn 2023-06-15 19:08:55 +10:00
parent 88c6b5afe0
commit 79350570fd
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
3 changed files with 206 additions and 20 deletions

View File

@ -30,6 +30,7 @@
<PackageReference Include="DnsClient" Version="1.7.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="Yubico.YubiKey" Version="1.7.0" />
</ItemGroup>
<ItemGroup>

View File

@ -95,6 +95,7 @@ namespace FireWallet
textBoxReceiveAddress = new TextBox();
labelReceive1 = new Label();
panelDomains = new Panel();
labelDomainSort = new Label();
comboBoxDomainSort = new ComboBox();
buttonExportDomains = new Button();
groupBoxDomains = new GroupBox();
@ -103,6 +104,7 @@ namespace FireWallet
textBoxDomainSearch = new TextBox();
panelSettings = new Panel();
groupBoxSettingsWallet = new GroupBox();
buttonSettingsYubikey = new Button();
buttonSettingsRescan = new Button();
buttonSeed = new Button();
groupBoxSettingsMisc = new GroupBox();
@ -123,7 +125,6 @@ namespace FireWallet
textBoxExAddr = new TextBox();
labelSettings4 = new Label();
textBoxExTX = new TextBox();
labelDomainSort = new Label();
statusStripmain.SuspendLayout();
panelaccount.SuspendLayout();
groupBoxaccount.SuspendLayout();
@ -241,7 +242,7 @@ namespace FireWallet
//
panelaccount.BackColor = Color.Transparent;
panelaccount.Controls.Add(groupBoxaccount);
panelaccount.Location = new Point(1082, 211);
panelaccount.Location = new Point(132, 30);
panelaccount.Name = "panelaccount";
panelaccount.Size = new Size(1074, 642);
panelaccount.TabIndex = 1;
@ -574,7 +575,7 @@ namespace FireWallet
panelSend.Controls.Add(labelSendingTo);
panelSend.Controls.Add(labelSendPrompt);
panelSend.Controls.Add(labelHIPArrow);
panelSend.Location = new Point(138, 33);
panelSend.Location = new Point(880, 441);
panelSend.Name = "panelSend";
panelSend.Size = new Size(974, 521);
panelSend.TabIndex = 2;
@ -792,12 +793,22 @@ namespace FireWallet
panelDomains.Controls.Add(groupBoxDomains);
panelDomains.Controls.Add(labelDomainSearch);
panelDomains.Controls.Add(textBoxDomainSearch);
panelDomains.Location = new Point(120, 48);
panelDomains.Location = new Point(861, 364);
panelDomains.Name = "panelDomains";
panelDomains.Size = new Size(920, 536);
panelDomains.TabIndex = 18;
panelDomains.Visible = false;
//
// labelDomainSort
//
labelDomainSort.AutoSize = true;
labelDomainSort.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point);
labelDomainSort.Location = new Point(638, 15);
labelDomainSort.Name = "labelDomainSort";
labelDomainSort.Size = new Size(42, 21);
labelDomainSort.TabIndex = 12;
labelDomainSort.Text = "Sort:";
//
// comboBoxDomainSort
//
comboBoxDomainSort.DropDownStyle = ComboBoxStyle.DropDownList;
@ -871,7 +882,7 @@ namespace FireWallet
panelSettings.Controls.Add(buttonSettingsSave);
panelSettings.Controls.Add(groupBoxSettingsExplorer);
panelSettings.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point);
panelSettings.Location = new Point(1065, 51);
panelSettings.Location = new Point(848, 306);
panelSettings.Name = "panelSettings";
panelSettings.Size = new Size(930, 550);
panelSettings.TabIndex = 19;
@ -879,6 +890,7 @@ namespace FireWallet
//
// groupBoxSettingsWallet
//
groupBoxSettingsWallet.Controls.Add(buttonSettingsYubikey);
groupBoxSettingsWallet.Controls.Add(buttonSettingsRescan);
groupBoxSettingsWallet.Controls.Add(buttonSeed);
groupBoxSettingsWallet.Location = new Point(507, 16);
@ -888,6 +900,17 @@ namespace FireWallet
groupBoxSettingsWallet.TabStop = false;
groupBoxSettingsWallet.Text = "Wallet Controls";
//
// buttonSettingsYubikey
//
buttonSettingsYubikey.FlatStyle = FlatStyle.Flat;
buttonSettingsYubikey.Location = new Point(6, 133);
buttonSettingsYubikey.Name = "buttonSettingsYubikey";
buttonSettingsYubikey.Size = new Size(98, 50);
buttonSettingsYubikey.TabIndex = 9;
buttonSettingsYubikey.Text = "YubiKey";
buttonSettingsYubikey.UseVisualStyleBackColor = true;
buttonSettingsYubikey.Click += buttonSettingsYubikey_Click;
//
// buttonSettingsRescan
//
buttonSettingsRescan.FlatStyle = FlatStyle.Flat;
@ -1081,25 +1104,15 @@ namespace FireWallet
textBoxExTX.Size = new Size(307, 29);
textBoxExTX.TabIndex = 1;
//
// labelDomainSort
//
labelDomainSort.AutoSize = true;
labelDomainSort.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point);
labelDomainSort.Location = new Point(638, 15);
labelDomainSort.Name = "labelDomainSort";
labelDomainSort.Size = new Size(42, 21);
labelDomainSort.TabIndex = 12;
labelDomainSort.Text = "Sort:";
//
// MainForm
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1152, 575);
Controls.Add(panelaccount);
Controls.Add(panelSettings);
Controls.Add(panelDomains);
Controls.Add(panelSend);
Controls.Add(panelSettings);
Controls.Add(panelaccount);
Controls.Add(panelPortfolio);
Controls.Add(panelRecieve);
Controls.Add(panelNav);
@ -1234,5 +1247,6 @@ namespace FireWallet
private Label labelSendingHIPAddress;
private ComboBox comboBoxDomainSort;
private Label labelDomainSort;
private Button buttonSettingsYubikey;
}
}

View File

@ -7,13 +7,14 @@ using QRCoder;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using System.Text;
using System.Security.Policy;
using System.Windows.Forms;
using System.Net;
using DnsClient;
using DnsClient.Protocol;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
// Used to use Yubikey to login
using Yubico.YubiKey;
using Yubico.YubiKey.Piv;
namespace FireWallet
{
@ -700,7 +701,27 @@ namespace FireWallet
}
account = comboBoxaccount.Text;
password = textBoxaccountpassword.Text;
if (textBoxaccountpassword.Text == "")
{
if (File.Exists(dir + account + ".yubikey"))
{
// Check if yubikey is plugged in
var devices = YubiKeyDevice.FindAll();
if (devices.Count() > 0)
{
// Get key from yubikey
password = YubiUnlock();
}
}
} else password = textBoxaccountpassword.Text;
bool loggedin = await Login();
if (loggedin)
{
@ -2246,5 +2267,155 @@ namespace FireWallet
{
UpdateDomains();
}
#region yubikey
static bool PinSubmitter(KeyEntryData pin)
{
string s = "123456";
var s_b = Encoding.UTF8.GetBytes(s);
pin.SubmitValue(s_b);
return true;
}
private void buttonSettingsYubikey_Click(object sender, EventArgs e)
{
if (password.Length < 0)
{
return;
}
NotifyForm notifyForm = new NotifyForm("Insert Yubikey\nThis will use your yubikey to encrypt your account password.");
notifyForm.ShowDialog();
notifyForm.Dispose();
NotifyForm yubiLoadingForm = new NotifyForm("Encrypting. . .", false);
yubiLoadingForm.Show();
// Wait for the form to load
Application.DoEvents();
try
{
//Assumes there is exactly one yubikey connected and it has a RSA2048 certificate in slot 9d
//PIV PIN is assumed to be 123456
var devices = YubiKeyDevice.FindAll();
var ykDevice = devices.First();
PivSession piv = new(ykDevice);
piv.KeyCollector += PinSubmitter;
piv.VerifyPin();
var slot = PivSlot.KeyManagement;
X509Certificate2 cert = piv.GetCertificate(slot);
if (cert.SignatureAlgorithm.FriendlyName != "sha256RSA")
throw new CryptographicException("Certificate must be RSA with SHA256");
var publicKey = cert.GetRSAPublicKey() ?? throw new CryptographicException("Couldn't get public key from certificate.");
Aes aesFirst = Aes.Create();
var encryptedKey = publicKey.Encrypt(aesFirst.Key, RSAEncryptionPadding.Pkcs1);
var decryptedKey = piv.Decrypt(slot, encryptedKey);
//MessageBox.Show($"aesFirst.Key.Length: {aesFirst.Key.Length}");
//MessageBox.Show($"encryptedKey.Length: {encryptedKey.Length}");
//MessageBox.Show($"decryptedKey.Length: {decryptedKey.Length}");
// split the message into blocks of 128 bytes
string message = password;
int blockSize = 128;
int blockCount = (int)Math.Ceiling((double)message.Length / blockSize);
string[] strings = new string[blockCount];
FileStream sw = new FileStream(dir + account + ".yubikey", FileMode.Create, FileAccess.Write);
for (int i = 0; i < blockCount; i++)
{
int size = Math.Min(blockSize, message.Length - i * blockSize);
strings[i] = message.Substring(i * blockSize, size);
byte[] bytes = Encoding.ASCII.GetBytes(strings[i]);
var encryptedBytes = publicKey.Encrypt(bytes, RSAEncryptionPadding.Pkcs1);
sw.Write(encryptedBytes, 0, encryptedBytes.Length);
}
sw.Close();
sw.Dispose();
}
catch (Exception ex)
{
AddLog(ex.Message);
}
yubiLoadingForm.CloseNotification();
}
private string YubiUnlock()
{
NotifyForm notifyForm = new NotifyForm("Insert Yubikey to unlock");
notifyForm.ShowDialog();
notifyForm.Dispose();
NotifyForm yubiLoadingForm = new NotifyForm("Decrypting. . .", false);
yubiLoadingForm.Show();
// Wait for the form to load
Application.DoEvents();
try
{
//Assumes there is exactly one yubikey connected and it has a RSA2048 certificate in slot 9d
//PIV PIN is assumed to be 123456
var devices = YubiKeyDevice.FindAll();
var ykDevice = devices.First();
PivSession piv = new(ykDevice);
piv.KeyCollector += PinSubmitter;
piv.VerifyPin();
var slot = PivSlot.KeyManagement;
X509Certificate2 cert = piv.GetCertificate(slot);
if (cert.SignatureAlgorithm.FriendlyName != "sha256RSA")
throw new CryptographicException("Certificate must be RSA with SHA256");
var publicKey = cert.GetRSAPublicKey() ?? throw new CryptographicException("Couldn't get public key from certificate.");
Aes aesFirst = Aes.Create();
var encryptedKey = publicKey.Encrypt(aesFirst.Key, RSAEncryptionPadding.Pkcs1);
var decryptedKey = piv.Decrypt(slot, encryptedKey);
byte[] input = File.ReadAllBytes(dir + account + ".yubikey");
// decrypt the input
int blockSize = 256;
int blockCount = (int)Math.Ceiling((double)input.Length / blockSize);
byte[][] blocks = new byte[blockCount][];
byte[] decripted = new byte[blockCount * blockSize];
string output = "";
for (int i = 0; i < blockCount; i++)
{
int size = Math.Min(blockSize, input.Length - i * blockSize);
blocks[i] = new byte[size];
Array.Copy(input, i * blockSize, blocks[i], 0, size);
var paddedDecryptedBytes = piv.Decrypt(slot, blocks[i]);
byte[] decryptedBytes;
bool couldParse = Yubico.YubiKey.Cryptography.RsaFormat.TryParsePkcs1Decrypt(paddedDecryptedBytes, out decryptedBytes);
Array.Copy(decryptedBytes, 0, decripted, i * blockSize, decryptedBytes.Length);
output += Encoding.ASCII.GetString(decryptedBytes);
}
yubiLoadingForm.CloseNotification();
return output;
}
catch (Exception ex)
{
AddLog(ex.Message);
yubiLoadingForm.CloseNotification();
return "";
}
}
#endregion
}
}