Support backupVersion ASCII Armor header

This commit is contained in:
Dominik Schürmann
2015-10-15 22:50:34 +02:00
parent c03dee6fe2
commit cac7c3234a
8 changed files with 95 additions and 25 deletions

View File

@@ -82,7 +82,6 @@ public final class Constants {
public static final class Path { public static final class Path {
public static final File APP_DIR = new File(Environment.getExternalStorageDirectory(), "OpenKeychain"); public static final File APP_DIR = new File(Environment.getExternalStorageDirectory(), "OpenKeychain");
public static final File APP_DIR_FILE = new File(APP_DIR, "export.asc");
} }
public static final class Notification { public static final class Notification {

View File

@@ -142,6 +142,7 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
PgpSignEncryptInputParcel inputParcel = new PgpSignEncryptInputParcel(); PgpSignEncryptInputParcel inputParcel = new PgpSignEncryptInputParcel();
inputParcel.setSymmetricPassphrase(exportInput.mSymmetricPassphrase); inputParcel.setSymmetricPassphrase(exportInput.mSymmetricPassphrase);
inputParcel.setEnableAsciiArmorOutput(true); inputParcel.setEnableAsciiArmorOutput(true);
inputParcel.setAddBackupHeader(true);
InputStream inStream = mContext.getContentResolver().openInputStream(exportOutputUri); InputStream inStream = mContext.getContentResolver().openInputStream(exportOutputUri);

View File

@@ -639,6 +639,7 @@ public abstract class OperationResult implements Parcelable {
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_CHARSET (LogLevel.DEBUG, R.string.msg_dc_charset),
MSG_DC_BACKUP_VERSION (LogLevel.DEBUG, R.string.msg_dc_backup_version),
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

@@ -31,6 +31,7 @@ import java.util.Iterator;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import org.openintents.openpgp.OpenPgpDecryptionResult; import org.openintents.openpgp.OpenPgpDecryptionResult;
@@ -201,6 +202,57 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
} }
private static class ArmorHeaders {
String charset = null;
Integer backupVersion = null;
}
private ArmorHeaders parseArmorHeaders(InputStream in, OperationLog log, int indent) {
ArmorHeaders armorHeaders = new ArmorHeaders();
// If the input stream is armored, and there is a charset specified, take a note for later
// https://tools.ietf.org/html/rfc4880#page56
if (in instanceof ArmoredInputStream) {
ArmoredInputStream aIn = (ArmoredInputStream) in;
if (aIn.getArmorHeaders() != null) {
for (String header : aIn.getArmorHeaders()) {
String[] pieces = header.split(":", 2);
if (pieces.length != 2
|| TextUtils.isEmpty(pieces[0])
|| TextUtils.isEmpty(pieces[1])) {
continue;
}
switch (pieces[0].toLowerCase()) {
case "charset": {
armorHeaders.charset = pieces[1].trim();
break;
}
case "backupversion": {
try {
armorHeaders.backupVersion = Integer.valueOf(pieces[1].trim());
} catch (NumberFormatException e) {
continue;
}
break;
}
default: {
// continue;
}
}
}
if (armorHeaders.charset != null) {
log.add(LogType.MSG_DC_CHARSET, indent, armorHeaders.charset);
}
if (armorHeaders.backupVersion != null) {
log.add(LogType.MSG_DC_BACKUP_VERSION, indent, armorHeaders.backupVersion);
}
}
}
return armorHeaders;
}
/** Decrypt and/or verify binary or ascii armored pgp data. */ /** Decrypt and/or verify binary or ascii armored pgp data. */
@NonNull @NonNull
private DecryptVerifyResult decryptVerify( private DecryptVerifyResult decryptVerify(
@@ -215,23 +267,12 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
int currentProgress = 0; int currentProgress = 0;
updateProgress(R.string.progress_reading_data, currentProgress, 100); updateProgress(R.string.progress_reading_data, currentProgress, 100);
// If the input stream is armored, and there is a charset specified, take a note for later // parse ASCII Armor headers
// https://tools.ietf.org/html/rfc4880#page56 ArmorHeaders armorHeaders = parseArmorHeaders(in, log, indent);
String charset = null; String charset = armorHeaders.charset;
if (in instanceof ArmoredInputStream) { boolean useBackupCode = false;
ArmoredInputStream aIn = (ArmoredInputStream) in; if (armorHeaders.backupVersion != null && armorHeaders.backupVersion == 1) {
if (aIn.getArmorHeaders() != null) { useBackupCode = true;
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);
}
}
} }
OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder(); OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder();
@@ -245,7 +286,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
if (obj instanceof PGPEncryptedDataList) { if (obj instanceof PGPEncryptedDataList) {
esResult = handleEncryptedPacket( esResult = handleEncryptedPacket(
input, cryptoInput, (PGPEncryptedDataList) obj, log, indent, currentProgress); input, cryptoInput, (PGPEncryptedDataList) obj, log, indent,
currentProgress, useBackupCode);
// if there is an error, nothing left to do here // if there is an error, nothing left to do here
if (esResult.errorResult != null) { if (esResult.errorResult != null) {
@@ -477,7 +519,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
} }
private EncryptStreamResult handleEncryptedPacket(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput, private EncryptStreamResult handleEncryptedPacket(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
PGPEncryptedDataList enc, OperationLog log, int indent, int currentProgress) throws PGPException { PGPEncryptedDataList enc, OperationLog log, int indent, int currentProgress, boolean useBackupCode) throws PGPException {
EncryptStreamResult result = new EncryptStreamResult(); EncryptStreamResult result = new EncryptStreamResult();
@@ -609,8 +651,11 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
if (passphrase == null) { if (passphrase == null) {
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1); log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
RequiredInputParcel requiredInputParcel = useBackupCode ?
RequiredInputParcel.createRequiredBackupCode() :
RequiredInputParcel.createRequiredSymmetricPassphrase();
return result.with(new DecryptVerifyResult(log, return result.with(new DecryptVerifyResult(log,
RequiredInputParcel.createRequiredSymmetricPassphrase(), requiredInputParcel,
cryptoInput)); cryptoInput));
} }
@@ -653,8 +698,10 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
result.cleartextStream = encryptedDataSymmetric.getDataStream(decryptorFactory); result.cleartextStream = encryptedDataSymmetric.getDataStream(decryptorFactory);
} catch (PGPDataValidationException e) { } catch (PGPDataValidationException e) {
log.add(LogType.MSG_DC_ERROR_SYM_PASSPHRASE, indent + 1); log.add(LogType.MSG_DC_ERROR_SYM_PASSPHRASE, indent + 1);
return result.with(new DecryptVerifyResult(log, RequiredInputParcel requiredInputParcel = useBackupCode ?
RequiredInputParcel.createRequiredSymmetricPassphrase(), cryptoInput)); RequiredInputParcel.createRequiredBackupCode() :
RequiredInputParcel.createRequiredSymmetricPassphrase();
return result.with(new DecryptVerifyResult(log, requiredInputParcel, cryptoInput));
} }
result.encryptedData = encryptedDataSymmetric; result.encryptedData = encryptedDataSymmetric;

View File

@@ -44,6 +44,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
protected boolean mDetachedSignature = false; protected boolean mDetachedSignature = false;
protected boolean mHiddenRecipients = false; protected boolean mHiddenRecipients = false;
protected boolean mIntegrityProtected = true; protected boolean mIntegrityProtected = true;
protected boolean mAddBackupHeader = false;
public PgpSignEncryptInputParcel() { public PgpSignEncryptInputParcel() {
@@ -70,6 +71,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
mDetachedSignature = source.readInt() == 1; mDetachedSignature = source.readInt() == 1;
mHiddenRecipients = source.readInt() == 1; mHiddenRecipients = source.readInt() == 1;
mIntegrityProtected = source.readInt() == 1; mIntegrityProtected = source.readInt() == 1;
mAddBackupHeader = source.readInt() == 1;
} }
@Override @Override
@@ -100,6 +102,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
dest.writeInt(mDetachedSignature ? 1 : 0); dest.writeInt(mDetachedSignature ? 1 : 0);
dest.writeInt(mHiddenRecipients ? 1 : 0); dest.writeInt(mHiddenRecipients ? 1 : 0);
dest.writeInt(mIntegrityProtected ? 1 : 0); dest.writeInt(mIntegrityProtected ? 1 : 0);
dest.writeInt(mAddBackupHeader ? 1 : 0);
} }
public String getCharset() { public String getCharset() {
@@ -244,6 +247,15 @@ public class PgpSignEncryptInputParcel implements Parcelable {
return this; return this;
} }
public PgpSignEncryptInputParcel setAddBackupHeader(boolean addBackupHeader) {
this.mAddBackupHeader = addBackupHeader;
return this;
}
public boolean isAddBackupHeader() {
return mAddBackupHeader;
}
public boolean isHiddenRecipients() { public boolean isHiddenRecipients() {
return mHiddenRecipients; return mHiddenRecipients;
} }

View File

@@ -149,6 +149,10 @@ public class PgpSignEncryptOperation extends BaseOperation {
if (input.getCharset() != null) { if (input.getCharset() != null) {
armorOut.setHeader("Charset", input.getCharset()); armorOut.setHeader("Charset", input.getCharset());
} }
// add proprietary header to indicate that this is a key backup
if (input.isAddBackupHeader()) {
armorOut.setHeader("BackupVersion", "1");
}
out = armorOut; out = armorOut;
} else { } else {
out = outputStream; out = outputStream;

View File

@@ -14,8 +14,8 @@ import java.util.Date;
public class RequiredInputParcel implements Parcelable { public class RequiredInputParcel implements Parcelable {
public enum RequiredInputType { public enum RequiredInputType {
PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_MOVE_KEY_TO_CARD, ENABLE_ORBOT, PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, NFC_SIGN, NFC_DECRYPT,
UPLOAD_FAIL_RETRY NFC_MOVE_KEY_TO_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY
} }
public Date mSignatureTime; public Date mSignatureTime;
@@ -117,6 +117,11 @@ public class RequiredInputParcel implements Parcelable {
null, null, null, null, null); null, null, null, null, null);
} }
public static RequiredInputParcel createRequiredBackupCode() {
return new RequiredInputParcel(RequiredInputType.BACKUP_CODE,
null, null, null, null, null);
}
public static RequiredInputParcel createRequiredPassphrase( public static RequiredInputParcel createRequiredPassphrase(
RequiredInputParcel req) { RequiredInputParcel req) {
return new RequiredInputParcel(RequiredInputType.PASSPHRASE, return new RequiredInputParcel(RequiredInputType.PASSPHRASE,

View File

@@ -1152,6 +1152,7 @@
<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_charset">"Found charset header: '%s'"</string>
<string name="msg_dc_backup_version">"Found backupVersion 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>