Wednesday, May 14, 2008

Improved IisRegMgmt

Thanks to CarlosAg from the IIS team, I was able to improve my tool for registering IIS 7 management modules.

IisRegMgmt01.zip


IIS
Wednesday, May 14, 2008 6:34:10 AM UTC  #   

P2P and WCF: The PeerName Tool

To play around with peer name registration and resolution, I wrote a little tool that makes this easy (yes - I know all this functionality is also available via netsh - but I wanted something more specialized).

Registering

Resolving

PeerName.zip (27.31 KB)

 


WCF
Wednesday, May 14, 2008 6:03:31 AM UTC  #   
 Monday, May 12, 2008

Using IdentityModel: Tracing

While reading through some of the code of System.IdentityModel, I noticed that there is some diagnostics tracing going on. Just add a trace listener for the source 'System.IdentityModel' to your config file.

HTH


IdentityModel | WCF
Monday, May 12, 2008 5:28:09 PM UTC  #   
 Friday, May 02, 2008

P2P and WCF: Some Resources

If you want to know more about P2P and its related protocols and components, here's a list of online resource I found useful while researching:

Have fun!


For Your Favourites | WCF
Friday, May 02, 2008 4:09:01 AM UTC  #   
 Thursday, May 01, 2008

P2P and WCF: Exposing a Service

The last post explained how to find a PNRP registered service. What else do you have to do for e.g. exposing a WCF service over the P2P infrastructure?

Code-wise nothing. If the WCF service listens on all NICs (the default), a client can do a resolution via the peer DNS name and connect to it. Easy.

Well - hold on - does that mean that arbitrary clients can now traverse my NAT and connect to my intranet machine? Kind of - yes...

For the service to be accessible you also have to adjust firewall rules:

  • the port the service is listening on must be openend (this will allow normal TCP/IP traffic to the endpoint)
  • to allow Teredo traffic to the service, additionally the "allow edge traversal" option must be checked. This option is only available via the advanced firewall (available via Administrative Tools or MMC). See screenshot:

So to recap - these are the prereqs for a globally reachable service:

  • P2P (PNRP and Teredo) must work properly
  • the service must be registered
  • the client (or peer) must know the peer name
  • the endpoint port must be opened in the firewall
  • Teredo traffic must be allowed for this port

But one thing is very true, you now allow (internet) inbound traffic to an intranet hosted service, which has some implications:

  • there is probably no security around that intranet machine (like a DMZ).
  • intranet machines are typically not hardened for exposing internet services.
  • this means that if the service has some security problem (e.g. directory traversal etc), there are no safe-nets that will stop an attacker e.g. accessing other machines or system resources.
  • your administrators may not like this!

Typical P2P scenarios don't necessarily involve publicly known peer names, so you maybe only have a limited exposure. But still - the traffic bypasses perimeter security and goes directly into the intranet. So be careful.

 


WCF
Thursday, May 01, 2008 9:26:52 AM UTC  #   

P2P and WCF: Finding a Service

After you have registered a service, the next step is to find it again. The System.Net.PeerToPeer API includes a PeerNameResolver class which does that.

You input the peer name and get back the registration details (IP addresses, port, comment and data):

static void Resolve(string name)
{
    PeerNameResolver resolver = new PeerNameResolver();
    PeerName peerName = new PeerName(name);

    Console.WriteLine("Resolving {0}...", peerName);
    Console.WriteLine();

    PeerNameRecordCollection results = resolver.Resolve(peerName);

    if (results.Count == 0)
    {
        Console.WriteLine("No records found.");
        return;
    }

    int count = 1;
    foreach (PeerNameRecord record in results)
    {
        Console.WriteLine("Record #{0}\n", count);
        Console.WriteLine("DNS Name: {0}", record.PeerName.PeerHostName);

        Console.WriteLine("Endpoints:");
        foreach (IPEndPoint endpoint in record.EndPointCollection)
        {
            Console.WriteLine("\t Endpoint:{0}", endpoint);
        }

        count++;
    }
}

The way you will resolve peer names more commonly is via the DNS format (the PeerHostName property in the above code). A peer name also has DNS name representation (e.g. foo.pnrp.net for an unsecured service named foo). Whenever you use this format (e.g. with ping or any other application that does DNS name resolution), Windows will use the P2P APIs internally to return the corresponding IP address). See here for the details.

This e.g. means that you could make a registration on a web server for port 80 and can use the browser to directly connect to the web server using the DNS format name.


WCF
Thursday, May 01, 2008 6:43:10 AM UTC  #   

P2P and WCF: Registering a Service

To make a service discoverable using the P2P infrastructure, you first have to do a so called peer name registration.

A peer name registration has the following properties:

  • a name (there are two different flavours: secured and unsecured - more on that later)
  • one or more IP addresses and scope
  • a port number
  • a comment (optional)
  • up to 4KB of binary data (optional)

Peer Name
The name of the service you want to register. Names have the following format: 'authorityId.Name'. Unsecured Names use a '0' as the authorityId and are easy to spoof/squat. When using a secured name, a key/pair is generated on the fly (the first time only) to sign the registration request. The public key hash becomes the authorityId in this case.

IP addresses and scope
That's the most fascinating (and complicated) part. The peer name registration can have local and/or a global scope. A global scope means that the service can be discovered and contacted - well - globally. How can that work, given the service is behind a NAT device? IPv6 is the answer.

Now you may ask yourself: "but my network/router hardware is not IPv6 enabled, can this still work?". Yes it does - making the transition between IPv4 and IPv6 is the job of so called transition or tunneling protocols. Teredo is the name of the protocol that is typically used here. Teredo has several jobs - one is to provide a globally unique IPv6 address, the other is to enable NAT traversal. I won't go into the Teredo details here, but this document describes how it works.

When you do a 'ipconfig' on the command line you may already see a bunch of IPv6 addresses. The one that is directly associated with your NIC is the local address. You may also see a "Tunnel Adapter" interface - that would be the global Teredo provided address.

You can check the status/health of the Teredo protocol by using this command: 'netsh int teredo show state'. This article helps you with troubleshooting if Teredo should not be enabled on your machine.

You can also have a look at the scope of your registration by checking the clouds to which your machine has access. This is done by doing a 'netsh p2p pnrp cloud show list'. You should see one or more LinkLocal_ clouds and a Global_ cloud.

Again this article has all the details on clouds and their background.

So to wrap it up - by default a peer name registration will use all available NICs/IP addresses. If you have a global IPv6 address (which means that Teredo is working properly and you can 'see' the global cloud) this one is used also. This in turn means that the service can be used by every client that also has a global address.

The remaining properties are self explaining I think.

The following code snippet would register a secured peer name in all available clouds (you can find the APIs in the System.Net assembly (v3.5):

private void Register(string name, int port, string comment)
{
    PeerName peerName = new PeerName(name, PeerNameType.Secured);
    PeerNameRegistration reg = new PeerNameRegistration();
   
    reg.PeerName = peerName;
    reg.Port = port;
    reg.Cloud = Cloud.Available;
    reg.Comment = _cl.Comment;

    reg.Start();
}

The next posts will deal with peer name resolution and how to host a WCF service over this infrastructure.


WCF
Thursday, May 01, 2008 5:42:58 AM UTC  #   
 Wednesday, April 30, 2008

P2P, PNRP, Teredo...the Motivation

Since I started playing around with computers, communication of machines over a "wire" has been fascinating to me. This is probably why I ended up in the distributed applications space.

Typically clients talk to servers and servers to servers - but less common clients directly to clients. But applications like MSN Messenger or Skype show useful use cases of client to client communication. With Vista and Server 2008 (and XP SP2 + some components) peer to peer networking has become part of the operating system. There is also a peer channel in WCF that sits on top of these core components. Time to have a closer look.

So what features is the P2P infrastructure supposed to give you?

  • Global/local registration of services
  • Global/local connectivity between peers which includes the capability to traverse NAT devices
  • Global/local virtual broadcasting networks
  • Peer/Service Discovery
  • Invitation/activation of P2P enabled applications

All of these capabilities are as fascinating as they are scary. More importantly I really think that P2P communication patterns will be a "big" thing and will also change the way we have to think about network/perimeter security.

In the next posts I will write about some of my findings. Stay tuned.


WCF
Wednesday, April 30, 2008 6:18:56 AM UTC  #   
 Monday, April 28, 2008

Using IdentityModel: Converting ADFS Security Properties to Claims

This little helper might be useful when you are working with ADFS, but want to use the IdentityModel types in your app:

public static ClaimSet ToClaimSet(this SingleSignOnIdentity identity)
{
    List<Claim> claims = new List<Claim>();

    claims.Add(new Claim(identity.NameType, identity.Name, Rights.Identity));

    foreach (SecurityProperty property in identity.SecurityPropertyCollection)
    {
        string claimType = property.Uri;
        if (claimType.EndsWith("NameValue"))
        {
            claimType = property.Name;
        }

        claims.Add(new Claim(claimType, property.Value, Rights.PossessProperty));
    }

    return new DefaultClaimSet(ClaimSet.System, claims);
}


IdentityModel
Monday, April 28, 2008 5:39:21 AM UTC  #   
 Tuesday, April 22, 2008

Ein Session Abstract ganz genau nach meinem Geschmack

Gesehen auf der JAX2008 Webseite:

Security Last – Sicherheitsentscheidungen spät treffen
Sicherheitsanforderungen wie Logins und Berechtigung sind wichtig – aber müssen diese wirklich gleich am Anfang umgesetzt werden? Das nachträgliche Hinzufügen dieser Anforderungen mit reinem Java und OOP ist sehr schwierig, weshalb dies meistens mit „Ja“ beantwortet wird. Erfahren Sie hier, wie mithilfe von Tools wie Spring Security, AspectJ und CAS auch spät in Anwendungen integriert werden kann.

Ohne Worte...

 


Microsoft Deutschland Security Portal
Tuesday, April 22, 2008 11:49:20 AM UTC  #   
 Sunday, April 20, 2008

Token Kidnapping

Interesting...and shocking.

Read more here:
http://www.argeniss.com/research/TokenKidnapping.pdf


For Your Favourites
Sunday, April 20, 2008 7:15:28 AM UTC  #   

Installing an IIS 7 Extension

Related to cleaning up my authentication module for Codeplex, I needed a way to (semi) automatically install a complete IIS extension (including schema, config sections and management extensions). I came up with a batch file that does the necessary steps (anybody out there that wants to write a real installer?).

1. Register all assemblies in the GAC
Usually an IIS 7 extension consists of at least three assemblies (module/handler, server extensions, client extensions). Gacutil.exe is your friend here (use the /if option). Also take into account, that IIS loads GACed assemblies domain neutral. That means that you have to recycle the worker process when you update e.g. your GACed module.

2. Register schema and config section
This involves copying your schema to the IIS' schema directory and add a <configSection> registration to applicationHost.config. Mike Volodarsky from the IIS team has written a nice tool call IisSchema that automates this step.

3. Registering the management extension
This involves adding the module to administration.config (in two different places). I haven't found an automated way of doing this, so I wrote a little tool to accomplish this task.

IisRegMgmt [install/uninstall] [assembly_to_register]

This will find all Microsoft.Web.Management.Server.ConfigurationModuleProvider derived classes in the specified assembly and register them in administration.config.

The code to do the registration is as follows:

private static void RegisterAdministration(string name, string type, string assembly)
{
    // get access to administration.config
    Configuration administration = _manager.GetAdministrationConfiguration();

    // get access to <moduleProviders>
    ConfigurationSection moduleProvidersSection =
        administration.GetSection("moduleProviders");
    ConfigurationElementCollection moduleProviders =
        moduleProvidersSection.GetCollection();

    // check for existing elements first
    Clean(name, moduleProviders);

    // create new element
    ConfigurationElement newModuleProvider =
        moduleProviders.CreateElement();

    // set attributes
    newModuleProvider.SetAttributeValue("name", name);
    newModuleProvider.SetAttributeValue("type", type + ", " + assembly);

    // add element
    moduleProviders.Add(newModuleProvider);


    // get access to <modules>
    ConfigurationSection modulesSection =
        administration.GetSection("modules");
    ConfigurationElementCollection modules = modulesSection.GetCollection();

    // check for existing element first
    Clean(name, modules);

    // create new element
    ConfigurationElement newModule = modules.CreateElement();

    // set attributes
    newModule.SetAttributeValue("name", name);

    // add element
    modules.Add(newModule);

    // save changes
    _manager.CommitChanges();
}

Don't forget to clean up the sections before you add the new module, otherwise you might end up with double entries:

private static void Clean(string name, ConfigurationElementCollection elements)
{
    var hits = from e in elements
               where (string)e.GetAttributeValue("name") == name
               select e;

    hits.ToList().ForEach(e => elements.Remove(e));
}

For completeness sake, here's how you can find the right management classes in the assembly (and because I was amused about 'Linq to Reflection' ;)

private static IEnumerable<string> GetManagementTypes(Assembly a)
{
    string baseType = "Microsoft.Web.Management.Server.ConfigurationModuleProvider";
   
    var types = from t in a.GetExportedTypes()
                where t.BaseType.FullName == baseType
                select t.FullName;

    return types;
}

 

IisRegMgmt01.zip (18.66 KB)

 


IIS
Sunday, April 20, 2008 6:50:57 AM UTC  #   
 Saturday, April 19, 2008

Custom Basic Authentication is now on Codeplex

I also moved my "Basic Authentication against non-Windows accounts for IIS" (phew) project to Codeplex

http://www.codeplex.com/CustomBasicAuth

Again, if you have feature ideas or want to contribute, contact me.

 


ASP.NET | IIS | WCF
Saturday, April 19, 2008 7:44:05 AM UTC  #   
 Wednesday, April 16, 2008

InfoCardSelector is now on Codeplex

I finally found a new home for my ASP.NET InfoCard control:

http://www.codeplex.com/InfoCardSelector

If you feel like contributing or suggesting new features, you can contact me via this page.


ASP.NET | CardSpace | IdentityModel | WCF
Wednesday, April 16, 2008 4:02:36 PM UTC  #   
 Saturday, April 12, 2008

Developing ASP.NET Applications in Medium Trust

Alex Smolen did a good write up of the various approaches for sandboxing ASP.NET applications. Recommended!


ASP.NET
Saturday, April 12, 2008 5:56:02 AM UTC  #   
 Thursday, April 03, 2008

Beware of Whitespaces in WAS Configuration

This took me a while to solve...

In the WAS configuration dialog there must be NO whitespaces in the enabled protocols configuration. Otherwise you will get this error:

"Could not find a base address that matches scheme net.tcp for the endpoint with binding NetTcpBinding."

 

HTH

 


IIS | WCF | Work in Progress
Thursday, April 03, 2008 8:04:24 AM UTC  #   
 Sunday, March 30, 2008

Using Information Cards in ASMX Web Services

As I wrote here - an Information Card token is just a string. This means that (with the help of some extra plumbing) you can seamlessly integrate cards into "legacy" technologies. Here's a sample walkthrough for ASMX web services.

To transmit the token to the service, I will use a SOAP header. So the first step is to define the header:

[XmlRoot(ElementName = "InformationCard",
    Namespace = "http://schemas.xmlsoap.org/ws/2005/05/identity")]
public class InfoCardTokenHeader : SoapHeader
{
    public string Token;
}

For metadata support, we can now annotate a web method with this header information:

[WebService(Namespace = "urn:leastprivilege")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class AsmxService : WebService
{
    public InfoCardTokenHeader InfoCardToken;

    [WebMethod]
    [SoapHeader("InfoCardToken", Direction = SoapHeaderDirection.In)]
    public string Ping()
    {
      
    }
}

The client can now use the CardSpaceSelector API (or my wrapper) to get a token manually. Afterwards the token gets transmitted using the header:

static void Main(string[] args)
{
    AsmxService proxy = new AsmxService();

    InfoCardTokenHeader token = new InfoCardTokenHeader();
    token.Token = GetInfoCardToken(proxy.Url);
    proxy.InformationCard = token;

    Console.WriteLine(proxy.Ping());
}

static string GetInfoCardToken(string targetUri)
{
    IdentitySelector selector = new IdentitySelector();
    selector.TargetUri = new Uri(targetUri);
    selector.SetTargetCertificate(targetUri);

    selector.RequiredClaims.Add(ClaimTypes.GivenName);
    selector.RequiredClaims.Add(ClaimTypes.Surname);
    selector.RequiredClaims.Add(ClaimTypes.Email);

    GenericXmlSecurityToken token = selector.GetToken();
    return token.TokenXml.OuterXml;
}

On the server side you could now retrieve the token from the header and use your favourite token decryption class to extract the claims. If you want to put in a little bit more work, you can improve the integration of that information using a SoapExtension.

The extension will check the incoming headers, extract the token and set Thread.CurrentPrincipal and Context.User to an instance of IdentityPrincipal that wraps the token generated authorization context. A corresponding extension attribute connects this logic with the web method:

[WebMethod]
[InfoCardSoapExtension(TokenRequired = true)]   
[SoapHeader("InfoCardToken", Direction = SoapHeaderDirection.In)]
public string Ping()
{
    return IdentityPrincipal.Current.ClaimSets.FindClaim(
        ClaimTypes.GivenName).Get<string>();
}

This gives the web service method seamless access to incoming claims.

 

The code for the SOAP extension is quite simple (the configuration code is omitted):

public class InfoCardSoapExtension : SoapExtension
{
  public override void ProcessMessage(SoapMessage message)
  {
    if (message.Stage == SoapMessageStage.AfterDeserialize)
    {
      foreach (SoapHeader header in message.Headers)
      {
        InfoCardTokenHeader tokenHeader =
          header as InfoCardTokenHeader;

        if (tokenHeader != null)
        {
 
        IdentityPrincipal principal;

          try
          {
            var token = new Token(tokenHeader.Token, true);
            principal = new IdentityPrincipal(token.AuthorizationContext);
          }
          catch
          {
            throw new HttpException(500, "Token validation failed");
          }

          HttpContext.Current.User = Thread.CurrentPrincipal = principal;
          return;
        }
      }

      if (_tokenRequired)
      {
        throw new HttpException(401, "Authentication required");
      }
    }
  }
}

Disclaimer: I know that this code could be written far more generic. Consider this as a proof of concept.

 

The LeastPrivilege.IdentityModel download contains the complete sample. Have fun.


ASP.NET | CardSpace | IdentityModel
Sunday, March 30, 2008 8:49:07 AM UTC  #   
 Saturday, March 22, 2008

Using IdentityModel: Some Samples

Here are some typical usage scenario of IdentityPrincipal in ASP.NET.

Simple IsInRole calls (checks for a status claim with a value of 'Gold'):

HttpContext.Current.User.IsInRole("Gold");

 

Retrieving the OrderHistory claim:

IdentityPrincipal ip = IdentityPrincipal.Current;
Claim orderHistory = ip.ClaimSets.FindClaim(
  Constants.OrderHistoryClaimType,
  Constants.ApplicationIssuerIdentityClaim);

var orders = orderHistory.Get<List<OrderDetail>>();

 

..or some authorization code from my CardSpace sample app - thanks to the unified authorization model, I can share this method across ASP.NET, ASMX and WCF:

public static IEnumerable<MessageBoard> GetBoardsForUser(
  AuthorizationContext context, bool includePublic)
{
    List<MessageBoard> boards = new List<MessageBoard>();

    foreach (Claim typeClaim in context.ClaimSets.FindClaims(
      AppClaims.UserTypeClaim,
      new ApplicationIssuerClaimSet()))
    {
        string type = typeClaim.Get<string>();
        if ("Public".Equals(type) && includePublic == false) continue;

        boards.AddRange(GetBoards(type));
    }

    return boards;
}

I have updated the source download here to include a console, WCF and ASP.NET test app that share the same authorization model.


ASP.NET | CardSpace | IdentityModel | WCF
Saturday, March 22, 2008 1:54:39 PM UTC  #   
 Friday, March 21, 2008

LINQ to SQL and Security

I don't know how many SQL injection demos I did in my life - and it is still surprising (or shocking rather) how many people don't know about this. It is even more surprising how many people fight for their string concat ad hoc queries and come up with all kind of dodgy excuses why it makes sense to use them.

What is wrong with SQL parameters? ;)

Recently I played around with LINQ to SQL and think it is a compelling (and time saving) way to do database interactions. The thing I really like about LINQ to SQL is that it does the right thing by default (security wise) when it comes to parameters.

Consider this query:

var products = from p in context.products
               where p.description.StartsWith(_txtSearch.Text)
               select new
               {
                   p.description,
                   p.price,
                   p.stock
               };

This gets turned into the following SQL (for a search value of 'sony'):

exec sp_executesql N'SELECT [t0].[description], [t0].[price], [t0].[stock]
FROM [dbo].[products] AS [t0]
WHERE [t0].[description] LIKE @p0',N'@p0 varchar(5)',@p0='sony%'

A (malicious) search value with an appended or 1=1 where clause would look like this:

exec sp_executesql N'SELECT [t0].[description], [t0].[price], [t0].[stock]
FROM [dbo].[products] AS [t0]
WHERE [t0].[description] LIKE @p0',N'@p0 varchar(16)',@p0='sony'' or 1=1 --%'

Which will not yield any results.

So if you really like ad hoc SQL - but for some reason are too lazy to work with parameters, LINQ is a compelling alternative (again purely from a security view).

Usually the databases I design (and how I recommend it to my customers), don't allow direct table access. All access is done via stored procedures. This is where LINQ to SQL comes in really handy for me - basically as a nice Sproc to C# code generator. Calling a GetData stored procedure and subsequent binding to a grid looks like this:

protected void _btnSearch_Click(object sender, EventArgs e)
{
    using (ComputerstoreDataContext context = new ComputerstoreDataContext())
    {
        var products = context.GetProducts(_txtSearch.Text);

        _gridProducts.DataSource = products;
        _gridProducts.DataBind();
    }
}

Which will result in this SQL:

declare @p4 int
set @p4=0
exec sp_executesql N'EXEC @RETURN_VALUE = [dbo].[GetProducts] @Search = @p0',N'@p0 nvarchar(4),@RETURN_VALUE int output',@p0=N'sony',@RETURN_VALUE=@p4 output
select @p4

This works for me.

Disclaimer: There's quite some controversy about LINQ to SQL and I am not a database expert enough to say if LINQ is good or bad. They way I use it (for Sprocs only) it seems to be fine. Again from a security point of view it does the right thing by default. And that's what I mostly care about ;)


