diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java index 7f51a9e9b..f88731800 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java @@ -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 { 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 { 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 { } boolean exportKeysToStream(OperationLog log, long[] masterKeyIds, boolean exportSecret, boolean exportPublic, - OutputStream outStream) { + OutputStream outStream, List extraSecretKeyHeaders) { // noinspection unused TODO use these in a log entry int okSecret = 0, okPublic = 0; @@ -253,9 +258,10 @@ public class BackupOperation extends BaseOperation { 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 { return true; } - private boolean writeSecretKeyToStream(long masterKeyId, OperationLog log, OutputStream outStream) + private boolean writeSecretKeyToStream(long masterKeyId, OperationLog log, OutputStream outStream, + List 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 { return true; } + private void addExtraHeadersToStream(ArmoredOutputStream arOutStream, List 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; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 46475c015..01e8238e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -829,10 +829,12 @@ public class OpenPgpService extends Service { } } + List 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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/BackupKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/BackupKeyringParcel.java index 78b67c2d4..f88580fe2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/BackupKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/BackupKeyringParcel.java @@ -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 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 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 extraHeaders) { + return new AutoValue_BackupKeyringParcel( + masterKeyIds, true, false, true, true, null, extraHeaders); } } \ No newline at end of file diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java index 190072597..7417d4314 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java @@ -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); diff --git a/extern/openpgp-api-lib b/extern/openpgp-api-lib index 3631265b3..c2ddaa76b 160000 --- a/extern/openpgp-api-lib +++ b/extern/openpgp-api-lib @@ -1 +1 @@ -Subproject commit 3631265b348a02ea3c79f4f38f6c031a82298ed5 +Subproject commit c2ddaa76bbb8819dafff55ae4af00ac40c94e6fb