Thursday, April 17, 2014

SPTrustedRootAuthority -- Most important internal class for Claims Based Authentication

In wisdom unknown to me the SPTrustedRootAuthority (TRA) class and its manager SPTrustedRootAuthorityManager (TRA manager, both in Microsoft.SharePoint.Administration) are internal and sealed.  Big statement, probably nobody cares but me, so hear me out.  What if you wanted to add on to Central Administration so that CBA could be maitained 100% through the UI?  Can't do it with the classes on hand, because we can't link directly to those classes.

SharePoint maintains an internal Certificate Authority (CA) which just happens to be managed by the SPTrustedRootAuthorityManager.  Microsoft graciously provided us with a form in Central Admin and Power Shell commands to work with the list of certificates (see get-command -noun SPTrustedRootAuthority), but the only way to work one is through then PS command scripts.  This is OK, but it seems like SharePoint team took the easy way out and didn't finish the GUI in Central Admin.

So what's a boy (or girl for that matter) to do?  I went and found all of the PS Cmdlets that parallel the steps in the Claims Walkthrough, just so I wouldn't have to write a Forms app just to setup the SPTrustedLoginProvider.   That was great for me, but I'm a consultant these days and I need to get my clients up and running on this stuff.  If I can barely remember the sequence of command, how could they.  Especially when you want to add new Known Claim Values when a new set of secureables comes out.


Well I took it upon myself to create my own user interface to manage CBA trust providers and developed it into a set of 14 hive application pages, in the ADMIN folder (/_admin/TrustConfig, etc.) so that they would only be available through Central Admin and not through a normal site.   Configuring Trust is an administrative task after all...


So everybody knows when you setup a new CBA Trust you do the following:

  1. Get an X.509 certificate w/o the private key attached in a DER file.
  2. Load the certificate into SharePoint's Trusted Authority Manager (CA or PS usually works)
  3. Create new SPTrustedLoginProvider that references the certificate.
There's some subtleties though.  A X.509 certificate can be used for only one SPTrustedLoginProvider at a time.  Off hand I can't remember if it throws an error when you create the provider or if it error when you try and use it.  Either way, I remember the error message isn't very helpful!  The cert has to be loaded in SP's private CA ahead of time too.  So, why doesn't allow you to just reference the cert's Subject & Issuer?  May because it's important that you have all of the bits in the cert on hand? Maybe, its like the old days, when you couldn't deposit a picture of a check, you had to hand it over to the teller.

Anyway, that was only one problem, when you first setup the Login Provider.  You need to come back to one and update the Known Claim Values, maybe add a new Claim Type now and again too.  But try describing that over the phone (you may be better at than I am).

Solution, add those application pages to Central Admin.  But you'll need to use some reflection magic to get a hold of that data.  Now if you review my code I'm about to post, you may notice that there may have been more direct ways to get to the data, like accessing the TRA manager directly, or calling the "Certificate" property on the TRA.  I'm not saying my code is perfect, but it got me there.

What, you're about to see, is what the GOF would call Adapter Classes.  The first TrustedRootAuthority wraps the SPTrustedRootAuthority to provide access to the X509Certificate2 and the second, RootAuthority, wraps the SPCmdletGetTrustedRootAuthority Power Shell Cmdlet to get the set of all installed certificates.

These additions don't let you install new certificates, but that wouldn't be too hard once you've gotten this far.  They do allow you to get a hold of all of the installed certs so that you can pick and choose the certificate you want to add to your newly minted SPTrustedLoginProvider.



class TrustedRootAuthority
{
    static Type traType;
    static TrustedRootAuthority() {
       Assembly a = Assembly.Load("Microsoft.SharePoint, Version=14.0.0.0, " 
                              +"Culture=neutral, PublicKeyToken=71e9bce111e9429c");
       traType = a.GetType("Microsoft.SharePoint.Administration.SPTrustedRootAuthority",
                              true, true);
    }

    object tra;
    public TrustedRootAuthority(object tra) {
       this.tra = tra;
    }

    public X509Certificate2 Certificate {
       get {
           return traType.InvokeMember("m_Certificate", BindingFlags.NonPublic | 
                            BindingFlags.GetField | BindingFlags.Instance, null,
                            tra, null) as X509Certificate2;
       }
    }

}
      
class RootAuthority
{
    static ConstructorInfo ctor;
    static MethodInfo rdo;
    static RootAuthority() {
       Assembly a = Assembly.Load("Microsoft.SharePoint.PowerShell, Version=14.0.0.0, "+
                                  "Culture=neutral, PublicKeyToken=71e9bce111e9429c");
       Type t = a.GetType("Microsoft.SharePoint.PowerShell.SPCmdletGetTrustedRootAuthority",
                                  true, true);
       ctor = t.GetConstructor(new Type[0]);
       rdo =  t.GetMethod("RetrieveDataObjects", BindingFlags.NonPublic 
                                     | BindingFlags.Instance);
    }

    private object tra;

    public RootAuthority() {
        tra = ctor.Invoke(null);
    }

    public IEnumerable<TrustedRootAuthority> RetrieveDataObjects() {
        IEnumerable src = rdo.Invoke(tra, null) as IEnumerable;
        List<TrustedRootAuthority> ret = new List<TrustedRootAuthority>();
        foreach (object o in src) {
            ret.Add(new TrustedRootAuthority(o));
        }
        return ret;
    }
}




No comments:

Post a Comment