Work in Progress
Friday, March 21, 2008 1:16:11 PM UTC  #   
 Thursday, March 20, 2008

Using IdentityModel: Adding ASP.NET Support Part 2 (Claims Manager)

The last step for integrating claims into ASP.NET is to write a module that loads authorization policies, creates an AuthorizationContext and persists that on Context.User/Thread.CurrentPrincipal.

My module has this simple configuration section:

<claimsManager enabled="true"
               addAuthenticationClaims="true"
               roleClaimType="urn:leastprivilege/claims/customers/status">
  <authorizationPolicies>
    <policy type="LeastPrivilege.CustomerIdAuthorizationPolicy, App_Code" />
    <policy type="LeastPrivilege.CustomerAuthorizationPolicy, App_Code" />
  </authorizationPolicies>
</claimsManager>

If addAuthenticationClaims is set to true, the policy that transforms authentication details to claims (see my last post) will be loaded before all the external policies. The roleClaimType attribute specifies the claim type that should be used for the IsInRole implementation of IdentityPrincipal. The authorizationPolicies collection specifies the claims transformation policies that should run.

The module itself subscribes to PostAuthenticateRequest, loads the policies and populates Context.User/Thread.CurrentPrincipal with the IdentityPrincipal (which in turn wraps the AuthorizationContext).

public class ClaimsManagerModule : IHttpModule
{
    public void Dispose()
    { }

    public void Init(HttpApplication context)
    {
        context.PostAuthenticateRequest += OnPostAuthenticateRequest;
    }

    private void OnPostAuthenticateRequest(object sender, EventArgs e)
    {
        // this code makes only sense when the user is authenticated
        if (!(HttpContext.Current.Request.IsAuthenticated))
        {
            return;
        }

        // make sure the module is enabled
        if (!Configuration.Enabled)
        {
            return;
        }

        HttpApplication app = sender as HttpApplication;
        HttpContext context = app.Context;

        IList<IAuthorizationPolicy> policies = ClaimsManagerModule.Policies;

        if (policies.Count != 0)
        {
            AuthorizationContext authContext =
              CreateAuthorizationContext(policies);

            IdentityPrincipal ip = new IdentityPrincipal(
              context.User.Identity, authContext);

            context.User = Thread.CurrentPrincipal = ip;
        }
    }

    private static AuthorizationContext CreateAuthorizationContext(
      IList<IAuthorizationPolicy> policies)
    {
        AuthorizationContext authContext =
            AuthorizationContext.CreateDefaultAuthorizationContext(policies);

        return authContext;
    }

    public static IList<IAuthorizationPolicy> Policies
    {
        get
        {
            List<IAuthorizationPolicy> policies = new
              List<IAuthorizationPolicy>();

            if (Configuration.AddAuthenticationClaims)
            {
                policies.Add(new AspNetAuthenticationPolicy());
            }

            foreach (PolicyType policy in
              Configuration.AuthorizationPolicies)
            {
                policies.Add(CreatePolicy(policy.Type));
            }

            return policies;
        }
    }

    private static IAuthorizationPolicy CreatePolicy(string typename)
    {
        Type type = Type.GetType(typename, true);

        if (!typeof(IAuthorizationPolicy).IsAssignableFrom(type))
        {
            throw new ConfigurationErrorsException(
              "Policy does not implement IAuthorizationPolicy");
        }

        return (IAuthorizationPolicy)Activator.CreateInstance(type);

    }

    public static ClaimsManagerSection Configuration
    {
        get
        {
            return (ClaimsManagerSection)
              WebConfigurationManager.GetSection("claimsManager");
        }
    }
}

 

Don't consider this as production ready code. There are things missing like error handling and caching. But this should get you started. With the next post I will show some usage scenarios and will also update the source download.

HTH


ASP.NET | IdentityModel
Thursday, March 20, 2008 6:54:32 AM UTC  #   
 Wednesday, March 19, 2008

Using IdentityModel: Adding ASP.NET Support Part 1 (Authentication based Claims)

Adding claims support to ASP.NET is a perfect candidate for an HTTP module. As a reminiscence to RoleManager, I called mine ClaimsManager. The job of the claims manager is this:

  • Creating claims based on the technical authentication details (Windows, Forms, client certificates etc.)
  • Invoking external claims transformation policies which then build the app specific claims based on the technical ones
  • Making the AuthorizationContext available to pages (via Thread.CurrentPrincipal/Context.User - see my previous post)

The first step is to write an IAuthorizationPolicy to map the ASP.NET authentication details to claims. This is done by inspecting Context.User.Identity and client certificates - if you use a custom identity, you would amend that code (check my previous posts about authorization policies):

// policy that adds ASP.NET authentication type specific claims
public class AspNetAuthenticationPolicy : IAuthorizationPolicy
{
    public bool Evaluate(
      EvaluationContext evaluationContext, ref object state)
    {
        HttpContext context = HttpContext.Current;
        List<ClaimSet> claimSets = new List<ClaimSet>();

        // Windows or Forms authentication
        if (context.User.Identity is WindowsIdentity)
        {
            claimSets.Add(
              new WindowsClaimSet(context.User.Identity as WindowsIdentity));
        }
        else if (context.User.Identity is FormsIdentity ||
                 context.User.Identity is GenericIdentity)
        {
            claimSets.Add(new UserNameClaimSet(context.User.Identity.Name))
        }

        // client certificate
        if (context.Request.ClientCertificate.IsPresent)
        {
            X509Certificate2 certificate = new X509Certificate2(
                context.Request.ClientCertificate.Certificate);

            claimSets.Add(new X509CertificateClaimSet(certificate));
        }

        claimSets.ForEach(set => evaluationContext.AddClaimSet(this, set));
        return true;
    }

    public System.IdentityModel.Claims.ClaimSet Issuer
    {
        get { return ClaimSet.System; }
    }

    public string Id
    {
        get
        {
          return
            "LeastPrivilege.IdentityModel.Web.AspNetAuthorizationPolicy";
        }  
    }
}

 

In the next post I will show the HTTP module, how it calls the above policy and how it chains in external policies.


ASP.NET | IdentityModel
Wednesday, March 19, 2008 7:23:49 AM UTC  #   

Using IdentityModel: IdentityPrincipal

Since V1 of .NET there is a "slot" to store authorization information about the current user: Thread.CurrentPrincipal. This data gets propagated to newly created threads and is deeply integrated into other application frameworks like ASP.NET.

To integrate claims into ASP.NET it makes sense to re-use this infrastructure.

To accomplish this the AuthorizationContext has to be wrapped by an IPrincipal implementation. I called mine IdentityPrincipal (which is part of LeastPrivilege.IdentityModel). I won't show you the source code here because this is not very exciting - but there are some things to note:

  • You can either new up the class by supplying a concrete AuthorizationContext or a list of claim sets.
  • If none of the above are specified, the class will try to use ServiceSecurityContext (for WCF support).
  • You can pass in one of the built-in IIdentity implementations (like WindowsIdentity, FormsIdentity etc). If you don't, the identity will be anoynmous (and it is assumed that you don't care about it).
  • I also created a ClaimsIdentity which simply wraps a claim and uses the resource as the Name property.
  • It features a static IdentityPrincipal.Current property which grabs the IPrincipal from Thread.CurrentPrincipal and tries to cast it to IdentityPrincipal.
  • The IsInRole implementation searches for a configurable claim type in the claim sets. Then the resource gets checked against the supplied "role name". This gives you compatibility for existing role based authorization code like ASP.NET UrlAuthorization (for simple scenarios).

The typical pattern would be:

  • Create an AuthorizationContext (or just one or more claim sets).
  • New up an IdentityPrincipal and put it on Thread.CurrentPrincipal (or Context.User for ASP.NET)
  • Call Thread.CurrentPrincipal.IsInRole (or Context.User.IsInRole) for simple checks
  • Call IdentityPrincipal.Current to get access to the claim sets and use the previously described extension methods for more advanced claims checking.

I updated the source download here to include the IdentityPrincipal as well as a console and WCF test app.

Have fun.


ASP.NET | IdentityModel | WCF
Wednesday, March 19, 2008 12:22:10 AM UTC  #   
 Tuesday, March 18, 2008

General Failure when pinging the local Machine Name

I have this strange problem that when I ping my local machine name, I get a General Failure. Forcing the ping to IPv4 with the /4 option works. I could live with that, but another side effect is that opening a http connection to my local IIS takes around 5 seconds to resolve the name. This is not acceptable.

It is even more strange that this problems stops as soon as I am on cabled network.

So this has something to do with IPv6 and Teredo and due to lack of time for finding the real reason, disabling Teredo worked for me.

netsh int teredo set state disabled

HTH


Misc
Tuesday, March 18, 2008 7:46:15 PM UTC  #   

Using IdentityModel: Adding Claims Support to ASP.NET (Spoiler)

Many people asked me how to use claims based authorization in ASP.NET. While I have it working here on my machine (hey - it works on my machine!), I still need to polish the bits before I can release them. For those who want to get their hands dirty immediately - here's what you'd have to do:

  • Write an IPrincipal implementation that wraps an AuthorizationContext
  • Write an HTTP module that creates the AuthorizationContext, wraps it in the IPrincipal and puts it on Thread.CurrentPrincipal and Context.User.
  • If you need compatibility with the existing URL authorization infrastructure, define a role claim type that should be checked by IPrincipal.IsInRole
  • For more functionality, pages would grab the principal from Context.User and retrieve the AuthorizationContext. From there you can get to the claim sets to use the previously presented APIs to do authorization.

There are some additional nice to have features to implement like a permission and a corresponding attribute as well as a configurable claim type for IsInRole.

Bear with me.


ASP.NET | IdentityModel
Tuesday, March 18, 2008 7:23:35 AM UTC  #   
 Sunday, March 16, 2008

Troopers08

My good friends at ERNW are organizing a fantastic security conference to take place in Munich in April. There are two tracks appropriately named "attack" and "defense" and interesting speakers like Dan Bernstein, Andrew Cushman, Enno Rey, Michael Thumann and more guarantee for high profile content. Check it out!

http://www.troopers08.org


Conferences | For Your Favourites
Sunday, March 16, 2008 8:56:42 AM UTC  #   
 Tuesday, March 11, 2008

STS? Available!

In my Post STS? Coming Soon! I linked to information about the upcoming framework for writing STSes (and more) from Microsoft. Unfortunately this is not yet available.

Along with Barry and David I am very happy to announce SharpSTS - a C# library for developing Information Card security token services.

You can find the source code on Codeplex and a demo website here. Don't expect a "click setup.exe and everything is fine" installation experience. There is still a lot of work to do. But you can get the complete source code along with installation instructions to play around. If you want to contribute or have feature ideas, leave me a comment.

Have fun!

 


CardSpace | IdentityModel | WCF
Tuesday, March 11, 2008 6:32:45 AM UTC  #   
 Tuesday, March 04, 2008

Using IdentityModel: Authorization Context and Claims Transformation outside of WCF

Here I showed you how to transform authentication specific claims to general application claims. The same model can be also used outside of WCF. I will use a client application here as an example and save ASP.NET for a later post (as this requires some extra work).

Typically, in client applications you don't need all these layers of indirection and flexibility since you will have (most likely) a single authentication type. But to prove the point that you can reuse your existing policies - here's what you would do:

  • Create a claim set for your "technical" user, e.g. from a username/password dialog, the Windows token or an inserted SmartCard
  • Map the "technical" identifier to an application identifier
  • Create the customer claims

It turns out that the only thing that is different to the WCF scenario is the initial "technical" claim set. The rest of the code stays the same.

You can create the authorization context by calling CreateDefaultAuthorizationContext on the AuthorizationContext class (and passing in the three policies described above). From that point on you can use the same authorization code as you would write in WCF.

The context creation code could look like this:

