Done refactoring scannerAPI; fixed android scanner to use modern settings api and allow URL+nonce

android-scanner
Tal Moran 2017-06-26 00:22:20 +03:00
parent 0dfd2480d2
commit 3c52eb2e8d
14 changed files with 245 additions and 105 deletions

View File

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

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
<tagEncoder>
<pattern>%logger{12}</pattern>
</tagEncoder>
<encoder>
<pattern>[%-20thread] %msg</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="logcat" />
</root>
</configuration>

View File

@ -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<Void, Void, Boolean> {
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<Void, Void, Boolean> {
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);
}
}

View File

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

View File

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

View File

@ -17,7 +17,7 @@
</string>
<string name="pref_title_psc_url">Scanner Service URL</string>
<string name="pref_default_psc_url">http://192.168.70.10:8880/</string>
<string name="pref_default_psc_url">http://192.168.70.10:8880/scan</string>
<string name="pref_title_add_friends_to_messages">Add friends to messages</string>
<string-array name="pref_example_list_titles">

View File

@ -12,6 +12,15 @@
android:maxLines="1"
android:inputType="textUri" />
<EditTextPreference
android:key="psc_nonce"
android:title="Nonce"
android:defaultValue="0"
android:selectAllOnFocus="true"
android:singleLine="true"
android:maxLines="1"
android:inputType="number"/>
<!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
dismiss it. -->
<!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->

View File

@ -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.+'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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