Merge pull request #5216

5ec654b [Qt] update paymentserver license and cleanup ordering (Philip Kaufmann)
4333e26 [Qt] add BIP70 DoS protection test (Philip Kaufmann)
31f8494 [Qt] add BIP70 payment request size DoS protection for URIs (Philip Kaufmann)
2284ccb [Qt] remove dup lock that is done in SetAddressBook() (Philip Kaufmann)
1ec753f [Qt] ensure socket is set to NULL in PaymentServer::ipcSendCommandLine (Philip Kaufmann)
814429d [Qt] add BIP70/BIP71 constants for all messages and mime types (Philip Kaufmann)
b82695b [Qt] make PaymentServer::ipcParseCommandLine void (Philip Kaufmann)
This commit is contained in:
Wladimir J. van der Laan 2014-12-09 10:15:23 +01:00
commit 7f76dda903
No known key found for this signature in database
GPG key ID: 74810B012346C9A6
7 changed files with 88 additions and 43 deletions

View file

@ -570,9 +570,9 @@ int main(int argc, char *argv[])
} }
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
// Parse URIs on command line -- this can affect Params() // Parse URIs on command line -- this can affect Params()
if (!PaymentServer::ipcParseCommandLine(argc, argv)) PaymentServer::ipcParseCommandLine(argc, argv);
exit(0);
#endif #endif
QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(QString::fromStdString(Params().NetworkIDString()))); QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(QString::fromStdString(Params().NetworkIDString())));
assert(!networkStyle.isNull()); assert(!networkStyle.isNull());
// Allow for separate UI settings for testnets // Allow for separate UI settings for testnets

View file

@ -38,9 +38,6 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80;
/* Maximum allowed URI length */ /* Maximum allowed URI length */
static const int MAX_URI_LENGTH = 255; static const int MAX_URI_LENGTH = 255;
/* Maximum somewhat-sane size of a payment request file */
static const int MAX_PAYMENT_REQUEST_SIZE = 50000; // bytes
/* QRCodeDialog -- size of exported QR Code image */ /* QRCodeDialog -- size of exported QR Code image */
#define EXPORT_IMAGE_SIZE 256 #define EXPORT_IMAGE_SIZE 256

View file

@ -1,5 +1,5 @@
// Copyright (c) 2011-2013 The Bitcoin developers // Copyright (c) 2011-2014 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
// //

View file

@ -1,5 +1,5 @@
// Copyright (c) 2011-2013 The Bitcoin developers // Copyright (c) 2011-2014 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_QT_PAYMENTREQUESTPLUS_H #ifndef BITCOIN_QT_PAYMENTREQUESTPLUS_H

View file