private static AuthorizationContext CreateContext()
{
    WindowsIdentity identity = WindowsIdentity.GetCurrent();

    AuthorizationContext context =
      AuthorizationContext.CreateDefaultAuthorizationContext(
        new List<IAuthorizationPolicy>()
        {
            // claim set based on Windows authentication
            new WindowsUserAuthorizationPolicy(identity),

            // mapping of authentication to application id
            new CustomerIdAuthorizationPolicy(),
           
            // application claim set
            new CustomerAuthorizationPolicy()
        });

    return context;
}

The only new code here is the policy for Windows authentication. This one is very simple:

class WindowsUserAuthorizationPolicy : IAuthorizationPolicy
{
    WindowsIdentity _identity;

    public WindowsUserAuthorizationPolicy(WindowsIdentity identity)
    {
        _identity = identity;
    }

    public bool Evaluate(EvaluationContext evaluationContext, ref object state)
    {
        evaluationContext.AddClaimSet(this, new WindowsClaimSet(_identity));

        return true;
    }

    public System.IdentityModel.Claims.ClaimSet Issuer
    {
        get { return ClaimSet.System; }
    }

    public string Id
    {
        get { return "WindowsUserAuthorizationPolicy"; }
    }
}

Now you have an AuthorizationContext. But there are some small things left to do. How do you make that context available to the rest of your app? How can you propagate this information across across thread switches. This brings us back to an old friend called Thead.CurrentPrincipal. Stay tuned.


IdentityModel
Tuesday, March 04, 2008 9:43:49 AM UTC  #   

Using IdentityModel: Simplifying Calculation of Information Card Unique IDs in WCF

The key to information card backed systems is calculating a stable unique identifier for your users based on the card claims. Typically this is done by hashing the issuer public key (plus some other information like a PPID for managed cards).

This involves the following steps:

  • Find the issuer claim set containing the issuer RSA key
  • Optionally find some other claims (e.g. a PPID)
  • Create a hash from that pieces of information

In LeastPrivilege.IdentityModel I added some helper extension methods to simplify this process. The first one extends the RSACryptoServiceProvider class and allows calculating the hash the from the key (plus optional extra information):

public static class RSAExtensions
{
    public static byte[] GetKeyHash(this RSACryptoServiceProvider rsa)
    {
        return rsa.GetKeyHash(string.Empty);
    }

    public static byte[] GetKeyHash(this RSACryptoServiceProvider rsa, string entropy)
    {
        int entropyLength = Encoding.UTF8.GetByteCount(entropy);
        RSAParameters rsaParams = rsa.ExportParameters(false);
        byte[] shaInput;
        byte[] shaOutput;

        int i = 0;
        shaInput = new byte[rsaParams.Modulus.Length +
          rsaParams.Exponent.Length + entropyLength];
        rsaParams.Modulus.CopyTo(shaInput, i);
        i += rsaParams.Modulus.Length;
        rsaParams.Exponent.CopyTo(shaInput, i);
        i += rsaParams.Exponent.Length;
        i += Encoding.UTF8.GetBytes(entropy, 0, entropy.Length, shaInput, i);

        using (SHA256 sha = SHA256.Create())
        {
            shaOutput = sha.ComputeHash(shaInput);
        }

        return shaOutput;
    }
}

 

I use the same algorithm here as in Microsoft's Token class for ASP.NET. This makes it easy to share the IDs between ASP.NET and other application types.

Another helper extends IEnumerable<ClaimSet> and simplifies retrieving the RSA claim:

public static RSACryptoServiceProvider FindIssuerRsaClaim(
  this IEnumerable<ClaimSet> claimSets)
{
    return claimSets.FindClaim(
        ClaimTypes.Rsa,
        ClaimSearchMode.Issuer).Get<RSACryptoServiceProvider>();
}

 

With these helpers you can retrieve the unique ID by using this code:

AuthorizationContext context =
    ServiceSecurityContext.Current.AuthorizationContext;
byte[] uniqueId = context.ClaimSets.FindIssuerRsaClaim().GetKeyHash();


IdentityModel | WCF
Tuesday, March 04, 2008 12:26:35 AM UTC  #   

Using IdentityModel: Claims Transformation in WCF

In the previous post I talked about claims transformation. Two authorization policies are necessary for the scenario I described. The first one maps the "technical" identity to an application identity and the second one creates application specific claims based on the application identity.

Mapping the identity
The first authorization policy grabs the first identity claim from the evaluation context and passes that to some mapping logic (omitted):

class CustomerIdAuthorizationPolicy : IAuthorizationPolicy
{
    public bool Evaluate(EvaluationContext evaluationContext, ref object state)
    {
        string userId;
        Claim id = evaluationContext.ClaimSets.FindIdentityClaim();

        userId = Map(id);
        evaluationContext.AddClaimSet(this, new CustomerIdClaimSet(userId));
       
        return true;
    }

    public ClaimSet Issuer
    {
        get { return ClaimSet.System; }
    }

    public string Id
    {
        get { return "CustomerIdAuthorizationPolicy"; }
    }
}

The mapping logic then checks the claim type and returns the user id from some data store.

Adding the customer claims
The second policy then checks for the customer id claim and adds the necessary information to the evaluation context:

class CustomerAuthorizationPolicy : IAuthorizationPolicy
{
    public bool Evaluate(EvaluationContext evaluationContext, ref object state)
    {
        string userId = evaluationContext.ClaimSets.FindClaim(
            Constants.CustomerIdClaimType,
            Constants.ApplicationIssuerIdentityClaim).Get<string>();

        evaluationContext.AddClaimSet(this, new CustomerClaimSet(userId));
        return true;
    }

    public ClaimSet Issuer
    {
        get { return ClaimSet.System; }
    }

    public string Id
    {
        get { return "CustomerAuthorizationPolicy"; }
    }
}

 

The last step is to to add the policies to the serviceAuthorization behavior in the right order:

<serviceAuthorization>
  <
authorizationPolicies>
    <
add policyType="LeastPrivilege.CustomerIdAuthorizationPolicy, Service" />
    <
add policyType="LeastPrivilege.CustomerAuthorizationPolicy, Service" />
  </
authorizationPolicies>
</
serviceAuthorization>

 

In the next post I will show you how to accomplish the same thing outside of WCF.


IdentityModel | WCF
Tuesday, March 04, 2008 12:04:18 AM UTC  #   
 Friday, February 29, 2008

Using IdentityModel: Authorization Policies, Context and Claims Transformation

In the previous posts I talked about claims and claim sets - but where do claim sets come from? The answer is easy - from authorization policies ;) OK - let's have a closer look.

The "container" for claim sets is a called the authorization context (defined in System.IdentityModel.Policy).

The authorization context gets created by calling CreateDefaultAuthorizationContext on the AuthorizationContext class. The input for this method is a list of objects that implement the IAuthorizationPolicy interface. This interface defines a method called Evaluate. Inside of Evaluate you create the claim sets you need and then call the AddClaimSet method on the EvaluationContext that gets passed into Evaluate. So much for the facts. How do we bring that all together?

Typically you want to distinguish between two kinds of authorization policies (and thus claim sets) in your system - I like to call them internal and external authorization policies.

An internal authorization policy is very close to the implementation details of the authentication process. WCF e.g. creates them based on the configured client credential type. These policies in turn create claim sets based on the incoming credential (Windows, certificates, usernames, SAML tokens).

External authorization policies do the application specific transformation of claims. They should not rely on any environment specific properties like ServiceSecurityContext (WCF), Context.User (ASP.NET) or Thread.CurrentPrincipal (whatever) but instead use solely the claims created by the internal policies to figure out all necessary information about the entity. This way external authorization policies can be used environment independent and can be shared between services or applications.

Let's do an example. Imagine a WCF service with multiple credential types. Regardless of the authentication type, you want to end up with a customer claim set that holds the necessary authorization information for your service. That could be done like this:

  1. WCF creates a claim set based on the incoming credential type, e.g. a WindowsClaimSet, a X509CertificateClaimSet or a claim set that holds assertions from an incoming SAML token.
  2. You create a policy that parses the WCF created claim set and transform the "technical" user identifier (e.g. Windows account name, X509 thumbprint, username) to an identifier that makes sense in your application. This identifier goes into a new claim set that gets added to the evaluation context.
  3. You create another policy that parses the application identifier from the previously added claim set and retrieves all necessary authorization information. Based on that you create a customer claim set and add it to the evaluation context.
  4. The business logic retrieves the information from the customer claim set and does authorization decisions.

If you follow this pattern you would only have to adjust the logic of the user id generation to your specific environment. The customer claim set creation policy is now easily re-usable.

In the next post I will show you some code ;)


IdentityModel | WCF
Friday, February 29, 2008 10:40:39 AM UTC  #   

New IIS7 Resources

HTH


IIS
Friday, February 29, 2008 8:35:41 AM UTC  #   
 Tuesday, February 26, 2008

Using IdentityModel: Creating Custom Claim Sets

As part of LeastPrivilege.IdentityModel I wrote a claim set derived class that you can use to create your own custom claim sets. The DeferredLoadClaimSet lazily loads the claims (which potentially involves roundtrips to data stores) on demand when the claims are accessed for the first time.

You simply derive from DeferredLoadClaimSet and implement the LoadClaims method like this:

class CustomerClaimSet : DeferredLoadClaimSet
{
    string _customerName;

    public CustomerClaimSet(string customerName)
    {
        _customerName = customerName;
    }

    protected override void LoadClaims(out ClaimSet issuer, out IList<Claim> claims)
    {
        claims = CreateClaims();
        issuer = new ApplicationIssuerClaimSet(Constants.ApplicationIssuerName);
    }
}

 

The ApplicationIssuerClaimSet also derives from DeferredLoadClaimSet and can be used for custom issuers.

public class ApplicationIssuerClaimSet : DeferredLoadClaimSet
{
    string _issuerName = "Application Issuer";

    public ApplicationIssuerClaimSet()
    { }

    public ApplicationIssuerClaimSet(string issuerName)
    {
        _issuerName = issuerName;
    }

    protected override void LoadClaims(out ClaimSet issuer, out IList<Claim> claims)
    {
        claims = new List<Claim>()
        {
            new Claim(ClaimTypes.System, _issuerName, Rights.Identity),
            new Claim(ClaimTypes.System, _issuerName, Rights.PossessProperty)
        };

        issuer = this;
    }
}

 

HTH


IdentityModel
Tuesday, February 26, 2008 9:40:02 AM UTC  #   
 Sunday, February 24, 2008

Using IdentityModel: Typical Operations on Claim Sets

In the previous posts I talked about claims and claim sets. Now how do you use claim sets for authorization?

Let's take WCF as an example. In WCF you get access to the system generated claim sets via the AuthorizationContext (I have not yet explained where the AuthorizationContext comes from and how it is created - but I will in a future post). The typical code you write to access claim sets would be:

IEnumerable<ClaimSet> claimSets =
    ServiceSecurityContext.Current.AuthorizationContext.ClaimSets;

 

Once you have access to the claim sets you typically do the following operations:

  • Check if a specified claim is in one of the claim sets
  • Search for occurrences of a specified claim type in the claim sets

In addition you also want to specify the issuer of the claim you are looking for, e.g. search for the Name claim issued by 'System' or 'Windows'.

Unfortunately this simple task is a little cumbersome to achieve. Since you only have the FindClaim and ContainsClaim methods on ClaimSet itself, but not on the claim set list, you'd have to do this:

  1. Cycle through all the claim sets and inspect the issuer.
  2. If the issuer matches the one you are looking for use the issued claim set
  3. Search the claim set for the claim you are looking for

Extension methods to the rescue. In LeastPrivilege.IdentityModel I have extended IEnumerable<ClaimSet> with search operations that work across a list of claim sets:

  • ContainsClaim
  • FindClaim(s)
  • FindIdentityClaim
  • GetClaimSetsByIssuer

All these operations let you specify an issuer claim (or claim set) and the various overloads also allow to specify a claim comparer or if you want to search in issuer or issued claims.

The following code would achieve the above task (searching for the Windows account name):

Claim name = claimSets.FindClaim(ClaimTypes.Name, ClaimSet.Windows);

To get the Windows SID identity claim issued to the user, you could do this:

Claim sid = claimSets.FindIdentityClaim(ClaimSet.Windows);

...or directly convert to a SecurityIdentifier:

SecurityIdentifier sid =
    claimSets.FindIdentityClaim(ClaimSet.Windows).Get<SecurityIdentifier>();

 

This makes working with claim sets a little easier. In the next posts I will talk about how to create an AuthorizationContext yourself (e.g. to use the same programming model outside of WCF) and how authorization policies come mix in here.


IdentityModel | WCF
Sunday, February 24, 2008 11:28:24 AM UTC  #   

Using IdentityModel: Windows and X509Certificate Claim Sets

In System.IdentityModel.Claims you can find two more specialized claim sets for Windows accounts and X509 certificates called WindowsClaimSet and X509CertificateClaimSet respectively.

WCF uses these classes to create claim sets for Windows/certificate clients. But you can also use them "standalone".

The WindowsClaimSet converts the content of a Windows token to a claim set. You new up this class by passing in a WindowsIdentity. You can get one of these via network authentication, via LogonUser, or by calling WindowsIdentity.GetCurrent(). The generated claims are:

  • A SID identity claim containing the token's account SID (also available as a possess property)
  • SID possess property claims containing the group SIDs
  • Name possess property claim containing the user name

The issuer of this claim set will be always ClaimSet.Windows.

 

The X509CertificateClaimSet converts some of the attributes of an X509 certificate (and its issuers) to a claim set. Simply pass a X509Certificate2 to the constructor. The generated claims are:

  • A thumbprint claim containing the cert hash as identity and possess property.
  • The subject name as a X500DistinguishedName claim (possess property)
  • The public key of the certificate as an RSA claim (possess property)
  • Various extended name variations (if contained in the certificate). The claim set calls GetNameInfo on the certificate for DnsName, SimpleName, EmailName, UpnName and UrlName to create DNS, Name, Email, UPN and URI claims respectively.

The claim set issuer chain reflects the certificate issuer chain (by calling X509Chain.Build):

  • When the cert is self issued, the issuer will point to itself.
  • When the issuing cert is available, the issuer will be a X509CertificateClaimSet.
  • When the issuing cert is not available, the issuer will be a simple X509DistinguishedNameClaimSet containing the distinguished name of the issuer as identity and possess property.

IdentityModel
Sunday, February 24, 2008 9:07:27 AM UTC  #   
 Saturday, February 23, 2008

Using IdentityModel: Inspecting Claim Sets

The following code is useful for inspecting the contents of claim sets:

 

public static void ShowClaims(IEnumerable<ClaimSet> claimSets)
{
    int count = 0;
    foreach (ClaimSet set in claimSets)
    {
        Heading(String.Format("Claim Set #{0}", ++count), ConsoleColor.Yellow);
        ShowClaimSet(set, false);
    }
}

private static void ShowClaimSet(ClaimSet set, bool isIssuer)
{
    if (set.HasIssuer())
    {
        ShowClaimSet(set.Issuer, true);
    }

    string setType = set.GetType().Name;
   
string setName = isIssuer ? "Issuer" : "Issued";

    Heading(String.Format("\n{0} Claims ({1})\n", setName, setType),

      ConsoleColor.Green);

   
    foreach (Claim claim in set)
    {
        if (claim.Right.Equals(Rights.Identity))
        {
            Console.ForegroundColor = ConsoleColor.White;
        }

        Console.WriteLine(claim.ClaimType);
        Console.WriteLine(claim.Resource);
        Console.WriteLine(claim.Right);
        Console.WriteLine();

        Console.ResetColor();
    }
}

private static void Heading(string text, ConsoleColor color)
{
    Console.ForegroundColor = color;
    Console.WriteLine(text);
    Console.WriteLine();
    Console.ResetColor();
}


IdentityModel
Saturday, February 23, 2008 8:42:27 PM UTC  #   

Using IdentityModel: Claim Sets

In the previous post I talked about claims, what they are and how to create them. Usually a claim doesn't come on its own - but is grouped into a claim set.

To create a claim set you either derive from ClaimSet or new up a DefaultClaimSet. Which approach you choose depends on your needs. DefaultClaimSet provides a default implementation of a claim set whereas deriving from Claim allows doing your own internal data management (a little bit like deriving from GenericIdentity opposed to implementing the IIdentity interface manually). I used the Claim-derived approach in LeastPrivilege.IdentityModel e.g. to implement a claim set that does lazy loading of claims.

Regardless which approach you choose, a claim set always consists of two parts: a list of claims and an issuer.

The list of claims should contain a single identity claim - this acts as the identity of the claim set. Optionally there can be a number of possess property claims.

The issuer is also described using a claim set. The typical layout of an issuer claim set is a single identity claim that uses the System claim type (http://schemas.xmlsoap.org/ws/2005/05/identity/claims/system) and the same claim as a possess property. The value of these claims is up to the issuer.

System.IdentityModel provides two pre-defined issuer claim sets (available as static properties from the ClaimSet class):

  • ClaimSet.System
    Has a System claim type with a value of 'System' (identity and possess property).
    Used to describe claim sets that come from the 'System'.
  • ClaimSet.Windows
    Has a system claim types with a value of S-1-5 (identity and possess property).
    Used as an issuer for WindowsClaimSets.

If a claim set's issuer points to itself, you have reached the chain root (use ReferenceEquals to check this).

Typically you use these public methods from ClaimSet:

  • ContainsClaim
    Returns true/false if a specified claim can be found in the claim set
  • FindClaims
    Returns an IEnumerable<Claim> for all matches of a specifed claim type/right

LeastPrivilege.IdentityModel adds two extension methods to ClaimSet:

  • FindIdentityClaim
    Returns the identity claim of the claim set
  • HasIssuer
    Tells you if the claim set has an issuer

 

So much for the facts. In the next posts I will talk about where claim sets come from, what are typical operations you do on claim sets and how you use them for authorization.


IdentityModel
Saturday, February 23, 2008 8:26:01 PM UTC  #   
 Friday, February 22, 2008

thinktecture & DevelopMentor: Guerilla Enterprise .NET Kurs in München

Geballtes Know-How - für Sie!

thinktecture und DevelopMentor kündigen im Rahmen einer Partnerschaft das erste in Deutschland stattfindende "Enterprise .NET"-Event an. Es wird in der Woche vom 26. Mai 2008 in München stattfinden und im berühmt-berüchtigten "Guerilla"-Format abgehalten: Über 50 Stunden in 5 Tagen!

Lernen Sie, robuste verteilte Lösungen auf Basis der Windows Communication Foundation (WCF), Windows Workflow Foundation (WF) und Biztalk Server zu entwerfen und zu implementieren. Der „Guerilla Enterprise .NET“-Kurs zeigt Ihnen, wie man diese Technologien und Produkte anwendet und verteilte Systeme damit erstellt. Dabei wird auf den jeweiligen Stärken der Technologien aufgebaut und versucht, die Schwächen zu umgehen – alles im Sinne Service-orientierter Prinzipien.

Erhalten Sie Antworten auf diese und weitere Fragen:

  1. Was bedeutet Service-Orientierung wirklich?
  2. Wie erstelle ich interoperable Services?
  3. Wie kann ich einzelne „Building Block“ Services komponieren?
  4. Wie funktionieren Sessions und wann sollte ich sie nutzen?
  5. Wie kann ich lang andauernde Geschäftsvorfälle modellieren und ausführen?

Lernen Sie, skalierbare und flexible Service-basierte Anwendungen mit den aktuellen Technologien aus dem .NET Framework 3.5 zu bauen.

Highlights des Kurses

  • Verstehen Sie die Architektur von WCF, WF und Biztalk Server
  • Schreiben Sie Services für Internet- und Intranet-Szenarios
  • Erstellen Sie robuste lang laufende Workflows
  • Verstehen Sie die Unterschiede und jeweiligen Stärken von Biztalk Server und WF
  • Erstellen Sie asynchrone nachrichtenbasierte Anwendungen
  • Integrieren Sie Services und Workflows mit WCF und WF

Wir würden uns freuen, Sie am 26. Mai 2008 in München begrüßen zu dürfen - "Take your enterprise .NET programming skills to the next level!"

Registrierung und weitere Informationen können per Email an office@thinktecture.com abgewickelt werden.

Flyer

 

Infos zum Guerilla-Format:

Anders als bei "üblichen" Trainings werden Sie in den fünf Tagen ständig durch 3 Trainer betreut – Montag bis Donnerstag auch bis in den späten Abend hinein. Sie können Ihre Kenntnisse und Fähigkeiten bei Programmier-Challenges mit den anderen Teilnehmern messen und attraktive (und auf jeden Fall auch Geek-)Preise gewinnen.

Wir (Christian, Ingo und Ich) würden uns freuen Sie zu begrüßen!
Work in Progress
Friday, February 22, 2008 2:58:22 PM UTC  #   

 Tuesday, February 19, 2008

Using IdentityModel: Claims

A claim is a piece of information that you want to associate with an entity (usually a user) in your system. Commonly used claims are e.g. the name of a user or his roles.

The usual course of events is, that one part of a system creates some claims and associates them with a certain context (e.g. an HTTP request or the current user of the system) and another part of that system uses this information to make decisions. Most commonly claims are used for authorization.

Again you can compare this to how roles-based authorization works (role providers, IPrincipal & IIdentity and Thread.CurrentPrincipal). But a role is a very simple claim - it only allows a binary (yes/no) decision. If you want to base more complex authorization decisions on roles (e.g. allowed to transfer money up to 5000€) you usually have to make additional round trips to your authorization store from within your code (or you have to create a TransferMoney5000 role which of course is suboptimal because it couples business details with the role name - what do you do if the limit changes to 7000€ ?).

System.IdentityModel defines a more general purpose data structure in a class called Claim (simplified version):

[DataContract(
  Namespace = "http://schemas.xmlsoap.org/ws/2005/05/identity")]
public class Claim
{
  [DataMember(Name = "ClaimType")]
  public string ClaimType;
       
  [DataMember(Name = "Resource")]
  public object Resource;

       
  [DataMember(Name = "Right")]
  public string Right;
}

 

A claim consists of three pieces of data:

  • The claim type is just a string giving this claim a unique name. You should use http:// namespace URIs like http://www.leastprivilege.com/claims/transfermoney (though this format is not a must, I ran across problems with serialization in SAML tokens if you use a different format).
  • The resource is some additional arbitrary data that you can associate with the claim. This could be simple integer like 5000 (to stick with my above example) up to complete object models (like a customer history).
  • The right describes the type of the claim. There are two commonly used values here: Identity and PossesProperty. The right becomes more important when you group claims into claim sets (something I will talk about in the next post). You can get these values from the Rights class.

The ClaimTypes class defines a bunch of standard claim types (name, email, gender, webpage...) so you don't have to remember their namespace values. To create a simple name claim, you would write code like this:

Claim name = new Claim(ClaimTypes.Name, "Alice", Rights.Identity);

 

The Claim class also features several static methods that create standard claims, e.g.:

Claim name = Claim.CreateNameClaim("Alice");

 

The static methods always create PossesProperty claims. If you need to create an identity claim, you have to manually construct the claim object.

A custom TransferMoney claim could be created like this:

Claim transferMoneyClaim = new Claim(
    "http://www.leastprivilege.com/claims/transfermoney",
    5000,
    Rights.PossessProperty);

 

LeastPrivilege.IdentityModel defines three extension methods for the Claim class:

  • Get<T> and TryGet<T> to convert the resource to a specific type.
  • IsIdentity to check if the claim is an identity claim.

 

A single claim on its own is generally not very useful. Claims typically come as part of claim sets - the topic of the next post about IdentityModel. Stay tuned.


IdentityModel | ASP.NET | WCF | Work in Progress
Tuesday, February 19, 2008 3:45:28 PM UTC  #   

Using System.IdentityModel and LeastPrivilege.IdentityModel

Michael (a reader) recently wrote:

"You posted on your blog that System.IdentityModel is not tied to WCF...I understand how the claims, rights, and resources work. And I have created an Authorization Policy that implements IAuthorizationPolicy. But how do I make it all work together?"

I spent the last couple of months building some applications that use the classes found in System.IdentityModel for identity and access control. And I really like this model.

As with most of the other features introduced in .NET 3.0, the API is meant as a foundation (compare to WCF) and is a little raw. So while writing my code I also built a companion library boldly called LeastPrivilege.IdentityModel that adds functionality that makes it easier to build claims/identity based applications.

In the next couple of posts I will do a walkthrough of System.IdentityModel, how to use the classes found there (together), how they are used in WCF, how to use them outside of WCF and what my library adds to it.

If you have any specific questions - just post them here and I can try to incorporate them. Have fun.


Work in Progress | IdentityModel
Tuesday, February 19, 2008 7:41:51 AM UTC  #   
 Thursday, February 14, 2008

Be careful with ServiceAuthorizationManager.CheckAccess()

I wrote here about a new method to override in ServiceAuthorizationManager.

Yesterday suddenly a service began to behave very strangely after adding an authorization manager to it - and after some debugging I found out that my custom claims were missing and authorization broke all over the place.

After a little more digging it turned out that this was caused by implementing the new CheckAccess method without calling the base class implementation.

The call sequence in ServiceAuthorizationManager is a little strange (I guess that's the result of adding the new method without introducing a breaking change):

  1. WCF plumbing starts the authorization process
    (System.ServiceModel.Dispatcher.AuthorizationBehavior.Authorize (ref MessageRpc))
  2. CheckAccess(OperationContext, ref Message)
  3. CheckAccess(OperationContext)
  4. CheckAccessCore(OperationContext)

In step #3 the external authorization policies get added to the message which in turn means that the first access of AuthorizationContext afterwards triggers their evaluation. That means if you don't call the base class implementation, the external policies never get attached - hence my claims were missing.

This is working:

class TestAuthorizationManager : ServiceAuthorizationManager
{
    public override bool CheckAccess(OperationContext operationContext, ref Message message)
    {
        base.CheckAccess(operationContext, ref message);

        // authZ code
    }
}


WCF | Work in Progress
Thursday, February 14, 2008 9:57:01 PM UTC  #   
 Tuesday, February 12, 2008

Extending IIS 7 Configuration

If you want to add your own configuration sections (e.g. for this or this) to IIS 7 you have to write a schema file and dop it into the schema folder. From that point on it can be picked up by appcmd or ServerManager - all is good.

Thanks to Brock I found an article that shows how to extend existing configuration sections. I wanted to do that for my custom basic authentication module to live under the security element in IIS 7 but shied away from it because I didn't want to modify the built-in schema files. Now it turns out that IIS 7 can merge multiple schema files.

So if you want to extend an existing section - simply write a schema file for that section with your additional elements and attributes and drop it into the schema folder. Through the eyes of the configuration system the section will contain the built in and added information. Cool!

See also here.


ASP.NET | IIS | Work in Progress
Tuesday, February 12, 2008 8:33:01 AM UTC  #   
 Sunday, February 10, 2008

PowerTab

Fantastic tab completion add-on for Powershell.


For Your Favourites
Sunday, February 10, 2008 9:47:02 AM UTC  #   
 Saturday, February 09, 2008

My +1 List

I don't really like to just "+1" other blog posts I find - but Google Reader allows me to conveniently create a list of blog entries that I like to share with other people.

You can access this list here - and subscribe to it here.


For Your Favourites
Saturday, February 09, 2008 6:57:28 AM UTC  #   
 Friday, February 08, 2008

New HTTP.SYS Namespace Reservation in 3.5

A while ago I wrote about the "temporary listeners" wildcard namespace reservation that comes with .NET 3.0. I didn't like it - and my opinion hasn't changed here.

It turns out that .NET 3.5 adds a new reservation during installation:

c:\> netsh http show urlacl

Reserved URL            : http://+:8731/Design_Time_Addresses/
    User: NT AUTHORITY\INTERACTIVE
        Listen: Yes
        Delegate: No
        SDDL: D:(A;;GX;;;IU)

I believe this reservation is made to make the "WCF Service Library" and the accompanying generic WCF service host work.

Actually this reservation is much more sensible than the "temporary" one because:

  • it is not on a commonly used port which means that chances that the firewall is already open are relatively low. Remember that the "temporary" one points to port 80 which is also used by IIS, SQL Server WS-RemoteManagement etc...
  • It is using INTERACTIVE as opposed to WORLD (aka Everyone). This means that this reservation only applies to services started by the carbon unit operating the machine.

I had a discussion today with my friend Oliver who ran into the namespace reservation problem and thought that the best workaround would be to use the "temporary" namespace as it is already registered and doesn't require any command line fiddling. After I told him that I disagree, he asked me what would be the recommended way of handling this problem at development time (without elevating Visual Studio to admin all the time, of course ;).

I tackled this for me by reserving a dev time base address like http://+:9000/Services/ (ACLed only for myself) and key off all my services from that URI. But actually I think the new design time addresses make sense in design time ;) They are already there and have sensible settings for most standard development scenarios (but not for NT Services - since these are not interactive). Too bad the old "temporary" reservation didn't go away in 3.5...

Will try that in the future and see how it works out.


WCF | Work in Progress
Friday, February 08, 2008 2:19:00 PM UTC  #   
 Saturday, February 02, 2008

Information Cards and PPIDs (again)

Barry is a hard working guy. He implemented a complete STS for Information Cards - and believe me - this is no fun.

In this post he summarized some details about PPID generation and under which circumstances you can change parts of your application without getting new PPIDs (which is a major pain because you would basically lose card to user mappings in your application).

Barry and me are currently working on a more general paper about PPIDs and how to use them - in the meanwhile have a look at his post since it is a nice wrap up.


CardSpace | For Your Favourites
Saturday, February 02, 2008 10:43:23 PM UTC  #   
 Thursday, January 31, 2008

UserName SupportingToken in WCF

There has been some mentioning of supporting tokens recently (here and here). Supporting tokens allow adding additional tokens to a SOAP security header - at least one token (usually the primary token) must be capable of providing the necessary key material to secure the message - the other tokens are just additional information. This is quite interesting when you have typical multi-hop architectures.

The following scenario is quite common. How can this be accomplished using WCF?

  • Three participants: a client (using a browser), a web application and a back-end WCF service.
  • The web application uses forms authentication (or some other scheme where you end up with a username) to authenticate clients.
  • The web application uses a client certificate to talk to the back-end WCF service.
  • The WCF service needs the identity of the (browser) client for authorization.

Now there are several hand-rolled ways to accomplish this. You could add the client name as an operation parameter - or better as a header - and could parse this information in the WCF service. But if you already bought into the WCF security model  and especially claims and claims based authorization - the supporting token approach integrates more nicely.

WCF will create claim sets for all incoming tokens. In our case the back-end WCF service would end up with two claim sets - one for the web app client certificate and one for the browser client. Perfect.

How does this work?

First you have to configure a binding for supporting tokens. You can either create a new binding - or modify an existing one. This works by creating a token parameter description (in our case a UserNameTokenParameter) and setting the inclusion mode (derived key can be disabled here).

There are some decisions to make when adding a support token:

Optional or required
Supporting tokens can be either optional or required. You express this by using the xxxSupportingTokenParameters or the OptionalxxxSupportingTokenParameters collection on the SecurityBindingElement.

Scope
You can either add the token for some specific operations or the complete endpoint. Again there is a collection for Endpoint and Operation supporting tokens.

Token Usage
You can specify whether this token is used to generate key material (Endorsing and SignedEndorsing) or just as extra information (Signed or SignedEncrypted).

Modifying an existing binding involves calling the GetBindingElements method, finding the right binding element to modify and afterwards creating a new binding using the modifications. The code looks like this:

static Binding AddUserNameSupportingTokenToBinding(Binding binding)
{
    BindingElementCollection elements = binding.CreateBindingElements();

    SecurityBindingElement security =
      elements.Find<SecurityBindingElement>();
    if (security != null)
    {
        UserNameSecurityTokenParameters tokenParameters = new
          UserNameSecurityTokenParameters();
        tokenParameters.InclusionMode =
          SecurityTokenInclusionMode.AlwaysToRecipient;
        tokenParameters.RequireDerivedKeys = false;
        security.EndpointSupportingTokenParameters.SignedEncrypted.Add(
          tokenParameters);

        return new CustomBinding(elements.ToArray());
    }

    throw new ArgumentException(
      "Binding contains no SecurityBindingElement");
}

Of course you have to modify the binding both on the client and the service (see the sample download). You then set both credentials on the client side - the client certificate and the username part of the UserName credential.

Since this scenario is using a trusted subsystem approach, the back-end service trusts the web app that the client has already been properly authenticated. This means that you don't have to send the client's password to the WCF service - just leave it empty. This also means that you don't have to "remember" the client's password in the web app - which is desired.

To allow empty passwords on the service side you have to additionally provide a UserNamePasswordValidator which is happy with empty passwords. (again - the code is provided in the sample download).

When you got all this up and running - you have several places in your WCF service that are impacted by the additional token. First you get a new claim set - you will see an X509CertificateClaimSet for the direct caller's client certificate and a UserNameClaimSet that contains the name of the original client. You can inspect the claim sets like this:

private void ShowAllClaims()
{
    foreach (ClaimSet set in ServiceSecurityContext.Current.AuthorizationContext.ClaimSets)
    {
        Heading("IssuerClaims (" + set.Issuer.GetType().Name + ")");
        foreach (Claim claim in set.Issuer)
        {
            Console.WriteLine("{0}\n{1}\n{2}\n",
               claim.ClaimType,
               claim.Resource,
               claim.Right);
        }

        Heading("IssuedClaims (" + set.GetType().Name + ")");
        foreach (Claim claim in set)
        {
            Console.WriteLine("{0}\n{1}\n{2}\n",
               claim.ClaimType,
               claim.Resource,
               claim.Right);
        }
    }
}

You can also gain direct access to supporting tokens via the OperationContext:

if (OperationContext.Current.HasSupportingTokens)
{
    Heading("Supporting tokens:");
    foreach (SupportingTokenSpecification spec in OperationContext.Current.SupportingTokens)
    {
        Console.WriteLine(spec.SecurityToken.GetType().Name);
    }
}

Cool. Mission accomplished, right? Well - there is one gotcha.

 

The ServiceSecurityContext.PrimaryIdentity typically returns an IIdentity for the caller of the service. I was surprised to see that when you are adding a supporting token to a message the primary identity suddenly changes to Anonymous. Inspecting the code with Reflector shows that the logic of PrimaryIdentity is like this:

  • if there is a single identity, return this identity
  • if there is no identity or more than one, return anonymous.

Maybe I am misunderstanding the concept of primary identity - but I thought the primary identity should still point to my direct caller whereas the supporting token is just some secondary identity information. So the current behavior feels wrong to me.

If you are relying on PrimaryIdentity somewhere in your code - be aware that adding a supporting token will break that code.

 

Workarounds
Primarily, I guess you have to be aware of that behavior. If you need the direct caller on PrimaryIdentity you could write an IAuthorizationPolicy that sets a single identity on the Properties dictionary at the end of the evalution cycle. Haven't tested that - but should work.

I chose to write two extension methods for ServiceSecurityContext that give me more options when working with identities. If you are on 3.5 you can add these to your toolbox.

The first one returns all available identites:

public static IList<IIdentity> GetIdentities(this ServiceSecurityContext context)
{
    return GetIdentities(context.AuthorizationContext);
}

private static IList<IIdentity> GetIdentities(AuthorizationContext authorizationContext)
{
    object list;
    if ((authorizationContext != null) && authorizationContext.Properties.TryGetValue("Identities", out list))
    {
        return (list as IList<IIdentity>);
    }

    return new List<IIdentity>();
}

 

The second one returns the first identity (or anonymous if no identity exists):

public static IIdentity GetFirstIdentity(this ServiceSecurityContext context)
{
    IList<IIdentity> identities = GetIdentities(context.AuthorizationContext);
    if (identities.Count > 0)
    {
        return identities[0];
    }
    else
    {
        return new GenericIdentity(string.Empty);
    }
}

 

SupportingUserNameToken.zip (42.56 KB)

HTH


ASP.NET | WCF | Work in Progress
Thursday, January 31, 2008 8:26:51 AM UTC  #   
 Tuesday, January 29, 2008

WCF 3.5 and Partial Trust

This is a good summary of things that work and don't work when WCF is used in partially trusted AppDomains.

I especially like this sentence:

"The best way to discover that a piece of information or action is unavailable when running in a partial trust environment is to try to access the resource or do the action inside of a try block, and then catch the failure."

This pretty much describes the situation we currently have in CAS.


WCF
Tuesday, January 29, 2008 2:49:17 PM UTC  #   
 Saturday, January 26, 2008

Web Deployment Projects for Visual Studio 2008

(note to self)

download here.


For Your Favourites
Saturday, January 26, 2008 8:34:39 AM UTC  #   
 Thursday, January 24, 2008

Jörg Neumann in da House

I am very happy to announce that Jörg Neumann has joined thinktecture.

Jörg is the UI master, a SQL Server expert and an overall really great guy. Looking forward working with him!

http://www.thinktecture.com/staff/joerg


Misc
Thursday, January 24, 2008 8:47:28 AM UTC  #   

LeastPrivilege on RunAs Radio

A few weeks ago I did a recording with Richard and Greg for RunAs Radio. That was fun.

It is now online. Have fun and support the show!

http://www.runasradio.com/default.aspx?showNum=41


For Your Favourites
Thursday, January 24, 2008 8:13:29 AM UTC  #   
 Friday, January 18, 2008

ASP.NET Internals Spelunking II

Suppose you want to step through one of the built-in HttpModules in ASP.NET. This doesn't work by traversing the callstack. There is another little "trick" to get this working:

  • Go to Debug/New Breakpoint/Break at Function
  • Enter the function name, e.g. FormsAuthenticationModule.OnEnter and select "C#"
  • Ignore the error message
  • Hit F5

Unfortunately you'd first have to use Reflector to find the function name before you can set a breakpoint. Could be easier.

Have fun!


ASP.NET | Work in Progress
Friday, January 18, 2008 6:19:02 PM UTC  #   

ASP.NET Internals Spelunking

When I wrote the ASP.NET book I pretty much lived in Reflector 24/7 to figure out all the gory implementation details. Back then it would have been great to be able to simply set breakpoints in some of the low level classes like HttpRuntime or modules.

Fortunately this is now possible, here's a quick walkthrough:

  • Set up your Visual Studio to work with the new .NET symbols. Also have a look at the various symbol loading options you have.
  • Open an ASP.NET app
  • Set a breakpoint somewhere in your code (e.g. in a Page_Load)
  • Let the debugger hit the breakpoint
  • Open the call stack window and navigate up the stack, e.g. to HttpRuntime.ProcessRequest or Page.ProcessRequest
  • Set a breakpoint (use HttpRuntime.Init or the (c)ctor to step through the whole initialization process)
  • Right click the breakpoint, select location and check the "Allow the source code to be different from the original version" option.
  • Debug again. The debugger should now hit the breakpoint in the ASP.NET infrastructure class
  • Depending on how early in processing you set the breakpoint, you may have to recycle the AppDomain to start over. Simply make a change to web.config and save to trigger recycling.

 

Happy spelunking ;)


ASP.NET | Work in Progress
Friday, January 18, 2008 9:50:03 AM UTC  #   
 Wednesday, January 16, 2008

Simplified Impersonation Model

While playing with the new Named Pipe classes in 3.5 I noticed that a simpler model for impersonating the client is used here. Instead of putting the burden on the user to call Impersonate on a WindowsIdentity (and making sure that impersonation is undone correctly), you simply pass a delegate that should run impersonated to RunAsClient...interesting.

using (var server = new NamedPipeServerStream("pipe"))
{
    Console.WriteLine("waiting");
    server.WaitForConnection();
    Console.WriteLine("connected");

    using (var reader = new StreamReader(server))
    {
        string message = reader.ReadLine();

        string client = server.GetImpersonationUserName();
        Console.WriteLine("{0} says {1}", client, message);

        // runs under client identity
        server.RunAsClient(delegate
        {
            Console.WriteLine("impersonated identity: {0}",
                WindowsIdentity.GetCurrent().Name);
            
            // access some resource
        });
    }
}

FX Security | Samples | Work in Progress
Wednesday, January 16, 2008 6:15:28 AM UTC  #   
 Tuesday, January 15, 2008

Most popular Content in 2007

Just looked through my server logs...

Top Downloads:

  1. InfoCardSelector for ASP.NET (1508)
  2. HTTP.SYS config utility (819)
  3. GetCertKeyFile (732)
  4. IfConfig 2.11 (683)
  5. SslHelper (588)

 

Top Documents:

  1. PenTest Slides from WinDev 2004 (1682)
  2. Integration mit Windows Security from Basta 2006 (1125)
  3. Hackproofing IIS6 from ADC 2004 (924)
  4. Smart Client Deployment Security from Basta 2006 (643)
  5. New Security Features in .NET 2.0 from DevWeek 2005 (628)

 

Now back to serious work....;)


