rewrite PgpDecryptVerify input, introduce PgpDecryptVerifyInputParcel

This commit is contained in:
Vincent Breitmoser
2015-05-30 13:25:47 +02:00
parent 36ecd60c1b
commit 0d8370be1d
10 changed files with 331 additions and 350 deletions

View File

@@ -58,6 +58,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
@@ -66,6 +67,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -73,147 +75,90 @@ import java.net.URLConnection;
import java.security.SignatureException;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
/**
* This class uses a Builder pattern!
*/
public class PgpDecryptVerify extends BaseOperation {
private InputData mData;
private OutputStream mOutStream;
private boolean mAllowSymmetricDecryption;
private Set<Long> mAllowedKeyIds;
private boolean mDecryptMetadataOnly;
private byte[] mDetachedSignature;
private String mRequiredSignerFingerprint;
private boolean mSignedLiteralData;
protected PgpDecryptVerify(Builder builder) {
super(builder.mContext, builder.mProviderHelper, builder.mProgressable);
// private Constructor can only be called from Builder
this.mData = builder.mData;
this.mOutStream = builder.mOutStream;
this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption;
this.mAllowedKeyIds = builder.mAllowedKeyIds;
this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
this.mDetachedSignature = builder.mDetachedSignature;
this.mSignedLiteralData = builder.mSignedLiteralData;
this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
}
public static class Builder {
// mandatory parameter
private Context mContext;
private ProviderHelper mProviderHelper;
private InputData mData;
// optional
private OutputStream mOutStream = null;
private Progressable mProgressable = null;
private boolean mAllowSymmetricDecryption = true;
private Set<Long> mAllowedKeyIds = null;
private boolean mDecryptMetadataOnly = false;
private byte[] mDetachedSignature = null;
private String mRequiredSignerFingerprint = null;
private boolean mSignedLiteralData = false;
public Builder(Context context, ProviderHelper providerHelper,
Progressable progressable,
InputData data, OutputStream outStream) {
mContext = context;
mProviderHelper = providerHelper;
mProgressable = progressable;
mData = data;
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;
}
/**
* Allow these key ids alone for decryption.
* This means only ciphertexts encrypted for one of these private key can be decrypted.
*/
public Builder setAllowedKeyIds(Set<Long> allowedKeyIds) {
mAllowedKeyIds = allowedKeyIds;
return this;
}
/**
* If enabled, the actual decryption/verification of the content will not be executed.
* The metadata only will be decrypted and returned.
*/
public Builder setDecryptMetadataOnly(boolean decryptMetadataOnly) {
mDecryptMetadataOnly = decryptMetadataOnly;
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);
}
public PgpDecryptVerify(Context context, ProviderHelper providerHelper, Progressable progressable) {
super(context, providerHelper, progressable);
}
/**
* Decrypts and/or verifies data based on parameters of class
*/
public DecryptVerifyResult execute(CryptoInputParcel cryptoInput) {
public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput) {
InputData inputData;
OutputStream outputStream;
if (input.getInputBytes() != null) {
byte[] inputBytes = input.getInputBytes();
inputData = new InputData(new ByteArrayInputStream(inputBytes), inputBytes.length);
} else {
try {
InputStream inputStream = mContext.getContentResolver().openInputStream(input.getInputUri());
long inputSize = FileHelper.getFileSize(mContext, input.getInputUri(), 0);
inputData = new InputData(inputStream, inputSize);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
if (input.getOutputUri() == null) {
outputStream = new ByteArrayOutputStream();
} else {
try {
outputStream = mContext.getContentResolver().openOutputStream(input.getOutputUri());
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
DecryptVerifyResult result = executeInternal(input, cryptoInput, inputData, outputStream);
if (outputStream instanceof ByteArrayOutputStream) {
byte[] outputData = ((ByteArrayOutputStream) outputStream).toByteArray();
result.setOutputBytes(outputData);
}
return result;
}
public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
InputData inputData, OutputStream outputStream) {
return executeInternal(input, cryptoInput, inputData, outputStream);
}
private DecryptVerifyResult executeInternal(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
InputData inputData, OutputStream outputStream) {
try {
if (mDetachedSignature != null) {
if (input.getDetachedSignature() != null) {
Log.d(Constants.TAG, "Detached signature present, verifying with this signature only");
return verifyDetachedSignature(mData.getInputStream(), 0);
return verifyDetachedSignature(input, inputData, outputStream, 0);
} else {
// automatically works with PGP ascii armor and PGP binary
InputStream in = PGPUtil.getDecoderStream(mData.getInputStream());
InputStream in = PGPUtil.getDecoderStream(inputData.getInputStream());
if (in instanceof ArmoredInputStream) {
ArmoredInputStream aIn = (ArmoredInputStream) in;
// it is ascii armored
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
if (mSignedLiteralData) {
return verifySignedLiteralData(aIn, 0);
if (input.isSignedLiteralData()) {
return verifySignedLiteralData(input, aIn, outputStream, 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(cryptoInput, in, 0);
return decryptVerify(input, cryptoInput, in, outputStream, 0);
}
} else {
return decryptVerify(cryptoInput, in, 0);
return decryptVerify(input, cryptoInput, in, outputStream, 0);
}
}
} catch (PGPException e) {
@@ -232,7 +177,8 @@ public class PgpDecryptVerify extends BaseOperation {
/**
* Verify Keybase.io style signed literal data
*/
private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent)
private DecryptVerifyResult verifySignedLiteralData(
PgpDecryptVerifyInputParcel input, InputStream in, OutputStream out, int indent)
throws IOException, PGPException {
OperationLog log = new OperationLog();
log.add(LogType.MSG_VL, indent);
@@ -283,9 +229,9 @@ public class PgpDecryptVerify extends BaseOperation {
}
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(signingRing.getFingerprint());
if (!(mRequiredSignerFingerprint.equals(fingerprint))) {
if (!(input.getRequiredSignerFingerprint().equals(fingerprint))) {
log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + mRequiredSignerFingerprint +
Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + input.getRequiredSignerFingerprint() +
" got " + fingerprint + "!");
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
@@ -317,7 +263,7 @@ public class PgpDecryptVerify extends BaseOperation {
int length;
byte[] buffer = new byte[1 << 16];
while ((length = dataIn.read(buffer)) > 0) {
mOutStream.write(buffer, 0, length);
out.write(buffer, 0, length);
signature.update(buffer, 0, length);
}
@@ -363,8 +309,9 @@ public class PgpDecryptVerify extends BaseOperation {
/**
* Decrypt and/or verifies binary or ascii armored pgp
*/
private DecryptVerifyResult decryptVerify(CryptoInputParcel cryptoInput,
InputStream in, int indent) throws IOException, PGPException {
private DecryptVerifyResult decryptVerify(
PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
InputStream in, OutputStream out, int indent) throws IOException, PGPException {
OperationLog log = new OperationLog();
@@ -455,13 +402,13 @@ public class PgpDecryptVerify extends BaseOperation {
}
// allow only specific keys for decryption?
if (mAllowedKeyIds != null) {
if (input.getAllowedKeyIds() != null) {
long masterKeyId = secretKeyRing.getMasterKeyId();
Log.d(Constants.TAG, "encData.getKeyID(): " + subKeyId);
Log.d(Constants.TAG, "mAllowedKeyIds: " + mAllowedKeyIds);
Log.d(Constants.TAG, "mAllowedKeyIds: " + input.getAllowedKeyIds());
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
if (!mAllowedKeyIds.contains(masterKeyId)) {
if (!input.getAllowedKeyIds().contains(masterKeyId)) {
// this key is in our db, but NOT allowed!
// continue with the next packet in the while loop
skippedDisallowedKey = true;
@@ -515,7 +462,7 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_SYM, indent);
if (!mAllowSymmetricDecryption) {
if (!input.isAllowSymmetricDecryption()) {
log.add(LogType.MSG_DC_SYM_SKIP, indent + 1);
continue;
}
@@ -764,7 +711,7 @@ public class PgpDecryptVerify extends BaseOperation {
}
// return here if we want to decrypt the metadata only
if (mDecryptMetadataOnly) {
if (input.isDecryptMetadataOnly()) {
log.add(LogType.MSG_DC_OK_META_ONLY, indent);
DecryptVerifyResult result =
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
@@ -787,13 +734,13 @@ public class PgpDecryptVerify extends BaseOperation {
InputStream dataIn = literalData.getInputStream();
long alreadyWritten = 0;
long wholeSize = mData.getSize() - mData.getStreamPosition();
long wholeSize = 0; // TODO inputData.getSize() - inputData.getStreamPosition();
int length;
byte[] buffer = new byte[1 << 16];
while ((length = dataIn.read(buffer)) > 0) {
Log.d(Constants.TAG, "read bytes: " + length);
if (mOutStream != null) {
mOutStream.write(buffer, 0, length);
// Log.d(Constants.TAG, "read bytes: " + length);
if (out != null) {
out.write(buffer, 0, length);
}
// update signature buffer if signature is also present
@@ -919,8 +866,8 @@ public class PgpDecryptVerify extends BaseOperation {
out.close();
byte[] clearText = out.toByteArray();
if (mOutStream != null) {
mOutStream.write(clearText);
if (out != null) {
out.write(clearText);
}
updateProgress(R.string.progress_processing_signature, 60, 100);
@@ -987,7 +934,8 @@ public class PgpDecryptVerify extends BaseOperation {
return result;
}
private DecryptVerifyResult verifyDetachedSignature(InputStream in, int indent)
private DecryptVerifyResult verifyDetachedSignature(
PgpDecryptVerifyInputParcel input, InputData inputData, OutputStream out, int indent)
throws IOException, PGPException {
OperationLog log = new OperationLog();
@@ -997,7 +945,7 @@ public class PgpDecryptVerify extends BaseOperation {
signatureResultBuilder.setSignatureOnly(true);
updateProgress(R.string.progress_processing_signature, 0, 100);
InputStream detachedSigIn = new ByteArrayInputStream(mDetachedSignature);
InputStream detachedSigIn = new ByteArrayInputStream(input.getDetachedSignature());
detachedSigIn = PGPUtil.getDecoderStream(detachedSigIn);
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(detachedSigIn);
@@ -1022,12 +970,13 @@ public class PgpDecryptVerify extends BaseOperation {
ProgressScaler progressScaler = new ProgressScaler(mProgressable, 60, 90, 100);
long alreadyWritten = 0;
long wholeSize = mData.getSize() - mData.getStreamPosition();
long wholeSize = inputData.getSize() - inputData.getStreamPosition();
int length;
byte[] buffer = new byte[1 << 16];
InputStream in = inputData.getInputStream();
while ((length = in.read(buffer)) > 0) {
if (mOutStream != null) {
mOutStream.write(buffer, 0, length);
if (out != null) {
out.write(buffer, 0, length);
}
// update signature buffer if signature is also present

View File

@@ -0,0 +1,162 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import java.util.HashSet;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
public class PgpDecryptVerifyInputParcel implements Parcelable {
private Uri mInputUri;
private Uri mOutputUri;
private byte[] mInputBytes;
private boolean mAllowSymmetricDecryption;
private HashSet<Long> mAllowedKeyIds;
private boolean mDecryptMetadataOnly;
private byte[] mDetachedSignature;
private String mRequiredSignerFingerprint;
private boolean mSignedLiteralData;
public PgpDecryptVerifyInputParcel() {
}
public PgpDecryptVerifyInputParcel(Uri inputUri, Uri outputUri) {
mInputUri = inputUri;
mOutputUri = outputUri;
}
public PgpDecryptVerifyInputParcel(byte[] inputBytes) {
mInputBytes = inputBytes;
}
PgpDecryptVerifyInputParcel(Parcel source) {
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
mInputUri = source.readParcelable(getClass().getClassLoader());
mOutputUri = source.readParcelable(getClass().getClassLoader());
mInputBytes = source.createByteArray();
mAllowSymmetricDecryption = source.readInt() != 0;
mAllowedKeyIds = (HashSet<Long>) source.readSerializable();
mDecryptMetadataOnly = source.readInt() != 0;
mDetachedSignature = source.createByteArray();
mRequiredSignerFingerprint = source.readString();
mSignedLiteralData = source.readInt() != 0;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mInputUri, 0);
dest.writeParcelable(mOutputUri, 0);
dest.writeByteArray(mInputBytes);
dest.writeInt(mAllowSymmetricDecryption ? 1 : 0);
dest.writeSerializable(mAllowedKeyIds);
dest.writeInt(mDecryptMetadataOnly ? 1 : 0);
dest.writeByteArray(mDetachedSignature);
dest.writeString(mRequiredSignerFingerprint);
dest.writeInt(mSignedLiteralData ? 1 : 0);
}
byte[] getInputBytes() {
return mInputBytes;
}
Uri getInputUri() {
return mInputUri;
}
Uri getOutputUri() {
return mOutputUri;
}
boolean isAllowSymmetricDecryption() {
return mAllowSymmetricDecryption;
}
public PgpDecryptVerifyInputParcel setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
mAllowSymmetricDecryption = allowSymmetricDecryption;
return this;
}
HashSet<Long> getAllowedKeyIds() {
return mAllowedKeyIds;
}
public PgpDecryptVerifyInputParcel setAllowedKeyIds(HashSet<Long> allowedKeyIds) {
mAllowedKeyIds = allowedKeyIds;
return this;
}
boolean isDecryptMetadataOnly() {
return mDecryptMetadataOnly;
}
public PgpDecryptVerifyInputParcel setDecryptMetadataOnly(boolean decryptMetadataOnly) {
mDecryptMetadataOnly = decryptMetadataOnly;
return this;
}
byte[] getDetachedSignature() {
return mDetachedSignature;
}
public PgpDecryptVerifyInputParcel setDetachedSignature(byte[] detachedSignature) {
mDetachedSignature = detachedSignature;
return this;
}
String getRequiredSignerFingerprint() {
return mRequiredSignerFingerprint;
}
public PgpDecryptVerifyInputParcel setRequiredSignerFingerprint(String requiredSignerFingerprint) {
mRequiredSignerFingerprint = requiredSignerFingerprint;
return this;
}
boolean isSignedLiteralData() {
return mSignedLiteralData;
}
public PgpDecryptVerifyInputParcel setSignedLiteralData(boolean signedLiteralData) {
mSignedLiteralData = signedLiteralData;
return this;
}
public static final Creator<PgpDecryptVerifyInputParcel> CREATOR = new Creator<PgpDecryptVerifyInputParcel>() {
public PgpDecryptVerifyInputParcel createFromParcel(final Parcel source) {
return new PgpDecryptVerifyInputParcel(source);
}
public PgpDecryptVerifyInputParcel[] newArray(final int size) {
return new PgpDecryptVerifyInputParcel[size];
}
};
}