]> Git Repo - VerusCoin.git/blob - src/qt/paymentrequestplus.cpp
Payment Protocol: X509-validated payment requests
[VerusCoin.git] / src / qt / paymentrequestplus.cpp
1 //
2 // Wraps dumb protocol buffer paymentRequest
3 // with some extra methods
4 //
5
6 #include <QDateTime>
7 #include <QDebug>
8 #include <QSslCertificate>
9
10 #include <openssl/x509.h>
11 #include <openssl/x509_vfy.h>
12
13 #include <stdexcept>
14
15 #include "paymentrequestplus.h"
16
17 class SSLVerifyError : public std::runtime_error
18 {
19 public:
20     SSLVerifyError(std::string err) : std::runtime_error(err) { }
21 };
22
23 bool PaymentRequestPlus::parse(const QByteArray& data)
24 {
25     bool parseOK = paymentRequest.ParseFromArray(data.data(), data.size());
26     if (!parseOK) {
27         qDebug() << "Error parsing payment request";
28         return false;
29     }
30     if (paymentRequest.payment_details_version() > 1) {
31         qDebug() << "Received up-version payment details, version=" << paymentRequest.payment_details_version();
32         return false;
33     }
34
35     parseOK = details.ParseFromString(paymentRequest.serialized_payment_details());
36     if (!parseOK)
37     {
38         qDebug() << "Error parsing payment details";
39         paymentRequest.Clear();
40         return false;
41     }
42     return true;
43 }
44
45 bool PaymentRequestPlus::SerializeToString(string* output) const
46 {
47     return paymentRequest.SerializeToString(output);
48 }
49
50 bool PaymentRequestPlus::IsInitialized() const
51 {
52     return paymentRequest.IsInitialized();
53 }
54
55 QString PaymentRequestPlus::getPKIType() const
56 {
57     if (!IsInitialized()) return QString("none");
58     return QString::fromStdString(paymentRequest.pki_type());
59 }
60
61 bool PaymentRequestPlus::getMerchant(X509_STORE* certStore, QString& merchant) const
62 {
63     merchant.clear();
64
65     if (!IsInitialized())
66         return false;
67
68     // One day we'll support more PKI types, but just
69     // x509 for now:
70     const EVP_MD* digestAlgorithm = NULL;
71     if (paymentRequest.pki_type() == "x509+sha256") {
72         digestAlgorithm = EVP_sha256();
73     }
74     else if (paymentRequest.pki_type() == "x509+sha1") {
75         digestAlgorithm = EVP_sha1();
76     }
77     else if (paymentRequest.pki_type() == "none") {
78         if (fDebug) qDebug() << "PaymentRequest: pki_type == none";
79         return false;
80     }
81     else {
82         qDebug() << "PaymentRequest: unknown pki_type " << paymentRequest.pki_type().c_str();
83         return false;
84     }
85
86     payments::X509Certificates certChain;
87     if (!certChain.ParseFromString(paymentRequest.pki_data())) {
88         qDebug() << "PaymentRequest: error parsing pki_data";
89         return false;
90     }
91
92     std::vector<X509*> certs;
93     const QDateTime currentTime = QDateTime::currentDateTime();
94     for (int i = 0; i < certChain.certificate_size(); i++) {
95         QByteArray certData(certChain.certificate(i).data(), certChain.certificate(i).size());
96         QSslCertificate qCert(certData, QSsl::Der);
97         if (currentTime < qCert.effectiveDate() || currentTime > qCert.expiryDate()) {
98             qDebug() << "PaymentRequest: certificate expired or not yet active: " << qCert;
99             return false;
100         }
101 #if QT_VERSION >= 0x050000
102         if (qCert.isBlacklisted()) {
103             qDebug() << "PaymentRequest: certificate blacklisted: " << qCert;
104             return false;
105         }
106 #endif
107         const unsigned char *data = (const unsigned char *)certChain.certificate(i).data();
108         X509 *cert = d2i_X509(NULL, &data, certChain.certificate(i).size());
109         if (cert)
110             certs.push_back(cert);
111     }
112     if (certs.empty()) {
113         qDebug() << "PaymentRequest: empty certificate chain";
114         return false;
115     }
116
117     // The first cert is the signing cert, the rest are untrusted certs that chain
118     // to a valid root authority. OpenSSL needs them separately.
119     STACK_OF(X509) *chain = sk_X509_new_null();
120     for (int i = certs.size()-1; i > 0; i--) {
121         sk_X509_push(chain, certs[i]);
122     }
123     X509 *signing_cert = certs[0];
124
125     // Now create a "store context", which is a single use object for checking,
126     // load the signing cert into it and verify.
127     X509_STORE_CTX *store_ctx = X509_STORE_CTX_new();
128     if (!store_ctx) {
129         qDebug() << "PaymentRequest: error creating X509_STORE_CTX";
130         return false;
131     }
132
133     char *website = NULL;
134     bool fResult = true;
135     try
136     {
137         if (!X509_STORE_CTX_init(store_ctx, certStore, signing_cert, chain))
138         {
139             int error = X509_STORE_CTX_get_error(store_ctx);
140             throw SSLVerifyError(X509_verify_cert_error_string(error));
141         }
142
143         // Now do the verification!
144         int result = X509_verify_cert(store_ctx);
145         if (result != 1) {
146             int error = X509_STORE_CTX_get_error(store_ctx);
147             throw SSLVerifyError(X509_verify_cert_error_string(error));
148         }
149         X509_NAME *certname = X509_get_subject_name(signing_cert);
150
151         // Valid cert; check signature:
152         payments::PaymentRequest rcopy(paymentRequest); // Copy
153         rcopy.set_signature(std::string(""));
154         std::string data_to_verify;                 // Everything but the signature
155         rcopy.SerializeToString(&data_to_verify);
156
157         EVP_MD_CTX ctx;
158         EVP_PKEY *pubkey = X509_get_pubkey(signing_cert);
159         EVP_MD_CTX_init(&ctx);
160         if (!EVP_VerifyInit_ex(&ctx, digestAlgorithm, NULL) ||
161             !EVP_VerifyUpdate(&ctx, data_to_verify.data(), data_to_verify.size()) ||
162             !EVP_VerifyFinal(&ctx, (const unsigned char*)paymentRequest.signature().data(), paymentRequest.signature().size(), pubkey)) {
163
164             throw SSLVerifyError("Bad signature, invalid PaymentRequest.");
165         }
166
167         // OpenSSL API for getting human printable strings from certs is baroque.
168         int textlen = X509_NAME_get_text_by_NID(certname, NID_commonName, NULL, 0);
169         website = new char[textlen + 1];
170         if (X509_NAME_get_text_by_NID(certname, NID_commonName, website, textlen + 1) == textlen && textlen > 0) {
171             merchant = website;
172         }
173         else {
174             throw SSLVerifyError("Bad certificate, missing common name");
175         }
176         // TODO: detect EV certificates and set merchant = business name instead of unfriendly NID_commonName ?
177     }
178     catch (SSLVerifyError& err)
179     {
180         fResult = false;
181         qDebug() << "PaymentRequestPlus::getMerchant SSL err: " << err.what();
182     }
183
184     if (website)
185         delete[] website;
186     X509_STORE_CTX_free(store_ctx);
187     for (unsigned int i = 0; i < certs.size(); i++)
188         X509_free(certs[i]);
189
190     return fResult;
191 }
192
193 QList<std::pair<CScript,qint64> > PaymentRequestPlus::getPayTo() const
194 {
195     QList<std::pair<CScript,qint64> > result;
196     for (int i = 0; i < details.outputs_size(); i++)
197     {
198         const unsigned char* scriptStr = (const unsigned char*)details.outputs(i).script().data();
199         CScript s(scriptStr, scriptStr+details.outputs(i).script().size());
200
201         result.append(make_pair(s, details.outputs(i).amount()));
202     }
203     return result;
204 }
This page took 0.03604 seconds and 4 git commands to generate.