Extract library (#1)

* Use gradle 3.2.1

* Use gradle 4.10.2

* Cleanup - this is not needed

* Cleanup

* Cleanup

* Leverage SystemClock

* Cleanup

* Separate the Application specific code

* Extract library

* Do not bleed out BouncyCastle into library API

Also change the logging as we do not have the byte[]->hex method from spongycastle anymore
This commit is contained in:
ligi 2018-10-27 07:29:41 +02:00 committed by Bitgamma
parent 60f1315263
commit 8950e15c2f
44 changed files with 241 additions and 199 deletions

View File

@ -1,26 +0,0 @@
package im.status.hardwallet_lite_android;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("im.status.hardwallet_lite_android", appContext.getPackageName());
}
}

View File

@ -1,43 +0,0 @@
package im.status.hardwallet_lite_android.app;
import android.nfc.NfcAdapter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import im.status.hardwallet_lite_android.R;
import im.status.hardwallet_lite_android.io.CardManager;
import java.security.Security;
public class MainActivity extends AppCompatActivity {
static {
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
}
private NfcAdapter nfcAdapter;
private CardManager cardManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
this.cardManager = new CardManager();
this.cardManager.start();
}
@Override
public void onResume() {
super.onResume();
if (nfcAdapter != null) {
nfcAdapter.enableReaderMode(this, this.cardManager, NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);
}
}
@Override
public void onPause() {
super.onPause();
if (nfcAdapter != null) {
nfcAdapter.disableReaderMode(this);
}
}
}

View File

@ -1,101 +0,0 @@
package im.status.hardwallet_lite_android.io;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.util.Log;
import im.status.hardwallet_lite_android.wallet.WalletAppletCommandSet;
import org.spongycastle.util.encoders.Hex;
import java.io.IOException;
public class CardManager extends Thread implements NfcAdapter.ReaderCallback {
private static final String TAG = "CardManager";
private IsoDep isoDep;
private boolean isRunning;
public boolean isConnected() {
return this.isoDep != null && this.isoDep.isConnected();
}
@Override
public void onTagDiscovered(Tag tag) {
this.isoDep = IsoDep.get(tag);
try {
this.isoDep = IsoDep.get(tag);
this.isoDep.connect();
this.isoDep.setTimeout(120000);
} catch (IOException e) {
Log.e(TAG, "error connecting to tag");
}
}
public void run() {
boolean connected = this.isConnected();
while(true) {
boolean newConnected = this.isConnected();
if (newConnected != connected) {
connected = newConnected;
Log.i(TAG, "tag " + (connected ? "connected" : "disconnected"));
if (connected && !isRunning) {
this.onCardConnected();
} else {
this.onCardDisconnected();
}
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Log.e(TAG, "error in TagManager thread: " + e.getMessage());
this.interrupt();
}
}
}
private void onCardConnected() {
this.isRunning = true;
try {
CardChannel cardChannel = new CardChannel(this.isoDep);
// Applet-specific code
WalletAppletCommandSet cmdSet = new WalletAppletCommandSet(cardChannel);
// First thing to do is selecting the applet on the card.
cmdSet.select().checkOK();
// In real projects, the pairing key should be saved and used for all new sessions.
cmdSet.autoPair("WalletAppletTest");
// Opening a Secure Channel is needed for all other applet commands
cmdSet.autoOpenSecureChannel();
// We send a GET STATUS command, which does not require PIN authentication
APDUResponse resp = cmdSet.getStatus(WalletAppletCommandSet.GET_STATUS_P1_APPLICATION).checkOK();
// PIN authentication allows execution of privileged commands
cmdSet.verifyPIN("000000").checkOK();
// Cleanup, in a real application you would not unpair and instead keep the pairing key for successive interactions.
// We also remove all other pairings so that we do not fill all slots with failing runs. Again in real application
// this would be a very bad idea to do.
cmdSet.unpairOthers();
cmdSet.autoUnpair();
Log.i(TAG, "GET STATUS response: " + Hex.toHexString(resp.getData()));
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
this.isRunning = false;
}
private void onCardDisconnected() {
this.isRunning = false;
this.isoDep = null;
}
}

View File

@ -1,17 +0,0 @@
package im.status.hardwallet_lite_android;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

View File

@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.0'
classpath 'com.android.tools.build:gradle:3.2.1'
// NOTE: Do not place your application dependencies here; they belong

View File

View File

@ -3,7 +3,7 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "im.status.hardwallet_lite_android"
applicationId "im.status.hardwallet_lite_android.demo"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
@ -19,11 +19,11 @@ android {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.madgag.spongycastle:core:1.58.0.0'
implementation 'com.madgag.spongycastle:prov:1.58.0.0'
implementation project(':lib')
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="im.status.hardwallet_lite_android">
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc.hce" android:required="true" />
package="im.status.hardwallet_lite_android.demo">
<application
android:allowBackup="true"

View File

@ -0,0 +1,95 @@
package im.status.hardwallet_lite_android.app;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import im.status.hardwallet_lite_android.demo.R;
import im.status.hardwallet_lite_android.io.APDUResponse;
import im.status.hardwallet_lite_android.io.CardChannel;
import im.status.hardwallet_lite_android.io.CardManager;
import im.status.hardwallet_lite_android.io.OnCardConnectedListener;
import im.status.hardwallet_lite_android.wallet.WalletAppletCommandSet;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private NfcAdapter nfcAdapter;
private CardManager cardManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
cardManager = new CardManager();
cardManager.setOnCardConnectedListener(new OnCardConnectedListener() {
@Override
public void onConnected(CardChannel cardChannel) {
try {
Log.i(TAG, "onCardConnected()");
// Applet-specific code
WalletAppletCommandSet cmdSet = new WalletAppletCommandSet(cardChannel);
// First thing to do is selecting the applet on the card.
cmdSet.select().checkOK();
Log.i(TAG, "Applet is installed on the connected card.");
// In real projects, the pairing key should be saved and used for all new sessions.
cmdSet.autoPair("WalletAppletTest");
Log.i(TAG, "Pairing with card is done.");
// Opening a Secure Channel is needed for all other applet commands
cmdSet.autoOpenSecureChannel();
Log.i(TAG, "Secure channel opened.");
// We send a GET STATUS command, which does not require PIN authentication
APDUResponse resp = cmdSet.getStatus(WalletAppletCommandSet.GET_STATUS_P1_APPLICATION).checkOK();
Log.i(TAG, "Got status (response length=" + resp.getData().length + ")." );
// PIN authentication allows execution of privileged commands
cmdSet.verifyPIN("000000").checkOK();
Log.i(TAG, "Pin Verified.");
// Cleanup, in a real application you would not unpair and instead keep the pairing key for successive interactions.
// We also remove all other pairings so that we do not fill all slots with failing runs. Again in real application
// this would be a very bad idea to do.
cmdSet.unpairOthers();
cmdSet.autoUnpair();
Log.i(TAG, "Unpaired.");
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
});
cardManager.start();
}
@Override
public void onResume() {
super.onResume();
if (nfcAdapter != null) {
nfcAdapter.enableReaderMode(this, this.cardManager, NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);
}
}
@Override
public void onPause() {
super.onPause();
if (nfcAdapter != null) {
nfcAdapter.disableReaderMode(this);
}
}
}

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

View File

@ -1,6 +1,5 @@
#Tue Oct 23 10:14:17 CEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

0
gradlew vendored Normal file → Executable file
View File

1
lib/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

26
lib/build.gradle Normal file
View File

@ -0,0 +1,26 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
}
task androidSourcesJar(type: Jar) {
from android.sourceSets.main.java.source
classifier = 'sources'
}
artifacts {
archives androidSourcesJar
}
}
dependencies {
implementation 'com.madgag.spongycastle:core:1.58.0.0'
implementation 'com.madgag.spongycastle:prov:1.58.0.0'
}

