diff options
21 files changed, 225 insertions, 191 deletions
diff --git a/.gitmodules b/.gitmodules index ca01e1ac6..919f7e1db 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,6 @@ [submodule "extern/minidns"] path = extern/minidns url = https://github.com/open-keychain/minidns.git +[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 f42787806..265411595 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -18,7 +18,7 @@ dependencies { compile project(':extern:SuperToasts:supertoasts') compile project(':extern:minidns') compile project(':extern:KeybaseLib:Lib') - + 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 33ab52bca..16b6173f0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -65,7 +65,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 5d765b663..a060092a3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java @@ -129,16 +129,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 2eb517697..ff82da07a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -1,5 +1,7 @@ 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; @@ -10,20 +12,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. @@ -42,6 +48,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; @@ -51,11 +62,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; } @@ -65,16 +88,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) { @@ -90,43 +153,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() @@ -144,7 +185,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(); } @@ -189,4 +231,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 e48fe5020..7d3e26000 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java @@ -62,8 +62,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 1ba028006..20bb1f97c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java @@ -95,7 +95,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 f0403e625..09c28e7c5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java @@ -26,9 +26,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; @@ -48,6 +48,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! @@ -66,11 +67,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 { @@ -96,10 +99,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 { @@ -118,11 +122,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; @@ -130,7 +136,7 @@ public class PgpSignEncrypt { this.mOutStream = outStream; } - public Builder setProgressable(Progressable progressable) { + public Builder setProgressable(Progressable progressable) { mProgressable = progressable; return this; } @@ -170,11 +176,6 @@ public class PgpSignEncrypt { return this; } - public Builder setSignatureForceV3(boolean signatureForceV3) { - mSignatureForceV3 = signatureForceV3; - return this; - } - public Builder setSignaturePassphrase(String signaturePassphrase) { mSignaturePassphrase = signaturePassphrase; return this; @@ -200,6 +201,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); } @@ -227,17 +234,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) @@ -255,16 +277,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) { @@ -276,7 +288,7 @@ public class PgpSignEncrypt { } try { signingKey = signingKeyRing.getSigningSubKey(); - } catch(PgpGeneralException e) { + } catch (PgpGeneralException e) { throw new NoSigningKeyException(); } @@ -287,10 +299,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); @@ -331,29 +352,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 */ @@ -367,11 +393,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(); @@ -389,11 +411,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; @@ -415,11 +433,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(); @@ -433,13 +447,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(); @@ -459,11 +468,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 @@ -475,11 +480,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(); @@ -490,10 +491,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); } } @@ -543,30 +545,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/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index 690317170..d29f19d67 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -16,9 +16,9 @@ import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel; import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType; +import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; @@ -27,11 +27,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.Iterator; -import java.util.List; import java.util.Set; import java.util.TreeSet; 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 c87e490be..7250a861d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -248,7 +248,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 c4ecfdec5..97d92dbf7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -38,6 +38,7 @@ import android.os.RemoteException; import android.support.v4.util.LongSparseArray; import android.support.v4.app.NotificationCompat; +import org.spongycastle.bcpg.S2K; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.Preferences; @@ -198,6 +199,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 dcacdbc9d..283b79b13 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java @@ -118,9 +118,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)); @@ -265,9 +262,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)); } @@ -391,18 +385,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 98f6da186..f4738f1da 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -121,7 +121,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/openkeychain-api-lib b/extern/openkeychain-api-lib -Subproject 02cb8ad24b780f3b97073f2526f83057c99d424 +Subproject 48941ca6ec58c4583cdcf9d647ad5174925e2f2 diff --git a/extern/openpgp-api-lib b/extern/openpgp-api-lib -Subproject e515a49027fc5de36b8977cf8b096afc9838a9b +Subproject 869ab96e6dcd4821fd5360248429e49dae6fbac diff --git a/extern/openpgp-card-nfc-lib b/extern/openpgp-card-nfc-lib new file mode 160000 +Subproject b293af0bd27739d4dcfe964819cf7eebddf1f2b diff --git a/extern/spongycastle b/extern/spongycastle -Subproject 9e4fb80c4f8efb8a0f8fd0c1cc1e74a421d1eb7 +Subproject a68ebd1ffc5af880903b2905c17e4f6ac0f1398 diff --git a/settings.gradle b/settings.gradle index 31ee13c8a..d568e09ce 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,3 +12,4 @@ include ':extern:spongycastle:prov' include ':extern:SuperToasts:supertoasts' include ':extern:minidns' include ':extern:KeybaseLib:Lib' +include ':extern:openpgp-card-nfc-lib:library' |