@ -1,11 +1,10 @@
// Copyright (c) 2011-2013 The Bitcoin developers // Copyright (c) 2011-2014 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "paymentserver.h" #include "paymentserver.h"
#include "bitcoinunits.h" #include "bitcoinunits.h"
#include "guiconstants.h"
#include "guiutil.h" #include "guiutil.h"
#include "optionsmodel.h" #include "optionsmodel.h"
@ -19,6 +18,7 @@
#include <openssl/x509.h> #include <openssl/x509.h>
#include <openssl/x509_vfy.h> #include <openssl/x509_vfy.h>
#include <QApplication> #include <QApplication>
#include <QByteArray> #include <QByteArray>
#include <QDataStream> #include <QDataStream>
@ -46,14 +46,20 @@
#include <QUrlQuery> #include <QUrlQuery>
#endif #endif
using namespace std;
using namespace boost; using namespace boost;
using namespace std;
const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
const QString BITCOIN_IPC_PREFIX("bitcoin:"); const QString BITCOIN_IPC_PREFIX("bitcoin:");
const char* BITCOIN_REQUEST_MIMETYPE = "application/bitcoin-paymentrequest"; // BIP70 payment protocol messages
const char* BITCOIN_PAYMENTACK_MIMETYPE = "application/bitcoin-paymentack"; const char* BIP70_MESSAGE_PAYMENTACK = "PaymentACK";
const char* BITCOIN_PAYMENTACK_CONTENTTYPE = "application/bitcoin-payment"; const char* BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest";
// BIP71 payment protocol media types
const char* BIP71_MIMETYPE_PAYMENT = "application/bitcoin-payment";
const char* BIP71_MIMETYPE_PAYMENTACK = "application/bitcoin-paymentack";
const char* BIP71_MIMETYPE_PAYMENTREQUEST = "application/bitcoin-paymentrequest";
// BIP70 max payment request size in bytes (DoS protection)
const qint64 BIP70_MAX_PAYMENTREQUEST_SIZE = 50000;
X509_STORE* PaymentServer::certStore = NULL; X509_STORE* PaymentServer::certStore = NULL;
void PaymentServer::freeCertStore() void PaymentServer::freeCertStore()
@ -184,7 +190,7 @@ void PaymentServer::LoadRootCAs(X509_STORE* _store)
// Warning: ipcSendCommandLine() is called early in init, // Warning: ipcSendCommandLine() is called early in init,
// so don't use "emit message()", but "QMessageBox::"! // so don't use "emit message()", but "QMessageBox::"!
// //
bool PaymentServer::ipcParseCommandLine(int argc, char* argv[]) void PaymentServer::ipcParseCommandLine(int argc, char* argv[])
{ {
for (int i = 1; i < argc; i++) for (int i = 1; i < argc; i++)
{ {
@ -192,6 +198,10 @@ bool PaymentServer::ipcParseCommandLine(int argc, char* argv[])
if (arg.startsWith("-")) if (arg.startsWith("-"))
continue; continue;
// If the bitcoin: URI contains a payment request, we are not able to detect the
// network as that would require fetching and parsing the payment request.
// That means clicking such an URI which contains a testnet payment request
// will start a mainnet instance and throw a "wrong network" error.
if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
{ {
savedPaymentRequests.append(arg); savedPaymentRequests.append(arg);
@ -216,7 +226,7 @@ bool PaymentServer::ipcParseCommandLine(int argc, char* argv[])
savedPaymentRequests.append(arg); savedPaymentRequests.append(arg);
PaymentRequestPlus request; PaymentRequestPlus request;
if (readPaymentRequest(arg, request)) if (readPaymentRequestFromFile(arg, request))
{ {
if (request.getDetails().network() == "main") if (request.getDetails().network() == "main")
{ {
@ -235,7 +245,6 @@ bool PaymentServer::ipcParseCommandLine(int argc, char* argv[])
qWarning() << "PaymentServer::ipcSendCommandLine : Payment request file does not exist: " << arg; qWarning() << "PaymentServer::ipcSendCommandLine : Payment request file does not exist: " << arg;
} }
} }
return true;
} }
// //
@ -254,6 +263,7 @@ bool PaymentServer::ipcSendCommandLine()
if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT)) if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT))
{ {
delete socket; delete socket;
socket = NULL;
return false; return false;
} }
@ -262,12 +272,14 @@ bool PaymentServer::ipcSendCommandLine()
out.setVersion(QDataStream::Qt_4_0); out.setVersion(QDataStream::Qt_4_0);
out << r; out << r;
out.device()->seek(0); out.device()->seek(0);
socket->write(block); socket->write(block);
socket->flush(); socket->flush();
socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT); socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
socket->disconnectFromServer(); socket->disconnectFromServer();
delete socket; delete socket;
socket = NULL;
fResult = true; fResult = true;
} }
@ -440,7 +452,7 @@ void PaymentServer::handleURIOrFile(const QString& s)
{ {
PaymentRequestPlus request; PaymentRequestPlus request;
SendCoinsRecipient recipient; SendCoinsRecipient recipient;
if (!readPaymentRequest(s, request)) if (!readPaymentRequestFromFile(s, request))
{ {
emit message(tr("Payment request file handling"), emit message(tr("Payment request file handling"),
tr("Payment request file cannot be read! This can be caused by an invalid payment request file."), tr("Payment request file cannot be read! This can be caused by an invalid payment request file."),
@ -474,18 +486,25 @@ void PaymentServer::handleURIConnection()
handleURIOrFile(msg); handleURIOrFile(msg);
} }
bool PaymentServer::readPaymentRequest(const QString& filename, PaymentRequestPlus& request) //
// Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine()
// so don't use "emit message()", but "QMessageBox::"!
//
bool PaymentServer::readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request)
{ {
QFile f(filename); QFile f(filename);
if (!f.open(QIODevice::ReadOnly)) if (!f.open(QIODevice::ReadOnly)) {
{ qWarning() << QString("PaymentServer::%1: Failed to open %2").arg(__func__).arg(filename);
qWarning() << "PaymentServer::readPaymentRequest : Failed to open " << filename;
return false; return false;
} }
if (f.size() > MAX_PAYMENT_REQUEST_SIZE) // BIP70 DoS protection
{ if (f.size() > BIP70_MAX_PAYMENTREQUEST_SIZE) {
qWarning() << "PaymentServer::readPaymentRequest : " << filename << " too large"; qWarning() << QString("PaymentServer::%1: Payment request %2 is too large (%3 bytes, allowed %4 bytes).")
.arg(__func__)
.arg(filename)
.arg(f.size())
.arg(BIP70_MAX_PAYMENTREQUEST_SIZE);
return false; return false;
} }
@ -580,10 +599,10 @@ bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, SendCoins
void PaymentServer::fetchRequest(const QUrl& url) void PaymentServer::fetchRequest(const QUrl& url)
{ {
QNetworkRequest netRequest; QNetworkRequest netRequest;
netRequest.setAttribute(QNetworkRequest::User, "PaymentRequest"); netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTREQUEST);
netRequest.setUrl(url); netRequest.setUrl(url);
netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str()); netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
netRequest.setRawHeader("Accept", BITCOIN_REQUEST_MIMETYPE); netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTREQUEST);
netManager->get(netRequest); netManager->get(netRequest);
} }
@ -594,11 +613,11 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipien
return; return;
QNetworkRequest netRequest; QNetworkRequest netRequest;
netRequest.setAttribute(QNetworkRequest::User, "PaymentACK"); netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK);
netRequest.setUrl(QString::fromStdString(details.payment_url())); netRequest.setUrl(QString::fromStdString(details.payment_url()));
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BITCOIN_PAYMENTACK_CONTENTTYPE); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BIP71_MIMETYPE_PAYMENT);
netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str()); netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
netRequest.setRawHeader("Accept", BITCOIN_PAYMENTACK_MIMETYPE); netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTACK);
payments::Payment payment; payments::Payment payment;
payment.set_merchant_data(details.merchant_data()); payment.set_merchant_data(details.merchant_data());
@ -616,7 +635,6 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipien
else { else {
CPubKey newKey; CPubKey newKey;
if (wallet->GetKeyFromPool(newKey)) { if (wallet->GetKeyFromPool(newKey)) {
LOCK(wallet->cs_wallet); // SetAddressBook
CKeyID keyID = newKey.GetID(); CKeyID keyID = newKey.GetID();
wallet->SetAddressBook(keyID, strAccount, "refund"); wallet->SetAddressBook(keyID, strAccount, "refund");
@ -646,13 +664,26 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipien
void PaymentServer::netRequestFinished(QNetworkReply* reply) void PaymentServer::netRequestFinished(QNetworkReply* reply)
{ {
reply->deleteLater(); reply->deleteLater();
if (reply->error() != QNetworkReply::NoError)
{ // BIP70 DoS protection
if (reply->size() > BIP70_MAX_PAYMENTREQUEST_SIZE) {
QString msg = tr("Payment request %2 is too large (%3 bytes, allowed %4 bytes).")
.arg(__func__)
.arg(reply->request().url().toString())
.arg(reply->size())
.arg(BIP70_MAX_PAYMENTREQUEST_SIZE);
qWarning() << QString("PaymentServer::%1:").arg(__func__) << msg;
emit message(tr("Payment request DoS protection"), msg, CClientUIInterface::MSG_ERROR);
return;
}
if (reply->error() != QNetworkReply::NoError) {
QString msg = tr("Error communicating with %1: %2") QString msg = tr("Error communicating with %1: %2")
.arg(reply->request().url().toString()) .arg(reply->request().url().toString())
.arg(reply->errorString()); .arg(reply->errorString());
qWarning() << "PaymentServer::netRequestFinished : " << msg; qWarning() << "PaymentServer::netRequestFinished: " << msg;
emit message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR); emit message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
return; return;
} }
@ -660,7 +691,7 @@ void PaymentServer::netRequestFinished(QNetworkReply* reply)
QByteArray data = reply->readAll(); QByteArray data = reply->readAll();
QString requestType = reply->request().attribute(QNetworkRequest::User).toString(); QString requestType = reply->request().attribute(QNetworkRequest::User).toString();
if (requestType == "PaymentRequest") if (requestType == BIP70_MESSAGE_PAYMENTREQUEST)
{ {
PaymentRequestPlus request; PaymentRequestPlus request;
SendCoinsRecipient recipient; SendCoinsRecipient recipient;
@ -676,7 +707,7 @@ void PaymentServer::netRequestFinished(QNetworkReply* reply)
return; return;
} }
else if (requestType == "PaymentACK") else if (requestType == BIP70_MESSAGE_PAYMENTACK)
{ {
payments::PaymentACK paymentACK; payments::PaymentACK paymentACK;
if (!paymentACK.ParseFromArray(data.data(), data.size())) if (!paymentACK.ParseFromArray(data.data(), data.size()))

View file

@ -1,5 +1,5 @@
// Copyright (c) 2011-2014 The Bitcoin developers // Copyright (c) 2011-2014 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_QT_PAYMENTSERVER_H #ifndef BITCOIN_QT_PAYMENTSERVER_H
@ -40,6 +40,8 @@
class OptionsModel; class OptionsModel;
class CWallet;
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QApplication; class QApplication;
class QByteArray; class QByteArray;
@ -50,7 +52,8 @@ class QSslError;
class QUrl; class QUrl;
QT_END_NAMESPACE QT_END_NAMESPACE
class CWallet; // BIP70 max payment request size in bytes (DoS protection)
extern const qint64 BIP70_MAX_PAYMENTREQUEST_SIZE;
class PaymentServer : public QObject class PaymentServer : public QObject
{ {
@ -59,7 +62,7 @@ class PaymentServer : public QObject
public: public:
// Parse URIs on command line // Parse URIs on command line
// Returns false on error // Returns false on error
static bool ipcParseCommandLine(int argc, char *argv[]); static void ipcParseCommandLine(int argc, char *argv[]);
// Returns true if there were URIs on the command line // Returns true if there were URIs on the command line
// which were successfully sent to an already-running // which were successfully sent to an already-running
@ -85,6 +88,9 @@ public:
// OptionsModel is used for getting proxy settings and display unit // OptionsModel is used for getting proxy settings and display unit
void setOptionsModel(OptionsModel *optionsModel); void setOptionsModel(OptionsModel *optionsModel);
// This is now public, because we use it in paymentservertests.cpp
static bool readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request);
signals: signals:
// Fired when a valid payment request is received // Fired when a valid payment request is received
void receivedPaymentRequest(SendCoinsRecipient); void receivedPaymentRequest(SendCoinsRecipient);
@ -118,7 +124,6 @@ protected:
bool eventFilter(QObject *object, QEvent *event); bool eventFilter(QObject *object, QEvent *event);
private: private:
static bool readPaymentRequest(const QString& filename, PaymentRequestPlus& request);
bool processPaymentRequest(PaymentRequestPlus& request, SendCoinsRecipient& recipient); bool processPaymentRequest(PaymentRequestPlus& request, SendCoinsRecipient& recipient);
void fetchRequest(const QUrl& url); void fetchRequest(const QUrl& url);

View file

@ -7,6 +7,7 @@
#include "optionsmodel.h" #include "optionsmodel.h"
#include "paymentrequestdata.h" #include "paymentrequestdata.h"
#include "random.h"
#include "util.h" #include "util.h"
#include "utilstrencodings.h" #include "utilstrencodings.h"
@ -108,6 +109,17 @@ void PaymentServerTests::paymentServerTests()
r.paymentRequest.getMerchant(caStore, merchant); r.paymentRequest.getMerchant(caStore, merchant);
QCOMPARE(merchant, QString("")); QCOMPARE(merchant, QString(""));
// Just get some random data big enough to trigger BIP70 DoS protection
unsigned char randData[BIP70_MAX_PAYMENTREQUEST_SIZE + 1];
GetRandBytes(randData, sizeof(randData));
// Write data to a temp file:
QTemporaryFile tempFile;
tempFile.open();
tempFile.write((const char*)randData, sizeof(randData));
tempFile.close();
// Trigger BIP70 DoS protection
QCOMPARE(PaymentServer::readPaymentRequestFromFile(tempFile.fileName(), r.paymentRequest), false);
delete server; delete server;
} }