/*
* Copyright 2017 rootkiwi
*
* AN2Linux-client is licensed under GNU General Public License 3.
*
* See LICENSE for more details.
*/
package kiwi.root.an2linuxclient.network;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Build;
import android.os.SystemClock;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.UUID;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import kiwi.root.an2linuxclient.crypto.Sha256Helper;
import kiwi.root.an2linuxclient.crypto.TlsHelper;
import kiwi.root.an2linuxclient.utils.ConnectionHelper;
import static kiwi.root.an2linuxclient.network.PairingConnectionCallbackMessage.CallbackType.FAILED_TO_CONNECT;
import static kiwi.root.an2linuxclient.network.PairingConnectionCallbackMessage.CallbackType.SERVER_ACCEPTED_PAIR;
import static kiwi.root.an2linuxclient.network.PairingConnectionCallbackMessage.CallbackType.SERVER_DENIED_PAIR;
import static kiwi.root.an2linuxclient.network.PairingConnectionCallbackMessage.CallbackType.SOCKET_CLOSED;
import static kiwi.root.an2linuxclient.network.PairingConnectionCallbackMessage.CallbackType.TLS_HANDSHAKE_COMPLETED;
public class BluetoothPairingConnection extends PairingConnection {
private String serverMacAddress;
private SSLEngine tlsEngine;
private ByteBuffer appDataBuf;
private ByteBuffer netDataBuf;
BluetoothPairingConnection(String serverMacAddress, Context c) {
super(c);
this.serverMacAddress = serverMacAddress;
}
@Override
void acceptPairing() {
try {
byte[] encrypted = TlsHelper.tlsEncrypt(tlsEngine, appDataBuf, netDataBuf, new byte[]{ACCEPT_PAIRING});
mOut.write(ConnectionHelper.intToByteArray(encrypted.length));
mOut.write(encrypted);
mPairResponseSent = true;
} catch (IOException ioe) {}
}
@Override
void denyPairing() {
try {
byte[] encrypted = TlsHelper.tlsEncrypt(tlsEngine, appDataBuf, netDataBuf, new byte[]{DENY_PAIRING});
mOut.write(ConnectionHelper.intToByteArray(encrypted.length));
mOut.write(encrypted);
mPairResponseSent = true;
} catch (IOException ioe) {}
}
private void createTlsEngine(){
tlsEngine = TlsHelper.getPairingTlsContext().createSSLEngine();
tlsEngine.setUseClientMode(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH){
tlsEngine.setEnabledProtocols(TlsHelper.TLS_VERSIONS);
tlsEngine.setEnabledCipherSuites(TlsHelper.TLS_CIPHERS);
} else {
tlsEngine.setEnabledProtocols(TlsHelper.TLS_VERSIONS_COMPAT_BT);
tlsEngine.setEnabledCipherSuites(TlsHelper.TLS_CIPHERS_COMPAT_BT);
}
}
private void createBuffers(){
appDataBuf = ByteBuffer.allocate(tlsEngine.getSession().getApplicationBufferSize());
netDataBuf = ByteBuffer.allocate(tlsEngine.getSession().getPacketBufferSize());
}
@Override
public void run() {
try {
BluetoothSocket bs = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(serverMacAddress)
.createRfcommSocketToServiceRecord(UUID.fromString("a97fbf21-2ef3-4daf-adfb-2a53ffa87b8e"));
try {
bs.connect();
} catch (IOException connectException) {
notifyObservers(new PairingConnectionCallbackMessage(FAILED_TO_CONNECT));
try {
bs.close();
} catch (IOException closeException) {}
SystemClock.sleep(500);
return;
}
mOut = bs.getOutputStream();
mIn = bs.getInputStream();
mOut.write(INITIATE_PAIRING);
createTlsEngine();
createBuffers();
byte[] clientCertBytes = TlsHelper.getCertificateBytes(c);
if (TlsHelper.doHandshake(tlsEngine, netDataBuf, mOut, mIn) == SSLEngineResult.HandshakeStatus.FINISHED){
try {
Certificate serverCert = tlsEngine.getSession().getPeerCertificates()[0];
byte[] sha256 = Sha256Helper.sha256(clientCertBytes, serverCert.getEncoded());
notifyObservers(new PairingConnectionCallbackMessage(
TLS_HANDSHAKE_COMPLETED,
Sha256Helper.getFourLineHexString(sha256),
serverCert));
} catch (CertificateEncodingException e){
Log.e("BluetoothPairingConn...", "run:handshakeCompleted");
Log.e("StackTrace", Log.getStackTraceString(e));
}
} else {
notifyObservers(new PairingConnectionCallbackMessage(FAILED_TO_CONNECT));
try {
mOut.close();
mIn.close();
bs.close();
} catch (IOException e2) {}
SystemClock.sleep(500);
return;
}
byte[] encryptedClientCert = TlsHelper.tlsEncrypt(tlsEngine, appDataBuf, netDataBuf, clientCertBytes);
/*I don't know how else to do this when using SSLEngine/SSL_BIO, but I don't see any security
issue with sending the length of the encrypted data mIn cleartext, using something like wireshark
it's possible to see the length anyway*/
mOut.write(ConnectionHelper.intToByteArray(encryptedClientCert.length));
mOut.write(encryptedClientCert);
while (!mCancel) {
/*Don't really know how to do non blocking or timeout in a good way with
bluetooth socket so I will currently with this solution not be able to
notice if the other peer (server) have closed the socket*/
if (mIn.available() > 0){
try {
int serverPairResponseSize = ByteBuffer.wrap(ConnectionHelper.readAll(4, mIn)).getInt();
byte[] serverPairResponseEncrypted = ConnectionHelper.readAll(serverPairResponseSize, mIn);
byte serverPairResponse = TlsHelper.tlsDecrypt(tlsEngine, appDataBuf, netDataBuf, serverPairResponseEncrypted)[0];
if (serverPairResponse == ACCEPT_PAIRING) {
notifyObservers(new PairingConnectionCallbackMessage(SERVER_ACCEPTED_PAIR));
while (!mCancel && !mPairResponseSent) {
SystemClock.sleep(1000);
}
mCancel = true;
} else if (serverPairResponse == DENY_PAIRING) {
notifyObservers(new PairingConnectionCallbackMessage(SERVER_DENIED_PAIR));
mCancel = true;
} else {
// recieved something strange
notifyObservers(new PairingConnectionCallbackMessage(SOCKET_CLOSED));
mCancel = true;
}
} catch (IOException ioe){
// socket closed
notifyObservers(new PairingConnectionCallbackMessage(SOCKET_CLOSED));
mCancel = true;
}
} else {
SystemClock.sleep(1000);
}
}
mIn.close();
mOut.close();
bs.close();
} catch (IOException e){
Log.e("BluetoothPairingConn...", "run");
Log.e("StackTrace", Log.getStackTraceString(e));
}
}
}