From b08b1dc7cec795cb7a78ef196d1bbb4670dd663c Mon Sep 17 00:00:00 2001 From: Bitgamma Date: Tue, 11 Dec 2018 11:50:53 +0300 Subject: [PATCH] Unified sdk (#12) * unifying Android and Desktop SDK * implement desktop SDK adapter * updating declarations * change include syntax * following jitpack.io guide for Android libraries * add install task to all artefacts, add javadoc generation * fixing javadoc * use explicit provider "SC" instead of relying on order * move to BouncyCastle for desktop compatibility * improve documentation --- README.md | 30 +++++++++-- android/.gitignore | 8 +++ android/build.gradle | 50 ++++++++++++++++++ {lib => android}/src/main/AndroidManifest.xml | 0 .../keycard/android}/NFCCardChannel.java | 5 +- .../keycard/android}/NFCCardManager.java | 11 ++-- build.gradle | 5 +- {demo => demo-android}/.gitignore | 0 {demo => demo-android}/build.gradle | 6 +-- {demo => demo-android}/proguard-rules.pro | 0 .../src/main/AndroidManifest.xml | 0 .../im/status/keycard/app/MainActivity.java | 2 +- .../drawable-v24/ic_launcher_foreground.xml | 0 .../res/drawable/ic_launcher_background.xml | 0 .../src/main/res/layout/activity_main.xml | 0 .../res/mipmap-anydpi-v26/ic_launcher.xml | 0 .../mipmap-anydpi-v26/ic_launcher_round.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../res/mipmap-hdpi/ic_launcher_round.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../res/mipmap-mdpi/ic_launcher_round.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher_round.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin .../src/main/res/values/colors.xml | 0 .../src/main/res/values/strings.xml | 0 .../src/main/res/values/styles.xml | 0 desktop/.gitignore | 8 +++ desktop/build.gradle | 21 ++++++++ .../keycard/desktop/PCSCCardChannel.java | 50 ++++++++++++++++++ lib/build.gradle | 41 +++++++------- .../keycard/applet/ApplicationInfo.java | 2 +- .../status/keycard/applet/BIP32KeyPair.java | 4 +- .../status/keycard/applet/CardDuplicator.java | 26 ++++----- .../im/status/keycard/applet/Identifiers.java | 2 +- .../keycard/applet/KeycardCommandSet.java | 8 ++- .../im/status/keycard/applet/Mnemonic.java | 2 +- .../im/status/keycard/applet/Pairing.java | 2 +- .../keycard/applet/RecoverableSignature.java | 18 +++---- .../keycard/applet/SecureChannelSession.java | 28 +++++----- .../status/keycard/globalplatform/Crypto.java | 30 +++++++---- .../GlobalPlatformCommandSet.java | 2 +- .../keycard/globalplatform/SecureChannel.java | 4 +- settings.gradle | 6 ++- 47 files changed, 268 insertions(+), 103 deletions(-) create mode 100644 android/.gitignore create mode 100644 android/build.gradle rename {lib => android}/src/main/AndroidManifest.xml (100%) rename {lib/src/main/java/im/status/keycard/io => android/src/main/java/im/status/keycard/android}/NFCCardChannel.java (86%) rename {lib/src/main/java/im/status/keycard/io => android/src/main/java/im/status/keycard/android}/NFCCardManager.java (93%) rename {demo => demo-android}/.gitignore (100%) rename {demo => demo-android}/build.gradle (91%) rename {demo => demo-android}/proguard-rules.pro (100%) rename {demo => demo-android}/src/main/AndroidManifest.xml (100%) rename {demo => demo-android}/src/main/java/im/status/keycard/app/MainActivity.java (99%) rename {demo => demo-android}/src/main/res/drawable-v24/ic_launcher_foreground.xml (100%) rename {demo => demo-android}/src/main/res/drawable/ic_launcher_background.xml (100%) rename {demo => demo-android}/src/main/res/layout/activity_main.xml (100%) rename {demo => demo-android}/src/main/res/mipmap-anydpi-v26/ic_launcher.xml (100%) rename {demo => demo-android}/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml (100%) rename {demo => demo-android}/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename {demo => demo-android}/src/main/res/mipmap-hdpi/ic_launcher_round.png (100%) rename {demo => demo-android}/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename {demo => demo-android}/src/main/res/mipmap-mdpi/ic_launcher_round.png (100%) rename {demo => demo-android}/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {demo => demo-android}/src/main/res/mipmap-xhdpi/ic_launcher_round.png (100%) rename {demo => demo-android}/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {demo => demo-android}/src/main/res/mipmap-xxhdpi/ic_launcher_round.png (100%) rename {demo => demo-android}/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {demo => demo-android}/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png (100%) rename {demo => demo-android}/src/main/res/values/colors.xml (100%) rename {demo => demo-android}/src/main/res/values/strings.xml (100%) rename {demo => demo-android}/src/main/res/values/styles.xml (100%) create mode 100644 desktop/.gitignore create mode 100644 desktop/build.gradle create mode 100644 desktop/src/main/java/im/status/keycard/desktop/PCSCCardChannel.java diff --git a/README.md b/README.md index 16d45bd..b3a21fb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,28 @@ -# Keycard Android SDK +# Keycard Java SDK for Android and Desktop This SDK simplifies integration with the [Status Keycard](https://github.com/status-im/status-keycard) in Android -applications. In this SDK you find both the classes needed for generic communication with SmartCards as well as classes -specifically addressing the Keycard. +and Desktop applications. In this SDK you find both the classes needed for generic communication with SmartCards as well +as classes specifically addressing the Keycard. -To get started, check the file demo/src/main/java/im/status/keycard/app/MainActivity.java which a simple -demo application showing how the SDK works and what you can do with it. \ No newline at end of file +To get started, check the file ```demo-android/src/main/java/im/status/keycard/app/MainActivity.java``` which a simple +demo application showing how the SDK works and what you can do with it. + +## Usage + +You can import the SDK in your Gradle or Maven project using [Jitpack.io](https://jitpack.io). + +### On Android + +```groovy +dependencies { + implementation 'com.github.status-im.status-keycard-java:android:2.0rc1' +} +``` + +### on the desktop + +```groovy +dependencies { + implementation 'com.github.status-im.status-keycard-java:desktop:2.0rc1' +} +``` \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..63cb77a --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +.idea +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..809ff2c --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,50 @@ +apply plugin: 'com.android.library' +apply plugin: 'com.github.dcendents.android-maven' + +group = 'com.github.status-im' + +android { + compileSdkVersion 28 + defaultConfig { + minSdkVersion 19 + targetSdkVersion 28 + versionCode 2 + versionName "2.0" + } +} + +dependencies { + api project(':lib') +} + +// build a jar with source files +task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' +} + +task javadoc(type: Javadoc) { + failOnError false + source = android.sourceSets.main.java.sourceFiles + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + classpath += configurations.compile +} + +// build a jar with javadoc +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives sourcesJar + archives javadocJar +} + +android.libraryVariants.all { variant -> + def name = variant.buildType.name + def task = project.tasks.create "jar${name.capitalize()}", Jar + task.dependsOn variant.javaCompile + task.from variant.javaCompile.destinationDir + artifacts.add('archives', task); +} diff --git a/lib/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml similarity index 100% rename from lib/src/main/AndroidManifest.xml rename to android/src/main/AndroidManifest.xml diff --git a/lib/src/main/java/im/status/keycard/io/NFCCardChannel.java b/android/src/main/java/im/status/keycard/android/NFCCardChannel.java similarity index 86% rename from lib/src/main/java/im/status/keycard/io/NFCCardChannel.java rename to android/src/main/java/im/status/keycard/android/NFCCardChannel.java index 117f45d..c379895 100644 --- a/lib/src/main/java/im/status/keycard/io/NFCCardChannel.java +++ b/android/src/main/java/im/status/keycard/android/NFCCardChannel.java @@ -1,7 +1,10 @@ -package im.status.keycard.io; +package im.status.keycard.android; import android.nfc.tech.IsoDep; import android.util.Log; +import im.status.keycard.io.APDUCommand; +import im.status.keycard.io.APDUResponse; +import im.status.keycard.io.CardChannel; import java.io.IOException; diff --git a/lib/src/main/java/im/status/keycard/io/NFCCardManager.java b/android/src/main/java/im/status/keycard/android/NFCCardManager.java similarity index 93% rename from lib/src/main/java/im/status/keycard/io/NFCCardManager.java rename to android/src/main/java/im/status/keycard/android/NFCCardManager.java index fd3c51b..517af38 100644 --- a/lib/src/main/java/im/status/keycard/io/NFCCardManager.java +++ b/android/src/main/java/im/status/keycard/android/NFCCardManager.java @@ -1,12 +1,14 @@ -package im.status.keycard.io; +package im.status.keycard.android; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.tech.IsoDep; import android.os.SystemClock; import android.util.Log; +import im.status.keycard.globalplatform.Crypto; +import im.status.keycard.io.CardListener; + import java.io.IOException; -import java.security.Security; /** * Manages connection of NFC-based cards. Extends Thread and must be started using the start() method. The thread has @@ -21,6 +23,10 @@ public class NFCCardManager extends Thread implements NfcAdapter.ReaderCallback private CardListener cardListener; private int loopSleepMS; + static { + Crypto.addSpongyCastleProvider(); + } + /** * Constructs an NFC Card Manager with default delay between loop iterations. */ @@ -35,7 +41,6 @@ public class NFCCardManager extends Thread implements NfcAdapter.ReaderCallback */ public NFCCardManager(int loopSleepMS) { this.loopSleepMS = loopSleepMS; - Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1); } /** diff --git a/build.gradle b/build.gradle index 8d3ef8e..42ed11c 100644 --- a/build.gradle +++ b/build.gradle @@ -8,10 +8,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:3.2.1' - - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' } } diff --git a/demo/.gitignore b/demo-android/.gitignore similarity index 100% rename from demo/.gitignore rename to demo-android/.gitignore diff --git a/demo/build.gradle b/demo-android/build.gradle similarity index 91% rename from demo/build.gradle rename to demo-android/build.gradle index f9161b0..86fb61d 100644 --- a/demo/build.gradle +++ b/demo-android/build.gradle @@ -6,8 +6,8 @@ android { applicationId "im.status.keycard.demo" minSdkVersion 19 targetSdkVersion 28 - versionCode 1 - versionName "1.0" + versionCode 2 + versionName "2.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { @@ -24,7 +24,7 @@ dependencies { implementation 'com.madgag.spongycastle:core:1.58.0.0' implementation 'com.madgag.spongycastle:prov:1.58.0.0' - implementation project(':lib') + implementation project(':android') testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' diff --git a/demo/proguard-rules.pro b/demo-android/proguard-rules.pro similarity index 100% rename from demo/proguard-rules.pro rename to demo-android/proguard-rules.pro diff --git a/demo/src/main/AndroidManifest.xml b/demo-android/src/main/AndroidManifest.xml similarity index 100% rename from demo/src/main/AndroidManifest.xml rename to demo-android/src/main/AndroidManifest.xml diff --git a/demo/src/main/java/im/status/keycard/app/MainActivity.java b/demo-android/src/main/java/im/status/keycard/app/MainActivity.java similarity index 99% rename from demo/src/main/java/im/status/keycard/app/MainActivity.java rename to demo-android/src/main/java/im/status/keycard/app/MainActivity.java index bbe4d9b..ca823fd 100644 --- a/demo/src/main/java/im/status/keycard/app/MainActivity.java +++ b/demo-android/src/main/java/im/status/keycard/app/MainActivity.java @@ -7,7 +7,7 @@ import android.util.Log; import im.status.keycard.demo.R; import im.status.keycard.io.CardChannel; import im.status.keycard.io.CardListener; -import im.status.keycard.io.NFCCardManager; +import im.status.keycard.android.NFCCardManager; import im.status.keycard.applet.*; import org.spongycastle.util.encoders.Hex; diff --git a/demo/src/main/res/drawable-v24/ic_launcher_foreground.xml b/demo-android/src/main/res/drawable-v24/ic_launcher_foreground.xml similarity index 100% rename from demo/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to demo-android/src/main/res/drawable-v24/ic_launcher_foreground.xml diff --git a/demo/src/main/res/drawable/ic_launcher_background.xml b/demo-android/src/main/res/drawable/ic_launcher_background.xml similarity index 100% rename from demo/src/main/res/drawable/ic_launcher_background.xml rename to demo-android/src/main/res/drawable/ic_launcher_background.xml diff --git a/demo/src/main/res/layout/activity_main.xml b/demo-android/src/main/res/layout/activity_main.xml similarity index 100% rename from demo/src/main/res/layout/activity_main.xml rename to demo-android/src/main/res/layout/activity_main.xml diff --git a/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/demo-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to demo-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/demo-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to demo-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/demo/src/main/res/mipmap-hdpi/ic_launcher.png b/demo-android/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from demo/src/main/res/mipmap-hdpi/ic_launcher.png rename to demo-android/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png b/demo-android/src/main/res/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from demo/src/main/res/mipmap-hdpi/ic_launcher_round.png rename to demo-android/src/main/res/mipmap-hdpi/ic_launcher_round.png diff --git a/demo/src/main/res/mipmap-mdpi/ic_launcher.png b/demo-android/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from demo/src/main/res/mipmap-mdpi/ic_launcher.png rename to demo-android/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png b/demo-android/src/main/res/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from demo/src/main/res/mipmap-mdpi/ic_launcher_round.png rename to demo-android/src/main/res/mipmap-mdpi/ic_launcher_round.png diff --git a/demo/src/main/res/mipmap-xhdpi/ic_launcher.png b/demo-android/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from demo/src/main/res/mipmap-xhdpi/ic_launcher.png rename to demo-android/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/demo-android/src/main/res/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png rename to demo-android/src/main/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png b/demo-android/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from demo/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to demo-android/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/demo-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png rename to demo-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/demo-android/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to demo-android/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/demo-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png rename to demo-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/demo/src/main/res/values/colors.xml b/demo-android/src/main/res/values/colors.xml similarity index 100% rename from demo/src/main/res/values/colors.xml rename to demo-android/src/main/res/values/colors.xml diff --git a/demo/src/main/res/values/strings.xml b/demo-android/src/main/res/values/strings.xml similarity index 100% rename from demo/src/main/res/values/strings.xml rename to demo-android/src/main/res/values/strings.xml diff --git a/demo/src/main/res/values/styles.xml b/demo-android/src/main/res/values/styles.xml similarity index 100% rename from demo/src/main/res/values/styles.xml rename to demo-android/src/main/res/values/styles.xml diff --git a/desktop/.gitignore b/desktop/.gitignore new file mode 100644 index 0000000..63cb77a --- /dev/null +++ b/desktop/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +.idea +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/desktop/build.gradle b/desktop/build.gradle new file mode 100644 index 0000000..8f39b25 --- /dev/null +++ b/desktop/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'java' +apply plugin: 'maven' + +dependencies { + compile project(':lib') +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives sourcesJar + archives javadocJar +} diff --git a/desktop/src/main/java/im/status/keycard/desktop/PCSCCardChannel.java b/desktop/src/main/java/im/status/keycard/desktop/PCSCCardChannel.java new file mode 100644 index 0000000..f266781 --- /dev/null +++ b/desktop/src/main/java/im/status/keycard/desktop/PCSCCardChannel.java @@ -0,0 +1,50 @@ +package im.status.keycard.desktop; + +import im.status.keycard.globalplatform.Crypto; +import im.status.keycard.io.APDUCommand; +import im.status.keycard.io.APDUResponse; +import im.status.keycard.io.CardChannel; + +import javax.smartcardio.CardException; +import javax.smartcardio.CommandAPDU; +import javax.smartcardio.ResponseAPDU; +import java.io.IOException; + +/** + * Implementation of a CardChannel using the Java Smartcard I/O API, + */ +public class PCSCCardChannel implements CardChannel { + static { + Crypto.addSpongyCastleProvider(); + } + + private javax.smartcardio.CardChannel cardChannel; + + /** + * Constructor. Wraps a Java Smartcard I/O CardChannel. + * @param cardChannel the card channel to wrap. + */ + public PCSCCardChannel(javax.smartcardio.CardChannel cardChannel) { + this.cardChannel = cardChannel; + } + + @Override + public APDUResponse send(APDUCommand cmd) throws IOException { + CommandAPDU capdu = new CommandAPDU(cmd.getCla(), cmd.getIns(), cmd.getP1(), cmd.getP2(), cmd.getData(), cmd.getNeedsLE() ? 0x100 : 0x00); + + ResponseAPDU rapdu; + + try { + rapdu = cardChannel.transmit(capdu); + } catch (CardException e) { + throw new IOException(e); + } + + return new APDUResponse(rapdu.getBytes()); + } + + @Override + public boolean isConnected() { + return true; + } +} diff --git a/lib/build.gradle b/lib/build.gradle index 395990a..8414710 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,26 +1,21 @@ -apply plugin: 'com.android.library' - -android { - compileSdkVersion 28 - defaultConfig { - minSdkVersion 19 - targetSdkVersion 28 - versionCode 2 - versionName "2.0" - } - - task androidSourcesJar(type: Jar) { - from android.sourceSets.main.java.source - classifier = 'sources' - } - - artifacts { - archives androidSourcesJar - } - -} +apply plugin: 'java' +apply plugin: 'maven' dependencies { - implementation 'com.madgag.spongycastle:core:1.58.0.0' - implementation 'com.madgag.spongycastle:prov:1.58.0.0' + implementation 'org.bouncycastle:bcprov-jdk15on:1.60' +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives sourcesJar + archives javadocJar } diff --git a/lib/src/main/java/im/status/keycard/applet/ApplicationInfo.java b/lib/src/main/java/im/status/keycard/applet/ApplicationInfo.java index b29d023..9ea9b15 100644 --- a/lib/src/main/java/im/status/keycard/applet/ApplicationInfo.java +++ b/lib/src/main/java/im/status/keycard/applet/ApplicationInfo.java @@ -100,7 +100,7 @@ public class ApplicationInfo { /** * The number of remaining pairing slots. If zero is returned, no further pairing is possible. - * @return + * @return the number of remaining pairing slots */ public byte getFreePairingSlots() { return freePairingSlots; diff --git a/lib/src/main/java/im/status/keycard/applet/BIP32KeyPair.java b/lib/src/main/java/im/status/keycard/applet/BIP32KeyPair.java index 670bd5a..bcbc359 100644 --- a/lib/src/main/java/im/status/keycard/applet/BIP32KeyPair.java +++ b/lib/src/main/java/im/status/keycard/applet/BIP32KeyPair.java @@ -1,7 +1,7 @@ package im.status.keycard.applet; -import org.spongycastle.crypto.digests.KeccakDigest; -import org.spongycastle.math.ec.ECPoint; +import org.bouncycastle.crypto.digests.KeccakDigest; +import org.bouncycastle.math.ec.ECPoint; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; diff --git a/lib/src/main/java/im/status/keycard/applet/CardDuplicator.java b/lib/src/main/java/im/status/keycard/applet/CardDuplicator.java index 73df6a8..60ae6f4 100644 --- a/lib/src/main/java/im/status/keycard/applet/CardDuplicator.java +++ b/lib/src/main/java/im/status/keycard/applet/CardDuplicator.java @@ -41,8 +41,8 @@ public class CardDuplicator { * @param pin the card PIN * @param deviceCount the number of devices which will be adding entropy for the key, including this one * - * @throws IOException - * @throws APDUException + * @throws IOException communication error + * @throws APDUException unexpected card response */ public void startDuplication(CardChannel channel, Pairing pairing, String pin, int deviceCount) throws IOException, APDUException { KeycardCommandSet cmdSet = preamble(channel, pairing, pin); @@ -56,8 +56,8 @@ public class CardDuplicator { * @param pairing the pairing info * @param pin the card PIN * - * @throws IOException - * @throws APDUException + * @throws IOException communication error + * @throws APDUException unexpected card response */ public byte[] exportKey(CardChannel channel, Pairing pairing, String pin) throws IOException, APDUException { KeycardCommandSet cmdSet = preamble(channel, pairing, pin); @@ -66,13 +66,13 @@ public class CardDuplicator { /** * Imports key. Must be used on all cards designated as the target for the duplication. - * @param channel - * @param pairing - * @param pin - * @param key - * @return - * @throws IOException - * @throws APDUException + * @param channel the card channel + * @param pairing the pairing info + * @param pin the user PIN + * @param key the key to import + * @return the key UID + * @throws IOException communication error + * @throws APDUException unexpected card response */ public byte[] importKey(CardChannel channel, Pairing pairing, String pin, byte[] key) throws IOException, APDUException { KeycardCommandSet cmdSet = preamble(channel, pairing, pin); @@ -84,8 +84,8 @@ public class CardDuplicator { * exactly once, except for the device which started the backup. * * @param channel - * @throws IOException - * @throws APDUException + * @throws IOException communication error + * @throws APDUException unexpected card response */ public void addEntropy(CardChannel channel) throws IOException, APDUException { KeycardCommandSet cmdSet = new KeycardCommandSet(channel); diff --git a/lib/src/main/java/im/status/keycard/applet/Identifiers.java b/lib/src/main/java/im/status/keycard/applet/Identifiers.java index 2cd63bf..adaf606 100644 --- a/lib/src/main/java/im/status/keycard/applet/Identifiers.java +++ b/lib/src/main/java/im/status/keycard/applet/Identifiers.java @@ -1,6 +1,6 @@ package im.status.keycard.applet; -import org.spongycastle.util.encoders.Hex; +import org.bouncycastle.util.encoders.Hex; public class Identifiers { public static final byte[] PACKAGE_AID = Hex.decode("53746174757357616C6C6574"); diff --git a/lib/src/main/java/im/status/keycard/applet/KeycardCommandSet.java b/lib/src/main/java/im/status/keycard/applet/KeycardCommandSet.java index 768265a..80b929b 100644 --- a/lib/src/main/java/im/status/keycard/applet/KeycardCommandSet.java +++ b/lib/src/main/java/im/status/keycard/applet/KeycardCommandSet.java @@ -4,9 +4,8 @@ import im.status.keycard.io.APDUCommand; import im.status.keycard.io.APDUException; import im.status.keycard.io.APDUResponse; import im.status.keycard.io.CardChannel; -import org.spongycastle.jce.interfaces.ECPrivateKey; -import org.spongycastle.jce.interfaces.ECPublicKey; -import org.spongycastle.util.encoders.Hex; +import org.bouncycastle.jce.interfaces.ECPrivateKey; +import org.bouncycastle.jce.interfaces.ECPublicKey; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; @@ -117,7 +116,6 @@ public class KeycardCommandSet { /** * Opens the secure channel. Calls the corresponding method of the SecureChannel class. * - * @return the raw card response * @throws IOException communication error */ public void autoOpenSecureChannel() throws IOException { @@ -145,7 +143,7 @@ public class KeycardCommandSet { SecretKey key; try { - SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256", "BC"); PBEKeySpec spec = new PBEKeySpec(pairingPassword.toCharArray(), "Keycard Pairing Password Salt".getBytes(), 50000, 32 * 8); key = skf.generateSecret(spec); } catch (Exception e) { diff --git a/lib/src/main/java/im/status/keycard/applet/Mnemonic.java b/lib/src/main/java/im/status/keycard/applet/Mnemonic.java index 054fa36..cb7cf0a 100644 --- a/lib/src/main/java/im/status/keycard/applet/Mnemonic.java +++ b/lib/src/main/java/im/status/keycard/applet/Mnemonic.java @@ -141,7 +141,7 @@ public class Mnemonic { SecretKey key; try { - SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); + SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512", "BC"); PBEKeySpec spec = new PBEKeySpec(mnemonicPhrase.toCharArray(), ("mnemonic" + password).getBytes(), 2048, 512); key = skf.generateSecret(spec); } catch (Exception e) { diff --git a/lib/src/main/java/im/status/keycard/applet/Pairing.java b/lib/src/main/java/im/status/keycard/applet/Pairing.java index 2495ab4..52ae8e7 100644 --- a/lib/src/main/java/im/status/keycard/applet/Pairing.java +++ b/lib/src/main/java/im/status/keycard/applet/Pairing.java @@ -1,6 +1,6 @@ package im.status.keycard.applet; -import org.spongycastle.util.encoders.Base64; +import org.bouncycastle.util.encoders.Base64; import java.util.Arrays; diff --git a/lib/src/main/java/im/status/keycard/applet/RecoverableSignature.java b/lib/src/main/java/im/status/keycard/applet/RecoverableSignature.java index 36d3b48..98133b9 100644 --- a/lib/src/main/java/im/status/keycard/applet/RecoverableSignature.java +++ b/lib/src/main/java/im/status/keycard/applet/RecoverableSignature.java @@ -1,13 +1,13 @@ package im.status.keycard.applet; -import org.spongycastle.asn1.x9.X9ECParameters; -import org.spongycastle.asn1.x9.X9IntegerConverter; -import org.spongycastle.crypto.ec.CustomNamedCurves; -import org.spongycastle.crypto.params.ECDomainParameters; -import org.spongycastle.math.ec.ECAlgorithms; -import org.spongycastle.math.ec.ECPoint; -import org.spongycastle.math.ec.FixedPointUtil; -import org.spongycastle.math.ec.custom.sec.SecP256K1Curve; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.asn1.x9.X9IntegerConverter; +import org.bouncycastle.crypto.ec.CustomNamedCurves; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.math.ec.FixedPointUtil; +import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve; import java.math.BigInteger; import java.util.Arrays; @@ -28,7 +28,7 @@ public class RecoverableSignature { static final ECDomainParameters CURVE; static { - FixedPointUtil.precompute(CURVE_PARAMS.getG(), 6); + FixedPointUtil.precompute(CURVE_PARAMS.getG()); CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), CURVE_PARAMS.getH()); } diff --git a/lib/src/main/java/im/status/keycard/applet/SecureChannelSession.java b/lib/src/main/java/im/status/keycard/applet/SecureChannelSession.java index 5a04107..ab8bf8b 100644 --- a/lib/src/main/java/im/status/keycard/applet/SecureChannelSession.java +++ b/lib/src/main/java/im/status/keycard/applet/SecureChannelSession.java @@ -4,13 +4,13 @@ import im.status.keycard.io.APDUCommand; import im.status.keycard.io.APDUException; import im.status.keycard.io.APDUResponse; import im.status.keycard.io.CardChannel; -import org.spongycastle.crypto.engines.AESEngine; -import org.spongycastle.crypto.macs.CBCBlockCipherMac; -import org.spongycastle.crypto.params.KeyParameter; -import org.spongycastle.jce.ECNamedCurveTable; -import org.spongycastle.jce.interfaces.ECPublicKey; -import org.spongycastle.jce.spec.ECParameterSpec; -import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.macs.CBCBlockCipherMac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.interfaces.ECPublicKey; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; import javax.crypto.Cipher; import javax.crypto.KeyAgreement; @@ -68,17 +68,17 @@ public class SecureChannelSession { public void generateSecret(byte[] keyData) { try { ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1"); - KeyPairGenerator g = KeyPairGenerator.getInstance("ECDH"); + KeyPairGenerator g = KeyPairGenerator.getInstance("ECDH", "BC"); g.initialize(ecSpec, random); KeyPair keyPair = g.generateKeyPair(); publicKey = ((ECPublicKey) keyPair.getPublic()).getQ().getEncoded(false); - KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH"); + KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "BC"); keyAgreement.init(keyPair.getPrivate()); ECPublicKeySpec cardKeySpec = new ECPublicKeySpec(ecSpec.getCurve().decodePoint(keyData), ecSpec); - ECPublicKey cardKey = (ECPublicKey) KeyFactory.getInstance("ECDSA").generatePublic(cardKeySpec); + ECPublicKey cardKey = (ECPublicKey) KeyFactory.getInstance("ECDSA", "BC").generatePublic(cardKeySpec); keyAgreement.doPhase(cardKey, true); secret = keyAgreement.generateSecret(); @@ -116,7 +116,6 @@ public class SecureChannelSession { * Follows the specifications from the SECURE_CHANNEL.md document. * * @param apduChannel the apdu channel - * @return the card response * @throws IOException communication error */ public void autoOpenSecureChannel(CardChannel apduChannel) throws IOException { @@ -155,7 +154,7 @@ public class SecureChannelSession { sessionEncKey = new SecretKeySpec(Arrays.copyOf(keyData, SC_SECRET_LENGTH), "AES"); sessionMacKey = new KeyParameter(keyData, SC_SECRET_LENGTH, SC_SECRET_LENGTH); - sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding"); + sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding", "BC"); sessionMac = new CBCBlockCipherMac(new AESEngine(), 128, null); open = true; } catch(Exception e) { @@ -196,7 +195,7 @@ public class SecureChannelSession { MessageDigest md; try { - md = MessageDigest.getInstance("SHA256"); + md = MessageDigest.getInstance("SHA256", "BC"); } catch(Exception e) { throw new RuntimeException("Is BouncyCastle in the classpath?", e); } @@ -309,7 +308,6 @@ public class SecureChannelSession { * Unpair all other clients * * @param apduChannel the apdu channel - * @return the raw card response * @throws IOException communication error */ public void unpairOthers(CardChannel apduChannel) throws IOException, APDUException { @@ -441,7 +439,7 @@ public class SecureChannelSession { random.nextBytes(iv); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); sessionEncKey = new SecretKeySpec(secret, "AES"); - sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding"); + sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding", "BC"); sessionCipher.init(Cipher.ENCRYPT_MODE, sessionEncKey, ivParameterSpec); initData = sessionCipher.doFinal(initData); byte[] encrypted = new byte[1 + publicKey.length + iv.length + initData.length]; diff --git a/lib/src/main/java/im/status/keycard/globalplatform/Crypto.java b/lib/src/main/java/im/status/keycard/globalplatform/Crypto.java index ce432d8..dd63a81 100644 --- a/lib/src/main/java/im/status/keycard/globalplatform/Crypto.java +++ b/lib/src/main/java/im/status/keycard/globalplatform/Crypto.java @@ -1,10 +1,8 @@ package im.status.keycard.globalplatform; -import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.security.*; import java.util.Arrays; import javax.crypto.BadPaddingException; @@ -23,6 +21,16 @@ public class Crypto { public static long PIN_BOUND = 999999L; public static long PUK_BOUND = 999999999999L; + private static boolean spongyCastleLoaded = false; + + public static void addSpongyCastleProvider() { + if (!spongyCastleLoaded) { + Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); + Security.addProvider(new BouncyCastleProvider()); + spongyCastleLoaded = true; + } + } + /** * Derives a session key for SCP02. * @@ -44,7 +52,7 @@ public class Crypto { SecretKeySpec tmpKey = new SecretKeySpec(key24, "DESede"); - Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding"); + Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding", "BC"); cipher.init(Cipher.ENCRYPT_MODE, tmpKey, new IvParameterSpec(NullBytes8)); return cipher.doFinal(derivationData); @@ -52,6 +60,8 @@ public class Crypto { throw new IllegalStateException("error generating session keys.", e); } catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) { throw new RuntimeException("error generating session keys.", e); + } catch (NoSuchProviderException e) { + throw new RuntimeException("SpongyCastle not installed"); } } @@ -100,7 +110,7 @@ public class Crypto { public static byte[] mac3des(byte[] keyData, byte[] data, byte[] iv) { try { SecretKeySpec key = new SecretKeySpec(resizeKey24(keyData), "DESede"); - Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding"); + Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding", "BC"); cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); byte[] result = cipher.doFinal(data, 0, 24); byte[] tail = new byte[8]; @@ -122,11 +132,11 @@ public class Crypto { public static byte[] macFull3des(byte[] keyData, byte[] data, byte[] iv) { try { SecretKeySpec keyDes = new SecretKeySpec(resizeKey8(keyData), "DES"); - Cipher cipherDes = Cipher.getInstance("DES/CBC/NoPadding"); + Cipher cipherDes = Cipher.getInstance("DES/CBC/NoPadding", "BC"); cipherDes.init(Cipher.ENCRYPT_MODE, keyDes, new IvParameterSpec(iv)); SecretKeySpec keyDes3 = new SecretKeySpec(resizeKey24(keyData), "DESede"); - Cipher cipherDes3 = Cipher.getInstance("DESede/CBC/NoPadding"); + Cipher cipherDes3 = Cipher.getInstance("DESede/CBC/NoPadding", "BC"); byte[] des3Iv = iv.clone(); if (data.length > 8) { @@ -183,7 +193,7 @@ public class Crypto { */ public static byte[] encryptICV(byte[] macKeyData, byte[] mac) { try { - Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding"); + Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding", "BC"); SecretKeySpec key = new SecretKeySpec(resizeKey8(macKeyData), "DES"); cipher.init(Cipher.ENCRYPT_MODE, key); return cipher.doFinal(mac); diff --git a/lib/src/main/java/im/status/keycard/globalplatform/GlobalPlatformCommandSet.java b/lib/src/main/java/im/status/keycard/globalplatform/GlobalPlatformCommandSet.java index 5534af8..c392f08 100644 --- a/lib/src/main/java/im/status/keycard/globalplatform/GlobalPlatformCommandSet.java +++ b/lib/src/main/java/im/status/keycard/globalplatform/GlobalPlatformCommandSet.java @@ -1,7 +1,7 @@ package im.status.keycard.globalplatform; import im.status.keycard.applet.Identifiers; -import org.spongycastle.util.encoders.Hex; +import org.bouncycastle.util.encoders.Hex; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/lib/src/main/java/im/status/keycard/globalplatform/SecureChannel.java b/lib/src/main/java/im/status/keycard/globalplatform/SecureChannel.java index d7d7e07..e30dba7 100644 --- a/lib/src/main/java/im/status/keycard/globalplatform/SecureChannel.java +++ b/lib/src/main/java/im/status/keycard/globalplatform/SecureChannel.java @@ -48,8 +48,8 @@ public class SecureChannel { * @param hostChallenge the host challenge * @param cardKeys the SCP02 keys * @param resp the response from the card to the INITIALIZE UPDATE oommand - * @return - * @throws APDUException + * @return the Session object built on succesful verification + * @throws APDUException communication error */ public static Session verifyChallenge(byte[] hostChallenge, SCP02Keys cardKeys, APDUResponse resp) throws APDUException { if (resp.getSw() == APDUResponse.SW_SECURITY_CONDITION_NOT_SATISFIED) { diff --git a/settings.gradle b/settings.gradle index 7f35a15..bf5b867 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,4 @@ -include ':lib' -include ':demo' +include 'lib' +include 'android' +include 'desktop' +include 'demo-android'