mirror of
https://github.com/moonlight-stream/moonlight-embedded.git
synced 2026-02-16 10:30:47 +00:00
Implement crypto provider and GFE 2.1 compatibility
Conflicts: .classpath libs/limelight-common.jar src/com/limelight/Limelight.java src/com/limelight/binding/PlatformBinding.java
This commit is contained in:
committed by
Iwan Timmer
parent
74b8c287c0
commit
ef1357477b
BIN
libs/bcpkix-jdk15on-150.jar
Normal file
BIN
libs/bcpkix-jdk15on-150.jar
Normal file
Binary file not shown.
BIN
libs/bcprov-jdk15on-150.jar
Normal file
BIN
libs/bcprov-jdk15on-150.jar
Normal file
Binary file not shown.
@@ -21,7 +21,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import com.limelight.nvstream.http.PairingManager;
|
||||
|
||||
/**
|
||||
* Main class for Limelight-pi
|
||||
@@ -68,7 +68,7 @@ public class Limelight implements NvConnectionListener {
|
||||
}
|
||||
}
|
||||
|
||||
conn = new NvConnection(host, this, streamConfig);
|
||||
conn = new NvConnection(host, this, streamConfig, PlatformBinding.getCryptoProvider());
|
||||
|
||||
GamepadMapping mapping = null;
|
||||
if (mappingFile!=null) {
|
||||
@@ -102,7 +102,7 @@ public class Limelight implements NvConnectionListener {
|
||||
* Creates a connection to the host and starts up the stream.
|
||||
*/
|
||||
private void startUpFake(StreamConfiguration streamConfig) {
|
||||
conn = new NvConnection(host, this, streamConfig);
|
||||
conn = new NvConnection(host, this, streamConfig, PlatformBinding.getCryptoProvider());
|
||||
conn.start(PlatformBinding.getDeviceName(), null,
|
||||
VideoDecoderRenderer.FLAG_PREFER_QUALITY,
|
||||
new FakeAudioRenderer(),
|
||||
@@ -130,21 +130,27 @@ public class Limelight implements NvConnectionListener {
|
||||
|
||||
try {
|
||||
httpConn = new NvHTTP(InetAddress.getByName(host),
|
||||
macAddress, PlatformBinding.getDeviceName());
|
||||
macAddress, PlatformBinding.getDeviceName(), PlatformBinding.getCryptoProvider());
|
||||
try {
|
||||
if (httpConn.getPairState()) {
|
||||
displayError("Pair", "Already paired");
|
||||
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
||||
displayError("pair", "Already paired");
|
||||
} else {
|
||||
int session = httpConn.getSessionId();
|
||||
if (session == 0) {
|
||||
displayError("Pair", "Pairing was declined by the target");
|
||||
} else {
|
||||
displayMessage("Pairing was successful");
|
||||
final String pinStr = PairingManager.generatePinString();
|
||||
|
||||
displayMessage("Please enter the following PIN on the target PC: "+pinStr);
|
||||
|
||||
PairingManager.PairState pairState = httpConn.pair(pinStr);
|
||||
if (pairState == PairingManager.PairState.PIN_WRONG) {
|
||||
displayError("pair", "Incorrect PIN");
|
||||
}
|
||||
else if (pairState == PairingManager.PairState.FAILED) {
|
||||
displayError("pair", "Pairing failed");
|
||||
}
|
||||
else if (pairState == PairingManager.PairState.PAIRED) {
|
||||
displayError("pair", "Paired successfully");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
displayError("Pair", e.getMessage());
|
||||
} catch (XmlPullParserException e) {
|
||||
} catch (Exception e) {
|
||||
displayError("Pair", e.getMessage());
|
||||
}
|
||||
} catch (UnknownHostException e1) {
|
||||
|
||||
@@ -5,8 +5,10 @@ import java.net.UnknownHostException;
|
||||
|
||||
import com.limelight.binding.audio.AlsaAudioRenderer;
|
||||
import com.limelight.binding.video.OmxDecoderRenderer;
|
||||
import com.limelight.binding.crypto.PcCryptoProvider;
|
||||
import com.limelight.nvstream.av.audio.AudioRenderer;
|
||||
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||
import com.limelight.nvstream.http.LimelightCryptoProvider;
|
||||
|
||||
/**
|
||||
* Used for platform-specific video/audio bindings.
|
||||
@@ -42,4 +44,12 @@ public class PlatformBinding {
|
||||
public static AudioRenderer getAudioRenderer(String device) {
|
||||
return new AlsaAudioRenderer(device);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance of a crypto provider
|
||||
* @return a PcCryptoProvider object
|
||||
*/
|
||||
public static LimelightCryptoProvider getCryptoProvider() {
|
||||
return new PcCryptoProvider();
|
||||
}
|
||||
}
|
||||
|
||||
262
src/com/limelight/binding/crypto/PcCryptoProvider.java
Normal file
262
src/com/limelight/binding/crypto/PcCryptoProvider.java
Normal file
@@ -0,0 +1,262 @@
|
||||
package com.limelight.binding.crypto;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openssl.PEMWriter;
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
import org.bouncycastle.x509.X509V3CertificateGenerator;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.http.LimelightCryptoProvider;
|
||||
import com.limelight.settings.SettingsManager;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class PcCryptoProvider implements LimelightCryptoProvider {
|
||||
|
||||
private File certFile = new File(SettingsManager.SETTINGS_DIR + File.separator + "client.crt");
|
||||
private File keyFile = new File(SettingsManager.SETTINGS_DIR + File.separator + "client.key");
|
||||
|
||||
private X509Certificate cert;
|
||||
private RSAPrivateKey key;
|
||||
private byte[] pemCertBytes;
|
||||
|
||||
static {
|
||||
// Install the Bouncy Castle provider
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
private byte[] loadFileToBytes(File f) {
|
||||
if (!f.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(f);
|
||||
byte[] fileData = new byte[(int) f.length()];
|
||||
fin.read(fileData);
|
||||
fin.close();
|
||||
return fileData;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean loadCertKeyPair() {
|
||||
byte[] certBytes = loadFileToBytes(certFile);
|
||||
byte[] keyBytes = loadFileToBytes(keyFile);
|
||||
|
||||
// If either file was missing, we definitely can't succeed
|
||||
if (certBytes == null || keyBytes == null) {
|
||||
LimeLog.info("Missing cert or key; need to generate a new one");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC");
|
||||
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certBytes));
|
||||
pemCertBytes = certBytes;
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
|
||||
key = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
|
||||
} catch (CertificateException e) {
|
||||
// May happen if the cert is corrupt
|
||||
LimeLog.warning("Corrupted certificate");
|
||||
return false;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Should never happen
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} catch (InvalidKeySpecException e) {
|
||||
// May happen if the key is corrupt
|
||||
LimeLog.warning("Corrupted key");
|
||||
return false;
|
||||
} catch (NoSuchProviderException e) {
|
||||
// Should never happen
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
LimeLog.info("Loaded key pair from disk");
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean generateCertKeyPair() {
|
||||
X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
|
||||
X500Principal principalName = new X500Principal("CN=NVIDIA GameStream Client");
|
||||
|
||||
byte[] snBytes = new byte[8];
|
||||
new SecureRandom().nextBytes(snBytes);
|
||||
|
||||
KeyPair keyPair;
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
|
||||
keyPairGenerator.initialize(2048);
|
||||
keyPair = keyPairGenerator.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException e1) {
|
||||
// Should never happen
|
||||
e1.printStackTrace();
|
||||
return false;
|
||||
} catch (NoSuchProviderException e) {
|
||||
// Should never happen
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
Date expirationDate = new Date();
|
||||
|
||||
// Expires in 20 years
|
||||
expirationDate.setYear(expirationDate.getYear() + 20);
|
||||
|
||||
certGenerator.setSerialNumber(new BigInteger(snBytes).abs());
|
||||
certGenerator.setIssuerDN(principalName);
|
||||
certGenerator.setNotBefore(now);
|
||||
certGenerator.setNotAfter(expirationDate);
|
||||
certGenerator.setSubjectDN(principalName);
|
||||
certGenerator.setPublicKey(keyPair.getPublic());
|
||||
certGenerator.setSignatureAlgorithm("SHA1withRSA");
|
||||
|
||||
try {
|
||||
cert = certGenerator.generate(keyPair.getPrivate(), "BC");
|
||||
key = (RSAPrivateKey) keyPair.getPrivate();
|
||||
} catch (Exception e) {
|
||||
// Nothing should go wrong here
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
LimeLog.info("Generated a new key pair");
|
||||
|
||||
// Save the resulting pair
|
||||
saveCertKeyPair();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void saveCertKeyPair() {
|
||||
try {
|
||||
FileOutputStream certOut = new FileOutputStream(certFile);
|
||||
FileOutputStream keyOut = new FileOutputStream(keyFile);
|
||||
|
||||
// Write the certificate in OpenSSL PEM format (important for the server)
|
||||
StringWriter strWriter = new StringWriter();
|
||||
PEMWriter pemWriter = new PEMWriter(strWriter);
|
||||
pemWriter.writeObject(cert);
|
||||
pemWriter.close();
|
||||
|
||||
// Line endings MUST be UNIX for the PC to accept the cert properly
|
||||
OutputStreamWriter certWriter = new OutputStreamWriter(certOut);
|
||||
String pemStr = strWriter.getBuffer().toString();
|
||||
for (int i = 0; i < pemStr.length(); i++) {
|
||||
char c = pemStr.charAt(i);
|
||||
if (c != '\r')
|
||||
certWriter.append(c);
|
||||
}
|
||||
certWriter.close();
|
||||
|
||||
// Write the private out in PKCS8 format
|
||||
keyOut.write(key.getEncoded());
|
||||
|
||||
certOut.close();
|
||||
keyOut.close();
|
||||
|
||||
LimeLog.info("Saved generated key pair to disk");
|
||||
} catch (IOException e) {
|
||||
// This isn't good because it means we'll have
|
||||
// to re-pair next time
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public X509Certificate getClientCertificate() {
|
||||
// Use a lock here to ensure only one guy will be generating or loading
|
||||
// the certificate and key at a time
|
||||
synchronized (this) {
|
||||
// Return a loaded cert if we have one
|
||||
if (cert != null) {
|
||||
return cert;
|
||||
}
|
||||
|
||||
// No loaded cert yet, let's see if we have one on disk
|
||||
if (loadCertKeyPair()) {
|
||||
// Got one
|
||||
return cert;
|
||||
}
|
||||
|
||||
// Try to generate a new key pair
|
||||
if (!generateCertKeyPair()) {
|
||||
// Failed
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load the generated pair
|
||||
loadCertKeyPair();
|
||||
return cert;
|
||||
}
|
||||
}
|
||||
|
||||
public RSAPrivateKey getClientPrivateKey() {
|
||||
// Use a lock here to ensure only one guy will be generating or loading
|
||||
// the certificate and key at a time
|
||||
synchronized (this) {
|
||||
// Return a loaded key if we have one
|
||||
if (key != null) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// No loaded key yet, let's see if we have one on disk
|
||||
if (loadCertKeyPair()) {
|
||||
// Got one
|
||||
return key;
|
||||
}
|
||||
|
||||
// Try to generate a new key pair
|
||||
if (!generateCertKeyPair()) {
|
||||
// Failed
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load the generated pair
|
||||
loadCertKeyPair();
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getPemEncodedClientCertificate() {
|
||||
synchronized (this) {
|
||||
// Call our helper function to do the cert loading/generation for us
|
||||
getClientCertificate();
|
||||
|
||||
// Return a cached value if we have it
|
||||
return pemCertBytes;
|
||||
}
|
||||
}
|
||||
|
||||
public String encodeBase64String(byte[] data) {
|
||||
return Base64.toBase64String(data);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user