How can I integrate complexity verification while using a PasswordBox? - c#

I have an application that needs to have the user create a passphrase. As such, there are various rules that need to be tested against the passphrase as it's being entered.
These rules are pretty typical-- the value must be a certain length, have one upper, one lower, include some special characters, etc.
However, since this is a WPF application using a PasswordBox control, and the resulting value is very sensitive, how can I setup a complexity comparison system that dynamically checks the passphrase as the user is typing?
To be clear, I have a list of all requirements in a text label below the passphrase creation PasswordBox element. As the user types, a validator shows which requirements are met and which ones are still needed. I also have an entropy calculator which give a typical "OK, Good, Strong, Very Strong" indicator once requirements are met. This is why I need to find a way to securely validate the value as the user is typing.
How can I accomplish this without emitting insecure .Net strings?

You can subscribe to PasswordChanged but don't use Password property if you care about securely storing your sensetive value. Instead, do this:
private void OnPasswordChanged(object sender, RoutedEventArgs e) {
using (var pwd = ((PasswordBox) sender).SecurePassword) {
int length = pwd.Length;
if (length == 0) {
// string empty, do something
return;
}
bool hasSpecial = false;
bool hasUpper = false;
bool hasLower = false;
bool hasDigit = false;
// etc
// allocate unmanaged memory and copy string there
IntPtr ptr = Marshal.SecureStringToBSTR(pwd);
try {
// each char in that string is 2 bytes, not one (it's UTF-16 string)
for (int i = 0; i < length * 2; i += 2) {
// so use ReadInt16 and convert resulting "short" to char
var ch = Convert.ToChar(Marshal.ReadInt16(ptr + i));
// run your checks
hasSpecial |= IsSpecialChar(ch);
hasUpper |= Char.IsUpper(ch);
hasLower |= Char.IsLower(ch);
hasDigit |= Char.IsDigit(ch);
}
}
finally {
// don't forget to zero memory to remove password from it
Marshal.ZeroFreeBSTR(ptr);
}
}
}
That way you never build .NET string during your validation, and every trace of password is cleared from memory when you finish.

Related

Caching PIN in multiple CMS Signatures

