valME.io
valME.io

Reputation: 109

Verify Coinbase IPNs using HTTP_X_SIGNATURE

I have integrated Coinbase for bitcoin transactions and, in addition to other security features already implemented, would like to verify that IPNs actually came from them using their signature.

Their support indicated to "Verify that the correct signature is included in the x_signature header. The signature included will be done with the following public key: https://www.coinbase.com/coinbase.pub."

Using the openssl_verify documentation, I put together the following:

function verifyX_SIGNATURE()
{
    // $data and $signature are assumed to contain the data and the signature

    // fetch public key from certificate and ready it
    $fp = fopen("https://www.coinbase.com/coinbase.pub", "r");
    $cert = fread($fp, 8192);
    fclose($fp);

    $pubkeyid = openssl_pkey_get_public($cert);

    // from $_SERVER['HTTP_X_SIGNATURE'] header (data masked)
    $signature = 'G8hpTMmWpwoW+6y6lNTbM5hCpHc9kJJcZ7Oij6I94UiwZeeS8zymuqhv+YE2tmXHCR6MqO+KU1HUrbo5simYfSaGjsRWtT0oQYhLI0qKqko3kxiVZ37B6gmflTfcJiiS0vWuUgOeEMIFL8CNPVFI+snr50c7CZdDl36Tg0Neu0XbxpVqfDBI5WVzQT3ujo6aR/CYnIbcWJ06Klfpd+EFTtzfKU0viMlqh7dWdZEnXg+u9Z3QMyfPGDd/G+QJzNTej7/L18OYo7TzfRHG+HAxxrURS8/616LNF6qvx3mMWtrHE77Hvw+DNhtjy0bzm58pencX5iuGDKo7hbP6fbqFqYgX3ZYokn/EhuqtUto8kI8WMqCV+wdLcVa5fasswAr58l9LuxnTrkCEB/uWAukDbL+qkkBLZabUTUrG2qJUk0ZHJura3XcfwTtCkanJ49ZaKsk0WaosAeWnrDxTBSwXhFGeFMGvK1u+5ffQlyG2Ndd7gtHPVQGpbMJr2FKyGYIuXDckOlQh6vG85PrE8OSMSY+/LozYyKXYiPaA75ZCpCCXuHgQ+pdxoB2QwM6zx+v8n8doW5OLudlP654GmDLG+wVwbdIQNQlU00ZD/ndAUTwa9pMJjRyL3uHIgAom7vXFfQPYlIvZ/unT5l1uHmmKbkLGkMh/IP5ZJauyfuBIUcg=';

    // IPN sent in JSON (data masked)
    $data = '{"order":{"id":"BWU44QUL","created_at":"2015-02-11T14:49:41-08:00","status":"completed","event":{"type":"completed"},"total_btc":{"cents":345660.0,"currency_iso":"BTC"},"total_native":{"cents":30.0,"currency_iso":"USD"},"total_payout":{"cents":0.0,"currency_iso":"USD"},"custom":"1089215012665154939","receive_address":"QRwGZSkh3eoj4XTJFk1rZsy74zTouHY5HA","button":{"type":"buy_now","subscription":false,"repeat":null,"name":"30 karma","description":"30 karma @ $0.01 USD each","id":"9bd1d424582687qac22c3037u5axacf1"},"refund_address":"1Fn2ou3rZRStYgtq8v6Taz47drueyQzrF2","transaction":{"id":"54b8441c7dsfbb9b38105022","hash":"ad9d5d6671b6764fc122923fff90a5b7cdb5ec531eb42cd92bda937465b98d76","confirmations":0}}}';

    // state whether signature is okay or not
    $ok = openssl_verify($data, $signature, $pubkeyid);

    if ($ok == 1) {
        echo "good";
    } elseif ($ok == 0) {
        echo "bad";
    } else {
        echo "ugly, error checking signature";
    }
    // free the key from memory
    openssl_free_key($pubkeyid);
}

This is not verifying (i.e., printing 'bad') and their API library doesn't have a function. I've masked the data but would appreciate any suggestions of what I'm doing wrong around the process. Thank you in advance.

Upvotes: 3

Views: 559

Answers (1)

Cysioland
Cysioland

Reputation: 1126

openssl_verify works on raw binary string, not its base64 representation. Just base64_decode the $signature prior to passing it to openssl_verify, and you should be good.

In addition to that, the third argument accepts regular PEM string key, so just pass it straight to the method.

Upvotes: 0

Related Questions