11using System . Diagnostics ;
22using System . Security . Cryptography . X509Certificates ;
33using System . Text . RegularExpressions ;
4+ using HttpSys . NetSh ;
45
56namespace HttpSys
67{
@@ -28,12 +29,49 @@ public static void DeleteBinding(string ipPort)
2829 Console . WriteLine ( "Disabled http.sys settings for mTLS" ) ;
2930 }
3031
31- public static bool BindingExists ( string ipPort , out string certThumbprint , out string appId )
32+
33+
34+ public static bool TryGetSslCertBinding ( string ipPort , out SslCertBinding result )
3235 {
33- certThumbprint = string . Empty ;
34- appId = string . Empty ;
36+ result = new SslCertBinding ( ) ;
3537
36- var bindings = ExecuteNetShCommand ( "http show sslcert" ) ;
38+ /*
39+ Example of output:
40+ -----------------
41+ IP:port : <ip:port>
42+ Certificate Hash : <hash>
43+ Application ID : {<guid>}
44+ Certificate Store Name : <store-name>
45+ Verify Client Certificate Revocation : Enabled
46+ Verify Revocation Using Cached Client Certificate Only : Disabled
47+ Usage Check : Enabled
48+ Revocation Freshness Time : 0
49+ URL Retrieval Timeout : 0
50+ Ctl Identifier : (null)
51+ Ctl Store Name : (null)
52+ DS Mapper Usage : Disabled
53+ Negotiate Client Certificate : Disabled
54+ Reject Connections : Disabled
55+ Disable HTTP2 : Not Set
56+ Disable QUIC : Not Set
57+ Disable TLS1.2 : Not Set
58+ Disable TLS1.3 : Not Set
59+ Disable OCSP Stapling : Not Set
60+ Enable Token Binding : Not Set
61+ Log Extended Events : Not Set
62+ Disable Legacy TLS Versions : Not Set
63+ Enable Session Ticket : Not Set
64+ Disable Session ID : Not Set
65+ Enable Caching Client Hello : Not Set
66+ Extended Properties:
67+ PropertyId : 0
68+ Receive Window : 1048576
69+ Extended Properties:
70+ PropertyId : 1
71+ Max Settings Per Frame : 2796202
72+ Max Settings Per Minute : 4294967295
73+ */
74+ var bindings = ExecuteNetShCommand ( $ "http show sslcert ipport={ ipPort } ") ;
3775 if ( string . IsNullOrEmpty ( bindings ) || ! bindings . Contains ( ipPort ) )
3876 {
3977 return false ;
@@ -43,22 +81,51 @@ public static bool BindingExists(string ipPort, out string certThumbprint, out s
4381 var thumbprintMatch = Regex . Match ( bindings , @"Certificate Hash\s+:\s+([a-fA-F0-9]+)" ) ;
4482 if ( thumbprintMatch . Success )
4583 {
46- certThumbprint = thumbprintMatch . Groups [ 1 ] . Value ;
84+ result . CertificateThumbprint = thumbprintMatch . Groups [ 1 ] . Value ;
4785 }
4886
4987 // Extract the application ID
5088 var appIdMatch = Regex . Match ( bindings , @"Application ID\s+:\s+{([a-fA-F0-9-]+)}" ) ;
5189 if ( appIdMatch . Success )
5290 {
53- appId = appIdMatch . Groups [ 1 ] . Value ;
91+ result . ApplicationId = appIdMatch . Groups [ 1 ] . Value ;
92+ }
93+
94+ var negotiateClientCertEnabledRegex = Regex . Match ( bindings , @"Negotiate Client Certificate\s+:\s+([a-zA-Z0-9]+)" ) ;
95+ if ( negotiateClientCertEnabledRegex . Success )
96+ {
97+ var negotiateClientCertValue = negotiateClientCertEnabledRegex . Groups [ 1 ] . Value ;
98+ result . NegotiateClientCertificate = ParseNetShFlag ( negotiateClientCertValue ) ;
99+ }
100+
101+ var disableSessionId = Regex . Match ( bindings , @"Disable Session ID\s+:\s+([a-zA-Z0-9 ]+)" ) ;
102+ if ( disableSessionId . Success )
103+ {
104+ var disableSessionIdValue = disableSessionId . Groups [ 1 ] . Value ;
105+ result . DisableSessionIdTlsResumption = ParseNetShFlag ( disableSessionIdValue ) ;
106+ }
107+
108+ var enableSessionTicket = Regex . Match ( bindings , @"Enable Session Ticket\s+:\s+([a-zA-Z0-9 ]+)" ) ;
109+ if ( enableSessionTicket . Success )
110+ {
111+ var enableSessionTicketValue = enableSessionTicket . Groups [ 1 ] . Value ;
112+ result . EnableSessionTicketTlsResumption = ParseNetShFlag ( enableSessionTicketValue ) ;
54113 }
55114
56115 return true ;
116+
117+ NetShFlag ParseNetShFlag ( string prop ) => prop switch
118+ {
119+ "Not Set" => NetShFlag . NotSet ,
120+ "Disable" or "Disabled" => NetShFlag . Disabled ,
121+ "Enable" or "Enabled" or "Set" => NetShFlag . Enable ,
122+ _ => throw new ArgumentOutOfRangeException ( nameof ( prop ) , $ "unexpected netsh flag '{ prop } ' for ssl cert binding") ,
123+ } ;
57124 }
58125
59- public static void Show ( )
126+ public static void LogSslCertBinding ( string ipPort )
60127 {
61- ExecuteNetShCommand ( "http show sslcert" , alwaysLogOutput : true ) ;
128+ ExecuteNetShCommand ( $ "http show sslcert ipport= { ipPort } ", alwaysLogOutput : true ) ;
62129 }
63130
64131 public static void SetTestCertBinding ( string ipPort , bool enableClientCertNegotiation )
@@ -76,7 +143,7 @@ public static void SetTestCertBinding(string ipPort, bool enableClientCertNegoti
76143 }
77144
78145 string certThumbprint = certificate . Thumbprint ;
79- SetCertBinding ( ipPort , certThumbprint , enableClientCertNegotiation : enableClientCertNegotiation ) ;
146+ AddCertBinding ( ipPort , certThumbprint , clientCertNegotiation : enableClientCertNegotiation ? NetShFlag . Enable : NetShFlag . Disabled ) ;
80147
81148 Console . WriteLine ( "Configured binding for testCert for http.sys" ) ;
82149 }
@@ -108,16 +175,77 @@ public static bool TrySelfSignCertificate(string ipPort, out string certThumbpri
108175 }
109176 }
110177
111- public static void SetCertBinding ( string ipPort , string certThumbprint , string appId = null , bool enableClientCertNegotiation = false )
178+ public static void AddCertBinding (
179+ string ipPort , string certThumbprint ,
180+ string ? appId = null ,
181+ NetShFlag clientCertNegotiation = NetShFlag . Disabled ,
182+ NetShFlag disablesessionid = NetShFlag . Enable ,
183+ NetShFlag enablesessionticket = NetShFlag . Disabled )
184+ => CertBindingCore ( "add" , ipPort , certThumbprint , appId , clientCertNegotiation , disablesessionid , enablesessionticket ) ;
185+
186+ public static void UpdateCertBinding ( string ipPort , SslCertBinding binding ) => UpdateCertBinding (
187+ ipPort ,
188+ binding . CertificateThumbprint ,
189+ binding . ApplicationId ,
190+ binding . NegotiateClientCertificate ,
191+ binding . DisableSessionIdTlsResumption ,
192+ binding . EnableSessionTicketTlsResumption ) ;
193+
194+ public static void UpdateCertBinding (
195+ string ipPort , string certThumbprint ,
196+ string ? appId = null ,
197+ NetShFlag clientCertNegotiation = NetShFlag . Disabled ,
198+ NetShFlag disablesessionid = NetShFlag . Enable ,
199+ NetShFlag enablesessionticket = NetShFlag . Disabled )
200+ => CertBindingCore ( "update" , ipPort , certThumbprint , appId , clientCertNegotiation , disablesessionid , enablesessionticket ) ;
201+
202+ private static void CertBindingCore (
203+ string httpOperation ,
204+ string ipPort , string certThumbprint ,
205+ string ? appId = null ,
206+ NetShFlag clientcertnegotiation = NetShFlag . Disabled ,
207+ NetShFlag disablesessionid = NetShFlag . Enable ,
208+ NetShFlag enablesessionticket = NetShFlag . Disabled )
112209 {
113- var negotiateClientCert = enableClientCertNegotiation ? "enable" : "disable" ;
114210 if ( string . IsNullOrEmpty ( appId ) )
115211 {
116212 appId = "00000000-0000-0000-0000-000000000000" ;
117213 }
118- string command = $ "http add sslcert ipport={ ipPort } certstorename=MY certhash={ certThumbprint } appid={{{appId}}} clientcertnegotiation={ negotiateClientCert } ";
119- ExecuteNetShCommand ( command ) ;
120- Console . WriteLine ( $ "Performed cert bindign for { ipPort } ") ;
214+
215+ var clientcertnegotiationFlag = GetFlagValue ( clientcertnegotiation ) ;
216+ var disablesessionidFlag = GetFlagValue ( disablesessionid ) ;
217+ var enablesessionticketFlag = GetFlagValue ( enablesessionticket ) ;
218+ string command = $ "http { httpOperation } sslcert ipport={ ipPort } certstorename=MY certhash={ certThumbprint } appid={{{appId}}}";
219+
220+ if ( clientcertnegotiationFlag != null )
221+ {
222+ command += $ " clientcertnegotiation={ clientcertnegotiationFlag } ";
223+ }
224+
225+ // below options are supported only in later versions of HTTP.SYS
226+ // you can identify if it is available by running `netsh http add sslcert help`
227+ // ---
228+ // workaround is to control SChannel settings via registry
229+
230+ //if (disablesessionidFlag != null)
231+ //{
232+ // command += $" disablesessionid={disablesessionidFlag}";
233+ //}
234+ //if (enablesessionticketFlag != null)
235+ //{
236+ // command += $" enablesessionticket={enablesessionticketFlag}";
237+ //}
238+
239+ ExecuteNetShCommand ( command , alwaysLogOutput : true ) ;
240+ Console . WriteLine ( $ "Performed cert binding for { ipPort } ") ;
241+
242+ string ? GetFlagValue ( NetShFlag flag ) => flag switch
243+ {
244+ NetShFlag . NotSet => null ,
245+ NetShFlag . Disabled => "disable" ,
246+ NetShFlag . Enable => "enable" ,
247+ _ => throw new ArgumentOutOfRangeException ( nameof ( flag ) ) ,
248+ } ;
121249 }
122250
123251 private static string ExecutePowershellCommand ( string command , bool alwaysLogOutput = false )
@@ -126,7 +254,7 @@ private static string ExecutePowershellCommand(string command, bool alwaysLogOut
126254 private static string ExecuteNetShCommand ( string command , bool alwaysLogOutput = false )
127255 => ExecuteCommand ( "netsh" , command , alwaysLogOutput ) ;
128256
129- private static string ExecuteCommand ( string fileName , string command , bool alwaysLogOutput = false )
257+ private static string ExecuteCommand ( string fileName , string command , bool logOutput = false )
130258 {
131259 ProcessStartInfo processInfo = new ProcessStartInfo ( fileName , command )
132260 {
@@ -141,7 +269,7 @@ private static string ExecuteCommand(string fileName, string command, bool alway
141269 string output = process . StandardOutput . ReadToEnd ( ) ;
142270 process . WaitForExit ( ) ;
143271
144- if ( alwaysLogOutput )
272+ if ( logOutput )
145273 {
146274 Console . WriteLine ( output ) ;
147275 }
0 commit comments