Skip to content

Instantly share code, notes, and snippets.

@haseebq
Last active January 25, 2023 09:43
Show Gist options
  • Save haseebq/adc51aaeb4e612c205291a411a7a8872 to your computer and use it in GitHub Desktop.
Save haseebq/adc51aaeb4e612c205291a411a7a8872 to your computer and use it in GitHub Desktop.
Golang code to verify Paddle's webhook signature
package main
import (
"fmt"
"crypto"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"net/url"
"sort"
"bytes"
"strconv"
"errors"
)
// VerifyPaddleSig verifies the p_signature parameter sent
// in Paddle webhooks. 'values' is the decoded form values sent
// in the webhook response body. You can get 'values' from a
// http.Request by calling request.Form()
func VerifyPaddleSig(values url.Values, publicKeyPEM string) error {
der, _ := pem.Decode([]byte(publicKeyPEM))
if der == nil {
return errors.New("Could not parse public key pem")
}
pub, err := x509.ParsePKIXPublicKey(der.Bytes)
if err != nil {
return errors.New("Could not parse public key pem der")
}
signingKey, ok := pub.(*rsa.PublicKey)
if !ok {
return errors.New("Not the correct key format")
}
sig, err := base64.StdEncoding.DecodeString(values.Get("p_signature"))
if err != nil {
return err
}
// Delete p_signature
values.Del("p_signature")
// Sort the keys
sortedKeys := make([]string, 0, len(values))
for k := range values {
sortedKeys = append(sortedKeys, k)
}
sort.Strings(sortedKeys)
// Php Serialize in sorted order
var sbuf bytes.Buffer
sbuf.WriteString("a:")
sbuf.WriteString(strconv.Itoa(len(sortedKeys)))
sbuf.WriteString(":{")
encodeString := func(s string) {
sbuf.WriteString("s:")
sbuf.WriteString(strconv.Itoa(len(s)))
sbuf.WriteString(":\"")
sbuf.WriteString(s)
sbuf.WriteString("\";")
}
for _, k := range sortedKeys {
encodeString(k)
encodeString(values.Get(k))
}
sbuf.WriteString("}")
sha1Sum := sha1.Sum(sbuf.Bytes())
err = rsa.VerifyPKCS1v15(signingKey, crypto.SHA1, sha1Sum[:], sig)
if err != nil {
return err
}
return nil
}
const DefaultPaddlePublicKey = `
-----BEGIN PUBLIC KEY-----
YOUR PUBLIC KEY
-----END PUBLIC KEY-----`
func main() {
testReq := `alert_name=subscription_created&cancel_url=https%3A%2F%2Fcheckout.paddle.com%2Fsubscription%2Fcancel%3Fuser%3D5%26subscription%3D2%26hash%3Dea8aec0ed511b91f58b44c5c7e519891de2e301e&checkout_id=2-d1bab8dbfc11d77-157eb37302&currency=USD&email=blanca66%40example.org&event_time=2018-10-30+19%3A55%3A06&marketing_consent=&next_bill_date=2018-11-05&passthrough=UserID&quantity=90&status=active&subscription_id=1&subscription_plan_id=4&unit_price=unit_price&update_url=https%3A%2F%2Fcheckout.paddle.com%2Fsubscription%2Fupdate%3Fuser%3D7%26subscription%3D6%26hash%3De88ee8ac9ef0ba6bd2fcdc577524030f02f14f67&p_signature=yDkylvQGOSkonSN8BcQaG8YIedGbnQOo8mvmpGKSIwbJlcymtQqtmzBPauCo2pvMnUn5H7XcYjUxIAD%2F2JqgtBA%2BY3GWahqEJBg2SPWjqUdqQkjaCwJ9KEZgpTg%2BddNXp5AVFklAaiZia9JpdqMlMDm%2B5AFCJ2F6W6KE2ehPR0%2B22VrtvNAikOJwr7Jq53GiSPDT9p9vWHY6Z%2FzTLPOGhnXoULZwLwIRWBQBk8lAcv13UdX5pErP3GvC4pbv53Pa1CUZrmx1MjsYG%2BgAMdZbmlYXXjmQfl9CnRc5qmus6CSOKrO7wKqQ0NrIoiQc8RoZPP47Qqc8OlVHhHei2TwejfcVQpIouUxnIjBCKaznu5bGFBuWj328WpKmrtUuLgk5SGZTjq6PPbsXr5yAML60Vl33TUfmz95Td3BsLJ9jzb4SXz4SCcVPu6BK7wP%2FWl8AcRrU9o7en6zIRHyjU3yrDWLoQaIm%2FvxbdSYv5AOrHQoy%2FZ82XOB%2FRFYANdKeuQoTpy51MriOB7qZQK%2FPAd98uQxHlTz2f%2BX5LvLyz6jLjGMmbfMgX9QoXJ3UN5f6R3czTbfqOo%2BQc0NE8hyWB5%2FjHgEOeEuPGTe%2BXNhluHb3tKgjN8cK6LuJI56d5wIWwkMm%2BkXbkHYjfxo1Z746mXbY6CLkfD3hVf5fsOyl47mPTEs%3D`
values, _ := url.ParseQuery(testReq)
fmt.Print(VerifyPaddleSig(values, DefaultPaddlePublicKey))
}
@austincollinpena
Copy link

<3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment