work on ad-hoc yubikey import support

This commit is contained in:
Vincent Breitmoser 2015-03-21 19:52:10 +01:00
parent 147003123f
commit 1ad3635d13
7 changed files with 366 additions and 38 deletions

View File

@ -90,6 +90,8 @@
android:name=".ui.CreateKeyActivity" android:name=".ui.CreateKeyActivity"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
android:label="@string/title_manage_my_keys" android:label="@string/title_manage_my_keys"
android:launchMode="singleTop"
android:allowTaskReparenting="true"
android:parentActivityName=".ui.MainActivity"> android:parentActivityName=".ui.MainActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"

View File

@ -31,13 +31,18 @@ public class PromoteKeyResult extends OperationResult {
public PromoteKeyResult(Parcel source) { public PromoteKeyResult(Parcel source) {
super(source); super(source);
mMasterKeyId = source.readLong(); mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
} }
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags); super.writeToParcel(dest, flags);
dest.writeLong(mMasterKeyId); if (mMasterKeyId != null) {
dest.writeInt(1);
dest.writeLong(mMasterKeyId);
} else {
dest.writeInt(0);
}
} }
public static Creator<PromoteKeyResult> CREATOR = new Creator<PromoteKeyResult>() { public static Creator<PromoteKeyResult> CREATOR = new Creator<PromoteKeyResult>() {

View File

@ -487,7 +487,7 @@ public class KeychainIntentService extends IntentService implements Progressable
case ACTION_PROMOTE_KEYRING: { case ACTION_PROMOTE_KEYRING: {
// Input // Input
long keyRingId = data.getInt(EXPORT_KEY_RING_MASTER_KEY_ID); long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID);
// Operation // Operation
PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled); PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled);

View File

@ -17,23 +17,18 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NfcAdapter;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
public class CreateKeyActivity extends BaseActivity { public class CreateKeyActivity extends BaseNfcActivity {
public static final String EXTRA_NAME = "name"; public static final String EXTRA_NAME = "name";
public static final String EXTRA_EMAIL = "email"; public static final String EXTRA_EMAIL = "email";
@ -85,6 +80,13 @@ public class CreateKeyActivity extends BaseActivity {
} }
} }
@Override
protected void onNfcPerform() throws IOException {
if (mCurrentFragment instanceof NfcListenerFragment) {
((NfcListenerFragment) mCurrentFragment).onNfcPerform();
}
}
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
@ -135,4 +137,8 @@ public class CreateKeyActivity extends BaseActivity {
getSupportFragmentManager().executePendingTransactions(); getSupportFragmentManager().executePendingTransactions();
} }
interface NfcListenerFragment {
public void onNfcPerform() throws IOException;
}
} }

View File

