@Test public void testPromoteDivert() throws Exception { PromoteKeyOperation op = new PromoteKeyOperation( RuntimeEnvironment.application, new ProviderHelper(RuntimeEnvironment.application), null, null); byte[] aid = Hex.decode("D2760001240102000000012345670000"); PromoteKeyResult result = op.execute(new PromoteKeyringParcel(mStaticRing.getMasterKeyId(), aid, null), null); Assert.assertTrue("promotion must succeed", result.success()); { CanonicalizedSecretKeyRing ring = new ProviderHelper(RuntimeEnvironment.application) .getCanonicalizedSecretKeyRing(mStaticRing.getMasterKeyId()); for (CanonicalizedSecretKey key : ring.secretKeyIterator()) { Assert.assertEquals( "all subkeys must be divert-to-card", SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyTypeSuperExpensive()); Assert.assertArrayEquals("all subkeys must have correct iv", aid, key.getIv()); } } }
@Test /** * Tests a master key which may sign, but is stripped. In this case, if there is a different * subkey available which can sign, that one should be selected. */ public void testImportStrippedFlags() throws Exception { UncachedKeyRing key = readRingFromResource("/test-keys/stripped_flags.asc"); long masterKeyId = key.getMasterKeyId(); SaveKeyringResult result; result = mProviderHelper.saveSecretKeyRing(key, new ProgressScaler()); Assert.assertTrue("import of keyring should succeed", result.success()); long signId; { CanonicalizedSecretKeyRing ring = mProviderHelper.getCanonicalizedSecretKeyRing(masterKeyId); Assert.assertTrue("master key should have sign flag", ring.getPublicKey().canSign()); Assert.assertTrue("master key should have encrypt flag", ring.getPublicKey().canEncrypt()); signId = ring.getSecretSignId(); Assert.assertNotEquals("encrypt id should not be 0", 0, signId); Assert.assertNotEquals( "encrypt key should be different from master key", masterKeyId, signId); } { CachedPublicKeyRing ring = mProviderHelper.getCachedPublicKeyRing(masterKeyId); Assert.assertEquals( "signing key should be same id cached as uncached", signId, ring.getSecretSignId()); } }
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // do not allow screenshots of passphrase input // to prevent "too easy" passphrase theft by root apps if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { getWindow() .setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); } CryptoInputParcel cryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT); if (cryptoInputParcel == null) { cryptoInputParcel = new CryptoInputParcel(); getIntent().putExtra(EXTRA_CRYPTO_INPUT, cryptoInputParcel); } // this activity itself has no content view (see manifest) RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT); if (requiredInput.mType != RequiredInputType.PASSPHRASE) { return; } // handle empty passphrases by directly returning an empty crypto input parcel try { CanonicalizedSecretKeyRing pubRing = new ProviderHelper(this).getCanonicalizedSecretKeyRing(requiredInput.getMasterKeyId()); // use empty passphrase for empty passphrase if (pubRing.getSecretKey(requiredInput.getSubKeyId()).getSecretKeyType() == SecretKeyType.PASSPHRASE_EMPTY) { // also return passphrase back to activity Intent returnIntent = new Intent(); cryptoInputParcel.mPassphrase = new Passphrase(""); returnIntent.putExtra(RESULT_CRYPTO_INPUT, cryptoInputParcel); setResult(RESULT_OK, returnIntent); finish(); } } catch (NotFoundException e) { Log.e(Constants.TAG, "Key not found?!", e); setResult(RESULT_CANCELED); finish(); } }
@Test public void testPromoteDivertSpecific() throws Exception { PromoteKeyOperation op = new PromoteKeyOperation( RuntimeEnvironment.application, new ProviderHelper(RuntimeEnvironment.application), null, null); byte[] aid = Hex.decode("D2760001240102000000012345670000"); // only promote the first, rest stays dummy long keyId = KeyringTestingHelper.getSubkeyId(mStaticRing, 1); PromoteKeyResult result = op.execute( new PromoteKeyringParcel(mStaticRing.getMasterKeyId(), aid, new long[] {keyId}), null); Assert.assertTrue("promotion must succeed", result.success()); { CanonicalizedSecretKeyRing ring = new ProviderHelper(RuntimeEnvironment.application) .getCanonicalizedSecretKeyRing(mStaticRing.getMasterKeyId()); for (CanonicalizedSecretKey key : ring.secretKeyIterator()) { if (key.getKeyId() == keyId) { Assert.assertEquals( "subkey must be divert-to-card", SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyTypeSuperExpensive()); Assert.assertArrayEquals("subkey must have correct iv", aid, key.getIv()); } else { Assert.assertEquals( "some subkeys must be gnu dummy", SecretKeyType.GNU_DUMMY, key.getSecretKeyTypeSuperExpensive()); } } } }
@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; }
@Test public void testImportDivertToCard() throws Exception { UncachedKeyRing sec = readRingFromResource("/test-keys/divert_to_card_sec.asc"); long keyId = sec.getMasterKeyId(); SaveKeyringResult result; result = mProviderHelper.saveSecretKeyRing(sec, new ProgressScaler()); Assert.assertTrue("import of secret keyring should succeed", result.success()); // make sure both the CanonicalizedSecretKeyRing as well as the CachedPublicKeyRing correctly // indicate the secret key type CachedPublicKeyRing cachedRing = mProviderHelper.getCachedPublicKeyRing(keyId); CanonicalizedSecretKeyRing secRing = mProviderHelper.getCanonicalizedSecretKeyRing(keyId); Iterator<CanonicalizedSecretKey> it = secRing.secretKeyIterator().iterator(); { // first subkey Assert.assertTrue("keyring should have 3 subkeys (1)", it.hasNext()); CanonicalizedSecretKey key = it.next(); Assert.assertEquals( "first subkey should be of type sign+certify", KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, (int) key.getKeyUsage()); Assert.assertEquals( "first subkey should be divert-to-card", SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType()); Assert.assertTrue("canCertify() should be true", key.canCertify()); Assert.assertTrue("canSign() should be true", key.canSign()); // cached Assert.assertEquals( "all subkeys from CachedPublicKeyRing should be divert-to-key", SecretKeyType.DIVERT_TO_CARD, cachedRing.getSecretKeyType(key.getKeyId())); } { // second subkey Assert.assertTrue("keyring should have 3 subkeys (2)", it.hasNext()); CanonicalizedSecretKey key = it.next(); Assert.assertEquals( "second subkey should be of type authenticate", KeyFlags.AUTHENTICATION, (int) key.getKeyUsage()); Assert.assertEquals( "second subkey should be divert-to-card", SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType()); Assert.assertTrue("canAuthenticate() should be true", key.canAuthenticate()); // cached Assert.assertEquals( "all subkeys from CachedPublicKeyRing should be divert-to-key", SecretKeyType.DIVERT_TO_CARD, cachedRing.getSecretKeyType(key.getKeyId())); } { // third subkey Assert.assertTrue("keyring should have 3 subkeys (3)", it.hasNext()); CanonicalizedSecretKey key = it.next(); Assert.assertEquals( "first subkey should be of type encrypt (both types)", KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, (int) key.getKeyUsage()); Assert.assertEquals( "third subkey should be divert-to-card", SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType()); Assert.assertTrue("canEncrypt() should be true", key.canEncrypt()); // cached Assert.assertEquals( "all subkeys from CachedPublicKeyRing should be divert-to-key", SecretKeyType.DIVERT_TO_CARD, cachedRing.getSecretKeyType(key.getKeyId())); } Assert.assertFalse("keyring should have 3 subkeys (4)", it.hasNext()); }