21
lib/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="im.status.hardwallet_lite_android">
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc.hce" android:required="true" />
</manifest>

View File

@ -0,0 +1,76 @@
package im.status.hardwallet_lite_android.io;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.SystemClock;
import android.util.Log;
import java.io.IOException;
import java.security.Security;
public class CardManager extends Thread implements NfcAdapter.ReaderCallback {
public CardManager() {
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
}
private static final String TAG = "CardManager";
private IsoDep isoDep;
private boolean isRunning;
private OnCardConnectedListener onCardConnectedListener;
public boolean isConnected() {
return isoDep != null && isoDep.isConnected();
}
@Override
public void onTagDiscovered(Tag tag) {
isoDep = IsoDep.get(tag);
try {
isoDep = IsoDep.get(tag);
isoDep.connect();
isoDep.setTimeout(120000);
} catch (IOException e) {
Log.e(TAG, "error connecting to tag");
}
}
public void run() {
boolean connected = isConnected();
while (true) {
boolean newConnected = isConnected();
if (newConnected != connected) {
connected = newConnected;
Log.i(TAG, "tag " + (connected ? "connected" : "disconnected"));
if (connected && !isRunning) {
onCardConnected();
} else {
onCardDisconnected();
}
}
SystemClock.sleep(50);
}
}
private void onCardConnected() {
isRunning = true;
onCardConnectedListener.onConnected(new CardChannel(isoDep));
isRunning = false;
}
private void onCardDisconnected() {
isRunning = false;
isoDep = null;
}
public void setOnCardConnectedListener(OnCardConnectedListener onConnectedListener) {
onCardConnectedListener = onConnectedListener;
}
}

View File

@ -0,0 +1,5 @@
package im.status.hardwallet_lite_android.io;
public interface OnCardConnectedListener {
void onConnected(CardChannel channel);
}

View File

@ -1 +1,2 @@
include ':app'
include ':lib'
include ':demo'