Require cert pinning for HTTPS

This commit is contained in:
Cameron Gutman
2018-12-24 17:48:01 -08:00
parent ecad4aa276
commit 0ddf07f4de
10 changed files with 159 additions and 64 deletions

View File

@@ -237,6 +237,7 @@ function moduleDidLoad() {
revivedHost.serverUid = hosts[hostUID].serverUid;
revivedHost.externalIP = hosts[hostUID].externalIP;
revivedHost.hostname = hosts[hostUID].hostname;
revivedHost.ppkstr = hosts[hostUID].ppkstr;
addHostToGrid(revivedHost);
}
console.log('%c[index.js]', 'color: green;', 'Loaded previously connected hosts');
@@ -277,26 +278,19 @@ function pairTo(nvhttpHost, onSuccess, onFailure) {
pairingDialog.close();
});
console.log('%c[index.js]', 'color: green;', 'Sending pairing request to ' + nvhttpHost.hostname + ' with random number' + randomNumber);
nvhttpHost.pair(randomNumber).then(function(paired) {
if (!paired) {
if (nvhttpHost.currentGame != 0) {
$('#pairingDialogText').html('Error: ' + nvhttpHost.hostname + ' is busy. Stop streaming to pair.');
} else {
$('#pairingDialogText').html('Error: failed to pair with ' + nvhttpHost.hostname + '.');
}
console.log('%c[index.js]', 'color: green;', 'Failed API object:', nvhttpHost, nvhttpHost.toString()); //Logging both the object and the toString version for text logs
onFailure();
return;
}
console.log('%c[index.js]', 'color: green;', 'Sending pairing request to ' + nvhttpHost.hostname + ' with PIN: ' + randomNumber);
nvhttpHost.pair(randomNumber).then(function() {
snackbarLog('Pairing successful');
pairingDialog.close();
onSuccess();
}, function(failedPairing) {
snackbarLog('Failed pairing to: ' + nvhttpHost.hostname);
console.error('%c[index.js]', 'color: green;', 'Pairing failed, and returned:', failedPairing);
console.error('%c[index.js]', 'color: green;', 'Failed API object:', nvhttpHost, nvhttpHost.toString()); //Logging both the object and the toString version for text logs
if (nvhttpHost.currentGame != 0) {
$('#pairingDialogText').html('Error: ' + nvhttpHost.hostname + ' is busy. Stop streaming to pair.');
} else {
$('#pairingDialogText').html('Error: failed to pair with ' + nvhttpHost.hostname + '.');
}
console.log('%c[index.js]', 'color: green;', 'Failed API object:', nvhttpHost, nvhttpHost.toString()); //Logging both the object and the toString version for text logs
onFailure();
});
});

View File

