Commit b6ef69be authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

webauthn: wire up caBLE's unlink button.

BUG=1002262

Change-Id: I91c23b26a01cfc03b77e57e86ad85c80d21f7646
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2531177
Commit-Queue: Adam Langley <agl@chromium.org>
Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#826930}
parent ad9b7ffb
...@@ -465,6 +465,16 @@ class CableAuthenticator { ...@@ -465,6 +465,16 @@ class CableAuthenticator {
}); });
} }
public void unlinkAllDevices() {
Log.i(TAG, "Unlinking devices");
byte[] newStateBytes = CableAuthenticatorJni.get().unlink();
SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
prefs.edit()
.putString(STATE_VALUE_NAME,
Base64.encodeToString(newStateBytes, Base64.NO_WRAP | Base64.NO_PADDING))
.apply();
}
public void close() { public void close() {
mTaskRunner.postTask(() -> { CableAuthenticatorJni.get().stop(); }); mTaskRunner.postTask(() -> { CableAuthenticatorJni.get().stop(); });
} }
...@@ -568,6 +578,14 @@ class CableAuthenticator { ...@@ -568,6 +578,14 @@ class CableAuthenticator {
boolean startQR( boolean startQR(
CableAuthenticator cableAuthenticator, String authenticatorName, String qrUrl); CableAuthenticator cableAuthenticator, String authenticatorName, String qrUrl);
/**
* unlink causes the root secret to be rotated and the FCM token to be rotated. This
* prevents all previously linked devices from being able to contact this device in the
* future -- they'll have to go via the QR-scanning path again. It returns the updated state
* which must be persisted.
*/
byte[] unlink();
/** /**
* Called after the notification created by {@link showNotification} has been pressed and * Called after the notification created by {@link showNotification} has been pressed and
* the {@link CableAuthenticatorUI} Fragment is now in the foreground for showing UI. * the {@link CableAuthenticatorUI} Fragment is now in the foreground for showing UI.
......
...@@ -8,6 +8,7 @@ import android.Manifest.permission; ...@@ -8,6 +8,7 @@ import android.Manifest.permission;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
...@@ -22,6 +23,7 @@ import android.view.ViewGroup; ...@@ -22,6 +23,7 @@ import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.res.ResourcesCompat; import androidx.core.content.res.ResourcesCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
...@@ -45,6 +47,7 @@ public class CableAuthenticatorUI extends Fragment ...@@ -45,6 +47,7 @@ public class CableAuthenticatorUI extends Fragment
private AndroidPermissionDelegate mPermissionDelegate; private AndroidPermissionDelegate mPermissionDelegate;
private CableAuthenticator mAuthenticator; private CableAuthenticator mAuthenticator;
private LinearLayout mQRButton; private LinearLayout mQRButton;
private LinearLayout mUnlinkButton;
private ImageView mHeader; private ImageView mHeader;
@Override @Override
...@@ -102,6 +105,9 @@ public class CableAuthenticatorUI extends Fragment ...@@ -102,6 +105,9 @@ public class CableAuthenticatorUI extends Fragment
mHeader = v.findViewById(R.id.qr_image); mHeader = v.findViewById(R.id.qr_image);
setHeader(R.style.idle); setHeader(R.style.idle);
mUnlinkButton = v.findViewById(R.id.unlink);
mUnlinkButton.setOnClickListener(this);
return v; return v;
} }
...@@ -125,7 +131,30 @@ public class CableAuthenticatorUI extends Fragment ...@@ -125,7 +131,30 @@ public class CableAuthenticatorUI extends Fragment
* Called when the button to scan a QR code is pressed. * Called when the button to scan a QR code is pressed.
*/ */
@Override @Override
@SuppressLint("SetTextI18n")
public void onClick(View v) { public void onClick(View v) {
if (v == mUnlinkButton) {
// TODO: localise strings.
new AlertDialog.Builder(getContext())
.setTitle("Unlink all devices")
.setMessage("Do you want to unlink all previously connected devices?"
+ " You will need to scan a QR code from a given device in"
+ " order to use it again."
+ " No credentials will be deleted.")
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
mAuthenticator.unlinkAllDevices();
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
return;
}
assert (v == mQRButton);
// If camera permission is already available, show the scanning // If camera permission is already available, show the scanning
// dialog. // dialog.
final Context context = getContext(); final Context context = getContext();
......
...@@ -497,6 +497,19 @@ static jboolean JNI_CableAuthenticator_StartQR( ...@@ -497,6 +497,19 @@ static jboolean JNI_CableAuthenticator_StartQR(
return true; return true;
} }
static ScopedJavaLocalRef<jbyteArray> JNI_CableAuthenticator_Unlink(
JNIEnv* env) {
std::vector<uint8_t> serialized_state;
std::array<uint8_t, device::cablev2::kRootSecretSize> root_secret;
std::tie(root_secret, serialized_state) = NewState();
GlobalData& global_data = GetGlobalData();
global_data.root_secret = root_secret;
global_data.registration->RotateContactID();
return ToJavaByteArray(env, serialized_state);
}
static void JNI_CableAuthenticator_OnInteractionReady( static void JNI_CableAuthenticator_OnInteractionReady(
JNIEnv* env, JNIEnv* env,
const JavaParamRef<jobject>& cable_authenticator) { const JavaParamRef<jobject>& cable_authenticator) {
......
...@@ -46,6 +46,8 @@ class FCMHandler : public gcm::GCMAppHandler, public Registration { ...@@ -46,6 +46,8 @@ class FCMHandler : public gcm::GCMAppHandler, public Registration {
} }
~FCMHandler() override { ~FCMHandler() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
instance_id_->gcm_driver()->RemoveAppHandler(kFCMAppId); instance_id_->gcm_driver()->RemoveAppHandler(kFCMAppId);
instance_id_driver_->RemoveInstanceID(kFCMAppId); instance_id_driver_->RemoveInstanceID(kFCMAppId);
} }
...@@ -53,6 +55,8 @@ class FCMHandler : public gcm::GCMAppHandler, public Registration { ...@@ -53,6 +55,8 @@ class FCMHandler : public gcm::GCMAppHandler, public Registration {
// Registration: // Registration:
void PrepareContactID() override { void PrepareContactID() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
instance_id_->GetToken( instance_id_->GetToken(
kFCMSenderId, instance_id::kGCMScope, kFCMSenderId, instance_id::kGCMScope,
/*time_to_live=*/base::TimeDelta(), /*options=*/{}, /*time_to_live=*/base::TimeDelta(), /*options=*/{},
...@@ -60,7 +64,18 @@ class FCMHandler : public gcm::GCMAppHandler, public Registration { ...@@ -60,7 +64,18 @@ class FCMHandler : public gcm::GCMAppHandler, public Registration {
base::BindOnce(&FCMHandler::GetTokenComplete, base::Unretained(this))); base::BindOnce(&FCMHandler::GetTokenComplete, base::Unretained(this)));
} }
void RotateContactID() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
registration_token_.reset();
instance_id_->DeleteToken(kFCMSenderId, instance_id::kGCMScope,
base::BindOnce(&FCMHandler::DeleteTokenComplete,
base::Unretained(this)));
}
base::Optional<std::vector<uint8_t>> contact_id() const override { base::Optional<std::vector<uint8_t>> contact_id() const override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!registration_token_) { if (!registration_token_) {
return base::nullopt; return base::nullopt;
} }
...@@ -119,6 +134,17 @@ class FCMHandler : public gcm::GCMAppHandler, public Registration { ...@@ -119,6 +134,17 @@ class FCMHandler : public gcm::GCMAppHandler, public Registration {
registration_token_ = token; registration_token_ = token;
} }
void DeleteTokenComplete(instance_id::InstanceID::Result result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (result != instance_id::InstanceID::SUCCESS) {
FIDO_LOG(ERROR) << "Deleting FCM token failed: "
<< static_cast<int>(result);
}
PrepareContactID();
}
static base::Optional<std::unique_ptr<Registration::Event>> MessageToEvent( static base::Optional<std::unique_ptr<Registration::Event>> MessageToEvent(
const gcm::MessageData& data) { const gcm::MessageData& data) {
auto event = std::make_unique<Registration::Event>(); auto event = std::make_unique<Registration::Event>();
......
...@@ -47,6 +47,10 @@ class Registration { ...@@ -47,6 +47,10 @@ class Registration {
// registration will be deferred until this is called. // registration will be deferred until this is called.
virtual void PrepareContactID() = 0; virtual void PrepareContactID() = 0;
// RotateContactID invalidates the current contact ID and prepares a fresh
// one.
virtual void RotateContactID() = 0;
// contact_id returns an opaque token that may be placed in pairing data for // contact_id returns an opaque token that may be placed in pairing data for
// desktops to later connect to. |nullopt| will be returned if the value is // desktops to later connect to. |nullopt| will be returned if the value is
// not yet ready. // not yet ready.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment