Merge branch 'feature/server-pow' into develop

This commit is contained in:
Christian Basler 2015-12-22 17:53:22 +01:00
commit 1c5aed0f6c
41 changed files with 1250 additions and 230 deletions

View File

@ -29,6 +29,7 @@ dependencies {
compile 'ch.dissem.jabit:jabit-domain:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-networking:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-security-spongy:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-extensions:0.2.1-SNAPSHOT'
compile 'org.slf4j:slf4j-android:1.7.12'

View File

@ -18,7 +18,7 @@
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name=".MessageListActivity"
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -29,28 +29,28 @@
<activity
android:name=".MessageDetailActivity"
android:label="@string/title_message_detail"
android:parentActivityName=".MessageListActivity"
android:parentActivityName=".MainActivity"
tools:ignore="UnusedAttribute">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MessageListActivity" />
android:value=".MainActivity" />
</activity>
<activity
android:name=".SubscriptionDetailActivity"
android:label="@string/title_subscription_detail"
android:parentActivityName=".MessageListActivity"
android:parentActivityName=".MainActivity"
tools:ignore="UnusedAttribute">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MessageListActivity" />
android:value=".MainActivity" />
</activity>
<activity
android:name=".ComposeMessageActivity"
android:label="Compose"
android:parentActivityName=".MessageListActivity">
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MessageListActivity" />
android:value=".MainActivity" />
<intent-filter>
<action android:name="android.intent.action.SENDTO" />
@ -79,7 +79,7 @@
<activity
android:name=".SettingsActivity"
android:label="@string/settings"
android:parentActivityName=".MessageListActivity">
android:parentActivityName=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />

View File

@ -8,6 +8,7 @@ CREATE TABLE Message (
sent INTEGER,
received INTEGER,
status VARCHAR(20) NOT NULL,
initial_hash BINARY(64) UNIQUE,
FOREIGN KEY (sender) REFERENCES Address (address),
FOREIGN KEY (recipient) REFERENCES Address (address)

View File

@ -0,0 +1,7 @@
-- This is done in V1.2, as SQLite doesn't support ADD CONSTRAINT and a proper migration
-- wasn't really necessary yet.
--
-- This file is here to reduce confusion regarding to the original migration files.
--ALTER TABLE Message ADD COLUMN initial_hash BINARY(64);
--ALTER TABLE Message ADD CONSTRAINT initial_hash_unique UNIQUE(initial_hash);

View File

@ -0,0 +1,7 @@
CREATE TABLE POW (
initial_hash BINARY(64) PRIMARY KEY,
data BLOB NOT NULL,
version BIGINT NOT NULL,
nonce_trials_per_byte BIGINT NOT NULL,
extra_bytes BIGINT NOT NULL
);

View File

@ -42,12 +42,16 @@ import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.listener.ListSelectionListener;
import ch.dissem.apps.abit.service.BitmessageService;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.synchronization.Authenticator;
import ch.dissem.apps.abit.synchronization.SyncAdapter;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
@ -77,13 +81,12 @@ import static ch.dissem.apps.abit.synchronization.StubProvider.AUTHORITY;
* to listen for item selections.
* </p>
*/
public class MessageListActivity extends AppCompatActivity
public class MainActivity extends AppCompatActivity
implements ListSelectionListener<Serializable>, ActionBarListener {
public static final String EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage";
public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox";
private static final Logger LOG = LoggerFactory.getLogger(MessageListActivity.class);
private static final long SYNC_FREQUENCY = 15 * 60; // seconds
private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class);
private static final int ADD_IDENTITY = 1;
/**
@ -99,8 +102,8 @@ public class MessageListActivity extends AppCompatActivity
private static ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MessageListActivity.service = new Messenger(service);
MessageListActivity.bound = true;
MainActivity.service = new Messenger(service);
MainActivity.bound = true;
}
@Override
@ -121,8 +124,10 @@ public class MessageListActivity extends AppCompatActivity
super.onCreate(savedInstanceState);
messageRepo = Singleton.getMessageRepository(this);
addressRepo = Singleton.getAddressRepository(this);
selectedLabel = messageRepo.getLabels().get(0);
List<Label> labels = messageRepo.getLabels();
if (selectedLabel == null) {
selectedLabel = labels.get(0);
}
setContentView(R.layout.activity_message_list);
@ -130,7 +135,8 @@ public class MessageListActivity extends AppCompatActivity
setSupportActionBar(toolbar);
MessageListFragment listFragment = new MessageListFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment).commit();
getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment)
.commit();
if (findViewById(R.id.message_detail_container) != null) {
// The detail container view will be present only in the
@ -144,7 +150,7 @@ public class MessageListActivity extends AppCompatActivity
listFragment.setActivateOnItemClick(true);
}
createDrawer(toolbar);
createDrawer(toolbar, labels);
Singleton.getMessageListener(this).resetNotification();
@ -153,21 +159,10 @@ public class MessageListActivity extends AppCompatActivity
onItemSelected(getIntent().getSerializableExtra(EXTRA_SHOW_MESSAGE));
}
createSyncAccount();
}
private void createSyncAccount() {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = new Account(Authenticator.ACCOUNT_NAME, Authenticator.ACCOUNT_TYPE);
if (AccountManager.get(this).addAccountExplicitly(account, null, null)) {
// Inform the system that this account supports sync
ContentResolver.setIsSyncable(account, AUTHORITY, 1);
// Inform the system that this account is eligible for auto sync when the network is up
ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
// Recommend a schedule for automatic synchronization. The system may modify this based
// on other scheduled syncs and network utilization.
ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY);
if (Preferences.useTrustedNode(this)) {
SyncAdapter.startSync(this);
} else {
SyncAdapter.stopSync(this);
}
}
@ -185,7 +180,7 @@ public class MessageListActivity extends AppCompatActivity
}
}
private void createDrawer(Toolbar toolbar) {
private void createDrawer(Toolbar toolbar, Collection<Label> labels) {
final ArrayList<IProfile> profiles = new ArrayList<>();
for (BitmessageAddress identity : addressRepo.getIdentities()) {
LOG.info("Adding identity " + identity.getAddress());
@ -216,10 +211,12 @@ public class MessageListActivity extends AppCompatActivity
.withProfiles(profiles)
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() {
@Override
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) {
public boolean onProfileChanged(View view, IProfile profile, boolean
currentProfile) {
if (profile.getIdentifier() == ADD_IDENTITY) {
try {
Message message = Message.obtain(null, BitmessageService.MSG_CREATE_IDENTITY);
Message message = Message.obtain(null, BitmessageService
.MSG_CREATE_IDENTITY);
message.replyTo = messenger;
service.send(message);
} catch (RemoteException e) {
@ -236,14 +233,15 @@ public class MessageListActivity extends AppCompatActivity
}
})
.build();
if (profiles.size() > 0) {
if (profiles.size() > 2) { // There's always the add and manage identity items
accountHeader.setActiveProfile(profiles.get(0), true);
}
incomingHandler.updateAccountHeader(accountHeader);
ArrayList<IDrawerItem> drawerItems = new ArrayList<>();
for (Label label : messageRepo.getLabels()) {
PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag(label);
for (Label label : labels) {
PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag
(label);
if (label.getType() == null) {
item.withIcon(CommunityMaterial.Icon.cmd_label);
} else {
@ -297,17 +295,21 @@ public class MessageListActivity extends AppCompatActivity
.withChecked(BitmessageService.isRunning())
.withOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(IDrawerItem drawerItem, CompoundButton buttonView, boolean isChecked) {
public void onCheckedChanged(IDrawerItem drawerItem,
CompoundButton buttonView,
boolean isChecked) {
if (messenger != null) {
if (isChecked) {
try {
service.send(Message.obtain(null, MSG_START_NODE));
service.send(Message.obtain(null,
MSG_START_NODE));
} catch (RemoteException e) {
LOG.error(e.getMessage(), e);
}
} else {
try {
service.send(Message.obtain(null, MSG_STOP_NODE));
service.send(Message.obtain(null,
MSG_STOP_NODE));
} catch (RemoteException e) {
LOG.error(e.getMessage(), e);
}
@ -318,7 +320,8 @@ public class MessageListActivity extends AppCompatActivity
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
public boolean onItemClick(AdapterView<?> adapterView, View view, int i, long l, IDrawerItem item) {
public boolean onItemClick(AdapterView<?> adapterView, View view, int i, long
l, IDrawerItem item) {
if (item.getTag() instanceof Label) {
selectedLabel = (Label) item.getTag();
showSelectedLabel();
@ -327,15 +330,18 @@ public class MessageListActivity extends AppCompatActivity
Nameable<?> ni = (Nameable<?>) item;
switch (ni.getNameRes()) {
case R.string.contacts_and_subscriptions:
if (!(getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof SubscriptionListFragment)) {
if (!(getSupportFragmentManager().findFragmentById(R.id
.item_list) instanceof SubscriptionListFragment)) {
changeList(new SubscriptionListFragment());
} else {
((SubscriptionListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list)).updateList();
}
break;
case R.string.settings:
startActivity(new Intent(MessageListActivity.this, SettingsActivity.class));
startActivity(new Intent(MainActivity.this, SettingsActivity
.class));
break;
case R.string.archive:
selectedLabel = null;
@ -353,7 +359,8 @@ public class MessageListActivity extends AppCompatActivity
}
private void showSelectedLabel() {
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof MessageListFragment) {
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof
MessageListFragment) {
((MessageListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list)).updateList(selectedLabel);
} else {
@ -381,7 +388,8 @@ public class MessageListActivity extends AppCompatActivity
else if (item instanceof BitmessageAddress)
fragment = new SubscriptionDetailFragment();
else
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was "
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
"was "
+ item.getClass().getSimpleName());
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
@ -396,7 +404,8 @@ public class MessageListActivity extends AppCompatActivity
else if (item instanceof BitmessageAddress)
detailIntent = new Intent(this, SubscriptionDetailActivity.class);
else
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was "
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
"was "
+ item.getClass().getSimpleName());
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item);
@ -417,7 +426,8 @@ public class MessageListActivity extends AppCompatActivity
@Override
protected void onStart() {
super.onStart();
bindService(new Intent(this, BitmessageService.class), connection, Context.BIND_AUTO_CREATE);
bindService(new Intent(this, BitmessageService.class), connection, Context
.BIND_AUTO_CREATE);
}
@Override
@ -459,8 +469,10 @@ public class MessageListActivity extends AppCompatActivity
.withEmail(identity.getAddress())
.withTag(identity);
if (accountHeader.getProfiles() != null) {
//we know that there are 2 setting elements. set the new profile above them ;)
accountHeader.addProfile(newProfile, accountHeader.getProfiles().size() - 2);
//we know that there are 2 setting elements. set the new profile
// above them ;)
accountHeader.addProfile(newProfile, accountHeader.getProfiles().size
() - 2);
} else {
accountHeader.addProfiles(newProfile);
}

View File

@ -3,7 +3,6 @@ package ch.dissem.apps.abit;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
@ -13,7 +12,7 @@ import android.view.MenuItem;
* An activity representing a single Message detail screen. This
* activity is only used on handset devices. On tablet-size devices,
* item details are presented side-by-side with a list of items
* in a {@link MessageListActivity}.
* in a {@link MainActivity}.
* <p/>
* This activity is mostly just a 'shell' activity containing nothing
* more than a {@link MessageDetailFragment}.
@ -64,7 +63,7 @@ public class MessageDetailActivity extends AppCompatActivity {
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
NavUtils.navigateUpTo(this, new Intent(this, MessageListActivity.class));
NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class));
return true;
}
return super.onOptionsItemSelected(item);

View File

@ -5,7 +5,6 @@ import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.util.Linkify;
import android.text.util.Linkify.TransformFilter;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -19,7 +18,6 @@ import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.util.Drawables;
@ -28,8 +26,6 @@ import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository;
import static android.text.util.Linkify.ALL;
import static android.text.util.Linkify.EMAIL_ADDRESSES;
import static android.text.util.Linkify.WEB_URLS;
import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_ADDRESS_PATTERN;
import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA;
@ -37,7 +33,7 @@ import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA;
/**
* A fragment representing a single Message detail screen.
* This fragment is either contained in a {@link MessageListActivity}
* This fragment is either contained in a {@link MainActivity}
* in two-pane mode (on tablets) or a {@link MessageDetailActivity}
* on handsets.
*/

View File

@ -1,7 +1,5 @@
package ch.dissem.apps.abit;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Bundle;
@ -55,7 +53,7 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> {
public void onResume() {
super.onResume();
updateList(((MessageListActivity) getActivity()).getSelectedLabel());
doUpdateList(((MainActivity) getActivity()).getSelectedLabel());
}
@Override
@ -64,6 +62,10 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> {
if (!isVisible()) return;
doUpdateList(label);
}
private void doUpdateList(Label label) {
setListAdapter(new ArrayAdapter<Plaintext>(
getActivity(),
android.R.layout.simple_list_item_activated_1,
@ -114,7 +116,7 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> {
@Override
public void onClick(View view) {
Intent intent = new Intent(getActivity().getApplicationContext(), ComposeMessageActivity.class);
intent.putExtra(ComposeMessageActivity.EXTRA_IDENTITY, ((MessageListActivity)getActivity()).getSelectedIdentity());
intent.putExtra(ComposeMessageActivity.EXTRA_IDENTITY, ((MainActivity) getActivity()).getSelectedIdentity());
startActivity(intent);
}
});

View File

@ -43,7 +43,6 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
import static ch.dissem.apps.abit.service.BitmessageService.DATA_FIELD_ADDRESS;
import static ch.dissem.apps.abit.service.BitmessageService.MSG_ADD_CONTACT;
import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE;
import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE_AND_ADD_CONTACT;
public class OpenBitmessageLinkActivity extends AppCompatActivity {
private static final Logger LOG = LoggerFactory.getLogger(OpenBitmessageLinkActivity.class);
@ -106,7 +105,7 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
final int what;
if (subscribe.isChecked())
what = MSG_SUBSCRIBE_AND_ADD_CONTACT;
what = MSG_SUBSCRIBE;
else
what = MSG_ADD_CONTACT;
@ -155,7 +154,8 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
@Override
protected void onStart() {
super.onStart();
bindService(new Intent(this, BitmessageService.class), connection, Context.BIND_AUTO_CREATE);
bindService(new Intent(this, BitmessageService.class), connection, Context
.BIND_AUTO_CREATE);
}
@Override

View File

@ -5,7 +5,7 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
/**
* Created by chris on 14.07.15.
* @author Christian Basler
*/
public class SettingsActivity extends AppCompatActivity {
@Override

View File

@ -1,13 +1,22 @@
package ch.dissem.apps.abit;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import ch.dissem.apps.abit.synchronization.SyncAdapter;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
/**
* Created by chris on 14.07.15.
* @author Christian Basler
*/
public class SettingsFragment extends PreferenceFragment {
public class SettingsFragment
extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -15,4 +24,32 @@ public class SettingsFragment extends PreferenceFragment {
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
}
@Override
public void onAttach(Context ctx) {
super.onAttach(ctx);
PreferenceManager.getDefaultSharedPreferences(ctx)
.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
switch (key) {
case PREFERENCE_TRUSTED_NODE:
String node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null);
if (node != null) {
SyncAdapter.startSync(getActivity());
} else {
SyncAdapter.stopSync(getActivity());
}
break;
case PREFERENCE_SERVER_POW:
if (sharedPreferences.getBoolean(PREFERENCE_SERVER_POW, false)) {
SyncAdapter.startPowSync(getActivity());
} else {
SyncAdapter.stopPowSync(getActivity());
}
break;
}
}
}

View File

@ -28,7 +28,7 @@ import android.view.MenuItem;
* An activity representing a single Subscription detail screen. This
* activity is only used on handset devices. On tablet-size devices,
* item details are presented side-by-side with a list of items
* in a {@link MessageListActivity}.
* in a {@link MainActivity}.
* <p/>
* This activity is mostly just a 'shell' activity containing nothing
* more than a {@link SubscriptionDetailFragment}.
@ -79,7 +79,7 @@ public class SubscriptionDetailActivity extends AppCompatActivity {
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
NavUtils.navigateUpTo(this, new Intent(this, MessageListActivity.class));
NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class));
return true;
}
return super.onOptionsItemSelected(item);

View File

@ -34,7 +34,7 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
/**
* A fragment representing a single Message detail screen.
* This fragment is either contained in a {@link MessageListActivity}
* This fragment is either contained in a {@link MainActivity}
* in two-pane mode (on tablets) or a {@link MessageDetailActivity}
* on handsets.
*/

View File

@ -16,6 +16,7 @@
package ch.dissem.apps.abit;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
@ -25,6 +26,7 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.valueobject.Label;
@ -100,12 +102,18 @@ public class SubscriptionListFragment extends AbstractItemListFragment<Bitmessag
});
}
@Override
public void onAttach(Context ctx) {
super.onAttach(ctx);
if (ctx instanceof ActionBarListener){
((ActionBarListener) ctx).updateTitle(getString(R.string.contacts_and_subscriptions));
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_contact_list, container, false);
return rootView;
return inflater.inflate(R.layout.fragment_contact_list, container, false);
}
@Override

View File

@ -0,0 +1,34 @@
package ch.dissem.apps.abit.adapter;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import ch.dissem.apps.abit.util.PRNGFixes;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.PlaintextHolder;
import ch.dissem.bitmessage.entity.payload.Broadcast;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import ch.dissem.bitmessage.security.sc.SpongySecurity;
import ch.dissem.bitmessage.utils.UnixTime;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
import static ch.dissem.bitmessage.entity.Plaintext.Status.SENT;
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
/**
* @author Christian Basler
*/
public class AndroidSecurity extends SpongySecurity {
public AndroidSecurity() {
PRNGFixes.apply();
}
}

View File

@ -0,0 +1,52 @@
package ch.dissem.apps.abit.adapter;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import java.util.Arrays;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
/**
* Switches between two {@link ProofOfWorkEngine}s depending on the configuration.
*
* @author Christian Basler
*/
public class SwitchingProofOfWorkEngine implements ProofOfWorkEngine, InternalContext.ContextHolder {
private final Context ctx;
private final String preference;
private final ProofOfWorkEngine option;
private final ProofOfWorkEngine fallback;
public SwitchingProofOfWorkEngine(Context ctx, String preference,
ProofOfWorkEngine option, ProofOfWorkEngine fallback) {
this.ctx = ctx;
this.preference = preference;
this.option = option;
this.fallback = fallback;
}
@Override
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
if (preferences.getBoolean(preference, false)) {
option.calculateNonce(initialHash, target, callback);
} else {
fallback.calculateNonce(initialHash, target, callback);
}
}
@Override
public void setContext(InternalContext context) {
for (ProofOfWorkEngine e : Arrays.asList(option, fallback)) {
if (e instanceof InternalContext.ContextHolder) {
((InternalContext.ContextHolder) e).setContext(context);
}
}
}
}

