diff options
author | Thialfihar <thialfihar@gmail.com> | 2010-04-15 16:37:32 +0000 |
---|---|---|
committer | Thialfihar <thialfihar@gmail.com> | 2010-04-15 16:37:32 +0000 |
commit | c212f28c446044acb5fc7ef7487b95b777b39c44 (patch) | |
tree | d6e2c7eaa5a32a728de4d92463f041cedb08489a /src/org | |
parent | acd71a45c09ad6668a03ec74399d8f526ab647e2 (diff) | |
download | open-keychain-c212f28c446044acb5fc7ef7487b95b777b39c44.tar.gz open-keychain-c212f28c446044acb5fc7ef7487b95b777b39c44.tar.bz2 open-keychain-c212f28c446044acb5fc7ef7487b95b777b39c44.zip |
rewrote sign-only code, also finally recognize sign-only emails in the list and allow opening them for verification
Diffstat (limited to 'src/org')
-rw-r--r-- | src/org/thialfihar/android/apg/Apg.java | 266 | ||||
-rw-r--r-- | src/org/thialfihar/android/apg/DecryptMessageActivity.java | 28 | ||||
-rw-r--r-- | src/org/thialfihar/android/apg/EncryptMessageActivity.java | 18 | ||||
-rw-r--r-- | src/org/thialfihar/android/apg/MailListActivity.java | 28 |
4 files changed, 308 insertions, 32 deletions
diff --git a/src/org/thialfihar/android/apg/Apg.java b/src/org/thialfihar/android/apg/Apg.java index affc78cd7..91bfb3b44 100644 --- a/src/org/thialfihar/android/apg/Apg.java +++ b/src/org/thialfihar/android/apg/Apg.java @@ -16,7 +16,9 @@ package org.thialfihar.android.apg;
+import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -42,6 +44,7 @@ import java.util.HashMap; import java.util.Vector;
import java.util.regex.Pattern;
+import org.bouncycastle2.bcpg.ArmoredInputStream;
import org.bouncycastle2.bcpg.ArmoredOutputStream;
import org.bouncycastle2.bcpg.BCPGOutputStream;
import org.bouncycastle2.bcpg.CompressionAlgorithmTags;
@@ -125,6 +128,10 @@ public class Apg { Pattern.compile(".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*",
Pattern.DOTALL);
+ public static Pattern PGP_SIGNED_MESSAGE =
+ Pattern.compile(".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
+ Pattern.DOTALL);
+
protected static boolean mInitialized = false;
protected static final int RETURN_NO_MASTER_KEY = -2;
@@ -1247,17 +1254,16 @@ public class Apg { progress.setProgress("done.", 100, 100);
}
- public static void sign(InputStream inStream, OutputStream outStream,
- long signatureKeyId, String signaturePassPhrase,
- ProgressDialogUpdater progress)
+ public static void signText(InputStream inStream, OutputStream outStream,
+ long signatureKeyId, String signaturePassPhrase,
+ int hashAlgorithm,
+ ProgressDialogUpdater progress)
throws GeneralException, PGPException, IOException, NoSuchAlgorithmException,
SignatureException {
Security.addProvider(new BouncyCastleProvider());
ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream);
armorOut.setHeader("Version", FULL_VERSION);
- OutputStream out = armorOut;
- OutputStream signOut = out;
PGPSecretKey signingKey = null;
PGPSecretKeyRing signingKeyRing = null;
@@ -1286,7 +1292,7 @@ public class Apg { progress.setProgress("preparing signature...", 30, 100);
signatureGenerator =
new PGPSignatureGenerator(signingKey.getPublicKey().getAlgorithm(),
- HashAlgorithmTags.SHA1,
+ hashAlgorithm,
new BouncyCastleProvider());
signatureGenerator.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey);
String userId = getMainUserId(getMasterKey(signingKeyRing));
@@ -1296,15 +1302,31 @@ public class Apg { signatureGenerator.setHashedSubpackets(spGen.generate());
progress.setProgress("signing...", 40, 100);
- int n = 0;
- byte[] buffer = new byte[1 << 16];
- while ((n = inStream.read(buffer)) > 0) {
- signatureGenerator.update(buffer, 0, n);
+
+ armorOut.beginClearText(hashAlgorithm);
+
+ ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
+ int lookAhead = readInputLine(lineOut, inStream);
+
+ processLine(armorOut, signatureGenerator, lineOut.toByteArray());
+
+ if (lookAhead != -1) {
+ do {
+ lookAhead = readInputLine(lineOut, lookAhead, inStream);
+
+ signatureGenerator.update((byte)'\r');
+ signatureGenerator.update((byte)'\n');
+
+ processLine(armorOut, signatureGenerator, lineOut.toByteArray());
+ }
+ while (lookAhead != -1);
}
- signatureGenerator.generate().encode(signOut);
- signOut.close();
- out.close();
+ armorOut.endClearText();
+
+ BCPGOutputStream bOut = new BCPGOutputStream(armorOut);
+ signatureGenerator.generate().encode(bOut);
+ armorOut.close();
progress.setProgress("done.", 100, 100);
}
@@ -1492,6 +1514,108 @@ public class Apg { return returnData;
}
+ public static Bundle verifyText(InputStream inStream, OutputStream outStream,
+ ProgressDialogUpdater progress)
+ throws IOException, GeneralException, PGPException, SignatureException {
+ Bundle returnData = new Bundle();
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ArmoredInputStream aIn = new ArmoredInputStream(inStream);
+
+ progress.setProgress("reading data...", 0, 100);
+
+ // mostly taken from CLearSignedFileProcessor
+ ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
+ int lookAhead = readInputLine(lineOut, aIn);
+ byte[] lineSep = getLineSeparator();
+
+ if (lookAhead != -1 && aIn.isClearText())
+ {
+ byte[] line = lineOut.toByteArray();
+ out.write(line, 0, getLengthWithoutSeparator(line));
+ out.write(lineSep);
+
+ while (lookAhead != -1 && aIn.isClearText())
+ {
+ lookAhead = readInputLine(lineOut, lookAhead, aIn);
+
+ line = lineOut.toByteArray();
+ out.write(line, 0, getLengthWithoutSeparator(line));
+ out.write(lineSep);
+ }
+ }
+
+ out.close();
+
+ byte[] clearText = out.toByteArray();
+ outStream.write(clearText);
+
+ returnData.putBoolean("signature", true);
+
+ progress.setProgress("processing signature...", 60, 100);
+ PGPObjectFactory pgpFact = new PGPObjectFactory(aIn);
+
+ PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
+ if (sigList == null) {
+ throw new GeneralException("corrupt data");
+ }
+ PGPSignature signature = null;
+ long signatureKeyId = 0;
+ PGPPublicKey signatureKey = null;
+ for (int i = 0; i < sigList.size(); ++i) {
+ signature = sigList.get(i);
+ signatureKey = findPublicKey(signature.getKeyID());
+ if (signatureKeyId == 0) {
+ signatureKeyId = signature.getKeyID();
+ }
+ if (signatureKey == null) {
+ signature = null;
+ } else {
+ signatureKeyId = signature.getKeyID();
+ String userId = null;
+ PGPPublicKeyRing sigKeyRing = findPublicKeyRing(signatureKeyId);
+ if (sigKeyRing != null) {
+ userId = getMainUserId(getMasterKey(sigKeyRing));
+ }
+ returnData.putString("signatureUserId", userId);
+ break;
+ }
+ }
+
+ returnData.putLong("signatureKeyId", signatureKeyId);
+
+ if (signature == null) {
+ returnData.putBoolean("signatureUnknown", true);
+ progress.setProgress("done.", 100, 100);
+ return returnData;
+ }
+
+ signature.initVerify(signatureKey, new BouncyCastleProvider());
+
+ 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);
+ }
+
+ returnData.putBoolean("signatureSuccess", signature.verify());
+
+ progress.setProgress("done.", 100, 100);
+ return returnData;
+ }
+
public static Vector<PGPPublicKeyRing> getPublicKeyRings() {
return mPublicKeyRings;
}
@@ -1499,4 +1623,120 @@ public class Apg { public static Vector<PGPSecretKeyRing> getSecretKeyRings() {
return mSecretKeyRings;
}
+
+
+ // taken from ClearSignedFileProcessor in BC
+ 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 = readPassedEOL(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 = readPassedEOL(bOut, ch, fIn);
+ break;
+ }
+ }
+ while ((ch = fIn.read()) >= 0);
+
+ if (ch < 0) {
+ lookAhead = -1;
+ }
+
+ return lookAhead;
+ }
+
+ private static int readPassedEOL(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 void processLine(PGPSignature sig, byte[] line)
+ throws SignatureException, IOException {
+ int length = getLengthWithoutWhiteSpace(line);
+ if (length > 0)
+ {
+ sig.update(line, 0, length);
+ }
+ }
+
+ private static void processLine(OutputStream aOut, PGPSignatureGenerator 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;
+
+ while (end >= 0 && isLineEnding(line[end])) {
+ end--;
+ }
+
+ return end + 1;
+ }
+
+ private static boolean isLineEnding(byte b) {
+ 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");
+ byte[] nlBytes = new byte[nl.length()];
+
+ for (int i = 0; i != nlBytes.length; i++) {
+ nlBytes[i] = (byte)nl.charAt(i);
+ }
+
+ return nlBytes;
+ }
}
diff --git a/src/org/thialfihar/android/apg/DecryptMessageActivity.java b/src/org/thialfihar/android/apg/DecryptMessageActivity.java index 055c8256c..8b7985c77 100644 --- a/src/org/thialfihar/android/apg/DecryptMessageActivity.java +++ b/src/org/thialfihar/android/apg/DecryptMessageActivity.java @@ -63,6 +63,7 @@ public class DecryptMessageActivity extends Activity private String mReplyTo = null; private String mSubject = null; + private boolean mSignedOnly = false; private ProgressDialog mProgressDialog = null; private Thread mRunningThread = null; @@ -193,6 +194,15 @@ public class DecryptMessageActivity extends Activity // replace non breakable spaces data = data.replaceAll("\\xa0", " "); mMessage.setText(data); + } else { + matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data); + if (matcher.matches()) { + data = matcher.group(1); + // replace non breakable spaces + data = data.replaceAll("\\xa0", " "); + mMessage.setText(data); + mDecryptButton.setText(R.string.btn_verify); + } } } mReplyTo = intent.getExtras().getString("replyTo"); @@ -266,8 +276,18 @@ public class DecryptMessageActivity extends Activity private void decryptClicked() { String error = null; + String messageData = mMessage.getText().toString(); + Matcher matcher = Apg.PGP_SIGNED_MESSAGE.matcher(messageData); + if (matcher.matches()) { + mSignedOnly = true; + decryptStart(); + return; + } + + // else treat it as an encrypted message + mSignedOnly = false; ByteArrayInputStream in = - new ByteArrayInputStream(mMessage.getText().toString().getBytes()); + new ByteArrayInputStream(messageData.getBytes()); try { mDecryptionKeyId = Apg.getDecryptionKeyId(in); showDialog(AskForSecretKeyPassPhrase.DIALOG_PASS_PHRASE); @@ -320,7 +340,11 @@ public class DecryptMessageActivity extends Activity ByteArrayOutputStream out = new ByteArrayOutputStream(); try { - data = Apg.decrypt(in, out, Apg.getPassPhrase(), this); + if (mSignedOnly) { + data = Apg.verifyText(in, out, this); + } else { + data = Apg.decrypt(in, out, Apg.getPassPhrase(), this); + } } catch (PGPException e) { error = e.getMessage(); } catch (IOException e) { diff --git a/src/org/thialfihar/android/apg/EncryptMessageActivity.java b/src/org/thialfihar/android/apg/EncryptMessageActivity.java index af2eac82d..b954f31a1 100644 --- a/src/org/thialfihar/android/apg/EncryptMessageActivity.java +++ b/src/org/thialfihar/android/apg/EncryptMessageActivity.java @@ -24,6 +24,7 @@ import java.security.NoSuchProviderException; import java.security.SignatureException; import java.util.Vector; +import org.bouncycastle2.bcpg.HashAlgorithmTags; import org.bouncycastle2.openpgp.PGPException; import org.bouncycastle2.openpgp.PGPPublicKey; import org.bouncycastle2.openpgp.PGPPublicKeyRing; @@ -104,16 +105,9 @@ public class EncryptMessageActivity extends Activity return; } else { String message = data.getString("message"); - String signature = data.getString("signature"); Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); emailIntent.setType("text/plain; charset=utf-8"); emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, message); - if (signature != null) { - String fullText = "-----BEGIN PGP SIGNED MESSAGE-----\n" + - "Hash: SHA256\n" + "\n" + - message + "\n" + signature; - emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, fullText); - } if (mSubject != null) { emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, mSubject); @@ -305,7 +299,9 @@ public class EncryptMessageActivity extends Activity message = message.replaceAll(" +\n", "\n"); message = message.replaceAll("\n\n+", "\n\n"); message = message.replaceFirst("^\n+", ""); - message = message.replaceFirst("\n+$", ""); + // make sure there'll be exactly one newline at the end + message = message.replaceFirst("\n*$", "\n"); + ByteArrayInputStream in = new ByteArrayInputStream(Strings.toUTF8ByteArray(message)); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -316,9 +312,9 @@ public class EncryptMessageActivity extends Activity Apg.getPassPhrase(), this); data.putString("message", new String(out.toByteArray())); } else { - Apg.sign(in, out, mSignatureKeyId, Apg.getPassPhrase(), this); - data.putString("message", message); - data.putString("signature", new String(out.toByteArray())); + Apg.signText(in, out, mSignatureKeyId, + Apg.getPassPhrase(), HashAlgorithmTags.SHA256, this); + data.putString("message", new String(out.toByteArray())); } } catch (IOException e) { error = e.getMessage(); diff --git a/src/org/thialfihar/android/apg/MailListActivity.java b/src/org/thialfihar/android/apg/MailListActivity.java index 570e761df..ed207d4cd 100644 --- a/src/org/thialfihar/android/apg/MailListActivity.java +++ b/src/org/thialfihar/android/apg/MailListActivity.java @@ -57,9 +57,11 @@ public class MailListActivity extends ListActivity { public String fromAddress;
public String data;
public String replyTo;
+ public boolean signedOnly;
public Message(Conversation parent, long id, String subject,
- String fromAddress, String replyTo, String data) {
+ String fromAddress, String replyTo,
+ String data, boolean signedOnly) {
this.parent = parent;
this.id = id;
this.subject = subject;
@@ -69,6 +71,7 @@ public class MailListActivity extends ListActivity { if (this.replyTo == null || this.replyTo.equals("")) {
this.replyTo = this.fromAddress;
}
+ this.signedOnly = signedOnly;
}
}
@@ -115,18 +118,26 @@ public class MailListActivity extends ListActivity { int bodyIndex = messageCursor.getColumnIndex("body");
String data = messageCursor.getString(bodyIndex);
data = Html.fromHtml(data).toString();
+ boolean signedOnly = false;
Matcher matcher = Apg.PGP_MESSAGE.matcher(data);
if (matcher.matches()) {
data = matcher.group(1);
} else {
- data = null;
+ matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data);
+ if (matcher.matches()) {
+ data = matcher.group(1);
+ signedOnly = true;
+ } else {
+ data = null;
+ }
}
Message message =
new Message(conversation,
messageCursor.getLong(idIndex),
messageCursor.getString(subjectIndex),
messageCursor.getString(fromAddressIndex),
- messageCursor.getString(replyToIndex), data);
+ messageCursor.getString(replyToIndex),
+ data, signedOnly);
messages.add(message);
mmessages.add(message);
@@ -186,14 +197,19 @@ public class MailListActivity extends ListActivity { TextView subject = (TextView) view.findViewById(R.id.subject);
TextView email = (TextView) view.findViewById(R.id.email_address);
- ImageView encrypted = (ImageView) view.findViewById(R.id.ic_encrypted);
+ ImageView status = (ImageView) view.findViewById(R.id.ic_status);
subject.setText(message.subject);
email.setText(message.fromAddress);
if (message.data != null) {
- encrypted.setVisibility(View.VISIBLE);
+ if (message.signedOnly) {
+ status.setImageResource(R.drawable.signed);
+ } else {
+ status.setImageResource(R.drawable.encrypted);
+ }
+ status.setVisibility(View.VISIBLE);
} else {
- encrypted.setVisibility(View.INVISIBLE);
+ status.setVisibility(View.INVISIBLE);
}
return view;
|