diff --git a/src/qt/paymentserver.h b/src/qt/paymentserver.h --- a/src/qt/paymentserver.h +++ b/src/qt/paymentserver.h @@ -68,6 +68,9 @@ explicit PaymentServer(QObject *parent, bool startLocalServer = true); ~PaymentServer(); + // OptionsModel is used for getting proxy settings and display unit + void setOptionsModel(OptionsModel *optionsModel); + // Load root certificate authorities. Pass nullptr (default) to read from // the file specified in the -rootcertificates setting, or, if that's not // set, to use the system default root certificates. If you pass in a store, @@ -78,9 +81,6 @@ // Return certificate store static X509_STORE *getCertStore(); - // OptionsModel is used for getting proxy settings and display unit - void setOptionsModel(OptionsModel *optionsModel); - // Verify that the payment request network matches the client network static bool verifyNetwork(interfaces::Node &node, const payments::PaymentDetails &requestDetails); @@ -95,26 +95,26 @@ // Fired when a valid payment request is received void receivedPaymentRequest(SendCoinsRecipient); - // Fired when a valid PaymentACK is received - void receivedPaymentACK(const QString &paymentACKMsg); - // Fired when a message should be reported to the user void message(const QString &title, const QString &message, unsigned int style); + // Fired when a valid PaymentACK is received + void receivedPaymentACK(const QString &paymentACKMsg); + public Q_SLOTS: // Signal this when the main window's UI is ready to display payment // requests to the user void uiReady(); + // Handle an incoming URI, URI with local file scheme or file + void handleURIOrFile(const QString &s); + // Submit Payment message to a merchant, get back PaymentACK: void fetchPaymentACK(WalletModel *walletModel, const SendCoinsRecipient &recipient, QByteArray transaction); - // Handle an incoming URI, URI with local file scheme or file - void handleURIOrFile(const QString &s); - private Q_SLOTS: void handleURIConnection(); void netRequestFinished(QNetworkReply *); @@ -127,9 +127,15 @@ bool eventFilter(QObject *object, QEvent *event) override; private: + // true during startup + bool saveURIs; + QLocalServer *uriServer; + OptionsModel *optionsModel; + + bool handleURI(const CChainParams ¶ms, const QString &s); + static bool readPaymentRequestFromFile(const QString &filename, PaymentRequestPlus &request); - bool handleURI(const CChainParams ¶ms, const QString &s); bool processPaymentRequest(const PaymentRequestPlus &request, SendCoinsRecipient &recipient); void fetchRequest(const QUrl &url); @@ -137,14 +143,8 @@ // Setup networking void initNetManager(); - // true during startup - bool saveURIs; - QLocalServer *uriServer; - // Used to fetch payment requests QNetworkAccessManager *netManager; - - OptionsModel *optionsModel; }; #endif // BITCOIN_QT_PAYMENTSERVER_H diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -53,19 +53,6 @@ const char *BIP71_MIMETYPE_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 certStore; -} - // // Create a name that is unique for: // testnet / non-testnet @@ -89,105 +76,6 @@ // static QList 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 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( - 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, bool useCashAddr) { const QString scheme = QString::fromStdString(params.CashAddrPrefix()); @@ -329,8 +217,8 @@ } PaymentServer::PaymentServer(QObject *parent, bool startLocalServer) - : QObject(parent), saveURIs(true), uriServer(0), netManager(0), - optionsModel(0) { + : QObject(parent), saveURIs(true), uriServer(0), optionsModel(0), + netManager(0) { // 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; @@ -388,36 +276,6 @@ 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() { initNetManager(); @@ -530,6 +388,153 @@ 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 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 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( + 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() // so don't use "Q_EMIT message()", but "QMessageBox::"! @@ -812,10 +817,6 @@ CClientUIInterface::MSG_ERROR); } -void PaymentServer::setOptionsModel(OptionsModel *_optionsModel) { - this->optionsModel = _optionsModel; -} - void PaymentServer::handlePaymentACK(const QString &paymentACKMsg) { // currently we don't further process or store the paymentACK message Q_EMIT message(tr("Payment acknowledged"), paymentACKMsg,