@@ -67,6 +67,7 @@ String.prototype.toHex = function() {
function NvHTTP(address, clientUid, userEnteredAddress = '') {
console.log('%c[utils.js, NvHTTP Object]', 'color: gray;', this);
this.address = address;
this.ppkstr = null;
this.paired = false;
this.currentGame = 0;
this.serverMajorVersion = 0;
@@ -111,29 +112,60 @@ function _base64ToArrayBuffer(base64) {
NvHTTP.prototype = {
refreshServerInfo: function() {
if (this.ppkstr == null) {
return sendMessage('openUrl', [this._baseUrlHttp + '/serverinfo?' + this._buildUidStr(), this.ppkstr, false]).then(function(retHttp) {
this._parseServerInfo(retHttp);
}.bind(this));
}
// try HTTPS first
return sendMessage('openUrl', [this._baseUrlHttps + '/serverinfo?' + this._buildUidStr(), false]).then(function(ret) {
return sendMessage('openUrl', [this._baseUrlHttps + '/serverinfo?' + this._buildUidStr(), this.ppkstr, false]).then(function(ret) {
if (!this._parseServerInfo(ret)) { // if that fails
// try HTTP as a failover. Useful to clients who aren't paired yet
return sendMessage('openUrl', [this._baseUrlHttp + '/serverinfo?' + this._buildUidStr(), false]).then(function(retHttp) {
return sendMessage('openUrl', [this._baseUrlHttp + '/serverinfo?' + this._buildUidStr(), this.ppkstr, false]).then(function(retHttp) {
this._parseServerInfo(retHttp);
}.bind(this));
}
}.bind(this));
}.bind(this),
function(error) {
if (error == -100) { // GS_CERT_MISMATCH
// Retry over HTTP
console.warn('%c[utils.js, utils.js, refreshServerInfo]', 'color: gray;', 'Certificate mismatch. Retrying over HTTP', this);
return sendMessage('openUrl', [this._baseUrlHttp + '/serverinfo?' + this._buildUidStr(), this.ppkstr, false]).then(function(retHttp) {
this._parseServerInfo(retHttp);
}.bind(this));
}
}.bind(this));
},
// refreshes the server info using a given address. This is useful for testing whether we can successfully ping a host at a given address
refreshServerInfoAtAddress: function(givenAddress) {
if (this.ppkstr == null) {
// Use HTTP if we have no pinned cert
return sendMessage('openUrl', ['http://' + givenAddress + ':47989' + '/serverinfo?' + this._buildUidStr(), this.ppkstr, false]).then(function(retHttp) {
return this._parseServerInfo(retHttp);
}.bind(this));
}
// try HTTPS first
return sendMessage('openUrl', ['https://' + givenAddress + ':47984' + '/serverinfo?' + this._buildUidStr(), false]).then(function(ret) {
return sendMessage('openUrl', ['https://' + givenAddress + ':47984' + '/serverinfo?' + this._buildUidStr(), this.ppkstr, false]).then(function(ret) {
if (!this._parseServerInfo(ret)) { // if that fails
console.log('%c[utils.js, utils.js, refreshServerInfoAtAddress]', 'color: gray;', 'Failed to parse serverinfo from HTTPS, falling back to HTTP');
console.log('%c[utils.js, utils.js, refreshServerInfoAtAddress]', 'color: gray;', 'Failed to parse serverinfo from HTTPS, falling back to HTTP');
// try HTTP as a failover. Useful to clients who aren't paired yet
return sendMessage('openUrl', ['http://' + givenAddress + ':47989' + '/serverinfo?' + this._buildUidStr(), false]).then(function(retHttp) {
return sendMessage('openUrl', ['http://' + givenAddress + ':47989' + '/serverinfo?' + this._buildUidStr(), this.ppkstr, false]).then(function(retHttp) {
return this._parseServerInfo(retHttp);
}.bind(this));
}
}.bind(this));
}.bind(this),
function(error) {
if (error == -100) { // GS_CERT_MISMATCH
// Retry over HTTP
console.warn('%c[utils.js, utils.js, refreshServerInfoAtAddress]', 'color: gray;', 'Certificate mismatch. Retrying over HTTP', this);
return sendMessage('openUrl', ['http://' + givenAddress + ':47989' + '/serverinfo?' + this._buildUidStr(), this.ppkstr, false]).then(function(retHttp) {
return this._parseServerInfo(retHttp);
}.bind(this));
}
}.bind(this));
},
// called every few seconds to poll the server for updated info
@@ -157,7 +189,7 @@ NvHTTP.prototype = {
// Poll for the app list every 10 successful serverinfo polls.
// Not including the first one to avoid PCs taking a while to show
// as online initially
if (this._pollCount++ % 10 == 1) {
if (this.paired && this._pollCount++ % 10 == 1) {
this.getAppListWithCacheFlush();
}
@@ -318,7 +350,7 @@ NvHTTP.prototype = {
},
getAppListWithCacheFlush: function() {
return sendMessage('openUrl', [this._baseUrlHttps + '/applist?' + this._buildUidStr(), false]).then(function(ret) {
return sendMessage('openUrl', [this._baseUrlHttps + '/applist?' + this._buildUidStr(), this.ppkstr, false]).then(function(ret) {
$xml = this._parseXML(ret);
$root = $xml.find("root");
@@ -377,6 +409,7 @@ NvHTTP.prototype = {
'/appasset?' + this._buildUidStr() +
'&appid=' + appId +
'&AssetType=2&AssetIdx=0',
this.ppkstr,
true
]).then(function(boxArtBuffer) {
var reader = new FileReader();
@@ -405,6 +438,7 @@ NvHTTP.prototype = {
'/appasset?' + this._buildUidStr() +
'&appid=' + appId +
'&AssetType=2&AssetIdx=0',
this.ppkstr,
true
]);
}
@@ -423,6 +457,7 @@ NvHTTP.prototype = {
'&surroundAudioInfo=' + surroundAudioInfo +
'&remoteControllersBitmap=' + gamepadMask +
'&gcmap=' + gamepadMask,
this.ppkstr,
false
]);
},
@@ -434,12 +469,13 @@ NvHTTP.prototype = {
'&rikey=' + rikey +
'&rikeyid=' + rikeyid +
'&surroundAudioInfo=' + surroundAudioInfo,
this.ppkstr,
false
]);
},
quitApp: function() {
return sendMessage('openUrl', [this._baseUrlHttps + '/cancel?' + this._buildUidStr(), false])
return sendMessage('openUrl', [this._baseUrlHttps + '/cancel?' + this._buildUidStr(), this.ppkstr, false])
// Refresh server info after quitting because it may silently fail if the
// session belongs to a different client.
// TODO: We should probably bubble this up to our caller.
@@ -460,14 +496,12 @@ NvHTTP.prototype = {
pair: function(randomNumber) {
return this.refreshServerInfo().then(function() {
if (this.paired)
if (this.paired && this.ppkstr)
return true;
if (this.currentGame != 0)
return false;
return sendMessage('pair', [this.serverMajorVersion.toString(), this.address, randomNumber]).then(function(pairStatus) {
return sendMessage('openUrl', [this._baseUrlHttps + '/pair?uniqueid=' + this.clientUid + '&devicename=roth&updateState=1&phrase=pairchallenge', false]).then(function(ret) {
return sendMessage('pair', [this.serverMajorVersion.toString(), this.address, randomNumber]).then(function(ppkstr) {
this.ppkstr = ppkstr;
return sendMessage('openUrl', [this._baseUrlHttps + '/pair?uniqueid=' + this.clientUid + '&devicename=roth&updateState=1&phrase=pairchallenge', this.ppkstr, false]).then(function(ret) {
$xml = this._parseXML(ret);
this.paired = $xml.find('paired').html() == "1";
return this.paired;