Misc
Tuesday, January 15, 2008 6:47:23 AM UTC  #   
 Saturday, January 12, 2008

HTTP Basic Authentication against Non-Windows Accounts in IIS7

The last posts (here, here, here and here) show how to build an HTTP Basic Authentication module using a membership provider (and including WCF support). Moving this code to IIS7 is technically not very difficult - but the mindset changes. In IIS6 a HTTP module is used to extend the managed ASP.NET pipeline. In IIS7 you are now building extensibility code for the web server itself. This has some implications:

  • The HTTP module is not registered in <system.web /> anymore. Since you extend IIS7, the module gets registered in <system.webServer /> now.
  • Same applies to the <customBasicAuthentication /> configuration section for the module itself.
  • The module now has easy access to the IIS security configuration and can behave diffently based on that
  • A proper IIS7 feature should also have a corresponding UI in the IIS Manager - including support for remote administration

I have already written a walkthrough on how to build IIS7 modules (here, here, here, here, here, here and here). I recommend reading them, since most of the code and structure is boilerplate.

The only real difference between the ServerHeader sample and the Basic Authentication UI integration is, that the authentication UI can register in the IIS7 Manager under the Authentication category:

From there you can open the custom Basic Authentication UI which controls the configuration settings in web.config for the currently selected application. Everything falls into place.

To register as a new authentication method you need to derive from a class called AuthenticationFeature. In this class you do some basic state management and are responsible for loading and showing the configurationUI. After that you register the feature in the Module derived class and call RegisterExtension on the extensibility manager.

// the module registers the custom UI with InetMgr
public class CustomBasicAuthenticationModule : Module
{
    protected override void Initialize(IServiceProvider serviceProvider, ModuleInfo moduleInfo)
    {
        base.Initialize(serviceProvider, moduleInfo);

        // add our module to the authentication section in the GUI
        IExtensibilityManager manager = 
            (IExtensibilityManager)serviceProvider.GetService(typeof(IExtensibilityManager));
        
        if (manager != null)
        {
            manager.RegisterExtension(
                typeof(AuthenticationFeature), 
                new CustomBasicAuthenticationFeature(this));
        }
    }
}

 

Download the full source here.

 


ASP.NET | IIS | WCF | Work in Progress
Saturday, January 12, 2008 8:49:51 PM UTC  #   

HTTP Basic Authentication against Non-Windows Accounts in IIS/ASP.NET (Part 3 - Adding WCF Support)

Taking this post as a starting point, we now have a working HTTP module that implements HTTP Basic Authentication against a membership provider. This is fine for ASP.NET applications and content, but to integrate a WCF service, there is some extra work necessary.

Even though WCF is technically also an HTTP module/handler participating in the ASP.NET pipeline, the WCF architects decided to keep a WCF service separate from the hosting environment. This means e.g. that you don't get access to the current HttpContext from within WCF by default.

Let's start with the service configuration itself. The service uses the basicHttpBinding and is configured for Transport security. Note that the clientCedentialType is set to None - this means that WCF itself is not doing any authentication. This is done by the HTTP module.

<system.serviceModel>
  <services>
    <service name="WcfService"
             behaviorConfiguration="Behavior">
      <endpoint address=""
                binding="basicHttpBinding"
                bindingConfiguration="CustomBasicAuth"
                contract="IWcfService" />
    </service>
  </services>

  <!-- turn off authentication in WCF, we get the client from the HttpContext -->
  <bindings>
    <basicHttpBinding>
      <binding name="CustomBasicAuth">
        <security mode="Transport">
          <transport clientCredentialType="None" />
        </security>
      </binding>
    </basicHttpBinding>
  </bindings>

  <!-- enable metadata -->
  <behaviors>
    <serviceBehaviors>
      <behavior name="Behavior">
        <serviceMetadata httpGetEnabled="true" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

The next step is to allow WCF to access the ASP.NET HttpContext to be able to access HttpContext.User which holds the identity of the client as authenticated by the Basic Authentication HttpModule.

This is done by adding a configuration setting to <system.serviceModel /> in web.config:

<!-- to enable IIS authentication for WCF, we have to switch to compatibility mode -->
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />

Additionally you also need to decorate the service implementation to opt-in to ASP.NET compatibility mode:

[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Required)]
public class WcfService : IWcfService

Now you could access HttpContext.Current.User in a service operation. This is not very natural for a WCF service developer since in WCF the client identity is typically exposed via ServiceSecurityContext.Current.PrimaryIdentity. To propagate the user information properly from ASP.NET to WCF you can use an IAuthorizationPolicy. This code takes the value from Context.User.Identity and puts it into a special dictionary which is later picked up by WCF to construct the ServiceSecurityContext.In addition you could also add claims for the user here.

// syncs ServiceSecurityContext.PrimaryIdentity in WCF with whatever is set 
// by the HTTP pipeline on Context.User.Identity (optional)
public class HttpContextIdentityPolicy : IAuthorizationPolicy
{
    public bool Evaluate(EvaluationContext evaluationContext, ref object state)
    {
        HttpContext context = HttpContext.Current;

        if (context != null)
        {
            // set the identity (for PrimaryIdentity)
            evaluationContext.Properties["Identities"] = 
                new List<IIdentity>() { context.User.Identity };

            // add a claim set containing the client name
            Claim name = Claim.CreateNameClaim(context.User.Identity.Name);
            ClaimSet set = new DefaultClaimSet(name);
            evaluationContext.AddClaimSet(this, set);
        }

        return true;
    }

    public System.IdentityModel.Claims.ClaimSet Issuer
    {
        get { return ClaimSet.System; }
    }

    public string Id
    {
        get { return "LeastPrivilege HttpContextIdentityPolicy"; }
    }
}

You wire up this policy in the <serviceAuthorization /> behavior:

<serviceAuthorization>
  <authorizationPolicies>
    <!-- sync ServiceSecurityContext.PrimaryIdentity with Context.User.Identity -->
    <add policyType="LeastPrivilege.HttpContextIdentityPolicy, LeastPrivilege.HttpContextPolicies" />
  </authorizationPolicies>
</serviceAuthorization>

Another "slot" for security information in WCF is Thread.CurrentPrincipal. This is used to support classic role-based IsInRole calls and the [PrincipalPermission] attribute. T.CP gets populated according to another configuration attribute in the <serviceAuthorization /> behavior called principalPermissionMode.

By default WCF tries to create a WindowsPrincipal which wouldn't work here. Another option would be to associate the user with an ASP.NET role provider by setting the principalPermissionMode to UseAspNetRoles. In this case T.CP is populated with a RoleProviderPrincipal which takes the user name from ServiceSecurityContext.PrimaryIdentity to find associated roles from a configured role provider datastore. Also read this post if you plan to use a role provider with WCF.

The most flexible option is to create your own custom IPrincipal and put it on Thread.CurrentPrincipal. This way you can optimize the IsInRole implementation to your specific back-end credential store. I wrote about this in detail here.

The sample app sets a custom IPrincipal by copying the IPrincipal provided by the ASP.NET pipeline to the WCF world. This may be useful if you want to share an existing principal implementation between ASP.NET and WCF:

// syncs Thread.CurrentPrincipal in WCF with whatever is set 
// by the HTTP pipeline on Context.User (optional)
public class HttpContextPrincipalPolicy : IAuthorizationPolicy
{
    public bool Evaluate(EvaluationContext evaluationContext, ref object state)
    {
        HttpContext context = HttpContext.Current;

        if (context != null)
        {
            evaluationContext.Properties["Principal"] = context.User;
        }

        return true;
    }

    public System.IdentityModel.Claims.ClaimSet Issuer
    {
        get { return ClaimSet.System; }
    }

    public string Id
    {
        get { return "LeastPrivilege HttpContextPrincipalPolicy"; }
    }
}

 

So that's it. You can now write an IIS hosted service that uses HTTP Basic Authentication against custom accounts while preserving the usual WCF security programming model. As you've seen this involves some plumbing and configuration and the interaction between all these configurations and extensibility points is sometimes quite confusing. Would be nicer if this functionality would be provided out of the box...

Attached are the WCF specific parts of the sample project. Just insert them into the download from the last post.

 


ASP.NET | WCF | Work in Progress
Saturday, January 12, 2008 7:48:22 PM UTC  #   

HTTP Basic Authentication against Non-Windows Accounts in IIS/ASP.NET (Part 3 - Setting up IIS6/ASP.NET)

In the previous post I explained how the authentication module works. Now which steps are required to get the demo app included in the download to work?

IIS

  1. Create a vdir in IIS that points to the sample site.
  2. Mark it as an application.
  3. Disable all authentication methods besides "anonymous".
  4. If you want to protect static content (e.g. jpg or xml), you also have to add the aspnet ISAPI extension as a wildcard mapping. Go to the application properties -> Home Directory -> Configuration. Go to the tab with all the script mappings, pick e.g. the .aspx mapping and copy the path to the ISAPI dll to the clipboard. In the bottom part of the same dialog add a wildcard mapping and set the previously copied path. Now ASP.NET will get invoked for all incoming requests.

This should be it. Now navigate to default.aspx in the browser and use alice/secret as the credential. You can also try the included asmx client. Be aware that you may have to change the requireSSL config attribute and/or the URLs.


ASP.NET | WCF | Work in Progress
Saturday, January 12, 2008 5:29:31 PM UTC  #   

HTTP Basic Authentication against Non-Windows Accounts in IIS/ASP.NET (Part 2 - The HTTP Module)

An HTTP module is one of the main extensibility points in ASP.NET/IIS7. Modules subscribe to notifications of certain stages in the HTTP request/response processing. Inside of the event handlers you can then inject your custom code and logic.

To allow some flexibility in the module's logic there is also a configuration section where the following settings can be configured:

  • enabled yes/no
  • realm name
  • the membership provider that should be used to validate credentials (default means use the default provider, otherwise provide the name of the provider)
  • require SSL
  • should credential caching be enabled - if yes how long (I mimick here the IIS built-in token caching and default to 15 minutes)

The following configuration section reflects these options:

<!-- custom basic authentication configuration -->
<customBasicAuthentication enabled="true"
                           realm="leastprivilege"
                           providerName="default"
                           cachingEnabled="true"
                           cachingDuration="15"
                           requireSSL="true" />

I skip the details on how to create configuration sections. There are also some differences when it comes to support IIS 6 and 7. In IIS6 this section would go to <system.web /> whereas in IIS 7 <system.webServer /> would be used.

The next step is to write a class that implements the IHttpModule interface and its Init method. Inside of Init the module subscribes to two events in the pipeline: AuthenticateRequest and EndRequest:

