2018-07-27 00:36:45 +02:00
// Copyright (c) 2011-2018 The Bitcoin Core developers
2014-12-05 09:40:58 +01:00
// Distributed under the MIT software license, see the accompanying
2013-02-12 00:52:30 +01:00
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
2017-11-06 19:12:47 +01:00
# if defined(HAVE_CONFIG_H)
# include <config/bitcoin-config.h>
# endif
2017-11-10 01:57:53 +01:00
# include <qt/paymentserver.h>
# include <qt/bitcoinunits.h>
# include <qt/guiutil.h>
# include <qt/optionsmodel.h>
# include <chainparams.h>
2018-04-07 09:42:02 +02:00
# include <interfaces/node.h>
2017-11-10 01:57:53 +01:00
# include <policy/policy.h>
2017-09-20 03:12:25 +02:00
# include <key_io.h>
2017-11-10 01:57:53 +01:00
# include <ui_interface.h>
# include <util.h>
# include <wallet/wallet.h>
2013-04-13 07:13:08 +02:00
# include <cstdlib>
2018-04-02 20:31:40 +02:00
# include <memory>
2013-04-13 07:13:08 +02:00
# include <openssl/x509_vfy.h>
2014-11-05 11:47:57 +01:00
2013-07-23 08:52:24 +02:00
# include <QApplication>
2013-02-12 00:52:30 +01:00
# include <QByteArray>
# include <QDataStream>
2013-07-22 08:50:39 +02:00
# include <QDateTime>
2013-02-12 00:52:30 +01:00
# include <QDebug>
2013-07-22 08:50:39 +02:00
# include <QFile>
2013-02-12 00:52:30 +01:00
# include <QFileOpenEvent>
# include <QHash>
2013-07-22 08:50:39 +02:00
# include <QList>
2013-02-12 00:52:30 +01:00
# include <QLocalServer>
# include <QLocalSocket>
2013-07-22 08:50:39 +02:00
# include <QNetworkAccessManager>
# include <QNetworkProxy>
# include <QNetworkReply>
# include <QNetworkRequest>
# include <QSslCertificate>
# include <QSslError>
# include <QSslSocket>
2013-04-13 07:13:08 +02:00
# include <QStringList>
# include <QTextDocument>
2013-07-22 08:50:39 +02:00
# include <QUrlQuery>
2013-02-12 00:52:30 +01:00
const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000 ; // milliseconds
const QString BITCOIN_IPC_PREFIX ( " bitcoin: " ) ;
2017-11-06 19:12:47 +01:00
# ifdef ENABLE_BIP70
2014-11-05 11:47:57 +01:00
// 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 " ;
2017-11-06 19:12:47 +01:00
# endif
2013-07-22 08:50:39 +02:00
2013-02-12 00:52:30 +01:00
//
// Create a name that is unique for:
// testnet / non-testnet
// data directory
//
static QString ipcServerName ( )
{
QString name ( " BitcoinQt " ) ;
// Append a simple hash of the datadir
// Note that GetDataDir(true) returns a different path
// for -testnet versus main net
2016-11-07 12:11:59 +01:00
QString ddir ( GUIUtil : : boostPathToQString ( GetDataDir ( true ) ) ) ;
2013-02-12 00:52:30 +01:00
name . append ( QString : : number ( qHash ( ddir ) ) ) ;
return name ;
}
//
2013-08-31 01:11:12 +02:00
// We store payment URIs and requests received before
2013-02-12 00:52:30 +01:00
// the main GUI window is up and ready to ask the user
// to send payment.
2013-07-22 08:50:39 +02:00
static QList < QString > savedPaymentRequests ;
2013-02-12 00:52:30 +01:00
//
// Sending to the server is done synchronously, at startup.
// If the server isn't already running, startup continues,
// and the items in savedPaymentRequest will be handled
// when uiReady() is called.
//
2013-11-16 01:54:29 +01:00
// Warning: ipcSendCommandLine() is called early in init,
2015-07-14 13:59:05 +02:00
// so don't use "Q_EMIT message()", but "QMessageBox::"!
2013-11-16 01:54:29 +01:00
//
2018-04-07 09:42:02 +02:00
void PaymentServer : : ipcParseCommandLine ( interfaces : : Node & node , int argc , char * argv [ ] )
2013-02-12 00:52:30 +01:00
{
2013-07-22 08:50:39 +02:00
for ( int i = 1 ; i < argc ; i + + )
2013-02-12 00:52:30 +01:00
{
2013-07-22 08:50:39 +02:00
QString arg ( argv [ i ] ) ;
if ( arg . startsWith ( " - " ) )
2013-02-12 00:52:30 +01:00
continue ;
2013-07-22 08:50:39 +02:00
2014-11-05 11:42:51 +01:00
// 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.
2013-10-18 11:44:05 +02:00
if ( arg . startsWith ( BITCOIN_IPC_PREFIX , Qt : : CaseInsensitive ) ) // bitcoin: URI
2013-07-22 08:50:39 +02:00
{
savedPaymentRequests . append ( arg ) ;
SendCoinsRecipient r ;
2014-07-02 09:42:15 +02:00
if ( GUIUtil : : parseBitcoinURI ( arg , & r ) & & ! r . address . isEmpty ( ) )
2013-07-22 08:50:39 +02:00
{
2015-11-28 15:04:35 +01:00
auto tempChainParams = CreateChainParams ( CBaseChainParams : : MAIN ) ;
2013-07-22 08:50:39 +02:00
2017-08-23 03:02:33 +02:00
if ( IsValidDestinationString ( r . address . toStdString ( ) , * tempChainParams ) ) {
2018-03-23 22:14:39 +01:00
node . selectParams ( CBaseChainParams : : MAIN ) ;
2017-08-23 03:02:33 +02:00
} else {
2015-11-28 15:04:35 +01:00
tempChainParams = CreateChainParams ( CBaseChainParams : : TESTNET ) ;
2017-08-23 03:02:33 +02:00
if ( IsValidDestinationString ( r . address . toStdString ( ) , * tempChainParams ) ) {
2018-03-23 22:14:39 +01:00
node . selectParams ( CBaseChainParams : : TESTNET ) ;
2017-08-23 03:02:33 +02:00
}
2013-07-22 08:50:39 +02:00
}
}
}
2017-11-06 19:12:47 +01:00
# ifdef ENABLE_BIP70
2013-07-22 08:50:39 +02:00
else if ( QFile : : exists ( arg ) ) // Filename
{
savedPaymentRequests . append ( arg ) ;
PaymentRequestPlus request ;
2014-11-19 12:53:57 +01:00
if ( readPaymentRequestFromFile ( arg , request ) )
2013-07-22 08:50:39 +02:00
{
if ( request . getDetails ( ) . network ( ) = = " main " )
2014-06-02 23:05:35 +02:00
{
2018-03-23 22:14:39 +01:00
node . selectParams ( CBaseChainParams : : MAIN ) ;
2014-06-02 23:05:35 +02:00
}
else if ( request . getDetails ( ) . network ( ) = = " test " )
{
2018-03-23 22:14:39 +01:00
node . selectParams ( CBaseChainParams : : TESTNET ) ;
2014-06-02 23:05:35 +02:00
}
2013-07-22 08:50:39 +02:00
}
}
else
{
// Printing to debug.log is about the best we can do here, the
// GUI hasn't started yet so we can't pop up a message box.
2015-01-08 11:44:25 +01:00
qWarning ( ) < < " PaymentServer::ipcSendCommandLine: Payment request file does not exist: " < < arg ;
2013-07-22 08:50:39 +02:00
}
2017-11-06 19:12:47 +01:00
# endif
2013-02-12 00:52:30 +01:00
}
2014-01-05 12:37:51 +01:00
}
2013-02-12 00:52:30 +01:00
2014-01-05 12:37:51 +01:00
//
// Sending to the server is done synchronously, at startup.
// If the server isn't already running, startup continues,
// and the items in savedPaymentRequest will be handled
// when uiReady() is called.
//
bool PaymentServer : : ipcSendCommandLine ( )
{
bool fResult = false ;
2017-06-02 03:25:02 +02:00
for ( const QString & r : savedPaymentRequests )
2013-02-12 00:52:30 +01:00
{
QLocalSocket * socket = new QLocalSocket ( ) ;
socket - > connectToServer ( ipcServerName ( ) , QIODevice : : WriteOnly ) ;
if ( ! socket - > waitForConnected ( BITCOIN_IPC_CONNECT_TIMEOUT ) )
2013-11-14 19:21:16 +01:00
{
delete socket ;
2017-08-07 07:36:37 +02:00
socket = nullptr ;
2013-02-12 00:52:30 +01:00
return false ;
2013-11-14 19:21:16 +01:00
}
2013-02-12 00:52:30 +01:00
QByteArray block ;
QDataStream out ( & block , QIODevice : : WriteOnly ) ;
out . setVersion ( QDataStream : : Qt_4_0 ) ;
2013-07-22 08:50:39 +02:00
out < < r ;
2013-02-12 00:52:30 +01:00
out . device ( ) - > seek ( 0 ) ;
2014-11-05 12:05:29 +01:00
2013-02-12 00:52:30 +01:00
socket - > write ( block ) ;
socket - > flush ( ) ;
socket - > waitForBytesWritten ( BITCOIN_IPC_CONNECT_TIMEOUT ) ;
socket - > disconnectFromServer ( ) ;
2014-11-05 12:05:29 +01:00
2013-02-12 00:52:30 +01:00
delete socket ;
2017-08-07 07:36:37 +02:00
socket = nullptr ;
2013-02-12 00:52:30 +01:00
fResult = true ;
}
2013-10-18 11:44:05 +02:00
2013-02-12 00:52:30 +01:00
return fResult ;
}
2013-10-24 16:16:39 +02:00
PaymentServer : : PaymentServer ( QObject * parent , bool startLocalServer ) :
QObject ( parent ) ,
saveURIs ( true ) ,
uriServer ( 0 ) ,
2017-11-09 16:41:15 +01:00
optionsModel ( 0 )
2017-11-06 19:12:47 +01:00
# ifdef ENABLE_BIP70
2017-11-09 16:41:15 +01:00
, netManager ( 0 )
2017-11-06 19:12:47 +01:00
# endif
2013-02-12 00:52:30 +01:00
{
2017-11-06 19:12:47 +01:00
# ifdef ENABLE_BIP70
2013-07-22 08:50:39 +02:00
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION ;
2017-11-06 19:12:47 +01:00
# endif
2013-07-22 08:50:39 +02:00
2013-10-18 11:44:05 +02:00
// Install global event filter to catch QFileOpenEvents
// on Mac: sent when you click bitcoin: links
2015-01-12 14:26:52 +01:00
// other OSes: helpful when dealing with payment request files
2013-07-22 08:50:39 +02:00
if ( parent )
parent - > installEventFilter ( this ) ;
2013-02-12 00:52:30 +01:00
QString name = ipcServerName ( ) ;
// Clean up old socket leftover from a crash:
QLocalServer : : removeServer ( name ) ;
2013-07-22 08:50:39 +02:00
if ( startLocalServer )
{
uriServer = new QLocalServer ( this ) ;
2013-02-12 00:52:30 +01:00
2013-11-14 19:21:16 +01:00
if ( ! uriServer - > listen ( name ) ) {
2015-07-14 13:59:05 +02:00
// constructor is called early in init, so don't use "Q_EMIT message()" here
2013-11-14 19:21:16 +01:00
QMessageBox : : critical ( 0 , tr ( " Payment request error " ) ,
tr ( " Cannot start bitcoin: click-to-pay handler " ) ) ;
}
2013-10-22 21:27:24 +02:00
else {
2018-06-24 17:18:22 +02:00
connect ( uriServer , & QLocalServer : : newConnection , this , & PaymentServer : : handleURIConnection ) ;
2017-11-06 19:12:47 +01:00
# ifdef ENABLE_BIP70
2018-06-24 17:18:22 +02:00
connect ( this , & PaymentServer : : receivedPaymentACK , this , & PaymentServer : : handlePaymentACK ) ;
2017-11-06 19:12:47 +01:00
# endif
2013-10-22 21:27:24 +02:00
}
2013-07-22 08:50:39 +02:00
}
2013-02-12 00:52:30 +01:00
}
2013-07-22 08:50:39 +02:00
PaymentServer : : ~ PaymentServer ( )
{
2017-11-06 19:12:47 +01:00
# ifdef ENABLE_BIP70
2013-07-22 08:50:39 +02:00
google : : protobuf : : ShutdownProtobufLibrary ( ) ;
2017-11-06 19:12:47 +01:00
# endif
2013-07-22 08:50:39 +02:00
}
//
2015-01-12 14:26:52 +01:00
// OSX-specific way of handling bitcoin: URIs and PaymentRequest mime types.
// Also used by paymentservertests.cpp and when opening a payment request file
// via "Open URI..." menu entry.
2013-07-22 08:50:39 +02:00
//
2013-11-14 19:21:16 +01:00
bool PaymentServer : : eventFilter ( QObject * object , QEvent * event )
2013-02-12 00:52:30 +01:00
{
2015-01-12 14:26:52 +01:00
if ( event - > type ( ) = = QEvent : : FileOpen ) {
2013-11-14 19:21:16 +01:00
QFileOpenEvent * fileEvent = static_cast < QFileOpenEvent * > ( event ) ;
2013-07-22 08:50:39 +02:00
if ( ! fileEvent - > file ( ) . isEmpty ( ) )
handleURIOrFile ( fileEvent - > file ( ) ) ;
else if ( ! fileEvent - > url ( ) . isEmpty ( ) )
handleURIOrFile ( fileEvent - > url ( ) . toString ( ) ) ;
return true ;
2013-02-12 00:52:30 +01:00
}
2013-11-14 19:21:16 +01:00
return QObject : : eventFilter ( object , event ) ;
2013-02-12 00:52:30 +01:00
}
void PaymentServer : : uiReady ( )
{
2017-11-06 19:12:47 +01:00
# ifdef ENABLE_BIP70
2013-10-30 11:30:53 +01:00
initNetManager ( ) ;
2017-11-06 19:12:47 +01:00
# endif
2013-07-22 08:50:39 +02:00
2013-02-12 00:52:30 +01:00
saveURIs = false ;
2017-06-02 03:25:02 +02:00
for ( const QString & s : savedPaymentRequests )
2013-07-22 08:50:39 +02:00
{
handleURIOrFile ( s ) ;
}
2013-02-12 00:52:30 +01:00
savedPaymentRequests . clear ( ) ;
}
2013-07-22 08:50:39 +02:00
void PaymentServer : : handleURIOrFile ( const QString & s )
{
if ( saveURIs )
{
savedPaymentRequests . append ( s ) ;
return ;
}
2018-03-19 12:07:17 +01:00
if ( s . startsWith ( " bitcoin:// " , Qt : : CaseInsensitive ) )
{
Q_EMIT message ( tr ( " URI handling " ) , tr ( " 'bitcoin://' is not a valid URI. Use 'bitcoin:' instead. " ) ,
CClientUIInterface : : MSG_ERROR ) ;
}
else if ( s . startsWith ( BITCOIN_IPC_PREFIX , Qt : : CaseInsensitive ) ) // bitcoin: URI
2013-07-22 08:50:39 +02:00
{
2013-04-13 07:13:08 +02:00
QUrlQuery uri ( ( QUrl ( s ) ) ) ;
2013-12-09 10:48:14 +01:00
if ( uri . hasQueryItem ( " r " ) ) // payment request URI
2013-07-22 08:50:39 +02:00
{
2017-11-06 19:12:47 +01:00
# ifdef ENABLE_BIP70
2018-10-09 23:16:32 +02:00
Q_EMIT message ( tr ( " URI handling " ) ,
tr ( " You are using a BIP70 URL which will be unsupported in the future. " ) ,
CClientUIInterface : : ICON_WARNING ) ;
2013-10-18 11:44:05 +02:00
QByteArray temp ;
2013-12-04 04:18:09 +01:00
temp . append ( uri . queryItemValue ( " r " ) ) ;
2013-07-22 08:50:39 +02:00
QString decoded = QUrl : : fromPercentEncoding ( temp ) ;
QUrl fetchUrl ( decoded , QUrl : : StrictMode ) ;
if ( fetchUrl . isValid ( ) )
2013-12-09 10:48:14 +01:00
{
2015-01-08 11:44:25 +01:00
qDebug ( ) < < " PaymentServer::handleURIOrFile: fetchRequest( " < < fetchUrl < < " ) " ;
2013-07-22 08:50:39 +02:00
fetchRequest ( fetchUrl ) ;
2013-12-09 10:48:14 +01:00
}
2013-07-22 08:50:39 +02:00
else
2013-12-09 10:48:14 +01:00
{
2015-01-08 11:44:25 +01:00
qWarning ( ) < < " PaymentServer::handleURIOrFile: Invalid URL: " < < fetchUrl ;
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " URI handling " ) ,
2013-12-09 10:48:14 +01:00
tr ( " Payment request fetch URL is invalid: %1 " ) . arg ( fetchUrl . toString ( ) ) ,
CClientUIInterface : : ICON_WARNING ) ;
}
2017-11-06 19:12:47 +01:00
# else
Q_EMIT message ( tr ( " URI handling " ) ,
tr ( " Cannot process payment request because BIP70 support was not compiled in. " ) ,
CClientUIInterface : : ICON_WARNING ) ;
# endif
2013-07-22 08:50:39 +02:00
return ;
}
2013-12-09 10:48:14 +01:00
else // normal URI
{
SendCoinsRecipient recipient ;
if ( GUIUtil : : parseBitcoinURI ( s , & recipient ) )
2013-11-16 01:54:29 +01:00
{
2017-08-23 03:02:33 +02:00
if ( ! IsValidDestinationString ( recipient . address . toStdString ( ) ) ) {
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " URI handling " ) , tr ( " Invalid payment address %1 " ) . arg ( recipient . address ) ,
2013-11-16 01:54:29 +01:00
CClientUIInterface : : MSG_ERROR ) ;
}
else
2015-07-14 13:59:05 +02:00
Q_EMIT receivedPaymentRequest ( recipient ) ;
2013-11-16 01:54:29 +01:00
}
2013-12-09 10:48:14 +01:00
else
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " URI handling " ) ,
2014-07-31 16:56:14 +02:00
tr ( " URI cannot be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters. " ) ,
2013-12-09 10:48:14 +01:00
CClientUIInterface : : ICON_WARNING ) ;
2013-07-22 08:50:39 +02:00
2013-12-09 10:48:14 +01:00
return ;
}
2013-07-22 08:50:39 +02:00
}
2017-11-06 19:12:47 +01:00
# ifdef ENABLE_BIP70
2013-12-09 10:48:14 +01:00
if ( QFile : : exists ( s ) ) // payment request file
2013-07-22 08:50:39 +02:00
{
PaymentRequestPlus request ;
2013-10-28 13:29:13 +01:00
SendCoinsRecipient recipient ;
2014-11-19 12:53:57 +01:00
if ( ! readPaymentRequestFromFile ( s , request ) )
2013-11-16 01:54:29 +01:00
{
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " Payment request file handling " ) ,
2014-07-31 16:56:14 +02:00
tr ( " Payment request file cannot be read! This can be caused by an invalid payment request file. " ) ,
2013-12-09 10:48:14 +01:00
CClientUIInterface : : ICON_WARNING ) ;
2013-11-16 01:54:29 +01:00
}
else if ( processPaymentRequest ( request , recipient ) )
2015-07-14 13:59:05 +02:00
Q_EMIT receivedPaymentRequest ( recipient ) ;
2013-10-28 13:29:13 +01:00
2013-07-22 08:50:39 +02:00
return ;
}
2017-11-06 19:12:47 +01:00
# endif
2013-07-22 08:50:39 +02:00
}
2013-02-12 00:52:30 +01:00
void PaymentServer : : handleURIConnection ( )
{
QLocalSocket * clientConnection = uriServer - > nextPendingConnection ( ) ;
while ( clientConnection - > bytesAvailable ( ) < ( int ) sizeof ( quint32 ) )
clientConnection - > waitForReadyRead ( ) ;
2018-06-24 17:18:22 +02:00
connect ( clientConnection , & QLocalSocket : : disconnected , clientConnection , & QLocalSocket : : deleteLater ) ;
2013-02-12 00:52:30 +01:00
QDataStream in ( clientConnection ) ;
in . setVersion ( QDataStream : : Qt_4_0 ) ;
if ( clientConnection - > bytesAvailable ( ) < ( int ) sizeof ( quint16 ) ) {
return ;
}
2013-10-18 11:44:05 +02:00
QString msg ;
in > > msg ;
2013-02-12 00:52:30 +01:00
2013-10-18 11:44:05 +02:00
handleURIOrFile ( msg ) ;
2013-07-22 08:50:39 +02:00
}
2017-11-09 16:41:15 +01:00
void PaymentServer : : setOptionsModel ( OptionsModel * _optionsModel )
{
this - > optionsModel = _optionsModel ;
}
2017-11-06 19:12:47 +01:00
# ifdef ENABLE_BIP70
2017-11-09 16:41:15 +01:00
struct X509StoreDeleter {
void operator ( ) ( X509_STORE * b ) {
X509_STORE_free ( b ) ;
}
} ;
struct X509Deleter {
void operator ( ) ( X509 * b ) { X509_free ( b ) ; }
} ;
namespace // Anon namespace
{
std : : unique_ptr < X509_STORE , X509StoreDeleter > certStore ;
}
static void ReportInvalidCertificate ( const QSslCertificate & cert )
{
qDebug ( ) < < QString ( " %1: Payment server found an invalid certificate: " ) . arg ( __func__ ) < < cert . serialNumber ( ) < < cert . subjectInfo ( QSslCertificate : : CommonName ) < < cert . subjectInfo ( QSslCertificate : : DistinguishedNameQualifier ) < < cert . subjectInfo ( QSslCertificate : : OrganizationalUnitName ) ;
}
//
// Load OpenSSL's list of root certificate authorities
//
void PaymentServer : : LoadRootCAs ( X509_STORE * _store )
{
// Unit tests mostly use this, to pass in fake root CAs:
if ( _store )
{
certStore . reset ( _store ) ;
return ;
}
// Normal execution, use either -rootcertificates or system certs:
certStore . reset ( X509_STORE_new ( ) ) ;
// Note: use "-system-" default here so that users can pass -rootcertificates=""
// and get 'I don't like X.509 certificates, don't trust anybody' behavior:
QString certFile = QString : : fromStdString ( gArgs . GetArg ( " -rootcertificates " , " -system- " ) ) ;
// Empty store
if ( certFile . isEmpty ( ) ) {
qDebug ( ) < < QString ( " PaymentServer::%1: Payment request authentication via X.509 certificates disabled. " ) . arg ( __func__ ) ;
return ;
}
QList < QSslCertificate > certList ;
if ( certFile ! = " -system- " ) {
qDebug ( ) < < QString ( " PaymentServer::%1: Using \" %2 \" as trusted root certificate. " ) . arg ( __func__ ) . arg ( certFile ) ;
certList = QSslCertificate : : fromPath ( certFile ) ;
// Use those certificates when fetching payment requests, too:
QSslSocket : : setDefaultCaCertificates ( certList ) ;
} else
certList = QSslSocket : : systemCaCertificates ( ) ;
int nRootCerts = 0 ;
const QDateTime currentTime = QDateTime : : currentDateTime ( ) ;
for ( const QSslCertificate & cert : certList ) {
// Don't log NULL certificates
if ( cert . isNull ( ) )
continue ;
// Not yet active/valid, or expired certificate
if ( currentTime < cert . effectiveDate ( ) | | currentTime > cert . expiryDate ( ) ) {
ReportInvalidCertificate ( cert ) ;
continue ;
}
// Blacklisted certificate
if ( cert . isBlacklisted ( ) ) {
ReportInvalidCertificate ( cert ) ;
continue ;
}
QByteArray certData = cert . toDer ( ) ;
const unsigned char * data = ( const unsigned char * ) certData . data ( ) ;
std : : unique_ptr < X509 , X509Deleter > x509 ( d2i_X509 ( 0 , & data , certData . size ( ) ) ) ;
if ( x509 & & X509_STORE_add_cert ( certStore . get ( ) , x509 . get ( ) ) )
{
// Note: X509_STORE increases the reference count to the X509 object,
// we still have to release our reference to it.
+ + nRootCerts ;
}
else
{
ReportInvalidCertificate ( cert ) ;
continue ;
}
}
qWarning ( ) < < " PaymentServer::LoadRootCAs: Loaded " < < nRootCerts < < " root certificates " ;
// Project for another day:
// Fetch certificate revocation lists, and add them to certStore.
// Issues to consider:
// performance (start a thread to fetch in background?)
// privacy (fetch through tor/proxy so IP address isn't revealed)
// would it be easier to just use a compiled-in blacklist?
// or use Qt's blacklist?
// "certificate stapling" with server-side caching is more efficient
}
void PaymentServer : : initNetManager ( )
{
if ( ! optionsModel )
return ;
delete netManager ;
// netManager is used to fetch paymentrequests given in bitcoin: URIs
netManager = new QNetworkAccessManager ( this ) ;
QNetworkProxy proxy ;
// Query active SOCKS5 proxy
if ( optionsModel - > getProxySettings ( proxy ) ) {
netManager - > setProxy ( proxy ) ;
qDebug ( ) < < " PaymentServer::initNetManager: Using SOCKS5 proxy " < < proxy . hostName ( ) < < " : " < < proxy . port ( ) ;
}
else
qDebug ( ) < < " PaymentServer::initNetManager: No active proxy server found. " ;
connect ( netManager , & QNetworkAccessManager : : finished , this , & PaymentServer : : netRequestFinished ) ;
connect ( netManager , & QNetworkAccessManager : : sslErrors , this , & PaymentServer : : reportSslErrors ) ;
}
2014-11-19 12:53:57 +01:00
//
// Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine()
2015-07-14 13:59:05 +02:00
// so don't use "Q_EMIT message()", but "QMessageBox::"!
2014-11-19 12:53:57 +01:00
//
bool PaymentServer : : readPaymentRequestFromFile ( const QString & filename , PaymentRequestPlus & request )
2013-07-22 08:50:39 +02:00
{
QFile f ( filename ) ;
2014-11-19 12:53:57 +01:00
if ( ! f . open ( QIODevice : : ReadOnly ) ) {
qWarning ( ) < < QString ( " PaymentServer::%1: Failed to open %2 " ) . arg ( __func__ ) . arg ( filename ) ;
2013-07-22 08:50:39 +02:00
return false ;
}
2014-11-19 12:53:57 +01:00
// BIP70 DoS protection
2015-01-15 09:13:32 +01:00
if ( ! verifySize ( f . size ( ) ) ) {
2013-07-22 08:50:39 +02:00
return false ;
}
QByteArray data = f . readAll ( ) ;
return request . parse ( data ) ;
}
2015-01-12 14:24:01 +01:00
bool PaymentServer : : processPaymentRequest ( const PaymentRequestPlus & request , SendCoinsRecipient & recipient )
2013-07-22 08:50:39 +02:00
{
2013-08-24 15:07:17 +02:00
if ( ! optionsModel )
return false ;
2013-11-16 01:54:29 +01:00
if ( request . IsInitialized ( ) ) {
// Payment request network matches client network?
2018-03-23 22:14:39 +01:00
if ( ! verifyNetwork ( optionsModel - > node ( ) , request . getDetails ( ) ) ) {
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " Payment request rejected " ) , tr ( " Payment request network doesn't match client network. " ) ,
2013-11-16 01:54:29 +01:00
CClientUIInterface : : MSG_ERROR ) ;
return false ;
}
2015-01-08 14:42:04 +01:00
// Make sure any payment requests involved are still valid.
// This is re-checked just before sending coins in WalletModel::sendCoins().
if ( verifyExpired ( request . getDetails ( ) ) ) {
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " Payment request rejected " ) , tr ( " Payment request expired. " ) ,
2013-11-16 01:54:29 +01:00
CClientUIInterface : : MSG_ERROR ) ;
return false ;
}
2015-01-08 14:42:04 +01:00
} else {
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " Payment request error " ) , tr ( " Payment request is not initialized. " ) ,
2013-11-16 01:54:29 +01:00
CClientUIInterface : : MSG_ERROR ) ;
return false ;
}
2013-10-28 13:29:13 +01:00
recipient . paymentRequest = request ;
recipient . message = GUIUtil : : HtmlEscape ( request . getDetails ( ) . memo ( ) ) ;
2013-10-24 16:02:39 +02:00
2016-11-19 16:12:23 +01:00
request . getMerchant ( certStore . get ( ) , recipient . authenticatedMerchant ) ;
2013-10-24 16:02:39 +02:00
2014-04-23 00:46:19 +02:00
QList < std : : pair < CScript , CAmount > > sendingTos = request . getPayTo ( ) ;
2013-10-30 11:26:44 +01:00
QStringList addresses ;
2013-10-24 16:02:39 +02:00
2017-06-02 03:28:42 +02:00
for ( const std : : pair < CScript , CAmount > & sendingTo : sendingTos ) {
2013-10-24 16:02:39 +02:00
// Extract and check destination addresses
CTxDestination dest ;
if ( ExtractDestination ( sendingTo . first , dest ) ) {
2013-10-30 11:26:44 +01:00
// Append destination address
2017-08-23 03:02:33 +02:00
addresses . append ( QString : : fromStdString ( EncodeDestination ( dest ) ) ) ;
2013-10-24 16:02:39 +02:00
}
2013-11-16 01:54:29 +01:00
else if ( ! recipient . authenticatedMerchant . isEmpty ( ) ) {
2015-01-12 14:26:52 +01:00
// Unauthenticated payment requests to custom bitcoin addresses are not supported
// (there is no good way to tell the user where they are paying in a way they'd
// have a chance of understanding).
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " Payment request rejected " ) ,
2013-10-24 16:02:39 +02:00
tr ( " Unverified payment requests to custom payment scripts are unsupported. " ) ,
CClientUIInterface : : MSG_ERROR ) ;
return false ;
}
2015-01-09 14:25:43 +01:00
// Bitcoin amounts are stored as (optional) uint64 in the protobuf messages (see paymentrequest.proto),
// but CAmount is defined as int64_t. Because of that we need to verify that amounts are in a valid range
// and no overflow has happened.
if ( ! verifyAmount ( sendingTo . second ) ) {
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " Payment request rejected " ) , tr ( " Invalid payment request. " ) , CClientUIInterface : : MSG_ERROR ) ;
2015-01-09 14:25:43 +01:00
return false ;
}
2013-10-24 16:02:39 +02:00
// Extract and check amounts
2013-07-22 08:50:39 +02:00
CTxOut txOut ( sendingTo . second , sendingTo . first ) ;
2018-03-23 22:14:39 +01:00
if ( IsDust ( txOut , optionsModel - > node ( ) . getDustRelayFee ( ) ) ) {
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " Payment request error " ) , tr ( " Requested payment amount of %1 is too small (considered dust). " )
2013-11-16 01:54:29 +01:00
. arg ( BitcoinUnits : : formatWithUnit ( optionsModel - > getDisplayUnit ( ) , sendingTo . second ) ) ,
CClientUIInterface : : MSG_ERROR ) ;
2013-09-04 11:52:45 +02:00
2013-07-22 08:50:39 +02:00
return false ;
}
2013-10-28 13:29:13 +01:00
recipient . amount + = sendingTo . second ;
2015-01-09 14:25:43 +01:00
// Also verify that the final amount is still in a valid range after adding additional amounts.
if ( ! verifyAmount ( recipient . amount ) ) {
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " Payment request rejected " ) , tr ( " Invalid payment request. " ) , CClientUIInterface : : MSG_ERROR ) ;
2015-01-09 14:25:43 +01:00
return false ;
}
2013-07-22 08:50:39 +02:00
}
2013-10-24 16:02:39 +02:00
// Store addresses and format them to fit nicely into the GUI
recipient . address = addresses . join ( " <br /> " ) ;
2013-07-22 08:50:39 +02:00
2013-10-24 16:02:39 +02:00
if ( ! recipient . authenticatedMerchant . isEmpty ( ) ) {
2015-01-08 11:44:25 +01:00
qDebug ( ) < < " PaymentServer::processPaymentRequest: Secure payment request from " < < recipient . authenticatedMerchant ;
2013-07-22 08:50:39 +02:00
}
else {
2015-01-08 11:44:25 +01:00
qDebug ( ) < < " PaymentServer::processPaymentRequest: Insecure payment request to " < < addresses . join ( " , " ) ;
2013-07-22 08:50:39 +02:00
}
return true ;
}
2013-08-31 01:11:12 +02:00
void PaymentServer : : fetchRequest ( const QUrl & url )
2013-07-22 08:50:39 +02:00
{
QNetworkRequest netRequest ;
2014-11-05 11:47:57 +01:00
netRequest . setAttribute ( QNetworkRequest : : User , BIP70_MESSAGE_PAYMENTREQUEST ) ;
2013-07-22 08:50:39 +02:00
netRequest . setUrl ( url ) ;
netRequest . setRawHeader ( " User-Agent " , CLIENT_NAME . c_str ( ) ) ;
2014-11-05 11:47:57 +01:00
netRequest . setRawHeader ( " Accept " , BIP71_MIMETYPE_PAYMENTREQUEST ) ;
2013-07-22 08:50:39 +02:00
netManager - > get ( netRequest ) ;
}
2018-03-23 22:14:39 +01:00
void PaymentServer : : fetchPaymentACK ( WalletModel * walletModel , const SendCoinsRecipient & recipient , QByteArray transaction )
2013-07-22 08:50:39 +02:00
{
const payments : : PaymentDetails & details = recipient . paymentRequest . getDetails ( ) ;
if ( ! details . has_payment_url ( ) )
return ;
QNetworkRequest netRequest ;
2014-11-05 11:47:57 +01:00
netRequest . setAttribute ( QNetworkRequest : : User , BIP70_MESSAGE_PAYMENTACK ) ;
2013-07-22 08:50:39 +02:00
netRequest . setUrl ( QString : : fromStdString ( details . payment_url ( ) ) ) ;
2014-11-05 11:47:57 +01:00
netRequest . setHeader ( QNetworkRequest : : ContentTypeHeader , BIP71_MIMETYPE_PAYMENT ) ;
2013-07-22 08:50:39 +02:00
netRequest . setRawHeader ( " User-Agent " , CLIENT_NAME . c_str ( ) ) ;
2014-11-05 11:47:57 +01:00
netRequest . setRawHeader ( " Accept " , BIP71_MIMETYPE_PAYMENTACK ) ;
2013-07-22 08:50:39 +02:00
payments : : Payment payment ;
payment . set_merchant_data ( details . merchant_data ( ) ) ;
payment . add_transactions ( transaction . data ( ) , transaction . size ( ) ) ;
// Create a new refund address, or re-use:
2017-12-01 01:49:11 +01:00
CPubKey newKey ;
2018-03-23 22:14:39 +01:00
if ( walletModel - > wallet ( ) . getKeyFromPool ( false /* internal */ , newKey ) ) {
2017-12-01 01:49:11 +01:00
// BIP70 requests encode the scriptPubKey directly, so we are not restricted to address
// types supported by the receiver. As a result, we choose the address format we also
// use for change. Despite an actual payment and not change, this is a close match:
// it's the output type we use subject to privacy issues, but not restricted by what
// other software supports.
2018-03-19 20:57:11 +01:00
const OutputType change_type = walletModel - > wallet ( ) . getDefaultChangeType ( ) ! = OutputType : : CHANGE_AUTO ? walletModel - > wallet ( ) . getDefaultChangeType ( ) : walletModel - > wallet ( ) . getDefaultAddressType ( ) ;
2018-03-23 22:14:39 +01:00
walletModel - > wallet ( ) . learnRelatedScripts ( newKey , change_type ) ;
2018-01-23 17:56:15 +01:00
CTxDestination dest = GetDestinationForKey ( newKey , change_type ) ;
2017-10-20 19:27:55 +02:00
std : : string label = tr ( " Refund from %1 " ) . arg ( recipient . authenticatedMerchant ) . toStdString ( ) ;
2018-03-23 22:14:39 +01:00
walletModel - > wallet ( ) . setAddressBook ( dest , label , " refund " ) ;
2017-12-01 01:49:11 +01:00
CScript s = GetScriptForDestination ( dest ) ;
2013-07-22 08:50:39 +02:00
payments : : Output * refund_to = payment . add_refund_to ( ) ;
refund_to - > set_script ( & s [ 0 ] , s . size ( ) ) ;
2017-12-01 01:49:11 +01:00
} else {
// This should never happen, because sending coins should have
// just unlocked the wallet and refilled the keypool.
qWarning ( ) < < " PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set " ;
2013-07-22 08:50:39 +02:00
}
int length = payment . ByteSize ( ) ;
netRequest . setHeader ( QNetworkRequest : : ContentLengthHeader , length ) ;
QByteArray serData ( length , ' \0 ' ) ;
if ( payment . SerializeToArray ( serData . data ( ) , length ) ) {
netManager - > post ( netRequest , serData ) ;
}
else {
2013-11-16 01:54:29 +01:00
// This should never happen, either.
2015-01-08 11:44:25 +01:00
qWarning ( ) < < " PaymentServer::fetchPaymentACK: Error serializing payment message " ;
2013-07-22 08:50:39 +02:00
}
}
2013-08-31 01:11:12 +02:00
void PaymentServer : : netRequestFinished ( QNetworkReply * reply )
2013-07-22 08:50:39 +02:00
{
reply - > deleteLater ( ) ;
2014-11-19 12:53:57 +01:00
// BIP70 DoS protection
2015-01-15 09:13:32 +01:00
if ( ! verifySize ( reply - > size ( ) ) ) {
Q_EMIT message ( tr ( " Payment request rejected " ) ,
tr ( " Payment request %1 is too large (%2 bytes, allowed %3 bytes). " )
. arg ( reply - > request ( ) . url ( ) . toString ( ) )
. arg ( reply - > size ( ) )
. arg ( BIP70_MAX_PAYMENTREQUEST_SIZE ) ,
CClientUIInterface : : MSG_ERROR ) ;
2014-11-19 12:53:57 +01:00
return ;
}
if ( reply - > error ( ) ! = QNetworkReply : : NoError ) {
2013-10-22 21:27:24 +02:00
QString msg = tr ( " Error communicating with %1: %2 " )
2013-07-22 08:50:39 +02:00
. arg ( reply - > request ( ) . url ( ) . toString ( ) )
. arg ( reply - > errorString ( ) ) ;
2013-10-22 21:27:24 +02:00
2014-11-19 12:53:57 +01:00
qWarning ( ) < < " PaymentServer::netRequestFinished: " < < msg ;
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " Payment request error " ) , msg , CClientUIInterface : : MSG_ERROR ) ;
2013-07-22 08:50:39 +02:00
return ;
}
QByteArray data = reply - > readAll ( ) ;
QString requestType = reply - > request ( ) . attribute ( QNetworkRequest : : User ) . toString ( ) ;
2014-11-05 11:47:57 +01:00
if ( requestType = = BIP70_MESSAGE_PAYMENTREQUEST )
2013-07-22 08:50:39 +02:00
{
PaymentRequestPlus request ;
2013-10-28 13:29:13 +01:00
SendCoinsRecipient recipient ;
2013-11-16 01:54:29 +01:00
if ( ! request . parse ( data ) )
2013-12-09 10:48:14 +01:00
{
2015-01-08 11:44:25 +01:00
qWarning ( ) < < " PaymentServer::netRequestFinished: Error parsing payment request " ;
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " Payment request error " ) ,
2014-07-31 16:56:14 +02:00
tr ( " Payment request cannot be parsed! " ) ,
2013-12-09 10:48:14 +01:00
CClientUIInterface : : MSG_ERROR ) ;
}
2013-11-16 01:54:29 +01:00
else if ( processPaymentRequest ( request , recipient ) )
2015-07-14 13:59:05 +02:00
Q_EMIT receivedPaymentRequest ( recipient ) ;
2013-10-22 21:27:24 +02:00
2013-07-22 08:50:39 +02:00
return ;
}
2014-11-05 11:47:57 +01:00
else if ( requestType = = BIP70_MESSAGE_PAYMENTACK )
2013-07-22 08:50:39 +02:00
{
payments : : PaymentACK paymentACK ;
if ( ! paymentACK . ParseFromArray ( data . data ( ) , data . size ( ) ) )
{
2013-10-22 21:27:24 +02:00
QString msg = tr ( " Bad response from server %1 " )
2013-07-22 08:50:39 +02:00
. arg ( reply - > request ( ) . url ( ) . toString ( ) ) ;
2013-10-22 21:27:24 +02:00
2015-01-08 11:44:25 +01:00
qWarning ( ) < < " PaymentServer::netRequestFinished: " < < msg ;
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " Payment request error " ) , msg , CClientUIInterface : : MSG_ERROR ) ;
2013-07-22 08:50:39 +02:00
}
2013-12-09 10:48:14 +01:00
else
{
2015-07-14 13:59:05 +02:00
Q_EMIT receivedPaymentACK ( GUIUtil : : HtmlEscape ( paymentACK . memo ( ) ) ) ;
2013-07-22 08:50:39 +02:00
}
}
}
2013-08-31 01:11:12 +02:00
void PaymentServer : : reportSslErrors ( QNetworkReply * reply , const QList < QSslError > & errs )
2013-07-22 08:50:39 +02:00
{
2013-08-31 01:11:12 +02:00
Q_UNUSED ( reply ) ;
2013-07-22 08:50:39 +02:00
QString errString ;
2017-06-02 03:25:02 +02:00
for ( const QSslError & err : errs ) {
2015-01-08 11:44:25 +01:00
qWarning ( ) < < " PaymentServer::reportSslErrors: " < < err ;
2013-07-22 08:50:39 +02:00
errString + = err . errorString ( ) + " \n " ;
}
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " Network request error " ) , errString , CClientUIInterface : : MSG_ERROR ) ;
2013-02-12 00:52:30 +01:00
}
2013-10-22 21:27:24 +02:00
void PaymentServer : : handlePaymentACK ( const QString & paymentACKMsg )
{
2015-08-09 01:17:27 +02:00
// currently we don't further process or store the paymentACK message
2015-07-14 13:59:05 +02:00
Q_EMIT message ( tr ( " Payment acknowledged " ) , paymentACKMsg , CClientUIInterface : : ICON_INFORMATION | CClientUIInterface : : MODAL ) ;
2013-10-22 21:27:24 +02:00
}
2015-01-12 08:43:08 +01:00
2018-04-07 09:42:02 +02:00
bool PaymentServer : : verifyNetwork ( interfaces : : Node & node , const payments : : PaymentDetails & requestDetails )
2015-01-12 08:43:08 +01:00
{
2018-03-23 22:14:39 +01:00
bool fVerified = requestDetails . network ( ) = = node . getNetwork ( ) ;
2015-01-12 08:43:08 +01:00
if ( ! fVerified ) {
qWarning ( ) < < QString ( " PaymentServer::%1: Payment request network \" %2 \" doesn't match client network \" %3 \" . " )
. arg ( __func__ )
. arg ( QString : : fromStdString ( requestDetails . network ( ) ) )
2018-03-23 22:14:39 +01:00
. arg ( QString : : fromStdString ( node . getNetwork ( ) ) ) ;
2015-01-12 08:43:08 +01:00
}
return fVerified ;
}
2015-01-08 14:42:04 +01:00
bool PaymentServer : : verifyExpired ( const payments : : PaymentDetails & requestDetails )
{
bool fVerified = ( requestDetails . has_expires ( ) & & ( int64_t ) requestDetails . expires ( ) < GetTime ( ) ) ;
if ( fVerified ) {
2018-02-28 16:46:31 +01:00
const QString requestExpires = QString : : fromStdString ( FormatISO8601DateTime ( ( int64_t ) requestDetails . expires ( ) ) ) ;
2015-01-08 14:42:04 +01:00
qWarning ( ) < < QString ( " PaymentServer::%1: Payment request expired \" %2 \" . " )
. arg ( __func__ )
. arg ( requestExpires ) ;
}
return fVerified ;
}
2015-01-09 14:25:43 +01:00
2015-01-15 09:13:32 +01:00
bool PaymentServer : : verifySize ( qint64 requestSize )
{
bool fVerified = ( requestSize < = BIP70_MAX_PAYMENTREQUEST_SIZE ) ;
if ( ! fVerified ) {
qWarning ( ) < < QString ( " PaymentServer::%1: Payment request too large (%2 bytes, allowed %3 bytes). " )
. arg ( __func__ )
. arg ( requestSize )
. arg ( BIP70_MAX_PAYMENTREQUEST_SIZE ) ;
}
return fVerified ;
}
2015-01-09 14:25:43 +01:00
bool PaymentServer : : verifyAmount ( const CAmount & requestAmount )
{
bool fVerified = MoneyRange ( requestAmount ) ;
if ( ! fVerified ) {
qWarning ( ) < < QString ( " PaymentServer::%1: Payment request amount out of allowed range (%2, allowed 0 - %3). " )
. arg ( __func__ )
. arg ( requestAmount )
. arg ( MAX_MONEY ) ;
}
return fVerified ;
}
2016-11-19 16:12:23 +01:00
X509_STORE * PaymentServer : : getCertStore ( )
{
return certStore . get ( ) ;
}
2017-11-06 19:12:47 +01:00
# endif