Freigeben über


Application password security

So you have written your neat application that uses WCF hopefully using the peer networking stack (aka PeerChannel) and you are all ready to distribute your application. Now, this is a good time to pause and think about a few things.

With all the connected applications that WCF will make easy to develop and distribute it is imperative on you (the application developers') part to map out the lifecycle of an application password. And by lifecycle I mean the following -
1. secure generation of a strong password,
2. secure storage of the password,
3. secure retrieval of the password, and
4. secure transmission (4.a electronic and 4.b otherwise) of the password.

In this article we will evaluate and implement the code for the first 3 points listed above. 4. is outside the scope of this article, and is a potential future blog entry candidate.

Item 1 can be tackled in a variety of ways, however, we will look at the approach that is exposed by the .Net Framework 2.0 libraries. Items 2 and 3 can be easily implemented using the Windows Credential Management API infrastructure (aka CredMan). We will be using the CredRead, CredWrite (and the associated CredFree) APIs. We recommend that you read through the AuthN APIs section of Windows Security Infrastructure to get a fuller understanding of the breadth and scope of this infrastructure.

The nitty gritty

What we are going to do is take an existing application and enhance it so it can securely manage the lifecycle of the password. We will use SecureChat sample that will be shipped with WinFX SDK Beta 2 for this purpose. You may wish to check out the original PeerChannel Chat sample here to get an idea of the structure of the application. Or check out Kevin Ransoms blog for a great start on PeerChannel programming.

Developing the CredManUtils library

We will start with the development of the managed CredMan library (lets call it CredManUtils) that provides a safe wrapper around the native methods (CredRead, CredWrite and CredFree). Most of the work in these wrappers will be around safely stuffing data into & safely retrieving data out of the CREDENTIAL structure. A standard practice in this type of interop programming is to define a managed structure that mirrors the native strucuture to the byte and then marshal the data back and forth between the native and managed structures. The managed application programs against the managed structure (Credential) while the native APIs interact with the native structure (NativeCredential). Since we will have to explicitly allocate and deallocate memory when we interact with native APIs, we should wrap the alloc and free semantics inside a CriticalHandle. By overriding the abstract method ReleaseHandle with the memory freeing code, we can be sure that when the object is disposed the credential memory is CredFree'd accordingly.

The CREDENTIAL structure will be configured using a number of enums that define the Type of the credential, the scope of Persistance of the credential. The enums are listed towards the end of this article.

Code Dirt

So much talk about the CredMan APIs that its now time to take a look at their signatures and how we invoke them. As you can see these native APIs are using the NativeCredential structure as mentioned earlier.

[DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool CredRead(string target, CRED_TYPE type, int reservedFlag, out IntPtr CredentialPtr);

[DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool CredWrite([In] ref NativeCredential userCredential, [In] UInt32 flags);

[DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)]
static extern bool CredFree([In] IntPtr cred);

Here is the native credential strucure that is used in the signatures -

[

StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct NativeCredential
{
   public UInt32 Flags;
   public CRED_TYPE Type;
   public IntPtr TargetName;
   public IntPtr Comment;
   public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
   public UInt32 CredentialBlobSize;
   public IntPtr CredentialBlob;
   public UInt32 Persist;
   public UInt32 AttributeCount;
   public IntPtr Attributes;
   public IntPtr TargetAlias;
   public IntPtr UserName;

/// <summary>
/// This method derives a NativeCredential instance from a given Credential instance.
/// </summary>
/// <param name="cred">The managed Credential counterpart containing data to be stored.</param>
/// <returns>A NativeCredential instance that is derived from the given Credential
/// instance.</returns>
internal static NativeCredential GetNativeCredential(Credential cred)
{
      NativeCredential ncred = new NativeCredential();
ncred.AttributeCount = 0;
ncred.Attributes = IntPtr.Zero;
ncred.Comment = IntPtr.Zero;
ncred.TargetAlias = IntPtr.Zero;
ncred.Type = CRED_TYPE.GENERIC;
ncred.Persist = (UInt32)Persistance.CRED_PERSIST_SESSION;
      ncred.CredentialBlobSize = (UInt32)cred.CredentialBlobSize;
ncred.TargetName = Marshal.StringToCoTaskMemUni(cred.TargetName);
ncred.CredentialBlob = Marshal.StringToCoTaskMemUni(cred.CredentialBlob);
ncred.UserName = Marshal.StringToCoTaskMemUni(System.Environment.UserName);
      return ncred;
}
}

And here is the Credential type used by the managed code -

[

StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct Credential
{
   public UInt32 Flags;
   public CRED_TYPE Type;
   public string TargetName;
   public string Comment;
   public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
   public UInt32 CredentialBlobSize;
   public string CredentialBlob;
   public Persistance Persist;
   public UInt32 AttributeCount;
   public IntPtr Attributes;
   public string TargetAlias;
   public string UserName;
}

As you can see, the difference between the Credential and NativeCredential types is essentially in the way the datatypes of the fields are declared. The managed Credential type uses friendly enum names, and strings, while the native counterpart deals in UInt32s and IntPtrs.

Now comes the critial part. This type provides a safe way to deal with (and importantly dispose of) Win32 native memory resource handles.
Note that CriticalHandle type classes are similar to SafeHandle but they do not deal with reference counting and are inherently not thread-safe. You (the app developer) will need to worry and take care of the thread-safetyness when using instances of this type.

#region

Critical Handle Type definition
sealed class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid
{
// Set the handle.
internal CriticalCredentialHandle(IntPtr preexistingHandle)
{
SetHandle(preexistingHandle);
}

internal Credential GetCredential()
{
if (!IsInvalid)
{
// Get the Credential from the mem location
NativeCredential ncred = (NativeCredential)Marshal.PtrToStructure(handle,
typeof(NativeCredential));

// Create a managed Credential type and fill it with data from the native counterpart.
Credential cred = new Credential();
cred.CredentialBlobSize = ncred.CredentialBlobSize;
cred.CredentialBlob = Marshal.PtrToStringUni(ncred.CredentialBlob,
(int)ncred.CredentialBlobSize / 2);
cred.UserName = Marshal.PtrToStringUni(ncred.UserName);
cred.TargetName = Marshal.PtrToStringUni(ncred.TargetName);
cred.TargetAlias = Marshal.PtrToStringUni(ncred.TargetAlias);
cred.Type = ncred.Type;
cred.Flags = ncred.Flags;
cred.Persist = (Persistance)ncred.Persist;
return cred;
}
else
{
throw new InvalidOperationException("Invalid CriticalHandle!");
}
}

// Perform any specific actions to release the handle in the ReleaseHandle method.
// Often, you need to use Pinvoke to make a call into the Win32 API to release the
// handle. In this case, however, we can use the Marshal class to release the unmanaged memory.

override protected bool ReleaseHandle()
{
// If the handle was set, free it. Return success.
if (!IsInvalid)
{
// NOTE: We should also ZERO out the memory allocated to the handle, before free'ing it
// so there are no traces of the sensitive data left in memory.
CredFree(handle);
// Mark the handle as invalid for future users.
SetHandleAsInvalid();
return true;
}
// Return false.
return false;
}
}
#endregion

The WriteCred and ReadCred API helper methods are fairly straightforward. They deal with safely trolling data between the managed and native Credential structures and this keeps the helpers simple and easy to understand.

public static int WriteCred(string key, string secret)
{
// Validations.

byte[] byteArray = Encoding.Unicode.GetBytes(secret);
if (byteArray.Length > 512)
throw new ArgumentOutOfRangeException("The secret message has exceeded 512 bytes.");

// Go ahead with what we have are stuff it into the CredMan structures.
Credential cred = new Credential();
cred.TargetName = key;
cred.CredentialBlob = secret;
cred.CredentialBlobSize = (UInt32)Encoding.Unicode.GetBytes(secret).Length;
cred.AttributeCount = 0;
cred.Attributes = IntPtr.Zero;
cred.Comment = null;
cred.TargetAlias = null;
cred.Type = CRED_TYPE.GENERIC;
cred.Persist = Persistance.CRED_PERSIST_SESSION;
NativeCredential ncred = NativeCredential.GetNativeCredential(cred);
// Write the info into the CredMan storage.
bool written = CredWrite(ref ncred, 0);
int lastError = Marshal.GetLastWin32Error();
if (written)
{
return 0;
}
else
{
string message = string.Format("CredWrite failed with the error code {0}.", lastError);
throw new Exception (message);
}
}

And here is the ReadCred method, simple and straightforward. Note that the code to free the returned NativeCredential structure is present in the critical handle instance which is implicitly invoked when the object is to be disposed.

public static string ReadCred(string key)
{
   // Validations.

   IntPtr nCredPtr;
   string readPasswordText = null;

   // Since we will *have* to call CredFree upon a successful CredRead operation, we will
   // employ the CER (Constrained Execution Region) feature of the .Net 2.0 runtime which
   // will ensure that the operations are carried out atomically.
   RuntimeHelpers.PrepareConstrainedRegions();

   try
   {
   }
   finally
   {
      // Make the API call using the P/Invoke signature
      bool read = CredRead (key, CRED_TYPE.GENERIC, 0, out nCredPtr);
      int lastError = Marshal.GetLastWin32Error();

      // If the API was successful then...
      if (read)
      {
         using (CriticalCredentialHandle critCred = new CriticalCredentialHandle(nCredPtr))
         {
            Credential cred = critCred.GetCredential();
            readPasswordText = cred.CredentialBlob;
         }
      }
      else
      {
         string message = string.Format("ReadCred failed with the error code {0}.", lastError);
         throw new Exception (message);
      }
      return readPasswordText;
   }
}

The above code snippets show us how to write and read passwords passwords to/from a secure storage. You may want to read more about the CER and reliability contracts feature of .NET 2.0 runtime.

Let there be a strong password!

Let us now consider the initial stage in the password lifecycle - The Generation of A Strong Password. The definition of a strong password is a moving target and is a subject of much debate. We will not dwelve into the details of this debate (at this time :) ) and instead focus on leveragingt the functionality provided by the amazing .Net framework 2.0. One of the newest and more-wanted features of .Net 2.0 is the System.Web.Security.Membership class. This class can be used to validate user credentials and manage user settings. While we do not deal with "user" info except passwords here, this class is nevertheless very useful even in non ASP.NET scenarios.

The GeneratePassword static method of this type is what we will be using in this sample. This method takes 2 parameters (the overall length of the password and the no. of non-alphanumeric chars *required* to be present in the generated password.

Here is how we will define a helper method that uses this GeneratePassword API -

internal static string GetRandomPassword()
{
   int length = 15;
   int numberOfNonAlphanumericCharacters = 5;

   string password = System.Web.Security.Membership.GeneratePassword(length,
numberOfNonAlphanumericCharacters);

   return password;
}

Although it does not generate the most secure password, this is a great step in the right direction for all applications that need to generate passwords.

Secure password storage & retrieval - Check.
Auto generation of strong password - Check.
Is the user supplied password Strong? - Check.

Now we have the ability to have the application generate strong password. In many cases, the application would have to allow the user to specify a password that can be used instead of the app generated password (think of a peer that is joining an already created mesh. In this case, the password is already generated by the mesh creator). Since the security chain is only as strong as the weakest link the application will need to validate and check if it conforms to the application password strength policy. If the password is weak then the application may choose to not allow to proceed.

A quick fire way to check for password policy conformance is to use regular expressions to validate the user supplied password according to our app password rules.

internal static bool IsPasswordStrong (string password)
{
   // So what we mean by 'strong' here is that the password satisfy the following rules - 

   //this is an example policy - you may want to boost it up based on your requirements.
   // a. Have atleast 15 characters
   // b. Have atleast 3 digits
   // c. Have atleast 2 special chars.

   System.Text.RegularExpressions.Regex strongEx =
      new System.Text.RegularExpressions.Regex(@"(?=.{15,})(?=(.*\d){3,})(?=(.*\W){2,})");

   return strongEx.IsMatch(password);
}

In this particular sample, if the password is deemed strong, then the user is allowed to create a secure chat mesh otherwise the app will indicate the reason and quit.

Finishing up the App

The final touches added to the sample application include - a neat command line interface (CLI) switch processing. The application in its current avatar runs from the command line and accepts the following parameters -

  • generatePassword - This switch instructs the app to generate a secure random password.

  • password:<value> - This switch allows the user to specify a password. This password is validated for conformance to the app password policy. If the password is deemed 'weak' then the application will mention the reason and quit.

  • storePassword - This switch instructs the app to store the password (user supplied, or generated) in the CredMan storage.

  • readPassword - This switch instructs the app to read the password already stored in the CredMan storage. (typically used in later app runs).

 

Wrapping up.

The major components of the sample application have been broken down and explained above. We have seen how to use the Windows Security infrastructure to store and retrieve sensitive information (passwords, et al) from the Credential Manager storage, as well as easy .NET 2.0 provided ways to generate random strong passwords that conform to the applications password policy. And finally, a quickfire way to validate a supplied password and check for its conformance to the application password policy.

These helper methods should give you folks an idea on how to leverage the Windows Security infrastructure and using it in conjunction with the PeerChannel app programming. Feel free to let us know if you have any questions.

Before I conclude, I'd like to thank Ram Pamulapati for his technical guidance and support in getting this sample app and blog done.
try {} finally {Thanks, Ram!}

Wishing you folks a happy & safe Diwali!

- Chandra Chivukula
This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at: https://www.microsoft.com/info/cpyright.htm

Show Code Hide Code

CredManUtils.cs

using

System;

using

System.Collections.Generic;

using

System.Text;

using

System.Runtime.InteropServices;

using

Microsoft.Win32;

using

System.Security.Permissions;

using

Microsoft.Win32.SafeHandles;

using

System.Web;

using

System.Runtime.CompilerServices;

namespace

CredMan

{

/// <summary>

/// Library exposes 2 friendly API frontends for the crypt'ic ReadCred and WriteCred crypto apis.

/// </summary>

internal class CredManUtils

{

#region

Structs and Enums

private enum CRED_TYPE : int

{

GENERIC = 1,

DOMAIN_PASSWORD = 2,

DOMAIN_CERTIFICATE = 3,

DOMAIN_VISIBLE_PASSWORD = 4,

MAXIMUM = 5

}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]

private struct Credential

{

public UInt32 Flags;

public CRED_TYPE Type;

public string TargetName;

public string Comment;

public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;

public UInt32 CredentialBlobSize;

public string CredentialBlob;

public Persistance Persist;

public UInt32 AttributeCount;

public IntPtr Attributes;

public string TargetAlias;

public string UserName;

}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]

private struct NativeCredential

{

public UInt32 Flags;

public CRED_TYPE Type;

public IntPtr TargetName;

public IntPtr Comment;

public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;

public UInt32 CredentialBlobSize;

public IntPtr CredentialBlob;

public UInt32 Persist;

public UInt32 AttributeCount;

public IntPtr Attributes;

public IntPtr TargetAlias;

public IntPtr UserName;

/// <summary>

/// This method derives a NativeCredential instance from a given Credential instance.

/// </summary>

/// <param name="cred">The managed Credential counterpart containing data to be stored.</param>

/// <returns>A NativeCredential instance that is derived from the given Credential instance.</returns>

internal static NativeCredential GetNativeCredential(Credential cred)

{

NativeCredential ncred = new NativeCredential();

ncred.AttributeCount = 0;

ncred.Attributes = IntPtr.Zero;

ncred.Comment = IntPtr.Zero;

ncred.TargetAlias = IntPtr.Zero;

ncred.Type = CRED_TYPE.GENERIC;

ncred.Persist = (UInt32)Persistance.CRED_PERSIST_SESSION;

ncred.CredentialBlobSize = (UInt32)cred.CredentialBlobSize;

ncred.TargetName = Marshal.StringToCoTaskMemUni(cred.TargetName);

ncred.CredentialBlob = Marshal.StringToCoTaskMemUni(cred.CredentialBlob);

ncred.UserName = Marshal.StringToCoTaskMemUni(System.Environment.UserName);

return ncred;

}

}

private enum Persistance : int

{

CRED_PERSIST_SESSION = 1,

CRED_PERSIST_LOCAL_MACHINE = 2,

CRED_PERSIST_ENTERPRISE = 3

}

#endregion

#region

P/Invoke API signatures

[DllImport("advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]

static extern bool CredRead(string target, CRED_TYPE type, int reservedFlag, out IntPtr CredentialPtr);

[DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]

static extern bool CredWrite([In] ref NativeCredential userCredential, [In] UInt32 flags);

[DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)]

static extern bool CredFree([In] IntPtr cred);

#endregion

#region

Critical Handle Type definition

sealed class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid

{

// Set the handle.

internal CriticalCredentialHandle(IntPtr preexistingHandle)

{

SetHandle(preexistingHandle);

}

internal Credential GetCredential()

{

if (!IsInvalid)

{

// Get the Credential from the mem location

NativeCredential ncred = (NativeCredential)Marshal.PtrToStructure(handle, typeof(NativeCredential));

// Create a managed Credential type and fill it with data from the native counterpart.

Credential cred = new Credential();

cred.CredentialBlobSize = ncred.CredentialBlobSize;

cred.CredentialBlob = Marshal.PtrToStringUni(ncred.CredentialBlob, (int)ncred.CredentialBlobSize / 2);

cred.UserName = Marshal.PtrToStringUni(ncred.UserName);

cred.TargetName = Marshal.PtrToStringUni(ncred.TargetName);

cred.TargetAlias = Marshal.PtrToStringUni(ncred.TargetAlias);

cred.Type = ncred.Type;

cred.Flags = ncred.Flags;

cred.Persist = (Persistance)ncred.Persist;

return cred;

}

else

{

throw new InvalidOperationException("Invalid CriticalHandle!");

}

}

// Perform any specific actions to release the handle in the ReleaseHandle method.

// Often, you need to use Pinvoke to make a call into the Win32 API to release the

// handle. In this case, however, we can use the Marshal class to release the unmanaged memory.

override protected bool ReleaseHandle()

{

// If the handle was set, free it. Return success.

if (!IsInvalid)

{

// TODO: We should also ZERO out the memory allocated to the handle, before free'ing it.

// Freedom!!!

CredFree(handle);

// Mark the handle as invalid for future users.

SetHandleAsInvalid();

return true;

}

// Return false.

return false;

}

}

#endregion

#region

The WriteCred library method

/// <summary>

/// Writes a given secret string under a given 'target' name.

/// ALL exceptions are bubbled up to the caller.

/// </summary>

/// <param name="name">Name</param>

/// <param name="secret">The secret message, must be less than 512 bytes.</param>

/// <returns>Zero on success, API error code if the WriteCred API fails.</returns>

public static int WriteCred(string key, string secret)

{

// Validations.

if (string.IsNullOrEmpty(key))

throw new ArgumentNullException("name");

if (null == secret)

throw new ArgumentNullException("secret");

byte[] byteArray = Encoding.Unicode.GetBytes(secret);

if (byteArray.Length > 512)

throw new ArgumentOutOfRangeException("The secret message has exceeded 512 bytes.");

// Go ahead with what we have are stuff it into the CredMan.

Credential cred = new Credential();

cred.TargetName = key;

cred.CredentialBlob = secret;

cred.CredentialBlobSize = (UInt32)Encoding.Unicode.GetBytes(secret).Length;

cred.AttributeCount = 0;

cred.Attributes = IntPtr.Zero;

cred.Comment = null;

cred.TargetAlias = null;

cred.Type = CRED_TYPE.GENERIC;

cred.Persist = Persistance.CRED_PERSIST_SESSION;

NativeCredential ncred = NativeCredential.GetNativeCredential(cred);

bool written = CredWrite(ref ncred, 0);

int lastError = Marshal.GetLastWin32Error();

if (written)

{

return 0;

}

else

{

string message = string.Format("CredWrite failed with the error code {0}.", lastError);

throw new Exception (message);

}

}

#endregion

#region

The ReadCred library method

/// <summary>

/// Calls the ReadCred API that actually reads the secret stored against a given name.

/// </summary>

/// <param name="name">Name containing the secret to retrieve</param>

/// <returns>The secret if it was successfully retrieved.

/// An exception is thrown if an API failure occurs.</returns>

public static string ReadCred(string key)

{

// Validations.

if (string.IsNullOrEmpty(key))

throw new ArgumentNullException("name");

IntPtr nCredPtr;

string readPasswordText = null;

// Since we will *have* to call CredFree upon a successful CredRead operation, we will

// employ the CER (Constrained Execution Region) feature of the .Net 2.0 runtime which

// will ensure that the operations are carried out atomically.

RuntimeHelpers.PrepareConstrainedRegions();

try

{

}

finally

{

// Make the API call using the P/Invoke signature

bool read = CredRead(key, CRED_TYPE.GENERIC, 0, out nCredPtr);

int lastError = Marshal.GetLastWin32Error();

// If the API was successful then...

if (read)

{

using (CriticalCredentialHandle critCred = new CriticalCredentialHandle(nCredPtr))

{

Credential cred = critCred.GetCredential();

readPasswordText = cred.CredentialBlob;

}

}

else

{

string message = string.Format("ReadCred failed with the error code {0}.", lastError);

throw new Exception(message);

}

}

return readPasswordText;

}

#endregion

#region

GeneratePassword library method.

internal static string GetRandomPassword()

{

int length = 15; int numberOfNonAlphanumericCharacters = 5;

string password = System.Web.Security.Membership.GeneratePassword(length, numberOfNonAlphanumericCharacters);

return password;

}

#endregion

#region

ValidatePassword library method.

internal static bool IsPasswordStrong (string password)

{

// So what we mean by 'strong' here is that the password satisfy the following rules -

// a. Have atleast 15 characters

// b. Have atleast 2 digits

// c. Have atleast 2 special chars.

System.Text.RegularExpressions.Regex strong = new System.Text.RegularExpressions.Regex(@"(?=.{15,})(?=(.*\d){2,})(?=(.*\W){2,})");

return strong.IsMatch(password);

}

#endregion

}

}

SecureChat.cs

// Copyright (c) Microsoft Corporation. All Rights Reserved.

using

System;

using

System.Configuration;

using

System.Security;

using

System.Security.Cryptography;

using

System.Security.Cryptography.X509Certificates;

using

System.ServiceModel;

using

System.ServiceModel.Channels;

using

System.Collections;

using

System.Collections.Specialized;

using

System.Text.RegularExpressions;

using

CredMan;

// Multi-party chat application using Peer Channel (a multi-party channel)

// If you are unfamiliar with new concepts used in this sample, refer to the Indigo Basic\GettingStarted sample.

namespace

Microsoft.ServiceModel.Samples

{

// Chat service contract

// Applying [PeerBehavior] attribute on the service contract enables retrieval of PeerNode from IClientChannel.

[ServiceContract(Namespace = "https://Microsoft.ServiceModel.Samples", CallbackContract = typeof(IChat))]

[PeerBehavior]

public interface IChat

{

[OperationContract(IsOneWay = true)]

void Join(string member);

[OperationContract(IsOneWay = true)]

void Chat(string member, string msg);

[OperationContract(IsOneWay = true)]

void Leave(string member);

}

public interface IChatChannel : IChat, IClientChannel

{

}

public class ChatApp : IChat

{

static string password, meshName, memberName;

#region

Command line processing madness

static bool ProcessCommandLineInput(string[] args)

{

NameValueCollection parameters = new NameValueCollection(args.Length);

bool unknownParam = true;

bool appCannotContinue = false;

#region

CLI processing

Regex cmd = new Regex(@"(?<prefix>/)(?<param>\w*)([:,=])*(?<value>\w*)", RegexOptions.IgnoreCase | RegexOptions.Compiled);

// We need to load all of the command line switches up into a collection to check if some

// of the switches are conflicting.

foreach (string arg in args)

{

Match m = cmd.Match(arg);

string param = m.Groups["param"].Value;

string value = m.Groups["value"].Value;

parameters.Add(param, value);

}

for (int i = 0; i < parameters.Count; i++)

{

string param = parameters.Keys[i];

string value = parameters[param];

// We assume we know the param unless otherwise discovered.

unknownParam = false;

switch (param.ToLower())

{

case "generatepassword":

// Check if a password was provided in the command line.

if (null != parameters.Get("password"))

{

Console.WriteLine("Conflicting switches! Cannot autogenerate password when a password is specified. The user specified password will be used.");

}

else

{

password = CredManUtils.GetRandomPassword();

Console.WriteLine("The randomly generated password is '{0}'", password);

}

break;

case "password":

password = value;

bool strongPassword = CredManUtils.IsPasswordStrong(password);

Console.WriteLine("Validating user provided password.... The password is deemed: {0}", (strongPassword ? "Strong" : "Weak"));

if (!strongPassword)

{

Console.WriteLine("Cannot continue with an insecure password. Please re-try with a strong password. Pattern: blah blah blah");

appCannotContinue = true;

}

break;

case "storepassword":

break;

case "readpassword":

break;

case "meshname":

meshName = value;

Console.WriteLine("The meshname we want to join now is {0}", meshName);

break;

default:

unknownParam = true;

#region

if DEBUG

#if

DEBUG

Console.WriteLine("Unknown switch /{0}:{1}", param, value);

#endif

#endregion

break;

}

// No point processing the remainder of switches as we will have to quit.

if (appCannotContinue)

{

return false;

}

if (!unknownParam)

{

parameters.Add(param, value);

}

}

// At this time we have either a user given password, or a system generated password

// and if the storepassword switch is provided, then store the pwd in the registry.

if (null != parameters.Get("storepassword"))

{

Console.WriteLine("Saving password '{0}' information for the current mesh to CredMan...", password);

CredManUtils.WriteCred(meshName, password);

Console.WriteLine("Password saved successfully.");

}

if (null != parameters.Get("readpassword"))

{

Console.WriteLine("Reading password from the CredMan");

string storedPassword = CredManUtils.ReadCred(meshName);

Console.WriteLine("Password '{0}' read successfully.", storedPassword);

password = storedPassword;

}

return true;

#endregion

}

#endregion

// Host the chat instance within this EXE console application.

public static void Main(string[] args)

{

// Get the memberId from configuration

memberName = ConfigurationManager.AppSettings["member"];

// Get the memberId from configuration

meshName = ConfigurationManager.AppSettings["meshName"];

#region

Process the command line params and exit if necessary.

// Process the command line switches. Command inputs overrides any config-loaded app settings.

bool success = ProcessCommandLineInput(args);

if (!success)

{

System.Environment.Exit(-1);

}

#endregion

// Construct InstanceContext to handle messages on callback interface.

// An instance of ChatApp is created and passed to the InstanceContext.

InstanceContext site = new InstanceContext(new ChatApp());

// Create the participant with the given endpoint configuration

// Each participant opens a duplex channel to the mesh

// participant is an instance of the chat application that has opened a channel to the mesh

NetPeerTcpBinding binding = new NetPeerTcpBinding("SecureChatBinding");

//create a PeerSecurityBehavior instance to pass PeerChannel-specific credentials.

//for PeerAuthenticationMode.Password, you need to specify a certificate and a password

PeerSecurityBehavior security = new PeerSecurityBehavior();

security.Password = password;

//if the certificate has a different name than the member, override it here.

X509Certificate2 certificate = GetCertificate(StoreName.My, StoreLocation.CurrentUser, "CN="+memberName, X509FindType.FindBySubjectDistinguishedName);

security.SetSelfCertificate(certificate);

using (ChannelFactory<IChatChannel> cf = new ChannelFactory<IChatChannel>("SecureChatEndpoint"))

{

//add the behavior to the channel factory.

cf.Description.Behaviors.Add(security);

using (IChatChannel participant = cf.CreateDuplexChannel(site))

{

// Retrieve the PeerNode associated with the participant and register for online/offline events

// PeerNode represents a node in the mesh. Mesh is the named collection of connected nodes.

PeerNode node = (participant as IClientChannel).Extensions.Find<PeerNode>();

node.Online += new EventHandler(OnOnline);

node.Offline += new EventHandler(OnOffline);

Console.WriteLine("{0} is ready", memberName);

Console.WriteLine("Type chat messages after going Online");

Console.WriteLine("Press q<ENTER> to terminate this instance.");

// Announce self to other participants

participant.Join(memberName);

// loop until the user quits

while (true)

{

string line = Console.ReadLine();

if (line == "q") break;

participant.Chat(memberName, line);

}

// Leave the mesh and close the proxy

participant.Leave(memberName);

}

}

}

// IChat implementation

public void Join(string member)

{

Console.WriteLine("[{0} joined]", member);

}

public void Chat(string member, string msg)

{

Console.WriteLine("[{0}] {1}", member, msg);

}

public void Leave(string member)

{

Console.WriteLine("[{0} left]", member);

}

// PeerNode event handlers

static void OnOnline(object sender, EventArgs e)

{

Console.WriteLine("** Online");

}

static void OnOffline(object sender, EventArgs e)

{

Console.WriteLine("** Offline");

}

static internal X509Certificate2 GetCertificate(StoreName storeName, StoreLocation storeLocation, string key, X509FindType findType)

{

X509Certificate2 result;

X509Store store = new X509Store(storeName, storeLocation);

store.Open(OpenFlags.ReadOnly);

try

{

X509Certificate2Collection matches;

matches = store.Certificates.Find(findType, key, false);

if (matches.Count > 1)

throw new InvalidOperationException(String.Format("More than one certificate with key '{0}' found in the store.", key));

if (matches.Count == 0)

throw new InvalidOperationException(String.Format("No certificates with key '{0}' found in the store.", key));

result = matches[0];

}

finally

{

store.Close();

}

return result;

}

}

}

App.config

<?

xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="https://schemas.microsoft.com/.NetConfiguration/v2.0">
<appSettings>
<!-- use appSetting to configure the Member Id -->
<add key="member" value="peer1" />
</appSettings>
<system.serviceModel>
<client>
<!-- chat instance participating in the mesh -->
<endpoint name="SecureChatEndpoint"address="net.p2p://SecureChatMesh/servicemodelsamples/chat"
binding="netPeerTcpBinding"
bindingConfiguration="SecureChatBinding"
contract="Microsoft.ServiceModel.Samples.IChat">
</endpoint>
</client>

<bindings>
<netPeerTcpBinding>
<binding name="SecureChatBinding" port="5290" resolverType="" />
</netPeerTcpBinding>
</bindings>
</system.serviceModel>
</configuration>

Comments