Well, most of the questions/answers I've found here are regarding not caching a Smartcard PIN which is the opposite case of what I'm looking for.
We have a console application that signs multiple hashes. For this we use Pkcs.CmsSigner because we need to validate the signed hashes server-side.
Normally a Smartcard's PIN should be cached automatically in the CSP per process and it does in Windows 7, but if we run our code in W10 it does not. Also we support both CNG and non-CNG certificates.
The method we use to sign is the following:
public string SignX509(string data, bool chkSignature, string timestampServer, X509Certificate2 selectedCertificate)
{
CmsSigner oSigner = null;
SignedCms oSignedData = null;
string hashText = String.Empty;
try
{
if (chkSignature)
{
oSigner = new CmsSigner();
oSigner.Certificate = selectedCertificate;
byte[] arrDataHashed = HashSHA1(data);
// hash the text to sign
ContentInfo info = new ContentInfo(arrDataHashed);
// put the hashed data into the signedData object
oSignedData = new SignedCms(info);
if (string.IsNullOrEmpty(timestampServer)) {
oSigner.SignedAttributes.Add(new Pkcs9SigningTime(DateTime.Now));
}
else {
TimeStampToken tsToken = GetTSAToken(arrDataHashed, timestampServer);
AsnEncodedData timeData = new Pkcs9AttributeObject(Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers.IdAASigningCertificate.Id, tsToken.GetEncoded());
oSigner.UnsignedAttributes.Add(timeData);
oSigner.SignedAttributes.Add(new Pkcs9SigningTime(tsToken.TimeStampInfo.GenTime.ToLocalTime()));
}
// sign the data
oSignedData.ComputeSignature(oSigner, false);
hashText = Convert.ToBase64String(oSignedData.Encode());
}
else
{
// just clean the hidden hash text
hashText = String.Empty;
}
}
catch (Exception ex)
{
Console.WriteLine("ERRNO [" + ex.Message + " ]");
return null;
}
return hashText;
}
What we've tried so far:
Using RSACryptoServiceProvider to explicitly persist the key in the CSP
RSACryptoServiceProvider key = (RSACryptoServiceProvider)cmsSigner.Certificate.PrivateKey;
key.PersistKeyInCsp = true;
This works if we use the SignHash method but as I've said before, we need to verify the signed data server-side and we do not have access to the certificate, therefore we need a PKCS envelope. If I set this bool and sign using the CMS code the behaviour is the same.
Setting the PIN programmatically
Another try was setting the PIN programmatically via CryptoContext, based on this answer:
private void SetPinForPrivateKey(X509Certificate2 certificate, string pin) {
if (certificate == null) throw new ArgumentNullException("certificate");
var key = (RSACryptoServiceProvider)certificate.PrivateKey;
var providerHandle = IntPtr.Zero;
var pinBuffer = System.Text.Encoding.ASCII.GetBytes(pin);
// provider handle is implicitly released when the certificate handle is released.
SafeNativeMethods.Execute(() => SafeNativeMethods.CryptAcquireContext(ref providerHandle,
key.CspKeyContainerInfo.KeyContainerName,
key.CspKeyContainerInfo.ProviderName,
key.CspKeyContainerInfo.ProviderType,
SafeNativeMethods.CryptContextFlags.Silent));
SafeNativeMethods.Execute(() => SafeNativeMethods.CryptSetProvParam(providerHandle,
SafeNativeMethods.CryptParameter.KeyExchangePin,
pinBuffer, 0));
SafeNativeMethods.Execute(() => SafeNativeMethods.CertSetCertificateContextProperty(
certificate.Handle,
SafeNativeMethods.CertificateProperty.CryptoProviderHandle,
0, providerHandle));
}
With this approach I am able to disable the PIN prompt by setting the PIN programmatically. The problem here is that I have to read the PIN the first time so I can set it in the subsequent signatures.
I've tried to read the PIN from the prompt using CryptoGetProvParam with the dwParam PP_ADMIN_PIN and PP_KEYEXCHANGE_PIN but without luck. My two guesses here are:
I'm not reading in the right time or way
CMS uses a different handler internally
Question 1:
Is there any way to read the PIN set in the Windows prompt?
Question 2:
If reading the PIN is not possible, is there any other way to force PIN caching?
Only now realized this question is still without an answer although we managed to bypass the whole 'read the PIN from Windows prompt' question.
This method does not answer my first question but I'll be answering the second question.
There was a bug in the smartcard CSP provider that disabled the PIN cache for all requests to SignHash even though they were made in the same process.
The smartcard provider has a SDK that exposes some smartcard operations, being one of those an operation to validate the smartcard PIN.
What we ended up doing was to create a simple WPF window that requests the user's PIN and uses the SDK to validate the PIN. If it is correct we use the method that I posted in the original question to force the PIN cache:
Another try was setting the PIN programmatically via CryptoContext, based on this answer:
private void SetPinForPrivateKey(X509Certificate2 certificate, string pin) {
if (certificate == null) throw new ArgumentNullException("certificate");
var key = (RSACryptoServiceProvider)certificate.PrivateKey;
var providerHandle = IntPtr.Zero;
var pinBuffer = System.Text.Encoding.ASCII.GetBytes(pin);
// provider handle is implicitly released when the certificate handle is released.
SafeNativeMethods.Execute(() => SafeNativeMethods.CryptAcquireContext(ref providerHandle,
key.CspKeyContainerInfo.KeyContainerName,
key.CspKeyContainerInfo.ProviderName,
key.CspKeyContainerInfo.ProviderType,
SafeNativeMethods.CryptContextFlags.Silent));
SafeNativeMethods.Execute(() => SafeNativeMethods.CryptSetProvParam(providerHandle,
SafeNativeMethods.CryptParameter.KeyExchangePin,
pinBuffer, 0));
SafeNativeMethods.Execute(() => SafeNativeMethods.CertSetCertificateContextProperty(
certificate.Handle,
SafeNativeMethods.CertificateProperty.CryptoProviderHandle,
0, providerHandle));
}
With this we are able to request the PIN only one time when signing multiple hashes until the smartcard provider fixes the bug on their side.

