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
// Parse URIs on command line -- this can affect Params()
if (!PaymentServer::ipcParseCommandLine(argc, argv))
exit(0);
PaymentServer::ipcParseCommandLine(argc, argv);
#endif
QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(QString::fromStdString(Params().NetworkIDString())));
assert(!networkStyle.isNull());
// Allow for separate UI settings for testnets

View file

@ -38,9 +38,6 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80;
/* Maximum allowed URI length */
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 */
#define EXPORT_IMAGE_SIZE 256

View file

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

View file

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

View file

@ -1,11 +1,10 @@
// Copyright (c) 2011-2013 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// Copyright (c) 2011-2014 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "paymentserver.h"
#include "bitcoinunits.h"
#include "guiconstants.h"
#include "guiutil.h"
#include "optionsmodel.h"
@ -19,6 +18,7 @@
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include <QApplication>
#include <QByteArray>
#include <QDataStream>
@ -46,14 +46,20 @@
#include <QUrlQuery>
#endif
using namespace std;
using namespace boost;
using namespace std;
const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
const QString BITCOIN_IPC_PREFIX("bitcoin:");
const char* BITCOIN_REQUEST_MIMETYPE = "application/bitcoin-paymentrequest";
const char* BITCOIN_PAYMENTACK_MIMETYPE = "application/bitcoin-paymentack";
const char* BITCOIN_PAYMENTACK_CONTENTTYPE = "application/bitcoin-payment";
// BIP70 payment protocol messages
const char* BIP70_MESSAGE_PAYMENTACK = "PaymentACK";
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;
void PaymentServer::freeCertStore()
@ -184,7 +190,7 @@ void PaymentServer::LoadRootCAs(X509_STORE* _store)
// Warning: ipcSendCommandLine() is called early in init,
// 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++)
{
@ -192,6 +198,10 @@ bool PaymentServer::ipcParseCommandLine(int argc, char* argv[])
if (arg.startsWith("-"))
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
{
savedPaymentRequests.append(arg);
@ -216,7 +226,7 @@ bool PaymentServer::ipcParseCommandLine(int argc, char* argv[])
savedPaymentRequests.append(arg);
PaymentRequestPlus request;
if (readPaymentRequest(arg, request))
if (readPaymentRequestFromFile(arg, request))
{
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;
}
}
return true;
}
//
@ -254,6 +263,7 @@ bool PaymentServer::ipcSendCommandLine()
if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT))
{
delete socket;
socket = NULL;
return false;
}
@ -262,12 +272,14 @@ bool PaymentServer::ipcSendCommandLine()
out.setVersion(QDataStream::Qt_4_0);
out << r;
out.device()->seek(0);
socket->write(block);
socket->flush();
socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
socket->disconnectFromServer();
delete socket;
socket = NULL;
fResult = true;
}
@ -440,7 +452,7 @@ void PaymentServer::handleURIOrFile(const QString& s)
{
PaymentRequestPlus request;
SendCoinsRecipient recipient;
if (!readPaymentRequest(s, request))
if (!readPaymentRequestFromFile(s, request))
{
emit message(tr("Payment request file handling"),
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);
}
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);
if (!f.open(QIODevice::ReadOnly))
{
qWarning() << "PaymentServer::readPaymentRequest : Failed to open " << filename;
if (!f.open(QIODevice::ReadOnly)) {
qWarning() << QString("PaymentServer::%1: Failed to open %2").arg(__func__).arg(filename);
return false;
}
if (f.size() > MAX_PAYMENT_REQUEST_SIZE)
{
qWarning() << "PaymentServer::readPaymentRequest : " << filename << " too large";
// BIP70 DoS protection
if (f.size() > BIP70_MAX_PAYMENTREQUEST_SIZE) {
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;
}
@ -580,10 +599,10 @@ bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, SendCoins
void PaymentServer::fetchRequest(const QUrl& url)
{
QNetworkRequest netRequest;
netRequest.setAttribute(QNetworkRequest::User, "PaymentRequest");
netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTREQUEST);
netRequest.setUrl(url);
netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
netRequest.setRawHeader("Accept", BITCOIN_REQUEST_MIMETYPE);
netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTREQUEST);
netManager->get(netRequest);
}
@ -594,11 +613,11 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipien
return;
QNetworkRequest netRequest;
netRequest.setAttribute(QNetworkRequest::User, "PaymentACK");
netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK);
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("Accept", BITCOIN_PAYMENTACK_MIMETYPE);
netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTACK);
payments::Payment payment;
payment.set_merchant_data(details.merchant_data());
@ -616,7 +635,6 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipien
else {
CPubKey newKey;
if (wallet->GetKeyFromPool(newKey)) {
LOCK(wallet->cs_wallet); // SetAddressBook
CKeyID keyID = newKey.GetID();
wallet->SetAddressBook(keyID, strAccount, "refund");
@ -646,13 +664,26 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipien
void PaymentServer::netRequestFinished(QNetworkReply* reply)
{
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")
.arg(reply->request().url().toString())
.arg(reply->errorString());
qWarning() << "PaymentServer::netRequestFinished : " << msg;
qWarning() << "PaymentServer::netRequestFinished: " << msg;
emit message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
return;
}
@ -660,7 +691,7 @@ void PaymentServer::netRequestFinished(QNetworkReply* reply)
QByteArray data = reply->readAll();
QString requestType = reply->request().attribute(QNetworkRequest::User).toString();
if (requestType == "PaymentRequest")
if (requestType == BIP70_MESSAGE_PAYMENTREQUEST)
{
PaymentRequestPlus request;
SendCoinsRecipient recipient;
@ -676,7 +707,7 @@ void PaymentServer::netRequestFinished(QNetworkReply* reply)
return;
}
else if (requestType == "PaymentACK")
else if (requestType == BIP70_MESSAGE_PAYMENTACK)
{
payments::PaymentACK paymentACK;
if (!paymentACK.ParseFromArray(data.data(), data.size()))

View file

@ -1,5 +1,5 @@
// 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.
#ifndef BITCOIN_QT_PAYMENTSERVER_H
@ -40,6 +40,8 @@
class OptionsModel;
class CWallet;
QT_BEGIN_NAMESPACE
class QApplication;
class QByteArray;
@ -50,7 +52,8 @@ class QSslError;
class QUrl;
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
{
@ -59,7 +62,7 @@ class PaymentServer : public QObject
public:
// Parse URIs on command line
// 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
// which were successfully sent to an already-running
@ -85,6 +88,9 @@ public:
// OptionsModel is used for getting proxy settings and display unit
void setOptionsModel(OptionsModel *optionsModel);
// This is now public, because we use it in paymentservertests.cpp
static bool readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request);
signals:
// Fired when a valid payment request is received
void receivedPaymentRequest(SendCoinsRecipient);
@ -118,7 +124,6 @@ protected:
bool eventFilter(QObject *object, QEvent *event);
private:
static bool readPaymentRequest(const QString& filename, PaymentRequestPlus& request);
bool processPaymentRequest(PaymentRequestPlus& request, SendCoinsRecipient& recipient);
void fetchRequest(const QUrl& url);

View file

@ -7,6 +7,7 @@
#include "optionsmodel.h"
#include "paymentrequestdata.h"
#include "random.h"
#include "util.h"
#include "utilstrencodings.h"
@ -108,6 +109,17 @@ void PaymentServerTests::paymentServerTests()
r.paymentRequest.getMerchant(caStore, merchant);
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;
}