Changeset View
Changeset View
Standalone View
Standalone View
src/qt/paymentserver.cpp
Show First 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | |||||
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"; | ||||
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 125 Lines • ▼ Show 20 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) { | netManager(0) { | ||||
// 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; | ||||
// 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) { | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | if (event->type() == QEvent::FileOpen) { | ||||
} | } | ||||
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() { | ||||
initNetManager(); | initNetManager(); | ||||
saveURIs = false; | saveURIs = false; | ||||
for (const QString &s : savedPaymentRequests) { | for (const QString &s : savedPaymentRequests) { | ||||
handleURIOrFile(s); | handleURIOrFile(s); | ||||
} | } | ||||
savedPaymentRequests.clear(); | savedPaymentRequests.clear(); | ||||
▲ Show 20 Lines • Show All 96 Lines • ▼ Show 20 Lines | 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; | |||||
} | |||||
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 54 Lines • Show Last 20 Lines |