Require cert pinning for HTTPS

This commit is contained in:
Cameron Gutman
2018-12-22 19:55:28 -08:00
parent 1e98594972
commit 61d7aa0400
4 changed files with 128 additions and 65 deletions
+40 -5
View File
@@ -538,12 +538,8 @@ signals:
void computerStateChanged(NvComputer* computer); void computerStateChanged(NvComputer* computer);
private: private:
void run() QString fetchServerInfo(NvHTTP& http)
{ {
NvHTTP http(m_Address, QSslCertificate());
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user");
QString serverInfo; QString serverInfo;
try { try {
// There's a race condition between GameStream servers reporting presence over // There's a race condition between GameStream servers reporting presence over
@@ -564,15 +560,52 @@ private:
throw e; throw e;
} }
} }
return serverInfo;
} catch (...) { } catch (...) {
if (!m_Mdns) { if (!m_Mdns) {
emit computerAddCompleted(false); emit computerAddCompleted(false);
} }
return QString();
}
}
void run()
{
NvHTTP http(m_Address, QSslCertificate());
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user");
// Perform initial serverinfo fetch over HTTP since we don't know which cert to use
QString serverInfo = fetchServerInfo(http);
if (serverInfo.isEmpty()) {
return; return;
} }
// Create initial newComputer using HTTP serverinfo with no pinned cert
NvComputer* newComputer = new NvComputer(m_Address, serverInfo, QSslCertificate()); NvComputer* newComputer = new NvComputer(m_Address, serverInfo, QSslCertificate());
// Check if we have a record of this host UUID to pull the pinned cert
NvComputer* existingComputer;
{
QReadLocker lock(&m_ComputerManager->m_Lock);
existingComputer = m_ComputerManager->m_KnownHosts[newComputer->uuid];
if (existingComputer != nullptr) {
http.setServerCert(existingComputer->serverCert);
}
}
// Fetch serverinfo again over HTTPS with the pinned cert
if (existingComputer != nullptr) {
serverInfo = fetchServerInfo(http);
if (serverInfo.isEmpty()) {
return;
}
// Update the polled computer with the HTTPS information
NvComputer httpsComputer(m_Address, serverInfo, QSslCertificate());
newComputer->update(httpsComputer);
}
// Update addresses depending on the context // Update addresses depending on the context
if (m_Mdns) { if (m_Mdns) {
newComputer->localAddress = m_Address; newComputer->localAddress = m_Address;
@@ -591,6 +624,7 @@ private:
newComputer->manualAddress = m_Address; newComputer->manualAddress = m_Address;
} }
{
// Check if this PC already exists // Check if this PC already exists
QWriteLocker lock(&m_ComputerManager->m_Lock); QWriteLocker lock(&m_ComputerManager->m_Lock);
NvComputer* existingComputer = m_ComputerManager->m_KnownHosts[newComputer->uuid]; NvComputer* existingComputer = m_ComputerManager->m_KnownHosts[newComputer->uuid];
@@ -632,6 +666,7 @@ private:
emit computerStateChanged(newComputer); emit computerStateChanged(newComputer);
} }
} }
}
ComputerManager* m_ComputerManager; ComputerManager* m_ComputerManager;
QString m_Address; QString m_Address;
+21 -6
View File
@@ -32,6 +32,11 @@ NvHTTP::NvHTTP(QString address, QSslCertificate serverCert) :
m_Nam.setProxy(noProxy); m_Nam.setProxy(noProxy);
} }
void NvHTTP::setServerCert(QSslCertificate serverCert)
{
m_ServerCert = serverCert;
}
QVector<int> QVector<int>
NvHTTP::parseQuad(QString quad) NvHTTP::parseQuad(QString quad)
{ {
@@ -74,6 +79,9 @@ NvHTTP::getServerInfo(NvLogLevel logLevel)
{ {
QString serverInfo; QString serverInfo;
// Check if we have a pinned cert for this host yet
if (!m_ServerCert.isNull())
{
try try
{ {
// Always try HTTPS first, since it properly reports // Always try HTTPS first, since it properly reports
@@ -104,6 +112,17 @@ NvHTTP::getServerInfo(NvLogLevel logLevel)
throw e; throw e;
} }
} }
}
else
{
// Only use HTTP prior to pairing
serverInfo = openConnectionToString(m_BaseUrlHttp,
"serverinfo",
nullptr,
true,
logLevel);
verifyResponseStatus(serverInfo);
}
return serverInfo; return serverInfo;
} }
@@ -391,11 +410,7 @@ NvHTTP::openConnection(QUrl baseUrl,
QNetworkReply* reply = m_Nam.get(request); QNetworkReply* reply = m_Nam.get(request);
if (m_ServerCert.isNull()) { if (!m_ServerCert.isNull()) {
// No server cert yet
reply->ignoreSslErrors();
}
else {
// Pin the server certificate received during pairing // Pin the server certificate received during pairing
QList<QSslError> expectedSslErrors; QList<QSslError> expectedSslErrors;
expectedSslErrors.append(QSslError(QSslError::HostNameMismatch, m_ServerCert)); expectedSslErrors.append(QSslError(QSslError::HostNameMismatch, m_ServerCert));
@@ -435,7 +450,7 @@ NvHTTP::openConnection(QUrl baseUrl,
qWarning() << command << " request failed with error " << reply->error(); qWarning() << command << " request failed with error " << reply->error();
} }
if (!m_ServerCert.isNull() && reply->error() == QNetworkReply::SslHandshakeFailedError) { if (reply->error() == QNetworkReply::SslHandshakeFailedError) {
// This will trigger falling back to HTTP for the serverinfo query // This will trigger falling back to HTTP for the serverinfo query
// then pairing again to get the updated certificate. // then pairing again to get the updated certificate.
GfeHttpResponseException exception(401, "Server certificate mismatch"); GfeHttpResponseException exception(401, "Server certificate mismatch");
+2
View File
@@ -152,6 +152,8 @@ public:
bool enableTimeout, bool enableTimeout,
NvLogLevel logLevel = NvLogLevel::VERBOSE); NvLogLevel logLevel = NvLogLevel::VERBOSE);
void setServerCert(QSslCertificate serverCert);
static static
QVector<int> QVector<int>
parseQuad(QString quad); parseQuad(QString quad);
+12 -1
View File
@@ -208,6 +208,18 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
return PairState::ALREADY_IN_PROGRESS; return PairState::ALREADY_IN_PROGRESS;
} }
serverCert = QSslCertificate(serverCertStr);
if (serverCert.isNull()) {
Q_ASSERT(!serverCert.isNull());
qCritical() << "Failed to parse plaincert";
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, true);
return PairState::FAILED;
}
// Pin this cert for TLS
m_Http.setServerCert(serverCert);
QByteArray randomChallenge = generateRandomBytes(16); QByteArray randomChallenge = generateRandomBytes(16);
QByteArray encryptedChallenge = encrypt(randomChallenge, &encKey); QByteArray encryptedChallenge = encrypt(randomChallenge, &encKey);
QString challengeXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, QString challengeXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp,
@@ -309,6 +321,5 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
return PairState::FAILED; return PairState::FAILED;
} }
serverCert = QSslCertificate(serverCertStr);
return PairState::PAIRED; return PairState::PAIRED;
} }