I am sending requests to Kraken api using a private key. The problem is, generated signature is not as expected.
Please note that the key you can see below it's just a sample. But it should generate the expected output. If so, that means the code is correct and it would work with an actual key.
The goal is to perform the following encryption:
HMAC-SHA512 of (URI path + SHA256(nonce + POST data)) and base64 decoded secret API key
So far, I've written this code:
public class KrakenApi
{
private static string ApiPrivateKey = "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==";
public static string GetKrakenSignature(string urlPath, ulong nonce, Dictionary<string,string> data)
{
var hash256 = new SHA256Managed();
var postData = string.Join("&", data.Select(e => e.Key + "=" + e.Value).ToArray());
var encoded = Encoding.UTF8.GetBytes(nonce + postData);
var message = Encoding.UTF8.GetBytes(urlPath).Concat(hash256.ComputeHash(encoded).ToArray());
var secretDecoded = (System.Convert.FromBase64String(ApiPrivateKey));
var hmacsha512 = new HMACSHA512(secretDecoded);
var hash = hmacsha512.ComputeHash(secretDecoded.Concat(message).ToArray());
return System.Convert.ToBase64String(hash);
}
}
Usage:
var data = new Dictionary<string, string> {
{ "nonce", "1616492376594" },
{ "ordertype", "limit" },
{ "pair", "XBTUSD" },
{ "price", "37500" },
{ "type", "buy" },
{ "volume", "1.25" }
};
var signature = KrakenApi.GetKrakenSignature("/0/private/AddOrder", ulong.Parse(data["nonce"]), data);
Console.WriteLine(signature);
Output should be:
4/dpxb3iT4tp/ZCVEwSnEsLxx0bqyhLpdfOpc6fn7OR8+UClSV5n9E6aSS8MPtnRfp32bAb0nmbRn6H8ndwLUQ==
There are a number of code samples for Phyton, Node and Golang here that work correctly. But above code in C# not generating expected output.
This generates the expected output. Finally I've read Python documentation for each method used in the code sample and reproduced the same steps in C#.
public static string GetKrakenSignature(string urlPath, ulong nonce, Dictionary<string,string> data)
{
var hash256 = new SHA256Managed();
var postData = string.Join("&", data.Select(e => e.Key + "=" + e.Value).ToArray());
var encoded = Encoding.UTF8.GetBytes(nonce + postData);
var message = Encoding.UTF8.GetBytes(urlPath).Concat(hash256.ComputeHash(encoded)).ToArray();
var mac = new HMACSHA512(Convert.FromBase64String(ApiPrivateKey));
return Convert.ToBase64String(mac.ComputeHash(message));
}
Related
I have to rewrite this php code to c#
$sign_params = ['name'=>"John", 'age'=>18];
$client_secret = 'test secret key';
ksort($sign_params); // Sort array by keys
$sign_params_query = http_build_query($sign_params); // Forming string like "param_name1=value¶m_name2=value"
$sign = rtrim(strtr(base64_encode(hash_hmac('sha256', $sign_params_query, $client_secret, true)), '+/', '-_'), '='); // get hash code
return $sign;
Here what I try:
public class apiHelper : MonoBehaviour
{
const string client_secret = "test secret key";
public static string checkSign(Dictionary<string, dynamic> fields)
{
//* sort by keys
var list = fields.Keys.ToList();
list.Sort();
string sign_params_query = "";
//* forming string like "param_name1=value¶m_name2=value"
foreach (var key in list)
{
sign_params_query = sign_params_query + key + "=" + fields[key];
if (key != list.Last()) sign_params_query = sign_params_query + "&";
}
//* get hash code
string sign = System.Convert.ToBase64String(GetHash(sign_params_query, client_secret));
char[] charsToTrim = { '=' };
return sign.Replace("+", "-").TrimEnd(charsToTrim);
}
static byte[] GetHash(string url, string key)
{
using (HMACSHA256 hmac = new HMACSHA256(Encoding.ASCII.GetBytes(key)))
{
byte[] data = hmac.ComputeHash(Encoding.UTF8.GetBytes(url));
return data;
}
}
}
Well, finally I get different hash than in php example ._. What did I wrong? Its my 1st time with cryptho or smth like that
Well, problem wath with strings which have special chars. I have to use url_encode c# equivalent to solve it.
I got "The account name or password that you have entered is incorrect" error when trying login using this api endpoint: https://steamcommunity.com/login/dologin/
I am using the credentials I use when logging in via Steam app or Steam web, so I don't think I have a problem with my credentials.
Here code which code I use:
public bool DoLogin(string username, string password)
{
var data = new NameValueCollection { { "username", username } };
// First get the RSA key with which we will encrypt our password.
string response = Fetch("https://steamcommunity.com/login/getrsakey", "POST", data, false);
GetRsaKey rsaJson = JsonConvert.DeserializeObject<GetRsaKey>(response);
// Validate, if we could get the rsa key.
if (!rsaJson.success)
{
return false;
}
// RSA Encryption.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAParameters rsaParameters = new RSAParameters
{
Exponent = HexToByte(rsaJson.publickey_exp),
Modulus = HexToByte(rsaJson.publickey_mod)
};
rsa.ImportParameters(rsaParameters);
// Encrypt the password and convert it.
byte[] bytePassword = Encoding.ASCII.GetBytes(password);
byte[] encodedPassword = rsa.Encrypt(bytePassword, false);
string encryptedBase64Password = Convert.ToBase64String(encodedPassword);
SteamResult loginJson = null;
CookieCollection cookieCollection;
string steamGuardText = "";
string steamGuardId = "";
// Do this while we need a captcha or need email authentification. Probably you have misstyped the captcha or the SteamGaurd code if this comes multiple times.
do
{
Console.WriteLine("SteamWeb: Logging In...");
bool captcha = loginJson != null && loginJson.captcha_needed;
bool steamGuard = loginJson != null && loginJson.emailauth_needed;
string time = Uri.EscapeDataString(rsaJson.timestamp);
string capGid = string.Empty;
// Response does not need to send if captcha is needed or not.
// ReSharper disable once MergeSequentialChecks
if (loginJson != null && loginJson.captcha_gid != null)
{
capGid = Uri.EscapeDataString(loginJson.captcha_gid);
}
data = new NameValueCollection { { "password", encryptedBase64Password }, { "username", username } };
// Captcha Check.
string capText = "";
if (captcha)
{
Console.WriteLine("SteamWeb: Captcha is needed.");
System.Diagnostics.Process.Start("https://steamcommunity.com/public/captcha.php?gid=" + loginJson.captcha_gid);
Console.WriteLine("SteamWeb: Type the captcha:");
string consoleText = Console.ReadLine();
if (!string.IsNullOrEmpty(consoleText))
{
capText = Uri.EscapeDataString(consoleText);
}
}
data.Add("captchagid", captcha ? capGid : "-1");
data.Add("captcha_text", captcha ? capText : "");
// Captcha end.
// Added Header for two factor code.
data.Add("twofactorcode", "");
// Added Header for remember login. It can also set to true.
data.Add("remember_login", "false");
// SteamGuard check. If SteamGuard is enabled you need to enter it. Care probably you need to wait 7 days to trade.
// For further information about SteamGuard see: https://support.steampowered.com/kb_article.php?ref=4020-ALZM-5519&l=english.
if (steamGuard)
{
Console.WriteLine("SteamWeb: SteamGuard is needed.");
Console.WriteLine("SteamWeb: Type the code:");
string consoleText = Console.ReadLine();
if (!string.IsNullOrEmpty(consoleText))
{
steamGuardText = Uri.EscapeDataString(consoleText);
}
steamGuardId = loginJson.emailsteamid;
// Adding the machine name to the NameValueCollection, because it is requested by steam.
Console.WriteLine("SteamWeb: Type your machine name:");
consoleText = Console.ReadLine();
var machineName = string.IsNullOrEmpty(consoleText) ? "" : Uri.EscapeDataString(consoleText);
data.Add("loginfriendlyname", machineName != "" ? machineName : "defaultSteamBotMachine");
}
data.Add("emailauth", steamGuardText);
data.Add("emailsteamid", steamGuardId);
// SteamGuard end.
// Added unixTimestamp. It is included in the request normally.
var unixTimestamp = (int)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
// Added three "0"'s because Steam has a weird unix timestamp interpretation.
data.Add("donotcache", unixTimestamp + "000");
data.Add("rsatimestamp", time);
// Sending the actual login.
using (HttpWebResponse webResponse = Request("https://steamcommunity.com/login/dologin/", "POST", data, false))
{
var stream = webResponse.GetResponseStream();
if (stream == null)
{
return false;
}
using (StreamReader reader = new StreamReader(stream))
{
string json = reader.ReadToEnd();
loginJson = JsonConvert.DeserializeObject<SteamResult>(json);
cookieCollection = webResponse.Cookies;
}
}
} while (loginJson.captcha_needed || loginJson.emailauth_needed);
// If the login was successful, we need to enter the cookies to steam.
if (loginJson.success)
{
_cookies = new CookieContainer();
foreach (Cookie cookie in cookieCollection)
{
_cookies.Add(cookie);
}
SubmitCookies(_cookies);
return true;
}
else
{
Console.WriteLine("SteamWeb Error: " + loginJson.message);
return false;
}
}
enter image description here
Is there another solution how to login to steam and get html?
Ok, so I checked your encrypting and it looks fine. It is a little bit of chaos in your code so i will explain it with my:
I prefer to use RestSharp and Newton Soft JSON to do this, so a little mandatory stuff in Class body:
private IRestClient restClientTemporary;
private string getKeysURL = "/login/getrsakey/";
private string loginWithKey = "/login/dologin/";
Create RestClient, and RestRequests:
restClientTemporary = new RestClient("https://steamcommunity.com");
var request = new RestRequest(getKeysURL, Method.POST);
request.AddParameter("username", "YourSteamLogin");
var resp = restClientTemporary.Execute(request);
GetRsaResult response = Newtonsoft.Json.JsonConvert.DeserializeObject<GetRsaResult>(resp.Content);
Then i used your code as method to encrypt:
public static string EncryptionSof(string password, GetRsaResult response)
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAParameters rsaParameters = new RSAParameters
{
Exponent = HexToByte(response.publickey_exp),
Modulus = HexToByte(response.publickey_mod)
};
rsa.ImportParameters(rsaParameters);
// Encrypt the password and convert it.
byte[] bytePassword = Encoding.ASCII.GetBytes(password);
byte[] encodedPassword = rsa.Encrypt(bytePassword, false);
return Convert.ToBase64String(encodedPassword);
}
And then used this method:
string password = EncryptionSof("admin123/*its your steam password i think*/", response);
Next step is make request to get login data:
var loginRequest = new RestRequest(loginWithKey);
loginRequest.AddParameter("username", "YourSteamLogin");
loginRequest.AddParameter("password", password);
loginRequest.AddParameter("rsatimestamp", response.timestamp);
loginRequest.AddParameter("remember_login", false);
//Captcha stuff if needed:
loginRequest.AddParameter("captchagid", 3086601225255895896);
loginRequest.AddParameter("captcha_text", "LHYJ2P");
And finally execute it:
var responseFinal = restClientTemporary.Execute(loginRequest);
In response i received everything i need in responseFinal.Content:
{
"success":true,
"requires_twofactor":false,
"login_complete":true,
"transfer_urls":[
"https:\\/\\/store.steampowered.com\\/login\\/transfer",
"https:\\/\\/help.steampowered.com\\/login\\/transfer"
],
"transfer_parameters":{
"steamid":"12344567898765432",
"token_secure":"xDDDDDDDDD",
"auth":"LOOOOOL",
"remember_login":false
}
}
GetRsaResult model looks like:
public class GetRsaResult
{
public bool success { get; set; }
public string publickey_mod { get; set; }
public string publickey_exp { get; set; }
public string timestamp { get; set; }
public string token_gid { get; set; }
}
Ah, and i forget about changing 16-string to byte[], its method found on Stackoverflow:
public static byte[] HexToByte(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
And you have to remember one thing You never want to send plaintext password to steam. First request /login/getrsakey/ is just only for get keys to encrypt password. Steam gives you key to encrypt your password. So, you will use your plaintext password (in my sample its admin123 ) in your program to encrypt it with keys you received from Steam.
In second request /login/dologin/ you must send encrypted password (result of EncryptionSoF method)
I am sending a SIGNED authnRequest to the idp using c# and asp.net. My code signs the authnRequest but the signature validation fails at idp.
Details
I tried a lot of solutions but in vain.
This is what i am doing following guidlines set by https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf:
STEPS
Deflate the auth request, then base64 encode it and finally Url Encode it. Lets call it AR
Url encode the RelayState. Lets call it RS
Url encode the signing Algorithm string. Lets call it SA
So the string to be signed now becomes
SAMLRequest=AR&RelayState=RS&SigAlg=SA
Now i sign the string we get in step #4 using our private key (service provider private key).
6.The resultant signature that i get, i base 64 encode it, and then URL encode it. Thus i get a base64 and url encoded signature. Lets call it SG
Now i append the signature we got in step #6 to the querystring in step #4. So the final querystring becomes
SAMLRequest=AR&RelayState=RS&SigAlg=SA&Signature=SG
All this works fine but the signature validation is failing !
Here's my code which is similar to the code found here https://github.com/Sustainsys/Saml2/blob/v0.21.2/Kentor.AuthServices/WebSSO/Saml2RedirectBinding.cs#L53-L68
protected void btnSendAuthRequest_Click(object sender, EventArgs e)
{
string authRequest = txtInput.Text;
//authRequest = authRequest.TrimEnd('\r', '\n');
authRequest = DeflateBase64UrlEncode(authRequest);
string spPrivateKey= txtKey.Text;
string relayState = HttpUtility.UrlEncode("https://example.com/pages/home.aspx");
string qs = "SAMLRequest=" + authRequest + "&RelayState=" + relayState;
qs = AddSignature(qs, spPrivateKey);
txtOutput.Text = qs;
}
public string AddSignature(string queryString, string PrivateKeyNoHeaders)
{
RSACryptoServiceProvider tmpRsa = RSAKeyTests.RSAKeyUtils.DecodePrivateKeyInfo(Convert.FromBase64String(PrivateKeyNoHeaders));
string signingAlgorithmUrl = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
queryString += "&SigAlg=" + HttpUtility.UrlEncode(signingAlgorithmUrl);
var signatureDescription = (SignatureDescription)CryptoConfig.CreateFromName(signingAlgorithmUrl);
HashAlgorithm hashAlg = signatureDescription.CreateDigest();
hashAlg.ComputeHash(Encoding.UTF8.GetBytes(queryString));
AsymmetricSignatureFormatter asymmetricSignatureFormatter =
signatureDescription.CreateFormatter(
((RSACryptoServiceProvider)tmpRsa));
//.GetSha256EnabledRSACryptoServiceProvider());
// Is the signature failing because of above ?
byte[] signatureValue = asymmetricSignatureFormatter.CreateSignature(hashAlg);
queryString += "&Signature=" + HttpUtility.UrlEncode(Convert.ToBase64String(signatureValue));
return queryString;
}
private string DeflateBase64UrlEncode(string input)
{
var inputs = string.Format(input, Guid.NewGuid());
var bytes = Encoding.UTF8.GetBytes(inputs);
using (var output = new MemoryStream())
{
using (var zip = new DeflateStream(output, CompressionMode.Compress))
{
zip.Write(bytes, 0, bytes.Length);
}
var base64 = Convert.ToBase64String(output.ToArray());
return HttpUtility.UrlEncode(base64);
}
}
CryptoConfig.createFromName(...) doesn't know about http://www.w3.org/2000/09/xmldsig#rsa-sha1 as the digest+signing algorithm. If CryptoConfig.createFromName() is not returning null, whatever algorithm is registered for http://www.w3.org/2000/09/xmldsig#rsa-sha1 might not be RSA-SHA1. Here's an explicit implementation of SignatureDescription with RSA and SHA1:
public class RSASHA1SignatureDescription : SignatureDescription {
public RSASHA1SignatureDescription() {
KeyAlgorithm = "System.Security.Cryptography.RSA";
DigestAlgorithm = "System.Security.Cryptography.SHA1Cng";
FormatterAlgorithm = "System.Security.Cryptography.RSAPKCS1SignatureFormatter";
DeformatterAlgorithm = "System.Security.Cryptography.RSAPKCS1SignatureDeformatter";
_hashAlgorithm = "SHA1";
}
public override AsymmetricSignatureDeformatter CreateDeformatter(AsymmetricAlgorithm key) {
AsymmetricSignatureDeformatter item = (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName(DeformatterAlgorithm);
item.setKey(key);
item.SetHashAlgorithm(_hashAlgorithm);
return item;
}
public override AsymmetricSignatureFormatter CreateFormatter(AsymmetricAlgorithm key) {
AsymmetricSignatureFormatter item = (AsymmetricSignatureFormatter) CryptoConfig.CreateFromName(FormatterAlgorithm);
item.setKey(key);
item.SetHashAlgorithm(_hashAlgorithm);
return item;
}
private string _hashAlgorithm;
}
The other possibility is that however you're validating the signature doesn't want rsa-sha1 (many identity providers prohibit rsa-sha1 via configuration) or the validation is incorrect. Try registering with a real IdP such as Okta or Salesforce and validate there.
I am trying to get access to my Withings/Nokia scales data via oauth (.net core C#).
Instructions can be found at:
https://oauth.withings.com/en/api/oauthguide
And API guide here:
https://developer.health.nokia.com/api#step1
I have achieved Part 1 - I get an auth token and secret.
Part 2 - manually I have retrieved a code authorizing my app's usage of my withings scales data - i.e. auth code as a result of the call back (via the API developers page). I am presuming this only needs to be done once to authorize my app's access permanently. I have hardcoded this value into my code and update it if I re-authorize the app.
Now I am stuck on Part 3 - getting the access token/secret.
ERROR = Invalid signature
(using the above page I have been able to retrieve my 4 years worth of scales data so I know it should work).
My base signature is identical to the above API test page (apart from the nonce, signature and timestamp).
My url is identical to to the above API test page (apart from the nonce and timestamp).
The mystery for me is why this works for Part 1 and not Part 3.
Is it the code that is bad or simply that the request token must be authorized against the application/users data before a request can be made?
But surely I don't have to re-authorize with the user every time??
I originally messed up the Part 1 and gave an invalid signature error - this was clearly an issue with the signature - but I have re-checked the signature in Part 3 and it is good.
private const string AUTH_VERSION = "1.0";
private const string SIGNATURE_METHOD = "HMAC-SHA1";
private const string BASE_URL_REQUEST_AUTH_TOKEN = "https://developer.health.nokia.com/account/request_token";
private const string BASE_URL_REQUEST_ACCESS_TOKEN = "https://developer.health.nokia.com/account/access_token";
...
Withings w = new Withings();
OAuthToken t = await w.GetOAuthToken();
string token = t.OAuth_Token;
string secret = t.OAuth_Token_Secret;
OAuthAccessToken at = await w.GetOAuthAccess(t);
string aToken = at.OAuth_Token;
string aTokenSecret = at.OAuth_Token_Secret;
...
public async Task<OAuthAccessToken> GetOAuthAccess(OAuthToken authToken)
{
OAuthAccessToken token = new OAuthAccessToken();
try
{
string random = GetRandomString();
string timestamp = GetTimestamp();
string baseSignature = GetOAuthAccessSignature(authToken, random, timestamp);
string hashSignature = ComputeHash(baseSignature, CONSUMER_SECRET, authToken.OAuth_Token_Secret);
string codeSignature = UrlEncode(hashSignature);
string requestUrl = GetOAuthAccessUrl(authToken, codeSignature, random, timestamp);
HttpResponseMessage response = await client.GetAsync(requestUrl);
string responseBodyAsText = await response.Content.ReadAsStringAsync();
string[] parameters = responseBodyAsText.Split('&');
token.OAuth_Token = parameters[0].Split('=')[1].ToString();
token.OAuth_Token_Secret = parameters[1].Split('=')[1].ToString();
}
catch (Exception ex)
{
}
return token;
}
private string GetOAuthAccessSignature(OAuthToken authToken, string random, string timestamp)
{
var urlDict = new SortedDictionary<string, string>
{
//{ "oauth_consumer_key", CONSUMER_KEY},
{ "oauth_nonce", random},
{ "oauth_signature_method", UrlEncode(SIGNATURE_METHOD)},
{ "oauth_timestamp", timestamp},
{ "oauth_token", END_USER_AUTHORISATION_REQUEST_TOKEN },
{ "oauth_version", AUTH_VERSION}
};
StringBuilder sb = new StringBuilder();
sb.Append("GET&" + UrlEncode(BASE_URL_REQUEST_ACCESS_TOKEN) + "&oauth_consumer_key%3D" + CONSUMER_KEY);
int count = 0;
foreach (var urlItem in urlDict)
{
count++;
if (count >= 1) sb.Append(UrlEncode("&"));
sb.Append(UrlEncode(urlItem.Key + "=" + urlItem.Value));
}
return sb.ToString();
}
private string GetOAuthAccessUrl(OAuthToken authToken, string signature, string random, string timestamp)
{
var urlDict = new SortedDictionary<string, string>
{
{ "oauth_consumer_key", CONSUMER_KEY},
{ "oauth_nonce", random},
{ "oauth_signature", signature },
{ "oauth_signature_method", UrlEncode(SIGNATURE_METHOD)},
{ "oauth_timestamp", timestamp},
{ "oauth_token", END_USER_AUTHORISATION_REQUEST_TOKEN },
{ "oauth_version", AUTH_VERSION}
};
StringBuilder sb = new StringBuilder();
sb.Append(BASE_URL_REQUEST_ACCESS_TOKEN + "?");
int count = 0;
foreach (var urlItem in urlDict)
{
count++;
if (count > 1) sb.Append("&");
sb.Append(urlItem.Key + "=" + urlItem.Value);
}
return sb.ToString();
}
Notes:
I have ordered my parameters
I have a decent urlencode (correct me wrong)
I have a hmac-sha1 hashing (correct me wrong)
Not interested in using open libraries - I want to fix this code without third party tools
Below is the helper methods I am using:
private string ComputeHash(string data, string consumerSecret, string tokenSecret = null)
{
// Construct secret key based on consumer key (and optionally include token secret)
string secretKey = consumerSecret + "&";
if (tokenSecret != null) secretKey += tokenSecret;
// Initialise with secret key
System.Security.Cryptography.HMACSHA1 hmacsha = new System.Security.Cryptography.HMACSHA1(Encoding.ASCII.GetBytes(secretKey));
hmacsha.Initialize();
// Convert data into byte array
byte[] dataBuffer = Encoding.ASCII.GetBytes(data);
// Computer hash of data byte array
byte[] hashBytes = hmacsha.ComputeHash(dataBuffer);
// Return the base 64 of the result
return Convert.ToBase64String(hashBytes);
}
// Get random string
private string GetRandomString()
{
return Guid.NewGuid().ToString().Replace("-", "");
}
// Get timestamp
private string GetTimestamp()
{
var ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalSeconds).ToString();
}
// Url Encode (as Uri.Escape is reported to be not appropriate for this purpose)
protected string UnreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
protected string UrlEncode(string value)
{
var result = new StringBuilder();
foreach (var symbol in value)
{
if (UnreservedChars.IndexOf(symbol) != -1)
result.Append(symbol);
else
result.Append('%' + $"{(int)symbol:X2}");
}
return result.ToString();
}
Thanks,Dan.
Solved - this was a problem with my understanding of how oauth works.
Step 1 - Get a token (this allows you to make requests based on your api account application)
Step 2 - Create a URL (using the 2 minute token above) that redirects the user to authorize your Withings api applications to use a specific user's account. The same token is returned as you passed it - but now it will be allowed to make the request in step 3.
Step 3 - Requests an access token - this will give you an token and secret string that permits your continued access to this user's account (for your api account application).
Step 4 - Requesting data - similar in method to all previous steps - quite easy. Returns a big long string of data. Read the API documents as you can filter - which is what I will be doing as I have about 4/5 years worth of 'interesting' weight data.
I was doing step 1 and then doing step 3 thinking that the code returned from step 2 (not having noticed it was the same as the one put in) could be stored and used for step 3 without having to re-authorize.
You can actually (and what I have done) is follow the API demo interface to generate the auth token and secret in step 3 and that's all you need to continue to request data. You only need user authorization once and store step 3 auth token/secret against a user account / a store of some sort.
Also note that you can invoke notifications - new weight triggers your website to automatically refresh the data. Great if you just want to login and see the latest data without having the manually trigger a refresh of the data / cause a further delay.
Be aware that the API has filtering options - so make sure you read up on those.
Here's some (basic) code which may be of some use:
public async Task<string> GetData_BodyMeasures()
{
string rawdata = "";
try
{
string random = GetRandomString();
string timestamp = GetTimestamp();
string baseSignature = GetDataSignature_BodyMeasure(random, timestamp);
string hashSignature = ComputeHash(baseSignature, CONSUMER_SECRET, ACCESS_OAUTH_TOKEN_SECRET);
string codeSignature = UrlEncode(hashSignature);
string requestUrl = GetData_BodyMeasure_Url(codeSignature, random, timestamp);
HttpResponseMessage response = await client.GetAsync(requestUrl);
string responseBodyAsText = await response.Content.ReadAsStringAsync();
rawdata = responseBodyAsText;
}
catch (Exception ex)
{
}
return rawdata;
}
private string GetDataSignature_BodyMeasure(string random, string timestamp)
{
var urlDict = new SortedDictionary<string, string>
{
{ "oauth_consumer_key", CONSUMER_KEY},
{ "oauth_nonce", random},
{ "oauth_signature_method", SIGNATURE_METHOD},
{ "oauth_timestamp", timestamp},
{ "oauth_token", ACCESS_OAUTH_TOKEN },
{ "oauth_version", AUTH_VERSION},
{ "userid", USER_ID }
};
StringBuilder sb = new StringBuilder();
sb.Append("GET&" + UrlEncode(BASE_URL_REQUEST_BODY_MEASURE) + "&action%3Dgetmeas");
int count = 0;
foreach (var urlItem in urlDict)
{
count++;
if (count >= 1) sb.Append(UrlEncode("&"));
sb.Append(UrlEncode(urlItem.Key + "=" + urlItem.Value));
}
return sb.ToString();
}
private string GetData_BodyMeasure_Url(string signature, string random, string timestamp)
{
var urlDict = new SortedDictionary<string, string>
{
{ "action", "getmeas"},
{ "oauth_consumer_key", CONSUMER_KEY},
{ "oauth_nonce", random},
{ "oauth_signature", signature },
{ "oauth_signature_method", UrlEncode(SIGNATURE_METHOD)},
{ "oauth_timestamp", timestamp},
{ "oauth_token", ACCESS_OAUTH_TOKEN },
{ "oauth_version", AUTH_VERSION},
{ "userid", USER_ID }
};
StringBuilder sb = new StringBuilder();
sb.Append(BASE_URL_REQUEST_BODY_MEASURE + "?");
int count = 0;
foreach (var urlItem in urlDict)
{
count++;
if (count >= 1) sb.Append("&");
sb.Append(urlItem.Key + "=" + urlItem.Value);
}
return sb.ToString();
}
This is what I have so far, but it isn't working as I don't understand how DotNetOpenAuth is supposed to work. I only need it to sign the outcome with my key, but I am not having luck. Everything seems to point towards me needing to get the client to authorize my access, but I just need to get it signed as I don't need the user for this request.
Refer to http://developer.netflix.com/docs/read/Security , the section labeled "Netflix API Requests"
public class class1
{
private void Main()
{
string consumerKey = "<MyAPIKey>";
string consumerSecret = "<MyAPISharedSecret>";
var tokenManager = new InMemoryTokenManager(consumerKey, consumerSecret);
MessageReceivingEndpoint oauthEndpoint =
new MessageReceivingEndpoint(new Uri("http://api-public.netflix.com/catalog/titles/index"),
HttpDeliveryMethods.PostRequest);
WebConsumer consumer = new WebConsumer(
new ServiceProviderDescription
{
RequestTokenEndpoint = oauthEndpoint,
UserAuthorizationEndpoint = oauthEndpoint,
AccessTokenEndpoint = oauthEndpoint,
TamperProtectionElements =
new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement()},
},
tokenManager);
var result = consumer.Channel.Request(new AccessProtectedResourceRequest());
}
internal class InMemoryTokenManager : IConsumerTokenManager
{
private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();
public InMemoryTokenManager(string consumerKey, string consumerSecret)
{
if (string.IsNullOrEmpty(consumerKey))
{
throw new ArgumentNullException("consumerKey");
}
this.ConsumerKey = consumerKey;
this.ConsumerSecret = consumerSecret;
}
public string ConsumerKey { get; private set; }
public string ConsumerSecret { get; private set; }
public string GetTokenSecret(string token)
{
return this.tokensAndSecrets[token];
}
public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response)
{
this.tokensAndSecrets[response.Token] = response.TokenSecret;
}
public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken,
string accessTokenSecret)
{
this.tokensAndSecrets.Remove(requestToken);
this.tokensAndSecrets[accessToken] = accessTokenSecret;
}
public TokenType GetTokenType(string token)
{
throw new NotImplementedException();
}
}
}
Your actual question should be something like 'Is it possible to use DotNetOpenAuth to sign requests with or without access token?", to answer that question I should say I don't know and even I can't find it out by reading DotNetOpenAuth codebase.
There is no single page of documentation available for DotNetOpenAuth and the codebase is so huge that you can't read it and understand what is supported by it or not.
I guess making non-authenticated request is not an issue as it is simply a query string parameter added to your request.
But to make signed requests you need to follow a simple process:
Collecting request parameters
Calculating signature
Making request(signed/protected)
Collecting request parameters
These are basically two categories of parameters, oauth specific parameters and Netflix API specific parameters.
Among the OAuth specific parameters is nonce, this is the code in which you can use to generate a nonce value:
public static string GenerateNonce()
{
byte[] bytes = new byte[32];
var first = Guid.NewGuid().ToByteArray();
var second = Guid.NewGuid().ToByteArray();
for (var i = 0; i < 16; i++)
bytes[i] = first[i];
for (var i = 16; i < 32; i++)
bytes[i] = second[i - 16];
var result = Convert.ToBase64String(bytes, Base64FormattingOptions.None);
result = new string(result.ToCharArray().Where(char.IsLetter).ToArray());
return result;
}
And another OAuth specific parameter is timestamp, this is the code in which you can use to calculate timestamp:
DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds
Other oauth specific parameters are easy to provision and no need to write a specific code for them.
API specific parameters are any value you add to query string or to the authorization headers(except the oauth_signature itself) or to the body request(if request content type is application/x-www-form-urlencoded).
Calculating signature
To make either a signed request or a protected signature you need to calculate a signature, which the process is almost the same, except the way that you create signing key:
Calculate signature base string
Calculate signing key
Creating the signature by signing the signature base string using signing key
To calculate signature base string you need to first concatenate all parameters into a string and the percent encode the whole string. This is the code which helps you doing percent encoding:
public static string Encode(string source)
{
Func<char, string> encodeCharacter = c => {
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '.' || c == '-' || c == '_' || c == '~'))
return new string(c, 1);
return EncodeCharacter(c);
};
return string.Concat(source.ToCharArray().Select(encodeCharacter));
}
Also you need to sort parameters in alphabetical order and be concatenated using '&'. Here is the code which you may have to write to do this:
public static string CalculateParameterString(KeyValuePair<string, string>[] parameters)
{
var q = from entry in parameters
let encodedkey = PercentEncode.Encode(entry.Key)
let encodedValue = PercentEncode.Encode(entry.Value)
let encodedEntry = encodedkey + "=" + encodedValue
orderby encodedEntry
select encodedEntry;
var result = string.Join("&", q.ToArray());
return result;
}
Lets call the above string 'parameters string'. Then to calculate signature base string all you need is to concatenate http verb of your request, your request's url and parameters string together using '&'. Also you need to percent encode them first. Here is the code which does that:
public static string CalcualteSignatureBaseString(string httpMethod, string baseUri, string parametersString)
{
return httpMethod.ToUpper() + "&" + PercentEncode.Encode(baseUri) + "&" + PercentEncode.Encode(parametersString);
}
Once you have created signature base string then you the next step is to calculate signing key.
If you just need to make a signed request, then you create signing key based on your consumer key(shared secret) only. This the signing key to be used to make a signed request.
During authorization process, if you just made a request token request and recieved a temporary oauth token, then your singing key is based on your consumer key and that oauth token. This is the signing key used to make request to get the access token.
If a user authorized your application and you have the relevant access token, then your signing key would be your consumer key and access token. This is the signing key to make a protected request.
This is the code that will generate the signing key:
public static string GetSigningKey(string ConsumerSecret, string OAuthTokenSecret = null)
{
return ConsumerSecret + "&" + (OAuthTokenSecret != null ? OAuthTokenSecret : "");
}
In your case, to make a signed request, you just need pass null value for OAuthTokenSecret parameter.
Ok, now you have a signature base string, all you need to do now is to sign using HMAC-SHA1 algorithm:
public static string Sign(string signatureBaseString, string signingKey)
{
var keyBytes = System.Text.Encoding.ASCII.GetBytes(signingKey);
using (var myhmacsha1 = new System.Security.Cryptography.HMACSHA1(keyBytes)) {
byte[] byteArray = System.Text.Encoding.ASCII.GetBytes(signatureBaseString);
var stream = new MemoryStream(byteArray);
var signedValue = myhmacsha1.ComputeHash(stream);
var result = Convert.ToBase64String(signedValue, Base64FormattingOptions.None);
return result;
}
}
To sum up this is the whole process for calculating signature:
public virtual string GetSignature(string consumerSecret, string tokenSecret, string uri, string method, params ParameterSet[] parameters)
{
var allParameters = parameters.SelectMany(p => p.ToList()).ToArray();
var parametersString = CalculateSignatureBaseString(allParameters);
var signatureBaseString = OAuth1aUtil.CalcualteSignatureBaseString(method, uri, parametersString);
var sigingKey = GetSigningKey(consumerSecret, tokenSecret);
var signature = Sign(signatureBaseString, sigingKey);
return signature;
}
Making request
Now you just need to make a valid http request and add the oauth parameters to the request as the 'Authorization' header.