aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--OpenKeychain/build.gradle2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java121
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java162
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java58
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java18
-rw-r--r--OpenKeychain/src/main/res/values/strings.xml1
-rw-r--r--OpenKeychain/src/main/res/xml/adv_preferences.xml12
m---------extern/openpgp-card-nfc-lib0
-rw-r--r--settings.gradle1
17 files changed, 224 insertions, 188 deletions
diff --git a/.gitmodules b/.gitmodules
index 064be5619..6fa51e40c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -31,3 +31,6 @@
[submodule "extern/TokenAutoComplete"]
path = extern/TokenAutoComplete
url = https://github.com/open-keychain/TokenAutoComplete
+[submodule "extern/openpgp-card-nfc-lib"]
+ path = extern/openpgp-card-nfc-lib
+ url = https://github.com/open-keychain/openpgp-card-nfc-lib.git \ No newline at end of file
diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle
index 26c49ce18..32ae8ceb0 100644
--- a/OpenKeychain/build.gradle
+++ b/OpenKeychain/build.gradle
@@ -19,7 +19,7 @@ dependencies {
compile project(':extern:minidns')
compile project(':extern:KeybaseLib:Lib')
compile project(':extern:TokenAutoComplete:library')
-
+ compile project(':extern:openpgp-card-nfc-lib:library')
}
android {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
index 05676c5d0..7d1af704d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
@@ -64,7 +64,6 @@ public final class Constants {
public static final String DEFAULT_FILE_COMPRESSION = "defaultFileCompression";
public static final String PASSPHRASE_CACHE_TTL = "passphraseCacheTtl";
public static final String LANGUAGE = "language";
- public static final String FORCE_V3_SIGNATURES = "forceV3Signatures";
public static final String KEY_SERVERS = "keyServers";
public static final String KEY_SERVERS_DEFAULT_VERSION = "keyServersDefaultVersion";
public static final String CONCEAL_PGP_APPLICATION = "concealPgpApplication";
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java
index 00f6b6715..491709354 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java
@@ -130,16 +130,6 @@ public class Preferences {
editor.commit();
}
- public boolean getForceV3Signatures() {
- return mSharedPreferences.getBoolean(Constants.Pref.FORCE_V3_SIGNATURES, false);
- }
-
- public void setForceV3Signatures(boolean value) {
- SharedPreferences.Editor editor = mSharedPreferences.edit();
- editor.putBoolean(Constants.Pref.FORCE_V3_SIGNATURES, value);
- editor.commit();
- }
-
public boolean isFirstTime() {
return mSharedPreferences.getBoolean(Constants.Pref.FIRST_TIME, true);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
index af058b618..e39924f7e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
@@ -17,6 +17,8 @@
package org.sufficientlysecure.keychain.pgp;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.S2K;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
@@ -27,20 +29,24 @@ import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.spongycastle.openpgp.PGPUtil;
-import org.spongycastle.openpgp.PGPV3SignatureGenerator;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
+import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
import org.sufficientlysecure.keychain.util.IterableIterator;
+import org.sufficientlysecure.keychain.util.Log;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
+import java.util.Date;
+import java.util.LinkedList;
import java.util.List;
/** Wrapper for a PGPSecretKey.
@@ -59,6 +65,11 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
private final PGPSecretKey mSecretKey;
private PGPPrivateKey mPrivateKey = null;
+ private int mPrivateKeyState = PRIVATE_KEY_STATE_LOCKED;
+ private static int PRIVATE_KEY_STATE_LOCKED = 0;
+ private static int PRIVATE_KEY_STATE_UNLOCKED = 1;
+ private static int PRIVATE_KEY_STATE_DIVERT_TO_CARD = 2;
+
CanonicalizedSecretKey(CanonicalizedSecretKeyRing ring, PGPSecretKey key) {
super(ring, key.getPublicKey());
mSecretKey = key;
@@ -68,11 +79,23 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
return (CanonicalizedSecretKeyRing) mRing;
}
+ /**
+ * Returns true on right passphrase
+ */
public boolean unlock(String passphrase) throws PgpGeneralException {
+ // handle keys on OpenPGP cards like they were unlocked
+ if (mSecretKey.getS2K().getType() == S2K.GNU_DUMMY_S2K
+ && mSecretKey.getS2K().getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
+ mPrivateKeyState = PRIVATE_KEY_STATE_DIVERT_TO_CARD;
+ return true;
+ }
+
+ // try to extract keys using the passphrase
try {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
mPrivateKey = mSecretKey.extractPrivateKey(keyDecryptor);
+ mPrivateKeyState = PRIVATE_KEY_STATE_UNLOCKED;
} catch (PGPException e) {
return false;
}
@@ -82,16 +105,56 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
return true;
}
- public PGPSignatureGenerator getSignatureGenerator(int hashAlgo, boolean cleartext)
+ // TODO: just a hack currently
+ public LinkedList<Integer> getSupportedHashAlgorithms() {
+ LinkedList<Integer> supported = new LinkedList<Integer>();
+
+ if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
+ // TODO: only works with SHA256 ?!
+ supported.add(HashAlgorithmTags.SHA256);
+ } else {
+ supported.add(HashAlgorithmTags.MD5);
+ supported.add(HashAlgorithmTags.RIPEMD160);
+ supported.add(HashAlgorithmTags.SHA1);
+ supported.add(HashAlgorithmTags.SHA224);
+ supported.add(HashAlgorithmTags.SHA256);
+ supported.add(HashAlgorithmTags.SHA384);
+ supported.add(HashAlgorithmTags.SHA512); // preferred is latest
+ }
+
+ return supported;
+ }
+
+ public PGPSignatureGenerator getSignatureGenerator(int hashAlgo, boolean cleartext,
+ byte[] nfcSignedHash, Date nfcCreationTimestamp)
throws PgpGeneralException {
- if(mPrivateKey == null) {
+ if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
}
- // content signer based on signing key algorithm and chosen hash algorithm
- JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
- mSecretKey.getPublicKey().getAlgorithm(), hashAlgo)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ PGPContentSignerBuilder contentSignerBuilder;
+ if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
+ // to sign using nfc PgpSignEncrypt is executed two times.
+ // the first time it stops to return the PendingIntent for nfc connection and signing the hash
+ // the second time the signed hash is used.
+ // to get the same hash we cache the timestamp for the second round!
+ if (nfcCreationTimestamp == null) {
+ nfcCreationTimestamp = new Date();
+ }
+
+ // use synchronous "NFC based" SignerBuilder
+ contentSignerBuilder = new NfcSyncPGPContentSignerBuilder(
+ mSecretKey.getPublicKey().getAlgorithm(), hashAlgo,
+ mSecretKey.getKeyID(), nfcSignedHash, nfcCreationTimestamp)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+
+ Log.d(Constants.TAG, "mSecretKey.getKeyID() "+ PgpKeyHelper.convertKeyIdToHex(mSecretKey.getKeyID()));
+ } else {
+ // content signer based on signing key algorithm and chosen hash algorithm
+ contentSignerBuilder = new JcaPGPContentSignerBuilder(
+ mSecretKey.getPublicKey().getAlgorithm(), hashAlgo)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ }
int signatureType;
if (cleartext) {
@@ -107,43 +170,21 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback());
+ if (nfcCreationTimestamp != null) {
+ spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
+ Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp);
+ }
signatureGenerator.setHashedSubpackets(spGen.generate());
return signatureGenerator;
} catch(PGPException e) {
- throw new PgpGeneralException("Error initializing signature!", e);
- }
- }
-
- public PGPV3SignatureGenerator getV3SignatureGenerator(int hashAlgo, boolean cleartext)
- throws PgpGeneralException {
- if(mPrivateKey == null) {
- throw new PrivateKeyNotUnlockedException();
- }
-
- // content signer based on signing key algorithm and chosen hash algorithm
- JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
- mSecretKey.getPublicKey().getAlgorithm(), hashAlgo)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
-
- int signatureType;
- if (cleartext) {
- // for sign-only ascii text
- signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT;
- } else {
- signatureType = PGPSignature.BINARY_DOCUMENT;
- }
-
- try {
- PGPV3SignatureGenerator signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
- signatureV3Generator.init(signatureType, mPrivateKey);
- return signatureV3Generator;
- } catch(PGPException e) {
+ // TODO: simply throw PGPException!
throw new PgpGeneralException("Error initializing signature!", e);
}
}
public PublicKeyDataDecryptorFactory getDecryptorFactory() {
- if(mPrivateKey == null) {
+ // TODO: divert to card missing
+ if (mPrivateKeyState != PRIVATE_KEY_STATE_UNLOCKED) {
throw new PrivateKeyNotUnlockedException();
}
return new JcePublicKeyDataDecryptorFactoryBuilder()
@@ -161,7 +202,8 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException,
PGPException, SignatureException {
- if(mPrivateKey == null) {
+ // TODO: divert to card missing
+ if (mPrivateKeyState != PRIVATE_KEY_STATE_UNLOCKED) {
throw new PrivateKeyNotUnlockedException();
}
@@ -206,4 +248,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
return new UncachedSecretKey(mSecretKey);
}
+ // HACK
+ public PGPSecretKey getSecretKey() {
+ return mSecretKey;
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java
index 80889f70d..4f74a2336 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java
@@ -78,8 +78,8 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
// then, mark exactly the keys we have available
for (PGPSecretKey sub : new IterableIterator<PGPSecretKey>(getRing().getSecretKeys())) {
S2K s2k = sub.getS2K();
- // Set to 1, except if the encryption type is GNU_DUMMY_S2K
- if(s2k == null || s2k.getType() != S2K.GNU_DUMMY_S2K) {
+ // add key, except if the private key has been stripped (GNU extension)
+ if(s2k == null || (s2k.getProtectionMode() != S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY)) {
result.add(sub.getKeyID());
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
index 0c640538f..459b80be2 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
@@ -91,7 +91,7 @@ public class PgpKeyHelper {
}
/**
- * Converts fingerprint to hex (optional: with whitespaces after 4 characters)
+ * Converts fingerprint to hex
* <p/>
* Fingerprint is shown using lowercase characters. Studies have shown that humans can
* better differentiate between numbers and letters when letters are lowercase.
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java
index d8bf0d4d9..67eced699 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java
@@ -27,9 +27,9 @@ import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPLiteralData;
import org.spongycastle.openpgp.PGPLiteralDataGenerator;
import org.spongycastle.openpgp.PGPSignatureGenerator;
-import org.spongycastle.openpgp.PGPV3SignatureGenerator;
import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
@@ -49,6 +49,7 @@ import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.util.Arrays;
import java.util.Date;
+import java.util.LinkedList;
/**
* This class uses a Builder pattern!
@@ -67,11 +68,13 @@ public class PgpSignEncrypt {
private int mSymmetricEncryptionAlgorithm;
private long mSignatureMasterKeyId;
private int mSignatureHashAlgorithm;
- private boolean mSignatureForceV3;
private String mSignaturePassphrase;
private boolean mEncryptToSigner;
private boolean mCleartextInput;
+ private byte[] mNfcSignedHash = null;
+ private Date mNfcCreationTimestamp = null;
+
private static byte[] NEW_LINE;
static {
@@ -97,10 +100,11 @@ public class PgpSignEncrypt {
this.mSymmetricEncryptionAlgorithm = builder.mSymmetricEncryptionAlgorithm;
this.mSignatureMasterKeyId = builder.mSignatureMasterKeyId;
this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm;
- this.mSignatureForceV3 = builder.mSignatureForceV3;
this.mSignaturePassphrase = builder.mSignaturePassphrase;
this.mEncryptToSigner = builder.mEncryptToSigner;
this.mCleartextInput = builder.mCleartextInput;
+ this.mNfcSignedHash = builder.mNfcSignedHash;
+ this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp;
}
public static class Builder {
@@ -119,11 +123,13 @@ public class PgpSignEncrypt {
private int mSymmetricEncryptionAlgorithm = 0;
private long mSignatureMasterKeyId = Constants.key.none;
private int mSignatureHashAlgorithm = 0;
- private boolean mSignatureForceV3 = false;
private String mSignaturePassphrase = null;
private boolean mEncryptToSigner = false;
private boolean mCleartextInput = false;
+ private byte[] mNfcSignedHash = null;
+ private Date mNfcCreationTimestamp = null;
+
public Builder(ProviderHelper providerHelper, String versionHeader, InputData data, OutputStream outStream) {
this.mProviderHelper = providerHelper;
this.mVersionHeader = versionHeader;
@@ -131,7 +137,7 @@ public class PgpSignEncrypt {
this.mOutStream = outStream;
}
- public Builder setProgressable(Progressable progressable) {
+ public Builder setProgressable(Progressable progressable) {
mProgressable = progressable;
return this;
}
@@ -171,11 +177,6 @@ public class PgpSignEncrypt {
return this;
}
- public Builder setSignatureForceV3(boolean signatureForceV3) {
- mSignatureForceV3 = signatureForceV3;
- return this;
- }
-
public Builder setSignaturePassphrase(String signaturePassphrase) {
mSignaturePassphrase = signaturePassphrase;
return this;
@@ -201,6 +202,12 @@ public class PgpSignEncrypt {
return this;
}
+ public Builder setNfcState(byte[] signedHash, Date creationTimestamp) {
+ mNfcSignedHash = signedHash;
+ mNfcCreationTimestamp = creationTimestamp;
+ return this;
+ }
+
public PgpSignEncrypt build() {
return new PgpSignEncrypt(this);
}
@@ -228,17 +235,32 @@ public class PgpSignEncrypt {
}
}
+ public static class WrongPassphraseException extends Exception {
+ public WrongPassphraseException() {
+ }
+ }
+
public static class NoSigningKeyException extends Exception {
public NoSigningKeyException() {
}
}
+ public static class NeedNfcDataException extends Exception {
+ public byte[] mHashToSign;
+ public Date mCreationTimestamp;
+
+ public NeedNfcDataException(byte[] hashToSign, Date creationTimestamp) {
+ mHashToSign = hashToSign;
+ mCreationTimestamp = creationTimestamp;
+ }
+ }
+
/**
* Signs and/or encrypts data based on parameters of class
*/
public void execute()
throws IOException, PGPException, NoSuchProviderException,
- NoSuchAlgorithmException, SignatureException, KeyExtractionException, NoSigningKeyException, NoPassphraseException {
+ NoSuchAlgorithmException, SignatureException, KeyExtractionException, NoSigningKeyException, NoPassphraseException, NeedNfcDataException, WrongPassphraseException {
boolean enableSignature = mSignatureMasterKeyId != Constants.key.none;
boolean enableEncryption = ((mEncryptionMasterKeyIds != null && mEncryptionMasterKeyIds.length > 0)
@@ -256,16 +278,6 @@ public class PgpSignEncrypt {
mEncryptionMasterKeyIds[mEncryptionMasterKeyIds.length - 1] = mSignatureMasterKeyId;
}
- ArmoredOutputStream armorOut = null;
- OutputStream out;
- if (mEnableAsciiArmorOutput) {
- armorOut = new ArmoredOutputStream(mOutStream);
- armorOut.setHeader("Version", mVersionHeader);
- out = armorOut;
- } else {
- out = mOutStream;
- }
-
/* Get keys for signature generation for later usage */
CanonicalizedSecretKey signingKey = null;
if (enableSignature) {
@@ -277,7 +289,7 @@ public class PgpSignEncrypt {
}
try {
signingKey = signingKeyRing.getSigningSubKey();
- } catch(PgpGeneralException e) {
+ } catch (PgpGeneralException e) {
throw new NoSigningKeyException();
}
@@ -288,10 +300,19 @@ public class PgpSignEncrypt {
updateProgress(R.string.progress_extracting_signature_key, 0, 100);
try {
- signingKey.unlock(mSignaturePassphrase);
+ if (!signingKey.unlock(mSignaturePassphrase)) {
+ throw new WrongPassphraseException();
+ }
} catch (PgpGeneralException e) {
throw new KeyExtractionException();
}
+
+ // check if hash algo is supported
+ LinkedList<Integer> supported = signingKey.getSupportedHashAlgorithms();
+ if (!supported.contains(mSignatureHashAlgorithm)) {
+ // get most preferred
+ mSignatureHashAlgorithm = supported.getLast();
+ }
}
updateProgress(R.string.progress_preparing_streams, 5, 100);
@@ -332,29 +353,34 @@ public class PgpSignEncrypt {
/* Initialize signature generator object for later usage */
PGPSignatureGenerator signatureGenerator = null;
- PGPV3SignatureGenerator signatureV3Generator = null;
if (enableSignature) {
updateProgress(R.string.progress_preparing_signature, 10, 100);
try {
boolean cleartext = mCleartextInput && mEnableAsciiArmorOutput && !enableEncryption;
- if (mSignatureForceV3) {
- signatureV3Generator = signingKey.getV3SignatureGenerator(
- mSignatureHashAlgorithm,cleartext);
- } else {
- signatureGenerator = signingKey.getSignatureGenerator(
- mSignatureHashAlgorithm, cleartext);
- }
+ signatureGenerator = signingKey.getSignatureGenerator(
+ mSignatureHashAlgorithm, cleartext, mNfcSignedHash, mNfcCreationTimestamp);
} catch (PgpGeneralException e) {
// TODO throw correct type of exception (which shouldn't be PGPException)
throw new KeyExtractionException();
}
}
+ ArmoredOutputStream armorOut = null;
+ OutputStream out;
+ if (mEnableAsciiArmorOutput) {
+ armorOut = new ArmoredOutputStream(mOutStream);
+ armorOut.setHeader("Version", mVersionHeader);
+ out = armorOut;
+ } else {
+ out = mOutStream;
+ }
+
PGPCompressedDataGenerator compressGen = null;
- OutputStream pOut;
+ OutputStream pOut = null;
OutputStream encryptionOut = null;
BCPGOutputStream bcpgOut;
+
if (enableEncryption) {
/* actual encryption */
@@ -368,11 +394,7 @@ public class PgpSignEncrypt {
}
if (enableSignature) {
- if (mSignatureForceV3) {
- signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut);
- } else {
- signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
- }
+ signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
}
PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
@@ -390,11 +412,7 @@ public class PgpSignEncrypt {
// update signature buffer if signature is requested
if (enableSignature) {
- if (mSignatureForceV3) {
- signatureV3Generator.update(buffer, 0, n);
- } else {
- signatureGenerator.update(buffer, 0, n);
- }
+ signatureGenerator.update(buffer, 0, n);
}
progress += n;
@@ -416,11 +434,7 @@ public class PgpSignEncrypt {
final BufferedReader reader = new BufferedReader(new InputStreamReader(in));
// update signature buffer with first line
- if (mSignatureForceV3) {
- processLineV3(reader.readLine(), armorOut, signatureV3Generator);
- } else {
- processLine(reader.readLine(), armorOut, signatureGenerator);
- }
+ processLine(reader.readLine(), armorOut, signatureGenerator);
while (true) {
String line = reader.readLine();
@@ -434,13 +448,8 @@ public class PgpSignEncrypt {
armorOut.write(NEW_LINE);
// update signature buffer with input line
- if (mSignatureForceV3) {
- signatureV3Generator.update(NEW_LINE);
- processLineV3(line, armorOut, signatureV3Generator);
- } else {
- signatureGenerator.update(NEW_LINE);
- processLine(line, armorOut, signatureGenerator);
- }
+ signatureGenerator.update(NEW_LINE);
+ processLine(line, armorOut, signatureGenerator);
}
armorOut.endClearText();
@@ -460,11 +469,7 @@ public class PgpSignEncrypt {
bcpgOut = new BCPGOutputStream(out);
}
- if (mSignatureForceV3) {
- signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut);
- } else {
- signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
- }
+ signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
// file name not needed, so empty string
@@ -476,11 +481,7 @@ public class PgpSignEncrypt {
while ((n = in.read(buffer)) > 0) {
pOut.write(buffer, 0, n);
- if (mSignatureForceV3) {
- signatureV3Generator.update(buffer, 0, n);
- } else {
- signatureGenerator.update(buffer, 0, n);
- }
+ signatureGenerator.update(buffer, 0, n);
}
literalGen.close();
@@ -491,10 +492,11 @@ public class PgpSignEncrypt {
if (enableSignature) {
updateProgress(R.string.progress_generating_signature, 95, 100);
- if (mSignatureForceV3) {
- signatureV3Generator.generate().encode(pOut);
- } else {
+ try {
signatureGenerator.generate().encode(pOut);
+ } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
+ // this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
+ throw new NeedNfcDataException(e.hashToSign, e.creationTimestamp);
}
}
@@ -544,30 +546,4 @@ public class PgpSignEncrypt {
pSignatureGenerator.update(data);
}
- private static void processLineV3(final String pLine, final ArmoredOutputStream pArmoredOutput,
- final PGPV3SignatureGenerator pSignatureGenerator)
- throws IOException, SignatureException {
-
- if (pLine == null) {
- return;
- }
-
- final char[] chars = pLine.toCharArray();
- int len = chars.length;
-
- while (len > 0) {
- if (!Character.isWhitespace(chars[len - 1])) {
- break;
- }
- len--;
- }
-
- final byte[] data = pLine.substring(0, len).getBytes("UTF-8");
-
- if (pArmoredOutput != null) {
- pArmoredOutput.write(data);
- }
- pSignatureGenerator.update(data);
- }
-
}
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 5ed95acb3..eae217e16 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
@@ -28,6 +28,7 @@ import org.openintents.openpgp.IOpenPgpService;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
+import org.openkeychain.nfc.NfcActivity;
import org.spongycastle.util.Arrays;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@@ -49,6 +50,7 @@ import org.sufficientlysecure.keychain.util.Log;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.Date;
import java.util.Set;
public class OpenPgpService extends RemoteService {
@@ -135,7 +137,26 @@ public class OpenPgpService extends RemoteService {
return result;
}
- private Intent getPassphraseBundleIntent(Intent data, long keyId) {
+ private Intent getNfcIntent(Intent data, byte[] hashToSign) {
+ // build PendingIntent for Yubikey NFC operations
+ Intent intent = new Intent(getBaseContext(), NfcActivity.class);
+ intent.setAction(NfcActivity.ACTION_SIGN_HASH);
+ intent.putExtra(NfcActivity.EXTRA_NFC_HASH_TO_SIGN, hashToSign);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ // pass params through to activity that it can be returned again later to repeat pgp operation
+ intent.putExtra(NfcActivity.EXTRA_DATA, data);
+ PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+
+ // return PendingIntent to be executed by client
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
+ return result;
+ }
+
+ private Intent getPassphraseIntent(Intent data, long keyId) {
// build PendingIntent for passphrase input
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.setAction(RemoteServiceActivity.ACTION_CACHE_PASSPHRASE);
@@ -167,10 +188,12 @@ public class OpenPgpService extends RemoteService {
}
if (passphrase == null) {
// get PendingIntent for passphrase input, add it to given params and return to client
- Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId());
- return passphraseBundle;
+ return getPassphraseIntent(data, accSettings.getKeyId());
}
+ byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
+ Date nfcCreationTimestamp = new Date(data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, 0));
+
// Get Input- and OutputStream from ParcelFileDescriptor
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
@@ -185,9 +208,9 @@ public class OpenPgpService extends RemoteService {
inputData, os);
builder.setEnableAsciiArmorOutput(asciiArmor)
.setSignatureHashAlgorithm(accSettings.getHashAlgorithm())
- .setSignatureForceV3(false)
.setSignatureMasterKeyId(accSettings.getKeyId())
- .setSignaturePassphrase(passphrase);
+ .setSignaturePassphrase(passphrase)
+ .setNfcState(nfcSignedHash, nfcCreationTimestamp);
// TODO: currently always assume cleartext input, no sign-only of binary currently!
builder.setCleartextInput(true);
@@ -200,8 +223,16 @@ public class OpenPgpService extends RemoteService {
throw new Exception(getString(R.string.error_could_not_extract_private_key));
} catch (PgpSignEncrypt.NoPassphraseException e) {
throw new Exception(getString(R.string.error_no_signature_passphrase));
+ } catch (PgpSignEncrypt.WrongPassphraseException e) {
+ throw new Exception(getString(R.string.error_wrong_passphrase));
} catch (PgpSignEncrypt.NoSigningKeyException e) {
throw new Exception(getString(R.string.error_no_signature_key));
+ } catch (PgpSignEncrypt.NeedNfcDataException e) {
+ // return PendingIntent to execute NFC activity
+ // pass through the signature creation timestamp to be used again on second execution
+ // of PgpSignEncrypt when we have the signed hash!
+ data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, e.mCreationTimestamp.getTime());
+ return getNfcIntent(data, e.mHashToSign);
}
} finally {
is.close();
@@ -212,6 +243,7 @@ public class OpenPgpService extends RemoteService {
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} catch (Exception e) {
+ Log.d(Constants.TAG, "signImpl", e);
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
@@ -282,13 +314,11 @@ public class OpenPgpService extends RemoteService {
}
if (passphrase == null) {
// get PendingIntent for passphrase input, add it to given params and return to client
- Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId());
- return passphraseBundle;
+ return getPassphraseIntent(data, accSettings.getKeyId());
}
// sign and encrypt
builder.setSignatureHashAlgorithm(accSettings.getHashAlgorithm())
- .setSignatureForceV3(false)
.setSignatureMasterKeyId(accSettings.getKeyId())
.setSignaturePassphrase(passphrase);
} else {
@@ -305,6 +335,8 @@ public class OpenPgpService extends RemoteService {
throw new Exception(getString(R.string.error_could_not_extract_private_key));
} catch (PgpSignEncrypt.NoPassphraseException e) {
throw new Exception(getString(R.string.error_no_signature_passphrase));
+ } catch (PgpSignEncrypt.WrongPassphraseException e) {
+ throw new Exception(getString(R.string.error_wrong_passphrase));
} catch (PgpSignEncrypt.NoSigningKeyException e) {
throw new Exception(getString(R.string.error_no_signature_key));
}
@@ -317,6 +349,7 @@ public class OpenPgpService extends RemoteService {
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} catch (Exception e) {
+ Log.d(Constants.TAG, "encryptAndSignImpl", e);
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
@@ -375,9 +408,7 @@ public class OpenPgpService extends RemoteService {
if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) {
// get PendingIntent for passphrase input, add it to given params and return to client
- Intent passphraseBundle =
- getPassphraseBundleIntent(data, decryptVerifyResult.getKeyIdPassphraseNeeded());
- return passphraseBundle;
+ return getPassphraseIntent(data, decryptVerifyResult.getKeyIdPassphraseNeeded());
} else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED ==
decryptVerifyResult.getStatus()) {
throw new PgpGeneralException("Decryption of symmetric content not supported by API!");
@@ -411,6 +442,7 @@ public class OpenPgpService extends RemoteService {
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} catch (Exception e) {
+ Log.d(Constants.TAG, "decryptAndVerifyImpl", e);
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
@@ -480,10 +512,8 @@ public class OpenPgpService extends RemoteService {
return result;
} else {
// get key ids based on given user ids
-
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
- Intent result = getKeyIdsFromEmails(data, userIds);
- return result;
+ return getKeyIdsFromEmails(data, userIds);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java
index 8df341f9e..b37405304 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java
@@ -17,9 +17,12 @@
package org.sufficientlysecure.keychain.remote.ui;
+import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
@@ -34,6 +37,8 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.util.Log;
+import java.util.List;
+
public class AppSettingsActivity extends ActionBarActivity {
private Uri mAppUri;
@@ -90,6 +95,7 @@ public class AppSettingsActivity extends ActionBarActivity {
return super.onOptionsItemSelected(item);
}
+ // disabled: breaks Yubikey NFC Foreground dispatching
private void startApp() {
Intent i;
PackageManager manager = getPackageManager();
@@ -97,6 +103,8 @@ public class AppSettingsActivity extends ActionBarActivity {
i = manager.getLaunchIntentForPackage(mAppSettings.getPackageName());
if (i == null)
throw new PackageManager.NameNotFoundException();
+ // start like the Android launcher would do
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
i.addCategory(Intent.CATEGORY_LAUNCHER);
startActivity(i);
} catch (PackageManager.NameNotFoundException e) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
index 30108d52d..a7115a53d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
@@ -254,7 +254,6 @@ public class KeychainIntentService extends IntentService
.setCompressionId(compressionId)
.setSymmetricEncryptionAlgorithm(
Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
- .setSignatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
.setEncryptionMasterKeyIds(encryptionKeyIds)
.setSymmetricPassphrase(symmetricPassphrase)
.setSignatureMasterKeyId(signatureKeyId)
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
index e95e737d5..e813ca26c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
@@ -37,6 +37,7 @@ import android.os.RemoteException;
import android.support.v4.app.NotificationCompat;
import android.support.v4.util.LongSparseArray;
+import org.spongycastle.bcpg.S2K;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.Preferences;
@@ -197,6 +198,13 @@ public class PassphraseCacheService extends Service {
return "";
}
+ // TODO: HACK
+ if (key.getSecretKey().getSecretKey().getS2K().getType() == S2K.GNU_DUMMY_S2K
+ && key.getSecretKey().getSecretKey().getS2K().getProtectionMode() == 2) {
+ // NFC!
+ return "123456";
+ }
+
// get cached passphrase
CachedPassphrase cachedPassphrase = mPassphraseCache.get(keyId);
if (cachedPassphrase == null) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java
index e0261730d..a6561dfad 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java
@@ -119,9 +119,6 @@ public class PreferencesActivity extends PreferenceActivity {
initializeAsciiArmor(
(CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR));
- initializeForceV3Signatures(
- (CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
-
initializeConcealPgpApplication(
(CheckBoxPreference) findPreference(Constants.Pref.CONCEAL_PGP_APPLICATION));
@@ -266,9 +263,6 @@ public class PreferencesActivity extends PreferenceActivity {
initializeAsciiArmor(
(CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR));
- initializeForceV3Signatures(
- (CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
-
initializeConcealPgpApplication(
(CheckBoxPreference) findPreference(Constants.Pref.CONCEAL_PGP_APPLICATION));
}
@@ -392,18 +386,6 @@ public class PreferencesActivity extends PreferenceActivity {
});
}
- private static void initializeForceV3Signatures(final CheckBoxPreference mForceV3Signatures) {
- mForceV3Signatures.setChecked(sPreferences.getForceV3Signatures());
- mForceV3Signatures
- .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- mForceV3Signatures.setChecked((Boolean) newValue);
- sPreferences.setForceV3Signatures((Boolean) newValue);
- return false;
- }
- });
- }
-
private static void initializeConcealPgpApplication(final CheckBoxPreference mConcealPgpApplication) {
mConcealPgpApplication.setChecked(sPreferences.getConcealPgpApplication());
mConcealPgpApplication.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml
index 09a88f4d9..952e315cf 100644
--- a/OpenKeychain/src/main/res/values/strings.xml
+++ b/OpenKeychain/src/main/res/values/strings.xml
@@ -102,7 +102,6 @@
<string name="label_passphrase_cache_ttl">Passphrase Cache</string>
<string name="label_message_compression">Message Compression</string>
<string name="label_file_compression">File Compression</string>
- <string name="label_force_v3_signature">Force old OpenPGPv3 Signatures</string>
<string name="label_keyservers">Keyservers</string>
<string name="label_key_id">Key ID</string>
<string name="label_creation">Creation</string>
diff --git a/OpenKeychain/src/main/res/xml/adv_preferences.xml b/OpenKeychain/src/main/res/xml/adv_preferences.xml
index a07ae06bb..0426ca298 100644
--- a/OpenKeychain/src/main/res/xml/adv_preferences.xml
+++ b/OpenKeychain/src/main/res/xml/adv_preferences.xml
@@ -16,7 +16,7 @@
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
- <PreferenceCategory android:title="@string/section_defaults" >
+ <PreferenceCategory android:title="@string/section_defaults">
<org.sufficientlysecure.keychain.ui.widget.IntegerListPreference
android:key="defaultEncryptionAlgorithm"
android:persistent="false"
@@ -33,22 +33,16 @@
android:key="defaultFileCompression"
android:persistent="false"
android:title="@string/label_file_compression" />
-
<CheckBoxPreference
android:key="defaultAsciiArmor"
android:persistent="false"
android:title="@string/label_ascii_armor" />
-
+ </PreferenceCategory>
+ <PreferenceCategory android:title="@string/section_advanced">
<CheckBoxPreference
android:key="concealPgpApplication"
android:persistent="false"
android:title="@string/label_conceal_pgp_application"
android:summary="@string/label_conceal_pgp_application_summary" />
</PreferenceCategory>
- <PreferenceCategory android:title="@string/section_advanced" >
- <CheckBoxPreference
- android:key="forceV3Signatures"
- android:persistent="false"
- android:title="@string/label_force_v3_signature"/>
- </PreferenceCategory>
</PreferenceScreen>
diff --git a/extern/openpgp-card-nfc-lib b/extern/openpgp-card-nfc-lib
new file mode 160000
+Subproject 1531e38c30a9c3e072e302c1931fef2999fe08d
diff --git a/settings.gradle b/settings.gradle
index b9569509e..7b9bb577a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -13,3 +13,4 @@ include ':extern:SuperToasts:supertoasts'
include ':extern:minidns'
include ':extern:KeybaseLib:Lib'
include ':extern:TokenAutoComplete:library'
+include ':extern:openpgp-card-nfc-lib:library'