SendKeys with C# PostMessage - Underscore

I'm trying to send an underscore with PostMessage but the best I can get is -, can anyone help here I cannot find the answer anywhere.
I send a string to a character loop that gets each one and uses PostMessage to send the key, which works fine for almost everything but I cannot figure out _.
public bool SendKeys(string message)
{
var success = false;
uint wparam = 0 << 29 | 0;
foreach (var child in GetChildWindows(Window.Windowhandle))
{
var sb = new StringBuilder(100);
GetClassName(child, sb, sb.Capacity);
//(IntPtr)Keys.H
if (sb.ToString() != Window.TargetWindow) continue;
foreach (var c in message)
{
var k = ConvertFromString(c.ToString());
if (String.Equals(c.ToString(), c.ToString().ToUpper(), StringComparison.Ordinal))
{
PostMessage(child, WM_CHAR, (IntPtr) k, (IntPtr) wparam);
}
else
{
PostMessage(child, WM_KEYDOWN, (IntPtr)k, (IntPtr)wparam);
}
success = true;
Thread.Sleep(200);
}
}
return success;
}
ConvertFromString is just (Keys)Enum.Parse(typeof (Keys), keystr); really, getting the key from System.Form.Keys
Can anyone identify how to send an underscore??
ConvertFromString is just (Keys)Enum.Parse(typeof (Keys), keystr);
That is the fundamental flaw, there is no Keys enumeration value that represent an underscore. Keys are virtual keys, they are the same anywhere in the world. But the characters they produce are not the same, it depends the modifier keys and the active keyboard layout that the user selected.
An American user must first press the Shift key, then press Keys.OemMinus. A German user must press Shift, then Keys.OemQuestion. A French user only presses Keys.D8. A Spanish user presses AltGr, then Keys.D7. Etcetera.
You'll need to get ahead by dropping your dependency on Keys if you want a predictable result. Use WM_CHAR and pass the actual character you want.

CredUIPromptForCredentials from .NET with SecureString

