Thursday, January 5, 2012

How to work with SSL certificate configuration records via HTTP Server API

Here is the code sample to work with SSL certificate configuration records via HTTP Server API. The HttpApiSslCert class contains methods to add, remove or retrieve bindings between the https port and the SSL certificate.
The comlete sample see on github

  1. internal static class HttpApiSslCert
  2. {
  3.     private static readonly HTTPAPI_VERSION HttpApiVersion = new HTTPAPI_VERSION(1, 0);
  4.  
  5.     #region DllImport
  6.  
  7.     [DllImport("httpapi.dll", SetLastError = true)]
  8.     static extern uint HttpInitialize(
  9.         HTTPAPI_VERSION version,
  10.         uint flags,
  11.         IntPtr pReserved);
  12.  
  13.     [DllImport("httpapi.dll", SetLastError = true)]
  14.     static extern uint HttpSetServiceConfiguration(
  15.             IntPtr serviceIntPtr,
  16.             HTTP_SERVICE_CONFIG_ID configId,
  17.             IntPtr pConfigInformation,
  18.             int configInformationLength,
  19.             IntPtr pOverlapped);
  20.  
  21.     [DllImport("httpapi.dll", SetLastError = true)]
  22.     static extern uint HttpDeleteServiceConfiguration(
  23.         IntPtr serviceIntPtr,
  24.         HTTP_SERVICE_CONFIG_ID configId,
  25.         IntPtr pConfigInformation,
  26.         int configInformationLength,
  27.         IntPtr pOverlapped);
  28.  
  29.     [DllImport("httpapi.dll", SetLastError = true)]
  30.     static extern uint HttpTerminate(
  31.         uint Flags,
  32.         IntPtr pReserved);
  33.  
  34.     [DllImport("httpapi.dll", SetLastError = true)]
  35.     static extern uint HttpQueryServiceConfiguration(
  36.             IntPtr serviceIntPtr,
  37.             HTTP_SERVICE_CONFIG_ID configId,
  38.             IntPtr pInputConfigInfo,
  39.             int inputConfigInfoLength,
  40.             IntPtr pOutputConfigInfo,
  41.             int outputConfigInfoLength,
  42.             [Optional]
  43.                     out int pReturnLength,
  44.             IntPtr pOverlapped);
  45.  
  46.        
  47.     enum HTTP_SERVICE_CONFIG_ID
  48.     {
  49.  
  50.         HttpServiceConfigIPListenList = 0,
  51.         HttpServiceConfigSSLCertInfo,
  52.         HttpServiceConfigUrlAclInfo,
  53.         HttpServiceConfigMax
  54.     }
  55.  
  56.     [StructLayout(LayoutKind.Sequential)]
  57.     struct HTTP_SERVICE_CONFIG_SSL_SET
  58.     {
  59.         public HTTP_SERVICE_CONFIG_SSL_KEY KeyDesc;
  60.         public HTTP_SERVICE_CONFIG_SSL_PARAM ParamDesc;
  61.     }
  62.  
  63.     [StructLayout(LayoutKind.Sequential)]
  64.     struct HTTP_SERVICE_CONFIG_SSL_KEY
  65.     {
  66.         public IntPtr pIpPort;
  67.  
  68.         public HTTP_SERVICE_CONFIG_SSL_KEY(IntPtr pIpPort)
  69.         {
  70.             this.pIpPort = pIpPort;
  71.         }
  72.     }
  73.  
  74.     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  75.     struct HTTP_SERVICE_CONFIG_SSL_PARAM
  76.     {
  77.         public int SslHashLength;
  78.         public IntPtr pSslHash;
  79.         public Guid AppId;
  80.         [MarshalAs(UnmanagedType.LPWStr)]
  81.         public string pSslCertStoreName;
  82.         public uint DefaultCertCheckMode;
  83.         public int DefaultRevocationFreshnessTime;
  84.         public int DefaultRevocationUrlRetrievalTimeout;
  85.         [MarshalAs(UnmanagedType.LPWStr)]
  86.         public string pDefaultSslCtlIdentifier;
  87.         [MarshalAs(UnmanagedType.LPWStr)]
  88.         public string pDefaultSslCtlStoreName;
  89.         public uint DefaultFlags;
  90.     }
  91.  
  92.     [StructLayout(LayoutKind.Sequential, Pack = 2)]
  93.     struct HTTPAPI_VERSION
  94.     {
  95.         public ushort HttpApiMajorVersion;
  96.         public ushort HttpApiMinorVersion;
  97.  
  98.         public HTTPAPI_VERSION(ushort majorVersion, ushort minorVersion)
  99.         {
  100.             HttpApiMajorVersion = majorVersion;
  101.             HttpApiMinorVersion = minorVersion;
  102.         }
  103.     }
  104.  
  105.     [StructLayout(LayoutKind.Sequential)]
  106.     struct HTTP_SERVICE_CONFIG_SSL_QUERY
  107.     {
  108.         public HTTP_SERVICE_CONFIG_QUERY_TYPE QueryDesc;
  109.         public HTTP_SERVICE_CONFIG_SSL_KEY KeyDesc;
  110.         public uint dwToken;
  111.     }
  112.  
  113.     enum HTTP_SERVICE_CONFIG_QUERY_TYPE
  114.     {
  115.         HttpServiceConfigQueryExact = 0,
  116.         HttpServiceConfigQueryNext,
  117.         HttpServiceConfigQueryMax
  118.     }
  119.         
  120.     #endregion
  121.  
  122.     #region Constants
  123.  
  124.     public const uint HTTP_INITIALIZE_CONFIG = 0x00000002;
  125.     public const uint HTTP_SERVICE_CONFIG_SSL_FLAG_NEGOTIATE_CLIENT_CERT = 0x00000002;
  126.     public const uint HTTP_SERVICE_CONFIG_SSL_FLAG_NO_RAW_FILTER = 0x00000004;
  127.     private const uint NOERROR = 0;
  128.     private const uint ERROR_INSUFFICIENT_BUFFER = 122;
  129.     private const uint ERROR_ALREADY_EXISTS = 183;
  130.     private const uint ERROR_FILE_NOT_FOUND = 2;
  131.     private const int ERROR_NO_MORE_ITEMS = 259;
  132.  
  133.     #endregion
  134.  
  135.     #region Public methods
  136.  
  137.     public class SslCertificateInfo
  138.     {
  139.         public byte[] Hash { get; set; }
  140.         public Guid AppId { get; set; }
  141.         public string StoreName { get; set; }
  142.         public IPEndPoint IpPort { get; set; }
  143.     }
  144.  
  145.     public static SslCertificateInfo QuerySslCertificateInfo(IPEndPoint ipPort)
  146.     {
  147.         SslCertificateInfo result = null;
  148.  
  149.         uint retVal;
  150.         CallHttpApi(delegate
  151.                 {
  152.                     GCHandle sockAddrHandle = CreateSockaddrStructure(ipPort);
  153.                     IntPtr pIpPort = sockAddrHandle.AddrOfPinnedObject();
  154.                     HTTP_SERVICE_CONFIG_SSL_KEY sslKey = new HTTP_SERVICE_CONFIG_SSL_KEY(pIpPort);
  155.  
  156.                     HTTP_SERVICE_CONFIG_SSL_QUERY inputConfigInfoQuery =
  157.                         new HTTP_SERVICE_CONFIG_SSL_QUERY
  158.                             {
  159.                                 QueryDesc = HTTP_SERVICE_CONFIG_QUERY_TYPE.HttpServiceConfigQueryExact,
  160.                                 KeyDesc = sslKey
  161.                             };
  162.  
  163.                     IntPtr pInputConfigInfo =
  164.                         Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof (HTTP_SERVICE_CONFIG_SSL_QUERY)));
  165.                     Marshal.StructureToPtr(inputConfigInfoQuery, pInputConfigInfo, false);
  166.  
  167.                     IntPtr pOutputConfigInfo = IntPtr.Zero;
  168.                     int returnLength = 0;
  169.  
  170.                     try
  171.                     {
  172.                         HTTP_SERVICE_CONFIG_ID queryType = HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo;
  173.                         int inputConfigInfoSize = Marshal.SizeOf(inputConfigInfoQuery);
  174.                         retVal = HttpQueryServiceConfiguration(IntPtr.Zero,
  175.                                                                 queryType,
  176.                                                                 pInputConfigInfo,
  177.                                                                 inputConfigInfoSize,
  178.                                                                 pOutputConfigInfo,
  179.                                                                 returnLength,
  180.                                                                 out returnLength,
  181.                                                                 IntPtr.Zero);
  182.                         if (retVal == ERROR_FILE_NOT_FOUND)
  183.                             return;
  184.  
  185.                         if (ERROR_INSUFFICIENT_BUFFER == retVal) // ERROR_INSUFFICIENT_BUFFER = 122
  186.                         {
  187.                             pOutputConfigInfo = Marshal.AllocCoTaskMem(returnLength);
  188.  
  189.                             try
  190.                             {
  191.                                 retVal = HttpQueryServiceConfiguration(IntPtr.Zero,
  192.                                                                                         queryType,
  193.                                                                                         pInputConfigInfo,
  194.                                                                                         inputConfigInfoSize,
  195.                                                                                         pOutputConfigInfo,
  196.                                                                                         returnLength,
  197.                                                                                         out returnLength,
  198.                                                                                         IntPtr.Zero);
  199.                                 ThrowWin32ExceptionIfError(retVal);
  200.  
  201.                                 var outputConfigInfo =
  202.                                     (HTTP_SERVICE_CONFIG_SSL_SET)
  203.                                     Marshal.PtrToStructure(pOutputConfigInfo, typeof(HTTP_SERVICE_CONFIG_SSL_SET));
  204.  
  205.                                 byte[] hash = new byte[outputConfigInfo.ParamDesc.SslHashLength];
  206.                                 Marshal.Copy(outputConfigInfo.ParamDesc.pSslHash, hash, 0, hash.Length);
  207.  
  208.                                 Guid appId = outputConfigInfo.ParamDesc.AppId;
  209.                                 string storeName = outputConfigInfo.ParamDesc.pSslCertStoreName;
  210.  
  211.                                 result = new SslCertificateInfo { AppId = appId, Hash = hash, StoreName = storeName, IpPort = ipPort };
  212.                             }
  213.                             finally
  214.                             {
  215.                                 Marshal.FreeCoTaskMem(pOutputConfigInfo);
  216.                             }
  217.                         }
  218.                         else
  219.                         {
  220.                             ThrowWin32ExceptionIfError(retVal);
  221.                         }
  222.  
  223.                     }
  224.                     finally
  225.                     {
  226.                         Marshal.FreeCoTaskMem(pInputConfigInfo);
  227.                         if (sockAddrHandle.IsAllocated)
  228.                             sockAddrHandle.Free();
  229.                     }
  230.                         
  231.                 });
  232.  
  233.         return result;
  234.     }
  235.  
  236.     public static void BindCertificate(IPEndPoint ipPort, byte[] hash, StoreName storeName, Guid appId)
  237.     {
  238.         if (ipPort == null) throw new ArgumentNullException("ipPort");
  239.         if (hash == null) throw new ArgumentNullException("hash");
  240.  
  241.         CallHttpApi(
  242.             delegate
  243.                 {
  244.                     HTTP_SERVICE_CONFIG_SSL_SET configSslSet = new HTTP_SERVICE_CONFIG_SSL_SET();
  245.  
  246.                     GCHandle sockAddrHandle = CreateSockaddrStructure(ipPort);
  247.                     IntPtr pIpPort = sockAddrHandle.AddrOfPinnedObject();
  248.                     HTTP_SERVICE_CONFIG_SSL_KEY httpServiceConfigSslKey =
  249.                         new HTTP_SERVICE_CONFIG_SSL_KEY(pIpPort);
  250.                     HTTP_SERVICE_CONFIG_SSL_PARAM configSslParam = new HTTP_SERVICE_CONFIG_SSL_PARAM();
  251.  
  252.  
  253.                     GCHandle handleHash = GCHandle.Alloc(hash, GCHandleType.Pinned);
  254.                     configSslParam.AppId = appId;
  255.                     configSslParam.DefaultCertCheckMode = 0;
  256.                     configSslParam.DefaultFlags = 0; //HTTP_SERVICE_CONFIG_SSL_FLAG_NEGOTIATE_CLIENT_CERT;
  257.                     configSslParam.DefaultRevocationFreshnessTime = 0;
  258.                     configSslParam.DefaultRevocationUrlRetrievalTimeout = 0;
  259.                     configSslParam.pSslCertStoreName = storeName.ToString();
  260.                     configSslParam.pSslHash = handleHash.AddrOfPinnedObject();
  261.                     configSslParam.SslHashLength = hash.Length;
  262.                     configSslSet.ParamDesc = configSslParam;
  263.                     configSslSet.KeyDesc = httpServiceConfigSslKey;
  264.  
  265.                     IntPtr pInputConfigInfo =
  266.                         Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof (HTTP_SERVICE_CONFIG_SSL_SET)));
  267.                     Marshal.StructureToPtr(configSslSet, pInputConfigInfo, false);
  268.  
  269.                     try
  270.                     {
  271.                         uint retVal = HttpSetServiceConfiguration(IntPtr.Zero,
  272.                                                                     HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo,
  273.                                                                     pInputConfigInfo,
  274.                                                                     Marshal.SizeOf(configSslSet),
  275.                                                                     IntPtr.Zero);
  276.  
  277.                         if (ERROR_ALREADY_EXISTS != retVal)
  278.                         {
  279.                             ThrowWin32ExceptionIfError(retVal);
  280.                         }
  281.                         else
  282.                         {
  283.                             retVal = HttpDeleteServiceConfiguration(IntPtr.Zero,
  284.                                                                     HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo,
  285.                                                                     pInputConfigInfo,
  286.                                                                     Marshal.SizeOf(configSslSet),
  287.                                                                     IntPtr.Zero);
  288.                             ThrowWin32ExceptionIfError(retVal);
  289.  
  290.                             retVal = HttpSetServiceConfiguration(IntPtr.Zero,
  291.                                                                     HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo,
  292.                                                                     pInputConfigInfo,
  293.                                                                     Marshal.SizeOf(configSslSet),
  294.                                                                     IntPtr.Zero);
  295.                             ThrowWin32ExceptionIfError(retVal);
  296.                         }
  297.                     }
  298.                     finally
  299.                     {
  300.                         Marshal.FreeCoTaskMem(pInputConfigInfo);
  301.                         if (handleHash.IsAllocated)
  302.                             handleHash.Free();
  303.                         if (sockAddrHandle.IsAllocated)
  304.                             sockAddrHandle.Free();
  305.                     }
  306.                 });
  307.     }
  308.  
  309.     public static void DeleteCertificateBinding(params IPEndPoint[] ipPorts)
  310.     {
  311.         if (ipPorts == null || ipPorts.Length == 0)
  312.             return;
  313.  
  314.         CallHttpApi(
  315.         delegate
  316.         {
  317.             foreach (var ipPort in ipPorts)
  318.             {
  319.                 HTTP_SERVICE_CONFIG_SSL_SET configSslSet =
  320.                     new HTTP_SERVICE_CONFIG_SSL_SET();
  321.  
  322.                 GCHandle sockAddrHandle = CreateSockaddrStructure(ipPort);
  323.                 IntPtr pIpPort = sockAddrHandle.AddrOfPinnedObject();
  324.                 HTTP_SERVICE_CONFIG_SSL_KEY httpServiceConfigSslKey =
  325.                     new HTTP_SERVICE_CONFIG_SSL_KEY(pIpPort);
  326.                 configSslSet.KeyDesc = httpServiceConfigSslKey;
  327.  
  328.                 IntPtr pInputConfigInfo =
  329.                     Marshal.AllocCoTaskMem(
  330.                         Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_SSL_SET)));
  331.                 Marshal.StructureToPtr(configSslSet, pInputConfigInfo, false);
  332.  
  333.                 try
  334.                 {
  335.                     uint retVal = HttpDeleteServiceConfiguration(IntPtr.Zero,
  336.                                                                     HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo,
  337.                                                                     pInputConfigInfo,
  338.                                                                     Marshal.SizeOf(configSslSet),
  339.                                                                     IntPtr.Zero);
  340.                     ThrowWin32ExceptionIfError(retVal);
  341.                 }
  342.                 finally
  343.                 {
  344.                     Marshal.FreeCoTaskMem(pInputConfigInfo);
  345.                     if (sockAddrHandle.IsAllocated)
  346.                         sockAddrHandle.Free();
  347.                 }
  348.             }
  349.         });
  350.     }
  351.  
  352.     public static SslCertificateInfo[] QuerySslCertificateInfo()
  353.     {
  354.         var result = new List<SslCertificateInfo>();
  355.  
  356.         CallHttpApi(
  357.             delegate
  358.             {
  359.                 uint token = 0;
  360.  
  361.                 uint retVal;
  362.                 do
  363.                 {
  364.                     HTTP_SERVICE_CONFIG_SSL_QUERY inputConfigInfoQuery =
  365.                         new HTTP_SERVICE_CONFIG_SSL_QUERY
  366.                         {
  367.                             QueryDesc = HTTP_SERVICE_CONFIG_QUERY_TYPE.HttpServiceConfigQueryNext,
  368.                             dwToken = token,
  369.                         };
  370.  
  371.                     IntPtr pInputConfigInfo =
  372.                         Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_SSL_QUERY)));
  373.                     Marshal.StructureToPtr(inputConfigInfoQuery, pInputConfigInfo, false);
  374.  
  375.                     IntPtr pOutputConfigInfo = IntPtr.Zero;
  376.                     int returnLength = 0;
  377.  
  378.                     const HTTP_SERVICE_CONFIG_ID queryType = HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo;
  379.  
  380.                     try
  381.                     {
  382.                         int inputConfigInfoSize = Marshal.SizeOf(inputConfigInfoQuery);
  383.                         retVal = HttpQueryServiceConfiguration(IntPtr.Zero,
  384.                                                                 queryType,
  385.                                                                 pInputConfigInfo,
  386.                                                                 inputConfigInfoSize,
  387.                                                                 pOutputConfigInfo,
  388.                                                                 returnLength,
  389.                                                                 out returnLength,
  390.                                                                 IntPtr.Zero);
  391.                         if (ERROR_NO_MORE_ITEMS == retVal)
  392.                             break;
  393.                         if (ERROR_INSUFFICIENT_BUFFER == retVal) // ERROR_INSUFFICIENT_BUFFER = 122
  394.                         {
  395.                             pOutputConfigInfo = Marshal.AllocCoTaskMem(returnLength);
  396.  
  397.                             try
  398.                             {
  399.                                 retVal = HttpQueryServiceConfiguration(IntPtr.Zero,
  400.                                                                     queryType,
  401.                                                                     pInputConfigInfo,
  402.                                                                     inputConfigInfoSize,
  403.                                                                     pOutputConfigInfo,
  404.                                                                     returnLength,
  405.                                                                     out returnLength,
  406.                                                                     IntPtr.Zero);
  407.                                 ThrowWin32ExceptionIfError(retVal);
  408.  
  409.                                 var outputConfigInfo = (HTTP_SERVICE_CONFIG_SSL_SET)Marshal.PtrToStructure(
  410.                                     pOutputConfigInfo, typeof(HTTP_SERVICE_CONFIG_SSL_SET));
  411.  
  412.                                 byte[] hash = new byte[outputConfigInfo.ParamDesc.SslHashLength];
  413.                                 Marshal.Copy(outputConfigInfo.ParamDesc.pSslHash, hash, 0, hash.Length);
  414.  
  415.                                 Guid appId = outputConfigInfo.ParamDesc.AppId;
  416.                                 string storeName = outputConfigInfo.ParamDesc.pSslCertStoreName;
  417.                                 IPEndPoint ipPort = ReadSockaddrStructure(outputConfigInfo.KeyDesc.pIpPort);
  418.  
  419.                                 var resultItem = new SslCertificateInfo
  420.                                 {
  421.                                     AppId = appId,
  422.                                     Hash = hash,
  423.                                     StoreName = storeName,
  424.                                     IpPort = ipPort
  425.                                 };
  426.                                 result.Add(resultItem);
  427.                                 token++;
  428.                             }
  429.                             finally
  430.                             {
  431.                                 Marshal.FreeCoTaskMem(pOutputConfigInfo);
  432.                             }
  433.                         }
  434.                         else
  435.                         {
  436.                             ThrowWin32ExceptionIfError(retVal);
  437.                         }
  438.                     }
  439.                     finally
  440.                     {
  441.                         Marshal.FreeCoTaskMem(pInputConfigInfo);
  442.                     }
  443.  
  444.                 } while (NOERROR == retVal);
  445.  
  446.             });
  447.  
  448.         return result.ToArray();
  449.     }
  450.  
  451.  
  452.     #endregion
  453.  
  454.     private static void ThrowWin32ExceptionIfError(uint retVal)
  455.     {
  456.         if (NOERROR != retVal)
  457.         {
  458.             throw new Win32Exception(Convert.ToInt32(retVal));
  459.         }
  460.     }
  461.  
  462.     private static void CallHttpApi(Action body)
  463.     {
  464.         uint retVal = HttpInitialize(HttpApiVersion, HTTP_INITIALIZE_CONFIG, IntPtr.Zero);
  465.         ThrowWin32ExceptionIfError(retVal);
  466.  
  467.         try
  468.         {
  469.             body();
  470.         }
  471.         finally
  472.         {
  473.             HttpTerminate(HTTP_INITIALIZE_CONFIG, IntPtr.Zero);
  474.         }
  475.     }
  476.  
  477. }

