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
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 (mDetachedSignature != null) {
|
||||
Log.d(Constants.TAG, "Detached signature present, verifying with this signature only");
|
||||
|
||||
if (in instanceof ArmoredInputStream) {
|
||||
ArmoredInputStream aIn = (ArmoredInputStream) in;
|
||||
// it is ascii armored
|
||||
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
|
||||
return verifyDetachedSignature(mData.getInputStream(), 0);
|
||||
} else {
|
||||
// automatically works with PGP ascii armor and PGP binary
|
||||
InputStream in = PGPUtil.getDecoderStream(mData.getInputStream());
|
||||
|
||||
if (aIn.isClearText()) {
|
||||
// a cleartext signature, verify it with the other method
|
||||
return verifyCleartextSignature(aIn, 0);
|
||||
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();
|
||||
@@ -195,6 +244,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
|
||||
*/
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user