mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-11 21:41:46 +00:00
So long redphone
// FREEBIE
This commit is contained in:
@@ -1,391 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
* Copyright (C) 2015 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.util.Log;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.thoughtcrime.redphone.ui.CallControls;
|
||||
import org.thoughtcrime.redphone.ui.CallScreen;
|
||||
import org.thoughtcrime.redphone.util.AudioUtils;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.events.RedPhoneEvent;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
|
||||
/**
|
||||
* The main UI class for RedPhone. Most of the heavy lifting is
|
||||
* done by RedPhoneService, so this activity is mostly responsible
|
||||
* for receiving events about the state of ongoing calls and displaying
|
||||
* the appropriate UI components.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class RedPhone extends Activity {
|
||||
|
||||
private static final String TAG = RedPhone.class.getSimpleName();
|
||||
|
||||
private static final int STANDARD_DELAY_FINISH = 1000;
|
||||
public static final int BUSY_SIGNAL_DELAY_FINISH = 5500;
|
||||
|
||||
public static final String ANSWER_ACTION = RedPhone.class.getCanonicalName() + ".ANSWER_ACTION";
|
||||
public static final String DENY_ACTION = RedPhone.class.getCanonicalName() + ".DENY_ACTION";
|
||||
public static final String END_CALL_ACTION = RedPhone.class.getCanonicalName() + ".END_CALL_ACTION";
|
||||
|
||||
private CallScreen callScreen;
|
||||
private BroadcastReceiver bluetoothStateReceiver;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
setContentView(R.layout.redphone);
|
||||
|
||||
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
|
||||
|
||||
initializeResources();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
initializeScreenshotSecurity();
|
||||
EventBus.getDefault().register(this);
|
||||
registerBluetoothReceiver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent){
|
||||
if (ANSWER_ACTION.equals(intent.getAction())) {
|
||||
handleAnswerCall();
|
||||
} else if (DENY_ACTION.equals(intent.getAction())) {
|
||||
handleDenyCall();
|
||||
} else if (END_CALL_ACTION.equals(intent.getAction())) {
|
||||
handleEndCall();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
EventBus.getDefault().unregister(this);
|
||||
unregisterReceiver(bluetoothStateReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfiguration) {
|
||||
super.onConfigurationChanged(newConfiguration);
|
||||
}
|
||||
|
||||
private void initializeScreenshotSecurity() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
|
||||
TextSecurePreferences.isScreenSecurityEnabled(this))
|
||||
{
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
callScreen = (CallScreen)findViewById(R.id.callScreen);
|
||||
callScreen.setHangupButtonListener(new HangupButtonListener());
|
||||
callScreen.setIncomingCallActionListener(new IncomingCallActionListener());
|
||||
callScreen.setMuteButtonListener(new MuteButtonListener());
|
||||
callScreen.setAudioButtonListener(new AudioButtonListener());
|
||||
}
|
||||
|
||||
private void handleSetMute(boolean enabled) {
|
||||
Intent intent = new Intent(this, RedPhoneService.class);
|
||||
intent.setAction(RedPhoneService.ACTION_SET_MUTE);
|
||||
intent.putExtra(RedPhoneService.EXTRA_MUTE, enabled);
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
private void handleAnswerCall() {
|
||||
RedPhoneEvent event = EventBus.getDefault().getStickyEvent(RedPhoneEvent.class);
|
||||
|
||||
if (event != null) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(org.thoughtcrime.securesms.R.string.RedPhone_answering));
|
||||
|
||||
Intent intent = new Intent(this, RedPhoneService.class);
|
||||
intent.setAction(RedPhoneService.ACTION_ANSWER_CALL);
|
||||
startService(intent);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDenyCall() {
|
||||
RedPhoneEvent event = EventBus.getDefault().getStickyEvent(RedPhoneEvent.class);
|
||||
|
||||
if (event != null) {
|
||||
Intent intent = new Intent(this, RedPhoneService.class);
|
||||
intent.setAction(RedPhoneService.ACTION_DENY_CALL);
|
||||
startService(intent);
|
||||
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(org.thoughtcrime.securesms.R.string.RedPhone_ending_call));
|
||||
delayedFinish();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEndCall() {
|
||||
Log.w(TAG, "Hangup pressed, handling termination now...");
|
||||
Intent intent = new Intent(RedPhone.this, RedPhoneService.class);
|
||||
intent.setAction(RedPhoneService.ACTION_HANGUP_CALL);
|
||||
startService(intent);
|
||||
|
||||
RedPhoneEvent event = EventBus.getDefault().getStickyEvent(RedPhoneEvent.class);
|
||||
|
||||
if (event != null) {
|
||||
RedPhone.this.handleTerminate(event.getRecipient());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleIncomingCall(@NonNull RedPhoneEvent event) {
|
||||
callScreen.setIncomingCall(event.getRecipient());
|
||||
}
|
||||
|
||||
private void handleOutgoingCall(@NonNull RedPhoneEvent event) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(org.thoughtcrime.securesms.R.string.RedPhone_dialing));
|
||||
}
|
||||
|
||||
private void handleTerminate(@NonNull Recipient recipient /*, int terminationType */) {
|
||||
Log.w(TAG, "handleTerminate called");
|
||||
Log.w(TAG, "Termination Stack:", new Exception());
|
||||
|
||||
callScreen.setActiveCall(recipient, getString(R.string.RedPhone_ending_call));
|
||||
EventBus.getDefault().removeStickyEvent(RedPhoneEvent.class);
|
||||
|
||||
delayedFinish();
|
||||
}
|
||||
|
||||
private void handleCallRinging(@NonNull RedPhoneEvent event) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_ringing));
|
||||
}
|
||||
|
||||
private void handleCallBusy(@NonNull RedPhoneEvent event) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_busy));
|
||||
|
||||
delayedFinish(BUSY_SIGNAL_DELAY_FINISH);
|
||||
}
|
||||
|
||||
private void handleCallConnected(@NonNull RedPhoneEvent event) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
|
||||
callScreen.setActiveCall(event.getRecipient(),
|
||||
getString(R.string.RedPhone_connected),
|
||||
event.getExtra());
|
||||
}
|
||||
|
||||
private void handleConnectingToInitiator(@NonNull RedPhoneEvent event) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_connecting));
|
||||
}
|
||||
|
||||
private void handleHandshakeFailed(@NonNull RedPhoneEvent event) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_handshake_failed));
|
||||
delayedFinish();
|
||||
}
|
||||
|
||||
private void handleRecipientUnavailable(@NonNull RedPhoneEvent event) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_recipient_unavailable));
|
||||
delayedFinish();
|
||||
}
|
||||
|
||||
private void handlePerformingHandshake(@NonNull RedPhoneEvent event) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_performing_handshake));
|
||||
}
|
||||
|
||||
private void handleServerFailure(@NonNull RedPhoneEvent event) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_network_failed));
|
||||
delayedFinish();
|
||||
}
|
||||
|
||||
private void handleClientFailure(final @NonNull RedPhoneEvent event) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_client_failed));
|
||||
if( event.getExtra() != null && !isFinishing() ) {
|
||||
AlertDialog.Builder ad = new AlertDialog.Builder(this);
|
||||
ad.setTitle(R.string.RedPhone_fatal_error);
|
||||
ad.setMessage(event.getExtra());
|
||||
ad.setCancelable(false);
|
||||
ad.setPositiveButton(android.R.string.ok, new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int arg) {
|
||||
RedPhone.this.handleTerminate(event.getRecipient());
|
||||
}
|
||||
});
|
||||
ad.show();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLoginFailed(@NonNull RedPhoneEvent event) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_login_failed));
|
||||
delayedFinish();
|
||||
}
|
||||
|
||||
private void handleServerMessage(final @NonNull RedPhoneEvent event) {
|
||||
if( isFinishing() ) return; //we're already shutting down, this might crash
|
||||
AlertDialog.Builder ad = new AlertDialog.Builder(this);
|
||||
ad.setTitle(R.string.RedPhone_message_from_the_server);
|
||||
ad.setMessage(event.getExtra());
|
||||
ad.setCancelable(false);
|
||||
ad.setPositiveButton(android.R.string.ok, new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int arg) {
|
||||
RedPhone.this.handleTerminate(event.getRecipient());
|
||||
}
|
||||
});
|
||||
ad.show();
|
||||
}
|
||||
|
||||
private void handleNoSuchUser(final @NonNull RedPhoneEvent event) {
|
||||
if (isFinishing()) return; // XXX Stuart added this check above, not sure why, so I'm repeating in ignorance. - moxie
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
|
||||
dialog.setTitle(R.string.RedPhone_number_not_registered);
|
||||
dialog.setIconAttribute(R.attr.dialog_alert_icon);
|
||||
dialog.setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice);
|
||||
dialog.setCancelable(true);
|
||||
dialog.setPositiveButton(R.string.RedPhone_got_it, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
RedPhone.this.handleTerminate(event.getRecipient());
|
||||
}
|
||||
});
|
||||
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
RedPhone.this.handleTerminate(event.getRecipient());
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void delayedFinish() {
|
||||
delayedFinish(STANDARD_DELAY_FINISH);
|
||||
}
|
||||
|
||||
private void delayedFinish(int delayMillis) {
|
||||
callScreen.postDelayed(new Runnable() {
|
||||
public void run() {
|
||||
RedPhone.this.finish();
|
||||
}
|
||||
}, delayMillis);
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(final RedPhoneEvent event) {
|
||||
Log.w(TAG, "Got message from service: " + event.getType());
|
||||
|
||||
switch (event.getType()) {
|
||||
case CALL_CONNECTED: handleCallConnected(event); break;
|
||||
case SERVER_FAILURE: handleServerFailure(event); break;
|
||||
case PERFORMING_HANDSHAKE: handlePerformingHandshake(event); break;
|
||||
case HANDSHAKE_FAILED: handleHandshakeFailed(event); break;
|
||||
case CONNECTING_TO_INITIATOR: handleConnectingToInitiator(event); break;
|
||||
case CALL_RINGING: handleCallRinging(event); break;
|
||||
case CALL_DISCONNECTED: handleTerminate(event.getRecipient()); break;
|
||||
case SERVER_MESSAGE: handleServerMessage(event); break;
|
||||
case NO_SUCH_USER: handleNoSuchUser(event); break;
|
||||
case RECIPIENT_UNAVAILABLE: handleRecipientUnavailable(event); break;
|
||||
case INCOMING_CALL: handleIncomingCall(event); break;
|
||||
case OUTGOING_CALL: handleOutgoingCall(event); break;
|
||||
case CALL_BUSY: handleCallBusy(event); break;
|
||||
case LOGIN_FAILED: handleLoginFailed(event); break;
|
||||
case CLIENT_FAILURE: handleClientFailure(event); break;
|
||||
}
|
||||
}
|
||||
|
||||
private class HangupButtonListener implements CallControls.HangupButtonListener {
|
||||
public void onClick() {
|
||||
handleEndCall();
|
||||
}
|
||||
}
|
||||
|
||||
private class MuteButtonListener implements CallControls.MuteButtonListener {
|
||||
@Override
|
||||
public void onToggle(boolean isMuted) {
|
||||
RedPhone.this.handleSetMute(isMuted);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerBluetoothReceiver() {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(AudioUtils.getScoUpdateAction());
|
||||
bluetoothStateReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
callScreen.notifyBluetoothChange();
|
||||
}
|
||||
};
|
||||
|
||||
registerReceiver(bluetoothStateReceiver, filter);
|
||||
callScreen.notifyBluetoothChange();
|
||||
}
|
||||
|
||||
private class AudioButtonListener implements CallControls.AudioButtonListener {
|
||||
@Override
|
||||
public void onAudioChange(AudioUtils.AudioMode mode) {
|
||||
switch(mode) {
|
||||
case DEFAULT:
|
||||
AudioUtils.enableDefaultRouting(RedPhone.this);
|
||||
break;
|
||||
case SPEAKER:
|
||||
AudioUtils.enableSpeakerphoneRouting(RedPhone.this);
|
||||
break;
|
||||
case HEADSET:
|
||||
AudioUtils.enableBluetoothRouting(RedPhone.this);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Audio mode " + mode + " is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class IncomingCallActionListener implements CallControls.IncomingCallActionListener {
|
||||
@Override
|
||||
public void onAcceptClick() {
|
||||
RedPhone.this.handleAnswerCall();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDenyClick() {
|
||||
RedPhone.this.handleDenyCall();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,607 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.ResultReceiver;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.thoughtcrime.redphone.audio.IncomingRinger;
|
||||
import org.thoughtcrime.redphone.audio.OutgoingRinger;
|
||||
import org.thoughtcrime.redphone.call.CallManager;
|
||||
import org.thoughtcrime.redphone.call.CallStateListener;
|
||||
import org.thoughtcrime.redphone.call.InitiatingCallManager;
|
||||
import org.thoughtcrime.redphone.call.LockManager;
|
||||
import org.thoughtcrime.redphone.call.ResponderCallManager;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.SASInfo;
|
||||
import org.thoughtcrime.redphone.pstn.IncomingPstnCallReceiver;
|
||||
import org.thoughtcrime.redphone.signaling.OtpCounterProvider;
|
||||
import org.thoughtcrime.redphone.signaling.SessionDescriptor;
|
||||
import org.thoughtcrime.redphone.signaling.SignalingException;
|
||||
import org.thoughtcrime.redphone.signaling.SignalingSocket;
|
||||
import org.thoughtcrime.redphone.ui.NotificationBarManager;
|
||||
import org.thoughtcrime.redphone.util.AudioUtils;
|
||||
import org.thoughtcrime.redphone.util.UncaughtExceptionHandlerManager;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.events.RedPhoneEvent;
|
||||
import org.thoughtcrime.securesms.events.RedPhoneEvent.Type;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.internal.util.concurrent.SettableFuture;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
|
||||
/**
|
||||
* The major entry point for all of the heavy lifting associated with
|
||||
* setting up, tearing down, or managing calls. The service spins up
|
||||
* either from a broadcast listener that has detected an incoming call,
|
||||
* or from a UI element that wants to initiate an outgoing call.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class RedPhoneService extends Service implements CallStateListener {
|
||||
|
||||
private static final String TAG = RedPhoneService.class.getSimpleName();
|
||||
|
||||
private static final int STATE_IDLE = 0;
|
||||
private static final int STATE_RINGING = 2;
|
||||
private static final int STATE_DIALING = 3;
|
||||
private static final int STATE_ANSWERING = 4;
|
||||
private static final int STATE_CONNECTED = 5;
|
||||
|
||||
public static final String EXTRA_REMOTE_NUMBER = "remote_number";
|
||||
public static final String EXTRA_SESSION_DESCRIPTOR = "session_descriptor";
|
||||
public static final String EXTRA_MUTE = "mute_value";
|
||||
public static final String EXTRA_RESULT_RECEIVER = "result_receiver";
|
||||
|
||||
public static final String ACTION_INCOMING_CALL = "org.thoughtcrime.redphone.RedPhoneService.INCOMING_CALL";
|
||||
public static final String ACTION_OUTGOING_CALL = "org.thoughtcrime.redphone.RedPhoneService.OUTGOING_CALL";
|
||||
public static final String ACTION_ANSWER_CALL = "org.thoughtcrime.redphone.RedPhoneService.ANSWER_CALL";
|
||||
public static final String ACTION_DENY_CALL = "org.thoughtcrime.redphone.RedPhoneService.DENY_CALL";
|
||||
public static final String ACTION_HANGUP_CALL = "org.thoughtcrime.redphone.RedPhoneService.HANGUP";
|
||||
public static final String ACTION_SET_MUTE = "org.thoughtcrime.redphone.RedPhoneService.SET_MUTE";
|
||||
public static final String ACTION_IS_IN_CALL_QUERY = "org.thoughtcrime.redphone.RedPhoneService.IS_IN_CALL";
|
||||
|
||||
private final Handler serviceHandler = new Handler();
|
||||
|
||||
private OutgoingRinger outgoingRinger;
|
||||
private IncomingRinger incomingRinger;
|
||||
|
||||
private int state;
|
||||
private byte[] zid;
|
||||
private String remoteNumber;
|
||||
private CallManager currentCallManager;
|
||||
private LockManager lockManager;
|
||||
private UncaughtExceptionHandlerManager uncaughtExceptionHandlerManager;
|
||||
|
||||
private IncomingPstnCallReceiver pstnCallListener;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
initializeResources();
|
||||
initializeRingers();
|
||||
initializePstnCallListener();
|
||||
registerUncaughtExceptionHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(Intent intent, int startId) {
|
||||
Log.w(TAG, "onStart(): " + intent);
|
||||
if (intent == null) return;
|
||||
new Thread(new IntentRunnable(intent)).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
unregisterReceiver(pstnCallListener);
|
||||
NotificationBarManager.setCallEnded(this);
|
||||
uncaughtExceptionHandlerManager.unregister();
|
||||
}
|
||||
|
||||
private synchronized void onIntentReceived(Intent intent) {
|
||||
Log.w(TAG, "Received Intent: " + intent.getAction());
|
||||
|
||||
if (intent.getAction().equals(ACTION_INCOMING_CALL) && isBusy()) handleBusyCall(intent);
|
||||
else if (intent.getAction().equals(ACTION_INCOMING_CALL)) handleIncomingCall(intent);
|
||||
else if (intent.getAction().equals(ACTION_OUTGOING_CALL) && isIdle()) handleOutgoingCall(intent);
|
||||
else if (intent.getAction().equals(ACTION_ANSWER_CALL)) handleAnswerCall(intent);
|
||||
else if (intent.getAction().equals(ACTION_DENY_CALL)) handleDenyCall(intent);
|
||||
else if (intent.getAction().equals(ACTION_HANGUP_CALL)) handleHangupCall(intent);
|
||||
else if (intent.getAction().equals(ACTION_SET_MUTE)) handleSetMute(intent);
|
||||
else if (intent.getAction().equals(ACTION_IS_IN_CALL_QUERY)) handleIsInCallQuery(intent);
|
||||
else Log.w(TAG, "Unhandled intent: " + intent.getAction() + ", state: " + state);
|
||||
}
|
||||
|
||||
///// Initializers
|
||||
|
||||
private void initializeRingers() {
|
||||
this.outgoingRinger = new OutgoingRinger(this);
|
||||
this.incomingRinger = new IncomingRinger(this);
|
||||
}
|
||||
|
||||
private void initializePstnCallListener() {
|
||||
pstnCallListener = new IncomingPstnCallReceiver();
|
||||
registerReceiver(pstnCallListener, new IntentFilter("android.intent.action.PHONE_STATE"));
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
this.state = STATE_IDLE;
|
||||
this.zid = getZID();
|
||||
this.lockManager = new LockManager(this);
|
||||
}
|
||||
|
||||
private void registerUncaughtExceptionHandler() {
|
||||
uncaughtExceptionHandlerManager = new UncaughtExceptionHandlerManager();
|
||||
uncaughtExceptionHandlerManager.registerHandler(new ProximityLockRelease(lockManager));
|
||||
}
|
||||
|
||||
/// Intent Handlers
|
||||
|
||||
private void handleIncomingCall(Intent intent) {
|
||||
initializeAudio();
|
||||
|
||||
String localNumber = TextSecurePreferences.getLocalNumber(this);
|
||||
String password = TextSecurePreferences.getPushServerPassword(this);
|
||||
SessionDescriptor session = intent.getParcelableExtra(EXTRA_SESSION_DESCRIPTOR);
|
||||
|
||||
remoteNumber = intent.getStringExtra(EXTRA_REMOTE_NUMBER);
|
||||
state = STATE_RINGING;
|
||||
|
||||
lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING);
|
||||
this.currentCallManager = new ResponderCallManager(this, this, remoteNumber, localNumber,
|
||||
password, session, zid);
|
||||
this.currentCallManager.start();
|
||||
}
|
||||
|
||||
private void handleOutgoingCall(Intent intent) {
|
||||
initializeAudio();
|
||||
|
||||
String localNumber = TextSecurePreferences.getLocalNumber(this);
|
||||
String password = TextSecurePreferences.getPushServerPassword(this);
|
||||
|
||||
remoteNumber = intent.getStringExtra(EXTRA_REMOTE_NUMBER);
|
||||
|
||||
if (remoteNumber == null || remoteNumber.length() == 0)
|
||||
return;
|
||||
|
||||
Recipient recipient = getRecipient();
|
||||
sendMessage(Type.OUTGOING_CALL, recipient, null);
|
||||
|
||||
state = STATE_DIALING;
|
||||
lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL);
|
||||
this.currentCallManager = new InitiatingCallManager(this, this, localNumber, password,
|
||||
remoteNumber, zid);
|
||||
this.currentCallManager.start();
|
||||
|
||||
NotificationBarManager.setCallInProgress(this, NotificationBarManager.TYPE_OUTGOING_RINGING, recipient);
|
||||
DatabaseFactory.getSmsDatabase(this).insertOutgoingCall(remoteNumber);
|
||||
|
||||
}
|
||||
|
||||
private void handleBusyCall(Intent intent) {
|
||||
String localNumber = TextSecurePreferences.getLocalNumber(this);
|
||||
String password = TextSecurePreferences.getPushServerPassword(this);
|
||||
SessionDescriptor session = intent.getParcelableExtra(EXTRA_SESSION_DESCRIPTOR);
|
||||
|
||||
if (currentCallManager != null && session.equals(currentCallManager.getSessionDescriptor())) {
|
||||
Log.w(TAG, "Duplicate incoming call signal, ignoring...");
|
||||
return;
|
||||
}
|
||||
|
||||
handleMissedCall(intent.getStringExtra(EXTRA_REMOTE_NUMBER), false);
|
||||
|
||||
try {
|
||||
SignalingSocket signalingSocket = new SignalingSocket(this, session.getFullServerName(),
|
||||
31337,
|
||||
localNumber, password,
|
||||
OtpCounterProvider.getInstance());
|
||||
|
||||
signalingSocket.setBusy(session.sessionId);
|
||||
signalingSocket.close();
|
||||
} catch (SignalingException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMissedCall(String remoteNumber, boolean signal) {
|
||||
Pair<Long, Long> messageAndThreadId = DatabaseFactory.getSmsDatabase(this).insertMissedCall(remoteNumber);
|
||||
MessageNotifier.updateNotification(this, KeyCachingService.getMasterSecret(this),
|
||||
messageAndThreadId.second, signal);
|
||||
}
|
||||
|
||||
private void handleAnswerCall(Intent intent) {
|
||||
state = STATE_ANSWERING;
|
||||
incomingRinger.stop();
|
||||
DatabaseFactory.getSmsDatabase(this).insertReceivedCall(remoteNumber);
|
||||
if (currentCallManager != null) {
|
||||
((ResponderCallManager)this.currentCallManager).answer(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDenyCall(Intent intent) {
|
||||
state = STATE_IDLE;
|
||||
incomingRinger.stop();
|
||||
DatabaseFactory.getSmsDatabase(this).insertMissedCall(remoteNumber);
|
||||
if(currentCallManager != null) {
|
||||
((ResponderCallManager)this.currentCallManager).answer(false);
|
||||
}
|
||||
this.terminate();
|
||||
}
|
||||
|
||||
private void handleHangupCall(Intent intent) {
|
||||
this.terminate();
|
||||
}
|
||||
|
||||
private void handleSetMute(Intent intent) {
|
||||
if(currentCallManager != null) {
|
||||
currentCallManager.setMute(intent.getBooleanExtra(EXTRA_MUTE, false));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleIsInCallQuery(Intent intent) {
|
||||
ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
|
||||
|
||||
if (resultReceiver != null) {
|
||||
resultReceiver.send(state != STATE_IDLE ? 1 : 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper Methods
|
||||
|
||||
private boolean isBusy() {
|
||||
TelephonyManager telephonyManager = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
|
||||
|
||||
return ((currentCallManager != null && state != STATE_IDLE) ||
|
||||
telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) ||
|
||||
WebRtcCallService.isCallActive(this);
|
||||
}
|
||||
|
||||
private boolean isIdle() {
|
||||
return state == STATE_IDLE;
|
||||
}
|
||||
|
||||
private void initializeAudio() {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(this);
|
||||
AudioUtils.resetConfiguration(this);
|
||||
|
||||
Log.d(TAG, "request STREAM_VOICE_CALL transient audio focus");
|
||||
audioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL,
|
||||
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
|
||||
}
|
||||
|
||||
private void shutdownAudio() {
|
||||
Log.d(TAG, "reset audio mode and abandon focus");
|
||||
AudioUtils.resetConfiguration(this);
|
||||
AudioManager am = ServiceUtil.getAudioManager(this);
|
||||
am.setMode(AudioManager.MODE_NORMAL);
|
||||
am.abandonAudioFocus(null);
|
||||
am.stopBluetoothSco();
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public @NonNull Recipient getRecipient() {
|
||||
if (!TextUtils.isEmpty(remoteNumber)) {
|
||||
//noinspection ConstantConditions
|
||||
return RecipientFactory.getRecipientsFromString(this, remoteNumber, true)
|
||||
.getPrimaryRecipient();
|
||||
} else {
|
||||
return Recipient.getUnknownRecipient();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getZID() {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
if (preferences.contains("ZID")) {
|
||||
try {
|
||||
return Base64.decode(preferences.getString("ZID", null));
|
||||
} catch (IOException e) {
|
||||
return setZID();
|
||||
}
|
||||
} else {
|
||||
return setZID();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] setZID() {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
try {
|
||||
byte[] zid = new byte[12];
|
||||
SecureRandom.getInstance("SHA1PRNG").nextBytes(zid);
|
||||
String encodedZid = Base64.encodeBytes(zid);
|
||||
|
||||
preferences.edit().putString("ZID", encodedZid).commit();
|
||||
|
||||
return zid;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void startCallCardActivity() {
|
||||
Intent activityIntent = new Intent();
|
||||
activityIntent.setClass(this, RedPhone.class);
|
||||
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(activityIntent);
|
||||
}
|
||||
|
||||
private synchronized void terminate() {
|
||||
Log.w(TAG, "termination stack", new Exception());
|
||||
lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING);
|
||||
NotificationBarManager.setCallEnded(this);
|
||||
|
||||
incomingRinger.stop();
|
||||
outgoingRinger.stop();
|
||||
|
||||
if (currentCallManager != null) {
|
||||
currentCallManager.terminate();
|
||||
currentCallManager = null;
|
||||
}
|
||||
|
||||
shutdownAudio();
|
||||
|
||||
state = STATE_IDLE;
|
||||
lockManager.updatePhoneState(LockManager.PhoneState.IDLE);
|
||||
}
|
||||
|
||||
///////// CallStateListener Implementation
|
||||
|
||||
public void notifyCallStale() {
|
||||
Log.w(TAG, "Got a stale call, probably an old SMS...");
|
||||
handleMissedCall(remoteNumber, true);
|
||||
this.terminate();
|
||||
}
|
||||
|
||||
public void notifyCallFresh() {
|
||||
Log.w(TAG, "Good call, time to ring and display call card...");
|
||||
sendMessage(Type.INCOMING_CALL, getRecipient(), null);
|
||||
|
||||
lockManager.updatePhoneState(LockManager.PhoneState.INTERACTIVE);
|
||||
|
||||
startCallCardActivity();
|
||||
incomingRinger.start();
|
||||
|
||||
NotificationBarManager.setCallInProgress(this, NotificationBarManager.TYPE_INCOMING_RINGING, getRecipient());
|
||||
}
|
||||
|
||||
public void notifyBusy() {
|
||||
Log.w("RedPhoneService", "Got busy signal from responder!");
|
||||
sendMessage(Type.CALL_BUSY, getRecipient(), null);
|
||||
|
||||
outgoingRinger.playBusy();
|
||||
serviceHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
RedPhoneService.this.terminate();
|
||||
}
|
||||
}, RedPhone.BUSY_SIGNAL_DELAY_FINISH);
|
||||
}
|
||||
|
||||
public void notifyCallRinging() {
|
||||
outgoingRinger.playRing();
|
||||
sendMessage(Type.CALL_RINGING, getRecipient(), null);
|
||||
}
|
||||
|
||||
public void notifyCallConnected(SASInfo sas) {
|
||||
outgoingRinger.playComplete();
|
||||
lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL);
|
||||
state = STATE_CONNECTED;
|
||||
Recipient recipient = getRecipient();
|
||||
sendMessage(Type.CALL_CONNECTED, recipient, sas.getSasText());
|
||||
|
||||
NotificationBarManager.setCallInProgress(this, NotificationBarManager.TYPE_ESTABLISHED, recipient);
|
||||
}
|
||||
|
||||
public void notifyConnectingtoInitiator() {
|
||||
sendMessage(Type.CONNECTING_TO_INITIATOR, getRecipient(), null);
|
||||
}
|
||||
|
||||
public void notifyCallDisconnected() {
|
||||
if (state == STATE_RINGING)
|
||||
handleMissedCall(remoteNumber, false);
|
||||
|
||||
sendMessage(Type.CALL_DISCONNECTED, getRecipient(), null);
|
||||
this.terminate();
|
||||
}
|
||||
|
||||
public void notifyHandshakeFailed() {
|
||||
state = STATE_IDLE;
|
||||
outgoingRinger.playFailure();
|
||||
sendMessage(Type.HANDSHAKE_FAILED, getRecipient(), null);
|
||||
this.terminate();
|
||||
}
|
||||
|
||||
public void notifyRecipientUnavailable() {
|
||||
state = STATE_IDLE;
|
||||
outgoingRinger.playFailure();
|
||||
sendMessage(Type.RECIPIENT_UNAVAILABLE, getRecipient(), null);
|
||||
this.terminate();
|
||||
}
|
||||
|
||||
public void notifyPerformingHandshake() {
|
||||
outgoingRinger.playHandshake();
|
||||
sendMessage(Type.PERFORMING_HANDSHAKE, getRecipient(), null);
|
||||
}
|
||||
|
||||
public void notifyServerFailure() {
|
||||
if (state == STATE_RINGING)
|
||||
handleMissedCall(remoteNumber, true);
|
||||
|
||||
state = STATE_IDLE;
|
||||
outgoingRinger.playFailure();
|
||||
sendMessage(Type.SERVER_FAILURE, getRecipient(), null);
|
||||
this.terminate();
|
||||
}
|
||||
|
||||
public void notifyClientFailure() {
|
||||
if (state == STATE_RINGING)
|
||||
handleMissedCall(remoteNumber, false);
|
||||
|
||||
state = STATE_IDLE;
|
||||
outgoingRinger.playFailure();
|
||||
sendMessage(Type.CLIENT_FAILURE, getRecipient(), null);
|
||||
this.terminate();
|
||||
}
|
||||
|
||||
public void notifyLoginFailed() {
|
||||
if (state == STATE_RINGING)
|
||||
handleMissedCall(remoteNumber, true);
|
||||
|
||||
state = STATE_IDLE;
|
||||
outgoingRinger.playFailure();
|
||||
sendMessage(Type.LOGIN_FAILED, getRecipient(), null);
|
||||
this.terminate();
|
||||
}
|
||||
|
||||
public void notifyNoSuchUser() {
|
||||
sendMessage(Type.NO_SUCH_USER, getRecipient(), null);
|
||||
this.terminate();
|
||||
}
|
||||
|
||||
public void notifyServerMessage(String message) {
|
||||
sendMessage(Type.SERVER_MESSAGE, getRecipient(), message);
|
||||
this.terminate();
|
||||
}
|
||||
|
||||
public void notifyClientError(String msg) {
|
||||
sendMessage(Type.CLIENT_FAILURE, getRecipient(), msg);
|
||||
this.terminate();
|
||||
}
|
||||
|
||||
public void notifyCallConnecting() {
|
||||
outgoingRinger.playSonar();
|
||||
}
|
||||
|
||||
public void notifyWaitingForResponder() {}
|
||||
|
||||
private void sendMessage(@NonNull Type type,
|
||||
@NonNull Recipient recipient,
|
||||
@Nullable String error)
|
||||
{
|
||||
EventBus.getDefault().postSticky(new RedPhoneEvent(type, recipient, error));
|
||||
}
|
||||
|
||||
private class IntentRunnable implements Runnable {
|
||||
private final Intent intent;
|
||||
|
||||
public IntentRunnable(Intent intent) {
|
||||
this.intent = intent;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
onIntentReceived(intent);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ProximityLockRelease implements Thread.UncaughtExceptionHandler {
|
||||
private final LockManager lockManager;
|
||||
|
||||
private ProximityLockRelease(LockManager lockManager) {
|
||||
this.lockManager = lockManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||
Log.d(TAG, "Uncaught exception - releasing proximity lock", throwable);
|
||||
lockManager.updatePhoneState(LockManager.PhoneState.IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static boolean isCallActive(Context context) {
|
||||
Log.w(TAG, "isCallActive()");
|
||||
|
||||
HandlerThread handlerThread = null;
|
||||
|
||||
try {
|
||||
handlerThread = new HandlerThread("webrtc-callback");
|
||||
handlerThread.start();
|
||||
|
||||
final SettableFuture<Boolean> future = new SettableFuture<>();
|
||||
|
||||
ResultReceiver resultReceiver = new ResultReceiver(new Handler(handlerThread.getLooper())) {
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
Log.w(TAG, "onReceiveResult");
|
||||
future.set(resultCode == 1);
|
||||
}
|
||||
};
|
||||
|
||||
Intent intent = new Intent(context, RedPhoneService.class);
|
||||
intent.setAction(ACTION_IS_IN_CALL_QUERY);
|
||||
intent.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver);
|
||||
|
||||
context.startService(intent);
|
||||
|
||||
Log.w(TAG, "Blocking on result...");
|
||||
return future.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
Log.w(TAG, e);
|
||||
return false;
|
||||
} finally {
|
||||
if (handlerThread != null) handlerThread.quit();
|
||||
}
|
||||
}
|
||||
|
||||
public static void isCallActive(Context context, ResultReceiver resultReceiver) {
|
||||
Intent intent = new Intent(context, RedPhoneService.class);
|
||||
intent.setAction(ACTION_IS_IN_CALL_QUERY);
|
||||
intent.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver);
|
||||
|
||||
context.startService(intent);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package org.thoughtcrime.redphone;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||
import org.thoughtcrime.securesms.util.DirectoryHelper;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class VoiceCallShare extends Activity {
|
||||
|
||||
private static final String TAG = VoiceCallShare.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
if (getIntent().getData() != null && "content".equals(getIntent().getData().getScheme())) {
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = getContentResolver().query(getIntent().getData(), null, null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
String destination = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.RawContacts.Data.DATA1));
|
||||
|
||||
if (!TextUtils.isEmpty(destination)) {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(this, destination, true);
|
||||
DirectoryHelper.UserCapabilities capabilities = DirectoryHelper.getUserCapabilities(this, recipients);
|
||||
|
||||
if (TextSecurePreferences.isWebrtcCallingEnabled(this) &&
|
||||
capabilities.getVideoCapability() == DirectoryHelper.UserCapabilities.Capability.SUPPORTED)
|
||||
{
|
||||
Intent serviceIntent = new Intent(this, WebRtcCallService.class);
|
||||
serviceIntent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL);
|
||||
serviceIntent.putExtra(WebRtcCallService.EXTRA_REMOTE_NUMBER, destination);
|
||||
startService(serviceIntent);
|
||||
|
||||
Intent activityIntent = new Intent(this, WebRtcCallActivity.class);
|
||||
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(activityIntent);
|
||||
} else {
|
||||
Intent serviceIntent = new Intent(this, RedPhoneService.class);
|
||||
serviceIntent.setAction(RedPhoneService.ACTION_OUTGOING_CALL);
|
||||
serviceIntent.putExtra(RedPhoneService.EXTRA_REMOTE_NUMBER, destination);
|
||||
startService(serviceIntent);
|
||||
|
||||
Intent activityIntent = new Intent(this, RedPhone.class);
|
||||
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(activityIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.thoughtcrime.redphone.audio;
|
||||
|
||||
/**
|
||||
* An exception related to the audio subsystem.
|
||||
*
|
||||
* @author Stuart O. Anderson
|
||||
*/
|
||||
public class AudioException extends Exception {
|
||||
private final String clientMessage;
|
||||
|
||||
public AudioException(String clientMessage) {
|
||||
this.clientMessage = clientMessage;
|
||||
}
|
||||
|
||||
public AudioException(AudioException cause) {
|
||||
super(cause);
|
||||
this.clientMessage = cause.clientMessage;
|
||||
}
|
||||
|
||||
public String getClientMessage() {
|
||||
return clientMessage;
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package org.thoughtcrime.redphone.audio;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.DatagramSocketImpl;
|
||||
import java.net.SocketException;
|
||||
|
||||
public class CallAudioManager {
|
||||
|
||||
private static final String TAG = CallAudioManager.class.getSimpleName();
|
||||
|
||||
static {
|
||||
System.loadLibrary("redphone-audio");
|
||||
}
|
||||
|
||||
private final long handle;
|
||||
|
||||
public CallAudioManager(DatagramSocket socket, String remoteHost, int remotePort,
|
||||
byte[] senderCipherKey, byte[] senderMacKey, byte[] senderSalt,
|
||||
byte[] receiverCipherKey, byte[] receiverMacKey, byte[] receiverSalt)
|
||||
throws SocketException, AudioException
|
||||
{
|
||||
try {
|
||||
this.handle = create(Build.VERSION.SDK_INT, getFileDescriptor(socket), remoteHost, remotePort,
|
||||
senderCipherKey, senderMacKey, senderSalt,
|
||||
receiverCipherKey, receiverMacKey, receiverSalt);
|
||||
} catch (NativeAudioException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new AudioException("Sorry, there was a problem initiating audio on your device");
|
||||
}
|
||||
}
|
||||
|
||||
public void setMute(boolean enabled) {
|
||||
setMute(handle, enabled);
|
||||
}
|
||||
|
||||
public void start(@NonNull Context context) throws AudioException {
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
Log.d(TAG, "set MODE_IN_COMMUNICATION audio mode");
|
||||
ServiceUtil.getAudioManager(context).setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||
} else {
|
||||
// ServiceUtil.getAudioManager(context).setMode(AudioManager.MODE_IN_CALL);
|
||||
}
|
||||
|
||||
try {
|
||||
start(handle);
|
||||
} catch (NativeAudioException | NoSuchMethodError e) {
|
||||
Log.w(TAG, e);
|
||||
throw new AudioException("Sorry, there was a problem initiating the audio on your device.");
|
||||
}
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
stop(handle);
|
||||
dispose(handle);
|
||||
}
|
||||
|
||||
private static int getFileDescriptor(DatagramSocket socket) throws SocketException {
|
||||
try {
|
||||
socket.setSoTimeout(5000);
|
||||
Field implField = DatagramSocket.class.getDeclaredField("impl");
|
||||
implField.setAccessible(true);
|
||||
|
||||
DatagramSocketImpl implValue = (DatagramSocketImpl)implField.get(socket);
|
||||
|
||||
Field fdField = DatagramSocketImpl.class.getDeclaredField("fd");
|
||||
fdField.setAccessible(true);
|
||||
|
||||
FileDescriptor fdValue = (FileDescriptor)fdField.get(implValue);
|
||||
|
||||
Field descField = FileDescriptor.class.getDeclaredField("descriptor");
|
||||
descField.setAccessible(true);
|
||||
|
||||
return (Integer)descField.get(fdValue);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private native long create(int androidSdkVersion,
|
||||
int socketFd, String serverIpString, int serverPort,
|
||||
byte[] senderCipherKey, byte[] senderMacKey, byte[] senderSalt,
|
||||
byte[] receiverCipherKey, byte[] receiverMacKey, byte[] receiverSalt)
|
||||
throws NativeAudioException;
|
||||
|
||||
private native void start(long handle) throws NativeAudioException;
|
||||
|
||||
private native void setMute(long handle, boolean enabled);
|
||||
|
||||
private native void stop(long handle);
|
||||
|
||||
private native void dispose(long handle);
|
||||
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.audio;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Plays the 'incoming call' ringtone and manages the audio player state associated with this
|
||||
* process.
|
||||
*
|
||||
* @author Stuart O. Anderson
|
||||
*/
|
||||
public class IncomingRinger {
|
||||
private static final String TAG = IncomingRinger.class.getName();
|
||||
private static final long[] VIBRATE_PATTERN = {0, 1000, 1000};
|
||||
private final Context context;
|
||||
private final Vibrator vibrator;
|
||||
private MediaPlayer player;
|
||||
private final MediaPlayer.OnErrorListener playerErrorListener =
|
||||
new MediaPlayer.OnErrorListener() {
|
||||
@Override
|
||||
public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
|
||||
player = null;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
public IncomingRinger(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
vibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
}
|
||||
|
||||
private MediaPlayer createPlayer() {
|
||||
MediaPlayer newPlayer = new MediaPlayer();
|
||||
try {
|
||||
Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
|
||||
newPlayer.setOnErrorListener(playerErrorListener);
|
||||
newPlayer.setDataSource(context, ringtoneUri);
|
||||
newPlayer.setLooping(true);
|
||||
newPlayer.setAudioStreamType(AudioManager.STREAM_RING);
|
||||
return newPlayer;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to create player for incoming call ringer");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
|
||||
if (player != null) player.release();
|
||||
player = createPlayer();
|
||||
|
||||
int ringerMode = audioManager.getRingerMode();
|
||||
|
||||
if (shouldVibrate()) {
|
||||
Log.i(TAG, "Starting vibration");
|
||||
vibrator.vibrate(VIBRATE_PATTERN, 1);
|
||||
}
|
||||
|
||||
if (player != null && ringerMode == AudioManager.RINGER_MODE_NORMAL ) {
|
||||
Log.d(TAG, "set MODE_RINGTONE audio mode");
|
||||
audioManager.setMode(AudioManager.MODE_RINGTONE);
|
||||
try {
|
||||
if(!player.isPlaying()) {
|
||||
player.prepare();
|
||||
player.start();
|
||||
Log.d(TAG, "Playing ringtone now...");
|
||||
} else {
|
||||
Log.d(TAG, "Ringtone is already playing, declining to restart.");
|
||||
}
|
||||
} catch (IllegalStateException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
player = null;
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, " mode: " + ringerMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (player != null) {
|
||||
Log.d(TAG, "Stopping ringer");
|
||||
player.release();
|
||||
player = null;
|
||||
}
|
||||
Log.d(TAG, "Cancelling vibrator");
|
||||
vibrator.cancel();
|
||||
|
||||
Log.d(TAG, "reset audio mode");
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
audioManager.setMode(AudioManager.MODE_NORMAL);
|
||||
}
|
||||
|
||||
private boolean shouldVibrate() {
|
||||
if(player == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
return shouldVibrateNew(context);
|
||||
}
|
||||
return shouldVibrateOld(context);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
private boolean shouldVibrateNew(Context context) {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
|
||||
if (vibrator == null || !vibrator.hasVibrator()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean vibrateWhenRinging = Settings.System.getInt(context.getContentResolver(), "vibrate_when_ringing", 0) != 0;
|
||||
int ringerMode = audioManager.getRingerMode();
|
||||
if (vibrateWhenRinging) {
|
||||
return ringerMode != AudioManager.RINGER_MODE_SILENT;
|
||||
} else {
|
||||
return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldVibrateOld(Context context) {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
return audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.thoughtcrime.redphone.audio;
|
||||
|
||||
public class NativeAudioException extends Exception {
|
||||
|
||||
public NativeAudioException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NativeAudioException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public NativeAudioException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
public NativeAudioException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.audio;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Handles loading and playing the sequence of sounds we use to indicate call initialization.
|
||||
*
|
||||
* @author Stuart O. Anderson
|
||||
*/
|
||||
public class OutgoingRinger implements MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener {
|
||||
|
||||
private static final String TAG = OutgoingRinger.class.getSimpleName();
|
||||
|
||||
private MediaPlayer mediaPlayer;
|
||||
private int currentSoundID;
|
||||
private boolean loopEnabled;
|
||||
private Context context;
|
||||
|
||||
public OutgoingRinger(Context context) {
|
||||
this.context = context;
|
||||
|
||||
loopEnabled = true;
|
||||
currentSoundID = -1;
|
||||
|
||||
}
|
||||
|
||||
public void playSonar() {
|
||||
start(R.raw.redphone_sonarping);
|
||||
}
|
||||
|
||||
public void playHandshake() {
|
||||
start(R.raw.redphone_handshake);
|
||||
}
|
||||
|
||||
public void playRing() {
|
||||
start(R.raw.redphone_outring);
|
||||
}
|
||||
|
||||
public void playComplete() {
|
||||
stop(R.raw.redphone_completed);
|
||||
}
|
||||
|
||||
public void playFailure() {
|
||||
stop(R.raw.redphone_failure);
|
||||
}
|
||||
|
||||
public void playBusy() {
|
||||
start(R.raw.redphone_busy);
|
||||
}
|
||||
|
||||
private void setSound( int soundID ) {
|
||||
currentSoundID = soundID;
|
||||
loopEnabled = true;
|
||||
}
|
||||
|
||||
private void start( int soundID ) {
|
||||
if( soundID == currentSoundID ) return;
|
||||
setSound( soundID );
|
||||
start();
|
||||
}
|
||||
|
||||
private void start() {
|
||||
if( mediaPlayer != null ) mediaPlayer.release();
|
||||
mediaPlayer = new MediaPlayer();
|
||||
mediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);
|
||||
mediaPlayer.setOnCompletionListener(this);
|
||||
mediaPlayer.setOnPreparedListener(this);
|
||||
mediaPlayer.setLooping(loopEnabled);
|
||||
|
||||
String packageName = context.getPackageName();
|
||||
Uri dataUri = Uri.parse("android.resource://" + packageName + "/" + currentSoundID);
|
||||
|
||||
try {
|
||||
mediaPlayer.setDataSource(context, dataUri);
|
||||
mediaPlayer.prepareAsync();
|
||||
} catch (IllegalArgumentException | SecurityException | IllegalStateException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
// TODO Auto-generated catch block
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (mediaPlayer == null) return;
|
||||
mediaPlayer.release();
|
||||
mediaPlayer = null;
|
||||
|
||||
currentSoundID = -1;
|
||||
}
|
||||
|
||||
private void stop( int soundID ) {
|
||||
setSound( soundID );
|
||||
loopEnabled = false;
|
||||
start();
|
||||
}
|
||||
|
||||
public void onCompletion(MediaPlayer mp) {
|
||||
//mediaPlayer.release();
|
||||
//mediaPlayer = null;
|
||||
}
|
||||
|
||||
public void onPrepared(MediaPlayer mp) {
|
||||
AudioManager am = ServiceUtil.getAudioManager(context);
|
||||
|
||||
if (am.isBluetoothScoAvailableOffCall()) {
|
||||
Log.d(TAG, "bluetooth sco is available");
|
||||
try {
|
||||
am.startBluetoothSco();
|
||||
} catch (NullPointerException e) {
|
||||
// Lollipop bug (https://stackoverflow.com/questions/26642218/audiomanager-startbluetoothsco-crashes-on-android-lollipop)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
mp.start();
|
||||
} catch (IllegalStateException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.call;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.redphone.audio.AudioException;
|
||||
import org.thoughtcrime.redphone.audio.CallAudioManager;
|
||||
import org.thoughtcrime.redphone.crypto.SecureRtpSocket;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.MasterSecret;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.NegotiationFailedException;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.RecipientUnavailableException;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.SASInfo;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.ZRTPSocket;
|
||||
import org.thoughtcrime.redphone.signaling.SessionDescriptor;
|
||||
import org.thoughtcrime.redphone.signaling.SignalingSocket;
|
||||
import org.thoughtcrime.redphone.util.AudioUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.SocketException;
|
||||
|
||||
|
||||
/**
|
||||
* The base class for both Initiating and Responder call
|
||||
* managers, which coordinate the setup of an outgoing or
|
||||
* incoming call.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public abstract class CallManager extends Thread {
|
||||
|
||||
private static final String TAG = CallManager.class.getSimpleName();
|
||||
|
||||
protected final String remoteNumber;
|
||||
protected final CallStateListener callStateListener;
|
||||
protected final Context context;
|
||||
|
||||
private boolean terminated;
|
||||
protected CallAudioManager callAudioManager;
|
||||
private SignalManager signalManager;
|
||||
private SASInfo sasInfo;
|
||||
private boolean muteEnabled;
|
||||
private boolean callConnected;
|
||||
|
||||
protected SessionDescriptor sessionDescriptor;
|
||||
protected ZRTPSocket zrtpSocket;
|
||||
protected SecureRtpSocket secureSocket;
|
||||
protected SignalingSocket signalingSocket;
|
||||
|
||||
public CallManager(Context context, CallStateListener callStateListener,
|
||||
String remoteNumber, String threadName)
|
||||
{
|
||||
super(threadName);
|
||||
this.remoteNumber = remoteNumber;
|
||||
this.callStateListener = callStateListener;
|
||||
this.terminated = false;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
|
||||
|
||||
try {
|
||||
Log.d(TAG, "negotiating...");
|
||||
if (!terminated) {
|
||||
zrtpSocket.negotiateStart();
|
||||
}
|
||||
|
||||
if (!terminated) {
|
||||
callStateListener.notifyPerformingHandshake();
|
||||
zrtpSocket.negotiateFinish();
|
||||
}
|
||||
|
||||
if (!terminated) {
|
||||
sasInfo = zrtpSocket.getSasInfo();
|
||||
callStateListener.notifyCallConnected(sasInfo);
|
||||
}
|
||||
|
||||
if (!terminated) {
|
||||
Log.d(TAG, "Finished handshake, calling run() on CallAudioManager...");
|
||||
callConnected = true;
|
||||
runAudio(zrtpSocket.getDatagramSocket(), zrtpSocket.getRemoteIp(),
|
||||
zrtpSocket.getRemotePort(), zrtpSocket.getMasterSecret(), muteEnabled);
|
||||
}
|
||||
|
||||
} catch (RecipientUnavailableException rue) {
|
||||
Log.w(TAG, rue);
|
||||
if (!terminated) callStateListener.notifyRecipientUnavailable();
|
||||
} catch (NegotiationFailedException nfe) {
|
||||
Log.w(TAG, nfe);
|
||||
if (!terminated) callStateListener.notifyHandshakeFailed();
|
||||
} catch (AudioException e) {
|
||||
Log.w(TAG, e);
|
||||
callStateListener.notifyClientError(e.getClientMessage());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
callStateListener.notifyCallDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
this.terminated = true;
|
||||
|
||||
if (callAudioManager != null)
|
||||
callAudioManager.terminate();
|
||||
|
||||
if (signalManager != null)
|
||||
signalManager.terminate();
|
||||
|
||||
if (zrtpSocket != null)
|
||||
zrtpSocket.close();
|
||||
}
|
||||
|
||||
public SessionDescriptor getSessionDescriptor() {
|
||||
return this.sessionDescriptor;
|
||||
}
|
||||
|
||||
public SASInfo getSasInfo() {
|
||||
return this.sasInfo;
|
||||
}
|
||||
|
||||
protected void processSignals() {
|
||||
Log.w(TAG, "Starting signal processing loop...");
|
||||
this.signalManager = new SignalManager(callStateListener, signalingSocket, sessionDescriptor);
|
||||
}
|
||||
|
||||
protected abstract void runAudio(DatagramSocket datagramSocket, String remoteIp, int remotePort,
|
||||
MasterSecret masterSecret, boolean muteEnabled)
|
||||
throws SocketException, AudioException;
|
||||
|
||||
|
||||
public void setMute(boolean enabled) {
|
||||
muteEnabled = enabled;
|
||||
if (callAudioManager != null) {
|
||||
callAudioManager.setMute(muteEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Did this call ever successfully complete SRTP setup
|
||||
* @return true if the call connected
|
||||
*/
|
||||
public boolean callConnected() {
|
||||
return callConnected;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.call;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.SASInfo;
|
||||
|
||||
/**
|
||||
* An interface for those interested in receiving information
|
||||
* about the state of calls. RedPhoneService is the notable
|
||||
* implementor, which coordinates that information and relays
|
||||
* it on to the UI.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public interface CallStateListener {
|
||||
public void notifyNoSuchUser();
|
||||
public void notifyWaitingForResponder();
|
||||
public void notifyConnectingtoInitiator();
|
||||
public void notifyServerFailure();
|
||||
public void notifyClientFailure();
|
||||
public void notifyServerMessage(String serverMessage);
|
||||
public void notifyCallDisconnected();
|
||||
public void notifyCallRinging();
|
||||
public void notifyCallConnected(SASInfo sas);
|
||||
public void notifyPerformingHandshake();
|
||||
public void notifyHandshakeFailed();
|
||||
public void notifyRecipientUnavailable();
|
||||
public void notifyBusy();
|
||||
public void notifyLoginFailed();
|
||||
public void notifyCallStale();
|
||||
public void notifyCallFresh();
|
||||
public void notifyClientError(String message);
|
||||
public void notifyCallConnecting();
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.call;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.redphone.audio.AudioException;
|
||||
import org.thoughtcrime.redphone.audio.CallAudioManager;
|
||||
import org.thoughtcrime.redphone.crypto.SecureRtpSocket;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.MasterSecret;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.ZRTPInitiatorSocket;
|
||||
import org.thoughtcrime.redphone.network.RtpSocket;
|
||||
import org.thoughtcrime.redphone.signaling.LoginFailedException;
|
||||
import org.thoughtcrime.redphone.signaling.NetworkConnector;
|
||||
import org.thoughtcrime.redphone.signaling.NoSuchUserException;
|
||||
import org.thoughtcrime.redphone.signaling.OtpCounterProvider;
|
||||
import org.thoughtcrime.redphone.signaling.ServerMessageException;
|
||||
import org.thoughtcrime.redphone.signaling.SessionInitiationFailureException;
|
||||
import org.thoughtcrime.redphone.signaling.SignalingException;
|
||||
import org.thoughtcrime.redphone.signaling.SignalingSocket;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
|
||||
/**
|
||||
* Call Manager for the coordination of outgoing calls. It initiates
|
||||
* signaling, negotiates ZRTP, and kicks off the call audio manager.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class InitiatingCallManager extends CallManager {
|
||||
|
||||
private static final String TAG = InitiatingCallManager.class.getSimpleName();
|
||||
|
||||
private final String localNumber;
|
||||
private final String password;
|
||||
private final byte[] zid;
|
||||
|
||||
public InitiatingCallManager(Context context, CallStateListener callStateListener,
|
||||
String localNumber, String password,
|
||||
String remoteNumber, byte[] zid)
|
||||
{
|
||||
super(context, callStateListener, remoteNumber, "InitiatingCallManager Thread");
|
||||
this.localNumber = localNumber;
|
||||
this.password = password;
|
||||
this.zid = zid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
callStateListener.notifyCallConnecting();
|
||||
|
||||
signalingSocket = new SignalingSocket(context, BuildConfig.REDPHONE_RELAY_HOST,
|
||||
31337, localNumber, password,
|
||||
OtpCounterProvider.getInstance());
|
||||
|
||||
sessionDescriptor = signalingSocket.initiateConnection(remoteNumber);
|
||||
|
||||
int localPort = new NetworkConnector(sessionDescriptor.sessionId,
|
||||
sessionDescriptor.getFullServerName(),
|
||||
sessionDescriptor.relayPort).makeConnection();
|
||||
|
||||
InetSocketAddress remoteAddress = new InetSocketAddress(sessionDescriptor.getFullServerName(),
|
||||
sessionDescriptor.relayPort);
|
||||
|
||||
secureSocket = new SecureRtpSocket(new RtpSocket(localPort, remoteAddress));
|
||||
|
||||
zrtpSocket = new ZRTPInitiatorSocket(context, secureSocket, zid, remoteNumber);
|
||||
|
||||
processSignals();
|
||||
|
||||
callStateListener.notifyWaitingForResponder();
|
||||
|
||||
super.run();
|
||||
} catch (NoSuchUserException nsue) {
|
||||
Log.w(TAG, nsue);
|
||||
callStateListener.notifyNoSuchUser();
|
||||
} catch (ServerMessageException ife) {
|
||||
Log.w(TAG, ife);
|
||||
callStateListener.notifyServerMessage(ife.getMessage());
|
||||
} catch (LoginFailedException lfe) {
|
||||
Log.w(TAG, lfe);
|
||||
callStateListener.notifyLoginFailed();
|
||||
} catch (SignalingException | SessionInitiationFailureException se) {
|
||||
Log.w(TAG, se);
|
||||
callStateListener.notifyServerFailure();
|
||||
} catch (SocketException e) {
|
||||
Log.w(TAG, e);
|
||||
callStateListener.notifyCallDisconnected();
|
||||
} catch( RuntimeException e ) {
|
||||
Log.e(TAG, "Died with unhandled exception!");
|
||||
Log.w(TAG, e);
|
||||
callStateListener.notifyClientFailure();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runAudio(DatagramSocket socket, String remoteIp, int remotePort,
|
||||
MasterSecret masterSecret, boolean muteEnabled)
|
||||
throws SocketException, AudioException
|
||||
{
|
||||
this.callAudioManager = new CallAudioManager(socket, remoteIp, remotePort,
|
||||
masterSecret.getInitiatorSrtpKey(),
|
||||
masterSecret.getInitiatorMacKey(),
|
||||
masterSecret.getInitiatorSrtpSalt(),
|
||||
masterSecret.getResponderSrtpKey(),
|
||||
masterSecret.getResponderMacKey(),
|
||||
masterSecret.getResponderSrtpSailt());
|
||||
this.callAudioManager.setMute(muteEnabled);
|
||||
this.callAudioManager.start(context);
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.call;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.redphone.audio.AudioException;
|
||||
import org.thoughtcrime.redphone.audio.CallAudioManager;
|
||||
import org.thoughtcrime.redphone.crypto.SecureRtpSocket;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.MasterSecret;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.ZRTPResponderSocket;
|
||||
import org.thoughtcrime.redphone.network.RtpSocket;
|
||||
import org.thoughtcrime.redphone.signaling.LoginFailedException;
|
||||
import org.thoughtcrime.redphone.signaling.NetworkConnector;
|
||||
import org.thoughtcrime.redphone.signaling.OtpCounterProvider;
|
||||
import org.thoughtcrime.redphone.signaling.SessionDescriptor;
|
||||
import org.thoughtcrime.redphone.signaling.SessionInitiationFailureException;
|
||||
import org.thoughtcrime.redphone.signaling.SessionStaleException;
|
||||
import org.thoughtcrime.redphone.signaling.SignalingException;
|
||||
import org.thoughtcrime.redphone.signaling.SignalingSocket;
|
||||
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
|
||||
/**
|
||||
* CallManager responsible for coordinating incoming calls.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class ResponderCallManager extends CallManager {
|
||||
|
||||
private static final String TAG = ResponderCallManager.class.getSimpleName();
|
||||
|
||||
private final String localNumber;
|
||||
private final String password;
|
||||
private final byte[] zid;
|
||||
|
||||
private int answer = 0;
|
||||
|
||||
public ResponderCallManager(Context context, CallStateListener callStateListener,
|
||||
String remoteNumber, String localNumber,
|
||||
String password, SessionDescriptor sessionDescriptor,
|
||||
byte[] zid)
|
||||
{
|
||||
super(context, callStateListener, remoteNumber, "ResponderCallManager Thread");
|
||||
this.localNumber = localNumber;
|
||||
this.password = password;
|
||||
this.sessionDescriptor = sessionDescriptor;
|
||||
this.zid = zid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
signalingSocket = new SignalingSocket(context,
|
||||
sessionDescriptor.getFullServerName(),
|
||||
31337,
|
||||
localNumber, password,
|
||||
OtpCounterProvider.getInstance());
|
||||
|
||||
signalingSocket.setRinging(sessionDescriptor.sessionId);
|
||||
callStateListener.notifyCallFresh();
|
||||
|
||||
processSignals();
|
||||
|
||||
if (!waitForAnswer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int localPort = new NetworkConnector(sessionDescriptor.sessionId,
|
||||
sessionDescriptor.getFullServerName(),
|
||||
sessionDescriptor.relayPort).makeConnection();
|
||||
|
||||
InetSocketAddress remoteAddress = new InetSocketAddress(sessionDescriptor.getFullServerName(),
|
||||
sessionDescriptor.relayPort);
|
||||
|
||||
secureSocket = new SecureRtpSocket(new RtpSocket(localPort, remoteAddress));
|
||||
zrtpSocket = new ZRTPResponderSocket(context, secureSocket, zid, remoteNumber, sessionDescriptor.version <= 0);
|
||||
|
||||
callStateListener.notifyConnectingtoInitiator();
|
||||
|
||||
super.run();
|
||||
} catch (SignalingException | SessionInitiationFailureException se) {
|
||||
Log.w(TAG, se);
|
||||
callStateListener.notifyServerFailure();
|
||||
} catch (SessionStaleException e) {
|
||||
Log.w(TAG, e);
|
||||
callStateListener.notifyCallStale();
|
||||
} catch (LoginFailedException lfe) {
|
||||
Log.w(TAG, lfe);
|
||||
callStateListener.notifyLoginFailed();
|
||||
} catch (SocketException e) {
|
||||
Log.w(TAG, e);
|
||||
callStateListener.notifyCallDisconnected();
|
||||
} catch( RuntimeException e ) {
|
||||
Log.e(TAG, "Died unhandled with exception!");
|
||||
Log.w(TAG, e);
|
||||
callStateListener.notifyClientFailure();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void answer(boolean answer) {
|
||||
this.answer = (answer ? 1 : 2);
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
private synchronized boolean waitForAnswer() {
|
||||
try {
|
||||
while (answer == 0)
|
||||
wait();
|
||||
} catch (InterruptedException ie) {
|
||||
throw new IllegalArgumentException(ie);
|
||||
}
|
||||
|
||||
return this.answer == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
synchronized (this) {
|
||||
if (answer == 0) {
|
||||
answer(false);
|
||||
}
|
||||
}
|
||||
|
||||
super.terminate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runAudio(DatagramSocket socket, String remoteIp, int remotePort,
|
||||
MasterSecret masterSecret, boolean muteEnabled)
|
||||
throws SocketException, AudioException
|
||||
{
|
||||
this.callAudioManager = new CallAudioManager(socket, remoteIp, remotePort,
|
||||
masterSecret.getResponderSrtpKey(),
|
||||
masterSecret.getResponderMacKey(),
|
||||
masterSecret.getResponderSrtpSailt(),
|
||||
masterSecret.getInitiatorSrtpKey(),
|
||||
masterSecret.getInitiatorMacKey(),
|
||||
masterSecret.getInitiatorSrtpSalt());
|
||||
this.callAudioManager.setMute(muteEnabled);
|
||||
this.callAudioManager.start(context);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package org.thoughtcrime.redphone.call;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.redphone.signaling.SessionDescriptor;
|
||||
import org.thoughtcrime.redphone.signaling.SignalingException;
|
||||
import org.thoughtcrime.redphone.signaling.SignalingSocket;
|
||||
import org.thoughtcrime.redphone.signaling.signals.ServerSignal;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class SignalManager {
|
||||
|
||||
private static final String TAG = SignalManager.class.getSimpleName();
|
||||
|
||||
private final ExecutorService queue = Executors.newSingleThreadExecutor();
|
||||
|
||||
private final SignalingSocket signalingSocket;
|
||||
private final SessionDescriptor sessionDescriptor;
|
||||
private final CallStateListener callStateListener;
|
||||
|
||||
private volatile boolean interrupted = false;
|
||||
|
||||
public SignalManager(CallStateListener callStateListener,
|
||||
SignalingSocket signalingSocket,
|
||||
SessionDescriptor sessionDescriptor)
|
||||
{
|
||||
this.callStateListener = callStateListener;
|
||||
this.signalingSocket = signalingSocket;
|
||||
this.sessionDescriptor = sessionDescriptor;
|
||||
|
||||
this.queue.execute(new SignalListenerTask());
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
Log.w(TAG, "Queuing hangup signal...");
|
||||
queue.execute(new Runnable() {
|
||||
public void run() {
|
||||
Log.w(TAG, "Sending hangup signal...");
|
||||
signalingSocket.setHangup(sessionDescriptor.sessionId);
|
||||
signalingSocket.close();
|
||||
queue.shutdownNow();
|
||||
}
|
||||
});
|
||||
|
||||
interrupted = true;
|
||||
}
|
||||
|
||||
private class SignalListenerTask implements Runnable {
|
||||
public void run() {
|
||||
Log.w(TAG, "Running Signal Listener...");
|
||||
|
||||
try {
|
||||
while (!interrupted) {
|
||||
if (signalingSocket.waitForSignal())
|
||||
break;
|
||||
}
|
||||
|
||||
Log.w(TAG, "Signal Listener Running, interrupted: " + interrupted);
|
||||
|
||||
if (!interrupted) {
|
||||
ServerSignal signal = signalingSocket.readSignal();
|
||||
long sessionId = sessionDescriptor.sessionId;
|
||||
|
||||
if (signal.isHangup(sessionId)) callStateListener.notifyCallDisconnected();
|
||||
else if (signal.isRinging(sessionId)) callStateListener.notifyCallRinging();
|
||||
else if (signal.isBusy(sessionId)) callStateListener.notifyBusy();
|
||||
else if (signal.isKeepAlive()) Log.w(TAG, "Received keep-alive...");
|
||||
|
||||
signalingSocket.sendOkResponse();
|
||||
}
|
||||
|
||||
interrupted = false;
|
||||
queue.execute(this);
|
||||
} catch (SignalingException e) {
|
||||
Log.w(TAG, e);
|
||||
callStateListener.notifyCallDisconnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* A class representing an encrypted "signal," typically a notification of
|
||||
* an incoming call that's delivered over either SMS or C2DM.
|
||||
*
|
||||
* Message format is $IV:ciphertext:MAC
|
||||
*
|
||||
* Version : 1 Byte
|
||||
* IV : Random.
|
||||
* Ciphertext: AES-128 encrypted with CBC mode.
|
||||
* MAC : Hmac-SHA1, truncated to 80 bits over everything preceding (encrypted then auth).
|
||||
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class EncryptedSignalMessage {
|
||||
|
||||
private static final int VERSION_OFFSET = 0;
|
||||
private static final int IV_OFFSET = VERSION_OFFSET + 1;
|
||||
private static final int CIPHERTEXT_OFFSET = IV_OFFSET + 16;
|
||||
private static final int MAC_LENGTH = 10;
|
||||
|
||||
private final String message;
|
||||
private final byte[] key;
|
||||
|
||||
public EncryptedSignalMessage(String message, String key) throws IOException {
|
||||
this.message = message;
|
||||
this.key = Base64.decode(key);
|
||||
}
|
||||
|
||||
private byte[] getCipherKey() throws InvalidEncryptedSignalException, IOException {
|
||||
byte[] cipherKeyBytes = new byte[16];
|
||||
|
||||
System.arraycopy(key, 0, cipherKeyBytes, 0, cipherKeyBytes.length);
|
||||
return cipherKeyBytes;
|
||||
}
|
||||
|
||||
private byte[] getMacKey() throws InvalidEncryptedSignalException, IOException {
|
||||
byte[] macKeyBytes = new byte[20];
|
||||
|
||||
System.arraycopy(key, 16, macKeyBytes, 0, macKeyBytes.length);
|
||||
return macKeyBytes;
|
||||
}
|
||||
|
||||
private boolean verifyMac(byte[] messageBytes)
|
||||
throws InvalidEncryptedSignalException, IOException,
|
||||
NoSuchAlgorithmException, InvalidKeyException
|
||||
{
|
||||
byte[] macKey = getMacKey();
|
||||
SecretKeySpec key = new SecretKeySpec(macKey, "HmacSHA1");
|
||||
Mac mac = Mac.getInstance("HmacSHA1");
|
||||
|
||||
mac.init(key);
|
||||
mac.update(messageBytes, 0, messageBytes.length-MAC_LENGTH);
|
||||
|
||||
byte[] ourDigestComplete = mac.doFinal();
|
||||
byte[] ourDigest = new byte[10];
|
||||
byte[] theirDigest = new byte[10];
|
||||
|
||||
System.arraycopy(ourDigestComplete, 10, ourDigest, 0, ourDigest.length);
|
||||
System.arraycopy(messageBytes, messageBytes.length - MAC_LENGTH,
|
||||
theirDigest, 0, theirDigest.length);
|
||||
|
||||
return Arrays.equals(ourDigest, theirDigest);
|
||||
}
|
||||
|
||||
private boolean isValidVersion(byte[] messageBytes) {
|
||||
return messageBytes[VERSION_OFFSET] == 0x00;
|
||||
}
|
||||
|
||||
private Cipher getCipher(byte[] messageBytes)
|
||||
throws InvalidEncryptedSignalException, InvalidKeyException,
|
||||
InvalidAlgorithmParameterException, NoSuchAlgorithmException,
|
||||
NoSuchPaddingException, IOException
|
||||
{
|
||||
SecretKeySpec cipherKey = new SecretKeySpec(getCipherKey(), "AES");
|
||||
byte[] ivBytes = new byte[16];
|
||||
|
||||
if (messageBytes.length < ivBytes.length)
|
||||
throw new InvalidEncryptedSignalException("Message shorter than IV length.");
|
||||
|
||||
System.arraycopy(messageBytes, IV_OFFSET, ivBytes, 0, ivBytes.length);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
||||
cipher.init(Cipher.DECRYPT_MODE, cipherKey, iv);
|
||||
|
||||
return cipher;
|
||||
}
|
||||
|
||||
public byte[] getPlaintext() throws InvalidEncryptedSignalException {
|
||||
try {
|
||||
byte[] messageBytes = Base64.decode(this.message);
|
||||
|
||||
if (!isValidVersion(messageBytes))
|
||||
throw new InvalidEncryptedSignalException("Unknown version: " +
|
||||
(byte)messageBytes[VERSION_OFFSET]);
|
||||
|
||||
if (!verifyMac(messageBytes))
|
||||
throw new InvalidEncryptedSignalException("Bad MAC");
|
||||
|
||||
Cipher cipher = getCipher(messageBytes);
|
||||
return cipher.doFinal(messageBytes, CIPHERTEXT_OFFSET,
|
||||
messageBytes.length - CIPHERTEXT_OFFSET - MAC_LENGTH);
|
||||
} catch (IOException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException e) {
|
||||
throw new InvalidEncryptedSignalException(e);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto;
|
||||
|
||||
/**
|
||||
* Decryption of a signal failed.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class InvalidEncryptedSignalException extends Exception {
|
||||
|
||||
public InvalidEncryptedSignalException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public InvalidEncryptedSignalException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public InvalidEncryptedSignalException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public InvalidEncryptedSignalException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* A utility class for calcuating a HOTP token from a user's
|
||||
* password and the current counter value.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class Otp {
|
||||
public static String calculateOtp(String password, long counter) {
|
||||
try {
|
||||
SecretKeySpec key = new SecretKeySpec(password.getBytes(), "HmacSHA1");
|
||||
Mac mac = Mac.getInstance("HmacSHA1");
|
||||
mac.init(key);
|
||||
|
||||
return Base64.encodeBytes(mac.doFinal((counter+"").getBytes()));
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException nsae) {
|
||||
throw new AssertionError(nsae);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto;
|
||||
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
|
||||
/**
|
||||
* A class representing an SRTP packet.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class SecureRtpPacket extends RtpPacket {
|
||||
|
||||
private static final int MAC_SIZE = 20;
|
||||
|
||||
private long logicalSequence;
|
||||
|
||||
public SecureRtpPacket(int payloadLength) {
|
||||
super(payloadLength + MAC_SIZE);
|
||||
setVersion();
|
||||
setTimeStamp(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public SecureRtpPacket(RtpPacket packet) {
|
||||
super(packet.getPacket(), packet.getPacketLength());
|
||||
}
|
||||
|
||||
public byte[] getMac() {
|
||||
byte[] mac = new byte[MAC_SIZE];
|
||||
System.arraycopy(data, packetLength - MAC_SIZE, mac, 0, mac.length);
|
||||
return mac;
|
||||
}
|
||||
|
||||
public void setMac(byte[] mac) {
|
||||
System.arraycopy(mac, 0, this.data, packetLength - MAC_SIZE, mac.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPayload(byte[] data, int length) {
|
||||
super.setPayload(data, length);
|
||||
super.packetLength += MAC_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getPayload() {
|
||||
int payloadLength = packetLength - HEADER_LENGTH - MAC_SIZE;
|
||||
byte[] payload = new byte[payloadLength];
|
||||
System.arraycopy(data, HEADER_LENGTH, payload, 0, payloadLength);
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
public long getLogicalSequence() {
|
||||
return logicalSequence;
|
||||
}
|
||||
|
||||
public void setLogicalSequence(long logicalSequence) {
|
||||
this.logicalSequence = logicalSequence;
|
||||
}
|
||||
|
||||
public byte[] getDataToMac() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public int getDataToMacLength() {
|
||||
return packetLength - MAC_SIZE;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.HandshakePacket;
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
import org.thoughtcrime.redphone.network.RtpSocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
|
||||
/**
|
||||
* A socket that does SRTP.
|
||||
*
|
||||
* Every outgoing packet is encrypted/authenticated, and every incoming
|
||||
* packet is verified/decrypted.
|
||||
*
|
||||
* EDIT: Now just a shim for passing handshake packets. TODO
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class SecureRtpSocket {
|
||||
|
||||
private static final String TAG = SecureRtpPacket.class.getSimpleName();
|
||||
|
||||
private final RtpSocket socket;
|
||||
|
||||
public SecureRtpSocket(RtpSocket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public String getRemoteIp() {
|
||||
return socket.getRemoteIp();
|
||||
}
|
||||
|
||||
public int getRemotePort() {
|
||||
return socket.getRemotePort();
|
||||
}
|
||||
|
||||
public DatagramSocket getDatagramSocket() {
|
||||
return socket.getDatagramSocket();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.socket.close();
|
||||
}
|
||||
|
||||
public void send(HandshakePacket packet) throws IOException {
|
||||
packet.setCRC();
|
||||
socket.send(packet);
|
||||
}
|
||||
|
||||
public HandshakePacket receiveHandshakePacket(boolean verifyCRC) throws IOException {
|
||||
RtpPacket barePacket = socket.receive();
|
||||
|
||||
if (barePacket == null)
|
||||
return null;
|
||||
|
||||
HandshakePacket handshakePacket = new HandshakePacket(barePacket);
|
||||
|
||||
if (!verifyCRC || handshakePacket.verifyCRC()) {
|
||||
return handshakePacket;
|
||||
} else {
|
||||
Log.w(TAG, "Bad CRC!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTimeout(int timeoutMillis) {
|
||||
socket.setTimeout(timeoutMillis);
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* ZRTP handshake 'Commit' packet.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class CommitPacket extends HandshakePacket {
|
||||
|
||||
public static final String TYPE = "Commit ";
|
||||
private static final int COMMIT_LENGTH = 116;
|
||||
|
||||
private static final int _LENGTH_OFFSET = MESSAGE_BASE + 2;
|
||||
private static final int _HASH_OFFSET = MESSAGE_BASE + 12;
|
||||
private static final int _ZID_OFFSET = MESSAGE_BASE + 44;
|
||||
private static final int _HASH_SPEC_OFFSET = MESSAGE_BASE + 56;
|
||||
private static final int _CIPHER_OFFSET = MESSAGE_BASE + 60;
|
||||
private static final int _AUTH_OFFSET = MESSAGE_BASE + 64;
|
||||
private static final int _AGREEMENT_OFFSET = MESSAGE_BASE + 68;
|
||||
private static final int _SAS_OFFSET = MESSAGE_BASE + 72;
|
||||
private static final int _HVI_OFFSET = MESSAGE_BASE + 76;
|
||||
private static final int _MAC_OFFSET = MESSAGE_BASE + 108;
|
||||
|
||||
private int LENGTH_OFFSET = _LENGTH_OFFSET;
|
||||
private int HASH_OFFSET = _HASH_OFFSET;
|
||||
private int ZID_OFFSET = _ZID_OFFSET;
|
||||
private int HASH_SPEC_OFFSET = _HASH_SPEC_OFFSET;
|
||||
private int CIPHER_OFFSET = _CIPHER_OFFSET;
|
||||
private int AUTH_OFFSET = _AUTH_OFFSET;
|
||||
private int AGREEMENT_OFFSET = _AGREEMENT_OFFSET;
|
||||
private int SAS_OFFSET = _SAS_OFFSET;
|
||||
private int HVI_OFFSET = _HVI_OFFSET;
|
||||
private int MAC_OFFSET = _MAC_OFFSET;
|
||||
|
||||
private static final byte[] HASH_SPEC = {'S', '2', '5', '6'};
|
||||
private static final byte[] CIPHER_SPEC = {'A', 'E', 'S', '1'};
|
||||
private static final byte[] AUTH_SPEC = {'H', 'S', '8', '0'};
|
||||
private static final byte[] SAS_SPEC = {'B', '2', '5', '6'};
|
||||
|
||||
public CommitPacket(RtpPacket packet) {
|
||||
super(packet);
|
||||
fixOffsetsForHeaderBug();
|
||||
}
|
||||
|
||||
public CommitPacket(RtpPacket packet, boolean deepCopy) {
|
||||
super(packet, deepCopy);
|
||||
fixOffsetsForHeaderBug();
|
||||
}
|
||||
|
||||
public CommitPacket(HashChain hashChain, byte[] helloBytes,
|
||||
DHPartTwoPacket dhPacket, byte[] zid,
|
||||
boolean includeLegacyHeaderBug)
|
||||
throws InvalidPacketException
|
||||
{
|
||||
super(TYPE, COMMIT_LENGTH, includeLegacyHeaderBug);
|
||||
fixOffsetsForHeaderBug();
|
||||
setHash(hashChain.getH2());
|
||||
setZID(zid);
|
||||
setSpec(dhPacket.getAgreementSpec());
|
||||
setHvi(calculateHvi(helloBytes, dhPacket.getMessageBytes()));
|
||||
setMac(hashChain.getH1(), MAC_OFFSET, COMMIT_LENGTH - 8);
|
||||
}
|
||||
|
||||
public byte[] getHvi() {
|
||||
byte[] hvi = new byte[32];
|
||||
System.arraycopy(this.data, HVI_OFFSET, hvi, 0, hvi.length);
|
||||
return hvi;
|
||||
}
|
||||
|
||||
public byte[] getHash() {
|
||||
byte[] hash = new byte[32];
|
||||
System.arraycopy(this.data, HASH_OFFSET, hash, 0, hash.length);
|
||||
return hash;
|
||||
}
|
||||
|
||||
public void verifyHvi(byte[] helloBytes, byte[] dhBytes) throws InvalidPacketException {
|
||||
byte[] calculatedHvi = calculateHvi(helloBytes, dhBytes);
|
||||
byte[] packetHvi = getHvi();
|
||||
|
||||
if (!Arrays.equals(calculatedHvi, packetHvi))
|
||||
throw new InvalidPacketException("HVI doesn't match.");
|
||||
}
|
||||
|
||||
public void verifyMac(byte[] key) throws InvalidPacketException {
|
||||
super.verifyMac(key, MAC_OFFSET, COMMIT_LENGTH - 8, getHash());
|
||||
}
|
||||
|
||||
private void setHash(byte[] hash) {
|
||||
System.arraycopy(hash, 0, this.data, HASH_OFFSET, hash.length);
|
||||
}
|
||||
|
||||
private void setZID(byte[] zid) {
|
||||
System.arraycopy(zid, 0, this.data, ZID_OFFSET, zid.length);
|
||||
}
|
||||
|
||||
private void setSpec(byte[] agreementSpec) {
|
||||
System.arraycopy(HASH_SPEC, 0, this.data, HASH_SPEC_OFFSET, HASH_SPEC.length);
|
||||
System.arraycopy(CIPHER_SPEC, 0, this.data, CIPHER_OFFSET, CIPHER_SPEC.length);
|
||||
System.arraycopy(AUTH_SPEC, 0, this.data, AUTH_OFFSET, AUTH_SPEC.length);
|
||||
System.arraycopy(agreementSpec, 0, this.data, AGREEMENT_OFFSET, agreementSpec.length);
|
||||
System.arraycopy(SAS_SPEC, 0, this.data, SAS_OFFSET, SAS_SPEC.length);
|
||||
}
|
||||
|
||||
private byte[] calculateHvi(byte[] helloBytes, byte[] dhBytes) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(dhBytes);
|
||||
md.update(helloBytes);
|
||||
|
||||
return md.digest();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setHvi(byte[] hvi) {
|
||||
System.arraycopy(hvi, 0, this.data, HVI_OFFSET, hvi.length);
|
||||
}
|
||||
|
||||
public byte[] getKeyAgreementType() {
|
||||
byte[] ka = new byte[4];
|
||||
System.arraycopy(this.data, AGREEMENT_OFFSET, ka, 0, ka.length);
|
||||
|
||||
return ka;
|
||||
}
|
||||
|
||||
private void fixOffsetsForHeaderBug() {
|
||||
int headerBugOffset = getHeaderBugOffset();
|
||||
|
||||
LENGTH_OFFSET += headerBugOffset;
|
||||
HASH_OFFSET += headerBugOffset;
|
||||
ZID_OFFSET += headerBugOffset;
|
||||
HASH_SPEC_OFFSET += headerBugOffset;
|
||||
CIPHER_OFFSET += headerBugOffset;
|
||||
AUTH_OFFSET += headerBugOffset;
|
||||
AGREEMENT_OFFSET += headerBugOffset;
|
||||
SAS_OFFSET += headerBugOffset;
|
||||
HVI_OFFSET += headerBugOffset;
|
||||
MAC_OFFSET += headerBugOffset;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.SecureRtpPacket;
|
||||
|
||||
/**
|
||||
* Conf2Ack ZRTP handshake packet.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class ConfAckPacket extends HandshakePacket {
|
||||
|
||||
public static final String TYPE = "Conf2Ack";
|
||||
|
||||
public ConfAckPacket(SecureRtpPacket packet) {
|
||||
super(packet);
|
||||
}
|
||||
|
||||
public ConfAckPacket(boolean includeLegacyHeaderBug) {
|
||||
super(TYPE, 12, includeLegacyHeaderBug);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
|
||||
/**
|
||||
* Confirm1 ZRTP handshake packet.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class ConfirmOnePacket extends ConfirmPacket {
|
||||
|
||||
public static final String TYPE = "Confirm1";
|
||||
|
||||
public ConfirmOnePacket(RtpPacket packet, boolean legacy) {
|
||||
super(packet, legacy);
|
||||
}
|
||||
|
||||
public ConfirmOnePacket(byte[] macKey, byte[] cipherKey, HashChain hashChain,
|
||||
boolean includeLegacyConfirmPacketBug,
|
||||
boolean includeLegacyHeaderBug)
|
||||
{
|
||||
super(TYPE, macKey, cipherKey, hashChain, includeLegacyConfirmPacketBug, includeLegacyHeaderBug);
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
import org.thoughtcrime.redphone.util.Conversions;
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Confirm ZRTP handshake packet.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class ConfirmPacket extends HandshakePacket {
|
||||
|
||||
private static final int CONFIRM_LENGTH = 76;
|
||||
private static final int ENCRYPTED_LENGTH = 40;
|
||||
|
||||
private static final int _LENGTH_OFFSET = MESSAGE_BASE + 2;
|
||||
private static final int _HMAC_OFFSET = MESSAGE_BASE + 12;
|
||||
private static final int _IV_OFFSET = MESSAGE_BASE + 20;
|
||||
private static final int _PREIMAGE_OFFSET = MESSAGE_BASE + 36;
|
||||
private static final int _CACHE_OFFSET = MESSAGE_BASE + 72;
|
||||
|
||||
private int LENGTH_OFFSET = _LENGTH_OFFSET;
|
||||
private int HMAC_OFFSET = _HMAC_OFFSET;
|
||||
private int IV_OFFSET = _IV_OFFSET;
|
||||
private int PREIMAGE_OFFSET = _PREIMAGE_OFFSET;
|
||||
private int CACHE_OFFSET = _CACHE_OFFSET;
|
||||
|
||||
private final boolean includeLegacyConfirmPacketBug;
|
||||
|
||||
public ConfirmPacket(RtpPacket packet, boolean includeLegacyConfirmPacketBug) {
|
||||
super(packet);
|
||||
this.includeLegacyConfirmPacketBug = includeLegacyConfirmPacketBug;
|
||||
fixOffsetsForHeaderBug();
|
||||
}
|
||||
|
||||
public ConfirmPacket(String type, byte[] macKey, byte[] cipherKey,
|
||||
HashChain hashChain,
|
||||
boolean includeLegacyConfirmPacketBug,
|
||||
boolean includeLegacyHeaderBug)
|
||||
{
|
||||
super(type, CONFIRM_LENGTH, includeLegacyHeaderBug);
|
||||
this.includeLegacyConfirmPacketBug = includeLegacyConfirmPacketBug;
|
||||
fixOffsetsForHeaderBug();
|
||||
setPreimage(hashChain.getH0());
|
||||
setCacheTime();
|
||||
setIv();
|
||||
computeCipherOperation(cipherKey, Cipher.ENCRYPT_MODE);
|
||||
setMac(macKey);
|
||||
}
|
||||
|
||||
public void verifyMac(byte[] macKey) throws InvalidPacketException {
|
||||
if (this.getPacketLength() - PREIMAGE_OFFSET < ENCRYPTED_LENGTH)
|
||||
throw new InvalidPacketException("Confirm packet too short.");
|
||||
|
||||
byte[] digest = calculateMac(macKey);
|
||||
byte[] truncatedDigest = new byte[8];
|
||||
System.arraycopy(digest, 0, truncatedDigest, 0, truncatedDigest.length);
|
||||
|
||||
byte[] givenDigest = getMac();
|
||||
|
||||
Log.w("ConfirmPacket", "Given Digest: " + Hex.toString(givenDigest));
|
||||
Log.w("ConfirmPacket", "Calcu Digest: " + Hex.toString(digest));
|
||||
|
||||
if (!Arrays.equals(truncatedDigest, givenDigest))
|
||||
throw new InvalidPacketException("HMAC doesn't match!");
|
||||
}
|
||||
|
||||
public void decrypt(byte[] cipherKey) {
|
||||
computeCipherOperation(cipherKey, Cipher.DECRYPT_MODE);
|
||||
}
|
||||
|
||||
public byte[] getPreimage() {
|
||||
byte[] preimage = new byte[32];
|
||||
System.arraycopy(this.data, PREIMAGE_OFFSET, preimage, 0, preimage.length);
|
||||
return preimage;
|
||||
}
|
||||
|
||||
private byte[] calculateMac(byte[] macKey) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(macKey, "HmacSHA256"));
|
||||
mac.update(this.data, PREIMAGE_OFFSET, ENCRYPTED_LENGTH);
|
||||
|
||||
return mac.doFinal();
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setMac(byte[] macKey) {
|
||||
byte[] digest = calculateMac(macKey);
|
||||
System.arraycopy(digest, 0, this.data, HMAC_OFFSET, 8);
|
||||
}
|
||||
|
||||
private byte[] getMac() {
|
||||
byte[] digest = new byte[8];
|
||||
System.arraycopy(this.data, HMAC_OFFSET, digest, 0, digest.length);
|
||||
return digest;
|
||||
}
|
||||
|
||||
private byte[] getIv() {
|
||||
byte[] iv = new byte[16];
|
||||
System.arraycopy(this.data, IV_OFFSET, iv, 0, iv.length);
|
||||
return iv;
|
||||
}
|
||||
|
||||
private void setIv() {
|
||||
try {
|
||||
byte[] iv = new byte[16];
|
||||
if (!includeLegacyConfirmPacketBug) { // Temporary implementation bug compatibility issue. See note in ZRTPSocket.
|
||||
SecureRandom.getInstance("SHA1PRNG").nextBytes(iv);
|
||||
}
|
||||
System.arraycopy(iv, 0, this.data, IV_OFFSET, iv.length);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void computeCipherOperation(byte[] cipherKey, int mode) {
|
||||
try {
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(getIv());
|
||||
SecretKeySpec keySpec = new SecretKeySpec(cipherKey, "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
|
||||
cipher.init(mode, keySpec, ivSpec);
|
||||
|
||||
byte[] encryptedData = cipher.doFinal(this.data, PREIMAGE_OFFSET, ENCRYPTED_LENGTH);
|
||||
System.arraycopy(encryptedData, 0, this.data, PREIMAGE_OFFSET, ENCRYPTED_LENGTH);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setCacheTime() {
|
||||
Conversions.longTo4ByteArray(this.data, CACHE_OFFSET, 0xffffffffL);
|
||||
}
|
||||
|
||||
private void setPreimage(byte[] preimage) {
|
||||
System.arraycopy(preimage, 0, this.data, PREIMAGE_OFFSET, preimage.length);
|
||||
}
|
||||
|
||||
public long getCacheTime() {
|
||||
byte[] cacheTime = new byte[4];
|
||||
System.arraycopy(this.data, CACHE_OFFSET, cacheTime, 0, cacheTime.length);
|
||||
return Conversions.byteArray4ToLong(cacheTime, 0);
|
||||
}
|
||||
|
||||
private void fixOffsetsForHeaderBug() {
|
||||
int headerBugOffset = getHeaderBugOffset();
|
||||
|
||||
LENGTH_OFFSET += headerBugOffset;
|
||||
HMAC_OFFSET += headerBugOffset;
|
||||
IV_OFFSET += headerBugOffset;
|
||||
PREIMAGE_OFFSET += headerBugOffset;
|
||||
CACHE_OFFSET += headerBugOffset;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
|
||||
/**
|
||||
* Confirm2 ZRTP handshake packet.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class ConfirmTwoPacket extends ConfirmPacket {
|
||||
public static final String TYPE = "Confirm2";
|
||||
|
||||
public ConfirmTwoPacket(RtpPacket packet, boolean legacy) {
|
||||
super(packet, legacy);
|
||||
}
|
||||
|
||||
public ConfirmTwoPacket(byte[] macKey, byte[] cipherKey, HashChain hashChain,
|
||||
boolean includeLegacyConfirmPacketBug,
|
||||
boolean includeLegacyHeaderBug)
|
||||
{
|
||||
super(TYPE, macKey, cipherKey, hashChain, includeLegacyConfirmPacketBug, includeLegacyHeaderBug);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecretsDerivatives;
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
|
||||
/**
|
||||
* A DHPartOnePacket for the DH3K KA type.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class DH3KDHPartOnePacket extends DHPartOnePacket {
|
||||
public DH3KDHPartOnePacket(RtpPacket packet) {
|
||||
super(packet, DHPacket.DH3K_AGREEMENT_TYPE);
|
||||
}
|
||||
|
||||
public DH3KDHPartOnePacket(RtpPacket packet, boolean deepCopy) {
|
||||
super(packet, DHPacket.DH3K_AGREEMENT_TYPE, deepCopy);
|
||||
}
|
||||
|
||||
public DH3KDHPartOnePacket(HashChain hashChain, byte[] pvr,
|
||||
RetainedSecretsDerivatives retainedSecrets,
|
||||
boolean includeLegacyHeaderBug)
|
||||
{
|
||||
super(DHPacket.DH3K_AGREEMENT_TYPE, hashChain, pvr, retainedSecrets, includeLegacyHeaderBug);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecretsDerivatives;
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
|
||||
/**
|
||||
* A DHPartTwoPacket for the DH3K KA type.
|
||||
*
|
||||
* @author moxie
|
||||
*
|
||||
*/
|
||||
public class DH3KDHPartTwoPacket extends DHPartTwoPacket {
|
||||
|
||||
private static final byte[] AGREEMENT_SPEC = {'D', 'H', '3', 'k'};
|
||||
|
||||
public DH3KDHPartTwoPacket(RtpPacket packet) {
|
||||
super(packet, DHPacket.DH3K_AGREEMENT_TYPE);
|
||||
}
|
||||
|
||||
public DH3KDHPartTwoPacket(RtpPacket packet, boolean deepCopy) {
|
||||
super(packet, DHPacket.DH3K_AGREEMENT_TYPE, deepCopy);
|
||||
}
|
||||
|
||||
public DH3KDHPartTwoPacket(HashChain hashChain, byte[] pvr,
|
||||
RetainedSecretsDerivatives retainedSecrets,
|
||||
boolean includeLegacyHeaderBug)
|
||||
{
|
||||
super(DHPacket.DH3K_AGREEMENT_TYPE, hashChain, pvr, retainedSecrets, includeLegacyHeaderBug);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getAgreementSpec() {
|
||||
return AGREEMENT_SPEC;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.redphone.util.Conversions;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.spec.DHPublicKeySpec;
|
||||
|
||||
/**
|
||||
* An instance of SecretCalculator that handles DH3K KA.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class DH3KSecretCalculator extends SecretCalculator {
|
||||
|
||||
@Override
|
||||
public byte[] calculateKeyAgreement(KeyPair localKey, byte[] publicKeyBytes) {
|
||||
try {
|
||||
Log.w("SecretCalculator", "Calculating DH secret...");
|
||||
DHPublicKeySpec keySpec = new DHPublicKeySpec(Conversions.byteArrayToBigInteger(publicKeyBytes),
|
||||
ZRTPSocket.PRIME, ZRTPSocket.GENERATOR);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("DH");
|
||||
PublicKey publicKey = keyFactory.generatePublic(keySpec);
|
||||
|
||||
KeyAgreement agreement = KeyAgreement.getInstance("DH");
|
||||
agreement.init(localKey.getPrivate());
|
||||
agreement.doPhase(publicKey, true);
|
||||
|
||||
return agreement.generateSecret();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.w("SecretCalculator", e);
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
Log.w("SecretCalculator", e);
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w("SecretCalculator", e);
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecretsDerivatives;
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* Base DH packet, from which DH part one and DH part two derive.
|
||||
* http://tools.ietf.org/html/rfc6189#section-5.5
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public abstract class DHPacket extends HandshakePacket {
|
||||
|
||||
/**
|
||||
* We switch on these two KA types in several places below. This
|
||||
* is really unfortunate, particularly because we have distinct
|
||||
* sub-classes for the various KA types, so it stands to reason that
|
||||
* we should be able to isolate KA functionality to each KA type.
|
||||
*
|
||||
* Unfortunately, however, we already have sub-classes for PartOne
|
||||
* and PartTwo, which we really need. So given the absence of multiple
|
||||
* inheritance, this is the only way to include the functionality without
|
||||
* duplicating code. =(
|
||||
*/
|
||||
|
||||
protected static final int DH3K_AGREEMENT_TYPE = 1;
|
||||
protected static final int EC25_AGREEMENT_TYPE = 2;
|
||||
|
||||
protected static final int DH3K_DH_LENGTH = 468;
|
||||
protected static final int EC25_DH_LENGTH = 148;
|
||||
|
||||
private static final int _LENGTH_OFFSET = MESSAGE_BASE + 2;
|
||||
private static final int _HASH_OFFSET = MESSAGE_BASE + 12;
|
||||
private static final int _RS1_OFFSET = MESSAGE_BASE + 44;
|
||||
private static final int _RS2_OFFSET = MESSAGE_BASE + 52;
|
||||
private static final int _AUX_OFFSET = MESSAGE_BASE + 60;
|
||||
private static final int _PBX_OFFSET = MESSAGE_BASE + 68;
|
||||
private static final int _PVR_OFFSET = MESSAGE_BASE + 76;
|
||||
private static final int _DH3K_MAC_OFFSET = _PVR_OFFSET + 384;
|
||||
private static final int _EC25_MAC_OFFSET = _PVR_OFFSET + 64;
|
||||
|
||||
private int LENGTH_OFFSET = _LENGTH_OFFSET;
|
||||
private int HASH_OFFSET = _HASH_OFFSET;
|
||||
private int RS1_OFFSET = _RS1_OFFSET;
|
||||
private int RS2_OFFSET = _RS2_OFFSET;
|
||||
private int AUX_OFFSET = _AUX_OFFSET;
|
||||
private int PBX_OFFSET = _PBX_OFFSET;
|
||||
private int PVR_OFFSET = _PVR_OFFSET;
|
||||
private int DH3K_MAC_OFFSET = _DH3K_MAC_OFFSET;
|
||||
private int EC25_MAC_OFFSET = _EC25_MAC_OFFSET;
|
||||
|
||||
private final int agreementType;
|
||||
|
||||
public DHPacket(RtpPacket packet, int agreementType) {
|
||||
super(packet);
|
||||
this.agreementType = agreementType;
|
||||
fixOffsetsForHeaderBug();
|
||||
}
|
||||
|
||||
public DHPacket(RtpPacket packet, int agreementType, boolean deepCopy) {
|
||||
super(packet, deepCopy);
|
||||
this.agreementType = agreementType;
|
||||
fixOffsetsForHeaderBug();
|
||||
}
|
||||
|
||||
public DHPacket(String typeTag, int agreementType, HashChain hashChain, byte[] pvr,
|
||||
RetainedSecretsDerivatives retainedSecrets,
|
||||
boolean includeLegacyHeaderBug)
|
||||
{
|
||||
super(typeTag,
|
||||
agreementType == DH3K_AGREEMENT_TYPE ? DH3K_DH_LENGTH : EC25_DH_LENGTH,
|
||||
includeLegacyHeaderBug);
|
||||
fixOffsetsForHeaderBug();
|
||||
|
||||
setHash(hashChain.getH1());
|
||||
setState(retainedSecrets);
|
||||
setPvr(pvr);
|
||||
|
||||
switch (agreementType) {
|
||||
case DH3K_AGREEMENT_TYPE:
|
||||
setMac(hashChain.getH0(), DH3K_MAC_OFFSET, DH3K_DH_LENGTH - 8);
|
||||
break;
|
||||
case EC25_AGREEMENT_TYPE:
|
||||
setMac(hashChain.getH0(), EC25_MAC_OFFSET, EC25_DH_LENGTH - 8);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Bad agreement type: " + agreementType);
|
||||
}
|
||||
|
||||
this.agreementType = agreementType;
|
||||
}
|
||||
|
||||
public byte[] getPvr() {
|
||||
switch (agreementType) {
|
||||
case DH3K_AGREEMENT_TYPE:
|
||||
byte[] dh3k_pvr = new byte[384];
|
||||
System.arraycopy(this.data, PVR_OFFSET, dh3k_pvr, 0, dh3k_pvr.length);
|
||||
return dh3k_pvr;
|
||||
case EC25_AGREEMENT_TYPE:
|
||||
byte[] ec25_pvr = new byte[64];
|
||||
System.arraycopy(this.data, PVR_OFFSET, ec25_pvr, 0, ec25_pvr.length);
|
||||
return ec25_pvr;
|
||||
default:
|
||||
throw new AssertionError("Bad agreement type: " + agreementType);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getDerivativeSecretOne() {
|
||||
byte[] rs1 = new byte[8];
|
||||
System.arraycopy(this.data, RS1_OFFSET, rs1, 0, rs1.length);
|
||||
return rs1;
|
||||
}
|
||||
|
||||
public byte[] getDerivativeSecretTwo() {
|
||||
byte[] rs2 = new byte[8];
|
||||
System.arraycopy(this.data, RS2_OFFSET, rs2, 0, rs2.length);
|
||||
return rs2;
|
||||
}
|
||||
|
||||
public byte[] getHash() {
|
||||
byte[] hash = new byte[32];
|
||||
System.arraycopy(this.data, HASH_OFFSET, hash, 0, hash.length);
|
||||
return hash;
|
||||
}
|
||||
|
||||
public void verifyMac(byte[] key) throws InvalidPacketException {
|
||||
switch (agreementType) {
|
||||
case DH3K_AGREEMENT_TYPE:
|
||||
super.verifyMac(key, DH3K_MAC_OFFSET, DH3K_DH_LENGTH-8, getHash());
|
||||
return;
|
||||
case EC25_AGREEMENT_TYPE:
|
||||
super.verifyMac(key, EC25_MAC_OFFSET, EC25_DH_LENGTH-8, getHash());
|
||||
return;
|
||||
default:
|
||||
throw new AssertionError("Bad agreement type: " + agreementType);
|
||||
}
|
||||
}
|
||||
|
||||
private void setHash(byte[] hash) {
|
||||
System.arraycopy(hash, 0, this.data, HASH_OFFSET, hash.length);
|
||||
}
|
||||
|
||||
private void setPvr(byte[] pvr) {
|
||||
System.arraycopy(pvr, 0, this.data, PVR_OFFSET, pvr.length);
|
||||
}
|
||||
|
||||
private void setState(RetainedSecretsDerivatives retainedSecrets) {
|
||||
setDerivativeSecret(retainedSecrets.getRetainedSecretOneDerivative(), RS1_OFFSET);
|
||||
setDerivativeSecret(retainedSecrets.getRetainedSecretTwoDerivative(), RS2_OFFSET);
|
||||
setDerivativeSecret(null , AUX_OFFSET);
|
||||
setDerivativeSecret(null , PBX_OFFSET);
|
||||
}
|
||||
|
||||
private void setDerivativeSecret(byte[] rs, int rsOffset) {
|
||||
try {
|
||||
if (rs != null) {
|
||||
System.arraycopy(rs, 0, this.data, rsOffset, rs.length);
|
||||
} else {
|
||||
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
||||
byte[] randomBytes = new byte[8];
|
||||
|
||||
random.nextBytes(randomBytes);
|
||||
System.arraycopy(randomBytes, 0, this.data, rsOffset, randomBytes.length);
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void fixOffsetsForHeaderBug() {
|
||||
int headerBugOffset = getHeaderBugOffset();
|
||||
|
||||
LENGTH_OFFSET += headerBugOffset;
|
||||
HASH_OFFSET += headerBugOffset;
|
||||
RS1_OFFSET += headerBugOffset;
|
||||
RS2_OFFSET += headerBugOffset;
|
||||
AUX_OFFSET += headerBugOffset;
|
||||
PBX_OFFSET += headerBugOffset;
|
||||
PVR_OFFSET += headerBugOffset;
|
||||
DH3K_MAC_OFFSET += headerBugOffset;
|
||||
EC25_MAC_OFFSET += headerBugOffset;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecretsDerivatives;
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
|
||||
/**
|
||||
* DH part one ZRTP handshake packet.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public abstract class DHPartOnePacket extends DHPacket {
|
||||
public static final String TYPE = "DHPart1 ";
|
||||
|
||||
public DHPartOnePacket(RtpPacket packet, int agreementType) {
|
||||
super(packet, agreementType);
|
||||
}
|
||||
|
||||
public DHPartOnePacket(RtpPacket packet, int agreementType, boolean deepCopy) {
|
||||
super(packet, agreementType, deepCopy);
|
||||
}
|
||||
|
||||
public DHPartOnePacket(int agreementType, HashChain hashChain, byte[] pvr,
|
||||
RetainedSecretsDerivatives retainedSecrets,
|
||||
boolean includeLegacyHeaderBug)
|
||||
{
|
||||
super(TYPE, agreementType, hashChain, pvr, retainedSecrets, includeLegacyHeaderBug);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecretsDerivatives;
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
|
||||
/**
|
||||
* DH part two ZRTP handshake packet.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public abstract class DHPartTwoPacket extends DHPacket {
|
||||
public static final String TYPE = "DHPart2 ";
|
||||
|
||||
public DHPartTwoPacket(RtpPacket packet, int agreementType) {
|
||||
super(packet, agreementType);
|
||||
}
|
||||
|
||||
public DHPartTwoPacket(RtpPacket packet, int agreementType, boolean deepCopy) {
|
||||
super(packet, agreementType, deepCopy);
|
||||
}
|
||||
|
||||
public DHPartTwoPacket(int agreementType, HashChain hashChain, byte[] pvr,
|
||||
RetainedSecretsDerivatives retainedSecrets,
|
||||
boolean includeLegacyHeaderBug)
|
||||
{
|
||||
super(TYPE, agreementType, hashChain, pvr, retainedSecrets, includeLegacyHeaderBug);
|
||||
}
|
||||
|
||||
public abstract byte[] getAgreementSpec();
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecretsDerivatives;
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
|
||||
/**
|
||||
* A DHPartOnePacket for the EC25 KA type.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class EC25DHPartOnePacket extends DHPartOnePacket {
|
||||
|
||||
public EC25DHPartOnePacket(RtpPacket packet) {
|
||||
super(packet, DHPacket.EC25_AGREEMENT_TYPE);
|
||||
}
|
||||
|
||||
public EC25DHPartOnePacket(RtpPacket packet, boolean deepCopy) {
|
||||
super(packet, DHPacket.EC25_AGREEMENT_TYPE, deepCopy);
|
||||
}
|
||||
|
||||
public EC25DHPartOnePacket(HashChain hashChain, byte[] pvr,
|
||||
RetainedSecretsDerivatives retainedSecrets,
|
||||
boolean includeLegacyHeaderBug)
|
||||
{
|
||||
super(DHPacket.EC25_AGREEMENT_TYPE, hashChain, pvr, retainedSecrets, includeLegacyHeaderBug);
|
||||
assert(pvr.length == 64);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecretsDerivatives;
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
|
||||
/**
|
||||
* A DHPartTwoPacket for the EC25 KA type.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class EC25DHPartTwoPacket extends DHPartTwoPacket {
|
||||
|
||||
private static final byte[] AGREEMENT_SPEC = {'E', 'C', '2', '5'};
|
||||
|
||||
public EC25DHPartTwoPacket(RtpPacket packet) {
|
||||
super(packet, DHPacket.EC25_AGREEMENT_TYPE);
|
||||
}
|
||||
|
||||
public EC25DHPartTwoPacket(RtpPacket packet, boolean deepCopy) {
|
||||
super(packet, DHPacket.EC25_AGREEMENT_TYPE, deepCopy);
|
||||
}
|
||||
|
||||
public EC25DHPartTwoPacket(HashChain hashChain, byte[] pvr,
|
||||
RetainedSecretsDerivatives retainedSecrets,
|
||||
boolean includeLegacyHeaderBug)
|
||||
{
|
||||
super(DHPacket.EC25_AGREEMENT_TYPE, hashChain, pvr, retainedSecrets, includeLegacyHeaderBug);
|
||||
assert(pvr.length == 64);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getAgreementSpec() {
|
||||
return AGREEMENT_SPEC;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.spongycastle.jce.ECNamedCurveTable;
|
||||
import org.spongycastle.jce.spec.ECParameterSpec;
|
||||
import org.spongycastle.jce.spec.ECPublicKeySpec;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
import org.thoughtcrime.redphone.util.Conversions;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
import javax.crypto.KeyAgreement;
|
||||
|
||||
/**
|
||||
* An instance of SecretCalculator that will do KA for EC25.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class EC25SecretCalculator extends SecretCalculator {
|
||||
|
||||
@Override
|
||||
public byte[] calculateKeyAgreement(KeyPair localKey, byte[] publicKeyBytes) {
|
||||
Log.w("EC25SecretCalculator", "Calculating EC25 Secret...");
|
||||
try {
|
||||
byte[] x = new byte[32];
|
||||
byte[] y = new byte[32];
|
||||
|
||||
System.arraycopy(publicKeyBytes, 0, x, 0, x.length);
|
||||
System.arraycopy(publicKeyBytes, x.length, y, 0, y.length);
|
||||
|
||||
ECParameterSpec params = ECNamedCurveTable.getParameterSpec("secp256r1");
|
||||
ECPoint point = params.getCurve().createPoint(Conversions.byteArrayToBigInteger(x),
|
||||
Conversions.byteArrayToBigInteger(y),
|
||||
false);
|
||||
|
||||
ECPublicKeySpec keySpec = new ECPublicKeySpec(point, params);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("ECDH", "SC");
|
||||
PublicKey publicKey = keyFactory.generatePublic(keySpec);
|
||||
|
||||
KeyAgreement agreement = KeyAgreement.getInstance("ECDH", "SC");
|
||||
agreement.init(localKey.getPrivate());
|
||||
agreement.doPhase(publicKey, true);
|
||||
|
||||
return agreement.generateSecret();
|
||||
} catch (NoSuchProviderException nspe) {
|
||||
throw new AssertionError(nspe);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
import org.thoughtcrime.redphone.util.Conversions;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Base ZRTP handshake packet, from which all
|
||||
* handshake packets derive.
|
||||
*
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class HandshakePacket extends RtpPacket {
|
||||
|
||||
private static final int PREFIX_OFFSET = 0;
|
||||
private static final int COOKIE_OFFSET = 4;
|
||||
protected static final int MESSAGE_BASE = 12;
|
||||
|
||||
private static final int _MAGIC_OFFSET = MESSAGE_BASE + 0;
|
||||
private static final int _LENGTH_OFFSET = MESSAGE_BASE + 2;
|
||||
private static final int _TYPE_OFFSET = MESSAGE_BASE + 4;
|
||||
|
||||
private int MAGIC_OFFSET = _MAGIC_OFFSET;
|
||||
private int LENGTH_OFFSET = _LENGTH_OFFSET;
|
||||
private int TYPE_OFFSET = _TYPE_OFFSET;
|
||||
|
||||
private static final int PREFIX_VALUE = 0x10;
|
||||
private static final int LEGACY_HEADER_BUG_PREFIX_VALUE = 0x20;
|
||||
private static final int MAGIC_VALUE = 0x505a;
|
||||
private static final long COOKIE_VALUE = 0x5a525450;
|
||||
|
||||
private static final int ZRTP_CRC_LENGTH = 4;
|
||||
|
||||
public HandshakePacket(RtpPacket packet) {
|
||||
super(packet.getPacket(), packet.getPacketLength());
|
||||
fixOffsetsForHeaderBug();
|
||||
}
|
||||
|
||||
public HandshakePacket(RtpPacket packet, boolean deepCopy) {
|
||||
super(packet.getPacket(), packet.getPacketLength(), deepCopy);
|
||||
fixOffsetsForHeaderBug();
|
||||
}
|
||||
|
||||
public HandshakePacket(String type, int length, boolean includeLegacyHeaderBug) {
|
||||
super(length + ZRTP_CRC_LENGTH + (includeLegacyHeaderBug ? RtpPacket.HEADER_LENGTH : 0));
|
||||
|
||||
setPrefix(includeLegacyHeaderBug);
|
||||
setCookie();
|
||||
fixOffsetsForHeaderBug();
|
||||
|
||||
setMagic();
|
||||
setLength(length);
|
||||
setType(type);
|
||||
}
|
||||
|
||||
public byte[] getMessageBytes() throws InvalidPacketException {
|
||||
if (this.getPacketLength() < (LENGTH_OFFSET + 3))
|
||||
throw new InvalidPacketException("Packet length shorter than length header.");
|
||||
|
||||
int messagePacketLength = this.getLength();
|
||||
|
||||
if (messagePacketLength + 4 > this.getPacketLength())
|
||||
throw new InvalidPacketException("Encoded packet length longer than length of packet.");
|
||||
|
||||
byte[] messageBytes = new byte[messagePacketLength];
|
||||
System.arraycopy(this.data, getHeaderBugOffset() + MESSAGE_BASE, messageBytes, 0, messagePacketLength);
|
||||
|
||||
return messageBytes;
|
||||
}
|
||||
|
||||
private void setPrefix(boolean includeLegacyHeaderBug) {
|
||||
if (includeLegacyHeaderBug) data[PREFIX_OFFSET] = LEGACY_HEADER_BUG_PREFIX_VALUE;
|
||||
else data[PREFIX_OFFSET] = PREFIX_VALUE;
|
||||
}
|
||||
|
||||
private void setCookie() {
|
||||
Conversions.longTo4ByteArray(this.data, COOKIE_OFFSET, COOKIE_VALUE);
|
||||
}
|
||||
|
||||
private int getLength() {
|
||||
return Conversions.byteArrayToShort(this.data, LENGTH_OFFSET);
|
||||
}
|
||||
|
||||
protected void setLength(int length) {
|
||||
Conversions.shortToByteArray(this.data, LENGTH_OFFSET, length);
|
||||
}
|
||||
|
||||
private byte[] calculateMac(byte[] key, int messageLength) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(key, "HmacSHA256"));
|
||||
mac.update(this.data, getHeaderBugOffset() + MESSAGE_BASE, messageLength);
|
||||
return mac.doFinal();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setMac(byte[] key, int macOffset, int messageLength) {
|
||||
byte[] digest = calculateMac(key, messageLength);
|
||||
System.arraycopy(digest, 0, this.data, macOffset, 8);
|
||||
}
|
||||
|
||||
protected void verifyMac(byte[] key, int macOffset, int messageLength, byte[] subhash)
|
||||
throws InvalidPacketException
|
||||
{
|
||||
byte[] digest = calculateMac(key, messageLength);
|
||||
byte[] truncatedDigest = new byte[8];
|
||||
byte[] messageDigest = new byte[8];
|
||||
|
||||
System.arraycopy(digest, 0, truncatedDigest, 0, truncatedDigest.length);
|
||||
System.arraycopy(this.data, macOffset, messageDigest, 0, messageDigest.length);
|
||||
|
||||
if (!Arrays.equals(truncatedDigest, messageDigest))
|
||||
throw new InvalidPacketException("Bad MAC!");
|
||||
|
||||
if (!verifySubHash(key, subhash))
|
||||
throw new InvalidPacketException("MAC key is not preimage of hash included in message!");
|
||||
}
|
||||
|
||||
private boolean verifySubHash(byte[] key, byte[] subhash) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] digest = md.digest(key);
|
||||
return Arrays.equals(digest, subhash);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private long calculateCRC(byte[] data, int packetLength) {
|
||||
CRC32 crc = new CRC32();
|
||||
crc.update(data, 0, packetLength-4);
|
||||
return crc.getValue();
|
||||
}
|
||||
|
||||
public boolean verifyCRC() {
|
||||
long myCRC = calculateCRC(this.data, getPacketLength());
|
||||
long theirCRC = Conversions.byteArray4ToLong(this.data, getPacketLength()-4);
|
||||
return myCRC == theirCRC;
|
||||
}
|
||||
|
||||
public void setCRC() {
|
||||
Conversions.longTo4ByteArray(this.data, getPacketLength()-4,
|
||||
calculateCRC(this.data, this.getPacketLength()));
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
if (this.data[PREFIX_OFFSET] != 0x10 && this.data[PREFIX_OFFSET] != 0x20) {
|
||||
return ConfAckPacket.TYPE;
|
||||
}
|
||||
|
||||
try {
|
||||
return new String(data, TYPE_OFFSET, 8, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setMagic() {
|
||||
Conversions.shortToByteArray(this.data, MAGIC_OFFSET, MAGIC_VALUE);
|
||||
}
|
||||
|
||||
private void setType(String type) {
|
||||
type.getBytes(0, type.length(), this.data, TYPE_OFFSET);
|
||||
}
|
||||
|
||||
// NOTE <10-09-2013> :: The RedPhone ZRTP packet format had two problems with it:
|
||||
//
|
||||
// 1) The wrong bit was set in the 'version' byte (0x20 instead of 0x10).
|
||||
//
|
||||
// 2) Each handshake packet included 12 extra null bytes in between the ZRTP header
|
||||
// and the ZRTP 'message'. These don't cause any problems or have any security
|
||||
// implications, but it's definitely incorrect. In order not to break backwards
|
||||
// compatibility, we have to intentionally do the wrong thing when it looks like
|
||||
// we're talking with old clients.
|
||||
//
|
||||
// The initiator indicates that it's a "new" client by setting a version of 1 in the
|
||||
// initiate signal. The responder indicates that it's a "new" client by either setting
|
||||
// the "new/correct" version byte (0x10) on the handshake packets it sends, or the
|
||||
// "old/incorrect" version byte (0x20).
|
||||
//
|
||||
/// This is a complete mess and it fucks up most of the handshake packet code. Eventually
|
||||
// we'll phase this out.
|
||||
protected int getHeaderBugOffset() {
|
||||
if (isLegacyHeaderBugPresent()) {
|
||||
Log.w("HandshakePacket", "Returning offset for legacy handshake bug...");
|
||||
return RtpPacket.HEADER_LENGTH;
|
||||
} else {
|
||||
Log.w("HandshakePacket", "Not including legacy handshake bug...");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLegacyHeaderBugPresent() {
|
||||
return data[PREFIX_OFFSET] == LEGACY_HEADER_BUG_PREFIX_VALUE;
|
||||
}
|
||||
|
||||
private void fixOffsetsForHeaderBug() {
|
||||
int headerBugOffset = getHeaderBugOffset();
|
||||
|
||||
MAGIC_OFFSET += headerBugOffset;
|
||||
LENGTH_OFFSET += headerBugOffset;
|
||||
TYPE_OFFSET += headerBugOffset;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* ZRTP hash commitment chain.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class HashChain {
|
||||
|
||||
private byte[] h0 = new byte[32];
|
||||
private byte[] h1;
|
||||
private byte[] h2;
|
||||
private byte[] h3;
|
||||
|
||||
public HashChain() {
|
||||
try {
|
||||
SecureRandom.getInstance("SHA1PRNG").nextBytes(h0);
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance("SHA256");
|
||||
h1 = md.digest(h0);
|
||||
h2 = md.digest(h1);
|
||||
h3 = md.digest(h2);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getH0() {
|
||||
return this.h0;
|
||||
}
|
||||
|
||||
public byte[] getH1() {
|
||||
return this.h1;
|
||||
}
|
||||
|
||||
public byte[] getH2() {
|
||||
return this.h2;
|
||||
}
|
||||
|
||||
public byte[] getH3() {
|
||||
return this.h3;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.SecureRtpPacket;
|
||||
|
||||
/**
|
||||
* ZRTP 'hello ack' handshake packet.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class HelloAckPacket extends HandshakePacket {
|
||||
|
||||
public static final String TYPE = "HelloAck";
|
||||
|
||||
public HelloAckPacket(SecureRtpPacket packet) {
|
||||
super(packet);
|
||||
}
|
||||
|
||||
public HelloAckPacket(boolean includeLegacyHeaderBug) {
|
||||
super(TYPE, 12, includeLegacyHeaderBug);
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.network.RtpPacket;
|
||||
import org.thoughtcrime.redphone.util.Conversions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* ZRTP 'hello' handshake packet.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6189#section-5.2
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class HelloPacket extends HandshakePacket {
|
||||
public static final String TYPE = "Hello ";
|
||||
|
||||
private static final List<byte[]> KEY_AGREEMENTS = new ArrayList<byte[]>(1) {{
|
||||
add(new byte[] {'E', 'C', '2', '5'});
|
||||
}};
|
||||
|
||||
private static final int HELLO_MIN_LENGTH = 88;
|
||||
private static final int OPTIONAL_VALUES_LENGTH = KEY_AGREEMENTS.size() * 4;
|
||||
|
||||
private static final int MAGIC_LENGTH = 2;
|
||||
private static final int LENGTH_LENGTH = 2;
|
||||
private static final int TYPE_LENGTH = 8;
|
||||
private static final int VERSION_LENGTH = 4;
|
||||
private static final int CLIENT_LENGTH = 16;
|
||||
private static final int H3_LENGTH = 32;
|
||||
private static final int ZID_LENGTH = 12;
|
||||
private static final int MAC_LENGTH = 8;
|
||||
|
||||
private static final int _LENGTH_OFFSET = MESSAGE_BASE + MAGIC_LENGTH;
|
||||
private static final int _TYPE_OFFSET = _LENGTH_OFFSET + LENGTH_LENGTH;
|
||||
private static final int _VERSION_OFFSET = _TYPE_OFFSET + TYPE_LENGTH;
|
||||
private static final int _CLIENT_OFFSET = _VERSION_OFFSET + VERSION_LENGTH;
|
||||
private static final int _H3_OFFSET = _CLIENT_OFFSET + CLIENT_LENGTH;
|
||||
private static final int _ZID_OFFSET = _H3_OFFSET + H3_LENGTH;
|
||||
private static final int _FLAGS_OFFSET = _ZID_OFFSET + ZID_LENGTH;
|
||||
|
||||
private static final int _HC_OFFSET = _FLAGS_OFFSET + 1;
|
||||
private static final int _CC_OFFSET = _FLAGS_OFFSET + 2;
|
||||
private static final int _AC_OFFSET = _FLAGS_OFFSET + 2;
|
||||
private static final int _KC_OFFSET = _FLAGS_OFFSET + 3;
|
||||
private static final int _SC_OFFSET = _FLAGS_OFFSET + 3;
|
||||
|
||||
private static final int _OPTIONS_OFFSET = _FLAGS_OFFSET + 4;
|
||||
|
||||
private int LENGTH_OFFSET = _LENGTH_OFFSET;
|
||||
private int TYPE_OFFSET = _TYPE_OFFSET;
|
||||
private int VERSION_OFFSET = _VERSION_OFFSET;
|
||||
private int CLIENT_OFFSET = _CLIENT_OFFSET;
|
||||
private int H3_OFFSET = _H3_OFFSET;
|
||||
private int ZID_OFFSET = _ZID_OFFSET;
|
||||
private int FLAGS_OFFSET = _FLAGS_OFFSET;
|
||||
|
||||
private int HC_OFFSET = _HC_OFFSET;
|
||||
private int CC_OFFSET = _CC_OFFSET;
|
||||
private int AC_OFFSET = _AC_OFFSET;
|
||||
private int KC_OFFSET = _KC_OFFSET;
|
||||
private int SC_OFFSET = _SC_OFFSET;
|
||||
|
||||
private int OPTIONS_OFFSET = _OPTIONS_OFFSET;
|
||||
|
||||
public HelloPacket(RtpPacket packet) {
|
||||
super(packet);
|
||||
fixOffsetsForHeaderBug();
|
||||
}
|
||||
|
||||
public HelloPacket(RtpPacket packet, boolean deepCopy) {
|
||||
super(packet, deepCopy);
|
||||
fixOffsetsForHeaderBug();
|
||||
}
|
||||
|
||||
public HelloPacket(HashChain hashChain, byte[] zid, boolean includeLegacyHeaderBug) {
|
||||
super(TYPE, HELLO_MIN_LENGTH + OPTIONAL_VALUES_LENGTH, includeLegacyHeaderBug);
|
||||
fixOffsetsForHeaderBug();
|
||||
setZrtpVersion();
|
||||
setClientId();
|
||||
setH3(hashChain.getH3());
|
||||
setZID(zid);
|
||||
setKeyAgreement();
|
||||
setMac(hashChain.getH2(),
|
||||
OPTIONS_OFFSET + OPTIONAL_VALUES_LENGTH,
|
||||
HELLO_MIN_LENGTH + OPTIONAL_VALUES_LENGTH - MAC_LENGTH);
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return Conversions.byteArrayToShort(data, LENGTH_OFFSET);
|
||||
}
|
||||
|
||||
public byte[] getZID() {
|
||||
byte[] zid = new byte[ZID_LENGTH];
|
||||
System.arraycopy(this.data, ZID_OFFSET, zid, 0, zid.length);
|
||||
return zid;
|
||||
}
|
||||
|
||||
private byte[] getH3() {
|
||||
byte[] hashValue = new byte[H3_LENGTH];
|
||||
System.arraycopy(this.data, H3_OFFSET, hashValue, 0, hashValue.length);
|
||||
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
public void verifyMac(byte[] key) throws InvalidPacketException {
|
||||
if (getLength() < HELLO_MIN_LENGTH)
|
||||
throw new InvalidPacketException("Encoded length longer than data length.");
|
||||
|
||||
super.verifyMac(key,
|
||||
OPTIONS_OFFSET + getOptionsLength(),
|
||||
getMessageLength() - MAC_LENGTH,
|
||||
getH3());
|
||||
}
|
||||
|
||||
private int getMessageLength() {
|
||||
return HELLO_MIN_LENGTH + getOptionsLength();
|
||||
}
|
||||
|
||||
private int getOptionsLength() {
|
||||
return (getHashOptionCount() * 4) +
|
||||
(getCipherOptionCount() * 4) +
|
||||
(getAuthTagOptionCount() * 4) +
|
||||
(getKeyAgreementOptionCount() * 4) +
|
||||
(getSasOptionCount() * 4);
|
||||
}
|
||||
|
||||
private int getHashOptionCount() {
|
||||
return this.data[HC_OFFSET] & 0x0F;
|
||||
}
|
||||
|
||||
private int getCipherOptionCount() {
|
||||
return (this.data[CC_OFFSET] & 0xFF) >> 4;
|
||||
}
|
||||
|
||||
private void setCipherOptionCount(int count) {
|
||||
this.data[CC_OFFSET] |= ((count & 0x0F) << 4);
|
||||
}
|
||||
|
||||
private int getAuthTagOptionCount() {
|
||||
return this.data[AC_OFFSET] & 0x0F;
|
||||
}
|
||||
|
||||
private int getKeyAgreementOptionCount() {
|
||||
return (this.data[KC_OFFSET] & 0xFF) >> 4;
|
||||
}
|
||||
|
||||
private void setKeyAgreementOptionsCount(int count) {
|
||||
this.data[KC_OFFSET] |= ((count << 4) & 0xFF);
|
||||
}
|
||||
|
||||
public Set<String> getKeyAgreementOptions() {
|
||||
Set<String> keyAgreementOptions = new HashSet<String>();
|
||||
|
||||
int keyAgreementOptionsOffset = OPTIONS_OFFSET +
|
||||
(getHashOptionCount() * 4) +
|
||||
(getCipherOptionCount() * 4) +
|
||||
(getAuthTagOptionCount() * 4);
|
||||
|
||||
for (int i=0;i<getKeyAgreementOptionCount();i++) {
|
||||
int keyAgreementOptionOffset = keyAgreementOptionsOffset + (i * 4);
|
||||
keyAgreementOptions.add(new String(this.data, keyAgreementOptionOffset, 4));
|
||||
}
|
||||
|
||||
return keyAgreementOptions;
|
||||
}
|
||||
|
||||
public void setKeyAgreementOptions(List<byte[]> options) {
|
||||
int keyAgreementOptionsOffset = OPTIONS_OFFSET +
|
||||
(getHashOptionCount() * 4) +
|
||||
(getCipherOptionCount() * 4) +
|
||||
(getAuthTagOptionCount() * 4);
|
||||
|
||||
for (int i=0;i<options.size();i++) {
|
||||
int optionOffset = keyAgreementOptionsOffset + (i * 4);
|
||||
byte[] option = options.get(i);
|
||||
System.arraycopy(option, 0, this.data, optionOffset, 4);
|
||||
}
|
||||
}
|
||||
|
||||
private int getSasOptionCount() {
|
||||
return this.data[SC_OFFSET] & 0x0F;
|
||||
}
|
||||
|
||||
private void setZrtpVersion() {
|
||||
"1.10".getBytes(0, 4, this.data, VERSION_OFFSET);
|
||||
}
|
||||
|
||||
private void setClientId() {
|
||||
"RedPhone 024 ".getBytes(0, 16, this.data, CLIENT_OFFSET);
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return new String(this.data, CLIENT_OFFSET, CLIENT_LENGTH);
|
||||
}
|
||||
|
||||
private void setH3(byte[] hash) {
|
||||
System.arraycopy(hash, 0, this.data, H3_OFFSET, hash.length);
|
||||
}
|
||||
|
||||
private void setZID(byte[] zid) {
|
||||
System.arraycopy(zid, 0, this.data, ZID_OFFSET, zid.length);
|
||||
}
|
||||
|
||||
private void setKeyAgreement() {
|
||||
setKeyAgreementOptionsCount(KEY_AGREEMENTS.size());
|
||||
setKeyAgreementOptions(KEY_AGREEMENTS);
|
||||
}
|
||||
|
||||
private void fixOffsetsForHeaderBug() {
|
||||
int headerBugOffset = getHeaderBugOffset();
|
||||
|
||||
LENGTH_OFFSET += headerBugOffset;
|
||||
TYPE_OFFSET += headerBugOffset;
|
||||
VERSION_OFFSET += headerBugOffset;
|
||||
CLIENT_OFFSET += headerBugOffset;
|
||||
H3_OFFSET += headerBugOffset;
|
||||
ZID_OFFSET += headerBugOffset;
|
||||
FLAGS_OFFSET += headerBugOffset;
|
||||
|
||||
HC_OFFSET += headerBugOffset;
|
||||
CC_OFFSET += headerBugOffset;
|
||||
AC_OFFSET += headerBugOffset;
|
||||
KC_OFFSET += headerBugOffset;
|
||||
SC_OFFSET += headerBugOffset;
|
||||
|
||||
OPTIONS_OFFSET += headerBugOffset;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
public class InvalidPacketException extends Exception {
|
||||
|
||||
public InvalidPacketException() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidPacketException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidPacketException(Throwable throwable) {
|
||||
super(throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidPacketException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.util.Conversions;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Class that represents the ZRTP master secret.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class MasterSecret {
|
||||
|
||||
private byte[] zidi;
|
||||
private byte[] zidr;
|
||||
private byte[] totalHash;
|
||||
private byte[] counter;
|
||||
private byte[] sharedSecret;
|
||||
|
||||
private byte[] srtpKeyI;
|
||||
private byte[] srtpKeyR;
|
||||
private byte[] srtpSaltI;
|
||||
private byte[] srtpSaltR;
|
||||
private byte[] macI;
|
||||
private byte[] macR;
|
||||
private byte[] zrtpKeyI;
|
||||
private byte[] zrtpKeyR;
|
||||
private byte[] sas;
|
||||
private byte[] rs1;
|
||||
|
||||
public MasterSecret(byte[] sharedSecret, byte[] totalHash, byte[] zidi, byte[] zidr) {
|
||||
this.zidi = zidi;
|
||||
this.zidr = zidr;
|
||||
this.totalHash = totalHash;
|
||||
this.sharedSecret = sharedSecret;
|
||||
this.counter = Conversions.intToByteArray(1);
|
||||
|
||||
this.srtpKeyI = calculateKDF("Initiator SRTP master key", 16);
|
||||
this.srtpKeyR = calculateKDF("Responder SRTP master key", 16);
|
||||
this.srtpSaltI = calculateKDF("Initiator SRTP master salt", 14);
|
||||
this.srtpSaltR = calculateKDF("Responder SRTP master salt", 14);
|
||||
|
||||
this.macI = calculateKDF("Initiator HMAC key", 20);
|
||||
this.macR = calculateKDF("Responder HMAC key", 20);
|
||||
|
||||
this.zrtpKeyI = calculateKDF("Initiator ZRTP key", 16);
|
||||
this.zrtpKeyR = calculateKDF("Responder ZRTP key", 16);
|
||||
|
||||
this.sas = calculateKDF("SAS", 4);
|
||||
|
||||
this.rs1 = calculateKDF("retained secret", 32);
|
||||
}
|
||||
|
||||
public byte[] getSAS() {
|
||||
return this.sas;
|
||||
}
|
||||
|
||||
public byte[] getInitiatorSrtpKey() {
|
||||
return this.srtpKeyI;
|
||||
}
|
||||
|
||||
public byte[] getResponderSrtpKey() {
|
||||
return this.srtpKeyR;
|
||||
}
|
||||
|
||||
public byte[] getInitiatorSrtpSalt() {
|
||||
return this.srtpSaltI;
|
||||
}
|
||||
|
||||
public byte[] getResponderSrtpSailt() {
|
||||
return this.srtpSaltR;
|
||||
}
|
||||
|
||||
public byte[] getInitiatorMacKey() {
|
||||
return this.macI;
|
||||
}
|
||||
|
||||
public byte[] getResponderMacKey() {
|
||||
return this.macR;
|
||||
}
|
||||
|
||||
public byte[] getInitiatorZrtpKey() {
|
||||
return this.zrtpKeyI;
|
||||
}
|
||||
|
||||
public byte[] getResponderZrtpKey() {
|
||||
return this.zrtpKeyR;
|
||||
}
|
||||
|
||||
public byte[] getRetainedSecret() {
|
||||
return rs1;
|
||||
}
|
||||
|
||||
private byte[] calculateKDF(String label, int truncatedLength) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(this.sharedSecret, "HmacSHA256"));
|
||||
|
||||
mac.update(counter);
|
||||
mac.update(label.getBytes());
|
||||
mac.update((byte)0x00);
|
||||
mac.update(zidi);
|
||||
mac.update(zidr);
|
||||
mac.update(totalHash);
|
||||
mac.update(Conversions.intToByteArray(truncatedLength));
|
||||
|
||||
byte[] digest = mac.doFinal();
|
||||
|
||||
if (digest.length == truncatedLength)
|
||||
return digest;
|
||||
|
||||
byte[] truncated = new byte[truncatedLength];
|
||||
System.arraycopy(digest, 0, truncated, 0, truncated.length);
|
||||
return truncated;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
public class NegotiationFailedException extends Exception {
|
||||
|
||||
public NegotiationFailedException() {}
|
||||
|
||||
public NegotiationFailedException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public NegotiationFailedException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public NegotiationFailedException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
public class RecipientUnavailableException extends NegotiationFailedException {
|
||||
|
||||
public RecipientUnavailableException() {}
|
||||
|
||||
public RecipientUnavailableException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public RecipientUnavailableException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public RecipientUnavailableException(String detailMessage,
|
||||
Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public class RedPhoneClientId {
|
||||
|
||||
private boolean isRedphoneClient;
|
||||
private int clientIdInteger;
|
||||
|
||||
public RedPhoneClientId(String clientId) {
|
||||
String[] clientIdParts = clientId.split(" ");
|
||||
|
||||
if (clientIdParts.length < 2) {
|
||||
isRedphoneClient = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!"RedPhone".equals(clientIdParts[0].trim())) {
|
||||
isRedphoneClient = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.clientIdInteger = Integer.parseInt(clientIdParts[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.w("RedPhoneClientId", nfe);
|
||||
this.isRedphoneClient = false;
|
||||
}
|
||||
|
||||
this.isRedphoneClient = true;
|
||||
}
|
||||
|
||||
public boolean isImplicitDh3kVersion() {
|
||||
return this.isRedphoneClient && (this.clientIdInteger == 19 || this.clientIdInteger == 24);
|
||||
}
|
||||
|
||||
public boolean isLegacyConfirmConnectionVersion() {
|
||||
return this.isRedphoneClient && this.clientIdInteger < 24;
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
/**
|
||||
* Calculate a Short Authentication String from the SAS bytes derived
|
||||
* out of the ZRTP handshake.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class SASCalculator {
|
||||
private static final String[] PGP_LIST_EVEN = {
|
||||
"aardvark", "absurd", "accrue", "acme", "adrift", "adult", "afflict", "ahead", "aimless",
|
||||
"Algol", "allow", "alone", "ammo", "ancient", "apple", "artist", "assume", "Athens", "atlas",
|
||||
"Aztec", "baboon", "backfield", "backward", "banjo", "beaming", "bedlamp", "beehive", "beeswax",
|
||||
"befriend", "Belfast", "berserk", "billiard", "bison", "blackjack", "blockade", "blowtorch",
|
||||
"bluebird", "bombast", "bookshelf", "brackish", "breadline", "breakup", "brickyard",
|
||||
"briefcase", "Burbank", "button", "buzzard", "cement", "chairlift", "chatter", "checkup",
|
||||
"chisel", "choking", "chopper", "Christmas", "clamshell", "classic", "classroom", "cleanup",
|
||||
"clockwork", "cobra", "commence", "concert", "cowbell", "crackdown", "cranky", "crowfoot",
|
||||
"crucial", "crumpled", "crusade", "cubic", "dashboard", "deadbolt", "deckhand", "dogsled",
|
||||
"dragnet", "drainage", "dreadful", "drifter", "dropper", "drumbeat", "drunken", "Dupont",
|
||||
"dwelling", "eating", "edict", "egghead", "eightball", "endorse", "endow", "enlist", "erase",
|
||||
"escape", "exceed", "eyeglass", "eyetooth", "facial", "fallout", "flagpole", "flatfoot",
|
||||
"flytrap", "fracture", "framework", "freedom", "frighten", "gazelle", "Geiger", "glitter",
|
||||
"glucose", "goggles", "goldfish", "gremlin", "guidance", "hamlet", "highchair", "hockey",
|
||||
"indoors", "indulge", "inverse", "involve", "island", "jawbone", "keyboard", "kickoff", "kiwi",
|
||||
"klaxon", "locale", "lockup", "merit", "minnow", "miser", "Mohawk", "mural", "music",
|
||||
"necklace", "Neptune", "newborn", "nightbird", "Oakland", "obtuse", "offload", "optic",
|
||||
"orca", "payday", "peachy", "pheasant", "physique", "playhouse", "Pluto", "preclude", "prefer",
|
||||
"preshrunk", "printer", "prowler", "pupil", "puppy", "python", "quadrant", "quiver", "quota",
|
||||
"ragtime", "ratchet", "rebirth", "reform", "regain", "reindeer", "rematch", "repay", "retouch",
|
||||
"revenge", "reward", "rhythm", "ribcage", "ringbolt", "robust", "rocker", "ruffled", "sailboat",
|
||||
"sawdust", "scallion", "scenic", "scorecard", "Scotland", "seabird", "select", "sentence",
|
||||
"shadow", "shamrock", "showgirl", "skullcap", "skydive", "slingshot", "slowdown", "snapline",
|
||||
"snapshot", "snowcap", "snowslide", "solo", "southward", "soybean", "spaniel", "spearhead",
|
||||
"spellbind", "spheroid", "spigot", "spindle", "spyglass", "stagehand", "stagnate", "stairway",
|
||||
"standard", "stapler", "steamship", "sterling", "stockman", "stopwatch", "stormy", "sugar",
|
||||
"surmount", "suspense", "sweatband", "swelter", "tactics", "talon", "tapeworm", "tempest",
|
||||
"tiger", "tissue", "tonic", "topmost", "tracker", "transit", "trauma", "treadmill", "Trojan",
|
||||
"trouble", "tumor", "tunnel", "tycoon", "uncut", "unearth", "unwind", "uproot", "upset",
|
||||
"upshot", "vapor", "village", "virus", "Vulcan", "waffle", "wallet", "watchword", "wayside",
|
||||
"willow", "woodlark", "Zulu"};
|
||||
|
||||
private static final String[] PGP_LIST_ODD = {
|
||||
"adroitness", "adviser", "aftermath", "aggregate", "alkali", "almighty", "amulet", "amusement",
|
||||
"antenna", "applicant", "Apollo", "armistice", "article", "asteroid", "Atlantic", "atmosphere",
|
||||
"autopsy", "Babylon", "backwater", "barbecue", "belowground", "bifocals", "bodyguard",
|
||||
"bookseller", "borderline", "bottomless", "Bradbury", "bravado", "Brazilian", "breakaway",
|
||||
"Burlington", "businessman", "butterfat", "Camelot", "candidate", "cannonball", "Capricorn",
|
||||
"caravan", "caretaker", "celebrate", "cellulose", "certify", "chambermaid", "Cherokee",
|
||||
"Chicago", "clergyman", "coherence", "combustion", "commando", "company", "component",
|
||||
"concurrent", "confidence", "conformist", "congregate", "consensus", "consulting", "corporate",
|
||||
"corrosion", "councilman", "crossover", "crucifix", "cumbersome", "customer", "Dakota",
|
||||
"decadence", "December", "decimal", "designing", "detector", "detergent", "determine",
|
||||
"dictator", "dinosaur", "direction", "disable", "disbelief", "disruptive", "distortion",
|
||||
"document", "embezzle", "enchanting", "enrollment", "enterprise", "equation", "equipment",
|
||||
"escapade", "Eskimo", "everyday", "examine", "existence", "exodus", "fascinate", "filament",
|
||||
"finicky", "forever", "fortitude", "frequency", "gadgetry", "Galveston", "getaway", "glossary",
|
||||
"gossamer", "graduate", "gravity", "guitarist", "hamburger", "Hamilton", "handiwork",
|
||||
"hazardous", "headwaters", "hemisphere", "hesitate", "hideaway", "holiness", "hurricane",
|
||||
"hydraulic", "impartial", "impetus", "inception", "indigo", "inertia", "infancy", "inferno",
|
||||
"informant", "insincere", "insurgent", "integrate", "intention", "inventive", "Istanbul",
|
||||
"Jamaica", "Jupiter", "leprosy", "letterhead", "liberty", "maritime", "matchmaker", "maverick",
|
||||
"Medusa", "megaton", "microscope", "microwave", "midsummer", "millionaire", "miracle",
|
||||
"misnomer", "molasses", "molecule", "Montana", "monument", "mosquito", "narrative", "nebula",
|
||||
"newsletter", "Norwegian", "October", "Ohio", "onlooker", "opulent", "Orlando", "outfielder",
|
||||
"Pacific", "pandemic", "Pandora", "paperweight", "paragon", "paragraph", "paramount",
|
||||
"passenger", "pedigree", "Pegasus", "penetrate", "perceptive", "performance", "pharmacy",
|
||||
"phonetic", "photograph", "pioneer", "pocketful", "politeness", "positive", "potato",
|
||||
"processor", "provincial", "proximate", "puberty", "publisher", "pyramid", "quantity",
|
||||
"racketeer", "rebellion", "recipe", "recover", "repellent", "replica", "reproduce", "resistor",
|
||||
"responsive", "retraction", "retrieval", "retrospect", "revenue", "revival", "revolver",
|
||||
"sandalwood", "sardonic", "Saturday", "savagery", "scavenger", "sensation", "sociable",
|
||||
"souvenir", "specialist", "speculate", "stethoscope", "stupendous", "supportive", "surrender",
|
||||
"suspicious", "sympathy", "tambourine", "telephone", "therapist", "tobacco", "tolerance",
|
||||
"tomorrow", "torpedo", "tradition", "travesty", "trombonist", "truncated", "typewriter",
|
||||
"ultimate", "undaunted", "underfoot", "unicorn", "unify", "universe", "unravel", "upcoming",
|
||||
"vacancy", "vagabond", "vertigo", "Virginia", "visitor", "vocalist", "voyager", "warranty",
|
||||
"Waterloo", "whimsical", "Wichita", "Wilmington", "Wyoming", "yesteryear", "Yucatan"};
|
||||
|
||||
public static String calculateSAS(byte[] sasBytes) {
|
||||
int wordIndexOne = (int)sasBytes[0] & 0xFF;
|
||||
int wordIndexTwo = (int)sasBytes[1] & 0xFF;
|
||||
|
||||
return PGP_LIST_EVEN[wordIndexOne] + " " + PGP_LIST_ODD[wordIndexTwo];
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
public class SASInfo {
|
||||
|
||||
private final String sasText;
|
||||
private final boolean verified;
|
||||
|
||||
public SASInfo(String sasText, boolean verified) {
|
||||
this.sasText = sasText;
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
public String getSasText() {
|
||||
return sasText;
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return verified;
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import org.thoughtcrime.redphone.util.Conversions;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Calculates a shared secret based on the DH parts.
|
||||
*
|
||||
* The various supported KA types (DH3K, EC25) are handled
|
||||
* in their respective subclasses.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public abstract class SecretCalculator {
|
||||
|
||||
public byte[] calculateSharedSecret(byte[] dhResult, byte[] totalHash, byte[] s1,
|
||||
byte[] zidi, byte[] zidr)
|
||||
{
|
||||
try {
|
||||
byte[] counter = Conversions.intToByteArray(1);
|
||||
byte[] s1Length = Conversions.intToByteArray(s1 == null ? 0 : s1.length);
|
||||
byte[] s2Length = Conversions.intToByteArray(0);
|
||||
byte[] s3Length = Conversions.intToByteArray(0);
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(counter);
|
||||
md.update(dhResult);
|
||||
md.update("ZRTP-HMAC-KDF".getBytes());
|
||||
md.update(zidi);
|
||||
md.update(zidr);
|
||||
md.update(totalHash);
|
||||
md.update(s1Length);
|
||||
if (s1 != null) {
|
||||
md.update(s1);
|
||||
}
|
||||
md.update(s2Length);
|
||||
md.update(s3Length);
|
||||
|
||||
return md.digest();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] calculateTotalHash(HelloPacket responderHello, CommitPacket commit,
|
||||
DHPartOnePacket dhPartOne, DHPartTwoPacket dhPartTwo)
|
||||
throws InvalidPacketException
|
||||
{
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(responderHello.getMessageBytes());
|
||||
md.update(commit.getMessageBytes());
|
||||
md.update(dhPartOne.getMessageBytes());
|
||||
md.update(dhPartTwo.getMessageBytes());
|
||||
return md.digest();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract byte[] calculateKeyAgreement(KeyPair localKey, byte[] publicKeyBytes);
|
||||
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.SecureRtpSocket;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.InitiatorRetainedSecretsCalculator;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecrets;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecretsCalculator;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecretsDerivatives;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* The "initiator" side of a ZRTP handshake. This side just hangs out and waits
|
||||
* for the "responder" to send a hello packet, then proceeds through the ZRTP handshake.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class ZRTPInitiatorSocket extends ZRTPSocket {
|
||||
|
||||
private HelloPacket foreignHello;
|
||||
private HelloPacket localHello;
|
||||
private CommitPacket commitPacket;
|
||||
|
||||
private DHPartOnePacket foreignDH;
|
||||
private DHPartTwoPacket localDH;
|
||||
|
||||
private ConfirmOnePacket confirmPacket;
|
||||
|
||||
private RetainedSecretsCalculator retainedSecretsCalculator;
|
||||
private boolean includeLegacyHeaderBug;
|
||||
|
||||
public ZRTPInitiatorSocket(Context context, SecureRtpSocket socket,
|
||||
byte[] localZid, String foreignNumber)
|
||||
{
|
||||
super(context, socket, localZid, foreignNumber, EXPECTING_HELLO);
|
||||
this.includeLegacyHeaderBug = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleCommit(HandshakePacket packet) {
|
||||
throw new AssertionError("Invalid state!");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleConfirmAck(HandshakePacket packet) {
|
||||
boolean continuity = retainedSecretsCalculator.hasContinuity(foreignDH.getDerivativeSecretOne(),
|
||||
foreignDH.getDerivativeSecretTwo());
|
||||
byte[] foreignZid = foreignHello.getZID();
|
||||
byte[] rs1 = masterSecret.getRetainedSecret();
|
||||
long expiration = System.currentTimeMillis() + (confirmPacket.getCacheTime() * 1000L);
|
||||
|
||||
// cacheRetainedSecret(remoteNumber, foreignZid, rs1, expiration, continuity);
|
||||
setState(HANDSHAKE_COMPLETE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleConfirmOne(HandshakePacket packet) throws InvalidPacketException {
|
||||
confirmPacket = new ConfirmOnePacket(packet, isLegacyConfirmConnection());
|
||||
|
||||
confirmPacket.verifyMac(masterSecret.getResponderMacKey());
|
||||
confirmPacket.decrypt(masterSecret.getResponderZrtpKey());
|
||||
|
||||
byte[] preimage = confirmPacket.getPreimage();
|
||||
foreignDH.verifyMac(preimage);
|
||||
|
||||
setState(EXPECTING_CONFIRM_ACK);
|
||||
sendFreshPacket(new ConfirmTwoPacket(masterSecret.getInitiatorMacKey(),
|
||||
masterSecret.getInitiatorZrtpKey(),
|
||||
this.hashChain, isLegacyConfirmConnection(),
|
||||
includeLegacyHeaderBug));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleConfirmTwo(HandshakePacket packet) throws InvalidPacketException {
|
||||
throw new InvalidPacketException("Initiator received a Confirm2 packet?");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleDH(HandshakePacket packet) throws InvalidPacketException {
|
||||
assert(localDH != null);
|
||||
|
||||
SecretCalculator calculator;
|
||||
|
||||
switch (getKeyAgreementType()) {
|
||||
case KA_TYPE_EC25:
|
||||
foreignDH = new EC25DHPartOnePacket(packet, true);
|
||||
calculator = new EC25SecretCalculator();
|
||||
break;
|
||||
case KA_TYPE_DH3K:
|
||||
foreignDH = new DH3KDHPartOnePacket(packet, true);
|
||||
calculator = new DH3KSecretCalculator();
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unknown KA type: " + getKeyAgreementType());
|
||||
}
|
||||
|
||||
byte[] h1 = foreignDH.getHash();
|
||||
byte[] h2 = calculateH2(h1);
|
||||
|
||||
foreignHello.verifyMac(h2);
|
||||
|
||||
byte[] dhResult = calculator.calculateKeyAgreement(getKeyPair(), foreignDH.getPvr());
|
||||
|
||||
byte[] totalHash = calculator.calculateTotalHash(foreignHello, commitPacket,
|
||||
foreignDH, localDH);
|
||||
|
||||
byte[] s1 = retainedSecretsCalculator.getS1(foreignDH.getDerivativeSecretOne(),
|
||||
foreignDH.getDerivativeSecretTwo());
|
||||
|
||||
byte[] sharedSecret = calculator.calculateSharedSecret(dhResult, totalHash, s1,
|
||||
localHello.getZID(),
|
||||
foreignHello.getZID());
|
||||
|
||||
this.masterSecret = new MasterSecret(sharedSecret, totalHash, localHello.getZID(),
|
||||
foreignHello.getZID());
|
||||
|
||||
setState(EXPECTING_CONFIRM_ONE);
|
||||
sendFreshPacket(localDH);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleHelloAck(HandshakePacket packet) throws InvalidPacketException {
|
||||
// RetainedSecrets retainedSecrets = getRetainedSecrets(remoteNumber, foreignHello.getZID());
|
||||
RetainedSecrets retainedSecrets = new RetainedSecrets(null, null);
|
||||
retainedSecretsCalculator = new InitiatorRetainedSecretsCalculator(retainedSecrets);
|
||||
RetainedSecretsDerivatives derivatives = retainedSecretsCalculator.getRetainedSecretsDerivatives();
|
||||
|
||||
switch (getKeyAgreementType()) {
|
||||
case KA_TYPE_EC25:
|
||||
localDH = new EC25DHPartTwoPacket(hashChain, getPublicKey(), derivatives, includeLegacyHeaderBug);
|
||||
break;
|
||||
case KA_TYPE_DH3K:
|
||||
localDH = new DH3KDHPartTwoPacket(hashChain, getPublicKey(), derivatives, includeLegacyHeaderBug);
|
||||
break;
|
||||
}
|
||||
|
||||
commitPacket = new CommitPacket(hashChain, foreignHello.getMessageBytes(), localDH, localZid, includeLegacyHeaderBug);
|
||||
|
||||
setState(EXPECTING_DH_1);
|
||||
sendFreshPacket(commitPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleHello(HandshakePacket packet) throws InvalidPacketException {
|
||||
foreignHello = new HelloPacket(packet, true);
|
||||
includeLegacyHeaderBug = foreignHello.isLegacyHeaderBugPresent();
|
||||
localHello = new HelloPacket(hashChain, localZid, includeLegacyHeaderBug);
|
||||
|
||||
setState(EXPECTING_HELLO_ACK);
|
||||
sendFreshPacket(localHello);
|
||||
}
|
||||
|
||||
private byte[] calculateH2(byte[] h1) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
return md.digest(h1);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void negotiateStart() throws NegotiationFailedException {
|
||||
super.negotiateStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getKeyAgreementType() {
|
||||
if (foreignHello == null)
|
||||
throw new AssertionError("We can't project agreement type until we've seen a hello!");
|
||||
|
||||
RedPhoneClientId foreignClientId = new RedPhoneClientId(foreignHello.getClientId());
|
||||
|
||||
if (foreignClientId.isImplicitDh3kVersion() ||
|
||||
foreignHello.getKeyAgreementOptions().contains("EC25"))
|
||||
{
|
||||
return KA_TYPE_EC25;
|
||||
} else {
|
||||
return KA_TYPE_DH3K;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HelloPacket getForeignHello() {
|
||||
return foreignHello;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.SecureRtpSocket;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.ResponderRetainedSecretsCalculator;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecrets;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecretsCalculator;
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecretsDerivatives;
|
||||
|
||||
/**
|
||||
* The "responder" side of a ZRTP handshake. We've received a signal from the
|
||||
* initiator, and they're waiting for us to kick off the handshake with a hello
|
||||
* message.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class ZRTPResponderSocket extends ZRTPSocket {
|
||||
|
||||
private HelloPacket localHello;
|
||||
private HelloPacket foreignHello;
|
||||
private CommitPacket foreignCommit;
|
||||
private DHPartOnePacket localDH;
|
||||
private DHPartTwoPacket foreignDH;
|
||||
|
||||
private RetainedSecretsCalculator retainedSecretsCalculator;
|
||||
private boolean includeLegacyHeaderBug;
|
||||
|
||||
public ZRTPResponderSocket(Context context, SecureRtpSocket socket,
|
||||
byte[] localZid, String foreignNumber,
|
||||
boolean includeLegacyHeaderBug)
|
||||
{
|
||||
super(context, socket, localZid, foreignNumber, EXPECTING_HELLO);
|
||||
Log.w("ZRTPResponderSocket", "includeLegacyHeaderBug: " + includeLegacyHeaderBug);
|
||||
this.includeLegacyHeaderBug = includeLegacyHeaderBug;
|
||||
this.localHello = new HelloPacket(hashChain, localZid, includeLegacyHeaderBug);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleHello(HandshakePacket packet) {
|
||||
foreignHello = new HelloPacket(packet, true);
|
||||
|
||||
setState(EXPECTING_COMMIT);
|
||||
sendFreshPacket(new HelloAckPacket(includeLegacyHeaderBug));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleCommit(HandshakePacket packet) throws InvalidPacketException {
|
||||
foreignCommit = new CommitPacket(packet, true);
|
||||
|
||||
|
||||
// RetainedSecrets retainedSecrets = getRetainedSecrets(remoteNumber, foreignHello.getZID());
|
||||
RetainedSecrets retainedSecrets = new RetainedSecrets(null, null);
|
||||
retainedSecretsCalculator = new ResponderRetainedSecretsCalculator(retainedSecrets);
|
||||
RetainedSecretsDerivatives derivatives = retainedSecretsCalculator.getRetainedSecretsDerivatives();
|
||||
|
||||
switch (getKeyAgreementType()) {
|
||||
case KA_TYPE_EC25: localDH = new EC25DHPartOnePacket(hashChain, getPublicKey(), derivatives, includeLegacyHeaderBug); break;
|
||||
case KA_TYPE_DH3K: localDH = new DH3KDHPartOnePacket(hashChain, getPublicKey(), derivatives, includeLegacyHeaderBug); break;
|
||||
}
|
||||
|
||||
foreignHello.verifyMac(foreignCommit.getHash());
|
||||
|
||||
setState(EXPECTING_DH_2);
|
||||
sendFreshPacket(localDH);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleDH(HandshakePacket packet) throws InvalidPacketException {
|
||||
SecretCalculator calculator;
|
||||
|
||||
switch (getKeyAgreementType()) {
|
||||
case KA_TYPE_EC25:
|
||||
foreignDH = new EC25DHPartTwoPacket(packet, true);
|
||||
calculator = new EC25SecretCalculator();
|
||||
break;
|
||||
case KA_TYPE_DH3K:
|
||||
foreignDH = new DH3KDHPartTwoPacket(packet, true);
|
||||
calculator = new DH3KSecretCalculator();
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unknown KA type: " + getKeyAgreementType());
|
||||
}
|
||||
|
||||
foreignCommit.verifyMac(foreignDH.getHash());
|
||||
foreignCommit.verifyHvi(localHello.getMessageBytes(), foreignDH.getMessageBytes());
|
||||
|
||||
byte[] dhResult = calculator.calculateKeyAgreement(getKeyPair(), foreignDH.getPvr());
|
||||
|
||||
byte[] totalHash = calculator.calculateTotalHash(localHello, foreignCommit,
|
||||
localDH, foreignDH);
|
||||
|
||||
byte[] s1 = retainedSecretsCalculator.getS1(foreignDH.getDerivativeSecretOne(),
|
||||
foreignDH.getDerivativeSecretTwo());
|
||||
|
||||
byte[] sharedSecret = calculator.calculateSharedSecret(dhResult, totalHash, s1,
|
||||
foreignHello.getZID(),
|
||||
localHello.getZID());
|
||||
|
||||
this.masterSecret = new MasterSecret(sharedSecret, totalHash, foreignHello.getZID(),
|
||||
localHello.getZID());
|
||||
|
||||
setState(EXPECTING_CONFIRM_TWO);
|
||||
sendFreshPacket(new ConfirmOnePacket(masterSecret.getResponderMacKey(),
|
||||
masterSecret.getResponderZrtpKey(),
|
||||
this.hashChain, isLegacyConfirmConnection(),
|
||||
includeLegacyHeaderBug));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleConfirmTwo(HandshakePacket packet) throws InvalidPacketException {
|
||||
ConfirmTwoPacket confirmPacket = new ConfirmTwoPacket(packet, isLegacyConfirmConnection());
|
||||
|
||||
confirmPacket.verifyMac(masterSecret.getInitiatorMacKey());
|
||||
confirmPacket.decrypt(masterSecret.getInitiatorZrtpKey());
|
||||
|
||||
byte[] preimage = confirmPacket.getPreimage();
|
||||
foreignDH.verifyMac(preimage);
|
||||
|
||||
setState(HANDSHAKE_COMPLETE);
|
||||
sendFreshPacket(new ConfAckPacket(includeLegacyHeaderBug));
|
||||
|
||||
boolean continuity = retainedSecretsCalculator.hasContinuity(foreignDH.getDerivativeSecretOne(),
|
||||
foreignDH.getDerivativeSecretTwo());
|
||||
byte[] foreignZid = foreignHello.getZID();
|
||||
byte[] rs1 = masterSecret.getRetainedSecret();
|
||||
long expiration = System.currentTimeMillis() + (confirmPacket.getCacheTime() * 1000L);
|
||||
|
||||
// cacheRetainedSecret(remoteNumber, foreignZid, rs1, expiration, continuity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleConfirmOne(HandshakePacket packet) throws InvalidPacketException {
|
||||
throw new InvalidPacketException("Responder received a Confirm1 Packet?");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleHelloAck(HandshakePacket packet) {
|
||||
throw new AssertionError("Invalid state!");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleConfirmAck(HandshakePacket packet) {
|
||||
throw new AssertionError("Invalid state!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void negotiateStart() throws NegotiationFailedException {
|
||||
sendFreshPacket(localHello);
|
||||
super.negotiateStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getKeyAgreementType() {
|
||||
if (foreignCommit == null)
|
||||
throw new AssertionError("Can't determine KA until we've seen foreign commit!");
|
||||
|
||||
String keyAgreementSpec = new String(foreignCommit.getKeyAgreementType());
|
||||
|
||||
if (keyAgreementSpec.equals("EC25")) {
|
||||
return KA_TYPE_EC25;
|
||||
} else {
|
||||
return KA_TYPE_DH3K;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HelloPacket getForeignHello() {
|
||||
return foreignHello;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,352 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.spongycastle.jce.interfaces.ECPublicKey;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
import org.thoughtcrime.redphone.crypto.SecureRtpSocket;
|
||||
import org.thoughtcrime.redphone.util.Conversions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.DatagramSocket;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Security;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
|
||||
import javax.crypto.interfaces.DHPublicKey;
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
|
||||
/**
|
||||
* The base ZRTP socket implementation.
|
||||
*
|
||||
* ZRTPInitiatorSocket and ZRTPResponderSocket extend this to implement their respective
|
||||
* parts in the ZRTP handshake.
|
||||
*
|
||||
* This is fundamentally just a simple state machine which iterates through the ZRTP handshake.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public abstract class ZRTPSocket {
|
||||
static {
|
||||
Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
|
||||
}
|
||||
|
||||
public static final BigInteger PRIME = new BigInteger("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", 16);
|
||||
public static final BigInteger GENERATOR = new BigInteger("02", 16);
|
||||
|
||||
private static final int RETRANSMIT_INTERVAL_MILLIS = 150;
|
||||
private static final int MAX_RETRANSMIT_COUNT = 45;
|
||||
|
||||
protected static final int EXPECTING_HELLO = 0;
|
||||
protected static final int EXPECTING_HELLO_ACK = 1;
|
||||
protected static final int EXPECTING_COMMIT = 2;
|
||||
protected static final int EXPECTING_DH_1 = 3;
|
||||
protected static final int EXPECTING_DH_2 = 4;
|
||||
protected static final int EXPECTING_CONFIRM_ONE = 5;
|
||||
protected static final int EXPECTING_CONFIRM_TWO = 9;
|
||||
protected static final int HANDSHAKE_COMPLETE = 6;
|
||||
protected static final int EXPECTING_CONFIRM_ACK = 7;
|
||||
protected static final int TERMINATED = 8;
|
||||
|
||||
protected static final int KA_TYPE_DH3K = 100;
|
||||
protected static final int KA_TYPE_EC25 = 200;
|
||||
|
||||
private long transmitStartTime = 0;
|
||||
private int retransmitInterval = RETRANSMIT_INTERVAL_MILLIS;
|
||||
private int retransmitCount = 0;
|
||||
private int sequence = 0;
|
||||
private int state;
|
||||
|
||||
private final Context context;
|
||||
private final SecureRtpSocket socket;
|
||||
protected final byte[] localZid;
|
||||
protected final String remoteNumber;
|
||||
|
||||
private HandshakePacket lastPacket;
|
||||
private KeyPair dh3kKeyPair;
|
||||
private KeyPair ec25KeyPair;
|
||||
|
||||
protected HashChain hashChain;
|
||||
protected MasterSecret masterSecret;
|
||||
|
||||
public ZRTPSocket(Context context, SecureRtpSocket socket,
|
||||
byte[] localZid, String remoteNumber, int initialState)
|
||||
{
|
||||
this.context = context.getApplicationContext();
|
||||
this.localZid = localZid;
|
||||
this.remoteNumber = remoteNumber;
|
||||
this.socket = socket;
|
||||
this.state = initialState;
|
||||
this.dh3kKeyPair = initializeDH3kKeys();
|
||||
this.ec25KeyPair = initializeEC25Keys();
|
||||
this.hashChain = new HashChain();
|
||||
|
||||
this.socket.setTimeout(RETRANSMIT_INTERVAL_MILLIS);
|
||||
}
|
||||
|
||||
public String getRemoteIp() {
|
||||
return socket.getRemoteIp();
|
||||
}
|
||||
|
||||
public int getRemotePort() {
|
||||
return socket.getRemotePort();
|
||||
}
|
||||
|
||||
public DatagramSocket getDatagramSocket() {
|
||||
return socket.getDatagramSocket();
|
||||
}
|
||||
|
||||
protected abstract void handleHello(HandshakePacket packet) throws InvalidPacketException;
|
||||
protected abstract void handleCommit(HandshakePacket packet) throws InvalidPacketException;
|
||||
protected abstract void handleDH(HandshakePacket packet) throws InvalidPacketException;
|
||||
protected abstract void handleConfirmOne(HandshakePacket packet) throws InvalidPacketException;
|
||||
protected abstract void handleConfirmTwo(HandshakePacket packet) throws InvalidPacketException;
|
||||
protected abstract void handleHelloAck(HandshakePacket packet) throws InvalidPacketException;
|
||||
protected abstract void handleConfirmAck(HandshakePacket packet) throws InvalidPacketException;
|
||||
|
||||
protected abstract int getKeyAgreementType();
|
||||
protected abstract HelloPacket getForeignHello();
|
||||
|
||||
protected byte[] getPublicKey() {
|
||||
switch (getKeyAgreementType()) {
|
||||
case KA_TYPE_EC25: return getPublicEC25Key();
|
||||
case KA_TYPE_DH3K: return getPublicDH3kKey();
|
||||
default: throw new AssertionError("Unknown KA type: " + getKeyAgreementType());
|
||||
}
|
||||
}
|
||||
|
||||
protected KeyPair getKeyPair() {
|
||||
switch (getKeyAgreementType()) {
|
||||
case KA_TYPE_EC25: return ec25KeyPair;
|
||||
case KA_TYPE_DH3K: return dh3kKeyPair;
|
||||
default: throw new AssertionError("Unknown KA type: " + getKeyAgreementType());
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getPublicDH3kKey() {
|
||||
byte[] temp = new byte[384];
|
||||
Conversions.bigIntegerToByteArray(temp, ((DHPublicKey)dh3kKeyPair.getPublic()).getY());
|
||||
return temp;
|
||||
}
|
||||
|
||||
private byte[] getPublicEC25Key() {
|
||||
ECPublicKey publicKey = (ECPublicKey)ec25KeyPair.getPublic();
|
||||
ECPoint q = publicKey.getQ();
|
||||
|
||||
byte[] x = new byte[32];
|
||||
byte[] y = new byte[32];
|
||||
|
||||
Conversions.bigIntegerToByteArray(x, q.getX().toBigInteger());
|
||||
Conversions.bigIntegerToByteArray(y, q.getY().toBigInteger());
|
||||
|
||||
return Conversions.combine(x, y);
|
||||
}
|
||||
|
||||
// protected RetainedSecrets getRetainedSecrets(String number, byte[] zid) {
|
||||
// RetainedSecretsDatabase database = DatabaseFactory.getRetainedSecretsDatabase(context);
|
||||
// return database.getRetainedSecrets(number, zid);
|
||||
// }
|
||||
|
||||
// protected void cacheRetainedSecret(String number, byte[] zid, byte[] rs1,
|
||||
// long expiration, boolean continuity)
|
||||
// {
|
||||
// RetainedSecretsDatabase database = DatabaseFactory.getRetainedSecretsDatabase(context);
|
||||
// database.setRetainedSecret(number, zid, rs1, expiration, continuity);
|
||||
// }
|
||||
|
||||
|
||||
// NOTE -- There was a bug in older versions of RedPhone in which the
|
||||
// Confirm message IVs were miscalculated. It didn't seem to be an
|
||||
// immediately exploitable problem, but was definitely wrong. Fixing it,
|
||||
// however, results in compatibility issues with devices that do not have
|
||||
// the fix. We're temporarily introducing a backwards compatibility setting
|
||||
// here, where we intentionally do the wrong thing for older devices. We'll
|
||||
// phase this out after a couple of months.
|
||||
protected boolean isLegacyConfirmConnection() {
|
||||
RedPhoneClientId clientId = new RedPhoneClientId(getForeignHello().getClientId());
|
||||
return clientId.isLegacyConfirmConnectionVersion();
|
||||
}
|
||||
|
||||
protected void setState(int state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
protected void sendFreshPacket(HandshakePacket packet) {
|
||||
retransmitCount = 0;
|
||||
retransmitInterval = RETRANSMIT_INTERVAL_MILLIS;
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
private void sendPacket(HandshakePacket packet) {
|
||||
transmitStartTime = System.currentTimeMillis();
|
||||
this.lastPacket = packet;
|
||||
|
||||
if (packet != null) {
|
||||
packet.setSequenceNumber(this.sequence++);
|
||||
try {
|
||||
socket.send(packet);
|
||||
} catch (IOException e) {
|
||||
Log.w("ZRTPSocket", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resendPacket() throws NegotiationFailedException {
|
||||
if (retransmitCount++ > MAX_RETRANSMIT_COUNT) {
|
||||
if (this.lastPacket != null) {
|
||||
throw new NegotiationFailedException("Retransmit threshold reached.");
|
||||
} else {
|
||||
throw new RecipientUnavailableException("Recipient unavailable.");
|
||||
}
|
||||
}
|
||||
|
||||
retransmitInterval = Math.min(retransmitInterval * 2, 1500);
|
||||
|
||||
sendPacket(lastPacket);
|
||||
}
|
||||
|
||||
private KeyPair initializeDH3kKeys() {
|
||||
try {
|
||||
KeyPairGenerator kg = KeyPairGenerator.getInstance("DH");
|
||||
DHParameterSpec dhSpec = new DHParameterSpec(PRIME, GENERATOR);
|
||||
kg.initialize(dhSpec);
|
||||
|
||||
return kg.generateKeyPair();
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private KeyPair initializeEC25Keys() {
|
||||
try {
|
||||
KeyPairGenerator kg = KeyPairGenerator.getInstance("ECDH", "SC");
|
||||
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
|
||||
kg.initialize(ecSpec);
|
||||
|
||||
return kg.generateKeyPair();
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
throw new AssertionError(nsae);
|
||||
} catch (NoSuchProviderException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRetransmitTime() {
|
||||
return (System.currentTimeMillis() - transmitStartTime >= retransmitInterval);
|
||||
}
|
||||
|
||||
private void resendPacketIfTimeout() throws NegotiationFailedException {
|
||||
if (isRetransmitTime()) {
|
||||
Log.w("ZRTPSocket", "Retransmitting after: " + retransmitInterval);
|
||||
resendPacket();
|
||||
}
|
||||
}
|
||||
|
||||
public MasterSecret getMasterSecret() {
|
||||
return this.masterSecret;
|
||||
}
|
||||
|
||||
public SASInfo getSasInfo() {
|
||||
// RetainedSecretsDatabase database = DatabaseFactory.getRetainedSecretsDatabase(context);
|
||||
String sasText = SASCalculator.calculateSAS(masterSecret.getSAS());
|
||||
// boolean sasVerified = database.isVerified(remoteNumber, getForeignHello().getZID());
|
||||
|
||||
return new SASInfo(sasText, false);
|
||||
}
|
||||
|
||||
// public void setSasVerified() {
|
||||
// DatabaseFactory.getRetainedSecretsDatabase(context).setVerified(remoteNumber, getForeignHello().getZID());
|
||||
// }
|
||||
|
||||
public void close() {
|
||||
state = TERMINATED;
|
||||
socket.close();
|
||||
}
|
||||
|
||||
public void negotiateStart() throws NegotiationFailedException {
|
||||
try {
|
||||
while (state == EXPECTING_HELLO) {
|
||||
HandshakePacket packet = socket.receiveHandshakePacket(true);
|
||||
|
||||
if (packet == null) {
|
||||
resendPacketIfTimeout();
|
||||
} else if (packet.getType().equals(HelloPacket.TYPE) && (state == EXPECTING_HELLO)) {
|
||||
handleHello(packet);
|
||||
} else if (isRetransmitTime()) {
|
||||
resendPacket();
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
Log.w("ZRTPSocket", ioe);
|
||||
if (state != TERMINATED)
|
||||
throw new NegotiationFailedException(ioe);
|
||||
} catch (InvalidPacketException ipe) {
|
||||
Log.w("ZRTPSocket", ipe);
|
||||
throw new NegotiationFailedException(ipe);
|
||||
}
|
||||
}
|
||||
|
||||
public void negotiateFinish() throws NegotiationFailedException {
|
||||
try {
|
||||
while (state != HANDSHAKE_COMPLETE && state != TERMINATED) {
|
||||
|
||||
HandshakePacket packet = socket.receiveHandshakePacket(state != EXPECTING_CONFIRM_ACK);
|
||||
|
||||
if( packet != null ) {
|
||||
Log.w("ZRTPSocket", "Received packet: " + (packet != null ? packet.getType() : "null"));
|
||||
}
|
||||
|
||||
if (packet == null) resendPacketIfTimeout();
|
||||
else if ((packet.getType().equals(HelloPacket.TYPE)) && (state == EXPECTING_HELLO)) handleHello(packet);
|
||||
else if ((packet.getType().equals(HelloAckPacket.TYPE)) && (state == EXPECTING_HELLO_ACK)) handleHelloAck(packet);
|
||||
else if ((packet.getType().equals(CommitPacket.TYPE)) && (state == EXPECTING_COMMIT)) handleCommit(packet);
|
||||
else if ((packet.getType().equals(DHPartOnePacket.TYPE)) && (state == EXPECTING_DH_1)) handleDH(packet);
|
||||
else if ((packet.getType().equals(DHPartTwoPacket.TYPE)) && (state == EXPECTING_DH_2)) handleDH(packet);
|
||||
else if ((packet.getType().equals(ConfirmOnePacket.TYPE)) && (state == EXPECTING_CONFIRM_ONE)) handleConfirmOne(packet);
|
||||
else if ((packet.getType().equals(ConfirmTwoPacket.TYPE)) && (state == EXPECTING_CONFIRM_TWO)) handleConfirmTwo(packet);
|
||||
else if ((packet.getType().equals(ConfAckPacket.TYPE)) && (state == EXPECTING_CONFIRM_ACK)) handleConfirmAck(packet);
|
||||
else if (isRetransmitTime()) resendPacket();
|
||||
}
|
||||
} catch (InvalidPacketException ipe) {
|
||||
Log.w("ZRTPSocket", ipe);
|
||||
throw new NegotiationFailedException(ipe);
|
||||
} catch (IOException ioe) {
|
||||
Log.w("ZRTPSocket", ioe);
|
||||
if (state != TERMINATED)
|
||||
throw new NegotiationFailedException(ioe);
|
||||
}
|
||||
|
||||
if (state != TERMINATED)
|
||||
this.socket.setTimeout(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp.retained;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* During the ZRTP handshake, both parties send 'rs1ID' and 'rs2ID' values.
|
||||
* These are hashed versions of the retained secrets ('rs1' and 'rs2') they
|
||||
* have cached for the (ZID, Phone Number) tuple they're communicating with.
|
||||
*
|
||||
* The 'rs1ID' and 'rs2ID' values are hashed differently for the initiator
|
||||
* and responder, such that there are actually four distinct values:
|
||||
*
|
||||
* 'rs1IDi', 'rs2IDi', 'rs1IDr', 'rs2IDr'
|
||||
*
|
||||
* This class determines whether both clients have a matching rs1 or rs2 value,
|
||||
* which is then used as 's1' during the 's0' master secret calculation.
|
||||
*
|
||||
* The matching is done by using the initiator's rs1 value if it matches the
|
||||
* responder's rs1 or rs2 values. Else, using the initiator's rs2 value if it
|
||||
* matches the responder's rs1 or rs2 values. Else, there is no match.
|
||||
*
|
||||
* https://tools.ietf.org/html/rfc6189#section-4.3
|
||||
*/
|
||||
|
||||
public class InitiatorRetainedSecretsCalculator extends RetainedSecretsCalculator {
|
||||
|
||||
private static final String ROLE = "Initiator";
|
||||
|
||||
public InitiatorRetainedSecretsCalculator(RetainedSecrets retainedSecrets) {
|
||||
super(ROLE, retainedSecrets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getS1(byte[] rs1IDr, byte[] rs2IDr) {
|
||||
ResponderRetainedSecretsCalculator calculator = new ResponderRetainedSecretsCalculator(retainedSecrets);
|
||||
RetainedSecretsDerivatives derivatives = calculator.getRetainedSecretsDerivatives();
|
||||
|
||||
byte[] rs1IDi = derivatives.getRetainedSecretOneDerivative();
|
||||
byte[] rs2IDi = derivatives.getRetainedSecretTwoDerivative();
|
||||
|
||||
if (rs1IDr != null && Arrays.equals(rs1IDi, rs1IDr)) return retainedSecrets.getRetainedSecretOne();
|
||||
if (rs2IDr != null && Arrays.equals(rs1IDi, rs2IDr)) return retainedSecrets.getRetainedSecretOne();
|
||||
if (rs1IDr != null && Arrays.equals(rs2IDi, rs1IDr)) return retainedSecrets.getRetainedSecretTwo();
|
||||
if (rs2IDr != null && Arrays.equals(rs2IDi, rs2IDr)) return retainedSecrets.getRetainedSecretTwo();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp.retained;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* During the ZRTP handshake, both parties send 'rs1ID' and 'rs2ID' values.
|
||||
* These are hashed versions of the retained secrets ('rs1' and 'rs2') they
|
||||
* have cached for the (ZID, Phone Number) tuple they're communicating with.
|
||||
*
|
||||
* The 'rs1ID' and 'rs2ID' values are hashed differently for the initiator
|
||||
* and responder, such that there are actually four distinct values:
|
||||
*
|
||||
* 'rs1IDi', 'rs2IDi', 'rs1IDr', 'rs2IDr'
|
||||
*
|
||||
* This class determines whether both clients have a matching rs1 or rs2 value,
|
||||
* which is then used as 's1' during the 's0' master secret calculation.
|
||||
*
|
||||
* The matching is done by using the initiator's rs1 value if it matches the
|
||||
* responder's rs1 or rs2 values. Else, using the initiator's rs2 value if it
|
||||
* matches the responder's rs1 or rs2 values. Else, there is no match.
|
||||
*
|
||||
* https://tools.ietf.org/html/rfc6189#section-4.3
|
||||
*/
|
||||
|
||||
public class ResponderRetainedSecretsCalculator extends RetainedSecretsCalculator {
|
||||
|
||||
private static final String ROLE = "Responder";
|
||||
|
||||
public ResponderRetainedSecretsCalculator(RetainedSecrets retainedSecrets) {
|
||||
super(ROLE, retainedSecrets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getS1(byte[] rs1IDi, byte[] rs2IDi) {
|
||||
InitiatorRetainedSecretsCalculator calculator = new InitiatorRetainedSecretsCalculator(retainedSecrets);
|
||||
RetainedSecretsDerivatives derivatives = calculator.getRetainedSecretsDerivatives();
|
||||
|
||||
byte[] rs1IDr = derivatives.getRetainedSecretOneDerivative();
|
||||
byte[] rs2IDr = derivatives.getRetainedSecretTwoDerivative();
|
||||
|
||||
if (rs1IDr != null && Arrays.equals(rs1IDi, rs1IDr)) return retainedSecrets.getRetainedSecretOne();
|
||||
if (rs2IDr != null && Arrays.equals(rs1IDi, rs2IDr)) return retainedSecrets.getRetainedSecretTwo();
|
||||
if (rs1IDr != null && Arrays.equals(rs2IDi, rs1IDr)) return retainedSecrets.getRetainedSecretOne();
|
||||
if (rs2IDr != null && Arrays.equals(rs2IDi, rs2IDr)) return retainedSecrets.getRetainedSecretTwo();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.thoughtcrime.redphone.crypto.zrtp.retained;
|
||||
|
||||
public class RetainedSecrets {
|
||||
|
||||
private final byte[] rs1;
|
||||
private final byte[] rs2;
|
||||
|
||||
public RetainedSecrets(byte[] rs1, byte[] rs2) {
|
||||
this.rs1 = rs1;
|
||||
this.rs2 = rs2;
|
||||
}
|
||||
|
||||
public byte[] getRetainedSecretOne() {
|
||||
return rs1;
|
||||
}
|
||||
|
||||
public byte[] getRetainedSecretTwo() {
|
||||
return rs2;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.crypto.zrtp.retained;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* This class is responsible for calculating the retained secret
|
||||
* derivatives (rs1IDi, rs2IDi, rs1IDr, rs2IDr) that are transmitted
|
||||
* with each client's DH message.
|
||||
*
|
||||
* These derivatives are subsequently compared to determine whether
|
||||
* the clients have a matching retained secret (rs1 or rs2 value).
|
||||
*
|
||||
* https://tools.ietf.org/html/rfc6189#section-4.3.1
|
||||
*/
|
||||
|
||||
public abstract class RetainedSecretsCalculator {
|
||||
|
||||
protected final RetainedSecrets retainedSecrets;
|
||||
protected final RetainedSecretsDerivatives retainedSecretsDerivatives;
|
||||
|
||||
public RetainedSecretsCalculator(String role, RetainedSecrets retainedSecrets) {
|
||||
this.retainedSecrets = retainedSecrets;
|
||||
this.retainedSecretsDerivatives = calculateDerivatives(role, retainedSecrets);
|
||||
}
|
||||
|
||||
public RetainedSecretsDerivatives getRetainedSecretsDerivatives() {
|
||||
return retainedSecretsDerivatives;
|
||||
}
|
||||
|
||||
private RetainedSecretsDerivatives calculateDerivatives(String role, RetainedSecrets retainedSecrets) {
|
||||
byte[] rs1 = retainedSecrets.getRetainedSecretOne();
|
||||
byte[] rs2 = retainedSecrets.getRetainedSecretTwo();
|
||||
|
||||
byte[] rs1ID = null;
|
||||
byte[] rs2ID = null;
|
||||
|
||||
if (rs1 != null) rs1ID = calculateDerivative(role, rs1);
|
||||
if (rs2 != null) rs2ID = calculateDerivative(role, rs2);
|
||||
|
||||
return new RetainedSecretsDerivatives(rs1ID, rs2ID);
|
||||
}
|
||||
|
||||
private byte[] calculateDerivative(String role, byte[] secret) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(secret, "HmacSHA256"));
|
||||
|
||||
byte[] derivative = mac.doFinal(role.getBytes("UTF-8"));
|
||||
byte[] truncated = new byte[8];
|
||||
|
||||
System.arraycopy(derivative, 0, truncated, 0, truncated.length);
|
||||
|
||||
return truncated;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasContinuity(byte[] receivedRs1ID, byte[] receivedRs2ID) {
|
||||
return getS1(receivedRs1ID, receivedRs2ID) != null;
|
||||
}
|
||||
|
||||
public abstract byte[] getS1(byte[] receivedRs1ID, byte[] receivedRs2ID);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package org.thoughtcrime.redphone.crypto.zrtp.retained;
|
||||
|
||||
public class RetainedSecretsDerivatives {
|
||||
private final byte[] rs1ID;
|
||||
private final byte[] rs2ID;
|
||||
|
||||
public RetainedSecretsDerivatives(byte[] rs1ID, byte[] rs2ID) {
|
||||
this.rs1ID = rs1ID;
|
||||
this.rs2ID = rs2ID;
|
||||
}
|
||||
|
||||
public byte[] getRetainedSecretOneDerivative() {
|
||||
return rs1ID;
|
||||
}
|
||||
|
||||
public byte[] getRetainedSecretTwoDerivative() {
|
||||
return rs2ID;
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.network;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* A multi-connect utility class. Given a list of addresses, connect
|
||||
* to all of them simultaneously, and return the connection that completes
|
||||
* first. Used as a client-centric form of server failover and discovery
|
||||
* of low-latency paths.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class LowLatencySocketConnector {
|
||||
|
||||
private static final String TAG = LowLatencySocketConnector.class.getSimpleName();
|
||||
|
||||
private static final int CONNECT_TIMEOUT_MILLIS = 10000;
|
||||
|
||||
public static Socket connect(InetAddress[] addresses, int port) throws IOException {
|
||||
Selector selector = Selector.open();
|
||||
SocketChannel[] channels = constructSocketChannels(selector, addresses.length);
|
||||
InetSocketAddress[] socketAddresses = constructSocketAddresses(addresses, port);
|
||||
|
||||
assert(channels.length == socketAddresses.length);
|
||||
|
||||
connectChannels(channels, socketAddresses);
|
||||
|
||||
return waitForFirstChannel(selector);
|
||||
}
|
||||
|
||||
private static Socket waitForFirstChannel(Selector selector) throws IOException {
|
||||
while (hasValidKeys(selector)) {
|
||||
int readyCount = selector.select(CONNECT_TIMEOUT_MILLIS);
|
||||
|
||||
if (readyCount == 0)
|
||||
throw new IOException("Connect timed out!");
|
||||
|
||||
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
SelectionKey key = iterator.next();
|
||||
iterator.remove();
|
||||
|
||||
if (!key.isValid()) continue;
|
||||
|
||||
SocketChannel channel = (SocketChannel)key.channel();
|
||||
boolean connected = isChannelConnected(channel);
|
||||
|
||||
if (connected) {
|
||||
closeAllButChannel(selector, channel);
|
||||
return channel.socket();
|
||||
} else {
|
||||
key.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException("All connections failed!");
|
||||
}
|
||||
|
||||
private static void closeAllButChannel(Selector selector, SocketChannel channel) {
|
||||
for (SelectionKey key : selector.keys()) {
|
||||
if (key.channel() != channel) {
|
||||
try {
|
||||
key.channel().close();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasValidKeys(Selector selector) {
|
||||
for (SelectionKey key : selector.keys())
|
||||
if (key.isValid())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isChannelConnected(SocketChannel channel) {
|
||||
try {
|
||||
return channel.finishConnect();
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void connectChannels(SocketChannel[] channels, InetSocketAddress[] addresses)
|
||||
throws IOException
|
||||
{
|
||||
for (int i=0;i<channels.length;i++) {
|
||||
channels[i].connect(addresses[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static InetSocketAddress[] constructSocketAddresses(InetAddress[] addresses, int port) {
|
||||
InetSocketAddress[] socketAddresses = new InetSocketAddress[addresses.length];
|
||||
|
||||
for (int i=0;i<socketAddresses.length;i++) {
|
||||
socketAddresses[i] = new InetSocketAddress(addresses[i], port);
|
||||
}
|
||||
|
||||
return socketAddresses;
|
||||
}
|
||||
|
||||
private static SocketChannel[] constructSocketChannels(Selector selector, int count)
|
||||
throws IOException
|
||||
{
|
||||
SocketChannel[] channels = new SocketChannel[count];
|
||||
|
||||
for (int i=0;i<channels.length;i++) {
|
||||
channels[i] = SocketChannel.open();
|
||||
channels[i].configureBlocking(false);
|
||||
channels[i].register(selector, SelectionKey.OP_CONNECT);
|
||||
}
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.network;
|
||||
|
||||
import org.thoughtcrime.redphone.util.Conversions;
|
||||
|
||||
/**
|
||||
* This class builds an RTP packet
|
||||
* reference: rfc3550
|
||||
*
|
||||
* warning: CSRC's are not supported CSRC count _must_ be zero
|
||||
*
|
||||
* @author Stuart O. Anderson
|
||||
*/
|
||||
public class RtpPacket {
|
||||
|
||||
private static final int PACKET_VERSION = 2;
|
||||
protected static final int HEADER_LENGTH = 12;
|
||||
|
||||
protected byte data[];
|
||||
protected int packetLength;
|
||||
|
||||
public RtpPacket( int payloadLength ) {
|
||||
packetLength = payloadLength + HEADER_LENGTH;
|
||||
data = new byte[packetLength];
|
||||
}
|
||||
|
||||
public RtpPacket(byte[] data, int packetLength) {
|
||||
this.data = data;
|
||||
this.packetLength = packetLength;
|
||||
}
|
||||
|
||||
public RtpPacket(byte[] data, int packetLength, boolean deepCopy) {
|
||||
this.data = new byte[packetLength];
|
||||
this.packetLength = packetLength;
|
||||
System.arraycopy(data, 0, this.data, 0, packetLength);
|
||||
}
|
||||
|
||||
public void setVersion(){
|
||||
data[0] = (byte) ((data[0] & 0x3F) | ((PACKET_VERSION & 0x03) << 6 ));
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return ((data[0] & 0xC0) >> 6);
|
||||
}
|
||||
|
||||
public void setPadding( boolean bit ) {
|
||||
data[0] = Conversions.setBit( data[0], 5, bit );
|
||||
}
|
||||
|
||||
public boolean getPadding() {
|
||||
return Conversions.getBit( data[0], 5 );
|
||||
}
|
||||
|
||||
public void setExtension( boolean bit ) {
|
||||
data[0] = Conversions.setBit( data[0], 4, bit );
|
||||
}
|
||||
public boolean getExtension() {
|
||||
return Conversions.getBit( data[0], 4 );
|
||||
}
|
||||
|
||||
public void setCSRCCount( int count ) {
|
||||
data[0] = (byte) ((data[0] & 0xF0) | ((count & 0x0F)));
|
||||
}
|
||||
|
||||
public int getCSRCCount( ) {
|
||||
return ( data[0] & 0x0F );
|
||||
}
|
||||
|
||||
public void setMarkerBit( boolean bit ){
|
||||
data[1] = Conversions.setBit( data[1], 7, bit );
|
||||
}
|
||||
|
||||
public boolean getMarkerBit( ) {
|
||||
return Conversions.getBit( data[1], 7 );
|
||||
}
|
||||
|
||||
public void setPayloadType( int type ){
|
||||
data[1] = (byte) ((data[1] & 0x80) | (type & 0x7F));
|
||||
}
|
||||
|
||||
public int getPayloadType() {
|
||||
return (data[1] & 0x7F);
|
||||
}
|
||||
|
||||
public void setSequenceNumber( int seqNum ){
|
||||
Conversions.shortToByteArray(data, 2, seqNum);
|
||||
}
|
||||
|
||||
public int getSequenceNumber( ) {
|
||||
return Conversions.byteArrayToShort(data, 2);
|
||||
}
|
||||
|
||||
public void setTimeStamp( long timestamp ) {
|
||||
Conversions.longTo4ByteArray(data, 4, timestamp);
|
||||
}
|
||||
|
||||
public long getTimeStamp( ) {
|
||||
return Conversions.byteArray4ToLong( data, 4 );
|
||||
}
|
||||
|
||||
public void setSSRC( long ssrc ) {
|
||||
Conversions.longTo4ByteArray(data, 8, ssrc);
|
||||
}
|
||||
|
||||
public long getSSRC( ) {
|
||||
return Conversions.byteArray4ToLong( data, 8);
|
||||
}
|
||||
|
||||
//not supported for now
|
||||
public void addCSRC( long csrc ) {
|
||||
}
|
||||
|
||||
public void setPayload(byte[] payload) {
|
||||
setPayload(payload, payload.length);
|
||||
}
|
||||
|
||||
public void setPayload( byte [] payload, int len ){
|
||||
System.arraycopy(payload, 0, data, HEADER_LENGTH, len);
|
||||
packetLength = len + HEADER_LENGTH;
|
||||
}
|
||||
|
||||
public byte[] getPayload(){
|
||||
int payloadLen = packetLength - HEADER_LENGTH;
|
||||
byte[] result = new byte[payloadLen];
|
||||
|
||||
System.arraycopy(data, HEADER_LENGTH, result, 0, payloadLen);
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] getPacket() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public int getPacketLength() {
|
||||
return packetLength;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.network;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
/**
|
||||
* RtpSocket wraps a {@link DatagramSocket}, allowing {@link RtpPacket}s to be sent a received.
|
||||
*
|
||||
* @author Stuart O. Anderson
|
||||
*/
|
||||
public class RtpSocket {
|
||||
|
||||
private static final String TAG = RtpSocket.class.getSimpleName();
|
||||
|
||||
private final byte [] buf = new byte[4096];
|
||||
|
||||
private final String remoteIp;
|
||||
private final int remotePort;
|
||||
|
||||
private final DatagramSocket socket;
|
||||
|
||||
public RtpSocket(int localPort, InetSocketAddress remoteAddress) throws SocketException {
|
||||
this.socket = new DatagramSocket(localPort);
|
||||
this.remoteIp = remoteAddress.getAddress().getHostAddress();
|
||||
this.remotePort = remoteAddress.getPort();
|
||||
|
||||
socket.connect(new InetSocketAddress(remoteIp, remotePort));
|
||||
Log.d(TAG, "Connected to: " + remoteIp);
|
||||
}
|
||||
|
||||
public String getRemoteIp() {
|
||||
return remoteIp;
|
||||
}
|
||||
|
||||
public int getRemotePort() {
|
||||
return remotePort;
|
||||
}
|
||||
|
||||
public DatagramSocket getDatagramSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
public void setTimeout(int timeoutMillis) {
|
||||
try {
|
||||
socket.setSoTimeout(timeoutMillis);
|
||||
} catch (SocketException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void send(RtpPacket outPacket) throws IOException {
|
||||
try {
|
||||
socket.send(new DatagramPacket(outPacket.getPacket(), outPacket.getPacketLength()));
|
||||
} catch (IOException e) {
|
||||
if (!socket.isClosed()) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RtpPacket receive() throws IOException {
|
||||
try {
|
||||
DatagramPacket dataPack = new DatagramPacket(buf, buf.length);
|
||||
socket.receive(dataPack);
|
||||
return new RtpPacket(dataPack.getData(), dataPack.getLength());
|
||||
} catch( SocketTimeoutException e ) {
|
||||
//Do Nothing.
|
||||
} catch (IOException e) {
|
||||
if (!socket.isClosed()) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
/**
|
||||
* An exception that marks a login failure.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class LoginFailedException extends Exception {
|
||||
|
||||
public LoginFailedException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public LoginFailedException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public LoginFailedException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public LoginFailedException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.redphone.signaling.signals.OpenPortSignal;
|
||||
import org.thoughtcrime.redphone.signaling.signals.Signal;
|
||||
import org.thoughtcrime.redphone.util.LineReader;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Responsible for getting the UDP connection flow started.
|
||||
*
|
||||
* We start by sending a "hello signal" to this call's remote UDP port,
|
||||
* which the server responds to. This should effectively punch open a
|
||||
* bidirectional UDP flow in any potential NAT devices.
|
||||
*
|
||||
* @author Moxie Marlinspke
|
||||
*
|
||||
*/
|
||||
|
||||
public class NetworkConnector {
|
||||
|
||||
private static final String TAG = NetworkConnector.class.getSimpleName();
|
||||
|
||||
private DatagramSocket socket;
|
||||
private final long sessionId;
|
||||
private final String server;
|
||||
private final int port;
|
||||
|
||||
public NetworkConnector(long sessionId, String server, int port) {
|
||||
Log.w(TAG, "Opening up port: " + server + " , " + port);
|
||||
this.sessionId = sessionId;
|
||||
this.server = server;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public int makeConnection() throws SessionInitiationFailureException {
|
||||
int result = -1;
|
||||
int timeout = 1000;
|
||||
for (int attempts = 0; attempts < 5; attempts++) {
|
||||
Log.d(TAG, "attempting connection");
|
||||
result = attemptConnection( timeout );
|
||||
if (result != -1)
|
||||
break;
|
||||
timeout *= 2;
|
||||
if( timeout > 10000 ) timeout = 10000;
|
||||
}
|
||||
|
||||
if (result == -1)
|
||||
throw new SessionInitiationFailureException("Could not connect to server.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int attemptConnection( int timeout ) {
|
||||
try {
|
||||
socket = new DatagramSocket();
|
||||
socket.connect(new InetSocketAddress(server, port));
|
||||
socket.setSoTimeout(timeout);
|
||||
sendSignal(new OpenPortSignal(sessionId));
|
||||
|
||||
SignalResponse response = readSignalResponse();
|
||||
|
||||
if (response.getStatusCode() != 200) {
|
||||
Log.e(TAG, "Bad response from server.");
|
||||
socket.close();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int localPort = socket.getLocalPort();
|
||||
socket.close();
|
||||
return localPort;
|
||||
|
||||
} catch (IOException | SignalingException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void sendSignal(Signal signal) throws IOException {
|
||||
byte[] signalBytes = signal.serialize().getBytes();
|
||||
DatagramPacket packet = new DatagramPacket(signalBytes, signalBytes.length);
|
||||
socket.send(packet);
|
||||
}
|
||||
|
||||
private SignalResponse readSignalResponse() throws SignalingException, IOException {
|
||||
byte[] responseBuffer = new byte[2048];
|
||||
DatagramPacket response = new DatagramPacket(responseBuffer, responseBuffer.length);
|
||||
|
||||
socket.receive(response);
|
||||
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(responseBuffer);
|
||||
LineReader lineReader = new LineReader(bais);
|
||||
SignalResponseReader responseReader = new SignalResponseReader(lineReader);
|
||||
|
||||
int statusCode = responseReader.readSignalResponseCode();
|
||||
Map<String, String> headers = responseReader.readSignalHeaders();
|
||||
byte[] body = responseReader.readSignalBody(headers);
|
||||
|
||||
return new SignalResponse(statusCode, headers, body);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
public class NoSuchUserException extends Exception {
|
||||
|
||||
public NoSuchUserException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NoSuchUserException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public NoSuchUserException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public NoSuchUserException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
|
||||
/**
|
||||
* The authentication mechanism uses HOTP, which requires
|
||||
* the client to keep track of a monotonically increasing counter.
|
||||
* Using this provider guarantees that the counter is incremented once
|
||||
* for each use.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class OtpCounterProvider {
|
||||
|
||||
private static final OtpCounterProvider provider = new OtpCounterProvider();
|
||||
|
||||
public static OtpCounterProvider getInstance() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public synchronized long getOtpCounter(Context context) {
|
||||
return 1;
|
||||
// SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
// long counter = preferences.getLong(Constants.PASSWORD_COUNTER_PREFERENCE, 1L);
|
||||
//
|
||||
// preferences.edit().putLong(Constants.PASSWORD_COUNTER_PREFERENCE, counter+1).commit();
|
||||
//
|
||||
// return counter;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class RedPhoneAccountAttributes {
|
||||
|
||||
@JsonProperty
|
||||
private String signalingKey;
|
||||
|
||||
@JsonProperty
|
||||
private String gcmRegistrationId;
|
||||
|
||||
@JsonProperty
|
||||
private boolean textsecure;
|
||||
|
||||
public RedPhoneAccountAttributes() {}
|
||||
|
||||
public RedPhoneAccountAttributes(String signalingKey, String gcmRegistrationId) {
|
||||
this.signalingKey = signalingKey;
|
||||
this.gcmRegistrationId = gcmRegistrationId;
|
||||
this.textsecure = true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.push.TrustStore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class RedPhoneAccountManager {
|
||||
|
||||
private final OkHttpClient client;
|
||||
private final String baseUrl;
|
||||
private final String login;
|
||||
private final String password;
|
||||
|
||||
public RedPhoneAccountManager(String baseUrl, TrustStore trustStore, String login, String password) {
|
||||
try {
|
||||
TrustManager[] trustManagers = getTrustManager(trustStore);
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
context.init(null, trustManagers, null);
|
||||
|
||||
this.baseUrl = baseUrl;
|
||||
this.login = login;
|
||||
this.password = password;
|
||||
this.client = new OkHttpClient.Builder()
|
||||
.sslSocketFactory(context.getSocketFactory(), (X509TrustManager)trustManagers[0])
|
||||
.build();
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setGcmId(Optional<String> gcmId) throws IOException {
|
||||
Request.Builder builder = new Request.Builder();
|
||||
builder.url(baseUrl + "/api/v1/accounts/gcm/");
|
||||
builder.header("Authorization", "Basic " + Base64.encodeBytes((login + ":" + password).getBytes()));
|
||||
|
||||
if (gcmId.isPresent()) {
|
||||
String body = JsonUtils.toJson(new RedPhoneGcmId(gcmId.get()));
|
||||
builder.put(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), body));
|
||||
} else {
|
||||
builder.delete();
|
||||
}
|
||||
|
||||
Response response = client.newCall(builder.build()).execute();
|
||||
|
||||
if (response.code() == 401 || response.code() == 403) {
|
||||
throw new UnauthorizedException("Failed to perform GCM operation: " + response.code());
|
||||
}
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
throw new IOException("Failed to perform GCM operation: " + response.code());
|
||||
}
|
||||
}
|
||||
|
||||
public void createAccount(String verificationToken, RedPhoneAccountAttributes attributes) throws IOException {
|
||||
String body = JsonUtils.toJson(attributes);
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(baseUrl + "/api/v1/accounts/token/" + verificationToken)
|
||||
.put(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), body))
|
||||
.header("Authorization", "Basic " + Base64.encodeBytes((login + ":" + password).getBytes()))
|
||||
.build();
|
||||
|
||||
Response response = client.newCall(request).execute();
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
throw new IOException("Failed to create account: " + response.code());
|
||||
}
|
||||
}
|
||||
|
||||
public TrustManager[] getTrustManager(TrustStore trustStore) {
|
||||
try {
|
||||
InputStream keyStoreInputStream = trustStore.getKeyStoreInputStream();
|
||||
KeyStore keyStore = KeyStore.getInstance("BKS");
|
||||
|
||||
keyStore.load(keyStoreInputStream, trustStore.getKeyStorePassword().toCharArray());
|
||||
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
|
||||
trustManagerFactory.init(keyStore);
|
||||
|
||||
return trustManagerFactory.getTrustManagers();
|
||||
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class RedPhoneGcmId {
|
||||
|
||||
@JsonProperty
|
||||
private String gcmRegistrationId;
|
||||
|
||||
public RedPhoneGcmId() {}
|
||||
|
||||
public RedPhoneGcmId(String gcmRegistrationId) {
|
||||
this.gcmRegistrationId = gcmRegistrationId;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.whispersystems.signalservice.api.push.TrustStore;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class RedPhoneTrustStore implements TrustStore {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public RedPhoneTrustStore(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getKeyStoreInputStream() {
|
||||
return context.getResources().openRawResource(R.raw.redphone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeyStorePassword() {
|
||||
return "whisper";
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
public class ServerMessageException extends Exception {
|
||||
|
||||
public ServerMessageException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ServerMessageException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public ServerMessageException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public ServerMessageException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
|
||||
/**
|
||||
* A helper tuple that encapsulates both a call's session ID
|
||||
* and remote UDP port.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class SessionDescriptor implements Parcelable {
|
||||
|
||||
public int relayPort;
|
||||
public long sessionId;
|
||||
public String serverName;
|
||||
public int version;
|
||||
|
||||
public SessionDescriptor() {}
|
||||
|
||||
public SessionDescriptor(String serverName, int relayPort, long sessionId, int version) {
|
||||
this.serverName = serverName;
|
||||
this.relayPort = relayPort;
|
||||
this.sessionId = sessionId;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public SessionDescriptor(Parcel in) {
|
||||
this.relayPort = in.readInt();
|
||||
this.sessionId = in.readLong();
|
||||
this.serverName = in.readString();
|
||||
this.version = in.readInt();
|
||||
}
|
||||
|
||||
public String getFullServerName() {
|
||||
return serverName + BuildConfig.REDPHONE_PREFIX_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) return false;
|
||||
if (!(other instanceof SessionDescriptor)) return false;
|
||||
|
||||
SessionDescriptor that = (SessionDescriptor)other;
|
||||
|
||||
return this.relayPort == that.relayPort &&
|
||||
this.sessionId == that.sessionId &&
|
||||
this.serverName.equals(that.serverName) &&
|
||||
this.version == that.version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.relayPort ^ ((int)this.sessionId) ^ this.serverName.hashCode() ^ this.version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(relayPort);
|
||||
dest.writeLong(sessionId);
|
||||
dest.writeString(serverName);
|
||||
dest.writeInt(version);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<SessionDescriptor> CREATOR =
|
||||
new Parcelable.Creator<SessionDescriptor>()
|
||||
{
|
||||
public SessionDescriptor createFromParcel(Parcel in) {
|
||||
return new SessionDescriptor(in);
|
||||
}
|
||||
|
||||
public SessionDescriptor[] newArray(int size) {
|
||||
return new SessionDescriptor[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
public class SessionInitiationFailureException extends Exception {
|
||||
|
||||
public SessionInitiationFailureException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SessionInitiationFailureException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public SessionInitiationFailureException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public SessionInitiationFailureException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
public class SessionStaleException extends Exception {
|
||||
|
||||
public SessionStaleException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SessionStaleException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public SessionStaleException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public SessionStaleException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.redphone.util.LineReader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A helper class that reads signal bytes off the wire.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class SignalReader {
|
||||
|
||||
protected final LineReader lineReader;
|
||||
|
||||
public SignalReader(LineReader lineReader) {
|
||||
this.lineReader = lineReader;
|
||||
}
|
||||
|
||||
public String[] readSignalRequest() throws SignalingException, IOException {
|
||||
String requestLine = lineReader.readLine();
|
||||
|
||||
if (requestLine == null || requestLine.length() == 0)
|
||||
throw new SignalingException("Server failure.");
|
||||
|
||||
String[] request = requestLine.split(" ");
|
||||
|
||||
if (request == null || request.length != 3)
|
||||
throw new SignalingException("Got strange request: " + requestLine);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
public Map<String, String> readSignalHeaders() throws IOException {
|
||||
Map<String, String> headers = new HashMap<String, String>();
|
||||
String header;
|
||||
|
||||
while ((header = lineReader.readLine()).length() != 0) {
|
||||
String[] split = header.split(":");
|
||||
|
||||
if (split == null || split.length != 2)
|
||||
continue;
|
||||
|
||||
headers.put(split[0].trim(), split[1].trim());
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
public byte[] readSignalBody(Map<String, String> headers) throws SignalingException, IOException {
|
||||
if (headers.containsKey("Content-Length")) {
|
||||
try {
|
||||
String contentLengthString = headers.get("Content-Length");
|
||||
int contentLength = Integer.parseInt(contentLengthString);
|
||||
|
||||
if (contentLength != 0) {
|
||||
return lineReader.readFully(contentLength);
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.w("SignalingSocket", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A helper class that encapsulates the status, headers, and body
|
||||
* of a signal response.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class SignalResponse {
|
||||
|
||||
private final int statusCode;
|
||||
private final Map<String, String> headers;
|
||||
private final byte[] body;
|
||||
|
||||
public SignalResponse(int statusCode, Map<String, String> headers, byte[] body) {
|
||||
this.statusCode = statusCode;
|
||||
this.headers = headers;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
import org.thoughtcrime.redphone.util.LineReader;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A helper class that reads signal response bytes off the wire.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class SignalResponseReader extends SignalReader {
|
||||
|
||||
public SignalResponseReader(LineReader lineReader) {
|
||||
super(lineReader);
|
||||
}
|
||||
|
||||
public int readSignalResponseCode() throws SignalingException, IOException {
|
||||
String responseLine = lineReader.readLine();
|
||||
|
||||
if (responseLine == null || responseLine.length() == 0)
|
||||
throw new SignalingException("Failed to read response.");
|
||||
|
||||
String[] responseLineParts = responseLine.split(" ", 3);
|
||||
|
||||
if (responseLineParts == null || responseLineParts.length != 3)
|
||||
throw new SignalingException("Failed to parse response line: " + responseLine);
|
||||
|
||||
try {
|
||||
return Integer.parseInt(responseLineParts[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new SignalingException("Failed to parse status code from: " + responseLine);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
public class SignalingException extends Exception {
|
||||
|
||||
public SignalingException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SignalingException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public SignalingException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public SignalingException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.thoughtcrime.redphone.network.LowLatencySocketConnector;
|
||||
import org.thoughtcrime.redphone.signaling.signals.BusySignal;
|
||||
import org.thoughtcrime.redphone.signaling.signals.HangupSignal;
|
||||
import org.thoughtcrime.redphone.signaling.signals.InitiateSignal;
|
||||
import org.thoughtcrime.redphone.signaling.signals.RingingSignal;
|
||||
import org.thoughtcrime.redphone.signaling.signals.ServerSignal;
|
||||
import org.thoughtcrime.redphone.signaling.signals.Signal;
|
||||
import org.thoughtcrime.redphone.util.LineReader;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.whispersystems.signalservice.api.push.TrustStore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
/**
|
||||
* A socket that speaks the signaling protocol with a whisperswitch.
|
||||
*
|
||||
* The signaling protocol is very similar to a RESTful HTTP API, where every
|
||||
* request yields a corresponding response, and authorization is done through
|
||||
* an Authorization header.
|
||||
*
|
||||
* Like SIP, however, both endpoints are simultaneously server and client, issuing
|
||||
* requests and responses to each-other.
|
||||
*
|
||||
* Connections are persistent, and the signaling connection
|
||||
* for any ongoing call must remain open, otherwise the call will drop.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class SignalingSocket {
|
||||
|
||||
private static final String TAG = SignalingSocket.class.getSimpleName();
|
||||
|
||||
protected static final int PROTOCOL_VERSION = 1;
|
||||
|
||||
private final Context context;
|
||||
private final Socket socket;
|
||||
|
||||
protected final LineReader lineReader;
|
||||
protected final OutputStream outputStream;
|
||||
protected final String localNumber;
|
||||
protected final String password;
|
||||
protected final OtpCounterProvider counterProvider;
|
||||
|
||||
private boolean connectionAttemptComplete;
|
||||
|
||||
public SignalingSocket(Context context, String host, int port,
|
||||
String localNumber, String password,
|
||||
OtpCounterProvider counterProvider)
|
||||
throws SignalingException
|
||||
{
|
||||
try {
|
||||
this.context = context.getApplicationContext();
|
||||
this.connectionAttemptComplete = false;
|
||||
this.socket = constructSSLSocket(context, host, port);
|
||||
this.outputStream = this.socket.getOutputStream();
|
||||
this.lineReader = new LineReader(socket.getInputStream());
|
||||
this.localNumber = localNumber;
|
||||
this.password = password;
|
||||
this.counterProvider = counterProvider;
|
||||
} catch (IOException ioe) {
|
||||
throw new SignalingException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private Socket constructSSLSocket(Context context, String host, int port)
|
||||
throws SignalingException
|
||||
{
|
||||
try {
|
||||
TrustManager[] trustManagers = getTrustManager(new RedPhoneTrustStore(context));
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, trustManagers, null);
|
||||
|
||||
return timeoutHackConnect(sslContext.getSocketFactory(), host, port);
|
||||
} catch (IOException ioe) {
|
||||
throw new SignalingException(ioe);
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Socket timeoutHackConnect(SSLSocketFactory sslSocketFactory, String host, int port)
|
||||
throws IOException
|
||||
{
|
||||
InetAddress[] addresses = InetAddress.getAllByName(host);
|
||||
Socket stagedSocket = LowLatencySocketConnector.connect(addresses, port);
|
||||
|
||||
Log.w(TAG, "Connected to: " + stagedSocket.getInetAddress().getHostAddress());
|
||||
|
||||
SocketConnectMonitor monitor = new SocketConnectMonitor(stagedSocket);
|
||||
|
||||
monitor.start();
|
||||
|
||||
Socket result = sslSocketFactory.createSocket(stagedSocket, host, port, true);
|
||||
|
||||
synchronized (this) {
|
||||
this.connectionAttemptComplete = true;
|
||||
notify();
|
||||
|
||||
if (result.isConnected()) return result;
|
||||
else throw new IOException("Socket timed out before " +
|
||||
"connection completed.");
|
||||
}
|
||||
}
|
||||
|
||||
public TrustManager[] getTrustManager(TrustStore trustStore) {
|
||||
try {
|
||||
InputStream keyStoreInputStream = trustStore.getKeyStoreInputStream();
|
||||
KeyStore keyStore = KeyStore.getInstance("BKS");
|
||||
|
||||
keyStore.load(keyStoreInputStream, trustStore.getKeyStorePassword().toCharArray());
|
||||
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
|
||||
trustManagerFactory.init(keyStore);
|
||||
|
||||
return trustManagerFactory.getTrustManagers();
|
||||
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
this.outputStream.close();
|
||||
this.socket.getInputStream().close();
|
||||
this.socket.close();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
public SessionDescriptor initiateConnection(String remoteNumber)
|
||||
throws ServerMessageException, SignalingException,
|
||||
NoSuchUserException, LoginFailedException
|
||||
{
|
||||
sendSignal(new InitiateSignal(localNumber, password,
|
||||
counterProvider.getOtpCounter(context),
|
||||
remoteNumber));
|
||||
|
||||
SignalResponse response = readSignalResponse();
|
||||
|
||||
try {
|
||||
switch (response.getStatusCode()) {
|
||||
case 404: throw new NoSuchUserException("No such redphone user.");
|
||||
case 402: throw new ServerMessageException(new String(response.getBody()));
|
||||
case 401: throw new LoginFailedException("Initiate threw 401");
|
||||
case 200: return JsonUtils.fromJson(response.getBody(), SessionDescriptor.class);
|
||||
default: throw new SignalingException("Unknown response: " + response.getStatusCode());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new SignalingException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setRinging(long sessionId)
|
||||
throws SignalingException, SessionStaleException, LoginFailedException
|
||||
{
|
||||
sendSignal(new RingingSignal(localNumber, password,
|
||||
counterProvider.getOtpCounter(context),
|
||||
sessionId));
|
||||
|
||||
SignalResponse response = readSignalResponse();
|
||||
|
||||
switch (response.getStatusCode()) {
|
||||
case 404: throw new SessionStaleException("No such session: " + sessionId);
|
||||
case 401: throw new LoginFailedException("Ringing threw 401");
|
||||
case 200: return;
|
||||
default: throw new SignalingException("Unknown response: " + response.getStatusCode());
|
||||
}
|
||||
}
|
||||
|
||||
public void setHangup(long sessionId) {
|
||||
try {
|
||||
sendSignal(new HangupSignal(localNumber, password,
|
||||
counterProvider.getOtpCounter(context),
|
||||
sessionId));
|
||||
readSignalResponse();
|
||||
} catch (SignalingException se) {}
|
||||
}
|
||||
|
||||
|
||||
public void setBusy(long sessionId) throws SignalingException {
|
||||
sendSignal(new BusySignal(localNumber, password,
|
||||
counterProvider.getOtpCounter(context),
|
||||
sessionId));
|
||||
readSignalResponse();
|
||||
}
|
||||
|
||||
public void sendOkResponse() throws SignalingException {
|
||||
try {
|
||||
this.outputStream.write("HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes());
|
||||
} catch (IOException ioe) {
|
||||
throw new SignalingException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean waitForSignal() throws SignalingException {
|
||||
try {
|
||||
socket.setSoTimeout(1500);
|
||||
return lineReader.waitForAvailable();
|
||||
} catch (IOException ioe) {
|
||||
throw new SignalingException(ioe);
|
||||
} finally {
|
||||
try {
|
||||
socket.setSoTimeout(0);
|
||||
} catch (SocketException e) {
|
||||
Log.w("SignalingSocket", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ServerSignal readSignal() throws SignalingException {
|
||||
try {
|
||||
SignalReader signalReader = new SignalReader(lineReader);
|
||||
String[] request = signalReader.readSignalRequest();
|
||||
Map<String, String> headers = signalReader.readSignalHeaders();
|
||||
byte[] body = signalReader.readSignalBody(headers);
|
||||
|
||||
return new ServerSignal(request[0].trim(), request[1].trim(), body);
|
||||
} catch (IOException ioe) {
|
||||
throw new SignalingException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendSignal(Signal signal) throws SignalingException {
|
||||
try {
|
||||
Log.d(TAG, "Sending signal...");
|
||||
this.outputStream.write(signal.serialize().getBytes());
|
||||
} catch (IOException ioe) {
|
||||
throw new SignalingException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
protected SignalResponse readSignalResponse() throws SignalingException {
|
||||
try {
|
||||
SignalResponseReader responseReader = new SignalResponseReader(lineReader);
|
||||
int responseCode = responseReader.readSignalResponseCode();
|
||||
Map<String, String> headers = responseReader.readSignalHeaders();
|
||||
byte[] body = responseReader.readSignalBody(headers);
|
||||
|
||||
return new SignalResponse(responseCode, headers, body);
|
||||
} catch (IOException ioe) {
|
||||
throw new SignalingException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private class SocketConnectMonitor extends Thread {
|
||||
private final Socket socket;
|
||||
|
||||
public SocketConnectMonitor(Socket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (SignalingSocket.this) {
|
||||
try {
|
||||
if (!SignalingSocket.this.connectionAttemptComplete) SignalingSocket.this.wait(10000);
|
||||
if (!SignalingSocket.this.connectionAttemptComplete) this.socket.close();
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.thoughtcrime.redphone.signaling;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class UnauthorizedException extends IOException {
|
||||
public UnauthorizedException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling.signals;
|
||||
|
||||
/**
|
||||
* A signal which indicates that the endpoint is "busy,"
|
||||
* and can't answer the call associated with the specified session.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class BusySignal extends Signal {
|
||||
|
||||
private final long sessionId;
|
||||
|
||||
public BusySignal(String localNumber, String password, long counter, long sessionId) {
|
||||
super(localNumber, password, counter);
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMethod() {
|
||||
return "BUSY";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLocation() {
|
||||
return "/session/" + sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBody() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling.signals;
|
||||
|
||||
/**
|
||||
* A signal which indicates that the endpoint has registered
|
||||
* for C2DM updates at the specified registration ID. The
|
||||
* server can now "push" signals to this endpoint via C2DM rather
|
||||
* than SMS.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class C2DMRegistrationSignal extends Signal {
|
||||
|
||||
private final String registrationId;
|
||||
|
||||
public C2DMRegistrationSignal(String localNumber, String password, String registrationId) {
|
||||
super(localNumber, password, -1);
|
||||
this.registrationId = registrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMethod() {
|
||||
return "PUT";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLocation() {
|
||||
return "/c2dm/" + registrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBody() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling.signals;
|
||||
|
||||
/**
|
||||
* A signal which indicates that the endpoint is no
|
||||
* longer registered with C2DM.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
public class C2DMUnregistrationSignal extends Signal {
|
||||
|
||||
public C2DMUnregistrationSignal(String localNumber, String password) {
|
||||
super(localNumber, password, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMethod() {
|
||||
return "DELETE";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLocation() {
|
||||
return "/c2dm";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBody() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,695 +0,0 @@
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: CompressedInitiateSignal.proto
|
||||
|
||||
package org.thoughtcrime.redphone.signaling.signals;
|
||||
|
||||
public final class CompressedInitiateSignalProtocol {
|
||||
private CompressedInitiateSignalProtocol() {}
|
||||
public static void registerAllExtensions(
|
||||
com.google.protobuf.ExtensionRegistry registry) {
|
||||
}
|
||||
public interface CompressedInitiateSignalOrBuilder
|
||||
extends com.google.protobuf.MessageOrBuilder {
|
||||
|
||||
// optional string initiator = 1;
|
||||
boolean hasInitiator();
|
||||
String getInitiator();
|
||||
|
||||
// optional uint64 sessionId = 2;
|
||||
boolean hasSessionId();
|
||||
long getSessionId();
|
||||
|
||||
// optional uint32 port = 3;
|
||||
boolean hasPort();
|
||||
int getPort();
|
||||
|
||||
// optional string serverName = 4;
|
||||
boolean hasServerName();
|
||||
String getServerName();
|
||||
|
||||
// optional uint32 version = 5;
|
||||
boolean hasVersion();
|
||||
int getVersion();
|
||||
}
|
||||
public static final class CompressedInitiateSignal extends
|
||||
com.google.protobuf.GeneratedMessage
|
||||
implements CompressedInitiateSignalOrBuilder {
|
||||
// Use CompressedInitiateSignal.newBuilder() to construct.
|
||||
private CompressedInitiateSignal(Builder builder) {
|
||||
super(builder);
|
||||
}
|
||||
private CompressedInitiateSignal(boolean noInit) {}
|
||||
|
||||
private static final CompressedInitiateSignal defaultInstance;
|
||||
public static CompressedInitiateSignal getDefaultInstance() {
|
||||
return defaultInstance;
|
||||
}
|
||||
|
||||
public CompressedInitiateSignal getDefaultInstanceForType() {
|
||||
return defaultInstance;
|
||||
}
|
||||
|
||||
public static final com.google.protobuf.Descriptors.Descriptor
|
||||
getDescriptor() {
|
||||
return org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.internal_static_redphone_CompressedInitiateSignal_descriptor;
|
||||
}
|
||||
|
||||
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
|
||||
internalGetFieldAccessorTable() {
|
||||
return org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.internal_static_redphone_CompressedInitiateSignal_fieldAccessorTable;
|
||||
}
|
||||
|
||||
private int bitField0_;
|
||||
// optional string initiator = 1;
|
||||
public static final int INITIATOR_FIELD_NUMBER = 1;
|
||||
private java.lang.Object initiator_;
|
||||
public boolean hasInitiator() {
|
||||
return ((bitField0_ & 0x00000001) == 0x00000001);
|
||||
}
|
||||
public String getInitiator() {
|
||||
java.lang.Object ref = initiator_;
|
||||
if (ref instanceof String) {
|
||||
return (String) ref;
|
||||
} else {
|
||||
com.google.protobuf.ByteString bs =
|
||||
(com.google.protobuf.ByteString) ref;
|
||||
String s = bs.toStringUtf8();
|
||||
if (com.google.protobuf.Internal.isValidUtf8(bs)) {
|
||||
initiator_ = s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
private com.google.protobuf.ByteString getInitiatorBytes() {
|
||||
java.lang.Object ref = initiator_;
|
||||
if (ref instanceof String) {
|
||||
com.google.protobuf.ByteString b =
|
||||
com.google.protobuf.ByteString.copyFromUtf8((String) ref);
|
||||
initiator_ = b;
|
||||
return b;
|
||||
} else {
|
||||
return (com.google.protobuf.ByteString) ref;
|
||||
}
|
||||
}
|
||||
|
||||
// optional uint64 sessionId = 2;
|
||||
public static final int SESSIONID_FIELD_NUMBER = 2;
|
||||
private long sessionId_;
|
||||
public boolean hasSessionId() {
|
||||
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||
}
|
||||
public long getSessionId() {
|
||||
return sessionId_;
|
||||
}
|
||||
|
||||
// optional uint32 port = 3;
|
||||
public static final int PORT_FIELD_NUMBER = 3;
|
||||
private int port_;
|
||||
public boolean hasPort() {
|
||||
return ((bitField0_ & 0x00000004) == 0x00000004);
|
||||
}
|
||||
public int getPort() {
|
||||
return port_;
|
||||
}
|
||||
|
||||
// optional string serverName = 4;
|
||||
public static final int SERVERNAME_FIELD_NUMBER = 4;
|
||||
private java.lang.Object serverName_;
|
||||
public boolean hasServerName() {
|
||||
return ((bitField0_ & 0x00000008) == 0x00000008);
|
||||
}
|
||||
public String getServerName() {
|
||||
java.lang.Object ref = serverName_;
|
||||
if (ref instanceof String) {
|
||||
return (String) ref;
|
||||
} else {
|
||||
com.google.protobuf.ByteString bs =
|
||||
(com.google.protobuf.ByteString) ref;
|
||||
String s = bs.toStringUtf8();
|
||||
if (com.google.protobuf.Internal.isValidUtf8(bs)) {
|
||||
serverName_ = s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
private com.google.protobuf.ByteString getServerNameBytes() {
|
||||
java.lang.Object ref = serverName_;
|
||||
if (ref instanceof String) {
|
||||
com.google.protobuf.ByteString b =
|
||||
com.google.protobuf.ByteString.copyFromUtf8((String) ref);
|
||||
serverName_ = b;
|
||||
return b;
|
||||
} else {
|
||||
return (com.google.protobuf.ByteString) ref;
|
||||
}
|
||||
}
|
||||
|
||||
// optional uint32 version = 5;
|
||||
public static final int VERSION_FIELD_NUMBER = 5;
|
||||
private int version_;
|
||||
public boolean hasVersion() {
|
||||
return ((bitField0_ & 0x00000010) == 0x00000010);
|
||||
}
|
||||
public int getVersion() {
|
||||
return version_;
|
||||
}
|
||||
|
||||
private void initFields() {
|
||||
initiator_ = "";
|
||||
sessionId_ = 0L;
|
||||
port_ = 0;
|
||||
serverName_ = "";
|
||||
version_ = 0;
|
||||
}
|
||||
private byte memoizedIsInitialized = -1;
|
||||
public final boolean isInitialized() {
|
||||
byte isInitialized = memoizedIsInitialized;
|
||||
if (isInitialized != -1) return isInitialized == 1;
|
||||
|
||||
memoizedIsInitialized = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void writeTo(com.google.protobuf.CodedOutputStream output)
|
||||
throws java.io.IOException {
|
||||
getSerializedSize();
|
||||
if (((bitField0_ & 0x00000001) == 0x00000001)) {
|
||||
output.writeBytes(1, getInitiatorBytes());
|
||||
}
|
||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
output.writeUInt64(2, sessionId_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
output.writeUInt32(3, port_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000008) == 0x00000008)) {
|
||||
output.writeBytes(4, getServerNameBytes());
|
||||
}
|
||||
if (((bitField0_ & 0x00000010) == 0x00000010)) {
|
||||
output.writeUInt32(5, version_);
|
||||
}
|
||||
getUnknownFields().writeTo(output);
|
||||
}
|
||||
|
||||
private int memoizedSerializedSize = -1;
|
||||
public int getSerializedSize() {
|
||||
int size = memoizedSerializedSize;
|
||||
if (size != -1) return size;
|
||||
|
||||
size = 0;
|
||||
if (((bitField0_ & 0x00000001) == 0x00000001)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeBytesSize(1, getInitiatorBytes());
|
||||
}
|
||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeUInt64Size(2, sessionId_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeUInt32Size(3, port_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000008) == 0x00000008)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeBytesSize(4, getServerNameBytes());
|
||||
}
|
||||
if (((bitField0_ & 0x00000010) == 0x00000010)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeUInt32Size(5, version_);
|
||||
}
|
||||
size += getUnknownFields().getSerializedSize();
|
||||
memoizedSerializedSize = size;
|
||||
return size;
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
@java.lang.Override
|
||||
protected java.lang.Object writeReplace()
|
||||
throws java.io.ObjectStreamException {
|
||||
return super.writeReplace();
|
||||
}
|
||||
|
||||
public static org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal parseFrom(
|
||||
com.google.protobuf.ByteString data)
|
||||
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||
return newBuilder().mergeFrom(data).buildParsed();
|
||||
}
|
||||
public static org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal parseFrom(
|
||||
com.google.protobuf.ByteString data,
|
||||
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||
return newBuilder().mergeFrom(data, extensionRegistry)
|
||||
.buildParsed();
|
||||
}
|
||||
public static org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal parseFrom(byte[] data)
|
||||
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||
return newBuilder().mergeFrom(data).buildParsed();
|
||||
}
|
||||
public static org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal parseFrom(
|
||||
byte[] data,
|
||||
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||
return newBuilder().mergeFrom(data, extensionRegistry)
|
||||
.buildParsed();
|
||||
}
|
||||
public static org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal parseFrom(java.io.InputStream input)
|
||||
throws java.io.IOException {
|
||||
return newBuilder().mergeFrom(input).buildParsed();
|
||||
}
|
||||
public static org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal parseFrom(
|
||||
java.io.InputStream input,
|
||||
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||
throws java.io.IOException {
|
||||
return newBuilder().mergeFrom(input, extensionRegistry)
|
||||
.buildParsed();
|
||||
}
|
||||
public static org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal parseDelimitedFrom(java.io.InputStream input)
|
||||
throws java.io.IOException {
|
||||
Builder builder = newBuilder();
|
||||
if (builder.mergeDelimitedFrom(input)) {
|
||||
return builder.buildParsed();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal parseDelimitedFrom(
|
||||
java.io.InputStream input,
|
||||
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||
throws java.io.IOException {
|
||||
Builder builder = newBuilder();
|
||||
if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
|
||||
return builder.buildParsed();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal parseFrom(
|
||||
com.google.protobuf.CodedInputStream input)
|
||||
throws java.io.IOException {
|
||||
return newBuilder().mergeFrom(input).buildParsed();
|
||||
}
|
||||
public static org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal parseFrom(
|
||||
com.google.protobuf.CodedInputStream input,
|
||||
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||
throws java.io.IOException {
|
||||
return newBuilder().mergeFrom(input, extensionRegistry)
|
||||
.buildParsed();
|
||||
}
|
||||
|
||||
public static Builder newBuilder() { return Builder.create(); }
|
||||
public Builder newBuilderForType() { return newBuilder(); }
|
||||
public static Builder newBuilder(org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal prototype) {
|
||||
return newBuilder().mergeFrom(prototype);
|
||||
}
|
||||
public Builder toBuilder() { return newBuilder(this); }
|
||||
|
||||
@java.lang.Override
|
||||
protected Builder newBuilderForType(
|
||||
com.google.protobuf.GeneratedMessage.BuilderParent parent) {
|
||||
Builder builder = new Builder(parent);
|
||||
return builder;
|
||||
}
|
||||
public static final class Builder extends
|
||||
com.google.protobuf.GeneratedMessage.Builder<Builder>
|
||||
implements org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignalOrBuilder {
|
||||
public static final com.google.protobuf.Descriptors.Descriptor
|
||||
getDescriptor() {
|
||||
return org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.internal_static_redphone_CompressedInitiateSignal_descriptor;
|
||||
}
|
||||
|
||||
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
|
||||
internalGetFieldAccessorTable() {
|
||||
return org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.internal_static_redphone_CompressedInitiateSignal_fieldAccessorTable;
|
||||
}
|
||||
|
||||
// Construct using org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal.newBuilder()
|
||||
private Builder() {
|
||||
maybeForceBuilderInitialization();
|
||||
}
|
||||
|
||||
private Builder(BuilderParent parent) {
|
||||
super(parent);
|
||||
maybeForceBuilderInitialization();
|
||||
}
|
||||
private void maybeForceBuilderInitialization() {
|
||||
if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
|
||||
}
|
||||
}
|
||||
private static Builder create() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public Builder clear() {
|
||||
super.clear();
|
||||
initiator_ = "";
|
||||
bitField0_ = (bitField0_ & ~0x00000001);
|
||||
sessionId_ = 0L;
|
||||
bitField0_ = (bitField0_ & ~0x00000002);
|
||||
port_ = 0;
|
||||
bitField0_ = (bitField0_ & ~0x00000004);
|
||||
serverName_ = "";
|
||||
bitField0_ = (bitField0_ & ~0x00000008);
|
||||
version_ = 0;
|
||||
bitField0_ = (bitField0_ & ~0x00000010);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder clone() {
|
||||
return create().mergeFrom(buildPartial());
|
||||
}
|
||||
|
||||
public com.google.protobuf.Descriptors.Descriptor
|
||||
getDescriptorForType() {
|
||||
return org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal.getDescriptor();
|
||||
}
|
||||
|
||||
public org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal getDefaultInstanceForType() {
|
||||
return org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal.getDefaultInstance();
|
||||
}
|
||||
|
||||
public org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal build() {
|
||||
org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal result = buildPartial();
|
||||
if (!result.isInitialized()) {
|
||||
throw newUninitializedMessageException(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal buildParsed()
|
||||
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||
org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal result = buildPartial();
|
||||
if (!result.isInitialized()) {
|
||||
throw newUninitializedMessageException(
|
||||
result).asInvalidProtocolBufferException();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal buildPartial() {
|
||||
org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal result = new org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal(this);
|
||||
int from_bitField0_ = bitField0_;
|
||||
int to_bitField0_ = 0;
|
||||
if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
|
||||
to_bitField0_ |= 0x00000001;
|
||||
}
|
||||
result.initiator_ = initiator_;
|
||||
if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
to_bitField0_ |= 0x00000002;
|
||||
}
|
||||
result.sessionId_ = sessionId_;
|
||||
if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
to_bitField0_ |= 0x00000004;
|
||||
}
|
||||
result.port_ = port_;
|
||||
if (((from_bitField0_ & 0x00000008) == 0x00000008)) {
|
||||
to_bitField0_ |= 0x00000008;
|
||||
}
|
||||
result.serverName_ = serverName_;
|
||||
if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
|
||||
to_bitField0_ |= 0x00000010;
|
||||
}
|
||||
result.version_ = version_;
|
||||
result.bitField0_ = to_bitField0_;
|
||||
onBuilt();
|
||||
return result;
|
||||
}
|
||||
|
||||
public Builder mergeFrom(com.google.protobuf.Message other) {
|
||||
if (other instanceof org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal) {
|
||||
return mergeFrom((org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal)other);
|
||||
} else {
|
||||
super.mergeFrom(other);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public Builder mergeFrom(org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal other) {
|
||||
if (other == org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal.getDefaultInstance()) return this;
|
||||
if (other.hasInitiator()) {
|
||||
setInitiator(other.getInitiator());
|
||||
}
|
||||
if (other.hasSessionId()) {
|
||||
setSessionId(other.getSessionId());
|
||||
}
|
||||
if (other.hasPort()) {
|
||||
setPort(other.getPort());
|
||||
}
|
||||
if (other.hasServerName()) {
|
||||
setServerName(other.getServerName());
|
||||
}
|
||||
if (other.hasVersion()) {
|
||||
setVersion(other.getVersion());
|
||||
}
|
||||
this.mergeUnknownFields(other.getUnknownFields());
|
||||
return this;
|
||||
}
|
||||
|
||||
public final boolean isInitialized() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Builder mergeFrom(
|
||||
com.google.protobuf.CodedInputStream input,
|
||||
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||
throws java.io.IOException {
|
||||
com.google.protobuf.UnknownFieldSet.Builder unknownFields =
|
||||
com.google.protobuf.UnknownFieldSet.newBuilder(
|
||||
this.getUnknownFields());
|
||||
while (true) {
|
||||
int tag = input.readTag();
|
||||
switch (tag) {
|
||||
case 0:
|
||||
this.setUnknownFields(unknownFields.build());
|
||||
onChanged();
|
||||
return this;
|
||||
default: {
|
||||
if (!parseUnknownField(input, unknownFields,
|
||||
extensionRegistry, tag)) {
|
||||
this.setUnknownFields(unknownFields.build());
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 10: {
|
||||
bitField0_ |= 0x00000001;
|
||||
initiator_ = input.readBytes();
|
||||
break;
|
||||
}
|
||||
case 16: {
|
||||
bitField0_ |= 0x00000002;
|
||||
sessionId_ = input.readUInt64();
|
||||
break;
|
||||
}
|
||||
case 24: {
|
||||
bitField0_ |= 0x00000004;
|
||||
port_ = input.readUInt32();
|
||||
break;
|
||||
}
|
||||
case 34: {
|
||||
bitField0_ |= 0x00000008;
|
||||
serverName_ = input.readBytes();
|
||||
break;
|
||||
}
|
||||
case 40: {
|
||||
bitField0_ |= 0x00000010;
|
||||
version_ = input.readUInt32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int bitField0_;
|
||||
|
||||
// optional string initiator = 1;
|
||||
private java.lang.Object initiator_ = "";
|
||||
public boolean hasInitiator() {
|
||||
return ((bitField0_ & 0x00000001) == 0x00000001);
|
||||
}
|
||||
public String getInitiator() {
|
||||
java.lang.Object ref = initiator_;
|
||||
if (!(ref instanceof String)) {
|
||||
String s = ((com.google.protobuf.ByteString) ref).toStringUtf8();
|
||||
initiator_ = s;
|
||||
return s;
|
||||
} else {
|
||||
return (String) ref;
|
||||
}
|
||||
}
|
||||
public Builder setInitiator(String value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
bitField0_ |= 0x00000001;
|
||||
initiator_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearInitiator() {
|
||||
bitField0_ = (bitField0_ & ~0x00000001);
|
||||
initiator_ = getDefaultInstance().getInitiator();
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
void setInitiator(com.google.protobuf.ByteString value) {
|
||||
bitField0_ |= 0x00000001;
|
||||
initiator_ = value;
|
||||
onChanged();
|
||||
}
|
||||
|
||||
// optional uint64 sessionId = 2;
|
||||
private long sessionId_ ;
|
||||
public boolean hasSessionId() {
|
||||
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||
}
|
||||
public long getSessionId() {
|
||||
return sessionId_;
|
||||
}
|
||||
public Builder setSessionId(long value) {
|
||||
bitField0_ |= 0x00000002;
|
||||
sessionId_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearSessionId() {
|
||||
bitField0_ = (bitField0_ & ~0x00000002);
|
||||
sessionId_ = 0L;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
|
||||
// optional uint32 port = 3;
|
||||
private int port_ ;
|
||||
public boolean hasPort() {
|
||||
return ((bitField0_ & 0x00000004) == 0x00000004);
|
||||
}
|
||||
public int getPort() {
|
||||
return port_;
|
||||
}
|
||||
public Builder setPort(int value) {
|
||||
bitField0_ |= 0x00000004;
|
||||
port_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearPort() {
|
||||
bitField0_ = (bitField0_ & ~0x00000004);
|
||||
port_ = 0;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
|
||||
// optional string serverName = 4;
|
||||
private java.lang.Object serverName_ = "";
|
||||
public boolean hasServerName() {
|
||||
return ((bitField0_ & 0x00000008) == 0x00000008);
|
||||
}
|
||||
public String getServerName() {
|
||||
java.lang.Object ref = serverName_;
|
||||
if (!(ref instanceof String)) {
|
||||
String s = ((com.google.protobuf.ByteString) ref).toStringUtf8();
|
||||
serverName_ = s;
|
||||
return s;
|
||||
} else {
|
||||
return (String) ref;
|
||||
}
|
||||
}
|
||||
public Builder setServerName(String value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
bitField0_ |= 0x00000008;
|
||||
serverName_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearServerName() {
|
||||
bitField0_ = (bitField0_ & ~0x00000008);
|
||||
serverName_ = getDefaultInstance().getServerName();
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
void setServerName(com.google.protobuf.ByteString value) {
|
||||
bitField0_ |= 0x00000008;
|
||||
serverName_ = value;
|
||||
onChanged();
|
||||
}
|
||||
|
||||
// optional uint32 version = 5;
|
||||
private int version_ ;
|
||||
public boolean hasVersion() {
|
||||
return ((bitField0_ & 0x00000010) == 0x00000010);
|
||||
}
|
||||
public int getVersion() {
|
||||
return version_;
|
||||
}
|
||||
public Builder setVersion(int value) {
|
||||
bitField0_ |= 0x00000010;
|
||||
version_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearVersion() {
|
||||
bitField0_ = (bitField0_ & ~0x00000010);
|
||||
version_ = 0;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
|
||||
// @@protoc_insertion_point(builder_scope:redphone.CompressedInitiateSignal)
|
||||
}
|
||||
|
||||
static {
|
||||
defaultInstance = new CompressedInitiateSignal(true);
|
||||
defaultInstance.initFields();
|
||||
}
|
||||
|
||||
// @@protoc_insertion_point(class_scope:redphone.CompressedInitiateSignal)
|
||||
}
|
||||
|
||||
private static com.google.protobuf.Descriptors.Descriptor
|
||||
internal_static_redphone_CompressedInitiateSignal_descriptor;
|
||||
private static
|
||||
com.google.protobuf.GeneratedMessage.FieldAccessorTable
|
||||
internal_static_redphone_CompressedInitiateSignal_fieldAccessorTable;
|
||||
|
||||
public static com.google.protobuf.Descriptors.FileDescriptor
|
||||
getDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
private static com.google.protobuf.Descriptors.FileDescriptor
|
||||
descriptor;
|
||||
static {
|
||||
java.lang.String[] descriptorData = {
|
||||
"\n\036CompressedInitiateSignal.proto\022\010redpho" +
|
||||
"ne\"s\n\030CompressedInitiateSignal\022\021\n\tinitia" +
|
||||
"tor\030\001 \001(\t\022\021\n\tsessionId\030\002 \001(\004\022\014\n\004port\030\003 \001" +
|
||||
"(\r\022\022\n\nserverName\030\004 \001(\t\022\017\n\007version\030\005 \001(\rB" +
|
||||
"O\n+org.thoughtcrime.redphone.signaling.s" +
|
||||
"ignalsB CompressedInitiateSignalProtocol"
|
||||
};
|
||||
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||
public com.google.protobuf.ExtensionRegistry assignDescriptors(
|
||||
com.google.protobuf.Descriptors.FileDescriptor root) {
|
||||
descriptor = root;
|
||||
internal_static_redphone_CompressedInitiateSignal_descriptor =
|
||||
getDescriptor().getMessageTypes().get(0);
|
||||
internal_static_redphone_CompressedInitiateSignal_fieldAccessorTable = new
|
||||
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
||||
internal_static_redphone_CompressedInitiateSignal_descriptor,
|
||||
new java.lang.String[] { "Initiator", "SessionId", "Port", "ServerName", "Version", },
|
||||
org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal.class,
|
||||
org.thoughtcrime.redphone.signaling.signals.CompressedInitiateSignalProtocol.CompressedInitiateSignal.Builder.class);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
com.google.protobuf.Descriptors.FileDescriptor
|
||||
.internalBuildGeneratedFileFrom(descriptorData,
|
||||
new com.google.protobuf.Descriptors.FileDescriptor[] {
|
||||
}, assigner);
|
||||
}
|
||||
|
||||
// @@protoc_insertion_point(outer_class_scope)
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling.signals;
|
||||
|
||||
/**
|
||||
* A signal which indicates that the endpoint is hanging
|
||||
* up the call associated with the specified session ID.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class HangupSignal extends Signal {
|
||||
|
||||
private final long sessionId;
|
||||
|
||||
public HangupSignal(String localNumber, String password, long counter, long sessionId) {
|
||||
super(localNumber, password, counter);
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMethod() {
|
||||
return "DELETE";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLocation() {
|
||||
return "/session/" + sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBody() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling.signals;
|
||||
|
||||
//import org.thoughtcrime.redphone.util.PhoneNumberFormatter;
|
||||
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
||||
|
||||
/**
|
||||
* A signal which initiates a call with the specified remote number.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class InitiateSignal extends Signal {
|
||||
|
||||
private final String remoteNumber;
|
||||
|
||||
public InitiateSignal(String localNumber, String password, long counter, String remoteNumber) {
|
||||
super(localNumber, password, counter);
|
||||
try {
|
||||
this.remoteNumber = PhoneNumberFormatter.formatNumber(remoteNumber, localNumber);
|
||||
} catch (InvalidNumberException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMethod() {
|
||||
return "GET";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLocation() {
|
||||
return "/session/1/" + remoteNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBody() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling.signals;
|
||||
|
||||
/**
|
||||
* Sent by the NetworkConnector via UDP in order to punch
|
||||
* holes in intermediate NATs.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class OpenPortSignal extends Signal {
|
||||
|
||||
private final long sessionId;
|
||||
|
||||
public OpenPortSignal(long sessionId) {
|
||||
super(null, null, -1);
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMethod() {
|
||||
return "GET";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLocation() {
|
||||
return "/open/" + sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBody() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling.signals;
|
||||
|
||||
/**
|
||||
* A signal which indicates that the endpoint has
|
||||
* received an "initiate" push notification, and that
|
||||
* the endpoint device is ringing.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class RingingSignal extends Signal {
|
||||
|
||||
private final long sessionId;
|
||||
|
||||
public RingingSignal(String localNumber, String password, long counter, long sessionId) {
|
||||
super(localNumber, password, counter);
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMethod() {
|
||||
return "RING";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLocation() {
|
||||
return "/session/" + sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBody() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling.signals;
|
||||
|
||||
/**
|
||||
* Typically client endpoints are sending signals and receiving responses,
|
||||
* but in some cases the switch sends a signal and the client endpoint responds.
|
||||
*
|
||||
* This class encapsulates signals sent by the server.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class ServerSignal {
|
||||
|
||||
private final String verb;
|
||||
private final String target;
|
||||
private final byte[] body;
|
||||
|
||||
private final long sessionId;
|
||||
|
||||
public ServerSignal(String verb, String target, byte[] body) {
|
||||
this.verb = verb;
|
||||
this.target = target;
|
||||
this.body = body;
|
||||
|
||||
if (target.startsWith("/session/")) {
|
||||
this.sessionId = Long.parseLong(target.substring("/session/".length()).trim());
|
||||
} else {
|
||||
this.sessionId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isKeepAlive() {
|
||||
return verb.equals("GET") && target.startsWith("/keepalive");
|
||||
}
|
||||
|
||||
public boolean isRinging(long sessionId) {
|
||||
return verb.equals("RING") && this.sessionId != -1 && this.sessionId == sessionId;
|
||||
}
|
||||
|
||||
public boolean isHangup(long sessionId) {
|
||||
return verb.equals("DELETE") && this.sessionId != -1 && this.sessionId == sessionId;
|
||||
}
|
||||
|
||||
public boolean isBusy(long sessionId) {
|
||||
return verb.equals("BUSY") && this.sessionId != -1 && this.sessionId == sessionId;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.signaling.signals;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.Otp;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
|
||||
/**
|
||||
* Base class for constructing a signal.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public abstract class Signal {
|
||||
|
||||
private final String localNumber;
|
||||
private final String password;
|
||||
private final long counter;
|
||||
|
||||
public Signal(String localNumber, String password, long counter) {
|
||||
this.localNumber = localNumber;
|
||||
this.password = password;
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
public String serialize() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
buildRequest(sb, getMethod(), getLocation());
|
||||
buildAuthorization(sb, password, counter);
|
||||
buildBody(sb, getBody());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void buildBody(StringBuffer sb, String body) {
|
||||
if (body != null && body.length() > 0) {
|
||||
sb.append("Content-Length: ");
|
||||
sb.append(body.length());
|
||||
sb.append("\r\n");
|
||||
}
|
||||
|
||||
sb.append("\r\n");
|
||||
|
||||
if (body!= null && body.length() > 0) {
|
||||
sb.append(body);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildAuthorization(StringBuffer sb, String password, long counter) {
|
||||
if (password != null && counter == -1) {
|
||||
sb.append("Authorization: Basic ");
|
||||
sb.append(Base64.encodeBytes((localNumber + ":" + password).getBytes()));
|
||||
sb.append("\r\n");
|
||||
} else if (password != null) {
|
||||
sb.append("Authorization: OTP ");
|
||||
sb.append(Base64.encodeBytes((localNumber + ":" +
|
||||
Otp.calculateOtp(password, counter) + ":" +
|
||||
counter).getBytes()));
|
||||
sb.append("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
private void buildRequest(StringBuffer sb, String method, String location) {
|
||||
sb.append(getMethod());
|
||||
sb.append(" ");
|
||||
sb.append(getLocation());
|
||||
sb.append(" HTTP/1.0\r\n");
|
||||
}
|
||||
|
||||
protected abstract String getMethod();
|
||||
protected abstract String getLocation();
|
||||
protected abstract String getBody();
|
||||
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
/**
|
||||
* The in-call display card.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class CallCard extends LinearLayout implements Recipient.RecipientModifiedListener {
|
||||
|
||||
private ImageView photo;
|
||||
private TextView name;
|
||||
private TextView phoneNumber;
|
||||
private TextView label;
|
||||
private TextView elapsedTime;
|
||||
private TextView status;
|
||||
|
||||
private Recipient recipient;
|
||||
|
||||
public CallCard(Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public CallCard(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
setPersonInfo(Recipient.getUnknownRecipient());
|
||||
this.status.setText("");
|
||||
this.recipient = null;
|
||||
}
|
||||
|
||||
public void setElapsedTime(String time) {
|
||||
this.elapsedTime.setText(time);
|
||||
}
|
||||
|
||||
private void setPersonInfo(final @NonNull Recipient recipient) {
|
||||
this.recipient = recipient;
|
||||
this.recipient.addListener(this);
|
||||
|
||||
final Context context = getContext();
|
||||
|
||||
new AsyncTask<Void, Void, ContactPhoto>() {
|
||||
@Override
|
||||
protected ContactPhoto doInBackground(Void... params) {
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Uri contentUri = ContactsContract.Contacts.lookupContact(context.getContentResolver(),
|
||||
recipient.getContactUri());
|
||||
windowManager.getDefaultDisplay().getMetrics(metrics);
|
||||
return ContactPhotoFactory.getContactPhoto(context, contentUri, null, metrics.widthPixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final ContactPhoto contactPhoto) {
|
||||
CallCard.this.photo.setImageDrawable(contactPhoto.asCallCard(context));
|
||||
}
|
||||
}.execute();
|
||||
|
||||
this.name.setText(recipient.getName());
|
||||
this.phoneNumber.setText(recipient.getNumber());
|
||||
}
|
||||
|
||||
public void setCard(Recipient recipient, String status) {
|
||||
setPersonInfo(recipient);
|
||||
this.status.setText(status);
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
LayoutInflater inflater = (LayoutInflater)getContext()
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
inflater.inflate(R.layout.redphone_call_card, this, true);
|
||||
|
||||
this.elapsedTime = (TextView)findViewById(R.id.elapsedTime);
|
||||
this.photo = (ImageView)findViewById(R.id.photo);
|
||||
this.phoneNumber = (TextView)findViewById(R.id.phoneNumber);
|
||||
this.name = (TextView)findViewById(R.id.name);
|
||||
this.label = (TextView)findViewById(R.id.label);
|
||||
this.status = (TextView)findViewById(R.id.callStateLabel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(Recipient recipient) {
|
||||
if (recipient == this.recipient) {
|
||||
setPersonInfo(recipient);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.SASInfo;
|
||||
import org.thoughtcrime.redphone.util.AudioUtils;
|
||||
import org.thoughtcrime.redphone.util.multiwaveview.MultiWaveView;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
/**
|
||||
* Displays the controls at the bottom of the in-call screen.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class CallControls extends RelativeLayout {
|
||||
|
||||
private ImageButton endCallButton;
|
||||
private TextView sasTextView;
|
||||
private View activeCallWidget;
|
||||
private MultiWaveView incomingCallWidget;
|
||||
private TextView redphoneLabel;
|
||||
|
||||
private CompoundButton muteButton;
|
||||
private InCallAudioButton audioButton;
|
||||
|
||||
private Handler handler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (incomingCallWidget.getVisibility() == View.VISIBLE) {
|
||||
incomingCallWidget.ping();
|
||||
handler.sendEmptyMessageDelayed(0, 1200);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public CallControls(Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public CallControls(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public CallControls(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public void setIncomingCall() {
|
||||
activeCallWidget.setVisibility(View.GONE);
|
||||
|
||||
Animation animation = incomingCallWidget.getAnimation();
|
||||
|
||||
if (animation != null) {
|
||||
animation.reset();
|
||||
incomingCallWidget.clearAnimation();
|
||||
}
|
||||
|
||||
incomingCallWidget.reset(false);
|
||||
incomingCallWidget.setVisibility(View.VISIBLE);
|
||||
redphoneLabel.setVisibility(View.VISIBLE);
|
||||
|
||||
handler.sendEmptyMessageDelayed(0, 500);
|
||||
}
|
||||
|
||||
public void setActiveCall() {
|
||||
incomingCallWidget.setVisibility(View.GONE);
|
||||
redphoneLabel.setVisibility(View.GONE);
|
||||
activeCallWidget.setVisibility(View.VISIBLE);
|
||||
sasTextView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void setActiveCall(@Nullable String sas) {
|
||||
setActiveCall();
|
||||
sasTextView.setText(sas);
|
||||
sasTextView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
incomingCallWidget.setVisibility(View.GONE);
|
||||
redphoneLabel.setVisibility(View.GONE);
|
||||
activeCallWidget.setVisibility(View.GONE);
|
||||
sasTextView.setText("");
|
||||
updateAudioButton();
|
||||
muteButton.setChecked(false);
|
||||
}
|
||||
|
||||
public void setHangupButtonListener(final HangupButtonListener listener) {
|
||||
endCallButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onClick();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setMuteButtonListener(final MuteButtonListener listener) {
|
||||
muteButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
|
||||
listener.onToggle(b);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setAudioButtonListener(final AudioButtonListener listener) {
|
||||
audioButton.setListener(listener);
|
||||
}
|
||||
|
||||
|
||||
public void setIncomingCallActionListener(final IncomingCallActionListener listener) {
|
||||
incomingCallWidget.setOnTriggerListener(new MultiWaveView.OnTriggerListener() {
|
||||
@Override
|
||||
public void onTrigger(View v, int target) {
|
||||
switch (target) {
|
||||
case 0: listener.onAcceptClick(); break;
|
||||
case 2: listener.onDenyClick(); break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReleased(View v, int handle) {}
|
||||
|
||||
@Override
|
||||
public void onGrabbedStateChange(View v, int handle) {}
|
||||
|
||||
@Override
|
||||
public void onGrabbed(View v, int handle) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
LayoutInflater inflater = (LayoutInflater)getContext()
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
inflater.inflate(R.layout.redphone_call_controls, this, true);
|
||||
|
||||
this.endCallButton = (ImageButton)findViewById(R.id.endButton);
|
||||
this.incomingCallWidget = (MultiWaveView)findViewById(R.id.incomingCallWidget);
|
||||
this.redphoneLabel = (TextView)findViewById(R.id.redphone_banner);
|
||||
this.activeCallWidget = (View)findViewById(R.id.inCallControls);
|
||||
this.sasTextView = (TextView)findViewById(R.id.sas);
|
||||
this.muteButton = (CompoundButton)findViewById(R.id.muteButton);
|
||||
this.audioButton = new InCallAudioButton((CompoundButton)findViewById(R.id.audioButton));
|
||||
|
||||
updateAudioButton();
|
||||
}
|
||||
|
||||
public void updateAudioButton() {
|
||||
audioButton.setAudioMode(AudioUtils.getCurrentAudioMode(getContext()));
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(AudioUtils.getScoUpdateAction());
|
||||
handleBluetoothIntent(getContext().registerReceiver(null, filter));
|
||||
}
|
||||
|
||||
|
||||
private void handleBluetoothIntent(Intent intent) {
|
||||
if (intent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!intent.getAction().equals(AudioUtils.getScoUpdateAction())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Integer state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
|
||||
if (state.equals(AudioManager.SCO_AUDIO_STATE_CONNECTED)) {
|
||||
audioButton.setHeadsetAvailable(true);
|
||||
} else if (state.equals(AudioManager.SCO_AUDIO_STATE_DISCONNECTED)) {
|
||||
audioButton.setHeadsetAvailable(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static interface HangupButtonListener {
|
||||
public void onClick();
|
||||
}
|
||||
|
||||
public static interface MuteButtonListener {
|
||||
public void onToggle(boolean isMuted);
|
||||
}
|
||||
|
||||
public static interface IncomingCallActionListener {
|
||||
public void onAcceptClick();
|
||||
public void onDenyClick();
|
||||
}
|
||||
|
||||
public static interface AudioButtonListener {
|
||||
public void onAudioChange(AudioUtils.AudioMode mode);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.thoughtcrime.redphone.crypto.zrtp.SASInfo;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
/**
|
||||
* A UI widget that encapsulates the entire in-call screen
|
||||
* for both initiators and responders.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class CallScreen extends FrameLayout {
|
||||
|
||||
private CallCard callCard;
|
||||
private CallControls callControls;
|
||||
|
||||
public CallScreen(Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public CallScreen(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public CallScreen(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public void setActiveCall(@NonNull Recipient personInfo, @NonNull String message, @Nullable String sas) {
|
||||
callCard.setCard(personInfo, message);
|
||||
callControls.setActiveCall(sas);
|
||||
}
|
||||
|
||||
public void setActiveCall(Recipient personInfo, String message) {
|
||||
callCard.setCard(personInfo, message);
|
||||
callControls.setActiveCall();
|
||||
}
|
||||
|
||||
public void setIncomingCall(Recipient personInfo) {
|
||||
callCard.setCard(personInfo, getContext().getString(R.string.CallScreen_Incoming_call));
|
||||
callControls.setIncomingCall();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
callCard.reset();
|
||||
callControls.reset();
|
||||
}
|
||||
|
||||
public void setHangupButtonListener(CallControls.HangupButtonListener listener) {
|
||||
callControls.setHangupButtonListener(listener);
|
||||
}
|
||||
|
||||
public void setIncomingCallActionListener(CallControls.IncomingCallActionListener listener) {
|
||||
callControls.setIncomingCallActionListener(listener);
|
||||
}
|
||||
|
||||
public void setMuteButtonListener(CallControls.MuteButtonListener listener) {
|
||||
callControls.setMuteButtonListener(listener);
|
||||
}
|
||||
|
||||
public void setAudioButtonListener(CallControls.AudioButtonListener listener) {
|
||||
callControls.setAudioButtonListener(listener);
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
LayoutInflater inflater = (LayoutInflater)getContext()
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
inflater.inflate(R.layout.redphone_call_screen, this, true);
|
||||
|
||||
this.callCard = (CallCard)findViewById(R.id.callCard);
|
||||
this.callControls = (CallControls)findViewById(R.id.callControls);
|
||||
}
|
||||
|
||||
public void notifyBluetoothChange() {
|
||||
callControls.updateAudioButton();
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
package org.thoughtcrime.redphone.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.support.v7.widget.PopupMenu;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.CompoundButton;
|
||||
|
||||
import org.thoughtcrime.redphone.util.AudioUtils;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import static org.thoughtcrime.redphone.util.AudioUtils.AudioMode.DEFAULT;
|
||||
import static org.thoughtcrime.redphone.util.AudioUtils.AudioMode.HEADSET;
|
||||
import static org.thoughtcrime.redphone.util.AudioUtils.AudioMode.SPEAKER;
|
||||
|
||||
/**
|
||||
* Manages the audio button displayed on the in-call screen
|
||||
*
|
||||
* The behavior of this button depends on the availability of headset audio, and changes from being a regular
|
||||
* toggle button (enabling speakerphone) to bringing up a model dialog that includes speakerphone, bluetooth,
|
||||
* and regular audio options.
|
||||
*
|
||||
* Based on com.android.phone.InCallTouchUI
|
||||
*
|
||||
* @author Stuart O. Anderson
|
||||
*/
|
||||
public class InCallAudioButton {
|
||||
|
||||
private static final String TAG = InCallAudioButton.class.getName();
|
||||
|
||||
private final CompoundButton mAudioButton;
|
||||
private boolean headsetAvailable;
|
||||
private AudioUtils.AudioMode currentMode;
|
||||
private Context context;
|
||||
private CallControls.AudioButtonListener listener;
|
||||
|
||||
public InCallAudioButton(CompoundButton audioButton) {
|
||||
mAudioButton = audioButton;
|
||||
|
||||
currentMode = DEFAULT;
|
||||
headsetAvailable = false;
|
||||
|
||||
updateView();
|
||||
setListener(new CallControls.AudioButtonListener() {
|
||||
@Override
|
||||
public void onAudioChange(AudioUtils.AudioMode mode) {
|
||||
//No Action By Default.
|
||||
}
|
||||
});
|
||||
context = audioButton.getContext();
|
||||
}
|
||||
|
||||
public void setHeadsetAvailable(boolean available) {
|
||||
headsetAvailable = available;
|
||||
updateView();
|
||||
}
|
||||
|
||||
public void setAudioMode(AudioUtils.AudioMode newMode) {
|
||||
currentMode = newMode;
|
||||
updateView();
|
||||
}
|
||||
|
||||
private void updateView() {
|
||||
// The various layers of artwork for this button come from
|
||||
// redphone_btn_compound_audio.xmlaudio.xml. Keep track of which layers we want to be
|
||||
// visible:
|
||||
//
|
||||
// - This selector shows the blue bar below the button icon when
|
||||
// this button is a toggle *and* it's currently "checked".
|
||||
boolean showToggleStateIndication = false;
|
||||
//
|
||||
// - This is visible if the popup menu is enabled:
|
||||
boolean showMoreIndicator = false;
|
||||
//
|
||||
// - Foreground icons for the button. Exactly one of these is enabled:
|
||||
boolean showSpeakerOnIcon = false;
|
||||
boolean showSpeakerOffIcon = false;
|
||||
boolean showHandsetIcon = false;
|
||||
boolean showHeadsetIcon = false;
|
||||
|
||||
boolean speakerOn = currentMode == AudioUtils.AudioMode.SPEAKER;
|
||||
|
||||
if (headsetAvailable) {
|
||||
mAudioButton.setEnabled(true);
|
||||
|
||||
// The audio button is NOT a toggle in this state. (And its
|
||||
// setChecked() state is irrelevant since we completely hide the
|
||||
// redphone_btn_compound_background layer anyway.)
|
||||
|
||||
// Update desired layers:
|
||||
showMoreIndicator = true;
|
||||
Log.d(TAG, "UI Mode: " + currentMode);
|
||||
if (currentMode == AudioUtils.AudioMode.HEADSET) {
|
||||
showHeadsetIcon = true;
|
||||
} else if (speakerOn) {
|
||||
showSpeakerOnIcon = true;
|
||||
} else {
|
||||
showHandsetIcon = true;
|
||||
}
|
||||
} else {
|
||||
mAudioButton.setEnabled(true);
|
||||
|
||||
mAudioButton.setChecked(speakerOn);
|
||||
showSpeakerOnIcon = speakerOn;
|
||||
showSpeakerOffIcon = !speakerOn;
|
||||
|
||||
showToggleStateIndication = true;
|
||||
}
|
||||
|
||||
final int HIDDEN = 0;
|
||||
final int VISIBLE = 255;
|
||||
|
||||
LayerDrawable layers = (LayerDrawable) mAudioButton.getBackground();
|
||||
|
||||
layers.findDrawableByLayerId(R.id.compoundBackgroundItem)
|
||||
.setAlpha(showToggleStateIndication ? VISIBLE : HIDDEN);
|
||||
|
||||
layers.findDrawableByLayerId(R.id.moreIndicatorItem)
|
||||
.setAlpha(showMoreIndicator ? VISIBLE : HIDDEN);
|
||||
|
||||
layers.findDrawableByLayerId(R.id.bluetoothItem)
|
||||
.setAlpha(showHeadsetIcon ? VISIBLE : HIDDEN);
|
||||
|
||||
layers.findDrawableByLayerId(R.id.handsetItem)
|
||||
.setAlpha(showHandsetIcon ? VISIBLE : HIDDEN);
|
||||
|
||||
layers.findDrawableByLayerId(R.id.speakerphoneOnItem)
|
||||
.setAlpha(showSpeakerOnIcon ? VISIBLE : HIDDEN);
|
||||
|
||||
layers.findDrawableByLayerId(R.id.speakerphoneOffItem)
|
||||
.setAlpha(showSpeakerOffIcon ? VISIBLE : HIDDEN);
|
||||
|
||||
mAudioButton.invalidate();
|
||||
}
|
||||
|
||||
private void log(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
}
|
||||
|
||||
public void setListener(final CallControls.AudioButtonListener listener) {
|
||||
this.listener = listener;
|
||||
mAudioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
|
||||
if(headsetAvailable) {
|
||||
displayAudioChoiceDialog();
|
||||
} else {
|
||||
currentMode = b ? AudioUtils.AudioMode.SPEAKER : DEFAULT;
|
||||
listener.onAudioChange(currentMode);
|
||||
updateView();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayAudioChoiceDialog() {
|
||||
Log.w(TAG, "Displaying popup...");
|
||||
PopupMenu popupMenu = new PopupMenu(context, mAudioButton);
|
||||
popupMenu.getMenuInflater().inflate(R.menu.redphone_audio_popup_menu, popupMenu.getMenu());
|
||||
popupMenu.setOnMenuItemClickListener(new AudioRoutingPopupListener());
|
||||
popupMenu.show();
|
||||
}
|
||||
|
||||
private class AudioRoutingPopupListener implements PopupMenu.OnMenuItemClickListener {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.handset:
|
||||
currentMode = DEFAULT;
|
||||
break;
|
||||
case R.id.headset:
|
||||
currentMode = HEADSET;
|
||||
break;
|
||||
case R.id.speaker:
|
||||
currentMode = SPEAKER;
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Unknown item selected in audio popup menu: " + item.toString());
|
||||
}
|
||||
Log.d(TAG, "Selected: " + currentMode + " -- " + item.getItemId());
|
||||
|
||||
listener.onAudioChange(currentMode);
|
||||
updateView();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Moxie Marlinspike
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.ui;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
|
||||
import org.thoughtcrime.redphone.RedPhone;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
/**
|
||||
* Manages the state of the RedPhone items in the Android notification bar.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class NotificationBarManager {
|
||||
|
||||
public static final int RED_PHONE_NOTIFICATION = 313388;
|
||||
|
||||
public static final int TYPE_INCOMING_RINGING = 1;
|
||||
public static final int TYPE_OUTGOING_RINGING = 2;
|
||||
public static final int TYPE_ESTABLISHED = 3;
|
||||
|
||||
public static void setCallEnded(Context context) {
|
||||
NotificationManager notificationManager = (NotificationManager)context
|
||||
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
notificationManager.cancel(RED_PHONE_NOTIFICATION);
|
||||
}
|
||||
|
||||
public static void setCallInProgress(Context context, int type, Recipient recipient) {
|
||||
NotificationManager notificationManager = (NotificationManager)context
|
||||
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
Intent contentIntent = new Intent(context, RedPhone.class);
|
||||
contentIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, 0);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
|
||||
.setSmallIcon(R.drawable.ic_call_secure_white_24dp)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(true)
|
||||
.setContentTitle(recipient.getName());
|
||||
|
||||
if (type == TYPE_INCOMING_RINGING) {
|
||||
builder.setContentText(context.getString(R.string.NotificationBarManager__incoming_signal_call));
|
||||
builder.addAction(getNotificationAction(context, RedPhone.DENY_ACTION, R.drawable.ic_close_grey600_32dp, R.string.NotificationBarManager__deny_call));
|
||||
builder.addAction(getNotificationAction(context, RedPhone.ANSWER_ACTION, R.drawable.ic_phone_grey600_32dp, R.string.NotificationBarManager__answer_call));
|
||||
} else if (type == TYPE_OUTGOING_RINGING) {
|
||||
builder.setContentText(context.getString(R.string.NotificationBarManager__establishing_signal_call));
|
||||
builder.addAction(getNotificationAction(context, RedPhone.END_CALL_ACTION, R.drawable.ic_call_end_grey600_32dp, R.string.NotificationBarManager__cancel_call));
|
||||
} else {
|
||||
builder.setContentText(context.getString(R.string.NotificationBarManager_signal_call_in_progress));
|
||||
builder.addAction(getNotificationAction(context, RedPhone.END_CALL_ACTION, R.drawable.ic_call_end_grey600_32dp, R.string.NotificationBarManager__end_call));
|
||||
}
|
||||
|
||||
notificationManager.notify(RED_PHONE_NOTIFICATION, builder.build());
|
||||
}
|
||||
|
||||
private static NotificationCompat.Action getNotificationAction(Context context, String action, int iconResId, int titleResId) {
|
||||
Intent intent = new Intent(context, RedPhone.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent.setAction(action);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
|
||||
return new NotificationCompat.Action(iconResId, context.getString(titleResId), pendingIntent);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package org.thoughtcrime.redphone.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
|
||||
/**
|
||||
* Utilities for manipulating device audio configuration
|
||||
*
|
||||
* @author Stuart O. Anderson
|
||||
*/
|
||||
public class AudioUtils {
|
||||
private static final String TAG = AudioUtils.class.getName();
|
||||
public static void enableDefaultRouting(Context context) {
|
||||
AudioManager am = ServiceUtil.getAudioManager(context);
|
||||
am.setSpeakerphoneOn(false);
|
||||
am.setBluetoothScoOn(false);
|
||||
Log.d(TAG, "Set default audio routing");
|
||||
}
|
||||
|
||||
public static void enableSpeakerphoneRouting(Context context) {
|
||||
AudioManager am = ServiceUtil.getAudioManager(context);
|
||||
am.setSpeakerphoneOn(true);
|
||||
Log.d(TAG, "Set speakerphone audio routing");
|
||||
}
|
||||
|
||||
public static void enableBluetoothRouting(Context context) {
|
||||
AudioManager am = ServiceUtil.getAudioManager(context);
|
||||
am.startBluetoothSco();
|
||||
am.setBluetoothScoOn(true);
|
||||
Log.d(TAG, "Set bluetooth audio routing");
|
||||
}
|
||||
|
||||
public static void resetConfiguration(Context context) {
|
||||
enableDefaultRouting(context);
|
||||
}
|
||||
|
||||
public static enum AudioMode {
|
||||
DEFAULT,
|
||||
HEADSET,
|
||||
SPEAKER,
|
||||
}
|
||||
|
||||
public static AudioMode getCurrentAudioMode(Context context) {
|
||||
AudioManager am = ServiceUtil.getAudioManager(context);
|
||||
if (am.isBluetoothScoOn()) {
|
||||
return AudioMode.HEADSET;
|
||||
} else if (am.isSpeakerphoneOn()) {
|
||||
return AudioMode.SPEAKER;
|
||||
} else {
|
||||
return AudioMode.DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getScoUpdateAction() {
|
||||
if (Build.VERSION.SDK_INT >= 14) {
|
||||
return AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED;
|
||||
} else {
|
||||
return AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getWiredHeadsetUpdateAction() {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
return AudioManager.ACTION_HEADSET_PLUG;
|
||||
} else {
|
||||
return Intent.ACTION_HEADSET_PLUG;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.util;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Utility class for doing conversions between various primitive
|
||||
* types. (Mostly intergal types to and from byte arrays).
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class Conversions {
|
||||
|
||||
public static byte[] combine(byte[] first, byte[] second) {
|
||||
byte[] combined = new byte[first.length + second.length];
|
||||
|
||||
System.arraycopy(first, 0, combined, 0, first.length);
|
||||
System.arraycopy(second, 0, combined, first.length, second.length);
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
public static void bigIntegerToByteArray(byte[] bytes, BigInteger value) {
|
||||
BigInteger mask = BigInteger.valueOf(0xFF);
|
||||
|
||||
for (int i=bytes.length-1;i>=0;i--) {
|
||||
bytes[i] = (byte)value.shiftRight((bytes.length-1-i) * 8).and(mask).intValue();
|
||||
}
|
||||
}
|
||||
|
||||
public static BigInteger byteArrayToBigInteger(byte[] bytes) {
|
||||
BigInteger value = BigInteger.valueOf(bytes[0]).and(BigInteger.valueOf(0xFF));
|
||||
|
||||
for (int i=1;i<bytes.length;i++) {
|
||||
value = value.shiftLeft(8);
|
||||
value = value.or(BigInteger.valueOf(bytes[i]).and(BigInteger.valueOf(0xFF)) );
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void stringToByteArray(byte[] bytes, int offset, String value) {
|
||||
for (int i=0,x=value.length();i<x;i++) {
|
||||
char index = value.charAt(i);
|
||||
bytes[offset+i] = (byte)index;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] stringToByteArray(String value) {
|
||||
byte[] results = new byte[value.length()];
|
||||
stringToByteArray(results, 0, value);
|
||||
return results;
|
||||
}
|
||||
|
||||
public static byte[] shortToByteArray(int value) {
|
||||
byte[] bytes = new byte[2];
|
||||
shortToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int shortToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset+1] = (byte)value;
|
||||
bytes[offset] = (byte)(value >> 8);
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static int shortToLittleEndianByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset] = (byte)value;
|
||||
bytes[offset+1] = (byte)(value >> 8);
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static int mediumToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset + 2] = (byte)value;
|
||||
bytes[offset + 1] = (byte)(value >> 8);
|
||||
bytes[offset] = (byte)(value >> 16);
|
||||
return 3;
|
||||
}
|
||||
|
||||
public static byte[] intToByteArray(int value) {
|
||||
byte[] bytes = new byte[4];
|
||||
intToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int intToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset + 3] = (byte)value;
|
||||
bytes[offset + 2] = (byte)(value >> 8);
|
||||
bytes[offset + 1] = (byte)(value >> 16);
|
||||
bytes[offset] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static byte setBit( byte b, int offset, boolean bit ) {
|
||||
if( bit )
|
||||
return (byte) (b | ( 1 << offset ));
|
||||
else
|
||||
return (byte) (b & ( 0xFF ^ (1 << offset) ) );
|
||||
}
|
||||
|
||||
public static boolean getBit( byte b, int offset ) {
|
||||
if ( (( b >> offset ) & 0x01) != 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int intToLittleEndianByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset] = (byte)value;
|
||||
bytes[offset+1] = (byte)(value >> 8);
|
||||
bytes[offset+2] = (byte)(value >> 16);
|
||||
bytes[offset+3] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static byte[] longToByteArray(long l) {
|
||||
byte[] bytes = new byte[8];
|
||||
longToByteArray(bytes, 0, l);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static long byteArray4ToLong( byte[] bytes, int offset) {
|
||||
return
|
||||
((bytes[offset + 0] & 0xffL) << 24) |
|
||||
((bytes[offset + 1] & 0xffL) << 16) |
|
||||
((bytes[offset + 2] & 0xffL) << 8) |
|
||||
((bytes[offset + 3] & 0xffL));
|
||||
}
|
||||
|
||||
public static void shortsToBytes( short[] shorts, byte[] bytes, int numShorts ) {
|
||||
for( int i=0; i < numShorts; i++ ) {
|
||||
shortToByteArray(bytes, i*2, shorts[i] );
|
||||
}
|
||||
}
|
||||
|
||||
public static void bytesToShorts( byte[] bytes, short[] shorts, int numShorts ) {
|
||||
for( int i=0; i < numShorts; i++ ) {
|
||||
shorts[i] = (short) byteArrayToShort( bytes, i*2 );
|
||||
}
|
||||
}
|
||||
|
||||
public static int longTo4ByteArray(byte[] bytes, int offset, long value) {
|
||||
bytes[offset + 3] = (byte)value;
|
||||
bytes[offset + 2] = (byte)(value >> 8);
|
||||
bytes[offset + 1] = (byte)(value >> 16);
|
||||
bytes[offset + 0] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static int longToByteArray(byte[] bytes, int offset, long value) {
|
||||
bytes[offset + 7] = (byte)value;
|
||||
bytes[offset + 6] = (byte)(value >> 8);
|
||||
bytes[offset + 5] = (byte)(value >> 16);
|
||||
bytes[offset + 4] = (byte)(value >> 24);
|
||||
bytes[offset + 3] = (byte)(value >> 32);
|
||||
bytes[offset + 2] = (byte)(value >> 40);
|
||||
bytes[offset + 1] = (byte)(value >> 48);
|
||||
bytes[offset] = (byte)(value >> 56);
|
||||
return 8;
|
||||
}
|
||||
|
||||
public static int byteArrayToShort(byte[] bytes) {
|
||||
return byteArrayToShort(bytes, 0);
|
||||
}
|
||||
|
||||
public static short byteArrayToShort(byte[] bytes, int offset) {
|
||||
return
|
||||
(short) ((bytes[offset] & 0xff) << 8 | (bytes[offset + 1] & 0xff));
|
||||
}
|
||||
|
||||
// The SSL patented 3-byte Value.
|
||||
public static int byteArrayToMedium(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 16 |
|
||||
(bytes[offset + 1] & 0xff) << 8 |
|
||||
(bytes[offset + 2] & 0xff);
|
||||
}
|
||||
|
||||
public static int byteArrayToInt(byte[] bytes) {
|
||||
return byteArrayToInt(bytes, 0);
|
||||
}
|
||||
|
||||
public static int byteArrayToInt(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 24 |
|
||||
(bytes[offset + 1] & 0xff) << 16 |
|
||||
(bytes[offset + 2] & 0xff) << 8 |
|
||||
(bytes[offset + 3] & 0xff);
|
||||
}
|
||||
|
||||
public static long byteArrayToLong(byte[] bytes) {
|
||||
return byteArrayToLong(bytes, 0);
|
||||
}
|
||||
|
||||
public static long byteArrayToLong(byte[] bytes, int offset) {
|
||||
return
|
||||
((bytes[offset] & 0xffL) << 56) |
|
||||
((bytes[offset + 1] & 0xffL) << 48) |
|
||||
((bytes[offset + 2] & 0xffL) << 40) |
|
||||
((bytes[offset + 3] & 0xffL) << 32) |
|
||||
((bytes[offset + 4] & 0xffL) << 24) |
|
||||
((bytes[offset + 5] & 0xffL) << 16) |
|
||||
((bytes[offset + 6] & 0xffL) << 8) |
|
||||
((bytes[offset + 7] & 0xffL));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.util;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* Reads lines off an InputStream. Seems like they have BufferedReaders
|
||||
* for that, but I'm sure I had a reason to write my own.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class LineReader {
|
||||
|
||||
private final InputStream in;
|
||||
private ByteArrayOutputStream baos;
|
||||
|
||||
public LineReader(InputStream in) {
|
||||
this.in = in;
|
||||
this.baos = new ByteArrayOutputStream();
|
||||
}
|
||||
|
||||
private byte[] chompLine(byte[] buffer, int newlineIndex, int bufferLength) {
|
||||
byte[] line = new byte[newlineIndex];
|
||||
System.arraycopy(buffer, 0, line, 0, newlineIndex);
|
||||
|
||||
Log.w("LineReader", "Chomped line: " + Hex.toString(line));
|
||||
Log.w("LineReader", "Buffer length: " + bufferLength);
|
||||
|
||||
if ((newlineIndex+2) < bufferLength) {
|
||||
Log.w("LineReader", "Writing remaining to buffer, offset: " + (newlineIndex + 2) +
|
||||
" Length: " + (bufferLength - (newlineIndex + 2)));
|
||||
baos.write(buffer, newlineIndex+2, bufferLength-(newlineIndex+2));
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
private int findNewline(byte[] buffer, int offset, int length) {
|
||||
for (int i=offset;i<length;i++) {
|
||||
if (buffer[i] == (byte)0x0A && i>0 && buffer[i-1] == (byte)0x0D)
|
||||
return i-1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean waitForAvailable() throws IOException {
|
||||
try {
|
||||
byte[] buffer = new byte[500];
|
||||
int read = in.read(buffer);
|
||||
|
||||
if (read <= 0)
|
||||
return false;
|
||||
|
||||
baos.write(buffer, 0, read);
|
||||
return true;
|
||||
} catch (InterruptedIOException iie) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String readLine() throws IOException {
|
||||
byte[] buffer = new byte[4096];
|
||||
int read = 0;
|
||||
|
||||
do {
|
||||
baos.write(buffer, 0, read);
|
||||
byte[] bufferedBytes = baos.toByteArray();
|
||||
int newlineIndex = 0;
|
||||
|
||||
if ((newlineIndex = findNewline(bufferedBytes, 0, bufferedBytes.length)) != -1) {
|
||||
baos.reset();
|
||||
return new String(chompLine(bufferedBytes, newlineIndex, bufferedBytes.length), "UTF8");
|
||||
}
|
||||
} while ((read = in.read(buffer)) != -1);
|
||||
|
||||
throw new IOException("Stream closed before newline found...");
|
||||
}
|
||||
|
||||
public byte[] readFully(int size) throws IOException {
|
||||
byte[] buffer = new byte[size];
|
||||
int remaining = size;
|
||||
|
||||
if (baos.size() > 0) {
|
||||
byte[] bufferedBytes = baos.toByteArray();
|
||||
int toCopy = Math.min(size, bufferedBytes.length);
|
||||
remaining -= toCopy;
|
||||
|
||||
System.arraycopy(bufferedBytes, 0, buffer, 0, toCopy);
|
||||
|
||||
baos.reset();
|
||||
baos.write(bufferedBytes, toCopy, bufferedBytes.length-toCopy);
|
||||
}
|
||||
|
||||
while (remaining > 0) {
|
||||
int read = in.read(buffer, size-remaining, remaining);
|
||||
|
||||
if (read == -1)
|
||||
throw new IOException("Socket closed before buffer filled...");
|
||||
|
||||
remaining -= read;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.util;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.text.Editable;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* Random utility functions.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class Util {
|
||||
|
||||
public static void sleep(long millis) {
|
||||
try {
|
||||
Thread.sleep(millis);
|
||||
} catch (InterruptedException ie) {
|
||||
throw new AssertionError(ie);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getBytes(String fromString) {
|
||||
try {
|
||||
return fromString.getBytes("UTF8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getString(byte[] fromBytes) {
|
||||
try {
|
||||
return new String(fromBytes, "UTF8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isEmpty(String value) {
|
||||
return (value == null || value.trim().length() == 0);
|
||||
}
|
||||
|
||||
public static boolean isEmpty(CharSequence value) {
|
||||
return value == null || isEmpty(value.toString());
|
||||
}
|
||||
|
||||
public static boolean isEmpty(Editable value) {
|
||||
return value == null || isEmpty(value.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,9 +19,6 @@ package org.thoughtcrime.securesms;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.StrictMode;
|
||||
import android.os.StrictMode.ThreadPolicy;
|
||||
import android.os.StrictMode.VmPolicy;
|
||||
import android.support.multidex.MultiDexApplication;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -30,7 +27,6 @@ import com.google.android.gms.security.ProviderInstaller;
|
||||
import org.thoughtcrime.securesms.crypto.PRNGFixes;
|
||||
import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.dependencies.RedPhoneCommunicationModule;
|
||||
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
|
||||
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
||||
import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
|
||||
@@ -137,7 +133,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
|
||||
private void initializeDependencyInjection() {
|
||||
this.objectGraph = ObjectGraph.create(new SignalCommunicationModule(this, new SignalServiceNetworkAccess(this)),
|
||||
new RedPhoneCommunicationModule(this),
|
||||
new AxolotlStorageModule(this));
|
||||
}
|
||||
|
||||
@@ -167,7 +162,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
}
|
||||
|
||||
private void initializeSetVideoCapable() {
|
||||
if (!TextSecurePreferences.isWebrtcCallingEnabled(this)) {
|
||||
if (TextSecurePreferences.isPushRegistered(this) &&
|
||||
!TextSecurePreferences.isWebrtcCallingEnabled(this))
|
||||
{
|
||||
TextSecurePreferences.setWebrtcCallingEnabled(this, true);
|
||||
jobManager.add(new RefreshAttributesJob(this));
|
||||
}
|
||||
|
||||
@@ -69,8 +69,6 @@ import com.google.protobuf.ByteString;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.thoughtcrime.redphone.RedPhone;
|
||||
import org.thoughtcrime.redphone.RedPhoneService;
|
||||
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
|
||||
import org.thoughtcrime.securesms.audio.AudioRecorder;
|
||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
||||
@@ -231,7 +229,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private int distributionType;
|
||||
private boolean archived;
|
||||
private boolean isSecureText;
|
||||
private boolean isSecureVideo;
|
||||
private boolean isDefaultSms = true;
|
||||
private boolean isMmsEnabled = true;
|
||||
|
||||
@@ -259,13 +256,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
initializeActionBar();
|
||||
initializeViews();
|
||||
initializeResources();
|
||||
initializeSecurity(false, false, isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
|
||||
initializeSecurity(false, isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
initializeDraft();
|
||||
}
|
||||
});
|
||||
initializeBetaCalling();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -285,7 +281,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
setIntent(intent);
|
||||
initializeResources();
|
||||
initializeSecurity(false, false, isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
|
||||
initializeSecurity(false, isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
initializeDraft();
|
||||
@@ -415,7 +411,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
setMedia(data.getData(), MediaType.IMAGE);
|
||||
break;
|
||||
case SMS_DEFAULT:
|
||||
initializeSecurity(isSecureText, isSecureText, isDefaultSms);
|
||||
initializeSecurity(isSecureText, isDefaultSms);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -777,9 +773,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private void handleDial(final Recipient recipient) {
|
||||
if (recipient == null) return;
|
||||
|
||||
if ((isSecureVideo && TextSecurePreferences.isWebrtcCallingEnabled(this)) ||
|
||||
(isSecureText && TextSecurePreferences.isGcmDisabled(this)))
|
||||
{
|
||||
if (isSecureText) {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_NUMBER, recipient.getNumber());
|
||||
@@ -788,15 +782,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
Intent activityIntent = new Intent(this, WebRtcCallActivity.class);
|
||||
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(activityIntent);
|
||||
} else if (isSecureText) {
|
||||
Intent intent = new Intent(this, RedPhoneService.class);
|
||||
intent.setAction(RedPhoneService.ACTION_OUTGOING_CALL);
|
||||
intent.putExtra(RedPhoneService.EXTRA_REMOTE_NUMBER, recipient.getNumber());
|
||||
startService(intent);
|
||||
|
||||
Intent activityIntent = new Intent(this, RedPhone.class);
|
||||
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(activityIntent);
|
||||
} else {
|
||||
try {
|
||||
Intent dialIntent = new Intent(Intent.ACTION_DIAL,
|
||||
@@ -845,9 +830,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void handleSecurityChange(boolean isSecureText, boolean isSecureVideo, boolean isDefaultSms) {
|
||||
private void handleSecurityChange(boolean isSecureText, boolean isDefaultSms) {
|
||||
this.isSecureText = isSecureText;
|
||||
this.isSecureVideo = isSecureVideo;
|
||||
this.isDefaultSms = isDefaultSms;
|
||||
|
||||
boolean isMediaMessage = !recipients.isSingleRecipient() || attachmentManager.isAttachmentPresent();
|
||||
@@ -928,14 +912,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> initializeSecurity(final boolean currentSecureText,
|
||||
final boolean currentSecureVideo,
|
||||
final boolean currentIsDefaultSms)
|
||||
{
|
||||
final SettableFuture<Boolean> future = new SettableFuture<>();
|
||||
|
||||
handleSecurityChange(currentSecureText || isPushGroupConversation(),
|
||||
currentSecureVideo && !isGroupConversation(),
|
||||
currentIsDefaultSms);
|
||||
handleSecurityChange(currentSecureText || isPushGroupConversation(), currentIsDefaultSms);
|
||||
|
||||
new AsyncTask<Recipients, Void, boolean[]>() {
|
||||
@Override
|
||||
@@ -955,15 +936,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
return new boolean[] {capabilities.getTextCapability() == Capability.SUPPORTED,
|
||||
capabilities.getVideoCapability() == Capability.SUPPORTED && !isSelfConversation(),
|
||||
Util.isDefaultSmsProvider(context)};
|
||||
return new boolean[] {capabilities.getTextCapability() == Capability.SUPPORTED, Util.isDefaultSmsProvider(context)};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(boolean[] result) {
|
||||
if (result[0] != currentSecureText || result[1] != currentSecureVideo || result[2] != currentIsDefaultSms) {
|
||||
handleSecurityChange(result[0], result[1], result[2]);
|
||||
if (result[0] != currentSecureText || result[1] != currentIsDefaultSms) {
|
||||
handleSecurityChange(result[0], result[1]);
|
||||
}
|
||||
future.set(true);
|
||||
onSecurityUpdated();
|
||||
@@ -973,42 +952,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
return future;
|
||||
}
|
||||
|
||||
private void initializeBetaCalling() {
|
||||
if (!TextSecurePreferences.isPushRegistered(this) ||
|
||||
!TextSecurePreferences.isWebrtcCallingEnabled(this) ||
|
||||
isGroupConversation())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Context context = ConversationActivity.this;
|
||||
UserCapabilities userCapabilities = DirectoryHelper.refreshDirectoryFor(context, masterSecret, recipients,
|
||||
TextSecurePreferences.getLocalNumber(context));
|
||||
|
||||
|
||||
final boolean secureText = userCapabilities.getTextCapability() == Capability.SUPPORTED;
|
||||
final boolean secureVideo = userCapabilities.getVideoCapability() == Capability.SUPPORTED;
|
||||
final boolean defaultSms = Util.isDefaultSmsProvider(context);
|
||||
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (secureText != isSecureText || secureVideo != isSecureVideo || defaultSms != isDefaultSms) {
|
||||
handleSecurityChange(secureText, secureVideo, defaultSms);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void onSecurityUpdated() {
|
||||
updateRecipientPreferences();
|
||||
}
|
||||
@@ -1202,7 +1145,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
securityUpdateReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
initializeSecurity(isSecureText, isSecureVideo, isDefaultSms);
|
||||
initializeSecurity(isSecureText, isDefaultSms);
|
||||
calculateCharactersRemaining();
|
||||
}
|
||||
};
|
||||
@@ -1865,7 +1808,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
public void onAttachmentChanged() {
|
||||
handleSecurityChange(isSecureText, isSecureVideo, isDefaultSms);
|
||||
handleSecurityChange(isSecureText, isDefaultSms);
|
||||
updateToggleButtonState();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import android.view.View.OnLongClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.redphone.util.Conversions;
|
||||
import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
@@ -41,6 +40,7 @@ import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.Conversions;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.LRUCache;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
|
||||
@@ -27,13 +27,13 @@ import android.view.View.OnClickListener;
|
||||
import android.view.View.OnLongClickListener;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.thoughtcrime.redphone.util.Conversions;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.util.Conversions;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thoughtcrime.redphone.util.multiwaveview;
|
||||
package org.thoughtcrime.securesms.components.multiwaveview;
|
||||
|
||||
|
||||
import android.animation.TimeInterpolator;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.util.multiwaveview;
|
||||
package org.thoughtcrime.securesms.components.multiwaveview;
|
||||
|
||||
//import android.animation.Animator;
|
||||
//import android.animation.Animator.AnimatorListener;
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thoughtcrime.redphone.util.multiwaveview;
|
||||
package org.thoughtcrime.securesms.components.multiwaveview;
|
||||
|
||||
|
||||
import android.content.res.Resources;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.redphone.util.multiwaveview;
|
||||
package org.thoughtcrime.securesms.components.multiwaveview;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.Animator.AnimatorListener;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user