@ -17,26 +17,77 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView;
import android.widget.ViewAnimator;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment;
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.widget.NameEditText; import org.sufficientlysecure.keychain.ui.widget.NameEditText;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
public class CreateKeyYubiFragment extends Fragment { public class CreateKeyYubiFragment extends Fragment implements NfcListenerFragment,
LoaderManager.LoaderCallbacks<Cursor> {
CreateKeyActivity mCreateKeyActivity; CreateKeyActivity mCreateKeyActivity;
NameEditText mNameEdit; NameEditText mNameEdit;
View mBackButton; View mBackButton;
View mNextButton; View mNextButton;
private TextView mUnknownFingerprint;
public static final String ARGS_MASTER_KEY_ID = "master_key_id";
private byte[] mScannedFingerprint;
private long mScannedMasterKeyId;
private ViewAnimator mAnimator;
private TextView mFingerprint;
private TextView mUserId;
private YubiImportState mState = YubiImportState.SCAN;
enum YubiImportState {
SCAN, // waiting for scan
UNKNOWN, // scanned unknown key (ready to import)
BAD_FINGERPRINT, // scanned key, bad fingerprint
IMPORTED, // imported key (ready to promote)
}
private static boolean isEditTextNotEmpty(Context context, EditText editText) { private static boolean isEditTextNotEmpty(Context context, EditText editText) {
boolean output = true; boolean output = true;
@ -55,6 +106,16 @@ public class CreateKeyYubiFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.create_yubikey_fragment, container, false); View view = inflater.inflate(R.layout.create_yubikey_fragment, container, false);
mAnimator = (ViewAnimator) view.findViewById(R.id.create_yubikey_animator);
mUnknownFingerprint = (TextView) view.findViewById(R.id.create_yubikey_unknown_fp);
mFingerprint = (TextView) view.findViewById(R.id.create_yubikey_fingerprint);
mUserId = (TextView) view.findViewById(R.id.create_yubikey_user_id);
mBackButton = view.findViewById(R.id.create_key_back_button);
mNextButton = view.findViewById(R.id.create_key_next_button);
mBackButton.setOnClickListener(new View.OnClickListener() { mBackButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -77,14 +138,200 @@ public class CreateKeyYubiFragment extends Fragment {
mCreateKeyActivity = (CreateKeyActivity) getActivity(); mCreateKeyActivity = (CreateKeyActivity) getActivity();
} }
private void nextClicked() { @Override
if (isEditTextNotEmpty(getActivity(), mNameEdit)) { public void onNfcPerform() throws IOException {
// save state
mCreateKeyActivity.mName = mNameEdit.getText().toString();
CreateKeyEmailFragment frag = CreateKeyEmailFragment.newInstance(); mScannedFingerprint = mCreateKeyActivity.nfcGetFingerprint(0);
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); mScannedMasterKeyId = getKeyIdFromFingerprint(mScannedFingerprint);
getLoaderManager().initLoader(0, null, this);
}
// These are the rows that we will retrieve.
static final String[] UNIFIED_PROJECTION = new String[]{
KeychainContract.KeyRings._ID,
KeychainContract.KeyRings.MASTER_KEY_ID,
KeychainContract.KeyRings.USER_ID,
KeychainContract.KeyRings.IS_REVOKED,
KeychainContract.KeyRings.IS_EXPIRED,
KeychainContract.KeyRings.HAS_ANY_SECRET,
KeychainContract.KeyRings.FINGERPRINT,
};
static final int INDEX_MASTER_KEY_ID = 1;
static final int INDEX_USER_ID = 2;
static final int INDEX_IS_REVOKED = 3;
static final int INDEX_IS_EXPIRED = 4;
static final int INDEX_HAS_ANY_SECRET = 5;
static final int INDEX_FINGERPRINT = 6;
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(
KeyRings.buildUnifiedKeyRingUri(mScannedMasterKeyId)
);
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (data.moveToFirst()) {
byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT);
if (!Arrays.equals(fingerprint, mScannedFingerprint)) {
mState = YubiImportState.BAD_FINGERPRINT;
Notify.create(getActivity(), "Fingerprint mismatch!", Style.ERROR);
return;
}
showKey(data);
} else {
showUnknownKey();
} }
} }
public void showUnknownKey() {
String fp = KeyFormattingUtils.convertFingerprintToHex(mScannedFingerprint);
mUnknownFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fp));
mAnimator.setDisplayedChild(1);
mState = YubiImportState.UNKNOWN;
}
public void showKey(Cursor data) {
String userId = data.getString(INDEX_USER_ID);
boolean hasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
String fp = KeyFormattingUtils.convertFingerprintToHex(mScannedFingerprint);
mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fp));
mUserId.setText(userId);
mAnimator.setDisplayedChild(2);
mState = YubiImportState.IMPORTED;
}
private void nextClicked() {
switch (mState) {
case UNKNOWN:
importKey();
break;
case IMPORTED:
promoteKey();
break;
}
}
public void promoteKey() {
// Message is received after decrypting is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity()) {
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
// get returned data bundle
Bundle returnData = message.getData();
PromoteKeyResult result =
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
result.createNotify(getActivity()).show();
}
}
};
// Send all information needed to service to decrypt in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
// fill values for this action
intent.setAction(KeychainIntentService.ACTION_PROMOTE_KEYRING);
Bundle data = new Bundle();
data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, mScannedMasterKeyId);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
// start service with intent
getActivity().startService(intent);
}
public void importKey() {
// Message is received after decrypting is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity()) {
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
// get returned data bundle
Bundle returnData = message.getData();
ImportKeyResult result =
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
result.createNotify(getActivity()).show();
}
}
};
// Send all information needed to service to decrypt in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
// fill values for this action
Bundle data = new Bundle();
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
String hexFp = KeyFormattingUtils.convertFingerprintToHex(mScannedFingerprint);
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
keyList.add(new ParcelableKeyRing(hexFp, null, null));
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyList);
{
Preferences prefs = Preferences.getPreferences(getActivity());
Preferences.CloudSearchPrefs cloudPrefs =
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
}
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
// start service with intent
getActivity().startService(intent);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
static long getKeyIdFromFingerprint(byte[] fingerprint) {
ByteBuffer buf = ByteBuffer.wrap(fingerprint);
// skip first 12 bytes of the fingerprint
buf.position(12);
// the last eight bytes are the key id (big endian, which is default order in ByteBuffer)
return buf.getLong();
}
} }

View File

@ -18,7 +18,6 @@ import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.NfcOperationActivity;
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Iso7816TLV;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -72,7 +71,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
*/ */
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
Log.d(Constants.TAG, "NfcOperationActivity.onPause"); Log.d(Constants.TAG, "BaseNfcActivity.onPause");
disableNfcForegroundDispatch(); disableNfcForegroundDispatch();
} }
@ -83,7 +82,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
*/ */
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
Log.d(Constants.TAG, "NfcOperationActivity.onResume"); Log.d(Constants.TAG, "BaseNfcActivity.onResume");
enableNfcForegroundDispatch(); enableNfcForegroundDispatch();
} }
@ -397,7 +396,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
*/ */
public void enableNfcForegroundDispatch() { public void enableNfcForegroundDispatch() {
mNfcAdapter = NfcAdapter.getDefaultAdapter(this); mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
Intent nfcI = new Intent(this, NfcOperationActivity.class) Intent nfcI = new Intent(this, getClass())
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT); PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT);
IntentFilter[] writeTagFilters = new IntentFilter[]{ IntentFilter[] writeTagFilters = new IntentFilter[]{

View File

@ -9,30 +9,97 @@
android:fillViewport="true" android:fillViewport="true"
android:layout_above="@+id/create_key_buttons"> android:layout_above="@+id/create_key_buttons">
<LinearLayout <ViewAnimator
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inAnimation="@anim/abc_fade_in"
android:outAnimation="@anim/abc_fade_out"
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp" android:paddingRight="16dp"
android:orientation="vertical"> android:id="@+id/create_yubikey_animator"
>
<TextView <LinearLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:orientation="vertical">
android:layout_marginLeft="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Hold Yubikey against device dawg" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginLeft="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Hold Yubikey against device dawg"
/>
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/yubikey_phone" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:orientation="vertical">
android:layout_marginLeft="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="(yubikey fingerprint)" />
</LinearLayout> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginLeft="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:id="@+id/create_yubikey_unknown_fp"
android:text="(yubikey fingerprint)" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginLeft="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Hit next to import this key"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginLeft="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:id="@+id/create_yubikey_fingerprint"
android:text="(yubikey fingerprint)" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginLeft="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:id="@+id/create_yubikey_user_id"
android:text="(yubikey fingerprint)" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginLeft="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Hit next to add this Yubikey to your own!"
/>
</LinearLayout>
</ViewAnimator>
</ScrollView> </ScrollView>
@ -79,5 +146,7 @@
android:gravity="right|center_vertical" android:gravity="right|center_vertical"
android:clickable="true" android:clickable="true"
style="?android:attr/borderlessButtonStyle" /> style="?android:attr/borderlessButtonStyle" />
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>