diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fce76f0..ba401db 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,7 @@ android:supportsRtl="true" android:theme="@style/AppTheme" tools:ignore="GoogleAppIndexingWarning"> - + @@ -23,7 +23,6 @@ - @@ -32,8 +31,8 @@ android:label="@string/login" /> + android:label="@string/sync" + android:parentActivityName=".activities.HomeActivity" /> \ No newline at end of file diff --git a/app/src/main/java/lu/circl/mispbump/activities/HomeActivity.java b/app/src/main/java/lu/circl/mispbump/activities/HomeActivity.java index d9ec4d8..4954665 100644 --- a/app/src/main/java/lu/circl/mispbump/activities/HomeActivity.java +++ b/app/src/main/java/lu/circl/mispbump/activities/HomeActivity.java @@ -3,23 +3,45 @@ package lu.circl.mispbump.activities; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; +import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.Snackbar; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; +import java.util.List; + import lu.circl.mispbump.R; +import lu.circl.mispbump.adapters.SyncAdapter; import lu.circl.mispbump.auxiliary.PreferenceManager; +import lu.circl.mispbump.models.UploadInformation; +import lu.circl.mispbump.restful_client.MispRestClient; import lu.circl.mispbump.restful_client.Organisation; +import lu.circl.mispbump.restful_client.User; import lu.circl.mispbump.security.KeyStoreWrapper; public class HomeActivity extends AppCompatActivity { + public static final String TAG = "Home"; + + private CoordinatorLayout layout; + private TextView title; + + private RecyclerView recyclerView; + + private PreferenceManager preferenceManager; + private MispRestClient mispRestClient; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -34,7 +56,28 @@ public class HomeActivity extends AppCompatActivity { ab.setDisplayHomeAsUpEnabled(false); } + View titleView = getLayoutInflater().inflate(R.layout.actionbar_home, null); + title = titleView.findViewById(R.id.actionbar_title); + + ActionBar.LayoutParams params = new ActionBar.LayoutParams( + ActionBar.LayoutParams.WRAP_CONTENT, + ActionBar.LayoutParams.MATCH_PARENT, + Gravity.CENTER); + + ab.setCustomView(titleView, params); + ab.setDisplayShowCustomEnabled(true); + ab.setDisplayShowTitleEnabled(false); + + layout = findViewById(R.id.layout); + + recyclerView = findViewById(R.id.recyclerView); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + + preferenceManager = PreferenceManager.getInstance(this); + mispRestClient = new MispRestClient(this); + populateViewsWithInfo(); + populateRecyclerView(); FloatingActionButton sync_fab = findViewById(R.id.home_fab); sync_fab.setOnClickListener(new View.OnClickListener() { @@ -54,29 +97,68 @@ public class HomeActivity extends AppCompatActivity { @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.main_menu_clear_and_logout: - clearDeviceAndLogOut(); - return true; - - default: - // invoke superclass to handle unrecognized item (eg. homeAsUp) - return super.onOptionsItemSelected(item); - + if (item.getItemId() == R.id.main_menu_clear_and_logout) { + clearDeviceAndLogOut(); + return true; } + + if (item.getItemId() == R.id.update) { + updateProfile(); + return true; + } + + // invoke superclass to handle unrecognized item (eg. homeAsUp) + return super.onOptionsItemSelected(item); } + public void updateProfile() { + mispRestClient.getMyUser(new MispRestClient.UserCallback() { + @Override + public void success(final User user) { + + preferenceManager.setUserInfo(user); + + mispRestClient.getOrganisation(user.org_id, new MispRestClient.OrganisationCallback() { + @Override + public void success(Organisation organisation) { + preferenceManager.setUserOrgInfo(organisation); + populateViewsWithInfo(); + } + + @Override + public void failure(String error) { + Snackbar.make(layout, error, Snackbar.LENGTH_LONG).show(); + } + }); + } + + @Override + public void failure(String error) { + Snackbar.make(layout, error, Snackbar.LENGTH_LONG).show(); + } + }); + } + private void populateViewsWithInfo() { - PreferenceManager preferenceManager = PreferenceManager.getInstance(this); - Organisation org = preferenceManager.getUserOrganisation(); + title.setText(org.name); - TextView orgTitle = findViewById(R.id.home_org_name); - TextView orgDesc = findViewById(R.id.home_org_desc); + TextView userCount = findViewById(R.id.user_count); + userCount.setText("" + org.user_count); - orgTitle.setText(org.name); - orgDesc.setText(org.description); + TextView sector = findViewById(R.id.sector); + sector.setText(org.sector); + + TextView nationality = findViewById(R.id.nationality); + nationality.setText(org.nationality); + } + + private void populateRecyclerView() { + List uploadInformationList = preferenceManager.getUploadInformation(); + Log.i(TAG, "Size: " + uploadInformationList.size()); + SyncAdapter syncAdapter = new SyncAdapter(uploadInformationList); + recyclerView.setAdapter(syncAdapter); } private void clearDeviceAndLogOut() { @@ -108,4 +190,9 @@ public class HomeActivity extends AppCompatActivity { builder.create().show(); } + @Override + protected void onResume() { + super.onResume(); + populateRecyclerView(); + } } diff --git a/app/src/main/java/lu/circl/mispbump/activities/LoginActivity.java b/app/src/main/java/lu/circl/mispbump/activities/LoginActivity.java index 734784c..344624e 100644 --- a/app/src/main/java/lu/circl/mispbump/activities/LoginActivity.java +++ b/app/src/main/java/lu/circl/mispbump/activities/LoginActivity.java @@ -1,7 +1,5 @@ package lu.circl.mispbump.activities; -import android.app.AlertDialog; -import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.constraint.ConstraintLayout; @@ -26,14 +24,66 @@ import lu.circl.mispbump.restful_client.MispRestClient; import lu.circl.mispbump.restful_client.Organisation; import lu.circl.mispbump.restful_client.User; +/** + * This activity is shown when the current device has no misp user associated with it. + * Takes care of downloading all information necessary for a sync with other misp instances. + */ public class LoginActivity extends AppCompatActivity { + private PreferenceManager preferenceManager; private ConstraintLayout constraintLayout; - private TextInputLayout serverUrl; private TextInputLayout serverAutomationKey; + private TextInputLayout serverUrl; private ProgressBar progressBar; - private PreferenceManager preferenceManager; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + + // populate Toolbar (Actionbar) + Toolbar myToolbar = findViewById(R.id.appbar); + setSupportActionBar(myToolbar); + + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setDisplayHomeAsUpEnabled(false); + } + + constraintLayout = findViewById(R.id.layout); + progressBar = findViewById(R.id.login_progressbar); + serverUrl = findViewById(R.id.login_server_url); + serverAutomationKey = findViewById(R.id.login_automation_key); + Button downloadInfoButton = findViewById(R.id.login_download_button); + + downloadInfoButton.setOnClickListener(onClickDownload); + + preferenceManager = PreferenceManager.getInstance(this); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_login, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_login_help: + DialogManager.loginHelpDialog(LoginActivity.this); + return true; + + default: + // invoke superclass to handle unrecognized item (eg. homeAsUp) + return super.onOptionsItemSelected(item); + + } + } + + /** + * Is called when the user clicks on the login button. + */ private View.OnClickListener onClickDownload = new View.OnClickListener() { @Override public void onClick(View v) { @@ -60,8 +110,9 @@ public class LoginActivity extends AppCompatActivity { return; } - // save authkey and url for login + // save authkey preferenceManager.setAutomationKey(authkey); + // save url preferenceManager.setServerUrl(url); // instance of MispRestClient with given URL @@ -104,59 +155,22 @@ public class LoginActivity extends AppCompatActivity { } }; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_login); - - // populate Toolbar (Actionbar) - Toolbar myToolbar = findViewById(R.id.toolbar); - setSupportActionBar(myToolbar); - - ActionBar ab = getSupportActionBar(); - if (ab != null) { - ab.setDisplayHomeAsUpEnabled(false); - } - - constraintLayout = findViewById(R.id.login_root); - progressBar = findViewById(R.id.login_progressbar); - serverUrl = findViewById(R.id.login_server_url); - serverAutomationKey = findViewById(R.id.login_automation_key); - Button downloadInfoButton = findViewById(R.id.login_download_button); - - downloadInfoButton.setOnClickListener(onClickDownload); - - preferenceManager = PreferenceManager.getInstance(this); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_login, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_login_help: - showHelpDialog(); - return true; - - default: - // invoke superclass to handle unrecognized item (eg. homeAsUp) - return super.onOptionsItemSelected(item); - - } - } - - private void showHelpDialog() { - DialogManager.loginHelpDialog(LoginActivity.this); - } - + /** + * Check if url is valid. + * + * @param url url to check + * @return true or false + */ private boolean isValidUrl(String url) { return url.startsWith("https://") || url.startsWith("http://"); } + /** + * Check if automation key is valid. + * + * @param automationKey the key to check + * @return true or false + */ private boolean isValidAutomationKey(String automationKey) { return !TextUtils.isEmpty(automationKey); } diff --git a/app/src/main/java/lu/circl/mispbump/activities/MainActivity.java b/app/src/main/java/lu/circl/mispbump/activities/MainActivity.java new file mode 100644 index 0000000..42eac6a --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/activities/MainActivity.java @@ -0,0 +1,24 @@ +package lu.circl.mispbump.activities; + +import android.os.Bundle; +import android.support.design.widget.BottomNavigationView; +import android.support.v7.app.AppCompatActivity; + +import lu.circl.mispbump.R; + +public class MainActivity extends AppCompatActivity { + + private BottomNavigationView bottomNavigationView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + initializeViews(); + } + + private void initializeViews() { +// bottomNavigationView = findViewById(R.id.bottom_navigation); + } +} diff --git a/app/src/main/java/lu/circl/mispbump/activities/SyncActivity.java b/app/src/main/java/lu/circl/mispbump/activities/SyncActivity.java index 67fb392..38517b5 100644 --- a/app/src/main/java/lu/circl/mispbump/activities/SyncActivity.java +++ b/app/src/main/java/lu/circl/mispbump/activities/SyncActivity.java @@ -2,7 +2,9 @@ package lu.circl.mispbump.activities; import android.graphics.Bitmap; import android.os.Bundle; +import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.Snackbar; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.ActionBar; @@ -11,7 +13,6 @@ import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.View; import android.widget.ImageView; -import android.widget.Toast; import com.google.gson.Gson; @@ -28,41 +29,72 @@ import lu.circl.mispbump.auxiliary.RandomString; import lu.circl.mispbump.cam.CameraFragment; import lu.circl.mispbump.fragments.SyncOptionsFragment; import lu.circl.mispbump.models.SyncInformation; +import lu.circl.mispbump.models.UploadInformation; import lu.circl.mispbump.restful_client.MispRestClient; import lu.circl.mispbump.restful_client.MispServer; import lu.circl.mispbump.restful_client.Organisation; import lu.circl.mispbump.restful_client.Server; import lu.circl.mispbump.restful_client.User; -import lu.circl.mispbump.security.AESSecurity; +import lu.circl.mispbump.security.DiffieHellman; /** - * Step 1: Add partner org as local org (uuid must be the same) - * Step 2: Add SyncUser to local partner org - * Step 3: Add SyncServer with SyncUser's authkey - *

- * What do we need to transmit? - * 1. Own organisation details - * 2. Authkey of SyncUser - * 3. Server url + * This class provides the sync functionality. + * It collects the necessary information, guides through the process and finally completes with + * the upload to the misp instance. */ public class SyncActivity extends AppCompatActivity { private static final String TAG = "SyncActivity"; - private AESSecurity aesSecurity; - private MispRestClient restClient; - private CameraFragment cameraFragment; + private CoordinatorLayout layout; private ImageView qrCodeView; private FloatingActionButton continueButton; + private CameraFragment cameraFragment; + private DiffieHellman diffieHellman; + private MispRestClient restClient; + + private UploadInformation uploadInformation; + private SyncState currentSyncState = SyncState.publicKeyExchange; + + private PreferenceManager preferenceManager; + private enum SyncState { publicKeyExchange, dataExchange } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sync); + + Toolbar myToolbar = findViewById(R.id.appbar); + setSupportActionBar(myToolbar); + + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setDisplayHomeAsUpEnabled(true); + } + + layout = findViewById(R.id.layout); + + qrCodeView = findViewById(R.id.qrcode); + continueButton = findViewById(R.id.continue_fab); + continueButton.setOnClickListener(onContinueClicked); + continueButton.hide(); + + diffieHellman = DiffieHellman.getInstance(); + restClient = new MispRestClient(this); + + preferenceManager = PreferenceManager.getInstance(this); + + enableSyncOptionsFragment(); + } + /** - * Callback to any + * This callback is called at the end of each sync step. */ private View.OnClickListener onContinueClicked = new View.OnClickListener() { @Override @@ -72,7 +104,6 @@ public class SyncActivity extends AppCompatActivity { switch (currentSyncState) { case publicKeyExchange: - DialogManager.confirmProceedDialog(SyncActivity.this, new DialogManager.IDialogFeedback() { @Override @@ -85,13 +116,21 @@ public class SyncActivity extends AppCompatActivity { @Override public void negative() { - // do nothing, just wait } }); break; case dataExchange: - // TODO upload + DialogManager.confirmProceedDialog(SyncActivity.this, new DialogManager.IDialogFeedback() { + @Override + public void positive() { + startUpload(); + } + + @Override + public void negative() { + } + }); break; } } @@ -109,69 +148,151 @@ public class SyncActivity extends AppCompatActivity { switch (currentSyncState) { case publicKeyExchange: try { - final PublicKey pk = AESSecurity.publicKeyFromString(qrData); + final PublicKey pk = DiffieHellman.publicKeyFromString(qrData); + diffieHellman.setForeignPublicKey(pk); - DialogManager.publicKeyDialog(pk.toString(), SyncActivity.this, - new DialogManager.IDialogFeedback() { - @Override - public void positive() { - aesSecurity.setForeignPublicKey(pk); - continueButton.show(); - } + runOnUiThread(new Runnable() { + @Override + public void run() { + continueButton.show(); + + Snackbar sb = Snackbar.make(continueButton, "Public key received", Snackbar.LENGTH_LONG); + sb.setAction("Details", new View.OnClickListener() { @Override - public void negative() { - // enable qr read again to scan another pk - cameraFragment.setReadQrEnabled(true); + public void onClick(View v) { + DialogManager.publicKeyDialog(pk, SyncActivity.this, null); } }); + sb.show(); + } + }); } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { - MakeToast("Invalid key"); + Snackbar.make(layout, "Invalid key", Snackbar.LENGTH_SHORT).show(); } - break; case dataExchange: - // disable qr read cameraFragment.setReadQrEnabled(false); - String data = aesSecurity.decrypt(qrData); - SyncInformation info = new Gson().fromJson(data, SyncInformation.class); + final SyncInformation remoteSyncInfo = new Gson().fromJson(diffieHellman.decrypt(qrData), SyncInformation.class); - Log.i(TAG, info.organisation.toString()); - Log.i(TAG, info.user.toString()); + DialogManager.syncInformationDialog(remoteSyncInfo, + SyncActivity.this, + new DialogManager.IDialogFeedback() { + @Override + public void positive() { + uploadInformation.remote = remoteSyncInfo; + continueButton.show(); + } + + @Override + public void negative() { + cameraFragment.setReadQrEnabled(true); + } + }); break; } } }; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_sync); + private void startUpload() { + // check if misp instance is available + restClient.isAvailable(new MispRestClient.AvailableCallback() { + @Override + public void unavailable() { + Snackbar sb = Snackbar.make(layout, "MISP instance not available", Snackbar.LENGTH_LONG); + sb.setAction("Retry", new View.OnClickListener() { + @Override + public void onClick(View v) { + startUpload(); // TODO check if this works + } + }); + sb.show(); + } - Toolbar myToolbar = findViewById(R.id.toolbar); - setSupportActionBar(myToolbar); + @Override + public void available() { - ActionBar ab = getSupportActionBar(); - if (ab != null) { - ab.setDisplayHomeAsUpEnabled(true); - } + restClient.addOrganisation(uploadInformation.remote.organisation, new MispRestClient.OrganisationCallback() { + @Override + public void success(final Organisation organisation) { + // create syncUser object from syncInfo + User syncUser = new User(); + syncUser.org_id = organisation.id; + syncUser.role_id = User.ROLE_SYNC_USER; - qrCodeView = findViewById(R.id.qrcode); - continueButton = findViewById(R.id.continue_fab); - continueButton.setOnClickListener(onContinueClicked); - continueButton.hide(); + // syncuser_ORG@REMOTE_ORG_EMAIL_DOMAIN + String emailSaveOrgName = organisation.name.replace(" ", "").toLowerCase(); + syncUser.email = "syncuser_" + emailSaveOrgName + "@misp.de"; - aesSecurity = AESSecurity.getInstance(); - restClient = new MispRestClient(this); + syncUser.password = uploadInformation.remote.syncUserPassword; + syncUser.authkey = uploadInformation.remote.syncUserAuthkey; + syncUser.termsaccepted = true; - enableSyncOptionsFragment(); + // add user to local organisation + restClient.addUser(syncUser, new MispRestClient.UserCallback() { + @Override + public void success(User user) { + Server server = new Server(); + server.name = organisation.name + "'s Sync Server"; + server.url = uploadInformation.remote.baseUrl; + server.remote_org_id = organisation.id; + server.authkey = uploadInformation.local.syncUserAuthkey; + server.self_signed = true; + + restClient.addServer(server, new MispRestClient.ServerCallback() { + @Override + public void success(List servers) { + } + + @Override + public void success(MispServer server) { + } + + @Override + public void success(Server server) { + uploadInformation.currentSyncStatus = UploadInformation.SyncStatus.COMPLETE; + preferenceManager.setUploadInformation(uploadInformation); + finish(); + } + + @Override + public void failure(String error) { + uploadInformation.currentSyncStatus = UploadInformation.SyncStatus.FAILURE; + preferenceManager.setUploadInformation(uploadInformation); + Snackbar.make(layout, error, Snackbar.LENGTH_LONG).show(); + Log.e(TAG, error); + } + }); + } + + @Override + public void failure(String error) { + uploadInformation.currentSyncStatus = UploadInformation.SyncStatus.FAILURE; + preferenceManager.setUploadInformation(uploadInformation); + Snackbar.make(layout, error, Snackbar.LENGTH_LONG).show(); + Log.e(TAG, error); + } + }); + } + + @Override + public void failure(String error) { + uploadInformation.currentSyncStatus = UploadInformation.SyncStatus.FAILURE; + preferenceManager.setUploadInformation(uploadInformation); + Snackbar.make(layout, error, Snackbar.LENGTH_LONG).show(); + Log.e(TAG, error); + } + }); + } + }); } /** * Creates the camera fragment used to scan the QR codes. + * Automatically starts processing images (search QR codes). */ private void enableCameraFragment() { cameraFragment = new CameraFragment(); @@ -185,7 +306,7 @@ public class SyncActivity extends AppCompatActivity { } /** - * options for this particular sync + * Creates fragment to tweak sync options. */ private void enableSyncOptionsFragment() { SyncOptionsFragment syncOptionsFragment = new SyncOptionsFragment(); @@ -205,39 +326,35 @@ public class SyncActivity extends AppCompatActivity { } /** - * Display public key QR code. + * Display QR code that contains the public key . */ private void showPublicKeyQr() { QrCodeGenerator qrCodeGenerator = new QrCodeGenerator(this); - Bitmap bm = qrCodeGenerator.generateQrCode(AESSecurity.publicKeyToString(aesSecurity.getPublicKey())); + Bitmap bm = qrCodeGenerator.generateQrCode(DiffieHellman.publicKeyToString(diffieHellman.getPublicKey())); qrCodeView.setImageBitmap(bm); qrCodeView.setVisibility(View.VISIBLE); } /** - * Display sync info QR code. + * Display QR code that contains mandatory information for a sync. */ private void showInformationQr() { PreferenceManager preferenceManager = PreferenceManager.getInstance(this); + + SyncInformation syncInformation = new SyncInformation(); + + syncInformation.organisation = preferenceManager.getUserOrganisation().syncOrganisation(); + syncInformation.syncUserAuthkey = new RandomString(40).nextString(); + syncInformation.baseUrl = preferenceManager.getServerUrl(); + syncInformation.syncUserPassword = "abcdefghijklmnop"; + + uploadInformation = new UploadInformation(syncInformation); + + // encrypt serialized content + String encrypted = diffieHellman.encrypt(new Gson().toJson(syncInformation)); + + // Generate QR code QrCodeGenerator qrCodeGenerator = new QrCodeGenerator(this); - Gson gson = new Gson(); - - // my organisation - Organisation org = preferenceManager.getUserOrganisation(); - User user = preferenceManager.getUserInfo(); - - Server server = new Server( - "SyncServer for " + org.name, - preferenceManager.getServerUrl(), - new RandomString(40).nextString(), - -1 - ); - - MispServer mispServer = new MispServer(server, org, null); - - SyncInformation syncInformation = new SyncInformation(user, org, server); - String encrypted = aesSecurity.encrypt(gson.toJson(syncInformation)); - final Bitmap bm = qrCodeGenerator.generateQrCode(encrypted); runOnUiThread(new Runnable() { @@ -249,61 +366,19 @@ public class SyncActivity extends AppCompatActivity { }); } - private void addPartnerOrg(Organisation organisation) { - restClient.addOrganisation(organisation, new MispRestClient.OrganisationCallback() { - @Override - public void success(Organisation organisation) { - - } - - @Override - public void failure(String error) { - - } - }); - } - - private void addSyncUser(User user) { - restClient.addUser(user, new MispRestClient.UserCallback() { - @Override - public void success(User user) { - - } - - @Override - public void failure(String error) { - - } - }); - } - - private void addServer(MispServer server) { - restClient.addServer(server, new MispRestClient.ServerCallback() { - @Override - public void success(List servers) { - - } - - @Override - public void success(MispServer server) { - - } - - @Override - public void failure(String error) { - - } - }); - } - - private void MakeToast(final String message) { - this.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); - } - }); - } +// /** +// * Display toast on UI thread. +// * +// * @param message message to display +// */ +// private void MakeToast(final String message) { +// this.runOnUiThread(new Runnable() { +// @Override +// public void run() { +// Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); +// } +// }); +// } // private View.OnClickListener onGetServers = new View.OnClickListener() { // @Override diff --git a/app/src/main/java/lu/circl/mispbump/adapters/SyncAdapter.java b/app/src/main/java/lu/circl/mispbump/adapters/SyncAdapter.java new file mode 100644 index 0000000..e513ad2 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/adapters/SyncAdapter.java @@ -0,0 +1,76 @@ +package lu.circl.mispbump.adapters; + +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.TextView; + +import java.util.List; + +import lu.circl.mispbump.R; +import lu.circl.mispbump.models.UploadInformation; + +public class SyncAdapter extends RecyclerView.Adapter { + + private List uploadInformationList; + + static class SyncViewHolder extends RecyclerView.ViewHolder { + TextView title, status; + ImageButton retry, delete; + + SyncViewHolder(View v) { + super(v); + + title = v.findViewById(R.id.title); + status = v.findViewById(R.id.syncStatus); + + retry = v.findViewById(R.id.retry_button); + delete = v.findViewById(R.id.delete_button); + } + } + + public SyncAdapter(List uploadInformationList) { + this.uploadInformationList = uploadInformationList; + } + + @NonNull + @Override + public SyncViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { + View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.viewholder_sync, viewGroup, false); + return new SyncViewHolder(v); + } + + @Override + public void onBindViewHolder(@NonNull SyncViewHolder syncViewHolder, int i) { + syncViewHolder.title.setText(uploadInformationList.get(i).remote.organisation.name); + + switch (uploadInformationList.get(i).currentSyncStatus) { + case COMPLETE: + syncViewHolder.status.setText("Synced"); + syncViewHolder.retry.setVisibility(View.GONE); + break; + case FAILURE: + syncViewHolder.status.setText("Error"); + syncViewHolder.retry.setVisibility(View.VISIBLE); + break; + case PENDING: + syncViewHolder.status.setText("Pending"); + syncViewHolder.retry.setVisibility(View.GONE); + break; + } + } + + @Override + public int getItemCount() { + if (uploadInformationList == null) { + return 0; + } + + return uploadInformationList.size(); + } + +} diff --git a/app/src/main/java/lu/circl/mispbump/auxiliary/DialogManager.java b/app/src/main/java/lu/circl/mispbump/auxiliary/DialogManager.java index f8f1833..b511039 100644 --- a/app/src/main/java/lu/circl/mispbump/auxiliary/DialogManager.java +++ b/app/src/main/java/lu/circl/mispbump/auxiliary/DialogManager.java @@ -5,7 +5,11 @@ import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import java.security.PublicKey; + import lu.circl.mispbump.R; +import lu.circl.mispbump.models.SyncInformation; +import lu.circl.mispbump.security.DiffieHellman; /** * Creates and show dialogs. @@ -20,21 +24,60 @@ public class DialogManager { * @param context needed to build and show the dialog * @param callback {@link IDialogFeedback} */ - public static void publicKeyDialog(String publicKey, Context context, final IDialogFeedback callback) { + public static void publicKeyDialog(PublicKey publicKey, Context context, final IDialogFeedback callback) { final AlertDialog.Builder adb = new AlertDialog.Builder(context); - adb.setTitle("Public Key Received"); - adb.setMessage(publicKey); + adb.setTitle("Public Key"); + + String message = "Algorithm: " + publicKey.getAlgorithm() + "\n" + + "Format: " + publicKey.getFormat() + "\n" + + "Content: \n" + DiffieHellman.publicKeyToString(publicKey); + + adb.setMessage(message); + adb.setPositiveButton("Okay", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (callback != null) { + callback.positive(); + } + } + }); + + Activity act = (Activity) context; + + act.runOnUiThread(new Runnable() { + @Override + public void run() { + adb.create().show(); + } + }); + } + + /** + * Dialog to display a received public key. + * + * @param syncInformation {@link SyncInformation} + * @param context needed to build and show the dialog + * @param callback {@link IDialogFeedback} + */ + public static void syncInformationDialog(SyncInformation syncInformation, Context context, final IDialogFeedback callback) { + final AlertDialog.Builder adb = new AlertDialog.Builder(context); + adb.setTitle("Sync information received"); + adb.setMessage(syncInformation.organisation.name); adb.setPositiveButton("Accept", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - callback.positive(); + if (callback != null) { + callback.positive(); + } } }); adb.setNegativeButton("Reject", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - callback.negative(); + if (callback != null) { + callback.negative(); + } } }); @@ -56,24 +99,27 @@ public class DialogManager { */ public static void confirmProceedDialog(Context context, final IDialogFeedback callback) { final AlertDialog.Builder adb = new AlertDialog.Builder(context); - adb.setTitle("Really continue?"); - adb.setMessage("Was this QR Code already scanned by your partner?"); - adb.setPositiveButton("Yes, continue", new DialogInterface.OnClickListener() { + adb.setTitle("Continue?"); + adb.setMessage("Only continue if your partner already scanned this QR code"); + adb.setPositiveButton("Continue", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - callback.positive(); + if (callback != null) { + callback.positive(); + } } }); - adb.setNegativeButton("No, show QR Code", new DialogInterface.OnClickListener() { + adb.setNegativeButton("Show QR code again", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - callback.negative(); + if (callback != null) { + callback.negative(); + } } }); Activity act = (Activity) context; - act.runOnUiThread(new Runnable() { @Override public void run() { diff --git a/app/src/main/java/lu/circl/mispbump/auxiliary/PreferenceManager.java b/app/src/main/java/lu/circl/mispbump/auxiliary/PreferenceManager.java index 1963166..c13c28d 100644 --- a/app/src/main/java/lu/circl/mispbump/auxiliary/PreferenceManager.java +++ b/app/src/main/java/lu/circl/mispbump/auxiliary/PreferenceManager.java @@ -2,10 +2,13 @@ package lu.circl.mispbump.auxiliary; import android.content.Context; import android.content.SharedPreferences; +import android.util.Log; import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import java.io.IOException; +import java.lang.reflect.Type; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; @@ -13,17 +16,23 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.List; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; +import lu.circl.mispbump.models.UploadInformation; import lu.circl.mispbump.restful_client.Organisation; import lu.circl.mispbump.restful_client.User; import lu.circl.mispbump.security.KeyStoreWrapper; +import static android.support.constraint.Constraints.TAG; + public class PreferenceManager { + private static final String TAG = "PreferenceManager"; private static final String PREFERENCES_FILE = "user_settings"; @@ -34,6 +43,8 @@ public class PreferenceManager { private static final String USER_INFOS = "user_infos"; private static final String USER_ORG_INFOS = "user_org_infos"; + private static final String UPLOAD_INFO = "upload_info"; + private SharedPreferences preferences; private static PreferenceManager instance; @@ -59,7 +70,7 @@ public class PreferenceManager { /** * Saves user infos from "users/view/me" (encrypted) * - * @param user + * @param user {@link User} */ public void setUserInfo(User user) { try { @@ -178,7 +189,7 @@ public class PreferenceManager { /** * Encrypts the automation key and stores it in preferences. * - * @param automationKey + * @param automationKey key entered in {@link lu.circl.mispbump.activities.LoginActivity} */ public void setAutomationKey(String automationKey) { try { @@ -202,7 +213,8 @@ public class PreferenceManager { /** * Decrypts the stored automation key and returns it. * - * @return the decr + * @return decrypted automation key associated with the current user. If no user exists an empty + * String is returned. */ public String getAutomationKey() { @@ -316,6 +328,37 @@ public class PreferenceManager { } + public void setUploadInformation(UploadInformation uploadInformation) { + String storedUploadInfoString = preferences.getString(UPLOAD_INFO, ""); + + Type type = new TypeToken>() {}.getType(); + List dataList; + + if (storedUploadInfoString.isEmpty()) { + dataList = new ArrayList<>(); + } else { + dataList = new Gson().fromJson(storedUploadInfoString, type); + } + + dataList.add(uploadInformation); + + SharedPreferences.Editor editor = preferences.edit(); + editor.putString(UPLOAD_INFO, new Gson().toJson(dataList)); + editor.apply(); + } + + public List getUploadInformation() { + String storedUploadInfoString = preferences.getString(UPLOAD_INFO, ""); + + if (storedUploadInfoString.isEmpty()) { + return null; + } + + Type type = new TypeToken>() {}.getType(); + return new Gson().fromJson(storedUploadInfoString, type); + } + + /** * Set if credentials (authkey & server url) should be saved locally. * diff --git a/app/src/main/java/lu/circl/mispbump/models/SyncInformation.java b/app/src/main/java/lu/circl/mispbump/models/SyncInformation.java index fb83f3f..abb3501 100644 --- a/app/src/main/java/lu/circl/mispbump/models/SyncInformation.java +++ b/app/src/main/java/lu/circl/mispbump/models/SyncInformation.java @@ -1,8 +1,6 @@ package lu.circl.mispbump.models; import lu.circl.mispbump.restful_client.Organisation; -import lu.circl.mispbump.restful_client.Server; -import lu.circl.mispbump.restful_client.User; /** * A Class that holds the information needed synchronize two misp instances. @@ -10,13 +8,10 @@ import lu.circl.mispbump.restful_client.User; */ public class SyncInformation { - public User user; public Organisation organisation; - public Server server; + public String syncUserPassword; + public String syncUserAuthkey; + public String baseUrl; - public SyncInformation(User user, Organisation organisation, Server server) { - this.user = user; - this.organisation = organisation; - this.server = server; - } + public SyncInformation() {} } diff --git a/app/src/main/java/lu/circl/mispbump/models/UploadInformation.java b/app/src/main/java/lu/circl/mispbump/models/UploadInformation.java new file mode 100644 index 0000000..b61b4ae --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/models/UploadInformation.java @@ -0,0 +1,27 @@ +package lu.circl.mispbump.models; + +public class UploadInformation { + + public enum SyncStatus { + COMPLETE, + FAILURE, + PENDING + } + + public SyncStatus currentSyncStatus = SyncStatus.PENDING; + public SyncInformation local; + public SyncInformation remote; + + public UploadInformation() { + } + + public UploadInformation(SyncInformation local) { + this.local = local; + } + + public UploadInformation(SyncInformation local, SyncInformation remote) { + this.local = local; + this.remote = remote; + } + +} diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/MispRestClient.java b/app/src/main/java/lu/circl/mispbump/restful_client/MispRestClient.java index 7c781e1..9c89081 100644 --- a/app/src/main/java/lu/circl/mispbump/restful_client/MispRestClient.java +++ b/app/src/main/java/lu/circl/mispbump/restful_client/MispRestClient.java @@ -2,9 +2,18 @@ package lu.circl.mispbump.restful_client; import android.annotation.SuppressLint; import android.content.Context; +import android.util.Log; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; + +import org.json.JSONException; +import org.json.JSONObject; import java.io.IOException; import java.security.cert.CertificateException; +import java.util.Iterator; import java.util.List; import javax.net.ssl.HostnameVerifier; @@ -18,6 +27,7 @@ import lu.circl.mispbump.auxiliary.PreferenceManager; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; +import okhttp3.ResponseBody; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Call; import retrofit2.Callback; @@ -31,6 +41,13 @@ import retrofit2.converter.gson.GsonConverterFactory; */ public class MispRestClient { + private static final String TAG = "restClient"; + + public interface AvailableCallback { + void available(); + void unavailable(); + } + public interface UserCallback { void success(User user); void failure(String error); @@ -44,6 +61,7 @@ public class MispRestClient { public interface ServerCallback { void success(List servers); void success(MispServer server); + void success(Server server); void failure(String error); } @@ -58,9 +76,13 @@ public class MispRestClient { public MispRestClient(Context context) { preferenceManager = PreferenceManager.getInstance(context); + String url = preferenceManager.getServerUrl(); + + Log.i(TAG, "URL: " + url); + try { Retrofit retrofit = new Retrofit.Builder() - .baseUrl(preferenceManager.getServerUrl()) + .baseUrl(url) .addConverterFactory(GsonConverterFactory.create()) .client(getUnsafeOkHttpClient()) .build(); @@ -137,11 +159,41 @@ public class MispRestClient { } } + // status routes + + /** + * Check via pyMispRoute if server is available + * @param callback {@link AvailableCallback} + */ + public void isAvailable(final AvailableCallback callback) { + Call call = mispRestService.pyMispVersion(); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (!response.isSuccessful()) { + if (response.code() == 403) { + callback.available(); + return; + } + + callback.unavailable(); + } else { + callback.available(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + callback.unavailable(); + } + }); + } + // user routes /** * Fetches information about the user that is associated with saved auth key. - * @param callback wrapper to return a user directly + * @param callback {@link UserCallback} wrapper to return user directly */ public void getMyUser(final UserCallback callback) { Call call = mispRestService.getMyUserInformation(); @@ -150,7 +202,7 @@ public class MispRestClient { @Override public void onResponse(Call call, Response response) { if(!response.isSuccessful()) { - callback.failure("" + response.code()); + callback.failure(extractError(response)); } else { if (response.body() != null) { callback.success(response.body().user); @@ -171,7 +223,7 @@ public class MispRestClient { /** * Get an user with specific ID. * @param userId user identifier - * @param callback wrapper to return user directly + * @param callback {@link UserCallback} wrapper to return user directly */ public void getUser(int userId, final UserCallback callback) { Call call = mispRestService.getUser(userId); @@ -180,7 +232,7 @@ public class MispRestClient { @Override public void onResponse(Call call, Response response) { if(!response.isSuccessful()) { - callback.failure("" + response.code()); + callback.failure(extractError(response)); } else { if (response.body() != null) { callback.success(response.body().user); @@ -201,7 +253,7 @@ public class MispRestClient { /** * Add a given user to the MISP instance referenced by url in preferences. * @param user user to add - * @param callback wrapper to return the created user directly + * @param callback {@link UserCallback} wrapper to return the created user directly */ public void addUser(User user, final UserCallback callback) { Call call = mispRestService.addUser(user); @@ -209,12 +261,11 @@ public class MispRestClient { call.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { - if (!response.isSuccessful()) { - callback.failure("" + response.code()); - return; + if(!response.isSuccessful()) { + callback.failure(extractError(response)); + } else { + callback.success(response.body().user); } - - callback.success(response.body().user); } @Override @@ -230,7 +281,7 @@ public class MispRestClient { /** * Get an organisation by a given organisation id. * @param orgId organisation identifier - * @param callback wrapper to return a organisation directly + * @param callback {@link OrganisationCallback} wrapper to return a organisation directly */ public void getOrganisation(int orgId, final OrganisationCallback callback) { Call call = mispRestService.getOrganisation(orgId); @@ -239,7 +290,7 @@ public class MispRestClient { @Override public void onResponse(Call call, Response response) { if(!response.isSuccessful()) { - callback.failure("" + response.code()); + callback.failure(extractError(response)); } else { if (response.body() != null) { callback.success(response.body().organisation); @@ -259,7 +310,7 @@ public class MispRestClient { /** * Add a given organisation to the MISP instance referenced by url in preferences. * @param organisation organisation to add - * @param callback wrapper to return the created organisation directly + * @param callback {@link OrganisationCallback} wrapper to return the created organisation directly */ public void addOrganisation(Organisation organisation, final OrganisationCallback callback) { Call call = mispRestService.addOrganisation(organisation); @@ -267,12 +318,11 @@ public class MispRestClient { call.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { - if (!response.isSuccessful()) { - callback.failure("" + response.code()); - return; + if(!response.isSuccessful()) { + callback.failure(extractError(response)); + } else { + callback.success(response.body().organisation); } - - callback.success(response.body().organisation); } @Override @@ -286,7 +336,7 @@ public class MispRestClient { /** * Get all servers on MISP instance. - * @param callback wrapper to return a list of servers directly + * @param callback {@link OrganisationCallback} wrapper to return a list of servers directly */ public void getServers(final ServerCallback callback) { Call> call = mispRestService.getServers(); @@ -294,12 +344,11 @@ public class MispRestClient { call.enqueue(new Callback>() { @Override public void onResponse(Call> call, Response> response) { - if (!response.isSuccessful()) { - callback.failure("" + response.code()); - return; + if(!response.isSuccessful()) { + callback.failure(extractError(response)); + } else { + callback.success(response.body()); } - - callback.success(response.body()); } @Override @@ -312,26 +361,87 @@ public class MispRestClient { /** * Add a server to the MISP instance * @param server the server to create - * @param callback wrapper to return the created server directly + * @param callback {@link ServerCallback} wrapper to return the created server directly */ - public void addServer(MispServer server, final ServerCallback callback) { - Call call = mispRestService.addServer(server); +// public void addServer(MispServer server, final ServerCallback callback) { +// Call call = mispRestService.addServer(server); +// +// call.enqueue(new Callback() { +// @Override +// public void onResponse(Call call, Response response) { +// if(!response.isSuccessful()) { +// callback.failure(extractError(response)); +// } else { +// callback.success(response.body()); +// } +// } +// +// @Override +// public void onFailure(Call call, Throwable t) { +// callback.failure(t.getMessage()); +// } +// }); +// } - call.enqueue(new Callback() { + public void addServer(Server server, final ServerCallback callback) { + Call call = mispRestService.addServer(server); + + call.enqueue(new Callback() { @Override - public void onResponse(Call call, Response response) { + public void onResponse(Call call, Response response) { if (!response.isSuccessful()) { - callback.failure("" + response.code()); - return; + callback.failure(extractError(response)); + } else { + callback.success(response.body()); } - - callback.success(response.body()); } @Override - public void onFailure(Call call, Throwable t) { + public void onFailure(Call call, Throwable t) { callback.failure(t.getMessage()); } }); } + + private String extractError(Response response) { + switch (response.code()) { + // bad request (malformed) + case 400: + return "Bad request"; + + // unauthorized + case 401: + return "Unauthorized"; + + // forbidden + case 403: + try { + JSONObject jsonError = new JSONObject(response.errorBody().string()); + + String name = jsonError.getString("name") + "\n"; + String reasons = ""; + JSONObject errorReasons = jsonError.getJSONObject("errors"); + + Iterator errorKeys = errorReasons.keys(); + + while (errorKeys.hasNext()) { + reasons = reasons.concat(errorReasons.getString(errorKeys.next()) + "\n"); + } + + return name + reasons; + } catch (JSONException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + return "Could not parse (403) error"; + + // not found + case 404: + return "Not found"; + } + + return "Unknown error"; + } } \ No newline at end of file diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/MispRestService.java b/app/src/main/java/lu/circl/mispbump/restful_client/MispRestService.java index eeb7786..92daf1a 100644 --- a/app/src/main/java/lu/circl/mispbump/restful_client/MispRestService.java +++ b/app/src/main/java/lu/circl/mispbump/restful_client/MispRestService.java @@ -13,6 +13,11 @@ import retrofit2.http.Path; */ public interface MispRestService { + // settings routes + + @GET("servers/getPyMISPVersion") + Call pyMispVersion(); + // user routes @GET("users/view/me") @@ -37,6 +42,9 @@ public interface MispRestService { @GET("servers/index") Call> getServers(); +// @POST("servers/add") +// Call addServer(@Body MispServer server); + @POST("servers/add") - Call addServer(@Body MispServer server); + Call addServer(@Body Server server); } \ No newline at end of file diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/MispServer.java b/app/src/main/java/lu/circl/mispbump/restful_client/MispServer.java index d8038b8..1c7b9b3 100644 --- a/app/src/main/java/lu/circl/mispbump/restful_client/MispServer.java +++ b/app/src/main/java/lu/circl/mispbump/restful_client/MispServer.java @@ -6,6 +6,8 @@ import com.google.gson.annotations.SerializedName; public class MispServer { + public MispServer() {} + public MispServer(Server server, Organisation organisation, Organisation remoteOrganisation) { this.server = server; this.organisation = organisation; diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/Organisation.java b/app/src/main/java/lu/circl/mispbump/restful_client/Organisation.java index 7ec4fb4..dcf0539 100644 --- a/app/src/main/java/lu/circl/mispbump/restful_client/Organisation.java +++ b/app/src/main/java/lu/circl/mispbump/restful_client/Organisation.java @@ -1,13 +1,13 @@ package lu.circl.mispbump.restful_client; -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; - /** * Information gathered from Misp API about a organisation. */ public class Organisation { + public Organisation() { + } + public Organisation(String name) { this.name = name; } @@ -17,49 +17,35 @@ public class Organisation { this.description = description; } - @SerializedName("id") - @Expose public Integer id; - @SerializedName("name") - @Expose public String name; - @SerializedName("date_created") - @Expose public String date_created; - @SerializedName("date_modified") - @Expose public String date_modified; - @SerializedName("type") - @Expose public String type; - @SerializedName("nationality") - @Expose public String nationality; - @SerializedName("sector") - @Expose public String sector; - @SerializedName("contacts") - @Expose public String contacts; - @SerializedName("description") - @Expose public String description; - @SerializedName("local") - @Expose public Boolean local; - @SerializedName("uuid") - @Expose public String uuid; - @SerializedName("restricted_to_domain") - @Expose public String restricted_to_domain; - @SerializedName("created_by") - @Expose public String created_by; - @SerializedName("user_count") - @Expose public Integer user_count; + public Organisation syncOrganisation() { + Organisation organisation = new Organisation(); + organisation.local = true; + organisation.name = name; + organisation.uuid = uuid; + organisation.description = description; + organisation.nationality = nationality; + organisation.sector = sector; + organisation.type = "Sync organisation"; + organisation.contacts = contacts; + + return organisation; + } + @Override public String toString() { return "Organisation{" + diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/Server.java b/app/src/main/java/lu/circl/mispbump/restful_client/Server.java index 0ec307c..99ba9ad 100644 --- a/app/src/main/java/lu/circl/mispbump/restful_client/Server.java +++ b/app/src/main/java/lu/circl/mispbump/restful_client/Server.java @@ -1,10 +1,11 @@ package lu.circl.mispbump.restful_client; -import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; public class Server { + public Server() {} + public Server(String name, String url, String authkey, Integer remote_org_id) { this.name = name; this.url = url; @@ -13,91 +14,69 @@ public class Server { } @SerializedName("id") - @Expose public Integer id; @SerializedName("name") - @Expose public String name; @SerializedName("url") - @Expose public String url; @SerializedName("authkey") - @Expose public String authkey; @SerializedName("org_id") - @Expose public Integer org_id; @SerializedName("push") - @Expose public Boolean push; @SerializedName("pull") - @Expose public Boolean pull; @SerializedName("lastpulledid") - @Expose public Object lastpulledid; @SerializedName("lastpushedid") - @Expose public Object lastpushedid; @SerializedName("organization") - @Expose public Object organization; @SerializedName("remote_org_id") - @Expose public Integer remote_org_id; @SerializedName("publish_without_email") - @Expose - public Boolean publish_without_email; + public Boolean publish_without_email = false; @SerializedName("unpublish_event") - @Expose public Boolean unpublish_event; @SerializedName("self_signed") - @Expose - public Boolean self_signed; + public Boolean self_signed = false; @SerializedName("pull_rules") - @Expose public String pull_rules; @SerializedName("push_rules") - @Expose public String push_rules; @SerializedName("cert_file") - @Expose public Object cert_file; @SerializedName("client_cert_file") - @Expose public Object client_cert_file; @SerializedName("internal") - @Expose public Boolean internal; @SerializedName("skip_proxy") - @Expose - public Boolean skip_proxy; + public Boolean skip_proxy = false; @SerializedName("caching_enabled") - @Expose public Boolean caching_enabled; @SerializedName("cache_timestamp") - @Expose public Boolean cache_timestamp; @Override diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/User.java b/app/src/main/java/lu/circl/mispbump/restful_client/User.java index 1458bc1..4535419 100644 --- a/app/src/main/java/lu/circl/mispbump/restful_client/User.java +++ b/app/src/main/java/lu/circl/mispbump/restful_client/User.java @@ -12,6 +12,9 @@ public class User { public static final int ROLE_SYNC_USER = 5; public static final int ROLE_READ_ONLY = 6; + public User() { + } + public User(Integer org_id, String email, Integer role_id) { this.org_id = org_id; this.email = email; diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/Version.java b/app/src/main/java/lu/circl/mispbump/restful_client/Version.java new file mode 100644 index 0000000..5ba921e --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/restful_client/Version.java @@ -0,0 +1,10 @@ +package lu.circl.mispbump.restful_client; + +import com.google.gson.annotations.SerializedName; + +public class Version { + + @SerializedName("version") + public String version; + +} diff --git a/app/src/main/java/lu/circl/mispbump/security/AESSecurity.java b/app/src/main/java/lu/circl/mispbump/security/DiffieHellman.java similarity index 90% rename from app/src/main/java/lu/circl/mispbump/security/AESSecurity.java rename to app/src/main/java/lu/circl/mispbump/security/DiffieHellman.java index 32b7b8e..6a0ebb7 100644 --- a/app/src/main/java/lu/circl/mispbump/security/AESSecurity.java +++ b/app/src/main/java/lu/circl/mispbump/security/DiffieHellman.java @@ -12,9 +12,11 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; -public class AESSecurity { - - private static final String TAG = "MISP_LOGGING"; +/** + * This class provides the functionality generate a shared secret key. + * Furthermore it contains the encryption/decryption methods. + */ +public class DiffieHellman { private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; private static final String KEY_PAIR_ALGORITHM = "EC"; @@ -22,7 +24,7 @@ public class AESSecurity { private static final String KEY_AGREEMENT_ALGORITHM = "ECDH"; private static final String KEY_FACTORY_ALGORITHM = "EC"; - private static AESSecurity instance; + private static DiffieHellman instance; private PublicKey publickey; private KeyAgreement keyAgreement; @@ -30,14 +32,19 @@ public class AESSecurity { private byte[] sharedSecret; private IvParameterSpec ivParameterSpec; - private AESSecurity() { + private DiffieHellman() { initialize(); } - public static AESSecurity getInstance() { + /** + * Singleton pattern + * @return {@link DiffieHellman} + */ + public static DiffieHellman getInstance() { if(instance == null) { - instance = new AESSecurity(); + instance = new DiffieHellman(); } + return instance; } @@ -102,7 +109,7 @@ public class AESSecurity { } /** - * Decrypts data. + * Decrypts data with the current shared secret. * @param data data to decrypt * @return To String converted and decrypted data */ diff --git a/app/src/main/java/lu/circl/mispbump/security/KeyStoreWrapper.java b/app/src/main/java/lu/circl/mispbump/security/KeyStoreWrapper.java index 01e5d67..7e044e1 100644 --- a/app/src/main/java/lu/circl/mispbump/security/KeyStoreWrapper.java +++ b/app/src/main/java/lu/circl/mispbump/security/KeyStoreWrapper.java @@ -16,6 +16,7 @@ import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; +import java.util.Arrays; import java.util.Enumeration; import javax.crypto.BadPaddingException; @@ -172,11 +173,10 @@ public class KeyStoreWrapper { final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); - String encryptedDataString = Base64.encodeToString(encryptedData, Base64.DEFAULT); - String ivString = Base64.encodeToString(cipher.getIV(), Base64.DEFAULT); - - return ivString + ":::" + encryptedDataString; + byte[] byteData = data.getBytes(StandardCharsets.UTF_8); +// byte[] byteData = Base64.decode(data, Base64.DEFAULT); + byte[] combined = getCombinedArray(cipher.getIV(), cipher.doFinal(byteData)); + return Base64.encodeToString(combined, Base64.NO_WRAP); } /** @@ -193,17 +193,19 @@ public class KeyStoreWrapper { public String decrypt(String input) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { // extract iv from save data - String[] parts = input.split(":::"); +// String[] parts = input.split(":::"); +// byte[] iv = Base64.decode(parts[0], Base64.DEFAULT); +// byte[] data = Base64.decode(parts[1], Base64.DEFAULT); - byte[] iv = Base64.decode(parts[0], Base64.DEFAULT); - byte[] data = Base64.decode(parts[1], Base64.DEFAULT); + byte[] in = Base64.decode(input, Base64.NO_WRAP); + IvAndData ivAndData = splitCombinedArray(in, 12); final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - final GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); + final GCMParameterSpec gcmSpec = new GCMParameterSpec(128, ivAndData.iv); cipher.init(Cipher.DECRYPT_MODE, getStoredKey(), gcmSpec); - return new String(cipher.doFinal(data), StandardCharsets.UTF_8); + return new String(cipher.doFinal(ivAndData.data), StandardCharsets.UTF_8); } /** @@ -242,11 +244,33 @@ public class KeyStoreWrapper { * @param encryptedData encrypted data * @return combination of iv and encrypted data */ - private static byte[] getCombinedArray(byte[] iv, byte[] encryptedData) { + private byte[] getCombinedArray(byte[] iv, byte[] encryptedData) { + + Log.i(TAG, "iv length = " + iv.length); + byte[] combined = new byte[iv.length + encryptedData.length]; for (int i = 0; i < combined.length; ++i) { combined[i] = i < iv.length ? iv[i] : encryptedData[i - iv.length]; } return combined; } + + private IvAndData splitCombinedArray(byte[] input, int ivLength) { + byte[] iv = Arrays.copyOfRange(input, 0, ivLength); + byte[] data = Arrays.copyOfRange(input, ivLength, input.length); + return new IvAndData(iv, data); + } + + public class IvAndData { + + public IvAndData() {} + + public IvAndData(byte[] iv, byte[] data) { + this.iv = iv; + this.data = data; + } + + public byte[] iv; + public byte[] data; + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 0000000..e3979cd --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_autorenew.xml b/app/src/main/res/drawable/ic_autorenew.xml new file mode 100644 index 0000000..3bdee48 --- /dev/null +++ b/app/src/main/res/drawable/ic_autorenew.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_domain.xml b/app/src/main/res/drawable/ic_domain.xml new file mode 100644 index 0000000..b7e6b25 --- /dev/null +++ b/app/src/main/res/drawable/ic_domain.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_location.xml b/app/src/main/res/drawable/ic_location.xml new file mode 100644 index 0000000..7ce2348 --- /dev/null +++ b/app/src/main/res/drawable/ic_location.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_person.xml b/app/src/main/res/drawable/ic_person.xml index c902e21..d7366bd 100644 --- a/app/src/main/res/drawable/ic_person.xml +++ b/app/src/main/res/drawable/ic_person.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_sector.xml b/app/src/main/res/drawable/ic_sector.xml new file mode 100644 index 0000000..eb94181 --- /dev/null +++ b/app/src/main/res/drawable/ic_sector.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/actionbar_home.xml b/app/src/main/res/layout/actionbar_home.xml new file mode 100644 index 0000000..de0f835 --- /dev/null +++ b/app/src/main/res/layout/actionbar_home.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index cd57ba8..09d81f2 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -1,119 +1,118 @@ - + android:layout_height="match_parent"> - + android:layout_height="wrap_content"> - + + android:layout_height="wrap_content" + android:paddingBottom="16dp"> -