Add cert pinning during pairing

This commit is contained in:
Cameron Gutman 2018-12-21 21:00:53 -08:00
parent 16b845ab84
commit db49077b9b
8 changed files with 89 additions and 23 deletions

View File

@ -390,8 +390,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
@Override @Override
public void run() { public void run() {
suspendGridUpdates = true; suspendGridUpdates = true;
ServerHelper.doQuit(AppView.this, ServerHelper.doQuit(AppView.this, computer,
ServerHelper.getCurrentAddressFromComputer(computer),
app.app, managerBinder, new Runnable() { app.app, managerBinder, new Runnable() {
@Override @Override
public void run() { public void run() {

View File

@ -68,6 +68,11 @@ import android.widget.FrameLayout;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.Toast; import android.widget.Toast;
import java.io.ByteArrayInputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
public class Game extends Activity implements SurfaceHolder.Callback, public class Game extends Activity implements SurfaceHolder.Callback,
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener, OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
@ -135,6 +140,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
public static final String EXTRA_PC_UUID = "UUID"; public static final String EXTRA_PC_UUID = "UUID";
public static final String EXTRA_PC_NAME = "PcName"; public static final String EXTRA_PC_NAME = "PcName";
public static final String EXTRA_APP_HDR = "HDR"; public static final String EXTRA_APP_HDR = "HDR";
public static final String EXTRA_SERVER_CERT = "ServerCert";
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -228,6 +234,17 @@ public class Game extends Activity implements SurfaceHolder.Callback,
String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID); String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID);
String pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME); String pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
boolean willStreamHdr = Game.this.getIntent().getBooleanExtra(EXTRA_APP_HDR, false); boolean willStreamHdr = Game.this.getIntent().getBooleanExtra(EXTRA_APP_HDR, false);
byte[] derCertData = Game.this.getIntent().getByteArrayExtra(EXTRA_SERVER_CERT);
X509Certificate serverCert = null;
try {
if (derCertData != null) {
serverCert = (X509Certificate) CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(derCertData));
}
} catch (CertificateException e) {
e.printStackTrace();
}
if (appId == StreamConfiguration.INVALID_APP_ID) { if (appId == StreamConfiguration.INVALID_APP_ID) {
finish(); finish();
@ -386,7 +403,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
.build(); .build();
// Initialize the connection // Initialize the connection
conn = new NvConnection(host, uniqueId, config, PlatformBinding.getCryptoProvider(this)); conn = new NvConnection(host, uniqueId, config, PlatformBinding.getCryptoProvider(this), serverCert);
controllerHandler = new ControllerHandler(this, conn, this, prefConfig); controllerHandler = new ControllerHandler(this, conn, this, prefConfig);
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE); InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);

View File

@ -373,9 +373,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer), httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer),
managerBinder.getUniqueId(), managerBinder.getUniqueId(),
PlatformBinding.getDeviceName(), computer.serverCert,
PlatformBinding.getCryptoProvider(PcView.this)); PlatformBinding.getCryptoProvider(PcView.this));
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) { if (httpConn.getPairState() == PairState.PAIRED) {
// Don't display any toast, but open the app list // Don't display any toast, but open the app list
message = null; message = null;
success = true; success = true;
@ -387,21 +387,26 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
Dialog.displayDialog(PcView.this, getResources().getString(R.string.pair_pairing_title), Dialog.displayDialog(PcView.this, getResources().getString(R.string.pair_pairing_title),
getResources().getString(R.string.pair_pairing_msg)+" "+pinStr, false); getResources().getString(R.string.pair_pairing_msg)+" "+pinStr, false);
PairingManager.PairState pairState = httpConn.pair(httpConn.getServerInfo(), pinStr); PairingManager pm = httpConn.getPairingManager();
if (pairState == PairingManager.PairState.PIN_WRONG) {
PairState pairState = pm.pair(httpConn.getServerInfo(), pinStr);
if (pairState == PairState.PIN_WRONG) {
message = getResources().getString(R.string.pair_incorrect_pin); message = getResources().getString(R.string.pair_incorrect_pin);
} }
else if (pairState == PairingManager.PairState.FAILED) { else if (pairState == PairState.FAILED) {
message = getResources().getString(R.string.pair_fail); message = getResources().getString(R.string.pair_fail);
} }
else if (pairState == PairingManager.PairState.ALREADY_IN_PROGRESS) { else if (pairState == PairState.ALREADY_IN_PROGRESS) {
message = getResources().getString(R.string.pair_already_in_progress); message = getResources().getString(R.string.pair_already_in_progress);
} }
else if (pairState == PairingManager.PairState.PAIRED) { else if (pairState == PairState.PAIRED) {
// Just navigate to the app view without displaying a toast // Just navigate to the app view without displaying a toast
message = null; message = null;
success = true; success = true;
// Pin this certificate for later HTTPS use
managerBinder.getComputer(computer.uuid).serverCert = pm.getPairedCert();
// Invalidate reachability information after pairing to force // Invalidate reachability information after pairing to force
// a refresh before reading pair state again // a refresh before reading pair state again
managerBinder.invalidateStateForComputer(computer.uuid); managerBinder.invalidateStateForComputer(computer.uuid);
@ -498,7 +503,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
try { try {
httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer), httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer),
managerBinder.getUniqueId(), managerBinder.getUniqueId(),
PlatformBinding.getDeviceName(), computer.serverCert,
PlatformBinding.getCryptoProvider(PcView.this)); PlatformBinding.getCryptoProvider(PcView.this));
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) { if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
httpConn.unpair(); httpConn.unpair();
@ -605,8 +610,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
UiHelper.displayQuitConfirmationDialog(this, new Runnable() { UiHelper.displayQuitConfirmationDialog(this, new Runnable() {
@Override @Override
public void run() { public void run() {
ServerHelper.doQuit(PcView.this, ServerHelper.doQuit(PcView.this, computer.details,
ServerHelper.getCurrentAddressFromComputer(computer.details),
new NvApp("app", 0, false), managerBinder, null); new NvApp("app", 0, false), managerBinder, null);
} }
}, null); }, null);

View File

@ -1,5 +1,11 @@
package com.limelight.computers; package com.limelight.computers;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -23,6 +29,7 @@ public class ComputerDatabaseManager {
private static final String REMOTE_ADDRESS_COLUMN_NAME = "RemoteAddress"; private static final String REMOTE_ADDRESS_COLUMN_NAME = "RemoteAddress";
private static final String MANUAL_ADDRESS_COLUMN_NAME = "ManualAddress"; private static final String MANUAL_ADDRESS_COLUMN_NAME = "ManualAddress";
private static final String MAC_ADDRESS_COLUMN_NAME = "MacAddress"; private static final String MAC_ADDRESS_COLUMN_NAME = "MacAddress";
private static final String SERVER_CERT_COLUMN_NAME = "ServerCert";
private SQLiteDatabase computerDb; private SQLiteDatabase computerDb;
@ -43,13 +50,21 @@ public class ComputerDatabaseManager {
} }
private void initializeDb(Context c) { private void initializeDb(Context c) {
// Add cert column to the table if not present
try {
computerDb.execSQL(String.format((Locale)null,
"ALTER TABLE %s ADD COLUMN %s TEXT",
COMPUTER_TABLE_NAME, SERVER_CERT_COLUMN_NAME));
} catch (SQLiteException e) {}
// Create tables if they aren't already there // Create tables if they aren't already there
computerDb.execSQL(String.format((Locale)null, computerDb.execSQL(String.format((Locale)null,
"CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY, %s TEXT NOT NULL, %s TEXT, %s TEXT, %s TEXT, %s TEXT)", "CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY, %s TEXT NOT NULL, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s TEXT)",
COMPUTER_TABLE_NAME, COMPUTER_TABLE_NAME,
COMPUTER_UUID_COLUMN_NAME, COMPUTER_NAME_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, COMPUTER_NAME_COLUMN_NAME,
LOCAL_ADDRESS_COLUMN_NAME, REMOTE_ADDRESS_COLUMN_NAME, MANUAL_ADDRESS_COLUMN_NAME, LOCAL_ADDRESS_COLUMN_NAME, REMOTE_ADDRESS_COLUMN_NAME, MANUAL_ADDRESS_COLUMN_NAME,
MAC_ADDRESS_COLUMN_NAME)); MAC_ADDRESS_COLUMN_NAME, SERVER_CERT_COLUMN_NAME));
// Move all computers from the old DB (if any) to the new one // Move all computers from the old DB (if any) to the new one
List<ComputerDetails> oldComputers = LegacyDatabaseReader.migrateAllComputers(c); List<ComputerDetails> oldComputers = LegacyDatabaseReader.migrateAllComputers(c);
@ -70,6 +85,17 @@ public class ComputerDatabaseManager {
values.put(REMOTE_ADDRESS_COLUMN_NAME, details.remoteAddress); values.put(REMOTE_ADDRESS_COLUMN_NAME, details.remoteAddress);
values.put(MANUAL_ADDRESS_COLUMN_NAME, details.manualAddress); values.put(MANUAL_ADDRESS_COLUMN_NAME, details.manualAddress);
values.put(MAC_ADDRESS_COLUMN_NAME, details.macAddress); values.put(MAC_ADDRESS_COLUMN_NAME, details.macAddress);
try {
if (details.serverCert != null) {
values.put(SERVER_CERT_COLUMN_NAME, details.serverCert.getEncoded());
}
else {
values.put(SERVER_CERT_COLUMN_NAME, (byte[])null);
}
} catch (CertificateEncodingException e) {
values.put(SERVER_CERT_COLUMN_NAME, (byte[])null);
e.printStackTrace();
}
return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE); return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
} }
@ -90,6 +116,17 @@ public class ComputerDatabaseManager {
details.manualAddress = c.getString(4); details.manualAddress = c.getString(4);
details.macAddress = c.getString(5); details.macAddress = c.getString(5);
try {
byte[] derCertData = c.getBlob(6);
if (derCertData != null) {
details.serverCert = (X509Certificate) CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(derCertData));
}
} catch (CertificateException e) {
e.printStackTrace();
}
// This signifies we don't have dynamic state (like pair state) // This signifies we don't have dynamic state (like pair state)
details.state = ComputerDetails.State.UNKNOWN; details.state = ComputerDetails.State.UNKNOWN;

View File

@ -435,8 +435,8 @@ public class ComputerManagerService extends Service {
} }
try { try {
NvHTTP http = new NvHTTP(address, idManager.getUniqueId(), NvHTTP http = new NvHTTP(address, idManager.getUniqueId(), details.serverCert,
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this)); PlatformBinding.getCryptoProvider(ComputerManagerService.this));
ComputerDetails newDetails = http.getComputerDetails(); ComputerDetails newDetails = http.getComputerDetails();
@ -698,7 +698,7 @@ public class ComputerManagerService extends Service {
try { try {
NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer), idManager.getUniqueId(), NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer), idManager.getUniqueId(),
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this)); computer.serverCert, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
String appList; String appList;
if (tuple != null) { if (tuple != null) {

View File

@ -22,7 +22,8 @@ public class NetworkAssetLoader {
public InputStream getBitmapStream(CachedAppAssetLoader.LoaderTuple tuple) { public InputStream getBitmapStream(CachedAppAssetLoader.LoaderTuple tuple) {
InputStream in = null; InputStream in = null;
try { try {
NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(tuple.computer), uniqueId, null, PlatformBinding.getCryptoProvider(context)); NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(tuple.computer), uniqueId,
tuple.computer.serverCert, PlatformBinding.getCryptoProvider(context));
in = http.getBoxArt(tuple.app); in = http.getBoxArt(tuple.app);
} catch (IOException ignored) {} } catch (IOException ignored) {}

View File

@ -15,6 +15,7 @@ import com.limelight.nvstream.http.NvHTTP;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.cert.CertificateEncodingException;
public class ServerHelper { public class ServerHelper {
public static String getCurrentAddressFromComputer(ComputerDetails computer) { public static String getCurrentAddressFromComputer(ComputerDetails computer) {
@ -31,6 +32,13 @@ public class ServerHelper {
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId()); intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString()); intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString());
intent.putExtra(Game.EXTRA_PC_NAME, computer.name); intent.putExtra(Game.EXTRA_PC_NAME, computer.name);
try {
if (computer.serverCert != null) {
intent.putExtra(Game.EXTRA_SERVER_CERT, computer.serverCert.getEncoded());
}
} catch (CertificateEncodingException e) {
e.printStackTrace();
}
return intent; return intent;
} }
@ -45,7 +53,7 @@ public class ServerHelper {
} }
public static void doQuit(final Activity parent, public static void doQuit(final Activity parent,
final String address, final ComputerDetails computer,
final NvApp app, final NvApp app,
final ComputerManagerService.ComputerManagerBinder managerBinder, final ComputerManagerService.ComputerManagerBinder managerBinder,
final Runnable onComplete) { final Runnable onComplete) {
@ -56,8 +64,8 @@ public class ServerHelper {
NvHTTP httpConn; NvHTTP httpConn;
String message; String message;
try { try {
httpConn = new NvHTTP(address, httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer),
managerBinder.getUniqueId(), null, PlatformBinding.getCryptoProvider(parent)); managerBinder.getUniqueId(), computer.serverCert, PlatformBinding.getCryptoProvider(parent));
if (httpConn.quitApp()) { if (httpConn.quitApp()) {
message = parent.getResources().getString(R.string.applist_quit_success) + " " + app.getAppName(); message = parent.getResources().getString(R.string.applist_quit_success) + " " + app.getAppName();
} else { } else {

@ -1 +1 @@
Subproject commit 1af5c24e56e3d0b4cae477346306075129b8ddf8 Subproject commit 364f34a5646dcfd89cf02270906b01892cd1f76e