devshorts
devshorts

Reputation: 8872

NullReferenceException in Microsoft.Web.Administration when adding https binding

I'm trying to programmatically add a binding to my default website however I am consistently getting a null reference exception within the Microsoft.Web.Administration dll. Originally I wanted to assign a certificate along with the binding. I was able to query the certificate that I wanted with this:

var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);

var certificate = store.Certificates.Find(X509FindType.FindByIssuerName,
                                                  "TEST_SELF_SIGNED", true)
                                                  .OfType<X509Certificate>().FirstOrDefault();

This properly gave me the certificate I wanted, it was non-null and had the information I expected.

Site site = GetSite("Default Web Site");
var binding = site.Bindings.Add("*:443", certificate.GetCertHash(), "https");

Given that none of my variables or any other items in the sample code are null (including GetCertHash which returns a 20 byte array) I'm confused as to why I'm getting a null here. I even tried the following overload:

site.Bindings.Add("*:443", "https");

And I still get the same null ref stack:

System.NullReferenceException was unhandled
  Message=Object reference not set to an instance of an object.
  Source=Microsoft.Web.Administration
  StackTrace:
       at Microsoft.Web.Administration.Configuration.SetDirty()
       at Microsoft.Web.Administration.ConfigurationElement.SetDirty()
       at Microsoft.Web.Administration.ConfigurationElement.SetAttributeValue(String attributeName, Object value)
       at Microsoft.Web.Administration.Binding.SetBindingProperty(String attributeName, String value)
       at Microsoft.Web.Administration.BindingCollection.Add(String bindingInformation, Byte[] certificateHash, String certificateStoreName)
       at TestApp.Program.Main(String[] args) in C:\Projects\Cube\trunk\src\AutoUpdate\TestApp\Program.cs:line 33
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 

Here is a full test-app that demonstartes the issue, along with the selfssl command line arguments I used to generate the sample certificate:

selfssl.exe /T /N:CN=TEST_SELF_SIGNED /K:512 /V:9999 /Q

class Program
{
    static void Main(string[] args)
    {

        using (ServerManager manager = new ServerManager())
        {
            var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
            store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);

            var certificate = store.Certificates.Find(X509FindType.FindByIssuerName, "TEST_SELF_SIGNED", true).OfType<X509Certificate>().FirstOrDefault();

            Site site = GetSite("Default Web Site");
            site.Bindings.Add("*:443", certificate.GetCertHash(), store.Name);

            store.Close();

            manager.CommitChanges();
        }
    }

    public static Site GetSite(string siteName)
    {
        using (var serverManager = new ServerManager())
        {
            return serverManager.Sites.Where(p => p.Name.ToLower() == siteName.ToLower()).FirstOrDefault();
        }
    }
}

Just to cover my bases, Iis is installed and manually assigning the certificate works just fine.

Upvotes: 8

Views: 3059

Answers (1)

devshorts
devshorts

Reputation: 8872

So I found the answer by decompiling the Microsoft.Web.Administration dll and poking through the stack. It turns out that if you get a Site with a helper function it doesn't set the internal ServerManager property on the site.

The function the dll that caused the issue was this in Microsoft.Web.Administration::Configuration

internal void SetDirty()
{
  if (this._hasBeenCommitted || this._configurationManager.Owner.ReadOnly)
    throw new InvalidOperationException(Resources.ObjectHasBeenCommited);
  this._isDirty = true;
}

The only thing that could have been null here was either _configurationManager or _configurationManager.Owner. I checked what Owner was and it was a ServerManager which tipped me off that I should probably query the Site from within a using block of server manager. Once I did that the null ref went away and everything worked. It's unfortunate that they aren't checking for null's but maybe the assumption is nobody would ever act on a site object without the server manager context.

Anyways, here is the updated code:

class Program
{
    static void Main(string[] args)
    {

        using (var serverManager = new ServerManager())
        {
            var selfSignedCnName = "TEST_SELF_SIGNED";
            var websiteName = "Default Web Site";

            var site = serverManager.Sites.Where(p => p.Name.ToLower() == websiteName.ToLower()).FirstOrDefault(); 
            var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
            store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);

            var certificate = store.Certificates.Find(X509FindType.FindByIssuerName, selfSignedCnName, true).OfType<X509Certificate>().FirstOrDefault();

            site.Bindings.Add("*:443:", certificate.GetCertHash(), store.Name);

            store.Close();

            serverManager.CommitChanges();
        }
    }

}

It's also clear from my initial post that wrapping the entire code block in a server manager doesn't mean anything, they aren't cascaded. You have to act on the site from the server manager it came from.

Upvotes: 11

Related Questions