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
@@ -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.
|
||||
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'
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
.idea
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
@@ -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;
|
||||
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,8 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
.idea
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
include ':lib'
|
||||
include ':demo'
|
||||
include 'lib'
|
||||
include 'android'
|
||||
include 'desktop'
|
||||
include 'demo-android'
|
||||
|
||||