View File

@ -18,25 +18,13 @@ package ch.dissem.apps.abit.listener;
import android.annotation.TargetApi;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.provider.ContactsContract;
import android.support.v7.app.NotificationCompat;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.StyleSpan;
import ch.dissem.apps.abit.Identicon;
import ch.dissem.apps.abit.MessageListActivity;
import ch.dissem.apps.abit.R;
import ch.dissem.apps.abit.notification.NewMessageNotification;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.Plaintext;

View File

@ -10,7 +10,7 @@ import android.support.v7.app.NotificationCompat;
import java.util.Timer;
import java.util.TimerTask;
import ch.dissem.apps.abit.MessageListActivity;
import ch.dissem.apps.abit.MainActivity;
import ch.dissem.apps.abit.R;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.utils.Property;
@ -64,7 +64,7 @@ public class NetworkNotification extends AbstractNotification {
}
builder.setContentText(info);
}
Intent showMessageIntent = new Intent(ctx, MessageListActivity.class);
Intent showMessageIntent = new Intent(ctx, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, showMessageIntent, 0);
builder.setContentIntent(pendingIntent);
notification = builder.build();

View File

@ -13,7 +13,7 @@ import android.text.style.StyleSpan;
import java.util.LinkedList;
import ch.dissem.apps.abit.Identicon;
import ch.dissem.apps.abit.MessageListActivity;
import ch.dissem.apps.abit.MainActivity;
import ch.dissem.apps.abit.R;
import ch.dissem.bitmessage.entity.Plaintext;
@ -38,8 +38,8 @@ public class NewMessageNotification extends AbstractNotification {
.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText))
.setContentInfo("Info");
Intent showMessageIntent = new Intent(ctx, MessageListActivity.class);
showMessageIntent.putExtra(MessageListActivity.EXTRA_SHOW_MESSAGE, plaintext);
Intent showMessageIntent = new Intent(ctx, MainActivity.class);
showMessageIntent.putExtra(MainActivity.EXTRA_SHOW_MESSAGE, plaintext);
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
@ -66,8 +66,8 @@ public class NewMessageNotification extends AbstractNotification {
}
builder.setStyle(inboxStyle);
Intent intent = new Intent(ctx, MessageListActivity.class);
intent.setAction(MessageListActivity.ACTION_SHOW_INBOX);
Intent intent = new Intent(ctx, MainActivity.class);
intent.setAction(MainActivity.ACTION_SHOW_INBOX);
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, intent, 0);
builder.setContentIntent(pendingIntent);
notification = builder.build();

