mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-30 04:22:18 -05:00
backend support for charset in ascii-armored streams
This commit is contained in:
parent
efe5c80b1c
commit
6c80025ead
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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()));
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user