#region License Information (GPL v3)
/*
ShareX - A program that allows you to take screenshots and share any file type
Copyright (c) 2007-2020 ShareX Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Optionally you can also view the license at .
*/
#endregion License Information (GPL v3)
using ShareX.HelpersLib;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace ShareX.UploadersLib
{
public static class OAuthManager
{
private const string ParameterConsumerKey = "oauth_consumer_key";
private const string ParameterSignatureMethod = "oauth_signature_method";
private const string ParameterSignature = "oauth_signature";
private const string ParameterTimestamp = "oauth_timestamp";
private const string ParameterNonce = "oauth_nonce";
private const string ParameterVersion = "oauth_version";
private const string ParameterToken = "oauth_token";
private const string ParameterTokenSecret = "oauth_token_secret";
private const string ParameterVerifier = "oauth_verifier";
internal const string ParameterCallback = "oauth_callback";
private const string PlainTextSignatureType = "PLAINTEXT";
private const string HMACSHA1SignatureType = "HMAC-SHA1";
private const string RSASHA1SignatureType = "RSA-SHA1";
public static string GenerateQuery(string url, Dictionary args, HttpMethod httpMethod, OAuthInfo oauth)
{
return GenerateQuery(url, args, httpMethod, oauth, out _);
}
public static string GenerateQuery(string url, Dictionary args, HttpMethod httpMethod, OAuthInfo oauth, out Dictionary parameters)
{
if (string.IsNullOrEmpty(oauth.ConsumerKey) ||
(oauth.SignatureMethod == OAuthInfo.OAuthInfoSignatureMethod.HMAC_SHA1 && string.IsNullOrEmpty(oauth.ConsumerSecret)) ||
(oauth.SignatureMethod == OAuthInfo.OAuthInfoSignatureMethod.RSA_SHA1 && string.IsNullOrEmpty(oauth.ConsumerPrivateKey)))
{
throw new Exception("ConsumerKey or ConsumerSecret or ConsumerPrivateKey empty.");
}
parameters = new Dictionary();
parameters.Add(ParameterVersion, oauth.OAuthVersion);
parameters.Add(ParameterNonce, GenerateNonce());
parameters.Add(ParameterTimestamp, GenerateTimestamp());
parameters.Add(ParameterConsumerKey, oauth.ConsumerKey);
switch (oauth.SignatureMethod)
{
case OAuthInfo.OAuthInfoSignatureMethod.HMAC_SHA1:
parameters.Add(ParameterSignatureMethod, HMACSHA1SignatureType);
break;
case OAuthInfo.OAuthInfoSignatureMethod.RSA_SHA1:
parameters.Add(ParameterSignatureMethod, RSASHA1SignatureType);
break;
default:
throw new NotImplementedException("Unsupported signature method");
}
string secret = null;
if (!string.IsNullOrEmpty(oauth.UserToken) && !string.IsNullOrEmpty(oauth.UserSecret))
{
secret = oauth.UserSecret;
parameters.Add(ParameterToken, oauth.UserToken);
}
else if (!string.IsNullOrEmpty(oauth.AuthToken) && !string.IsNullOrEmpty(oauth.AuthSecret))
{
secret = oauth.AuthSecret;
parameters.Add(ParameterToken, oauth.AuthToken);
if (!string.IsNullOrEmpty(oauth.AuthVerifier))
{
parameters.Add(ParameterVerifier, oauth.AuthVerifier);
}
}
if (args != null)
{
foreach (KeyValuePair arg in args)
{
parameters[arg.Key] = arg.Value;
}
}
string normalizedUrl = NormalizeUrl(url);
string normalizedParameters = NormalizeParameters(parameters);
string signatureBase = GenerateSignatureBase(httpMethod, normalizedUrl, normalizedParameters);
byte[] signatureData;
switch (oauth.SignatureMethod)
{
case OAuthInfo.OAuthInfoSignatureMethod.HMAC_SHA1:
signatureData = GenerateSignature(signatureBase, oauth.ConsumerSecret, secret);
break;
case OAuthInfo.OAuthInfoSignatureMethod.RSA_SHA1:
signatureData = GenerateSignatureRSASHA1(signatureBase, oauth.ConsumerPrivateKey);
break;
default:
throw new NotImplementedException("Unsupported signature method");
}
string signature = Convert.ToBase64String(signatureData);
parameters[ParameterSignature] = signature;
return string.Format("{0}?{1}&{2}={3}", normalizedUrl, normalizedParameters, ParameterSignature, URLHelpers.URLEncode(signature));
}
public static string GetAuthorizationURL(string requestTokenResponse, OAuthInfo oauth, string authorizeURL, string callback = null)
{
string url = null;
NameValueCollection args = HttpUtility.ParseQueryString(requestTokenResponse);
if (args[ParameterToken] != null)
{
oauth.AuthToken = args[ParameterToken];
url = string.Format("{0}?{1}={2}", authorizeURL, ParameterToken, oauth.AuthToken);
if (!string.IsNullOrEmpty(callback))
{
url += string.Format("&{0}={1}", ParameterCallback, URLHelpers.URLEncode(callback));
}
if (args[ParameterTokenSecret] != null)
{
oauth.AuthSecret = args[ParameterTokenSecret];
}
}
return url;
}
public static NameValueCollection ParseAccessTokenResponse(string accessTokenResponse, OAuthInfo oauth)
{
NameValueCollection args = HttpUtility.ParseQueryString(accessTokenResponse);
if (args != null && args[ParameterToken] != null)
{
oauth.UserToken = args[ParameterToken];
if (args[ParameterTokenSecret] != null)
{
oauth.UserSecret = args[ParameterTokenSecret];
return args;
}
}
return null;
}
private static string GenerateSignatureBase(HttpMethod httpMethod, string normalizedUrl, string normalizedParameters)
{
StringBuilder signatureBase = new StringBuilder();
signatureBase.AppendFormat("{0}&", httpMethod.ToString());
signatureBase.AppendFormat("{0}&", URLHelpers.URLEncode(normalizedUrl));
signatureBase.AppendFormat("{0}", URLHelpers.URLEncode(normalizedParameters));
return signatureBase.ToString();
}
private static byte[] GenerateSignature(string signatureBase, string consumerSecret, string userSecret = null)
{
using (HMACSHA1 hmacsha1 = new HMACSHA1())
{
string key = string.Format("{0}&{1}", Uri.EscapeDataString(consumerSecret),
string.IsNullOrEmpty(userSecret) ? "" : Uri.EscapeDataString(userSecret));
hmacsha1.Key = Encoding.ASCII.GetBytes(key);
byte[] dataBuffer = Encoding.ASCII.GetBytes(signatureBase);
return hmacsha1.ComputeHash(dataBuffer);
}
}
private static byte[] GenerateSignatureRSASHA1(string signatureBase, string privateKey)
{
byte[] dataBuffer = Encoding.ASCII.GetBytes(signatureBase);
using (SHA1CryptoServiceProvider sha1 = GenerateSha1Hash(dataBuffer))
using (AsymmetricAlgorithm algorithm = new RSACryptoServiceProvider())
{
algorithm.FromXmlString(privateKey);
RSAPKCS1SignatureFormatter formatter = new RSAPKCS1SignatureFormatter(algorithm);
formatter.SetHashAlgorithm("MD5");
return formatter.CreateSignature(sha1);
}
}
private static SHA1CryptoServiceProvider GenerateSha1Hash(byte[] dataBuffer)
{
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
using (CryptoStream cs = new CryptoStream(Stream.Null, sha1, CryptoStreamMode.Write))
{
cs.Write(dataBuffer, 0, dataBuffer.Length);
}
return sha1;
}
private static string GenerateTimestamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalSeconds).ToString();
}
private static string GenerateNonce()
{
return Helpers.GetRandomAlphanumeric(12);
}
private static string NormalizeUrl(string url)
{
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
{
string port = "";
if ((uri.Scheme == "http" && uri.Port != 80) ||
(uri.Scheme == "https" && uri.Port != 443) ||
(uri.Scheme == "ftp" && uri.Port != 20))
{
port = ":" + uri.Port;
}
url = uri.Scheme + "://" + uri.Host + port + uri.AbsolutePath;
}
return url;
}
private static string NormalizeParameters(Dictionary parameters)
{
return string.Join("&", parameters.OrderBy(x => x.Key).ThenBy(x => x.Value).Select(x => x.Key + "=" + URLHelpers.URLEncode(x.Value)).ToArray());
}
}
}