Cleanup and bugfix pairing code.

This commit is contained in:
Cameron Gutman 2014-06-15 18:54:32 -07:00
parent 07cf96c5ce
commit 6a92ea74fc
3 changed files with 38 additions and 28 deletions

View File

@ -6,4 +6,5 @@ import java.security.interfaces.RSAPrivateKey;
public interface LimelightCryptoProvider { public interface LimelightCryptoProvider {
public X509Certificate getClientCertificate(); public X509Certificate getClientCertificate();
public RSAPrivateKey getClientPrivateKey(); public RSAPrivateKey getClientPrivateKey();
public byte[] getPemEncodedClientCertificate();
} }

View File

@ -29,6 +29,8 @@ public class NvHTTP {
public static final int PORT = 47984; public static final int PORT = 47984;
public static final int CONNECTION_TIMEOUT = 5000; public static final int CONNECTION_TIMEOUT = 5000;
private final boolean verbose = false;
public String baseUrl; public String baseUrl;
public NvHTTP(InetAddress host, String uniqueId, String deviceName, LimelightCryptoProvider cryptoProvider) { public NvHTTP(InetAddress host, String uniqueId, String deviceName, LimelightCryptoProvider cryptoProvider) {
@ -96,7 +98,9 @@ public class NvHTTP {
private InputStream openHttpConnection(String url) throws IOException { private InputStream openHttpConnection(String url) throws IOException {
URLConnection conn = new URL(url).openConnection(); URLConnection conn = new URL(url).openConnection();
System.out.println(conn.getURL()); if (verbose) {
System.out.println(url);
}
conn.setConnectTimeout(CONNECTION_TIMEOUT); conn.setConnectTimeout(CONNECTION_TIMEOUT);
conn.setUseCaches(false); conn.setUseCaches(false);
conn.connect(); conn.connect();
@ -112,7 +116,11 @@ public class NvHTTP {
} }
s.close(); s.close();
System.out.println(str);
if (verbose) {
System.out.println(str);
}
return str; return str;
} }

View File

@ -19,6 +19,7 @@ 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;
import java.util.Random;
public class PairingManager { public class PairingManager {
@ -27,9 +28,7 @@ public class PairingManager {
private PrivateKey pk; private PrivateKey pk;
private X509Certificate cert; private X509Certificate cert;
private SecretKey aesKey; private SecretKey aesKey;
private byte[] pemCertBytes;
byte[] privKeyBytes;
byte[] pubKeyBytes;
public enum PairState { public enum PairState {
NOT_PAIRED, NOT_PAIRED,
@ -41,13 +40,14 @@ public class PairingManager {
public PairingManager(NvHTTP http, LimelightCryptoProvider cryptoProvider) { public PairingManager(NvHTTP http, LimelightCryptoProvider cryptoProvider) {
this.http = http; this.http = http;
this.cert = cryptoProvider.getClientCertificate(); this.cert = cryptoProvider.getClientCertificate();
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 // Update the trust manager and key manager to use our certificate and PK
installSslKeysAndTrust(); installSslKeysAndTrust();
} }
public void installSslKeysAndTrust() { private void installSslKeysAndTrust() {
// Create a trust manager that does not validate certificate chains // Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() { new X509TrustManager() {
@ -99,7 +99,9 @@ public class PairingManager {
sc.init(ourKeyman, trustAllCerts, new SecureRandom()); sc.init(ourKeyman, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(hv); HttpsURLConnection.setDefaultHostnameVerifier(hv);
} catch (Exception e) {} } catch (Exception e) {
e.printStackTrace();
}
} }
final private static char[] hexArray = "0123456789ABCDEF".toCharArray(); final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
@ -145,15 +147,16 @@ public class PairingManager {
return saltedPin; return saltedPin;
} }
private static byte[] toSHA1Bytes(byte[] convertme) { private static byte[] toSHA1Bytes(byte[] data) {
MessageDigest md = null;
try { try {
md = MessageDigest.getInstance("SHA-1"); MessageDigest md = MessageDigest.getInstance("SHA-1");
return md.digest(data);
} }
catch(NoSuchAlgorithmException e) { catch (NoSuchAlgorithmException e) {
e.printStackTrace(); // Shouldn't ever happen
e.printStackTrace();
return null;
} }
return md.digest(convertme);
} }
private static boolean verifySignature(byte[] data, byte[] signature, Certificate cert) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { private static boolean verifySignature(byte[] data, byte[] signature, Certificate cert) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
@ -199,7 +202,6 @@ public class PairingManager {
private static SecretKey generateAesKey(byte[] keyData) { private static SecretKey generateAesKey(byte[] keyData) {
byte[] aesTruncated = Arrays.copyOf(toSHA1Bytes(keyData), 16); byte[] aesTruncated = Arrays.copyOf(toSHA1Bytes(keyData), 16);
System.out.println("AES key data: "+bytesToHex(aesTruncated));
return new SecretKeySpec(aesTruncated, "AES"); return new SecretKeySpec(aesTruncated, "AES");
} }
@ -210,6 +212,13 @@ public class PairingManager {
return c; return c;
} }
public static String generatePinString() {
Random r = new Random();
return String.format("%d%d%d%d",
r.nextInt(10), r.nextInt(10),
r.nextInt(10), r.nextInt(10));
}
public PairState getPairState(String uniqueId) throws MalformedURLException, IOException, XmlPullParserException { public PairState getPairState(String uniqueId) throws MalformedURLException, IOException, XmlPullParserException {
String serverInfo = http.openHttpConnectionToString(http.baseUrl + "/serverinfo?uniqueid="+uniqueId); String serverInfo = http.openHttpConnectionToString(http.baseUrl + "/serverinfo?uniqueid="+uniqueId);
if (!NvHTTP.getXmlString(serverInfo, "PairStatus").equals("1")) { if (!NvHTTP.getXmlString(serverInfo, "PairStatus").equals("1")) {
@ -228,7 +237,6 @@ public class PairingManager {
public PairState pair(String uniqueId, String pin) throws MalformedURLException, IOException, XmlPullParserException, CertificateException, InvalidKeyException, NoSuchAlgorithmException, SignatureException, ShortBufferException, IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException { public PairState pair(String uniqueId, String pin) throws MalformedURLException, IOException, XmlPullParserException, CertificateException, InvalidKeyException, NoSuchAlgorithmException, SignatureException, ShortBufferException, IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException {
// Generate a salt for hashing the PIN // Generate a salt for hashing the PIN
byte[] salt = generateRandomBytes(16); byte[] salt = generateRandomBytes(16);
System.out.println("Using salt: "+bytesToHex(salt));
// Combine the salt and pin, then create an AES key from them // Combine the salt and pin, then create an AES key from them
byte[] saltAndPin = saltPin(salt, pin); byte[] saltAndPin = saltPin(salt, pin);
@ -236,46 +244,40 @@ public class PairingManager {
// Send the salt and get the server cert // Send the salt and get the server cert
String getCert = http.openHttpConnectionToString(http.baseUrl + String getCert = http.openHttpConnectionToString(http.baseUrl +
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&phrase=getservercert&salt="+bytesToHex(salt)+"&clientcert="+bytesToHex(pubKeyBytes)); "/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&phrase=getservercert&salt="+bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes));
if (!NvHTTP.getXmlString(getCert, "paired").equals("1")) { if (!NvHTTP.getXmlString(getCert, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
return PairState.FAILED; return PairState.FAILED;
} }
X509Certificate serverCert = extractPlainCert(getCert); X509Certificate serverCert = extractPlainCert(getCert);
System.out.println(serverCert);
// Generate a random challenge and encrypt it with our AES key // Generate a random challenge and encrypt it with our AES key
byte[] randomChallenge = generateRandomBytes(16); byte[] randomChallenge = generateRandomBytes(16);
System.out.println("Unencrypted challenge: "+bytesToHex(randomChallenge));
byte[] encryptedChallenge = encryptAes(randomChallenge, aesKey); byte[] encryptedChallenge = encryptAes(randomChallenge, aesKey);
System.out.println("Encrypted challenge: "+bytesToHex(encryptedChallenge));
// Send the encrypted challenge to the server // Send the encrypted challenge to the server
String challengeResp = http.openHttpConnectionToString(http.baseUrl + 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));
if (!NvHTTP.getXmlString(challengeResp, "paired").equals("1")) { if (!NvHTTP.getXmlString(challengeResp, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
return PairState.FAILED; return PairState.FAILED;
} }
// Decode the server's response and subsequent challenge // Decode the server's response and subsequent challenge
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse")); byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse"));
System.out.println("Encrypted challenge response: "+bytesToHex(encServerChallengeResponse));
byte[] decServerChallengeResponse = decryptAes(encServerChallengeResponse, aesKey); byte[] decServerChallengeResponse = decryptAes(encServerChallengeResponse, aesKey);
System.out.println("Decrypted challenge response: "+bytesToHex(decServerChallengeResponse));
byte[] serverResponse = Arrays.copyOfRange(decServerChallengeResponse, 0, 20); byte[] serverResponse = Arrays.copyOfRange(decServerChallengeResponse, 0, 20);
byte[] serverChallenge = Arrays.copyOfRange(decServerChallengeResponse, 20, 36); byte[] serverChallenge = Arrays.copyOfRange(decServerChallengeResponse, 20, 36);
System.out.println("Server response: "+bytesToHex(serverResponse));
System.out.println("Server challenge: "+bytesToHex(serverChallenge));
// Using another 16 bytes secret, compute a challenge response hash using the secret, our cert sig, and the challenge // Using another 16 bytes secret, compute a challenge response hash using the secret, our cert sig, and the challenge
byte[] clientSecret = generateRandomBytes(16); byte[] clientSecret = generateRandomBytes(16);
byte[] challengeRespHash = toSHA1Bytes(concatBytes(concatBytes(serverChallenge, cert.getSignature()), clientSecret)); byte[] challengeRespHash = toSHA1Bytes(concatBytes(concatBytes(serverChallenge, cert.getSignature()), clientSecret));
System.out.println("Client challenge response hash: "+bytesToHex(challengeRespHash));
byte[] challengeRespEncrypted = encryptAes(challengeRespHash, aesKey); byte[] challengeRespEncrypted = encryptAes(challengeRespHash, aesKey);
System.out.println("Client challenge response encrypted: "+bytesToHex(challengeRespEncrypted));
String secretResp = http.openHttpConnectionToString(http.baseUrl + 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));
if (!NvHTTP.getXmlString(secretResp, "paired").equals("1")) { if (!NvHTTP.getXmlString(secretResp, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
return PairState.FAILED; return PairState.FAILED;
} }
@ -295,8 +297,6 @@ public class PairingManager {
// Ensure the server challenge matched what we expected (aka the PIN was correct) // Ensure the server challenge matched what we expected (aka the PIN was correct)
byte[] serverChallengeRespHash = toSHA1Bytes(concatBytes(concatBytes(randomChallenge, serverCert.getSignature()), serverSecret)); byte[] serverChallengeRespHash = toSHA1Bytes(concatBytes(concatBytes(randomChallenge, serverCert.getSignature()), serverSecret));
System.out.println("Re-calculated server challenge response hash: "+bytesToHex(serverChallengeRespHash));
System.out.println("Original challenge response: "+bytesToHex(serverResponse));
if (!Arrays.equals(serverChallengeRespHash, serverResponse)) { if (!Arrays.equals(serverChallengeRespHash, serverResponse)) {
// Cancel the pairing process // Cancel the pairing process
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId); http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
@ -307,16 +307,17 @@ public class PairingManager {
// Send the server our signed secret // Send the server our signed secret
byte[] clientPairingSecret = concatBytes(clientSecret, signData(clientSecret, pk)); byte[] clientPairingSecret = concatBytes(clientSecret, signData(clientSecret, pk));
System.out.println("Client pairing secret: "+bytesToHex(clientPairingSecret));
String clientSecretResp = http.openHttpConnectionToString(http.baseUrl + 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));
if (!NvHTTP.getXmlString(clientSecretResp, "paired").equals("1")) { if (!NvHTTP.getXmlString(clientSecretResp, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
return PairState.FAILED; return PairState.FAILED;
} }
// Do the initial challenge (seems neccessary for us to show as paired) // 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");
if (!NvHTTP.getXmlString(pairChallenge, "paired").equals("1")) { if (!NvHTTP.getXmlString(pairChallenge, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
return PairState.FAILED; return PairState.FAILED;
} }