// event registration
public void Init(HttpApplication context)
{
    context.AuthenticateRequest += OnEnter;
    context.EndRequest += OnLeave;
}

What's happening in AuthenticateRequest
Here the module hast to distinguish between two states:

  • No Authorization header present.
    This means the request is anonymous. The module now has to check if anoynmous access is allowed - if this is the case, the module does nothing and passes the request on. Right after the AuthenticateRequest stage runs the AuthorizeRequest stage. If one of the subscribed modules determines that authentication is needed for the requested resource, it will put a 401 on the status code and jump directly to EndRequest. Here our EndRequest logic kicks in (more on that in a second).
  • Authorization header is present.
    This means the user tries to authenticate. In this case the module extracts the credentials from the header and passes them on to the configured membership provider (you could of course plugin whatever credentials verification logic you want).

Checking if anonymous access is configured in IIS is only included in the IIS7 version of the module.

The corresponding code looks like this:

void OnEnter(object sender, EventArgs e)
{
    HttpContext context = HttpContext.Current;
    
    // check if module is enabled
    if (!Configuration.Enabled)
        return;

    // check if SSL is required and enabled
    if (Configuration.RequireSSL && !context.Request.IsSecureConnection)
    {
        throw new HttpException(403, "SSL required for Basic Authentication");
    }

    // try to authenticate user - otherwise set status code and end request
    if (IsHeaderPresent)
    {
        if (!AuthenticateUser())
        {
            DenyAccess();
        }
    }
    else
    {
        // if anonymous requests are not allowed - end the request
        if (!IsAnonymousAllowed)
        {
            DenyAccess();
        }
    }
}

The DenyAccess method sets the 401 status code and ends the current request. Ending a request means that processing jumps directly to EndRequest. This is were the OnLeave handler kicks in.

private static void DenyAccess()
{
    HttpContext context = HttpContext.Current;

    context.Response.StatusCode = 401;
    context.Response.End();
}

 

What's happening in EndRequest?
The logic in EndRequest is simple. It checks if a 401 status code is set (by the module itself or maybe by an authorization module or a page) and if that is the case, sends the necessary HTTP headers back to start the authentication handshake.

void OnLeave(object sender, EventArgs e)
{
    // check if module is enabled
    if (Configuration.Enabled)
    {
        if (HttpContext.Current.Response.StatusCode == 401)
        {
            SendAuthenticationHeader();
        }
    }
}

 

Authenticating the user
The authentication logic uses a membership provider under the covers and additionally implements caching of the client "token".

private bool AuthenticateUser()
{
    string username = "", password = "";
    string authHeader = HttpContext.Current.Request.Headers["Authorization"];

    if (authHeader != null && authHeader.StartsWith("Basic"))
    {
        // extract credentials from header
        string[] credentials = ExtractCredentials(authHeader);
        username = credentials[0];
        password = credentials[1];

        if (IsCredentialCached(username, password))
        {
            SetPrincipal(username);
            return true;
        }
        else if (Provider.ValidateUser(username, password))
        {
            if (Configuration.CachingEnabled)
            {
                CacheCredential(username, password);
            }

            SetPrincipal(username);
            new AuthenticationSuccessEvent(this, username).Raise();

            return true;
        }
    }

    new AuthenticationFailureEvent(this, username).Raise();
    return false;
}
The AuthenticationFailureEvent/AuthenticationSuccessEvent classes are Health Monitoring WebEvents (System.Web.Management). They push tracing information out to configured listeners. The nice thing is that in IIS7 you can forward these WebEvents to the FREB tracing infrastructure.

 

Caching
Once the Authorization header is initially set, it gets re-sent on every subsequent request. This means that the authentication logic kicks in every time. If you would do a (remote) database roundtrip to your credential store on every authentication request, this could impact performance.

To improve this situation, you can implement a simple caching scheme:

  • Once the use is authenticated, cache a unique identifier for that username/password pair
  • When re-authentication happens, re-create that unique identifier from the supplied credentials and check if this id is in the cache

The two methods IsCredentialCached and CacheCredential implement this logic:

// caches a credential identifier
private void CacheCredential(string username, string password)
{
    string identifier = GetIdentifier(username, password);

    try
    {
        HttpContext.Current.Cache.Add(
            identifier,
            "ok",
            null,
            DateTime.Now.AddMinutes(Configuration.CachingDuration),
            Cache.NoSlidingExpiration,
            CacheItemPriority.Normal,
            null);

        new CredentialCacheAddEvent(this, username).Raise();
    }
    catch (Exception ex)
    {
        new CredentialCacheAddErrorEvent(this, username, ex).Raise();
    }
}// checks if the credential has been cached already
private bool IsCredentialCached(string username, string password)
{
    if (!Configuration.CachingEnabled)
        return false;

    string identifier = GetIdentifier(username, password);
    bool cacheHit = (HttpContext.Current.Cache[identifier] != null);

    if (cacheHit)
    {
        new CredentialCacheHitEvent(this, username).Raise();
    }
    else
    {
        new CredentialCacheMissEvent(this, username).Raise();
    }

    return cacheHit;
}

Creating an identifier
To get rid of any clear text secrets in your cache, simply hash the username and password. If you are paranoid, add a salt to it - but if an attacker is so close to your RAM to read the cache, you are in trouble anyways ;)

// create a string identifier for the cache key
// format: prefix + Base64(Hash(username+password))
private string GetIdentifier(string username, string password)
{
    // use default hash algorithm configured on this machine via CryptoMappings
    // usually SHA1CryptoServiceProvider
    HashAlgorithm hash = HashAlgorithm.Create();

    string identifier = username + password;
    byte[] identifierBytes = Encoding.UTF8.GetBytes(identifier);
    byte[] identifierHash = hash.ComputeHash(identifierBytes);

    return _cachePrefix + Convert.ToBase64String(identifierHash);
}

Setting the authentication header
The last missing piece is the code that sets the Authenticate header and kicks off the authentication handshake.

// send header to start Basic Authentication handshake
private void SendAuthenticationHeader()
{
    HttpContext context = HttpContext.Current;

    context.Response.StatusCode = 401;
    context.Response.AddHeader(
        "WWW-Authenticate",
        String.Format("Basic realm=\"{0}\"", Configuration.Realm));
}

 

OK - that's it. You can download the full code here. There is also a test webapp included. In the next post I will detail how to configure IIS/ASP.NET to use the authentication module.


ASP.NET | WCF | Work in Progress
Saturday, January 12, 2008 10:42:42 AM UTC  #   
 Friday, January 11, 2008

HTTP Basic Authentication against Non-Windows Accounts in IIS/ASP.NET (Part 1 - Basic Authentication)

The first step in implementing an authentication module for IIS/ASP.NET is to understand the authentication protocol (doh ;)

It turns out the Basic Authentication is quite simple. Whenever the server wants to start the authentication handshake, he sends a 401 HTTP status code with a special HTTP header named WWW-Authenticate e.g.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="leastprivilege"

When a browser receives such an HTTP response, he typically opens a logon dialog box. The realm is some extra parameter that can be used by the client.

After the user has entered his credentials, these are sent back to the server as a base64 encoded HTTP header named Authorization:

GET /default.aspx HTTP/1.1
Authorization: Basic YWxpY2U6Z29vZHdvcmtub3d5b3VjYW5zZWVteXBhc3N3b3Jk

The server then grabs the authorization header, decodes it, and verifies the credentials against some back end store. Very simple. After that the server either grants access or returns an HTTP 403 (forbidden).

