Changeset View
Changeset View
Standalone View
Standalone View
src/qt/paymentserver.cpp
// Copyright (c) 2011-2016 The Bitcoin Core developers | // Copyright (c) 2011-2016 The Bitcoin Core developers | ||||
// Distributed under the MIT 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. | ||||
#if defined(HAVE_CONFIG_H) | |||||
#include <config/bitcoin-config.h> | |||||
#endif | |||||
#include <qt/paymentserver.h> | #include <qt/paymentserver.h> | ||||
#include <cashaddrenc.h> | #include <cashaddrenc.h> | ||||
#include <chainparams.h> | #include <chainparams.h> | ||||
#include <interfaces/node.h> | #include <interfaces/node.h> | ||||
#include <key_io.h> | #include <key_io.h> | ||||
#include <policy/policy.h> | #include <policy/policy.h> | ||||
#include <qt/bitcoinunits.h> | #include <qt/bitcoinunits.h> | ||||
Show All 25 Lines | |||||
#include <QSslSocket> | #include <QSslSocket> | ||||
#include <QStringList> | #include <QStringList> | ||||
#include <QTextDocument> | #include <QTextDocument> | ||||
#include <QUrlQuery> | #include <QUrlQuery> | ||||
#include <cstdlib> | #include <cstdlib> | ||||
const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds | const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds | ||||
#ifdef ENABLE_BIP70 | |||||
// BIP70 payment protocol messages | // BIP70 payment protocol messages | ||||
const char *BIP70_MESSAGE_PAYMENTACK = "PaymentACK"; | const char *BIP70_MESSAGE_PAYMENTACK = "PaymentACK"; | ||||
const char *BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest"; | const char *BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest"; | ||||
// BIP71 payment protocol media types | // BIP71 payment protocol media types | ||||
const char *BIP71_MIMETYPE_PAYMENT = "application/bitcoincash-payment"; | const char *BIP71_MIMETYPE_PAYMENT = "application/bitcoincash-payment"; | ||||
const char *BIP71_MIMETYPE_PAYMENTACK = "application/bitcoincash-paymentack"; | const char *BIP71_MIMETYPE_PAYMENTACK = "application/bitcoincash-paymentack"; | ||||
const char *BIP71_MIMETYPE_PAYMENTREQUEST = | const char *BIP71_MIMETYPE_PAYMENTREQUEST = | ||||
"application/bitcoincash-paymentrequest"; | "application/bitcoincash-paymentrequest"; | ||||
#endif | |||||
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; | |||||
} | |||||
// | // | ||||
// Create a name that is unique for: | // Create a name that is unique for: | ||||
// testnet / non-testnet | // testnet / non-testnet | ||||
// data directory | // data directory | ||||
// | // | ||||
static QString ipcServerName() { | static QString ipcServerName() { | ||||
QString name("BitcoinQt"); | QString name("BitcoinQt"); | ||||
// Append a simple hash of the datadir | // Append a simple hash of the datadir | ||||
// Note that GetDataDir(true) returns a different path for -testnet versus | // Note that GetDataDir(true) returns a different path for -testnet versus | ||||
// main net | // main net | ||||
QString ddir(GUIUtil::boostPathToQString(GetDataDir(true))); | QString ddir(GUIUtil::boostPathToQString(GetDataDir(true))); | ||||
name.append(QString::number(qHash(ddir))); | name.append(QString::number(qHash(ddir))); | ||||
return name; | return name; | ||||
} | } | ||||
// | // | ||||
// We store payment URIs and requests received before the main GUI window is up | // We store payment URIs and requests received before the main GUI window is up | ||||
// and ready to ask the user to send payment. | // and ready to ask the user to send payment. | ||||
// | // | ||||
static QList<QString> savedPaymentRequests; | static QList<QString> savedPaymentRequests; | ||||
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 nullptr 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 uint8_t *data = (const uint8_t *)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 | |||||
} | |||||
static std::string ipcParseURI(const QString &arg, const CChainParams ¶ms, | static std::string ipcParseURI(const QString &arg, const CChainParams ¶ms, | ||||
bool useCashAddr) { | bool useCashAddr) { | ||||
const QString scheme = QString::fromStdString(params.CashAddrPrefix()); | const QString scheme = QString::fromStdString(params.CashAddrPrefix()); | ||||
if (!arg.startsWith(scheme + ":", Qt::CaseInsensitive)) { | if (!arg.startsWith(scheme + ":", Qt::CaseInsensitive)) { | ||||
return {}; | return {}; | ||||
} | } | ||||
SendCoinsRecipient r; | SendCoinsRecipient r; | ||||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | for (int i = 1; i < argc; i++) { | ||||
} | } | ||||
if (ipcCanParseLegacyURI(arg, *net)) { | if (ipcCanParseLegacyURI(arg, *net)) { | ||||
itemNetwork = net; | itemNetwork = net; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
#ifdef ENABLE_BIP70 | |||||
if (!itemNetwork && QFile::exists(arg)) { | if (!itemNetwork && QFile::exists(arg)) { | ||||
// Filename | // Filename | ||||
PaymentRequestPlus request; | PaymentRequestPlus request; | ||||
if (readPaymentRequestFromFile(arg, request)) { | if (readPaymentRequestFromFile(arg, request)) { | ||||
for (auto net : networks) { | for (auto net : networks) { | ||||
if (*net == request.getDetails().network()) { | if (*net == request.getDetails().network()) { | ||||
itemNetwork = net; | itemNetwork = net; | ||||
} | } | ||||
Show All 15 Lines | #ifdef ENABLE_BIP70 | ||||
"from network " | "from network " | ||||
<< QString(itemNetwork->c_str()) | << QString(itemNetwork->c_str()) | ||||
<< " does not match already chosen network " | << " does not match already chosen network " | ||||
<< QString(chosenNetwork->c_str()); | << QString(chosenNetwork->c_str()); | ||||
continue; | continue; | ||||
} | } | ||||
savedPaymentRequests.append(arg); | savedPaymentRequests.append(arg); | ||||
#endif | |||||
chosenNetwork = itemNetwork; | chosenNetwork = itemNetwork; | ||||
} | } | ||||
if (chosenNetwork) { | if (chosenNetwork) { | ||||
node.selectParams(*chosenNetwork); | node.selectParams(*chosenNetwork); | ||||
} | } | ||||
} | } | ||||
Show All 28 Lines | for (const QString &r : savedPaymentRequests) { | ||||
socket = nullptr; | socket = nullptr; | ||||
fResult = true; | fResult = true; | ||||
} | } | ||||
return fResult; | return fResult; | ||||
} | } | ||||
PaymentServer::PaymentServer(QObject *parent, bool startLocalServer) | PaymentServer::PaymentServer(QObject *parent, bool startLocalServer) | ||||
: QObject(parent), saveURIs(true), uriServer(0), netManager(0), | : QObject(parent), saveURIs(true), uriServer(0), optionsModel(0) | ||||
optionsModel(0) { | #ifdef ENABLE_BIP70 | ||||
, | |||||
netManager(0) | |||||
#endif | |||||
{ | |||||
#ifdef ENABLE_BIP70 | |||||
// Verify that the version of the library that we linked against is | // Verify that the version of the library that we linked against is | ||||
// compatible with the version of the headers we compiled against. | // compatible with the version of the headers we compiled against. | ||||
GOOGLE_PROTOBUF_VERIFY_VERSION; | GOOGLE_PROTOBUF_VERIFY_VERSION; | ||||
#endif | |||||
// Install global event filter to catch QFileOpenEvents | // Install global event filter to catch QFileOpenEvents | ||||
// on Mac: sent when you click bitcoincash: links | // on Mac: sent when you click bitcoincash: links | ||||
// other OSes: helpful when dealing with payment request files | // other OSes: helpful when dealing with payment request files | ||||
if (parent) { | if (parent) { | ||||
parent->installEventFilter(this); | parent->installEventFilter(this); | ||||
} | } | ||||
QString name = ipcServerName(); | QString name = ipcServerName(); | ||||
// Clean up old socket leftover from a crash: | // Clean up old socket leftover from a crash: | ||||
QLocalServer::removeServer(name); | QLocalServer::removeServer(name); | ||||
if (startLocalServer) { | if (startLocalServer) { | ||||
uriServer = new QLocalServer(this); | uriServer = new QLocalServer(this); | ||||
if (!uriServer->listen(name)) { | if (!uriServer->listen(name)) { | ||||
// constructor is called early in init, so don't use "Q_EMIT | // constructor is called early in init, so don't use "Q_EMIT | ||||
// message()" here | // message()" here | ||||
QMessageBox::critical(0, tr("Payment request error"), | QMessageBox::critical(0, tr("Payment request error"), | ||||
tr("Cannot start click-to-pay handler")); | tr("Cannot start click-to-pay handler")); | ||||
} else { | } else { | ||||
connect(uriServer, &QLocalServer::newConnection, this, | connect(uriServer, &QLocalServer::newConnection, this, | ||||
&PaymentServer::handleURIConnection); | &PaymentServer::handleURIConnection); | ||||
#ifdef ENABLE_BIP70 | |||||
connect(this, &PaymentServer::receivedPaymentACK, this, | connect(this, &PaymentServer::receivedPaymentACK, this, | ||||
&PaymentServer::handlePaymentACK); | &PaymentServer::handlePaymentACK); | ||||
#endif | |||||
} | } | ||||
} | } | ||||
} | } | ||||
PaymentServer::~PaymentServer() { | PaymentServer::~PaymentServer() { | ||||
#ifdef ENABLE_BIP70 | |||||
google::protobuf::ShutdownProtobufLibrary(); | google::protobuf::ShutdownProtobufLibrary(); | ||||
#endif | |||||
} | } | ||||
// | // | ||||
// OSX-specific way of handling bitcoincash: URIs and PaymentRequest mime types. | // OSX-specific way of handling bitcoincash: URIs and PaymentRequest mime types. | ||||
// Also used by paymentservertests.cpp and when opening a payment request file | // Also used by paymentservertests.cpp and when opening a payment request file | ||||
// via "Open URI..." menu entry. | // via "Open URI..." menu entry. | ||||
// | // | ||||
bool PaymentServer::eventFilter(QObject *object, QEvent *event) { | bool PaymentServer::eventFilter(QObject *object, QEvent *event) { | ||||
if (event->type() == QEvent::FileOpen) { | if (event->type() == QEvent::FileOpen) { | ||||
QFileOpenEvent *fileEvent = static_cast<QFileOpenEvent *>(event); | QFileOpenEvent *fileEvent = static_cast<QFileOpenEvent *>(event); | ||||
if (!fileEvent->file().isEmpty()) { | if (!fileEvent->file().isEmpty()) { | ||||
handleURIOrFile(fileEvent->file()); | handleURIOrFile(fileEvent->file()); | ||||
} else if (!fileEvent->url().isEmpty()) { | } else if (!fileEvent->url().isEmpty()) { | ||||
handleURIOrFile(fileEvent->url().toString()); | handleURIOrFile(fileEvent->url().toString()); | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
return QObject::eventFilter(object, event); | return QObject::eventFilter(object, event); | ||||
} | } | ||||
void PaymentServer::initNetManager() { | |||||
if (!optionsModel) { | |||||
return; | |||||
} | |||||
if (netManager != nullptr) { | |||||
delete netManager; | |||||
} | |||||
// netManager is used to fetch paymentrequests given in bitcoincash: 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); | |||||
} | |||||
void PaymentServer::uiReady() { | void PaymentServer::uiReady() { | ||||
#ifdef ENABLE_BIP70 | |||||
initNetManager(); | initNetManager(); | ||||
#endif | |||||
saveURIs = false; | saveURIs = false; | ||||
for (const QString &s : savedPaymentRequests) { | for (const QString &s : savedPaymentRequests) { | ||||
handleURIOrFile(s); | handleURIOrFile(s); | ||||
} | } | ||||
savedPaymentRequests.clear(); | savedPaymentRequests.clear(); | ||||
} | } | ||||
bool PaymentServer::handleURI(const CChainParams ¶ms, const QString &s) { | bool PaymentServer::handleURI(const CChainParams ¶ms, const QString &s) { | ||||
const QString scheme = QString::fromStdString(params.CashAddrPrefix()); | const QString scheme = QString::fromStdString(params.CashAddrPrefix()); | ||||
if (!s.startsWith(scheme + ":", Qt::CaseInsensitive)) { | if (!s.startsWith(scheme + ":", Qt::CaseInsensitive)) { | ||||
return false; | return false; | ||||
} | } | ||||
QUrlQuery uri((QUrl(s))); | QUrlQuery uri((QUrl(s))); | ||||
if (uri.hasQueryItem("r")) { | |||||
// payment request URI | // payment request URI | ||||
if (uri.hasQueryItem("r")) { | |||||
#ifdef ENABLE_BIP70 | |||||
Q_EMIT message(tr("URI handling"), | |||||
tr("You are using a BIP70 URL which will be unsupported " | |||||
"in the future."), | |||||
CClientUIInterface::ICON_WARNING); | |||||
QByteArray temp; | QByteArray temp; | ||||
temp.append(uri.queryItemValue("r")); | temp.append(uri.queryItemValue("r")); | ||||
QString decoded = QUrl::fromPercentEncoding(temp); | QString decoded = QUrl::fromPercentEncoding(temp); | ||||
QUrl fetchUrl(decoded, QUrl::StrictMode); | QUrl fetchUrl(decoded, QUrl::StrictMode); | ||||
if (fetchUrl.isValid()) { | if (fetchUrl.isValid()) { | ||||
qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" | qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" | ||||
<< fetchUrl << ")"; | << fetchUrl << ")"; | ||||
fetchRequest(fetchUrl); | fetchRequest(fetchUrl); | ||||
} else { | } else { | ||||
qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " | qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " | ||||
<< fetchUrl; | << fetchUrl; | ||||
Q_EMIT message(tr("URI handling"), | Q_EMIT message(tr("URI handling"), | ||||
tr("Payment request fetch URL is invalid: %1") | tr("Payment request fetch URL is invalid: %1") | ||||
.arg(fetchUrl.toString()), | .arg(fetchUrl.toString()), | ||||
CClientUIInterface::ICON_WARNING); | CClientUIInterface::ICON_WARNING); | ||||
} | } | ||||
#else | |||||
Q_EMIT message(tr("URI handling"), | |||||
tr("Cannot process payment request because BIP70 " | |||||
"support was not compiled in."), | |||||
CClientUIInterface::ICON_WARNING); | |||||
#endif | |||||
return true; | return true; | ||||
} | } | ||||
// normal URI | // normal URI | ||||
SendCoinsRecipient recipient; | SendCoinsRecipient recipient; | ||||
if (GUIUtil::parseBitcoinURI(scheme, s, &recipient)) { | if (GUIUtil::parseBitcoinURI(scheme, s, &recipient)) { | ||||
if (!IsValidDestinationString(recipient.address.toStdString(), | if (!IsValidDestinationString(recipient.address.toStdString(), | ||||
params)) { | params)) { | ||||
Show All 21 Lines | if (saveURIs) { | ||||
return; | return; | ||||
} | } | ||||
// bitcoincash: CashAddr URI | // bitcoincash: CashAddr URI | ||||
if (handleURI(Params(), s)) { | if (handleURI(Params(), s)) { | ||||
return; | return; | ||||
} | } | ||||
#ifdef ENABLE_BIP70 | |||||
// payment request file | // payment request file | ||||
if (QFile::exists(s)) { | if (QFile::exists(s)) { | ||||
PaymentRequestPlus request; | PaymentRequestPlus request; | ||||
SendCoinsRecipient recipient; | SendCoinsRecipient recipient; | ||||
if (!readPaymentRequestFromFile(s, request)) { | if (!readPaymentRequestFromFile(s, request)) { | ||||
Q_EMIT message(tr("Payment request file handling"), | Q_EMIT message(tr("Payment request file handling"), | ||||
tr("Payment request file cannot be read! This can " | tr("Payment request file cannot be read! This can " | ||||
"be caused by an invalid payment request file."), | "be caused by an invalid payment request file."), | ||||
CClientUIInterface::ICON_WARNING); | CClientUIInterface::ICON_WARNING); | ||||
} else if (processPaymentRequest(request, recipient)) { | } else if (processPaymentRequest(request, recipient)) { | ||||
Q_EMIT receivedPaymentRequest(recipient); | Q_EMIT receivedPaymentRequest(recipient); | ||||
} | } | ||||
return; | return; | ||||
} | } | ||||
#endif | |||||
} | } | ||||
void PaymentServer::handleURIConnection() { | void PaymentServer::handleURIConnection() { | ||||
QLocalSocket *clientConnection = uriServer->nextPendingConnection(); | QLocalSocket *clientConnection = uriServer->nextPendingConnection(); | ||||
while (clientConnection->bytesAvailable() < (int)sizeof(quint32)) { | while (clientConnection->bytesAvailable() < (int)sizeof(quint32)) { | ||||
clientConnection->waitForReadyRead(); | clientConnection->waitForReadyRead(); | ||||
} | } | ||||
connect(clientConnection, &QLocalSocket::disconnected, clientConnection, | connect(clientConnection, &QLocalSocket::disconnected, clientConnection, | ||||
&QLocalSocket::deleteLater); | &QLocalSocket::deleteLater); | ||||
QDataStream in(clientConnection); | QDataStream in(clientConnection); | ||||
in.setVersion(QDataStream::Qt_4_0); | in.setVersion(QDataStream::Qt_4_0); | ||||
if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) { | if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) { | ||||
return; | return; | ||||
} | } | ||||
QString msg; | QString msg; | ||||
in >> msg; | in >> msg; | ||||
handleURIOrFile(msg); | handleURIOrFile(msg); | ||||
} | } | ||||
void PaymentServer::setOptionsModel(OptionsModel *_optionsModel) { | |||||
this->optionsModel = _optionsModel; | |||||
} | |||||
#ifdef ENABLE_BIP70 | |||||
struct X509StoreDeleter { | |||||
void operator()(X509_STORE *b) { X509_STORE_free(b); } | |||||
}; | |||||
struct X509Deleter { | |||||
void operator()(X509 *b) { X509_free(b); } | |||||
}; | |||||
// Anon namespace | |||||
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 uint8_t *data = (const uint8_t *)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; | |||||
} | |||||
if (netManager != nullptr) { | |||||
delete netManager; | |||||
} | |||||
// netManager is used to fetch paymentrequests given in bitcoincash: 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); | |||||
} | |||||
// | // | ||||
// Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine() | // Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine() | ||||
// so don't use "Q_EMIT message()", but "QMessageBox::"! | // so don't use "Q_EMIT message()", but "QMessageBox::"! | ||||
// | // | ||||
bool PaymentServer::readPaymentRequestFromFile(const QString &filename, | bool PaymentServer::readPaymentRequestFromFile(const QString &filename, | ||||
PaymentRequestPlus &request) { | PaymentRequestPlus &request) { | ||||
QFile f(filename); | QFile f(filename); | ||||
if (!f.open(QIODevice::ReadOnly)) { | if (!f.open(QIODevice::ReadOnly)) { | ||||
▲ Show 20 Lines • Show All 266 Lines • ▼ Show 20 Lines | void PaymentServer::reportSslErrors(QNetworkReply *reply, | ||||
for (const QSslError &err : errs) { | for (const QSslError &err : errs) { | ||||
qWarning() << "PaymentServer::reportSslErrors: " << err; | qWarning() << "PaymentServer::reportSslErrors: " << err; | ||||
errString += err.errorString() + "\n"; | errString += err.errorString() + "\n"; | ||||
} | } | ||||
Q_EMIT message(tr("Network request error"), errString, | Q_EMIT message(tr("Network request error"), errString, | ||||
CClientUIInterface::MSG_ERROR); | CClientUIInterface::MSG_ERROR); | ||||
} | } | ||||
void PaymentServer::setOptionsModel(OptionsModel *_optionsModel) { | |||||
this->optionsModel = _optionsModel; | |||||
} | |||||
void PaymentServer::handlePaymentACK(const QString &paymentACKMsg) { | void PaymentServer::handlePaymentACK(const QString &paymentACKMsg) { | ||||
// currently we don't further process or store the paymentACK message | // currently we don't further process or store the paymentACK message | ||||
Q_EMIT message(tr("Payment acknowledged"), paymentACKMsg, | Q_EMIT message(tr("Payment acknowledged"), paymentACKMsg, | ||||
CClientUIInterface::ICON_INFORMATION | | CClientUIInterface::ICON_INFORMATION | | ||||
CClientUIInterface::MODAL); | CClientUIInterface::MODAL); | ||||
} | } | ||||
bool PaymentServer::verifyNetwork( | bool PaymentServer::verifyNetwork( | ||||
▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | if (!fVerified) { | ||||
.arg(MAX_MONEY / SATOSHI); | .arg(MAX_MONEY / SATOSHI); | ||||
} | } | ||||
return fVerified; | return fVerified; | ||||
} | } | ||||
X509_STORE *PaymentServer::getCertStore() { | X509_STORE *PaymentServer::getCertStore() { | ||||
return certStore.get(); | return certStore.get(); | ||||
} | } | ||||
#endif |