问题
I'm looking to learn how to verify a Paddle webhook with Ruby? Their example has an option on how to do it with PHP, Python and JavaScript, but no Ruby. Any ideas on how to do it?
This following old example doesn't work:
require 'base64'
require 'php_serialize'
require 'openssl'
public_key = '-----BEGIN PUBLIC KEY-----
MIICIjANBgkqh...'
# 'data' represents all of the POST fields sent with the request.
# Get the p_signature parameter & base64 decode it.
signature = Base64.decode64(data['p_signature'])
# Remove the p_signature parameter
data.delete('p_signature')
# Ensure all the data fields are strings
data.each {|key, value|data[key] = String(value)}
# Sort the data
data_sorted = data.sort_by{|key, value| key}
# and serialize the fields
# serialization library is available here: https://github.com/jqr/php-serialize
data_serialized = PHP.serialize(data_sorted, true)
# verify the data
digest = OpenSSL::Digest::SHA1.new
pub_key = OpenSSL::PKey::RSA.new(public_key).public_key
verified = pub_key.verify(digest, signature, data_serialized)
if verified
puts "Yay! Signature is valid!"
else
puts "The signature is invalid!"
end
Here is their example in JS:
// Node.js & Express implementation
const express = require('express');
const querystring = require('querystring');
const crypto = require('crypto');
const Serialize = require('php-serialize');
const router = express.Router();
const pubKey = `-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----`
function ksort(obj){
let keys = Object.keys(obj).sort();
let sortedObj = {};
for (var i in keys) {
sortedObj[keys[i]] = obj[keys[i]];
}
return sortedObj;
}
function validateWebhook(jsonObj) {
const mySig = Buffer.from(jsonObj.p_signature, 'base64');
delete jsonObj.p_signature;
// Need to serialize array and assign to data object
jsonObj = ksort(jsonObj);
for (var property in jsonObj) {
if (jsonObj.hasOwnProperty(property) && (typeof jsonObj[property]) !== "string") {
if (Array.isArray(jsonObj[property])) { // is it an array
jsonObj[property] = jsonObj[property].toString();
} else { //if its not an array and not a string, then it is a JSON obj
jsonObj[property] = JSON.stringify(jsonObj[property]);
}
}
}
const serialized = Serialize.serialize(jsonObj);
// End serialize data object
const verifier = crypto.createVerify('sha1');
verifier.update(serialized);
verifier.end();
let verification = verifier.verify(pubKey, mySig);
if (verification) {
return 'Yay! Signature is valid!';
} else {
return 'The signature is invalid!';
}
}
/* Validate a Paddle webhook to this endpoint, or wherever in your app you are listening for Paddle webhooks */
router.post('/', function(req, res, next) {
res.send(validateWebhook(req.body));
});
module.exports = router;
How could I verify the webhook with Ruby? Is there an alternative way to verify the webhook?
Here is an example webhook request:
(
[alert_name] => subscription_created
[cancel_url] => https://checkout.paddle.com/subscription/cancel?user=4&subscription=8&hash=b0bd354fexamplec39b0ff93c917804acf
[checkout_id] => 1-61ff5b400-756ea301a9
[currency] => USD
[email] => wleffler@example.net
[event_time] => 2019-08-10 18:33:58
[marketing_consent] =>
[next_bill_date] => 2019-08-18
[passthrough] => 1132
[quantity] => 67
[status] => active
[subscription_id] => 4
[subscription_plan_id] => 5
[unit_price] => unit_price
[update_url] => https://checkout.paddle.com/subscription/update?user=5&subscription=4&hash=e937ed03f1637e45d912f4f4d293a
[user_id] => 6
[p_signature] => HM2Isn1k6Sy1cKySQGoFH...
)
EDIT:
I'm using Ruby 2.5.5 and Ruby on Rails 5. Currently still getting always "false" in the end. I'll go through it on my console:
Here is the (fake) data, that I get in Rails:
data = {
"alert_id"=>"1",
"alert_name"=>"alert_created",
"cancel_url"=>"https://...",
"checkout_id"=>"1",
"user_id"=>"1",
"p_signature"=>"fwWXqR9C..."
}
public_key = '-----BEGIN PUBLIC KEY-----sDFKJSD2332FKJLWJF......'
Then I do the following:
signature = Base64.decode64(data['p_signature'])
data.delete('p_signature')
data.each {|key, value|data[key] = String(value)}
data_sorted = data.sort_by{|key, value| key}
data_serialized = data_sorted.to_json
digest = OpenSSL::Digest::SHA1.new
pub_key = OpenSSL::PKey::RSA.new(public_key)
verified = pub_key.verify(digest, signature, data_serialized)
In the end verified is always false. What am I doing wrong?
回答1:
The Ruby example that you mentioned doesn't work because you need get the data variable. This must be sent from controller to some class that process the request.
Try this:
in routes.rb
get 'check', to: 'test#check'
in controller
class TestController < ApplicationController
def check
verification = SignatureVerifier.new(check_params.as_json)
if verification
#... do something
end
end
private
def check_params
params.permit.all
end
end
in verifier class
require 'base64'
require 'json'
require 'openssl'
class SignatureVerifier
def initialize(data)
@data = data
@public_key_path = '/path/to/file'
end
#data = {
# "alert_name": "payment_succeeded",
# "balance_currency": "USD",
# "balance_earnings": 355.05,
# "balance_fee": 177.36,
# "balance_gross": 180.85,
# "balance_tax": 433.43,
# "checkout_id": "4-601ee0e3d793922-ab8910b010",
# "currency": "USD",
# "customer_name": "customer_name",
# "earnings": 292.87,
# "product_name": "Example",
# "quantity": 12,
# "p_signature": "dl8PN7OrxiYHSJzT3CLUDlElodOE2j8puZkDNPHX9rZnTgig123f4KhtUXZT/HjbU5D7g/PZggxSCt9YrMcWrbSkfINJROTb+YrlhYKAVyTbmMWJV8u+YU6VcGNkhcGK7tIZNBJuaKMBrByrYA14gR3TvMjgXbQWNSFJ8LgJKMWoovbpuOkQwzkKze4vavt3WhElW0izPZwpiqVWTVXAlIvDxHTNT+sS1jXqAHdoli6sVblQQtAujSxdGm2OXB92yifcV0oHhrsqt8rCk1TzJOqsVrhQz1lqSYsbdhlM40QPHM7nHPGe5RITly4t8BjsuCB+V1aeof3N5A0ZDk+2M2Cox6S+vEahEdbW8QdecIKN12SMAYI5kx9zMMiUZ9XZqqC6orXE3uVAcTvMwiTRDDmEVr1HtsBZRo/Ykg7+fMYPc/o7rDpA16/EIOcce1zp+vgilL6rSxIuMFfWlP9qxzrV1MtcmQa86NxEU0GJtebkhehXZfh/eDCAjysmrrBM5xkqE19M+Ye4jZCRTzQTHyDJxjdNYefk7bVfivwRI606JJCGYUMTD6NIsn4rinw2SxKkZquqjTykcob5gn3HH+0AxyjuDj7fsLyqEl3gE9tgo/oMKRBy+zsYzQk4v291sh2PbUfH36W4aL4YYztlsarfMIBWqJshc8rf0RL3pAM="
#}
def verify
data = @data
signature = Base64.decode64(data[:p_signature])
# Remove the p_signature parameter
data.delete(:p_signature)
# Ensure all the data fields are strings
data.each {|key, value|data[key] = String(value)}
# Sort the data
data_sorted = data.sort_by{|key, value| key}
# Serialized with JSON library
data_serialized = data_sorted.to_json
# verify the data
digest = OpenSSL::Digest::SHA1.new
pub_key = OpenSSL::PKey::RSA.new(File.read(@public_key_path))
verified = pub_key.verify(digest, signature, data_serialized)
verified
end
end
来源:https://stackoverflow.com/questions/57444778/how-to-verify-a-webhook-with-ruby-in-rails