allow setting custom headers in autocrypt setup message

This commit is contained in:
Vincent Breitmoser 2018-05-03 15:11:35 +02:00
parent a63aca623d
commit 8edd084212
5 changed files with 62 additions and 11 deletions

View File

@ -26,8 +26,10 @@ import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import android.content.Context;
import android.database.Cursor;
@ -84,6 +86,9 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
private static final int INDEX_MASTER_KEY_ID = 0;
private static final int INDEX_HAS_ANY_SECRET = 1;
// this is a very simple matcher, we only need basic sanitization
private static final Pattern HEADER_PATTERN = Pattern.compile("[a-zA-Z0-9_-]+: [^\\n]+");
public BackupOperation(Context context, KeyRepository keyRepository, Progressable
progressable) {
super(context, keyRepository, progressable);
@ -130,7 +135,7 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
CountingOutputStream outStream = new CountingOutputStream(new BufferedOutputStream(plainOut));
boolean backupSuccess = exportKeysToStream(log, backupInput.getMasterKeyIds(),
backupInput.getExportSecret(), backupInput.getExportPublic(), outStream);
backupInput.getExportSecret(), backupInput.getExportPublic(), outStream, backupInput.getExtraHeaders());
if (!backupSuccess) {
// if there was an error, it will be in the log so we just have to return
@ -215,7 +220,7 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
}
boolean exportKeysToStream(OperationLog log, long[] masterKeyIds, boolean exportSecret, boolean exportPublic,
OutputStream outStream) {
OutputStream outStream, List<String> extraSecretKeyHeaders) {
// noinspection unused TODO use these in a log entry
int okSecret = 0, okPublic = 0;
@ -253,9 +258,10 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
boolean hasSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) > 0;
if (exportSecret && hasSecret) {
log.add(LogType.MSG_BACKUP_SECRET, 2, KeyFormattingUtils.beautifyKeyId(masterKeyId));
if (writeSecretKeyToStream(masterKeyId, log, outStream)) {
if (writeSecretKeyToStream(masterKeyId, log, outStream, extraSecretKeyHeaders)) {
okSecret += 1;
}
extraSecretKeyHeaders = null;
}
}
@ -300,12 +306,17 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
return true;
}
private boolean writeSecretKeyToStream(long masterKeyId, OperationLog log, OutputStream outStream)
private boolean writeSecretKeyToStream(long masterKeyId, OperationLog log, OutputStream outStream,
List<String> extraSecretKeyHeaders)
throws IOException {
ArmoredOutputStream arOutStream = null;
try {
arOutStream = new ArmoredOutputStream(outStream);
if (extraSecretKeyHeaders != null) {
addExtraHeadersToStream(arOutStream, extraSecretKeyHeaders);
}
byte[] data = mKeyRepository.loadSecretKeyRingData(masterKeyId);
UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(data);
CanonicalizedSecretKeyRing ring = (CanonicalizedSecretKeyRing) uncachedKeyRing.canonicalize(log, 2, true);
@ -320,6 +331,16 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
return true;
}
private void addExtraHeadersToStream(ArmoredOutputStream arOutStream, List<String> headers) {
for (String header : headers) {
if (!HEADER_PATTERN.matcher(header).matches()) {
throw new IllegalArgumentException("bad header format");
}
int sep = header.indexOf(':');
arOutStream.setHeader(header.substring(0, sep), header.substring(sep + 2));
}
}
private Cursor queryForKeys(long[] masterKeyIds) {
String selection = null, selectionArgs[] = null;

View File

@ -829,10 +829,12 @@ public class OpenPgpService extends Service {
}
}
List<String> headerLines = data.getStringArrayListExtra(OpenPgpApi.EXTRA_CUSTOM_HEADERS);
Passphrase autocryptTransferCode = Numeric9x4PassphraseUtil.generateNumeric9x4Passphrase();
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel(autocryptTransferCode);
BackupKeyringParcel input = BackupKeyringParcel.createExportAutocryptSetupMessage(masterKeyIds);
BackupKeyringParcel input = BackupKeyringParcel.createExportAutocryptSetupMessage(masterKeyIds, headerLines);
BackupOperation op = new BackupOperation(this, mKeyRepository, null);
ExportResult pgpResult = op.execute(input, inputParcel, outputStream);

View File

@ -18,6 +18,8 @@
package org.sufficientlysecure.keychain.service;
import java.util.List;
import android.net.Uri;
import android.os.Parcelable;
import android.support.annotation.Nullable;
@ -36,15 +38,24 @@ public abstract class BackupKeyringParcel implements Parcelable {
public abstract boolean getEnableAsciiArmorOutput();
@Nullable
public abstract Uri getOutputUri();
@Nullable
public abstract List<String> getExtraHeaders();
public static BackupKeyringParcel create(long[] masterKeyIds, boolean exportSecret,
boolean isEncrypted, boolean enableAsciiArmorOutput, Uri outputUri) {
return new AutoValue_BackupKeyringParcel(
masterKeyIds, exportSecret, true, isEncrypted, enableAsciiArmorOutput, outputUri);
masterKeyIds, exportSecret, true, isEncrypted, enableAsciiArmorOutput, outputUri, null);
}
public static BackupKeyringParcel createExportAutocryptSetupMessage(long[] masterKeyIds) {
public static BackupKeyringParcel create(long[] masterKeyIds, boolean exportSecret,
boolean isEncrypted, boolean enableAsciiArmorOutput, Uri outputUri, List<String> extraHeaders) {
return new AutoValue_BackupKeyringParcel(
masterKeyIds, true, false, true, true, null);
masterKeyIds, exportSecret, true, isEncrypted, enableAsciiArmorOutput, outputUri, extraHeaders);
}
public static BackupKeyringParcel createExportAutocryptSetupMessage(long[] masterKeyIds,
List<String> extraHeaders) {
return new AutoValue_BackupKeyringParcel(
masterKeyIds, true, false, true, true, null, extraHeaders);
}
}

