diff options
author | Dominik Schürmann <dominik@dominikschuermann.de> | 2015-04-21 20:09:52 +0200 |
---|---|---|
committer | Dominik Schürmann <dominik@dominikschuermann.de> | 2015-04-21 20:09:52 +0200 |
commit | 7d273ecfa661400aa56f6d2dd1acbae5198912ef (patch) | |
tree | 5a761a87c10995361b22a61eda474822a29958c3 /OpenKeychain/src | |
parent | 7a097dd5df441b5ad2a79a8444ff973bcdc1822a (diff) | |
parent | b0efee2526e5bb60abf459607e7ab80e4ef28420 (diff) | |
download | open-keychain-7d273ecfa661400aa56f6d2dd1acbae5198912ef.tar.gz open-keychain-7d273ecfa661400aa56f6d2dd1acbae5198912ef.tar.bz2 open-keychain-7d273ecfa661400aa56f6d2dd1acbae5198912ef.zip |
Merge branch 'development' of github.com:open-keychain/open-keychain into development
Diffstat (limited to 'OpenKeychain/src')
10 files changed, 212 insertions, 52 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index d0f50d5cc..8c66176fd 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -624,6 +624,23 @@ <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" /> </intent-filter> + <!-- VIEW from keyserver urls opened in a browser --> + <intent-filter android:label="@string/intent_import_key"> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + + <data android:scheme="https"/> + <data android:scheme="http"/> + <!-- if we don't specify a host, pathPattern will be ignored--> + <data android:host="*"/> + <!-- convention for keyserver paths specified by internet draft + draft-shaw-openpgp-hkp-00.txt + (http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-3) --> + <data android:pathPattern="/pks/lookup.*"/> + </intent-filter> + <!-- Keychain's own Actions --> <!-- IMPORT_KEY with files TODO: does this work? --> <intent-filter android:label="@string/intent_import_key"> diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java index 1cd0aaf2f..db62d53c5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java @@ -123,7 +123,8 @@ public class CreateKeyYubiImportFragment extends Fragment implements NfcListener }); } - mListFragment = ImportKeysListFragment.newInstance(null, null, "0x" + mNfcFingerprint, true); + mListFragment = ImportKeysListFragment.newInstance(null, null, + "0x" + mNfcFingerprint, true, null); view.findViewById(R.id.button_search).setOnClickListener(new OnClickListener() { @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 7fe5be793..5d9950db6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -39,6 +39,7 @@ import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.service.CloudImportService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; @@ -62,6 +63,8 @@ public class ImportKeysActivity extends BaseNfcActivity { // Actions for internal use only: public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_FILE"; + public static final String ACTION_SEARCH_KEYSERVER_FROM_URL = Constants.INTENT_PREFIX + + "SEARCH_KEYSERVER_FROM_URL"; public static final String EXTRA_RESULT = "result"; // only used by ACTION_IMPORT_KEY @@ -112,15 +115,19 @@ public class ImportKeysActivity extends BaseNfcActivity { } if (action == null) { - startCloudFragment(savedInstanceState, null, false); - startListFragment(savedInstanceState, null, null, null); + startCloudFragment(savedInstanceState, null, false, null); + startListFragment(savedInstanceState, null, null, null, null); return; } if (Intent.ACTION_VIEW.equals(action)) { - // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) - // delegate action to ACTION_IMPORT_KEY - action = ACTION_IMPORT_KEY; + if (scheme.equals("http") || scheme.equals("https")) { + action = ACTION_SEARCH_KEYSERVER_FROM_URL; + } else { + // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) + // delegate action to ACTION_IMPORT_KEY + action = ACTION_IMPORT_KEY; + } } switch (action) { @@ -130,12 +137,12 @@ public class ImportKeysActivity extends BaseNfcActivity { if (dataUri != null) { // action: directly load data - startListFragment(savedInstanceState, null, dataUri, null); + startListFragment(savedInstanceState, null, dataUri, null, null); } else if (extras.containsKey(EXTRA_KEY_BYTES)) { byte[] importData = extras.getByteArray(EXTRA_KEY_BYTES); // action: directly load data - startListFragment(savedInstanceState, importData, null, null); + startListFragment(savedInstanceState, importData, null, null, null); } break; } @@ -162,10 +169,10 @@ public class ImportKeysActivity extends BaseNfcActivity { if (query != null && query.length() > 0) { // display keyserver fragment with query - startCloudFragment(savedInstanceState, query, false); + startCloudFragment(savedInstanceState, query, false, null); // action: search immediately - startListFragment(savedInstanceState, null, null, query); + startListFragment(savedInstanceState, null, null, query, null); } else { Log.e(Constants.TAG, "Query is empty!"); return; @@ -181,10 +188,10 @@ public class ImportKeysActivity extends BaseNfcActivity { String query = "0x" + fingerprint; // display keyserver fragment with query - startCloudFragment(savedInstanceState, query, true); + startCloudFragment(savedInstanceState, query, true, null); // action: search immediately - startListFragment(savedInstanceState, null, null, query); + startListFragment(savedInstanceState, null, null, query, null); } } else { Log.e(Constants.TAG, @@ -200,7 +207,29 @@ public class ImportKeysActivity extends BaseNfcActivity { startFileFragment(savedInstanceState); // no immediate actions! - startListFragment(savedInstanceState, null, null, null); + startListFragment(savedInstanceState, null, null, null, null); + break; + } + case ACTION_SEARCH_KEYSERVER_FROM_URL: { + // need to process URL to get search query and keyserver authority + String query = dataUri.getQueryParameter("search"); + String keyserver = dataUri.getAuthority(); + // if query not specified, we still allow users to search the keyserver in the link + if (query == null) { + Notify.create(this, R.string.import_url_warn_no_search_parameter, Notify.LENGTH_INDEFINITE, + Notify.Style.WARN).show(mTopFragment); + // we just set the keyserver + startCloudFragment(savedInstanceState, null, false, keyserver); + // it's not necessary to set the keyserver for ImportKeysListFragment since + // it'll be taken care of by ImportKeysCloudFragment when the user clicks + // the search button + startListFragment(savedInstanceState, null, null, null, null); + } else { + // we allow our users to edit the query if they wish + startCloudFragment(savedInstanceState, query, false, keyserver); + // search immediately + startListFragment(savedInstanceState, null, null, query, keyserver); + } break; } case ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN: { @@ -208,18 +237,31 @@ public class ImportKeysActivity extends BaseNfcActivity { startFileFragment(savedInstanceState); // no immediate actions! - startListFragment(savedInstanceState, null, null, null); + startListFragment(savedInstanceState, null, null, null, null); break; } default: { - startCloudFragment(savedInstanceState, null, false); - startListFragment(savedInstanceState, null, null, null); + startCloudFragment(savedInstanceState, null, false, null); + startListFragment(savedInstanceState, null, null, null, null); break; } } } - private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri, String serverQuery) { + + /** + * if the fragment is started with non-null bytes/dataUri/serverQuery, it will immediately + * load content + * + * @param savedInstanceState + * @param bytes bytes containing list of keyrings to import + * @param dataUri uri to file to import keyrings from + * @param serverQuery query to search for on the keyserver + * @param keyserver keyserver authority to search on. If null will use keyserver from + * user preferences + */ + private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri, + String serverQuery, String keyserver) { // 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. @@ -227,8 +269,8 @@ public class ImportKeysActivity extends BaseNfcActivity { return; } - // Create an instance of the fragment - mListFragment = ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery); + mListFragment = ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false, + keyserver); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! @@ -259,7 +301,18 @@ public class ImportKeysActivity extends BaseNfcActivity { getSupportFragmentManager().executePendingTransactions(); } - private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit) { + /** + * loads the CloudFragment, which consists of the search bar, search button and settings icon + * visually. + * + * @param savedInstanceState + * @param query search query + * @param disableQueryEdit if true, user will not be able to edit the search query + * @param keyserver keyserver authority to use for search, if null will use keyserver + * specified in user preferences + */ + + private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit, String keyserver) { // 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. @@ -268,7 +321,7 @@ public class ImportKeysActivity extends BaseNfcActivity { } // Create an instance of the fragment - mTopFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit); + mTopFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit, keyserver); // 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/ImportKeysCloudFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java index 91ca93c36..1cd5c24f3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java @@ -45,6 +45,7 @@ import java.util.List; public class ImportKeysCloudFragment extends Fragment { public static final String ARG_QUERY = "query"; public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit"; + public static final String ARG_KEYSERVER = "keyserver"; private ImportKeysActivity mImportActivity; @@ -54,13 +55,20 @@ public class ImportKeysCloudFragment extends Fragment { /** * Creates new instance of this fragment + * + * @param query query to search for + * @param disableQueryEdit if true, user cannot edit query + * @param keyserver specified keyserver authority to use. If null, will use keyserver + * specified in user preferences */ - public static ImportKeysCloudFragment newInstance(String query, boolean disableQueryEdit) { + public static ImportKeysCloudFragment newInstance(String query, boolean disableQueryEdit, + String keyserver) { ImportKeysCloudFragment frag = new ImportKeysCloudFragment(); Bundle args = new Bundle(); args.putString(ARG_QUERY, query); args.putBoolean(ARG_DISABLE_QUERY_EDIT, disableQueryEdit); + args.putString(ARG_KEYSERVER, keyserver); frag.setArguments(args); @@ -151,8 +159,17 @@ public class ImportKeysCloudFragment extends Fragment { } private void search(String query) { - Preferences prefs = Preferences.getPreferences(getActivity()); - mImportActivity.loadCallback(new ImportKeysListFragment.CloudLoaderState(query, prefs.getCloudSearchPrefs())); + Preferences.CloudSearchPrefs cloudSearchPrefs; + String explicitKeyserver = getArguments().getString(ARG_KEYSERVER); + // no explicit keyserver passed + if (explicitKeyserver == null) { + cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs(); + } else { + // assume we are also meant to search keybase.io + cloudSearchPrefs = new Preferences.CloudSearchPrefs(true, true, explicitKeyserver); + } + mImportActivity.loadCallback( + new ImportKeysListFragment.CloudLoaderState(query, cloudSearchPrefs)); toggleKeyboard(false); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index b9fdbea5c..81ff87216 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -58,6 +58,7 @@ public class ImportKeysListFragment extends ListFragment implements private static final String ARG_BYTES = "bytes"; public static final String ARG_SERVER_QUERY = "query"; public static final String ARG_NON_INTERACTIVE = "non_interactive"; + public static final String ARG_KEYSERVER_URL = "keyserver_url"; private Activity mActivity; private ImportKeysAdapter mAdapter; @@ -78,7 +79,8 @@ public class ImportKeysListFragment extends ListFragment implements return mAdapter.getData(); } - /** Returns an Iterator (with size) of the selected data items. + /** + * Returns an Iterator (with size) of the selected data items. * This iterator is sort of a tradeoff, it's slightly more complex than an * ArrayList would have been, but we save some memory by just returning * relevant elements on demand. @@ -121,12 +123,36 @@ public class ImportKeysListFragment extends ListFragment implements } - public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery) { - return newInstance(bytes, dataUri, serverQuery, false); + /** + * Creates an interactive ImportKeyListFragment which reads keyrings from bytes, or file specified + * by dataUri, or searches a keyserver for serverQuery, if parameter is not null, in that order + * + * @param bytes byte data containing list of keyrings to be imported + * @param dataUri file from which keyrings are to be imported + * @param serverQuery query to search for on keyserver + * @param keyserver if not null, will perform search on specified keyserver. Else, uses + * keyserver specified in user preferences + * @return fragment with arguments set based on passed parameters + */ + public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery, + String keyserver) { + return newInstance(bytes, dataUri, serverQuery, false, keyserver); } - public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, - String serverQuery, boolean nonInteractive) { + /** + * Visually consists of a list of keyrings with checkboxes to specify which are to be imported + * Can immediately load keyrings specified by any of its parameters + * + * @param bytes byte data containing list of keyrings to be imported + * @param dataUri file from which keyrings are to be imported + * @param serverQuery query to search for on keyserver + * @param nonInteractive if true, users will not be able to check/uncheck items in the list + * @param keyserver if set, will perform search on specified keyserver. If null, falls back + * to keyserver specified in user preferences + * @return fragment with arguments set based on passed parameters + */ + public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery, + boolean nonInteractive, String keyserver) { ImportKeysListFragment frag = new ImportKeysListFragment(); Bundle args = new Bundle(); @@ -134,6 +160,7 @@ public class ImportKeysListFragment extends ListFragment implements args.putParcelable(ARG_DATA_URI, dataUri); args.putString(ARG_SERVER_QUERY, serverQuery); args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive); + args.putString(ARG_KEYSERVER_URL, keyserver); frag.setArguments(args); @@ -180,16 +207,23 @@ public class ImportKeysListFragment extends ListFragment implements setListAdapter(mAdapter); Bundle args = getArguments(); - Uri dataUri = args.containsKey(ARG_DATA_URI) ? args.<Uri>getParcelable(ARG_DATA_URI) : null; - byte[] bytes = args.containsKey(ARG_BYTES) ? args.getByteArray(ARG_BYTES) : null; - String query = args.containsKey(ARG_SERVER_QUERY) ? args.getString(ARG_SERVER_QUERY) : null; - mNonInteractive = args.containsKey(ARG_NON_INTERACTIVE) ? args.getBoolean(ARG_NON_INTERACTIVE) : false; + Uri dataUri = args.getParcelable(ARG_DATA_URI); + byte[] bytes = args.getByteArray(ARG_BYTES); + String query = args.getString(ARG_SERVER_QUERY); + String keyserver = args.getString(ARG_KEYSERVER_URL); + mNonInteractive = args.getBoolean(ARG_NON_INTERACTIVE, false); if (dataUri != null || bytes != null) { mLoaderState = new BytesLoaderState(bytes, dataUri); } else if (query != null) { - Preferences prefs = Preferences.getPreferences(getActivity()); - mLoaderState = new CloudLoaderState(query, prefs.getCloudSearchPrefs()); + Preferences.CloudSearchPrefs cloudSearchPrefs; + if (keyserver != null) { + cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs(); + } else { + cloudSearchPrefs = new Preferences.CloudSearchPrefs(true, true, keyserver); + } + + mLoaderState = new CloudLoaderState(query, cloudSearchPrefs); } getListView().setOnTouchListener(new OnTouchListener() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java index b9f1bf870..5c49de727 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java @@ -58,13 +58,12 @@ import java.util.Locale; public class ImportKeysProxyActivity extends FragmentActivity { public static final String ACTION_QR_CODE_API = OpenKeychainIntents.IMPORT_KEY_FROM_QR_CODE; + // implies activity returns scanned fingerprint as extra and does not import public static final String ACTION_SCAN_WITH_RESULT = Constants.INTENT_PREFIX + "SCAN_QR_CODE_WITH_RESULT"; public static final String ACTION_SCAN_IMPORT = Constants.INTENT_PREFIX + "SCAN_QR_CODE_IMPORT"; public static final String EXTRA_FINGERPRINT = "fingerprint"; - boolean returnResult; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -82,10 +81,8 @@ public class ImportKeysProxyActivity extends FragmentActivity { if (scheme != null && scheme.toLowerCase(Locale.ENGLISH).equals(Constants.FINGERPRINT_SCHEME)) { // Scanning a fingerprint directly with Barcode Scanner, thus we already have scanned - returnResult = false; processScannedContent(dataUri); } else if (ACTION_SCAN_IMPORT.equals(action) || ACTION_QR_CODE_API.equals(action)) { - returnResult = false; IntentIntegrator integrator = new IntentIntegrator(this); integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES) .setPrompt(getString(R.string.import_qr_code_text)) @@ -93,7 +90,6 @@ public class ImportKeysProxyActivity extends FragmentActivity { integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); integrator.initiateScan(); } else if (ACTION_SCAN_WITH_RESULT.equals(action)) { - returnResult = true; IntentIntegrator integrator = new IntentIntegrator(this); integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES) .setPrompt(getString(R.string.import_qr_code_text)) @@ -103,7 +99,6 @@ public class ImportKeysProxyActivity extends FragmentActivity { } else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { // Check to see if the Activity started due to an Android Beam if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - returnResult = false; handleActionNdefDiscovered(getIntent()); } else { Log.e(Constants.TAG, "Android Beam not supported by Android < 4.1"); @@ -148,6 +143,8 @@ public class ImportKeysProxyActivity extends FragmentActivity { private void processScannedContent(Uri uri) { + String action = getIntent().getAction(); + Log.d(Constants.TAG, "scanned: " + uri); String fingerprint = null; @@ -166,7 +163,7 @@ public class ImportKeysProxyActivity extends FragmentActivity { return; } - if (returnResult) { + if (ACTION_SCAN_WITH_RESULT.equals(action)) { Intent result = new Intent(); result.putExtra(EXTRA_FINGERPRINT, fingerprint); setResult(RESULT_OK, result); @@ -177,15 +174,17 @@ public class ImportKeysProxyActivity extends FragmentActivity { } public void returnResult(Intent data) { - if (returnResult) { - setResult(RESULT_OK, data); - finish(); - } else { + String action = getIntent().getAction(); + + if (ACTION_QR_CODE_API.equals(action)) { // display last log message but as Toast for calls from outside OpenKeychain OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); String str = getString(result.getLog().getLast().mType.getMsgId()); Toast.makeText(this, str, Toast.LENGTH_LONG).show(); finish(); + } else { + setResult(RESULT_OK, data); + finish(); } } @@ -209,7 +208,7 @@ public class ImportKeysProxyActivity extends FragmentActivity { } - private void startImportService (ArrayList<ParcelableKeyRing> keyRings) { + private void startImportService(ArrayList<ParcelableKeyRing> keyRings) { // Message is received after importing is done in KeychainIntentService ServiceProgressHandler serviceHandler = new ServiceProgressHandler( diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java index 5447c5f96..139512ba9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java @@ -82,12 +82,12 @@ public class ImportKeysListLoader @Override protected void onStartLoading() { - forceLoad(); + super.forceLoad(); } @Override protected void onStopLoading() { - cancelLoad(); + super.cancelLoad(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java index db7aba519..1d09b281f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java @@ -57,6 +57,9 @@ public abstract class BaseNfcActivity extends BaseActivity { public static final int REQUEST_CODE_PASSPHRASE = 1; protected Passphrase mPin; + protected boolean mPw1ValidForMultipleSignatures; + protected boolean mPw1ValidatedForSignature; + protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? private NfcAdapter mNfcAdapter; private IsoDep mIsoDep; @@ -197,10 +200,15 @@ public abstract class BaseNfcActivity extends BaseActivity { + "06" // Lc (number of bytes) + "D27600012401" // Data (6 bytes) + "00"; // Le - if ( ! nfcCommunicate(opening).equals(accepted)) { // activate connection + if ( ! nfcCommunicate(opening).endsWith(accepted)) { // activate connection throw new IOException("Initialization failed!"); } + byte[] pwStatusBytes = nfcGetPwStatusBytes(); + mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1); + mPw1ValidatedForSignature = false; + mPw1ValidatedForDecrypt = false; + onNfcPerform(); mIsoDep.close(); @@ -278,6 +286,15 @@ public abstract class BaseNfcActivity extends BaseActivity { return fptlv.mV; } + /** Return the PW Status Bytes from the card. This is a simple DO; no TLV decoding needed. + * + * @return Seven bytes in fixed format, plus 0x9000 status word at the end. + */ + public byte[] nfcGetPwStatusBytes() throws IOException { + String data = "00CA00C400"; + return mIsoDep.transceive(Hex.decode(data)); + } + /** Return the fingerprint from application specific data stored on tag, or * null if it doesn't exist. * @@ -316,7 +333,9 @@ public abstract class BaseNfcActivity extends BaseActivity { * @return a big integer representing the MPI for the given hash */ public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException { - nfcVerifyPIN(0x81); // (Verify PW1 with mode 81 for signing) + if (!mPw1ValidatedForSignature) { + nfcVerifyPIN(0x81); // (Verify PW1 with mode 81 for signing) + } // dsi, including Lc String dsi; @@ -391,6 +410,10 @@ public abstract class BaseNfcActivity extends BaseActivity { Log.d(Constants.TAG, "final response:" + status); + if (!mPw1ValidForMultipleSignatures) { + mPw1ValidatedForSignature = false; + } + if ( ! "9000".equals(status)) { throw new IOException("Bad NFC response code: " + status); } @@ -410,7 +433,9 @@ public abstract class BaseNfcActivity extends BaseActivity { * @return the decoded session key */ public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException { - nfcVerifyPIN(0x82); // (Verify PW1 with mode 82 for decryption) + if (!mPw1ValidatedForDecrypt) { + nfcVerifyPIN(0x82); // (Verify PW1 with mode 82 for decryption) + } String firstApdu = "102a8086fe"; String secondApdu = "002a808603"; @@ -458,6 +483,12 @@ public abstract class BaseNfcActivity extends BaseActivity { handlePinError(); throw new IOException("Bad PIN!"); } + + if (mode == 0x81) { + mPw1ValidatedForSignature = true; + } else if (mode == 0x82) { + mPw1ValidatedForDecrypt = true; + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java index 8a7638054..303687315 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -193,6 +193,11 @@ public class Preferences { public final boolean searchKeybase; public final String keyserver; + /** + * @param searchKeyserver should passed keyserver be searched + * @param searchKeybase should keybase.io be searched + * @param keyserver the keyserver url authority to search on + */ public CloudSearchPrefs(boolean searchKeyserver, boolean searchKeybase, String keyserver) { this.searchKeyserver = searchKeyserver; this.searchKeybase = searchKeybase; diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index c1e3b51f9..d4c67aa23 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -404,6 +404,9 @@ <string name="import_qr_code_button">"Scan QR Code"</string> <string name="import_qr_code_text">"Place your camera over the QR Code!"</string> + <!-- Import from URL --> + <string name="import_url_warn_no_search_parameter">"No search parameter found. You may still attempt manually searching the keyserver."</string> + <!-- Generic result toast --> <string name="view_log">"Details"</string> <string name="with_warnings">", with warnings"</string> |