View File

@ -5,7 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.support.v7.app.NotificationCompat;
import ch.dissem.apps.abit.MessageListActivity;
import ch.dissem.apps.abit.MainActivity;
import ch.dissem.apps.abit.R;
/**
@ -18,7 +18,7 @@ public class ProofOfWorkNotification extends AbstractNotification {
super(ctx);
NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx);
Intent showMessageIntent = new Intent(ctx, MessageListActivity.class);
Intent showMessageIntent = new Intent(ctx, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

View File

@ -0,0 +1,81 @@
package ch.dissem.apps.abit.pow;
import android.content.Context;
import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.synchronization.SyncAdapter;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.extensions.CryptoCustomMessage;
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* @author Christian Basler
*/
public class ServerPowEngine implements ProofOfWorkEngine, InternalContext
.ContextHolder {
private static final Logger LOG = LoggerFactory.getLogger(ServerPowEngine.class);
private final Context ctx;
private InternalContext context;
private final ExecutorService pool;
public ServerPowEngine(Context ctx) {
this.ctx = ctx;
pool = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread thread = Executors.defaultThreadFactory().newThread(r);
thread.setPriority(Thread.MIN_PRIORITY);
return thread;
}
});
}
@Override
public void calculateNonce(final byte[] initialHash, final byte[] target, Callback callback) {
pool.execute(new Runnable() {
@Override
public void run() {
BitmessageAddress identity = Singleton.getIdentity(ctx);
if (identity == null) throw new RuntimeException("No Identity for calculating POW");
ProofOfWorkRequest request = new ProofOfWorkRequest(identity, initialHash,
CALCULATE, target);
SyncAdapter.startPowSync(ctx);
try {
CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<>
(request);
cryptoMsg.signAndEncrypt(
identity,
security().createPublicKey(identity.getPublicDecryptionKey())
);
context.getNetworkHandler().send(
Preferences.getTrustedNode(ctx), Preferences.getTrustedNodePort(ctx),
cryptoMsg);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
});
}
@Override
public void setContext(InternalContext context) {
this.context = context;
}
}

View File

@ -19,6 +19,7 @@ package ch.dissem.apps.abit.repository;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
@ -27,6 +28,7 @@ import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.utils.Encode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -118,27 +120,30 @@ public class AndroidAddressRepository implements AddressRepository {
COLUMN_SUBSCRIBED
};
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
where,
null, null, null, null
);
try {
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
where,
null, null, null, null
);
c.moveToFirst();
while (!c.isAfterLast()) {
BitmessageAddress address;
byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY));
if (privateKeyBytes != null) {
PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream(privateKeyBytes));
PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream
(privateKeyBytes));
address = new BitmessageAddress(privateKey);
} else {
address = new BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS)));
byte[] publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY));
if (publicKeyBytes != null) {
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(),
new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length, false);
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address
.getStream(),
new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length,
false);
if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) {
pubkey = new V4Pubkey((V3Pubkey) pubkey);
}
@ -153,8 +158,9 @@ public class AndroidAddressRepository implements AddressRepository {
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
} finally {
c.close();
}
return result;
}
@ -173,9 +179,14 @@ public class AndroidAddressRepository implements AddressRepository {
private boolean exists(BitmessageAddress address) {
SQLiteDatabase db = sql.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address.getAddress() + "'", null);
cursor.moveToFirst();
return cursor.getInt(0) > 0;
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address
.getAddress() + "'", null);
try {
cursor.moveToFirst();
return cursor.getInt(0) > 0;
} finally {
cursor.close();
}
}
private void update(BitmessageAddress address) throws IOException {
@ -194,7 +205,8 @@ public class AndroidAddressRepository implements AddressRepository {
values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
int update = db.update(TABLE_NAME, values, "address = '" + address.getAddress() + "'", null);
int update = db.update(TABLE_NAME, values, "address = '" + address.getAddress() +
"'", null);
if (update < 0) {
LOG.error("Could not update address " + address);
}

View File

@ -74,15 +74,20 @@ public class AndroidInventory implements Inventory {
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
(includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join(streams) + ")",
(includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join
(streams) + ")",
null, null, null, null
);
c.moveToFirst();
List<InventoryVector> result = new LinkedList<>();
while (!c.isAfterLast()) {
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH));
result.add(new InventoryVector(blob));
c.moveToNext();
try {
c.moveToFirst();
while (!c.isAfterLast()) {
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH));
result.add(new InventoryVector(blob));
c.moveToNext();
}
} finally {
c.close();
}
return result;
}
@ -108,15 +113,19 @@ public class AndroidInventory implements Inventory {
"hash = X'" + vector + "'",
null, null, null, null
);
c.moveToFirst();
if (c.isAfterLast()) {
LOG.info("Object requested that we don't have. IV: " + vector);
return null;
}
try {
c.moveToFirst();
if (c.isAfterLast()) {
LOG.info("Object requested that we don't have. IV: " + vector);
return null;
}
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length);
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length);
} finally {
c.close();
}
}
@Override
@ -144,13 +153,18 @@ public class AndroidInventory implements Inventory {
where.toString(),
null, null, null, null
);
c.moveToFirst();
List<ObjectMessage> result = new LinkedList<>();
while (!c.isAfterLast()) {
int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob), blob.length));
c.moveToNext();
try {
c.moveToFirst();
while (!c.isAfterLast()) {
int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob),
blob.length));
c.moveToNext();
}
} finally {
c.close();
}
return result;
}
@ -187,7 +201,11 @@ public class AndroidInventory implements Inventory {
"hash = X'" + object.getInventoryVector() + "'",
null, null, null, null
);
return c.getCount() > 0;
try {
return c.getCount() > 0;
} finally {
c.close();
}
}
@Override