5 comments:

  1. Oooh, I always love to have new code to toy around with for my ssl certificates, thankyou very much!

    ReplyDelete
  2. Segor, I like how strightforward your code is, and I'd like to use it in my app. Which license covers this class? The BSD license (example from Yahoo: http://developer.yahoo.com/yui/license.html) or similar?

    Thanks!

    ReplyDelete
    Replies
    1. You are free to copy, use, modify or distribute any source code contributions available on the Site for commercial or non-commercial purposes.

      Delete
  3. Hi
    Code works great, thanks. Still there's something I don't understand regarding freeing the allocated memory. For example in QuerySslCertificateInfo, the pOutputConfigInfo points to
    HTTP_SERVICE_CONFIG_SSL_SET which contains HTTP_SERVICE_CONFIG_SSL_PARAM which contains an IntPtr which points to the sslHash byte array. My question is how does the sslHash byte array gets freed? isn't calling Marshal.FreeCoTaskMem(pOutputConfigInfo) only frees the HTTP_SERVICE_CONFIG_SSL_SET and the inside HTTP_SERVICE_CONFIG_SSL_PARAM members, but not the actual sslHash array?
    thanks very much

    ReplyDelete
    Replies
    1. Hi
      This line
      pOutputConfigInfo = Marshal.AllocCoTaskMem(returnLength);
      allocates all necessary memory for HTTP_SERVICE_CONFIG_SSL_SET structure including nested fields like sslHash. So you don't have to free every nested field separately.

      Delete