registerHttp.xsh

Register a Windows server to use https

<?xml version="1.0" encoding="utf-8"?>
<xsharper xmlns="http://www.xsharper.com/schemas/1.0" requireAdmin="1">

<versionInfo>Program to grant access to a HTTP/HTTPS registration to a given user, as well as associate an SSL certificate with such address.</versionInfo>
<usage options="ifNoArguments default autosuffix" />
<param name="listenUri" required="true" value="Server URI, for example https://127.0.0.1:443/cp/ " />
<param name="accountName" default="${=WindowsIdentity.GetCurrent().Name}" value="Account to grant privileges to." />
<param name="certFilter" value="Certificate subject filter for MY store of the local machine, if an SSL certificate is to be associated with the address. * and ? characters can be used." />
<param name="certGuid" value="Application GUID to identify the owning application. New GUID is generated if not specified." />
<param />
<param>Switches:</param>
<param />
<param switch="unregister" count="none" default="0" unspecified="1" value="Unregister the URI" />
<param switch="unregisterSSL" count="none" default="0" unspecified="1" value="Unregister the URI and remove the SSL certificate from port:host" />

<set sid="${accountName}" />
<if isFalse="${=$sid.StartsWith('S-1-')}">
    <set sid="${=new NTAccount($accountName).Translate(typeof(SecurityIdentifier)).ToString()}" />
</if>
<set sddl="D:(A;;GX;;;${sid})" />
<?_ var store=new X509Store(StoreName.My,StoreLocation.LocalMachine);
    var ub=new UriBuilder(c.GetStr("listenUri"));
    
    try
    {   
        var uri=ub.Scheme+"://+:"+ub.Port+ub.Path;
        if (c.GetBool("unregister") || c.GetBool("unregisterSSL"))
        {
            HttpCfg.ReserveUrl(uri, null);
            c.Info.WriteLine("Access to "+uri+" removed.");
        
            if (c.GetBool("unregisterSSL"))
            {   
                HttpCfg.SetSslCert(new Guid(c.GetStr("certGuid",Guid.Empty.ToString())), ub.Host, ub.Port, null,null);
                c.Info.WriteLine("SSL certificate for  "+ub.Host+":"+ub.Port+" removed.");      
            }
        }
        else
        {
            HttpCfg.ReserveUrl(uri, c.GetStr("sddl"));
            c.Info.WriteLine("Access to "+uri+" granted to user "+c.GetStr("accountName"));     

            if (c.IsSet("certFilter"))
            {
                store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
    
                X509Certificate2 cert=null;
                var filter=new XS.StringFilter(c.GetStr("certFilter"));
                foreach (var crt in store.Certificates)
                    if (filter.IsMatch(crt.SubjectName.Format(false)))
                    {
                        if (cert!=null)
                            throw new ApplicationException("Too many certificates match the filter");
                        cert=crt;
                    }

                if (cert==null)
                    throw new ApplicationException("Certificate not found");
        
                HttpCfg.SetSslCert(new Guid(c.GetStr("certGuid",Guid.NewGuid().ToString())), ub.Host, ub.Port, store.Name, cert.GetCertHash());
                c.Info.WriteLine("SSL certificate '"+cert.SubjectName.Format(false) +"' registered for "+ub.Host+":"+ub.Port+".");      
            }
        }
    }
    finally
    {
        store.Close();
    }
?>

<!-- =========================================================================== -->

