Compare commits

..
9 changed files with 61 additions and 52 deletions
+2 -2
View File
@@ -15,7 +15,7 @@ You can import the SDK in your Gradle or Maven project using [Jitpack.io](https:
```groovy
dependencies {
implementation 'com.github.status-im.status-keycard-java:android:2.2.0'
implementation 'com.github.status-im.status-keycard-java:android:3.0.2'
}
```
@@ -23,6 +23,6 @@ dependencies {
```groovy
dependencies {
implementation 'com.github.status-im.status-keycard-java:desktop:2.2.0'
implementation 'com.github.status-im.status-keycard-java:desktop:3.0.2'
}
```
+2 -2
View File
@@ -8,8 +8,8 @@ android {
defaultConfig {
minSdkVersion 19
targetSdkVersion 28
versionCode 300
versionName "3.0.0"
versionCode 303
versionName "3.0.3"
}
compileOptions {
@@ -34,8 +34,4 @@ public class NFCCardChannel implements CardChannel {
public boolean isConnected() {
return this.isoDep.isConnected();
}
public int pairingPasswordPBKDF2IterationCount() {
return 50000;
}
}
@@ -47,8 +47,4 @@ public class PCSCCardChannel implements CardChannel {
public boolean isConnected() {
return true;
}
public int pairingPasswordPBKDF2IterationCount() {
return 50000;
}
}
@@ -164,8 +164,9 @@ public class KeycardCommandSet {
* Opens the secure channel. Calls the corresponding method of the SecureChannel class.
*
* @throws IOException communication error
* @throws APDUException secure channel error
*/
public void autoOpenSecureChannel() throws IOException {
public void autoOpenSecureChannel() throws IOException, APDUException {
secureChannel.autoOpenSecureChannel(apduChannel);
}
@@ -173,8 +174,9 @@ public class KeycardCommandSet {
* Automatically pairs. Derives the secret from the given password.
*
* @throws IOException communication error
* @throws APDUException pairing error
*/
public void autoPair(String pairingPassword) throws IOException {
public void autoPair(String pairingPassword) throws IOException, APDUException {
byte[] secret = pairingPasswordToSecret(pairingPassword);
secureChannel.autoPair(apduChannel, secret);
@@ -203,8 +205,9 @@ public class KeycardCommandSet {
* Automatically pairs. Calls the corresponding method of the SecureChannel class.
*
* @throws IOException communication error
* @throws APDUException pairing error
*/
public void autoPair(byte[] sharedSecret) throws IOException {
public void autoPair(byte[] sharedSecret) throws IOException, APDUException {
secureChannel.autoPair(apduChannel, sharedSecret);
}
@@ -212,8 +215,9 @@ public class KeycardCommandSet {
* Automatically unpairs. Calls the corresponding method of the SecureChannel class.
*
* @throws IOException communication error
* @throws APDUException unpairing error
*/
public void autoUnpair() throws IOException {
public void autoUnpair() throws IOException, APDUException {
secureChannel.autoUnpair(apduChannel);
}
@@ -254,6 +258,9 @@ public class KeycardCommandSet {
/**
* Unpair all other clients.
*
* @throws IOException communication error
* @throws APDUException unpairing error
*/
public void unpairOthers() throws IOException, APDUException {
secureChannel.unpairOthers(apduChannel);
@@ -118,24 +118,14 @@ public class SecureChannelSession {
* @param apduChannel the apdu channel
* @throws IOException communication error
*/
public void autoOpenSecureChannel(CardChannel apduChannel) throws IOException {
public void autoOpenSecureChannel(CardChannel apduChannel) throws IOException, APDUException {
APDUResponse response = openSecureChannel(apduChannel, pairing.getPairingIndex(), publicKey);
if (response.getSw() != 0x9000) {
throw new IOException("OPEN SECURE CHANNEL failed");
}
processOpenSecureChannelResponse(response);
response = mutuallyAuthenticate(apduChannel);
if (response.getSw() != 0x9000) {
throw new IOException("MUTUALLY AUTHENTICATE failed");
}
if(!verifyMutuallyAuthenticateResponse(response)) {
throw new IOException("Invalid authentication data from the card");
}
response.checkOK("MUTUALLY AUTHENTICATE failed");
verifyMutuallyAuthenticateResponse(response);
}
/**
@@ -168,8 +158,10 @@ public class SecureChannelSession {
* @param response the card response
* @return true if response is correct, false otherwise
*/
public boolean verifyMutuallyAuthenticateResponse(APDUResponse response) {
return response.getData().length == SC_SECRET_LENGTH;
public void verifyMutuallyAuthenticateResponse(APDUResponse response) throws APDUException {
if (response.getData().length != SC_SECRET_LENGTH) {
throw new APDUException("Invalid authentication data from the card");
}
}
/**
@@ -178,14 +170,10 @@ public class SecureChannelSession {
* @param apduChannel the apdu channel
* @throws IOException communication error
*/
public void autoPair(CardChannel apduChannel, byte[] sharedSecret) throws IOException {
public void autoPair(CardChannel apduChannel, byte[] sharedSecret) throws IOException, APDUException {
byte[] challenge = new byte[32];
random.nextBytes(challenge);
APDUResponse resp = pair(apduChannel, PAIR_P1_FIRST_STEP, challenge);
if (resp.getSw() != 0x9000) {
throw new IOException("Pairing failed on step 1");
}
APDUResponse resp = pair(apduChannel, PAIR_P1_FIRST_STEP, challenge).checkOK("Pairing failed on step 1");
byte[] respData = resp.getData();
byte[] cardCryptogram = Arrays.copyOf(respData, 32);
@@ -204,18 +192,13 @@ public class SecureChannelSession {
checkCryptogram = md.digest(challenge);
if (!Arrays.equals(checkCryptogram, cardCryptogram)) {
throw new IOException("Invalid card cryptogram");
throw new APDUException("Invalid card cryptogram");
}
md.update(sharedSecret);
checkCryptogram = md.digest(cardChallenge);
resp = pair(apduChannel, PAIR_P1_LAST_STEP, checkCryptogram);
if (resp.getSw() != 0x9000) {
throw new IOException("Pairing failed on step 2");
}
resp = pair(apduChannel, PAIR_P1_LAST_STEP, checkCryptogram).checkOK("Pairing failed on step 2");
respData = resp.getData();
md.update(sharedSecret);
pairing = new Pairing(md.digest(Arrays.copyOfRange(respData, 1, respData.length)), respData[0]);
@@ -227,12 +210,8 @@ public class SecureChannelSession {
* @param apduChannel the apdu channel
* @throws IOException communication error
*/
public void autoUnpair(CardChannel apduChannel) throws IOException {
APDUResponse resp = unpair(apduChannel, pairing.getPairingIndex());
if (resp.getSw() != 0x9000) {
throw new IOException("Unpairing failed");
}
public void autoUnpair(CardChannel apduChannel) throws IOException, APDUException {
unpair(apduChannel, pairing.getPairingIndex()).checkOK("Unpairing failed");
}
/**
@@ -61,7 +61,7 @@ public class Crypto {
} catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
throw new RuntimeException("error generating session keys.", e);
} catch (NoSuchProviderException e) {
throw new RuntimeException("SpongyCastle not installed");
throw new RuntimeException("BouncyCastle not installed");
}
}
@@ -88,6 +88,35 @@ public class APDUResponse {
}
}
/**
* Asserts that the SW is 0x9000. Throws an exception with the given message if it isn't
*
* @param message the error message
* @return this object, to simplify chaining
* @throws APDUException if the SW is not 0x9000
*/
public APDUResponse checkOK(String message) throws APDUException {
return checkSW(message, SW_OK);
}
/**
* Asserts that the SW is contained in the given list. Throws an exception with the given message if it isn't.
*
* @param message the error message
* @param codes the list of SWs to match.
* @return this object, to simplify chaining
* @throws APDUException if the SW is not 0x9000
*/
public APDUResponse checkSW(String message, int... codes) throws APDUException {
for (int code : codes) {
if (this.sw == code) {
return this;
}
}
throw new APDUException(this.sw, message);
}
/**
* Checks response from an authentication command (VERIFY PIN, UNBLOCK PUK)
*
@@ -29,5 +29,7 @@ public interface CardChannel {
*
* @return the iteration count
*/
int pairingPasswordPBKDF2IterationCount();
default int pairingPasswordPBKDF2IterationCount() {
return 50000;
}
}