I'd like to show the standard system dialog to ask the user for an account username and password to use this information to start a process with these credentials.
I've been pointed to the CredUIPromptForCredentials function that shows that dialog. It returns username and password as string. But the ProcessStartInfo structure expects a password as SecureString.
I understand that I could now use the password as string and convert it to a SecureString character by character (there's no single function for that) - but it would defeat the idea behind the SecureString entirely.
So I guess there must be some way to directly accept the password from the unmanaged call to CredUIPromptForCredentials as SecureString in .NET. After all, I really don't need to access the password in my application in any way. It's just supposed to be used to start another process and can then be forgotten as soon as possible.
So how would my P/Invoke declaration for CredUIPromptForCredentials look like with a SecureString? (I've started with the one from pinvoke.net for C#.)
Update: Oh, and if somebody has an example for the new function CredUIPromptForWindowsCredentials in Windows Vista/7, that would be cool as well, because I can't even figure out how to use that at the moment.
You can cast the IntPtr of an unmanaged string buffer to char* and use the SecureString(char*, int) constructor.
// somehow, we come into posession of an IntPtr to a string
// obviously, this would be a foolish way to come into it in
// production, since stringOriginalContents is already in managed
// code, and the lifetime can therefore not be guaranteed...
var stringOriginalContents = "foobar";
IntPtr strPtr = Marshal.StringToHGlobalUni(stringOriginalContents);
int strLen = stringOriginalContents.Length;
int maxLen = 100;
// we copy the IntPtr to a SecureString, and zero out the old location
SecureString ssNew;
unsafe
{
char* strUPtr = (char*)strPtr;
// if we don't know the length, calculate
//for (strLen = 0; *(strUPtr + strLen) != '\0'
// // stop if the string is invalid
// && strLen < maxLen; strLen++)
// ;
ssNew = new SecureString((char*)strPtr, strLen);
// zero out the old memory and release, or use a Zero Free method
//for (int i = 0; i < strLen; i++)
// *(strUPtr + i) = '\0';
//Marshal.FreeHGlobal(strPtr);
// (only do one of these)
Marshal.ZeroFreeGlobalAllocUnicode(strPtr);
}
// now the securestring has the protected data, and the old memory has been
// zeroed, we can check that the securestring is correct. This, also should
// not be in production code.
string strInSecureString =
Marshal.PtrToStringUni(
Marshal.SecureStringToGlobalAllocUnicode(ssNew));
Assert.AreEqual(strInSecureString, stringOriginalContents);

Get Safe sender list in Outlook 2007 C# Add in

I have created an Outlook 2007 add-in in C#.NET 4.0.
I want to read the safe sender list in my C# code.
if (oBoxItem is Outlook.MailItem)
{
Outlook.MailItem miEmail = (Outlook.MailItem)oBoxItem;
OlDefaultFolders f = Outlook.OlDefaultFolders.olFolderContacts;
if (miEmail != null)
{
string body = miEmail.Body;
double score = spamFilterObject.CalculateSpamScore(body);
if (score <= 0.9)
{
miEmail.Move(mfJunkEmail);
}
}
}
So, the above code moves all email to spam, even though they are present in the safe sender list. Thus I want to get the safe sender list so that I can avoid this spam checking.
Could anybody please help me on this?
The Outlook object model doesn't expose these lists (for more or less obvious reasons). The safe sender list can be read straight from the registry at:
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\[PROFILE NAME]\0a0d020000000000c000000000000046\001f0418
This binary registry key contains double-byte characters, separated by a semicolon (;).
The MAPI property mapping onto this registry key is
PR_SPAM_TRUSTED_SENDERS_W, documented here.
Chavan, I assume since this hasn't been updated in over 4 years, you don't need any more information, but this question and the answer helped me find what I was looking for (it was very hard to find) and enabled me to write the code below that may help if you're still looking for an answer.
This code runs in LINQPad, so if you aren't a LINQPad user, remove the .Dump() methods and replace with Console.WriteLine or Debug.WriteLine.
Cheers!
const string valueNameBlocked = "001f0426";
const string valueNameSafe = "001f0418";
// Note: I'm using Office 2013 (15.0) and my profile name is "Outlook"
// You may need to replace the 15.0 or the "Outlook" at the end of your string as needed.
string keyPath = #"Software\Microsoft\Office\15.0\Outlook\Profiles\Outlook";
string subKey = null;
var emptyBytes = new byte[] { };
var semi = new[] { ';' };
string blocked = null, safe = null;
// I found that my subkey under the profile was not the same on different machines,
// so I wrote this block to look for it.
using (var key = Registry.CurrentUser.OpenSubKey(keyPath))
{
var match =
// Get the subkeys and all of their value names
key.GetSubKeyNames().SelectMany(sk =>
{
using (var subkey = key.OpenSubKey(sk))
return subkey.GetValueNames().Select(valueName => new { subkey = sk, valueName });
})
// But only the one that matches Blocked Senders
.FirstOrDefault(sk => valueNameBlocked == sk.valueName);
// If we got one, get the data from the values
if (match != null)
{
// Simultaneously setting subKey string for later while opening the registry key
using (var subkey = key.OpenSubKey(subKey = match.subkey))
{
blocked = Encoding.Unicode.GetString((byte[])subkey.GetValue(valueNameBlocked, emptyBytes));
safe = Encoding.Unicode.GetString((byte[])subkey.GetValue(valueNameSafe, emptyBytes));
}
}
}
// Remove empty items and the null-terminator (sometimes there is one, but not always)
Func<string, List<string>> cleanList = s => s.Split(semi, StringSplitOptions.RemoveEmptyEntries).Where(e => e != "\0").ToList();
// Convert strings to lists (dictionaries might be preferred)
var blockedList = cleanList(blocked).Dump("Blocked Senders");
var safeList = cleanList(safe).Dump("Safe Senders");
byte[] bytes;
// To convert a modified list back to a string for saving:
blocked = string.Join(";", blockedList) + ";\0";
bytes = Encoding.Unicode.GetBytes(blocked);
// Write to the registry
using (var key = Registry.CurrentUser.OpenSubKey(keyPath + '\\' + subKey, true))
key.SetValue(valueNameBlocked, bytes, RegistryValueKind.Binary);
// In LINQPad, this is what I used to view my binary data
string.Join("", bytes.Select(b => b.ToString("x2"))).Dump("Blocked Senders: binary data");
safe = string.Join(";", safeList) + ";\0"; bytes = Encoding.Unicode.GetBytes(safe);
string.Join("", bytes.Select(b => b.ToString("x2"))).Dump("Safe Senders: binary data");
PST and IMAP4 (ost) stores keep the list in the profile section in the registry. Profile section guid is {00020D0A-0000-0000-C000-000000000046}. To access the data directly, you will need to know the Outlook version and the profile name.
Exchange store keeps this data as a part of the server side rule that processes incoming messages on the server side. You can see the rule data in OutlookSpy (I am its author) - go to the Inbox folder, "Associated Contents" tab, find the entry named (PR_RuleMsgName) == "Junk E-mail Rule", double click on it, take a look at the PR_EXTENDED_RULE_CONDITION property.
Outlook Object Model does not expose Junk mail settings. If using Redemption (I am also its author) is an option, it exposes the RDOJunkEmailOptions.TrustedSenders collection (works both for the PST and Exchange stores):
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set Store = Session.Stores.DefaultStore
set TrustedSenders = Store.JunkEmailOptions.TrustedSenders
for each v in TrustedSenders
debug.print v
next

What to do when you can not save a password as a hash

I have a program that uses System.DirectoryServices.AccountManagement.PrincipalContext to verify that the information a user entered in a setup screen is a valid user on the domain (the computer itself is not on the domain) and do some operations on the users of the domain. The issue is I do not want the user to need to enter his or her password every time they run the program so I want to save it, but I do not feel comfortable storing the password as plain-text in their app.config file. PrincipalContext needs a plain-text password so I can not do a salted hash as everyone recommends for password storing.
This is what I did
const byte[] mySalt = //It's a secret to everybody.
[global::System.Configuration.UserScopedSettingAttribute()]
public global::System.Net.NetworkCredential ServerLogin
{
get
{
var tmp = ((global::System.Net.NetworkCredential)(this["ServerLogin"]));
if(tmp != null)
tmp.Password = new System.Text.ASCIIEncoding().GetString(ProtectedData.Unprotect(Convert.FromBase64String(tmp.Password), mySalt, DataProtectionScope.CurrentUser));
return tmp;
}
set
{
var tmp = value;
tmp.Password = Convert.ToBase64String(ProtectedData.Protect(new System.Text.ASCIIEncoding().GetBytes(tmp.Password), mySalt, DataProtectionScope.CurrentUser));
this["ServerLogin"] = value;
}
}
Was this the right thing to do or is there a better way?
EDIT --
Here is a updated version based on everyone's suggestions
private MD5 md5 = MD5.Create();
[global::System.Configuration.UserScopedSettingAttribute()]
public global::System.Net.NetworkCredential ServerLogin
{
get
{
var tmp = ((global::System.Net.NetworkCredential)(this["ServerLogin"]));
if(tmp != null)
tmp.Password = System.Text.Encoding.UTF8.GetString(ProtectedData.Unprotect(Convert.FromBase64String(tmp.Password), md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(tmp.UserName.ToUpper())), DataProtectionScope.CurrentUser));
return tmp;
}
set
{
var tmp = value;
tmp.Password = Convert.ToBase64String(ProtectedData.Protect(System.Text.Encoding.UTF8.GetBytes(tmp.Password), md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(tmp.UserName.ToUpper())), DataProtectionScope.CurrentUser));
this["ServerLogin"] = tmp;
}
}
For the salt, I'd do a transformation on the username (hash it) rather than share the same salt for everyone.
For something like this, I'd also look for a way to keep the existing session alive longer rather than saving the password to create new sessions.
Instead of writing new System.Text.ASCIIEncoding(), you should write System.Text.Encoding.ASCII.
Also, I recommend using UTF8 instead.
Other than that, your code looks pretty good.
I like the JoelCoehoorn approach.
Use a value unique for the user machine as the password salt.
So it will be different in each deplyment ; ).
UPDATE: See this thread for ideas: How-To-Get-Unique-Machine-Signature

Categories

Resources