I am using this great method, found in this article https://codingstill.com/2016/01/verify-jwt-token-signed-with-rs256-using-the-public-key/#comment-3232, to validate my Azure idToken, which is signed with RS256.
I noticed that Azure AD changes the public key in a period of time. By reading this article, https://nicksnettravels.builttoroam.com/post-2017-01-24-verifying-azure-active-directory-jwt-tokens-aspx. Therefore, I used HttpClient (c#) to obtain the public key from the URL below, every time when I need to validate the token.
https://login.microsoftonline.com/{TenantId}/discovery/v2.0/keys?appid={AppId}
I did get the result of the public key, which is a string array in x5c key. Below is my code:
public static string GetPublicKeyAsync(string kid)
{
using (var client = new HttpClient())
{
HttpResponseMessage response = client.GetAsync("https://login.microsoftonline.com/{TenantId}/discovery/v2.0/keys?appid={AppId}").Result;
if (response.IsSuccessStatusCode)
{
var responseContent = response.Content;
// by calling .Result you are synchronously reading the result
string responseBody = responseContent.ReadAsStringAsync().Result;
JObject json = JObject.Parse(responseBody);
string c = json["keys"].ToString();
List<ADPublic> allPublicKeys = JsonConvert.DeserializeObject<List<ADPublic>>(json["keys"].ToString());
foreach (ADPublic key in allPublicKeys)
{
if (key.kid == kid)
{
string certificateString = key.x5c[0];
var certificate = new X509Certificate2(Convert.FromBase64String(certificateString));
string pkey = Convert.ToBase64String(certificate.PublicKey.EncodedKeyValue.RawData);
var x509SecurityKey = new X509SecurityKey(certificate)
{
KeyId = key.kid
};
return pkey;
}
}
}
return null;
}
}
When I pass my public key over to the validation method. I always get an error at the line (PublicKeyFactory.CreateKey(keyBytes) method ):
Please refer to the verify method I mentioned in the beginning of this question for the code below:
// call my get public key function...
string key = GetPublicKeyAsync(headerData["kid"].ToString());
var keyBytes = Convert.FromBase64String(key); // your key here
**AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(keyBytes);**
Unknown object in GetInstance: Org.BouncyCastle.Asn1.DerInteger Parameter name: obj
I think I am almost there, but missing the last part, could you please help? Thank you very much!
I got the same error when using certificate.PublicKey.EncodedKeyValue.RawData to get the public key. I referred to this issue and finally succeeded.
The code shows getting AsymmetricKeyParameter from x5c string. Decode(...) function is referred to in the article.
string token = "<the token that you want to verify>";
string certificateString = "<the first x5c of the first key from https://login.microsoftonline.com/{TenantId}/discovery/v2.0/keys?appid={AppId}>";
byte[] buffer = Convert.FromBase64String(certificateString);
X509CertificateParser parser = new X509CertificateParser();
var _certificate = parser.ReadCertificate(buffer);
AsymmetricKeyParameter publicKey = _certificate.GetPublicKey();
string result = Decode(token, publicKey);
Console.WriteLine("result: " + result);
I'm just start using azure functions. Follow the instructions here, i modified the code & deploy it to my function
[FunctionName("Function1")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
// parse query parameter
string name = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
.Value;
if (name == null)
{
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
name = data?.name;
}
return name == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
: req.CreateResponse(HttpStatusCode.OK, "Hello " + Multiply().ToString());
}
Combine with my code
public static int Multipy() {
Random rnd = new Random();
return rnd.Next(1, 10)*rnd.Next(1, 10);
}
The function run successfully when deploy, but after a few times rebuild & deploy again, this show up
enter image description here
Remove name query & it work as usual
enter image description here
Is there something wrong with my code or Azure has limitation for deploy function? How may i fix this?
Isn't it problem with a query symbol? it is already ? used so another param should be joined with & sign
{url}?code={someCode}&name={name}
I'm trying to extract MD5 and length (size) of the upload blob using Azure function using Http Trigger, Below the code im experimenting, but I always get null and -1. Please someone confirm the code is correct or any other option is available
public static async Task<IActionResult> Run(HttpRequest req,string inputBlob, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
log.LogInformation($"name,{inputBlob}");
log.LogInformation("Blob content: " + inputBlob.Properties.Length); //This is printing content of blob
CloudBlockBlob blob;
var credentials = new StorageCredentials("xxx", "xxxx");
var client = new CloudBlobClient(new Uri("https://xxx.blob.core.windows.net"), credentials);
var container = client.GetContainerReference("parent");
blob = container.GetBlockBlobReference("file.csv");
log.LogInformation("Blob details: " + blob.Properties.Length); //This is printing -1, if i provide ContentMD5 its showing null. Bascially its not able to read the blob
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
return name != null
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}
You are missing FetchAttributesAsync() (or FetchAttributes()) method before you try to retrieve any properties of the blob.
//your other code
blob = container.GetBlockBlobReference("file.csv");
blob.FetchAttributesAsync()(); //or FetchAttributes()
//then you can try to get any property here.
I am receiving webhooks from a woocommerce site into a nodejs/express application. I am trying to verify the webhook's signature to prove authenticity, yet the hash I compute never corresponds to the signature that woocommerce reports in the hook's signature header.
Here is the code I am using to verify the authenticity:
function verifySignature(signature, payload, key){
var computedSignature = crypto.createHmac("sha256", key).update(payload).digest('base64');
debug('computed signature: %s', computedSignature);
return computedSignature === signature;
}
This function is being called with the following parameters:
var signature = req.headers['x-wc-webhook-signature'];
verifySignature(signature, JSON.stringify(req.body), config.wooCommence.accounts.api[config.env].webhookSecret)
The webhook's signature headers reports the signature as BewIV/zZMbmuJkHaUwaQxjX8yR6jRktPZQN9j2+67Oo=. The result of the above operation, however, is S34YqftH1R8F4uH4Ya2BSM1rn0H9NiqEA2Nr7W1CWZs=
I have manually configured the secret on the webhook, and as you see in the code above, this same secret is also hardcoded in the express application. So either I am taking the wrong payload to compute the signature, or there is something else fishy that prevents me from verifying these signature.
Would appreciate any pointers to help me solve this issue.
For people using node, this should do the trick.
var processWebHookSignature = function (secret, body, signature) {
signatureComputed = crypto.createHmac('SHA256', secret).update(
new Buffer(JSON.stringify(body), 'utf8')).digest('base64');
return ( signatureComputed === signature ) ? true : false;
}
Since this is the top Google result for this question and there isn't a complete answer out there, here's a Python version using Flask that validates the WooCommerce webhook signature. It took a bit of trial and error, hope it helps someone out there:
import json
import base64
import hmac
import hashlib
from flask import Flask, request, Response
app = Flask(__name__)
# The WooCommerce webhook secret
WEBHOOK_SECRET = 'abc123456'
# Function that compares the computed signature to the one in the request
def verify_woocommerce_signature(body, signature, secret):
digest = hmac.new(bytes(secret, 'utf-8'), body, hashlib.sha256).digest()
encoded = base64.b64encode(digest).decode()
return encoded == signature
# WooCommerce Order Creation Event
#app.route('/webhooks/woocommerce/order_created', methods=['POST'])
def webhooks_woocommerce_order_created():
# Get raw request body
body = request.get_data()
# Get request signature
signature = request.headers['X-WC-WEBHOOK-SIGNATURE']
# Verify webhook signature and handle mismatch
if verify_woocommerce_signature(body, signature, WEBHOOK_SECRET) is False:
msg = {"success": False}
return Response(json.dumps(msg), status=400, mimetype='application/json')
# Signatures match, process the payload
Old question but maybe it helps some poor soul out there.
The signature needs to be checked against the body and not the JSON it contains. i.e. the raw bytes of the body.
pseudo:
byte[] body = request.Body;
string signature = request.Header["X-WC-Webhook-Signature"];
byte[] secretUtf8 = GetUtf8Bytes("yoursecrethere");
byte[] hash = HMAC_SHA256.ComputeHash(body, secretUtf8);
string hashBase64 = ToBase64String(hash);
bool isValid = hashBase64 == signature;
I stumbled upon this while searching for a solution to have an Asp.NET application check signature of the Woocommerce web hook. My answer is based on the pseudo code Johannes provided which worked great. I implemented a custom controller attribute to intercept the request and check the signature before it hits the API controller method:
public class HmacSignatureFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var requestContent = actionContext.Request.Content;
var jsonContent = requestContent.ReadAsStringAsync().Result;
var byteContent = requestContent.ReadAsByteArrayAsync().Result;
//if the request contains this, it's the verification request from Woocommerce
//when the webhook is created so let it pass through so it can be verified
if (!jsonContent.Contains("webhook_id"))
{
var requestSignature = actionContext.Request.Headers;
var bodyHash = HashHMAC("test", byteContent); //this is the shared key between Woo and custom API. should be from config or database table.
var signature = actionContext.Request.Headers.GetValues("x-wc-webhook-signature");
if (bodyHash != signature.FirstOrDefault())
{
throw new HttpResponseException(HttpStatusCode.Forbidden);
}
}
base.OnActionExecuting(actionContext);
}
private static string HashHMAC(string key, byte[] message)
{
var keyBytes = Encoding.UTF8.GetBytes(key);
var hash = new HMACSHA256(keyBytes);
var computedHash = hash.ComputeHash(message);
return Convert.ToBase64String(computedHash);
}
}
Then to use the filter in your Api controller:
[RoutePrefix("api/woo")]
public class WooController : ApiController
{
public SomeService _service;
public WooController()
{
this._service = new SomeService();
}
// POST api/values
[Route("orderCreated")]
[HttpPost]
[HmacSignatureFilter]
public string Post()
{
var requestContent = Request.Content;
var jsonContent = requestContent.ReadAsStringAsync().Result;
//this is the test request from Woocommerce. Don't do anything but
//respond so it can verify the endpoint
if (jsonContent.Contains("webhook_id"))
{
return "Webhook Test Success";
}
var wooOrder = JsonConvert.DeserializeObject<WooOrderModel>(jsonContent);
//call a service to use the order data provided by WooCommerce
_service.AddOrder(wooOrder);
return "Success";
}
}
Note: Hashing code was referenced from this SO post.
SOLVED in TypeScript. I added this in the server.ts:
this.app.use(bodyParser.json({
verify: function(req, res, buf) {
(req as any).rawBody = buf;
}
}));
and than:
const computedSignature = crypto.createHmac("sha256", process.env.WOOCOMMERCE_SECRET).update((req as any).rawBody).digest("base64");
Hash must be calculated over the 'raw body'. When used in an 'express application' and using JSON bodyParser middleware 'raw body' is lost, see How to access the raw body of the request before bodyparser? to hold-on to the 'raw body'.
For example:
// 'misuse' verify option
app.use(bodyParser.json({
verify: function(req,res,buf) {
req.rawBody=buf;
}
}));
var wcSignature = req.get('X-Wc-Webhook-Signature');
debug('wc signature: %s', wcSignature);
var calculatedSignature = crypto.createHmac('SHA256', secret)
.update(req.rawBody, 'utf8')
.digest('base64');
debug('calculated signature: %s', calculatedSignature);
Hope to save someone time, below works for me.
// Make sure to add a WISTIA_SECRET_KEY in your Environment Variables
// See https://docs.pipedream.com/environment-variables/
const secret = process.env.SELF_AUTOMATE_KEY;
const signature = event.headers["x-wc-webhook-signature"];
const body = steps.trigger.raw_event["body_b64"];
const clean_Body = body.replace("body_b64: ", "");
//const body = steps.trigger.raw_event;
console.log(event.headers["x-wc-webhook-signature"]);
console.log("Print Body", clean_Body);
if (process.env.SELF_AUTOMATE_KEY === undefined) {
$end("No WISTIA_SECRET_KEY environment variable defined. Exiting.")
}
if (!("x-wc-webhook-signature" in event.headers)) {
$end("No x-wc-webhook-signature header present in the request. Exiting.")
}
// Once we've confirmed we have a signature, we want to
// validate it by generating an HMAC SHA-256 hexdigest
const crypto = require('crypto');
const hash = crypto.createHmac('sha256',
secret).update(JSON.stringify(clean_Body), 'base64').digest('base64');
console.log(hash);
// $end() ends the execution of a pipeline, presenting a nice message in the "Messages"
// column in the inspector above. See https://docs.pipedream.com/notebook/code/#end
if (hash !== signature) {
$end("The correct secret key was not passed in the event. Exiting!")
}
SOLVED IN C# API. For people using it as an Attribute in a C# API for a controller, I made a good solution with multiple threads on StackOverflow:
[AttributeUsage(AttributeTargets.Method)]
public class ShaKeyAttribute : Attribute, IAuthorizationFilter
{
private readonly ShaAuthOptions _options;
private readonly ILogger<ShaKeyAttribute> _log;
public ShaKeyAttribute(IOptions<ShaAuthOptions> options, ILogger<ShaKeyAttribute> log)
{
this._options = options.Value;
this._log = log;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
this._log.LogInformation("Entering in Sha Auth...");
if (!context.HttpContext.Request.Headers.TryGetValue("x-wc-webhook-signature", out var extractedSignature))
{
this._log.LogError("Sha Auth failed. Signature was not provided");
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Signature was not provided",
};
return;
}
// This logic for body rewind comes from: https://stackoverflow.com/a/40994711/14010438
string bodyString;
var req = context.HttpContext.Request;
// Allows using several time the stream in ASP.Net Core
req.EnableBuffering();
// Arguments: Stream, Encoding, detect encoding, buffer size
// AND, the most important: keep stream opened
using (StreamReader reader
= new StreamReader(req.Body, Encoding.UTF8, true, 1024, true))
{
bodyString = reader.ReadToEnd();
}
// Rewind, so the core is not lost when it looks the body for the request
req.Body.Position = 0;
// From this point, DON'T TOUCH THE REQUEST BODY. Instead, use bodyString
// https://stackoverflow.com/a/62032738/14010438
byte[] requestData = Encoding.UTF8.GetBytes(bodyString);
var encoding = new UTF8Encoding();
var key = this._options.Secret;
var keyBytes = encoding.GetBytes(key);
var hash = new HMACSHA256(keyBytes);
var computedHash = hash.ComputeHash(requestData);
var computedHashString = Convert.ToBase64String(computedHash);
if (extractedSignature != computedHashString)
{
this._log.LogError("Sha Auth failed. Signature is not valid");
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Signature is not valid",
};
}
this._log.LogInformation("Successfully passed Sha Auth.");
}
}
You also need to add this in your startup.cs:
// https://stackoverflow.com/questions/47735133/asp-net-core-synchronous-operations-are-disallowed-call-writeasync-or-set-all
// Needed for ShaAuth
services.Configure<KestrelServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
Here is my controller:
[AllowAnonymous]
[HttpPost]
[ServiceFilter(typeof(ShaKeyAttribute))]
public async Task<IActionResult> Create([FromBody] WooCommerceRequest request)
{
this._log.LogInformation("Executing Create of Subscription controller...");
var response = await this._mediator.Send(new AddSubscriptionCommand() { OrderId = request.OrderId });
this._log.LogInformation("Finished executing Create of Subscription controller.");
return this.Ok();
}
Hope this help somebody.
I want to call Api function (1st) . from 2nd Api function using HttpClient. But I always get 404 Error.
1st Api Function (EndPoint : http : // localhost : xxxxx /api/Test/)
public HttpResponseMessage Put(int id, int accountId, byte[] content)
[...]
2nd Api function
public HttpResponseMessage Put(int id, int aid, byte[] filecontent)
{
WebRequestHandler handler = new WebRequestHandler()
{
AllowAutoRedirect = false,
UseProxy = false
};
using (HttpClient client = new HttpClient(handler))
{
client.BaseAddress = new Uri("http://localhost:xxxxx/");
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var param = new object[6];
param[0] = id;
param[1] = "/";
param[2] = "?aid=";
param[3] = aid;
param[4] = "&content=";
param[5] = filecontent;
using (HttpResponseMessage response = client.PutAsJsonAsync("api/Test/", param).Result)
{
return response.EnsureSuccessStatusCode();
}
}
}
So My question is that. Can I post Method Parameter as an object array from HttpClient as I did ? I don't want to Pass model as method parameter.
What is the wrong in my code ?
Unable to get any response , after change code to
return client.PutAsJsonAsync(uri, filecontent)
.ContinueWith<HttpResponseMessage>
(
task => task.Result.EnsureSuccessStatusCode()
);
OR
return client.PutAsJsonAsync(uri, filecontent)
.ContinueWith
(
task => task.Result.EnsureSuccessStatusCode()
);
As you probably found out, no you can't. When you call PostAsJsonAsync, the code will convert the parameter to JSON and send it in the request body. Your parameter is a JSON array which will look something like the array below:
[1,"/","?aid",345,"&content=","aGVsbG8gd29ybGQ="]
Which isn't what the first function is expecting (at least that's what I imagine, since you haven't showed the route info). There are a couple of problems here:
By default, parameters of type byte[] (reference types) are passed in the body of the request, not in the URI (unless you explicitly tag the parameter with the [FromUri] attribute).
The other parameters (again, based on my guess about your route) need to be part of the URI, not the body.
The code would look something like this:
var uri = "api/Test/" + id + "/?aid=" + aid;
using (HttpResponseMessage response = client.PutAsJsonAsync(uri, filecontent).Result)
{
return response.EnsureSuccessStatusCode();
}
Now, there's another potential issue with the code above. It's waiting on the network response (that's what happens when you access the .Result property in the Task<HttpResponseMessage> returned by PostAsJsonAsync. Depending on the environment, the worse that can happen is that it may deadlock (waiting on a thread in which the network response will arrive). In the best case this thread will be blocked for the duration of the network call, which is also bad. Consider using the asynchronous mode (awaiting the result, returning a Task<T> in your action) instead, like in the example below
public async Task<HttpResponseMessage> Put(int id, int aid, byte[] filecontent)
{
// ...
var uri = "api/Test/" + id + "/?aid=" + aid;
HttpResponseMessage response = await client.PutAsJsonAsync(uri, filecontent);
return response.EnsureSuccessStatusCode();
}
Or without the async / await keywords:
public Task<HttpResponseMessage> Put(int id, int aid, byte[] filecontent)
{
// ...
var uri = "api/Test/" + id + "/?aid=" + aid;
return client.PutAsJsonAsync(uri, filecontent).ContinueWith<HttpResponseMessage>(
task => task.Result.EnsureSuccessStatusCode());
}