View File

@ -30,6 +30,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -58,6 +59,7 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
private static final String COLUMN_SENT = "sent";
private static final String COLUMN_RECEIVED = "received";
private static final String COLUMN_STATUS = "status";
private static final String COLUMN_INITIAL_HASH = "initial_hash";
private static final String JOIN_TABLE_NAME = "Message_Label";
private static final String JT_COLUMN_MESSAGE = "message_id";
@ -112,10 +114,14 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
null, null, null,
LBL_COLUMN_ORDER
);
c.moveToFirst();
while (!c.isAfterLast()) {
result.add(getLabel(c));
c.moveToNext();
try {
c.moveToFirst();
while (!c.isAfterLast()) {
result.add(getLabel(c));
c.moveToNext();
}
} finally {
c.close();
}
return result;
}
@ -124,27 +130,31 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
String typeName = c.getString(c.getColumnIndex(LBL_COLUMN_TYPE));
Label.Type type = typeName == null ? null : Label.Type.valueOf(typeName);
String text;
switch (type) {
case INBOX:
text = ctx.getString(R.string.inbox);
break;
case DRAFT:
text = ctx.getString(R.string.draft);
break;
case SENT:
text = ctx.getString(R.string.sent);
break;
case UNREAD:
text = ctx.getString(R.string.unread);
break;
case TRASH:
text = ctx.getString(R.string.trash);
break;
case BROADCAST:
text = ctx.getString(R.string.broadcasts);
break;
default:
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
if (type == null) {
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
} else {
switch (type) {
case INBOX:
text = ctx.getString(R.string.inbox);
break;
case DRAFT:
text = ctx.getString(R.string.draft);
break;
case SENT:
text = ctx.getString(R.string.sent);
break;
case UNREAD:
text = ctx.getString(R.string.unread);
break;
case TRASH:
text = ctx.getString(R.string.trash);
break;
case BROADCAST:
text = ctx.getString(R.string.broadcasts);
break;
default:
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
}
}
Label label = new Label(
text,
@ -158,7 +168,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
public int countUnread(Label label) {
String where;
if (label != null) {
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ") AND ";
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId()
+ ") AND ";
} else {
where = "";
}
@ -169,13 +180,32 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
"SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))",
null, null, null, null
);
return c.getColumnCount();
try {
return c.getColumnCount();
} finally {
c.close();
}
}
@Override
public Plaintext getMessage(byte[] initialHash) {
List<Plaintext> results = find("initial_hash=X'" + Strings.hex(initialHash) + "'");
switch (results.size()) {
case 0:
return null;
case 1:
return results.get(0);
default:
throw new RuntimeException("This shouldn't happen, found " + results.size() +
" messages, one or none was expected");
}
}
@Override
public List<Plaintext> findMessages(Label label) {
if (label != null) {
return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")");
return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label
.getId() + ")");
} else {
return find("id NOT IN (SELECT message_id FROM Message_Label)");
}
@ -183,7 +213,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
@Override
public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) {
return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + "'");
return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() +
"'");
}
@Override
@ -213,34 +244,41 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
COLUMN_STATUS
};
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
where,
null, null, null,
COLUMN_RECEIVED + " DESC"
);
try {
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
where,
null, null, null,
COLUMN_RECEIVED + " DESC"
);
c.moveToFirst();
while (!c.isAfterLast()) {
byte[] iv = c.getBlob(c.getColumnIndex(COLUMN_IV));
byte[] data = c.getBlob(c.getColumnIndex(COLUMN_DATA));
Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex(COLUMN_TYPE)));
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new ByteArrayInputStream(data));
Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex
(COLUMN_TYPE)));
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new
ByteArrayInputStream(data));
long id = c.getLong(c.getColumnIndex(COLUMN_ID));
builder.id(id);
builder.IV(new InventoryVector(iv));
builder.from(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_SENDER))));
builder.to(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_RECIPIENT))));
builder.from(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex
(COLUMN_SENDER))));
builder.to(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex
(COLUMN_RECIPIENT))));
builder.sent(c.getLong(c.getColumnIndex(COLUMN_SENT)));
builder.received(c.getLong(c.getColumnIndex(COLUMN_RECEIVED)));
builder.status(Plaintext.Status.valueOf(c.getString(c.getColumnIndex(COLUMN_STATUS))));
builder.status(Plaintext.Status.valueOf(c.getString(c.getColumnIndex
(COLUMN_STATUS))));
builder.labels(findLabels(id));
result.add(builder.build());
c.moveToNext();
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
} finally {
c.close();
}
return result;
}
@ -257,12 +295,13 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
// save from address if necessary
if (message.getId() == null) {
BitmessageAddress savedAddress = bmc.getAddressRepo().getAddress(message.getFrom().getAddress());
BitmessageAddress savedAddress = bmc.getAddressRepository().getAddress(message
.getFrom().getAddress());
if (savedAddress == null || savedAddress.getPrivateKey() == null) {
if (savedAddress != null && savedAddress.getAlias() != null) {
message.getFrom().setAlias(savedAddress.getAlias());
}
bmc.getAddressRepo().save(message.getFrom());
bmc.getAddressRepository().save(message.getFrom());
}
}
@ -295,7 +334,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
private void insert(SQLiteDatabase db, Plaintext message) throws IOException {
ContentValues values = new ContentValues();
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash());
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message
.getInventoryVector().getHash());
values.put(COLUMN_TYPE, message.getType().name());
values.put(COLUMN_SENDER, message.getFrom().getAddress());
values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress());
@ -303,13 +343,15 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
values.put(COLUMN_SENT, message.getSent());
values.put(COLUMN_RECEIVED, message.getReceived());
values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name());
values.put(COLUMN_INITIAL_HASH, message.getInitialHash());
long id = db.insertOrThrow(TABLE_NAME, null, values);
message.setId(id);
}
private void update(SQLiteDatabase db, Plaintext message) throws IOException {
ContentValues values = new ContentValues();
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash());
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message
.getInventoryVector().getHash());
values.put(COLUMN_TYPE, message.getType().name());
values.put(COLUMN_SENDER, message.getFrom().getAddress());
values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress());
@ -317,6 +359,7 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
values.put(COLUMN_SENT, message.getSent());
values.put(COLUMN_RECEIVED, message.getReceived());
values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name());
values.put(COLUMN_INITIAL_HASH, message.getInitialHash());
db.update(TABLE_NAME, values, "id = " + message.getId(), null);
}

