I am trying to verify the signature of my webhooks from github. Following their documentation, there is only an example for ruby:
post '/payload' do
request.body.rewind
payload_body = request.body.read
verify_signature(payload_body)
push = JSON.parse(payload_body)
"I got some JSON: #{push.inspect}"
end
def verify_signature(payload_body)
signature = 'sha256=' OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)
return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_HUB_SIGNATURE_256'])
end
There is a built-in hmac
function for Lucee here, and following some ruby examples I have been able to recreate the same hashes for simple messages like "the fox jumps over the thing." However, once I try to recreate the signature using the request payload I never get the right signature. I have triple checked my secret, and it is consistent with the one used in the webhook, so I am pretty sure the issue lies with how I'm parsing the payload. Below is my current code:
headers = request.wheels.HTTPREQUESTDATA.headers;
signature = structKeyExists(headers, "X-Hub-Signature-256") ? headers["X-Hub-Signature-256"] : "error";
payload = request.wheels.params.payload;
mac = 'sha256=' & lCase(hmac(payload, secret, "HMACSHA256"));
Which results in two different "sha256=..." strings. My payload string (appears to be the correct format):
{"action":"added_to_repository",...
Any advice is appreciated.
CodePudding user response:
I've just created a test Github webhook and was able to successfully verify a push event in Lucee using the following basic code:
boolean function verifySignature( required string signature, required string payload, required string secret ){
var expectedSignature = "sha256=" & HMAC( arguments.payload, arguments.secret, "HmacSHA256", "utf-8" ).LCase()
return ( arguments.signature == expectedSignature )
}
requestData = GetHTTPRequestData()
payload = requestData.content.Trim()
signature = requestData.headers[ "X-Hub-Signature-256" ]
secret = "mysecret"
result.verified = verifySignature( signature, payload, secret )
dump( result )
A few small differences with yours:
- I'm using the
GetHTTPRequestData()
built-in function directly rather than CFWheels-provided values - I've trimmed the payload
- I've specified
utf-8
as the character encoding to theHMAC()
function