-
Notifications
You must be signed in to change notification settings - Fork 25
Feature/refactoring #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
de194d2
3fb6f45
0594780
a63e4e2
93ff827
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,22 +13,23 @@ public interface IAuthProvider | |
byte[] Encrypt(byte[] data); | ||
byte[] PromptToDecrypt(byte[] data); | ||
void ClaimCurrentCacheType(AuthCacheType authCacheType); | ||
AuthCacheType CurrentCacheType { get; } | ||
AuthCacheType CurrentCacheType { get; set; } | ||
} | ||
|
||
static class AuthProviderFactory | ||
{ | ||
public static IAuthProvider GetInstance(IntPtr keePassWindowHandle, AuthCacheType authCacheType) | ||
public static IAuthProvider Create(IntPtr keePassWindowHandle) | ||
{ | ||
var authCacheType = Settings.Instance.GetAuthCacheType(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Не очень нравится что тут зависимость на настройки. Всё-таки кажется что раз метод статический, то хочется чтобы он был чистым. |
||
#if DEBUG | ||
var provider = new XorProvider(authCacheType); | ||
#else | ||
var provider = WinHelloProvider.CreateInstance(authCacheType); | ||
var provider = new WinHelloProvider(authCacheType); | ||
#endif | ||
if (UAC.IsCurrentProcessElevated) | ||
return new WinHelloProviderForegroundDecorator(provider, keePassWindowHandle); | ||
else | ||
return provider; | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -123,15 +123,15 @@ private static extern SECURITY_STATUS NCryptDecrypt(SafeNCryptKeyHandle hKey, | |
#endregion | ||
|
||
private static readonly Lazy<string> _currentSID = new Lazy<string>(WindowsIdentity.GetCurrent().User.ToString); | ||
|
||
private static readonly object _mutex = new object(); | ||
private static WeakReference _instance; | ||
|
||
private const string Domain = Settings.ProductName; | ||
private const string SubDomain = ""; | ||
private const string PersistentName = Settings.ProductName; | ||
private const string InvalidatedKeyMessage = "Persistent key has not met integrity requirements. It might be caused by a spoofing attack. Try to recreate the key."; | ||
|
||
private AuthCacheType _currentCacheType; | ||
|
||
private static string LocalKeyName | ||
{ | ||
get | ||
|
@@ -154,99 +154,45 @@ private static string PersistentKeyName | |
|
||
private string CurrentKeyName | ||
{ | ||
get { return CurrentCacheType == AuthCacheType.Local ? LocalKeyName : PersistentKeyName; } | ||
get { return _currentCacheType == AuthCacheType.Local ? LocalKeyName : PersistentKeyName; } | ||
} | ||
|
||
private WinHelloProvider(AuthCacheType authCacheType) | ||
public WinHelloProvider(AuthCacheType authCacheType) | ||
{ | ||
CurrentCacheType = authCacheType; | ||
|
||
if (authCacheType == AuthCacheType.Local) | ||
{ | ||
DeletePersistentKey(); | ||
} | ||
else | ||
{ | ||
System.Diagnostics.Debug.Assert(authCacheType == AuthCacheType.Persistent); | ||
|
||
SafeNCryptKeyHandle ngcKeyHandle; | ||
if (!TryOpenPersistentKey(out ngcKeyHandle)) | ||
throw new AuthProviderKeyNotFoundException("Persistent key does not exist."); | ||
|
||
using (ngcKeyHandle) | ||
{ | ||
if (!VerifyPersistentKeyIntegrity(ngcKeyHandle)) | ||
{ | ||
ngcKeyHandle.Close(); | ||
DeletePersistentKey(); | ||
throw new AuthProviderInvalidKeyException(InvalidatedKeyMessage); | ||
} | ||
} | ||
} | ||
_currentCacheType = authCacheType; | ||
} | ||
|
||
public AuthCacheType CurrentCacheType { get; private set; } | ||
|
||
public static WinHelloProvider CreateInstance(AuthCacheType authCacheType) | ||
public AuthCacheType CurrentCacheType | ||
{ | ||
EnsureWinHelloAvailability(); | ||
|
||
lock (_mutex) | ||
{ | ||
WinHelloProvider winHelloProvider = null; | ||
if (_instance != null && (winHelloProvider = _instance.Target as WinHelloProvider) != null) | ||
{ | ||
if (winHelloProvider.CurrentCacheType == authCacheType) | ||
return winHelloProvider; | ||
else | ||
throw new AuthProviderException("Incompatible cache type with existing instance."); | ||
} | ||
|
||
winHelloProvider = new WinHelloProvider(authCacheType); | ||
_instance = new WeakReference(winHelloProvider); | ||
|
||
return winHelloProvider; | ||
} | ||
get { return EnsureKeyAvailability(_currentCacheType); } | ||
set { _currentCacheType = EnsureKeyAvailability(value); } | ||
} | ||
|
||
public void ClaimCurrentCacheType(AuthCacheType authCacheType) | ||
{ | ||
if (CurrentCacheType == authCacheType) | ||
return; | ||
|
||
lock (_mutex) | ||
{ | ||
if (authCacheType == AuthCacheType.Local) | ||
try | ||
{ | ||
DeletePersistentKey(); | ||
CurrentCacheType = authCacheType; | ||
} | ||
else | ||
catch (AuthProviderKeyNotFoundException) | ||
{ | ||
Debug.Assert(authCacheType == AuthCacheType.Persistent); | ||
|
||
SafeNCryptKeyHandle ngcKeyHandle; | ||
if (TryOpenPersistentKey(out ngcKeyHandle)) | ||
{ | ||
try | ||
{ | ||
if (!VerifyPersistentKeyIntegrity(ngcKeyHandle)) | ||
throw new AuthProviderInvalidKeyException(InvalidatedKeyMessage); | ||
ngcKeyHandle.Dispose(); | ||
} | ||
catch | ||
{ | ||
ngcKeyHandle.Dispose(); | ||
DeletePersistentKey(); | ||
throw; | ||
} | ||
} | ||
else | ||
{ | ||
CreatePersistentKey(false).Dispose(); | ||
} | ||
CreatePersistentKey(false).Dispose(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Раз уж тронули, добавь плиз |
||
_currentCacheType = authCacheType; | ||
} | ||
catch (AuthProviderInvalidKeyException) | ||
{ | ||
Debug.Assert(authCacheType == AuthCacheType.Persistent); | ||
CreatePersistentKey(true).Dispose(); | ||
_currentCacheType = authCacheType; | ||
} | ||
finally | ||
{ | ||
if (authCacheType == AuthCacheType.Local) | ||
DeletePersistentKey(); | ||
} | ||
|
||
CurrentCacheType = authCacheType; | ||
} | ||
} | ||
|
||
|
@@ -256,7 +202,8 @@ public byte[] Encrypt(byte[] data) | |
{ | ||
try | ||
{ | ||
return Encrypt(data, retry: i > 0); | ||
var verifyIntegrity = _currentCacheType == AuthCacheType.Persistent; | ||
return Encrypt(data, CurrentKeyName, verifyIntegrity); | ||
} | ||
catch (AuthProviderSystemErrorException ex) | ||
{ | ||
|
@@ -283,7 +230,8 @@ public byte[] PromptToDecrypt(byte[] data) | |
{ | ||
try | ||
{ | ||
return PromptToDecrypt(data, retry: i > 0); | ||
var verifyIntegrity = _currentCacheType == AuthCacheType.Persistent; | ||
return PromptToDecrypt(data, CurrentKeyName, verifyIntegrity, retry: i > 0); | ||
} | ||
catch (AuthProviderSystemErrorException ex) | ||
{ | ||
|
@@ -304,44 +252,44 @@ public byte[] PromptToDecrypt(byte[] data) | |
} | ||
} | ||
|
||
private byte[] Encrypt(byte[] data, bool retry) | ||
private static byte[] Encrypt(byte[] data, string keyName, bool verifyIntegrity) | ||
{ | ||
byte[] cbResult; | ||
SafeNCryptProviderHandle ngcProviderHandle; | ||
NCryptOpenStorageProvider(out ngcProviderHandle, MS_NGC_KEY_STORAGE_PROVIDER, 0).ThrowOnError("NCryptOpenStorageProvider"); | ||
using (ngcProviderHandle) | ||
{ | ||
SafeNCryptKeyHandle ngcKeyHandle; | ||
NCryptOpenKey(ngcProviderHandle, out ngcKeyHandle, CurrentKeyName, 0, CngKeyOpenOptions.Silent).ThrowOnError("NCryptOpenKey"); | ||
NCryptOpenKey(ngcProviderHandle, out ngcKeyHandle, keyName, 0, CngKeyOpenOptions.Silent).ThrowOnError("NCryptOpenKey"); | ||
using (ngcKeyHandle) | ||
{ | ||
if (CurrentCacheType == AuthCacheType.Persistent && !VerifyPersistentKeyIntegrity(ngcKeyHandle)) | ||
if (verifyIntegrity && !VerifyPersistentKeyIntegrity(ngcKeyHandle)) | ||
throw new AuthProviderInvalidKeyException(InvalidatedKeyMessage); | ||
|
||
int pcbResult; | ||
NCryptEncrypt(ngcKeyHandle, data, data.Length, IntPtr.Zero, null, 0, out pcbResult, NCRYPT_PAD_PKCS1_FLAG).ThrowOnError("NCryptEncrypt"); | ||
|
||
cbResult = new byte[pcbResult]; | ||
NCryptEncrypt(ngcKeyHandle, data, data.Length, IntPtr.Zero, cbResult, cbResult.Length, out pcbResult, NCRYPT_PAD_PKCS1_FLAG).ThrowOnError("NCryptEncrypt"); | ||
System.Diagnostics.Debug.Assert(cbResult.Length == pcbResult); | ||
Debug.Assert(cbResult.Length == pcbResult); | ||
} | ||
} | ||
|
||
return cbResult; | ||
} | ||
|
||
private byte[] PromptToDecrypt(byte[] data, bool retry) | ||
private static byte[] PromptToDecrypt(byte[] data, string keyName, bool verifyIntegrity, bool retry) | ||
{ | ||
byte[] cbResult; | ||
SafeNCryptProviderHandle ngcProviderHandle; | ||
NCryptOpenStorageProvider(out ngcProviderHandle, MS_NGC_KEY_STORAGE_PROVIDER, 0).ThrowOnError("NCryptOpenStorageProvider"); | ||
using (ngcProviderHandle) | ||
{ | ||
SafeNCryptKeyHandle ngcKeyHandle; | ||
NCryptOpenKey(ngcProviderHandle, out ngcKeyHandle, CurrentKeyName, 0, CngKeyOpenOptions.None).ThrowOnError("NCryptOpenKey"); | ||
NCryptOpenKey(ngcProviderHandle, out ngcKeyHandle, keyName, 0, CngKeyOpenOptions.None).ThrowOnError("NCryptOpenKey"); | ||
using (ngcKeyHandle) | ||
{ | ||
if (CurrentCacheType == AuthCacheType.Persistent && !VerifyPersistentKeyIntegrity(ngcKeyHandle)) | ||
if (verifyIntegrity && !VerifyPersistentKeyIntegrity(ngcKeyHandle)) | ||
throw new AuthProviderInvalidKeyException(InvalidatedKeyMessage); | ||
|
||
ApplyUIContext(ngcKeyHandle, retry); | ||
|
@@ -353,14 +301,47 @@ private byte[] PromptToDecrypt(byte[] data, bool retry) | |
cbResult = new byte[data.Length * 2]; | ||
int pcbResult; | ||
NCryptDecrypt(ngcKeyHandle, data, data.Length, IntPtr.Zero, cbResult, cbResult.Length, out pcbResult, NCRYPT_PAD_PKCS1_FLAG).ThrowOnError("NCryptDecrypt"); | ||
// TODO: secure resize | ||
Array.Resize(ref cbResult, pcbResult); | ||
|
||
if (cbResult.Length > pcbResult) | ||
{ | ||
var res = new byte[pcbResult]; | ||
Array.Copy(cbResult, res, pcbResult); | ||
Array.Clear(cbResult, 0, cbResult.Length); | ||
cbResult = res; | ||
} | ||
} | ||
} | ||
|
||
return cbResult; | ||
} | ||
|
||
private static AuthCacheType EnsureKeyAvailability(AuthCacheType authCacheType) | ||
{ | ||
EnsureWinHelloAvailability(); | ||
|
||
if (authCacheType == AuthCacheType.Persistent) lock (_mutex) | ||
{ | ||
SafeNCryptKeyHandle ngcKeyHandle; | ||
if (!TryOpenPersistentKey(out ngcKeyHandle)) | ||
throw new AuthProviderKeyNotFoundException("Persistent key does not exist."); | ||
|
||
using (ngcKeyHandle) | ||
{ | ||
if (!VerifyPersistentKeyIntegrity(ngcKeyHandle)) | ||
throw new AuthProviderInvalidKeyException(InvalidatedKeyMessage); | ||
} | ||
} | ||
else if (authCacheType != AuthCacheType.Local) | ||
throw new NotSupportedException("Unknown cache type: " + authCacheType); | ||
|
||
return authCacheType; | ||
} | ||
|
||
private static void EnsureWinHelloAvailability() | ||
{ | ||
var dummy = LocalKeyName; // throw an exception if not available | ||
} | ||
|
||
private static void RetrieveKeys(out string localKey, out string persistentKey) | ||
{ | ||
NgcGetDefaultDecryptionKeyName(_currentSID.Value, 0, 0, out localKey); | ||
|
@@ -371,11 +352,6 @@ private static void RetrieveKeys(out string localKey, out string persistentKey) | |
throw new AuthProviderIsUnavailableException("Windows Hello is not available."); | ||
} | ||
|
||
private static void EnsureWinHelloAvailability() | ||
{ | ||
var dummy = LocalKeyName; // throw an exception if not available | ||
} | ||
|
||
private static void DeletePersistentKey() | ||
{ | ||
SafeNCryptKeyHandle ngcKeyHandle; | ||
|
@@ -423,10 +399,8 @@ private static bool VerifyPersistentKeyIntegrity(SafeNCryptKeyHandle ngcKeyHandl | |
{ | ||
NCryptGetProperty(ngcKeyHandle, NCRYPT_NGC_CACHE_TYPE_PROPERTY_DEPRECATED, ref cacheType, sizeof(int), out pcbResult, CngPropertyOptions.None).ThrowOnError("NCRYPT_NGC_CACHE_TYPE_PROPERTY_DEPRECATED"); | ||
} | ||
if (cacheType != NCRYPT_NGC_CACHE_TYPE_PROPERTY_AUTH_MANDATORY_FLAG) | ||
return false; | ||
|
||
return true; | ||
return cacheType == NCRYPT_NGC_CACHE_TYPE_PROPERTY_AUTH_MANDATORY_FLAG; | ||
} | ||
|
||
private static SafeNCryptKeyHandle CreatePersistentKey(bool overwriteExisting) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Наличие сеттера вводит в заблужение о цели существования
ClaimCurrentCacheType
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Наверное, просто
Claim...
не должен быть частьюAuthProvider
аThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ещё надо подумать – можно ли как-то обойтись от зависимости на тип кэша у провайдера совсем. Например, этот же энам у нас используется и при создании storage'а, но там мы на разные реализации всё разнесли и свитч только в фабрике присутствует. Может и тут нам также поступить и декомпозировать ответственность за шифровку/дешивровку в отдельный класс, а за выбор ключа сделать ответственным кого-нибудь другого? Можно передавать провайдер ключа как зависимость
AuthProvider'у
, например. Что думаешь?