· 7 years ago · Jan 09, 2018, 09:36 PM
1public class OAuthBase
2 {
3 /// <summary>
4 /// Provides a predefined set of algorithms that are supported officially by the protocol
5 /// </summary>
6 public enum SignatureTypes
7 {
8 HMACSHA1,
9 PLAINTEXT,
10 RSASHA1
11 }
12
13 /// <summary>
14 /// Provides an internal structure to sort the query parameter
15 /// </summary>
16 protected class QueryParameter
17 {
18 private string name = null;
19 private string value = null;
20
21 public QueryParameter(string name, string value)
22 {
23 this.name = name;
24 this.value = value;
25 }
26
27 public string Name
28 {
29 get { return name; }
30 }
31
32 public string Value
33 {
34 get { return value; }
35 }
36 }
37
38 /// <summary>
39 /// Comparer class used to perform the sorting of the query parameters
40 /// </summary>
41 protected class QueryParameterComparer : IComparer<QueryParameter>
42 {
43
44 #region IComparer<QueryParameter> Members
45
46 public int Compare(QueryParameter x, QueryParameter y)
47 {
48 if (x.Name == y.Name)
49 {
50 return string.Compare(x.Value, y.Value);
51 }
52 else
53 {
54 return string.Compare(x.Name, y.Name);
55 }
56 }
57
58 #endregion
59 }
60
61 protected const string OAuthVersion = "1.0";
62 protected const string OAuthParameterPrefix = "oauth_";
63
64 //
65 // List of know and used oauth parameters' names
66 //
67 protected const string OAuthConsumerKeyKey = "oauth_consumer_key";
68 protected const string OAuthCallbackKey = "oauth_callback";
69 protected const string OAuthVersionKey = "oauth_version";
70 protected const string OAuthSignatureMethodKey = "oauth_signature_method";
71 protected const string OAuthSignatureKey = "oauth_signature";
72 protected const string OAuthTimestampKey = "oauth_timestamp";
73 protected const string OAuthNonceKey = "oauth_nonce";
74 protected const string OAuthTokenKey = "oauth_token";
75 protected const string OAuthTokenSecretKey = "oauth_token_secret";
76 protected const string OAuthVerifier = "oauth_verifier"; // OAuth 1.0a
77
78 protected const string HMACSHA1SignatureType = "HMAC-SHA1";
79 protected const string PlainTextSignatureType = "PLAINTEXT";
80 protected const string RSASHA1SignatureType = "RSA-SHA1";
81
82 protected Random random = new Random();
83
84 protected string unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
85
86 /// <summary>
87 /// Helper function to compute a hash value
88 /// </summary>
89 /// <param name="hashAlgorithm">The hashing algoirhtm used. If that algorithm needs some initialization, like HMAC and its derivatives, they should be initialized prior to passing it to this function</param>
90 /// <param name="data">The data to hash</param>
91 /// <returns>a Base64 string of the hash value</returns>
92 private string ComputeHash(HashAlgorithm hashAlgorithm, string data)
93 {
94 if (hashAlgorithm == null)
95 {
96 throw new ArgumentNullException("hashAlgorithm");
97 }
98
99 if (string.IsNullOrEmpty(data))
100 {
101 throw new ArgumentNullException("data");
102 }
103
104 byte[] dataBuffer = System.Text.Encoding.ASCII.GetBytes(data);
105 byte[] hashBytes = hashAlgorithm.ComputeHash(dataBuffer);
106
107 return Convert.ToBase64String(hashBytes);
108 }
109
110 /// <summary>
111 /// Internal function to cut out all non oauth query string parameters (all parameters not begining with "oauth_")
112 /// </summary>
113 /// <param name="parameters">The query string part of the Url</param>
114 /// <returns>A list of QueryParameter each containing the parameter name and value</returns>
115 private List<QueryParameter> GetQueryParameters(string parameters)
116 {
117 if (parameters.StartsWith("?"))
118 {
119 parameters = parameters.Remove(0, 1);
120 }
121
122 List<QueryParameter> result = new List<QueryParameter>();
123
124 if (!string.IsNullOrEmpty(parameters))
125 {
126 string[] p = parameters.Split('&');
127 foreach (string s in p)
128 {
129 if (!string.IsNullOrEmpty(s) && !s.StartsWith(OAuthParameterPrefix))
130 {
131 if (s.IndexOf('=') > -1)
132 {
133 string[] temp = s.Split('=');
134 result.Add(new QueryParameter(temp[0], temp[1]));
135 }
136 else
137 {
138 result.Add(new QueryParameter(s, string.Empty));
139 }
140 }
141 }
142 }
143
144 return result;
145 }
146
147 /// <summary>
148 /// This is a different Url Encode implementation since the default .NET one outputs the percent encoding in lower case.
149 /// While this is not a problem with the percent encoding spec, it is used in upper case throughout OAuth
150 /// </summary>
151 /// <param name="value">The value to Url encode</param>
152 /// <returns>Returns a Url encoded string</returns>
153 protected string UrlEncode(string value)
154 {
155 StringBuilder result = new StringBuilder();
156
157 foreach (char symbol in value)
158 {
159 if (unreservedChars.IndexOf(symbol) != -1)
160 {
161 result.Append(symbol);
162 }
163 else
164 {
165 result.Append('%' + String.Format("{0:X2}", (int)symbol));
166 }
167 }
168
169 return result.ToString();
170 }
171
172 // added by H.Tsujimura 20090406 (for multibyte characters; cf. Japanese, Chinese, ......)
173 protected string UrlEncode(string value, Encoding encode)
174 {
175 StringBuilder result = new StringBuilder();
176 byte[] data = encode.GetBytes(value);
177 int len = data.Length;
178
179 for (int i = 0; i < len; i++)
180 {
181 int c = data[i];
182 if (c < 0x80 && unreservedChars.IndexOf((char)c) != -1)
183 {
184 result.Append((char)c);
185 }
186 else
187 {
188 result.Append('%' + String.Format("{0:X2}", (int)data[i]));
189 }
190 }
191
192 return result.ToString();
193 }
194
195 /// <summary>
196 /// Normalizes the request parameters according to the spec
197 /// </summary>
198 /// <param name="parameters">The list of parameters already sorted</param>
199 /// <returns>a string representing the normalized parameters</returns>
200 protected string NormalizeRequestParameters(IList<QueryParameter> parameters)
201 {
202 StringBuilder sb = new StringBuilder();
203 QueryParameter p = null;
204 for (int i = 0; i < parameters.Count; i++)
205 {
206 p = parameters[i];
207 sb.AppendFormat("{0}={1}", p.Name, p.Value);
208
209 if (i < parameters.Count - 1)
210 {
211 sb.Append("&");
212 }
213 }
214
215 return sb.ToString();
216 }
217
218 /// <summary>
219 /// Generate the signature base that is used to produce the signature
220 /// </summary>
221 /// <param name="url">The full url that needs to be signed including its non OAuth url parameters</param>
222 /// <param name="consumerKey">The consumer key</param>
223 /// <param name="token">The token, if available. If not available pass null or an empty string</param>
224 /// <param name="tokenSecret">The token secret, if available. If not available pass null or an empty string</param>
225 /// <param name="httpMethod">The http method used. Must be a valid HTTP method verb (POST,GET,PUT, etc)</param>
226 /// <param name="signatureType">The signature type. To use the default values use <see cref="OAuthBase.SignatureTypes">OAuthBase.SignatureTypes</see>.</param>
227 /// <returns>The signature base</returns>
228 public string GenerateSignatureBase(
229 Uri url, string consumerKey, string token, string tokenSecret,
230 string httpMethod, string timeStamp, string nonce, string signatureType,
231 string verifier,
232 out string normalizedUrl, out string normalizedRequestParameters)
233 {
234 if (token == null)
235 {
236 token = string.Empty;
237 }
238
239 if (tokenSecret == null)
240 {
241 tokenSecret = string.Empty;
242 }
243
244 if (string.IsNullOrEmpty(consumerKey))
245 {
246 throw new ArgumentNullException("consumerKey");
247 }
248
249 if (string.IsNullOrEmpty(httpMethod))
250 {
251 throw new ArgumentNullException("httpMethod");
252 }
253
254 if (string.IsNullOrEmpty(signatureType))
255 {
256 throw new ArgumentNullException("signatureType");
257 }
258
259 normalizedUrl = null;
260 normalizedRequestParameters = null;
261
262 List<QueryParameter> parameters = GetQueryParameters(url.Query);
263 parameters.Add(new QueryParameter(OAuthVersionKey, OAuthVersion));
264 parameters.Add(new QueryParameter(OAuthNonceKey, nonce));
265 parameters.Add(new QueryParameter(OAuthTimestampKey, timeStamp));
266 parameters.Add(new QueryParameter(OAuthSignatureMethodKey, signatureType));
267 parameters.Add(new QueryParameter(OAuthConsumerKeyKey, consumerKey));
268 if (verifier != null && verifier != "")
269 parameters.Add(new QueryParameter(OAuthVerifier, verifier));
270
271 if (!string.IsNullOrEmpty(token))
272 {
273 parameters.Add(new QueryParameter(OAuthTokenKey, token));
274 }
275
276 parameters.Sort(new QueryParameterComparer());
277
278 normalizedUrl = string.Format("{0}://{1}", url.Scheme, url.Host);
279 if (!((url.Scheme == "http" && url.Port == 80) || (url.Scheme == "https" && url.Port == 443)))
280 {
281 normalizedUrl += ":" + url.Port;
282 }
283 normalizedUrl += url.AbsolutePath;
284 normalizedRequestParameters = NormalizeRequestParameters(parameters);
285
286 StringBuilder signatureBase = new StringBuilder();
287 signatureBase.AppendFormat("{0}&", httpMethod.ToUpper());
288 signatureBase.AppendFormat("{0}&", UrlEncode(normalizedUrl));
289 signatureBase.AppendFormat("{0}", UrlEncode(normalizedRequestParameters));
290
291 return signatureBase.ToString();
292 }
293
294 /// <summary>
295 /// Generate the signature value based on the given signature base and hash algorithm
296 /// </summary>
297 /// <param name="signatureBase">The signature based as produced by the GenerateSignatureBase method or by any other means</param>
298 /// <param name="hash">The hash algorithm used to perform the hashing. If the hashing algorithm requires initialization or a key it should be set prior to calling this method</param>
299 /// <returns>A base64 string of the hash value</returns>
300 public string GenerateSignatureUsingHash(string signatureBase, HashAlgorithm hash)
301 {
302 return ComputeHash(hash, signatureBase);
303 }
304
305 /// <summary>
306 /// Generates a signature using the HMAC-SHA1 algorithm
307 /// </summary>
308 /// <param name="url">The full url that needs to be signed including its non OAuth url parameters</param>
309 /// <param name="consumerKey">The consumer key</param>
310 /// <param name="consumerSecret">The consumer seceret</param>
311 /// <param name="token">The token, if available. If not available pass null or an empty string</param>
312 /// <param name="tokenSecret">The token secret, if available. If not available pass null or an empty string</param>
313 /// <param name="httpMethod">The http method used. Must be a valid HTTP method verb (POST,GET,PUT, etc)</param>
314 /// <returns>A base64 string of the hash value</returns>
315 public string GenerateSignature(
316 Uri url, string consumerKey, string consumerSecret, string token, string tokenSecret,
317 string httpMethod, string timeStamp, string nonce,
318 out string normalizedUrl, out string normalizedRequestParameters)
319 {
320 return GenerateSignature(
321 url, consumerKey, consumerSecret, token, tokenSecret,
322 httpMethod, timeStamp, nonce, SignatureTypes.HMACSHA1, null,
323 out normalizedUrl, out normalizedRequestParameters);
324 }
325
326 public string GenerateSignature(
327 Uri url, string consumerKey, string consumerSecret, string token, string tokenSecret,
328 string httpMethod, string timeStamp, string nonce,
329 string verifier,
330 out string normalizedUrl, out string normalizedRequestParameters)
331 {
332 return GenerateSignature(
333 url, consumerKey, consumerSecret, token, tokenSecret,
334 httpMethod, timeStamp, nonce, SignatureTypes.HMACSHA1, verifier,
335 out normalizedUrl, out normalizedRequestParameters);
336 }
337
338 /// <summary>
339 /// Generates a signature using the specified signatureType
340 /// </summary>
341 /// <param name="url">The full url that needs to be signed including its non OAuth url parameters</param>
342 /// <param name="consumerKey">The consumer key</param>
343 /// <param name="consumerSecret">The consumer seceret</param>
344 /// <param name="token">The token, if available. If not available pass null or an empty string</param>
345 /// <param name="tokenSecret">The token secret, if available. If not available pass null or an empty string</param>
346 /// <param name="httpMethod">The http method used. Must be a valid HTTP method verb (POST,GET,PUT, etc)</param>
347 /// <param name="signatureType">The type of signature to use</param>
348 /// <returns>A base64 string of the hash value</returns>
349 public string GenerateSignature(
350 Uri url, string consumerKey, string consumerSecret, string token, string tokenSecret,
351 string httpMethod, string timeStamp, string nonce, SignatureTypes signatureType,
352 string verifier,
353 out string normalizedUrl, out string normalizedRequestParameters)
354 {
355 normalizedUrl = null;
356 normalizedRequestParameters = null;
357
358 switch (signatureType)
359 {
360 case SignatureTypes.PLAINTEXT:
361 return HttpUtility.UrlEncode(string.Format("{0}&{1}", consumerSecret, tokenSecret));
362 case SignatureTypes.HMACSHA1:
363 string signatureBase =
364 GenerateSignatureBase(
365 url, consumerKey, token, tokenSecret,
366 httpMethod, timeStamp, nonce, HMACSHA1SignatureType, verifier,
367 out normalizedUrl, out normalizedRequestParameters);
368
369 HMACSHA1 hmacsha1 = new HMACSHA1();
370 hmacsha1.Key = Encoding.ASCII.GetBytes(string.Format("{0}&{1}", UrlEncode(consumerSecret), string.IsNullOrEmpty(tokenSecret) ? "" : UrlEncode(tokenSecret)));
371
372 return GenerateSignatureUsingHash(signatureBase, hmacsha1);
373 case SignatureTypes.RSASHA1:
374 throw new NotImplementedException();
375 default:
376 throw new ArgumentException("Unknown signature type", "signatureType");
377 }
378 }
379
380 /// <summary>
381 /// Generate the timestamp for the signature
382 /// </summary>
383 /// <returns></returns>
384 public virtual string GenerateTimeStamp()
385 {
386 // Default implementation of UNIX time of the current UTC time
387 TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
388 return Convert.ToInt64(ts.TotalSeconds).ToString();
389 }
390
391 /// <summary>
392 /// Generate a nonce
393 /// </summary>
394 /// <returns></returns>
395 public virtual string GenerateNonce()
396 {
397 // Just a simple implementation of a random number between 123400 and 9999999
398 return random.Next(123400, 9999999).ToString();
399 }
400 }