After reviewing the OAuth 1.0 documentation for signing requests, I'm struggling getting my signature to match what Postman generates. I've checked things are capitalized / lowercased when necessary, parameters are sorted, encoding is done where appropriate, but I'm missing something.
public string SignRequest(string method, string url, string tokenSecret, Dictionary<string, string> parameters)
{
//, string consumerKey, string token, string timestamp, string nonce, string consumerSecret, string tokenSecret, string identityStatement
string baseString = method.ToUpper() + "&" + Uri.EscapeDataString(url) + "&";
string paramString = "";
var list = parameters.Keys.ToList<string>();
list.Sort();
foreach (string key in list)
{
paramString += key + "=" + Uri.EscapeDataString(parameters[key]) + "&";
}
paramString = paramString.Remove(paramString.Length - 1, 1);
baseString += Uri.EscapeDataString(paramString);
string signingKey = Uri.EscapeDataString(consumerSecret) + "&" + Uri.EscapeDataString(tokenSecret);
var signatureEncoding = new ASCIIEncoding();
var keyBytes = signatureEncoding.GetBytes(signingKey);
var signatureBaseBytes = signatureEncoding.GetBytes(baseString);
var hmacsha1 = new HMACSHA1(keyBytes);
var hashBytes = hmacsha1.ComputeHash(signatureBaseBytes);
var signatureString = Convert.ToBase64String(hashBytes);
return signatureString;
}
I've tried to simplify it down by all the parameters being "1", both secrets "1", the consumer key "1", and a dummy URL for both my implementation and Postman - still getting different signatures. An example of calling it with "1"s and a bogus URL:
postKeys.Add("oauth_consumer_key", "1");
postKeys.Add("oauth_token", "1");
postKeys.Add("oauth_signature_method", "HMAC-SHA1");
postKeys.Add("oauth_timestamp", "1");
postKeys.Add("oauth_nonce", "1");
postKeys.Add("oauth_version", "1");
string signature = SignRequest("GET", "http://hi.com", "1", postKeys);
When I use the same method for the initial retrieval of a token (no token secret yet), my signatures do match a Postman request.
I just can't figure out what I'm missing in here. This seems to match other implementations in other languages, but I can't figure out what I have wrong.
Related
I'm trying to get a local c# program that I have to connect to GCP (Bigquery).
I have gotten a credentials json file from GCP which looks something like:
{
"type": "service_account",
"project_id": "project-id-1",
"private_key_id": "veryprivatekeything",
"private_key": "-----BEGIN PRIVATE KEY-----\nmany_letters_and_symbols_here\n-----END PRIVATE KEY-----\n",
"client_email": "bq-access-for-test#project-id-1.iam.gserviceaccount.com",
"client_id": "1234567891011",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/bq-access-for-test%40project-id-1.iam.gserviceaccount.com"
}
My code looks something like:
string path2key = "C:/Users/me/Downloads/project-id-1-letters.json";
byte[] jsonBytes = File.ReadAllBytes(path2key);
// I also tried another encoding but same deal
// string jsonString = Encoding.UTF32.GetString(jsonBytes);
string jsonString = File.ReadAllText(path2key);
Console.WriteLine(jsonString);
// This is where the error is
string jsonStringDeserialized = JsonConvert.DeserializeObject<string>(jsonString);
Console.WriteLine(jsonStringDeserialized);
// I presume I'm passing json as a string???
var credential = GoogleCredential.FromJson(jsonStringDeserialized);
if (credential.IsCreateScopedRequired)
{
credential = credential.CreateScoped(new[]
{
BigqueryService.Scope.Bigquery
});
}
Console.WriteLine("Credentials Created");
var client = BigQueryClient.Create("projectId", credential);
Console.WriteLine("Client Created");
var table = client.GetTable("bigquery-public-data", "samples", "shakespeare");
var sql =
$" SELECT corpus AS title" +
$" , COUNT(word) AS unique_words " +
$" FROM {table} " +
$" GROUP BY title " +
$" ORDER BY unique_words DESC " +
$" LIMIT 10"
;
var results = client.ExecuteQuery(sql, parameters: null);
foreach (var row in results)
{
Console.WriteLine($"{row["title"]}: {row["unique_words"]}");
}
However when I get to the line that tries to deserialize the jsonString it complains that
Newtonsoft.Json.JsonReaderException: 'Unexpected character encountered while parsing value: {. Path '', line 1, position 1.'
I'm assuming the json isn't malformed as it was downloaded directly from GCP and I've checked with linters that it's valid.
What's the next thing I should be trying?
you don' t need this code, it doesn' t create anything , except the error
string jsonStringDeserialized = JsonConvert.DeserializeObject<string>(jsonString);
Console.WriteLine(jsonStringDeserialized);
try just this
string jsonString = File.ReadAllText(path2key);
var credential = GoogleCredential.FromJson(jsonString);
.... your code
I am trying to use the Google Streetview Static API to get mass amounts of streetview images. I have a working API key and URL signing secret but I am having trouble with encoding the signature. No matter what I have tried I get the wrong signature and the url does not work. Any help would be appreciated.
Here is what I have done (the Encode method is not mine):
static void Main(string[] args)
{
Process.Start(GenerateURL(0, 0, "40.7488115", "-73.9855688", 1920, 1080, 90));
Console.ReadLine();
}
public static string GenerateURL(double heading, double pitch, string locationLat, string locationLong, int resX, int resY, int fov)
{
string universalURL = "size=" + resX + "x" + resY + "&location=" + locationLat + "," + locationLong + "&heading=" + heading + "&pitch=" + pitch + "&fov=" + fov + "&key=" + apiKey;
string getURL = "https://maps.googleapis.com/maps/api/streetview?" + universalURL;
string getSignature = "_maps_api_streetview?" + universalURL;
return getURL + "&signature=" + Encode(getSignature, signingKey);
}
public static string Encode(string input, string inputkey)
{
byte[] key = Encoding.ASCII.GetBytes(inputkey);
byte[] byteArray = Encoding.ASCII.GetBytes(input);
using (var myhmacsha1 = new HMACSHA1(key))
{
var hashArray = myhmacsha1.ComputeHash(byteArray);
return hashArray.Aggregate("", (s, e) => s + String.Format("{0:x2}", e), s => s);
}
}
The reason I use _ instead of / for getSignature is because here it says it needs to be replaced. I have already tried with / and it does not work.
Thanks for any help.
EDIT:
I have found the solution on the google website:
static void Main(string[] args)
{
Process.Start(GenerateURL(0, 0, "-26.235859", "28.077619", 500, 500, 90));
Console.ReadLine();
}
public static string GenerateURL(double heading, double pitch, string locationLat, string locationLong, int resX, int resY, int fov)
{
return Sign("https://maps.googleapis.com/maps/api/streetview?size=" + resX + "x" + resY + "&location=" + locationLat + "," + locationLong + "&heading=" + heading + "&pitch=" + pitch + "&fov=" + fov + "&key=" + apiKey, signingKey);
}
public static string Sign(string url, string keyString)
{
ASCIIEncoding encoding = new ASCIIEncoding();
// converting key to bytes will throw an exception, need to replace '-' and '_' characters first.
string usablePrivateKey = keyString.Replace("-", "+").Replace("_", "/");
byte[] privateKeyBytes = Convert.FromBase64String(usablePrivateKey);
Uri uri = new Uri(url);
byte[] encodedPathAndQueryBytes = encoding.GetBytes(uri.LocalPath + uri.Query);
// compute the hash
HMACSHA1 algorithm = new HMACSHA1(privateKeyBytes);
byte[] hash = algorithm.ComputeHash(encodedPathAndQueryBytes);
// convert the bytes to string and make url-safe by replacing '+' and '/' characters
string signature = Convert.ToBase64String(hash).Replace("+", "-").Replace("/", "_");
// Add the signature to the existing URI.
return uri.Scheme + "://" + uri.Host + uri.LocalPath + uri.Query + "&signature=" + signature;
}
I'm using an example for setting up HMAC authentication for a Web API project. The original example source code/project is available here:
http://bitoftech.net/2014/12/15/secure-asp-net-web-api-using-api-key-authentication-hmac-authentication/
I'm trying to get Postman to construct and send a GET request in it's pre-request script. However the request always fails with a 401 and I can't figure out why.
Postman pre-request script:
var AppId = "4d53bce03ec34c0a911182d4c228ee6c";
var APIKey = "A93reRTUJHsCuQSHR+L3GxqOJyDmQpCgps102ciuabc=";
var requestURI = "http%3a%2f%2flocalhost%3a55441%2fapi%2fv1%2fdata";
var requestMethod = "GET";
var requestTimeStamp = "{{$timestamp}}";
var nonce = "1";
var requestContentBase64String = "";
var signatureRawData = AppId + requestMethod + requestURI + requestTimeStamp + nonce + requestContentBase64String; //check
var signature = CryptoJS.enc.Utf8.parse(signatureRawData);
var secretByteArray = CryptoJS.enc.Base64.parse(APIKey);
var signatureBytes = CryptoJS.HmacSHA256(signature,secretByteArray)
var requestSignatureBase64String = CryptoJS.enc.Base64.stringify(signatureBytes);
postman.setGlobalVariable("key", "amx " + AppId + ":" + requestSignatureBase64String + ":" + nonce + ":" + requestTimeStamp);
This is the code I'm using in my Pre-Script. It works for any query GET, PUT, POST, DELETE.
You need to change the AppId & the APIKey values and on the last line adjust the name of the environment variable "hmacKey" with yours.
function interpolate (value) {
const {Property} = require('postman-collection');
return Property.replaceSubstitutions(value, pm.variables.toObject());
}
var uuid = require('uuid');
var moment = require("moment")
var hmacPrefix = "hmac";
var AppId = "4d53bce03ec34c0a911182d4c228ee6c";
var APIKey = "A93reRTUJHsCuQSHR+L3GxqOJyDmQpCgps102ciuabc=";
var requestURI = encodeURIComponent(pm.environment.values.substitute(pm.request.url, null, false).toString().toLowerCase());
var requestMethod = pm.request.method;
var requestTimeStamp = moment(new Date().toUTCString()).valueOf() / 1000;
var nonce = uuid.v4();
var requestContentBase64String = "";
var bodyString = interpolate(pm.request.body.toString());
if (bodyString) {
var md5 = CryptoJS.MD5(bodyString);
requestContentBase64String = CryptoJS.enc.Base64.stringify(md5);
}
var signatureRawData = AppId + requestMethod + requestURI + requestTimeStamp + nonce + requestContentBase64String; //check
var signature = CryptoJS.enc.Utf8.parse(signatureRawData);
var secretByteArray = CryptoJS.enc.Base64.parse(APIKey);
var signatureBytes = CryptoJS.HmacSHA256(signature,secretByteArray);
var requestSignatureBase64String = CryptoJS.enc.Base64.stringify(signatureBytes);
var hmacKey = hmacPrefix + " " + AppId + ":" + requestSignatureBase64String + ":" + nonce + ":" + requestTimeStamp;
postman.setEnvironmentVariable("hmacKey", hmacKey);
After a few days of testing I figured out the problem. It was actually to do with the variable placeholders provided by Postman of all things. In testing the placeholder {{$timestamp}} at face value was passing a valid value. When I stripped the signature back to start with just a single segment I was getting authenticated successfully. Until of course I put the timestamp placeholder back in.
When I swapped out the placeholder for the actual value passed in the header it worked fine. I can only conclude that there must be some extra character I can't see. Perhaps on the Postman side when it creates the signature. The problem extends to other placeholders such as {{$guid}}.
If you are having trouble with the script provided by Florian SANTI and you are using Postman v8.0 or higher. You'll need to make sure that the empty string is set by the variable requestContentBase64String is set properly by correctly inspecting the request body.
This is how I solved the issue.
var uuid = require('uuid');
var moment = require("moment")
var AppId = "4d53bce03ec34c0a911182d4c228ee6c";
var APIKey = "A93reRTUJHsCuQSHR+L3GxqOJyDmQpCgps102ciuabc=";
var requestURI = encodeURIComponent(pm.environment.values.substitute(pm.request.url, null, false).toString()).toLowerCase();
var requestMethod = pm.request.method;
var requestTimeStamp = moment(new Date().toUTCString()).valueOf() / 1000;
var nonce = uuid.v4();
var hasBody = (pm.request.body !== null);
if (hasBody) {
hasBody = (!pm.request.body.isEmpty);
}
var requestContentBase64String = "";
if (hasBody) {
var md5 = CryptoJS.MD5(JSON.stringify(postBody));
requestContentBase64String = CryptoJS.enc.Base64.stringify(md5);
}
var signatureRawData = AppId + requestMethod + requestURI + requestTimeStamp + nonce + requestContentBase64String; //check
var signature = CryptoJS.enc.Utf8.parse(signatureRawData);
var secretByteArray = CryptoJS.enc.Base64.parse(APIKey);
var signatureBytes = CryptoJS.HmacSHA256(signature,secretByteArray);
var requestSignatureBase64String = CryptoJS.enc.Base64.stringify(signatureBytes);
var hmacKey = "amx " + AppId + ":" + requestSignatureBase64String + ":" + nonce + ":" + requestTimeStamp;
postman.setEnvironmentVariable("hmacKey", hmacKey );
I'm trying to clone/download my private bitbucket.org repository using C#, but I want to do it using pure HTTPS REST calls, and not a third party lib, i want to learn how it works.
So far I could only find example code for version 1 of the api.
This is what i've got working so far in C#:
static void AUthenticate()
{
var time = GenerateTimeStamp();
var url = "https://bitbucket.org/api/1.0/oauth/request_token";
var secret = "SECRET";
var key = "KEY";
var sigBaseStringParams = "";
sigBaseStringParams += "oauth_callback=http%3A%2F%2Flocal%3Fdump";
sigBaseStringParams += "&" + "oauth_consumer_key=" + key;
sigBaseStringParams += "&" + "oauth_nonce=" + GetNonce();
sigBaseStringParams += "&" + "oauth_signature_method=" + "HMAC-SHA1";
sigBaseStringParams += "&" + "oauth_timestamp=" + time;
sigBaseStringParams += "&" + "oauth_version=1.0";
var sigBaseString = "POST&";
sigBaseString += Uri.EscapeDataString(url) + "&" + Uri.EscapeDataString(sigBaseStringParams);
var signature = GetSignature(sigBaseString, secret);
var res = PostData(url, sigBaseStringParams + "&oauth_signature=" + Uri.EscapeDataString(signature));
var items = GetParameters(res);
var tokenSecret = items["oauth_token_secret"];
var token = items["oauth_token"];
var callbackConfirmed = items["oauth_callback_confirmed"];
url = "https://bitbucket.org/api/1.0/oauth/authenticate?oauth_token=" + token;
}
This authenticates and I get 3 values back. The last URL i paste into a browser, where i grant my application access and i end up with an oauth_verifier
Note: I don't really want to do this as I am writing a server program which won't really be able to send a user to a browser link (but one thing at a time)
I then run the following code:
var url = "https://bitbucket.org/api/1.0/oauth/access_token";
var token = "TOKEN FROM PREV CALL";
var time = GenerateTimeStamp();
var sigBaseStringParams = "";
//sigBaseStringParams += "oauth_callback=http%3A%2F%2Flocal%3Fdump";
var secret = "SECRET";
var key = "KEY";
sigBaseStringParams += "oauth_consumer_key=" + key;
sigBaseStringParams += "&" + "oauth_token=" + token;
sigBaseStringParams += "&" + "oauth_nonce=" + GetNonce();
sigBaseStringParams += "&" + "oauth_signature_method=" + "HMAC-SHA1";
sigBaseStringParams += "&" + "oauth_timestamp=" + time;
sigBaseStringParams += "&" + "oauth_verifier=AUTH VERIFIER FROM PREV CALL";
var sigBaseString = "POST&";
sigBaseString += Uri.EscapeDataString(url) + "&" + Uri.EscapeDataString(sigBaseStringParams);
var tokenSecret = "TOKEN SECRET FROM PREVIOUS CALL";
var signature = GetSignature(sigBaseString, secret, tokenSecret);
var res = PostData(url, sigBaseStringParams + "&oauth_signature=" + Uri.EscapeDataString(signature));
This gives me a 400 bad request. I can't see much else.
I'm following the steps on this page: https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html#OAuthonBitbucketCloud-Refreshtokens
Why I'm doing this is because i want to eventually make a POST request (or GET) to this URL:
var url2 = "https://bitbucket.org/ACCOUNT/REPOSITORY/get/tip.zip?access_token="+token;
Because I think this will give me the entire repository in a zip file. If i put this in the browser it works because i'm logged into bitbucket.
If there is a newer/easier/better way i'm open to suggestions.
Thanks in advance
I found a very simple solution.
I just had to provide credentials to a GET request:
public static void Downloadfiles(string username, string password, string account, string repository, string pathToSave)
{
var creds = Base64Encode(String.Format("{0}:{1}", username, password));
var url = String.Format("https://bitbucket.org/{0}/{1}/get/tip.zip", account, repository);
using (var client = new WebClient())
{
client.Headers.Add("Authorization", "Basic " + creds);
client.Headers.Add("Content-Type", "application/octet-stream");
client.DownloadFile(url, pathToSave);
}
}
I'm sending an SMS from my web application which is build in ASP.NET C# but for some reason when I add the SourceAddress with parameter.Add("SourceAddress", "BPD.co.uk"); the working BPD.co.uk comes through as BPDcouk.
How can I make the points appear?
Heres my C# code:
public void SendSms(string MobileNumber, string SMSContent)
{
Dictionary<string, string> parameter = new Dictionary<string, string>();
parameter.Add("Action", "Send");
parameter.Add("DestinationAddress", MobileNumber);
parameter.Add("SourceAddress", "BPD.co.uk");
parameter.Add("Body", SMSContent);
parameter.Add("ValidityPeriod", "86400");
string resultcode = api_connect("nsp04456", "pword", parameter);
}
Heres the call to the API_Connect
private string api_connect(string Username, string Password, Dictionary<string, string> ParametersDict)
{
string url = "http://api.sms.co.uk/api/api.php";
string poststring = "Username=" + Username + "&";
poststring = poststring + "Password=" + Password;
// Turn the parameter dictionary object into the variables
foreach (KeyValuePair<string, string> item in ParametersDict)
{
poststring = poststring + "&" + item.Key + "=" + item.Value;
}
MSXML2.ServerXMLHTTP60 xmlHttp = new MSXML2.ServerXMLHTTP60();// Server.CreateObject("MSXML2.ServerXMLHTTP.4.0");
xmlHttp.open("POST", url, false);
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlHttp.send(poststring);
return xmlHttp.responseText;
}
I guessing that the API you are using is sanitising your parameters and removing stuff it thinks shouldn't be there. You could try escaping the values for your parameters and adding slashes like BPD\.co\.uk and see what happens