source: TFP-WebServer/WebServer/src/OpenID.cs@ 511

Last change on this file since 511 was 511, checked in by alloc, 17 hours ago

Compatibility for V 2.5

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