private Intent createSendIntent(byte[] resultBytes) { Intent sendIntent; sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.setType(Constants.ENCRYPTED_TEXT_MIME); sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes)); EncryptActivity modeInterface = (EncryptActivity) getActivity(); EncryptModeFragment modeFragment = modeInterface.getModeFragment(); if (!modeFragment.isAsymmetric()) { return sendIntent; } String[] encryptionUserIds = modeFragment.getAsymmetricEncryptionUserIds(); if (encryptionUserIds == null) { return sendIntent; } Set<String> users = new HashSet<>(); for (String user : encryptionUserIds) { KeyRing.UserId userId = KeyRing.splitUserId(user); if (userId.email != null) { users.add(userId.email); } } // pass trough email addresses as extra for email applications sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()])); return sendIntent; }
@Override public void bindView(View view, Context context, Cursor cursor) { Highlighter highlighter = new Highlighter(context, mQuery); ViewHolderItem h = (ViewHolderItem) view.getTag(); String userId = cursor.getString(mIndexUserId); KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId); if (userIdSplit.name != null) { h.mainUserId.setText(highlighter.highlight(userIdSplit.name)); } else { h.mainUserId.setText(R.string.user_id_no_name); } if (userIdSplit.email != null) { h.mainUserIdRest.setVisibility(View.VISIBLE); h.mainUserIdRest.setText(highlighter.highlight(userIdSplit.email)); } else { h.mainUserIdRest.setVisibility(View.GONE); } boolean duplicate = cursor.getLong(mIndexDuplicateUserId) > 0; if (duplicate) { String dateTime = DateUtils.formatDateTime( context, cursor.getLong(mIndexCreation) * 1000, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); h.creation.setText(context.getString(R.string.label_key_created, dateTime)); h.creation.setVisibility(View.VISIBLE); } else { h.creation.setVisibility(View.GONE); } boolean enabled; if (cursor.getInt(mIndexIsRevoked) != 0) { h.statusIcon.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage( mContext, h.statusIcon, null, State.REVOKED, R.color.key_flag_gray); enabled = false; } else if (cursor.getInt(mIndexIsExpiry) != 0) { h.statusIcon.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage( mContext, h.statusIcon, null, State.EXPIRED, R.color.key_flag_gray); enabled = false; } else { h.statusIcon.setVisibility(View.GONE); enabled = true; } h.statusIcon.setTag(enabled); }
/** * Links all keys with secrets to the main ("me") contact * http://developer.android.com/reference/android/provider/ContactsContract.Profile.html * * @param context */ public static void writeKeysToMainProfileContact(Context context, ContentResolver resolver) { // deletes contacts hidden by the user so they can be reinserted if necessary deleteFlaggedMainProfileRawContacts(resolver); Set<Long> keysToDelete = getMainProfileMasterKeyIds(resolver); // get all keys which have associated secret keys // TODO: figure out why using selectionArgs does not work in this case Cursor cursor = resolver.query( KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), KEYS_TO_CONTACT_PROJECTION, KeychainContract.KeyRings.HAS_ANY_SECRET + "!=0", null, null); if (cursor != null) try { while (cursor.moveToNext()) { long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0; boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; KeyRing.UserId userIdSplit = KeyRing.splitUserId(cursor.getString(INDEX_USER_ID)); if (!isExpired && !isRevoked && userIdSplit.name != null) { // if expired or revoked will not be removed from keysToDelete or inserted // into main profile ("me" contact) boolean existsInMainProfile = keysToDelete.remove(masterKeyId); if (!existsInMainProfile) { long rawContactId = -1; // new raw contact Log.d(Constants.TAG, "masterKeyId with secret " + masterKeyId); ArrayList<ContentProviderOperation> ops = new ArrayList<>(); insertMainProfileRawContact(ops, masterKeyId); writeContactKey(ops, context, rawContactId, masterKeyId, userIdSplit.name); try { resolver.applyBatch(ContactsContract.AUTHORITY, ops); } catch (Exception e) { Log.w(Constants.TAG, e); } } } } } finally { cursor.close(); } for (long masterKeyId : keysToDelete) { deleteMainProfileRawContactByMasterKeyId(resolver, masterKeyId); Log.d(Constants.TAG, "Delete main profile raw contact with masterKeyId " + masterKeyId); } }
/** Write all known email addresses of a key (derived from user ids) to a given raw contact */ private static void writeContactEmail( ArrayList<ContentProviderOperation> ops, ContentResolver resolver, long rawContactId, long masterKeyId) { ops.add( selectByRawContactAndItemType( ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI), rawContactId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) .build()); Cursor ids = resolver.query( UserPackets.buildUserIdsUri(masterKeyId), new String[] {UserPackets.USER_ID}, UserPackets.IS_REVOKED + "=0", null, null); if (ids != null) { while (ids.moveToNext()) { KeyRing.UserId userId = KeyRing.splitUserId(ids.getString(0)); if (userId.email != null) { ops.add( referenceRawContact( ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI), rawContactId) .withValue( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) .withValue(ContactsContract.CommonDataKinds.Email.DATA, userId.email) .build()); } } ids.close(); } }
@Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { MatrixCursor matrix = new MatrixCursor(new String[] {"_id", "user_data", "grouped"}) { @Override public byte[] getBlob(int column) { return super.getBlob(column); } }; data.moveToFirst(); long lastMasterKeyId = 0; String lastName = ""; ArrayList<String> uids = new ArrayList<>(); boolean header = true; // Iterate over all rows while (!data.isAfterLast()) { long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); String userId = data.getString(INDEX_USER_ID); KeyRing.UserId pieces = KeyRing.splitUserId(userId); // Two cases: boolean grouped = masterKeyId == lastMasterKeyId; boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name); // Remember for next loop lastName = pieces.name; Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped")); if (!subGrouped) { // 1. This name should NOT be grouped with the previous, so we flush the buffer Parcel p = Parcel.obtain(); p.writeStringList(uids); byte[] d = p.marshall(); p.recycle(); matrix.addRow(new Object[] {lastMasterKeyId, d, header ? 1 : 0}); // indicate that we have a header for this masterKeyId header = false; // Now clear the buffer, and add the new user id, for the next round uids.clear(); } // 2. This name should be grouped with the previous, just add to buffer uids.add(userId); lastMasterKeyId = masterKeyId; // If this one wasn't grouped, the next one's gotta be a header if (!grouped) { header = true; } // Regardless of the outcome, move to next entry data.moveToNext(); } // If there is anything left in the buffer, flush it one last time if (!uids.isEmpty()) { Parcel p = Parcel.obtain(); p.writeStringList(uids); byte[] d = p.marshall(); p.recycle(); matrix.addRow(new Object[] {lastMasterKeyId, d, header ? 1 : 0}); } mUserIdsAdapter.swapCursor(matrix); }
public OpenPgpUtils.UserId getUserId() { return KeyRing.splitUserId(getRawUserId()); }
@SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated public static void setStatus(Context context, StatusHolder holder, DecryptVerifyResult result) { OpenPgpSignatureResult signatureResult = result.getSignatureResult(); if (holder.hasEncrypt()) { int encText, encIcon, encColor; if (signatureResult != null && signatureResult.isSignatureOnly()) { encIcon = R.drawable.status_lock_open_24dp; encText = R.string.decrypt_result_not_encrypted; encColor = R.color.android_red_light; } else { encIcon = R.drawable.status_lock_closed_24dp; encText = R.string.decrypt_result_encrypted; encColor = R.color.android_green_light; } int encColorRes = context.getResources().getColor(encColor); holder.getEncryptionStatusIcon().setColorFilter(encColorRes, PorterDuff.Mode.SRC_IN); holder .getEncryptionStatusIcon() .setImageDrawable(context.getResources().getDrawable(encIcon)); holder.getEncryptionStatusText().setText(encText); holder.getEncryptionStatusText().setTextColor(encColorRes); } int sigText, sigIcon, sigColor; int sigActionText, sigActionIcon; if (signatureResult == null) { sigText = R.string.decrypt_result_no_signature; sigIcon = R.drawable.status_signature_invalid_cutout_24dp; sigColor = R.color.bg_gray; // won't be used, but makes compiler happy sigActionText = 0; sigActionIcon = 0; } else switch (signatureResult.getStatus()) { case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: { sigText = R.string.decrypt_result_signature_certified; sigIcon = R.drawable.status_signature_verified_cutout_24dp; sigColor = R.color.android_green_light; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; break; } case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: { sigText = R.string.decrypt_result_signature_uncertified; sigIcon = R.drawable.status_signature_unverified_cutout_24dp; sigColor = R.color.android_orange_light; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; break; } case OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED: { sigText = R.string.decrypt_result_signature_revoked_key; sigIcon = R.drawable.status_signature_revoked_cutout_24dp; sigColor = R.color.android_red_light; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; break; } case OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED: { sigText = R.string.decrypt_result_signature_expired_key; sigIcon = R.drawable.status_signature_expired_cutout_24dp; sigColor = R.color.android_red_light; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; break; } case OpenPgpSignatureResult.SIGNATURE_KEY_MISSING: { sigText = R.string.decrypt_result_signature_missing_key; sigIcon = R.drawable.status_signature_unknown_cutout_24dp; sigColor = R.color.android_red_light; sigActionText = R.string.decrypt_result_action_Lookup; sigActionIcon = R.drawable.ic_file_download_grey_24dp; break; } default: case OpenPgpSignatureResult.SIGNATURE_ERROR: { sigText = R.string.decrypt_result_invalid_signature; sigIcon = R.drawable.status_signature_invalid_cutout_24dp; sigColor = R.color.android_red_light; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; break; } } int sigColorRes = context.getResources().getColor(sigColor); holder.getSignatureStatusIcon().setColorFilter(sigColorRes, PorterDuff.Mode.SRC_IN); holder.getSignatureStatusIcon().setImageDrawable(context.getResources().getDrawable(sigIcon)); holder.getSignatureStatusText().setText(sigText); holder.getSignatureStatusText().setTextColor(sigColorRes); if (signatureResult != null) { holder.getSignatureLayout().setVisibility(View.VISIBLE); holder.getSignatureAction().setText(sigActionText); holder.getSignatureAction().setCompoundDrawablesWithIntrinsicBounds(0, 0, sigActionIcon, 0); String userId = signatureResult.getPrimaryUserId(); KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId); if (userIdSplit.name != null) { holder.getSignatureUserName().setText(userIdSplit.name); } else { holder.getSignatureUserName().setText(R.string.user_id_no_name); } if (userIdSplit.email != null) { holder.getSignatureUserEmail().setVisibility(View.VISIBLE); holder.getSignatureUserEmail().setText(userIdSplit.email); } else { holder.getSignatureUserEmail().setVisibility(View.GONE); } } else { holder.getSignatureLayout().setVisibility(View.GONE); } }
@NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Activity activity = getActivity(); ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); mRequiredInput = getArguments().getParcelable(EXTRA_REQUIRED_INPUT); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme); // No title, see http://www.google.com/design/spec/components/dialogs.html#dialogs-alerts // alert.setTitle() if (mRequiredInput.mType == RequiredInputType.BACKUP_CODE) { LayoutInflater inflater = LayoutInflater.from(theme); View view = inflater.inflate(R.layout.passphrase_dialog_backup_code, null); alert.setView(view); mBackupCodeEditText = new EditText[4]; mBackupCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1); mBackupCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2); mBackupCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3); mBackupCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4); setupEditTextFocusNext(mBackupCodeEditText); AlertDialog dialog = alert.create(); dialog.setButton( DialogInterface.BUTTON_POSITIVE, activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null); return dialog; } long subKeyId = mRequiredInput.getSubKeyId(); LayoutInflater inflater = LayoutInflater.from(theme); mLayout = (ViewAnimator) inflater.inflate(R.layout.passphrase_dialog, null); alert.setView(mLayout); mPassphraseText = (TextView) mLayout.findViewById(R.id.passphrase_text); mPassphraseEditText = (EditText) mLayout.findViewById(R.id.passphrase_passphrase); View vTimeToLiveLayout = mLayout.findViewById(R.id.remember_layout); vTimeToLiveLayout.setVisibility(mRequiredInput.mSkipCaching ? View.GONE : View.VISIBLE); mTimeToLiveSpinner = (CacheTTLSpinner) mLayout.findViewById(R.id.ttl_spinner); alert.setNegativeButton( android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); String userId; CanonicalizedSecretKey.SecretKeyType keyType = CanonicalizedSecretKey.SecretKeyType.PASSPHRASE; String message; String hint; if (mRequiredInput.mType == RequiredInputType.PASSPHRASE_SYMMETRIC) { message = getString(R.string.passphrase_for_symmetric_encryption); hint = getString(R.string.label_passphrase); } else { try { ProviderHelper helper = new ProviderHelper(activity); mSecretRing = helper.getCanonicalizedSecretKeyRing( KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); // yes the inner try/catch block is necessary, otherwise the final variable // above can't be statically verified to have been set in all cases because // the catch clause doesn't return. try { String mainUserId = mSecretRing.getPrimaryUserIdWithFallback(); KeyRing.UserId mainUserIdSplit = KeyRing.splitUserId(mainUserId); if (mainUserIdSplit.name != null) { userId = mainUserIdSplit.name; } else { userId = getString(R.string.user_id_no_name); } } catch (PgpKeyNotFoundException e) { userId = null; } keyType = mSecretRing.getSecretKey(subKeyId).getSecretKeyType(); switch (keyType) { case PASSPHRASE: message = getString(R.string.passphrase_for, userId); hint = getString(R.string.label_passphrase); break; case PIN: message = getString(R.string.pin_for, userId); hint = getString(R.string.label_pin); break; case DIVERT_TO_CARD: message = getString(R.string.yubikey_pin_for, userId); hint = getString(R.string.label_pin); break; // special case: empty passphrase just returns the empty passphrase case PASSPHRASE_EMPTY: finishCaching(new Passphrase("")); default: throw new AssertionError("Unhandled SecretKeyType (should not happen)"); } } catch (ProviderHelper.NotFoundException e) { alert.setTitle(R.string.title_key_not_found); alert.setMessage(getString(R.string.key_not_found, mRequiredInput.getSubKeyId())); alert.setPositiveButton( android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dismiss(); } }); alert.setCancelable(false); return alert.create(); } } mPassphraseText.setText(message); mPassphraseEditText.setHint(hint); // Hack to open keyboard. // This is the only method that I found to work across all Android versions // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/ // Notes: * onCreateView can't be used because we want to add buttons to the dialog // * opening in onActivityCreated does not work on Android 4.4 mPassphraseEditText.setOnFocusChangeListener( new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { mPassphraseEditText.post( new Runnable() { @Override public void run() { if (getActivity() == null || mPassphraseEditText == null) { return; } InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT); } }); } }); mPassphraseEditText.requestFocus(); mPassphraseEditText.setImeActionLabel( getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); mPassphraseEditText.setOnEditorActionListener(this); if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin()) || keyType == CanonicalizedSecretKey.SecretKeyType.PIN) { mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER); mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); } else { mPassphraseEditText.setRawInputType( InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); } mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); AlertDialog dialog = alert.create(); dialog.setButton( DialogInterface.BUTTON_POSITIVE, activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null); return dialog; }
private static void writeKeysToNormalContacts(Context context, ContentResolver resolver) { // delete raw contacts flagged for deletion by user so they can be reinserted deleteFlaggedNormalRawContacts(resolver); Set<Long> deletedKeys = getRawContactMasterKeyIds(resolver); // Load all public Keys from OK // TODO: figure out why using selectionArgs does not work in this case Cursor cursor = resolver.query( KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), KEYS_TO_CONTACT_PROJECTION, KeychainContract.KeyRings.HAS_ANY_SECRET + "=0", null, null); if (cursor != null) { while (cursor.moveToNext()) { long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); KeyRing.UserId userIdSplit = KeyRing.splitUserId(cursor.getString(INDEX_USER_ID)); boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0; boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0; Log.d(Constants.TAG, "masterKeyId: " + masterKeyId); deletedKeys.remove(masterKeyId); // get raw contact to this master key id long rawContactId = findRawContactId(resolver, masterKeyId); Log.d(Constants.TAG, "rawContactId: " + rawContactId); ArrayList<ContentProviderOperation> ops = new ArrayList<>(); // Do not store expired or revoked or unverified keys in contact db - and // remove them if they already exist. Secret keys do not reach this point if (isExpired || isRevoked || !isVerified) { Log.d( Constants.TAG, "Expired or revoked or unverified: Deleting rawContactId " + rawContactId); if (rawContactId != -1) { deleteRawContactById(resolver, rawContactId); } } else if (userIdSplit.name != null) { // Create a new rawcontact with corresponding key if it does not exist yet if (rawContactId == -1) { Log.d(Constants.TAG, "Insert new raw contact with masterKeyId " + masterKeyId); insertContact(ops, context, masterKeyId); writeContactKey(ops, context, rawContactId, masterKeyId, userIdSplit.name); } // We always update the display name (which is derived from primary user id) // and email addresses from user id writeContactDisplayName(ops, rawContactId, userIdSplit.name); writeContactEmail(ops, resolver, rawContactId, masterKeyId); try { resolver.applyBatch(ContactsContract.AUTHORITY, ops); } catch (Exception e) { Log.w(Constants.TAG, e); } } } cursor.close(); } // Delete master key ids that are no longer present in OK for (Long masterKeyId : deletedKeys) { Log.d(Constants.TAG, "Delete raw contact with masterKeyId " + masterKeyId); deleteRawContactByMasterKeyId(resolver, masterKeyId); } }