Skip to content

Commit 7f76dda

Browse files
committed
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)
2 parents 4f85383 + 5ec654b commit 7f76dda

File tree

7 files changed

+88
-43
lines changed

7 files changed

+88
-43
lines changed

src/qt/bitcoin.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -570,9 +570,9 @@ int main(int argc, char *argv[])
570570
}
571571
#ifdef ENABLE_WALLET
572572
// Parse URIs on command line -- this can affect Params()
573-
if (!PaymentServer::ipcParseCommandLine(argc, argv))
574-
exit(0);
573+
PaymentServer::ipcParseCommandLine(argc, argv);
575574
#endif
575+
576576
QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(QString::fromStdString(Params().NetworkIDString())));
577577
assert(!networkStyle.isNull());
578578
// Allow for separate UI settings for testnets

src/qt/guiconstants.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80;
3838
/* Maximum allowed URI length */
3939
static const int MAX_URI_LENGTH = 255;
4040

41-
/* Maximum somewhat-sane size of a payment request file */
42-
static const int MAX_PAYMENT_REQUEST_SIZE = 50000; // bytes
43-
4441
/* QRCodeDialog -- size of exported QR Code image */
4542
#define EXPORT_IMAGE_SIZE 256
4643

src/qt/paymentrequestplus.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// Copyright (c) 2011-2013 The Bitcoin developers
2-
// Distributed under the MIT/X11 software license, see the accompanying
1+
// Copyright (c) 2011-2014 The Bitcoin developers
2+
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
//

src/qt/paymentrequestplus.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// Copyright (c) 2011-2013 The Bitcoin developers
2-
// Distributed under the MIT/X11 software license, see the accompanying
1+
// Copyright (c) 2011-2014 The Bitcoin developers
2+
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#ifndef BITCOIN_QT_PAYMENTREQUESTPLUS_H

src/qt/paymentserver.cpp

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
// Copyright (c) 2011-2013 The Bitcoin developers
2-
// Distributed under the MIT/X11 software license, see the accompanying
1+
// Copyright (c) 2011-2014 The Bitcoin developers
2+
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include "paymentserver.h"
66

77
#include "bitcoinunits.h"
8-
#include "guiconstants.h"
98
#include "guiutil.h"
109
#include "optionsmodel.h"
1110

@@ -19,6 +18,7 @@
1918

2019
#include <openssl/x509.h>
2120
#include <openssl/x509_vfy.h>
21+
2222
#include <QApplication>
2323
#include <QByteArray>
2424
#include <QDataStream>
@@ -46,14 +46,20 @@
4646
#include <QUrlQuery>
4747
#endif
4848

49-
using namespace std;
5049
using namespace boost;
50+
using namespace std;
5151

5252
const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
5353
const QString BITCOIN_IPC_PREFIX("bitcoin:");
54-
const char* BITCOIN_REQUEST_MIMETYPE = "application/bitcoin-paymentrequest";
55-
const char* BITCOIN_PAYMENTACK_MIMETYPE = "application/bitcoin-paymentack";
56-
const char* BITCOIN_PAYMENTACK_CONTENTTYPE = "application/bitcoin-payment";
54+
// BIP70 payment protocol messages
55+
const char* BIP70_MESSAGE_PAYMENTACK = "PaymentACK";
56+
const char* BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest";
57+
// BIP71 payment protocol media types
58+
const char* BIP71_MIMETYPE_PAYMENT = "application/bitcoin-payment";
59+
const char* BIP71_MIMETYPE_PAYMENTACK = "application/bitcoin-paymentack";
60+
const char* BIP71_MIMETYPE_PAYMENTREQUEST = "application/bitcoin-paymentrequest";
61+
// BIP70 max payment request size in bytes (DoS protection)
62+
const qint64 BIP70_MAX_PAYMENTREQUEST_SIZE = 50000;
5763

5864
X509_STORE* PaymentServer::certStore = NULL;
5965
void PaymentServer::freeCertStore()
@@ -184,14 +190,18 @@ void PaymentServer::LoadRootCAs(X509_STORE* _store)
184190
// Warning: ipcSendCommandLine() is called early in init,
185191
// so don't use "emit message()", but "QMessageBox::"!
186192
//
187-
bool PaymentServer::ipcParseCommandLine(int argc, char* argv[])
193+
void PaymentServer::ipcParseCommandLine(int argc, char* argv[])
188194
{
189195
for (int i = 1; i < argc; i++)
190196
{
191197
QString arg(argv[i]);
192198
if (arg.startsWith("-"))
193199
continue;
194200

201+
// If the bitcoin: URI contains a payment request, we are not able to detect the
202+
// network as that would require fetching and parsing the payment request.
203+
// That means clicking such an URI which contains a testnet payment request
204+
// will start a mainnet instance and throw a "wrong network" error.
195205
if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
196206
{
197207
savedPaymentRequests.append(arg);
@@ -216,7 +226,7 @@ bool PaymentServer::ipcParseCommandLine(int argc, char* argv[])
216226
savedPaymentRequests.append(arg);
217227

218228
PaymentRequestPlus request;
219-
if (readPaymentRequest(arg, request))
229+
if (readPaymentRequestFromFile(arg, request))
220230
{
221231
if (request.getDetails().network() == "main")
222232
{
@@ -235,7 +245,6 @@ bool PaymentServer::ipcParseCommandLine(int argc, char* argv[])
235245
qWarning() << "PaymentServer::ipcSendCommandLine : Payment request file does not exist: " << arg;
236246
}
237247
}
238-
return true;
239248
}
240249

241250
//
@@ -254,6 +263,7 @@ bool PaymentServer::ipcSendCommandLine()
254263
if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT))
255264
{
256265
delete socket;
266+
socket = NULL;
257267
return false;
258268
}
259269

