diff --git a/identitymanager.cpp b/identitymanager.cpp new file mode 100644 index 00000000..bd8220dd --- /dev/null +++ b/identitymanager.cpp @@ -0,0 +1,132 @@ +#include "identitymanager.h" +#include "utils.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +IdentityManager::IdentityManager(QDir directory) +{ + QFile uniqueIdFile(directory.filePath("uniqueid")); + if (uniqueIdFile.open(QIODevice::ReadOnly)) + { + m_CachedUniqueId = QTextStream(&uniqueIdFile).readAll(); + qDebug() << "Loaded cached unique ID: " << m_CachedUniqueId; + } + else + { + for (int i = 0; i < 16; i++) + { + int n = qrand() % 16; + m_CachedUniqueId.append(QString::number(n, 16)); + } + + qDebug() << "Generated new unique ID: " << m_CachedUniqueId; + + uniqueIdFile.open(QIODevice::ReadWrite); + QTextStream(&uniqueIdFile) << m_CachedUniqueId; + } + + QFile certificateFile(m_RootDirectory.filePath("cert")); + QFile privateKeyFile(m_RootDirectory.filePath("key")); + + if (certificateFile.open(QIODevice::ReadOnly) && privateKeyFile.open(QIODevice::ReadOnly)) + { + // Not cached yet, but it's on disk + m_CachedPemCert = certificateFile.readAll(); + m_CachedPrivateKey = privateKeyFile.readAll(); + + // Make sure it really loads + if (!QSslKey(m_CachedPrivateKey, QSsl::Rsa).isNull() && !QSslCertificate(m_CachedPemCert).isNull()) + { + qDebug() << "Loaded cached identity key pair from disk"; + return; + } + + qDebug() << "Regenerating corrupted local key pair"; + } + + privateKeyFile.close(); + certificateFile.close(); + + X509* cert = X509_new(); + THROW_BAD_ALLOC_IF_NULL(cert); + + EVP_PKEY* pk = EVP_PKEY_new(); + THROW_BAD_ALLOC_IF_NULL(pk); + + BIGNUM* bne = BN_new(); + THROW_BAD_ALLOC_IF_NULL(bne); + + RSA* rsa = RSA_new(); + THROW_BAD_ALLOC_IF_NULL(rsa); + + BN_set_word(bne, RSA_F4); + RSA_generate_key_ex(rsa, 2048, bne, nullptr); + + EVP_PKEY_assign_RSA(pk, rsa); + + X509_set_version(cert, 2); + ASN1_INTEGER_set(X509_get_serialNumber(cert), 0); + X509_gmtime_adj(X509_get_notBefore(cert), 0); + X509_gmtime_adj(X509_get_notAfter(cert), 60 * 60 * 24 * 365 * 20); // 20 yrs + X509_set_pubkey(cert, pk); + + X509_NAME* name = X509_get_subject_name(cert); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, reinterpret_cast(const_cast("NVIDIA GameStream Client")), -1, -1, 0); + X509_set_issuer_name(cert, name); + + X509_sign(cert, pk, EVP_sha1()); + + BIO* biokey = BIO_new(BIO_s_mem()); + THROW_BAD_ALLOC_IF_NULL(biokey); + PEM_write_bio_PrivateKey(biokey, pk, NULL, NULL, 0, NULL, NULL); + + BIO* biocert = BIO_new(BIO_s_mem()); + THROW_BAD_ALLOC_IF_NULL(biocert); + PEM_write_bio_X509(biocert, cert); + + privateKeyFile.open(QIODevice::WriteOnly); + certificateFile.open(QIODevice::WriteOnly); + + BUF_MEM* mem; + BIO_get_mem_ptr(biokey, &mem); + m_CachedPrivateKey = QByteArray(mem->data, mem->length); + QTextStream(&privateKeyFile) << m_CachedPrivateKey; + + BIO_get_mem_ptr(biocert, &mem); + m_CachedPemCert = QByteArray(mem->data, mem->length); + QTextStream(&certificateFile) << m_CachedPemCert; + + X509_free(cert); + EVP_PKEY_free(pk); + BN_free(bne); + BIO_free(biokey); + BIO_free(biocert); + + qDebug() << "Wrote new identity credentials to disk"; +} + +QString +IdentityManager::getUniqueId() +{ + return m_CachedUniqueId; +} + +QByteArray +IdentityManager::getCertificate() +{ + return m_CachedPemCert; +} + +QByteArray +IdentityManager::getPrivateKey() +{ + return m_CachedPrivateKey; +} diff --git a/identitymanager.h b/identitymanager.h new file mode 100644 index 00000000..bda49413 --- /dev/null +++ b/identitymanager.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +class IdentityManager +{ +public: + IdentityManager(QDir directory); + + QString + getUniqueId(); + + QByteArray + getCertificate(); + + QByteArray + getPrivateKey(); + +private: + QDir m_RootDirectory; + + QByteArray m_CachedPrivateKey; + QByteArray m_CachedPemCert; + QString m_CachedUniqueId; +}; diff --git a/moonlight-qt.pro b/moonlight-qt.pro index 40157b2b..cb58a097 100644 --- a/moonlight-qt.pro +++ b/moonlight-qt.pro @@ -20,20 +20,29 @@ DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. -#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ - main.cpp \ - mainwindow.cpp \ - nvhttp.cpp + main.cpp \ + mainwindow.cpp \ + nvhttp.cpp \ + nvpairingmanager.cpp \ + identitymanager.cpp HEADERS += \ - mainwindow.h \ - nvhttp.h + mainwindow.h \ + nvhttp.h \ + nvpairingmanager.h \ + identitymanager.h \ + utils.h FORMS += \ - mainwindow.ui + mainwindow.ui RESOURCES += \ resources.qrc + +LIBS += \ + -lssl \ + -lcrypto diff --git a/nvhttp.cpp b/nvhttp.cpp index 8caf423a..520eed11 100644 --- a/nvhttp.cpp +++ b/nvhttp.cpp @@ -1,15 +1,18 @@ #include "nvhttp.h" +#include #include #include #include #include #include +#include #define REQUEST_TIMEOUT_MS 5000 -NvHTTP::NvHTTP(QString address) : - m_Address(address) +NvHTTP::NvHTTP(QString address, IdentityManager im) : + m_Address(address), + m_Im(im) { m_BaseUrlHttp.setScheme("http"); m_BaseUrlHttps.setScheme("https"); @@ -58,6 +61,21 @@ NvHTTP::getComputerInfo() return computer; } +QVector +NvHTTP::getServerVersionQuad(QString serverInfo) +{ + QString quad = getXmlString(serverInfo, "appversion"); + QStringList parts = quad.split("."); + QVector ret; + + for (int i = 0; i < 4; i++) + { + ret.append(parts.at(i).toInt()); + } + + return ret; +} + int NvHTTP::getCurrentGame(QString serverInfo) { @@ -65,10 +83,12 @@ NvHTTP::getCurrentGame(QString serverInfo) // has the semantics that its name would indicate. To contain the effects of this change as much // as possible, we'll force the current game to zero if the server isn't in a streaming session. QString serverState = getXmlString(serverInfo, "state"); - if (serverState != nullptr && serverState.endsWith("_SERVER_BUSY")) { + if (serverState != nullptr && serverState.endsWith("_SERVER_BUSY")) + { return getXmlString(serverInfo, "currentgame").toInt(); } - else { + else + { return 0; } } @@ -89,17 +109,22 @@ NvHTTP::getServerInfo() // Throws if the request failed verifyResponseStatus(serverInfo); } - catch (GfeHttpResponseException& e) + catch (const GfeHttpResponseException& e) { if (e.getStatusCode() == 401) { // Certificate validation error, fallback to HTTP - serverInfo = openConnectionToString(m_BaseUrlHttps, + serverInfo = openConnectionToString(m_BaseUrlHttp, "serverinfo", nullptr, true); verifyResponseStatus(serverInfo); } + else + { + // Rethrow real errors + throw e; + } } return serverInfo; @@ -130,17 +155,35 @@ NvHTTP::verifyResponseStatus(QString xml) } } +QByteArray +NvHTTP::getXmlStringFromHex(QString xml, + QString tagName) +{ + QString str = getXmlString(xml, tagName); + if (str == nullptr) + { + return nullptr; + } + + return QByteArray::fromHex(str.toLatin1()); +} + QString NvHTTP::getXmlString(QString xml, QString tagName) { QXmlStreamReader xmlReader(xml); - while (xmlReader.readNextStartElement()) + while (!xmlReader.atEnd()) { + if (xmlReader.readNext() != QXmlStreamReader::StartElement) + { + continue; + } + if (xmlReader.name() == tagName) { - return xmlReader.text().toString(); + return xmlReader.readElementText(); } } @@ -170,15 +213,23 @@ NvHTTP::openConnection(QUrl baseUrl, { // Build a URL for the request QUrl url(baseUrl); - url.setPath(command + - "?uniqueid=" + "0" + - "&uuid=" + QUuid::createUuid().toString() + - ((arguments != nullptr) ? (arguments + "&") : "")); + url.setPath("/" + command); + url.setQuery("uniqueid=" + m_Im.getUniqueId() + + "&uuid=" + QUuid::createUuid().toRfc4122().toHex() + + ((arguments != nullptr) ? ("&" + arguments) : "")); - QNetworkReply* reply = m_Nam.get(QNetworkRequest(url)); + QNetworkRequest request = QNetworkRequest(url); + + // Add our client certificate + QSslConfiguration sslConfig(QSslConfiguration::defaultConfiguration()); + sslConfig.setLocalCertificate(QSslCertificate(m_Im.getCertificate())); + sslConfig.setPrivateKey(QSslKey(m_Im.getPrivateKey(), QSsl::Rsa)); + request.setSslConfiguration(sslConfig); + + QNetworkReply* reply = m_Nam.get(request); // Ignore self-signed certificate errors (since GFE uses them) - reply->ignoreSslErrors(QList{ QSslError::SelfSignedCertificate }); + reply->ignoreSslErrors(); // Run the request with a timeout if requested QEventLoop loop; @@ -200,9 +251,10 @@ NvHTTP::openConnection(QUrl baseUrl, // Handle error if (reply->error() != QNetworkReply::NoError) { - qDebug() << command << " request for failed with error " << reply->error(); + qDebug() << command << " request failed with error " << reply->error(); + GfeHttpResponseException exception(reply->error(), reply->errorString()); delete reply; - throw new GfeHttpResponseException(reply->error(), reply->errorString()); + throw exception; } return reply; diff --git a/nvhttp.h b/nvhttp.h index f0a3fe5f..6df48887 100644 --- a/nvhttp.h +++ b/nvhttp.h @@ -1,5 +1,7 @@ #pragma once +#include "identitymanager.h" + #include #include @@ -15,15 +17,15 @@ public: const char* what() const throw() { - return m_StatusMessage.toStdString().c_str(); + return m_StatusMessage.toLatin1(); } - const char* getStatusMessage() + const char* getStatusMessage() const { - return m_StatusMessage.toStdString().c_str(); + return m_StatusMessage.toLatin1(); } - int getStatusCode() + int getStatusCode() const { return m_StatusCode; } @@ -65,14 +67,7 @@ public: class NvHTTP { public: - NvHTTP(QString address); - -private: - QNetworkReply* - openConnection(QUrl baseUrl, - QString command, - QString arguments, - bool enableTimeout); + NvHTTP(QString address, IdentityManager im); NvComputer getComputerInfo(); @@ -90,14 +85,29 @@ private: getXmlString(QString xml, QString tagName); + QByteArray + getXmlStringFromHex(QString xml, + QString tagName); + QString openConnectionToString(QUrl baseUrl, QString command, QString arguments, bool enableTimeout); - QString m_Address; + QVector + getServerVersionQuad(QString serverInfo); + QUrl m_BaseUrlHttp; QUrl m_BaseUrlHttps; +private: + QNetworkReply* + openConnection(QUrl baseUrl, + QString command, + QString arguments, + bool enableTimeout); + + QString m_Address; QNetworkAccessManager m_Nam; + IdentityManager m_Im; }; diff --git a/nvpairingmanager.cpp b/nvpairingmanager.cpp new file mode 100644 index 00000000..1d626ff7 --- /dev/null +++ b/nvpairingmanager.cpp @@ -0,0 +1,308 @@ +#include "nvpairingmanager.h" +#include "utils.h" + +#include + +#include +#include +#include +#include +#include +#include + +NvPairingManager::NvPairingManager(QString address, IdentityManager im) : + m_Http(address, im), + m_Im(im) +{ + QByteArray cert = m_Im.getCertificate(); + BIO *bio = BIO_new_mem_buf(cert.data(), -1); + THROW_BAD_ALLOC_IF_NULL(bio); + + m_Cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); + BIO_free_all(bio); + if (m_Cert == nullptr) + { + throw new std::runtime_error("Unable to load certificate"); + } + + QByteArray pk = m_Im.getPrivateKey(); + bio = BIO_new_mem_buf(pk.data(), -1); + THROW_BAD_ALLOC_IF_NULL(bio); + + PEM_read_bio_PrivateKey(bio, &m_PrivateKey, nullptr, nullptr); + BIO_free_all(bio); + if (m_Cert == nullptr) + { + throw new std::runtime_error("Unable to load private key"); + } +} + +NvPairingManager::~NvPairingManager() +{ + X509_free(m_Cert); + EVP_PKEY_free(m_PrivateKey); +} + +QString +NvPairingManager::generatePinString() +{ + return QString::asprintf("%04d", QRandomGenerator::global()->bounded(10000)); +} + +QByteArray +NvPairingManager::generateRandomBytes(int length) +{ + char* data = static_cast(alloca(length)); + RAND_bytes(reinterpret_cast(data), length); + return QByteArray(data, length); +} + +QByteArray +NvPairingManager::encrypt(QByteArray plaintext, AES_KEY* key) +{ + QByteArray ciphertext(plaintext.size(), 0); + + for (int i = 0; i < plaintext.size(); i += 16) + { + AES_encrypt(reinterpret_cast(&plaintext.data()[i]), + reinterpret_cast(&ciphertext.data()[i]), + key); + } + + return ciphertext; +} + +QByteArray +NvPairingManager::decrypt(QByteArray ciphertext, AES_KEY* key) +{ + QByteArray plaintext(ciphertext.size(), 0); + + for (int i = 0; i < plaintext.size(); i += 16) + { + AES_decrypt(reinterpret_cast(&ciphertext.data()[i]), + reinterpret_cast(&plaintext.data()[i]), + key); + } + + return plaintext; +} + +QByteArray +NvPairingManager::getSignatureFromPemCert(QByteArray certificate) +{ + BIO* bio = BIO_new_mem_buf(certificate.data(), -1); + THROW_BAD_ALLOC_IF_NULL(bio); + + X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); + BIO_free_all(bio); + + const ASN1_BIT_STRING *asnSignature; + X509_get0_signature(&asnSignature, NULL, cert); + + QByteArray signature(reinterpret_cast(asnSignature->data), asnSignature->length); + + X509_free(cert); + + return signature; +} + +bool +NvPairingManager::verifySignature(QByteArray data, QByteArray signature, QByteArray serverCertificate) +{ + BIO* bio = BIO_new_mem_buf(serverCertificate.data(), -1); + THROW_BAD_ALLOC_IF_NULL(bio); + + X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); + BIO_free_all(bio); + + EVP_PKEY* pubKey = X509_get_pubkey(cert); + THROW_BAD_ALLOC_IF_NULL(pubKey); + + EVP_MD_CTX* mdctx = EVP_MD_CTX_create(); + THROW_BAD_ALLOC_IF_NULL(mdctx); + + EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, pubKey); + EVP_DigestVerifyUpdate(mdctx, data.data(), data.length()); + int result = EVP_DigestVerifyFinal(mdctx, reinterpret_cast(signature.data()), signature.length()); + + EVP_PKEY_free(pubKey); + EVP_MD_CTX_destroy(mdctx); + X509_free(cert); + + return result > 0; +} + +QByteArray +NvPairingManager::signMessage(QByteArray message) +{ + EVP_MD_CTX *ctx = EVP_MD_CTX_create(); + THROW_BAD_ALLOC_IF_NULL(ctx); + + const EVP_MD *md = EVP_get_digestbyname("SHA256"); + THROW_BAD_ALLOC_IF_NULL(md); + + EVP_DigestInit_ex(ctx, md, NULL); + EVP_DigestSignInit(ctx, NULL, md, NULL, m_PrivateKey); + EVP_DigestSignUpdate(ctx, reinterpret_cast(message.data()), message.length()); + + size_t signatureLength = 0; + EVP_DigestSignFinal(ctx, NULL, &signatureLength); + + QByteArray signature(signatureLength, 0); + EVP_DigestSignFinal(ctx, reinterpret_cast(signature.data()), &signatureLength); + + EVP_MD_CTX_destroy(ctx); + + return signature; +} + +QByteArray +NvPairingManager::saltPin(QByteArray salt, QString pin) +{ + return QByteArray().append(salt).append(pin.toLatin1()); +} + +NvPairingManager::PairState +NvPairingManager::pair(QString serverInfo, QString pin) +{ + int serverMajorVersion = m_Http.getServerVersionQuad(serverInfo).at(0); + qDebug() << "Pairing with server generation: " << serverMajorVersion; + + QCryptographicHash::Algorithm hashAlgo; + int hashLength; + if (serverMajorVersion >= 7) + { + // Gen 7+ uses SHA-256 hashing + hashAlgo = QCryptographicHash::Sha256; + hashLength = 32; + } + else + { + // Prior to Gen 7 uses SHA-1 hashing + hashAlgo = QCryptographicHash::Sha1; + hashLength = 20; + } + + QByteArray salt = generateRandomBytes(16); + QByteArray saltedPin = saltPin(salt, pin); + + AES_KEY encKey, decKey; + AES_set_decrypt_key(reinterpret_cast(QCryptographicHash::hash(saltedPin, hashAlgo).data()), 128, &decKey); + AES_set_encrypt_key(reinterpret_cast(QCryptographicHash::hash(saltedPin, hashAlgo).data()), 128, &encKey); + + QString getCert = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, + "pair", + "devicename=roth&updateState=1&phrase=getservercert&salt=" + + salt.toHex() + "&clientcert=" + m_Im.getCertificate().toHex(), + false); + m_Http.verifyResponseStatus(getCert); + if (m_Http.getXmlString(getCert, "paired") != "1") + { + qDebug() << "Failed pairing at stage #1"; + return PairState::FAILED; + } + + QByteArray serverCert = m_Http.getXmlStringFromHex(getCert, "plaincert"); + if (serverCert == nullptr) + { + qDebug() << "Server likely already pairing"; + m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true); + return PairState::ALREADY_IN_PROGRESS; + } + + QByteArray randomChallenge = generateRandomBytes(16); + QByteArray encryptedChallenge = encrypt(randomChallenge, &encKey); + QString challengeXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, + "pair", + "devicename=roth&updateState=1&clientchallenge=" + + encryptedChallenge.toHex(), + true); + m_Http.verifyResponseStatus(challengeXml); + if (m_Http.getXmlString(challengeXml, "paired") != "1") + { + qDebug() << "Failed pairing at stage #2"; + m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true); + return PairState::FAILED; + } + + QByteArray challengeResponseData = decrypt(m_Http.getXmlStringFromHex(challengeXml, "challengeresponse"), &decKey); + QByteArray clientSecretData = generateRandomBytes(16); + QByteArray challengeResponse; + QByteArray serverResponse(challengeResponseData.data(), hashLength); + + const ASN1_BIT_STRING *asnSignature; + X509_get0_signature(&asnSignature, NULL, m_Cert); + + challengeResponse.append(challengeResponseData.data() + hashLength, 16); + challengeResponse.append(reinterpret_cast(asnSignature->data), asnSignature->length); + challengeResponse.append(clientSecretData); + + QByteArray encryptedChallengeResponseHash = encrypt(QCryptographicHash::hash(challengeResponse, hashAlgo), &encKey); + QString respXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, + "pair", + "devicename=roth&updateState=1&serverchallengeresp=" + + encryptedChallengeResponseHash.toHex(), + true); + m_Http.verifyResponseStatus(respXml); + if (m_Http.getXmlString(respXml, "paired") != "1") + { + qDebug() << "Failed pairing at stage #3"; + m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true); + return PairState::FAILED; + } + + QByteArray pairingSecret = m_Http.getXmlStringFromHex(respXml, "pairingsecret"); + QByteArray serverSecret = QByteArray(pairingSecret.data(), 16); + QByteArray serverSignature = QByteArray(&pairingSecret.data()[16], 256); + + if (!verifySignature(serverSecret, + serverSignature, + serverCert)) + { + qDebug() << "MITM detected"; + m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true); + return PairState::FAILED; + } + + QByteArray expectedResponseData; + expectedResponseData.append(randomChallenge); + expectedResponseData.append(getSignatureFromPemCert(serverCert)); + expectedResponseData.append(serverSecret); + if (QCryptographicHash::hash(expectedResponseData, hashAlgo) != serverResponse) + { + qDebug() << "Incorrect PIN"; + m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true); + return PairState::PIN_WRONG; + } + + QByteArray clientPairingSecret; + clientPairingSecret.append(clientSecretData); + clientPairingSecret.append(signMessage(clientSecretData)); + + QString secretRespXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, + "pair", + "devicename=roth&updateState=1&clientpairingsecret=" + + clientPairingSecret.toHex(), + true); + m_Http.verifyResponseStatus(secretRespXml); + if (m_Http.getXmlString(secretRespXml, "paired") != "1") + { + qDebug() << "Failed pairing at stage #4"; + m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true); + return PairState::FAILED; + } + + QString pairChallengeXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttps, + "pair", + "devicename=roth&updateState=1&phase=pairchallenge", + true); + m_Http.verifyResponseStatus(pairChallengeXml); + if (m_Http.getXmlString(pairChallengeXml, "paired") != "1") + { + qDebug() << "Failed pairing at stage #5"; + m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true); + return PairState::FAILED; + } + + return PairState::PAIRED; +} diff --git a/nvpairingmanager.h b/nvpairingmanager.h new file mode 100644 index 00000000..a44ef27e --- /dev/null +++ b/nvpairingmanager.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#include +#include +#include + +class NvPairingManager +{ +public: + enum PairState + { + NOT_PAIRED, + PAIRED, + PIN_WRONG, + FAILED, + ALREADY_IN_PROGRESS + }; + + NvPairingManager(QString address, IdentityManager im); + + ~NvPairingManager(); + + QString + generatePinString(); + + PairState + pair(QString serverInfo, QString pin); + +private: + QByteArray + generateRandomBytes(int length); + + QByteArray + saltPin(QByteArray salt, QString pin); + + QByteArray + encrypt(QByteArray plaintext, AES_KEY* key); + + QByteArray + decrypt(QByteArray ciphertext, AES_KEY* key); + + QByteArray + getSignatureFromPemCert(QByteArray certificate); + + bool + verifySignature(QByteArray data, QByteArray signature, QByteArray serverCertificate); + + QByteArray + signMessage(QByteArray message); + + NvHTTP m_Http; + IdentityManager m_Im; + X509* m_Cert; + EVP_PKEY* m_PrivateKey; +}; diff --git a/utils.h b/utils.h new file mode 100644 index 00000000..c5b2897c --- /dev/null +++ b/utils.h @@ -0,0 +1,4 @@ +#pragma once + +#define THROW_BAD_ALLOC_IF_NULL(x) \ + if ((x) == nullptr) throw new std::bad_alloc()