diff --git a/android-scanner/build.gradle b/android-scanner/build.gradle index 1e33557..ba8b498 100644 --- a/android-scanner/build.gradle +++ b/android-scanner/build.gradle @@ -56,23 +56,18 @@ dependencies { compile project(':meerkat-common') compile project(':scanner-api-common') - // compile 'com.android.support.constraint:constraint-layout:1.0.0-beta3' - - //compile 'com.android.support:appcompat-v7:23.1.0' - // Google protobufs - // Retrofit -// compile 'com.squareup.retrofit2:retrofit:2.1.0' -// compile 'com.squareup.retrofit2:converter-protobuf:2.2.0' - - // androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { -// exclude group: 'com.android.support', module: 'support-annotations' -// }) - compile 'com.android.support:appcompat-v7:26.0.+' compile 'com.google.protobuf:protobuf-java:3.+' compile 'com.android.support:support-v4:26.0.+' compile 'com.android.support:support-vector-drawable:26.0.+' testCompile 'junit:junit:4.12' + + // Android logging + compile 'com.github.tony19:logback-android-core:1.1.1-6' + compile('com.github.tony19:logback-android-classic:1.1.1-6') { + // workaround issue #73 + exclude group: 'com.google.android', module: 'android' + } } repositories { diff --git a/android-scanner/src/main/assets/logback.xml b/android-scanner/src/main/assets/logback.xml new file mode 100644 index 0000000..5a9f909 --- /dev/null +++ b/android-scanner/src/main/assets/logback.xml @@ -0,0 +1,15 @@ + + + + + %logger{12} + + + [%-20thread] %msg + + + + + + + \ No newline at end of file diff --git a/android-scanner/src/main/java/com/meerkat/laura/fakescannerapp/MainActivity.java b/android-scanner/src/main/java/com/meerkat/laura/fakescannerapp/MainActivity.java index e6aad8b..8ef376f 100644 --- a/android-scanner/src/main/java/com/meerkat/laura/fakescannerapp/MainActivity.java +++ b/android-scanner/src/main/java/com/meerkat/laura/fakescannerapp/MainActivity.java @@ -4,7 +4,9 @@ package com.meerkat.laura.fakescannerapp; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Color; +import android.os.AsyncTask; import android.os.Bundle; +import android.preference.Preference; import android.preference.PreferenceManager; import android.support.v7.app.AppCompatActivity; import android.util.Base64; @@ -16,7 +18,9 @@ import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; +import ch.qos.logback.classic.android.BasicLogcatConfigurator; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; import meerkat.crypto.DigitalSignatureGenerator; @@ -31,7 +35,12 @@ import java.util.Date; import static com.meerkat.laura.fakescannerapp.R.xml.preferences; -public class MainActivity extends AppCompatActivity implements View.OnClickListener { +public class MainActivity extends AppCompatActivity implements View.OnClickListener, SharedPreferences.OnSharedPreferenceChangeListener { + + static { + BasicLogcatConfigurator.configureDefaultContext(); + } + final public static String SCANNER_NAME = "AndroidScanner"; SharedPreferences sharedPref; @@ -40,6 +49,90 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe PollingStationScanner.ScannerClient scannerClient; DigitalSignatureGenerator signer; + class AsyncScanConnect extends AsyncTask { + PollingStation.ConnectionServerData serverData; + + public AsyncScanConnect(PollingStation.ConnectionServerData connectionServerData) { + this.serverData = connectionServerData; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + serverStatus.setTextColor(Color.BLUE); + serverStatus.setText("Connecting to " + serverData.getServerUrl()); + } + + @Override + protected Boolean doInBackground(Void... foo) { + try { + return scannerClient.connect(serverData); + } catch (RuntimeException e) { + Log.e("MainActivity", "Exception during connect: " + e.toString()); + return false; + } + } + + @Override + protected void onPostExecute(Boolean res) { + super.onPostExecute(res); + if (res) { + serverStatus.setTextColor(Color.GREEN); + serverStatus.setText("Connected to " + serverData.getServerUrl()); + } else { + serverStatus.setTextColor(Color.RED); + serverStatus.setText("Connection failed: " + serverData.getServerUrl()); + } + } + } + + class AsyncScanSend extends AsyncTask { + Message data; + + public AsyncScanSend(Message data) { + this.data = data; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + serverStatus.setTextColor(Color.BLUE); + serverStatus.setText("Sending to server..."); + } + + @Override + protected Boolean doInBackground(Void... foo) { + try { + if (data instanceof PollingStation.ScannedBallot) { + return scannerClient.newScan((PollingStation.ScannedBallot) data); + } else if (data instanceof PollingStation.ScanError) { + return scannerClient.reportError((PollingStation.ScanError) data); + } else { + Log.e("MainActivity", "Trying to send invalid message to scanner"); + return false; + } + } catch (RuntimeException e) { + Log.e("MainActivity", "Exception during send: " + e.toString()); + return false; + } + } + + @Override + protected void onPostExecute(Boolean res) { + super.onPostExecute(res); + if (res) { + serverStatus.setTextColor(Color.GREEN); + serverStatus.setText("Sent successfully"); + Log.i("MainActivity", "post submitted to API."); + } else { + serverStatus.setTextColor(Color.RED); + serverStatus.setText("Send failed"); + Log.e("MainActivity", "Unable to submit post to API."); + } + } + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); @@ -61,60 +154,52 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe } void setNewScannerClientAPI(SharedPreferences sharedPref) { - String pscUrlString = sharedPref.getString(SettingsActivity.PSC_URL, ""); + String pscUrlString = sharedPref.getString(SettingsFragment.PSC_URL, ""); + long nonce = 0; - scannerClient = new ScannerClientAPI(signer); + try { + nonce = Long.parseLong(sharedPref.getString(SettingsFragment.PSC_NONCE, "0")); + } catch (NumberFormatException e) { + // Ignore + } - serverStatus.setTextColor(Color.BLUE); - serverStatus.setText("Connecting to " + pscUrlString); + if (pscUrlString.isEmpty()) + return; PollingStation.ConnectionServerData serverData = PollingStation.ConnectionServerData.newBuilder() .setServerUrl(pscUrlString) - .setNonce(0) + .setNonce(nonce) .build(); - boolean res = scannerClient.connect(serverData); - if (res) { - serverStatus.setTextColor(Color.GREEN); - serverStatus.setText("Connected to " + pscUrlString); - } else { - serverStatus.setTextColor(Color.RED); - serverStatus.setText("Connection failed: " + pscUrlString); - } - + new AsyncScanConnect(serverData).execute(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - PreferenceManager.setDefaultValues(this, preferences, false); - sharedPref = PreferenceManager.getDefaultSharedPreferences(this); - sharedPref.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() { - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { - if (s.equals(SettingsActivity.PSC_URL)) - setNewScannerClientAPI(sharedPreferences); - } - }); - - scanBtn = (Button)findViewById(R.id.scan_button); - formatTxt = (TextView)findViewById(R.id.scan_format); - contentTxt = (TextView)findViewById(R.id.scan_content); - responseTxt = (TextView)findViewById(R.id.server_response); - serverStatus = (TextView) findViewById(R.id.serverStatus); - signer = new ECDSADeterministicSignature(); signer.generateSigningCertificate(BigInteger.ONE, new Date(System.currentTimeMillis()), - new Date(System.currentTimeMillis() + 1000*60*60*24*365), SCANNER_NAME); + new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 365), SCANNER_NAME); + scannerClient = new ScannerClientAPI(signer); + + PreferenceManager.setDefaultValues(this, preferences, false); + sharedPref = PreferenceManager.getDefaultSharedPreferences(this); + sharedPref.registerOnSharedPreferenceChangeListener(this); + + scanBtn = (Button) findViewById(R.id.scan_button); + formatTxt = (TextView) findViewById(R.id.scan_format); + contentTxt = (TextView) findViewById(R.id.scan_content); + responseTxt = (TextView) findViewById(R.id.server_response); + serverStatus = (TextView) findViewById(R.id.serverStatus); setNewScannerClientAPI(sharedPref); scanBtn.setOnClickListener(this); } - public void onClick(View v){ - if(v.getId()==R.id.scan_button){ + public void onClick(View v) { + if (v.getId() == R.id.scan_button) { IntentIntegrator scanIntegrator = new IntentIntegrator(this); formatTxt.setText("Initiating scan..."); scanIntegrator.initiateScan(); @@ -131,28 +216,24 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe contentTxt.setText("CONTENT: " + scanContent + "sending data to server..."); // sendPost(rawBytes); sendPost(scanContent); - } else{ + } else { Toast toast = Toast.makeText(getApplicationContext(), "No scan data received!", Toast.LENGTH_SHORT); toast.show(); } } -// public void sendPost(byte[] body) { -public void sendPost(String scanContent) { + // public void sendPost(byte[] body) { + public void sendPost(String scanContent) { try { PollingStation.ScannedBallot scannedBallot = PollingStation.ScannedBallot.parseFrom(Base64.decode(scanContent.getBytes(), Base64.DEFAULT)); - // - // PollingStation.ScannedData scannedData = PollingStation.ScannedData.newBuilder() - // .setChannel(ByteString.copyFrom(body)) - // .build(); + // + // PollingStation.ScannedData scannedData = PollingStation.ScannedData.newBuilder() + // .setChannel(ByteString.copyFrom(body)) + // .build(); - if (scannerClient.newScan(scannedBallot)) { - Log.i("MainActivity", "post submitted to API."); - } else { - Log.e("MainActivity", "Unable to submit post to API."); - } + new AsyncScanSend(scannedBallot).execute(); } catch (InvalidProtocolBufferException e) { e.printStackTrace(); } @@ -162,4 +243,10 @@ public void sendPost(String scanContent) { responseTxt.setText(response); } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key){ + setNewScannerClientAPI(sharedPreferences); + } + } diff --git a/android-scanner/src/main/java/com/meerkat/laura/fakescannerapp/SettingsActivity.java b/android-scanner/src/main/java/com/meerkat/laura/fakescannerapp/SettingsActivity.java index 4e0af64..56660ad 100644 --- a/android-scanner/src/main/java/com/meerkat/laura/fakescannerapp/SettingsActivity.java +++ b/android-scanner/src/main/java/com/meerkat/laura/fakescannerapp/SettingsActivity.java @@ -1,14 +1,26 @@ package com.meerkat.laura.fakescannerapp; +import android.app.Activity; +import android.content.SharedPreferences; import android.os.Bundle; -import android.preference.PreferenceActivity; +import android.preference.PreferenceManager; -public class SettingsActivity extends PreferenceActivity { - public final static String PSC_URL = "psc_url"; +/** + * Created by talm on 25/06/17. + */ +public class SettingsActivity extends Activity { + SettingsFragment settings; @Override - public void onCreate(Bundle savedInstanceState) { + protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.preferences); + + settings = new SettingsFragment(); + PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(settings); + + // Display the fragment as the main content. + getFragmentManager().beginTransaction() + .replace(android.R.id.content, settings) + .commit(); } -} \ No newline at end of file +} diff --git a/android-scanner/src/main/java/com/meerkat/laura/fakescannerapp/SettingsFragment.java b/android-scanner/src/main/java/com/meerkat/laura/fakescannerapp/SettingsFragment.java new file mode 100644 index 0000000..0adaa66 --- /dev/null +++ b/android-scanner/src/main/java/com/meerkat/laura/fakescannerapp/SettingsFragment.java @@ -0,0 +1,34 @@ +package com.meerkat.laura.fakescannerapp; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; + +public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { + public final static String PSC_URL = "psc_url"; + public final static String PSC_NONCE = "psc_nonce"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + Preference pref = findPreference(key); + if (pref == null) + return; + switch(key) { + case SettingsFragment.PSC_URL: + pref.setSummary(sharedPreferences.getString(key, "")); + break; + case SettingsFragment.PSC_NONCE: + pref.setSummary(sharedPreferences.getString(key, "")); + break; + } + } +} \ No newline at end of file diff --git a/android-scanner/src/main/res/values/strings.xml b/android-scanner/src/main/res/values/strings.xml index 3dd1e94..d84450c 100644 --- a/android-scanner/src/main/res/values/strings.xml +++ b/android-scanner/src/main/res/values/strings.xml @@ -17,7 +17,7 @@ Scanner Service URL - http://192.168.70.10:8880/ + http://192.168.70.10:8880/scan Add friends to messages diff --git a/android-scanner/src/main/res/xml/preferences.xml b/android-scanner/src/main/res/xml/preferences.xml index 909e7af..a3876df 100644 --- a/android-scanner/src/main/res/xml/preferences.xml +++ b/android-scanner/src/main/res/xml/preferences.xml @@ -12,6 +12,15 @@ android:maxLines="1" android:inputType="textUri" /> + + diff --git a/meerkat-common/build.gradle b/meerkat-common/build.gradle index 2c29a9e..8ba3dc7 100644 --- a/meerkat-common/build.gradle +++ b/meerkat-common/build.gradle @@ -42,7 +42,7 @@ version += "${isSnapshot ? '-SNAPSHOT' : ''}" dependencies { // Logging - compile 'org.slf4j:slf4j-api:1.7.7' + compile 'org.slf4j:slf4j-api:1.7.21' runtime 'ch.qos.logback:logback-classic:1.1.2' runtime 'ch.qos.logback:logback-core:1.1.2' @@ -53,12 +53,18 @@ dependencies { compile 'com.google.protobuf:protobuf-java:3.+' // ListeningExecutor - compile 'com.google.guava:guava:15.0' + compile 'com.google.guava:guava:19.0' // Crypto compile 'org.factcenter.qilin:qilin:1.2.+' - compile 'org.bouncycastle:bcprov-jdk15on:1.57' - compile 'org.bouncycastle:bcpkix-jdk15on:1.57' // For certificate generation + + // Use SpongyCastle instead of bouncycastle for android compatibility +// compile 'org.bouncycastle:bcprov-jdk15on:1.57' +// compile 'org.bouncycastle:bcpkix-jdk15on:1.57' // For certificate generation + + compile 'com.madgag.spongycastle:prov:1.56.0.0' + compile 'com.madgag.spongycastle:bcpkix-jdk15on:1.56.0.0' // For certificate generation + testCompile 'junit:junit:4.+' diff --git a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java index 4bafe3d..287b481 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java +++ b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java @@ -6,17 +6,17 @@ import meerkat.crypto.DigitalSignatureGenerator; import meerkat.protobuf.Crypto; import meerkat.protobuf.Crypto.Signature; import meerkat.util.Hex; -import org.bouncycastle.asn1.ASN1EncodableVector; -import org.bouncycastle.asn1.DERSequence; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x509.*; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.cert.X509v3CertificateBuilder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.*; +import org.spongycastle.asn1.x509.Extension; +import org.spongycastle.cert.X509v3CertificateBuilder; +import org.spongycastle.cert.jcajce.JcaX509CertificateConverter; +import org.spongycastle.cert.jcajce.JcaX509ExtensionUtils; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.operator.ContentSigner; +import org.spongycastle.operator.jcajce.JcaContentSignerBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/meerkat-common/src/main/java/meerkat/crypto/concrete/GlobalCryptoSetup.java b/meerkat-common/src/main/java/meerkat/crypto/concrete/GlobalCryptoSetup.java index 5333a25..67ec7b4 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/concrete/GlobalCryptoSetup.java +++ b/meerkat-common/src/main/java/meerkat/crypto/concrete/GlobalCryptoSetup.java @@ -1,6 +1,6 @@ package meerkat.crypto.concrete; -import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.jce.provider.BouncyCastleProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/meerkat-common/src/main/resources/logback.groovy b/meerkat-common/src/main/resources/logback.groovy index 076b6c4..9bf5095 100644 --- a/meerkat-common/src/main/resources/logback.groovy +++ b/meerkat-common/src/main/resources/logback.groovy @@ -1,9 +1,7 @@ - - import ch.qos.logback.classic.encoder.PatternLayoutEncoder import ch.qos.logback.classic.filter.ThresholdFilter -import ch.qos.logback.core.ConsoleAppender import ch.qos.logback.core.util.Duration + import static ch.qos.logback.classic.Level.* if (System.getProperty("log.debug") != null) { @@ -41,6 +39,4 @@ if (haveBeagle) { appenders += ["SOCKET"] } -root(LOG_LEVEL, appenders) - - +root(LOG_LEVEL, appenders) \ No newline at end of file diff --git a/polling-station/src/main/java/meerkat/pollingstation/PollingStationToyRun.java b/polling-station/src/main/java/meerkat/pollingstation/PollingStationToyRun.java index c274794..b5d082d 100644 --- a/polling-station/src/main/java/meerkat/pollingstation/PollingStationToyRun.java +++ b/polling-station/src/main/java/meerkat/pollingstation/PollingStationToyRun.java @@ -29,7 +29,7 @@ public class PollingStationToyRun { scanner = new PollingStationWebScanner(0, CONTEXT_PATH); PollingStation.ConnectionServerData serverData = scanner.start(true); - logger.info("Started polling station web scanner on {}", serverData.getServerUrl()); + logger.info("Started polling station web scanner on {} (nonce: {})", serverData.getServerUrl(), Long.toUnsignedString(serverData.getNonce())); PollingStationMainController controller = new PollingStationMainController(); controller.init(scanner); diff --git a/polling-station/src/main/java/meerkat/pollingstation/PollingStationWebScanner.java b/polling-station/src/main/java/meerkat/pollingstation/PollingStationWebScanner.java index a733867..34bd315 100644 --- a/polling-station/src/main/java/meerkat/pollingstation/PollingStationWebScanner.java +++ b/polling-station/src/main/java/meerkat/pollingstation/PollingStationWebScanner.java @@ -242,7 +242,8 @@ public class PollingStationWebScanner implements PollingStationScanner.PollingSt @Override public PollingStation.ConnectionServerData start(boolean verifyScanner) throws Exception { this.verifyNonce = this.verifySignatures = verifyScanner; - nonce = new SecureRandom().nextLong(); + // Use a 40-bit nonce (should be enough, since you can't do offline verification). + nonce = new SecureRandom().nextLong() & 0xffffffffffL; server.start(); diff --git a/scanner-api-common/build.gradle b/scanner-api-common/build.gradle index 70701b1..e4c02b9 100644 --- a/scanner-api-common/build.gradle +++ b/scanner-api-common/build.gradle @@ -26,7 +26,7 @@ sourceCompatibility = '1.7' ext { isSnapshot = false } ext { - groupId = 'org.factcenter.meerkat' + groupId = 'org.factcenter.meerkat' // Credentials for publishing repositories publishRepository = "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" publishUser = project.hasProperty('publishUser') ? project.property('publishUser') : "" @@ -161,21 +161,6 @@ if (project.hasProperty('mainClassName') && (mainClassName != null)) { *===================================*/ repositories { - - // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) - maven { - url nexusRepository - - if (isSnapshot) { - credentials { username - password - - username nexusUser - password nexusPassword - } - } - } - // Use local maven repository mavenLocal()