View File

@ -24,6 +24,7 @@ import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.security.Security;
import java.util.Arrays;
import java.util.Iterator;
import android.app.Application;
@ -157,7 +158,7 @@ public class BackupOperationTest {
assertTrue("second keyring has local certification", checkForLocal(mStaticRing2));
ByteArrayOutputStream out = new ByteArrayOutputStream();
boolean result = op.exportKeysToStream(new OperationLog(), null, false, true, out);
boolean result = op.exportKeysToStream(new OperationLog(), null, false, true, out, null);
assertTrue("export must be a success", result);
@ -194,7 +195,7 @@ public class BackupOperationTest {
}
out = new ByteArrayOutputStream();
result = op.exportKeysToStream(new OperationLog(), null, true, true, out);
result = op.exportKeysToStream(new OperationLog(), null, true, true, out, null);
assertTrue("export must be a success", result);
@ -238,6 +239,22 @@ public class BackupOperationTest {
}
@Test
public void testExportWithExtraHeaders() throws Exception {
BackupOperation op = new BackupOperation(RuntimeEnvironment.application,
KeyWritableRepository.create(RuntimeEnvironment.application), null);
ByteArrayOutputStream out = new ByteArrayOutputStream();
boolean result = op.exportKeysToStream(
new OperationLog(), new long[] { mStaticRing1.getMasterKeyId() }, true, false,
out, Arrays.asList("header: value"));
assertTrue(result);
String resultData = new String(out.toByteArray());
assertTrue(resultData.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\nheader: value\n\n"));
}
@Test
public void testExportUnencrypted() throws Exception {
ContentResolver mockResolver = mock(ContentResolver.class);

@ -1 +1 @@
Subproject commit 3631265b348a02ea3c79f4f38f6c031a82298ed5
Subproject commit c2ddaa76bbb8819dafff55ae4af00ac40c94e6fb