Signature Validation for a SAML Authn Request via GET method fails - c#

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.

Related

"The account name or password that you have entered is incorrect" Steam Api error

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)

NetSuite C# Console App Web Services Token Authentication Returning "Ambiguous Authentication"

I'm trying to get my integrations updated to handle this forced use of token authentication for NetSuite web services. However, I'm stuck getting the following:
[System.Web.Services.Protocols.SoapException] Ambiguous authentication
I've tried various things with no improvement. Doesn't seem to matter if I use a token created beforehand, or have one generated. I'm not creating a "Cookie Container", or adding a standard Passport object. I've tried a few different methods for generating a signature and a nonce value. I've even switched between SHA1 and SHA256 thinking that might make a difference. I'm going to include my code here. Hopefully someone can see what I'm doing wrong.
FYI, there are some components in here from trying what I found in this post: Ambiguous Authentication in Netsuite Token Based API call
static void Main(string[] args) {
NameValueCollection _dataCollection = ConfigurationManager.AppSettings;
NSCreds crd = new NSCreds(_dataCollection); /// just building a data object to handle credentials and keys
NSWS ns = new NSWS(crd, _dataCollection["appId"]); // token passport gets built here
// now to make a call to just get a single file from the File Cabinet
RecordRef pullFile = new RecordRef();
pullFile.type = RecordType.file;
pullFile.typeSpecified = true;
pullFile.internalId = _dataCollection["fileId"];
ReadResponse rres = ns.service.get(pullFile); // this line throws the error highlighted above
}
public NSWS(NSCreds c, String appId) {
CheckConnectionSecurity(); // makes sure connection security is set to TLS 1.2
_pageSize = 100;
service = new NetSuiteService();
service.Timeout = 1000 * 60 * 60 * 2;
service.tokenPassport = prepareTokenPassport(c);
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.applicationId = appId;
service.applicationInfo = appInfo;
}
public TokenPassport prepareTokenPassport(NSCreds c) {
long TimeStamp = ComputeTimestamp();
String nonce = CreateNonce_1();
NSToken token = null;
if (String.IsNullOrEmpty(c.tokenId) && String.IsNullOrEmpty(c.tokenSecret)) {
token = GetToken(c.customerKey, c); // make calls to NetSuite to generate token data and build custom object
} else {
token = new NSToken(c.tokenId,c.tokenSecret); // build custom object from apps settings data
}
String signature = ComputeSignature(c.account, c.customerKey, c.customerSecret, token.tokenId, token.tokenSecret, nonce, TimeStamp);
TokenPassport tokenPassport = new TokenPassport();
tokenPassport.account = c.account;
tokenPassport.consumerKey = c.customerKey;
tokenPassport.token = token.tokenId;
tokenPassport.nonce = nonce;
tokenPassport.timestamp = TimeStamp;
TokenPassportSignature signatureElement = new TokenPassportSignature();
signatureElement.algorithm = "HMAC-SHA1"; // "HMAC-SHA256";
signatureElement.Value = signature;
tokenPassport.signature = signatureElement;
return tokenPassport;
}
private static long ComputeTimestamp() {
return ((long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
}
private String CreateNonce_1() {
int length = 20;
String AllowedChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
StringBuilder nonce = new StringBuilder();
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] rnd = new byte[1];
for (int i = 0; i < length; i++) {
while (true) {
rng.GetBytes(rnd);
char c = (char)rnd[0];
if (AllowedChars.IndexOf(c) != (-1)) {
nonce.Append(rnd[0]);
break;
}
}
}
return nonce.ToString();
}
private static string CreateNonce_2() {
return Guid.NewGuid().ToString("N");
}
private String CreateNonce_3() {
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] data = new byte[20];
rng.GetBytes(data);
int value = Math.Abs(BitConverter.ToInt32(data, 0));
return value.ToString();
}
private string ComputeSignature(String account, String cKey, String cSecret, String token, String tokenSecret, String nonce, long timeStamp) {
String baseString = String.Format("{0}&{1}&{2}&{3}&{4}",account,cKey,token,nonce,timeStamp);
String key = String.Format("{0}&{1}", cSecret, tokenSecret);
// previous method for encoding the signature
// Mac is a custom object found from another post here
// EncryptionMethods is an enumeration from that same post
/*
//using (var secretKey = new SecretKeySpec(GetBytes(key), EncryptionMethods.HMACSHA256))
using (var secretKey = new SecretKeySpec(GetBytes(key), EncryptionMethods.HMACSHA1))
using (Mac mac = new Mac(secretKey, baseString)) {
return mac.AsBase64();
}
*/
//HMACSHA256 hashObject = new HMACSHA256(Encoding.UTF8.GetBytes(key));
HMACSHA1 hashObject = new HMACSHA1(Encoding.UTF8.GetBytes(key));
byte[] signature = hashObject.ComputeHash(Encoding.UTF8.GetBytes(baseString));
string encodedSignature = Convert.ToBase64String(signature);
return encodedSignature;
}
It turns out that the problem was setting the Application ID while also specifying a Token Passport. Doing so actually creates a conflict with the system not knowing which to use for authentication, since the token itself references the Application ID internally. So, removed the bit where the Application ID was being set to the service object and everything started working correctly.
Try to use this code for token based authentication
TokenPassport tokenPassport = new TokenPassport();
tokenPassport.account = account; //Account ID
tokenPassport.consumerKey = consumerKey; //Consumer Key
tokenPassport.token = tokenId; // Token ID
tokenPassport.nonce = nonce; //It is some calculated value with the help of RNGCryptoServiceProvider class.
tokenPassport.timestamp = timestamp;
tokenPassport.signature = signature; // It is TokenPassportSignature
TokenPassportSignature also uses Account ID, Token ID & Token Secret. It has some algorithms.