View File

@ -0,0 +1,133 @@
package ch.dissem.apps.abit.repository;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Strings;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* @author Christian Basler
*/
public class AndroidProofOfWorkRepository implements ProofOfWorkRepository {
private static final Logger LOG = LoggerFactory.getLogger(AndroidProofOfWorkRepository.class);
private static final String TABLE_NAME = "POW";
private static final String COLUMN_INITIAL_HASH = "initial_hash";
private static final String COLUMN_DATA = "data";
private static final String COLUMN_VERSION = "version";
private static final String COLUMN_NONCE_TRIALS_PER_BYTE = "nonce_trials_per_byte";
private static final String COLUMN_EXTRA_BYTES = "extra_bytes";
private final SqlHelper sql;
public AndroidProofOfWorkRepository(SqlHelper sql) {
this.sql = sql;
}
@Override
public Item getItem(byte[] initialHash) {
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
COLUMN_DATA,
COLUMN_VERSION,
COLUMN_NONCE_TRIALS_PER_BYTE,
COLUMN_EXTRA_BYTES
};
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
"initial_hash = X'" + Strings.hex(initialHash) + "'",
null, null, null, null
);
try {
c.moveToFirst();
if (!c.isAfterLast()) {
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
return new Item(
Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob
.length),
c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)),
c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES))
);
}
} finally {
c.close();
}
throw new RuntimeException("Object requested that we don't have. Initial hash: " +
Strings.hex(initialHash));
}
@Override
public List<byte[]> getItems() {
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
COLUMN_INITIAL_HASH
};
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
null, null, null, null, null
);
List<byte[]> result = new LinkedList<>();
try {
c.moveToFirst();
while (!c.isAfterLast()) {
byte[] initialHash = c.getBlob(c.getColumnIndex(COLUMN_INITIAL_HASH));
result.add(initialHash);
c.moveToNext();
}
} finally {
c.close();
}
return result;
}
@Override
public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
try {
SQLiteDatabase db = sql.getWritableDatabase();
// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(COLUMN_INITIAL_HASH, security().getInitialHash(object));
values.put(COLUMN_DATA, Encode.bytes(object));
values.put(COLUMN_VERSION, object.getVersion());
values.put(COLUMN_NONCE_TRIALS_PER_BYTE, nonceTrialsPerByte);
values.put(COLUMN_EXTRA_BYTES, extraBytes);
db.insertOrThrow(TABLE_NAME, null, values);
} catch (SQLiteConstraintException e) {
LOG.trace(e.getMessage(), e);
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
@Override
public void removeObject(byte[] initialHash) {
SQLiteDatabase db = sql.getWritableDatabase();
db.delete(TABLE_NAME,
"initial_hash = X'" + Strings.hex(initialHash) + "'",
null);
}
}

View File

