mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-23 04:52:48 +00:00
Add an Android TLS hack to fix GFE 2.1.4 and migrate to OkHttp classes for HTTPS
This commit is contained in:
parent
2cc2d05c2f
commit
7bc325fa08
@ -5,5 +5,7 @@
|
|||||||
<classpathentry kind="lib" path="libs/xpp3-1.1.4c.jar"/>
|
<classpathentry kind="lib" path="libs/xpp3-1.1.4c.jar"/>
|
||||||
<classpathentry kind="lib" path="libs/tinyrtsp.jar"/>
|
<classpathentry kind="lib" path="libs/tinyrtsp.jar"/>
|
||||||
<classpathentry kind="lib" path="libs/jmdns-fixed.jar"/>
|
<classpathentry kind="lib" path="libs/jmdns-fixed.jar"/>
|
||||||
|
<classpathentry kind="lib" path="libs/okhttp-2.1.0-RC1.jar"/>
|
||||||
|
<classpathentry kind="lib" path="libs/okio-1.0.1.jar"/>
|
||||||
<classpathentry kind="output" path="bin"/>
|
<classpathentry kind="output" path="bin"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
BIN
moonlight-common/libs/okhttp-2.1.0-RC1.jar
Normal file
BIN
moonlight-common/libs/okhttp-2.1.0-RC1.jar
Normal file
Binary file not shown.
BIN
moonlight-common/libs/okio-1.0.1.jar
Normal file
BIN
moonlight-common/libs/okio-1.0.1.jar
Normal file
Binary file not shown.
@ -8,14 +8,25 @@ import java.io.StringReader;
|
|||||||
import java.net.Inet6Address;
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.Socket;
|
||||||
import java.net.URLConnection;
|
import java.security.Principal;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509KeyManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
@ -23,6 +34,9 @@ import org.xmlpull.v1.XmlPullParserFactory;
|
|||||||
|
|
||||||
import com.limelight.nvstream.StreamConfiguration;
|
import com.limelight.nvstream.StreamConfiguration;
|
||||||
import com.limelight.nvstream.http.PairingManager.PairState;
|
import com.limelight.nvstream.http.PairingManager.PairState;
|
||||||
|
import com.squareup.okhttp.OkHttpClient;
|
||||||
|
import com.squareup.okhttp.Request;
|
||||||
|
import com.squareup.okhttp.ResponseBody;
|
||||||
|
|
||||||
|
|
||||||
public class NvHTTP {
|
public class NvHTTP {
|
||||||
@ -38,6 +52,51 @@ public class NvHTTP {
|
|||||||
|
|
||||||
public String baseUrl;
|
public String baseUrl;
|
||||||
|
|
||||||
|
private OkHttpClient httpClient = new OkHttpClient();
|
||||||
|
private OkHttpClient httpClientWithReadTimeout;
|
||||||
|
|
||||||
|
private TrustManager[] trustAllCerts;
|
||||||
|
private KeyManager[] ourKeyman;
|
||||||
|
|
||||||
|
private void initializeHttpState(final LimelightCryptoProvider cryptoProvider) {
|
||||||
|
trustAllCerts = new TrustManager[] {
|
||||||
|
new X509TrustManager() {
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return new X509Certificate[0];
|
||||||
|
}
|
||||||
|
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
||||||
|
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
|
||||||
|
}};
|
||||||
|
|
||||||
|
ourKeyman = new KeyManager[] {
|
||||||
|
new X509KeyManager() {
|
||||||
|
public String chooseClientAlias(String[] keyTypes,
|
||||||
|
Principal[] issuers, Socket socket) { return "Limelight-RSA"; }
|
||||||
|
public String chooseServerAlias(String keyType, Principal[] issuers,
|
||||||
|
Socket socket) { return null; }
|
||||||
|
public X509Certificate[] getCertificateChain(String alias) {
|
||||||
|
return new X509Certificate[] {cryptoProvider.getClientCertificate()};
|
||||||
|
}
|
||||||
|
public String[] getClientAliases(String keyType, Principal[] issuers) { return null; }
|
||||||
|
public PrivateKey getPrivateKey(String alias) {
|
||||||
|
return cryptoProvider.getClientPrivateKey();
|
||||||
|
}
|
||||||
|
public String[] getServerAliases(String keyType, Principal[] issuers) { return null; }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ignore differences between given hostname and certificate hostname
|
||||||
|
HostnameVerifier hv = new HostnameVerifier() {
|
||||||
|
public boolean verify(String hostname, SSLSession session) { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
httpClient.setHostnameVerifier(hv);
|
||||||
|
httpClient.setConnectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
httpClientWithReadTimeout = httpClient.clone();
|
||||||
|
httpClientWithReadTimeout.setReadTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
public NvHTTP(InetAddress host, String uniqueId, String deviceName, LimelightCryptoProvider cryptoProvider) {
|
public NvHTTP(InetAddress host, String uniqueId, String deviceName, LimelightCryptoProvider cryptoProvider) {
|
||||||
this.uniqueId = uniqueId;
|
this.uniqueId = uniqueId;
|
||||||
this.address = host;
|
this.address = host;
|
||||||
@ -51,6 +110,8 @@ public class NvHTTP {
|
|||||||
safeAddress = host.getHostAddress();
|
safeAddress = host.getHostAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializeHttpState(cryptoProvider);
|
||||||
|
|
||||||
this.baseUrl = "https://" + safeAddress + ":" + PORT;
|
this.baseUrl = "https://" + safeAddress + ":" + PORT;
|
||||||
this.pm = new PairingManager(this, cryptoProvider);
|
this.pm = new PairingManager(this, cryptoProvider);
|
||||||
}
|
}
|
||||||
@ -148,27 +209,41 @@ public class NvHTTP {
|
|||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This hack is Android-specific but we do it on all platforms
|
||||||
|
// because it doesn't really matter
|
||||||
|
private void performAndroidTlsHack(OkHttpClient client) {
|
||||||
|
// Doing this each time we create a socket is required
|
||||||
|
// to avoid the SSLv3 fallback that causes connection failures
|
||||||
|
try {
|
||||||
|
SSLContext sc = SSLContext.getInstance("TLSv1");
|
||||||
|
sc.init(ourKeyman, trustAllCerts, new SecureRandom());
|
||||||
|
|
||||||
|
client.setSslSocketFactory(sc.getSocketFactory());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Read timeout should be enabled for any HTTP query that requires no outside action
|
// 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.
|
// 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
|
// The initial pair query does require outside action (user entering a PIN) but subsequent pairing
|
||||||
// queries do not.
|
// queries do not.
|
||||||
private InputStream openHttpConnection(String url, boolean enableReadTimeout) throws IOException {
|
private ResponseBody openHttpConnection(String url, boolean enableReadTimeout) throws IOException {
|
||||||
URLConnection conn = new URL(url).openConnection();
|
Request request = new Request.Builder().url(url).build();
|
||||||
if (verbose) {
|
|
||||||
System.out.println(url);
|
|
||||||
}
|
|
||||||
conn.setConnectTimeout(CONNECTION_TIMEOUT);
|
|
||||||
if (enableReadTimeout) {
|
|
||||||
conn.setReadTimeout(READ_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.setUseCaches(false);
|
if (enableReadTimeout) {
|
||||||
conn.connect();
|
performAndroidTlsHack(httpClientWithReadTimeout);
|
||||||
return conn.getInputStream();
|
return httpClientWithReadTimeout.newCall(request).execute().body();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
performAndroidTlsHack(httpClient);
|
||||||
|
return httpClient.newCall(request).execute().body();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String openHttpConnectionToString(String url, boolean enableReadTimeout) throws MalformedURLException, IOException {
|
String openHttpConnectionToString(String url, boolean enableReadTimeout) throws MalformedURLException, IOException {
|
||||||
Scanner s = new Scanner(openHttpConnection(url, enableReadTimeout));
|
ResponseBody resp = openHttpConnection(url, enableReadTimeout);
|
||||||
|
Scanner s = new Scanner(resp.byteStream());
|
||||||
|
|
||||||
String str = "";
|
String str = "";
|
||||||
while (s.hasNext()) {
|
while (s.hasNext()) {
|
||||||
@ -176,6 +251,7 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.close();
|
s.close();
|
||||||
|
resp.close();
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
System.out.println(str);
|
System.out.println(str);
|
||||||
@ -216,20 +292,13 @@ public class NvHTTP {
|
|||||||
return pm.pair(uniqueId, pin);
|
return pm.pair(uniqueId, pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 + "/appasset?uniqueid="+uniqueId+"&appid="+
|
|
||||||
app.getAppId()+"&AssetType=2&AssetIdx=0", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException {
|
public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException {
|
||||||
InputStream in = openHttpConnection(baseUrl + "/applist?uniqueid=" + uniqueId, true);
|
ResponseBody resp = openHttpConnection(baseUrl + "/applist?uniqueid=" + uniqueId, true);
|
||||||
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
||||||
factory.setNamespaceAware(true);
|
factory.setNamespaceAware(true);
|
||||||
XmlPullParser xpp = factory.newPullParser();
|
XmlPullParser xpp = factory.newPullParser();
|
||||||
|
|
||||||
xpp.setInput(new InputStreamReader(in));
|
xpp.setInput(new InputStreamReader(resp.byteStream()));
|
||||||
int eventType = xpp.getEventType();
|
int eventType = xpp.getEventType();
|
||||||
LinkedList<NvApp> appList = new LinkedList<NvApp>();
|
LinkedList<NvApp> appList = new LinkedList<NvApp>();
|
||||||
Stack<String> currentTag = new Stack<String>();
|
Stack<String> currentTag = new Stack<String>();
|
||||||
@ -261,11 +330,14 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
eventType = xpp.next();
|
eventType = xpp.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resp.close();
|
||||||
|
|
||||||
return appList;
|
return appList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unpair() throws IOException {
|
public void unpair() throws IOException {
|
||||||
openHttpConnection(baseUrl + "/unpair?uniqueid=" + uniqueId, true);
|
openHttpConnectionToString(baseUrl + "/unpair?uniqueid=" + uniqueId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
||||||
@ -280,7 +352,7 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int launchApp(int appId, SecretKey inputKey, int riKeyId, StreamConfiguration config) throws IOException, XmlPullParserException {
|
public int launchApp(int appId, SecretKey inputKey, int riKeyId, StreamConfiguration config) throws IOException, XmlPullParserException {
|
||||||
InputStream in = openHttpConnection(baseUrl +
|
String xmlStr = openHttpConnectionToString(baseUrl +
|
||||||
"/launch?uniqueid=" + uniqueId +
|
"/launch?uniqueid=" + uniqueId +
|
||||||
"&appid=" + appId +
|
"&appid=" + appId +
|
||||||
"&mode=" + config.getWidth() + "x" + config.getHeight() + "x" + config.getRefreshRate() +
|
"&mode=" + config.getWidth() + "x" + config.getHeight() + "x" + config.getRefreshRate() +
|
||||||
@ -288,21 +360,21 @@ public class NvHTTP {
|
|||||||
"&rikey="+bytesToHex(inputKey.getEncoded()) +
|
"&rikey="+bytesToHex(inputKey.getEncoded()) +
|
||||||
"&rikeyid="+riKeyId +
|
"&rikeyid="+riKeyId +
|
||||||
"&localAudioPlayMode=" + (config.getPlayLocalAudio() ? 1 : 0), false);
|
"&localAudioPlayMode=" + (config.getPlayLocalAudio() ? 1 : 0), false);
|
||||||
String gameSession = getXmlString(in, "gamesession");
|
String gameSession = getXmlString(xmlStr, "gamesession");
|
||||||
return Integer.parseInt(gameSession);
|
return Integer.parseInt(gameSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean resumeApp(SecretKey inputKey, int riKeyId) throws IOException, XmlPullParserException {
|
public boolean resumeApp(SecretKey inputKey, int riKeyId) throws IOException, XmlPullParserException {
|
||||||
InputStream in = openHttpConnection(baseUrl + "/resume?uniqueid=" + uniqueId +
|
String xmlStr = openHttpConnectionToString(baseUrl + "/resume?uniqueid=" + uniqueId +
|
||||||
"&rikey="+bytesToHex(inputKey.getEncoded()) +
|
"&rikey="+bytesToHex(inputKey.getEncoded()) +
|
||||||
"&rikeyid="+riKeyId, false);
|
"&rikeyid="+riKeyId, false);
|
||||||
String resume = getXmlString(in, "resume");
|
String resume = getXmlString(xmlStr, "resume");
|
||||||
return Integer.parseInt(resume) != 0;
|
return Integer.parseInt(resume) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean quitApp() throws IOException, XmlPullParserException {
|
public boolean quitApp() throws IOException, XmlPullParserException {
|
||||||
InputStream in = openHttpConnection(baseUrl + "/cancel?uniqueid=" + uniqueId, false);
|
String xmlStr = openHttpConnectionToString(baseUrl + "/cancel?uniqueid=" + uniqueId, false);
|
||||||
String cancel = getXmlString(in, "cancel");
|
String cancel = getXmlString(xmlStr, "cancel");
|
||||||
return Integer.parseInt(cancel) != 0;
|
return Integer.parseInt(cancel) != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import javax.crypto.NoSuchPaddingException;
|
|||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.ShortBufferException;
|
import javax.crypto.ShortBufferException;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import javax.net.ssl.*;
|
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
@ -15,7 +14,6 @@ import java.security.cert.Certificate;
|
|||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.Socket;
|
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
import java.security.cert.*;
|
import java.security.cert.*;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -43,66 +41,6 @@ public class PairingManager {
|
|||||||
this.cert = cryptoProvider.getClientCertificate();
|
this.cert = cryptoProvider.getClientCertificate();
|
||||||
this.pemCertBytes = cryptoProvider.getPemEncodedClientCertificate();
|
this.pemCertBytes = cryptoProvider.getPemEncodedClientCertificate();
|
||||||
this.pk = cryptoProvider.getClientPrivateKey();
|
this.pk = cryptoProvider.getClientPrivateKey();
|
||||||
|
|
||||||
// Update the trust manager and key manager to use our certificate and PK
|
|
||||||
installSslKeysAndTrust();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void installSslKeysAndTrust() {
|
|
||||||
// Create a trust manager that does not validate certificate chains
|
|
||||||
TrustManager[] trustAllCerts = new TrustManager[] {
|
|
||||||
new X509TrustManager() {
|
|
||||||
public X509Certificate[] getAcceptedIssuers() {
|
|
||||||
return new X509Certificate[0];
|
|
||||||
}
|
|
||||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
|
||||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
|
|
||||||
}};
|
|
||||||
|
|
||||||
KeyManager[] ourKeyman = new KeyManager[] {
|
|
||||||
new X509KeyManager() {
|
|
||||||
public String chooseClientAlias(String[] keyTypes,
|
|
||||||
Principal[] issuers, Socket socket) {
|
|
||||||
return "Limelight-RSA";
|
|
||||||
}
|
|
||||||
|
|
||||||
public String chooseServerAlias(String keyType, Principal[] issuers,
|
|
||||||
Socket socket) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public X509Certificate[] getCertificateChain(String alias) {
|
|
||||||
return new X509Certificate[] {cert};
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getClientAliases(String keyType, Principal[] issuers) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrivateKey getPrivateKey(String alias) {
|
|
||||||
return pk;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getServerAliases(String keyType, Principal[] issuers) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ignore differences between given hostname and certificate hostname
|
|
||||||
HostnameVerifier hv = new HostnameVerifier() {
|
|
||||||
public boolean verify(String hostname, SSLSession session) { return true; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Install the all-trusting trust manager
|
|
||||||
try {
|
|
||||||
SSLContext sc = SSLContext.getInstance("SSL");
|
|
||||||
sc.init(ourKeyman, trustAllCerts, new SecureRandom());
|
|
||||||
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
|
||||||
HttpsURLConnection.setDefaultHostnameVerifier(hv);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user