Some notes

  • the value of the Authorization header is using the iso-8859-1 character set.
  • the credentials are only base64 encoded. This means they are sent in clear text. You should only use Basic Authentication when you layer SSL on top of it.
  • IIS forces authentication when Basic Authentication is the only selected authentication method.
  • When anonymous access is allowed too, IIS only starts the authentication handshake when it sees a 401 status code coming back (e.g. from ASP.NET's URL authorization)
  • IIS uses the username/password values to call Win32 LogonUser. The result is a Windows token.
  • the Authorization header is sent to the server on every request after the user entered credentials. That means the server has to somehow verify the credentials on each request.
  • IIS caches the Windows token - by default for 15 minutes. This can be configured in HKLM\System\CurrentControlSet\Services\InetInfo\Parameters\UserTokenTTL

OK - now we understand how the protocol works. In the next post I will show you how to use the HTTP pipeline to implement the authentication module.


ASP.NET | IIS | WCF | Work in Progress
Friday, January 11, 2008 6:13:57 PM UTC  #   

HTTP Basic Authentication against Non-Windows Accounts in IIS/ASP.NET (Part 0 - Intro)

Imagine this (quite common) scenario: You have some web content/resources (e.g. static files, aspx pages, asmx or WCF services) in IIS that you want to protect using HTTP Basic Authentication. The problem is, that when you enable Basic Authentication in IIS - all authentication is done against Windows accounts. That means that IIS grabs the username/password pair from the HTTP header and tries to create a Windows token using these credentials. This is a problem when you don't have Windows accounts for your clients, but rather want to store the accounts in e.g. a database (which is much more common IMHO).

Unfortunately IIS (including IIS 7) does not support this out of the box. And to make things worse, WCF with the new support for usernames over transport can't help here either.

A while ago I wrote all the necessary code to enable this scenario for IIS 6/7 and since I get a lot of questions about this, I will post the bits here step-by-step.

In the next days I will post the following series of blog entries:

  • How does Basic Authentication work
  • The Basic Authentication HTTP module
  • Using the module in IIS 6
  • Adding WCF support
  • Adding IIS7 support (configuration and UI module)

Stay tuned!


ASP.NET | WCF | Work in Progress
Friday, January 11, 2008 8:24:39 AM UTC  #   

System.AddIn Pipeline Builder

I recently spent quite some time in the System.AddIn namespace. I really like the new extensibility model and once you take the initial hurdle of understanding the infrastructure (or pipepline in MAF-speak) it is a really sensible model.

To get you started more easily the CLR AddIn guys (Jesse, Jack, TQ) have published a tool that allows code-gen'ing the complete pipeline from the contract. Give it a try!

http://www.codeplex.com/clraddins


For Your Favourites | AddIns
Friday, January 11, 2008 6:57:09 AM UTC  #   
 Thursday, January 10, 2008

System.DirectoryServices.AccountManagement

Looking through some of the new 3.5 stuff I stumbled over a new assembly named "System.DirectoryServices.AccountManagement" - that caught my attention.

The whole namespace reminds a little bit of ADSI - an API tailored to create user, group and machine accounts (local and domain). There are some easy to use classes for common tasks. Have a look yourself.

Two examples of things that can be achieved with S.DS.AM are:

Verifying the password of a user:

public static bool ValidateCredentials(string username, string password, ContextType type)
{
    return new PrincipalContext(type).ValidateCredentials(username, password);
}

Checking if a given user is a member of some group:

public static bool IsUserInGroup(string username, string groupname, ContextType type)
{
    PrincipalContext context = new PrincipalContext(type);

    UserPrincipal user = UserPrincipal.FindByIdentity(
        context,
        IdentityType.SamAccountName,
        username);
    GroupPrincipal group = GroupPrincipal.FindByIdentity(
        context, groupname);

    return user.IsMemberOf(group);
}

In both cases the ContextType could be either Machine, Domain or a ADAM database...


Work in Progress
Thursday, January 10, 2008 7:55:00 PM UTC  #   
 Tuesday, January 08, 2008

Configuration Section Designer

Writing configuration sections is tedious - a perfect candidate for code-gen.

Try this. Very nice.


For Your Favourites
Tuesday, January 08, 2008 6:54:20 AM UTC  #   
 Friday, January 04, 2008

Walkthrough: Setting up a self hosted WCF Service with SSL

This is really just a compilation of already known bits of information. But since I had to do this recently and was struggling to get these pieces together, I thought I'll document all the steps here.

Scenario:
WCF service that uses transport security with an HTTP based binding. The service is self-hosted using a least privilege account. How does that work?

 

1. Create and install an SSL certificate

The first step is to install an SSL certificate on the target machine. SSL certs are just normal certs with an intended usage of "Server Authentication" (and Key Exchange). You can get them from public CAs like Verisign, an internal CA like Windows Server Certificate Services or generate them yourself using tools like makecert or selfssl (for testing purposes only of course).

The syntax for makecert would be (admin privs required):

makecert -r -pe -n CN="DNS_NAME_OF_SERVICE" -eku 1.3.6.1.5.5.7.3.1 -ss my -sr localmachine
-sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12

SSL certificates must be installed into the "Personal" certificate store of the machine account (the above makecert command does this).

Afterwards - browse to the certificate using the MMC snap-in and make sure it is marked as valid and the details dialog says "You have a private key that corresponds to this certificate"

2. Map the SSL cert to the WCF listening port

For the next step you need several pieces of data beforehand.

  • the port on which your service will listen on
  • the thumbprint of the SSL certificate you want to use. Get this data e.g. from the MMC snap-in details dialog. Copy the "Thumbprint" value to a text editor and remove the whitespaces.
  • A GUID. Use e.g. the Visual Studio "Create GUID" tool (use the registry format).

On Vista+ you can use the netsh tool to do the mapping (admin privs required):

netsh http add sslcert ipport:0.0.0.0:port certhash=thumbprint appid=GUID

On pre-Vista you can use httpcfg.exe to accomplish the same thing:

httpcfg set ssl /i 0.0.0.0:port /h thumbprint /g "GUID"

 

You can also use the corresponding tools to view the mapping afterwards.

 

3. Set an URL ACL for the listening URI

The last step is to set an URI ACL on the listening URI for the service account (see also here). You should use the base address of the service as the entry URI.

Again there are two tools to accomplish this. On Vista use e.g.:

netsh http add urlacl url=https://+:port/Services user=authority\serviceaccount

For pre-Vista use e.g. this tool.

 

4. Configure the WCF service

The WCF service has to be configured for transport security (and an https protocol moniker) and for the base address you ACLed in step 4. My config looks like this:

<system.serviceModel>
  <services>
    <service name="Service"
             behaviorConfiguration="md">
      <endpoint address="SslService"
                binding="basicHttpBinding"
                bindingConfiguration="security"
                contract="IService" />

      <host>
        <baseAddresses>
          <add baseAddress="https://WcfSelfHost:4433/Services" />
        </baseAddresses>
      </host>
    </service>
  </services>

  <bindings>
    <basicHttpBinding>
      <binding name="security">
        <security mode="Transport">
          <transport clientCredentialType="Basic" />
        </security>
      </binding>
    </basicHttpBinding>
  </bindings>

  <behaviors>
    <serviceBehaviors>
      <behavior name="md">
        <serviceMetadata httpsGetEnabled="true" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

 

HTH


WCF
Friday, January 04, 2008 10:57:24 AM UTC  #   
 Thursday, January 03, 2008
 Thursday, December 27, 2007

The Future of the ASP.NET InfoCardSelector Control

From now on I will make all updates to the control code available via this link.

The zip contains the control itself plus the code for the demo site you can find here. I will also include a changelog in the near future.

I thought about moving the control to Codeplex - would anyone be interested in contributing? What features would you like to see?


CardSpace
Thursday, December 27, 2007 9:52:24 AM UTC  #   
 Wednesday, December 19, 2007

Demo Site for InfoCardSelector ASP.NET Control

I have uploaded a mini site to test the InfoCardSelector ASP.NET control. The site features three options: self-issued cards with and without SSL and a managed card (using the Microsoft FederatedIdentity test STS).

Have fun!

http://www.leastprivilege.com/InfoCardSelector


CardSpace
Wednesday, December 19, 2007 6:23:13 PM UTC  #   
 Sunday, December 16, 2007

Updated InfoCardSelector ASP.NET Control for No-SSL Scenarios

Starting with .NET 3.5, CardSpace does not demand SSL connections anymore (see here). Kim shows how to get this scenario to work with "less than 30 lines of code" (link). This involves setting up the object tag, retrieving the clear text token from the post data and extracting the claims using XPATH. Easy. I think Keith uses the right term for that which is "Quick and Dirty".

Quoting from Kim's screencast: "If interception is not a concern, you can ignore the signature section in the token. The PPID becomes the shared secret between you and the web application."

While I agree with Kim that this (especially for the low security web app scenarios he is talking about) is much better than a password, I still have some comments:

  • using the PPID only brings us back into the world of shared secrets (and that is what we are trying to get away from with InfoCards).It is generally agreed that PPIDs only should not be used for authentication (see here). If someone can eavesdrop the connection he could simply create a new token, copy the PPID and impersonate the user (which is of course the same with clear text passwords or cookies over non protected transports).
  • InfoCard tokens include a public key with which the signature can be verified. This public key is a much better identifier for the user. The corresponding private key (which is the real secret) is never transmitted over the wire and much harder to spoof than a PPID.
  • removing SSL also removes all SSL provided security features like man-in-the-middle, spoofing and replay protection. So you can replay tokens anyway. Still - relying on the public key is better because tokens have a limited lifetime. This means you can only replay the complete token for a limited amount of time. The PPID alone can be basically replayed forever (much like a password besides that you cannot change PPIDs once they got compromised).
  • PPIDs are only guaranteed to be unique per site in self-issued cards scenarios. Depending on the STS implementation (e.g. non-auditing or just a bad one) the PPID may be always the same. But in managed card scenarios you should require SSL anyways.

Anyways, the Token class shipping with the ASP.NET InfoCard Toolkit cannot handle No-SSL scenarios out of the box - nor can my InfoCardSelector ASP.NET control (which is based on the Token class). I updated both so that they work fine now.

My control does verify the signature and thus gives you the choice of using the public key, PPID or whatever other combination you like. The programming model stays the same regardless of SSL or not.

Simply add a NoSSL="true" attribute to the control tag (see the DefaultNoSSL.aspx page as an example).

(download)

 


CardSpace
Sunday, December 16, 2007 9:29:37 AM UTC  #   
 Friday, December 07, 2007

Least Privilege, WCF and Named Pipes

Christian ran into an interesting problem with least privileged WCF service accounts and the NetNamedPipeBinding.

 

Bottom line: I can only say it again: develop and test your stuff using a standard user account. Don't do it for me - do it for yourself and make "deployment day" less painful ;)


For Your Favourites | WCF
Friday, December 07, 2007 11:15:45 AM UTC  #   

Extension Methods for AntiXss

Playing around with some C# 3.0 language features, I came up with something which is quite useful if you are doing a lot of web in/output encoding.

The following extension methods wrap the AntiXss library:

public static class Extensions

{

    public static string UrlEncode(this string input)

    {

        return AntiXss.UrlEncode(input);

    }

 

    public static string HtmlEncode(this string input)

    {

        return AntiXss.HtmlEncode(input);

    }

 

    // rest omitted

}

This allows doing something like this:

string input = "<h1>leastprivilege rocks</h1>";

Response.Write(input.HtmlEncode());

AntiXssExtensions.zip (17.19 KB)

 


ASP.NET | Work in Progress
Friday, December 07, 2007 8:36:48 AM UTC  #   
 Sunday, November 25, 2007

Poor Man's File Replication using Robocopy

Robocopy has an option to monitor a directory tree for changes - and if a change occurs robocopy will re-rerun the last copy operation. This way you can have something like a "realtime" backup of data.

Example:

robocopy c:\etc\source e:\source /MIR /MON:1


Work in Progress
Sunday, November 25, 2007 11:30:23 AM UTC  #   
 Friday, November 23, 2007

WCF Usernames over Transport and IIS Hosting

I wrote here that WCF 3.5 now supports usernames over transport security. This does not work for IIS hosted services though.

The problem is this:

When you set the clientCredentialType on the binding to Basic, you also have to enable Basic Authentication in IIS for the .svc file. Otherwise you will get this error:

"Security settings for his service require 'Basic' Authentication but it is not enabled for the IIS application that hosts this service."

But when you enable Basic Auth in IIS, the request will bounce already at the IIS level because the custom credentials don't map to a Windows account. bummer.

That said - you can get Basic Authentication for custom accounts to work in IIS by plugging into the ASP.NET pipeline. I have a proof of concept implementation of that in my book and a complete implementation (including IIS7 UI integration) somewhere on my hard drive. Will post that when I find time.


WCF | Work in Progress
Friday, November 23, 2007 3:30:37 PM UTC  #   

Small change to WCF ServiceAuthorizationManager in 3.5

An often wanted feature for the WCF ServiceAuthorizationManager in 3.0 was to get easy access to the incoming message.

This is now possible in 3.5 - there is a new overload for CheckAccess which passes in the Message object as a ref parameter. done.


WCF | Work in Progress
Friday, November 23, 2007 3:15:55 PM UTC  #   

Authorizing Access to WCF Metadata

This post may be interesting for you because you eiter

  • want to control who has access to a service's metadata
  • retrieving metadata suddenly stops working when adding a ServiceAuthorizationManager to your service

Metadata retrieval requests pass just like normal requests the ServiceAuthorizationManager (if registered). Such requests will have a an action of http://schemas.xmlsoap.org/ws/2004/09/transfer/Get. You can retrieve this value from operationContext.IncomingMessageHeaders.Action.

So if you want to secure your MEX endpoint as described in this post, you can use a ServiceAuthorizationManager to distinguish between metadata requests and "normal" requests to make authorization decisions. If you don't care about MEX security but use an authorization manager, make sure you watch out for the metadata retrieval action, otherwise your clients probably can't update their client proxies.

UPDATE: DrNick has some more alternatives for detecting metadata requests.


WCF | Work in Progress
Friday, November 23, 2007 1:22:48 PM UTC  #   

Securing WCF Metadata

This question came up in a recent consulting gig - so I thought I summarize the options and provide some links.

WCF Metadata Architecture Overview

 

Base Address

The easiest way to expose metadata is to provide a base address and enable metadata publishing in the serviceMetadata behavior. You enable publishing by setting the http(s)GetEnabled attribute to true. When it comes to security, your options are limited. You won't get any authentication this way - but you can enforce SSL.  You can also specify the publishing URL which might solve the security problem at an infrastructure level.

This option only allows to retrieve metadata via HTTP.

How to: Secure WCF Metadata Endpoints

 

MEX endpoint

A more general pupose approach is to expose a Metadata Exchange (MEX) endpoint. This endpoint has to implement the (pre-defined) IMetadataExchange interface and is typically used with a mex* binding (for HTTP(S), TCP and NamedPipes) e.g.

<endpoint address="mex"

          binding="mexHttpBinding"            
           contract
="IMetadataExchange" />

There are also situations where MEX endpoints are required, e.g. WCF needs them to query policy when issued tokens and STSes are used.

The mex* bindings don't support security but you can expose metadata over arbitrary bindings as long as you use the IMetadataExchange contract. This gives you the full WCF security feature set.

The only problem is that svcutil.exe will not work out of the box with other bindings besides mex*. To make this work you would have to modify the svcutil.exe.config file in the framework directory. If you want to programmatically retrieve the metadata you have to configure the client channel accordingly.

If you want to secure a MEX endpoint, either use a secure binding or put the endpoint on some uri/port that is not generally reachable.

Secure MEX Endpoints

Retrieving Metadata

 

Metadata via WMI

Another way to retrieve metadata from a WCF service is via WMI. This is totally indepent from the base address, the serviceMetadata behavior or any MEX endpoints. By ACLing the WMI namespace in the computer management MMC snap-in you can control who has access to the WMI objects.

To enable WMI publishing you have to add this fragment to your serviceModel config:

<diagnostics wmiProviderEnabled="true" />

To retrieve the metadata, this code snippet should get you started:

private static void DumpAllMetadata()

{

    ManagementClass mc =

      new ManagementClass(@"\root\ServiceModel:Service");

 

    ManagementObjectCollection mos = mc.GetInstances();

 

    foreach (ManagementObject mo in mos)

    {

        Console.WriteLine("\n"+ mo["Name"] + "\n");

 

        foreach (string s in (string[])mo["Metadata"])

        {

            Console.WriteLine("more...");

            Console.ReadLine();

            Console.WriteLine(s);

        }

    }

}

 


WCF | Work in Progress
Friday, November 23, 2007 12:54:43 PM UTC  #   
 Tuesday, November 20, 2007

STS? Coming soon!

Vittorio has a sneak peek of the upcoming identity framework up on his blog.

Like many others I wrote my own STS (or tried to) - and I am happy that in the future this tedious work will be the job of some base class to derive from.

I have also seen Vittorio's talk at TechEd and have seen some more good upcoming stuff. In the meanwhile you can still download my InfoCard ASP.NET control to make things easier ;)


CardSpace | For Your Favourites | WCF
Tuesday, November 20, 2007 12:04:28 PM UTC  #   
 Thursday, November 01, 2007

WCF and SecurityAccessDeniedException

When you return false from the ServiceAuthorizationManager's CheckAccessCore method, WCF sends a special fault message back to the client. The logic looks more or less like this:

private Exception CreateAccessDeniedFault()
{
  FaultCode code = FaultCode.CreateSenderFaultCode(
    "FailedAuthentication",
    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");

    FaultReasonText faultText = 
new FaultReasonText("Access is Denied.", CultureInfo.CurrentCulture);
return new FaultException(new FaultReason(faultText), code); }

This fault message gets turned into a SecurityAccessDeniedException in the (WCF) client that you can catch.

Now maybe you are also doing authorization from within your service operation and wanna return the same fault in case access is denied (and you don't want to come up with some custom fault which would mean you have to look for either the access denied exception *or* some fault exception). How does that work?

Well - first I used the above code to handcraft the fault but it turns out that it is much simpler.

The only thing you have to do is to throw a SecurityException from your operation. doh.


WCF | Work in Progress
Thursday, November 01, 2007 12:56:49 PM UTC  #   
 Wednesday, October 31, 2007

Finally! Usernames over Transport Authentication in WCF

Sometimes you have to wonder why the most basic features are missing in a v1 product...

Imagine this scenario: You have a public facing web service. You want the widest possible reach and compatibility - so the perfect technologies for that are HTTP, SSL and basic authentication (usernames and passwords) - and your customer accounts are no Windows accounts.

There was no sane way to pull this off in WCF v1 (besides fiddling around with the HTTP pipeline and simulating the basic auth handshake - but that tied you to IIS/ASP.NET).

You may say now - isn't that exactly what TransportWithMessageCredential is supposed to do? Not exactly - because this involves sending a basic WS-Security SOAP header inside of the message. I want simple HTTP basic auth....

(Christian and me had this very scenario at a customer just a few weeks ago...)

The good news is that WCF in .NET 3.5 will finally support this!

How does that work?

a) use transport security (e.g. basicHttpBinding)

<bindings>
  <basicHttpBinding>
    <binding name="secureBasic">
      <security mode="Transport">
        <transport clientCredentialType="Basic" />
      </security>
    </binding>
  </basicHttpBinding>
</bindings>

b) write a username/password validator

public class CustomUsernamePasswordValidator : UserNamePasswordValidator
{
    public override void Validate(string userName, string password)
    {
        if (!AuthenticateUser(userName, password))
            throw new SecurityTokenValidationException("...");
    }
}

c) hook up the validator in a serviceCredentials behavior

<behaviors>
  <serviceBehaviors>
    <behavior name="userName">
      <serviceCredentials>
        <userNameAuthentication customUserNamePasswordValidatorType="..."
                                userNamePasswordValidationMode="Custom" />
      </serviceCredentials>
    </behavior>
  </serviceBehaviors>
</behaviors>

d) send a username/password using the client proxy

ChannelFactory<IService> cf = new ChannelFactory<IService>("Service");
cf.Credentials.UserName.UserName = "bob";
cf.Credentials.UserName.Password = "foo";

IService proxy = cf.CreateChannel();
Console.WriteLine(proxy.Ping());

((IClientChannel)proxy).Close();

WCF | Work in Progress
Wednesday, October 31, 2007 1:51:19 PM UTC  #   

Does Microsoft regret the Security Push?

Well - at least parts of it - but this did get your attention, right?

Not sure what to think about that.

Remember the ILoveYou virus? It replicated itself to file shares to spread across intranets.

I think the argument that unmanaged code is not affected anyways doesn't count - we have a chance to make it better in the future (see also here).

I'd rather like to see a poll on how people would like the idea to finally be able to deploy CAS policies in a sane way...

What are your feelings about that?


Work in Progress
Wednesday, October 31, 2007 7:42:49 AM UTC  #   
 Thursday, October 25, 2007

Try XSSDetect

Exciting things are happening over at the ACE team at Microsoft.

One is XSSDetect - a Visual Studio plugin that analyzes your code to find potential XSS vulnerabilities. Mark also gives a sneak preview of other upcoming tools. Interesting!

 


For Your Favourites
Thursday, October 25, 2007 6:04:41 AM UTC  #   
 Tuesday, October 23, 2007

ValidateRequest does not mitigate XSS completely

I often mentioned that to customers and students. Here is the "official" word.

 


ASP.NET | For Your Favourites
Tuesday, October 23, 2007 9:02:29 AM UTC  #   
 Wednesday, October 10, 2007

.net@movies Episode 1 - Web Security

Am 17.Dezember findet die erste Veranstaltung der brandneuen .net@movies Serie des ProDev Colleges statt.

Bei diesem ersten Event geht es um ein Thema, das mir schon lange sehr am Herzen liegt: Web Security.

Mein geschätzer Kollege Christian Wenz und ich werden an diesem abwechslungsreichen Tag über Angriffe, Verteidigungsmaßnahmen, ASP.NET Sicherheitsfeatures und Web 2.0 Security reden.

Das ganze wird durch ein nettes Abendprogramm abgerundet ;)

Die Details findet Ihr hier.

Vielleicht sieht man sich ja!

 


Microsoft Deutschland Security Portal
Wednesday, October 10, 2007 7:09:23 AM UTC  #   
 Thursday, October 04, 2007

Sichere Software mit Microsoft .NET entwickeln

...ist der Titel eines neuen Buches vom Entwickler.Press Verlag.

Darin könnt Ihr eine Artikelsammlung von Autoren wie Michael Howard, Steve Lippner, Christian Wenz, Darius Parys und mir finden.

Behandelt werden Themen wie Windows, ASP.NET, .NET und WCF Security. Das ganze ist abgerundet mit ein paar interessanten Interviews...

Zuschlagen!

 


Microsoft Deutschland Security Portal
Thursday, October 04, 2007 7:20:23 AM UTC  #   

TechEd:Developer 2007 Security Track

TechEd:Developer in Barcelona this year will be the first TechEd ever that has a "physical" security track. That's great.

There are great speakers and interesting sessions on this track, e.g. Michael Howard (SDL, Threat Modeling), Keith Brown (identity, claims and ADFS), Mike Downen (Silverlight and 3.5 security), Rafal Lukawiecki (crypto and Vista security), Chris Chorio (UAC and WIM for developers), Vittorio Bertocci (CardSpace) and Alik Levin (web security).

I'll add some stuff about my favourite topics: WCF security and CardSpace.

Also join Keith and me for the panel discussion on CardSpace and the Identity Metasystem if you are around. Should be fun.

I am excited. Thanks to Sebastian for making this happen!!!

 


Conferences
Thursday, October 04, 2007 7:13:31 AM UTC  #   
 Thursday, September 27, 2007

Details of CardSpace RP Identity Generation

Ever wondered how exactly the RP identity is "calculated" for EV and non EV certs? Get the details here.

Just search for "OrgIdBytes".

 


For Your Favourites
Thursday, September 27, 2007 9:01:45 AM UTC  #   
 Tuesday, September 25, 2007

CardSpace in 3.5 doesn't require SSL

Important change to CardSpace in .NET 3.5 - read the details here.

 


For Your Favourites
Tuesday, September 25, 2007 1:29:39 PM UTC  #   
 Sunday, August 26, 2007

Certificate based Authentication and WCF (Mode independent)

My third approach for restricting trust when using client certificates works for transport and message security. Furthermore it does not involve any OS level configuration.

WCF has a piece of plumbing called the Service Authorization Manager. The SAM gets called on every incoming request and at this point the claim sets are already populated. In the issuer claim set of the client, you can find the thumbprint of the issuing CA (you can also find the thumbprint of the end certificate of course).

With that information you can apply the same logic as in the certificate validator I showed earlier.

Writing a SAM involves deriving from a class called ServiceAuthorizationManager and implementing the CheckAccessCore method. The logic looks like this:

protected override bool CheckAccessCore(OperationContext operationContext)
{
    string[] trustedThumbprints = GetTrustedThumbprints();
    string[] thumbprints;

    if (ValidationMode == ValidationMode.EndCertificate)
    {
        thumbprints = new string[] { 
            GetEndCertificateThumbprint(operationContext) };
    }
    else
    {
        thumbprints = GetIssuerThumbprints(operationContext);
    }

    return IsTrusted(thumbprints, trustedThumbprints);
}

To get the thumbprint from the claim set, you simply search for a thumbprint identity claim. One thing to note here is that the thumbprint gets stored as a byte[]. You can use the BitConverter class to convert that into a string, e.g.

private string GetEndCertificateThumbprint(OperationContext operationContext)
{
    foreach (ClaimSet set in 
operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets) { foreach (Claim claim in set.FindClaims(ClaimTypes.Thumbprint, Rights.Identity)) { string tb = BitConverter.ToString((byte[])claim.Resource); tb = tb.Replace("-", ""); return tb; } } throw new Exception("No thumbprint claim found"); }
The last step is to register the SAM in the serviceAuthorization behavior:
<behaviors>
  <serviceBehaviors>
    <behavior name="behavior">
      <serviceAuthorization serviceAuthorizationManagerType="type" />
    </behavior>
  </serviceBehaviors>
</behaviors>

Be aware that the SAM gets called on every request. Make sure the logic is as inexpensive as possible.

In the download you can find a SAM base class which follows the same design pattern as the certificate validator. Just override the GetTrustedThumbprints method.

CertAM.zip (51.86 KB)

 


WCF | Work in Progress
Sunday, August 26, 2007 1:52:54 PM UTC  #   

Certificate based Authentication and WCF (Transport Security)

When using SSL you need to set up a Certificate Trust List (CTL) for the listener port. In the CTL you can define which issuers to trust. CTLs are very low level and are used during the inital SSL handshake. If the incoming certificate is not trusted, the connection gets refused already at the protocol level.

