Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9431d7c497 | ||
|
|
f97363704b | ||
|
|
144474415d | ||
|
|
3f8966f1a8 | ||
|
|
3acea10750 |
@@ -30,7 +30,7 @@ android {
|
||||
dependencies {
|
||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.60'
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.65'
|
||||
|
||||
implementation project(':android')
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ apply plugin: 'java'
|
||||
apply plugin: 'maven'
|
||||
|
||||
dependencies {
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.60'
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.65'
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.Arrays;
|
||||
public class KeycardCommandSet {
|
||||
static final byte INS_INIT = (byte) 0xFE;
|
||||
static final byte INS_GET_STATUS = (byte) 0xF2;
|
||||
static final byte INS_SET_NDEF = (byte) 0xF3;
|
||||
static final byte INS_VERIFY_PIN = (byte) 0x20;
|
||||
static final byte INS_CHANGE_PIN = (byte) 0x21;
|
||||
static final byte INS_UNBLOCK_PIN = (byte) 0x22;
|
||||
@@ -163,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);
|
||||
}
|
||||
|
||||
@@ -172,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);
|
||||
@@ -202,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);
|
||||
}
|
||||
|
||||
@@ -211,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);
|
||||
}
|
||||
|
||||
@@ -253,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);
|
||||
@@ -683,15 +691,20 @@ public class KeycardCommandSet {
|
||||
* @throws IOException communication error
|
||||
*/
|
||||
public APDUResponse setNDEF(byte[] ndef) throws IOException {
|
||||
if ((ndef.length - 2) != ((ndef[0] << 8) | ndef[1])) {
|
||||
byte[] tmp = new byte[ndef.length + 2];
|
||||
tmp[0] = (byte) (ndef.length >> 8);
|
||||
tmp[1] = (byte) (ndef.length & 0xff);
|
||||
System.arraycopy(ndef, 0, tmp, 2, ndef.length);
|
||||
ndef = tmp;
|
||||
}
|
||||
if ((info.getAppVersion() >> 8) > 2) {
|
||||
if ((ndef.length - 2) != ((ndef[0] << 8) | ndef[1])) {
|
||||
byte[] tmp = new byte[ndef.length + 2];
|
||||
tmp[0] = (byte) (ndef.length >> 8);
|
||||
tmp[1] = (byte) (ndef.length & 0xff);
|
||||
System.arraycopy(ndef, 0, tmp, 2, ndef.length);
|
||||
ndef = tmp;
|
||||
}
|
||||
|
||||
return storeData(ndef, STORE_DATA_P1_NDEF);
|
||||
return storeData(ndef, STORE_DATA_P1_NDEF);
|
||||
} else {
|
||||
APDUCommand setNDEF = secureChannel.protectedCommand(0x80, INS_SET_NDEF, 0, 0, ndef);
|
||||
return secureChannel.transmit(apduChannel, setNDEF);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,28 +41,11 @@ public class Mnemonic {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the official BIP39 english wordlist from GitHub.
|
||||
* Returns the official BIP39 english wordlist as fetched from https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/english.txt on 23 Oct 2019.
|
||||
*
|
||||
* @throws IOException network error
|
||||
*/
|
||||
public void fetchBIP39EnglishWordlist() throws IOException {
|
||||
URL remoteList = new URL("https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/english.txt");
|
||||
Scanner scanner = new Scanner(remoteList.openStream());
|
||||
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
|
||||
while(scanner.hasNextLine()) {
|
||||
list.add(scanner.nextLine());
|
||||
}
|
||||
|
||||
scanner.close();
|
||||
|
||||
if (list.size() != WORDLIST_SIZE) {
|
||||
throw new IllegalArgumentException("The list must contain exactly 2048 entries");
|
||||
}
|
||||
|
||||
this.wordlist = new String[WORDLIST_SIZE];
|
||||
list.toArray(this.wordlist);
|
||||
public void fetchBIP39EnglishWordlist() {
|
||||
this.wordlist = MnemonicEnglishDictionary.words;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user