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;