Withings API - Invalid signature

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();
}

Instagram API Enforce Signed Requests + Invalid signed-request: Signature does not match

I'am new to Instagram API, I have a client that needs to display the latest media on his website, but am doing the test under my personal Instagram account.
Under Security Tab both [Disable implicit OAuth] & [Enforce signed requests] are checked.
The Endpoint am trying to call is: [users/{user-id}/media/recent]
Signature Generation Method:
string GenerateSignature(string endpoint,
Dictionary<string, string> parameters, string secret) {
StringBuilder message = new StringBuilder(endpoint);
foreach (var param in parameters.OrderBy(p => p.Key))
{
message.AppendFormat("|{0}={1}", param.Key, param.Value);
}
return HMACSHA256_Hash(secret, message.ToString());
}
string HMACSHA256_Hash(string secret, string message)
{
// Create a HMAC-SHA256 digest of the message using the secret key
HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
byte[] digest = hmac.ComputeHash(Encoding.UTF8.GetBytes(message.ToString()));
// Return the digest as a hexstring to be used as a signature for the request
return ByteArrayToString(digest);
}
string ByteArrayToString(byte[] array)
{
// Convert the bytes in the array to a lower-case hexstring
return array.Aggregate(new StringBuilder(), (sb, b) => sb.Append(b.ToString("x2"))).ToString();
}
Get Recent Media:
protected void GetUserDetails()
{
string instagram_Username = "";
string instagram_UserID = "";
string instagram_AccessToken = "";
string instagram_ClientID = "";
string instagram_ClientSecret = "";
int count = 1;
string url = String.Format(#"https://api.instagram.com/v1/users/{0}/media/recent?client_id={1}&count={2}", instagram_UserID, instagram_ClientID, count);
string endPoint = String.Format("/users/{0}/media/recent", instagram_UserID);
Dictionary<string, string> endPointParameters = new Dictionary<string, string>();
endPointParameters.Add("client_id", instagram_ClientID);
endPointParameters.Add("count", count.ToString());
string signature = GenerateSignature(endPoint, endPointParameters, instagram_AccessToken);
url = url + "&sig=" + signature;
}
URL being called: https://api.instagram.com/v1/users/{user-id}/media/recent?client_id={cliend_id}&count=1&sig=7c024d67c1000106686c412778dc3534614d43822f9e13554418c3d3d5386872
Response: {"code": 403, "error_type": "OAuthForbiddenException", "error_message": "Invalid signed-request: Signature does not match"}
IN ADDITION; can I please know if access token will expire Or I can use same one for all my calls, noting that the call will be done each time the home page of the website is rendered :-)
I appreciate the help ! many thanks
I've missed that the secret should be the client Secret.
string signature = GenerateSignature(endPoint, endPointParameters, instagram_ClientSecret);

How do I use DotNetOpenAuth with the Netflix API?

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.

Categories