source: binary-improvements2/MapRendering/Web/OpenID.cs@ 384

Last change on this file since 384 was 382, checked in by alloc, 2 years ago

Switched to use SpaceWizards.HttpListener

File size: 7.7 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Collections.Specialized;
4using System.IO;
5using System.Net;
6using System.Net.Security;
7using System.Reflection;
8using System.Security.Cryptography.X509Certificates;
9using System.Text;
10using System.Text.RegularExpressions;
11using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
12
13namespace AllocsFixes.NetConnections.Servers.Web {
14 public static class OpenID {
15 private const string STEAM_LOGIN = "https://steamcommunity.com/openid/login";
16
17 private static readonly Regex steamIdUrlMatcher =
18 new Regex (@"^https?:\/\/steamcommunity\.com\/openid\/id\/([0-9]{17,18})");
19
20 private static readonly X509Certificate2 caCert =
21 new X509Certificate2 (Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location) +
22 "/steam-rootca.cer");
23
24 private static readonly X509Certificate2 caIntermediateCert =
25 new X509Certificate2 (Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location) +
26 "/steam-intermediate.cer");
27
28 private const bool verboseSsl = false;
29 public static bool debugOpenId;
30
31 static OpenID () {
32 for (int i = 0; i < Environment.GetCommandLineArgs ().Length; i++) {
33 if (Environment.GetCommandLineArgs () [i].EqualsCaseInsensitive ("-debugopenid")) {
34 debugOpenId = true;
35 }
36 }
37
38 ServicePointManager.ServerCertificateValidationCallback = (_srvPoint, _certificate, _chain, _errors) => {
39 if (_errors == SslPolicyErrors.None) {
40 if (verboseSsl) {
41 Log.Out ("Steam certificate: No error (1)");
42 }
43
44 return true;
45 }
46
47 X509Chain privateChain = new X509Chain ();
48 privateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
49
50 privateChain.ChainPolicy.ExtraStore.Add (caCert);
51 privateChain.ChainPolicy.ExtraStore.Add (caIntermediateCert);
52
53 if (privateChain.Build (new X509Certificate2 (_certificate))) {
54 // No errors, immediately return
55 privateChain.Reset ();
56 if (verboseSsl) {
57 Log.Out ("Steam certificate: No error (2)");
58 }
59
60 return true;
61 }
62
63 if (privateChain.ChainStatus.Length == 0) {
64 // No errors, immediately return
65 privateChain.Reset ();
66 if (verboseSsl) {
67 Log.Out ("Steam certificate: No error (3)");
68 }
69
70 return true;
71 }
72
73 // Iterate all chain elements
74 foreach (X509ChainElement chainEl in privateChain.ChainElements) {
75 if (verboseSsl) {
76 Log.Out ("Validating cert: " + chainEl.Certificate.Subject);
77 }
78
79 // Iterate all status flags of the current cert
80 foreach (X509ChainStatus chainStatus in chainEl.ChainElementStatus) {
81 if (verboseSsl) {
82 Log.Out (" Status: " + chainStatus.Status);
83 }
84
85 if (chainStatus.Status == X509ChainStatusFlags.NoError) {
86 // This status is not an error, skip
87 continue;
88 }
89
90 if (chainStatus.Status == X509ChainStatusFlags.UntrustedRoot && chainEl.Certificate.Equals (caCert)) {
91 // This status is about the cert being an untrusted root certificate but the certificate is one of those we added, ignore
92 continue;
93 }
94
95 // This status is an error, print information
96 Log.Warning ("Steam certificate error: " + chainEl.Certificate.Subject + " ### Error: " +
97 chainStatus.Status);
98 privateChain.Reset ();
99 return false;
100 }
101 }
102
103 foreach (X509ChainStatus chainStatus in privateChain.ChainStatus) {
104 if (chainStatus.Status != X509ChainStatusFlags.NoError &&
105 chainStatus.Status != X509ChainStatusFlags.UntrustedRoot) {
106 Log.Warning ("Steam certificate error: " + chainStatus.Status);
107 privateChain.Reset ();
108 return false;
109 }
110 }
111
112 // We didn't find any errors, chain is valid
113 privateChain.Reset ();
114 if (verboseSsl) {
115 Log.Out ("Steam certificate: No error (4)");
116 }
117
118 return true;
119 };
120 }
121
122 public static string GetOpenIdLoginUrl (string _returnHost, string _returnUrl) {
123 Dictionary<string, string> queryParams = new Dictionary<string, string> ();
124
125 queryParams.Add ("openid.ns", "http://specs.openid.net/auth/2.0");
126 queryParams.Add ("openid.mode", "checkid_setup");
127 queryParams.Add ("openid.return_to", _returnUrl);
128 queryParams.Add ("openid.realm", _returnHost);
129 queryParams.Add ("openid.identity", "http://specs.openid.net/auth/2.0/identifier_select");
130 queryParams.Add ("openid.claimed_id", "http://specs.openid.net/auth/2.0/identifier_select");
131
132 return STEAM_LOGIN + '?' + buildUrlParams (queryParams);
133 }
134
135 public static ulong Validate (HttpListenerRequest _req) {
136 string mode = getValue (_req, "openid.mode");
137 if (mode == "cancel") {
138 Log.Warning ("Steam OpenID login canceled");
139 return 0;
140 }
141
142 if (mode == "error") {
143 Log.Warning ("Steam OpenID login error: " + getValue (_req, "openid.error"));
144 if (debugOpenId) {
145 PrintOpenIdResponse (_req);
146 }
147
148 return 0;
149 }
150
151 string steamIdString = getValue (_req, "openid.claimed_id");
152 ulong steamId;
153 Match steamIdMatch = steamIdUrlMatcher.Match (steamIdString);
154 if (steamIdMatch.Success) {
155 steamId = ulong.Parse (steamIdMatch.Groups [1].Value);
156 } else {
157 Log.Warning ("Steam OpenID login result did not give a valid SteamID");
158 if (debugOpenId) {
159 PrintOpenIdResponse (_req);
160 }
161
162 return 0;
163 }
164
165 Dictionary<string, string> queryParams = new Dictionary<string, string> ();
166
167 queryParams.Add ("openid.ns", "http://specs.openid.net/auth/2.0");
168
169 queryParams.Add ("openid.assoc_handle", getValue (_req, "openid.assoc_handle"));
170 queryParams.Add ("openid.signed", getValue (_req, "openid.signed"));
171 queryParams.Add ("openid.sig", getValue (_req, "openid.sig"));
172 queryParams.Add ("openid.identity", "http://specs.openid.net/auth/2.0/identifier_select");
173 queryParams.Add ("openid.claimed_id", "http://specs.openid.net/auth/2.0/identifier_select");
174
175 string[] signeds = getValue (_req, "openid.signed").Split (',');
176 foreach (string s in signeds) {
177 queryParams ["openid." + s] = getValue (_req, "openid." + s);
178 }
179
180 queryParams.Add ("openid.mode", "check_authentication");
181
182 byte[] postData = Encoding.ASCII.GetBytes (buildUrlParams (queryParams));
183 HttpWebRequest request = (HttpWebRequest) WebRequest.Create (STEAM_LOGIN);
184 request.Method = "POST";
185 request.ContentType = "application/x-www-form-urlencoded";
186 request.ContentLength = postData.Length;
187 request.Headers.Add (HttpRequestHeader.AcceptLanguage, "en");
188 using (Stream st = request.GetRequestStream ()) {
189 st.Write (postData, 0, postData.Length);
190 }
191
192 HttpWebResponse response = (HttpWebResponse) request.GetResponse ();
193 string responseString;
194 using (Stream st = response.GetResponseStream ()) {
195 using (StreamReader str = new StreamReader (st)) {
196 responseString = str.ReadToEnd ();
197 }
198 }
199
200 if (responseString.ContainsCaseInsensitive ("is_valid:true")) {
201 return steamId;
202 }
203
204 Log.Warning ("Steam OpenID login failed: {0}", responseString);
205 return 0;
206 }
207
208 private static string buildUrlParams (Dictionary<string, string> _queryParams) {
209 string[] paramsArr = new string[_queryParams.Count];
210 int i = 0;
211 foreach (KeyValuePair<string, string> kvp in _queryParams) {
212 paramsArr [i++] = kvp.Key + "=" + Uri.EscapeDataString (kvp.Value);
213 }
214
215 return string.Join ("&", paramsArr);
216 }
217
218 private static string getValue (HttpListenerRequest _req, string _name) {
219 NameValueCollection nvc = _req.QueryString;
220 if (nvc [_name] == null) {
221 throw new MissingMemberException ("OpenID parameter \"" + _name + "\" missing");
222 }
223
224 return nvc [_name];
225 }
226
227 private static void PrintOpenIdResponse (HttpListenerRequest _req) {
228 NameValueCollection nvc = _req.QueryString;
229 for (int i = 0; i < nvc.Count; i++) {
230 Log.Out (" " + nvc.GetKey (i) + " = " + nvc [i]);
231 }
232 }
233 }
234}
Note: See TracBrowser for help on using the repository browser.