From 1b5afc32471c5c056acdb1ccdc749eda45f135cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Thu, 27 Aug 2015 21:53:33 +0200 Subject: Experimental word comparison --- .../keychain/ui/CertifyFingerprintActivity.java | 9 +- .../keychain/ui/CertifyFingerprintFragment.java | 36 +++++- .../keychain/ui/ViewKeyActivity.java | 12 +- .../keychain/ui/util/ExperimentalWordConfirm.java | 126 +++++++++++++++++++++ 4 files changed, 174 insertions(+), 9 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java index 016ab5f3c..c5528e40b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java @@ -30,6 +30,8 @@ public class CertifyFingerprintActivity extends BaseActivity { protected Uri mDataUri; + public static final String EXTRA_ENABLE_WORD_CONFIRM = "enable_word_confirm"; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -40,6 +42,7 @@ public class CertifyFingerprintActivity extends BaseActivity { finish(); return; } + boolean enableWordConfirm = getIntent().getBooleanExtra(EXTRA_ENABLE_WORD_CONFIRM, false); setFullScreenDialogClose(new View.OnClickListener() { @Override @@ -50,7 +53,7 @@ public class CertifyFingerprintActivity extends BaseActivity { Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); - startFragment(savedInstanceState, mDataUri); + startFragment(savedInstanceState, mDataUri, enableWordConfirm); } @Override @@ -58,7 +61,7 @@ public class CertifyFingerprintActivity extends BaseActivity { setContentView(R.layout.certify_fingerprint_activity); } - private void startFragment(Bundle savedInstanceState, Uri dataUri) { + private void startFragment(Bundle savedInstanceState, Uri dataUri, boolean enableWordConfirm) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. @@ -67,7 +70,7 @@ public class CertifyFingerprintActivity extends BaseActivity { } // Create an instance of the fragment - CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri); + CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri, enableWordConfirm); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java index 7a9df1576..552fa34c0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.database.Cursor; +import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.LoaderManager; @@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.util.ExperimentalWordConfirm; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; @@ -44,20 +46,24 @@ public class CertifyFingerprintFragment extends LoaderFragment implements static final int REQUEST_CERTIFY = 1; public static final String ARG_DATA_URI = "uri"; + public static final String ARG_ENABLE_WORD_CONFIRM = "enable_word_confirm"; private TextView mFingerprint; + private TextView mIntro; private static final int LOADER_ID_UNIFIED = 0; private Uri mDataUri; + private boolean mEnableWordConfirm; /** * Creates new instance of this fragment */ - public static CertifyFingerprintFragment newInstance(Uri dataUri) { + public static CertifyFingerprintFragment newInstance(Uri dataUri, boolean enableWordConfirm) { CertifyFingerprintFragment frag = new CertifyFingerprintFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_DATA_URI, dataUri); + args.putBoolean(ARG_ENABLE_WORD_CONFIRM, enableWordConfirm); frag.setArguments(args); @@ -73,6 +79,7 @@ public class CertifyFingerprintFragment extends LoaderFragment implements View actionYes = view.findViewById(R.id.certify_fingerprint_button_yes); mFingerprint = (TextView) view.findViewById(R.id.certify_fingerprint_fingerprint); + mIntro = (TextView) view.findViewById(R.id.certify_fingerprint_intro); actionNo.setOnClickListener(new View.OnClickListener() { @Override @@ -100,6 +107,11 @@ public class CertifyFingerprintFragment extends LoaderFragment implements getActivity().finish(); return; } + mEnableWordConfirm = getArguments().getBoolean(ARG_ENABLE_WORD_CONFIRM); + + if (mEnableWordConfirm) { + mIntro.setText(R.string.certify_fingerprint_text_words); + } loadData(dataUri); } @@ -146,10 +158,13 @@ public class CertifyFingerprintFragment extends LoaderFragment implements switch (loader.getId()) { case LOADER_ID_UNIFIED: { if (data.moveToFirst()) { - byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT); - String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob); - mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint)); + + if (mEnableWordConfirm) { + displayWordConfirm(fingerprintBlob); + } else { + displayHexConfirm(fingerprintBlob); + } break; } @@ -159,6 +174,19 @@ public class CertifyFingerprintFragment extends LoaderFragment implements setContentShown(true); } + private void displayHexConfirm(byte[] fingerprintBlob) { + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob); + mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint)); + } + + private void displayWordConfirm(byte[] fingerprintBlob) { + String fingerprint = ExperimentalWordConfirm.getWords(getActivity(), fingerprintBlob); + + mFingerprint.setTextSize(24); + mFingerprint.setTypeface(Typeface.DEFAULT, Typeface.BOLD); + mFingerprint.setText(fingerprint); + } + /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 1d0e085da..1c4a096b2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -349,7 +349,11 @@ public class ViewKeyActivity extends BaseNfcActivity implements return true; } case R.id.menu_key_view_certify_fingerprint: { - certifyFingeprint(mDataUri); + certifyFingeprint(mDataUri, false); + return true; + } + case R.id.menu_key_view_certify_fingerprint_word: { + certifyFingeprint(mDataUri, true); return true; } } @@ -364,6 +368,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements exportKey.setVisible(mIsSecret); MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint); certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked); + MenuItem certifyFingerprintWord = menu.findItem(R.id.menu_key_view_certify_fingerprint_word); + certifyFingerprintWord.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked + && Preferences.getPreferences(this).getExperimentalEnableWordConfirm()); return true; } @@ -375,9 +382,10 @@ public class ViewKeyActivity extends BaseNfcActivity implements startActivityForResult(scanQrCode, REQUEST_QR_FINGERPRINT); } - private void certifyFingeprint(Uri dataUri) { + private void certifyFingeprint(Uri dataUri, boolean enableWordConfirm) { Intent intent = new Intent(this, CertifyFingerprintActivity.class); intent.setData(dataUri); + intent.putExtra(CertifyFingerprintActivity.EXTRA_ENABLE_WORD_CONFIRM, enableWordConfirm); startActivityForResult(intent, REQUEST_CERTIFY); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java new file mode 100644 index 000000000..43ccac24f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.util; + +import android.content.Context; + +import org.spongycastle.util.Arrays; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.BitSet; + +public class ExperimentalWordConfirm { + + public static String getWords(Context context, byte[] fingerprintBlob) { + ArrayList words = new ArrayList<>(); + + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader( + context.getAssets().open("word_confirm_list.txt"), + "UTF-8" + )); + + String line = reader.readLine(); + while (line != null) { + words.add(line); + + line = reader.readLine(); + } + } catch (IOException e) { + throw new RuntimeException("IOException", e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException ignored) { + } + } + } + + String fingerprint = ""; + + // NOTE: 160 bit SHA-1 truncated to 156 bit + byte[] fingerprintBlobTruncated = Arrays.copyOfRange(fingerprintBlob, 0, 156 / 8); + + // TODO: implement key stretching to minimize fp length? + + // BitSet bits = BitSet.valueOf(fingerprintBlob); // min API 19 and little endian! + BitSet bits = bitSetToByteArray(fingerprintBlobTruncated); + Log.d(Constants.TAG, "bits: " + bits.toString()); + + final int CHUNK_SIZE = 13; + final int LAST_CHUNK_INDEX = fingerprintBlobTruncated.length * 8 / CHUNK_SIZE; // 12 + Log.d(Constants.TAG, "LAST_CHUNK_INDEX: " + LAST_CHUNK_INDEX); + + int from = 0; + int to = CHUNK_SIZE; + for (int i = 0; i < (LAST_CHUNK_INDEX + 1); i++) { + Log.d(Constants.TAG, "from: " + from + " to: " + to); + + BitSet setIndex = bits.get(from, to); + int wordIndex = (int) bitSetToLong(setIndex); + // int wordIndex = (int) setIndex.toLongArray()[0]; // min API 19 + + fingerprint += words.get(wordIndex); + + if (i != LAST_CHUNK_INDEX) { + // line break every 3 words + if (to % (CHUNK_SIZE * 3) == 0) { + fingerprint += "\n"; + } else { + fingerprint += " "; + } + } + + from = to; + to += CHUNK_SIZE; + } + + return fingerprint; + } + + /** + * Returns a BitSet containing the values in bytes. + * BIG ENDIAN! + */ + private static BitSet bitSetToByteArray(byte[] bytes) { + int arrayLength = bytes.length * 8; + BitSet bits = new BitSet(); + + for (int i = 0; i < arrayLength; i++) { + if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) { + bits.set(i); + } + } + return bits; + } + + private static long bitSetToLong(BitSet bits) { + long value = 0L; + for (int i = 0; i < bits.length(); ++i) { + value += bits.get(i) ? (1L << i) : 0L; + } + return value; + } +} -- cgit v1.2.3