backend support for charset in ascii-armored streams

This commit is contained in:
Vincent Breitmoser 2015-01-27 09:17:23 +01:00
parent efe5c80b1c
commit 6c80025ead
8 changed files with 120 additions and 4 deletions

View File

@ -438,6 +438,60 @@ public class PgpEncryptDecryptTest {
} }
@Test
public void testForeignEncoding () throws Exception {
String plaintext = "ウィキペディア";
byte[] plaindata = plaintext.getBytes("iso-2022-jp");
{ // some quick sanity checks
Assert.assertEquals(plaintext, new String(plaindata, "iso-2022-jp"));
Assert.assertNotEquals(plaintext, new String(plaindata, "utf-8"));
}
byte[] ciphertext;
{ // encrypt data with a given passphrase
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(plaindata);
InputData data = new InputData(in, in.available());
Builder b = new PgpSignEncrypt.Builder(
Robolectric.application,
new ProviderHelper(Robolectric.application),
null, // new DummyPassphraseCache(mPassphrase, 0L),
data, out);
b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() });
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
// this only works with ascii armored output!
b.setEnableAsciiArmorOutput(true);
b.setCharset("iso-2022-jp");
SignEncryptResult result = b.build().execute();
Assert.assertTrue("encryption must succeed", result.success());
ciphertext = out.toByteArray();
}
{ // decryption with provided passphrase should yield the same result
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, null, null, null);
b.setPassphrase(mKeyPhrase1);
DecryptVerifyResult result = b.build().execute();
Assert.assertTrue("decryption with provided passphrase must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext should equal plaintext bytes",
out.toByteArray(), plaindata);
Assert.assertEquals("charset should be read correctly",
"iso-2022-jp", result.getCharset());
Assert.assertEquals("decrypted ciphertext should equal plaintext",
new String(out.toByteArray(), result.getCharset()), plaintext);
Assert.assertNull("signature be empty", result.getSignatureResult());
}
}
private PgpDecryptVerify.Builder builderWithFakePassphraseCache ( private PgpDecryptVerify.Builder builderWithFakePassphraseCache (
InputData data, OutputStream out, InputData data, OutputStream out,
final String passphrase, final Long checkMasterKeyId, final Long checkSubKeyId) { final String passphrase, final Long checkMasterKeyId, final Long checkSubKeyId) {

View File

@ -41,6 +41,9 @@ public class DecryptVerifyResult extends OperationResult {
OpenPgpSignatureResult mSignatureResult; OpenPgpSignatureResult mSignatureResult;
OpenPgpMetadata mDecryptMetadata; OpenPgpMetadata mDecryptMetadata;
// This holds the charset which was specified in the ascii armor, if specified
// https://tools.ietf.org/html/rfc4880#page56
String mCharset;
public long getKeyIdPassphraseNeeded() { public long getKeyIdPassphraseNeeded() {
return mKeyIdPassphraseNeeded; return mKeyIdPassphraseNeeded;
@ -84,6 +87,14 @@ public class DecryptVerifyResult extends OperationResult {
mDecryptMetadata = decryptMetadata; mDecryptMetadata = decryptMetadata;
} }
public String getCharset () {
return mCharset;
}
public void setCharset(String charset) {
mCharset = charset;
}
public boolean isPending() { public boolean isPending() {
return (mResult & RESULT_PENDING) == RESULT_PENDING; return (mResult & RESULT_PENDING) == RESULT_PENDING;
} }

View File

@ -564,6 +564,7 @@ public abstract class OperationResult implements Parcelable {
MSG_DC_ASKIP_NO_KEY (LogLevel.DEBUG, R.string.msg_dc_askip_no_key), MSG_DC_ASKIP_NO_KEY (LogLevel.DEBUG, R.string.msg_dc_askip_no_key),
MSG_DC_ASKIP_NOT_ALLOWED (LogLevel.DEBUG, R.string.msg_dc_askip_not_allowed), MSG_DC_ASKIP_NOT_ALLOWED (LogLevel.DEBUG, R.string.msg_dc_askip_not_allowed),
MSG_DC_ASYM (LogLevel.DEBUG, R.string.msg_dc_asym), MSG_DC_ASYM (LogLevel.DEBUG, R.string.msg_dc_asym),
MSG_DC_CHARSET (LogLevel.DEBUG, R.string.msg_dc_charset),
MSG_DC_CLEAR_DATA (LogLevel.DEBUG, R.string.msg_dc_clear_data), MSG_DC_CLEAR_DATA (LogLevel.DEBUG, R.string.msg_dc_clear_data),
MSG_DC_CLEAR_DECOMPRESS (LogLevel.DEBUG, R.string.msg_dc_clear_decompress), MSG_DC_CLEAR_DECOMPRESS (LogLevel.DEBUG, R.string.msg_dc_clear_decompress),
MSG_DC_CLEAR_META_FILE (LogLevel.DEBUG, R.string.msg_dc_clear_meta_file), MSG_DC_CLEAR_META_FILE (LogLevel.DEBUG, R.string.msg_dc_clear_meta_file),

View File

@ -234,6 +234,22 @@ public class PgpDecryptVerify extends BaseOperation {
boolean symmetricPacketFound = false; boolean symmetricPacketFound = false;
boolean anyPacketFound = false; boolean anyPacketFound = false;
// If the input stream is armored, and there is a charset specified, take a note for later
// 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;
}
}
if (charset != null) {
log.add(LogType.MSG_DC_CHARSET, indent, charset);
}
}
// go through all objects and find one we can decrypt // go through all objects and find one we can decrypt
while (it.hasNext()) { while (it.hasNext()) {
Object obj = it.next(); Object obj = it.next();
@ -550,6 +566,7 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_OK_META_ONLY, indent); log.add(LogType.MSG_DC_OK_META_ONLY, indent);
DecryptVerifyResult result = DecryptVerifyResult result =
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
result.setCharset(charset);
result.setDecryptMetadata(metadata); result.setDecryptMetadata(metadata);
return result; return result;
} }
@ -647,6 +664,7 @@ public class PgpDecryptVerify extends BaseOperation {
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
result.setDecryptMetadata(metadata); result.setDecryptMetadata(metadata);
result.setSignatureResult(signatureResultBuilder.build()); result.setSignatureResult(signatureResultBuilder.build());
result.setCharset(charset);
return result; return result;
} }
@ -807,7 +825,7 @@ public class PgpDecryptVerify extends BaseOperation {
while ((ch = fIn.read()) >= 0) { while ((ch = fIn.read()) >= 0) {
bOut.write(ch); bOut.write(ch);
if (ch == '\r' || ch == '\n') { if (ch == '\r' || ch == '\n') {
lookAhead = readPassedEOL(bOut, ch, fIn); lookAhead = readPastEOL(bOut, ch, fIn);
break; break;
} }
} }
@ -824,7 +842,7 @@ public class PgpDecryptVerify extends BaseOperation {
do { do {
bOut.write(ch); bOut.write(ch);
if (ch == '\r' || ch == '\n') { if (ch == '\r' || ch == '\n') {
lookAhead = readPassedEOL(bOut, ch, fIn); lookAhead = readPastEOL(bOut, ch, fIn);
break; break;
} }
} while ((ch = fIn.read()) >= 0); } while ((ch = fIn.read()) >= 0);
@ -836,7 +854,7 @@ public class PgpDecryptVerify extends BaseOperation {
return lookAhead; return lookAhead;
} }
private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) private static int readPastEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
throws IOException { throws IOException {
int lookAhead = fIn.read(); int lookAhead = fIn.read();

View File

@ -81,6 +81,7 @@ public class PgpSignEncrypt extends BaseOperation {
private boolean mCleartextInput; private boolean mCleartextInput;
private String mOriginalFilename; private String mOriginalFilename;
private boolean mFailOnMissingEncryptionKeyIds; private boolean mFailOnMissingEncryptionKeyIds;
private String mCharset;
private byte[] mNfcSignedHash = null; private byte[] mNfcSignedHash = null;
private Date mNfcCreationTimestamp = null; private Date mNfcCreationTimestamp = null;
@ -118,6 +119,7 @@ public class PgpSignEncrypt extends BaseOperation {
this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp; this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp;
this.mOriginalFilename = builder.mOriginalFilename; this.mOriginalFilename = builder.mOriginalFilename;
this.mFailOnMissingEncryptionKeyIds = builder.mFailOnMissingEncryptionKeyIds; this.mFailOnMissingEncryptionKeyIds = builder.mFailOnMissingEncryptionKeyIds;
this.mCharset = builder.mCharset;
} }
public static class Builder { public static class Builder {
@ -145,6 +147,7 @@ public class PgpSignEncrypt extends BaseOperation {
private byte[] mNfcSignedHash = null; private byte[] mNfcSignedHash = null;
private Date mNfcCreationTimestamp = null; private Date mNfcCreationTimestamp = null;
private boolean mFailOnMissingEncryptionKeyIds = false; private boolean mFailOnMissingEncryptionKeyIds = false;
private String mCharset = null;
public Builder(Context context, ProviderHelper providerHelper, Progressable progressable, public Builder(Context context, ProviderHelper providerHelper, Progressable progressable,
InputData data, OutputStream outStream) { InputData data, OutputStream outStream) {
@ -211,6 +214,11 @@ public class PgpSignEncrypt extends BaseOperation {
return this; return this;
} }
public Builder setCharset(String charset) {
mCharset = charset;
return this;
}
/** /**
* Also encrypt with the signing keyring * Also encrypt with the signing keyring
* *
@ -283,6 +291,10 @@ public class PgpSignEncrypt extends BaseOperation {
if (mVersionHeader != null) { if (mVersionHeader != null) {
armorOut.setHeader("Version", mVersionHeader); armorOut.setHeader("Version", mVersionHeader);
} }
// if we have a charset, put it in the header
if (mCharset != null) {
armorOut.setHeader("Charset", mCharset);
}
out = armorOut; out = armorOut;
} else { } else {
out = mOutStream; out = mOutStream;

View File

@ -557,6 +557,12 @@ public class OpenPgpService extends RemoteService {
result.putExtra(OpenPgpApi.RESULT_METADATA, metadata); result.putExtra(OpenPgpApi.RESULT_METADATA, metadata);
} }
} }
String charset = pgpResult.getCharset();
if (charset != null) {
result.putExtra(OpenPgpApi.RESULT_CHARSET, charset);
}
} else { } else {
LogEntryParcel errorMsg = pgpResult.getLog().getLast(); LogEntryParcel errorMsg = pgpResult.getLog().getLast();
throw new Exception(getString(errorMsg.mType.getMsgId())); throw new Exception(getString(errorMsg.mType.getMsgId()));

View File

@ -41,6 +41,8 @@ import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ShareHelper; import org.sufficientlysecure.keychain.util.ShareHelper;
import java.io.UnsupportedEncodingException;
public class DecryptTextFragment extends DecryptFragment { public class DecryptTextFragment extends DecryptFragment {
public static final String ARG_CIPHERTEXT = "ciphertext"; public static final String ARG_CIPHERTEXT = "ciphertext";
@ -194,7 +196,18 @@ public class DecryptTextFragment extends DecryptFragment {
byte[] decryptedMessage = returnData byte[] decryptedMessage = returnData
.getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES); .getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES);
mText.setText(new String(decryptedMessage)); String displayMessage;
if (pgpResult.getCharset() != null) {
try {
displayMessage = new String(decryptedMessage, pgpResult.getCharset());
} catch (UnsupportedEncodingException e) {
// if we can't decode properly, just fall back to utf-8
displayMessage = new String(decryptedMessage);
}
} else {
displayMessage = new String(decryptedMessage);
}
mText.setText(displayMessage);
pgpResult.createNotify(getActivity()).show(); pgpResult.createNotify(getActivity()).show();

View File

@ -923,6 +923,7 @@
<string name="msg_dc_askip_no_key">"Data not encrypted with known key, skipping…"</string> <string name="msg_dc_askip_no_key">"Data not encrypted with known key, skipping…"</string>
<string name="msg_dc_askip_not_allowed">"Data not encrypted with allowed key, skipping…"</string> <string name="msg_dc_askip_not_allowed">"Data not encrypted with allowed key, skipping…"</string>
<string name="msg_dc_asym">"Found block of asymmetrically encrypted data for key %s"</string> <string name="msg_dc_asym">"Found block of asymmetrically encrypted data for key %s"</string>
<string name="msg_dc_charset">"Found charset header: '%s'"</string>
<string name="msg_dc_clear_data">"Processing literal data"</string> <string name="msg_dc_clear_data">"Processing literal data"</string>
<string name="msg_dc_clear_decompress">"Unpacking compressed data"</string> <string name="msg_dc_clear_decompress">"Unpacking compressed data"</string>
<string name="msg_dc_clear_meta_file">"Filename: %s"</string> <string name="msg_dc_clear_meta_file">"Filename: %s"</string>