Working with Certificates – Part III, Embedding the certificate as a resource and providing authorization for custom username and password authentication

April 2nd, 2009 | Tags:

In my previous article, I showed how to implement custom username and password validation for WCF and how to use a certificate to encrypt the communication (including the username and password).

In this article, I will extend that sample and embed the certificate as a resource in the service library as well as create a IPrincipal implementation from the authenticated username and password.

Embedding the certificate in the service library instead of hooking it up in the configuration file is quite easy.

The first thing you need to do is add the certificate to your project and set the Build Action to Embedded Resource.

Then, we’ll implement a behavior extension for WCF, like this:

public class EmbeddedCertificateAttribute : Attribute,
    IServiceBehavior
{
    private string mResourceName;
    private SecureString mCertificatePassword;

    public EmbeddedCertificateAttribute(string resourceName,
        string certificatePassword)
        : base()
    {
        this.mResourceName = resourceName;

        // SecureString is not very secure, but provides better
        // security than no protection at all.
        // For more information, look at
        // http://www.hexadecimal.se/2009/02/14/NotSoProtectedMemory.aspx
        this.mCertificatePassword = new SecureString();
        foreach (char c in certificatePassword)
            mCertificatePassword.AppendChar(c);
        mCertificatePassword.MakeReadOnly();
    }

    public void AddBindingParameters(
        ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase,
        Collection<ServiceEndpoint> endpoints,
        BindingParameterCollection bindingParameters)
    {
        // Read the raw certificate data from an embedded
        // resource
        Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(mResourceName);
        byte[] certData = new byte[stream.Length];
        stream.Read(certData, 0, certData.Length);            

        // Decrypt the password
        IntPtr ptr = Marshal.SecureStringToBSTR(
            mCertificatePassword);
        string password = Marshal.PtrToStringUni(ptr);

        // Load the certificate into a X509 certificate
        // instance
        X509Certificate2 cert = new X509Certificate2(certData,
            password);                

        // Add the certificate to our service credentials
        serviceHostBase.Credentials.ServiceCertificate.Certificate = cert;
    }

    public void ApplyDispatchBehavior(
        ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase)
    {
        // Do nothing
    }

    public void Validate(ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase)
    {
        // Do nothing
    }
}

Then you just add the attribute we just created to your service implementation like this:

[EmbeddedCertificate("Hexadecimal.SampleService.SampleCertificate.p12", "")]
public class SecureService : ISecureService
{
    public string GetData(int value)
    {
        return string.Format("You entered: {0}", value);
    }

    public CompositeType GetDataUsingDataContract(
      CompositeType composite)
    {
        if (composite.BoolValue)
        {
            composite.StringValue += "Suffix";
        }
        return composite;
    }
}

Change the resource name and password for the certificate and that’s it!

Your certificate will now be embedded in the assembly, and extracted by the service extension and dynamically added to your service whenever it’s instantiated.

Check out my previous post Automating WCF headers and safe-guarding against unhandled exceptions in WCF services for more WCF behavior extension examples.

To add authorization to our sample, I’ll first create an IPrincipal implementation to hold the authentication details.

public class SamplePrincipal : IPrincipal
{
    /// <summary>
    /// Holds a typed reference to a SampleIdentity instance
    /// </summary>
    private SampleIdentity mIdentity;

    /// <summary>
    /// Initializes a new SamplePrincipal
    /// </summary>
    /// <param name="identity">Identity associated with the
    /// principal</param>
    public SamplePrincipal(SampleIdentity identity)
        : base()
    {
        this.mIdentity = identity;
    }

    /// <summary>
    /// Gets the Identity of the current principal
    /// </summary>
    public SampleIdentity SampleIdentity
    {
        get { return mIdentity; }
    }

    /// <summary>
    /// Gets the current SampleIdentity, if any
    /// </summary>
    public static SamplePrincipal Current
    {
        get
        {
            if (Thread.CurrentPrincipal is SamplePrincipal)
                return Thread.CurrentPrincipal as
                    SamplePrincipal;
            else
                return null;
        }
    }

    #region IPrincipal Members

    public IIdentity Identity
    {
        get {  return mIdentity; }
    }

    public bool IsInRole(string role)
    {
        return false;
    }

    #endregion
}

public class SampleIdentity : IIdentity
{
    /// <summary>
    /// Holds the name of the identity
    /// </summary>
    private string mName;

    /// <summary>
    /// Initializes a new SampleIdentity
    /// </summary>
    /// <param name="name">Name of the identity</param>
    public SampleIdentity(string name)
    {
        this.mName = name;
    }

    /// <summary>
    /// Gets the current SampleIdentity, if any
    /// </summary>
    public static SampleIdentity Current
    {
        get
        {
            SamplePrincipal principal =
                SamplePrincipal.Current;
            if (principal == null)
                return null;
            return principal.SampleIdentity;
        }
    }

