githubhmacluceecfwheelsgithub-webhook

Validate Github Webhook HMAC Signature Lucee Coldfusion and CFWheels


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.


Solution

  • 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: