diff --git a/res/values/strings.xml b/res/values/strings.xml index c9f35824c..a64873b0f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -46,6 +46,7 @@ Keys General Defaults + Advanced Sign To Clipboard @@ -106,6 +107,7 @@ Message Compression File Compression Language + Force V3 Signatures Select 1 Selected diff --git a/res/xml/apg_preferences.xml b/res/xml/apg_preferences.xml index 07471a4b9..aa2defc87 100644 --- a/res/xml/apg_preferences.xml +++ b/res/xml/apg_preferences.xml @@ -66,4 +66,14 @@ + + + + + + diff --git a/src/org/thialfihar/android/apg/Apg.java b/src/org/thialfihar/android/apg/Apg.java index 725136ef2..fb6d50afd 100644 --- a/src/org/thialfihar/android/apg/Apg.java +++ b/src/org/thialfihar/android/apg/Apg.java @@ -78,6 +78,7 @@ import org.bouncycastle2.openpgp.PGPSignatureList; import org.bouncycastle2.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle2.openpgp.PGPSignatureSubpacketVector; import org.bouncycastle2.openpgp.PGPUtil; +import org.bouncycastle2.openpgp.PGPV3SignatureGenerator; import org.thialfihar.android.apg.provider.DataProvider; import org.thialfihar.android.apg.provider.Database; import org.thialfihar.android.apg.provider.KeyRings; @@ -1123,6 +1124,7 @@ public class Apg { String signaturePassPhrase, ProgressDialogUpdater progress, int symmetricAlgorithm, int hashAlgorithm, int compression, + boolean forceV3Signature, String passPhrase) throws IOException, GeneralException, PGPException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException { @@ -1165,7 +1167,6 @@ public class Apg { new BouncyCastleProvider()); } - PGPSignatureGenerator signatureGenerator = null; progress.setProgress(R.string.progress_preparingStreams, 5, 100); // encrypt and compress input file content PGPEncryptedDataGenerator cPk = @@ -1184,18 +1185,29 @@ public class Apg { } encryptOut = cPk.open(out, new byte[1 << 16]); + PGPSignatureGenerator signatureGenerator = null; + PGPV3SignatureGenerator signatureV3Generator = null; + if (signatureKeyId != 0) { progress.setProgress(R.string.progress_preparingSignature, 10, 100); - signatureGenerator = - new PGPSignatureGenerator(signingKey.getPublicKey().getAlgorithm(), - hashAlgorithm, - new BouncyCastleProvider()); - signatureGenerator.initSign(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); - String userId = getMainUserId(getMasterKey(signingKeyRing)); + if (forceV3Signature) { + signatureV3Generator = + new PGPV3SignatureGenerator(signingKey.getPublicKey().getAlgorithm(), + hashAlgorithm, + new BouncyCastleProvider()); + signatureV3Generator.initSign(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); + } else { + signatureGenerator = + new PGPSignatureGenerator(signingKey.getPublicKey().getAlgorithm(), + hashAlgorithm, + new BouncyCastleProvider()); + signatureGenerator.initSign(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - spGen.setSignerUserID(false, userId); - signatureGenerator.setHashedSubpackets(spGen.generate()); + String userId = getMainUserId(getMasterKey(signingKeyRing)); + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + spGen.setSignerUserID(false, userId); + signatureGenerator.setHashedSubpackets(spGen.generate()); + } } PGPCompressedDataGenerator compressGen = null; @@ -1207,7 +1219,11 @@ public class Apg { bcpgOut = new BCPGOutputStream(compressGen.open(encryptOut)); } if (signatureKeyId != 0) { - signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); + if (forceV3Signature) { + signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut); + } else { + signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); + } } PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); @@ -1223,7 +1239,11 @@ public class Apg { while ((n = in.read(buffer)) > 0) { pOut.write(buffer, 0, n); if (signatureKeyId != 0) { - signatureGenerator.update(buffer, 0, n); + if (forceV3Signature) { + signatureV3Generator.update(buffer, 0, n); + } else { + signatureGenerator.update(buffer, 0, n); + } } done += n; if (data.getSize() != 0) { @@ -1235,7 +1255,11 @@ public class Apg { if (signatureKeyId != 0) { progress.setProgress(R.string.progress_generatingSignature, 95, 100); - signatureGenerator.generate().encode(pOut); + if (forceV3Signature) { + signatureV3Generator.generate().encode(pOut); + } else { + signatureGenerator.generate().encode(pOut); + } } if (compressGen != null) { compressGen.close(); @@ -1252,6 +1276,7 @@ public class Apg { InputData data, OutputStream outStream, long signatureKeyId, String signaturePassPhrase, int hashAlgorithm, + boolean forceV3Signature, ProgressDialogUpdater progress) throws GeneralException, PGPException, IOException, NoSuchAlgorithmException, SignatureException { @@ -1281,20 +1306,30 @@ public class Apg { signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), new BouncyCastleProvider()); - PGPSignatureGenerator signatureGenerator = null; progress.setProgress(R.string.progress_preparingStreams, 0, 100); progress.setProgress(R.string.progress_preparingSignature, 30, 100); - signatureGenerator = - new PGPSignatureGenerator(signingKey.getPublicKey().getAlgorithm(), - hashAlgorithm, - new BouncyCastleProvider()); - signatureGenerator.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); - String userId = getMainUserId(getMasterKey(signingKeyRing)); - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - spGen.setSignerUserID(false, userId); - signatureGenerator.setHashedSubpackets(spGen.generate()); + PGPSignatureGenerator signatureGenerator = null; + PGPV3SignatureGenerator signatureV3Generator = null; + if (forceV3Signature) { + signatureV3Generator = + new PGPV3SignatureGenerator(signingKey.getPublicKey().getAlgorithm(), + hashAlgorithm, + new BouncyCastleProvider()); + signatureV3Generator.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); + } else { + signatureGenerator = + new PGPSignatureGenerator(signingKey.getPublicKey().getAlgorithm(), + hashAlgorithm, + new BouncyCastleProvider()); + signatureGenerator.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); + + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + String userId = getMainUserId(getMasterKey(signingKeyRing)); + spGen.setSignerUserID(false, userId); + signatureGenerator.setHashedSubpackets(spGen.generate()); + } progress.setProgress(R.string.progress_signing, 40, 100); @@ -1304,16 +1339,29 @@ public class Apg { InputStream inStream = data.getInputStream(); int lookAhead = readInputLine(lineOut, inStream); - processLine(armorOut, signatureGenerator, lineOut.toByteArray()); + if (forceV3Signature) { + processLine(armorOut, signatureV3Generator, lineOut.toByteArray()); + } else { + processLine(armorOut, signatureGenerator, lineOut.toByteArray()); + } if (lookAhead != -1) { do { lookAhead = readInputLine(lineOut, lookAhead, inStream); - signatureGenerator.update((byte)'\r'); - signatureGenerator.update((byte)'\n'); + if (forceV3Signature) { + signatureV3Generator.update((byte)'\r'); + signatureV3Generator.update((byte)'\n'); + } else { + signatureGenerator.update((byte)'\r'); + signatureGenerator.update((byte)'\n'); + } - processLine(armorOut, signatureGenerator, lineOut.toByteArray()); + if (forceV3Signature) { + processLine(armorOut, signatureV3Generator, lineOut.toByteArray()); + } else { + processLine(armorOut, signatureGenerator, lineOut.toByteArray()); + } } while (lookAhead != -1); } @@ -1321,7 +1369,11 @@ public class Apg { armorOut.endClearText(); BCPGOutputStream bOut = new BCPGOutputStream(armorOut); - signatureGenerator.generate().encode(bOut); + if (forceV3Signature) { + signatureV3Generator.generate().encode(bOut); + } else { + signatureGenerator.generate().encode(bOut); + } armorOut.close(); progress.setProgress(R.string.progress_done, 100, 100); @@ -1802,6 +1854,16 @@ public class Apg { aOut.write(line, 0, line.length); } + private static void processLine(OutputStream aOut, PGPV3SignatureGenerator sGen, byte[] line) + throws SignatureException, IOException { + int length = getLengthWithoutWhiteSpace(line); + if (length > 0) { + sGen.update(line, 0, length); + } + + aOut.write(line, 0, line.length); +} + private static int getLengthWithoutSeparator(byte[] line) { int end = line.length - 1; diff --git a/src/org/thialfihar/android/apg/Constants.java b/src/org/thialfihar/android/apg/Constants.java index b61395269..b8704117c 100644 --- a/src/org/thialfihar/android/apg/Constants.java +++ b/src/org/thialfihar/android/apg/Constants.java @@ -33,5 +33,6 @@ public final class Constants { public static final String default_file_compression = "defaultFileCompression"; public static final String pass_phrase_cache_ttl = "passPhraseCacheTtl"; public static final String language = "language"; + public static final String force_v3_signatures = "forceV3Signatures"; } } diff --git a/src/org/thialfihar/android/apg/EncryptActivity.java b/src/org/thialfihar/android/apg/EncryptActivity.java index b3b002346..e1e773216 100644 --- a/src/org/thialfihar/android/apg/EncryptActivity.java +++ b/src/org/thialfihar/android/apg/EncryptActivity.java @@ -619,16 +619,15 @@ public class EncryptActivity extends BaseActivity { String error = null; Bundle data = new Bundle(); Message msg = new Message(); - fillDataSource(); - fillDataDestination(); + try { InputData in; OutputStream out; boolean useAsciiArmour = true; long encryptionKeyIds[] = null; long signatureKeyId = 0; - boolean signOnly = false; int compressionId = 0; + boolean signOnly = false; String passPhrase = null; if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { @@ -642,6 +641,9 @@ public class EncryptActivity extends BaseActivity { signOnly = (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0); } + fillDataSource(signOnly && !mReturnResult); + fillDataDestination(); + // streams in = mDataSource.getInputData(this, true); out = mDataDestination.getOutputStream(this); @@ -661,14 +663,18 @@ public class EncryptActivity extends BaseActivity { if (signOnly) { Apg.signText(this, in, out, getSecretKeyId(), Apg.getCachedPassPhrase(getSecretKeyId()), - mPreferences.getDefaultHashAlgorithm(), this); + mPreferences.getDefaultHashAlgorithm(), + mPreferences.getForceV3Signatures(), + this); } else { Apg.encrypt(this, in, out, useAsciiArmour, encryptionKeyIds, signatureKeyId, Apg.getCachedPassPhrase(signatureKeyId), this, mPreferences.getDefaultEncryptionAlgorithm(), mPreferences.getDefaultHashAlgorithm(), - compressionId, passPhrase); + compressionId, + mPreferences.getForceV3Signatures(), + passPhrase); } out.close(); @@ -930,7 +936,7 @@ public class EncryptActivity extends BaseActivity { return super.onCreateDialog(id); } - protected void fillDataSource() { + protected void fillDataSource(boolean fixContent) { mDataSource = new DataSource(); if (mContentUri != null) { mDataSource.setUri(mContentUri); @@ -940,7 +946,19 @@ public class EncryptActivity extends BaseActivity { if (mData != null) { mDataSource.setData(mData); } else { - mDataSource.setText(mMessage.getText().toString()); + String message = mMessage.getText().toString(); + if (fixContent) { + // fix the message a bit, trailing spaces and newlines break stuff, + // because GMail sends as HTML and such things fuck up the + // signature, + // TODO: things like "<" and ">" also fuck up the signature + message = message.replaceAll(" +\n", "\n"); + message = message.replaceAll("\n\n+", "\n\n"); + message = message.replaceFirst("^\n+", ""); + // make sure there'll be exactly one newline at the end + message = message.replaceFirst("\n*$", "\n"); + } + mDataSource.setText(message); } } } diff --git a/src/org/thialfihar/android/apg/Preferences.java b/src/org/thialfihar/android/apg/Preferences.java index 0df29718c..705da21cc 100644 --- a/src/org/thialfihar/android/apg/Preferences.java +++ b/src/org/thialfihar/android/apg/Preferences.java @@ -103,6 +103,16 @@ public class Preferences { editor.commit(); } + public boolean getForceV3Signatures() { + return mSharedPreferences.getBoolean(Constants.pref.force_v3_signatures, false); + } + + public void setForceV3Signatures(boolean value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(Constants.pref.force_v3_signatures, value); + editor.commit(); + } + public boolean hasSeenChangeLog(String version) { return mSharedPreferences.getBoolean(Constants.pref.has_seen_change_log + version, false); diff --git a/src/org/thialfihar/android/apg/PreferencesActivity.java b/src/org/thialfihar/android/apg/PreferencesActivity.java index 6e7e6f657..c226a8b2a 100644 --- a/src/org/thialfihar/android/apg/PreferencesActivity.java +++ b/src/org/thialfihar/android/apg/PreferencesActivity.java @@ -37,6 +37,7 @@ public class PreferencesActivity extends PreferenceActivity { private IntegerListPreference mMessageCompression = null; private IntegerListPreference mFileCompression = null; private CheckBoxPreference mAsciiArmour = null; + private CheckBoxPreference mForceV3Signatures = null; private Preferences mPreferences; @Override @@ -210,6 +211,18 @@ public class PreferencesActivity extends PreferenceActivity { return false; } }); + + mForceV3Signatures = (CheckBoxPreference) findPreference(Constants.pref.force_v3_signatures); + mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures()); + mForceV3Signatures.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() + { + public boolean onPreferenceChange(Preference preference, Object newValue) + { + mForceV3Signatures.setChecked((Boolean)newValue); + mPreferences.setForceV3Signatures((Boolean)newValue); + return false; + } + }); } }