    #region IIdentity Members

    public string AuthenticationType
    {
        get { return typeof(SampleAuthenticator).Name; }
    }

    public bool IsAuthenticated
    {
        get { return true; }
    }

    public string Name
    {
        get { return mName; }
    }

    #endregion
}

And this is where it gets interesting! WCF operates with claim sets, which are as the name implies, a bunch of claims made by either the client or the service. The service will be claiming to have a certificate that the service and client can use to talk privately, and the client will claim to have a valid username and password. They can claim all sorts of things, like being on the local subnet (zone security). In other words, all sorts of meaningful affiliations which make them more trustworthy to one another.

To provide authorization, we need to implement the IAuthorizationPolicy interface, sort through all the claims made by the user and find the one that holds the username used to authenticate the user, using our authenticator class which we implemented in my previous article.

The code is not overly complex, but has a potential to do a lot of complex things. We could for instance operate on more than just the username here, we could also restrict access based on if the user is located on the local subnet or way out on the internet, so the user might be able to do administrative tasks if the user is an administrator, but only if using the client on the subnet, not from the internet.

/// <summary>
/// A custom authorizor implementation that creates a
/// SamplePrincipal instance if the authorization succeeds.
/// </summary>
public class SampleAuthorizor : IAuthorizationPolicy
{
    /// <summary>
    /// Extracts identity claims from an EvaluationContext
    /// </summary>
    /// <param name="evaluationContext">EvaluationContext to
    /// process</param>
    /// <returns>A list of IIdentity claims made by the
    /// client</returns>
    private IList<IIdentity> getIdentities(
        EvaluationContext evaluationContext)
    {
        object obj;
        if (evaluationContext.Properties.TryGetValue(
            "Identities", out obj) && obj != null)
            return obj as IList<IIdentity>;
        return null;
    }

    /// <summary>
    /// Evaluates if a user meets the requirements for this
    /// policy
    /// </summary>
    /// <param name="evaluationContext">
    /// Claim set to evaluate</param>
    /// <param name="state">Custom state</param>
    /// <returns>Returns true if successful, otherwise
    /// false</returns>
    public bool Evaluate(EvaluationContext evaluationContext,
        ref object state)
    {
        // Extract identity claims made by client
        var identities = getIdentities(evaluationContext);

        // Fault if no identity claims were made by the client
        if (identities == null)
            throw new SecurityTokenException(
                "No identity claims were made.");

        // Iterate the identity claims made by client
        foreach (var identity in identities)
        {
            // If this claim was authenticated using our
            // sample authenticator...
            if (identity.AuthenticationType ==
                typeof(SampleAuthenticator).Name)
            {
                // Then create a new SamplePrincipal instance
                // holding the authentication data.
                SamplePrincipal principal =
                    new SamplePrincipal(
                    new SampleIdentity(identity.Name));

                // Associate it with the context
                evaluationContext.Properties["Principal"] =
                  principal;

                // And return success
                return true;
            }
        }
        // Failure, unable to authorize the user
        return false;
    }

    /// <summary>
    /// Get a claim set representing the issuer of the
    /// authorization policy
    /// </summary>
    public ClaimSet Issuer
    {
        get
        {
            // System specific, as opposed to Windows based
            return ClaimSet.System;
        }
    }

    /// <summary>
    /// Gets the Id of this authorization component
    /// </summary>
    public string Id
    {
        get
        {
            return "SampleAuthorizor";
        }
    }
}

That’s it!

We can now go ahead use declarative security with PrincipalPermission and all that nifty out-of-the-box functionality we have in .NET.

Let’s try it out by modifying the service implementation.

public string GetData(int value)
{
    // Return current username to the caller
    string currentUserName;

    SampleIdentity identity = SampleIdentity.Current;
    if (identity != null)
        currentUserName = identity.Name;
    else
        currentUserName = "You are not logged in.";

    return string.Format("User name: {0}", currentUserName);
}

// Require that the user be in the role Manager to call
// this method.
[PrincipalPermission(SecurityAction.Demand, Role="Manager")]
public CompositeType GetDataUsingDataContract(
    CompositeType composite)
{
    if (composite.BoolValue)
    {
        composite.StringValue += "Suffix";
    }
    return composite;
}

And make a few calls from the client:

// Make a service call
Console.WriteLine(proxy.GetData(15));

try
{
    // This will cause an exception, since we're not in
    // the 'Manager' role.
    proxy.GetDataUsingDataContract(new CompositeType());
}
catch (Exception error)
{
    Console.WriteLine("The service call faulted with the " +
      "following error: {0}", error.Message);
}

Running the sample now will yield this output:

(click the image for a larger view)

consoleoutput

Technorati Tags: ,,,,
No comments yet.