<?h using System.Security.Principal;
    using System.Runtime.InteropServices;
    using System.Net;
    using System.ComponentModel;
    using System.Security.Cryptography.X509Certificates;


    public static class HttpCfg
    {
        public static void SetSslCert(Guid appId, string ipAddress, int port, string storeName, byte[] hash)
        {
            var httpApiVersion = new HTTPAPI_VERSION(1, 0);
            uint retVal = HttpInitialize(httpApiVersion, HTTP_INITIALIZE_CONFIG, IntPtr.Zero);
            if (NOERROR != retVal)
                throw new Win32Exception(Convert.ToInt32(retVal));

            try
            {
                IPAddress ip = IPAddress.Parse(ipAddress);
                var ipEndPoint = new IPEndPoint(ip, port);

                // serialize the endpoint to a SocketAddress and create an array to hold the values.  Pin the array.
                SocketAddress socketAddress = ipEndPoint.Serialize();
                byte[] socketBytes = new byte[socketAddress.Size];
                GCHandle handleSocketAddress = GCHandle.Alloc(socketBytes, GCHandleType.Pinned);
                // Should copy the first 16 bytes (the SocketAddress has a 32 byte buffer, the size will only be 16,
                //which is what the SOCKADDR accepts
                for (int i = 0; i < socketAddress.Size; ++i)
                    socketBytes[i] = socketAddress[i];

                if (hash == null)
                    hash = new byte[0];
                GCHandle handleHash = GCHandle.Alloc(hash, GCHandleType.Pinned);

                var configSslSet = new HTTP_SERVICE_CONFIG_SSL_SET
                                       {
                                           ParamDesc = new HTTP_SERVICE_CONFIG_SSL_PARAM
                                                           {
                                                               AppId = appId,
                                                               DefaultCertCheckMode = 0,
                                                               DefaultFlags = HTTP_SERVICE_CONFIG_SSL_FLAG_NEGOTIATE_CLIENT_CERT,
                                                               DefaultRevocationFreshnessTime = 0,
                                                               DefaultRevocationUrlRetrievalTimeout = 0,
                                                               pSslCertStoreName = storeName,
                                                               SslHashLength = hash.Length,
                                                               pSslHash = handleHash.AddrOfPinnedObject()
                                                           },
                                           KeyDesc = new HTTP_SERVICE_CONFIG_SSL_KEY
                                                         {
                                                             pIpPort = handleSocketAddress.AddrOfPinnedObject()
                                                         }
                                       };

                IntPtr pInputConfigInfo = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_SSL_SET)));
                try
                {
                    Marshal.StructureToPtr(configSslSet, pInputConfigInfo, false);

                    retVal = HttpDeleteServiceConfiguration(IntPtr.Zero,
                                                            HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo,
                                                            pInputConfigInfo, Marshal.SizeOf(configSslSet), IntPtr.Zero);
                    if (storeName != null)
                        retVal = HttpSetServiceConfiguration(IntPtr.Zero,
                                                             HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo,
                                                             pInputConfigInfo,
                                                             Marshal.SizeOf(configSslSet),
                                                             IntPtr.Zero);
                }
                finally
                {
                    handleSocketAddress.Free();
                    handleHash.Free();
                    Marshal.FreeCoTaskMem(pInputConfigInfo);
                }
                if (NOERROR != retVal && !(retVal == ERROR_NOT_FOUND && storeName == null))
                    throw new Win32Exception(Convert.ToInt32(retVal));
            }
            finally
            {
                HttpTerminate(HTTP_INITIALIZE_CONFIG, IntPtr.Zero);
            }
        }

        public static void ReserveUrl(string networkUrl, string sddl)
        {
            var version = new HTTPAPI_VERSION(1, 0);
            uint retVal = HttpInitialize(version, HTTP_INITIALIZE_CONFIG, IntPtr.Zero);
            if (NOERROR != retVal)
                throw new Win32Exception(Convert.ToInt32(retVal));

            try
            {
                var keyDesc = new HTTP_SERVICE_CONFIG_URLACL_KEY { UrlPrefix = networkUrl };
                var paramDesc = new HTTP_SERVICE_CONFIG_URLACL_PARAM { Sddl = sddl ?? "" };
                var inputConfigInfoSet = new HTTP_SERVICE_CONFIG_URLACL_SET { Key = keyDesc, Param = paramDesc };
                var pInputConfigInfo = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_URLACL_SET)));
                try
                {
                    Marshal.StructureToPtr(inputConfigInfoSet, pInputConfigInfo, false);
                    retVal = HttpDeleteServiceConfiguration(IntPtr.Zero,
                                                            HTTP_SERVICE_CONFIG_ID.HttpServiceConfigUrlAclInfo,
                                                            pInputConfigInfo, Marshal.SizeOf(inputConfigInfoSet),
                                                            IntPtr.Zero);
                    if (sddl != null)
                        retVal = HttpSetServiceConfiguration(IntPtr.Zero,
                                                             HTTP_SERVICE_CONFIG_ID.HttpServiceConfigUrlAclInfo,
                                                             pInputConfigInfo,
                                                             Marshal.SizeOf(inputConfigInfoSet),
                                                             IntPtr.Zero);
                }
                finally
                {
                    Marshal.FreeCoTaskMem(pInputConfigInfo);
                }
                if (NOERROR != retVal && !(retVal==ERROR_NOT_FOUND && sddl==null))
                    throw new Win32Exception(Convert.ToInt32(retVal));
            }
            finally
            {
                HttpTerminate(HTTP_INITIALIZE_CONFIG, IntPtr.Zero);
            }

        }


        #region DllImport

        [DllImport("httpapi.dll", SetLastError = true)]
        static extern uint HttpInitialize(HTTPAPI_VERSION Version, uint Flags, IntPtr pReserved);

        [DllImport("httpapi.dll", SetLastError = true)]
        static extern uint HttpSetServiceConfiguration(IntPtr ServiceIntPtr, HTTP_SERVICE_CONFIG_ID ConfigId, IntPtr pConfigInformation, int ConfigInformationLength, IntPtr pOverlapped);

        [DllImport("httpapi.dll", SetLastError = true)]
        static extern uint HttpDeleteServiceConfiguration(IntPtr ServiceIntPtr, HTTP_SERVICE_CONFIG_ID ConfigId, IntPtr pConfigInformation, int ConfigInformationLength, IntPtr pOverlapped);

        [DllImport("httpapi.dll", SetLastError = true)]
        static extern uint HttpTerminate(uint Flags, IntPtr pReserved);

        [StructLayout(LayoutKind.Sequential)]
        struct HTTP_SERVICE_CONFIG_URLACL_KEY
        {
            [MarshalAs(UnmanagedType.LPWStr)]
            public string UrlPrefix;
        }
        [StructLayout(LayoutKind.Sequential)]
        struct HTTP_SERVICE_CONFIG_URLACL_PARAM
        {
            [MarshalAs(UnmanagedType.LPWStr)]
            public string Sddl;
        }
        [StructLayout(LayoutKind.Sequential)]
        struct HTTP_SERVICE_CONFIG_URLACL_SET
        {
            public HTTP_SERVICE_CONFIG_URLACL_KEY Key;
            public HTTP_SERVICE_CONFIG_URLACL_PARAM Param;
        }

        enum HTTP_SERVICE_CONFIG_ID
        {
            HttpServiceConfigIPListenList = 0,
            HttpServiceConfigSSLCertInfo,
            HttpServiceConfigUrlAclInfo,
            HttpServiceConfigMax
        }

        [StructLayout(LayoutKind.Sequential)]
        struct HTTP_SERVICE_CONFIG_IP_LISTEN_PARAM
        {
            public ushort AddrLength;
            public IntPtr pAddress;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct HTTP_SERVICE_CONFIG_SSL_SET
        {
            public HTTP_SERVICE_CONFIG_SSL_KEY KeyDesc;
            public HTTP_SERVICE_CONFIG_SSL_PARAM ParamDesc;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct HTTP_SERVICE_CONFIG_SSL_KEY
        {
            public IntPtr pIpPort;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        struct HTTP_SERVICE_CONFIG_SSL_PARAM
        {
            public int SslHashLength;
            public IntPtr pSslHash;
            public Guid AppId;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pSslCertStoreName;
            public uint DefaultCertCheckMode;
            public int DefaultRevocationFreshnessTime;
            public int DefaultRevocationUrlRetrievalTimeout;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pDefaultSslCtlIdentifier;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pDefaultSslCtlStoreName;
            public uint DefaultFlags;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 2)]
        struct HTTPAPI_VERSION
        {
            public ushort HttpApiMajorVersion;
            public ushort HttpApiMinorVersion;

            public HTTPAPI_VERSION(ushort majorVersion, ushort minorVersion)
            {
                HttpApiMajorVersion = majorVersion;
                HttpApiMinorVersion = minorVersion;
            }
        }

        #endregion

        #region Constants

        public const uint HTTP_INITIALIZE_CONFIG = 0x00000002;
        public const uint HTTP_SERVICE_CONFIG_SSL_FLAG_USE_DS_MAPPER = 0x00000001;
        public const uint HTTP_SERVICE_CONFIG_SSL_FLAG_NEGOTIATE_CLIENT_CERT = 0x00000002;
        public const uint HTTP_SERVICE_CONFIG_SSL_FLAG_NO_RAW_FILTER = 0x00000004;

        private const uint NOERROR = 0;
        private const uint ERROR_NOT_FOUND = 2;
        #endregion
    }
?>

</xsharper>