Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NullReferenceException in Microsoft.Web.Administration when adding https binding

Tags:

c#

iis-7

ssl

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.

like image 923
devshorts Avatar asked Apr 30 '12 14:04

devshorts


1 Answers

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.

like image 102
devshorts Avatar answered Nov 06 '22 16:11

devshorts