mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-11 16:43:39 +00:00
WIP: clean up unused signal stuff
This commit is contained in:
parent
c138f20be5
commit
b2225697b4
@ -32,7 +32,6 @@ import androidx.multidex.MultiDexApplication;
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
|
||||
import org.conscrypt.Conscrypt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.session.libsession.messaging.MessagingConfiguration;
|
||||
import org.session.libsession.messaging.avatars.AvatarHelper;
|
||||
import org.session.libsession.utilities.SSKEnvironment;
|
||||
@ -49,7 +48,6 @@ import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager;
|
||||
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@ -127,6 +125,8 @@ import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant;
|
||||
*/
|
||||
public class ApplicationContext extends MultiDexApplication implements DependencyInjector, DefaultLifecycleObserver {
|
||||
|
||||
public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
|
||||
|
||||
private static final String TAG = ApplicationContext.class.getSimpleName();
|
||||
|
||||
private ExpiringMessageManager expiringMessageManager;
|
||||
@ -535,7 +535,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
TextSecurePreferences.setIsUsingFCM(this, isUsingFCM);
|
||||
TextSecurePreferences.setProfileName(this, displayName);
|
||||
}
|
||||
MasterSecretUtil.clear(this);
|
||||
getSharedPreferences(PREFERENCES_NAME, 0).edit().clear().commit();
|
||||
if (!deleteDatabase("signal.db")) {
|
||||
Log.d("Loki", "Failed to delete database.");
|
||||
}
|
||||
|
@ -9,8 +9,6 @@ import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
@ -29,11 +27,9 @@ import android.widget.Toast;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
|
||||
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationStickerSuggestionAdapter;
|
||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
|
||||
@ -48,7 +44,6 @@ import org.session.libsignal.utilities.concurrent.ListenableFuture;
|
||||
import org.session.libsignal.utilities.concurrent.SettableFuture;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
@ -56,15 +51,13 @@ import network.loki.messenger.R;
|
||||
public class InputPanel extends LinearLayout
|
||||
implements MicrophoneRecorderView.Listener,
|
||||
KeyboardAwareLinearLayout.OnKeyboardShownListener,
|
||||
EmojiKeyboardProvider.EmojiEventListener,
|
||||
ConversationStickerSuggestionAdapter.EventListener
|
||||
EmojiKeyboardProvider.EmojiEventListener
|
||||
{
|
||||
|
||||
private static final String TAG = InputPanel.class.getSimpleName();
|
||||
|
||||
private static final int FADE_TIME = 150;
|
||||
|
||||
private RecyclerView stickerSuggestion;
|
||||
private QuoteView quoteView;
|
||||
private LinkPreviewView linkPreview;
|
||||
private EmojiToggle mediaKeyboard;
|
||||
@ -82,8 +75,6 @@ public class InputPanel extends LinearLayout
|
||||
private @Nullable Listener listener;
|
||||
private boolean emojiVisible;
|
||||
|
||||
private ConversationStickerSuggestionAdapter stickerSuggestionAdapter;
|
||||
|
||||
public InputPanel(Context context) {
|
||||
super(context);
|
||||
}
|
||||
@ -103,7 +94,6 @@ public class InputPanel extends LinearLayout
|
||||
|
||||
View quoteDismiss = findViewById(R.id.quote_dismiss);
|
||||
|
||||
this.stickerSuggestion = findViewById(R.id.input_panel_sticker_suggestion);
|
||||
this.quoteView = findViewById(R.id.quote_view);
|
||||
this.linkPreview = findViewById(R.id.link_preview);
|
||||
this.mediaKeyboard = findViewById(R.id.emoji_toggle);
|
||||
@ -139,11 +129,6 @@ public class InputPanel extends LinearLayout
|
||||
listener.onLinkPreviewCanceled();
|
||||
}
|
||||
});
|
||||
|
||||
stickerSuggestionAdapter = new ConversationStickerSuggestionAdapter(GlideApp.with(this), this);
|
||||
|
||||
stickerSuggestion.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
|
||||
stickerSuggestion.setAdapter(stickerSuggestionAdapter);
|
||||
}
|
||||
|
||||
public void setListener(final @NonNull Listener listener) {
|
||||
@ -206,24 +191,6 @@ public class InputPanel extends LinearLayout
|
||||
this.mediaKeyboard.attach(mediaKeyboard);
|
||||
}
|
||||
|
||||
public void setStickerSuggestions(@NonNull List<StickerRecord> stickers) {
|
||||
stickerSuggestion.setVisibility(stickers.isEmpty() ? View.GONE : View.VISIBLE);
|
||||
stickerSuggestionAdapter.setStickers(stickers);
|
||||
}
|
||||
|
||||
public void showMediaKeyboardToggle(boolean show) {
|
||||
emojiVisible = show;
|
||||
mediaKeyboard.setVisibility(show ? View.VISIBLE : GONE);
|
||||
}
|
||||
|
||||
public void setMediaKeyboardToggleMode(boolean isSticker) {
|
||||
mediaKeyboard.setStickerMode(isSticker);
|
||||
}
|
||||
|
||||
public View getMediaKeyboardToggleAnchorView() {
|
||||
return mediaKeyboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordPermissionRequired() {
|
||||
if (listener != null) listener.onRecorderPermissionRequired();
|
||||
@ -335,13 +302,6 @@ public class InputPanel extends LinearLayout
|
||||
composeText.insertEmoji(emoji);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStickerSuggestionClicked(@NonNull StickerRecord sticker) {
|
||||
if (listener != null) {
|
||||
listener.onStickerSuggestionSelected(sticker);
|
||||
}
|
||||
}
|
||||
|
||||
private int readDimen(@DimenRes int dimenRes) {
|
||||
return getResources().getDimensionPixelSize(dimenRes);
|
||||
}
|
||||
|
@ -1,125 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
||||
import org.session.libsession.utilities.ViewUtil;
|
||||
|
||||
public class ThreadPhotoRailView extends FrameLayout {
|
||||
|
||||
@NonNull private final RecyclerView recyclerView;
|
||||
@Nullable private OnItemClickedListener listener;
|
||||
|
||||
public ThreadPhotoRailView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ThreadPhotoRailView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public ThreadPhotoRailView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
inflate(context, R.layout.recipient_preference_photo_rail, this);
|
||||
|
||||
this.recyclerView = ViewUtil.findById(this, R.id.photo_list);
|
||||
this.recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
|
||||
this.recyclerView.setItemAnimator(new DefaultItemAnimator());
|
||||
this.recyclerView.setNestedScrollingEnabled(false);
|
||||
}
|
||||
|
||||
public void setListener(@Nullable OnItemClickedListener listener) {
|
||||
this.listener = listener;
|
||||
|
||||
if (this.recyclerView.getAdapter() != null) {
|
||||
((ThreadPhotoRailAdapter)this.recyclerView.getAdapter()).setListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void setCursor(@NonNull GlideRequests glideRequests, @Nullable Cursor cursor) {
|
||||
this.recyclerView.setAdapter(new ThreadPhotoRailAdapter(getContext(), glideRequests, cursor, this.listener));
|
||||
}
|
||||
|
||||
private static class ThreadPhotoRailAdapter extends CursorRecyclerViewAdapter<ThreadPhotoRailAdapter.ThreadPhotoViewHolder> {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = ThreadPhotoRailAdapter.class.getSimpleName();
|
||||
|
||||
@NonNull private final GlideRequests glideRequests;
|
||||
|
||||
@Nullable private OnItemClickedListener clickedListener;
|
||||
|
||||
private ThreadPhotoRailAdapter(@NonNull Context context,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable Cursor cursor,
|
||||
@Nullable OnItemClickedListener listener)
|
||||
{
|
||||
super(context, cursor);
|
||||
this.glideRequests = glideRequests;
|
||||
this.clickedListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadPhotoViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
||||
View itemView = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.recipient_preference_photo_rail_item, parent, false);
|
||||
|
||||
return new ThreadPhotoViewHolder(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(ThreadPhotoViewHolder viewHolder, @NonNull Cursor cursor) {
|
||||
ThumbnailView imageView = viewHolder.imageView;
|
||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), cursor);
|
||||
Slide slide = MediaUtil.getSlideForAttachment(getContext(), mediaRecord.getAttachment());
|
||||
|
||||
if (slide != null) {
|
||||
imageView.setImageResource(glideRequests, slide, false, false);
|
||||
}
|
||||
|
||||
imageView.setOnClickListener(v -> {
|
||||
if (clickedListener != null) clickedListener.onItemClicked(mediaRecord);
|
||||
});
|
||||
}
|
||||
|
||||
public void setListener(@Nullable OnItemClickedListener listener) {
|
||||
this.clickedListener = listener;
|
||||
}
|
||||
|
||||
static class ThreadPhotoViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
ThumbnailView imageView;
|
||||
|
||||
ThreadPhotoViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.imageView = ViewUtil.findById(itemView, R.id.thumbnail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnItemClickedListener {
|
||||
void onItemClicked(MediaDatabase.MediaRecord mediaRecord);
|
||||
}
|
||||
}
|
@ -1,226 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
|
||||
import org.session.libsession.utilities.ThemeUtil;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
/**
|
||||
* Class for creating simple tooltips to show throughout the app. Utilizes a popup window so you
|
||||
* don't have to worry about view hierarchies or anything.
|
||||
*/
|
||||
public class TooltipPopup extends PopupWindow {
|
||||
|
||||
public static final int POSITION_ABOVE = 0;
|
||||
public static final int POSITION_BELOW = 1;
|
||||
public static final int POSITION_START = 2;
|
||||
public static final int POSITION_END = 3;
|
||||
|
||||
private static final int POSITION_LEFT = 4;
|
||||
private static final int POSITION_RIGHT = 5;
|
||||
|
||||
private final View anchor;
|
||||
private final ImageView arrow;
|
||||
private final int position;
|
||||
|
||||
public static Builder forTarget(@NonNull View anchor) {
|
||||
return new Builder(anchor);
|
||||
}
|
||||
|
||||
private TooltipPopup(@NonNull View anchor,
|
||||
int rawPosition,
|
||||
@NonNull String text,
|
||||
@ColorInt int backgroundTint,
|
||||
@ColorInt int textColor,
|
||||
@Nullable Object iconGlideModel,
|
||||
@Nullable OnDismissListener dismissListener)
|
||||
{
|
||||
super(LayoutInflater.from(anchor.getContext()).inflate(R.layout.tooltip, null),
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
this.anchor = anchor;
|
||||
this.position = getRtlPosition(anchor.getContext(), rawPosition);
|
||||
|
||||
switch (rawPosition) {
|
||||
case POSITION_ABOVE: arrow = getContentView().findViewById(R.id.tooltip_arrow_bottom); break;
|
||||
case POSITION_BELOW: arrow = getContentView().findViewById(R.id.tooltip_arrow_top); break;
|
||||
case POSITION_START: arrow = getContentView().findViewById(R.id.tooltip_arrow_end); break;
|
||||
case POSITION_END: arrow = getContentView().findViewById(R.id.tooltip_arrow_start); break;
|
||||
default: throw new AssertionError("Invalid position!");
|
||||
}
|
||||
|
||||
arrow.setVisibility(View.VISIBLE);
|
||||
|
||||
TextView textView = getContentView().findViewById(R.id.tooltip_text);
|
||||
textView.setText(text);
|
||||
|
||||
if (textColor != 0) {
|
||||
textView.setTextColor(textColor);
|
||||
}
|
||||
|
||||
View bubble = getContentView().findViewById(R.id.tooltip_bubble);
|
||||
|
||||
if (backgroundTint == 0) {
|
||||
bubble.getBackground().setColorFilter(ThemeUtil.getThemedColor(anchor.getContext(), R.attr.tooltip_default_color), PorterDuff.Mode.MULTIPLY);
|
||||
arrow.setColorFilter(ThemeUtil.getThemedColor(anchor.getContext(), R.attr.tooltip_default_color), PorterDuff.Mode.MULTIPLY);
|
||||
} else {
|
||||
bubble.getBackground().setColorFilter(backgroundTint, PorterDuff.Mode.MULTIPLY);
|
||||
arrow.setColorFilter(backgroundTint, PorterDuff.Mode.MULTIPLY);
|
||||
}
|
||||
|
||||
if (iconGlideModel != null) {
|
||||
ImageView iconView = getContentView().findViewById(R.id.tooltip_icon);
|
||||
iconView.setVisibility(View.VISIBLE);
|
||||
GlideApp.with(anchor.getContext()).load(iconGlideModel).into(iconView);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
setElevation(10);
|
||||
}
|
||||
|
||||
getContentView().setOnClickListener(v -> dismiss());
|
||||
|
||||
setOnDismissListener(dismissListener);
|
||||
setBackgroundDrawable(null);
|
||||
setOutsideTouchable(true);
|
||||
}
|
||||
|
||||
private void show() {
|
||||
if (anchor.getWidth() == 0 && anchor.getHeight() == 0) {
|
||||
anchor.post(this::show);
|
||||
return;
|
||||
}
|
||||
|
||||
getContentView().measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
|
||||
int tooltipSpacing = anchor.getContext().getResources().getDimensionPixelOffset(R.dimen.tooltip_popup_margin);
|
||||
|
||||
int xoffset;
|
||||
int yoffset;
|
||||
|
||||
switch (position) {
|
||||
case POSITION_ABOVE:
|
||||
xoffset = 0;
|
||||
yoffset = -(2 * anchor.getWidth() + tooltipSpacing);
|
||||
onLayout(() -> setArrowHorizontalPosition(arrow, anchor));
|
||||
break;
|
||||
case POSITION_BELOW:
|
||||
xoffset = 0;
|
||||
yoffset = tooltipSpacing;
|
||||
onLayout(() -> setArrowHorizontalPosition(arrow, anchor));
|
||||
break;
|
||||
case POSITION_LEFT:
|
||||
xoffset = -getContentView().getMeasuredWidth() - tooltipSpacing;
|
||||
yoffset = -(getContentView().getMeasuredHeight()/2 + anchor.getHeight()/2);
|
||||
break;
|
||||
case POSITION_RIGHT:
|
||||
xoffset = anchor.getWidth() + tooltipSpacing;
|
||||
yoffset = -(getContentView().getMeasuredHeight()/2 + anchor.getHeight()/2);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Invalid tooltip position!");
|
||||
}
|
||||
|
||||
showAsDropDown(anchor, xoffset, yoffset);
|
||||
}
|
||||
|
||||
private void onLayout(@NonNull Runnable runnable) {
|
||||
getContentView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
getContentView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
runnable.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void setArrowHorizontalPosition(@NonNull View arrow, @NonNull View anchor) {
|
||||
int arrowCenterX = getAbsolutePosition(arrow)[0] + arrow.getWidth()/2;
|
||||
int anchorCenterX = getAbsolutePosition(anchor)[0] + anchor.getWidth()/2;
|
||||
|
||||
arrow.setX(anchorCenterX - arrowCenterX);
|
||||
}
|
||||
|
||||
private static int[] getAbsolutePosition(@NonNull View view) {
|
||||
int[] position = new int[2];
|
||||
view.getLocationOnScreen(position);
|
||||
return position;
|
||||
}
|
||||
|
||||
private static int getRtlPosition(@NonNull Context context, int position) {
|
||||
if (position == POSITION_ABOVE || position == POSITION_BELOW) {
|
||||
return position;
|
||||
} else if (context.getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
|
||||
return position == POSITION_START ? POSITION_RIGHT : POSITION_LEFT;
|
||||
} else {
|
||||
return position == POSITION_START ? POSITION_LEFT : POSITION_RIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final View anchor;
|
||||
|
||||
private int backgroundTint;
|
||||
private int textColor;
|
||||
private int textResId;
|
||||
private Object iconGlideModel;
|
||||
private OnDismissListener dismissListener;
|
||||
|
||||
private Builder(@NonNull View anchor) {
|
||||
this.anchor = anchor;
|
||||
}
|
||||
|
||||
public Builder setBackgroundTint(int color) {
|
||||
this.backgroundTint = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTextColor(int color) {
|
||||
this.textColor = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setText(@StringRes int stringResId) {
|
||||
this.textResId = stringResId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setIconGlideModel(Object model) {
|
||||
this.iconGlideModel = model;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOnDismissListener(OnDismissListener dismissListener) {
|
||||
this.dismissListener = dismissListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TooltipPopup show(int position) {
|
||||
String text = anchor.getContext().getString(textResId);
|
||||
TooltipPopup tooltip = new TooltipPopup(anchor, position, text, backgroundTint, textColor, iconGlideModel, dismissListener);
|
||||
|
||||
tooltip.show();
|
||||
|
||||
return tooltip;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package org.thoughtcrime.securesms.contactshare;
|
||||
|
||||
public interface Selectable {
|
||||
void setSelected(boolean selected);
|
||||
boolean isSelected();
|
||||
}
|
@ -117,7 +117,6 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
|
||||
import org.thoughtcrime.securesms.contactshare.ContactUtil;
|
||||
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase;
|
||||
@ -345,7 +344,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
broadcastReceivers.add(broadcastReceiver);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter("clockOutOfSync"));
|
||||
|
||||
initializeReceivers();
|
||||
initializeActionBar();
|
||||
initializeViews();
|
||||
initializeResources();
|
||||
@ -1534,19 +1532,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeReceivers() {
|
||||
securityUpdateReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
initializeSecurity(true, isDefaultSms);
|
||||
}
|
||||
};
|
||||
|
||||
registerReceiver(securityUpdateReceiver,
|
||||
new IntentFilter(SecurityEvent.SECURITY_UPDATE_EVENT),
|
||||
KeyCachingService.KEY_PERMISSION, null);
|
||||
}
|
||||
|
||||
//////// Helper Methods
|
||||
|
||||
private void addAttachment(int type) {
|
||||
|
@ -1,87 +0,0 @@
|
||||
package org.thoughtcrime.securesms.conversation;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
||||
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
public class ConversationStickerSuggestionAdapter extends RecyclerView.Adapter<ConversationStickerSuggestionAdapter.StickerSuggestionViewHolder> {
|
||||
|
||||
private final GlideRequests glideRequests;
|
||||
private final EventListener eventListener;
|
||||
private final List<StickerRecord> stickers;
|
||||
|
||||
public ConversationStickerSuggestionAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) {
|
||||
this.glideRequests = glideRequests;
|
||||
this.eventListener = eventListener;
|
||||
this.stickers = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull StickerSuggestionViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
|
||||
return new StickerSuggestionViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.sticker_suggestion_list_item, viewGroup, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull StickerSuggestionViewHolder viewHolder, int i) {
|
||||
viewHolder.bind(glideRequests, eventListener, stickers.get(i));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(@NonNull StickerSuggestionViewHolder holder) {
|
||||
holder.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return stickers.size();
|
||||
}
|
||||
|
||||
public void setStickers(@NonNull List<StickerRecord> stickers) {
|
||||
this.stickers.clear();
|
||||
this.stickers.addAll(stickers);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
static class StickerSuggestionViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final ImageView image;
|
||||
|
||||
StickerSuggestionViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
this.image = itemView.findViewById(R.id.sticker_suggestion_item_image);
|
||||
}
|
||||
|
||||
void bind(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener, @NonNull StickerRecord sticker) {
|
||||
glideRequests.load(new DecryptableUri(sticker.getUri()))
|
||||
.transition(DrawableTransitionOptions.withCrossFade())
|
||||
.into(image);
|
||||
|
||||
itemView.setOnClickListener(v -> {
|
||||
eventListener.onStickerSuggestionClicked(sticker);
|
||||
});
|
||||
}
|
||||
|
||||
void recycle() {
|
||||
itemView.setOnClickListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
public interface EventListener {
|
||||
void onStickerSuggestionClicked(@NonNull StickerRecord sticker);
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
* 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.securesms.crypto;
|
||||
|
||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
|
||||
/**
|
||||
* When a user first initializes TextSecure, a few secrets
|
||||
* are generated. These are:
|
||||
*
|
||||
* 1) A 128bit symmetric encryption key.
|
||||
* 2) A 160bit symmetric MAC key.
|
||||
* 3) An ECC keypair.
|
||||
*
|
||||
* The first two, along with the ECC keypair's private key, are
|
||||
* then encrypted on disk using PBE.
|
||||
*
|
||||
* This class represents the ECC keypair.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class AsymmetricMasterSecret {
|
||||
|
||||
private final ECPublicKey djbPublicKey;
|
||||
private final ECPrivateKey djbPrivateKey;
|
||||
|
||||
|
||||
public AsymmetricMasterSecret(ECPublicKey djbPublicKey, ECPrivateKey djbPrivateKey)
|
||||
{
|
||||
this.djbPublicKey = djbPublicKey;
|
||||
this.djbPrivateKey = djbPrivateKey;
|
||||
}
|
||||
|
||||
public ECPublicKey getDjbPublicKey() {
|
||||
return djbPublicKey;
|
||||
}
|
||||
|
||||
|
||||
public ECPrivateKey getPrivateKey() {
|
||||
return djbPrivateKey;
|
||||
}
|
||||
|
||||
}
|
@ -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.securesms.crypto;
|
||||
|
||||
public class InvalidPassphraseException extends Exception {
|
||||
|
||||
public InvalidPassphraseException() {
|
||||
super();
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidPassphraseException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidPassphraseException(Throwable throwable) {
|
||||
super(throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidPassphraseException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
}
|
@ -1,226 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
* 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.securesms.crypto;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
|
||||
import org.session.libsignal.utilities.Base64;
|
||||
import org.session.libsignal.utilities.Hex;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Class that handles encryption for local storage.
|
||||
*
|
||||
* The protocol format is roughly:
|
||||
*
|
||||
* 1) 16 byte random IV.
|
||||
* 2) AES-CBC(plaintext)
|
||||
* 3) HMAC-SHA1 of 1 and 2
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class MasterCipher {
|
||||
|
||||
private static final String TAG = MasterCipher.class.getSimpleName();
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
private final Cipher encryptingCipher;
|
||||
private final Cipher decryptingCipher;
|
||||
private final Mac hmac;
|
||||
|
||||
public MasterCipher(MasterSecret masterSecret) {
|
||||
try {
|
||||
this.masterSecret = masterSecret;
|
||||
this.encryptingCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
this.decryptingCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
this.hmac = Mac.getInstance("HmacSHA1");
|
||||
} catch (NoSuchPaddingException | NoSuchAlgorithmException nspe) {
|
||||
throw new AssertionError(nspe);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] encryptKey(ECPrivateKey privateKey) {
|
||||
return encryptBytes(privateKey.serialize());
|
||||
}
|
||||
|
||||
public String encryptBody(@NonNull String body) {
|
||||
return encryptAndEncodeBytes(body.getBytes());
|
||||
}
|
||||
|
||||
public String decryptBody(String body) throws InvalidMessageException {
|
||||
return new String(decodeAndDecryptBytes(body));
|
||||
}
|
||||
|
||||
public ECPrivateKey decryptKey(byte[] key)
|
||||
throws org.session.libsignal.libsignal.InvalidKeyException
|
||||
{
|
||||
try {
|
||||
return Curve.decodePrivatePoint(decryptBytes(key));
|
||||
} catch (InvalidMessageException ime) {
|
||||
throw new org.session.libsignal.libsignal.InvalidKeyException(ime);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decryptBytes(@NonNull byte[] decodedBody) throws InvalidMessageException {
|
||||
try {
|
||||
Mac mac = getMac(masterSecret.getMacKey());
|
||||
byte[] encryptedBody = verifyMacBody(mac, decodedBody);
|
||||
|
||||
Cipher cipher = getDecryptingCipher(masterSecret.getEncryptionKey(), encryptedBody);
|
||||
byte[] encrypted = getDecryptedBody(cipher, encryptedBody);
|
||||
|
||||
return encrypted;
|
||||
} catch (GeneralSecurityException ge) {
|
||||
throw new InvalidMessageException(ge);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] encryptBytes(byte[] body) {
|
||||
try {
|
||||
Cipher cipher = getEncryptingCipher(masterSecret.getEncryptionKey());
|
||||
Mac mac = getMac(masterSecret.getMacKey());
|
||||
|
||||
byte[] encryptedBody = getEncryptedBody(cipher, body);
|
||||
byte[] encryptedAndMacBody = getMacBody(mac, encryptedBody);
|
||||
|
||||
return encryptedAndMacBody;
|
||||
} catch (GeneralSecurityException ge) {
|
||||
Log.w("bodycipher", ge);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean verifyMacFor(String content, byte[] theirMac) {
|
||||
byte[] ourMac = getMacFor(content);
|
||||
Log.i(TAG, "Our Mac: " + Hex.toString(ourMac));
|
||||
Log.i(TAG, "Thr Mac: " + Hex.toString(theirMac));
|
||||
return Arrays.equals(ourMac, theirMac);
|
||||
}
|
||||
|
||||
public byte[] getMacFor(String content) {
|
||||
Log.w(TAG, "Macing: " + content);
|
||||
try {
|
||||
Mac mac = getMac(masterSecret.getMacKey());
|
||||
return mac.doFinal(content.getBytes());
|
||||
} catch (GeneralSecurityException ike) {
|
||||
throw new AssertionError(ike);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] decodeAndDecryptBytes(String body) throws InvalidMessageException {
|
||||
try {
|
||||
byte[] decodedBody = Base64.decode(body);
|
||||
return decryptBytes(decodedBody);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidMessageException("Bad Base64 Encoding...", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String encryptAndEncodeBytes(@NonNull byte[] bytes) {
|
||||
byte[] encryptedAndMacBody = encryptBytes(bytes);
|
||||
return Base64.encodeBytes(encryptedAndMacBody);
|
||||
}
|
||||
|
||||
private byte[] verifyMacBody(@NonNull Mac hmac, @NonNull byte[] encryptedAndMac) throws InvalidMessageException {
|
||||
if (encryptedAndMac.length < hmac.getMacLength()) {
|
||||
throw new InvalidMessageException("length(encrypted body + MAC) < length(MAC)");
|
||||
}
|
||||
|
||||
byte[] encrypted = new byte[encryptedAndMac.length - hmac.getMacLength()];
|
||||
System.arraycopy(encryptedAndMac, 0, encrypted, 0, encrypted.length);
|
||||
|
||||
byte[] remoteMac = new byte[hmac.getMacLength()];
|
||||
System.arraycopy(encryptedAndMac, encryptedAndMac.length - remoteMac.length, remoteMac, 0, remoteMac.length);
|
||||
|
||||
byte[] localMac = hmac.doFinal(encrypted);
|
||||
|
||||
if (!Arrays.equals(remoteMac, localMac))
|
||||
throw new InvalidMessageException("MAC doesen't match.");
|
||||
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
private byte[] getDecryptedBody(Cipher cipher, byte[] encryptedBody) throws IllegalBlockSizeException, BadPaddingException {
|
||||
return cipher.doFinal(encryptedBody, cipher.getBlockSize(), encryptedBody.length - cipher.getBlockSize());
|
||||
}
|
||||
|
||||
private byte[] getEncryptedBody(Cipher cipher, byte[] body) throws IllegalBlockSizeException, BadPaddingException {
|
||||
byte[] encrypted = cipher.doFinal(body);
|
||||
byte[] iv = cipher.getIV();
|
||||
|
||||
byte[] ivAndBody = new byte[iv.length + encrypted.length];
|
||||
System.arraycopy(iv, 0, ivAndBody, 0, iv.length);
|
||||
System.arraycopy(encrypted, 0, ivAndBody, iv.length, encrypted.length);
|
||||
|
||||
return ivAndBody;
|
||||
}
|
||||
|
||||
private Mac getMac(SecretKeySpec key) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
// Mac hmac = Mac.getInstance("HmacSHA1");
|
||||
hmac.init(key);
|
||||
|
||||
return hmac;
|
||||
}
|
||||
|
||||
private byte[] getMacBody(Mac hmac, byte[] encryptedBody) {
|
||||
byte[] mac = hmac.doFinal(encryptedBody);
|
||||
byte[] encryptedAndMac = new byte[encryptedBody.length + mac.length];
|
||||
|
||||
System.arraycopy(encryptedBody, 0, encryptedAndMac, 0, encryptedBody.length);
|
||||
System.arraycopy(mac, 0, encryptedAndMac, encryptedBody.length, mac.length);
|
||||
|
||||
return encryptedAndMac;
|
||||
}
|
||||
|
||||
private Cipher getDecryptingCipher(SecretKeySpec key, byte[] encryptedBody) throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException {
|
||||
// Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
IvParameterSpec iv = new IvParameterSpec(encryptedBody, 0, decryptingCipher.getBlockSize());
|
||||
decryptingCipher.init(Cipher.DECRYPT_MODE, key, iv);
|
||||
|
||||
return decryptingCipher;
|
||||
}
|
||||
|
||||
private Cipher getEncryptingCipher(SecretKeySpec key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
|
||||
// Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
encryptingCipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
|
||||
return encryptingCipher;
|
||||
}
|
||||
|
||||
}
|
@ -1,119 +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.securesms.crypto;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* When a user first initializes TextSecure, a few secrets
|
||||
* are generated. These are:
|
||||
*
|
||||
* 1) A 128bit symmetric encryption key.
|
||||
* 2) A 160bit symmetric MAC key.
|
||||
* 3) An ECC keypair.
|
||||
*
|
||||
* The first two, along with the ECC keypair's private key, are
|
||||
* then encrypted on disk using PBE.
|
||||
*
|
||||
* This class represents 1 and 2.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class MasterSecret implements Parcelable {
|
||||
|
||||
private final SecretKeySpec encryptionKey;
|
||||
private final SecretKeySpec macKey;
|
||||
|
||||
public static final Parcelable.Creator<MasterSecret> CREATOR = new Parcelable.Creator<MasterSecret>() {
|
||||
@Override
|
||||
public MasterSecret createFromParcel(Parcel in) {
|
||||
return new MasterSecret(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MasterSecret[] newArray(int size) {
|
||||
return new MasterSecret[size];
|
||||
}
|
||||
};
|
||||
|
||||
public MasterSecret(SecretKeySpec encryptionKey, SecretKeySpec macKey) {
|
||||
this.encryptionKey = encryptionKey;
|
||||
this.macKey = macKey;
|
||||
}
|
||||
|
||||
private MasterSecret(Parcel in) {
|
||||
byte[] encryptionKeyBytes = new byte[in.readInt()];
|
||||
in.readByteArray(encryptionKeyBytes);
|
||||
|
||||
byte[] macKeyBytes = new byte[in.readInt()];
|
||||
in.readByteArray(macKeyBytes);
|
||||
|
||||
this.encryptionKey = new SecretKeySpec(encryptionKeyBytes, "AES");
|
||||
this.macKey = new SecretKeySpec(macKeyBytes, "HmacSHA1");
|
||||
|
||||
// SecretKeySpec does an internal copy in its constructor.
|
||||
Arrays.fill(encryptionKeyBytes, (byte) 0x00);
|
||||
Arrays.fill(macKeyBytes, (byte)0x00);
|
||||
}
|
||||
|
||||
|
||||
public SecretKeySpec getEncryptionKey() {
|
||||
return this.encryptionKey;
|
||||
}
|
||||
|
||||
public SecretKeySpec getMacKey() {
|
||||
return this.macKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeInt(encryptionKey.getEncoded().length);
|
||||
out.writeByteArray(encryptionKey.getEncoded());
|
||||
out.writeInt(macKey.getEncoded().length);
|
||||
out.writeByteArray(macKey.getEncoded());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public MasterSecret parcelClone() {
|
||||
Parcel thisParcel = Parcel.obtain();
|
||||
Parcel thatParcel = Parcel.obtain();
|
||||
byte[] bytes = null;
|
||||
|
||||
thisParcel.writeValue(this);
|
||||
bytes = thisParcel.marshall();
|
||||
|
||||
thatParcel.unmarshall(bytes, 0, bytes.length);
|
||||
thatParcel.setDataPosition(0);
|
||||
|
||||
MasterSecret that = (MasterSecret)thatParcel.readValue(MasterSecret.class.getClassLoader());
|
||||
|
||||
thisParcel.recycle();
|
||||
thatParcel.recycle();
|
||||
|
||||
return that;
|
||||
}
|
||||
|
||||
}
|
@ -1,375 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
* 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.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
|
||||
import org.session.libsignal.utilities.Base64;
|
||||
import org.session.libsession.utilities.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.PBEParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Helper class for generating and securely storing a MasterSecret.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class MasterSecretUtil {
|
||||
|
||||
public static final String UNENCRYPTED_PASSPHRASE = "unencrypted";
|
||||
public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
|
||||
|
||||
private static final String ASYMMETRIC_LOCAL_PUBLIC_DJB = "asymmetric_master_secret_curve25519_public";
|
||||
private static final String ASYMMETRIC_LOCAL_PRIVATE_DJB = "asymmetric_master_secret_curve25519_private";
|
||||
|
||||
public static MasterSecret changeMasterSecretPassphrase(Context context,
|
||||
MasterSecret masterSecret,
|
||||
String newPassphrase)
|
||||
{
|
||||
try {
|
||||
byte[] combinedSecrets = Util.combine(masterSecret.getEncryptionKey().getEncoded(),
|
||||
masterSecret.getMacKey().getEncoded());
|
||||
|
||||
byte[] encryptionSalt = generateSalt();
|
||||
int iterations = generateIterationCount(newPassphrase, encryptionSalt);
|
||||
byte[] encryptedMasterSecret = encryptWithPassphrase(encryptionSalt, iterations, combinedSecrets, newPassphrase);
|
||||
byte[] macSalt = generateSalt();
|
||||
byte[] encryptedAndMacdMasterSecret = macWithPassphrase(macSalt, iterations, encryptedMasterSecret, newPassphrase);
|
||||
|
||||
save(context, "encryption_salt", encryptionSalt);
|
||||
save(context, "mac_salt", macSalt);
|
||||
save(context, "passphrase_iterations", iterations);
|
||||
save(context, "master_secret", encryptedAndMacdMasterSecret);
|
||||
save(context, "passphrase_initialized", true);
|
||||
|
||||
return masterSecret;
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new AssertionError(gse);
|
||||
}
|
||||
}
|
||||
|
||||
public static MasterSecret changeMasterSecretPassphrase(Context context,
|
||||
String originalPassphrase,
|
||||
String newPassphrase)
|
||||
throws InvalidPassphraseException
|
||||
{
|
||||
MasterSecret masterSecret = getMasterSecret(context, originalPassphrase);
|
||||
changeMasterSecretPassphrase(context, masterSecret, newPassphrase);
|
||||
|
||||
return masterSecret;
|
||||
}
|
||||
|
||||
public static MasterSecret getMasterSecret(Context context, String passphrase)
|
||||
throws InvalidPassphraseException
|
||||
{
|
||||
try {
|
||||
byte[] encryptedAndMacdMasterSecret = retrieve(context, "master_secret");
|
||||
byte[] macSalt = retrieve(context, "mac_salt");
|
||||
int iterations = retrieve(context, "passphrase_iterations", 100);
|
||||
byte[] encryptedMasterSecret = verifyMac(macSalt, iterations, encryptedAndMacdMasterSecret, passphrase);
|
||||
byte[] encryptionSalt = retrieve(context, "encryption_salt");
|
||||
byte[] combinedSecrets = decryptWithPassphrase(encryptionSalt, iterations, encryptedMasterSecret, passphrase);
|
||||
byte[] encryptionSecret = Util.split(combinedSecrets, 16, 20)[0];
|
||||
byte[] macSecret = Util.split(combinedSecrets, 16, 20)[1];
|
||||
|
||||
return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
|
||||
new SecretKeySpec(macSecret, "HmacSHA1"));
|
||||
} catch (GeneralSecurityException e) {
|
||||
Log.w("keyutil", e);
|
||||
return null; //XXX
|
||||
} catch (IOException e) {
|
||||
Log.w("keyutil", e);
|
||||
return null; //XXX
|
||||
}
|
||||
}
|
||||
|
||||
public static AsymmetricMasterSecret getAsymmetricMasterSecret(@NonNull Context context,
|
||||
@Nullable MasterSecret masterSecret)
|
||||
{
|
||||
try {
|
||||
byte[] djbPublicBytes = retrieve(context, ASYMMETRIC_LOCAL_PUBLIC_DJB);
|
||||
byte[] djbPrivateBytes = retrieve(context, ASYMMETRIC_LOCAL_PRIVATE_DJB);
|
||||
|
||||
ECPublicKey djbPublicKey = null;
|
||||
ECPrivateKey djbPrivateKey = null;
|
||||
|
||||
if (djbPublicBytes != null) {
|
||||
djbPublicKey = Curve.decodePoint(djbPublicBytes, 0);
|
||||
}
|
||||
|
||||
if (masterSecret != null) {
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
|
||||
if (djbPrivateBytes != null) {
|
||||
djbPrivateKey = masterCipher.decryptKey(djbPrivateBytes);
|
||||
}
|
||||
}
|
||||
|
||||
return new AsymmetricMasterSecret(djbPublicKey, djbPrivateKey);
|
||||
} catch (InvalidKeyException | IOException ike) {
|
||||
throw new AssertionError(ike);
|
||||
}
|
||||
}
|
||||
|
||||
public static AsymmetricMasterSecret generateAsymmetricMasterSecret(Context context,
|
||||
MasterSecret masterSecret)
|
||||
{
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
|
||||
save(context, ASYMMETRIC_LOCAL_PUBLIC_DJB, keyPair.getPublicKey().serialize());
|
||||
save(context, ASYMMETRIC_LOCAL_PRIVATE_DJB, masterCipher.encryptKey(keyPair.getPrivateKey()));
|
||||
|
||||
return new AsymmetricMasterSecret(keyPair.getPublicKey(), keyPair.getPrivateKey());
|
||||
}
|
||||
|
||||
public static MasterSecret generateMasterSecret(Context context, String passphrase) {
|
||||
try {
|
||||
byte[] encryptionSecret = generateEncryptionSecret();
|
||||
byte[] macSecret = generateMacSecret();
|
||||
byte[] masterSecret = Util.combine(encryptionSecret, macSecret);
|
||||
byte[] encryptionSalt = generateSalt();
|
||||
int iterations = generateIterationCount(passphrase, encryptionSalt);
|
||||
byte[] encryptedMasterSecret = encryptWithPassphrase(encryptionSalt, iterations, masterSecret, passphrase);
|
||||
byte[] macSalt = generateSalt();
|
||||
byte[] encryptedAndMacdMasterSecret = macWithPassphrase(macSalt, iterations, encryptedMasterSecret, passphrase);
|
||||
|
||||
save(context, "encryption_salt", encryptionSalt);
|
||||
save(context, "mac_salt", macSalt);
|
||||
save(context, "passphrase_iterations", iterations);
|
||||
save(context, "master_secret", encryptedAndMacdMasterSecret);
|
||||
save(context, "passphrase_initialized", true);
|
||||
|
||||
return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
|
||||
new SecretKeySpec(macSecret, "HmacSHA1"));
|
||||
} catch (GeneralSecurityException e) {
|
||||
Log.w("keyutil", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasAsymmericMasterSecret(Context context) {
|
||||
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
|
||||
return settings.contains(ASYMMETRIC_LOCAL_PUBLIC_DJB);
|
||||
}
|
||||
|
||||
public static boolean isPassphraseInitialized(Context context) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, 0);
|
||||
return preferences.getBoolean("passphrase_initialized", false);
|
||||
}
|
||||
|
||||
public static void clear(Context context) {
|
||||
context.getSharedPreferences(PREFERENCES_NAME, 0).edit().clear().commit();
|
||||
}
|
||||
|
||||
private static void save(Context context, String key, int value) {
|
||||
if (!context.getSharedPreferences(PREFERENCES_NAME, 0)
|
||||
.edit()
|
||||
.putInt(key, value)
|
||||
.commit())
|
||||
{
|
||||
throw new AssertionError("failed to save a shared pref in MasterSecretUtil");
|
||||
}
|
||||
}
|
||||
|
||||
private static void save(Context context, String key, byte[] value) {
|
||||
if (!context.getSharedPreferences(PREFERENCES_NAME, 0)
|
||||
.edit()
|
||||
.putString(key, Base64.encodeBytes(value))
|
||||
.commit())
|
||||
{
|
||||
throw new AssertionError("failed to save a shared pref in MasterSecretUtil");
|
||||
}
|
||||
}
|
||||
|
||||
private static void save(Context context, String key, boolean value) {
|
||||
if (!context.getSharedPreferences(PREFERENCES_NAME, 0)
|
||||
.edit()
|
||||
.putBoolean(key, value)
|
||||
.commit())
|
||||
{
|
||||
throw new AssertionError("failed to save a shared pref in MasterSecretUtil");
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] retrieve(Context context, String key) throws IOException {
|
||||
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
|
||||
String encodedValue = settings.getString(key, "");
|
||||
|
||||
if (TextUtils.isEmpty(encodedValue)) return null;
|
||||
else return Base64.decode(encodedValue);
|
||||
}
|
||||
|
||||
private static int retrieve(Context context, String key, int defaultValue) throws IOException {
|
||||
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
|
||||
return settings.getInt(key, defaultValue);
|
||||
}
|
||||
|
||||
private static byte[] generateEncryptionSecret() {
|
||||
try {
|
||||
KeyGenerator generator = KeyGenerator.getInstance("AES");
|
||||
generator.init(128);
|
||||
|
||||
SecretKey key = generator.generateKey();
|
||||
return key.getEncoded();
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
Log.w("keyutil", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] generateMacSecret() {
|
||||
try {
|
||||
KeyGenerator generator = KeyGenerator.getInstance("HmacSHA1");
|
||||
return generator.generateKey().getEncoded();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.w("keyutil", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] generateSalt() {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] salt = new byte[16];
|
||||
random.nextBytes(salt);
|
||||
|
||||
return salt;
|
||||
}
|
||||
|
||||
private static int generateIterationCount(String passphrase, byte[] salt) {
|
||||
int TARGET_ITERATION_TIME = 50; //ms
|
||||
int MINIMUM_ITERATION_COUNT = 100; //default for low-end devices
|
||||
int BENCHMARK_ITERATION_COUNT = 10000; //baseline starting iteration count
|
||||
|
||||
try {
|
||||
PBEKeySpec keyspec = new PBEKeySpec(passphrase.toCharArray(), salt, BENCHMARK_ITERATION_COUNT);
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWITHSHA1AND128BITAES-CBC-BC");
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
skf.generateSecret(keyspec);
|
||||
long finishTime = System.currentTimeMillis();
|
||||
|
||||
int scaledIterationTarget = (int) (((double)BENCHMARK_ITERATION_COUNT / (double)(finishTime - startTime)) * TARGET_ITERATION_TIME);
|
||||
|
||||
if (scaledIterationTarget < MINIMUM_ITERATION_COUNT) return MINIMUM_ITERATION_COUNT;
|
||||
else return scaledIterationTarget;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.w("MasterSecretUtil", e);
|
||||
return MINIMUM_ITERATION_COUNT;
|
||||
} catch (InvalidKeySpecException e) {
|
||||
Log.w("MasterSecretUtil", e);
|
||||
return MINIMUM_ITERATION_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
private static SecretKey getKeyFromPassphrase(String passphrase, byte[] salt, int iterations)
|
||||
throws GeneralSecurityException
|
||||
{
|
||||
PBEKeySpec keyspec = new PBEKeySpec(passphrase.toCharArray(), salt, iterations);
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWITHSHA1AND128BITAES-CBC-BC");
|
||||
return skf.generateSecret(keyspec);
|
||||
}
|
||||
|
||||
private static Cipher getCipherFromPassphrase(String passphrase, byte[] salt, int iterations, int opMode)
|
||||
throws GeneralSecurityException
|
||||
{
|
||||
SecretKey key = getKeyFromPassphrase(passphrase, salt, iterations);
|
||||
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
|
||||
cipher.init(opMode, key, new PBEParameterSpec(salt, iterations));
|
||||
|
||||
return cipher;
|
||||
}
|
||||
|
||||
private static byte[] encryptWithPassphrase(byte[] encryptionSalt, int iterations, byte[] data, String passphrase)
|
||||
throws GeneralSecurityException
|
||||
{
|
||||
Cipher cipher = getCipherFromPassphrase(passphrase, encryptionSalt, iterations, Cipher.ENCRYPT_MODE);
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
|
||||
private static byte[] decryptWithPassphrase(byte[] encryptionSalt, int iterations, byte[] data, String passphrase)
|
||||
throws GeneralSecurityException, IOException
|
||||
{
|
||||
Cipher cipher = getCipherFromPassphrase(passphrase, encryptionSalt, iterations, Cipher.DECRYPT_MODE);
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
|
||||
private static Mac getMacForPassphrase(String passphrase, byte[] salt, int iterations)
|
||||
throws GeneralSecurityException
|
||||
{
|
||||
SecretKey key = getKeyFromPassphrase(passphrase, salt, iterations);
|
||||
byte[] pbkdf2 = key.getEncoded();
|
||||
SecretKeySpec hmacKey = new SecretKeySpec(pbkdf2, "HmacSHA1");
|
||||
Mac hmac = Mac.getInstance("HmacSHA1");
|
||||
hmac.init(hmacKey);
|
||||
|
||||
return hmac;
|
||||
}
|
||||
|
||||
private static byte[] verifyMac(byte[] macSalt, int iterations, byte[] encryptedAndMacdData, String passphrase) throws InvalidPassphraseException, GeneralSecurityException, IOException {
|
||||
Mac hmac = getMacForPassphrase(passphrase, macSalt, iterations);
|
||||
|
||||
byte[] encryptedData = new byte[encryptedAndMacdData.length - hmac.getMacLength()];
|
||||
System.arraycopy(encryptedAndMacdData, 0, encryptedData, 0, encryptedData.length);
|
||||
|
||||
byte[] givenMac = new byte[hmac.getMacLength()];
|
||||
System.arraycopy(encryptedAndMacdData, encryptedAndMacdData.length-hmac.getMacLength(), givenMac, 0, givenMac.length);
|
||||
|
||||
byte[] localMac = hmac.doFinal(encryptedData);
|
||||
|
||||
if (Arrays.equals(givenMac, localMac)) return encryptedData;
|
||||
else throw new InvalidPassphraseException("MAC Error");
|
||||
}
|
||||
|
||||
private static byte[] macWithPassphrase(byte[] macSalt, int iterations, byte[] data, String passphrase) throws GeneralSecurityException {
|
||||
Mac hmac = getMacForPassphrase(passphrase, macSalt, iterations);
|
||||
byte[] mac = hmac.doFinal(data);
|
||||
byte[] result = new byte[data.length + mac.length];
|
||||
|
||||
System.arraycopy(data, 0, result, 0, data.length);
|
||||
System.arraycopy(mac, 0, result, data.length, mac.length);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -13,10 +13,6 @@ import java.io.IOException;
|
||||
|
||||
public class ProfileKeyUtil {
|
||||
|
||||
public static synchronized boolean hasProfileKey(@NonNull Context context) {
|
||||
return TextSecurePreferences.getProfileKey(context) != null;
|
||||
}
|
||||
|
||||
public static synchronized @NonNull byte[] getProfileKey(@NonNull Context context) {
|
||||
try {
|
||||
String encodedProfileKey = TextSecurePreferences.getProfileKey(context);
|
||||
@ -40,11 +36,6 @@ public class ProfileKeyUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized @NonNull byte[] rotateProfileKey(@NonNull Context context) {
|
||||
TextSecurePreferences.setProfileKey(context, null);
|
||||
return getProfileKey(context);
|
||||
}
|
||||
|
||||
public static synchronized @NonNull String generateEncodedProfileKey(@NonNull Context context) {
|
||||
return Util.getSecret(32);
|
||||
}
|
||||
|
@ -1,104 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
* 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.securesms.crypto;
|
||||
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.session.libsignal.utilities.Hex;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsession.utilities.Conversions;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class PublicKey {
|
||||
|
||||
private static final String TAG = PublicKey.class.getSimpleName();
|
||||
|
||||
public static final int KEY_SIZE = 3 + ECPublicKey.KEY_SIZE;
|
||||
|
||||
private final ECPublicKey publicKey;
|
||||
private int id;
|
||||
|
||||
public PublicKey(PublicKey publicKey) {
|
||||
this.id = publicKey.id;
|
||||
|
||||
// FIXME :: This not strictly an accurate copy constructor.
|
||||
this.publicKey = publicKey.publicKey;
|
||||
}
|
||||
|
||||
public PublicKey(int id, ECPublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public PublicKey(byte[] bytes, int offset) throws InvalidKeyException {
|
||||
Log.i(TAG, "PublicKey Length: " + (bytes.length - offset));
|
||||
|
||||
if ((bytes.length - offset) < KEY_SIZE)
|
||||
throw new InvalidKeyException("Provided bytes are too short.");
|
||||
|
||||
this.id = Conversions.byteArrayToMedium(bytes, offset);
|
||||
this.publicKey = Curve.decodePoint(bytes, offset + 3);
|
||||
}
|
||||
|
||||
public PublicKey(byte[] bytes) throws InvalidKeyException {
|
||||
this(bytes, 0);
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return publicKey.getType();
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public ECPublicKey getKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public String getFingerprint() {
|
||||
return Hex.toString(getFingerprintBytes());
|
||||
}
|
||||
|
||||
public byte[] getFingerprintBytes() {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
return md.digest(serialize());
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
Log.w("LocalKeyPair", nsae);
|
||||
throw new IllegalArgumentException("SHA-1 isn't supported!");
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
byte[] keyIdBytes = Conversions.mediumToByteArray(id);
|
||||
byte[] serializedPoint = publicKey.serialize();
|
||||
|
||||
Log.i(TAG, "Serializing public key point: " + Hex.toString(serializedPoint));
|
||||
|
||||
return Util.combine(keyIdBytes, serializedPoint);
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
|
||||
/**
|
||||
* This class processes key exchange interactions.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class SecurityEvent {
|
||||
|
||||
public static final String SECURITY_UPDATE_EVENT = "org.thoughtcrime.securesms.KEY_EXCHANGE_UPDATE";
|
||||
|
||||
public static void broadcastSecurityUpdateEvent(Context context) {
|
||||
Intent intent = new Intent(SECURITY_UPDATE_EVENT);
|
||||
intent.setPackage(context.getPackageName());
|
||||
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
|
||||
}
|
||||
|
||||
}
|
@ -103,11 +103,6 @@ public class MessageSender {
|
||||
}
|
||||
}
|
||||
|
||||
public static void resendGroupMessage(Context context, MessageRecord messageRecord, Address filterAddress) {
|
||||
if (!messageRecord.isMms()) throw new AssertionError("Not Group");
|
||||
sendGroupPush(context, messageRecord.getRecipient(), messageRecord.getId(), filterAddress);
|
||||
}
|
||||
|
||||
public static void resend(Context context, MessageRecord messageRecord) {
|
||||
long messageId = messageRecord.getId();
|
||||
boolean forceSms = messageRecord.isForcedSms();
|
||||
|
Loading…
x
Reference in New Issue
Block a user