So long redphone

// FREEBIE
This commit is contained in:
Moxie Marlinspike
2017-03-14 13:24:24 -07:00
parent 2f46c6ca1f
commit 94964474b2
2436 changed files with 37 additions and 793280 deletions

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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];
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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";
}
}

View File

@@ -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);
}
}

View File

@@ -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];
}
};
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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];
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -1,9 +0,0 @@
package org.thoughtcrime.redphone.signaling;
import java.io.IOException;
public class UnauthorizedException extends IOException {
public UnauthorizedException(String s) {
super(s);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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)
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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));
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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