@ -26,7 +26,7 @@ import ch.dissem.apps.abit.util.Assets;
*/
public class SqlHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version.
public static final int DATABASE_VERSION = 1;
public static final int DATABASE_VERSION = 2;
public static final String DATABASE_NAME = "jabit.db";
protected final Context ctx;
@ -38,7 +38,7 @@ public class SqlHelper extends SQLiteOpenHelper {
@Override
public void onCreate(SQLiteDatabase db) {
onUpgrade(db, 0, 1);
onUpgrade(db, 0, 2);
}
@Override
@ -48,6 +48,9 @@ public class SqlHelper extends SQLiteOpenHelper {
executeMigration(db, "V1.0__Create_table_inventory");
executeMigration(db, "V1.1__Create_table_address");
executeMigration(db, "V1.2__Create_table_message");
case 1:
// executeMigration(db, "V2.0__Update_table_message");
executeMigration(db, "V2.1__Create_table_POW");
default:
// Nothing to do. Let's assume we won't upgrade from a version that's newer than DATABASE_VERSION.
}

View File

@ -34,7 +34,6 @@ public class BitmessageService extends Service {
public static final int MSG_CREATE_IDENTITY = 10;
public static final int MSG_SUBSCRIBE = 20;
public static final int MSG_ADD_CONTACT = 21;
public static final int MSG_SUBSCRIBE_AND_ADD_CONTACT = 23;
public static final int MSG_SEND_MESSAGE = 30;
public static final int MSG_SEND_BROADCAST = 31;
public static final int MSG_START_NODE = 100;
@ -122,6 +121,13 @@ public class BitmessageService extends Service {
}
break;
}
case MSG_ADD_CONTACT: {
Serializable data = msg.getData().getSerializable(DATA_FIELD_ADDRESS);
if (data instanceof BitmessageAddress) {
bmc.addContact((BitmessageAddress) data);
}
break;
}
case MSG_SEND_MESSAGE: {
Serializable identity = msg.getData().getSerializable(DATA_FIELD_IDENTITY);
Serializable address = msg.getData().getSerializable(DATA_FIELD_ADDRESS);

View File

@ -73,9 +73,9 @@ public class ProofOfWorkService extends Service {
service.startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification());
engine.calculateNonce(initialHash, target, new ProofOfWorkEngine.Callback() {
@Override
public void onNonceCalculated(byte[] nonce) {
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
try {
callback.onNonceCalculated(nonce);
callback.onNonceCalculated(initialHash, nonce);
} finally {
service.stopForeground(true);
service.stopSelf();

View File

@ -53,8 +53,8 @@ public class ServicePowEngine implements ProofOfWorkEngine, ProofOfWorkEngine.Ca
}
@Override
public void onNonceCalculated(byte[] bytes) {
callback.onNonceCalculated(bytes);
public void onNonceCalculated(byte[] initialHash, byte[] bytes) {
callback.onNonceCalculated(initialHash, bytes);
ctx.unbindService(connection);
}

View File

@ -2,17 +2,27 @@ package ch.dissem.apps.abit.service;
import android.content.Context;
import java.util.List;
import ch.dissem.apps.abit.adapter.AndroidSecurity;
import ch.dissem.apps.abit.adapter.SwitchingProofOfWorkEngine;
import ch.dissem.apps.abit.listener.MessageListener;
import ch.dissem.apps.abit.pow.ServerPowEngine;
import ch.dissem.apps.abit.repository.AndroidAddressRepository;
import ch.dissem.apps.abit.repository.AndroidInventory;
import ch.dissem.apps.abit.repository.AndroidMessageRepository;
import ch.dissem.apps.abit.repository.AndroidProofOfWorkRepository;
import ch.dissem.apps.abit.repository.SqlHelper;
import ch.dissem.apps.abit.util.Constants;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.security.sc.SpongySecurity;
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
/**
* Provides singleton objects across the application.
@ -21,6 +31,8 @@ public class Singleton {
public static final Object lock = new Object();
private static BitmessageContext bitmessageContext;
private static MessageListener messageListener;
private static BitmessageAddress identity;
private static AndroidProofOfWorkRepository powRepo;
public static BitmessageContext getBitmessageContext(Context context) {
if (bitmessageContext == null) {
@ -28,15 +40,23 @@ public class Singleton {
if (bitmessageContext == null) {
final Context ctx = context.getApplicationContext();
SqlHelper sqlHelper = new SqlHelper(ctx);
powRepo = new AndroidProofOfWorkRepository(sqlHelper);
bitmessageContext = new BitmessageContext.Builder()
.proofOfWorkEngine(new ServicePowEngine(ctx))
.security(new SpongySecurity())
.proofOfWorkEngine(new SwitchingProofOfWorkEngine(
ctx, Constants.PREFERENCE_SERVER_POW,
new ServerPowEngine(ctx),
new ServicePowEngine(ctx)
))
.security(new AndroidSecurity())
.nodeRegistry(new MemoryNodeRegistry())
.inventory(new AndroidInventory(sqlHelper))
.addressRepo(new AndroidAddressRepository(sqlHelper))
.messageRepo(new AndroidMessageRepository(sqlHelper, ctx))
.powRepo(powRepo)
.networkHandler(new DefaultNetworkHandler())
.listener(getMessageListener(ctx))
.doNotSendPubkeyOnIdentityCreation()
.pubkeyTTL(2 * DAY)
.build();
}
}
@ -62,4 +82,24 @@ public class Singleton {
public static AddressRepository getAddressRepository(Context ctx) {
return getBitmessageContext(ctx).addresses();
}
public static ProofOfWorkRepository getProofOfWorkRepository(Context ctx) {
if (powRepo == null) getBitmessageContext(ctx);
return powRepo;
}
public static BitmessageAddress getIdentity(Context ctx) {
if (identity == null) {
synchronized (Singleton.class) {
if (identity == null) {
List<BitmessageAddress> identities = getBitmessageContext(ctx).addresses()
.getIdentities();
if (identities.size() > 0) {
identity = identities.get(0);
}
}
}
}
return identity;
}
}

View File

@ -12,8 +12,8 @@ import android.os.Bundle;
* of its methods
*/
public class Authenticator extends AbstractAccountAuthenticator {
public static final String ACCOUNT_NAME = "Bitmessage";
public static final String ACCOUNT_TYPE = "ch.dissem.bitmessage";
public static final Account ACCOUNT_SYNC = new Account("Bitmessage", "ch.dissem.bitmessage");
public static final Account ACCOUNT_POW = new Account("Proof of Work ", "ch.dissem.bitmessage");
// Simple constructor
public Authenticator(Context context) {

View File

@ -1,24 +1,34 @@
package ch.dissem.apps.abit.synchronization;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SyncResult;
import android.os.Bundle;
import android.preference.PreferenceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import ch.dissem.apps.abit.R;
import ch.dissem.apps.abit.notification.ErrorNotification;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.CustomMessage;
import ch.dissem.bitmessage.extensions.CryptoCustomMessage;
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
import static ch.dissem.apps.abit.synchronization.Authenticator.ACCOUNT_POW;
import static ch.dissem.apps.abit.synchronization.Authenticator.ACCOUNT_SYNC;
import static ch.dissem.apps.abit.synchronization.StubProvider.AUTHORITY;
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE;
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.COMPLETE;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* Sync Adapter to synchronize with the Bitmessage network - fetches
@ -27,6 +37,8 @@ import ch.dissem.bitmessage.BitmessageContext;
public class SyncAdapter extends AbstractThreadedSyncAdapter {
private final static Logger LOG = LoggerFactory.getLogger(SyncAdapter.class);
private static final long SYNC_FREQUENCY = 15 * 60; // seconds
private final BitmessageContext bmc;
/**
@ -38,7 +50,17 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
if (account.equals(Authenticator.ACCOUNT_SYNC))
syncData();
else if (account.equals(Authenticator.ACCOUNT_POW))
syncPOW();
else
throw new RuntimeException("Unknown " + account);
}
private void syncData() {
// If the Bitmessage context acts as a full node, synchronization isn't necessary
if (bmc.isRunning()) {
LOG.info("Synchronization skipped, Abit is acting as a full node");
@ -46,40 +68,103 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
}
LOG.info("Synchronizing Bitmessage");
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
String trustedNode = preferences.getString("trusted_node", null);
if (trustedNode == null) return;
trustedNode = trustedNode.trim();
if (trustedNode.isEmpty()) return;
int port;
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
int index = trustedNode.lastIndexOf(':');
String portString = trustedNode.substring(index + 1);
trustedNode = trustedNode.substring(0, index);
try {
port = Integer.parseInt(portString);
} catch (NumberFormatException e) {
new ErrorNotification(getContext())
.setError(R.string.error_invalid_sync_port, portString)
.show();
return;
}
} else {
port = 8444;
}
long timeoutInSeconds = Long.parseLong(preferences.getString("sync_timeout", "120"));
try {
LOG.info("Synchronization started");
bmc.synchronize(InetAddress.getByName(trustedNode), port, timeoutInSeconds, true);
bmc.synchronize(
Preferences.getTrustedNode(getContext()),
Preferences.getTrustedNodePort(getContext()),
Preferences.getTimeoutInSeconds(getContext()),
true);
LOG.info("Synchronization finished");
} catch (UnknownHostException e) {
new ErrorNotification(getContext())
.setError(R.string.error_invalid_sync_host)
.show();
} catch (RuntimeException e) {
LOG.error(e.getMessage(), e);
}
}
private void syncPOW() {
// If the Bitmessage context acts as a full node, synchronization isn't necessary
LOG.info("Looking for completed POW");
try {
BitmessageAddress identity = Singleton.getIdentity(getContext());
byte[] privateKey = identity.getPrivateKey().getPrivateEncryptionKey();
byte[] signingKey = security().createPublicKey(identity.getPublicDecryptionKey());
ProofOfWorkRequest.Reader reader = new ProofOfWorkRequest.Reader(identity);
ProofOfWorkRepository powRepo = Singleton.getProofOfWorkRepository(getContext());
List<byte[]> items = powRepo.getItems();
for (byte[] initialHash : items) {
ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
byte[] target = security().getProofOfWorkTarget(item.object, item
.nonceTrialsPerByte, item.extraBytes);
CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<>(
new ProofOfWorkRequest(identity, initialHash, CALCULATE, target));
cryptoMsg.signAndEncrypt(identity, signingKey);
CustomMessage response = bmc.send(
Preferences.getTrustedNode(getContext()),
Preferences.getTrustedNodePort(getContext()),
cryptoMsg
);
if (response.isError()) {
LOG.error("Server responded with error: " + new String(response.getData(),
"UTF-8"));
} else {
ProofOfWorkRequest decryptedResponse = CryptoCustomMessage.read(
response, reader).decrypt(privateKey);
if (decryptedResponse.getRequest() == COMPLETE) {
bmc.internals().getProofOfWorkService().onNonceCalculated(
initialHash, decryptedResponse.getData());
}
}
}
if (items.size() == 0) {
stopPowSync(getContext());
}
LOG.info("Synchronization finished");
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
public static void startSync(Context ctx) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = addAccount(ctx, ACCOUNT_SYNC);
// Recommend a schedule for automatic synchronization. The system may modify this based
// on other scheduled syncs and network utilization.
ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY);
}
public static void stopSync(Context ctx) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = addAccount(ctx, ACCOUNT_SYNC);
ContentResolver.removePeriodicSync(account, AUTHORITY, new Bundle());
}
public static void startPowSync(Context ctx) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = addAccount(ctx, ACCOUNT_POW);
// Recommend a schedule for automatic synchronization. The system may modify this based
// on other scheduled syncs and network utilization.
ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY);
}
public static void stopPowSync(Context ctx) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = addAccount(ctx, ACCOUNT_POW);
ContentResolver.removePeriodicSync(account, AUTHORITY, new Bundle());
}
private static Account addAccount(Context ctx, Account account) {
if (AccountManager.get(ctx).addAccountExplicitly(account, null, null)) {
// Inform the system that this account supports sync
ContentResolver.setIsSyncable(account, AUTHORITY, 1);
// Inform the system that this account is eligible for auto sync when the network is up
ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
}
return account;
}
}

View File

@ -3,9 +3,14 @@ package ch.dissem.apps.abit.util;
import java.util.regex.Pattern;
/**
* Created by chrigu on 16.11.15.
* @author Christian Basler
*/
public class Constants {
public static final String PREFERENCE_WIFI_ONLY = "wifi_only";
public static final String PREFERENCE_TRUSTED_NODE = "trusted_node";
public static final String PREFERENCE_SYNC_TIMEOUT = "sync_timeout";
public static final String PREFERENCE_SERVER_POW = "server_pow";
public static final String BITMESSAGE_URL_SCHEMA = "bitmessage:";
public static final Pattern BITMESSAGE_ADDRESS_PATTERN = Pattern.compile("\\bBM-[a-zA-Z0-9]+\\b");
}

View File

@ -0,0 +1,341 @@
package ch.dissem.apps.abit.util;
/*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will Google be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, as long as the origin is not misrepresented.
*/
import android.os.Build;
import android.os.Process;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.security.Security;
/**
* Fixes for the output of the default PRNG having low entropy.
* <p/>
* The fixes need to be applied via {@link #apply()} before any use of Java
* Cryptography Architecture primitives. A good place to invoke them is in the
* application's {@code onCreate}.
*
* @see <a href="http://android-developers.blogspot.ch/2013/08/some-securerandom-thoughts.html">
* http://android-developers.blogspot.ch/2013/08/some-securerandom-thoughts.html</a>
*/
public final class PRNGFixes {
private static final int VERSION_CODE_JELLY_BEAN = 16;
private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
getBuildFingerprintAndDeviceSerial();
/**
* Hidden constructor to prevent instantiation.
*/
private PRNGFixes() {
}
/**
* Applies all fixes.
*
* @throws SecurityException if a fix is needed but could not be applied.
*/
public static void apply() {
applyOpenSSLFix();
installLinuxPRNGSecureRandom();
}
/**
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
* fix is not needed.
*
* @throws SecurityException if the fix is needed but could not be applied.
*/
private static void applyOpenSSLFix() throws SecurityException {
if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
|| (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
// No need to apply the fix
return;
}
try {
// Mix in the device- and invocation-specific seed.
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class)
.invoke(null, generateSeed());
// Mix output of Linux PRNG into OpenSSL's PRNG
int bytesRead = (Integer) Class.forName(
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_load_file", String.class, long.class)
.invoke(null, "/dev/urandom", 1024);
if (bytesRead != 1024) {
throw new IOException(
"Unexpected number of bytes read from Linux PRNG: "
+ bytesRead);
}
} catch (Exception e) {
throw new SecurityException("Failed to seed OpenSSL PRNG", e);
}
}
/**
* Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
* default. Does nothing if the implementation is already the default or if
* there is not need to install the implementation.
*
* @throws SecurityException if the fix is needed but could not be applied.
*/
private static void installLinuxPRNGSecureRandom()
throws SecurityException {
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
// No need to apply the fix
return;
}
// Install a Linux PRNG-based SecureRandom implementation as the
// default, if not yet installed.
Provider[] secureRandomProviders =
Security.getProviders("SecureRandom.SHA1PRNG");
if ((secureRandomProviders == null)
|| (secureRandomProviders.length < 1)
|| (!LinuxPRNGSecureRandomProvider.class.equals(
secureRandomProviders[0].getClass()))) {
Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
}
// Assert that new SecureRandom() and
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
// by the Linux PRNG-based SecureRandom implementation.
SecureRandom rng1 = new SecureRandom();
if (!LinuxPRNGSecureRandomProvider.class.equals(
rng1.getProvider().getClass())) {
throw new SecurityException(
"new SecureRandom() backed by wrong Provider: "
+ rng1.getProvider().getClass());
}
SecureRandom rng2;
try {
rng2 = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new SecurityException("SHA1PRNG not available", e);
}
if (!LinuxPRNGSecureRandomProvider.class.equals(
rng2.getProvider().getClass())) {
throw new SecurityException(
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
+ " Provider: " + rng2.getProvider().getClass());
}
}
/**
* {@code Provider} of {@code SecureRandom} engines which pass through
* all requests to the Linux PRNG.
*/
private static class LinuxPRNGSecureRandomProvider extends Provider {
public LinuxPRNGSecureRandomProvider() {
super("LinuxPRNG",
1.0,
"A Linux-specific random number provider that uses"
+ " /dev/urandom");
// Although /dev/urandom is not a SHA-1 PRNG, some apps
// explicitly request a SHA1PRNG SecureRandom and we thus need to
// prevent them from getting the default implementation whose output
// may have low entropy.
put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
}
}
/**
* {@link SecureRandomSpi} which passes all requests to the Linux PRNG
* ({@code /dev/urandom}).
*/
public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
/*
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
* are passed through to the Linux PRNG (/dev/urandom). Instances of
* this class seed themselves by mixing in the current time, PID, UID,
* build fingerprint, and hardware serial number (where available) into
* Linux PRNG.
*
* Concurrency: Read requests to the underlying Linux PRNG are
* serialized (on sLock) to ensure that multiple threads do not get
* duplicated PRNG output.
*/
private static final File URANDOM_FILE = new File("/dev/urandom");
private static final Object sLock = new Object();
/**
* Input stream for reading from Linux PRNG or {@code null} if not yet
* opened.
*
* @GuardedBy("sLock")
*/
private static DataInputStream sUrandomIn;
/**
* Output stream for writing to Linux PRNG or {@code null} if not yet
* opened.
*
* @GuardedBy("sLock")
*/
private static OutputStream sUrandomOut;
/**
* Whether this engine instance has been seeded. This is needed because
* each instance needs to seed itself if the client does not explicitly
* seed it.
*/
private boolean mSeeded;
@Override
protected void engineSetSeed(byte[] bytes) {
try {
OutputStream out;
synchronized (sLock) {
out = getUrandomOutputStream();
}
out.write(bytes);
out.flush();
} catch (IOException e) {
// On a small fraction of devices /dev/urandom is not writable.
// Log and ignore.
Log.w(PRNGFixes.class.getSimpleName(),
"Failed to mix seed into " + URANDOM_FILE);
} finally {
mSeeded = true;
}
}
@Override
protected void engineNextBytes(byte[] bytes) {
if (!mSeeded) {
// Mix in the device- and invocation-specific seed.
engineSetSeed(generateSeed());
}
try {
DataInputStream in;
synchronized (sLock) {
in = getUrandomInputStream();
}
synchronized (in) {
in.readFully(bytes);
}
} catch (IOException e) {
throw new SecurityException(
"Failed to read from " + URANDOM_FILE, e);
}
}
@Override
protected byte[] engineGenerateSeed(int size) {
byte[] seed = new byte[size];
engineNextBytes(seed);
return seed;
}
private DataInputStream getUrandomInputStream() {
synchronized (sLock) {
if (sUrandomIn == null) {
// NOTE: Consider inserting a BufferedInputStream between
// DataInputStream and FileInputStream if you need higher
// PRNG output performance and can live with future PRNG
// output being pulled into this process prematurely.
try {
sUrandomIn = new DataInputStream(
new FileInputStream(URANDOM_FILE));
} catch (IOException e) {
throw new SecurityException("Failed to open "
+ URANDOM_FILE + " for reading", e);
}
}
return sUrandomIn;
}
}
private OutputStream getUrandomOutputStream() throws IOException {
synchronized (sLock) {
if (sUrandomOut == null) {
sUrandomOut = new FileOutputStream(URANDOM_FILE);
}
return sUrandomOut;
}
}
}
/**
* Generates a device- and invocation-specific seed to be mixed into the
* Linux PRNG.
*/
private static byte[] generateSeed() {
try {
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
DataOutputStream seedBufferOut =
new DataOutputStream(seedBuffer);
seedBufferOut.writeLong(System.currentTimeMillis());
seedBufferOut.writeLong(System.nanoTime());
seedBufferOut.writeInt(Process.myPid());
seedBufferOut.writeInt(Process.myUid());
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
seedBufferOut.close();
return seedBuffer.toByteArray();
} catch (IOException e) {
throw new SecurityException("Failed to generate seed", e);
}
}
/**
* Gets the hardware serial number of this device.
*
* @return serial number or {@code null} if not available.
*/
private static String getDeviceSerialNumber() {
// We're using the Reflection API because Build.SERIAL is only available
// since API Level 9 (Gingerbread, Android 2.3).
try {
return (String) Build.class.getField("SERIAL").get(null);
} catch (Exception ignored) {
return null;
}
}
private static byte[] getBuildFingerprintAndDeviceSerial() {
StringBuilder result = new StringBuilder();
String fingerprint = Build.FINGERPRINT;
if (fingerprint != null) {
result.append(fingerprint);
}
String serial = getDeviceSerialNumber();
if (serial != null) {
result.append(serial);
}
try {
return result.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported");
}
}
}

View File

@ -0,0 +1,90 @@
package ch.dissem.apps.abit.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import ch.dissem.apps.abit.R;
import ch.dissem.apps.abit.notification.ErrorNotification;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
/**
* Created by chrig on 01.12.2015.
*/
public class Preferences {
private static Logger LOG = LoggerFactory.getLogger(Preferences.class);
public static boolean useTrustedNode(Context ctx) {
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
return trustedNode == null || trustedNode.trim().isEmpty();
}
/**
* Warning, this method might do a network call and therefore can't be called from
* the UI thread.
*/
public static InetAddress getTrustedNode(Context ctx) {
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
if (trustedNode == null) return null;
trustedNode = trustedNode.trim();
if (trustedNode.isEmpty()) return null;
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
int index = trustedNode.lastIndexOf(':');
trustedNode = trustedNode.substring(0, index);
}
try {
return InetAddress.getByName(trustedNode);
} catch (UnknownHostException e) {
new ErrorNotification(ctx)
.setError(R.string.error_invalid_sync_host)
.show();
LOG.error(e.getMessage(), e);
return null;
}
}
public static int getTrustedNodePort(Context ctx) {
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
if (trustedNode == null) return 8444;
trustedNode = trustedNode.trim();
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
int index = trustedNode.lastIndexOf(':');
String portString = trustedNode.substring(index + 1);
try {
return Integer.parseInt(portString);
} catch (NumberFormatException e) {
new ErrorNotification(ctx)
.setError(R.string.error_invalid_sync_port, portString)
.show();
}
}
return 8444;
}
public static long getTimeoutInSeconds(Context ctx) {
String preference = getPreference(ctx, PREFERENCE_SYNC_TIMEOUT);
return preference == null ? 120 : Long.parseLong(preference);
}
public static boolean isServerPOW(Context ctx) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
return preferences.getBoolean(PREFERENCE_SERVER_POW, false);
}
private static String getPreference(Context ctx, String name) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
return preferences.getString(name, null);
}
}

View File

@ -1,21 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
android:padding="24dp">
<TextView
android:id="@+id/address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:text="BM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
android:textAppearance="?android:attr/textAppearanceSmall" />
android:textSize="10dp"
tools:ignore="SpUsage" />
<android.support.design.widget.TextInputLayout
android:id="@+id/label_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/address">
android:layout_alignLeft="@+id/address"
android:layout_alignStart="@+id/address"
android:layout_below="@+id/address"
android:layout_marginTop="16dp">
<EditText
android:id="@+id/label"
@ -30,8 +37,9 @@
android:id="@+id/subscribe"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/address"
android:layout_alignStart="@+id/address"
android:layout_below="@+id/label_wrapper"
android:layout_centerHorizontal="true"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:text="@string/subscribe" />

View File

@ -49,4 +49,6 @@
<string name="compose_body_hint">Nachricht schreiben</string>
<string name="contacts_and_subscriptions">Kontakte</string>
<string name="subscribed">Abonniert</string>
<string name="server_pow">Server POW</string>
<string name="server_pow_summary">Der vertrauenswürdige Knoten macht den Proof of Work</string>
</resources>

View File

@ -49,4 +49,6 @@
<string name="compose_body_hint">Write message</string>
<string name="contacts_and_subscriptions">Contacts</string>
<string name="subscribed">Subscribed</string>
<string name="server_pow">Server POW</string>
<string name="server_pow_summary">Trusted node does proof of work</string>
</resources>

View File

@ -24,4 +24,11 @@
android:key="sync_timeout"
android:summary="@string/sync_timeout_summary"
android:title="@string/sync_timeout" />
<SwitchPreference
android:defaultValue="false"
android:key="server_pow"
android:dependency="trusted_node"
android:title="@string/server_pow"
android:summary="@string/server_pow_summary"
/>
</PreferenceScreen>

View File

@ -9,7 +9,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0-beta1'
classpath 'com.android.tools.build:gradle:1.5.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files