diff options
author | Vincent Breitmoser <valodim@mugenguild.com> | 2015-03-02 14:39:28 +0100 |
---|---|---|
committer | Vincent Breitmoser <valodim@mugenguild.com> | 2015-03-02 14:39:28 +0100 |
commit | 2f83291920e024b0f8038fe0caa747051b41cf1c (patch) | |
tree | 780fc0975e56b3dbd8a1eb4cb2988ac3e2e23ea1 /OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp | |
parent | aa22a0defcf94d5f734d3a00288f5c8d916d2e57 (diff) | |
parent | 4e29d027af05fc574dc5398d2fb3afcdf3defc70 (diff) | |
download | open-keychain-2f83291920e024b0f8038fe0caa747051b41cf1c.tar.gz open-keychain-2f83291920e024b0f8038fe0caa747051b41cf1c.tar.bz2 open-keychain-2f83291920e024b0f8038fe0caa747051b41cf1c.zip |
NON-WORKING Merge branch 'development' into linked-identities
Conflicts:
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java
OpenKeychain/src/main/res/layout/view_key_main_fragment.xml
OpenKeychain/src/main/res/values/strings.xml
extern/spongycastle
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp')
16 files changed, 1202 insertions, 479 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java index db0a9b137..4adacaf23 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.openpgp.PGPKeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.util.IterableIterator; @@ -80,9 +79,8 @@ public abstract class CanonicalizedKeyRing extends KeyRing { public boolean isExpired() { // Is the master key expired? - Date creationDate = getRing().getPublicKey().getCreationTime(); - Date expiryDate = getRing().getPublicKey().getValidSeconds() > 0 - ? new Date(creationDate.getTime() + getRing().getPublicKey().getValidSeconds() * 1000) : null; + Date creationDate = getPublicKey().getCreationTime(); + Date expiryDate = getPublicKey().getExpiryTime(); Date now = new Date(); return creationDate.after(now) || (expiryDate != null && expiryDate.before(now)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java index 3539a4ceb..303070333 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java @@ -20,8 +20,16 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.IterableIterator; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Iterator; /** Wrapper for a PGPPublicKey. * @@ -53,7 +61,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey { public boolean canSign() { // if key flags subpacket is available, honor it! - if (getKeyUsage() != null) { + if (getKeyUsage() != 0) { return (getKeyUsage() & KeyFlags.SIGN_DATA) != 0; } @@ -66,7 +74,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey { public boolean canCertify() { // if key flags subpacket is available, honor it! - if (getKeyUsage() != null) { + if (getKeyUsage() != 0) { return (getKeyUsage() & KeyFlags.CERTIFY_OTHER) != 0; } @@ -79,7 +87,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey { public boolean canEncrypt() { // if key flags subpacket is available, honor it! - if (getKeyUsage() != null) { + if (getKeyUsage() != 0) { return (getKeyUsage() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0; } @@ -93,13 +101,79 @@ public class CanonicalizedPublicKey extends UncachedPublicKey { public boolean canAuthenticate() { // if key flags subpacket is available, honor it! - if (getKeyUsage() != null) { + if (getKeyUsage() != 0) { return (getKeyUsage() & KeyFlags.AUTHENTICATION) != 0; } return false; } + public boolean isRevoked() { + return mPublicKey.getSignaturesOfType(isMasterKey() + ? PGPSignature.KEY_REVOCATION + : PGPSignature.SUBKEY_REVOCATION).hasNext(); + } + + public boolean isExpired () { + Date expiry = getExpiryTime(); + return expiry != null && expiry.before(new Date()); + } + + public long getValidSeconds() { + + long seconds; + + // the getValidSeconds method is unreliable for master keys. we need to iterate all + // user ids, then use the most recent certification from a non-revoked user id + if (isMasterKey()) { + Date latestCreation = null; + seconds = 0; + + for (byte[] rawUserId : getUnorderedRawUserIds()) { + Iterator<WrappedSignature> sigs = getSignaturesForRawId(rawUserId); + + // there is always a certification, so this call is safe + WrappedSignature sig = sigs.next(); + + // we know a user id has at most two sigs: one certification, one revocation. + // if the sig is a revocation, or there is another sig (which is a revocation), + // the data in this uid is not relevant + if (sig.isRevocation() || sigs.hasNext()) { + continue; + } + + // this is our revocation, UNLESS there is a newer certificate! + if (latestCreation == null || latestCreation.before(sig.getCreationTime())) { + latestCreation = sig.getCreationTime(); + seconds = sig.getKeyExpirySeconds(); + } + } + } else { + seconds = mPublicKey.getValidSeconds(); + } + + return seconds; + } + + public Date getExpiryTime() { + long seconds = getValidSeconds(); + + if (seconds > Integer.MAX_VALUE) { + Log.e(Constants.TAG, "error, expiry time too large"); + return null; + } + if (seconds == 0) { + // no expiry + return null; + } + Date creationDate = getCreationTime(); + Calendar calendar = GregorianCalendar.getInstance(); + calendar.setTime(creationDate); + calendar.add(Calendar.SECOND, (int) seconds); + + return calendar.getTime(); + } + /** Same method as superclass, but we make it public. */ public Integer getKeyUsage() { return super.getKeyUsage(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java index 85ef3eaa4..c2506685d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java @@ -18,9 +18,11 @@ package org.sufficientlysecure.keychain.pgp; +import org.spongycastle.bcpg.S2K; import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.util.IterableIterator; @@ -76,7 +78,7 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() { @SuppressWarnings("unchecked") final Iterator<PGPPublicKey> it = getRing().getPublicKeys(); - return new IterableIterator<CanonicalizedPublicKey>(new Iterator<CanonicalizedPublicKey>() { + return new IterableIterator<>(new Iterator<CanonicalizedPublicKey>() { @Override public boolean hasNext() { return it.hasNext(); @@ -94,4 +96,13 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { }); } + /** Create a dummy secret ring from this key */ + public UncachedKeyRing createDummySecretRing () { + + PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), + S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY); + return new UncachedKeyRing(secRing); + + } + }
\ No newline at end of file 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 6ccadac2e..40f2f48ad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -182,7 +182,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { * @return */ public LinkedList<Integer> getSupportedHashAlgorithms() { - LinkedList<Integer> supported = new LinkedList<Integer>(); + LinkedList<Integer> supported = new LinkedList<>(); if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) { // No support for MD5 @@ -247,7 +247,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { int signatureType; if (cleartext) { - // for sign-only ascii text + // for sign-only ascii text (cleartext signature) signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT; } else { signatureType = PGPSignature.BINARY_DOCUMENT; @@ -262,11 +262,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { spGen.setSignatureCreationTime(false, nfcCreationTimestamp); signatureGenerator.setHashedSubpackets(spGen.generate()); return signatureGenerator; - } catch (PgpKeyNotFoundException e) { + } catch (PgpKeyNotFoundException | PGPException e) { // TODO: simply throw PGPException! throw new PgpGeneralException("Error initializing signature!", e); - } catch (PGPException e) { - throw new PgpGeneralException("Error initializing signature!", e); } } 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 eb589c3f9..b5f6a5b09 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java @@ -18,27 +18,19 @@ package org.sufficientlysecure.keychain.pgp; -import org.spongycastle.bcpg.S2K; -import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPObjectFactory; -import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSignature; -import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing { @@ -94,7 +86,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing { public IterableIterator<CanonicalizedSecretKey> secretKeyIterator() { final Iterator<PGPSecretKey> it = mRing.getSecretKeys(); - return new IterableIterator<CanonicalizedSecretKey>(new Iterator<CanonicalizedSecretKey>() { + return new IterableIterator<>(new Iterator<CanonicalizedSecretKey>() { @Override public boolean hasNext() { return it.hasNext(); @@ -114,7 +106,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing { public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() { final Iterator<PGPPublicKey> it = getRing().getPublicKeys(); - return new IterableIterator<CanonicalizedPublicKey>(new Iterator<CanonicalizedPublicKey>() { + return new IterableIterator<>(new Iterator<CanonicalizedPublicKey>() { @Override public boolean hasNext() { return it.hasNext(); @@ -133,7 +125,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing { } public HashMap<String,String> getLocalNotationData() { - HashMap<String,String> result = new HashMap<String,String>(); + HashMap<String,String> result = new HashMap<>(); Iterator<PGPSignature> it = getRing().getPublicKey().getKeySignatures(); while (it.hasNext()) { WrappedSignature sig = new WrappedSignature(it.next()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index aa324c7ed..46defebf7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.pgp; import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.util.Log; @@ -33,7 +32,7 @@ public class OpenPgpSignatureResultBuilder { // OpenPgpSignatureResult private boolean mSignatureOnly = false; private String mPrimaryUserId; - private ArrayList<String> mUserIds = new ArrayList<String>(); + private ArrayList<String> mUserIds = new ArrayList<>(); private long mKeyId; // builder @@ -105,8 +104,8 @@ public class OpenPgpSignatureResultBuilder { setUserIds(signingRing.getUnorderedUserIds()); // either master key is expired/revoked or this specific subkey is expired/revoked - setKeyExpired(signingRing.isExpired() || signingKey.isExpired()); - setKeyRevoked(signingRing.isRevoked() || signingKey.isRevoked()); + setKeyExpired(signingRing.isExpired() || signingKey.isMaybeExpired()); + setKeyRevoked(signingRing.isRevoked() || signingKey.isMaybeRevoked()); } public OpenPgpSignatureResult build() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index b58df085f..2ba0b6231 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -22,13 +22,13 @@ import android.content.Context; import android.webkit.MimeTypeMap; import org.openintents.openpgp.OpenPgpMetadata; +import org.openintents.openpgp.OpenPgpSignatureResult; import org.spongycastle.bcpg.ArmoredInputStream; import org.spongycastle.openpgp.PGPCompressedData; import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedDataList; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPLiteralData; -import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPOnePassSignature; import org.spongycastle.openpgp.PGPOnePassSignatureList; import org.spongycastle.openpgp.PGPPBEEncryptedData; @@ -36,10 +36,10 @@ import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureList; import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory; import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; @@ -48,11 +48,15 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.BaseOperation; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; @@ -83,6 +87,9 @@ public class PgpDecryptVerify extends BaseOperation { private Set<Long> mAllowedKeyIds; private boolean mDecryptMetadataOnly; private byte[] mDecryptedSessionKey; + private byte[] mDetachedSignature; + private String mRequiredSignerFingerprint; + private boolean mSignedLiteralData; protected PgpDecryptVerify(Builder builder) { super(builder.mContext, builder.mProviderHelper, builder.mProgressable); @@ -96,6 +103,9 @@ public class PgpDecryptVerify extends BaseOperation { this.mAllowedKeyIds = builder.mAllowedKeyIds; this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly; this.mDecryptedSessionKey = builder.mDecryptedSessionKey; + this.mDetachedSignature = builder.mDetachedSignature; + this.mSignedLiteralData = builder.mSignedLiteralData; + this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint; } public static class Builder { @@ -103,15 +113,18 @@ public class PgpDecryptVerify extends BaseOperation { private Context mContext; private ProviderHelper mProviderHelper; private InputData mData; - private OutputStream mOutStream; // optional + private OutputStream mOutStream = null; private Progressable mProgressable = null; private boolean mAllowSymmetricDecryption = true; private String mPassphrase = null; private Set<Long> mAllowedKeyIds = null; private boolean mDecryptMetadataOnly = false; private byte[] mDecryptedSessionKey = null; + private byte[] mDetachedSignature = null; + private String mRequiredSignerFingerprint = null; + private boolean mSignedLiteralData = false; public Builder(Context context, ProviderHelper providerHelper, Progressable progressable, @@ -123,6 +136,24 @@ public class PgpDecryptVerify extends BaseOperation { mOutStream = outStream; } + /** + * This is used when verifying signed literals to check that they are signed with + * the required key + */ + public Builder setRequiredSignerFingerprint(String fingerprint) { + mRequiredSignerFingerprint = fingerprint; + return this; + } + + /** + * This is to force a mode where the message is just the signature key id and + * then a literal data packet; used in Keybase.io proofs + */ + public Builder setSignedLiteralData(boolean signedLiteralData) { + mSignedLiteralData = signedLiteralData; + return this; + } + public Builder setAllowSymmetricDecryption(boolean allowSymmetricDecryption) { mAllowSymmetricDecryption = allowSymmetricDecryption; return this; @@ -156,6 +187,14 @@ public class PgpDecryptVerify extends BaseOperation { return this; } + /** + * If detachedSignature != null, it will be used exclusively to verify the signature + */ + public Builder setDetachedSignature(byte[] detachedSignature) { + mDetachedSignature = detachedSignature; + return this; + } + public PgpDecryptVerify build() { return new PgpDecryptVerify(this); } @@ -166,22 +205,32 @@ public class PgpDecryptVerify extends BaseOperation { */ public DecryptVerifyResult execute() { try { - // automatically works with ascii armor input and binary - InputStream in = PGPUtil.getDecoderStream(mData.getInputStream()); - - if (in instanceof ArmoredInputStream) { - ArmoredInputStream aIn = (ArmoredInputStream) in; - // it is ascii armored - Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine()); + if (mDetachedSignature != null) { + Log.d(Constants.TAG, "Detached signature present, verifying with this signature only"); - if (aIn.isClearText()) { - // a cleartext signature, verify it with the other method - return verifyCleartextSignature(aIn, 0); + return verifyDetachedSignature(mData.getInputStream(), 0); + } else { + // automatically works with PGP ascii armor and PGP binary + InputStream in = PGPUtil.getDecoderStream(mData.getInputStream()); + + if (in instanceof ArmoredInputStream) { + ArmoredInputStream aIn = (ArmoredInputStream) in; + // it is ascii armored + Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine()); + + if (mSignedLiteralData) { + return verifySignedLiteralData(aIn, 0); + } else if (aIn.isClearText()) { + // a cleartext signature, verify it with the other method + return verifyCleartextSignature(aIn, 0); + } else { + // else: ascii armored encryption! go on... + return decryptVerify(in, 0); + } + } else { + return decryptVerify(in, 0); } - // else: ascii armored encryption! go on... } - - return decryptVerify(in, 0); } catch (PGPException e) { Log.d(Constants.TAG, "PGPException", e); OperationLog log = new OperationLog(); @@ -196,6 +245,136 @@ public class PgpDecryptVerify extends BaseOperation { } /** + * Verify Keybase.io style signed literal data + */ + private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent) throws IOException, PGPException { + OperationLog log = new OperationLog(); + log.add(LogType.MSG_VL, indent); + + // thinking that the proof-fetching operation is going to take most of the time + updateProgress(R.string.progress_reading_data, 75, 100); + + JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in); + Object o = pgpF.nextObject(); + if (o instanceof PGPCompressedData) { + log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1); + + pgpF = new JcaPGPObjectFactory(((PGPCompressedData) o).getDataStream()); + o = pgpF.nextObject(); + updateProgress(R.string.progress_decompressing_data, 80, 100); + } + + // all we want to see is a OnePassSignatureList followed by LiteralData + if (!(o instanceof PGPOnePassSignatureList)) { + log.add(LogType.MSG_VL_ERROR_MISSING_SIGLIST, indent); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) o; + + // go through all signatures (should be just one), make sure we have + // the key and it matches the one we’re looking for + CanonicalizedPublicKeyRing signingRing = null; + CanonicalizedPublicKey signingKey = null; + int signatureIndex = -1; + for (int i = 0; i < sigList.size(); ++i) { + try { + long sigKeyId = sigList.get(i).getKeyID(); + signingRing = mProviderHelper.getCanonicalizedPublicKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId) + ); + signingKey = signingRing.getPublicKey(sigKeyId); + signatureIndex = i; + } catch (ProviderHelper.NotFoundException e) { + Log.d(Constants.TAG, "key not found, trying next signature..."); + } + } + + // there has to be a key, and it has to be the right one + if (signingKey == null) { + log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent); + Log.d(Constants.TAG, "Failed to find key in signed-literal message"); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(signingRing.getFingerprint()); + if (!(mRequiredSignerFingerprint.equals(fingerprint))) { + log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent); + Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + mRequiredSignerFingerprint + + " got " + fingerprint + "!"); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + + OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); + + PGPOnePassSignature signature = sigList.get(signatureIndex); + signatureResultBuilder.initValid(signingRing, signingKey); + + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = + new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey()); + + o = pgpF.nextObject(); + + if (!(o instanceof PGPLiteralData)) { + log.add(LogType.MSG_VL_ERROR_MISSING_LITERAL, indent); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + + PGPLiteralData literalData = (PGPLiteralData) o; + + log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1); + updateProgress(R.string.progress_decrypting, 85, 100); + + InputStream dataIn = literalData.getInputStream(); + + int length; + byte[] buffer = new byte[1 << 16]; + while ((length = dataIn.read(buffer)) > 0) { + mOutStream.write(buffer, 0, length); + signature.update(buffer, 0, length); + } + + updateProgress(R.string.progress_verifying_signature, 95, 100); + log.add(LogType.MSG_VL_CLEAR_SIGNATURE_CHECK, indent + 1); + + PGPSignatureList signatureList = (PGPSignatureList) pgpF.nextObject(); + PGPSignature messageSignature = signatureList.get(signatureIndex); + + // these are not cleartext signatures! + // TODO: what about binary signatures? + signatureResultBuilder.setSignatureOnly(false); + + // Verify signature and check binding signatures + boolean validSignature = signature.verify(messageSignature); + if (validSignature) { + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1); + } else { + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1); + } + signatureResultBuilder.setValidSignature(validSignature); + + OpenPgpSignatureResult signatureResult = signatureResultBuilder.build(); + + if (signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED + && signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED) { + log.add(LogType.MSG_VL_ERROR_INTEGRITY_CHECK, indent); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + + updateProgress(R.string.progress_done, 100, 100); + + log.add(LogType.MSG_VL_OK, indent); + + // Return a positive result, with metadata and verification info + DecryptVerifyResult result = + new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); + result.setSignatureResult(signatureResult); + return result; + } + + + /** * Decrypt and/or verifies binary or ascii armored pgp */ private DecryptVerifyResult decryptVerify(InputStream in, int indent) throws IOException, PGPException { @@ -205,7 +384,7 @@ public class PgpDecryptVerify extends BaseOperation { log.add(LogType.MSG_DC, indent); indent += 1; - PGPObjectFactory pgpF = new PGPObjectFactory(in, new JcaKeyFingerprintCalculator()); + JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in); PGPEncryptedDataList enc; Object o = pgpF.nextObject(); @@ -234,6 +413,25 @@ public class PgpDecryptVerify extends BaseOperation { boolean symmetricPacketFound = false; boolean anyPacketFound = false; + // If the input stream is armored, and there is a charset specified, take a note for later + // https://tools.ietf.org/html/rfc4880#page56 + String charset = null; + if (in instanceof ArmoredInputStream) { + ArmoredInputStream aIn = (ArmoredInputStream) in; + if (aIn.getArmorHeaders() != null) { + for (String header : aIn.getArmorHeaders()) { + String[] pieces = header.split(":", 2); + if (pieces.length == 2 && "charset".equalsIgnoreCase(pieces[0])) { + charset = pieces[1].trim(); + break; + } + } + if (charset != null) { + log.add(LogType.MSG_DC_CHARSET, indent, charset); + } + } + } + // go through all objects and find one we can decrypt while (it.hasNext()) { Object obj = it.next(); @@ -257,19 +455,19 @@ public class PgpDecryptVerify extends BaseOperation { ); } catch (ProviderHelper.NotFoundException e) { // continue with the next packet in the while loop - log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent +1); + log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1); continue; } if (secretKeyRing == null) { // continue with the next packet in the while loop - log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent +1); + log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1); continue; } // get subkey which has been used for this encryption packet secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId); if (secretEncryptionKey == null) { // should actually never happen, so no need to be more specific. - log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent +1); + log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1); continue; } @@ -283,7 +481,7 @@ public class PgpDecryptVerify extends BaseOperation { if (!mAllowedKeyIds.contains(masterKeyId)) { // this key is in our db, but NOT allowed! // continue with the next packet in the while loop - log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent +1); + log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent + 1); continue; } } @@ -298,15 +496,15 @@ public class PgpDecryptVerify extends BaseOperation { try { // returns "" if key has no passphrase mPassphrase = getCachedPassphrase(subKeyId); - log.add(LogType.MSG_DC_PASS_CACHED, indent +1); + log.add(LogType.MSG_DC_PASS_CACHED, indent + 1); } catch (PassphraseCacheInterface.NoSecretKeyException e) { - log.add(LogType.MSG_DC_ERROR_NO_KEY, indent +1); + log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } // if passphrase was not cached, return here indicating that a passphrase is missing! if (mPassphrase == null) { - log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent +1); + log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1); DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE, log); result.setKeyIdPassphraseNeeded(subKeyId); @@ -322,8 +520,8 @@ public class PgpDecryptVerify extends BaseOperation { log.add(LogType.MSG_DC_SYM, indent); - if (! mAllowSymmetricDecryption) { - log.add(LogType.MSG_DC_SYM_SKIP, indent +1); + if (!mAllowSymmetricDecryption) { + log.add(LogType.MSG_DC_SYM_SKIP, indent + 1); continue; } @@ -338,7 +536,7 @@ public class PgpDecryptVerify extends BaseOperation { // if no passphrase is given, return here // indicating that a passphrase is missing! if (mPassphrase == null) { - log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent +1); + log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE, log); } @@ -383,13 +581,13 @@ public class PgpDecryptVerify extends BaseOperation { updateProgress(R.string.progress_extracting_key, currentProgress, 100); try { - log.add(LogType.MSG_DC_UNLOCKING, indent +1); + log.add(LogType.MSG_DC_UNLOCKING, indent + 1); if (!secretEncryptionKey.unlock(mPassphrase)) { - log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent +1); + log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } } catch (PgpGeneralException e) { - log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent +1); + log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent + 1); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } @@ -401,7 +599,7 @@ public class PgpDecryptVerify extends BaseOperation { = secretEncryptionKey.getDecryptorFactory(mDecryptedSessionKey); clear = encryptedDataAsymmetric.getDataStream(decryptorFactory); } catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) { - log.add(LogType.MSG_DC_PENDING_NFC, indent +1); + log.add(LogType.MSG_DC_PENDING_NFC, indent + 1); DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_NFC, log); result.setNfcState(secretEncryptionKey.getKeyId(), e.encryptedSessionKey, mPassphrase); @@ -412,11 +610,11 @@ public class PgpDecryptVerify extends BaseOperation { // If we didn't find any useful data, error out // no packet has been found where we have the corresponding secret key in our db log.add( - anyPacketFound ? LogType.MSG_DC_ERROR_NO_KEY : LogType.MSG_DC_ERROR_NO_DATA, indent +1); + anyPacketFound ? LogType.MSG_DC_ERROR_NO_KEY : LogType.MSG_DC_ERROR_NO_DATA, indent + 1); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } - PGPObjectFactory plainFact = new PGPObjectFactory(clear, new JcaKeyFingerprintCalculator()); + JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear); Object dataChunk = plainFact.nextObject(); OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); int signatureIndex = -1; @@ -427,25 +625,27 @@ public class PgpDecryptVerify extends BaseOperation { indent += 1; if (dataChunk instanceof PGPCompressedData) { - log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent +1); + log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1); currentProgress += 2; updateProgress(R.string.progress_decompressing_data, currentProgress, 100); PGPCompressedData compressedData = (PGPCompressedData) dataChunk; - PGPObjectFactory fact = new PGPObjectFactory(compressedData.getDataStream(), new JcaKeyFingerprintCalculator()); + JcaPGPObjectFactory fact = new JcaPGPObjectFactory(compressedData.getDataStream()); dataChunk = fact.nextObject(); plainFact = fact; } PGPOnePassSignature signature = null; if (dataChunk instanceof PGPOnePassSignatureList) { - log.add(LogType.MSG_DC_CLEAR_SIGNATURE, indent +1); + log.add(LogType.MSG_DC_CLEAR_SIGNATURE, indent + 1); currentProgress += 2; updateProgress(R.string.progress_processing_signature, currentProgress, 100); PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; + // NOTE: following code is similar to processSignature, but for PGPOnePassSignature + // go through all signatures // and find out for which signature we have a key in our database for (int i = 0; i < sigList.size(); ++i) { @@ -491,19 +691,15 @@ public class PgpDecryptVerify extends BaseOperation { OpenPgpMetadata metadata; if (dataChunk instanceof PGPLiteralData) { - log.add(LogType.MSG_DC_CLEAR_DATA, indent +1); + log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1); indent += 2; currentProgress += 4; updateProgress(R.string.progress_decrypting, currentProgress, 100); PGPLiteralData literalData = (PGPLiteralData) dataChunk; - // TODO: how to get the real original size? - // this is the encrypted size so if we enable compression this value is wrong! - long originalSize = mData.getSize() - mData.getStreamPosition(); - if (originalSize < 0) { - originalSize = 0; - } + // reported size may be null if partial packets are involved (highly unlikely though) + Long originalSize = literalData.getDataLengthIfAvailable(); String originalFilename = literalData.getFileName(); String mimeType = null; @@ -531,18 +727,20 @@ public class PgpDecryptVerify extends BaseOperation { originalFilename, mimeType, literalData.getModificationTime().getTime(), - originalSize); + originalSize == null ? 0 : originalSize); - if ( ! originalFilename.equals("")) { + if (!"".equals(originalFilename)) { log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename); } - log.add(LogType.MSG_DC_CLEAR_META_MIME, indent +1, + log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, mimeType); - log.add(LogType.MSG_DC_CLEAR_META_TIME, indent +1, + log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1, new Date(literalData.getModificationTime().getTime()).toString()); - if (originalSize != 0) { + if (originalSize != null) { log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1, Long.toString(originalSize)); + } else { + log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1); } // return here if we want to decrypt the metadata only @@ -550,6 +748,7 @@ public class PgpDecryptVerify extends BaseOperation { log.add(LogType.MSG_DC_OK_META_ONLY, indent); DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); + result.setCharset(charset); result.setDecryptMetadata(metadata); return result; } @@ -572,7 +771,10 @@ public class PgpDecryptVerify extends BaseOperation { int length; byte[] buffer = new byte[1 << 16]; while ((length = dataIn.read(buffer)) > 0) { - mOutStream.write(buffer, 0, length); + Log.d(Constants.TAG, "read bytes: " + length); + if (mOutStream != null) { + mOutStream.write(buffer, 0, length); + } // update signature buffer if signature is also present if (signature != null) { @@ -587,9 +789,8 @@ public class PgpDecryptVerify extends BaseOperation { progress = 100; } progressScaler.setProgress((int) progress, 100); - } else { - // TODO: slow annealing to fake a progress? } + // TODO: slow annealing to fake a progress? } if (signature != null) { @@ -606,9 +807,9 @@ public class PgpDecryptVerify extends BaseOperation { // Verify signature and check binding signatures boolean validSignature = signature.verify(messageSignature); if (validSignature) { - log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent +1); + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1); } else { - log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent +1); + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1); } signatureResultBuilder.setValidSignature(validSignature); } @@ -632,6 +833,7 @@ public class PgpDecryptVerify extends BaseOperation { // If no valid signature is present: // Handle missing integrity protection like failed integrity protection! // The MDC packet can be stripped by an attacker! + Log.d(Constants.TAG, "MDC fail"); if (!signatureResultBuilder.isValidSignature()) { log.add(LogType.MSG_DC_ERROR_INTEGRITY_MISSING, indent); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); @@ -647,6 +849,7 @@ public class PgpDecryptVerify extends BaseOperation { new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); result.setDecryptMetadata(metadata); result.setSignatureResult(signatureResultBuilder.build()); + result.setCharset(charset); return result; } @@ -669,7 +872,7 @@ public class PgpDecryptVerify extends BaseOperation { ByteArrayOutputStream out = new ByteArrayOutputStream(); - updateProgress(R.string.progress_done, 0, 100); + updateProgress(R.string.progress_reading_data, 0, 100); ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); int lookAhead = readInputLine(lineOut, aIn); @@ -689,10 +892,12 @@ public class PgpDecryptVerify extends BaseOperation { out.close(); byte[] clearText = out.toByteArray(); - mOutStream.write(clearText); + if (mOutStream != null) { + mOutStream.write(clearText); + } updateProgress(R.string.progress_processing_signature, 60, 100); - PGPObjectFactory pgpFact = new PGPObjectFactory(aIn, new JcaKeyFingerprintCalculator()); + JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(aIn); PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); if (sigList == null) { @@ -700,45 +905,7 @@ public class PgpDecryptVerify extends BaseOperation { return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } - CanonicalizedPublicKeyRing signingRing = null; - CanonicalizedPublicKey signingKey = null; - int signatureIndex = -1; - - // go through all signatures - // and find out for which signature we have a key in our database - for (int i = 0; i < sigList.size(); ++i) { - try { - long sigKeyId = sigList.get(i).getKeyID(); - signingRing = mProviderHelper.getCanonicalizedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId) - ); - signingKey = signingRing.getPublicKey(sigKeyId); - signatureIndex = i; - } catch (ProviderHelper.NotFoundException e) { - Log.d(Constants.TAG, "key not found, trying next signature..."); - } - } - - PGPSignature signature = null; - - if (signingKey != null) { - // key found in our database! - signature = sigList.get(signatureIndex); - - signatureResultBuilder.initValid(signingRing, signingKey); - - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = - new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey()); - } else { - // no key in our database -> return "unknown pub key" status including the first key id - if (!sigList.isEmpty()) { - signatureResultBuilder.setSignatureAvailable(true); - signatureResultBuilder.setKnownKey(false); - signatureResultBuilder.setKeyId(sigList.get(0).getKeyID()); - } - } + PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder); if (signature != null) { try { @@ -786,6 +953,133 @@ public class PgpDecryptVerify extends BaseOperation { return result; } + private DecryptVerifyResult verifyDetachedSignature(InputStream in, int indent) + throws IOException, PGPException { + + OperationLog log = new OperationLog(); + + OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); + // detached signatures are never encrypted + signatureResultBuilder.setSignatureOnly(true); + + updateProgress(R.string.progress_processing_signature, 0, 100); + InputStream detachedSigIn = new ByteArrayInputStream(mDetachedSignature); + detachedSigIn = PGPUtil.getDecoderStream(detachedSigIn); + + JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(detachedSigIn); + + PGPSignatureList sigList; + Object o = pgpFact.nextObject(); + if (o instanceof PGPCompressedData) { + PGPCompressedData c1 = (PGPCompressedData) o; + pgpFact = new JcaPGPObjectFactory(c1.getDataStream()); + sigList = (PGPSignatureList) pgpFact.nextObject(); + } else if (o instanceof PGPSignatureList) { + sigList = (PGPSignatureList) o; + } else { + log.add(LogType.MSG_DC_ERROR_INVALID_SIGLIST, 0); + return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); + } + + PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder); + + if (signature != null) { + updateProgress(R.string.progress_reading_data, 60, 100); + + ProgressScaler progressScaler = new ProgressScaler(mProgressable, 60, 90, 100); + long alreadyWritten = 0; + long wholeSize = mData.getSize() - mData.getStreamPosition(); + int length; + byte[] buffer = new byte[1 << 16]; + while ((length = in.read(buffer)) > 0) { + if (mOutStream != null) { + mOutStream.write(buffer, 0, length); + } + + // update signature buffer if signature is also present + signature.update(buffer, 0, length); + + alreadyWritten += length; + if (wholeSize > 0) { + long progress = 100 * alreadyWritten / wholeSize; + // stop at 100% for wrong file sizes... + if (progress > 100) { + progress = 100; + } + progressScaler.setProgress((int) progress, 100); + } + // TODO: slow annealing to fake a progress? + } + + updateProgress(R.string.progress_verifying_signature, 90, 100); + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent); + + // these are not cleartext signatures! + signatureResultBuilder.setSignatureOnly(false); + + // Verify signature and check binding signatures + boolean validSignature = signature.verify(); + if (validSignature) { + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1); + } else { + log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1); + } + signatureResultBuilder.setValidSignature(validSignature); + } + + updateProgress(R.string.progress_done, 100, 100); + + log.add(LogType.MSG_DC_OK, indent); + + DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); + result.setSignatureResult(signatureResultBuilder.build()); + return result; + } + + private PGPSignature processPGPSignatureList(PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder) throws PGPException { + CanonicalizedPublicKeyRing signingRing = null; + CanonicalizedPublicKey signingKey = null; + int signatureIndex = -1; + + // go through all signatures + // and find out for which signature we have a key in our database + for (int i = 0; i < sigList.size(); ++i) { + try { + long sigKeyId = sigList.get(i).getKeyID(); + signingRing = mProviderHelper.getCanonicalizedPublicKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId) + ); + signingKey = signingRing.getPublicKey(sigKeyId); + signatureIndex = i; + } catch (ProviderHelper.NotFoundException e) { + Log.d(Constants.TAG, "key not found, trying next signature..."); + } + } + + PGPSignature signature = null; + + if (signingKey != null) { + // key found in our database! + signature = sigList.get(signatureIndex); + + signatureResultBuilder.initValid(signingRing, signingKey); + + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = + new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey()); + } else { + // no key in our database -> return "unknown pub key" status including the first key id + if (!sigList.isEmpty()) { + signatureResultBuilder.setSignatureAvailable(true); + signatureResultBuilder.setKnownKey(false); + signatureResultBuilder.setKeyId(sigList.get(0).getKeyID()); + } + } + + return signature; + } + /** * Mostly taken from ClearSignedFileProcessor in Bouncy Castle */ @@ -807,7 +1101,7 @@ public class PgpDecryptVerify extends BaseOperation { while ((ch = fIn.read()) >= 0) { bOut.write(ch); if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); + lookAhead = readPastEOL(bOut, ch, fIn); break; } } @@ -824,7 +1118,7 @@ public class PgpDecryptVerify extends BaseOperation { do { bOut.write(ch); if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); + lookAhead = readPastEOL(bOut, ch, fIn); break; } } while ((ch = fIn.read()) >= 0); @@ -836,7 +1130,7 @@ public class PgpDecryptVerify extends BaseOperation { return lookAhead; } - private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) + private static int readPastEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) throws IOException { int lookAhead = fIn.read(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java index 0a15fd98f..12de2f637 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java @@ -24,8 +24,8 @@ import android.content.pm.PackageManager.NameNotFoundException; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import java.io.File; import java.io.IOException; 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 4bab7f2b9..1a251eb79 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -135,7 +135,7 @@ public class PgpKeyOperation { public PgpKeyOperation(Progressable progress) { super(); if (progress != null) { - mProgress = new Stack<Progressable>(); + mProgress = new Stack<>(); mProgress.push(progress); } } @@ -288,13 +288,11 @@ public class PgpKeyOperation { // build new key pair return new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date()); - } catch(NoSuchProviderException e) { + } catch(NoSuchProviderException | InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } catch(NoSuchAlgorithmException e) { log.add(LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent); return null; - } catch(InvalidAlgorithmParameterException e) { - throw new RuntimeException(e); } catch(PGPException e) { Log.e(Constants.TAG, "internal pgp error", e); log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent); @@ -389,6 +387,9 @@ public class PgpKeyOperation { * with a passphrase fails, the operation will fail with an unlocking error. More specific * handling of errors should be done in UI code! * + * If the passphrase is null, only a restricted subset of operations will be available, + * namely stripping of subkeys and changing the protection mode of dummy keys. + * */ public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel, String passphrase) { @@ -429,12 +430,17 @@ public class PgpKeyOperation { return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } + // If we have no passphrase, only allow restricted operation + if (passphrase == null) { + return internalRestricted(sKR, saveParcel, log); + } + // read masterKeyFlags, and use the same as before. // since this is the master key, this contains at least CERTIFY_OTHER PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); int masterKeyFlags = readKeyFlags(masterPublicKey) | KeyFlags.CERTIFY_OTHER; - long masterKeyExpiry = masterPublicKey.getValidSeconds() == 0L ? 0L : - masterPublicKey.getCreationTime().getTime() / 1000 + masterPublicKey.getValidSeconds(); + Date expiryTime = wsKR.getPublicKey().getExpiryTime(); + long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L; return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, passphrase, log); @@ -496,7 +502,7 @@ public class PgpKeyOperation { @SuppressWarnings("unchecked") Iterator<PGPSignature> it = modifiedPublicKey.getSignaturesForID(userId); if (it != null) { - for (PGPSignature cert : new IterableIterator<PGPSignature>(it)) { + for (PGPSignature cert : new IterableIterator<>(it)) { if (cert.getKeyID() != masterPublicKey.getKeyID()) { // foreign certificate?! error error error log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent); @@ -715,6 +721,27 @@ public class PgpKeyOperation { return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } + if (change.mDummyStrip || change.mDummyDivert != null) { + // IT'S DANGEROUS~ + // no really, it is. this operation irrevocably removes the private key data from the key + if (change.mDummyStrip) { + sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey()); + } else { + // the serial number must be 16 bytes in length + if (change.mDummyDivert.length != 16) { + log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL, + indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + } + sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); + } + + // This doesn't concern us any further + if (!change.mRecertify && (change.mExpiry == null && change.mFlags == null)) { + continue; + } + // expiry must not be in the past if (change.mExpiry != null && change.mExpiry != 0 && new Date(change.mExpiry*1000).before(new Date())) { @@ -805,30 +832,6 @@ public class PgpKeyOperation { } subProgressPop(); - // 4c. For each subkey to be stripped... do so - subProgressPush(65, 70); - for (int i = 0; i < saveParcel.mStripSubKeys.size(); i++) { - - progress(R.string.progress_modify_subkeystrip, (i-1) * (100 / saveParcel.mStripSubKeys.size())); - long strip = saveParcel.mStripSubKeys.get(i); - log.add(LogType.MSG_MF_SUBKEY_STRIP, - indent, KeyFormattingUtils.convertKeyIdToHex(strip)); - - PGPSecretKey sKey = sKR.getSecretKey(strip); - if (sKey == null) { - log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING, - indent+1, KeyFormattingUtils.convertKeyIdToHex(strip)); - return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); - } - - // IT'S DANGEROUS~ - // no really, it is. this operation irrevocably removes the private key data from the key - sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey()); - sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); - - } - subProgressPop(); - // 5. Generate and add new subkeys subProgressPush(70, 90); for (int i = 0; i < saveParcel.mAddSubKeys.size(); i++) { @@ -937,6 +940,73 @@ public class PgpKeyOperation { } + /** This method does the actual modifications in a keyring just like internal, except it + * supports only the subset of operations which require no passphrase, and will error + * otherwise. + */ + private PgpEditKeyResult internalRestricted(PGPSecretKeyRing sKR, SaveKeyringParcel saveParcel, + OperationLog log) { + + int indent = 1; + + progress(R.string.progress_modify, 0); + + // Make sure the saveParcel includes only operations available without passphrae! + if (!saveParcel.isRestrictedOnly()) { + log.add(LogType.MSG_MF_ERROR_RESTRICTED, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + + // Check if we were cancelled + if (checkCancelled()) { + log.add(LogType.MSG_OPERATION_CANCELLED, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); + } + + // The only operation we can do here: + // 4a. Strip secret keys, or change their protection mode (stripped/divert-to-card) + subProgressPush(50, 60); + for (int i = 0; i < saveParcel.mChangeSubKeys.size(); i++) { + + progress(R.string.progress_modify_subkeychange, (i - 1) * (100 / saveParcel.mChangeSubKeys.size())); + SaveKeyringParcel.SubkeyChange change = saveParcel.mChangeSubKeys.get(i); + log.add(LogType.MSG_MF_SUBKEY_CHANGE, + indent, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); + + PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId); + if (sKey == null) { + log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING, + indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + + if (change.mDummyStrip || change.mDummyDivert != null) { + // IT'S DANGEROUS~ + // no really, it is. this operation irrevocably removes the private key data from the key + if (change.mDummyStrip) { + sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey()); + } else { + // the serial number must be 16 bytes in length + if (change.mDummyDivert.length != 16) { + log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL, + indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert); + } + sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); + } + + } + + // And we're done! + progress(R.string.progress_done, 100); + log.add(LogType.MSG_MF_SUCCESS, indent); + return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); + + } + + private static PGPSecretKeyRing applyNewUnlock( PGPSecretKeyRing sKR, PGPPublicKey masterPublicKey, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java new file mode 100644 index 000000000..9318be006 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java @@ -0,0 +1,176 @@ +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.bcpg.CompressionAlgorithmTags; +import org.sufficientlysecure.keychain.Constants; + +import java.util.Date; + +public class PgpSignEncryptInput { + + protected String mVersionHeader = null; + protected boolean mEnableAsciiArmorOutput = false; + protected int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED; + protected long[] mEncryptionMasterKeyIds = null; + protected String mSymmetricPassphrase = null; + protected int mSymmetricEncryptionAlgorithm = 0; + protected long mSignatureMasterKeyId = Constants.key.none; + protected Long mSignatureSubKeyId = null; + protected int mSignatureHashAlgorithm = 0; + protected String mSignaturePassphrase = null; + protected long mAdditionalEncryptId = Constants.key.none; + protected byte[] mNfcSignedHash = null; + protected Date mNfcCreationTimestamp = null; + protected boolean mFailOnMissingEncryptionKeyIds = false; + protected String mCharset; + protected boolean mCleartextSignature; + protected boolean mDetachedSignature; + + public String getCharset() { + return mCharset; + } + + public void setCharset(String mCharset) { + this.mCharset = mCharset; + } + + public boolean ismFailOnMissingEncryptionKeyIds() { + return mFailOnMissingEncryptionKeyIds; + } + + public Date getNfcCreationTimestamp() { + return mNfcCreationTimestamp; + } + + public byte[] getNfcSignedHash() { + return mNfcSignedHash; + } + + public long getAdditionalEncryptId() { + return mAdditionalEncryptId; + } + + public PgpSignEncryptInput setAdditionalEncryptId(long additionalEncryptId) { + mAdditionalEncryptId = additionalEncryptId; + return this; + } + + public String getSignaturePassphrase() { + return mSignaturePassphrase; + } + + public PgpSignEncryptInput setSignaturePassphrase(String signaturePassphrase) { + mSignaturePassphrase = signaturePassphrase; + return this; + } + + public int getSignatureHashAlgorithm() { + return mSignatureHashAlgorithm; + } + + public PgpSignEncryptInput setSignatureHashAlgorithm(int signatureHashAlgorithm) { + mSignatureHashAlgorithm = signatureHashAlgorithm; + return this; + } + + public Long getSignatureSubKeyId() { + return mSignatureSubKeyId; + } + + public PgpSignEncryptInput setSignatureSubKeyId(long signatureSubKeyId) { + mSignatureSubKeyId = signatureSubKeyId; + return this; + } + + public long getSignatureMasterKeyId() { + return mSignatureMasterKeyId; + } + + public PgpSignEncryptInput setSignatureMasterKeyId(long signatureMasterKeyId) { + mSignatureMasterKeyId = signatureMasterKeyId; + return this; + } + + public int getSymmetricEncryptionAlgorithm() { + return mSymmetricEncryptionAlgorithm; + } + + public PgpSignEncryptInput setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { + mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm; + return this; + } + + public String getSymmetricPassphrase() { + return mSymmetricPassphrase; + } + + public PgpSignEncryptInput setSymmetricPassphrase(String symmetricPassphrase) { + mSymmetricPassphrase = symmetricPassphrase; + return this; + } + + public long[] getEncryptionMasterKeyIds() { + return mEncryptionMasterKeyIds; + } + + public PgpSignEncryptInput setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) { + mEncryptionMasterKeyIds = encryptionMasterKeyIds; + return this; + } + + public int getCompressionId() { + return mCompressionId; + } + + public PgpSignEncryptInput setCompressionId(int compressionId) { + mCompressionId = compressionId; + return this; + } + + public boolean ismEnableAsciiArmorOutput() { + return mEnableAsciiArmorOutput; + } + + public String getVersionHeader() { + return mVersionHeader; + } + + public PgpSignEncryptInput setVersionHeader(String versionHeader) { + mVersionHeader = versionHeader; + return this; + } + + public PgpSignEncryptInput setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) { + mEnableAsciiArmorOutput = enableAsciiArmorOutput; + return this; + } + + public PgpSignEncryptInput setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) { + mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds; + return this; + } + + public PgpSignEncryptInput setNfcState(byte[] signedHash, Date creationTimestamp) { + mNfcSignedHash = signedHash; + mNfcCreationTimestamp = creationTimestamp; + return this; + } + + public PgpSignEncryptInput setCleartextSignature(boolean cleartextSignature) { + this.mCleartextSignature = cleartextSignature; + return this; + } + + public boolean isCleartextSignature() { + return mCleartextSignature; + } + + public PgpSignEncryptInput setDetachedSignature(boolean detachedSignature) { + this.mDetachedSignature = detachedSignature; + return this; + } + + public boolean isDetachedSignature() { + return mDetachedSignature; + } +} + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 3c3bcc890..2fa01d241 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -25,6 +25,7 @@ import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.BCPGOutputStream; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.openpgp.PGPCompressedDataGenerator; +import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedDataGenerator; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPLiteralData; @@ -37,19 +38,20 @@ import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.BaseOperation; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -59,31 +61,23 @@ import java.security.SignatureException; import java.util.Arrays; import java.util.Date; import java.util.LinkedList; +import java.util.concurrent.atomic.AtomicBoolean; -/** - * This class uses a Builder pattern! +/** This class supports a single, low-level, sign/encrypt operation. + * + * The operation of this class takes an Input- and OutputStream plus a + * PgpSignEncryptInput, and signs and/or encrypts the stream as + * parametrized in the PgpSignEncryptInput object. It returns its status + * and a possible detached signature as a SignEncryptResult. + * + * For a high-level operation based on URIs, see SignEncryptOperation. + * + * @see org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput + * @see org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult + * @see org.sufficientlysecure.keychain.operations.SignEncryptOperation + * */ -public class PgpSignEncrypt extends BaseOperation { - private String mVersionHeader; - private InputData mData; - private OutputStream mOutStream; - - private boolean mEnableAsciiArmorOutput; - private int mCompressionId; - private long[] mEncryptionMasterKeyIds; - private String mSymmetricPassphrase; - private int mSymmetricEncryptionAlgorithm; - private long mSignatureMasterKeyId; - private Long mSignatureSubKeyId; - private int mSignatureHashAlgorithm; - private String mSignaturePassphrase; - private long mAdditionalEncryptId; - private boolean mCleartextInput; - private String mOriginalFilename; - private boolean mFailOnMissingEncryptionKeyIds; - - private byte[] mNfcSignedHash = null; - private Date mNfcCreationTimestamp = null; +public class PgpSignEncryptOperation extends BaseOperation { private static byte[] NEW_LINE; @@ -95,241 +89,107 @@ public class PgpSignEncrypt extends BaseOperation { } } - protected PgpSignEncrypt(Builder builder) { - super(builder.mContext, builder.mProviderHelper, builder.mProgressable); - - // private Constructor can only be called from Builder - this.mVersionHeader = builder.mVersionHeader; - this.mData = builder.mData; - this.mOutStream = builder.mOutStream; - - this.mEnableAsciiArmorOutput = builder.mEnableAsciiArmorOutput; - this.mCompressionId = builder.mCompressionId; - this.mEncryptionMasterKeyIds = builder.mEncryptionMasterKeyIds; - this.mSymmetricPassphrase = builder.mSymmetricPassphrase; - this.mSymmetricEncryptionAlgorithm = builder.mSymmetricEncryptionAlgorithm; - this.mSignatureMasterKeyId = builder.mSignatureMasterKeyId; - this.mSignatureSubKeyId = builder.mSignatureSubKeyId; - this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm; - this.mSignaturePassphrase = builder.mSignaturePassphrase; - this.mAdditionalEncryptId = builder.mAdditionalEncryptId; - this.mCleartextInput = builder.mCleartextInput; - this.mNfcSignedHash = builder.mNfcSignedHash; - this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp; - this.mOriginalFilename = builder.mOriginalFilename; - this.mFailOnMissingEncryptionKeyIds = builder.mFailOnMissingEncryptionKeyIds; + public PgpSignEncryptOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean cancelled) { + super(context, providerHelper, progressable, cancelled); } - public static class Builder { - // mandatory parameter - private Context mContext; - private ProviderHelper mProviderHelper; - private Progressable mProgressable; - private InputData mData; - private OutputStream mOutStream; - - // optional - private String mVersionHeader = null; - private boolean mEnableAsciiArmorOutput = false; - private int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED; - private long[] mEncryptionMasterKeyIds = null; - private String mSymmetricPassphrase = null; - private int mSymmetricEncryptionAlgorithm = 0; - private long mSignatureMasterKeyId = Constants.key.none; - private Long mSignatureSubKeyId = null; - private int mSignatureHashAlgorithm = 0; - private String mSignaturePassphrase = null; - private long mAdditionalEncryptId = Constants.key.none; - private boolean mCleartextInput = false; - private String mOriginalFilename = ""; - private byte[] mNfcSignedHash = null; - private Date mNfcCreationTimestamp = null; - private boolean mFailOnMissingEncryptionKeyIds = false; - - public Builder(Context context, ProviderHelper providerHelper, Progressable progressable, - InputData data, OutputStream outStream) { - mContext = context; - mProviderHelper = providerHelper; - mProgressable = progressable; - - mData = data; - mOutStream = outStream; - } - - public Builder setVersionHeader(String versionHeader) { - mVersionHeader = versionHeader; - return this; - } - - public Builder setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) { - mEnableAsciiArmorOutput = enableAsciiArmorOutput; - return this; - } - - public Builder setCompressionId(int compressionId) { - mCompressionId = compressionId; - return this; - } - - public Builder setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) { - mEncryptionMasterKeyIds = encryptionMasterKeyIds; - return this; - } - - public Builder setSymmetricPassphrase(String symmetricPassphrase) { - mSymmetricPassphrase = symmetricPassphrase; - return this; - } - - public Builder setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { - mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm; - return this; - } - - public Builder setSignatureMasterKeyId(long signatureMasterKeyId) { - mSignatureMasterKeyId = signatureMasterKeyId; - return this; - } - - public Builder setSignatureSubKeyId(long signatureSubKeyId) { - mSignatureSubKeyId = signatureSubKeyId; - return this; - } - - public Builder setSignatureHashAlgorithm(int signatureHashAlgorithm) { - mSignatureHashAlgorithm = signatureHashAlgorithm; - return this; - } - - public Builder setSignaturePassphrase(String signaturePassphrase) { - mSignaturePassphrase = signaturePassphrase; - return this; - } - - public Builder setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) { - mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds; - return this; - } - - /** - * Also encrypt with the signing keyring - * - * @param additionalEncryptId - * @return - */ - public Builder setAdditionalEncryptId(long additionalEncryptId) { - mAdditionalEncryptId = additionalEncryptId; - return this; - } - - /** - * TODO: test this option! - * - * @param cleartextInput - * @return - */ - public Builder setCleartextInput(boolean cleartextInput) { - mCleartextInput = cleartextInput; - return this; - } - - public Builder setOriginalFilename(String originalFilename) { - mOriginalFilename = originalFilename; - return this; - } - - public Builder setNfcState(byte[] signedHash, Date creationTimestamp) { - mNfcSignedHash = signedHash; - mNfcCreationTimestamp = creationTimestamp; - return this; - } - - public PgpSignEncrypt build() { - return new PgpSignEncrypt(this); - } + public PgpSignEncryptOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { + super(context, providerHelper, progressable); } /** * Signs and/or encrypts data based on parameters of class */ - public SignEncryptResult execute() { + public PgpSignEncryptResult execute(PgpSignEncryptInput input, + InputData inputData, OutputStream outputStream) { int indent = 0; OperationLog log = new OperationLog(); - log.add(LogType.MSG_SE, indent); + log.add(LogType.MSG_PSE, indent); indent += 1; - boolean enableSignature = mSignatureMasterKeyId != Constants.key.none; - boolean enableEncryption = ((mEncryptionMasterKeyIds != null && mEncryptionMasterKeyIds.length > 0) - || mSymmetricPassphrase != null); - boolean enableCompression = (mCompressionId != CompressionAlgorithmTags.UNCOMPRESSED); + boolean enableSignature = input.getSignatureMasterKeyId() != Constants.key.none; + boolean enableEncryption = ((input.getEncryptionMasterKeyIds() != null && input.getEncryptionMasterKeyIds().length > 0) + || input.getSymmetricPassphrase() != null); + boolean enableCompression = (input.getCompressionId() != CompressionAlgorithmTags.UNCOMPRESSED); Log.d(Constants.TAG, "enableSignature:" + enableSignature + "\nenableEncryption:" + enableEncryption + "\nenableCompression:" + enableCompression - + "\nenableAsciiArmorOutput:" + mEnableAsciiArmorOutput); + + "\nenableAsciiArmorOutput:" + input.ismEnableAsciiArmorOutput()); // add additional key id to encryption ids (mostly to do self-encryption) - if (enableEncryption && mAdditionalEncryptId != Constants.key.none) { - mEncryptionMasterKeyIds = Arrays.copyOf(mEncryptionMasterKeyIds, mEncryptionMasterKeyIds.length + 1); - mEncryptionMasterKeyIds[mEncryptionMasterKeyIds.length - 1] = mAdditionalEncryptId; + if (enableEncryption && input.getAdditionalEncryptId() != Constants.key.none) { + input.setEncryptionMasterKeyIds(Arrays.copyOf(input.getEncryptionMasterKeyIds(), input.getEncryptionMasterKeyIds().length + 1)); + input.getEncryptionMasterKeyIds()[input.getEncryptionMasterKeyIds().length - 1] = input.getAdditionalEncryptId(); } ArmoredOutputStream armorOut = null; OutputStream out; - if (mEnableAsciiArmorOutput) { - armorOut = new ArmoredOutputStream(mOutStream); - if (mVersionHeader != null) { - armorOut.setHeader("Version", mVersionHeader); + if (input.ismEnableAsciiArmorOutput()) { + armorOut = new ArmoredOutputStream(outputStream); + if (input.getVersionHeader() != null) { + armorOut.setHeader("Version", input.getVersionHeader()); + } + // if we have a charset, put it in the header + if (input.getCharset() != null) { + armorOut.setHeader("Charset", input.getCharset()); } out = armorOut; } else { - out = mOutStream; + out = outputStream; } /* Get keys for signature generation for later usage */ CanonicalizedSecretKey signingKey = null; - long signKeyId; if (enableSignature) { try { // fetch the indicated master key id (the one whose name we sign in) CanonicalizedSecretKeyRing signingKeyRing = - mProviderHelper.getCanonicalizedSecretKeyRing(mSignatureMasterKeyId); + mProviderHelper.getCanonicalizedSecretKeyRing(input.getSignatureMasterKeyId()); + + long signKeyId; + // use specified signing subkey, or find the one to use + if (input.getSignatureSubKeyId() == null) { + signKeyId = signingKeyRing.getSecretSignId(); + } else { + signKeyId = input.getSignatureSubKeyId(); + } + // fetch the specific subkey to sign with, or just use the master key if none specified - signKeyId = mSignatureSubKeyId != null ? mSignatureSubKeyId : mSignatureMasterKeyId; signingKey = signingKeyRing.getSecretKey(signKeyId); - // make sure it's a signing key alright! - } catch (ProviderHelper.NotFoundException e) { - log.add(LogType.MSG_SE_ERROR_SIGN_KEY, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + + } catch (ProviderHelper.NotFoundException | PgpGeneralException e) { + log.add(LogType.MSG_PSE_ERROR_SIGN_KEY, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } // Make sure we are allowed to sign here! if (!signingKey.canSign()) { - log.add(LogType.MSG_SE_ERROR_KEY_SIGN, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } // if no passphrase was explicitly set try to get it from the cache service - if (mSignaturePassphrase == null) { + if (input.getSignaturePassphrase() == null) { try { // returns "" if key has no passphrase - mSignaturePassphrase = getCachedPassphrase(signKeyId); + input.setSignaturePassphrase(getCachedPassphrase(signingKey.getKeyId())); // TODO // log.add(LogType.MSG_DC_PASS_CACHED, indent + 1); } catch (PassphraseCacheInterface.NoSecretKeyException e) { // TODO // log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } // if passphrase was not cached, return here indicating that a passphrase is missing! - if (mSignaturePassphrase == null) { - log.add(LogType.MSG_SE_PENDING_PASSPHRASE, indent + 1); - SignEncryptResult result = new SignEncryptResult(SignEncryptResult.RESULT_PENDING_PASSPHRASE, log); - result.setKeyIdPassphraseNeeded(signKeyId); + if (input.getSignaturePassphrase() == null) { + log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1); + PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE, log); + result.setKeyIdPassphraseNeeded(signingKey.getKeyId()); return result; } } @@ -337,20 +197,24 @@ public class PgpSignEncrypt extends BaseOperation { updateProgress(R.string.progress_extracting_signature_key, 0, 100); try { - if (!signingKey.unlock(mSignaturePassphrase)) { - log.add(LogType.MSG_SE_ERROR_BAD_PASSPHRASE, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + if (!signingKey.unlock(input.getSignaturePassphrase())) { + log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } catch (PgpGeneralException e) { - log.add(LogType.MSG_SE_ERROR_UNLOCK, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } // check if hash algo is supported + int requestedAlgorithm = input.getSignatureHashAlgorithm(); LinkedList<Integer> supported = signingKey.getSupportedHashAlgorithms(); - if (!supported.contains(mSignatureHashAlgorithm)) { + if (requestedAlgorithm == 0) { // get most preferred - mSignatureHashAlgorithm = supported.getLast(); + input.setSignatureHashAlgorithm(supported.getLast()); + } else if (!supported.contains(requestedAlgorithm)) { + log.add(LogType.MSG_PSE_ERROR_HASH_ALGO, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } updateProgress(R.string.progress_preparing_streams, 2, 100); @@ -358,44 +222,48 @@ public class PgpSignEncrypt extends BaseOperation { /* Initialize PGPEncryptedDataGenerator for later usage */ PGPEncryptedDataGenerator cPk = null; if (enableEncryption) { + int algo = input.getSymmetricEncryptionAlgorithm(); + if (algo == 0) { + algo = PGPEncryptedData.AES_128; + } // has Integrity packet enabled! JcePGPDataEncryptorBuilder encryptorBuilder = - new JcePGPDataEncryptorBuilder(mSymmetricEncryptionAlgorithm) + new JcePGPDataEncryptorBuilder(algo) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME) .setWithIntegrityPacket(true); cPk = new PGPEncryptedDataGenerator(encryptorBuilder); - if (mSymmetricPassphrase != null) { + if (input.getSymmetricPassphrase() != null) { // Symmetric encryption - log.add(LogType.MSG_SE_SYMMETRIC, indent); + log.add(LogType.MSG_PSE_SYMMETRIC, indent); JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = - new JcePBEKeyEncryptionMethodGenerator(mSymmetricPassphrase.toCharArray()); + new JcePBEKeyEncryptionMethodGenerator(input.getSymmetricPassphrase().toCharArray()); cPk.addMethod(symmetricEncryptionGenerator); } else { - log.add(LogType.MSG_SE_ASYMMETRIC, indent); + log.add(LogType.MSG_PSE_ASYMMETRIC, indent); // Asymmetric encryption - for (long id : mEncryptionMasterKeyIds) { + for (long id : input.getEncryptionMasterKeyIds()) { try { CanonicalizedPublicKeyRing keyRing = mProviderHelper.getCanonicalizedPublicKeyRing( KeyRings.buildUnifiedKeyRingUri(id)); CanonicalizedPublicKey key = keyRing.getEncryptionSubKey(); cPk.addMethod(key.getPubKeyEncryptionGenerator()); - log.add(LogType.MSG_SE_KEY_OK, indent + 1, + log.add(LogType.MSG_PSE_KEY_OK, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id)); } catch (PgpKeyNotFoundException e) { - log.add(LogType.MSG_SE_KEY_WARN, indent + 1, + log.add(LogType.MSG_PSE_KEY_WARN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id)); - if (mFailOnMissingEncryptionKeyIds) { - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + if (input.ismFailOnMissingEncryptionKeyIds()) { + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } catch (ProviderHelper.NotFoundException e) { - log.add(LogType.MSG_SE_KEY_UNKNOWN, indent + 1, + log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id)); - if (mFailOnMissingEncryptionKeyIds) { - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + if (input.ismFailOnMissingEncryptionKeyIds()) { + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } } @@ -408,12 +276,12 @@ public class PgpSignEncrypt extends BaseOperation { updateProgress(R.string.progress_preparing_signature, 4, 100); try { - boolean cleartext = mCleartextInput && mEnableAsciiArmorOutput && !enableEncryption; + boolean cleartext = input.isCleartextSignature() && input.ismEnableAsciiArmorOutput() && !enableEncryption; signatureGenerator = signingKey.getSignatureGenerator( - mSignatureHashAlgorithm, cleartext, mNfcSignedHash, mNfcCreationTimestamp); + input.getSignatureHashAlgorithm(), cleartext, input.getNfcSignedHash(), input.getNfcCreationTimestamp()); } catch (PgpGeneralException e) { - log.add(LogType.MSG_SE_ERROR_NFC, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + log.add(LogType.MSG_PSE_ERROR_NFC, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } @@ -424,14 +292,18 @@ public class PgpSignEncrypt extends BaseOperation { OutputStream encryptionOut = null; BCPGOutputStream bcpgOut; + ByteArrayOutputStream detachedByteOut = null; + ArmoredOutputStream detachedArmorOut = null; + BCPGOutputStream detachedBcpgOut = null; + try { if (enableEncryption) { /* actual encryption */ updateProgress(R.string.progress_encrypting, 8, 100); log.add(enableSignature - ? LogType.MSG_SE_SIGCRYPTING - : LogType.MSG_SE_ENCRYPTING, + ? LogType.MSG_PSE_SIGCRYPTING + : LogType.MSG_PSE_ENCRYPTING, indent ); indent += 1; @@ -439,8 +311,8 @@ public class PgpSignEncrypt extends BaseOperation { encryptionOut = cPk.open(out, new byte[1 << 16]); if (enableCompression) { - log.add(LogType.MSG_SE_COMPRESSING, indent); - compressGen = new PGPCompressedDataGenerator(mCompressionId); + log.add(LogType.MSG_PSE_COMPRESSING, indent); + compressGen = new PGPCompressedDataGenerator(input.getCompressionId()); bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut)); } else { bcpgOut = new BCPGOutputStream(encryptionOut); @@ -452,18 +324,18 @@ public class PgpSignEncrypt extends BaseOperation { PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); char literalDataFormatTag; - if (mCleartextInput) { + if (input.isCleartextSignature()) { literalDataFormatTag = PGPLiteralData.UTF8; } else { literalDataFormatTag = PGPLiteralData.BINARY; } - pOut = literalGen.open(bcpgOut, literalDataFormatTag, mOriginalFilename, new Date(), - new byte[1 << 16]); + pOut = literalGen.open(bcpgOut, literalDataFormatTag, + inputData.getOriginalFilename(), new Date(), new byte[1 << 16]); long alreadyWritten = 0; int length; byte[] buffer = new byte[1 << 16]; - InputStream in = mData.getInputStream(); + InputStream in = inputData.getInputStream(); while ((length = in.read(buffer)) > 0) { pOut.write(buffer, 0, length); @@ -473,8 +345,8 @@ public class PgpSignEncrypt extends BaseOperation { } alreadyWritten += length; - if (mData.getSize() > 0) { - long progress = 100 * alreadyWritten / mData.getSize(); + if (inputData.getSize() > 0) { + long progress = 100 * alreadyWritten / inputData.getSize(); progressScaler.setProgress((int) progress, 100); } } @@ -482,16 +354,16 @@ public class PgpSignEncrypt extends BaseOperation { literalGen.close(); indent -= 1; - } else if (enableSignature && mCleartextInput && mEnableAsciiArmorOutput) { + } else if (enableSignature && input.isCleartextSignature() && input.ismEnableAsciiArmorOutput()) { /* cleartext signature: sign-only of ascii text */ updateProgress(R.string.progress_signing, 8, 100); - log.add(LogType.MSG_SE_SIGNING, indent); + log.add(LogType.MSG_PSE_SIGNING_CLEARTEXT, indent); // write -----BEGIN PGP SIGNED MESSAGE----- - armorOut.beginClearText(mSignatureHashAlgorithm); + armorOut.beginClearText(input.getSignatureHashAlgorithm()); - InputStream in = mData.getInputStream(); + InputStream in = inputData.getInputStream(); final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); // update signature buffer with first line @@ -517,16 +389,53 @@ public class PgpSignEncrypt extends BaseOperation { armorOut.endClearText(); pOut = new BCPGOutputStream(armorOut); - } else if (enableSignature && !mCleartextInput) { + } else if (enableSignature && input.isDetachedSignature()) { + /* detached signature */ + + updateProgress(R.string.progress_signing, 8, 100); + log.add(LogType.MSG_PSE_SIGNING_DETACHED, indent); + + InputStream in = inputData.getInputStream(); + + // handle output stream separately for detached signatures + detachedByteOut = new ByteArrayOutputStream(); + OutputStream detachedOut = detachedByteOut; + if (input.ismEnableAsciiArmorOutput()) { + detachedArmorOut = new ArmoredOutputStream(detachedOut); + if (input.getVersionHeader() != null) { + detachedArmorOut.setHeader("Version", input.getVersionHeader()); + } + + detachedOut = detachedArmorOut; + } + detachedBcpgOut = new BCPGOutputStream(detachedOut); + + long alreadyWritten = 0; + int length; + byte[] buffer = new byte[1 << 16]; + while ((length = in.read(buffer)) > 0) { + // no output stream is written, no changed to original data! + + signatureGenerator.update(buffer, 0, length); + + alreadyWritten += length; + if (inputData.getSize() > 0) { + long progress = 100 * alreadyWritten / inputData.getSize(); + progressScaler.setProgress((int) progress, 100); + } + } + + pOut = null; + } else if (enableSignature && !input.isCleartextSignature() && !input.isDetachedSignature()) { /* sign-only binary (files/data stream) */ updateProgress(R.string.progress_signing, 8, 100); - log.add(LogType.MSG_SE_ENCRYPTING, indent); + log.add(LogType.MSG_PSE_SIGNING, indent); - InputStream in = mData.getInputStream(); + InputStream in = inputData.getInputStream(); if (enableCompression) { - compressGen = new PGPCompressedDataGenerator(mCompressionId); + compressGen = new PGPCompressedDataGenerator(input.getCompressionId()); bcpgOut = new BCPGOutputStream(compressGen.open(out)); } else { bcpgOut = new BCPGOutputStream(out); @@ -535,7 +444,8 @@ public class PgpSignEncrypt extends BaseOperation { signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); - pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, mOriginalFilename, new Date(), + pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, + inputData.getOriginalFilename(), new Date(), new byte[1 << 16]); long alreadyWritten = 0; @@ -547,8 +457,8 @@ public class PgpSignEncrypt extends BaseOperation { signatureGenerator.update(buffer, 0, length); alreadyWritten += length; - if (mData.getSize() > 0) { - long progress = 100 * alreadyWritten / mData.getSize(); + if (inputData.getSize() > 0) { + long progress = 100 * alreadyWritten / inputData.getSize(); progressScaler.setProgress((int) progress, 100); } } @@ -556,61 +466,89 @@ public class PgpSignEncrypt extends BaseOperation { literalGen.close(); } else { pOut = null; - log.add(LogType.MSG_SE_CLEARSIGN_ONLY, indent); + // TODO: Is this log right? + log.add(LogType.MSG_PSE_CLEARSIGN_ONLY, indent); } if (enableSignature) { updateProgress(R.string.progress_generating_signature, 95, 100); try { - signatureGenerator.generate().encode(pOut); + if (detachedBcpgOut != null) { + signatureGenerator.generate().encode(detachedBcpgOut); + } else { + signatureGenerator.generate().encode(pOut); + } } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) { // this secret key diverts to a OpenPGP card, throw exception with hash that will be signed - log.add(LogType.MSG_SE_PENDING_NFC, indent); - SignEncryptResult result = - new SignEncryptResult(SignEncryptResult.RESULT_PENDING_NFC, log); + log.add(LogType.MSG_PSE_PENDING_NFC, indent); + PgpSignEncryptResult result = + new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_NFC, log); // Note that the checked key here is the master key, not the signing key // (although these are always the same on Yubikeys) - result.setNfcData(mSignatureSubKeyId, e.hashToSign, e.hashAlgo, e.creationTimestamp, mSignaturePassphrase); - Log.d(Constants.TAG, "e.hashToSign"+ Hex.toHexString(e.hashToSign)); + result.setNfcData(input.getSignatureSubKeyId(), e.hashToSign, e.hashAlgo, e.creationTimestamp, input.getSignaturePassphrase()); + Log.d(Constants.TAG, "e.hashToSign" + Hex.toHexString(e.hashToSign)); return result; } } // closing outputs // NOTE: closing needs to be done in the correct order! - // TODO: closing bcpgOut and pOut??? - if (enableEncryption) { - if (enableCompression) { + if (encryptionOut != null) { + if (compressGen != null) { compressGen.close(); } encryptionOut.close(); } - if (mEnableAsciiArmorOutput) { + // Note: Closing ArmoredOutputStream does not close the underlying stream + if (armorOut != null) { armorOut.close(); } - - out.close(); - mOutStream.close(); + // Note: Closing ArmoredOutputStream does not close the underlying stream + if (detachedArmorOut != null) { + detachedArmorOut.close(); + } + // Also closes detachedBcpgOut + if (detachedByteOut != null) { + detachedByteOut.close(); + } + if (out != null) { + out.close(); + } + if (outputStream != null) { + outputStream.close(); + } } catch (SignatureException e) { - log.add(LogType.MSG_SE_ERROR_SIG, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + log.add(LogType.MSG_PSE_ERROR_SIG, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } catch (PGPException e) { - log.add(LogType.MSG_SE_ERROR_PGP, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + log.add(LogType.MSG_PSE_ERROR_PGP, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } catch (IOException e) { - log.add(LogType.MSG_SE_ERROR_IO, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log); + log.add(LogType.MSG_PSE_ERROR_IO, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } updateProgress(R.string.progress_done, 100, 100); - log.add(LogType.MSG_SE_OK, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_OK, log); - + log.add(LogType.MSG_PSE_OK, indent); + PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_OK, log); + if (detachedByteOut != null) { + try { + detachedByteOut.flush(); + detachedByteOut.close(); + } catch (IOException e) { + // silently catch + } + result.setDetachedSignature(detachedByteOut.toByteArray()); + } + return result; } + /** + * Remove whitespaces on line endings + */ private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, final PGPSignatureGenerator pSignatureGenerator) throws IOException, SignatureException { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java new file mode 100644 index 000000000..a4ed33397 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java @@ -0,0 +1,135 @@ +package org.sufficientlysecure.keychain.pgp; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +/** This parcel stores the input of one or more PgpSignEncrypt operations. + * All operations will use the same general paramters, differing only in + * input and output. Each input/output set depends on the paramters: + * + * - Each input uri is individually encrypted/signed + * - If a byte array is supplied, it is treated as an input before uris are processed + * - The number of output uris must match the number of input uris, plus one more + * if there is a byte array present. + * - Once the output uris are empty, there must be exactly one input (uri xor bytes) + * left, which will be returned in a byte array as part of the result parcel. + * + */ +public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable { + + public ArrayList<Uri> mInputUris = new ArrayList<>(); + public ArrayList<Uri> mOutputUris = new ArrayList<>(); + public byte[] mBytes; + + public SignEncryptParcel() { + super(); + } + + public SignEncryptParcel(Parcel src) { + + // we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable + mVersionHeader = src.readString(); + mEnableAsciiArmorOutput = src.readInt() == 1; + mCompressionId = src.readInt(); + mEncryptionMasterKeyIds = src.createLongArray(); + mSymmetricPassphrase = src.readString(); + mSymmetricEncryptionAlgorithm = src.readInt(); + mSignatureMasterKeyId = src.readLong(); + mSignatureSubKeyId = src.readInt() == 1 ? src.readLong() : null; + mSignatureHashAlgorithm = src.readInt(); + mSignaturePassphrase = src.readString(); + mAdditionalEncryptId = src.readLong(); + mNfcSignedHash = src.createByteArray(); + mNfcCreationTimestamp = src.readInt() == 1 ? new Date(src.readLong()) : null; + mFailOnMissingEncryptionKeyIds = src.readInt() == 1; + mCharset = src.readString(); + mCleartextSignature = src.readInt() == 1; + mDetachedSignature = src.readInt() == 1; + + mInputUris = src.createTypedArrayList(Uri.CREATOR); + mOutputUris = src.createTypedArrayList(Uri.CREATOR); + mBytes = src.createByteArray(); + + } + + public byte[] getBytes() { + return mBytes; + } + + public void setBytes(byte[] bytes) { + mBytes = bytes; + } + + public List<Uri> getInputUris() { + return Collections.unmodifiableList(mInputUris); + } + + public void addInputUris(Collection<Uri> inputUris) { + mInputUris.addAll(inputUris); + } + + public List<Uri> getOutputUris() { + return Collections.unmodifiableList(mOutputUris); + } + + public void addOutputUris(ArrayList<Uri> outputUris) { + mOutputUris.addAll(outputUris); + } + + @Override + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mVersionHeader); + dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0); + dest.writeInt(mCompressionId); + dest.writeLongArray(mEncryptionMasterKeyIds); + dest.writeString(mSymmetricPassphrase); + dest.writeInt(mSymmetricEncryptionAlgorithm); + dest.writeLong(mSignatureMasterKeyId); + if (mSignatureSubKeyId != null) { + dest.writeInt(1); + dest.writeLong(mSignatureSubKeyId); + } else { + dest.writeInt(0); + } + dest.writeInt(mSignatureHashAlgorithm); + dest.writeString(mSignaturePassphrase); + dest.writeLong(mAdditionalEncryptId); + dest.writeByteArray(mNfcSignedHash); + if (mNfcCreationTimestamp != null) { + dest.writeInt(1); + dest.writeLong(mNfcCreationTimestamp.getTime()); + } else { + dest.writeInt(0); + } + dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0); + dest.writeString(mCharset); + dest.writeInt(mCleartextSignature ? 1 : 0); + dest.writeInt(mDetachedSignature ? 1 : 0); + + dest.writeTypedList(mInputUris); + dest.writeTypedList(mOutputUris); + dest.writeByteArray(mBytes); + } + + public static final Creator<SignEncryptParcel> CREATOR = new Creator<SignEncryptParcel>() { + public SignEncryptParcel createFromParcel(final Parcel source) { + return new SignEncryptParcel(source); + } + + public SignEncryptParcel[] newArray(final int size) { + return new SignEncryptParcel[size]; + } + }; + +} 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 04fb955fa..681aff56d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -35,9 +35,9 @@ import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector; 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.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; @@ -445,7 +445,7 @@ public class UncachedKeyRing { } } - ArrayList<String> processedUserIds = new ArrayList<String>(); + ArrayList<String> processedUserIds = new ArrayList<>(); for (byte[] rawUserId : new IterableIterator<byte[]>(masterKey.getRawUserIDs())) { String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId); @@ -470,7 +470,7 @@ public class UncachedKeyRing { @SuppressWarnings("unchecked") Iterator<PGPSignature> signaturesIt = masterKey.getSignaturesForID(rawUserId); if (signaturesIt != null) { - for (PGPSignature zert : new IterableIterator<PGPSignature>(signaturesIt)) { + for (PGPSignature zert : new IterableIterator<>(signaturesIt)) { WrappedSignature cert = new WrappedSignature(zert); long certId = cert.getKeyId(); @@ -635,7 +635,7 @@ public class UncachedKeyRing { @SuppressWarnings("unchecked") Iterator<PGPSignature> signaturesIt = masterKey.getSignaturesForUserAttribute(userAttribute); if (signaturesIt != null) { - for (PGPSignature zert : new IterableIterator<PGPSignature>(signaturesIt)) { + for (PGPSignature zert : new IterableIterator<>(signaturesIt)) { WrappedSignature cert = new WrappedSignature(zert); long certId = cert.getKeyId(); @@ -778,7 +778,7 @@ public class UncachedKeyRing { } // Keep track of ids we encountered so far - Set<Long> knownIds = new HashSet<Long>(); + Set<Long> knownIds = new HashSet<>(); // Process all keys for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(ring.getPublicKeys())) { @@ -1042,7 +1042,7 @@ public class UncachedKeyRing { } // remember which certs we already added. this is cheaper than semantic deduplication - Set<byte[]> certs = new TreeSet<byte[]>(new Comparator<byte[]>() { + Set<byte[]> certs = new TreeSet<>(new Comparator<byte[]>() { public int compare(byte[] left, byte[] right) { // check for length equality if (left.length != right.length) { @@ -1124,7 +1124,7 @@ public class UncachedKeyRing { if (signaturesIt == null) { continue; } - for (PGPSignature cert : new IterableIterator<PGPSignature>(signaturesIt)) { + for (PGPSignature cert : new IterableIterator<>(signaturesIt)) { // Don't merge foreign stuff into secret keys if (cert.getKeyID() != masterKeyId && isSecret()) { continue; @@ -1149,7 +1149,7 @@ public class UncachedKeyRing { if (signaturesIt == null) { continue; } - for (PGPSignature cert : new IterableIterator<PGPSignature>(signaturesIt)) { + for (PGPSignature cert : new IterableIterator<>(signaturesIt)) { // Don't merge foreign stuff into secret keys if (cert.getKeyID() != masterKeyId && isSecret()) { continue; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index 9e3528515..0173a1d83 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.ECPublicBCPGKey; import org.spongycastle.bcpg.SignatureSubpacketTags; -import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureSubpacketVector; @@ -51,7 +50,7 @@ public class UncachedPublicKey { } /** The revocation signature is NOT checked here, so this may be false! */ - public boolean isRevoked() { + public boolean isMaybeRevoked() { return mPublicKey.getSignaturesOfType(isMasterKey() ? PGPSignature.KEY_REVOCATION : PGPSignature.SUBKEY_REVOCATION).hasNext(); @@ -61,25 +60,8 @@ public class UncachedPublicKey { return mPublicKey.getCreationTime(); } - public Date getExpiryTime() { - long seconds = mPublicKey.getValidSeconds(); - if (seconds > Integer.MAX_VALUE) { - Log.e(Constants.TAG, "error, expiry time too large"); - return null; - } - if (seconds == 0) { - // no expiry - return null; - } - Date creationDate = getCreationTime(); - Calendar calendar = GregorianCalendar.getInstance(); - calendar.setTime(creationDate); - calendar.add(Calendar.SECOND, (int) seconds); - - return calendar.getTime(); - } - - public boolean isExpired() { + /** The revocation signature is NOT checked here, so this may be false! */ + public boolean isMaybeExpired() { Date creationDate = mPublicKey.getCreationTime(); Date expiryDate = mPublicKey.getValidSeconds() > 0 ? new Date(creationDate.getTime() + mPublicKey.getValidSeconds() * 1000) : null; @@ -136,7 +118,7 @@ public class UncachedPublicKey { continue; } - for (PGPSignature sig : new IterableIterator<PGPSignature>(signaturesIt)) { + for (PGPSignature sig : new IterableIterator<>(signaturesIt)) { try { // if this is a revocation, this is not the user id @@ -200,7 +182,7 @@ public class UncachedPublicKey { } public ArrayList<String> getUnorderedUserIds() { - ArrayList<String> userIds = new ArrayList<String>(); + ArrayList<String> userIds = new ArrayList<>(); for (byte[] rawUserId : new IterableIterator<byte[]>(mPublicKey.getRawUserIDs())) { // use our decoding method userIds.add(Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId)); @@ -209,7 +191,7 @@ public class UncachedPublicKey { } public ArrayList<byte[]> getUnorderedRawUserIds() { - ArrayList<byte[]> userIds = new ArrayList<byte[]>(); + ArrayList<byte[]> userIds = new ArrayList<>(); for (byte[] userId : new IterableIterator<byte[]>(mPublicKey.getRawUserIDs())) { userIds.add(userId); } @@ -217,7 +199,7 @@ public class UncachedPublicKey { } public ArrayList<WrappedUserAttribute> getUnorderedUserAttributes() { - ArrayList<WrappedUserAttribute> userAttributes = new ArrayList<WrappedUserAttribute>(); + ArrayList<WrappedUserAttribute> userAttributes = new ArrayList<>(); for (PGPUserAttributeSubpacketVector userAttribute : new IterableIterator<PGPUserAttributeSubpacketVector>(mPublicKey.getUserAttributes())) { userAttributes.add(new WrappedUserAttribute(userAttribute)); @@ -305,28 +287,79 @@ public class UncachedPublicKey { * * Note that this method has package visiblity because it is used in test * cases. Certificates of UncachedPublicKey instances can NOT be assumed to - * be verified, so the result of this method should not be used in other - * places! + * be verified or even by the correct key, so the result of this method + * should never be used in other places! */ @SuppressWarnings("unchecked") Integer getKeyUsage() { if (mCacheUsage == null) { + PGPSignature mostRecentSig = null; for (PGPSignature sig : new IterableIterator<PGPSignature>(mPublicKey.getSignatures())) { if (mPublicKey.isMasterKey() && sig.getKeyID() != mPublicKey.getKeyID()) { continue; } - PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); + switch (sig.getSignatureType()) { + case PGPSignature.DEFAULT_CERTIFICATION: + case PGPSignature.POSITIVE_CERTIFICATION: + case PGPSignature.CASUAL_CERTIFICATION: + case PGPSignature.NO_CERTIFICATION: + case PGPSignature.SUBKEY_BINDING: + break; + // if this is not one of the above types, don't care + default: + continue; + } + + // If we have no sig yet, take the first we can get + if (mostRecentSig == null) { + mostRecentSig = sig; + continue; + } + + // If the new sig is less recent, skip it + if (mostRecentSig.getCreationTime().after(sig.getCreationTime())) { + continue; + } + + // Otherwise, note it down as the new "most recent" one + mostRecentSig = sig; + } + + // Initialize to 0 as cached but empty value, if there is no sig (can't happen + // for canonicalized keyring), or there is no KEY_FLAGS packet in the sig + mCacheUsage = 0; + if (mostRecentSig != null) { + // If a mostRecentSig has been found, (cache and) return its flags + PGPSignatureSubpacketVector hashed = mostRecentSig.getHashedSubPackets(); if (hashed != null && hashed.getSubpacket(SignatureSubpacketTags.KEY_FLAGS) != null) { - // init if at least one key flag subpacket has been found - if (mCacheUsage == null) { - mCacheUsage = 0; - } - mCacheUsage |= hashed.getKeyFlags(); + mCacheUsage = hashed.getKeyFlags(); } } + } return mCacheUsage; } + // this method relies on UNSAFE assumptions about the keyring, and should ONLY be used for + // TEST CASES!! + Date getUnsafeExpiryTimeForTesting () { + long valid = mPublicKey.getValidSeconds(); + + if (valid > Integer.MAX_VALUE) { + Log.e(Constants.TAG, "error, expiry time too large"); + return null; + } + if (valid == 0) { + // no expiry + return null; + } + Date creationDate = getCreationTime(); + Calendar calendar = GregorianCalendar.getInstance(); + calendar.setTime(creationDate); + calendar.add(Calendar.SECOND, (int) valid); + + return calendar.getTime(); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java index cb03970e2..c6fad1a73 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -36,7 +36,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; -import java.security.SignatureException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -79,8 +78,12 @@ public class WrappedSignature { return mSig.getCreationTime(); } + public long getKeyExpirySeconds() { + return mSig.getHashedSubPackets().getKeyExpirationTime(); + } + public ArrayList<WrappedSignature> getEmbeddedSignatures() { - ArrayList<WrappedSignature> sigs = new ArrayList<WrappedSignature>(); + ArrayList<WrappedSignature> sigs = new ArrayList<>(); if (!mSig.hasSubpackets()) { return sigs; } @@ -255,7 +258,7 @@ public class WrappedSignature { } public HashMap<String,String> getNotation() { - HashMap<String,String> result = new HashMap<String,String>(); + HashMap<String,String> result = new HashMap<>(); // If there is any notation data if (mSig.getHashedSubPackets() != null diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java index 370c3f89d..8f967499e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java @@ -1,5 +1,8 @@ /* +<<<<<<< HEAD * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> +======= +>>>>>>> development * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify @@ -33,7 +36,6 @@ import java.io.IOException; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; public class WrappedUserAttribute implements Serializable { |