@@ -262,12 +272,14 @@ bool PaymentServer::ipcSendCommandLine()
262272
out.setVersion(QDataStream::Qt_4_0);
263273
out << r;
264274
out.device()->seek(0);
275+
265276
socket->write(block);
266277
socket->flush();
267-
268278
socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
269279
socket->disconnectFromServer();
280+
270281
delete socket;
282+
socket = NULL;
271283
fResult = true;
272284
}
273285

@@ -440,7 +452,7 @@ void PaymentServer::handleURIOrFile(const QString& s)
440452
{
441453
PaymentRequestPlus request;
442454
SendCoinsRecipient recipient;
443-
if (!readPaymentRequest(s, request))
455+
if (!readPaymentRequestFromFile(s, request))
444456
{
445457
emit message(tr("Payment request file handling"),
446458
tr("Payment request file cannot be read! This can be caused by an invalid payment request file."),
@@ -474,18 +486,25 @@ void PaymentServer::handleURIConnection()
474486
handleURIOrFile(msg);
475487
}
476488

477-
bool PaymentServer::readPaymentRequest(const QString& filename, PaymentRequestPlus& request)
489+
//
490+
// Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine()
491+
// so don't use "emit message()", but "QMessageBox::"!
492+
//
493+
bool PaymentServer::readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request)
478494
{
479495
QFile f(filename);
480-
if (!f.open(QIODevice::ReadOnly))
481-
{
482-
qWarning() << "PaymentServer::readPaymentRequest : Failed to open " << filename;
496+
if (!f.open(QIODevice::ReadOnly)) {
497+
qWarning() << QString("PaymentServer::%1: Failed to open %2").arg(__func__).arg(filename);
483498
return false;
484499
}
485500

486-
if (f.size() > MAX_PAYMENT_REQUEST_SIZE)
487-
{
488-
qWarning() << "PaymentServer::readPaymentRequest : " << filename << " too large";
501+
// BIP70 DoS protection
502+
if (f.size() > BIP70_MAX_PAYMENTREQUEST_SIZE) {
503+
qWarning() << QString("PaymentServer::%1: Payment request %2 is too large (%3 bytes, allowed %4 bytes).")
504+
.arg(__func__)
505+
.arg(filename)
506+
.arg(f.size())
507+
.arg(BIP70_MAX_PAYMENTREQUEST_SIZE);
489508
return false;
490509
}
491510

@@ -580,10 +599,10 @@ bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, SendCoins
580599
void PaymentServer::fetchRequest(const QUrl& url)
581600
{
582601
QNetworkRequest netRequest;
583-
netRequest.setAttribute(QNetworkRequest::User, "PaymentRequest");
602+
netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTREQUEST);
584603
netRequest.setUrl(url);
585604
netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
586-
netRequest.setRawHeader("Accept", BITCOIN_REQUEST_MIMETYPE);
605+
netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTREQUEST);
587606
netManager->get(netRequest);
588607
}
589608

@@ -594,11 +613,11 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipien
594613
return;
595614

596615
QNetworkRequest netRequest;
597-
netRequest.setAttribute(QNetworkRequest::User, "PaymentACK");
616+
netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK);
598617
netRequest.setUrl(QString::fromStdString(details.payment_url()));
599-
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BITCOIN_PAYMENTACK_CONTENTTYPE);
618+
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BIP71_MIMETYPE_PAYMENT);
600619
netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
601-
netRequest.setRawHeader("Accept", BITCOIN_PAYMENTACK_MIMETYPE);
620+
netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTACK);
602621

