source: binary-improvements2/WebServer/src/OpenID.cs@ 397

Last change on this file since 397 was 391, checked in by alloc, 2 years ago

Major refactoring/cleanup

File size: 7.5 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 Webserver {
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 { "openid.ns", "http://specs.openid.net/auth/2.0" },
125 { "openid.mode", "checkid_setup" },
126 { "openid.return_to", _returnUrl },
127 { "openid.realm", _returnHost },
128 { "openid.identity", "http://specs.openid.net/auth/2.0/identifier_select" },
129 { "openid.claimed_id", "http://specs.openid.net/auth/2.0/identifier_select" }
130 };
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 { "openid.ns", "http://specs.openid.net/auth/2.0" },
167 { "openid.assoc_handle", getValue (_req, "openid.assoc_handle") },
168 { "openid.signed", getValue (_req, "openid.signed") },
169 { "openid.sig", getValue (_req, "openid.sig") },
170 { "openid.identity", "http://specs.openid.net/auth/2.0/identifier_select" },
171 { "openid.claimed_id", "http://specs.openid.net/auth/2.0/identifier_select" }
172 };
173
174 string[] signeds = getValue (_req, "openid.signed").Split (',');
175 foreach (string s in signeds) {
176 queryParams ["openid." + s] = getValue (_req, "openid." + s);
177 }
178
179 queryParams.Add ("openid.mode", "check_authentication");
180
181 byte[] postData = Encoding.ASCII.GetBytes (buildUrlParams (queryParams));
182 HttpWebRequest request = (HttpWebRequest) WebRequest.Create (STEAM_LOGIN);
183 request.Method = "POST";
184 request.ContentType = "application/x-www-form-urlencoded";
185 request.ContentLength = postData.Length;
186 request.Headers.Add (HttpRequestHeader.AcceptLanguage, "en");
187 using (Stream st = request.GetRequestStream ()) {
188 st.Write (postData, 0, postData.Length);
189 }
190
191 HttpWebResponse response = (HttpWebResponse) request.GetResponse ();
192 string responseString;
193 using (Stream st = response.GetResponseStream ()) {
194 using (StreamReader str = new StreamReader (st)) {
195 responseString = str.ReadToEnd ();
196 }
197 }
198
199 if (responseString.ContainsCaseInsensitive ("is_valid:true")) {
200 return steamId;
201 }
202
203 Log.Warning ("Steam OpenID login failed: {0}", responseString);
204 return 0;
205 }
206
207 private static string buildUrlParams (Dictionary<string, string> _queryParams) {
208 string[] paramsArr = new string[_queryParams.Count];
209 int i = 0;
210 foreach ((string argName, string argValue) in _queryParams) {
211 paramsArr [i++] = argName + "=" + Uri.EscapeDataString (argValue);
212 }
213
214 return string.Join ("&", paramsArr);
215 }
216
217 private static string getValue (HttpListenerRequest _req, string _name) {
218 NameValueCollection nvc = _req.QueryString;
219 if (nvc [_name] == null) {
220 throw new MissingMemberException ("OpenID parameter \"" + _name + "\" missing");
221 }
222
223 return nvc [_name];
224 }
225
226 private static void PrintOpenIdResponse (HttpListenerRequest _req) {
227 NameValueCollection nvc = _req.QueryString;
228 for (int i = 0; i < nvc.Count; i++) {
229 Log.Out (" " + nvc.GetKey (i) + " = " + nvc [i]);
230 }
231 }
232 }
233}
Note: See TracBrowser for help on using the repository browser.