diff --git a/.idea/misc.xml b/.idea/misc.xml index 5d19981..fbb6828 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -37,7 +37,7 @@ - + diff --git a/app/build.gradle b/app/build.gradle index 0c3c7ad..f655f4a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "io.rtek.rtvoice" minSdkVersion 21 targetSdkVersion 25 - versionCode 4 - versionName "1.0.3" + versionCode 12 + versionName "1.0.8" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4631e11..7292599 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,7 +4,7 @@ - + diff --git a/app/src/main/assets/dialing.wav b/app/src/main/assets/dialing.wav new file mode 100644 index 0000000..4e60a1d Binary files /dev/null and b/app/src/main/assets/dialing.wav differ diff --git a/app/src/main/java/io/rtek/rtvoice/Call.java b/app/src/main/java/io/rtek/rtvoice/Call.java index de6ec75..37f5544 100644 --- a/app/src/main/java/io/rtek/rtvoice/Call.java +++ b/app/src/main/java/io/rtek/rtvoice/Call.java @@ -12,6 +12,7 @@ import android.util.Log; import java.io.IOException; +import java.math.BigInteger; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; @@ -57,7 +58,7 @@ public class Call extends Thread { //Interrupt private boolean closing = false; private boolean secure = false; - private String key; + private byte[] key; @@ -67,6 +68,16 @@ public Call(ICallListener listener, int localCallerId, int remoteCallerId){ this.remoteCallerId = remoteCallerId; } + public boolean hasKey(){ + return key != null; + } + + public byte[] generateKey() throws Exception{ + SecureRandom random = new SecureRandom(); + this.key = getRawKey(new BigInteger(130, random).toByteArray()); + return this.key; + } + public void close(){ this.closing = true; if (sock != null){ @@ -87,12 +98,31 @@ public void close(){ listener.callEnded(); } - public void start(String server, String encryptionKey){ + //Supply new key + public void start(String server, String encryptionKey) throws Exception{ this.server = server; - if(!encryptionKey.isEmpty()){ - secure = true; - this.key = encryptionKey; + if(encryptionKey.isEmpty()){ + throw new Exception("No encryption key supplied!"); } + this.secure = true; + this.key = hexStringToByteArray(encryptionKey); + start(); + } + + //Use previously generated key + public void start(String server) throws Exception{ + this.server = server; + if(key == null){ + throw new Exception("No encryption key generated!"); + } + this.secure = true; + start(); + } + + //Legacy + public void startWithoutEncryption(String server){ + this.server = server; + this.secure = false; start(); } @@ -141,7 +171,7 @@ public void run() { DatagramPacket receivePacket = new DatagramPacket(receiveBuffer_Encrypted, receiveBuffer_Encrypted.length); sock.receive(receivePacket); Log.w("VOICE", "Packet of " + receivePacket.getLength() + " was received from " + receivePacket.getAddress()); - receiveBuffer = decrypt(hexStringToByteArray(key), receiveBuffer_Encrypted); + receiveBuffer = decrypt(key, receiveBuffer_Encrypted); track.write(receiveBuffer, 0, receiveBuffer.length); }else { DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length); @@ -176,7 +206,7 @@ public void run() { return; int read = recorder.read(transmittBuffer, 0, transmittBuffer.length); if(secure){ - transmittBuffer_Encrypted = encrypt(hexStringToByteArray(key), transmittBuffer); + transmittBuffer_Encrypted = encrypt(key, transmittBuffer); DatagramPacket packet = new DatagramPacket(transmittBuffer_Encrypted, transmittBuffer_Encrypted.length); sock.send(packet); }else{ @@ -210,14 +240,7 @@ private byte[] encrypt(byte[] raw, byte[] clear) throws Exception { SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); - byte[] data = cipher.doFinal(clear); - byte[] iv = cipher.getIV(); - byte[] packet = new byte[data.length+iv.length]; - for (int i = 0; i < iv.length; i++) - packet[i] = iv[i]; - for (int i = 0; i < data.length; i++) - packet[iv.length+i] = data[i]; - return data; + return cipher.doFinal(clear); } private byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception { @@ -225,8 +248,7 @@ private byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec ivspec = new IvParameterSpec(encrypted, 0, 16); cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivspec); - byte[] decrypted = cipher.doFinal(encrypted, 16, encrypted.length-16); - return decrypted; + return cipher.doFinal(encrypted, 16, encrypted.length-16); } public static byte[] hexStringToByteArray(String s) { @@ -238,4 +260,13 @@ public static byte[] hexStringToByteArray(String s) { } return data; } + private static byte[] getRawKey(byte[] seed) throws Exception { + KeyGenerator kgen = KeyGenerator.getInstance("AES"); + SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); + sr.setSeed(seed); + kgen.init(128, sr); + SecretKey skey = kgen.generateKey(); + byte[] raw = skey.getEncoded(); + return raw; + } } diff --git a/app/src/main/java/io/rtek/rtvoice/ControlChannel.java b/app/src/main/java/io/rtek/rtvoice/ControlChannel.java index d3f37aa..7290395 100644 --- a/app/src/main/java/io/rtek/rtvoice/ControlChannel.java +++ b/app/src/main/java/io/rtek/rtvoice/ControlChannel.java @@ -1,5 +1,6 @@ package io.rtek.rtvoice; +import android.content.res.Resources; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; @@ -14,19 +15,39 @@ import org.apache.http.conn.ssl.SSLSocketFactory; +import java.io.BufferedInputStream; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.PrintWriter; +import java.math.BigInteger; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.Socket; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.SecureRandom; +import java.security.Signature; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; @@ -47,6 +68,7 @@ public class ControlChannel extends Thread implements ICallListener { BufferedReader in; String server; Call currentCall; + KeyPair identity; public ControlChannel(IControlChannelListener listener, String server){ this.listener = listener; @@ -100,8 +122,6 @@ public int getNumber(){ return this.number; } - private int createCall = 0; - public void createIncoming(int number){ if (!activeCall()) { listener.incoming(number); @@ -119,6 +139,48 @@ public void run() { } }).start(); } + + public void generateIdentity() throws Exception{ + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(1024, new SecureRandom()); + identity = generator.genKeyPair(); + } + + public String getPublicKey(){ + return bytesToHex(identity.getPublic().getEncoded()); + } + + private String getPrivateKey(){ + return bytesToHex(identity.getPrivate().getEncoded()); + } + + private void fromKeyPair(String publicKey, String privateKey) throws Exception{ + byte[] clearPublicKey = hexStringToByteArray(publicKey); + byte[] clearPrivateKey = hexStringToByteArray(privateKey); + X509EncodedKeySpec spec = new X509EncodedKeySpec(clearPublicKey); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey key = keyFactory.generatePublic(spec); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(clearPrivateKey); + KeyFactory fact = KeyFactory.getInstance("RSA"); + PrivateKey priv = fact.generatePrivate(keySpec); + this.identity = new KeyPair(key, priv); + } + + public String encryptRSA(final String publickey, String message) throws Exception{ + Cipher cipher = Cipher.getInstance("RSA"); + X509EncodedKeySpec spec = new X509EncodedKeySpec(hexStringToByteArray(publickey)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey key = keyFactory.generatePublic(spec); + cipher.init(Cipher.ENCRYPT_MODE, key); + return bytesToHex(cipher.doFinal(message.getBytes())); + } + + public String decryptRSA(String message) throws Exception{ + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, identity.getPrivate()); + return new String(cipher.doFinal(hexStringToByteArray(message))); + } + Socket socket; private boolean closing = false; @@ -130,55 +192,116 @@ public boolean isCancelled(){ } //Main public void run() { + try{ + if(listener.getString("identity-public").isEmpty() || listener.getString("identity-private").isEmpty()){ + generateIdentity(); + listener.saveString("identity-public", getPublicKey()); + listener.saveString("identity-private", getPrivateKey()); + }else{ + try{ + fromKeyPair(listener.getString("identity-public"), listener.getString("identity-private")); + }catch (Exception e){ + generateIdentity(); + listener.saveString("identity-public", getPublicKey()); + listener.saveString("identity-private", getPrivateKey()); + } + } + }catch (Exception e) { + //Could not generate nor save identity + } while(!isCancelled()){ try { listener.ControlChannelDisconnected(); - Pair km = listener.getTrustManagerFactory(); - if(km == null || km.first == null || km.second == null){ + TrustManagerFactory tmf; + KeyManagerFactory kmfactory; + try{ + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null); + InputStream stream = listener.getServerCertificate(); + BufferedInputStream bis = new BufferedInputStream(stream); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + while (bis.available() > 0) { + Certificate cert = cf.generateCertificate(bis); + trustStore.setCertificateEntry("cert" + bis.available(), cert); + } + kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmfactory.init(trustStore, "1234".toCharArray()); + tmf=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustStore); + }catch (Exception e){ throw new Exception("No valid truststore provided"); } SSLContext context = SSLContext.getInstance("SSL"); - KeyManager[] keymanagers = km.second.getKeyManagers(); - context.init(keymanagers, km.first.getTrustManagers(), new SecureRandom()); + KeyManager[] keymanagers = kmfactory.getKeyManagers(); + context.init(keymanagers, tmf.getTrustManagers(), new SecureRandom()); socket = context.getSocketFactory().createSocket(this.server, 1338); Log.w("CC", "Connecting"); while(!socket.isConnected()){} - listener.ControlChannelConnected(); Log.w("CC", "Connected"); out = new PrintWriter(socket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + // If no new identity and phone number saved, signed data of current date + // If new identity and no saved phone number, send number request and public key to server + // If new identity and save password, send authorized key and public key to server String savedNumber = listener.getString("number"); - if(!savedNumber.isEmpty()){ - //Try to register - out.println("REGISTER " + savedNumber.split(":")[0] + " " + savedNumber.split(":")[1]); - String response = in.readLine(); - if(response.equals("UNKNOWN NUMBER")){ - //Number is unknown, generate new - }else if (response.equals("INVALID KEY")){ - //Key was invalid, generate new - }else if (response.equals("ASSOCIATED")){ - //All was ok, we can now set number locally - this.number = Integer.parseInt(savedNumber.split(":")[0]); - listener.setNumber(this.number); - if (!listener.getString("gcm").equals("registered")){ - if(listener.getGcmDeviceId() != null) - out.println("DEVICE " + listener.getGcmDeviceId()); - } + if (identity == null){ + throw new Exception("RSA not supported on device"); + } + + //Do auth + if (savedNumber.isEmpty()){ + //No number saved, request a new one with identity + Log.w("CC", "Requesting new number"); + out.println("REQUEST NUMBER " + getPublicKey()); + }else{ + //We have a number, determaine auth method + if (savedNumber.contains(":")){ + Log.w("CC", "Registering and upgrading"); + //We have a ':' in the number, use authkey method and upgrade number + out.println("REGISTER " + savedNumber.split(":")[0] + " " + savedNumber.split(":")[1] + " " + getPublicKey()); + }else{ + //We have no ':' in the number, use identity version + //Request random for auth + Log.w("CC", "Requesting random"); + out.println("AUTH"); + String rnd = in.readLine(); + Log.w("CC", "Got random"); + out.println("SIG " + savedNumber + " " + signData(rnd)); + Log.w("CC", "Auth with RSA " + signData(rnd)); } } - //If no number has been saved - if(number == 0){ - out.println("REQUEST NUMBER"); - String numberKey = in.readLine(); - this.number = Integer.parseInt(numberKey.split(" ")[1]); - listener.setNumber(this.number); - Log.w("CC", Integer.toString(number)); - listener.saveString("number", numberKey.split(" ")[1] + ":" + numberKey.split(" ")[2]); - out.println("DEVICE " + listener.getGcmDeviceId()); + //Handle number response + String response = in.readLine(); + if(response.startsWith("NUMBER ")){ + Log.w("CC", "Got a new number"); + //We got a new number, transmitt our device id + listener.saveString("number", response.split(" ")[1]); + this.number = Integer.parseInt(response.split(" ")[1]); + if(listener.getGcmDeviceId() != null) + out.println("DEVICE " + listener.getGcmDeviceId()); + }else if(response.equals("ASSOCIATED")){ //If we successfully associated we must have successfully requested an upgrade or successfully authed with our identity + Log.w("CC", "Associated"); + //We successfully associated + String current = listener.getString("number"); + if (current.contains(":")){ + //Remove old auth key + listener.saveString("number", current.split(":")[0]); + this.number = Integer.parseInt(current.split(":")[0]); + }else{ + this.number = Integer.parseInt(current); + } + if (!listener.getString("gcm").equals("registered")){ + if(listener.getGcmDeviceId() != null) + out.println("DEVICE " + listener.getGcmDeviceId()); + } + }else{ + throw new Exception("Unknown exception when connecting"); } + listener.setNumber(this.number); + listener.ControlChannelConnected(); while(socket.isConnected() && !isCancelled()){ final String cmd = in.readLine(); listener.messageReceived(cmd); @@ -198,14 +321,8 @@ public void run() { if(cmd.startsWith("LINK ")) { int callerId = Integer.parseInt(cmd.split(" ")[1]); listener.initializeCall(callerId); - String key = ""; - if(cmd.split(" ").length == 4){ - key = cmd.split(" ")[3]; - listener.callSecure(true); - }else{ - listener.callSecure(false); - } - currentCall.start(server + ":" + cmd.split(" ")[2], key); + currentCall.start(server + ":" + cmd.split(" ")[2], cmd.split(" ")[3]); + listener.callSecure(true); } if(cmd.startsWith("HANGUP ")){ int callerId = Integer.parseInt(cmd.split(" ")[1]); @@ -236,5 +353,48 @@ public void run() { } return; } + + public String signData(String message) throws Exception{ + byte[] data = message.getBytes(); + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initSign(identity.getPrivate()); + sig.update(data); + return bytesToHex(sig.sign()); + } + + public boolean verifyData(String message, String signature, String publickey) throws Exception{ + X509EncodedKeySpec spec = new X509EncodedKeySpec(hexStringToByteArray(publickey)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey key = keyFactory.generatePublic(spec); + byte[] data = message.getBytes(); + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initVerify(key); + sig.update(data); + return sig.verify(hexStringToByteArray(signature)); + } + + + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } + + public static String bytesToHex(byte[] bytes) { + char[] hexArray = "0123456789ABCDEF".toCharArray(); + char[] hexChars = new char[bytes.length * 2]; + for ( int j = 0; j < bytes.length; j++ ) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + } diff --git a/app/src/main/java/io/rtek/rtvoice/IControlChannelListener.java b/app/src/main/java/io/rtek/rtvoice/IControlChannelListener.java index 5e89517..8a19286 100644 --- a/app/src/main/java/io/rtek/rtvoice/IControlChannelListener.java +++ b/app/src/main/java/io/rtek/rtvoice/IControlChannelListener.java @@ -2,6 +2,7 @@ import android.support.v4.util.Pair; +import java.io.InputStream; import java.security.KeyStore; import javax.net.ssl.KeyManagerFactory; @@ -23,6 +24,7 @@ public interface IControlChannelListener { void saveString(String key, String value); void ControlChannelConnected(); void ControlChannelDisconnected(); - Pair getTrustManagerFactory(); + + InputStream getServerCertificate(); String getGcmDeviceId(); } diff --git a/app/src/main/java/io/rtek/rtvoice/Main.java b/app/src/main/java/io/rtek/rtvoice/Main.java index 756e0ba..c3c59e2 100644 --- a/app/src/main/java/io/rtek/rtvoice/Main.java +++ b/app/src/main/java/io/rtek/rtvoice/Main.java @@ -3,7 +3,6 @@ import android.Manifest; import android.app.Activity; import android.app.ActivityManager; -import android.app.IntentService; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -11,13 +10,15 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.res.AssetFileDescriptor; import android.database.Cursor; -import android.graphics.drawable.Icon; import android.media.AudioManager; +import android.media.MediaPlayer; import android.media.Ringtone; +import android.os.Vibrator; + import android.media.RingtoneManager; import android.net.Uri; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.PowerManager; @@ -28,53 +29,45 @@ import android.support.v4.content.ContextCompat; import android.support.v4.util.Pair; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.View; -import android.view.Menu; -import android.view.MenuItem; import android.view.WindowManager; -import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.InputMethodManager; import android.widget.Button; -import android.widget.CheckBox; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; - -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GooglePlayServicesUtil; -import com.google.android.gms.gcm.GcmListenerService; import com.google.android.gms.gcm.GoogleCloudMessaging; -import com.google.android.gms.iid.InstanceID; - import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.security.KeyStore; -import java.security.KeyStoreException; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.util.List; - -import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; public class Main extends AppCompatActivity implements IControlChannelListener{ - Snackbar sb; - ControlChannel cc; - int callerId = 0; - String server = Settings.server; - final Activity main = this; + private Snackbar sb; + private ControlChannel cc; + private final Activity main = this; private BroadcastReceiver receiver; + private static final int RESULT_PICK_CONTACT = 10; + private PowerManager powerManager; + private PowerManager.WakeLock wakeLock; + private static int field = 0x00000020; + private Ringtone r; + MediaPlayer dialingSound; + + AssetFileDescriptor afd; + @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - cc = new ControlChannel(this, server); + cc = new ControlChannel(this, Settings.server); cc.start(); getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); @@ -92,15 +85,12 @@ protected void onCreate(Bundle savedInstanceState) { wakeLock = powerManager.newWakeLock(field, getLocalClassName()); setMainView(); - //Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - //setSupportActionBar(toolbar); AudioManager m_amAudioManager; m_amAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); m_amAudioManager.setMode(AudioManager.MODE_IN_CALL); m_amAudioManager.setSpeakerphoneOn(false); m_amAudioManager.setRouting(AudioManager.MODE_NORMAL,AudioManager.ROUTE_EARPIECE, AudioManager.ROUTE_ALL); - requestRecordAudioPermission(); this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE|WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); @@ -119,6 +109,16 @@ public void onReceive(Context context, Intent intent) { } }; registerReceiver(receiver, filter); + try { + dialingSound = new MediaPlayer(); + afd = getActivity().getAssets().openFd("dialing.wav"); + dialingSound.setLooping(true); + dialingSound.setDataSource(afd.getFileDescriptor(),afd.getStartOffset(),afd.getLength()); + dialingSound.setAudioStreamType(AudioManager.STREAM_VOICE_CALL); + dialingSound.setVolume(0.3F,0.3F); + } catch (Exception e) { + e.printStackTrace(); + } } @Override @@ -141,7 +141,7 @@ protected void onDestroy() { cc.close(); super.onDestroy(); } - private static final int RESULT_PICK_CONTACT = 10; + private void setMainView(){ setContentView(R.layout.activity_main); TextView tv = (TextView) findViewById(R.id.textView2); @@ -152,6 +152,8 @@ private void setMainView(){ sb = Snackbar.make(findViewById(R.id.content_main), "Unknown error", Snackbar.LENGTH_INDEFINITE); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); final IControlChannelListener main = this; + if(dialingSound != null) + dialingSound.stop(); tv.setText(Integer.toString(cc.getNumber())); fab.setOnClickListener(new View.OnClickListener() { @Override @@ -162,6 +164,19 @@ public void onClick(View view) { } if (number.getText().length() != 0){ cc.dial(Integer.parseInt(number.getText().toString())); + if (dialingSound != null) { + dialingSound.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + mp.start(); + } + }); + try { + dialingSound.prepareAsync(); + } catch (Exception e) { + e.printStackTrace(); + } + } setInCallView(); } } @@ -223,13 +238,6 @@ public void onStop() { hideKeyboard(); } - int createIncoming = 0; - - - private PowerManager powerManager; - private PowerManager.WakeLock wakeLock; - private int field = 0x00000020; - private void setInCallView(){ hideKeyboard(); setContentView(R.layout.in_call); @@ -237,6 +245,8 @@ private void setInCallView(){ hangupBtn.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View view) { + if(dialingSound != null) + dialingSound.stop(); cc.hangup(); } }); @@ -252,14 +262,27 @@ private void setConnectingView(){ hideKeyboard(); setContentView(R.layout.connecting); } - Ringtone r; + private void setIncomingCallView(final int number){ hideKeyboard(); setContentView(R.layout.incoming_call); final Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE); r = RingtoneManager.getRingtone(getApplicationContext(), notification); r.play(); - + new Thread(new Runnable() { + @Override + public void run() { + try { + while(r.isPlaying()) { + Vibrator v = (Vibrator) main.getSystemService(Context.VIBRATOR_SERVICE); + v.vibrate(1000); + Thread.sleep(5000); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).start(); if(isAppIsInBackground(this)) { Intent intent = new Intent(this, Main.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -378,6 +401,8 @@ public void run() { case CONNECTED: tv.setText("Ongoing call"); pb.setVisibility(View.INVISIBLE); + if(dialingSound != null) + dialingSound.stop(); break; case ERROR: setMainView(); @@ -459,29 +484,15 @@ public void run() { } @Override - public Pair getTrustManagerFactory() { + public InputStream getServerCertificate() { try { - KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - trustStore.load(null); - InputStream stream = this.getAssets().open("server.crt"); - BufferedInputStream bis = new BufferedInputStream(stream); - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - while (bis.available() > 0) { - Certificate cert = cf.generateCertificate(bis); - trustStore.setCertificateEntry("cert" + bis.available(), cert); - } - KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmfactory.init(trustStore, "1234".toCharArray()); - TrustManagerFactory tmf=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init(trustStore); - return new Pair<>(tmf, kmfactory); - } catch (Exception e) { + return this.getAssets().open("server.crt"); + } catch (IOException e) { e.printStackTrace(); + return null; } - return null; } - @Override public String getGcmDeviceId() { try { diff --git a/server/Switchboard.java b/server/Switchboard.java index aa90e16..9bca555 100644 --- a/server/Switchboard.java +++ b/server/Switchboard.java @@ -24,22 +24,6 @@ public static void main(String[] args) { //Create SSLSocket SSLServerSocket sslserversocket = (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(1338); - //Backwards compatiability (remove later!!!) - //Allow unencrypted on other port (for old clients) - new Thread(new Runnable() { - @Override - public void run() { - try { - ServerSocket serverSocket = new ServerSocket(1337); - while (true){ - new VoicePeer(serverSocket.accept()).start(); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - }).start(); - while (true){ new VoicePeer((SSLSocket) sslserversocket.accept()).start(); } diff --git a/server/VoicePeer.java b/server/VoicePeer.java index 701bb8e..9bc49a2 100644 --- a/server/VoicePeer.java +++ b/server/VoicePeer.java @@ -13,41 +13,61 @@ import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; public class VoicePeer extends Thread { private SSLSocket socket; - private Socket unsafeSocket; private int number = 0; private VoiceRelay relay; + private PublicKey publicKey; PrintWriter out; BufferedReader in; + private String authKey; public VoicePeer(SSLSocket socket) { this.socket = socket; } - public VoicePeer(Socket socket) { - this.unsafeSocket = socket; - } - public boolean secure(){ - return socket != null; - } public void run() { try { - if (socket == null){ - //Use unsafe socket if safe is unavailable (old clients) - out = new PrintWriter(unsafeSocket.getOutputStream(), true); - in = new BufferedReader(new InputStreamReader(unsafeSocket.getInputStream())); - }else{ - out = new PrintWriter(socket.getOutputStream(), true); - in = new BufferedReader(new InputStreamReader(socket.getInputStream())); - } + out = new PrintWriter(socket.getOutputStream(), true); + in = new BufferedReader(new InputStreamReader(socket.getInputStream())); String inputLine, outputLine; while ((inputLine = in.readLine()) != null) { System.out.println(inputLine); - if(inputLine.equals("REQUEST NUMBER")){ + + //Client requested some auth key data, save this and transmitt + if (inputLine.equals("AUTH")){ + SecureRandom random = new SecureRandom(); + this.authKey = new BigInteger(130, random).toString(32); + out.println(this.authKey); + continue; + } + + if(inputLine.startsWith("REQUEST NUMBER")){ + boolean rsaSupport = inputLine.split(" ").length > 2; Random rand = new Random(); try{ boolean exists = true; @@ -59,15 +79,21 @@ public void run() { } SecureRandom random = new SecureRandom(); PrintWriter writer = new PrintWriter(Settings.rootPath +Integer.toString(this.number) + ".rtv", "UTF-8"); - String key = new BigInteger(130, random).toString(32); - writer.println("key=" + key); + if (!rsaSupport){ + String key = new BigInteger(130, random).toString(32); + writer.println("key=" + key); + out.println("NUMBER " + Integer.toString(this.number) + " " + key); + } + if(rsaSupport){ + writer.println("publickey=" + inputLine.split(" ")[2]); + setPublicKey(inputLine.split(" ")[2]); + System.out.println("NUMBER " + Integer.toString(this.number)); + out.println("NUMBER " + Integer.toString(this.number)); + } writer.close(); - //Associate this peer with the server Switchboard.peers.put(this.number, this); - out.println("NUMBER " + Integer.toString(this.number) + " " + key); - System.out.println("NUMBER " + Integer.toString(this.number)); - } catch (IOException e) { - // do something + } catch (Exception e) { + e.printStackTrace(); } }else if (inputLine.startsWith("REGISTER ")){ try { @@ -80,6 +106,14 @@ public void run() { } BufferedReader reader = new BufferedReader(new FileReader(file)); String text = null; + //Check for public key and denie register if exist + while ((text = reader.readLine()) != null) { + if (text.startsWith("publickey=")){ + out.println("USE PUBLIC KEY"); + continue; + } + } + reader = new BufferedReader(new FileReader(file)); while ((text = reader.readLine()) != null) { if (text.startsWith("key=")){ if (!text.split("=")[1].equals(key)){ @@ -87,15 +121,48 @@ public void run() { }else{ Switchboard.peers.put(this.number, this); out.println("ASSOCIATED"); - System.out.println("ASSOCIATED " + Integer.toString(this.number) + " " + ((socket == null) ? "INSECURE" : "SECURE")); + System.out.println("ASSOCIATED " + Integer.toString(this.number) + " SECURE"); + if (inputLine.split(" ").length == 4){ + System.out.println("ASSOCIATED " + Integer.toString(this.number) + " SECURE AND UPGRADED"); + Writer output = new BufferedWriter(new FileWriter(Settings.rootPath + Integer.toString(this.number) + ".rtv", true)); + output.append("\npublickey=" + inputLine.split(" ")[3]); + output.close(); + } } - break; } } } catch (Exception e) { out.println("INVALID NUMBER FORMAT"); continue; } + }else if (inputLine.startsWith("SIG ")){ + try { + this.number = Integer.parseInt(inputLine.split(" ")[1]); + File file = new File(Settings.rootPath + Integer.toString(this.number) + ".rtv"); + if (!file.exists()){ + out.println("UNKNOWN NUMBER"); + continue; + } + BufferedReader reader = new BufferedReader(new FileReader(file)); + String text = null; + while ((text = reader.readLine()) != null) { + if (text.startsWith("publickey=")){ + setPublicKey(text.split("=")[1]); + } + } + if (verifyData(this.authKey, inputLine.split(" ")[2])){ + Switchboard.peers.put(this.number, this); + out.println("ASSOCIATED"); + System.out.println("ASSOCIATED " + Integer.toString(this.number) + " SECURE" + " RSA"); + }else{ + out.println("INVALID KEY"); + System.out.println("RSA VALIDATION FAILED FOR " + Integer.toString(this.number)); + } + } catch (Exception e) { + e.printStackTrace(); + out.println("SERVER ERROR"); + continue; + } } //Do not allow to continue below if not associated with a number @@ -165,14 +232,9 @@ public void run() { this.relay = callRelay; dstPeer.relay = callRelay; SecureRandom random = new SecureRandom(); - String key = new BigInteger(130, random).toString(); - if(dstPeer.secure() && this.secure()){ - dstPeer.out.println("LINK " + Integer.toString(this.number) + " " + Integer.toString(callRelay.getPair2Port()) + " " + bytesToHex(getRawKey(key.getBytes()))); - out.println("LINK " + Integer.toString(dstPeer.number) + " " + Integer.toString(callRelay.getPair1Port()) + " " + bytesToHex(getRawKey(key.getBytes()))); - }else{ - dstPeer.out.println("LINK " + Integer.toString(this.number) + " " + Integer.toString(callRelay.getPair2Port())); - out.println("LINK " + Integer.toString(dstPeer.number) + " " + Integer.toString(callRelay.getPair1Port())); - } + String key = bytesToHex(getRawKey(new BigInteger(130, random).toString().getBytes())); + dstPeer.out.println("LINK " + Integer.toString(this.number) + " " + Integer.toString(callRelay.getPair2Port()) + " " + key); + out.println("LINK " + Integer.toString(dstPeer.number) + " " + Integer.toString(callRelay.getPair1Port()) + " " + key); } catch (Exception e) { out.println("ASSOCIATION ERROR"); } @@ -244,8 +306,39 @@ public void run() { Switchboard.peers.remove(this.number); } - final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); + public boolean verifyData(String message, String signature) throws Exception{ + byte[] data = message.getBytes(); + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initVerify(this.publicKey); + sig.update(data); + return sig.verify(hexStringToByteArray(signature)); + } + + public String encryptRSA(String message) throws Exception{ + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, this.publicKey); + return bytesToHex(cipher.doFinal(message.getBytes())); + } + + + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } + + private void setPublicKey(String publicKey) throws Exception{ + X509EncodedKeySpec spec = new X509EncodedKeySpec(hexStringToByteArray(publicKey)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + this.publicKey = keyFactory.generatePublic(spec); + } + public static String bytesToHex(byte[] bytes) { + char[] hexArray = "0123456789ABCDEF".toCharArray(); char[] hexChars = new char[bytes.length * 2]; for ( int j = 0; j < bytes.length; j++ ) { int v = bytes[j] & 0xFF;