aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java3
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java33
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java22
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java80
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardException.java34
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyType.java65
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java113
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java757
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Transport.java58
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java304
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java37
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java32
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java53
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java35
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java38
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java33
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java17
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java11
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java82
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java3
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java59
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java176
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java59
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java33
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java82
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java860
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java19
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java18
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java97
44 files changed, 2168 insertions, 1135 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
index 79b42ecc4..b4b27f7ab 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
@@ -47,7 +47,7 @@ import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
-import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Passphrase;
@@ -144,7 +144,7 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0;
- NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(
+ SecurityTokenSignOperationsBuilder allRequiredInput = new SecurityTokenSignOperationsBuilder(
cryptoInput.getSignatureTime(), masterKeyId, masterKeyId);
// Work through all requested certifications
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java
index 2ca74063c..5bca372cb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java
@@ -43,7 +43,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
-import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData;
@@ -80,7 +80,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
int total = inputBytes != null ? 1 : inputUris.size(), count = 0;
ArrayList<PgpSignEncryptResult> results = new ArrayList<>();
- NfcSignOperationsBuilder pendingInputBuilder = null;
+ SecurityTokenSignOperationsBuilder pendingInputBuilder = null;
// if signing subkey has not explicitly been set, get first usable subkey capable of signing
if (input.getSignatureMasterKeyId() != Constants.key.none
@@ -161,7 +161,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
return new SignEncryptResult(log, requiredInput, results, cryptoInput);
}
if (pendingInputBuilder == null) {
- pendingInputBuilder = new NfcSignOperationsBuilder(requiredInput.mSignatureTime,
+ pendingInputBuilder = new SecurityTokenSignOperationsBuilder(requiredInput.mSignatureTime,
input.getSignatureMasterKeyId(), input.getSignatureSubKeyId());
}
pendingInputBuilder.addAll(requiredInput);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java
index 1a8f10d4f..7c394fc1e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java
@@ -131,7 +131,8 @@ public class DeleteResult extends InputPendingResult {
else if (mFail == 0) {
str = activity.getString(R.string.delete_nothing);
} else {
- str = activity.getResources().getQuantityString(R.plurals.delete_fail, mFail);
+ str = activity.getResources().getQuantityString(
+ R.plurals.delete_fail, mFail, mFail);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java
index 77977b691..d2384e679 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java
@@ -61,6 +61,8 @@ public abstract class KeyRing {
private static final Pattern USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$");
+ private static final Pattern EMAIL_PATTERN = Pattern.compile("^.*@.*\\..*$");
+
/**
* Splits userId string into naming part, email part, and comment part
* <p/>
@@ -71,25 +73,38 @@ public abstract class KeyRing {
if (!TextUtils.isEmpty(userId)) {
final Matcher matcher = USER_ID_PATTERN.matcher(userId);
if (matcher.matches()) {
- return new UserId(matcher.group(1), matcher.group(3), matcher.group(2));
+ String name = matcher.group(1).isEmpty() ? null : matcher.group(1);
+ String comment = matcher.group(2);
+ String email = matcher.group(3);
+ if (comment == null && email == null && name != null && EMAIL_PATTERN.matcher(name).matches()) {
+ email = name;
+ name = null;
+ }
+ return new UserId(name, email, comment);
}
}
return new UserId(null, null, null);
}
/**
- * Returns a composed user id. Returns null if name is null!
+ * Returns a composed user id. Returns null if name, email and comment are empty.
*/
public static String createUserId(UserId userId) {
- String userIdString = userId.name; // consider name a required value
- if (userIdString != null && !TextUtils.isEmpty(userId.comment)) {
- userIdString += " (" + userId.comment + ")";
+ StringBuilder userIdBuilder = new StringBuilder();
+ if (!TextUtils.isEmpty(userId.name)) {
+ userIdBuilder.append(userId.comment);
}
- if (userIdString != null && !TextUtils.isEmpty(userId.email)) {
- userIdString += " <" + userId.email + ">";
+ if (!TextUtils.isEmpty(userId.comment)) {
+ userIdBuilder.append(" (");
+ userIdBuilder.append(userId.comment);
+ userIdBuilder.append(")");
}
-
- return userIdString;
+ if (!TextUtils.isEmpty(userId.email)) {
+ userIdBuilder.append(" <");
+ userIdBuilder.append(userId.email);
+ userIdBuilder.append(">");
+ }
+ return userIdBuilder.length() == 0 ? null : userIdBuilder.toString();
}
public static class UserId implements Serializable {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java
index c4525e5cd..31a3f91b6 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java
@@ -26,6 +26,8 @@ public class OpenPgpDecryptionResultBuilder {
// builder
private boolean mInsecure = false;
private boolean mEncrypted = false;
+ private byte[] sessionKey;
+ private byte[] decryptedSessionKey;
public void setInsecure(boolean insecure) {
this.mInsecure = insecure;
@@ -36,24 +38,26 @@ public class OpenPgpDecryptionResultBuilder {
}
public OpenPgpDecryptionResult build() {
- OpenPgpDecryptionResult result = new OpenPgpDecryptionResult();
-
if (mInsecure) {
Log.d(Constants.TAG, "RESULT_INSECURE");
- result.setResult(OpenPgpDecryptionResult.RESULT_INSECURE);
- return result;
+ return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_INSECURE, sessionKey, decryptedSessionKey);
}
if (mEncrypted) {
Log.d(Constants.TAG, "RESULT_ENCRYPTED");
- result.setResult(OpenPgpDecryptionResult.RESULT_ENCRYPTED);
- } else {
- Log.d(Constants.TAG, "RESULT_NOT_ENCRYPTED");
- result.setResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED);
+ return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_ENCRYPTED, sessionKey, decryptedSessionKey);
}
- return result;
+ Log.d(Constants.TAG, "RESULT_NOT_ENCRYPTED");
+ return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED);
}
+ public void setSessionKey(byte[] sessionKey, byte[] decryptedSessionKey) {
+ if ((sessionKey == null) != (decryptedSessionKey == null)) {
+ throw new AssertionError("sessionKey must be null iff decryptedSessionKey is null!");
+ }
+ this.sessionKey = sessionKey;
+ this.decryptedSessionKey = decryptedSessionKey;
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java
index aa1c2e037..ae0a31191 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java
@@ -37,7 +37,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
-import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
@@ -76,7 +76,7 @@ public class PgpCertifyOperation {
// get the master subkey (which we certify for)
PGPPublicKey publicKey = publicRing.getPublicKey().getPublicKey();
- NfcSignOperationsBuilder requiredInput = new NfcSignOperationsBuilder(creationTimestamp,
+ SecurityTokenSignOperationsBuilder requiredInput = new SecurityTokenSignOperationsBuilder(creationTimestamp,
publicKey.getKeyID(), publicKey.getKeyID());
try {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
index e15139a7f..a27e4a8d5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
@@ -26,9 +26,12 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.security.SignatureException;
import java.util.Date;
import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
import android.content.Context;
import android.support.annotation.NonNull;
@@ -60,7 +63,6 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.key;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.BaseOperation;
-import org.sufficientlysecure.keychain.util.CharsetVerifier;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
@@ -73,6 +75,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.util.CharsetVerifier;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
@@ -197,6 +200,10 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
PGPEncryptedData encryptedData;
InputStream cleartextStream;
+ // the cached session key
+ byte[] sessionKey;
+ byte[] decryptedSessionKey;
+
int symmetricEncryptionAlgo = 0;
boolean skippedDisallowedKey = false;
@@ -304,6 +311,9 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
// if this worked out so far, the data is encrypted
decryptionResultBuilder.setEncrypted(true);
+ if (esResult.sessionKey != null && esResult.decryptedSessionKey != null) {
+ decryptionResultBuilder.setSessionKey(esResult.sessionKey, esResult.decryptedSessionKey);
+ }
if (esResult.insecureEncryptionKey) {
log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
@@ -545,10 +555,14 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
boolean asymmetricPacketFound = false;
boolean symmetricPacketFound = false;
boolean anyPacketFound = false;
+ boolean decryptedSessionKeyAvailable = false;
PGPPublicKeyEncryptedData encryptedDataAsymmetric = null;
PGPPBEEncryptedData encryptedDataSymmetric = null;
CanonicalizedSecretKey decryptionKey = null;
+ CachingDataDecryptorFactory cachedKeyDecryptorFactory = new CachingDataDecryptorFactory(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME, cryptoInput.getCryptoData());
+ ;
Passphrase passphrase = null;
@@ -569,6 +583,13 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
log.add(LogType.MSG_DC_ASYM, indent,
KeyFormattingUtils.convertKeyIdToHex(subKeyId));
+ decryptedSessionKeyAvailable = cachedKeyDecryptorFactory.hasCachedSessionData(encData);
+ if (decryptedSessionKeyAvailable) {
+ asymmetricPacketFound = true;
+ encryptedDataAsymmetric = encData;
+ break;
+ }
+
CachedPublicKeyRing cachedPublicKeyRing;
try {
// get actual keyring object based on master key id
@@ -746,34 +767,38 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
currentProgress += 2;
updateProgress(R.string.progress_extracting_key, currentProgress, 100);
- try {
- log.add(LogType.MSG_DC_UNLOCKING, indent + 1);
- if (!decryptionKey.unlock(passphrase)) {
- log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1);
+ CachingDataDecryptorFactory decryptorFactory;
+ if (decryptedSessionKeyAvailable) {
+ decryptorFactory = cachedKeyDecryptorFactory;
+ } else {
+ try {
+ log.add(LogType.MSG_DC_UNLOCKING, indent + 1);
+ if (!decryptionKey.unlock(passphrase)) {
+ log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1);
+ return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
+ }
+ } catch (PgpGeneralException e) {
+ log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent + 1);
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
}
- } catch (PgpGeneralException e) {
- log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent + 1);
- return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
- }
-
- currentProgress += 2;
- updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
- CachingDataDecryptorFactory decryptorFactory
- = decryptionKey.getCachingDecryptorFactory(cryptoInput);
+ currentProgress += 2;
+ updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
- // special case: if the decryptor does not have a session key cached for this encrypted
- // data, and can't actually decrypt on its own, return a pending intent
- if (!decryptorFactory.canDecrypt()
- && !decryptorFactory.hasCachedSessionData(encryptedDataAsymmetric)) {
+ decryptorFactory = decryptionKey.getCachingDecryptorFactory(cryptoInput);
- log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
- return result.with(new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(
- decryptionKey.getRing().getMasterKeyId(),
- decryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]
- ), cryptoInput));
+ // special case: if the decryptor does not have a session key cached for this encrypted
+ // data, and can't actually decrypt on its own, return a pending intent
+ if (!decryptorFactory.canDecrypt()
+ && !decryptorFactory.hasCachedSessionData(encryptedDataAsymmetric)) {
+ log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
+ return result.with(new DecryptVerifyResult(log,
+ RequiredInputParcel.createSecurityTokenDecryptOperation(
+ decryptionKey.getRing().getMasterKeyId(),
+ decryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]
+ ), cryptoInput));
+ }
}
try {
@@ -786,8 +811,13 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
result.symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
result.encryptedData = encryptedDataAsymmetric;
- cryptoInput.addCryptoData(decryptorFactory.getCachedSessionKeys());
-
+ Map<ByteBuffer, byte[]> cachedSessionKeys = decryptorFactory.getCachedSessionKeys();
+ cryptoInput.addCryptoData(cachedSessionKeys);
+ if (cachedSessionKeys.size() >= 1) {
+ Entry<ByteBuffer, byte[]> entry = cachedSessionKeys.entrySet().iterator().next();
+ result.sessionKey = entry.getKey().array();
+ result.decryptedSessionKey = entry.getValue();
+ }
} else {
// there wasn't even any useful data
if (!anyPacketFound) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
index 102e11674..ce9c30894 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
@@ -78,8 +78,8 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
-import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcKeyToCardOperationsBuilder;
-import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenKeyToCardOperationsBuilder;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
@@ -496,10 +496,10 @@ public class PgpKeyOperation {
OperationLog log,
int indent) {
- NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(
+ SecurityTokenSignOperationsBuilder nfcSignOps = new SecurityTokenSignOperationsBuilder(
cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(),
masterSecretKey.getKeyID());
- NfcKeyToCardOperationsBuilder nfcKeyToCardOps = new NfcKeyToCardOperationsBuilder(
+ SecurityTokenKeyToCardOperationsBuilder nfcKeyToCardOps = new SecurityTokenKeyToCardOperationsBuilder(
masterSecretKey.getKeyID());
progress(R.string.progress_modify, 0);
@@ -1277,7 +1277,7 @@ public class PgpKeyOperation {
PGPPublicKey masterPublicKey,
int flags, long expiry,
CryptoInputParcel cryptoInput,
- NfcSignOperationsBuilder nfcSignOps,
+ SecurityTokenSignOperationsBuilder nfcSignOps,
int indent, OperationLog log)
throws PGPException, IOException, SignatureException {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
index 48ba68fa6..0bb6419eb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
@@ -528,7 +528,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
} catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
// this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
log.add(LogType.MSG_PSE_PENDING_NFC, indent);
- return new PgpSignEncryptResult(log, RequiredInputParcel.createNfcSignOperation(
+ return new PgpSignEncryptResult(log, RequiredInputParcel.createSecurityTokenSignOperation(
signingKey.getRing().getMasterKeyId(), signingKey.getKeyId(),
e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()), cryptoInput);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
index 04c14491b..0eb7a0cdb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
@@ -54,7 +54,7 @@ import java.io.IOException;
*/
public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "openkeychain.db";
- private static final int DATABASE_VERSION = 16;
+ private static final int DATABASE_VERSION = 17;
static Boolean apgHack = false;
private Context mContext;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
index 9c5d0c054..8a5d09d7b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
@@ -456,12 +456,12 @@ public class KeychainProvider extends ContentProvider {
if (i != 0) {
emailWhere += " OR ";
}
- emailWhere += "tmp." + UserPackets.USER_ID + " LIKE ";
- // match '*<email>', so it has to be at the *end* of the user id
if (match == KEY_RINGS_FIND_BY_EMAIL) {
- emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">");
+ emailWhere += "tmp." + UserPackets.EMAIL + " LIKE "
+ + DatabaseUtils.sqlEscapeString(chunks[i]);
} else {
- emailWhere += DatabaseUtils.sqlEscapeString("%" + chunks[i] + "%");
+ emailWhere += "tmp." + UserPackets.USER_ID + " LIKE "
+ + DatabaseUtils.sqlEscapeString("%" + chunks[i] + "%");
}
gotCondition = true;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java
index 690a4d1a2..001fa9dc8 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java
@@ -50,10 +50,10 @@ public class ApiPendingIntentFactory {
CryptoInputParcel cryptoInput) {
switch (requiredInput.mType) {
- case NFC_MOVE_KEY_TO_CARD:
- case NFC_DECRYPT:
- case NFC_SIGN: {
- return createNfcOperationPendingIntent(data, requiredInput, cryptoInput);
+ case SECURITY_TOKEN_MOVE_KEY_TO_CARD:
+ case SECURITY_TOKEN_DECRYPT:
+ case SECURITY_TOKEN_SIGN: {
+ return createSecurityTokenOperationPendingIntent(data, requiredInput, cryptoInput);
}
case PASSPHRASE: {
@@ -65,7 +65,7 @@ public class ApiPendingIntentFactory {
}
}
- private PendingIntent createNfcOperationPendingIntent(Intent data, RequiredInputParcel requiredInput, CryptoInputParcel cryptoInput) {
+ private PendingIntent createSecurityTokenOperationPendingIntent(Intent data, RequiredInputParcel requiredInput, CryptoInputParcel cryptoInput) {
Intent intent = new Intent(mContext, RemoteSecurityTokenOperationActivity.class);
// pass params through to activity that it can be returned again later to repeat pgp operation
intent.putExtra(RemoteSecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
index 5a2aa6e71..fd58da558 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
@@ -35,6 +35,7 @@ import android.app.Service;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
@@ -452,6 +453,14 @@ public class OpenPgpService extends Service {
cryptoInput.mPassphrase =
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
}
+ if (data.hasExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT_WRAPPER)) {
+ Bundle wrapperBundle = data.getBundleExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT_WRAPPER);
+ wrapperBundle.setClassLoader(getClassLoader());
+ OpenPgpDecryptionResult decryptionResult = wrapperBundle.getParcelable(OpenPgpApi.EXTRA_DECRYPTION_RESULT);
+ if (decryptionResult != null && decryptionResult.hasDecryptedSessionKey()) {
+ cryptoInput.addCryptoData(decryptionResult.sessionKey, decryptionResult.decryptedSessionKey);
+ }
+ }
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardException.java
new file mode 100644
index 000000000..905deca90
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.securitytoken;
+
+import java.io.IOException;
+
+public class CardException extends IOException {
+ private short mResponseCode;
+
+ public CardException(String detailMessage, short responseCode) {
+ super(detailMessage);
+ mResponseCode = responseCode;
+ }
+
+ public short getResponseCode() {
+ return mResponseCode;
+ }
+
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyType.java
new file mode 100644
index 000000000..0e28f022b
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyType.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.securitytoken;
+
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
+
+public enum KeyType {
+ SIGN(0, 0xB6, 0xCE, 0xC7),
+ ENCRYPT(1, 0xB8, 0xCF, 0xC8),
+ AUTH(2, 0xA4, 0xD0, 0xC9),;
+
+ private final int mIdx;
+ private final int mSlot;
+ private final int mTimestampObjectId;
+ private final int mFingerprintObjectId;
+
+ KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) {
+ this.mIdx = idx;
+ this.mSlot = slot;
+ this.mTimestampObjectId = timestampObjectId;
+ this.mFingerprintObjectId = fingerprintObjectId;
+ }
+
+ public static KeyType from(final CanonicalizedSecretKey key) {
+ if (key.canSign() || key.canCertify()) {
+ return SIGN;
+ } else if (key.canEncrypt()) {
+ return ENCRYPT;
+ } else if (key.canAuthenticate()) {
+ return AUTH;
+ }
+ return null;
+ }
+
+ public int getIdx() {
+ return mIdx;
+ }
+
+ public int getmSlot() {
+ return mSlot;
+ }
+
+ public int getTimestampObjectId() {
+ return mTimestampObjectId;
+ }
+
+ public int getmFingerprintObjectId() {
+ return mFingerprintObjectId;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java
new file mode 100644
index 000000000..3b2dd838d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.securitytoken;
+
+import android.nfc.Tag;
+
+import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity;
+
+import java.io.IOException;
+
+import nordpol.IsoCard;
+import nordpol.android.AndroidCard;
+
+public class NfcTransport implements Transport {
+ // timeout is set to 100 seconds to avoid cancellation during calculation
+ private static final int TIMEOUT = 100 * 1000;
+ private final Tag mTag;
+ private IsoCard mIsoCard;
+
+ public NfcTransport(Tag tag) {
+ this.mTag = tag;
+ }
+
+ /**
+ * Transmit and receive data
+ * @param data data to transmit
+ * @return received data
+ * @throws IOException
+ */
+ @Override
+ public byte[] transceive(final byte[] data) throws IOException {
+ return mIsoCard.transceive(data);
+ }
+
+ /**
+ * Disconnect and release connection
+ */
+ @Override
+ public void release() {
+ // Not supported
+ }
+
+ @Override
+ public boolean isConnected() {
+ return mIsoCard != null && mIsoCard.isConnected();
+ }
+
+ /**
+ * Check if Transport supports persistent connections e.g connections which can
+ * handle multiple operations in one session
+ * @return true if transport supports persistent connections
+ */
+ @Override
+ public boolean isPersistentConnectionAllowed() {
+ return false;
+ }
+
+ /**
+ * Connect to NFC device.
+ * <p/>
+ * On general communication, see also
+ * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx
+ * <p/>
+ * References to pages are generally related to the OpenPGP Application
+ * on ISO SmartCard Systems specification.
+ */
+ @Override
+ public void connect() throws IOException {
+ mIsoCard = AndroidCard.get(mTag);
+ if (mIsoCard == null) {
+ throw new BaseSecurityTokenNfcActivity.IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)");
+ }
+
+ mIsoCard.setTimeout(TIMEOUT);
+ mIsoCard.connect();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final NfcTransport that = (NfcTransport) o;
+
+ if (mTag != null ? !mTag.equals(that.mTag) : that.mTag != null) return false;
+ if (mIsoCard != null ? !mIsoCard.equals(that.mIsoCard) : that.mIsoCard != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mTag != null ? mTag.hashCode() : 0;
+ result = 31 * result + (mIsoCard != null ? mIsoCard.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java
new file mode 100644
index 000000000..0040d6958
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java
@@ -0,0 +1,757 @@
+/*
+ * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
+ * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ * Copyright (C) 2013-2014 Signe Rüsch
+ * Copyright (C) 2013-2014 Philipp Jakubeit
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+package org.sufficientlysecure.keychain.securitytoken;
+
+import android.support.annotation.NonNull;
+
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.util.Iso7816TLV;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Passphrase;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.interfaces.RSAPrivateCrtKey;
+
+import nordpol.Apdu;
+
+/**
+ * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
+ * devices.
+ * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
+ */
+public class SecurityTokenHelper {
+ // Fidesmo constants
+ private static final String FIDESMO_APPS_AID_PREFIX = "A000000617";
+
+ private static final byte[] BLANK_FINGERPRINT = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ private Transport mTransport;
+
+ private Passphrase mPin;
+ private Passphrase mAdminPin;
+ private boolean mPw1ValidForMultipleSignatures;
+ private boolean mPw1ValidatedForSignature;
+ private boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming?
+ private boolean mPw3Validated;
+
+ protected SecurityTokenHelper() {
+ }
+
+ public static SecurityTokenHelper getInstance() {
+ return LazyHolder.SECURITY_TOKEN_HELPER;
+ }
+
+ private static String getHex(byte[] raw) {
+ return new String(Hex.encode(raw));
+ }
+
+ private String getHolderName(String name) {
+ try {
+ String slength;
+ int ilength;
+ name = name.substring(6);
+ slength = name.substring(0, 2);
+ ilength = Integer.parseInt(slength, 16) * 2;
+ name = name.substring(2, ilength + 2);
+ name = (new String(Hex.decode(name))).replace('<', ' ');
+ return name;
+ } catch (IndexOutOfBoundsException e) {
+ // try-catch for https://github.com/FluffyKaon/OpenPGP-Card
+ // Note: This should not happen, but happens with
+ // https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now!
+
+ Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e);
+ return "";
+ }
+ }
+
+ public Passphrase getPin() {
+ return mPin;
+ }
+
+ public void setPin(final Passphrase pin) {
+ this.mPin = pin;
+ }
+
+ public Passphrase getAdminPin() {
+ return mAdminPin;
+ }
+
+ public void setAdminPin(final Passphrase adminPin) {
+ this.mAdminPin = adminPin;
+ }
+
+ public void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException {
+ long keyGenerationTimestamp = secretKey.getCreationTime().getTime() / 1000;
+ byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array();
+ KeyType keyType = KeyType.from(secretKey);
+
+ if (keyType == null) {
+ throw new IOException("Inappropriate key flags for smart card key.");
+ }
+
+ // Slot is empty, or contains this key already. PUT KEY operation is safe
+ boolean canPutKey = isSlotEmpty(keyType)
+ || keyMatchesFingerPrint(keyType, secretKey.getFingerprint());
+
+ if (!canPutKey) {
+ throw new IOException(String.format("Key slot occupied; card must be reset to put new %s key.",
+ keyType.toString()));
+ }
+
+ putKey(keyType.getmSlot(), secretKey, passphrase);
+ putData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint());
+ putData(keyType.getTimestampObjectId(), timestampBytes);
+ }
+
+ private boolean isSlotEmpty(KeyType keyType) throws IOException {
+ // Note: special case: This should not happen, but happens with
+ // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true
+ if (getKeyFingerprint(keyType) == null) return true;
+
+ return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT);
+ }
+
+ public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException {
+ return java.util.Arrays.equals(getKeyFingerprint(keyType), fingerprint);
+ }
+
+ /**
+ * Connect to device and select pgp applet
+ *
+ * @throws IOException
+ */
+ public void connectToDevice() throws IOException {
+ // Connect on transport layer
+ mTransport.connect();
+
+ // Connect on smartcard layer
+
+ // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
+ // See specification, page 51
+ String accepted = "9000";
+
+ // Command APDU (page 51) for SELECT FILE command (page 29)
+ String opening =
+ "00" // CLA
+ + "A4" // INS
+ + "04" // P1
+ + "00" // P2
+ + "06" // Lc (number of bytes)
+ + "D27600012401" // Data (6 bytes)
+ + "00"; // Le
+ String response = communicate(opening); // activate connection
+ if (!response.endsWith(accepted)) {
+ throw new CardException("Initialization failed!", parseCardStatus(response));
+ }
+
+ byte[] pwStatusBytes = getPwStatusBytes();
+ mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1);
+ mPw1ValidatedForSignature = false;
+ mPw1ValidatedForDecrypt = false;
+ mPw3Validated = false;
+ }
+
+ /**
+ * Parses out the status word from a JavaCard response string.
+ *
+ * @param response A hex string with the response from the card
+ * @return A short indicating the SW1/SW2, or 0 if a status could not be determined.
+ */
+ private short parseCardStatus(String response) {
+ if (response.length() < 4) {
+ return 0; // invalid input
+ }
+
+ try {
+ return Short.parseShort(response.substring(response.length() - 4), 16);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ /**
+ * Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for
+ * conformance to the token's requirements for key length.
+ *
+ * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83.
+ * @param newPin The new PW1 or PW3.
+ */
+ public void modifyPin(int pw, byte[] newPin) throws IOException {
+ final int MAX_PW1_LENGTH_INDEX = 1;
+ final int MAX_PW3_LENGTH_INDEX = 3;
+
+ byte[] pwStatusBytes = getPwStatusBytes();
+
+ if (pw == 0x81) {
+ if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) {
+ throw new IOException("Invalid PIN length");
+ }
+ } else if (pw == 0x83) {
+ if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) {
+ throw new IOException("Invalid PIN length");
+ }
+ } else {
+ throw new IOException("Invalid PW index for modify PIN operation");
+ }
+
+ byte[] pin;
+ if (pw == 0x83) {
+ pin = mAdminPin.toStringUnsafe().getBytes();
+ } else {
+ pin = mPin.toStringUnsafe().getBytes();
+ }
+
+ // Command APDU for CHANGE REFERENCE DATA command (page 32)
+ String changeReferenceDataApdu = "00" // CLA
+ + "24" // INS
+ + "00" // P1
+ + String.format("%02x", pw) // P2
+ + String.format("%02x", pin.length + newPin.length) // Lc
+ + getHex(pin)
+ + getHex(newPin);
+ String response = communicate(changeReferenceDataApdu); // change PIN
+ if (!response.equals("9000")) {
+ throw new CardException("Failed to change PIN", parseCardStatus(response));
+ }
+ }
+
+ /**
+ * Call DECIPHER command
+ *
+ * @param encryptedSessionKey the encoded session key
+ * @return the decoded session key
+ */
+ public byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException {
+ if (!mPw1ValidatedForDecrypt) {
+ verifyPin(0x82); // (Verify PW1 with mode 82 for decryption)
+ }
+
+ String firstApdu = "102a8086fe";
+ String secondApdu = "002a808603";
+ String le = "00";
+
+ byte[] one = new byte[254];
+ // leave out first byte:
+ System.arraycopy(encryptedSessionKey, 1, one, 0, one.length);
+
+ byte[] two = new byte[encryptedSessionKey.length - 1 - one.length];
+ for (int i = 0; i < two.length; i++) {
+ two[i] = encryptedSessionKey[i + one.length + 1];
+ }
+
+ communicate(firstApdu + getHex(one));
+ String second = communicate(secondApdu + getHex(two) + le);
+
+ String decryptedSessionKey = getDataField(second);
+
+ return Hex.decode(decryptedSessionKey);
+ }
+
+ /**
+ * Verifies the user's PW1 or PW3 with the appropriate mode.
+ *
+ * @param mode For PW1, this is 0x81 for signing, 0x82 for everything else.
+ * For PW3 (Admin PIN), mode is 0x83.
+ */
+ private void verifyPin(int mode) throws IOException {
+ if (mPin != null || mode == 0x83) {
+
+ byte[] pin;
+ if (mode == 0x83) {
+ pin = mAdminPin.toStringUnsafe().getBytes();
+ } else {
+ pin = mPin.toStringUnsafe().getBytes();
+ }
+
+ // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
+ // See specification, page 51
+ String accepted = "9000";
+ String response = tryPin(mode, pin); // login
+ if (!response.equals(accepted)) {
+ throw new CardException("Bad PIN!", parseCardStatus(response));
+ }
+
+ if (mode == 0x81) {
+ mPw1ValidatedForSignature = true;
+ } else if (mode == 0x82) {
+ mPw1ValidatedForDecrypt = true;
+ } else if (mode == 0x83) {
+ mPw3Validated = true;
+ }
+ }
+ }
+
+ /**
+ * Stores a data object on the token. Automatically validates the proper PIN for the operation.
+ * Supported for all data objects < 255 bytes in length. Only the cardholder certificate
+ * (0x7F21) can exceed this length.
+ *
+ * @param dataObject The data object to be stored.
+ * @param data The data to store in the object
+ */
+ private void putData(int dataObject, byte[] data) throws IOException {
+ if (data.length > 254) {
+ throw new IOException("Cannot PUT DATA with length > 254");
+ }
+ if (dataObject == 0x0101 || dataObject == 0x0103) {
+ if (!mPw1ValidatedForDecrypt) {
+ verifyPin(0x82); // (Verify PW1 for non-signing operations)
+ }
+ } else if (!mPw3Validated) {
+ verifyPin(0x83); // (Verify PW3)
+ }
+
+ String putDataApdu = "00" // CLA
+ + "DA" // INS
+ + String.format("%02x", (dataObject & 0xFF00) >> 8) // P1
+ + String.format("%02x", dataObject & 0xFF) // P2
+ + String.format("%02x", data.length) // Lc
+ + getHex(data);
+
+ String response = communicate(putDataApdu); // put data
+ if (!response.equals("9000")) {
+ throw new CardException("Failed to put data.", parseCardStatus(response));
+ }
+ }
+
+
+ /**
+ * Puts a key on the token in the given slot.
+ *
+ * @param slot The slot on the token where the key should be stored:
+ * 0xB6: Signature Key
+ * 0xB8: Decipherment Key
+ * 0xA4: Authentication Key
+ */
+ private void putKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase)
+ throws IOException {
+ if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) {
+ throw new IOException("Invalid key slot");
+ }
+
+ RSAPrivateCrtKey crtSecretKey;
+ try {
+ secretKey.unlock(passphrase);
+ crtSecretKey = secretKey.getCrtSecretKey();
+ } catch (PgpGeneralException e) {
+ throw new IOException(e.getMessage());
+ }
+
+ // Shouldn't happen; the UI should block the user from getting an incompatible key this far.
+ if (crtSecretKey.getModulus().bitLength() > 2048) {
+ throw new IOException("Key too large to export to Security Token.");
+ }
+
+ // Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
+ if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
+ throw new IOException("Invalid public exponent for smart Security Token.");
+ }
+
+ if (!mPw3Validated) {
+ verifyPin(0x83); // (Verify PW3 with mode 83)
+ }
+
+ byte[] header = Hex.decode(
+ "4D82" + "03A2" // Extended header list 4D82, length of 930 bytes. (page 23)
+ + String.format("%02x", slot) + "00" // CRT to indicate targeted key, no length
+ + "7F48" + "15" // Private key template 0x7F48, length 21 (decimal, 0x15 hex)
+ + "9103" // Public modulus, length 3
+ + "928180" // Prime P, length 128
+ + "938180" // Prime Q, length 128
+ + "948180" // Coefficient (1/q mod p), length 128
+ + "958180" // Prime exponent P (d mod (p - 1)), length 128
+ + "968180" // Prime exponent Q (d mod (1 - 1)), length 128
+ + "97820100" // Modulus, length 256, last item in private key template
+ + "5F48" + "820383");// DO 5F48; 899 bytes of concatenated key data will follow
+ byte[] dataToSend = new byte[934];
+ byte[] currentKeyObject;
+ int offset = 0;
+
+ System.arraycopy(header, 0, dataToSend, offset, header.length);
+ offset += header.length;
+ currentKeyObject = crtSecretKey.getPublicExponent().toByteArray();
+ System.arraycopy(currentKeyObject, 0, dataToSend, offset, 3);
+ offset += 3;
+ // NOTE: For a 2048-bit key, these lengths are fixed. However, bigint includes a leading 0
+ // in the array to represent sign, so we take care to set the offset to 1 if necessary.
+ currentKeyObject = crtSecretKey.getPrimeP().toByteArray();
+ System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
+ Arrays.fill(currentKeyObject, (byte) 0);
+ offset += 128;
+ currentKeyObject = crtSecretKey.getPrimeQ().toByteArray();
+ System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
+ Arrays.fill(currentKeyObject, (byte) 0);
+ offset += 128;
+ currentKeyObject = crtSecretKey.getCrtCoefficient().toByteArray();
+ System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
+ Arrays.fill(currentKeyObject, (byte) 0);
+ offset += 128;
+ currentKeyObject = crtSecretKey.getPrimeExponentP().toByteArray();
+ System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
+ Arrays.fill(currentKeyObject, (byte) 0);
+ offset += 128;
+ currentKeyObject = crtSecretKey.getPrimeExponentQ().toByteArray();
+ System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
+ Arrays.fill(currentKeyObject, (byte) 0);
+ offset += 128;
+ currentKeyObject = crtSecretKey.getModulus().toByteArray();
+ System.arraycopy(currentKeyObject, currentKeyObject.length - 256, dataToSend, offset, 256);
+
+ String putKeyCommand = "10DB3FFF";
+ String lastPutKeyCommand = "00DB3FFF";
+
+ // Now we're ready to communicate with the token.
+ offset = 0;
+ String response;
+ while (offset < dataToSend.length) {
+ int dataRemaining = dataToSend.length - offset;
+ if (dataRemaining > 254) {
+ response = communicate(
+ putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254)
+ );
+ offset += 254;
+ } else {
+ int length = dataToSend.length - offset;
+ response = communicate(
+ lastPutKeyCommand + String.format("%02x", length)
+ + Hex.toHexString(dataToSend, offset, length));
+ offset += length;
+ }
+
+ if (!response.endsWith("9000")) {
+ throw new CardException("Key export to Security Token failed", parseCardStatus(response));
+ }
+ }
+
+ // Clear array with secret data before we return.
+ Arrays.fill(dataToSend, (byte) 0);
+ }
+
+ /**
+ * Return fingerprints of all keys from application specific data stored
+ * on tag, or null if data not available.
+ *
+ * @return The fingerprints of all subkeys in a contiguous byte array.
+ */
+ public byte[] getFingerprints() throws IOException {
+ String data = "00CA006E00";
+ byte[] buf = mTransport.transceive(Hex.decode(data));
+
+ Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true);
+ Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint());
+
+ Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5);
+ if (fptlv == null) {
+ return null;
+ }
+ return fptlv.mV;
+ }
+
+ /**
+ * Return the PW Status Bytes from the token. This is a simple DO; no TLV decoding needed.
+ *
+ * @return Seven bytes in fixed format, plus 0x9000 status word at the end.
+ */
+ private byte[] getPwStatusBytes() throws IOException {
+ String data = "00CA00C400";
+ return mTransport.transceive(Hex.decode(data));
+ }
+
+ public byte[] getAid() throws IOException {
+ String info = "00CA004F00";
+ return mTransport.transceive(Hex.decode(info));
+ }
+
+ public String getUserId() throws IOException {
+ String info = "00CA006500";
+ return getHolderName(communicate(info));
+ }
+
+ /**
+ * Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value
+ *
+ * @param hash the hash for signing
+ * @return a big integer representing the MPI for the given hash
+ */
+ public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException {
+ if (!mPw1ValidatedForSignature) {
+ verifyPin(0x81); // (Verify PW1 with mode 81 for signing)
+ }
+
+ // dsi, including Lc
+ String dsi;
+
+ Log.i(Constants.TAG, "Hash: " + hashAlgo);
+ switch (hashAlgo) {
+ case HashAlgorithmTags.SHA1:
+ if (hash.length != 20) {
+ throw new IOException("Bad hash length (" + hash.length + ", expected 10!");
+ }
+ dsi = "23" // Lc
+ + "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes
+ + "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes
+ + "0605" + "2B0E03021A" // OID of SHA1
+ + "0500" // TLV coding of ZERO
+ + "0414" + getHex(hash); // 0x14 are 20 hash bytes
+ break;
+ case HashAlgorithmTags.RIPEMD160:
+ if (hash.length != 20) {
+ throw new IOException("Bad hash length (" + hash.length + ", expected 20!");
+ }
+ dsi = "233021300906052B2403020105000414" + getHex(hash);
+ break;
+ case HashAlgorithmTags.SHA224:
+ if (hash.length != 28) {
+ throw new IOException("Bad hash length (" + hash.length + ", expected 28!");
+ }
+ dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash);
+ break;
+ case HashAlgorithmTags.SHA256:
+ if (hash.length != 32) {
+ throw new IOException("Bad hash length (" + hash.length + ", expected 32!");
+ }
+ dsi = "333031300D060960864801650304020105000420" + getHex(hash);
+ break;
+ case HashAlgorithmTags.SHA384:
+ if (hash.length != 48) {
+ throw new IOException("Bad hash length (" + hash.length + ", expected 48!");
+ }
+ dsi = "433041300D060960864801650304020205000430" + getHex(hash);
+ break;
+ case HashAlgorithmTags.SHA512:
+ if (hash.length != 64) {
+ throw new IOException("Bad hash length (" + hash.length + ", expected 64!");
+ }
+ dsi = "533051300D060960864801650304020305000440" + getHex(hash);
+ break;
+ default:
+ throw new IOException("Not supported hash algo!");
+ }
+
+ // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
+ String apdu =
+ "002A9E9A" // CLA, INS, P1, P2
+ + dsi // digital signature input
+ + "00"; // Le
+
+ String response = communicate(apdu);
+
+ if (response.length() < 4) {
+ throw new CardException("Bad response", (short) 0);
+ }
+ // split up response into signature and status
+ String status = response.substring(response.length() - 4);
+ String signature = response.substring(0, response.length() - 4);
+
+ // while we are getting 0x61 status codes, retrieve more data
+ while (status.substring(0, 2).equals("61")) {
+ Log.d(Constants.TAG, "requesting more data, status " + status);
+ // Send GET RESPONSE command
+ response = communicate("00C00000" + status.substring(2));
+ status = response.substring(response.length() - 4);
+ signature += response.substring(0, response.length() - 4);
+ }
+
+ Log.d(Constants.TAG, "final response:" + status);
+
+ if (!mPw1ValidForMultipleSignatures) {
+ mPw1ValidatedForSignature = false;
+ }
+
+ if (!"9000".equals(status)) {
+ throw new CardException("Bad NFC response code: " + status, parseCardStatus(response));
+ }
+
+ // Make sure the signature we received is actually the expected number of bytes long!
+ if (signature.length() != 256 && signature.length() != 512) {
+ throw new IOException("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2);
+ }
+
+ return Hex.decode(signature);
+ }
+
+ /**
+ * Transceive data via NFC encoded as Hex
+ */
+ private String communicate(String apdu) throws IOException {
+ return getHex(mTransport.transceive(Hex.decode(apdu)));
+ }
+
+ public Transport getTransport() {
+ return mTransport;
+ }
+
+ public void setTransport(Transport mTransport) {
+ this.mTransport = mTransport;
+ }
+
+ public boolean isFidesmoToken() {
+ if (isConnected()) { // Check if we can still talk to the card
+ try {
+ // By trying to select any apps that have the Fidesmo AID prefix we can
+ // see if it is a Fidesmo device or not
+ byte[] mSelectResponse = mTransport.transceive(Apdu.select(FIDESMO_APPS_AID_PREFIX));
+ // Compare the status returned by our select with the OK status code
+ return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU);
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "Card communication failed!", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key),
+ * this command also has the effect of resetting the digital signature counter.
+ * NOTE: This does not set the key fingerprint data object! After calling this command, you
+ * must construct a public key packet using the returned public key data objects, compute the
+ * key fingerprint, and store it on the card using: putData(0xC8, key.getFingerprint())
+ *
+ * @param slot The slot on the card where the key should be generated:
+ * 0xB6: Signature Key
+ * 0xB8: Decipherment Key
+ * 0xA4: Authentication Key
+ * @return the public key data objects, in TLV format. For RSA this will be the public modulus
+ * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required.
+ */
+ public byte[] generateKey(int slot) throws IOException {
+ if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) {
+ throw new IOException("Invalid key slot");
+ }
+
+ if (!mPw3Validated) {
+ verifyPin(0x83); // (Verify PW3 with mode 83)
+ }
+
+ String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000";
+ String getResponseApdu = "00C00000";
+
+ String first = communicate(generateKeyApdu);
+ String second = communicate(getResponseApdu);
+
+ if (!second.endsWith("9000")) {
+ throw new IOException("On-card key generation failed");
+ }
+
+ String publicKeyData = getDataField(first) + getDataField(second);
+
+ Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData);
+
+ return Hex.decode(publicKeyData);
+ }
+
+ private String getDataField(String output) {
+ return output.substring(0, output.length() - 4);
+ }
+
+ private String tryPin(int mode, byte[] pin) throws IOException {
+ // Command APDU for VERIFY command (page 32)
+ String login =
+ "00" // CLA
+ + "20" // INS
+ + "00" // P1
+ + String.format("%02x", mode) // P2
+ + String.format("%02x", pin.length) // Lc
+ + Hex.toHexString(pin);
+
+ return communicate(login);
+ }
+
+ /**
+ * Resets security token, which deletes all keys and data objects.
+ * This works by entering a wrong PIN and then Admin PIN 4 times respectively.
+ * Afterwards, the token is reactivated.
+ */
+ public void resetAndWipeToken() throws IOException {
+ String accepted = "9000";
+
+ // try wrong PIN 4 times until counter goes to C0
+ byte[] pin = "XXXXXX".getBytes();
+ for (int i = 0; i <= 4; i++) {
+ String response = tryPin(0x81, pin);
+ if (response.equals(accepted)) { // Should NOT accept!
+ throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response));
+ }
+ }
+
+ // try wrong Admin PIN 4 times until counter goes to C0
+ byte[] adminPin = "XXXXXXXX".getBytes();
+ for (int i = 0; i <= 4; i++) {
+ String response = tryPin(0x83, adminPin);
+ if (response.equals(accepted)) { // Should NOT accept!
+ throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response));
+ }
+ }
+
+ // reactivate token!
+ String reactivate1 = "00" + "e6" + "00" + "00";
+ String reactivate2 = "00" + "44" + "00" + "00";
+ String response1 = communicate(reactivate1);
+ String response2 = communicate(reactivate2);
+ if (!response1.equals(accepted) || !response2.equals(accepted)) {
+ throw new CardException("Reactivating failed!", parseCardStatus(response1));
+ }
+
+ }
+
+ /**
+ * Return the fingerprint from application specific data stored on tag, or
+ * null if it doesn't exist.
+ *
+ * @param keyType key type
+ * @return The fingerprint of the requested key, or null if not found.
+ */
+ public byte[] getKeyFingerprint(@NonNull KeyType keyType) throws IOException {
+ byte[] data = getFingerprints();
+ if (data == null) {
+ return null;
+ }
+
+ // return the master key fingerprint
+ ByteBuffer fpbuf = ByteBuffer.wrap(data);
+ byte[] fp = new byte[20];
+ fpbuf.position(keyType.getIdx() * 20);
+ fpbuf.get(fp, 0, 20);
+
+ return fp;
+ }
+
+ public boolean isPersistentConnectionAllowed() {
+ return mTransport != null && mTransport.isPersistentConnectionAllowed();
+ }
+
+ public boolean isConnected() {
+ return mTransport != null && mTransport.isConnected();
+ }
+
+ private static class LazyHolder {
+ private static final SecurityTokenHelper SECURITY_TOKEN_HELPER = new SecurityTokenHelper();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Transport.java
new file mode 100644
index 000000000..294eaa9ee
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Transport.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.securitytoken;
+
+import java.io.IOException;
+
+/**
+ * Abstraction for transmitting APDU commands
+ */
+public interface Transport {
+ /**
+ * Transmit and receive data
+ * @param data data to transmit
+ * @return received data
+ * @throws IOException
+ */
+ byte[] transceive(byte[] data) throws IOException;
+
+ /**
+ * Disconnect and release connection
+ */
+ void release();
+
+ /**
+ * Check if device is was connected to and still is connected
+ * @return connection status
+ */
+ boolean isConnected();
+
+ /**
+ * Check if Transport supports persistent connections e.g connections which can
+ * handle multiple operations in one session
+ * @return true if transport supports persistent connections
+ */
+ boolean isPersistentConnectionAllowed();
+
+
+ /**
+ * Connect to device
+ * @throws IOException
+ */
+ void connect() throws IOException;
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java
new file mode 100644
index 000000000..dfe91427e
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.securitytoken;
+
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Pair;
+
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Based on USB CCID Specification rev. 1.1
+ * http://www.usb.org/developers/docs/devclass_docs/DWG_Smart-Card_CCID_Rev110.pdf
+ * Implements small subset of these features
+ */
+public class UsbTransport implements Transport {
+ private static final int USB_CLASS_SMARTCARD = 11;
+ private static final int TIMEOUT = 20 * 1000; // 20s
+
+ private final UsbManager mUsbManager;
+ private final UsbDevice mUsbDevice;
+ private UsbInterface mUsbInterface;
+ private UsbEndpoint mBulkIn;
+ private UsbEndpoint mBulkOut;
+ private UsbDeviceConnection mConnection;
+ private byte mCounter;
+
+ public UsbTransport(UsbDevice usbDevice, UsbManager usbManager) {
+ mUsbDevice = usbDevice;
+ mUsbManager = usbManager;
+ }
+
+
+ /**
+ * Manage ICC power, Yubikey requires to power on ICC
+ * Spec: 6.1.1 PC_to_RDR_IccPowerOn; 6.1.2 PC_to_RDR_IccPowerOff
+ *
+ * @param on true to turn ICC on, false to turn it off
+ * @throws UsbTransportException
+ */
+ private void setIccPower(boolean on) throws UsbTransportException {
+ final byte[] iccPowerCommand = {
+ (byte) (on ? 0x62 : 0x63),
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ mCounter++,
+ 0x00,
+ 0x00, 0x00
+ };
+
+ sendRaw(iccPowerCommand);
+ byte[] bytes;
+ do {
+ bytes = receive();
+ } while (isDataBlockNotReady(bytes));
+ checkDataBlockResponse(bytes);
+ }
+
+ /**
+ * Get first class 11 (Chip/Smartcard) interface of the device
+ *
+ * @param device {@link UsbDevice} which will be searched
+ * @return {@link UsbInterface} of smartcard or null if it doesn't exist
+ */
+ @Nullable
+ private static UsbInterface getSmartCardInterface(UsbDevice device) {
+ for (int i = 0; i < device.getInterfaceCount(); i++) {
+ UsbInterface anInterface = device.getInterface(i);
+ if (anInterface.getInterfaceClass() == USB_CLASS_SMARTCARD) {
+ return anInterface;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get device's bulk-in and bulk-out endpoints
+ *
+ * @param usbInterface usb device interface
+ * @return pair of builk-in and bulk-out endpoints respectively
+ */
+ @NonNull
+ private static Pair<UsbEndpoint, UsbEndpoint> getIoEndpoints(final UsbInterface usbInterface) {
+ UsbEndpoint bulkIn = null, bulkOut = null;
+ for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
+ final UsbEndpoint endpoint = usbInterface.getEndpoint(i);
+ if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) {
+ continue;
+ }
+
+ if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) {
+ bulkIn = endpoint;
+ } else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) {
+ bulkOut = endpoint;
+ }
+ }
+ return new Pair<>(bulkIn, bulkOut);
+ }
+
+ /**
+ * Release interface and disconnect
+ */
+ @Override
+ public void release() {
+ if (mConnection != null) {
+ mConnection.releaseInterface(mUsbInterface);
+ mConnection.close();
+ mConnection = null;
+ }
+
+ Log.d(Constants.TAG, "Usb transport disconnected");
+ }
+
+ /**
+ * Check if device is was connected to and still is connected
+ * @return true if device is connected
+ */
+ @Override
+ public boolean isConnected() {
+ return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice) &&
+ mConnection.getSerial() != null;
+ }
+
+ /**
+ * Check if Transport supports persistent connections e.g connections which can
+ * handle multiple operations in one session
+ * @return true if transport supports persistent connections
+ */
+ @Override
+ public boolean isPersistentConnectionAllowed() {
+ return true;
+ }
+
+ /**
+ * Connect to OTG device
+ * @throws IOException
+ */
+ @Override
+ public void connect() throws IOException {
+ mCounter = 0;
+ mUsbInterface = getSmartCardInterface(mUsbDevice);
+ if (mUsbInterface == null) {
+ // Shouldn't happen as we whitelist only class 11 devices
+ throw new UsbTransportException("USB error - device doesn't have class 11 interface");
+ }
+
+ final Pair<UsbEndpoint, UsbEndpoint> ioEndpoints = getIoEndpoints(mUsbInterface);
+ mBulkIn = ioEndpoints.first;
+ mBulkOut = ioEndpoints.second;
+
+ if (mBulkIn == null || mBulkOut == null) {
+ throw new UsbTransportException("USB error - invalid class 11 interface");
+ }
+
+ mConnection = mUsbManager.openDevice(mUsbDevice);
+ if (mConnection == null) {
+ throw new UsbTransportException("USB error - failed to connect to device");
+ }
+
+ if (!mConnection.claimInterface(mUsbInterface, true)) {
+ throw new UsbTransportException("USB error - failed to claim interface");
+ }
+
+ setIccPower(true);
+ Log.d(Constants.TAG, "Usb transport connected");
+ }
+
+ /**
+ * Transmit and receive data
+ * @param data data to transmit
+ * @return received data
+ * @throws UsbTransportException
+ */
+ @Override
+ public byte[] transceive(byte[] data) throws UsbTransportException {
+ sendXfrBlock(data);
+ byte[] bytes;
+ do {
+ bytes = receive();
+ } while (isDataBlockNotReady(bytes));
+
+ checkDataBlockResponse(bytes);
+ // Discard header
+ return Arrays.copyOfRange(bytes, 10, bytes.length);
+ }
+
+ /**
+ * Transmits XfrBlock
+ * 6.1.4 PC_to_RDR_XfrBlock
+ * @param payload payload to transmit
+ * @throws UsbTransportException
+ */
+ private void sendXfrBlock(byte[] payload) throws UsbTransportException {
+ int l = payload.length;
+ byte[] data = Arrays.concatenate(new byte[]{
+ 0x6f,
+ (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24),
+ 0x00,
+ mCounter++,
+ 0x00,
+ 0x00, 0x00},
+ payload);
+
+ int send = 0;
+ while (send < data.length) {
+ final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send);
+ sendRaw(Arrays.copyOfRange(data, send, send + len));
+ send += len;
+ }
+ }
+
+ private byte[] receive() throws UsbTransportException {
+ byte[] buffer = new byte[mBulkIn.getMaxPacketSize()];
+ byte[] result = null;
+ int readBytes = 0, totalBytes = 0;
+
+ do {
+ int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT);
+ if (res < 0) {
+ throw new UsbTransportException("USB error - failed to receive response " + res);
+ }
+ if (result == null) {
+ if (res < 10) {
+ throw new UsbTransportException("USB-CCID error - failed to receive CCID header");
+ }
+ totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10;
+ result = new byte[totalBytes];
+ }
+ System.arraycopy(buffer, 0, result, readBytes, res);
+ readBytes += res;
+ } while (readBytes < totalBytes);
+
+ return result;
+ }
+
+ private void sendRaw(final byte[] data) throws UsbTransportException {
+ final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT);
+ if (tr1 != data.length) {
+ throw new UsbTransportException("USB error - failed to transmit data " + tr1);
+ }
+ }
+
+ private byte getStatus(byte[] bytes) {
+ return (byte) ((bytes[7] >> 6) & 0x03);
+ }
+
+ private void checkDataBlockResponse(byte[] bytes) throws UsbTransportException {
+ final byte status = getStatus(bytes);
+ if (status != 0) {
+ throw new UsbTransportException("USB-CCID error - status " + status + " error code: " + Hex.toHexString(bytes, 8, 1));
+ }
+ }
+
+ private boolean isDataBlockNotReady(byte[] bytes) {
+ return getStatus(bytes) == 2;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final UsbTransport that = (UsbTransport) o;
+
+ return mUsbDevice != null ? mUsbDevice.equals(that.mUsbDevice) : that.mUsbDevice == null;
+ }
+
+ @Override
+ public int hashCode() {
+ return mUsbDevice != null ? mUsbDevice.hashCode() : 0;
+ }
+
+ public UsbDevice getUsbDevice() {
+ return mUsbDevice;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java
new file mode 100644
index 000000000..6d9212d9f
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.securitytoken;
+
+import java.io.IOException;
+
+public class UsbTransportException extends IOException {
+ public UsbTransportException() {
+ }
+
+ public UsbTransportException(final String detailMessage) {
+ super(detailMessage);
+ }
+
+ public UsbTransportException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ public UsbTransportException(final Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java
index 849418905..080c34c04 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java
@@ -17,18 +17,18 @@
package org.sufficientlysecure.keychain.service.input;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import org.sufficientlysecure.keychain.util.ParcelableProxy;
-import org.sufficientlysecure.keychain.util.Passphrase;
-import java.net.Proxy;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import org.sufficientlysecure.keychain.util.ParcelableProxy;
+import org.sufficientlysecure.keychain.util.Passphrase;
+
/**
* This is a base class for the input of crypto operations.
*/
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java
index 429d7a7e5..84c139d0b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java
@@ -14,8 +14,8 @@ import java.util.Date;
public class RequiredInputParcel implements Parcelable {
public enum RequiredInputType {
- PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, NFC_SIGN, NFC_DECRYPT,
- NFC_MOVE_KEY_TO_CARD, NFC_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY,
+ PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, SECURITY_TOKEN_SIGN, SECURITY_TOKEN_DECRYPT,
+ SECURITY_TOKEN_MOVE_KEY_TO_CARD, SECURITY_TOKEN_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY,
}
public Date mSignatureTime;
@@ -89,22 +89,22 @@ public class RequiredInputParcel implements Parcelable {
return new RequiredInputParcel(RequiredInputType.ENABLE_ORBOT, null, null, null, 0L, 0L);
}
- public static RequiredInputParcel createNfcSignOperation(
+ public static RequiredInputParcel createSecurityTokenSignOperation(
long masterKeyId, long subKeyId,
byte[] inputHash, int signAlgo, Date signatureTime) {
- return new RequiredInputParcel(RequiredInputType.NFC_SIGN,
+ return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_SIGN,
new byte[][] { inputHash }, new int[] { signAlgo },
signatureTime, masterKeyId, subKeyId);
}
- public static RequiredInputParcel createNfcDecryptOperation(
+ public static RequiredInputParcel createSecurityTokenDecryptOperation(
long masterKeyId, long subKeyId, byte[] encryptedSessionKey) {
- return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT,
+ return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_DECRYPT,
new byte[][] { encryptedSessionKey }, null, null, masterKeyId, subKeyId);
}
- public static RequiredInputParcel createNfcReset() {
- return new RequiredInputParcel(RequiredInputType.NFC_RESET_CARD,
+ public static RequiredInputParcel createSecurityTokenReset() {
+ return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_RESET_CARD,
null, null, null, null, null);
}
@@ -188,14 +188,14 @@ public class RequiredInputParcel implements Parcelable {
}
};
- public static class NfcSignOperationsBuilder {
+ public static class SecurityTokenSignOperationsBuilder {
Date mSignatureTime;
ArrayList<Integer> mSignAlgos = new ArrayList<>();
ArrayList<byte[]> mInputHashes = new ArrayList<>();
long mMasterKeyId;
long mSubKeyId;
- public NfcSignOperationsBuilder(Date signatureTime, long masterKeyId, long subKeyId) {
+ public SecurityTokenSignOperationsBuilder(Date signatureTime, long masterKeyId, long subKeyId) {
mSignatureTime = signatureTime;
mMasterKeyId = masterKeyId;
mSubKeyId = subKeyId;
@@ -209,7 +209,7 @@ public class RequiredInputParcel implements Parcelable {
signAlgos[i] = mSignAlgos.get(i);
}
- return new RequiredInputParcel(RequiredInputType.NFC_SIGN,
+ return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_SIGN,
inputHashes, signAlgos, mSignatureTime, mMasterKeyId, mSubKeyId);
}
@@ -222,7 +222,7 @@ public class RequiredInputParcel implements Parcelable {
if (!mSignatureTime.equals(input.mSignatureTime)) {
throw new AssertionError("input times must match, this is a programming error!");
}
- if (input.mType != RequiredInputType.NFC_SIGN) {
+ if (input.mType != RequiredInputType.SECURITY_TOKEN_SIGN) {
throw new AssertionError("operation types must match, this is a progrmming error!");
}
@@ -238,13 +238,13 @@ public class RequiredInputParcel implements Parcelable {
}
- public static class NfcKeyToCardOperationsBuilder {
+ public static class SecurityTokenKeyToCardOperationsBuilder {
ArrayList<byte[]> mSubkeysToExport = new ArrayList<>();
Long mMasterKeyId;
byte[] mPin;
byte[] mAdminPin;
- public NfcKeyToCardOperationsBuilder(Long masterKeyId) {
+ public SecurityTokenKeyToCardOperationsBuilder(Long masterKeyId) {
mMasterKeyId = masterKeyId;
}
@@ -264,7 +264,7 @@ public class RequiredInputParcel implements Parcelable {
ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0));
// We need to pass in a subkey here...
- return new RequiredInputParcel(RequiredInputType.NFC_MOVE_KEY_TO_CARD,
+ return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD,
inputData, null, null, mMasterKeyId, buf.getLong());
}
@@ -287,7 +287,7 @@ public class RequiredInputParcel implements Parcelable {
if (!mMasterKeyId.equals(input.mMasterKeyId)) {
throw new AssertionError("Master keys must match, this is a programming error!");
}
- if (input.mType != RequiredInputType.NFC_MOVE_KEY_TO_CARD) {
+ if (input.mType != RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD) {
throw new AssertionError("Operation types must match, this is a programming error!");
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
index b1fec3aae..44b185c52 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
@@ -47,9 +47,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
public static final String EXTRA_SECURITY_TOKEN_PIN = "yubi_key_pin";
public static final String EXTRA_SECURITY_TOKEN_ADMIN_PIN = "yubi_key_admin_pin";
- public static final String EXTRA_NFC_USER_ID = "nfc_user_id";
- public static final String EXTRA_NFC_AID = "nfc_aid";
- public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints";
+ public static final String EXTRA_SECURITY_TOKEN_USER_ID = "nfc_user_id";
+ public static final String EXTRA_SECURITY_TOKEN_AID = "nfc_aid";
+ public static final String EXTRA_SECURITY_FINGERPRINTS = "nfc_fingerprints";
public static final String FRAGMENT_TAG = "currentFragment";
@@ -66,8 +66,8 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
byte[] mScannedFingerprints;
- byte[] mNfcAid;
- String mNfcUserId;
+ byte[] mSecurityTokenAid;
+ String mSecurityTokenUserId;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -107,10 +107,10 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false);
mCreateSecurityToken = intent.getBooleanExtra(EXTRA_CREATE_SECURITY_TOKEN, false);
- if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) {
- byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
- String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
- byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
+ if (intent.hasExtra(EXTRA_SECURITY_FINGERPRINTS)) {
+ byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_SECURITY_FINGERPRINTS);
+ String nfcUserId = intent.getStringExtra(EXTRA_SECURITY_TOKEN_USER_ID);
+ byte[] nfcAid = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_AID);
if (containsKeys(nfcFingerprints)) {
Fragment frag = CreateSecurityTokenImportResetFragment.newInstance(
@@ -143,24 +143,29 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
}
@Override
- protected void doNfcInBackground() throws IOException {
- if (mCurrentFragment instanceof NfcListenerFragment) {
- ((NfcListenerFragment) mCurrentFragment).doNfcInBackground();
+ protected void doSecurityTokenInBackground() throws IOException {
+ if (mCurrentFragment instanceof SecurityTokenListenerFragment) {
+ ((SecurityTokenListenerFragment) mCurrentFragment).doSecurityTokenInBackground();
return;
}
- mScannedFingerprints = nfcGetFingerprints();
- mNfcAid = nfcGetAid();
- mNfcUserId = nfcGetUserId();
+ mScannedFingerprints = mSecurityTokenHelper.getFingerprints();
+ mSecurityTokenAid = mSecurityTokenHelper.getAid();
+ mSecurityTokenUserId = mSecurityTokenHelper.getUserId();
}
@Override
- protected void onNfcPostExecute() {
- if (mCurrentFragment instanceof NfcListenerFragment) {
- ((NfcListenerFragment) mCurrentFragment).onNfcPostExecute();
+ protected void onSecurityTokenPostExecute() {
+ if (mCurrentFragment instanceof SecurityTokenListenerFragment) {
+ ((SecurityTokenListenerFragment) mCurrentFragment).onSecurityTokenPostExecute();
return;
}
+ // We don't want get back to wait activity mainly because it looks weird with otg token
+ if (mCurrentFragment instanceof CreateSecurityTokenWaitFragment) {
+ getSupportFragmentManager().popBackStackImmediate();
+ }
+
if (containsKeys(mScannedFingerprints)) {
try {
long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mScannedFingerprints);
@@ -169,15 +174,15 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
Intent intent = new Intent(this, ViewKeyActivity.class);
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
- intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid);
- intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mScannedFingerprints);
startActivity(intent);
finish();
} catch (PgpKeyNotFoundException e) {
Fragment frag = CreateSecurityTokenImportResetFragment.newInstance(
- mScannedFingerprints, mNfcAid, mNfcUserId);
+ mScannedFingerprints, mSecurityTokenAid, mSecurityTokenUserId);
loadFragment(frag, FragAction.TO_RIGHT);
}
} else {
@@ -255,9 +260,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
}
- interface NfcListenerFragment {
- void doNfcInBackground() throws IOException;
- void onNfcPostExecute();
+ interface SecurityTokenListenerFragment {
+ void doSecurityTokenInBackground() throws IOException;
+ void onSecurityTokenPostExecute();
}
@Override
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java
index b020a0dba..b871f471c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java
@@ -44,7 +44,6 @@ import org.sufficientlysecure.keychain.ui.widget.EmailEditText;
import java.util.ArrayList;
import java.util.List;
-import java.util.regex.Pattern;
public class CreateKeyEmailFragment extends Fragment {
private CreateKeyActivity mCreateKeyActivity;
@@ -52,10 +51,6 @@ public class CreateKeyEmailFragment extends Fragment {
private ArrayList<EmailAdapter.ViewModel> mAdditionalEmailModels = new ArrayList<>();
private EmailAdapter mEmailAdapter;
- // NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS
- // EMAIL_ADDRESS fails for mails with umlauts for example
- private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\S]+@[\\S]+\\.[a-z]+$");
-
/**
* Creates new instance of this fragment
*/
@@ -76,16 +71,15 @@ public class CreateKeyEmailFragment extends Fragment {
* @return true if EditText is not empty
*/
private boolean isMainEmailValid(EditText editText) {
- boolean output = true;
- if (!checkEmail(editText.getText().toString(), false)) {
+ if (editText.getText().length() == 0) {
editText.setError(getString(R.string.create_key_empty));
editText.requestFocus();
- output = false;
- } else {
- editText.setError(null);
+ return false;
+ } else if (!checkEmail(editText.getText().toString(), false)){
+ return false;
}
-
- return output;
+ editText.setError(null);
+ return true;
}
@Override
@@ -146,10 +140,9 @@ public class CreateKeyEmailFragment extends Fragment {
* @return
*/
private boolean checkEmail(String email, boolean additionalEmail) {
- // check for email format or if the user did any input
- if (!isEmailFormatValid(email)) {
+ if (email.isEmpty()) {
Notify.create(getActivity(),
- getString(R.string.create_key_email_invalid_email),
+ getString(R.string.create_key_email_empty_email),
Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this);
return false;
}
@@ -167,18 +160,6 @@ public class CreateKeyEmailFragment extends Fragment {
}
/**
- * Checks the email format
- * Uses the default Android Email Pattern
- *
- * @param email
- * @return
- */
- private boolean isEmailFormatValid(String email) {
- // check for email format or if the user did any input
- return !(email.length() == 0 || !EMAIL_PATTERN.matcher(email).matches());
- }
-
- /**
* Checks for duplicated emails inside the additional email adapter.
*
* @param email
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
index cbf862074..896df0ad2 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
@@ -57,6 +57,7 @@ import org.sufficientlysecure.keychain.util.Preferences;
import java.util.Date;
import java.util.Iterator;
+import java.util.regex.Pattern;
public class CreateKeyFinalFragment extends Fragment {
@@ -81,6 +82,10 @@ public class CreateKeyFinalFragment extends Fragment {
private OperationResult mQueuedFinishResult;
private EditKeyResult mQueuedDisplayResult;
+ // NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS
+ // EMAIL_ADDRESS fails for mails with umlauts for example
+ private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\S]+@[\\S]+\\.[a-z]+$");
+
public static CreateKeyFinalFragment newInstance() {
CreateKeyFinalFragment frag = new CreateKeyFinalFragment();
frag.setRetainInstance(true);
@@ -106,7 +111,11 @@ public class CreateKeyFinalFragment extends Fragment {
CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity();
// set values
- mNameEdit.setText(createKeyActivity.mName);
+ if (createKeyActivity.mName != null) {
+ mNameEdit.setText(createKeyActivity.mName);
+ } else {
+ mNameEdit.setText(getString(R.string.user_id_no_name));
+ }
if (createKeyActivity.mAdditionalEmails != null && createKeyActivity.mAdditionalEmails.size() > 0) {
String emailText = createKeyActivity.mEmail + ", ";
Iterator<?> it = createKeyActivity.mAdditionalEmails.iterator();
@@ -122,6 +131,8 @@ public class CreateKeyFinalFragment extends Fragment {
mEmailEdit.setText(createKeyActivity.mEmail);
}
+ checkEmailValidity();
+
mCreateButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -309,6 +320,31 @@ public class CreateKeyFinalFragment extends Fragment {
return saveKeyringParcel;
}
+ private void checkEmailValidity() {
+ CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity();
+
+ boolean emailsValid = true;
+ if (!EMAIL_PATTERN.matcher(createKeyActivity.mEmail).matches()) {
+ emailsValid = false;
+ }
+ if (createKeyActivity.mAdditionalEmails != null && createKeyActivity.mAdditionalEmails.size() > 0) {
+ for (Iterator<?> it = createKeyActivity.mAdditionalEmails.iterator(); it.hasNext(); ) {
+ if (!EMAIL_PATTERN.matcher(it.next().toString()).matches()) {
+ emailsValid = false;
+ }
+ }
+ }
+ if (!emailsValid) {
+ mEmailEdit.setError(getString(R.string.create_key_final_email_valid_warning));
+ mEmailEdit.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mNameEdit.requestFocus(); // Workaround to remove focus from email
+ }
+ });
+ }
+ }
+
private void createKey() {
CreateKeyActivity activity = (CreateKeyActivity) getActivity();
if (activity == null) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java
index 7480367bb..3332b9cf9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java
@@ -18,13 +18,11 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
-import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.EditText;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
@@ -50,27 +48,6 @@ public class CreateKeyNameFragment extends Fragment {
return frag;
}
- /**
- * Checks if text of given EditText is not empty. If it is empty an error is
- * set and the EditText gets the focus.
- *
- * @param context
- * @param editText
- * @return true if EditText is not empty
- */
- private static boolean isEditTextNotEmpty(Context context, EditText editText) {
- boolean output = true;
- if (editText.getText().length() == 0) {
- editText.setError(context.getString(R.string.create_key_empty));
- editText.requestFocus();
- output = false;
- } else {
- editText.setError(null);
- }
-
- return output;
- }
-
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.create_key_name_fragment, container, false);
@@ -109,13 +86,11 @@ public class CreateKeyNameFragment extends Fragment {
}
private void nextClicked() {
- if (isEditTextNotEmpty(getActivity(), mNameEdit)) {
- // save state
- mCreateKeyActivity.mName = mNameEdit.getText().toString();
+ // save state
+ mCreateKeyActivity.mName = mNameEdit.getText().length() == 0 ? null : mNameEdit.getText().toString();
- CreateKeyEmailFragment frag = CreateKeyEmailFragment.newInstance();
- mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
- }
+ CreateKeyEmailFragment frag = CreateKeyEmailFragment.newInstance();
+ mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java
index ea57fe558..6f35fdd38 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java
@@ -25,7 +25,6 @@ import java.util.ArrayList;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
-import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
@@ -43,7 +42,7 @@ import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
-import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment;
+import org.sufficientlysecure.keychain.ui.CreateKeyActivity.SecurityTokenListenerFragment;
import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Preferences;
@@ -51,7 +50,7 @@ import org.sufficientlysecure.keychain.util.Preferences;
public class CreateSecurityTokenImportResetFragment
extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult>
- implements NfcListenerFragment {
+ implements SecurityTokenListenerFragment {
private static final int REQUEST_CODE_RESET = 0x00005001;
@@ -231,7 +230,7 @@ public class CreateSecurityTokenImportResetFragment
public void resetCard() {
Intent intent = new Intent(getActivity(), SecurityTokenOperationActivity.class);
- RequiredInputParcel resetP = RequiredInputParcel.createNfcReset();
+ RequiredInputParcel resetP = RequiredInputParcel.createSecurityTokenReset();
intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, resetP);
intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, new CryptoInputParcel());
startActivityForResult(intent, REQUEST_CODE_RESET);
@@ -248,11 +247,11 @@ public class CreateSecurityTokenImportResetFragment
}
@Override
- public void doNfcInBackground() throws IOException {
+ public void doSecurityTokenInBackground() throws IOException {
- mTokenFingerprints = mCreateKeyActivity.nfcGetFingerprints();
- mTokenAid = mCreateKeyActivity.nfcGetAid();
- mTokenUserId = mCreateKeyActivity.nfcGetUserId();
+ mTokenFingerprints = mCreateKeyActivity.getSecurityTokenHelper().getFingerprints();
+ mTokenAid = mCreateKeyActivity.getSecurityTokenHelper().getAid();
+ mTokenUserId = mCreateKeyActivity.getSecurityTokenHelper().getUserId();
byte[] fp = new byte[20];
ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20);
@@ -260,7 +259,7 @@ public class CreateSecurityTokenImportResetFragment
}
@Override
- public void onNfcPostExecute() {
+ public void onSecurityTokenPostExecute() {
setData();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java
index a3ea38e40..5dc2c478b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java
@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.os.Bundle;
+import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
@@ -26,6 +27,7 @@ import android.view.ViewGroup;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
+import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity;
public class CreateSecurityTokenWaitFragment extends Fragment {
@@ -34,6 +36,15 @@ public class CreateSecurityTokenWaitFragment extends Fragment {
View mBackButton;
@Override
+ public void onCreate(@Nullable final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (this.getActivity() instanceof BaseSecurityTokenNfcActivity) {
+ ((BaseSecurityTokenNfcActivity) this.getActivity()).checkDeviceConnection();
+ }
+ }
+
+ @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.create_security_token_wait_fragment, container, false);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
index 4cbb4724e..9ed8e369d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
@@ -35,6 +35,7 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
@@ -440,50 +441,45 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring
}
break;
}
- case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_CARD: {
- // TODO: enable later when Admin PIN handling is resolved
- Notify.create(getActivity(),
- "This feature will be available in an upcoming OpenKeychain version.",
- Notify.Style.WARN).show();
- break;
+ case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN: {
+ SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
+ if (secretKeyType == SecretKeyType.DIVERT_TO_CARD ||
+ secretKeyType == SecretKeyType.GNU_DUMMY) {
+ Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_stripped, Notify.Style.ERROR)
+ .show();
+ break;
+ }
+
+ int algorithm = mSubkeysAdapter.getAlgorithm(position);
+ if (algorithm != PublicKeyAlgorithmTags.RSA_GENERAL
+ && algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT
+ && algorithm != PublicKeyAlgorithmTags.RSA_SIGN) {
+ Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR)
+ .show();
+ break;
+ }
+
+ if (mSubkeysAdapter.getKeySize(position) != 2048) {
+ Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR)
+ .show();
+ break;
+ }
-// Activity activity = EditKeyFragment.this.getActivity();
-// SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
-// if (secretKeyType == SecretKeyType.DIVERT_TO_CARD ||
-// secretKeyType == SecretKeyType.GNU_DUMMY) {
-// Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR)
-// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
-// break;
-// }
-// int algorithm = mSubkeysAdapter.getAlgorithm(position);
-// // these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN
-// if (algorithm != 1 && algorithm != 2 && algorithm != 3) {
-// Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR)
-// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
-// break;
-// }
-// if (mSubkeysAdapter.getKeySize(position) != 2048) {
-// Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR)
-// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
-// break;
-// }
-//
-//
-// SubkeyChange change;
-// change = mSaveKeyringParcel.getSubkeyChange(keyId);
-// if (change == null) {
-// mSaveKeyringParcel.mChangeSubKeys.add(
-// new SubkeyChange(keyId, false, true)
-// );
-// break;
-// }
-// // toggle
-// change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken;
-// if (change.mMoveKeyToSecurityToken && change.mDummyStrip) {
-// // User had chosen to strip key, but now wants to divert it.
-// change.mDummyStrip = false;
-// }
-// break;
+ SubkeyChange change;
+ change = mSaveKeyringParcel.getSubkeyChange(keyId);
+ if (change == null) {
+ mSaveKeyringParcel.mChangeSubKeys.add(
+ new SubkeyChange(keyId, false, true)
+ );
+ break;
+ }
+ // toggle
+ change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken;
+ if (change.mMoveKeyToSecurityToken && change.mDummyStrip) {
+ // User had chosen to strip key, but now wants to divert it.
+ change.mDummyStrip = false;
+ }
+ break;
}
}
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
index 22202a35f..d5c540856 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
@@ -729,6 +729,8 @@ public class EncryptFilesFragment
// make sure this is correct at this point
mAfterEncryptAction = AfterEncryptAction.SAVE;
cryptoOperation(new CryptoInputParcel(new Date()));
+ } else if (resultCode == Activity.RESULT_CANCELED) {
+ onCryptoOperationCancelled();
}
return;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java
index b2b85ec14..51022094b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java
@@ -21,6 +21,7 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.ImageView;
import android.widget.ViewAnimator;
import com.tokenautocomplete.TokenCompleteTextView;
@@ -109,6 +110,14 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
}
});
+ ImageView addRecipientImgView = (ImageView) view.findViewById(R.id.add_recipient);
+ addRecipientImgView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mEncryptKeyView.showAllKeys();
+ }
+ });
+
return view;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
index af60a1d9b..37e01e98f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
@@ -90,8 +90,9 @@ public class MainActivity extends BaseSecurityTokenNfcActivity implements FabCon
@Override
public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
if (drawerItem != null) {
+ PrimaryDrawerItem item = (PrimaryDrawerItem) drawerItem;
Intent intent = null;
- switch (drawerItem.getIdentifier()) {
+ switch ((int) item.getIdentifier()) {
case ID_KEYS:
onKeysSelected();
break;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java
new file mode 100644
index 000000000..5cb680a57
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AlertDialog;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
+
+public class RedirectImportKeysActivity extends BaseActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ startQrCodeCaptureActivity();
+ }
+
+ private void startQrCodeCaptureActivity() {
+ final Intent scanQrCode = new Intent(this, ImportKeysProxyActivity.class);
+ scanQrCode.setAction(ImportKeysProxyActivity.ACTION_QR_CODE_API);
+
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.redirect_import_key_title)
+ .setMessage(R.string.redirect_import_key_message)
+ .setPositiveButton(R.string.redirect_import_key_yes, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // directly scan with OpenKeychain
+ startActivity(scanQrCode);
+ finish();
+ }
+ })
+ .setNegativeButton(R.string.redirect_import_key_no, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // close window
+ finish();
+ }
+ })
+ .show();
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java
index d2ecce242..925ad19d4 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java
@@ -3,6 +3,7 @@
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
* Copyright (C) 2013-2014 Signe Rüsch
* Copyright (C) 2013-2014 Philipp Jakubeit
+ * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -35,10 +36,12 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.securitytoken.KeyType;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.OrientationUtils;
@@ -69,8 +72,6 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
private RequiredInputParcel mRequiredInput;
- private static final byte[] BLANK_FINGERPRINT = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-
private CryptoInputParcel mInputParcel;
@Override
@@ -137,9 +138,33 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
private void obtainPassphraseIfRequired() {
// obtain passphrase for this subkey
- if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD
- && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_RESET_CARD) {
+ if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD
+ && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SECURITY_TOKEN_RESET_CARD) {
obtainSecurityTokenPin(mRequiredInput);
+ checkPinAvailability();
+ } else {
+ checkDeviceConnection();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (REQUEST_CODE_PIN == requestCode) {
+ checkPinAvailability();
+ }
+ }
+
+ private void checkPinAvailability() {
+ try {
+ Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this,
+ mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
+ if (passphrase != null) {
+ checkDeviceConnection();
+ }
+ } catch (PassphraseCacheService.KeyNotFoundException e) {
+ throw new AssertionError(
+ "tried to find passphrase for non-existing key. this is a programming error!");
}
}
@@ -149,39 +174,53 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
}
@Override
- public void onNfcPreExecute() {
+ public void onSecurityTokenPreExecute() {
// start with indeterminate progress
vAnimator.setDisplayedChild(1);
nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.TRANSFERRING);
}
@Override
- protected void doNfcInBackground() throws IOException {
+ protected void doSecurityTokenInBackground() throws IOException {
switch (mRequiredInput.mType) {
- case NFC_DECRYPT: {
+ case SECURITY_TOKEN_DECRYPT: {
+ long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(
+ mSecurityTokenHelper.getKeyFingerprint(KeyType.ENCRYPT));
+
+ if (tokenKeyId != mRequiredInput.getSubKeyId()) {
+ throw new IOException(getString(R.string.error_wrong_security_token));
+ }
+
for (int i = 0; i < mRequiredInput.mInputData.length; i++) {
byte[] encryptedSessionKey = mRequiredInput.mInputData[i];
- byte[] decryptedSessionKey = nfcDecryptSessionKey(encryptedSessionKey);
+ byte[] decryptedSessionKey = mSecurityTokenHelper.decryptSessionKey(encryptedSessionKey);
mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey);
}
break;
}
- case NFC_SIGN: {
+ case SECURITY_TOKEN_SIGN: {
+ long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(
+ mSecurityTokenHelper.getKeyFingerprint(KeyType.SIGN));
+
+ if (tokenKeyId != mRequiredInput.getSubKeyId()) {
+ throw new IOException(getString(R.string.error_wrong_security_token));
+ }
+
mInputParcel.addSignatureTime(mRequiredInput.mSignatureTime);
for (int i = 0; i < mRequiredInput.mInputData.length; i++) {
byte[] hash = mRequiredInput.mInputData[i];
int algo = mRequiredInput.mSignAlgos[i];
- byte[] signedHash = nfcCalculateSignature(hash, algo);
+ byte[] signedHash = mSecurityTokenHelper.calculateSignature(hash, algo);
mInputParcel.addCryptoData(hash, signedHash);
}
break;
}
- case NFC_MOVE_KEY_TO_CARD: {
+ case SECURITY_TOKEN_MOVE_KEY_TO_CARD: {
// TODO: assume PIN and Admin PIN to be default for this operation
- mPin = new Passphrase("123456");
- mAdminPin = new Passphrase("12345678");
+ mSecurityTokenHelper.setPin(new Passphrase("123456"));
+ mSecurityTokenHelper.setAdminPin(new Passphrase("12345678"));
ProviderHelper providerHelper = new ProviderHelper(this);
CanonicalizedSecretKeyRing secretKeyRing;
@@ -202,11 +241,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
long subkeyId = buf.getLong();
CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId);
-
- long keyGenerationTimestampMillis = key.getCreationTime().getTime();
- long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000;
- byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array();
- byte[] tokenSerialNumber = Arrays.copyOf(nfcGetAid(), 16);
+ byte[] tokenSerialNumber = Arrays.copyOf(mSecurityTokenHelper.getAid(), 16);
Passphrase passphrase;
try {
@@ -216,46 +251,20 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
throw new IOException("Unable to get cached passphrase!");
}
- if (key.canSign() || key.canCertify()) {
- if (shouldPutKey(key.getFingerprint(), 0)) {
- nfcPutKey(0xB6, key, passphrase);
- nfcPutData(0xCE, timestampBytes);
- nfcPutData(0xC7, key.getFingerprint());
- } else {
- throw new IOException("Key slot occupied; token must be reset to put new signature key.");
- }
- } else if (key.canEncrypt()) {
- if (shouldPutKey(key.getFingerprint(), 1)) {
- nfcPutKey(0xB8, key, passphrase);
- nfcPutData(0xCF, timestampBytes);
- nfcPutData(0xC8, key.getFingerprint());
- } else {
- throw new IOException("Key slot occupied; token must be reset to put new decryption key.");
- }
- } else if (key.canAuthenticate()) {
- if (shouldPutKey(key.getFingerprint(), 2)) {
- nfcPutKey(0xA4, key, passphrase);
- nfcPutData(0xD0, timestampBytes);
- nfcPutData(0xC9, key.getFingerprint());
- } else {
- throw new IOException("Key slot occupied; token must be reset to put new authentication key.");
- }
- } else {
- throw new IOException("Inappropriate key flags for Security Token key.");
- }
+ mSecurityTokenHelper.changeKey(key, passphrase);
// TODO: Is this really used anywhere?
mInputParcel.addCryptoData(subkeyBytes, tokenSerialNumber);
}
// change PINs afterwards
- nfcModifyPin(0x81, newPin);
- nfcModifyPin(0x83, newAdminPin);
+ mSecurityTokenHelper.modifyPin(0x81, newPin);
+ mSecurityTokenHelper.modifyPin(0x83, newAdminPin);
break;
}
- case NFC_RESET_CARD: {
- nfcReset();
+ case SECURITY_TOKEN_RESET_CARD: {
+ mSecurityTokenHelper.resetAndWipeToken();
break;
}
@@ -267,7 +276,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
}
@Override
- protected final void onNfcPostExecute() {
+ protected final void onSecurityTokenPostExecute() {
handleResult(mInputParcel);
// show finish
@@ -275,28 +284,33 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE);
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- // check all 200ms if Security Token has been taken away
- while (true) {
- if (isNfcConnected()) {
- try {
- Thread.sleep(200);
- } catch (InterruptedException ignored) {
+ if (mSecurityTokenHelper.isPersistentConnectionAllowed()) {
+ // Just close
+ finish();
+ } else {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ // check all 200ms if Security Token has been taken away
+ while (true) {
+ if (isSecurityTokenConnected()) {
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException ignored) {
+ }
+ } else {
+ return null;
}
- } else {
- return null;
}
}
- }
- @Override
- protected void onPostExecute(Void result) {
- super.onPostExecute(result);
- finish();
- }
- }.execute();
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ finish();
+ }
+ }.execute();
+ }
}
/**
@@ -311,7 +325,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
}
@Override
- protected void onNfcError(String error) {
+ protected void onSecurityTokenError(String error) {
pauseTagHandling();
vErrorText.setText(error + "\n\n" + getString(R.string.security_token_nfc_try_again_text));
@@ -321,31 +335,11 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
}
@Override
- public void onNfcPinError(String error) {
- onNfcError(error);
+ public void onSecurityTokenPinError(String error) {
+ onSecurityTokenError(error);
// clear (invalid) passphrase
PassphraseCacheService.clearCachedPassphrase(
this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
}
-
- private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException {
- byte[] tokenFingerprint = nfcGetMasterKeyFingerprint(idx);
-
- // Note: special case: This should not happen, but happens with
- // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true
- if (tokenFingerprint == null) {
- return true;
- }
-
- // Slot is empty, or contains this key already. PUT KEY operation is safe
- if (Arrays.equals(tokenFingerprint, BLANK_FINGERPRINT) ||
- Arrays.equals(tokenFingerprint, fingerprint)) {
- return true;
- }
-
- // Slot already contains a different key; don't overwrite it.
- return false;
- }
-
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java
new file mode 100644
index 000000000..05b30b1ae
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class UsbEventReceiverActivity extends Activity {
+ public static final String ACTION_USB_PERMISSION =
+ "org.sufficientlysecure.keychain.ui.USB_PERMISSION";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ final UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
+
+ Intent intent = getIntent();
+ if (intent != null) {
+ if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
+ UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+
+ Log.d(Constants.TAG, "Requesting permission for " + usbDevice.getDeviceName());
+ usbManager.requestPermission(usbDevice,
+ PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0));
+ }
+ }
+
+ // Close the activity
+ finish();
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
index d81797454..e47ca1db9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
@@ -32,7 +32,6 @@ import android.app.ActivityOptions;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
-import android.graphics.Color;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.nfc.NfcAdapter;
@@ -172,9 +171,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
private byte[] mFingerprint;
private String mFingerprintString;
- private byte[] mNfcFingerprints;
- private String mNfcUserId;
- private byte[] mNfcAid;
+ private byte[] mSecurityTokenFingerprints;
+ private String mSecurityTokenUserId;
+ private byte[] mSecurityTokenAid;
@SuppressLint("InflateParams")
@Override
@@ -647,17 +646,17 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
}
@Override
- protected void doNfcInBackground() throws IOException {
+ protected void doSecurityTokenInBackground() throws IOException {
- mNfcFingerprints = nfcGetFingerprints();
- mNfcUserId = nfcGetUserId();
- mNfcAid = nfcGetAid();
+ mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints();
+ mSecurityTokenUserId = mSecurityTokenHelper.getUserId();
+ mSecurityTokenAid = mSecurityTokenHelper.getAid();
}
@Override
- protected void onNfcPostExecute() {
+ protected void onSecurityTokenPostExecute() {
- long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
+ long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints);
try {
@@ -668,7 +667,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
// if the master key of that key matches this one, just show the token dialog
if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprintString)) {
- showSecurityTokenFragment(mNfcFingerprints, mNfcUserId, mNfcAid);
+ showSecurityTokenFragment(mSecurityTokenFingerprints, mSecurityTokenUserId, mSecurityTokenAid);
return;
}
@@ -681,9 +680,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
Intent intent = new Intent(
ViewKeyActivity.this, ViewKeyActivity.class);
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
- intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid);
- intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId);
- intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints);
+ intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints);
startActivity(intent);
finish();
}
@@ -696,9 +695,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
public void onAction() {
Intent intent = new Intent(
ViewKeyActivity.this, CreateKeyActivity.class);
- intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid);
- intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId);
- intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints);
+ intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints);
startActivity(intent);
finish();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java
index fc6db1b92..93b38af9b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java
@@ -39,6 +39,7 @@ import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.ViewAnimator;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
@@ -346,50 +347,45 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
}
break;
}
- case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_CARD: {
- // TODO: enable later when Admin PIN handling is resolved
- Notify.create(getActivity(),
- "This feature will be available in an upcoming OpenKeychain version.",
- Notify.Style.WARN).show();
- break;
+ case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN: {
+ SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
+ if (secretKeyType == SecretKeyType.DIVERT_TO_CARD ||
+ secretKeyType == SecretKeyType.GNU_DUMMY) {
+ Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_stripped, Notify.Style.ERROR)
+ .show();
+ break;
+ }
+
+ int algorithm = mSubkeysAdapter.getAlgorithm(position);
+ if (algorithm != PublicKeyAlgorithmTags.RSA_GENERAL
+ && algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT
+ && algorithm != PublicKeyAlgorithmTags.RSA_SIGN) {
+ Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR)
+ .show();
+ break;
+ }
+
+ if (mSubkeysAdapter.getKeySize(position) != 2048) {
+ Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR)
+ .show();
+ break;
+ }
-// Activity activity = EditKeyFragment.this.getActivity();
-// SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
-// if (secretKeyType == SecretKeyType.DIVERT_TO_CARD ||
-// secretKeyType == SecretKeyType.GNU_DUMMY) {
-// Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR)
-// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
-// break;
-// }
-// int algorithm = mSubkeysAdapter.getAlgorithm(position);
-// // these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN
-// if (algorithm != 1 && algorithm != 2 && algorithm != 3) {
-// Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR)
-// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
-// break;
-// }
-// if (mSubkeysAdapter.getKeySize(position) != 2048) {
-// Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR)
-// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
-// break;
-// }
-//
-//
-// SubkeyChange change;
-// change = mSaveKeyringParcel.getSubkeyChange(keyId);
-// if (change == null) {
-// mSaveKeyringParcel.mChangeSubKeys.add(
-// new SubkeyChange(keyId, false, true)
-// );
-// break;
-// }
-// // toggle
-// change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken;
-// if (change.mMoveKeyToSecurityToken && change.mDummyStrip) {
-// // User had chosen to strip key, but now wants to divert it.
-// change.mDummyStrip = false;
-// }
-// break;
+ SubkeyChange change;
+ change = mEditModeSaveKeyringParcel.getSubkeyChange(keyId);
+ if (change == null) {
+ mEditModeSaveKeyringParcel.mChangeSubKeys.add(
+ new SubkeyChange(keyId, false, true)
+ );
+ break;
+ }
+ // toggle
+ change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken;
+ if (change.mMoveKeyToSecurityToken && change.mDummyStrip) {
+ // User had chosen to strip key, but now wants to divert it.
+ change.mDummyStrip = false;
+ }
+ break;
}
}
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java
index 77044cd2b..f4c0a9365 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java
@@ -3,6 +3,7 @@
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
* Copyright (C) 2013-2014 Signe Rüsch
* Copyright (C) 2013-2014 Philipp Jakubeit
+ * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,41 +21,33 @@
package org.sufficientlysecure.keychain.ui.base;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.security.interfaces.RSAPrivateCrtKey;
-
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.TagLostException;
import android.os.AsyncTask;
import android.os.Bundle;
-import nordpol.Apdu;
-import nordpol.android.TagDispatcher;
-import nordpol.android.AndroidCard;
-import nordpol.android.OnDiscoveredTagListener;
-import nordpol.IsoCard;
-
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.util.Arrays;
-import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
-import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
-import org.sufficientlysecure.keychain.service.PassphraseCacheService.KeyNotFoundException;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+import org.sufficientlysecure.keychain.securitytoken.CardException;
+import org.sufficientlysecure.keychain.securitytoken.NfcTransport;
+import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper;
+import org.sufficientlysecure.keychain.securitytoken.Transport;
+import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher;
+import org.sufficientlysecure.keychain.securitytoken.UsbTransport;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
@@ -63,56 +56,52 @@ import org.sufficientlysecure.keychain.ui.dialog.FidesmoPgpInstallDialog;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
-import org.sufficientlysecure.keychain.util.Iso7816TLV;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
-public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implements OnDiscoveredTagListener {
+import java.io.IOException;
+
+import nordpol.android.OnDiscoveredTagListener;
+import nordpol.android.TagDispatcher;
+
+public abstract class BaseSecurityTokenNfcActivity extends BaseActivity
+ implements OnDiscoveredTagListener, UsbConnectionDispatcher.OnDiscoveredUsbDeviceListener {
public static final int REQUEST_CODE_PIN = 1;
public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled";
- // Fidesmo constants
- private static final String FIDESMO_APPS_AID_PREFIX = "A000000617";
private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android";
- protected Passphrase mPin;
- protected Passphrase mAdminPin;
- protected boolean mPw1ValidForMultipleSignatures;
- protected boolean mPw1ValidatedForSignature;
- protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming?
- protected boolean mPw3Validated;
+ protected SecurityTokenHelper mSecurityTokenHelper = SecurityTokenHelper.getInstance();
protected TagDispatcher mTagDispatcher;
- private IsoCard mIsoCard;
+ protected UsbConnectionDispatcher mUsbDispatcher;
private boolean mTagHandlingEnabled;
- private static final int TIMEOUT = 100000;
-
- private byte[] mNfcFingerprints;
- private String mNfcUserId;
- private byte[] mNfcAid;
+ private byte[] mSecurityTokenFingerprints;
+ private String mSecurityTokenUserId;
+ private byte[] mSecurityTokenAid;
/**
- * Override to change UI before NFC handling (UI thread)
+ * Override to change UI before SecurityToken handling (UI thread)
*/
- protected void onNfcPreExecute() {
+ protected void onSecurityTokenPreExecute() {
}
/**
- * Override to implement NFC operations (background thread)
+ * Override to implement SecurityToken operations (background thread)
*/
- protected void doNfcInBackground() throws IOException {
- mNfcFingerprints = nfcGetFingerprints();
- mNfcUserId = nfcGetUserId();
- mNfcAid = nfcGetAid();
+ protected void doSecurityTokenInBackground() throws IOException {
+ mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints();
+ mSecurityTokenUserId = mSecurityTokenHelper.getUserId();
+ mSecurityTokenAid = mSecurityTokenHelper.getAid();
}
/**
- * Override to handle result of NFC operations (UI thread)
+ * Override to handle result of SecurityToken operations (UI thread)
*/
- protected void onNfcPostExecute() {
+ protected void onSecurityTokenPostExecute() {
- final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
+ final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints);
try {
CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(
@@ -121,15 +110,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
Intent intent = new Intent(this, ViewKeyActivity.class);
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
- intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid);
- intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId);
- intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints);
+ intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints);
startActivity(intent);
} catch (PgpKeyNotFoundException e) {
Intent intent = new Intent(this, CreateKeyActivity.class);
- intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, mNfcAid);
- intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId);
- intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints);
+ intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
+ intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
+ intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_FINGERPRINTS, mSecurityTokenFingerprints);
startActivity(intent);
}
}
@@ -137,32 +126,49 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
/**
* Override to use something different than Notify (UI thread)
*/
- protected void onNfcError(String error) {
+ protected void onSecurityTokenError(String error) {
Notify.create(this, error, Style.WARN).show();
}
/**
* Override to do something when PIN is wrong, e.g., clear passphrases (UI thread)
*/
- protected void onNfcPinError(String error) {
- onNfcError(error);
+ protected void onSecurityTokenPinError(String error) {
+ onSecurityTokenError(error);
}
public void tagDiscovered(final Tag tag) {
// Actual NFC operations are executed in doInBackground to not block the UI thread
- if(!mTagHandlingEnabled)
+ if (!mTagHandlingEnabled)
+ return;
+
+ securityTokenDiscovered(new NfcTransport(tag));
+ }
+
+ public void usbDeviceDiscovered(final UsbDevice usbDevice) {
+ // Actual USB operations are executed in doInBackground to not block the UI thread
+ if (!mTagHandlingEnabled)
+ return;
+
+ UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
+ securityTokenDiscovered(new UsbTransport(usbDevice, usbManager));
+ }
+
+ public void securityTokenDiscovered(final Transport transport) {
+ // Actual Security Token operations are executed in doInBackground to not block the UI thread
+ if (!mTagHandlingEnabled)
return;
new AsyncTask<Void, Void, IOException>() {
@Override
protected void onPreExecute() {
super.onPreExecute();
- onNfcPreExecute();
+ onSecurityTokenPreExecute();
}
@Override
protected IOException doInBackground(Void... params) {
try {
- handleTagDiscovered(tag);
+ handleSecurityToken(transport);
} catch (IOException e) {
return e;
}
@@ -175,11 +181,11 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
super.onPostExecute(exception);
if (exception != null) {
- handleNfcError(exception);
+ handleSecurityTokenError(exception);
return;
}
- onNfcPostExecute();
+ onSecurityTokenPostExecute();
}
}.execute();
}
@@ -197,6 +203,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
super.onCreate(savedInstanceState);
mTagDispatcher = TagDispatcher.get(this, this, false, false, true, false);
+ mUsbDispatcher = new UsbConnectionDispatcher(this, this);
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
@@ -230,15 +237,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
mTagDispatcher.interceptIntent(intent);
}
- private void handleNfcError(IOException e) {
+ private void handleSecurityTokenError(IOException e) {
if (e instanceof TagLostException) {
- onNfcError(getString(R.string.security_token_error_tag_lost));
+ onSecurityTokenError(getString(R.string.security_token_error_tag_lost));
return;
}
if (e instanceof IsoDepNotSupportedException) {
- onNfcError(getString(R.string.security_token_error_iso_dep_not_supported));
+ onSecurityTokenError(getString(R.string.security_token_error_iso_dep_not_supported));
return;
}
@@ -253,7 +260,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
if ((status & (short) 0xFFF0) == 0x63C0) {
int tries = status & 0x000F;
// hook to do something different when PIN is wrong
- onNfcPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries));
+ onSecurityTokenPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries));
return;
}
@@ -262,61 +269,61 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
// These errors should not occur in everyday use; if they are returned, it means we
// made a mistake sending data to the token, or the token is misbehaving.
case 0x6A80: {
- onNfcError(getString(R.string.security_token_error_bad_data));
+ onSecurityTokenError(getString(R.string.security_token_error_bad_data));
break;
}
case 0x6883: {
- onNfcError(getString(R.string.security_token_error_chaining_error));
+ onSecurityTokenError(getString(R.string.security_token_error_chaining_error));
break;
}
case 0x6B00: {
- onNfcError(getString(R.string.security_token_error_header, "P1/P2"));
+ onSecurityTokenError(getString(R.string.security_token_error_header, "P1/P2"));
break;
}
case 0x6D00: {
- onNfcError(getString(R.string.security_token_error_header, "INS"));
+ onSecurityTokenError(getString(R.string.security_token_error_header, "INS"));
break;
}
case 0x6E00: {
- onNfcError(getString(R.string.security_token_error_header, "CLA"));
+ onSecurityTokenError(getString(R.string.security_token_error_header, "CLA"));
break;
}
// These error conditions are more likely to be experienced by an end user.
case 0x6285: {
- onNfcError(getString(R.string.security_token_error_terminated));
+ onSecurityTokenError(getString(R.string.security_token_error_terminated));
break;
}
case 0x6700: {
- onNfcPinError(getString(R.string.security_token_error_wrong_length));
+ onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length));
break;
}
case 0x6982: {
- onNfcError(getString(R.string.security_token_error_security_not_satisfied));
+ onSecurityTokenError(getString(R.string.security_token_error_security_not_satisfied));
break;
}
case 0x6983: {
- onNfcError(getString(R.string.security_token_error_authentication_blocked));
+ onSecurityTokenError(getString(R.string.security_token_error_authentication_blocked));
break;
}
case 0x6985: {
- onNfcError(getString(R.string.security_token_error_conditions_not_satisfied));
+ onSecurityTokenError(getString(R.string.security_token_error_conditions_not_satisfied));
break;
}
// 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases.
case 0x6A88:
case 0x6A83: {
- onNfcError(getString(R.string.security_token_error_data_not_found));
+ onSecurityTokenError(getString(R.string.security_token_error_data_not_found));
break;
}
// 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an
// unhandled exception on the security token.
case 0x6F00: {
- onNfcError(getString(R.string.security_token_error_unknown));
+ onSecurityTokenError(getString(R.string.security_token_error_unknown));
break;
}
// 6A82 app not installed on security token!
case 0x6A82: {
- if (isFidesmoToken()) {
+ if (mSecurityTokenHelper.isFidesmoToken()) {
// Check if the Fidesmo app is installed
if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) {
promptFidesmoPgpInstall();
@@ -324,12 +331,12 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
promptFidesmoAppInstall();
}
} else { // Other (possibly) compatible hardware
- onNfcError(getString(R.string.security_token_error_pgp_app_not_installed));
+ onSecurityTokenError(getString(R.string.security_token_error_pgp_app_not_installed));
}
break;
}
default: {
- onNfcError(getString(R.string.security_token_error, e.getMessage()));
+ onSecurityTokenError(getString(R.string.security_token_error, e.getMessage()));
break;
}
}
@@ -363,7 +370,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this,
requiredInput.getMasterKeyId(), requiredInput.getSubKeyId());
if (passphrase != null) {
- mPin = passphrase;
+ mSecurityTokenHelper.setPin(passphrase);
return;
}
@@ -371,7 +378,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT,
RequiredInputParcel.createRequiredPassphrase(requiredInput));
startActivityForResult(intent, REQUEST_CODE_PIN);
- } catch (KeyNotFoundException e) {
+ } catch (PassphraseCacheService.KeyNotFoundException e) {
throw new AssertionError(
"tried to find passphrase for non-existing key. this is a programming error!");
}
@@ -388,7 +395,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
return;
}
CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
- mPin = input.getPassphrase();
+ mSecurityTokenHelper.setPin(input.getPassphrase());
break;
}
default:
@@ -396,639 +403,22 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
}
}
- /** Handle NFC communication and return a result.
- *
- * This method is called by onNewIntent above upon discovery of an NFC tag.
- * It handles initialization and login to the application, subsequently
- * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then
- * finishes the activity with an appropriate result.
- *
- * On general communication, see also
- * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx
- *
- * References to pages are generally related to the OpenPGP Application
- * on ISO SmartCard Systems specification.
- *
- */
- protected void handleTagDiscovered(Tag tag) throws IOException {
-
- // Connect to the detected tag, setting a couple of settings
- mIsoCard = AndroidCard.get(tag);
- if (mIsoCard == null) {
- throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)");
- }
- mIsoCard.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation
- mIsoCard.connect();
-
- // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
- // See specification, page 51
- String accepted = "9000";
-
- // Command APDU (page 51) for SELECT FILE command (page 29)
- String opening =
- "00" // CLA
- + "A4" // INS
- + "04" // P1
- + "00" // P2
- + "06" // Lc (number of bytes)
- + "D27600012401" // Data (6 bytes)
- + "00"; // Le
- String response = nfcCommunicate(opening); // activate connection
- if ( ! response.endsWith(accepted) ) {
- throw new CardException("Initialization failed!", parseCardStatus(response));
- }
-
- byte[] pwStatusBytes = nfcGetPwStatusBytes();
- mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1);
- mPw1ValidatedForSignature = false;
- mPw1ValidatedForDecrypt = false;
- mPw3Validated = false;
-
- doNfcInBackground();
-
- }
-
- public boolean isNfcConnected() {
- return mIsoCard.isConnected();
- }
-
- /** Return the key id from application specific data stored on tag, or null
- * if it doesn't exist.
- *
- * @param idx Index of the key to return the fingerprint from.
- * @return The long key id of the requested key, or null if not found.
- */
- public Long nfcGetKeyId(int idx) throws IOException {
- byte[] fp = nfcGetMasterKeyFingerprint(idx);
- if (fp == null) {
- return null;
- }
- ByteBuffer buf = ByteBuffer.wrap(fp);
- // skip first 12 bytes of the fingerprint
- buf.position(12);
- // the last eight bytes are the key id (big endian, which is default order in ByteBuffer)
- return buf.getLong();
- }
-
- /** Return fingerprints of all keys from application specific data stored
- * on tag, or null if data not available.
- *
- * @return The fingerprints of all subkeys in a contiguous byte array.
- */
- public byte[] nfcGetFingerprints() throws IOException {
- String data = "00CA006E00";
- byte[] buf = mIsoCard.transceive(Hex.decode(data));
-
- Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true);
- Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint());
-
- Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5);
- if (fptlv == null) {
- return null;
- }
-
- return fptlv.mV;
- }
-
- /** Return the PW Status Bytes from the token. This is a simple DO; no TLV decoding needed.
- *
- * @return Seven bytes in fixed format, plus 0x9000 status word at the end.
- */
- public byte[] nfcGetPwStatusBytes() throws IOException {
- String data = "00CA00C400";
- return mIsoCard.transceive(Hex.decode(data));
- }
-
- /** Return the fingerprint from application specific data stored on tag, or
- * null if it doesn't exist.
- *
- * @param idx Index of the key to return the fingerprint from.
- * @return The fingerprint of the requested key, or null if not found.
- */
- public byte[] nfcGetMasterKeyFingerprint(int idx) throws IOException {
- byte[] data = nfcGetFingerprints();
- if (data == null) {
- return null;
- }
-
- // return the master key fingerprint
- ByteBuffer fpbuf = ByteBuffer.wrap(data);
- byte[] fp = new byte[20];
- fpbuf.position(idx * 20);
- fpbuf.get(fp, 0, 20);
-
- return fp;
- }
-
- public byte[] nfcGetAid() throws IOException {
- String info = "00CA004F00";
- return mIsoCard.transceive(Hex.decode(info));
- }
-
- public String nfcGetUserId() throws IOException {
- String info = "00CA006500";
- return getHolderName(nfcCommunicate(info));
- }
-
- /**
- * Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value
- *
- * @param hash the hash for signing
- * @return a big integer representing the MPI for the given hash
- */
- public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException {
- if (!mPw1ValidatedForSignature) {
- nfcVerifyPin(0x81); // (Verify PW1 with mode 81 for signing)
- }
-
- // dsi, including Lc
- String dsi;
-
- Log.i(Constants.TAG, "Hash: " + hashAlgo);
- switch (hashAlgo) {
- case HashAlgorithmTags.SHA1:
- if (hash.length != 20) {
- throw new IOException("Bad hash length (" + hash.length + ", expected 10!");
- }
- dsi = "23" // Lc
- + "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes
- + "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes
- + "0605" + "2B0E03021A" // OID of SHA1
- + "0500" // TLV coding of ZERO
- + "0414" + getHex(hash); // 0x14 are 20 hash bytes
- break;
- case HashAlgorithmTags.RIPEMD160:
- if (hash.length != 20) {
- throw new IOException("Bad hash length (" + hash.length + ", expected 20!");
- }
- dsi = "233021300906052B2403020105000414" + getHex(hash);
- break;
- case HashAlgorithmTags.SHA224:
- if (hash.length != 28) {
- throw new IOException("Bad hash length (" + hash.length + ", expected 28!");
- }
- dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash);
- break;
- case HashAlgorithmTags.SHA256:
- if (hash.length != 32) {
- throw new IOException("Bad hash length (" + hash.length + ", expected 32!");
- }
- dsi = "333031300D060960864801650304020105000420" + getHex(hash);
- break;
- case HashAlgorithmTags.SHA384:
- if (hash.length != 48) {
- throw new IOException("Bad hash length (" + hash.length + ", expected 48!");
- }
- dsi = "433041300D060960864801650304020205000430" + getHex(hash);
- break;
- case HashAlgorithmTags.SHA512:
- if (hash.length != 64) {
- throw new IOException("Bad hash length (" + hash.length + ", expected 64!");
- }
- dsi = "533051300D060960864801650304020305000440" + getHex(hash);
- break;
- default:
- throw new IOException("Not supported hash algo!");
- }
-
- // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
- String apdu =
- "002A9E9A" // CLA, INS, P1, P2
- + dsi // digital signature input
- + "00"; // Le
-
- String response = nfcCommunicate(apdu);
-
- // split up response into signature and status
- String status = response.substring(response.length()-4);
- String signature = response.substring(0, response.length() - 4);
-
- // while we are getting 0x61 status codes, retrieve more data
- while (status.substring(0, 2).equals("61")) {
- Log.d(Constants.TAG, "requesting more data, status " + status);
- // Send GET RESPONSE command
- response = nfcCommunicate("00C00000" + status.substring(2));
- status = response.substring(response.length()-4);
- signature += response.substring(0, response.length()-4);
- }
-
- Log.d(Constants.TAG, "final response:" + status);
-
- if (!mPw1ValidForMultipleSignatures) {
- mPw1ValidatedForSignature = false;
- }
-
- if ( ! "9000".equals(status)) {
- throw new CardException("Bad NFC response code: " + status, parseCardStatus(response));
- }
-
- // Make sure the signature we received is actually the expected number of bytes long!
- if (signature.length() != 256 && signature.length() != 512) {
- throw new IOException("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2);
- }
-
- return Hex.decode(signature);
- }
-
- /**
- * Call DECIPHER command
- *
- * @param encryptedSessionKey the encoded session key
- * @return the decoded session key
- */
- public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException {
- if (!mPw1ValidatedForDecrypt) {
- nfcVerifyPin(0x82); // (Verify PW1 with mode 82 for decryption)
+ protected void handleSecurityToken(Transport transport) throws IOException {
+ // Don't reconnect if device was already connected
+ if (!(mSecurityTokenHelper.isPersistentConnectionAllowed()
+ && mSecurityTokenHelper.isConnected()
+ && mSecurityTokenHelper.getTransport().equals(transport))) {
+ mSecurityTokenHelper.setTransport(transport);
+ mSecurityTokenHelper.connectToDevice();
}
-
- String firstApdu = "102a8086fe";
- String secondApdu = "002a808603";
- String le = "00";
-
- byte[] one = new byte[254];
- // leave out first byte:
- System.arraycopy(encryptedSessionKey, 1, one, 0, one.length);
-
- byte[] two = new byte[encryptedSessionKey.length - 1 - one.length];
- for (int i = 0; i < two.length; i++) {
- two[i] = encryptedSessionKey[i + one.length + 1];
- }
-
- nfcCommunicate(firstApdu + getHex(one));
- String second = nfcCommunicate(secondApdu + getHex(two) + le);
-
- String decryptedSessionKey = getDataField(second);
-
- return Hex.decode(decryptedSessionKey);
+ doSecurityTokenInBackground();
}
- /** Verifies the user's PW1 or PW3 with the appropriate mode.
- *
- * @param mode For PW1, this is 0x81 for signing, 0x82 for everything else.
- * For PW3 (Admin PIN), mode is 0x83.
- */
- public void nfcVerifyPin(int mode) throws IOException {
- if (mPin != null || mode == 0x83) {
-
- byte[] pin;
- if (mode == 0x83) {
- pin = mAdminPin.toStringUnsafe().getBytes();
- } else {
- pin = mPin.toStringUnsafe().getBytes();
- }
-
- // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
- // See specification, page 51
- String accepted = "9000";
- String response = nfcTryPin(mode, pin); // login
- if (!response.equals(accepted)) {
- throw new CardException("Bad PIN!", parseCardStatus(response));
- }
-
- if (mode == 0x81) {
- mPw1ValidatedForSignature = true;
- } else if (mode == 0x82) {
- mPw1ValidatedForDecrypt = true;
- } else if (mode == 0x83) {
- mPw3Validated = true;
- }
- }
+ public boolean isSecurityTokenConnected() {
+ return mSecurityTokenHelper.isConnected();
}
- /**
- * Resets security token, which deletes all keys and data objects.
- * This works by entering a wrong PIN and then Admin PIN 4 times respectively.
- * Afterwards, the token is reactivated.
- */
- public void nfcReset() throws IOException {
- String accepted = "9000";
-
- // try wrong PIN 4 times until counter goes to C0
- byte[] pin = "XXXXXX".getBytes();
- for (int i = 0; i <= 4; i++) {
- String response = nfcTryPin(0x81, pin);
- if (response.equals(accepted)) { // Should NOT accept!
- throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response));
- }
- }
-
- // try wrong Admin PIN 4 times until counter goes to C0
- byte[] adminPin = "XXXXXXXX".getBytes();
- for (int i = 0; i <= 4; i++) {
- String response = nfcTryPin(0x83, adminPin);
- if (response.equals(accepted)) { // Should NOT accept!
- throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response));
- }
- }
-
- // reactivate token!
- String reactivate1 = "00" + "e6" + "00" + "00";
- String reactivate2 = "00" + "44" + "00" + "00";
- String response1 = nfcCommunicate(reactivate1);
- String response2 = nfcCommunicate(reactivate2);
- if (!response1.equals(accepted) || !response2.equals(accepted)) {
- throw new CardException("Reactivating failed!", parseCardStatus(response1));
- }
-
- }
-
- private String nfcTryPin(int mode, byte[] pin) throws IOException {
- // Command APDU for VERIFY command (page 32)
- String login =
- "00" // CLA
- + "20" // INS
- + "00" // P1
- + String.format("%02x", mode) // P2
- + String.format("%02x", pin.length) // Lc
- + Hex.toHexString(pin);
-
- return nfcCommunicate(login);
- }
-
- /** Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for
- * conformance to the token's requirements for key length.
- *
- * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83.
- * @param newPin The new PW1 or PW3.
- */
- public void nfcModifyPin(int pw, byte[] newPin) throws IOException {
- final int MAX_PW1_LENGTH_INDEX = 1;
- final int MAX_PW3_LENGTH_INDEX = 3;
-
- byte[] pwStatusBytes = nfcGetPwStatusBytes();
-
- if (pw == 0x81) {
- if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) {
- throw new IOException("Invalid PIN length");
- }
- } else if (pw == 0x83) {
- if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) {
- throw new IOException("Invalid PIN length");
- }
- } else {
- throw new IOException("Invalid PW index for modify PIN operation");
- }
-
- byte[] pin;
- if (pw == 0x83) {
- pin = mAdminPin.toStringUnsafe().getBytes();
- } else {
- pin = mPin.toStringUnsafe().getBytes();
- }
-
- // Command APDU for CHANGE REFERENCE DATA command (page 32)
- String changeReferenceDataApdu = "00" // CLA
- + "24" // INS
- + "00" // P1
- + String.format("%02x", pw) // P2
- + String.format("%02x", pin.length + newPin.length) // Lc
- + getHex(pin)
- + getHex(newPin);
- String response = nfcCommunicate(changeReferenceDataApdu); // change PIN
- if (!response.equals("9000")) {
- throw new CardException("Failed to change PIN", parseCardStatus(response));
- }
- }
-
- /**
- * Stores a data object on the token. Automatically validates the proper PIN for the operation.
- * Supported for all data objects < 255 bytes in length. Only the cardholder certificate
- * (0x7F21) can exceed this length.
- *
- * @param dataObject The data object to be stored.
- * @param data The data to store in the object
- */
- public void nfcPutData(int dataObject, byte[] data) throws IOException {
- if (data.length > 254) {
- throw new IOException("Cannot PUT DATA with length > 254");
- }
- if (dataObject == 0x0101 || dataObject == 0x0103) {
- if (!mPw1ValidatedForDecrypt) {
- nfcVerifyPin(0x82); // (Verify PW1 for non-signing operations)
- }
- } else if (!mPw3Validated) {
- nfcVerifyPin(0x83); // (Verify PW3)
- }
-
- String putDataApdu = "00" // CLA
- + "DA" // INS
- + String.format("%02x", (dataObject & 0xFF00) >> 8) // P1
- + String.format("%02x", dataObject & 0xFF) // P2
- + String.format("%02x", data.length) // Lc
- + getHex(data);
-
- String response = nfcCommunicate(putDataApdu); // put data
- if (!response.equals("9000")) {
- throw new CardException("Failed to put data.", parseCardStatus(response));
- }
- }
-
- /**
- * Puts a key on the token in the given slot.
- *
- * @param slot The slot on the token where the key should be stored:
- * 0xB6: Signature Key
- * 0xB8: Decipherment Key
- * 0xA4: Authentication Key
- */
- public void nfcPutKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase)
- throws IOException {
- if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) {
- throw new IOException("Invalid key slot");
- }
-
- RSAPrivateCrtKey crtSecretKey;
- try {
- secretKey.unlock(passphrase);
- crtSecretKey = secretKey.getCrtSecretKey();
- } catch (PgpGeneralException e) {
- throw new IOException(e.getMessage());
- }
-
- // Shouldn't happen; the UI should block the user from getting an incompatible key this far.
- if (crtSecretKey.getModulus().bitLength() > 2048) {
- throw new IOException("Key too large to export to Security Token.");
- }
-
- // Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
- if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
- throw new IOException("Invalid public exponent for smart Security Token.");
- }
-
- if (!mPw3Validated) {
- nfcVerifyPin(0x83); // (Verify PW3 with mode 83)
- }
-
- byte[] header= Hex.decode(
- "4D82" + "03A2" // Extended header list 4D82, length of 930 bytes. (page 23)
- + String.format("%02x", slot) + "00" // CRT to indicate targeted key, no length
- + "7F48" + "15" // Private key template 0x7F48, length 21 (decimal, 0x15 hex)
- + "9103" // Public modulus, length 3
- + "928180" // Prime P, length 128
- + "938180" // Prime Q, length 128
- + "948180" // Coefficient (1/q mod p), length 128
- + "958180" // Prime exponent P (d mod (p - 1)), length 128
- + "968180" // Prime exponent Q (d mod (1 - 1)), length 128
- + "97820100" // Modulus, length 256, last item in private key template
- + "5F48" + "820383");// DO 5F48; 899 bytes of concatenated key data will follow
- byte[] dataToSend = new byte[934];
- byte[] currentKeyObject;
- int offset = 0;
-
- System.arraycopy(header, 0, dataToSend, offset, header.length);
- offset += header.length;
- currentKeyObject = crtSecretKey.getPublicExponent().toByteArray();
- System.arraycopy(currentKeyObject, 0, dataToSend, offset, 3);
- offset += 3;
- // NOTE: For a 2048-bit key, these lengths are fixed. However, bigint includes a leading 0
- // in the array to represent sign, so we take care to set the offset to 1 if necessary.
- currentKeyObject = crtSecretKey.getPrimeP().toByteArray();
- System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
- Arrays.fill(currentKeyObject, (byte)0);
- offset += 128;
- currentKeyObject = crtSecretKey.getPrimeQ().toByteArray();
- System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
- Arrays.fill(currentKeyObject, (byte)0);
- offset += 128;
- currentKeyObject = crtSecretKey.getCrtCoefficient().toByteArray();
- System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
- Arrays.fill(currentKeyObject, (byte)0);
- offset += 128;
- currentKeyObject = crtSecretKey.getPrimeExponentP().toByteArray();
- System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
- Arrays.fill(currentKeyObject, (byte)0);
- offset += 128;
- currentKeyObject = crtSecretKey.getPrimeExponentQ().toByteArray();
- System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
- Arrays.fill(currentKeyObject, (byte)0);
- offset += 128;
- currentKeyObject = crtSecretKey.getModulus().toByteArray();
- System.arraycopy(currentKeyObject, currentKeyObject.length - 256, dataToSend, offset, 256);
-
- String putKeyCommand = "10DB3FFF";
- String lastPutKeyCommand = "00DB3FFF";
-
- // Now we're ready to communicate with the token.
- offset = 0;
- String response;
- while(offset < dataToSend.length) {
- int dataRemaining = dataToSend.length - offset;
- if (dataRemaining > 254) {
- response = nfcCommunicate(
- putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254)
- );
- offset += 254;
- } else {
- int length = dataToSend.length - offset;
- response = nfcCommunicate(
- lastPutKeyCommand + String.format("%02x", length)
- + Hex.toHexString(dataToSend, offset, length));
- offset += length;
- }
-
- if (!response.endsWith("9000")) {
- throw new CardException("Key export to Security Token failed", parseCardStatus(response));
- }
- }
-
- // Clear array with secret data before we return.
- Arrays.fill(dataToSend, (byte) 0);
- }
-
- /**
- * Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key),
- * this command also has the effect of resetting the digital signature counter.
- * NOTE: This does not set the key fingerprint data object! After calling this command, you
- * must construct a public key packet using the returned public key data objects, compute the
- * key fingerprint, and store it on the card using: nfcPutData(0xC8, key.getFingerprint())
- *
- * @param slot The slot on the card where the key should be generated:
- * 0xB6: Signature Key
- * 0xB8: Decipherment Key
- * 0xA4: Authentication Key
- * @return the public key data objects, in TLV format. For RSA this will be the public modulus
- * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required.
- */
- public byte[] nfcGenerateKey(int slot) throws IOException {
- if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) {
- throw new IOException("Invalid key slot");
- }
-
- if (!mPw3Validated) {
- nfcVerifyPin(0x83); // (Verify PW3 with mode 83)
- }
-
- String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000";
- String getResponseApdu = "00C00000";
-
- String first = nfcCommunicate(generateKeyApdu);
- String second = nfcCommunicate(getResponseApdu);
-
- if (!second.endsWith("9000")) {
- throw new IOException("On-card key generation failed");
- }
-
- String publicKeyData = getDataField(first) + getDataField(second);
-
- Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData);
-
- return Hex.decode(publicKeyData);
- }
-
- /**
- * Transceive data via NFC encoded as Hex
- */
- public String nfcCommunicate(String apdu) throws IOException {
- return getHex(mIsoCard.transceive(Hex.decode(apdu)));
- }
-
- /**
- * Parses out the status word from a JavaCard response string.
- *
- * @param response A hex string with the response from the token
- * @return A short indicating the SW1/SW2, or 0 if a status could not be determined.
- */
- short parseCardStatus(String response) {
- if (response.length() < 4) {
- return 0; // invalid input
- }
-
- try {
- return Short.parseShort(response.substring(response.length() - 4), 16);
- } catch (NumberFormatException e) {
- return 0;
- }
- }
-
- public String getHolderName(String name) {
- try {
- String slength;
- int ilength;
- name = name.substring(6);
- slength = name.substring(0, 2);
- ilength = Integer.parseInt(slength, 16) * 2;
- name = name.substring(2, ilength + 2);
- name = (new String(Hex.decode(name))).replace('<', ' ');
- return name;
- } catch (IndexOutOfBoundsException e) {
- // try-catch for https://github.com/FluffyKaon/OpenPGP-Card
- // Note: This should not happen, but happens with
- // https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now!
-
- Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e);
- return "";
- }
- }
-
- private String getDataField(String output) {
- return output.substring(0, output.length() - 4);
- }
-
- public static String getHex(byte[] raw) {
- return new String(Hex.encode(raw));
- }
-
- public class IsoDepNotSupportedException extends IOException {
+ public static class IsoDepNotSupportedException extends IOException {
public IsoDepNotSupportedException(String detailMessage) {
super(detailMessage);
@@ -1036,38 +426,9 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
}
- public class CardException extends IOException {
- private short mResponseCode;
-
- public CardException(String detailMessage, short responseCode) {
- super(detailMessage);
- mResponseCode = responseCode;
- }
-
- public short getResponseCode() {
- return mResponseCode;
- }
-
- }
-
- private boolean isFidesmoToken() {
- if (isNfcConnected()) { // Check if we can still talk to the card
- try {
- // By trying to select any apps that have the Fidesmo AID prefix we can
- // see if it is a Fidesmo device or not
- byte[] mSelectResponse = mIsoCard.transceive(Apdu.select(FIDESMO_APPS_AID_PREFIX));
- // Compare the status returned by our select with the OK status code
- return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU);
- } catch (IOException e) {
- Log.e(Constants.TAG, "Card communication failed!", e);
- }
- }
- return false;
- }
-
/**
* Ask user if she wants to install PGP onto her Fidesmo token
- */
+ */
private void promptFidesmoPgpInstall() {
FidesmoPgpInstallDialog fidesmoPgpInstallDialog = new FidesmoPgpInstallDialog();
fidesmoPgpInstallDialog.show(getSupportFragmentManager(), "fidesmoPgpInstallDialog");
@@ -1084,6 +445,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
/**
* Use the package manager to detect if an application is installed on the phone
+ *
* @param uri an URI identifying the application's package
* @return 'true' if the app is installed
*/
@@ -1099,4 +461,28 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
}
return mAppInstalled;
}
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mUsbDispatcher.onStop();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mUsbDispatcher.onStart();
+ }
+
+ public SecurityTokenHelper getSecurityTokenHelper() {
+ return mSecurityTokenHelper;
+ }
+
+ /**
+ * Run Security Token routines if last used token is connected and supports
+ * persistent connections
+ */
+ public void checkDeviceConnection() {
+ mUsbDispatcher.rescanDevices();
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
index 451065d6b..ad15c8f68 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
@@ -130,9 +130,9 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
switch (requiredInput.mType) {
// always use CryptoOperationHelper.startActivityForResult!
- case NFC_MOVE_KEY_TO_CARD:
- case NFC_DECRYPT:
- case NFC_SIGN: {
+ case SECURITY_TOKEN_MOVE_KEY_TO_CARD:
+ case SECURITY_TOKEN_DECRYPT:
+ case SECURITY_TOKEN_SIGN: {
Intent intent = new Intent(activity, SecurityTokenOperationActivity.class);
intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java
index 9f5ffc358..0c0877bae 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java
@@ -35,7 +35,7 @@ public class EditSubkeyDialogFragment extends DialogFragment {
public static final int MESSAGE_CHANGE_EXPIRY = 1;
public static final int MESSAGE_REVOKE = 2;
public static final int MESSAGE_STRIP = 3;
- public static final int MESSAGE_MOVE_KEY_TO_CARD = 4;
+ public static final int MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN = 4;
public static final int SUBKEY_MENU_CHANGE_EXPIRY = 0;
public static final int SUBKEY_MENU_REVOKE_SUBKEY = 1;
public static final int SUBKEY_MENU_STRIP_SUBKEY = 2;
@@ -82,7 +82,7 @@ public class EditSubkeyDialogFragment extends DialogFragment {
showAlertDialog();
break;
case SUBKEY_MENU_MOVE_TO_SECURITY_TOKEN:
- sendMessageToHandler(MESSAGE_MOVE_KEY_TO_CARD, null);
+ sendMessageToHandler(MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN, null);
break;
default:
break;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java
index 5fcc3d58b..4dc0ebaa0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java
@@ -64,8 +64,8 @@ public class KeyFormattingUtils {
String algorithmStr;
switch (algorithm) {
- case PublicKeyAlgorithmTags.RSA_ENCRYPT:
case PublicKeyAlgorithmTags.RSA_GENERAL:
+ case PublicKeyAlgorithmTags.RSA_ENCRYPT:
case PublicKeyAlgorithmTags.RSA_SIGN: {
algorithmStr = "RSA";
break;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java
index 49b37692c..55d5aec0c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java
@@ -23,15 +23,11 @@ import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.AttributeSet;
-import android.util.Patterns;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
-import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.ContactHelper;
-import java.util.regex.Matcher;
-
public class EmailEditText extends AppCompatAutoCompleteTextView {
public EmailEditText(Context context) {
@@ -70,20 +66,7 @@ public class EmailEditText extends AppCompatAutoCompleteTextView {
@Override
public void afterTextChanged(Editable editable) {
- String email = editable.toString();
- if (email.length() > 0) {
- Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
- if (emailMatcher.matches()) {
- EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0,
- R.drawable.ic_stat_retyped_ok, 0);
- } else {
- EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0,
- R.drawable.ic_stat_retyped_bad, 0);
- }
- } else {
- // remove drawable if email is empty
- EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
- }
+
}
};
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java
index 78f9f431b..4074d391b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java
@@ -177,4 +177,22 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView<KeyItem>
mLoaderManager.restartLoader(0, args, this);
}
+ @Override
+ public boolean enoughToFilter() {
+ return true;
+ }
+
+ public void showAllKeys(){
+ Bundle args = new Bundle();
+ args.putString(ARG_QUERY, "");
+ mLoaderManager.restartLoader(0, args, this);
+ super.showDropDown();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ // increase width to include add button
+ this.setDropDownWidth(this.getRight());
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java
new file mode 100644
index 000000000..60fc84dba
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.securitytoken.UsbTransport;
+import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity;
+
+public class UsbConnectionDispatcher {
+ private Activity mActivity;
+
+ private OnDiscoveredUsbDeviceListener mListener;
+ private UsbManager mUsbManager;
+ /**
+ * Receives broadcast when a supported USB device get permission.
+ */
+ private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ switch (action) {
+ case UsbEventReceiverActivity.ACTION_USB_PERMISSION: {
+ android.hardware.usb.UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,
+ false);
+ if (permission) {
+ Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName());
+ mListener.usbDeviceDiscovered(usbDevice);
+ }
+ break;
+ }
+ }
+ }
+ };
+
+ public UsbConnectionDispatcher(final Activity activity, final OnDiscoveredUsbDeviceListener listener) {
+ this.mActivity = activity;
+ this.mListener = listener;
+ this.mUsbManager = (UsbManager) activity.getSystemService(Context.USB_SERVICE);
+ }
+
+ public void onStart() {
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION);
+
+ mActivity.registerReceiver(mUsbReceiver, intentFilter);
+ }
+
+ public void onStop() {
+ mActivity.unregisterReceiver(mUsbReceiver);
+ }
+
+ /**
+ * Rescans devices and triggers {@link OnDiscoveredUsbDeviceListener}
+ */
+ public void rescanDevices() {
+ // Note: we don't check devices VID/PID because
+ // we check for permisssion instead.
+ // We should have permission only for matching devices
+ for (UsbDevice device : mUsbManager.getDeviceList().values()) {
+ if (mUsbManager.hasPermission(device)) {
+ if (mListener != null) {
+ mListener.usbDeviceDiscovered(device);
+ }
+ break;
+ }
+ }
+ }
+
+ public interface OnDiscoveredUsbDeviceListener {
+ void usbDeviceDiscovered(UsbDevice usbDevice);
+ }
+}