diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java index 57daf3430..c336f8502 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java @@ -37,6 +37,7 @@ public class SignEncryptResult extends OperationResult { int mNfcAlgo; Date mNfcTimestamp; String mNfcPassphrase; + byte[] mDetachedSignature; public long getKeyIdPassphraseNeeded() { return mKeyIdPassphraseNeeded; @@ -54,6 +55,10 @@ public class SignEncryptResult extends OperationResult { mNfcPassphrase = passphrase; } + public void setDetachedSignature(byte[] detachedSignature) { + mDetachedSignature = detachedSignature; + } + public long getNfcKeyId() { return mNfcKeyId; } @@ -74,6 +79,10 @@ public class SignEncryptResult extends OperationResult { return mNfcPassphrase; } + public byte[] getDetachedSignature() { + return mDetachedSignature; + } + public boolean isPending() { return (mResult & RESULT_PENDING) == RESULT_PENDING; } @@ -87,6 +96,7 @@ public class SignEncryptResult extends OperationResult { mNfcHash = source.readInt() != 0 ? source.createByteArray() : null; mNfcAlgo = source.readInt(); mNfcTimestamp = source.readInt() != 0 ? new Date(source.readLong()) : null; + mDetachedSignature = source.readInt() != 0 ? source.createByteArray() : null; } public int describeContents() { @@ -108,6 +118,12 @@ public class SignEncryptResult extends OperationResult { } else { dest.writeInt(0); } + if (mDetachedSignature != null) { + dest.writeInt(1); + dest.writeByteArray(mDetachedSignature); + } else { + dest.writeInt(0); + } } public static final Creator CREATOR = new Creator() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index cac5c1d97..40f2f48ad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -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; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index 6c987f484..a69c5fe36 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -28,7 +28,6 @@ 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,6 +35,7 @@ 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; @@ -83,6 +83,7 @@ public class PgpDecryptVerify extends BaseOperation { private Set mAllowedKeyIds; private boolean mDecryptMetadataOnly; private byte[] mDecryptedSessionKey; + private byte[] mDetachedSignature; protected PgpDecryptVerify(Builder builder) { super(builder.mContext, builder.mProviderHelper, builder.mProgressable); @@ -96,6 +97,7 @@ public class PgpDecryptVerify extends BaseOperation { this.mAllowedKeyIds = builder.mAllowedKeyIds; this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly; this.mDecryptedSessionKey = builder.mDecryptedSessionKey; + this.mDetachedSignature = builder.mDetachedSignature; } public static class Builder { @@ -103,15 +105,16 @@ 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 mAllowedKeyIds = null; private boolean mDecryptMetadataOnly = false; private byte[] mDecryptedSessionKey = null; + private byte[] mDetachedSignature = null; public Builder(Context context, ProviderHelper providerHelper, Progressable progressable, @@ -156,6 +159,17 @@ public class PgpDecryptVerify extends BaseOperation { return this; } + /** + * If detachedSignature != null, it will be used exclusively to verify the signature + * + * @param detachedSignature + * @return + */ + public Builder setDetachedSignature(byte[] detachedSignature) { + mDetachedSignature = detachedSignature; + return this; + } + public PgpDecryptVerify build() { return new PgpDecryptVerify(this); } @@ -166,22 +180,30 @@ 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 (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(); @@ -205,7 +227,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(); @@ -238,15 +260,18 @@ public class PgpDecryptVerify extends BaseOperation { // https://tools.ietf.org/html/rfc4880#page56 String charset = null; if (in instanceof ArmoredInputStream) { - for (String header : ((ArmoredInputStream) in).getArmorHeaders()) { - String[] pieces = header.split(":", 2); - if (pieces.length == 2 && "charset".equalsIgnoreCase(pieces[0])) { - charset = pieces[1].trim(); - break; + 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); } - } - if (charset != null) { - log.add(LogType.MSG_DC_CHARSET, indent, charset); } } @@ -273,19 +298,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; } @@ -299,7 +324,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; } } @@ -314,15 +339,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); @@ -338,8 +363,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; } @@ -354,7 +379,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); } @@ -399,13 +424,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); } @@ -417,7 +442,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); @@ -428,11 +453,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; @@ -443,25 +468,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) { @@ -507,7 +534,7 @@ 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); @@ -549,12 +576,12 @@ public class PgpDecryptVerify extends BaseOperation { literalData.getModificationTime().getTime(), originalSize); - if ( ! originalFilename.equals("")) { + if (!originalFilename.equals("")) { 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) { log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1, @@ -589,7 +616,9 @@ public class PgpDecryptVerify extends BaseOperation { int length; byte[] buffer = new byte[1 << 16]; while ((length = dataIn.read(buffer)) > 0) { - mOutStream.write(buffer, 0, length); + if (mOutStream != null) { + mOutStream.write(buffer, 0, length); + } // update signature buffer if signature is also present if (signature != null) { @@ -623,9 +652,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); } @@ -687,7 +716,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); @@ -707,10 +736,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) { @@ -718,45 +749,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 { @@ -804,6 +797,134 @@ 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); + } else { + // 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 */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java index c2fa811bd..060db96b4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java @@ -50,6 +50,7 @@ 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; @@ -78,7 +79,8 @@ public class PgpSignEncrypt extends BaseOperation { private int mSignatureHashAlgorithm; private String mSignaturePassphrase; private long mAdditionalEncryptId; - private boolean mCleartextInput; + private boolean mCleartextSignature; + private boolean mDetachedSignature; private String mOriginalFilename; private boolean mFailOnMissingEncryptionKeyIds; private String mCharset; @@ -114,7 +116,8 @@ public class PgpSignEncrypt extends BaseOperation { this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm; this.mSignaturePassphrase = builder.mSignaturePassphrase; this.mAdditionalEncryptId = builder.mAdditionalEncryptId; - this.mCleartextInput = builder.mCleartextInput; + this.mCleartextSignature = builder.mCleartextSignature; + this.mDetachedSignature = builder.mDetachedSignature; this.mNfcSignedHash = builder.mNfcSignedHash; this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp; this.mOriginalFilename = builder.mOriginalFilename; @@ -142,7 +145,8 @@ public class PgpSignEncrypt extends BaseOperation { private int mSignatureHashAlgorithm = 0; private String mSignaturePassphrase = null; private long mAdditionalEncryptId = Constants.key.none; - private boolean mCleartextInput = false; + private boolean mCleartextSignature = false; + private boolean mDetachedSignature = false; private String mOriginalFilename = ""; private byte[] mNfcSignedHash = null; private Date mNfcCreationTimestamp = null; @@ -230,14 +234,13 @@ public class PgpSignEncrypt extends BaseOperation { return this; } - /** - * TODO: test this option! - * - * @param cleartextInput - * @return - */ - public Builder setCleartextInput(boolean cleartextInput) { - mCleartextInput = cleartextInput; + public Builder setCleartextSignature(boolean cleartextSignature) { + mCleartextSignature = cleartextSignature; + return this; + } + + public Builder setDetachedSignature(boolean detachedSignature) { + mDetachedSignature = detachedSignature; return this; } @@ -420,7 +423,7 @@ public class PgpSignEncrypt extends BaseOperation { updateProgress(R.string.progress_preparing_signature, 4, 100); try { - boolean cleartext = mCleartextInput && mEnableAsciiArmorOutput && !enableEncryption; + boolean cleartext = mCleartextSignature && mEnableAsciiArmorOutput && !enableEncryption; signatureGenerator = signingKey.getSignatureGenerator( mSignatureHashAlgorithm, cleartext, mNfcSignedHash, mNfcCreationTimestamp); } catch (PgpGeneralException e) { @@ -436,6 +439,10 @@ public class PgpSignEncrypt extends BaseOperation { OutputStream encryptionOut = null; BCPGOutputStream bcpgOut; + ByteArrayOutputStream detachedByteOut = null; + ArmoredOutputStream detachedArmorOut = null; + BCPGOutputStream detachedBcpgOut = null; + try { if (enableEncryption) { @@ -464,7 +471,7 @@ public class PgpSignEncrypt extends BaseOperation { PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); char literalDataFormatTag; - if (mCleartextInput) { + if (mCleartextSignature) { literalDataFormatTag = PGPLiteralData.UTF8; } else { literalDataFormatTag = PGPLiteralData.BINARY; @@ -494,7 +501,7 @@ public class PgpSignEncrypt extends BaseOperation { literalGen.close(); indent -= 1; - } else if (enableSignature && mCleartextInput && mEnableAsciiArmorOutput) { + } else if (enableSignature && mCleartextSignature && mEnableAsciiArmorOutput) { /* cleartext signature: sign-only of ascii text */ updateProgress(R.string.progress_signing, 8, 100); @@ -529,11 +536,48 @@ public class PgpSignEncrypt extends BaseOperation { armorOut.endClearText(); pOut = new BCPGOutputStream(armorOut); - } else if (enableSignature && !mCleartextInput) { + } else if (enableSignature && mDetachedSignature) { + /* detached signature */ + + updateProgress(R.string.progress_signing, 8, 100); + log.add(LogType.MSG_SE_SIGNING, indent); + + InputStream in = mData.getInputStream(); + + // handle output stream separately for detached signatures + detachedByteOut = new ByteArrayOutputStream(); + OutputStream detachedOut = detachedByteOut; + if (mEnableAsciiArmorOutput) { + detachedArmorOut = new ArmoredOutputStream(detachedOut); + if (mVersionHeader != null) { + detachedArmorOut.setHeader("Version", mVersionHeader); + } + + 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 (mData.getSize() > 0) { + long progress = 100 * alreadyWritten / mData.getSize(); + progressScaler.setProgress((int) progress, 100); + } + } + + pOut = null; + } else if (enableSignature && !mCleartextSignature && !mDetachedSignature) { /* sign-only binary (files/data stream) */ updateProgress(R.string.progress_signing, 8, 100); - log.add(LogType.MSG_SE_ENCRYPTING, indent); + log.add(LogType.MSG_SE_SIGNING, indent); InputStream in = mData.getInputStream(); @@ -568,13 +612,18 @@ public class PgpSignEncrypt extends BaseOperation { literalGen.close(); } else { pOut = null; + // TODO: Is this log right? log.add(LogType.MSG_SE_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); @@ -583,27 +632,38 @@ public class PgpSignEncrypt extends BaseOperation { // 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)); + 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 (mOutStream != null) { + mOutStream.close(); + } } catch (SignatureException e) { log.add(LogType.MSG_SE_ERROR_SIG, indent); @@ -619,10 +679,22 @@ public class PgpSignEncrypt extends BaseOperation { updateProgress(R.string.progress_done, 100, 100); log.add(LogType.MSG_SE_OK, indent); - return new SignEncryptResult(SignEncryptResult.RESULT_OK, log); - + SignEncryptResult result = new SignEncryptResult(SignEncryptResult.RESULT_OK, log); + if (detachedByteOut != null) { + try { + detachedByteOut.flush(); + detachedByteOut.close(); + } catch (IOException e) { + // silently catch + } + result.setDetachedSignature(detachedByteOut.toByteArray()); + } + return result; } + /** + * Remove whitespaces on line endings + */ private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, final PGPSignatureGenerator pSignatureGenerator) throws IOException, SignatureException { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 68677fb9c..d967931ce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -222,9 +222,10 @@ public class OpenPgpService extends RemoteService { } private Intent signImpl(Intent data, ParcelFileDescriptor input, - ParcelFileDescriptor output, AccountSettings accSettings) { + ParcelFileDescriptor output, AccountSettings accSettings, + boolean cleartextSign) { try { - boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); + boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH); if (nfcSignedHash != null) { @@ -243,7 +244,12 @@ public class OpenPgpService extends RemoteService { // Get Input- and OutputStream from ParcelFileDescriptor InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input); - OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output); + OutputStream os = null; + if (cleartextSign) { + // output stream only needed for cleartext signatures, + // detached signatures are returned as extra + os = new ParcelFileDescriptor.AutoCloseOutputStream(output); + } try { long inputLength = is.available(); InputData inputData = new InputData(is, inputLength); @@ -284,6 +290,8 @@ public class OpenPgpService extends RemoteService { inputData, os ); builder.setEnableAsciiArmorOutput(asciiArmor) + .setCleartextSignature(cleartextSign) + .setDetachedSignature(!cleartextSign) .setVersionHeader(PgpHelper.getVersionForHeader(this)) .setSignatureHashAlgorithm(accSettings.getHashAlgorithm()) .setSignatureMasterKeyId(accSettings.getKeyId()) @@ -291,9 +299,6 @@ public class OpenPgpService extends RemoteService { .setSignaturePassphrase(passphrase) .setNfcState(nfcSignedHash, nfcCreationDate); - // TODO: currently always assume cleartext input, no sign-only of binary currently! - builder.setCleartextInput(true); - // execute PGP operation! SignEncryptResult pgpResult = builder.build().execute(); @@ -313,20 +318,22 @@ public class OpenPgpService extends RemoteService { "Encountered unhandled type of pending action not supported by API!"); } } else if (pgpResult.success()) { - // see end of method + Intent result = new Intent(); + if (!cleartextSign) { + result.putExtra(OpenPgpApi.RESULT_DETACHED_SIGNATURE, pgpResult.getDetachedSignature()); + } + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + return result; } else { LogEntryParcel errorMsg = pgpResult.getLog().getLast(); throw new Exception(getString(errorMsg.mType.getMsgId())); } - } finally { is.close(); - os.close(); + if (os != null) { + os.close(); + } } - - Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); - return result; } catch (Exception e) { Log.d(Constants.TAG, "signImpl", e); Intent result = new Intent(); @@ -444,7 +451,9 @@ public class OpenPgpService extends RemoteService { "Encountered unhandled type of pending action not supported by API!"); } } else if (pgpResult.success()) { - // see end of method + Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + return result; } else { LogEntryParcel errorMsg = pgpResult.getLog().getLast(); throw new Exception(getString(errorMsg.mType.getMsgId())); @@ -454,10 +463,6 @@ public class OpenPgpService extends RemoteService { is.close(); os.close(); } - - Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); - return result; } catch (Exception e) { Log.d(Constants.TAG, "encryptAndSignImpl", e); Intent result = new Intent(); @@ -476,13 +481,13 @@ public class OpenPgpService extends RemoteService { InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input); OutputStream os; - if (decryptMetadataOnly) { + // output is optional, e.g., for verifying detached signatures + if (decryptMetadataOnly || output == null) { os = null; } else { os = new ParcelFileDescriptor.AutoCloseOutputStream(output); } - Intent result = new Intent(); try { String passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); long inputLength = is.available(); @@ -494,15 +499,17 @@ public class OpenPgpService extends RemoteService { byte[] nfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY); + byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE); + // allow only private keys associated with accounts of this app // no support for symmetric encryption builder.setPassphrase(passphrase) .setAllowSymmetricDecryption(false) .setAllowedKeyIds(allowedKeyIds) .setDecryptMetadataOnly(decryptMetadataOnly) - .setNfcState(nfcDecryptedSessionKey); + .setNfcState(nfcDecryptedSessionKey) + .setDetachedSignature(detachedSignature); - // TODO: currently does not support binary signed-only content DecryptVerifyResult pgpResult = builder.build().execute(); if (pgpResult.isPending()) { @@ -522,6 +529,7 @@ public class OpenPgpService extends RemoteService { "Encountered unhandled type of pending action not supported by API!"); } } else if (pgpResult.success()) { + Intent result = new Intent(); OpenPgpSignatureResult signatureResult = pgpResult.getSignatureResult(); if (signatureResult != null) { @@ -563,6 +571,8 @@ public class OpenPgpService extends RemoteService { result.putExtra(OpenPgpApi.RESULT_CHARSET, charset); } + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + return result; } else { LogEntryParcel errorMsg = pgpResult.getLog().getLast(); throw new Exception(getString(errorMsg.mType.getMsgId())); @@ -573,9 +583,6 @@ public class OpenPgpService extends RemoteService { os.close(); } } - - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); - return result; } catch (Exception e) { Log.d(Constants.TAG, "decryptAndVerifyImpl", e); Intent result = new Intent(); @@ -674,15 +681,16 @@ public class OpenPgpService extends RemoteService { // version code is required and needs to correspond to version code of service! // History of versions in org.openintents.openpgp.util.OpenPgpApi - // we support 3, 4, 5 + // we support 3, 4, 5, 6 if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 3 && data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 4 - && data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 5) { + && data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 5 + && data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 6) { Intent result = new Intent(); OpenPgpError error = new OpenPgpError (OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!\n" + "used API version: " + data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) + "\n" - + "supported API versions: 3, 4"); + + "supported API versions: 3, 4, 5, 6"); result.putExtra(OpenPgpApi.RESULT_ERROR, error); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; @@ -724,33 +732,36 @@ public class OpenPgpService extends RemoteService { } String action = data.getAction(); - switch (action) { - case OpenPgpApi.ACTION_SIGN: - return signImpl(data, input, output, accSettings); - case OpenPgpApi.ACTION_ENCRYPT: - return encryptAndSignImpl(data, input, output, accSettings, false); - case OpenPgpApi.ACTION_SIGN_AND_ENCRYPT: - return encryptAndSignImpl(data, input, output, accSettings, true); - case OpenPgpApi.ACTION_DECRYPT_VERIFY: { - String currentPkg = getCurrentCallingPackage(); - Set allowedKeyIds = - mProviderHelper.getAllKeyIdsForApp( - ApiAccounts.buildBaseUri(currentPkg)); - return decryptAndVerifyImpl(data, input, output, allowedKeyIds, false); - } - case OpenPgpApi.ACTION_DECRYPT_METADATA: { - String currentPkg = getCurrentCallingPackage(); - Set allowedKeyIds = - mProviderHelper.getAllKeyIdsForApp( - ApiAccounts.buildBaseUri(currentPkg)); - return decryptAndVerifyImpl(data, input, output, allowedKeyIds, true); - } - case OpenPgpApi.ACTION_GET_KEY: - return getKeyImpl(data); - case OpenPgpApi.ACTION_GET_KEY_IDS: - return getKeyIdsImpl(data); - default: - return null; + if (OpenPgpApi.ACTION_CLEARTEXT_SIGN.equals(action)) { + return signImpl(data, input, output, accSettings, true); + } else if (OpenPgpApi.ACTION_SIGN.equals(action)) { + // DEPRECATED: same as ACTION_CLEARTEXT_SIGN + Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!"); + return signImpl(data, input, output, accSettings, true); + } else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(action)) { + return signImpl(data, input, output, accSettings, false); + } else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) { + return encryptAndSignImpl(data, input, output, accSettings, false); + } else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) { + return encryptAndSignImpl(data, input, output, accSettings, true); + } else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) { + String currentPkg = getCurrentCallingPackage(); + Set allowedKeyIds = + mProviderHelper.getAllKeyIdsForApp( + ApiAccounts.buildBaseUri(currentPkg)); + return decryptAndVerifyImpl(data, input, output, allowedKeyIds, false); + } else if (OpenPgpApi.ACTION_DECRYPT_METADATA.equals(action)) { + String currentPkg = getCurrentCallingPackage(); + Set allowedKeyIds = + mProviderHelper.getAllKeyIdsForApp( + ApiAccounts.buildBaseUri(currentPkg)); + return decryptAndVerifyImpl(data, input, output, allowedKeyIds, true); + } else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) { + return getKeyImpl(data); + } else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) { + return getKeyIdsImpl(data); + } else { + return null; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index c88e0bd8d..515711d1f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -486,11 +486,6 @@ public class KeychainIntentService extends IntentService implements Progressable // TODO Just silently drop the requested signature? Shouldn't we throw here? } - // this assumes that the bytes are cleartext (valid for current implementation!) - if (source == IO_BYTES) { - builder.setCleartextInput(true); - } - SignEncryptResult result = builder.build().execute(); resultData.putParcelable(SignEncryptResult.EXTRA_RESULT, result); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index a84461a92..21377bcd2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -34,13 +34,13 @@ import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker; import java.util.regex.Matcher; -public class DecryptActivity extends DrawerActivity { +public class DecryptActivity extends BaseActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - activateDrawerNavigation(savedInstanceState); +// activateDrawerNavigation(savedInstanceState); View actionFile = findViewById(R.id.decrypt_files); View actionFromClipboard = findViewById(R.id.decrypt_from_clipboard); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index 11780e761..611078f95 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -14,7 +14,7 @@ import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import java.util.Date; -public abstract class EncryptActivity extends NavDrawerActivity { +public abstract class EncryptActivity extends BaseActivity { public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; public static final int REQUEST_CODE_NFC = 0x00008002; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java index 6a5eaa26c..63708ae8e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java @@ -325,10 +325,10 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi mUseArmor = Preferences.getPreferences(this).getDefaultAsciiArmor(); } -// @Override -// protected void initLayout() { -// setContentView(R.layout.encrypt_files_activity); -// } + @Override + protected void initLayout() { + setContentView(R.layout.encrypt_files_activity); + } @Override public boolean onCreateOptionsMenu(Menu menu) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java index f9faf683d..b4c12996e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java @@ -303,10 +303,10 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv updateModeFragment(); } -// @Override -// protected void initLayout() { -// setContentView(R.layout.encrypt_text_activity); -// } + @Override + protected void initLayout() { + setContentView(R.layout.encrypt_text_activity); + } @Override public boolean onCreateOptionsMenu(Menu menu) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NavDrawerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NavDrawerActivity.java index 092334ac3..5381b1ab3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NavDrawerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NavDrawerActivity.java @@ -19,9 +19,12 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.remote.ui.AppsListActivity; +import org.sufficientlysecure.keychain.remote.ui.AppsListFragment; import it.neokree.materialnavigationdrawer.MaterialNavigationDrawer; @@ -29,19 +32,25 @@ public abstract class NavDrawerActivity extends MaterialNavigationDrawer { @Override public void init(Bundle savedInstanceState) { + // don't open drawer on first run + disableLearningPattern(); + +// addMultiPaneSupport(); // set the header image - this.setDrawerHeaderImage(R.drawable.mat2); + // create and set the header + View view = LayoutInflater.from(this).inflate(R.layout.custom_drawer, null); + setDrawerHeaderCustom(view); // create sections - this.addSection(newSection(getString(R.string.app_name), R.drawable.ic_vpn_key_black_24dp, new KeyListFragment())); + addSection(newSection(getString(R.string.title_keys), R.drawable.ic_vpn_key_black_24dp, new KeyListFragment())); - this.addSection(newSection(getString(R.string.title_encrypt_text), R.drawable.ic_lock_outline_black_24dp, new Intent(this, EncryptTextActivity.class))); - this.addSection(newSection(getString(R.string.title_encrypt_files), R.drawable.ic_lock_outline_black_24dp, new Intent(this, EncryptFilesActivity.class))); - this.addSection(newSection(getString(R.string.title_decrypt), R.drawable.ic_lock_open_black_24dp, new Intent(this, DecryptActivity.class))); - this.addSection(newSection(getString(R.string.title_api_registered_apps), R.drawable.ic_apps_black_24dp, new Intent(this, AppsListActivity.class))); + addSection(newSection(getString(R.string.title_encrypt_text), R.drawable.ic_lock_outline_black_24dp, new Intent(this, EncryptTextActivity.class))); + addSection(newSection(getString(R.string.title_encrypt_files), R.drawable.ic_lock_outline_black_24dp, new Intent(this, EncryptFilesActivity.class))); + addSection(newSection(getString(R.string.title_decrypt), R.drawable.ic_lock_open_black_24dp, new Intent(this, DecryptActivity.class))); + addSection(newSection(getString(R.string.title_api_registered_apps), R.drawable.ic_apps_black_24dp, new AppsListFragment())); // create bottom section - this.addBottomSection(newSection(getString(R.string.menu_preferences), R.drawable.ic_settings_black_24dp, new Intent(this, SettingsActivity.class))); + addBottomSection(newSection(getString(R.string.menu_preferences), R.drawable.ic_settings_black_24dp, new Intent(this, SettingsActivity.class))); } } diff --git a/OpenKeychain/src/main/res/drawable/mat2.jpg b/OpenKeychain/src/main/res/drawable/mat2.jpg deleted file mode 100644 index e35902ec7..000000000 Binary files a/OpenKeychain/src/main/res/drawable/mat2.jpg and /dev/null differ diff --git a/OpenKeychain/src/main/res/layout/custom_drawer.xml b/OpenKeychain/src/main/res/layout/custom_drawer.xml new file mode 100644 index 000000000..021226ba3 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/custom_drawer.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index a51420ba1..dfb675a37 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -41,6 +41,7 @@ "Create Key" "Exchange Keys" "Advanced Key Info" + "Keys" "Identities" diff --git a/OpenKeychain/src/main/res/values/themes.xml b/OpenKeychain/src/main/res/values/themes.xml index 5b9e43c3a..3b7d66963 100644 --- a/OpenKeychain/src/main/res/values/themes.xml +++ b/OpenKeychain/src/main/res/values/themes.xml @@ -12,8 +12,16 @@ @color/accent - @integer/DRAWERTYPE_IMAGE - true + @integer/DRAWERTYPE_CUSTOM + @style/ThemeOverlay.AppCompat.Dark.ActionBar + @style/Base.V21.Theme.AppCompat.Light.Dialog + #fafafa + false + @style/MaterialSectionTheme.Light + @style/MaterialSubheaderTheme.Light + false + false + false diff --git a/extern/openpgp-api-lib b/extern/openpgp-api-lib index edcc61970..4f5fcbf94 160000 --- a/extern/openpgp-api-lib +++ b/extern/openpgp-api-lib @@ -1 +1 @@ -Subproject commit edcc61970f24bb3601af26d89f49d384e7a3aff6 +Subproject commit 4f5fcbf94063301929d50370ab44895c6b4e1bd3