Commit 4ec595d6 authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

chrome/browser/ui: add initial QR-code display.

The QR code display will, in the future, allow pairing of phones as
security keys. This CL adds initial support for generating and
displaying the QR codes.

This UI is not yet reachable in any way but allows and is only intended
for experimentation.

Change-Id: I3c3696f0e680a4d1de415a83c18e4e5ff50f66f4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1659133
Commit-Queue: Adam Langley <agl@chromium.org>
Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#678888}
parent 5d05bb55
......@@ -3081,6 +3081,10 @@ jumbo_split_static_library("ui") {
"views/webauthn/authenticator_client_pin_entry_sheet_view.h",
"views/webauthn/authenticator_client_pin_entry_view.cc",
"views/webauthn/authenticator_client_pin_entry_view.h",
"views/webauthn/authenticator_qr_code.cc",
"views/webauthn/authenticator_qr_code.h",
"views/webauthn/authenticator_qr_sheet_view.cc",
"views/webauthn/authenticator_qr_sheet_view.h",
"views/webauthn/authenticator_request_dialog_view.cc",
"views/webauthn/authenticator_request_dialog_view.h",
"views/webauthn/authenticator_request_sheet_view.cc",
......
This diff is collapsed.
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_QR_CODE_H_
#define CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_QR_CODE_H_
#include <stddef.h>
#include <stdint.h>
// AuthenticatorQRCode generates version three, class Q QR codes that carry 32
// bytes of raw data. References in the following comments refer to ISO 18004
// (3rd edition).
class AuthenticatorQRCode {
public:
// kSize is the number of "tiles" in each dimension for a v3 QR code. See
// table 1. (The colored squares in in QR codes are called tiles in the
// spec.)
static constexpr int kSize = 29;
// These values are taken from table 9 (page 38) for a version three, class Q
// QR code.
static constexpr size_t kTotalBytes = 70;
static constexpr size_t kNumSegments = 2;
static constexpr size_t kSegmentDataBytes = 17;
static constexpr size_t kSegmentBytes = kTotalBytes / kNumSegments;
static constexpr size_t kSegmentECBytes = kSegmentBytes - kSegmentDataBytes;
static constexpr size_t kDataBytes = kSegmentDataBytes * kNumSegments;
// Two bytes of overhead are needed for QR framing.
static constexpr size_t kInputBytes = kDataBytes - 2;
// Generate generates a QR code containing the given data and returns a
// pointer to an array of kSize×kSize bytes where the least-significant bit of
// each byte is set if that tile should be "black".
const uint8_t* Generate(const uint8_t in[kInputBytes]);
private:
// MaskFunction3 implements one of the data-masking functions. See figure 21.
static uint8_t MaskFunction3(int x, int y);
// PutFinder paints a finder symbol at the given coordinates.
void PutFinder(int x, int y);
// PutAlignment paints an alignment symbol centered at the given coordinates.
void PutAlignment(int x, int y);
// PutVerticalTiming paints the vertical timing signal.
void PutVerticalTiming(int x);
// PutVerticalTiming paints the horizontal timing signal.
void PutHorizontalTiming(int y);
// PutFormatBits paints the 15-bit, pre-encoded format metadata. See page 56
// for the location of the format bits.
void PutFormatBits(const uint16_t format);
// PutBits writes the given data into the QR code in correct order, avoiding
// structural elements that must have already been painted. See section 7.7.3
// about the placement algorithm.
void PutBits(const uint8_t* data,
size_t data_len,
uint8_t (*mask_func)(int, int));
// at returns a reference to the given element of |d_|.
uint8_t& at(int x, int y);
// fillAt sets the |len| elements at (x, y) to |value|.
void fillAt(int x, int y, size_t len, uint8_t value);
// copyTo copies |len| elements from |data| to the elements at (x, y).
void copyTo(int x, int y, const uint8_t* data, size_t len);
// clipped returns a reference to the given element of |d_|, or to
// |clip_dump_| if the coordinates are out of bounds.
uint8_t& clipped(int x, int y);
// GF28Mul returns the product of |a| and |b| (which must be field elements,
// i.e. < 256) in the field GF(2^8) mod x^8 + x^4 + x^3 + x^2 + 1.
static uint8_t GF28Mul(uint16_t a, uint16_t b);
// AddErrorCorrection writes the Reed-Solomon expanded version of |in| to
// |out|.
static void AddErrorCorrection(uint8_t out[kSegmentBytes],
const uint8_t in[kSegmentDataBytes]);
// d_ represents a QR code with one byte per pixel. Each byte is one pixel.
// The LSB is set if the pixel is "black". The second bit is set if the pixel
// is part of the structure of the QR code, i.e. finder or alignment symbols,
// timing patterns, or format data.
uint8_t d_[kSize * kSize];
// clip_dump_ is the target of paints that would otherwise fall outside of the
// QR code.
uint8_t clip_dump_;
};
#endif // CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_QR_CODE_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/webauthn/authenticator_qr_code.h"
#include "testing/gtest/include/gtest/gtest.h"
TEST(AuthenticatorQRCode, Generate) {
// Without a QR decoder implementation, there's a limit to how much we can
// test the QR encoder. Therefore this test just runs a generation to ensure
// that no DCHECKs are hit and that the output has the correct structure. When
// run under ASan, this will also check that every byte of the output has been
// written to.
AuthenticatorQRCode qr;
uint8_t input[AuthenticatorQRCode::kInputBytes];
memset(input, 'a', sizeof(input));
const uint8_t* qr_data = qr.Generate(input);
for (int y = 0; y < AuthenticatorQRCode::kSize; y++) {
for (int x = 0; x < AuthenticatorQRCode::kSize; x++) {
ASSERT_EQ(0, *(qr_data++) & 0b11111100);
}
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/webauthn/authenticator_qr_sheet_view.h"
#include "base/base64url.h"
#include "base/rand_util.h"
#include "base/strings/string_piece.h"
#include "chrome/browser/ui/views/webauthn/authenticator_qr_code.h"
#include "ui/gfx/canvas.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view.h"
using QRCode = AuthenticatorQRCode;
namespace {
static constexpr int kDinoWidth = 20;
static constexpr int kDinoHeight = 22;
static constexpr int kDinoHeadHeight = 8;
static constexpr int kDinoWidthBytes = (kDinoWidth + 7) / 8;
static constexpr int kDinoBodyHeight = kDinoHeight - kDinoHeadHeight;
static const uint8_t kDinoHeadRight[kDinoWidthBytes * kDinoHeadHeight] = {
// clang-format off
0b00000000, 0b00011111, 0b11100000,
0b00000000, 0b00111111, 0b11110000,
0b00000000, 0b00110111, 0b11110000,
0b00000000, 0b00111111, 0b11110000,
0b00000000, 0b00111111, 0b11110000,
0b00000000, 0b00111111, 0b11110000,
0b00000000, 0b00111110, 0b00000000,
0b00000000, 0b00111111, 0b11000000,
// clang-format on
};
static const uint8_t kDinoHeadLeft[kDinoWidthBytes * kDinoHeadHeight] = {
// clang-format off
0b00000111, 0b11111000, 0b00000000,
0b00001111, 0b11111100, 0b00000000,
0b00001111, 0b11101100, 0b00000000,
0b00001111, 0b11111100, 0b00000000,
0b00001111, 0b11111100, 0b00000000,
0b00001111, 0b11111100, 0b00000000,
0b00000000, 0b01111100, 0b00000000,
0b00000011, 0b11111100, 0b00000000,
// clang-format on
};
static const uint8_t kDinoBody[kDinoWidthBytes * kDinoBodyHeight] = {
// clang-format off
0b10000000, 0b01111100, 0b00000000,
0b10000001, 0b11111100, 0b00000000,
0b11000011, 0b11111111, 0b00000000,
0b11100111, 0b11111101, 0b00000000,
0b11111111, 0b11111100, 0b00000000,
0b11111111, 0b11111100, 0b00000000,
0b01111111, 0b11111000, 0b00000000,
0b00111111, 0b11111000, 0b00000000,
0b00011111, 0b11110000, 0b00000000,
0b00001111, 0b11100000, 0b00000000,
0b00000111, 0b01100000, 0b00000000,
0b00000110, 0b00100000, 0b00000000,
0b00000100, 0b00100000, 0b00000000,
0b00000110, 0b00110000, 0b00000000,
// clang-format on
};
// QRView displays a QR code.
class QRView : public views::View {
public:
// kTilePixels is the height and width, in pixels, of a single tile from the
// QR code.
static constexpr int kTilePixels = 10;
// kDinoTilePixels is the height and width, in pixels, of a single bit from
// the dino image.
static constexpr int kDinoTilePixels = 3;
// kMid is the pixel offset from the top (or left) to the middle of the
// displayed QR code.
static constexpr int kMid = (kTilePixels * (2 + QRCode::kSize + 2)) / 2;
// kDinoX is the x-coordinate of the dino image.
static constexpr int kDinoX = kMid - (kDinoWidth * kDinoTilePixels) / 2;
// kDinoY is the y-coordinate of the dino image.
static constexpr int kDinoY = kMid - (kDinoHeight * kDinoTilePixels) / 2;
explicit QRView(const uint8_t qr_data[QRCode::kInputBytes]) {
static_assert(QRCode::kInputBytes == QRCode::kInputBytes,
"QR lengths mismatch");
qr_tiles_ = qr_.Generate(qr_data);
}
~QRView() override {}
void RefreshQRCode(const uint8_t new_qr_data[QRCode::kInputBytes]) {
state_++;
qr_tiles_ = qr_.Generate(new_qr_data);
SchedulePaint();
}
// View:
gfx::Size CalculatePreferredSize() const override {
// A two-tile border is required around the QR code.
return gfx::Size((2 + QRCode::kSize + 2) * kTilePixels,
(2 + QRCode::kSize + 2) * kTilePixels);
}
void OnPaint(gfx::Canvas* canvas) override {
const SkColor off = SkColorSetARGB(0xff, 0xff, 0xff, 0xff);
// kV is the intensity of the colors in the QR code.
constexpr uint8_t kV = 0x70;
SkColor on;
switch (state_ % 6) {
case 0:
on = SkColorSetARGB(0xff, kV, 0, 0);
break;
case 1:
on = SkColorSetARGB(0xff, 0, kV, 0);
break;
case 2:
on = SkColorSetARGB(0xff, 0, 0, kV);
break;
case 3:
on = SkColorSetARGB(0xff, kV, kV, 0);
break;
case 4:
on = SkColorSetARGB(0xff, kV, 0, kV);
break;
case 5:
on = SkColorSetARGB(0xff, 0, kV, kV);
break;
}
// Draw the two-tile border around the edge.
// Top.
canvas->FillRect(
gfx::Rect(0, 0, (2 + QRCode::kSize + 2) * kTilePixels, 2 * kTilePixels),
off);
// Bottom.
canvas->FillRect(
gfx::Rect(0, (2 + QRCode::kSize) * kTilePixels,
(2 + QRCode::kSize + 2) * kTilePixels, 2 * kTilePixels),
off);
// Left
canvas->FillRect(gfx::Rect(0, 2 * kTilePixels, 2 * kTilePixels,
QRCode::kSize * kTilePixels),
off);
// Right
canvas->FillRect(
gfx::Rect((2 + QRCode::kSize) * kTilePixels, 2 * kTilePixels,
2 * kTilePixels, QRCode::kSize * kTilePixels),
off);
// Paint the QR code.
for (int y = 0; y < QRCode::kSize; y++) {
for (int x = 0; x < QRCode::kSize; x++) {
SkColor tile_color = (*qr_tiles_++) & 1 ? on : off;
canvas->FillRect(gfx::Rect((x + 2) * kTilePixels, (y + 2) * kTilePixels,
kTilePixels, kTilePixels),
tile_color);
}
}
PaintDinoSegment(canvas, (state_ & 1) ? kDinoHeadLeft : kDinoHeadRight,
kDinoHeadHeight, 0);
PaintDinoSegment(canvas, kDinoBody, kDinoHeight - kDinoHeadHeight,
kDinoHeadHeight);
}
private:
void PaintDinoSegment(gfx::Canvas* canvas,
const uint8_t* data,
const int rows,
const int y_offset) {
const SkColor color = SkColorSetARGB(0xff, 0x00, 0x00, 0x00);
for (int y = 0; y < rows; y++) {
uint8_t current_byte;
unsigned bits = 0;
for (int x = 0; x < kDinoWidth; x++) {
if (bits == 0) {
current_byte = *data++;
bits = 8;
}
const bool is_set = (current_byte & 128) != 0;
current_byte <<= 1;
bits--;
if (is_set) {
canvas->FillRect(gfx::Rect(kDinoX + x * kDinoTilePixels,
kDinoY + (y + y_offset) * kDinoTilePixels,
kDinoTilePixels, kDinoTilePixels),
color);
}
}
}
}
QRCode qr_;
const uint8_t* qr_tiles_ = nullptr;
unsigned state_ = 0;
DISALLOW_COPY_AND_ASSIGN(QRView);
};
// RandomURL writes a caBLE key URL to |out_bytes|. This consists of the prefix
// "fido://c1/" followed by a base64url-encoded, 16-byte random key.
void RandomURL(uint8_t out_bytes[QRCode::kInputBytes]) {
uint8_t rand_bytes[16];
base::RandBytes(rand_bytes, sizeof(rand_bytes));
std::string encoded;
base::Base64UrlEncode(
base::StringPiece(reinterpret_cast<const char*>(rand_bytes),
sizeof(rand_bytes)),
base::Base64UrlEncodePolicy::OMIT_PADDING, &encoded);
static_assert(QRCode::kInputBytes == 10 + 22, "QR input length mismatch");
memcpy(out_bytes, "fido://c1/", 10);
memcpy(&out_bytes[10], encoded.data(), 22);
}
} // anonymous namespace
class AuthenticatorQRViewCentered : public views::View {
public:
explicit AuthenticatorQRViewCentered(
const uint8_t qr_data[QRCode::kInputBytes]) {
views::BoxLayout* layout =
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal));
layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
qr_view_ = new QRView(qr_data);
AddChildView(qr_view_);
}
void RefreshQRCode(const uint8_t new_qr_data[QRCode::kInputBytes]) {
qr_view_->RefreshQRCode(new_qr_data);
}
QRView* qr_view_;
};
AuthenticatorQRSheetView::AuthenticatorQRSheetView(
std::unique_ptr<AuthenticatorQRSheetModel> sheet_model)
: AuthenticatorRequestSheetView(std::move(sheet_model)) {}
AuthenticatorQRSheetView::~AuthenticatorQRSheetView() = default;
void AuthenticatorQRSheetView::RefreshQRCode(
const uint8_t new_qr_data[QRCode::kInputBytes]) {
qr_view_->RefreshQRCode(new_qr_data);
}
std::unique_ptr<views::View>
AuthenticatorQRSheetView::BuildStepSpecificContent() {
// TODO: data for the QR code should come from a caBLE discovery. Since that
// isn't plumbed yet, we generate random data here.
uint8_t qr_data[QRCode::kInputBytes];
RandomURL(qr_data);
auto qr_view = std::make_unique<AuthenticatorQRViewCentered>(qr_data);
qr_view_ = qr_view.get();
timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(600), this,
&AuthenticatorQRSheetView::Update);
return qr_view;
}
void AuthenticatorQRSheetView::Update() {
// TODO: fresh random values should come from the caBLE discovery. Until
// that's plumbed in, generate them here.
uint8_t qr_data[QRCode::kInputBytes];
RandomURL(qr_data);
qr_view_->RefreshQRCode(qr_data);
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_QR_SHEET_VIEW_H_
#define CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_QR_SHEET_VIEW_H_
#include <memory>
#include "base/macros.h"
#include "base/timer/timer.h"
#include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
#include "chrome/browser/ui/webauthn/sheet_models.h"
class AuthenticatorQRViewCentered;
class AuthenticatorQRSheetView : public AuthenticatorRequestSheetView {
public:
explicit AuthenticatorQRSheetView(
std::unique_ptr<AuthenticatorQRSheetModel> model);
~AuthenticatorQRSheetView() override;
// RefreshQRCode causes a fresh QR code to be painted.
void RefreshQRCode(const uint8_t new_qr_data[24]);
private:
// AuthenticatorRequestSheetView:
std::unique_ptr<views::View> BuildStepSpecificContent() override;
void Update();
AuthenticatorQRViewCentered* qr_view_ = nullptr;
base::RepeatingTimer timer_;
DISALLOW_COPY_AND_ASSIGN(AuthenticatorQRSheetView);
};
#endif // CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_QR_SHEET_VIEW_H_
......@@ -7,6 +7,7 @@
#include "base/logging.h"
#include "chrome/browser/ui/views/webauthn/authenticator_ble_pin_entry_sheet_view.h"
#include "chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_sheet_view.h"
#include "chrome/browser/ui/views/webauthn/authenticator_qr_sheet_view.h"
#include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
#include "chrome/browser/ui/views/webauthn/authenticator_select_account_sheet_view.h"
#include "chrome/browser/ui/views/webauthn/authenticator_transport_selector_sheet_view.h"
......@@ -181,6 +182,10 @@ std::unique_ptr<AuthenticatorRequestSheetView> CreateSheetViewForCurrentStepOf(
std::make_unique<AttestationPermissionRequestSheetModel>(
dialog_model));
break;
case Step::kQRCode:
sheet_view = std::make_unique<AuthenticatorQRSheetView>(
std::make_unique<AuthenticatorQRSheetModel>(dialog_model));
break;
case Step::kNotStarted:
case Step::kClosed:
sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
......
......@@ -152,6 +152,8 @@ class AuthenticatorDialogTest : public DialogBrowserTest {
base::Bind([](device::AuthenticatorGetAssertionResponse) {}));
} else if (name == "request_attestation_permission") {
model->RequestAttestationPermission(base::DoNothing());
} else if (name == "qr_code") {
model->SetCurrentStep(AuthenticatorRequestDialogModel::Step::kQRCode);
}
ShowAuthenticatorRequestDialog(
......@@ -299,3 +301,7 @@ IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_request_attestation_permission) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_qr_code) {
ShowAndVerifyUi();
}
......@@ -1208,3 +1208,29 @@ base::string16 AttestationPermissionRequestSheetModel::GetCancelButtonLabel()
// make a change post string freeze and therefore reused this.
return l10n_util::GetStringUTF16(IDS_PERMISSION_DENY);
}
// AuthenticatorQRSheetModel --------------------------------------------------
AuthenticatorQRSheetModel::AuthenticatorQRSheetModel(
AuthenticatorRequestDialogModel* dialog_model)
: AuthenticatorSheetModelBase(dialog_model) {}
AuthenticatorQRSheetModel::~AuthenticatorQRSheetModel() = default;
const gfx::VectorIcon& AuthenticatorQRSheetModel::GetStepIllustration(
ImageColorScheme color_scheme) const {
return color_scheme == ImageColorScheme::kDark ? kWebauthnPermissionDarkIcon
: kWebauthnPermissionIcon;
}
base::string16 AuthenticatorQRSheetModel::GetStepTitle() const {
// TODO: this UI is not yet reachable, but will need a translated string
// once it is.
return base::UTF8ToUTF16("Title");
}
base::string16 AuthenticatorQRSheetModel::GetStepDescription() const {
// TODO: this UI is not yet reachable, but will need a translated string
// once it is.
return base::UTF8ToUTF16("Description");
}
......@@ -541,4 +541,19 @@ class AttestationPermissionRequestSheetModel
bool IsCancelButtonVisible() const override;
base::string16 GetCancelButtonLabel() const override;
};
class AuthenticatorQRSheetModel : public AuthenticatorSheetModelBase {
public:
explicit AuthenticatorQRSheetModel(
AuthenticatorRequestDialogModel* dialog_model);
~AuthenticatorQRSheetModel() override;
private:
// AuthenticatorSheetModelBase:
const gfx::VectorIcon& GetStepIllustration(
ImageColorScheme color_scheme) const override;
base::string16 GetStepTitle() const override;
base::string16 GetStepDescription() const override;
};
#endif // CHROME_BROWSER_UI_WEBAUTHN_SHEET_MODELS_H_
......@@ -104,6 +104,9 @@ class AuthenticatorRequestDialogModel {
// Attestation permission request.
kAttestationPermissionRequest,
// Display QR code for phone pairing.
kQRCode,
};
// Implemented by the dialog to observe this model and show the UI panels
......
......@@ -1222,6 +1222,7 @@ if (!is_android) {
"../browser/ui/views/intent_picker_bubble_view_browsertest.cc",
"../browser/ui/views/try_chrome_dialog_win/try_chrome_dialog_browsertest.cc",
"../browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc",
"../browser/ui/views/webauthn/authenticator_qr_code_test.cc",
"../browser/ui/views/webview_accessibility_browsertest.cc",
"../browser/ui/web_applications/bookmark_app_browsertest.cc",
"../browser/ui/web_applications/web_app_ui_manager_impl_browsertest.cc",
......
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