The whole process is straightforward if you are hosting in IIS. Simply edit the "Secure Communication" settings for the site. Since SSL (and thus CTLs) is TCP port based, you can only edit theses settings at the site level.

When self hosting, you have to do the following:

  1. Define the CTL. In the SDK you can find a tool called MakeCTL.exe which helps you with that. At the end of this process MakeCTL allows to store the CTL in the Windows certificate store. You will get a GUID as the identifier for the stored CTL.
  2. Use httpcfg.exe or netsh.exe (when on Vista/Server 2008) to assign the CTL from the store to the SSL endpoint (you need that GUID here again to make the link)

While this is the "right" way of restricting trust with transport security, you have some additional deployment steps. In the next post I show you a way that is security mode independent - but also slightly less efficient.


WCF | Work in Progress
Sunday, August 26, 2007 1:38:42 PM UTC  #   

Good Article on System.IdentityModel

Keith wrote a good intro to the identity model APIs in .NET 3.0 here. Recommended.

A lot of people think that System.IdentityModel is somehow tied to WCF - this is not the case - it is a general API which happens to be nicely integrated into WCF - but you can use it in arbitrary applications - try this to get you started:

WindowsClaimSet windows =
  new WindowsClaimSet (WindowsIdentity.GetCurrent());

Have fun!

 


For Your Favourites | WCF
Sunday, August 26, 2007 9:38:37 AM UTC  #   
 Saturday, August 25, 2007

Certificate based Authentication and WCF (Message Security)

When using message security, the intended way to validate an incoming credential (== token) is a token validator. You can find several internal validators in the System.IdentityModel.Selectors namespace (e.g. for UserName, X.509 or Windows tokens). The X509 token validators gets called whenever an incoming certificate has to be validated - when you have secure conversation enabled, this happens only on the first request which makes this approach very efficient.

WCF has three builtin validators for X.509 certificates and you can choose which one to use via a service/endpoint behavior. I will show the service side settings here, but the same switches also exist on the client side.

<behaviors>
  <serviceBehaviors>
    <behavior name="behavior">
      <serviceCredentials>
        <clientCertificate>
          <authentication certificateValidationMode="ChainTrust"
                          revocationMode="Online" />
        </clientCertificate>
      </serviceCredentials>
    </behavior>
  </serviceBehaviors>
</behaviors>

The certificateValidationMode specifies how incoming certificates are validated and how trust is determined:

  • None. No validation is performed. Not recommended.
  • ChainTrust. The certificate has to chain up to one of the CAs in your trusted CA certificate folder.
  • PeerTrust. The incoming certificate has to be in the Trusted People certificate folder.
  • PeerOrChainTrust. A combination of Chain and Peer trust.
  • Custom. For all other cases.

The trustedStoreLocation attribute determines whether the current user or local machine store is used for peer or chain checks - defaults to local machine. Furthermore you specify how revocation lists should get checked via the revocationMode attribute (no check, offline or online).

See this post for a more detailed description of the validation modes. Also, as mentioned in that post, the standard validation modes are mostly useful in niche situations.

This is where the Custom mode comes into play. With this mode it is your responsibility to validate the certificate following your own guidelines. You then make decisions if you want to accept the certificate.

A custom certificate validator involves deriving from X509CertificateValidator and implementing the Validate() method. The WCF plumbing passes the incoming certificate into this method. If you want to reject the certificate you throw a SecurityTokenValidationException inside Validate().

So far so good - now, which steps are involved to validate a certificate? Before you can rely on any information in the cert, you have to make sure it's valid - typcially by checking it is not expired or revoked and is issued by a trusted CA. Afterwards you can check certain properties of the certificate or its issuer to further restrict the allowed certs. Another approach would be - like PeerTrust - to check the certificate against a list of allowed certificates.

For checking the trust chain, you use the X509Chain class - the general logic of Validate() looks like this:

public override void Validate(X509Certificate2 certificate)
{
    // create chain and set validation options
    X509Chain chain = new X509Chain();
    SetValidationSettings(chain);

    // check if cert is valid and chains up to a trusted CA
    if (!chain.Build(certificate))
    {
        throw new SecurityTokenValidationException(
"Client certificate is not valid"); } // check if cert is from our trusted list if (!IsTrusted(chain, GetTrustedThumbprints())) { throw new SecurityTokenValidationException(
"Client certificate is not trusted"); } }

How the certificate should be exactly validated can be specified on the ChainPolicy property of X509Chain. Besides the configurable revocation mode, WCF uses the default settings for the chain policy, which are:

protected override void SetValidationSettings(X509Chain chain)
{
    chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
    chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
    chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
    chain.ChainPolicy.VerificationTime = DateTime.Now;
    chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 0);
}

My implementation of IsTrusted then checks if either an issuer or a the end certificate itself (specified by the ValidationMode property) is in a trust list. The check is done by comparing the thumbprint of the certificate in question against a list.

protected virtual bool IsTrusted(X509Chain chain, string[] trustedThumbprints)
{
    int depth = 0;

    if (ValidationMode == ValidationMode.EndCertificate)
    {
        // only check the end certificate
        return CheckThumbprint(chain.ChainElements[0].Certificate, trustedThumbprints);
    }
    else
    {
        // check the rest of the chain
        foreach (X509ChainElement element in chain.ChainElements)
        {
            if (++depth == 1)
            {
                continue;
            }

            if (CheckThumbprint(element.Certificate, trustedThumbprints))
            {
                return true;
            }
        }
    }

    return false;
}
The last step is to register the validator in a serviceCredentials behavior.
<serviceCredentials>
  <serviceCertificate findValue="Service"
                      x509FindType="FindBySubjectName"
                      storeLocation="CurrentUser"
                      storeName="My" />

  <clientCertificate>
    <authentication certificateValidationMode="Custom"
                    customCertificateValidatorType="type" />
  </clientCertificate>
</serviceCredentials>

In the download you can find a ready to use validator base class from which you can derive from. You just have to implement the GetTrustedThumbprints method and return a string[] of thumbprints. You can get the thumbprints from the certificate UI (just remove the blanks). Have fun!

CertValidator.zip (16.98 KB)

 


WCF | Work in Progress
Saturday, August 25, 2007 3:31:47 PM UTC  #   

Live ID and Information Cards - just good friends...

Read more here and here. finally.

 


For Your Favourites
Saturday, August 25, 2007 10:45:10 AM UTC  #   

Certificate based Authentication and WCF

Certificate based authentication with WCF has two components - configuring credentials and determining trust.

The first part is easy - you simply set the clientCredentialType in the binding's security configuration to Certificate. This means that WCF will demand that the client sends a certificate along with the (first) request - either as a WS-Security X509 token or using SSL client certificates (depending on the security mode this also means that this requirement becomes part of the WSDL/Policy).

Furthermore the "plumbing" (either SSL or WCF message security) will make sure that the incoming certs are technically valid. This includes making sure that the certificate is not malformed and that the signature matches the public key. If this is not the case, the request gets rejected at a very low level and usually your service code would never get invoked.

At this point you have a technically valid certificate - but this does not necessarily mean that you also trust that certificate. The default validation strategy for certificates (regardless of message vs. transport) is called Chain Trust, this means:

  • the certificate must be issued from a CA in your trusted CA list (in the machine certificate store)
  • this intended purpose of that CA must include "Client Authentication"
  • the current date/time must be within the certificate's validitiy period

With message security you also get a mode called Peer Trust. In this mode WCF simply checks if the incoming certificate is installed in the Trusted People folder in the certificate store (the expiration time is checked too).

When to use which mode?

Peer Trust
Since the Trusted People folder must hold all allowed certificates, this means that the service (or the client) has to know every peer a priori. This is OK if you have a limited amount of certificates that you want to allow - but does not scale very well. If you have more than one service on a machine and want to use peer trust, you have to run them under different accounts and use the current user certificate store to separate the "allowed" list.

Chain Trust
Chain trust is for scenarios where you don't know every certificate explicitly, but want to establish trust based on the issuer. The problem you have here is, that by default the trusted CA list is quite, errm, extensive. It includes popular CAs like VeriSign and not so popular ones (which I never heard before). So using chain trust it is quite easy to get a certificate that would be trusted by your service.

So in a lot of cases peer trust is not enough and chain trust is too much. What you typically want to have is first validating the trust chain and afterwards restrict to a specific CA or specific properties in the client cert. How do you accomplish that? Well - it depends ;)

In classic SSL transport security this is accomplished by using a Certificate Trust List (CTL). The moral equivalent in message based security is a X509 Certificate Validator. A third approach (which works in both modes) would be a WCF Service Authorization Manager.

In the next posts I will have a look at these technique and show you the up/downsides of each. Stay tuned.


Work in Progress
Saturday, August 25, 2007 7:17:12 AM UTC  #   
 Monday, August 20, 2007

Guidance on User and Password Management

The ACE blog has a good checklist on the above topic. Something to have around when implementing the next password based system.

 


For Your Favourites
Monday, August 20, 2007 8:07:56 AM UTC  #   
 Thursday, August 09, 2007

Extend Your WCF Services Beyond HTTP With WAS

..is the title of an article by Christian, Steve and me. Online and in the September issue of MSDN Magazine. Enjoy.

 


For Your Favourites
Thursday, August 09, 2007 7:17:24 AM UTC  #   
 Wednesday, August 08, 2007

Custom Principals and WCF

The question how to setup a custom principal in WCF services comes up every once in a while. Since it is not obvious how this works, I knocked up a little walkthrough and a boilerplate sample.

Principal Permission Mode
WCF has two authorization systems - roles-based and claims-based. Roles-based security is centered around an IPrincipal implementation placed on Thread.CurrentPrincipal. What WCF puts on T.CP depends on the principalPermissionMode attribute in the serviceAuthorization behavior. There are four options:

  • None. T.CP will contain a GenericPrincipal with no roles set.
  • UseWindowsGroups. T.CP will contain a WindowsPrincipal. IsInRole queries the Windows token for group membership. Only supported for Windows authentication.
  • UseAspNetRoles. T.CP will contain a RoleProviderPrincipal. The IsInRole method calls the role provider's IsUserInRole implementation.
  • Custom. You have to set your own principal implementation.

Authorization Policies
The place to set a custom principal implementation is a class implementing the IAuthorizationPolicy interface. Generally, authorization policies are used to add claim sets to the authorization context, but in this scenario the policy has the special purpose of setting the principal. You will also get a runtime exception when the principalPermissionMode is set to custom and you don't set a principal.

The IAuthorizationPolicy interface has three member that you have to implement:

  • Id. You typically return a unique ID here, e.g. a GUID.
  • Issuer. This returns a claim set describing who has added the policy information.
  • Evaluate. You create and set the custom principal here.

The way to get to the authenticated identity and the principal in the Evaluate method is a little "hidden". The evaluation context that gets passed into Evaluate contains a collection named Properties. This collection has two well known keys called Identities and Principal (see also here).

To get to the client identity you have to extract an IList<IIdentity> from this collection:

private IIdentity GetClientIdentity(EvaluationContext evaluationContext)
{
    object obj;
    if (!evaluationContext.Properties.TryGetValue("Identities", out obj))
        throw new Exception("No Identity found");

    IList<IIdentity> identities = obj as IList<IIdentity>;
    if (identities == null || identities.Count <= 0)
        throw new Exception("No Identity found");

    return identities[0];
}

Based on that identity you typically create your custom principal and set it back to the properties collection. The WCF plumbing takes the principal from there and sets it on T.CP.

The following authorization policy combines the groups of a Windows user with application defined roles and creates a new principal that can be queried in the service implementation using Thread.CurrentPrincipal.IsInRole() or PrincipalPermission[Attribute]. The custom principal that is used here is omitted but can be found in the download (it simply mimics the GenericPrincipal, but allows to get back the roles using the Roles property - which can be handy and is a limitation of GenericPrincipal IMO).

class CombinedRolesPolicy : IAuthorizationPolicy
{
    Guid id = Guid.NewGuid();

    public bool Evaluate(EvaluationContext evaluationContext, ref object state)
    {
        // will hold the combined roles
        List<string> roles = new List<string>();

        // get the authenticated client identity
        IIdentity client = GetClientIdentity(evaluationContext);

        // this policy is intended for Windows accounts only
        WindowsIdentity windowsClient = client as WindowsIdentity;
        if (windowsClient == null)
        {
            throw new SecurityTokenValidationException(
"Only Windows accounts supported"); } // add Windows groups roles.AddRange(GetWindowsRoles(windowsClient)); // add application defined roles roles.AddRange(GetAppRoles(windowsClient)); // set a new principal holding the combined roles // this could be your own IPrincipal implementation evaluationContext.Properties["Principal"] = new CustomPrincipal(windowsClient, roles.ToArray()); return true; } private IEnumerable<string> GetWindowsRoles(WindowsIdentity windowsClient) { List<string> roles = new List<string>(); IdentityReferenceCollection groups =
windowsClient.Groups.Translate(typeof(NTAccount)); foreach (IdentityReference group in groups) { roles.Add(group.Value); } return roles; } private IEnumerable<string> GetAppRoles(WindowsIdentity windowsClient) { List<string> roles = new List<string>(); // simulated database access roles.Add("MyApplicationRoleA"); roles.Add("MyApplicationRoleB"); return roles; } public System.IdentityModel.Claims.ClaimSet Issuer { get { return ClaimSet.System; } } public string Id { get { return id.ToString(); } } }

The last step is to register the authorization policy with your service:

<behaviors>
  <serviceBehaviors>
      <serviceAuthorization principalPermissionMode="Custom">
        <authorizationPolicies>
          <add policyType="LeastPrivilege.CombinedRolesPolicy, CustomPrincipalService" />
        </authorizationPolicies>
      </serviceAuthorization>
    </behavior>
  </serviceBehaviors>
</behaviors>

In you service implementation, you can use the usual techniques to make role based checks, or you cast the principal to its concrete type to use some specialized features you have implemented (see the sample code). Have fun.

WcfCustomPrincipal.zip (24.83 KB)

 


Work in Progress
Wednesday, August 08, 2007 1:20:59 PM UTC  #   
 Tuesday, July 24, 2007

InfoCardSelector for ASP.NET V1.0

OK - so I settled for a final name (since the control also works for other identity selectors than CardSpace this is more appropriate) and resetted the version.

There are only minor (but breaking) changes to the last version I posted:

  • Changed the name to <lp:InfoCardSelector />
  • Incorporated the official token decryption class from the ASP.NET Toolkit
  • Added an AutoDecryptToken property. If set to true the control will decrypt the token an pass the Token instance back to the page via the event handler
  • Added a BSD license

I also made some minor changes to the Token class to retrieve the public key hash of the issuer without combining it with the PPID.

InfoCardSelectorV1.zip (100.47 KB)

 


Tools | Tools for Thinktecture | Work in Progress
Tuesday, July 24, 2007 7:06:57 AM UTC  #   
 Saturday, July 21, 2007

Excellent ASP.NET Information

Daniel currently publishes an internal Microsoft feed on his blog. Excellent information about ASP.NET internals like process models, dynamic compilation and the pipeline (more to come).

Recommended!

 


For Your Favourites
Saturday, July 21, 2007 4:42:48 AM UTC  #   
 Thursday, July 12, 2007

Windows Integrity Mechanism

Another very interesting security feature (even revolutionary for Windows) in Vista/2008 is mandatory access control (as opposed to discretionary access control).

Michael just posted the link to the new technical reference. Enjoy.


For Your Favourites
Thursday, July 12, 2007 7:49:50 AM UTC  #   

Windows Service Hardening

I am pretty impressed with the changes and new features for good old NT Services in Vista/Server 2008.

My favourite feature is, that every service can now have a distinct SID that can be used to ACL kernel objects. Imagine you have to deploy a bunch of services that have to be isolated from each other. Pre-Vista you would have to create an account for each service. Now you can simply use the new NT Service\ServiceName SID to control authorization. Very neat.

Even better the SIDs for services are deterministic across machines (S-1-5-80-SHA1(Servicename) to be exact), and you can also pre-calculate the SID even when the service doesn't exist yet:

sc showsid FooService

This makes it very easy in server farms to copy data *and* ACLs (e.g. using robocopy) between nodes...

Additionally you can write-restrict the service tokens - that means that the service won't have write access to any kernel object unless the service SID is explicitly part of the ACL.

Other nice features are that services no longer share the same desktop with interactive users and that you can bundle network access rules with services. These rules are independent from the Windows firewall and would even work if the firewall is disabled.

Nice least privilege work, guys!

Voy has written a series of blog posts about the new features (recommended reading):


Work in Progress
Thursday, July 12, 2007 7:00:12 AM UTC  #   
 Wednesday, July 11, 2007

New WCF Security Samples

Justin wrote me an email this morning:

"Dominick,

I hope you are well.

There are several new WCF-security samples posted at http://wcf.netfx3.com/files/53/default.aspx (today)

I thought you might be interested, especially if you are planning on writing that WCF security book…"

That's great news! They all look very interesting!

Thanks for getting me into trouble, Justin ;)

 


For Your Favourites
Wednesday, July 11, 2007 6:38:04 AM UTC  #