603622
payments::Payment payment;
604623
payment.set_merchant_data(details.merchant_data());
@@ -616,7 +635,6 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipien
616635
else {
617636
CPubKey newKey;
618637
if (wallet->GetKeyFromPool(newKey)) {
619-
LOCK(wallet->cs_wallet); // SetAddressBook
620638
CKeyID keyID = newKey.GetID();
621639
wallet->SetAddressBook(keyID, strAccount, "refund");
622640

@@ -646,21 +664,34 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipien
646664
void PaymentServer::netRequestFinished(QNetworkReply* reply)
647665
{
648666
reply->deleteLater();
649-
if (reply->error() != QNetworkReply::NoError)
650-
{
667+
668+
// BIP70 DoS protection
669+
if (reply->size() > BIP70_MAX_PAYMENTREQUEST_SIZE) {
670+
QString msg = tr("Payment request %2 is too large (%3 bytes, allowed %4 bytes).")
671+
.arg(__func__)
672+
.arg(reply->request().url().toString())
673+
.arg(reply->size())
674+
.arg(BIP70_MAX_PAYMENTREQUEST_SIZE);
675+
676+
qWarning() << QString("PaymentServer::%1:").arg(__func__) << msg;
677+
emit message(tr("Payment request DoS protection"), msg, CClientUIInterface::MSG_ERROR);
678+
return;
679+
}
680+
681+
if (reply->error() != QNetworkReply::NoError) {
651682
QString msg = tr("Error communicating with %1: %2")
652683
.arg(reply->request().url().toString())
653684
.arg(reply->errorString());
654685

655-
qWarning() << "PaymentServer::netRequestFinished : " << msg;
686+
qWarning() << "PaymentServer::netRequestFinished: " << msg;
656687
emit message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
657688
return;
658689
}
659690

660691
QByteArray data = reply->readAll();
661692

662693
QString requestType = reply->request().attribute(QNetworkRequest::User).toString();
663-
if (requestType == "PaymentRequest")
694+
if (requestType == BIP70_MESSAGE_PAYMENTREQUEST)
664695
{
665696
PaymentRequestPlus request;
666697
SendCoinsRecipient recipient;
@@ -676,7 +707,7 @@ void PaymentServer::netRequestFinished(QNetworkReply* reply)
676707

677708
return;
678709
}
679-
else if (requestType == "PaymentACK")
710+
else if (requestType == BIP70_MESSAGE_PAYMENTACK)
680711
{
681712
payments::PaymentACK paymentACK;
682713
if (!paymentACK.ParseFromArray(data.data(), data.size()))

src/qt/paymentserver.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) 2011-2014 The Bitcoin developers
2-
// Distributed under the MIT/X11 software license, see the accompanying
2+
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#ifndef BITCOIN_QT_PAYMENTSERVER_H
@@ -40,6 +40,8 @@
4040

4141
class OptionsModel;
4242

43+
class CWallet;
44+
4345
QT_BEGIN_NAMESPACE
4446
class QApplication;
4547
class QByteArray;
@@ -50,7 +52,8 @@ class QSslError;
5052
class QUrl;
5153
QT_END_NAMESPACE
5254

53-
class CWallet;
55+
// BIP70 max payment request size in bytes (DoS protection)
56+
extern const qint64 BIP70_MAX_PAYMENTREQUEST_SIZE;
5457

5558
class PaymentServer : public QObject
5659
{
@@ -59,7 +62,7 @@ class PaymentServer : public QObject
5962
public:
6063
// Parse URIs on command line
6164
// Returns false on error
62-
static bool ipcParseCommandLine(int argc, char *argv[]);
65+
static void ipcParseCommandLine(int argc, char *argv[]);
6366

6467
// Returns true if there were URIs on the command line
6568
// which were successfully sent to an already-running
@@ -85,6 +88,9 @@ class PaymentServer : public QObject
8588
// OptionsModel is used for getting proxy settings and display unit
8689
void setOptionsModel(OptionsModel *optionsModel);
8790

91+
// This is now public, because we use it in paymentservertests.cpp
92+
static bool readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request);
93+
8894
signals:
8995
// Fired when a valid payment request is received
9096
void receivedPaymentRequest(SendCoinsRecipient);
@@ -118,7 +124,6 @@ private slots:
118124
bool eventFilter(QObject *object, QEvent *event);
119125

120126
private:
121-
static bool readPaymentRequest(const QString& filename, PaymentRequestPlus& request);
122127
bool processPaymentRequest(PaymentRequestPlus& request, SendCoinsRecipient& recipient);
123128
void fetchRequest(const QUrl& url);
124129

src/qt/test/paymentservertests.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "optionsmodel.h"
88
#include "paymentrequestdata.h"
99

10+
#include "random.h"
1011
#include "util.h"
1112
#include "utilstrencodings.h"
1213

@@ -108,6 +109,17 @@ void PaymentServerTests::paymentServerTests()
108109
r.paymentRequest.getMerchant(caStore, merchant);
109110
QCOMPARE(merchant, QString(""));
110111

112+
// Just get some random data big enough to trigger BIP70 DoS protection
113+
unsigned char randData[BIP70_MAX_PAYMENTREQUEST_SIZE + 1];
114+
GetRandBytes(randData, sizeof(randData));
115+
// Write data to a temp file:
116+
QTemporaryFile tempFile;
117+
tempFile.open();
118+
tempFile.write((const char*)randData, sizeof(randData));
119+
tempFile.close();
120+
// Trigger BIP70 DoS protection
121+
QCOMPARE(PaymentServer::readPaymentRequestFromFile(tempFile.fileName(), r.paymentRequest), false);
122+
111123
delete server;
112124
}
113125

0 commit comments

Comments
 (0)