diff options
Diffstat (limited to 'OpenKeychain/src')
10 files changed, 533 insertions, 441 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java index aaff0a07c..f3ceac681 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java @@ -151,7 +151,6 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat              PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable);              PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(messageBytes) -                    .setSignedLiteralData(true)                      .setRequiredSignerFingerprint(requiredFingerprint);              DecryptVerifyResult decryptVerifyResult = op.execute(input, new CryptoInputParcel()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 00c88089a..f959ddd76 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -661,6 +661,7 @@ public abstract class OperationResult implements Parcelable {          MSG_DC_ERROR_INPUT (LogLevel.ERROR, R.string.msg_dc_error_input),          MSG_DC_ERROR_NO_DATA (LogLevel.ERROR, R.string.msg_dc_error_no_data),          MSG_DC_ERROR_NO_KEY (LogLevel.ERROR, R.string.msg_dc_error_no_key), +        MSG_DC_ERROR_NO_SIGNATURE (LogLevel.ERROR, R.string.msg_dc_error_no_signature),          MSG_DC_ERROR_PGP_EXCEPTION (LogLevel.ERROR, R.string.msg_dc_error_pgp_exception),          MSG_DC_INTEGRITY_CHECK_OK (LogLevel.INFO, R.string.msg_dc_integrity_check_ok),          MSG_DC_OK_META_ONLY (LogLevel.OK, R.string.msg_dc_ok_meta_only), @@ -687,6 +688,7 @@ public abstract class OperationResult implements Parcelable {          MSG_VL_ERROR_MISSING_SIGLIST (LogLevel.ERROR, R.string.msg_vl_error_no_siglist),          MSG_VL_ERROR_MISSING_LITERAL (LogLevel.ERROR, R.string.msg_vl_error_missing_literal),          MSG_VL_ERROR_MISSING_KEY (LogLevel.ERROR, R.string.msg_vl_error_wrong_key), +        MSG_VL_ERROR_NO_SIGNATURE (LogLevel.ERROR, R.string.msg_vl_error_no_signature),          MSG_VL_CLEAR_SIGNATURE_CHECK (LogLevel.DEBUG, R.string.msg_vl_clear_signature_check),          MSG_VL_ERROR_INTEGRITY_CHECK (LogLevel.ERROR, R.string.msg_vl_error_integrity_check),          MSG_VL_OK (LogLevel.OK, R.string.msg_vl_ok), @@ -703,7 +705,6 @@ public abstract class OperationResult implements Parcelable {          // pgpsignencrypt          MSG_PSE_ASYMMETRIC (LogLevel.INFO, R.string.msg_pse_asymmetric), -        MSG_PSE_CLEARSIGN_ONLY (LogLevel.DEBUG, R.string.msg_pse_clearsign_only),          MSG_PSE_COMPRESSING (LogLevel.DEBUG, R.string.msg_pse_compressing),          MSG_PSE_ENCRYPTING (LogLevel.DEBUG, R.string.msg_pse_encrypting),          MSG_PSE_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_pse_error_bad_passphrase), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java index 412468a48..476b4e59c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java @@ -44,13 +44,17 @@ import java.util.Iterator;  public class CanonicalizedPublicKey extends UncachedPublicKey {      // this is the parent key ring -    final KeyRing mRing; +    final CanonicalizedKeyRing mRing; -    CanonicalizedPublicKey(KeyRing ring, PGPPublicKey key) { +    CanonicalizedPublicKey(CanonicalizedKeyRing ring, PGPPublicKey key) {          super(key);          mRing = ring;      } +    public CanonicalizedKeyRing getKeyRing() { +        return mRing; +    } +      public IterableIterator<String> getUserIds() {          return new IterableIterator<String>(mPublicKey.getUserIDs());      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index 9d059b58f..2dd1e2dde 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -91,11 +91,12 @@ public class OpenPgpSignatureResultBuilder {          return mInsecure;      } -    public void initValid(CanonicalizedPublicKeyRing signingRing, -                          CanonicalizedPublicKey signingKey) { +    public void initValid(CanonicalizedPublicKey signingKey) {          setSignatureAvailable(true);          setKnownKey(true); +        CanonicalizedKeyRing signingRing = signingKey.getKeyRing(); +          // from RING          setKeyId(signingRing.getMasterKeyId());          try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java index 3eef7759c..bc9b54cd6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java @@ -18,7 +18,7 @@  package org.sufficientlysecure.keychain.pgp; -import java.io.InputStream; +  import java.util.HashSet;  import android.net.Uri; @@ -36,7 +36,6 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {      private boolean mDecryptMetadataOnly;      private byte[] mDetachedSignature;      private String mRequiredSignerFingerprint; -    private boolean mSignedLiteralData;      public PgpDecryptVerifyInputParcel() {      } @@ -61,7 +60,6 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {          mDecryptMetadataOnly = source.readInt() != 0;          mDetachedSignature = source.createByteArray();          mRequiredSignerFingerprint = source.readString(); -        mSignedLiteralData = source.readInt() != 0;      }      @Override @@ -80,7 +78,6 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {          dest.writeInt(mDecryptMetadataOnly ? 1 : 0);          dest.writeByteArray(mDetachedSignature);          dest.writeString(mRequiredSignerFingerprint); -        dest.writeInt(mSignedLiteralData ? 1 : 0);      }      byte[] getInputBytes() { @@ -150,15 +147,6 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {          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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 36b4f5e1e..0709d4f62 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -18,13 +18,23 @@  package org.sufficientlysecure.keychain.pgp; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SignatureException; +import java.util.Date; +import java.util.Iterator; +  import android.content.Context;  import android.support.annotation.NonNull;  import android.webkit.MimeTypeMap;  import org.openintents.openpgp.OpenPgpDecryptionResult;  import org.openintents.openpgp.OpenPgpMetadata; -import org.openintents.openpgp.OpenPgpSignatureResult;  import org.spongycastle.bcpg.ArmoredInputStream;  import org.spongycastle.openpgp.PGPCompressedData;  import org.spongycastle.openpgp.PGPDataValidationException; @@ -33,18 +43,14 @@ import org.spongycastle.openpgp.PGPEncryptedDataList;  import org.spongycastle.openpgp.PGPException;  import org.spongycastle.openpgp.PGPKeyValidationException;  import org.spongycastle.openpgp.PGPLiteralData; -import org.spongycastle.openpgp.PGPOnePassSignature; -import org.spongycastle.openpgp.PGPOnePassSignatureList;  import org.spongycastle.openpgp.PGPPBEEncryptedData;  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.jcajce.CachingDataDecryptorFactory; -import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;  import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;  import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;  import org.spongycastle.util.encoders.DecoderException; @@ -68,17 +74,6 @@ import org.sufficientlysecure.keychain.util.Log;  import org.sufficientlysecure.keychain.util.Passphrase;  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; -import java.security.SignatureException; -import java.util.Date; -import java.util.Iterator; -  public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInputParcel> {      public PgpDecryptVerifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { @@ -153,9 +148,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp                      // it is ascii armored                      Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine()); -                    if (input.isSignedLiteralData()) { -                        return verifySignedLiteralData(input, aIn, outputStream, 0); -                    } else if (aIn.isClearText()) { +                    if (aIn.isClearText()) {                          // a cleartext signature, verify it with the other method                          return verifyCleartextSignature(aIn, outputStream, 0);                      } else { @@ -186,133 +179,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp          }      } -    /**Verify signed plaintext data (PGP/INLINE). */ -    @NonNull -    private DecryptVerifyResult verifySignedLiteralData( -            PgpDecryptVerifyInputParcel input, InputStream in, OutputStream out, 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 (!(input.getRequiredSignerFingerprint().equals(fingerprint))) { -            log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent); -            Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + input.getRequiredSignerFingerprint() + -                    " 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) { -            out.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); - -        // 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.getResult() != OpenPgpSignatureResult.RESULT_VALID_CONFIRMED -                && signatureResult.getResult() != OpenPgpSignatureResult.RESULT_VALID_UNCONFIRMED) { -            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); -        result.setDecryptionResult( -                new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED)); -        return result; -    } -      private static class EncryptStreamResult {          // this is non-null iff an error occured, return directly @@ -368,7 +234,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp              }          } -        OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();          OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder();          JcaPGPObjectFactory plainFact; @@ -415,10 +280,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp          log.add(LogType.MSG_DC_PREP_STREAMS, indent); -        int signatureIndex = -1; -        CanonicalizedPublicKeyRing signingRing = null; -        CanonicalizedPublicKey signingKey = null; -          log.add(LogType.MSG_DC_CLEAR, indent);          indent += 1; @@ -435,58 +296,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp              plainFact = fact;          } -        // resolve leading signature data -        PGPOnePassSignature signature = null; -        if (dataChunk instanceof PGPOnePassSignatureList) { -            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) { -                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..."); -                } -            } - -            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()); -                } -            } - -            // check for insecure signing key -            // TODO: checks on signingRing ? -            if (signingKey != null && ! PgpSecurityConstants.isSecureKey(signingKey)) { -                log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1); -                signatureResultBuilder.setInsecure(true); -            } - +        PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mProviderHelper); +        if (signatureChecker.initializeOnePassSignature(dataChunk, log, indent +1)) {              dataChunk = plainFact.nextObject();          } @@ -567,16 +378,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp              return result;          } -        int endProgress; -        if (signature != null) { -            endProgress = 90; -        } else if (esResult != null && esResult.encryptedData.isIntegrityProtected()) { -            endProgress = 95; -        } else { -            endProgress = 100; -        }          ProgressScaler progressScaler = -                new ProgressScaler(mProgressable, currentProgress, endProgress, 100); +                new ProgressScaler(mProgressable, currentProgress, 95, 100);          InputStream dataIn = literalData.getInputStream(); @@ -591,11 +394,10 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp              }              // update signature buffer if signature is also present -            if (signature != null) { -                signature.update(buffer, 0, length); -            } +            signatureChecker.updateSignatureData(buffer, 0, length);              alreadyWritten += length; +            // noinspection ConstantConditions, TODO progress              if (wholeSize > 0) {                  long progress = 100 * alreadyWritten / wholeSize;                  // stop at 100% for wrong file sizes... @@ -604,34 +406,20 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp                  }                  progressScaler.setProgress((int) progress, 100);              } -            // TODO: slow annealing to fake a progress?          }          metadata = new OpenPgpMetadata(                  originalFilename, mimeType, literalData.getModificationTime().getTime(), alreadyWritten, charset); -        if (signature != null) { -            updateProgress(R.string.progress_verifying_signature, 90, 100); -            log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent); - -            PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject(); -            PGPSignature messageSignature = signatureList.get(signatureIndex); +        if (signatureChecker.isInitialized()) { -            // Verify signature -            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); -            } +            Object o = plainFact.nextObject(); +            boolean signatureCheckOk = signatureChecker.verifySignatureOnePass(o, log, indent + 1); -            // check for insecure hash algorithms -            if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) { -                log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1); -                signatureResultBuilder.setInsecure(true); +            if (!signatureCheckOk) { +                return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);              } -            signatureResultBuilder.setValidSignature(validSignature);          }          indent -= 1; @@ -646,7 +434,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp                      log.add(LogType.MSG_DC_ERROR_INTEGRITY_CHECK, indent);                      return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);                  } -            } else if (signature == null) { +            } else if ( ! signatureChecker.isInitialized() ) {                  // If no signature is present, we *require* an MDC!                  // Handle missing integrity protection like failed integrity protection!                  // The MDC packet can be stripped by an attacker! @@ -663,7 +451,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp          DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);          result.setCachedCryptoInputParcel(cryptoInput); -        result.setSignatureResult(signatureResultBuilder.build()); +        result.setSignatureResult(signatureChecker.getSignatureResult());          result.setDecryptionResult(decryptionResultBuilder.build());          result.setDecryptionMetadata(metadata); @@ -674,13 +462,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp      private EncryptStreamResult handleEncryptedPacket(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,              PGPEncryptedDataList enc, OperationLog log, int indent, int currentProgress) throws PGPException { -        // TODO is this necessary? -        /* -        else if (obj instanceof PGPEncryptedDataList) { -            enc = (PGPEncryptedDataList) pgpF.nextObject(); -        } -        */ -          EncryptStreamResult result = new EncryptStreamResult();          boolean asymmetricPacketFound = false; @@ -721,11 +502,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp                      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); -                    continue; -                }                  // allow only specific keys for decryption?                  if (input.getAllowedKeyIds() != null) { @@ -745,11 +521,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp                  // 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); -                    continue; -                }                  /* secret key exists in database and is allowed! */                  asymmetricPacketFound = true; @@ -951,30 +722,31 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp          OperationLog log = new OperationLog(); -        OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); - -        ByteArrayOutputStream out = new ByteArrayOutputStream(); - -        updateProgress(R.string.progress_reading_data, 0, 100); +        byte[] clearText; +        { // read cleartext +            ByteArrayOutputStream out = new ByteArrayOutputStream(); -        ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); -        int lookAhead = readInputLine(lineOut, aIn); -        byte[] lineSep = getLineSeparator(); +            updateProgress(R.string.progress_reading_data, 0, 100); -        byte[] line = lineOut.toByteArray(); -        out.write(line, 0, getLengthWithoutSeparator(line)); -        out.write(lineSep); +            ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); +            int lookAhead = readInputLine(lineOut, aIn); +            byte[] lineSep = getLineSeparator(); -        while (lookAhead != -1 && aIn.isClearText()) { -            lookAhead = readInputLine(lineOut, lookAhead, aIn); -            line = lineOut.toByteArray(); +            byte[] line = lineOut.toByteArray();              out.write(line, 0, getLengthWithoutSeparator(line));              out.write(lineSep); -        } -        out.close(); +            while (lookAhead != -1 && aIn.isClearText()) { +                lookAhead = readInputLine(lineOut, lookAhead, aIn); +                line = lineOut.toByteArray(); +                out.write(line, 0, getLengthWithoutSeparator(line)); +                out.write(lineSep); +            } + +            out.close(); +            clearText = out.toByteArray(); +        } -        byte[] clearText = out.toByteArray();          if (outputStream != null) {              outputStream.write(clearText);              outputStream.close(); @@ -983,51 +755,20 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp          updateProgress(R.string.progress_processing_signature, 60, 100);          JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(aIn); -        PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); -        if (sigList == null) { +        PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mProviderHelper); + +        Object o = pgpFact.nextObject(); +        if (!signatureChecker.initializeSignature(o, log, indent+1)) {              log.add(LogType.MSG_DC_ERROR_INVALID_DATA, 0);              return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);          } -        PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder, log, indent); - -        if (signature != null) { +        if (signatureChecker.isInitialized()) {              try {                  updateProgress(R.string.progress_verifying_signature, 90, 100); -                log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent); - -                InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText)); - -                lookAhead = readInputLine(lineOut, sigIn); - -                processLine(signature, lineOut.toByteArray()); - -                if (lookAhead != -1) { -                    do { -                        lookAhead = readInputLine(lineOut, lookAhead, sigIn); -                        signature.update((byte) '\r'); -                        signature.update((byte) '\n'); - -                        processLine(signature, lineOut.toByteArray()); -                    } while (lookAhead != -1); -                } - -                // 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); -                } - -                // check for insecure hash algorithms -                if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) { -                    log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1); -                    signatureResultBuilder.setInsecure(true); -                } - -                signatureResultBuilder.setValidSignature(validSignature); +                signatureChecker.updateSignatureWithCleartext(clearText); +                signatureChecker.verifySignature(log, indent);              } catch (SignatureException e) {                  Log.d(Constants.TAG, "SignatureException", e); @@ -1046,7 +787,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp                  clearText.length);          DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); -        result.setSignatureResult(signatureResultBuilder.build()); +        result.setSignatureResult(signatureChecker.getSignatureResult());          result.setDecryptionResult(                  new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));          result.setDecryptionMetadata(metadata); @@ -1060,30 +801,28 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp          OperationLog log = new OperationLog(); -        OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); -          updateProgress(R.string.progress_processing_signature, 0, 100);          InputStream detachedSigIn = new ByteArrayInputStream(input.getDetachedSignature());          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 { +            o = pgpFact.nextObject(); +        } + +        PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mProviderHelper); + +        if ( ! signatureChecker.initializeSignature(o, log, indent+1)) {              log.add(LogType.MSG_DC_ERROR_INVALID_DATA, 0);              return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);          } -        PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder, log, indent); +        if (signatureChecker.isInitialized()) { -        if (signature != null) {              updateProgress(R.string.progress_reading_data, 60, 100);              ProgressScaler progressScaler = new ProgressScaler(mProgressable, 60, 90, 100); @@ -1098,7 +837,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp                  }                  // update signature buffer if signature is also present -                signature.update(buffer, 0, length); +                signatureChecker.updateSignatureData(buffer, 0, length);                  alreadyWritten += length;                  if (wholeSize > 0) { @@ -1109,105 +848,28 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp                      }                      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); -            // 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); -            } - -            // check for insecure hash algorithms -            if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) { -                log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1); -                signatureResultBuilder.setInsecure(true); -            } +            signatureChecker.verifySignature(log, indent); -            signatureResultBuilder.setValidSignature(validSignature);          }          updateProgress(R.string.progress_done, 100, 100);          log.add(LogType.MSG_DC_OK, indent); +        // TODO return metadata object? +          DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); -        result.setSignatureResult(signatureResultBuilder.build()); +        result.setSignatureResult(signatureChecker.getSignatureResult());          result.setDecryptionResult(                  new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));          return result;      } -    private PGPSignature processPGPSignatureList( -            PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder, -            OperationLog log, int indent) -            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()); -            } -        } - -        // check for insecure signing key -        // TODO: checks on signingRing ? -        if (signingKey != null && ! PgpSecurityConstants.isSecureKey(signingKey)) { -            log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1); -            signatureResultBuilder.setInsecure(true); -        } - -        return signature; -    } - -    /** -     * Mostly taken from ClearSignedFileProcessor in Bouncy Castle -     */ -    private static void processLine(PGPSignature sig, byte[] line) -            throws SignatureException { -        int length = getLengthWithoutWhiteSpace(line); -        if (length > 0) { -            sig.update(line, 0, length); -        } -    } -      private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)              throws IOException {          bOut.reset(); @@ -1273,22 +935,9 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp          return b == '\r' || b == '\n';      } -    private static int getLengthWithoutWhiteSpace(byte[] line) { -        int end = line.length - 1; - -        while (end >= 0 && isWhiteSpace(line[end])) { -            end--; -        } - -        return end + 1; -    } - -    private static boolean isWhiteSpace(byte b) { -        return b == '\r' || b == '\n' || b == '\t' || b == ' '; -    } -      private static byte[] getLineSeparator() {          String nl = System.getProperty("line.separator");          return nl.getBytes();      } +  } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 29b2ef727..406c64ae8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -491,9 +491,7 @@ public class PgpSignEncryptOperation extends BaseOperation {                  literalGen.close();              } else { -                pOut = null; -                // TODO: Is this log right? -                log.add(LogType.MSG_PSE_CLEARSIGN_ONLY, indent); +                throw new AssertionError("cannot clearsign in non-ascii armored text, this is a bug!");              }              if (enableSignature) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java new file mode 100644 index 000000000..a892a8a0d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java @@ -0,0 +1,336 @@ +package org.sufficientlysecure.keychain.pgp; + + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.SignatureException; + +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPOnePassSignature; +import org.spongycastle.openpgp.PGPOnePassSignatureList; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureList; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.util.Log; + + +/** This class is used to track the state of a single signature verification. + * + * + */ +class PgpSignatureChecker { + +    OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); + +    private CanonicalizedPublicKey signingKey; + +    private int signatureIndex; +    PGPOnePassSignature onePassSignature; +    PGPSignature signature; + +    ProviderHelper mProviderHelper; + +    PgpSignatureChecker(ProviderHelper providerHelper) { +        mProviderHelper = providerHelper; +    } + +    boolean initializeSignature(Object dataChunk, OperationLog log, int indent) throws PGPException { + +        if (!(dataChunk instanceof PGPSignatureList)) { +            return false; +        } + +        PGPSignatureList sigList = (PGPSignatureList) dataChunk; +        findAvailableSignature(sigList); + +        if (signingKey != null) { + +            // key found in our database! +            signatureResultBuilder.initValid(signingKey); + +            JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = +                    new JcaPGPContentVerifierBuilderProvider() +                            .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); +            signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey()); +            checkKeySecurity(log, indent); + + +        } else if (!sigList.isEmpty()) { + +            signatureResultBuilder.setSignatureAvailable(true); +            signatureResultBuilder.setKnownKey(false); +            signatureResultBuilder.setKeyId(sigList.get(0).getKeyID()); + +        } + +        return true; + +    } + +    boolean initializeOnePassSignature(Object dataChunk, OperationLog log, int indent) throws PGPException { + +        if (!(dataChunk instanceof PGPOnePassSignatureList)) { +            return false; +        } + +        log.add(LogType.MSG_DC_CLEAR_SIGNATURE, indent + 1); + +        PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; +        findAvailableSignature(sigList); + +        if (signingKey != null) { + +            // key found in our database! +            signatureResultBuilder.initValid(signingKey); + +            JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = +                    new JcaPGPContentVerifierBuilderProvider() +                            .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); +            onePassSignature.init(contentVerifierBuilderProvider, signingKey.getPublicKey()); + +            checkKeySecurity(log, indent); + +        } else if (!sigList.isEmpty()) { + +            signatureResultBuilder.setSignatureAvailable(true); +            signatureResultBuilder.setKnownKey(false); +            signatureResultBuilder.setKeyId(sigList.get(0).getKeyID()); + +        } + +        return true; + +    } + +    private void checkKeySecurity(OperationLog log, int indent) { +        // TODO: checks on signingRing ? +        if (!PgpSecurityConstants.isSecureKey(signingKey)) { +            log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1); +            signatureResultBuilder.setInsecure(true); +        } +    } + +    public boolean isInitialized() { +        return signingKey != null; +    } + +    private void findAvailableSignature(PGPOnePassSignatureList sigList) { +        // go through all signatures (should be just one), make sure we have +        //  the key and it matches the one we’re looking for +        for (int i = 0; i < sigList.size(); ++i) { +            try { +                long sigKeyId = sigList.get(i).getKeyID(); +                CanonicalizedPublicKeyRing signingRing = mProviderHelper.getCanonicalizedPublicKeyRing( +                        KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId) +                ); +                signatureIndex = i; +                signingKey = signingRing.getPublicKey(sigKeyId); +                onePassSignature = sigList.get(i); +                return; +            } catch (ProviderHelper.NotFoundException e) { +                Log.d(Constants.TAG, "key not found, trying next signature..."); +            } +        } +    } + +    public void findAvailableSignature(PGPSignatureList sigList) { +        // go through all signatures (should be just one), make sure we have +        //  the key and it matches the one we’re looking for +        for (int i = 0; i < sigList.size(); ++i) { +            try { +                long sigKeyId = sigList.get(i).getKeyID(); +                CanonicalizedPublicKeyRing signingRing = mProviderHelper.getCanonicalizedPublicKeyRing( +                        KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId) +                ); +                signatureIndex = i; +                signingKey = signingRing.getPublicKey(sigKeyId); +                signature = sigList.get(i); +                return; +            } catch (ProviderHelper.NotFoundException e) { +                Log.d(Constants.TAG, "key not found, trying next signature..."); +            } +        } +    } + +    public void updateSignatureWithCleartext(byte[] clearText) throws IOException, SignatureException { + +        InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText)); + +        ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream(); + +        int lookAhead = readInputLine(outputBuffer, sigIn); + +        processLine(signature, outputBuffer.toByteArray()); + +        while (lookAhead != -1) { +            lookAhead = readInputLine(outputBuffer, lookAhead, sigIn); + +            signature.update((byte) '\r'); +            signature.update((byte) '\n'); + +            processLine(signature, outputBuffer.toByteArray()); +        } + +    } + +    public void updateSignatureData(byte[] buf, int off, int len) { +        if (signature != null) { +            signature.update(buf, off, len); +        } else if (onePassSignature != null) { +            onePassSignature.update(buf, off, len); +        } +    } + +    void verifySignature(OperationLog log, int indent) throws PGPException { + +        log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent); + +        // Verify signature +        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); +        } + +        // check for insecure hash algorithms +        if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) { +            log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1); +            signatureResultBuilder.setInsecure(true); +        } + +        signatureResultBuilder.setValidSignature(validSignature); + +    } + +    boolean verifySignatureOnePass(Object o, OperationLog log, int indent) throws PGPException { + +        if (!(o instanceof PGPSignatureList)) { +            log.add(LogType.MSG_DC_ERROR_NO_SIGNATURE, indent); +            return false; +        } +        PGPSignatureList signatureList = (PGPSignatureList) o; +        if (signatureList.size() <= signatureIndex) { +            log.add(LogType.MSG_DC_ERROR_NO_SIGNATURE, indent); +            return false; +        } + +        // PGPOnePassSignature and PGPSignature packets are "bracketed", +        // so we need to take the last-minus-index'th element here +        PGPSignature messageSignature = signatureList.get(signatureList.size() - 1 - signatureIndex); + +        // Verify signature +        boolean validSignature = onePassSignature.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); +        } + +        // check for insecure hash algorithms +        if (!PgpSecurityConstants.isSecureHashAlgorithm(onePassSignature.getHashAlgorithm())) { +            log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1); +            signatureResultBuilder.setInsecure(true); +        } + +        signatureResultBuilder.setValidSignature(validSignature); + +        return true; + +    } + +    public byte[] getSigningFingerprint() { +        return signingKey.getFingerprint(); +    } + +    public OpenPgpSignatureResult getSignatureResult() { +        return signatureResultBuilder.build(); +    } + +    /** +     * Mostly taken from ClearSignedFileProcessor in Bouncy Castle +     */ + +    private static void processLine(PGPSignature sig, byte[] line) +            throws SignatureException { +        int length = getLengthWithoutWhiteSpace(line); +        if (length > 0) { +            sig.update(line, 0, length); +        } +    } + +    private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) +            throws IOException { +        bOut.reset(); + +        int lookAhead = -1; +        int ch; + +        while ((ch = fIn.read()) >= 0) { +            bOut.write(ch); +            if (ch == '\r' || ch == '\n') { +                lookAhead = readPastEOL(bOut, ch, fIn); +                break; +            } +        } + +        return lookAhead; +    } + +    private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) +            throws IOException { +        bOut.reset(); + +        int ch = lookAhead; + +        do { +            bOut.write(ch); +            if (ch == '\r' || ch == '\n') { +                lookAhead = readPastEOL(bOut, ch, fIn); +                break; +            } +        } while ((ch = fIn.read()) >= 0); + +        if (ch < 0) { +            lookAhead = -1; +        } + +        return lookAhead; +    } + +    private static int readPastEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) +            throws IOException { +        int lookAhead = fIn.read(); + +        if (lastCh == '\r' && lookAhead == '\n') { +            bOut.write(lookAhead); +            lookAhead = fIn.read(); +        } + +        return lookAhead; +    } + +    private static int getLengthWithoutWhiteSpace(byte[] line) { +        int end = line.length - 1; + +        while (end >= 0 && isWhiteSpace(line[end])) { +            end--; +        } + +        return end + 1; +    } + +    private static boolean isWhiteSpace(byte b) { +        return b == '\r' || b == '\n' || b == '\t' || b == ' '; +    } + +} diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index e39132f42..03493d697 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -436,7 +436,6 @@      <string name="progress_decrypting">"decrypting data…"</string>      <string name="progress_preparing_signature">"preparing signature…"</string>      <string name="progress_generating_signature">"generating signature…"</string> -    <string name="progress_processing_signature">"processing signature…"</string>      <string name="progress_verifying_signature">"verifying signature…"</string>      <string name="progress_signing">"signing…"</string>      <string name="progress_certifying">"certifying…"</string> @@ -1174,6 +1173,7 @@      <string name="msg_dc_error_input">"Error opening input data stream!"</string>      <string name="msg_dc_error_no_data">"No encrypted data found in stream!"</string>      <string name="msg_dc_error_no_key">"No encrypted data with known secret key found in stream!"</string> +    <string name="msg_dc_error_no_signature">"Missing signature data!"</string>      <string name="msg_dc_error_pgp_exception">"Encountered OpenPGP Exception during operation!"</string>      <string name="msg_dc_integrity_check_ok">"Integrity check OK!"</string>      <string name="msg_dc_ok_meta_only">"Only metadata was requested, skipping decryption"</string> @@ -1197,8 +1197,9 @@      <!-- Messages for VerifySignedLiteralData operation -->      <string name="msg_vl">"Starting signature check"</string> -    <string name="msg_vl_error_no_siglist">"No signature list in signed literal data"</string> -    <string name="msg_vl_error_wrong_key">"Message not signed with right key"</string> +    <string name="msg_vl_error_no_siglist">"No signature list in signed literal data!"</string> +    <string name="msg_vl_error_wrong_key">"Message not signed with expected key!"</string> +    <string name="msg_vl_error_no_signature">"Missing signature data!"</string>      <string name="msg_vl_error_missing_literal">"No payload in signed literal data"</string>      <string name="msg_vl_clear_meta_file">"Filename: %s"</string>      <string name="msg_vl_clear_meta_mime">"MIME type: %s"</string> @@ -1220,7 +1221,6 @@      <!-- Messages for PgpSignEncrypt operation -->      <string name="msg_pse_asymmetric">"Preparing public keys for encryption"</string> -    <string name="msg_pse_clearsign_only">"Signing of cleartext input not supported!"</string>      <string name="msg_pse_compressing">"Preparing compression"</string>      <string name="msg_pse_encrypting">"Encrypting data"</string>      <string name="msg_pse_error_bad_passphrase">"Bad password!"</string> diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index 9f2d4e015..be233d0b3 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -27,6 +27,7 @@ import java.util.Date;  import java.util.HashSet;  import java.util.Iterator; +import org.apache.tools.ant.util.StringUtils;  import org.junit.Assert;  import org.junit.Before;  import org.junit.BeforeClass; @@ -287,7 +288,7 @@ public class PgpEncryptDecryptTest {      }      @Test -    public void testAsymmetricSign() { +    public void testAsymmetricSignLiteral() {          String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true);          byte[] ciphertext; @@ -341,6 +342,121 @@ public class PgpEncryptDecryptTest {      }      @Test +    public void testAsymmetricSignCleartext() { + +        String plaintext = "dies ist ein\r\nplaintext\n ☭" + TestingUtils.genPassphrase(true); +        byte[] ciphertext; + +        { // encrypt data with key +            ByteArrayOutputStream out = new ByteArrayOutputStream(); +            ByteArrayInputStream in = new ByteArrayInputStream(plaintext.getBytes()); + +            PgpSignEncryptOperation op = new PgpSignEncryptOperation(RuntimeEnvironment.application, +                    new ProviderHelper(RuntimeEnvironment.application), null); + +            InputData data = new InputData(in, in.available()); +            PgpSignEncryptInputParcel input = new PgpSignEncryptInputParcel(); + +            // only sign, as cleartext +            input.setSignatureMasterKeyId(mStaticRing1.getMasterKeyId()); +            input.setSignatureSubKeyId(KeyringTestingHelper.getSubkeyId(mStaticRing1, 1)); +            input.setCleartextSignature(true); +            input.setEnableAsciiArmorOutput(true); +            input.setDetachedSignature(false); + +            PgpSignEncryptResult result = op.execute(input, new CryptoInputParcel(mKeyPhrase1), data, out); +            Assert.assertTrue("signing must succeed", result.success()); + +            ciphertext = out.toByteArray(); +        } + +        Assert.assertTrue("clearsigned text must contain plaintext (ignoring newlines)", +                new String(ciphertext).replace("\r\n", "").contains(plaintext.replace("\r", "").replace("\n", ""))); + +        { // verification should succeed + +            ByteArrayOutputStream out = new ByteArrayOutputStream(); +            ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); +            InputData data = new InputData(in, in.available()); + +            PgpDecryptVerifyOperation op = operationWithFakePassphraseCache(null, null, null); +            PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(); +            DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(), data, out); + +            Assert.assertTrue("verification must succeed", result.success()); + +            Assert.assertTrue("verification text should equal plaintext (ignoring newlines)", +                    new String(out.toByteArray()).replace(StringUtils.LINE_SEP, "") +                            .equals(plaintext.replace("\r", "").replace("\n", ""))); +            Assert.assertEquals("decryptionResult should be RESULT_NOT_ENCRYPTED", +                    OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED, result.getDecryptionResult().getResult()); +            Assert.assertEquals("signatureResult should be RESULT_VALID_CONFIRMED", +                    OpenPgpSignatureResult.RESULT_VALID_CONFIRMED, result.getSignatureResult().getResult()); + +            OpenPgpMetadata metadata = result.getDecryptionMetadata(); +            Assert.assertEquals("filesize must be correct", +                    out.toByteArray().length, metadata.getOriginalSize()); + +        } + +    } + +    @Test +    public void testAsymmetricSignDetached() { + +        String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true); +        byte[] detachedSignature; + +        { // encrypt data with key +            ByteArrayOutputStream out = new ByteArrayOutputStream(); +            ByteArrayInputStream in = new ByteArrayInputStream(plaintext.getBytes()); + +            PgpSignEncryptOperation op = new PgpSignEncryptOperation(RuntimeEnvironment.application, +                    new ProviderHelper(RuntimeEnvironment.application), null); + +            InputData data = new InputData(in, in.available()); +            PgpSignEncryptInputParcel input = new PgpSignEncryptInputParcel(); + +            // only sign, as cleartext +            input.setSignatureMasterKeyId(mStaticRing1.getMasterKeyId()); +            input.setSignatureSubKeyId(KeyringTestingHelper.getSubkeyId(mStaticRing1, 1)); +            input.setDetachedSignature(true); + +            PgpSignEncryptResult result = op.execute(input, new CryptoInputParcel(mKeyPhrase1), data, out); +            Assert.assertTrue("signing must succeed", result.success()); + +            detachedSignature = result.getDetachedSignature(); +        } + +        { // verification should succeed + +            ByteArrayOutputStream out = new ByteArrayOutputStream(); +            ByteArrayInputStream in = new ByteArrayInputStream(plaintext.getBytes()); +            InputData data = new InputData(in, in.available()); + +            PgpDecryptVerifyOperation op = operationWithFakePassphraseCache(null, null, null); +            PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(); +            input.setDetachedSignature(detachedSignature); +            DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(), data, out); + +            Assert.assertTrue("verification must succeed", result.success()); +            Assert.assertArrayEquals("verification text should equal plaintext (save for a newline)", +                    plaintext.getBytes(), out.toByteArray()); +            Assert.assertEquals("decryptionResult should be RESULT_NOT_ENCRYPTED", +                    OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED, result.getDecryptionResult().getResult()); +            Assert.assertEquals("signatureResult should be RESULT_VALID_CONFIRMED", +                    OpenPgpSignatureResult.RESULT_VALID_CONFIRMED, result.getSignatureResult().getResult()); + +            // TODO should detached verify return any metadata? +            // OpenPgpMetadata metadata = result.getDecryptionMetadata(); +            // Assert.assertEquals("filesize must be correct", +                    // out.toByteArray().length, metadata.getOriginalSize()); + +        } + +    } + +    @Test      public void testAsymmetricEncryptDecrypt() {          String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true);  | 
