moonlight-qt/app/backend/nvpairingmanager.cpp

328 lines
12 KiB
C++
Raw Normal View History

2018-04-29 02:01:00 +00:00
#include "nvpairingmanager.h"
2018-04-29 05:14:27 +00:00
#include "utils.h"
2018-04-29 02:01:00 +00:00
2018-04-29 05:14:27 +00:00
#include <openssl/bio.h>
2018-04-29 02:01:00 +00:00
#include <openssl/aes.h>
2018-04-29 05:14:27 +00:00
#include <openssl/rand.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/evp.h>
2018-04-29 02:01:00 +00:00
2019-01-06 22:35:33 +00:00
#define REQUEST_TIMEOUT_MS 5000
2018-06-27 02:01:40 +00:00
NvPairingManager::NvPairingManager(QString address) :
2018-12-22 02:08:07 +00:00
m_Http(address, QSslCertificate())
2018-04-29 02:01:00 +00:00
{
2018-06-27 02:01:40 +00:00
QByteArray cert = IdentityManager::get()->getCertificate();
2018-04-29 07:55:18 +00:00
BIO *bio = BIO_new_mem_buf(cert.data(), -1);
2018-04-29 05:14:27 +00:00
THROW_BAD_ALLOC_IF_NULL(bio);
2018-04-29 02:01:00 +00:00
2018-04-29 05:14:27 +00:00
m_Cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
BIO_free_all(bio);
if (m_Cert == nullptr)
{
throw std::runtime_error("Unable to load certificate");
2018-04-29 05:14:27 +00:00
}
2018-06-27 02:01:40 +00:00
QByteArray pk = IdentityManager::get()->getPrivateKey();
2018-04-29 07:55:18 +00:00
bio = BIO_new_mem_buf(pk.data(), -1);
2018-04-29 05:14:27 +00:00
THROW_BAD_ALLOC_IF_NULL(bio);
m_PrivateKey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr);
2018-04-29 05:14:27 +00:00
BIO_free_all(bio);
if (m_PrivateKey == nullptr)
2018-04-29 05:14:27 +00:00
{
throw std::runtime_error("Unable to load private key");
2018-04-29 05:14:27 +00:00
}
}
NvPairingManager::~NvPairingManager()
{
X509_free(m_Cert);
EVP_PKEY_free(m_PrivateKey);
2018-04-29 02:01:00 +00:00
}
QByteArray
NvPairingManager::generateRandomBytes(int length)
{
2018-04-29 05:14:27 +00:00
char* data = static_cast<char*>(alloca(length));
RAND_bytes(reinterpret_cast<unsigned char*>(data), length);
return QByteArray(data, length);
}
QByteArray
NvPairingManager::encrypt(QByteArray plaintext, AES_KEY* key)
{
QByteArray ciphertext(plaintext.size(), 0);
2018-04-29 02:01:00 +00:00
2018-04-29 05:14:27 +00:00
for (int i = 0; i < plaintext.size(); i += 16)
2018-04-29 02:01:00 +00:00
{
2018-04-29 05:14:27 +00:00
AES_encrypt(reinterpret_cast<unsigned char*>(&plaintext.data()[i]),
reinterpret_cast<unsigned char*>(&ciphertext.data()[i]),
key);
2018-04-29 02:01:00 +00:00
}
2018-04-29 05:14:27 +00:00
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<unsigned char*>(&ciphertext.data()[i]),
reinterpret_cast<unsigned char*>(&plaintext.data()[i]),
key);
}
return plaintext;
}
2018-04-29 08:48:41 +00:00
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);
2018-07-22 02:02:12 +00:00
#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
ASN1_BIT_STRING *asnSignature = cert->signature;
#elif (OPENSSL_VERSION_NUMBER < 0x10100000L)
ASN1_BIT_STRING *asnSignature;
X509_get0_signature(&asnSignature, NULL, cert);
#else
2018-07-22 02:02:12 +00:00
const ASN1_BIT_STRING *asnSignature;
2018-04-29 08:48:41 +00:00
X509_get0_signature(&asnSignature, NULL, cert);
2018-07-22 02:02:12 +00:00
#endif
2018-04-29 08:48:41 +00:00
QByteArray signature(reinterpret_cast<char*>(asnSignature->data), asnSignature->length);
X509_free(cert);
return signature;
}
2018-04-29 05:14:27 +00:00
bool
2018-04-29 08:48:41 +00:00
NvPairingManager::verifySignature(QByteArray data, QByteArray signature, QByteArray serverCertificate)
2018-04-29 05:14:27 +00:00
{
2018-04-29 08:48:41 +00:00
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);
2018-04-29 05:14:27 +00:00
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<unsigned char*>(signature.data()), signature.length());
EVP_PKEY_free(pubKey);
EVP_MD_CTX_destroy(mdctx);
2018-04-29 08:48:41 +00:00
X509_free(cert);
2018-04-29 05:14:27 +00:00
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<unsigned char*>(message.data()), message.length());
size_t signatureLength = 0;
EVP_DigestSignFinal(ctx, NULL, &signatureLength);
2018-06-24 05:16:59 +00:00
QByteArray signature((int)signatureLength, 0);
2018-04-29 05:14:27 +00:00
EVP_DigestSignFinal(ctx, reinterpret_cast<unsigned char*>(signature.data()), &signatureLength);
EVP_MD_CTX_destroy(ctx);
return signature;
2018-04-29 02:01:00 +00:00
}
QByteArray
NvPairingManager::saltPin(QByteArray salt, QString pin)
{
2018-04-29 05:14:27 +00:00
return QByteArray().append(salt).append(pin.toLatin1());
2018-04-29 02:01:00 +00:00
}
NvPairingManager::PairState
2018-12-22 02:08:07 +00:00
NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverCert)
2018-04-29 02:01:00 +00:00
{
2018-07-06 06:12:55 +00:00
int serverMajorVersion = NvHTTP::parseQuad(appVersion).at(0);
qInfo() << "Pairing with server generation:" << serverMajorVersion;
2018-04-29 02:01:00 +00:00
QCryptographicHash::Algorithm hashAlgo;
2018-04-29 05:14:27 +00:00
int hashLength;
2018-04-29 02:01:00 +00:00
if (serverMajorVersion >= 7)
{
// Gen 7+ uses SHA-256 hashing
hashAlgo = QCryptographicHash::Sha256;
2018-04-29 05:14:27 +00:00
hashLength = 32;
2018-04-29 02:01:00 +00:00
}
else
{
// Prior to Gen 7 uses SHA-1 hashing
hashAlgo = QCryptographicHash::Sha1;
2018-04-29 05:14:27 +00:00
hashLength = 20;
2018-04-29 02:01:00 +00:00
}
QByteArray salt = generateRandomBytes(16);
QByteArray saltedPin = saltPin(salt, pin);
AES_KEY encKey, decKey;
2018-04-29 05:14:27 +00:00
AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(QCryptographicHash::hash(saltedPin, hashAlgo).data()), 128, &decKey);
AES_set_encrypt_key(reinterpret_cast<const unsigned char*>(QCryptographicHash::hash(saltedPin, hashAlgo).data()), 128, &encKey);
2018-04-29 02:01:00 +00:00
QString getCert = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp,
"pair",
2018-04-29 05:14:27 +00:00
"devicename=roth&updateState=1&phrase=getservercert&salt=" +
2018-06-27 02:01:40 +00:00
salt.toHex() + "&clientcert=" + IdentityManager::get()->getCertificate().toHex(),
2019-01-06 22:35:33 +00:00
0);
2018-06-27 04:49:33 +00:00
NvHTTP::verifyResponseStatus(getCert);
if (NvHTTP::getXmlString(getCert, "paired") != "1")
2018-04-29 05:14:27 +00:00
{
qCritical() << "Failed pairing at stage #1";
2018-04-29 05:14:27 +00:00
return PairState::FAILED;
}
2018-12-22 02:08:07 +00:00
QByteArray serverCertStr = NvHTTP::getXmlStringFromHex(getCert, "plaincert");
if (serverCertStr == nullptr)
2018-04-29 08:48:41 +00:00
{
qCritical() << "Server likely already pairing";
2019-01-06 22:35:33 +00:00
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
2018-04-29 08:48:41 +00:00
return PairState::ALREADY_IN_PROGRESS;
}
2018-12-23 03:55:28 +00:00
serverCert = QSslCertificate(serverCertStr);
if (serverCert.isNull()) {
Q_ASSERT(!serverCert.isNull());
qCritical() << "Failed to parse plaincert";
2019-01-06 22:35:33 +00:00
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
2018-12-23 03:55:28 +00:00
return PairState::FAILED;
}
// Pin this cert for TLS
m_Http.setServerCert(serverCert);
2018-04-29 08:48:41 +00:00
QByteArray randomChallenge = generateRandomBytes(16);
QByteArray encryptedChallenge = encrypt(randomChallenge, &encKey);
2018-04-29 05:14:27 +00:00
QString challengeXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp,
"pair",
"devicename=roth&updateState=1&clientchallenge=" +
encryptedChallenge.toHex(),
2019-01-06 22:35:33 +00:00
REQUEST_TIMEOUT_MS);
2018-06-27 04:49:33 +00:00
NvHTTP::verifyResponseStatus(challengeXml);
if (NvHTTP::getXmlString(challengeXml, "paired") != "1")
2018-04-29 05:14:27 +00:00
{
qCritical() << "Failed pairing at stage #2";
2019-01-06 22:35:33 +00:00
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
2018-04-29 05:14:27 +00:00
return PairState::FAILED;
}
2018-04-29 08:48:41 +00:00
QByteArray challengeResponseData = decrypt(m_Http.getXmlStringFromHex(challengeXml, "challengeresponse"), &decKey);
2018-04-29 05:14:27 +00:00
QByteArray clientSecretData = generateRandomBytes(16);
QByteArray challengeResponse;
2018-04-29 08:48:41 +00:00
QByteArray serverResponse(challengeResponseData.data(), hashLength);
2018-04-29 05:14:27 +00:00
2018-07-22 02:02:12 +00:00
#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
ASN1_BIT_STRING *asnSignature = m_Cert->signature;
#elif (OPENSSL_VERSION_NUMBER < 0x10100000L)
ASN1_BIT_STRING *asnSignature;
2018-07-22 02:02:12 +00:00
X509_get0_signature(&asnSignature, NULL, m_Cert);
#else
const ASN1_BIT_STRING *asnSignature;
2018-04-29 05:14:27 +00:00
X509_get0_signature(&asnSignature, NULL, m_Cert);
2018-07-22 02:02:12 +00:00
#endif
2018-04-29 05:14:27 +00:00
challengeResponse.append(challengeResponseData.data() + hashLength, 16);
2018-04-29 08:48:41 +00:00
challengeResponse.append(reinterpret_cast<char*>(asnSignature->data), asnSignature->length);
2018-04-29 05:14:27 +00:00
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(),
2019-01-06 22:35:33 +00:00
REQUEST_TIMEOUT_MS);
2018-06-27 04:49:33 +00:00
NvHTTP::verifyResponseStatus(respXml);
if (NvHTTP::getXmlString(respXml, "paired") != "1")
2018-04-29 05:14:27 +00:00
{
qCritical() << "Failed pairing at stage #3";
2019-01-06 22:35:33 +00:00
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
2018-04-29 05:14:27 +00:00
return PairState::FAILED;
}
2018-06-27 04:49:33 +00:00
QByteArray pairingSecret = NvHTTP::getXmlStringFromHex(respXml, "pairingsecret");
2018-04-29 08:48:41 +00:00
QByteArray serverSecret = QByteArray(pairingSecret.data(), 16);
QByteArray serverSignature = QByteArray(&pairingSecret.data()[16], 256);
2018-04-29 05:14:27 +00:00
2018-04-29 08:48:41 +00:00
if (!verifySignature(serverSecret,
serverSignature,
2018-12-22 02:08:07 +00:00
serverCertStr))
2018-04-29 05:14:27 +00:00
{
qCritical() << "MITM detected";
2019-01-06 22:35:33 +00:00
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
2018-04-29 05:14:27 +00:00
return PairState::FAILED;
}
2018-04-29 08:48:41 +00:00
QByteArray expectedResponseData;
expectedResponseData.append(randomChallenge);
2018-12-22 02:08:07 +00:00
expectedResponseData.append(getSignatureFromPemCert(serverCertStr));
2018-04-29 08:48:41 +00:00
expectedResponseData.append(serverSecret);
if (QCryptographicHash::hash(expectedResponseData, hashAlgo) != serverResponse)
{
qCritical() << "Incorrect PIN";
2019-01-06 22:35:33 +00:00
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
2018-04-29 08:48:41 +00:00
return PairState::PIN_WRONG;
}
2018-04-29 05:14:27 +00:00
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(),
2019-01-06 22:35:33 +00:00
REQUEST_TIMEOUT_MS);
2018-06-27 04:49:33 +00:00
NvHTTP::verifyResponseStatus(secretRespXml);
if (NvHTTP::getXmlString(secretRespXml, "paired") != "1")
2018-04-29 05:14:27 +00:00
{
qCritical() << "Failed pairing at stage #4";
2019-01-06 22:35:33 +00:00
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
2018-04-29 05:14:27 +00:00
return PairState::FAILED;
}
QString pairChallengeXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttps,
"pair",
2018-06-27 05:58:15 +00:00
"devicename=roth&updateState=1&phrase=pairchallenge",
2019-01-06 22:35:33 +00:00
REQUEST_TIMEOUT_MS);
2018-06-27 04:49:33 +00:00
NvHTTP::verifyResponseStatus(pairChallengeXml);
if (NvHTTP::getXmlString(pairChallengeXml, "paired") != "1")
2018-04-29 05:14:27 +00:00
{
qCritical() << "Failed pairing at stage #5";
2019-01-06 22:35:33 +00:00
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
2018-04-29 05:14:27 +00:00
return PairState::FAILED;
}
return PairState::PAIRED;
2018-04-29 02:01:00 +00:00
}