Add read timeouts to HTTP requests that should come back immediately if GFE is working properly

This commit is contained in:
Cameron Gutman 2014-09-27 15:45:13 -07:00
parent c9d003ca6d
commit 5f42ca66fe
2 changed files with 41 additions and 23 deletions

View File

@ -31,6 +31,7 @@ public class NvHTTP {
public static final int PORT = 47984;
public static final int CONNECTION_TIMEOUT = 2000;
public static final int READ_TIMEOUT = 2000;
private final boolean verbose = false;
@ -101,7 +102,7 @@ public class NvHTTP {
}
public String getServerInfo(String uniqueId) throws MalformedURLException, IOException {
return openHttpConnectionToString(baseUrl + "/serverinfo?uniqueid=" + uniqueId);
return openHttpConnectionToString(baseUrl + "/serverinfo?uniqueid=" + uniqueId, true);
}
public ComputerDetails getComputerDetails() throws MalformedURLException, IOException, XmlPullParserException {
@ -146,19 +147,27 @@ public class NvHTTP {
return details;
}
private InputStream openHttpConnection(String url) throws IOException {
// Read timeout should be enabled for any HTTP query that requires no outside action
// on the GFE server. Examples of queries that DO require outside action are launch, resume, and quit.
// The initial pair query does require outside action (user entering a PIN) but subsequent pairing
// queries do not.
private InputStream openHttpConnection(String url, boolean enableReadTimeout) throws IOException {
URLConnection conn = new URL(url).openConnection();
if (verbose) {
System.out.println(url);
}
conn.setConnectTimeout(CONNECTION_TIMEOUT);
if (enableReadTimeout) {
conn.setReadTimeout(READ_TIMEOUT);
}
conn.setUseCaches(false);
conn.connect();
return conn.getInputStream();
}
String openHttpConnectionToString(String url) throws MalformedURLException, IOException {
Scanner s = new Scanner(openHttpConnection(url));
String openHttpConnectionToString(String url, boolean enableReadTimeout) throws MalformedURLException, IOException {
Scanner s = new Scanner(openHttpConnection(url, enableReadTimeout));
String str = "";
while (s.hasNext()) {
@ -207,12 +216,14 @@ public class NvHTTP {
}
public InputStream getBoxArtPng(NvApp app) throws IOException {
// FIXME: Investigate whether this should be subject to the 2 second read timeout
// or not.
return openHttpConnection(baseUrl + "/applist?uniqueid="+uniqueId+"&appid="+
app.getAppId()+"&AssetType=2&AssetIdx=0");
app.getAppId()+"&AssetType=2&AssetIdx=0", false);
}
public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException {
InputStream in = openHttpConnection(baseUrl + "/applist?uniqueid=" + uniqueId);
InputStream in = openHttpConnection(baseUrl + "/applist?uniqueid=" + uniqueId, true);
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
@ -253,7 +264,7 @@ public class NvHTTP {
}
public void unpair() throws IOException {
openHttpConnection(baseUrl + "/unpair?uniqueid=" + uniqueId);
openHttpConnection(baseUrl + "/unpair?uniqueid=" + uniqueId, true);
}
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
@ -274,7 +285,7 @@ public class NvHTTP {
"&mode=" + width + "x" + height + "x" + refreshRate +
"&additionalStates=1&sops=" + (sops ? 1 : 0) +
"&rikey="+bytesToHex(inputKey.getEncoded()) +
"&rikeyid="+riKeyId);
"&rikeyid="+riKeyId, false);
String gameSession = getXmlString(in, "gamesession");
return Integer.parseInt(gameSession);
}
@ -282,13 +293,13 @@ public class NvHTTP {
public boolean resumeApp(SecretKey inputKey, int riKeyId) throws IOException, XmlPullParserException {
InputStream in = openHttpConnection(baseUrl + "/resume?uniqueid=" + uniqueId +
"&rikey="+bytesToHex(inputKey.getEncoded()) +
"&rikeyid="+riKeyId);
"&rikeyid="+riKeyId, false);
String resume = getXmlString(in, "resume");
return Integer.parseInt(resume) != 0;
}
public boolean quitApp() throws IOException, XmlPullParserException {
InputStream in = openHttpConnection(baseUrl + "/cancel?uniqueid=" + uniqueId);
InputStream in = openHttpConnection(baseUrl + "/cancel?uniqueid=" + uniqueId, false);
String cancel = getXmlString(in, "cancel");
return Integer.parseInt(cancel) != 0;
}

View File

@ -235,11 +235,14 @@ public class PairingManager {
byte[] saltAndPin = saltPin(salt, pin);
aesKey = generateAesKey(saltAndPin);
// Send the salt and get the server cert
// Send the salt and get the server cert. This doesn't have a read timeout
// because the user must enter the PIN before the server responds
String getCert = http.openHttpConnectionToString(http.baseUrl +
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&phrase=getservercert&salt="+bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes));
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&phrase=getservercert&salt="+
bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes),
false);
if (!NvHTTP.getXmlString(getCert, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
return PairState.FAILED;
}
X509Certificate serverCert = extractPlainCert(getCert);
@ -250,9 +253,10 @@ public class PairingManager {
// Send the encrypted challenge to the server
String challengeResp = http.openHttpConnectionToString(http.baseUrl +
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&clientchallenge="+bytesToHex(encryptedChallenge));
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&clientchallenge="+bytesToHex(encryptedChallenge),
true);
if (!NvHTTP.getXmlString(challengeResp, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
return PairState.FAILED;
}
@ -268,9 +272,10 @@ public class PairingManager {
byte[] challengeRespHash = toSHA1Bytes(concatBytes(concatBytes(serverChallenge, cert.getSignature()), clientSecret));
byte[] challengeRespEncrypted = encryptAes(challengeRespHash, aesKey);
String secretResp = http.openHttpConnectionToString(http.baseUrl +
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&serverchallengeresp="+bytesToHex(challengeRespEncrypted));
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&serverchallengeresp="+bytesToHex(challengeRespEncrypted),
true);
if (!NvHTTP.getXmlString(secretResp, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
return PairState.FAILED;
}
@ -282,7 +287,7 @@ public class PairingManager {
// Ensure the authenticity of the data
if (!verifySignature(serverSecret, serverSignature, serverCert)) {
// Cancel the pairing process
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
// Looks like a MITM
return PairState.FAILED;
@ -292,7 +297,7 @@ public class PairingManager {
byte[] serverChallengeRespHash = toSHA1Bytes(concatBytes(concatBytes(randomChallenge, serverCert.getSignature()), serverSecret));
if (!Arrays.equals(serverChallengeRespHash, serverResponse)) {
// Cancel the pairing process
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
// Probably got the wrong PIN
return PairState.PIN_WRONG;
@ -301,16 +306,18 @@ public class PairingManager {
// Send the server our signed secret
byte[] clientPairingSecret = concatBytes(clientSecret, signData(clientSecret, pk));
String clientSecretResp = http.openHttpConnectionToString(http.baseUrl +
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&clientpairingsecret="+bytesToHex(clientPairingSecret));
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&clientpairingsecret="+bytesToHex(clientPairingSecret),
true);
if (!NvHTTP.getXmlString(clientSecretResp, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
return PairState.FAILED;
}
// Do the initial challenge (seems neccessary for us to show as paired)
String pairChallenge = http.openHttpConnectionToString(http.baseUrl + "/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&phrase=pairchallenge");
String pairChallenge = http.openHttpConnectionToString(http.baseUrl +
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&phrase=pairchallenge", true);
if (!NvHTTP.getXmlString(pairChallenge, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
return PairState.FAILED;
}