From b06e7cd737c9f85c37fb2d17533cc1a2b25715a9 Mon Sep 17 00:00:00 2001 From: William Faulk Date: Wed, 29 Apr 2015 19:59:32 -0400 Subject: [PATCH] Fix Bluetooth share without breaking others --- .../keychain/KeychainApplication.java | 13 ++++ .../keychain/ui/ViewKeyAdvShareFragment.java | 65 +++++++++++++++++-- .../keychain/ui/util/KeyFormattingUtils.java | 30 ++++++++- OpenKeychain/src/main/res/values/strings.xml | 1 + 4 files changed, 100 insertions(+), 9 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index 710dbf8aa..161979ce3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -40,6 +40,8 @@ import org.sufficientlysecure.keychain.util.PRNGFixes; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.TlsHelper; +import java.io.File; +import java.io.FilenameFilter; import java.security.Security; import java.util.HashMap; @@ -88,6 +90,17 @@ public class KeychainApplication extends Application { } } + // Clean up leftover Bluetooth Share files + for (File toDelete : this.getExternalCacheDir().listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String filename) { + if (filename.matches("^key-[0-9a-fA-F]{8}\\.pgp\\.asc$")) { + return true; + } + return false; + } + })) { toDelete.delete(); } + brandGlowEffect(getApplicationContext(), getApplicationContext().getResources().getColor(R.color.primary)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index 6bd3a9303..4141da202 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -26,6 +26,7 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.os.FileObserver; import android.support.v4.app.ActivityCompat; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; @@ -54,6 +55,9 @@ import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.NfcHelper; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; import java.io.IOException; @@ -175,11 +179,11 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements boolean toClipboard) { try { String content; + byte[] fingerprintData = (byte[]) providerHelper.getGenericData( + KeyRings.buildUnifiedKeyRingUri(dataUri), + Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); if (fingerprintOnly) { - byte[] data = (byte[]) providerHelper.getGenericData( - KeyRings.buildUnifiedKeyRingUri(dataUri), - Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); - String fingerprint = KeyFormattingUtils.convertFingerprintToHex(data); + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintData); if (!toClipboard) { content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; } else { @@ -213,13 +217,62 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements Intent sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, content); sendIntent.setType("text/plain"); + String title; if (fingerprintOnly) { title = getResources().getString(R.string.title_share_fingerprint_with); } else { title = getResources().getString(R.string.title_share_key); } - startActivity(Intent.createChooser(sendIntent, title)); + Intent shareChooser = Intent.createChooser(sendIntent, title); + + // Bluetooth Share will convert text/plain sent via EXTRA_TEXT to HTML + // Add replacement extra to send a text/plain file instead. + try { + final File contentFile = new File(getActivity().getExternalCacheDir(), + "key-" + KeyFormattingUtils.getShortKeyIdAsHexFromFingerprint(fingerprintData, false) + + ".pgp.asc"); + FileWriter contentFileWriter = new FileWriter(contentFile, false); + BufferedWriter contentWriter = new BufferedWriter(contentFileWriter); + contentWriter.write(content); + contentWriter.close(); + Uri contentUri = Uri.fromFile(contentFile); + + final Runnable deleteContentFile = new Runnable() { + public void run() { + contentFile.delete(); + } + }; + + // delete the file after Bluetooth Share closes the file + FileObserver tempFileObserver = new FileObserver(contentFile.getAbsolutePath(), + FileObserver.CLOSE_NOWRITE) { + @Override + public void onEvent(int event, String path) { + // Hopefully it will only be opened and then closed by the share process once + getContainer().post(deleteContentFile); + } + }; + tempFileObserver.startWatching(); + + // If it's not complete in 1m, the file was not used; delete it + getContainer().postDelayed(deleteContentFile, 1 * 60 * 1000); + + // create replacement extras inside try{}: + // if file creation fails, just don't add the replacements + Bundle replacements = new Bundle(); + shareChooser.putExtra(Intent.EXTRA_REPLACEMENT_EXTRAS, replacements); + + Bundle bluetoothExtra = new Bundle(sendIntent.getExtras()); + replacements.putBundle("com.android.bluetooth", bluetoothExtra); + + bluetoothExtra.putParcelable(Intent.EXTRA_STREAM, contentUri); + } catch (IOException e) { + Log.e(Constants.TAG, "error creating temporary Bluetooth key share file!", e); + Notify.create(getActivity(), R.string.error_bluetooth_file, Notify.Style.ERROR).show(); + } + + startActivity(shareChooser); } } catch (PgpGeneralException | IOException e) { Log.e(Constants.TAG, "error processing key!", e); @@ -379,4 +432,4 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements } -} \ No newline at end of file +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index 91a7d361a..0b80b5fe9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -227,6 +227,14 @@ public class KeyFormattingUtils { return buf.getLong(); } + public static int getShortKeyIdFromFingerprint(byte[] fingerprint) { + ByteBuffer buf = ByteBuffer.wrap(fingerprint); + // skip first 16 bytes of the fingerprint + buf.position(16); + // the last four bytes are the short key id (big endian, which is default order in ByteBuffer) + return buf.getInt(); + } + /** * Convert key id from long to 64 bit hex string *

@@ -238,16 +246,24 @@ public class KeyFormattingUtils { * @return */ public static String convertKeyIdToHex(long keyId) { + return convertKeyIdToHex(keyId, true); + } + + public static String convertKeyIdToHex(long keyId, boolean header) { long upper = keyId >> 32; if (upper == 0) { // this is a short key id - return convertKeyIdToHexShort(keyId); + return convertKeyIdToHexShort(keyId, header); } - return "0x" + convertKeyIdToHex32bit(keyId >> 32) + convertKeyIdToHex32bit(keyId); + return header?"0x":"" + convertKeyIdToHex32bit(keyId >> 32) + convertKeyIdToHex32bit(keyId); } public static String convertKeyIdToHexShort(long keyId) { - return "0x" + convertKeyIdToHex32bit(keyId); + return convertKeyIdToHexShort(keyId, true); + } + + public static String convertKeyIdToHexShort(long keyId, boolean header) { + return header?"0x":"" + convertKeyIdToHex32bit(keyId); } private static String convertKeyIdToHex32bit(long keyId) { @@ -258,6 +274,14 @@ public class KeyFormattingUtils { return hexString; } + public static String getKeyIdAsHexFromFingerprint(byte[] fingerprint, boolean header) { + return convertKeyIdToHex(getKeyIdFromFingerprint(fingerprint), header); + } + + public static String getShortKeyIdAsHexFromFingerprint(byte[] fingerprint, boolean header) { + return convertKeyIdToHex(getShortKeyIdFromFingerprint(fingerprint), header); + } + /** * Makes a human-readable version of a key ID, which is usually 64 bits: lower-case, no * leading 0x, space-separated quartets (for keys whose length in hex is divisible by 4) diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 72ba46702..45b1dc26b 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1289,5 +1289,6 @@ Different key stored on YubiKey! "NFC Error: %s" Default PIN was rejected! + Error creating temporary file. Bluetooth sharing will fail.