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 {
});
}
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() {
mTaskRunner.postTask(() -> { CableAuthenticatorJni.get().stop(); });
}
......@@ -568,6 +578,14 @@ class CableAuthenticator {
boolean startQR(
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
* the {@link CableAuthenticatorUI} Fragment is now in the foreground for showing UI.
......
......@@ -8,6 +8,7 @@ import android.Manifest.permission;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
......@@ -22,6 +23,7 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.res.ResourcesCompat;
import androidx.fragment.app.Fragment;
......@@ -45,6 +47,7 @@ public class CableAuthenticatorUI extends Fragment
private AndroidPermissionDelegate mPermissionDelegate;
private CableAuthenticator mAuthenticator;
private LinearLayout mQRButton;
private LinearLayout mUnlinkButton;
private ImageView mHeader;
@Override
......@@ -102,6 +105,9 @@ public class CableAuthenticatorUI extends Fragment
mHeader = v.findViewById(R.id.qr_image);
setHeader(R.style.idle);
mUnlinkButton = v.findViewById(R.id.unlink);
mUnlinkButton.setOnClickListener(this);
return v;
}
......@@ -125,7 +131,30 @@ public class CableAuthenticatorUI extends Fragment
* Called when the button to scan a QR code is pressed.
*/
@Override
@SuppressLint("SetTextI18n")
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
// dialog.
final Context context = getContext();
......
......@@ -497,6 +497,19 @@ static jboolean JNI_CableAuthenticator_StartQR(
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(
JNIEnv* env,
const JavaParamRef<jobject>& cable_authenticator) {
......
......@@ -46,6 +46,8 @@ class FCMHandler : public gcm::GCMAppHandler, public Registration {
}
~FCMHandler() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
instance_id_->gcm_driver()->RemoveAppHandler(kFCMAppId);
instance_id_driver_->RemoveInstanceID(kFCMAppId);
}
......@@ -53,6 +55,8 @@ class FCMHandler : public gcm::GCMAppHandler, public Registration {
// Registration:
void PrepareContactID() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
instance_id_->GetToken(
kFCMSenderId, instance_id::kGCMScope,
/*time_to_live=*/base::TimeDelta(), /*options=*/{},
......@@ -60,7 +64,18 @@ class FCMHandler : public gcm::GCMAppHandler, public Registration {
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 {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!registration_token_) {
return base::nullopt;
}
......@@ -119,6 +134,17 @@ class FCMHandler : public gcm::GCMAppHandler, public Registration {
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(
const gcm::MessageData& data) {
auto event = std::make_unique<Registration::Event>();
......
......@@ -47,6 +47,10 @@ class Registration {
// registration will be deferred until this is called.
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
// desktops to later connect to. |nullopt| will be returned if the value is
// 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