Commit 9730b37c authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

webauthn: switch caBLEv2 QR code to use CBOR.

In order to make things more easily extendable in the future, make the
contents of the caBLEv2 QR codes be a CBOR map. This should eliminate
the need to version-switch based on a URL prefix, so drop the "/c1/"
from the URL.

BUG=1002262

Change-Id: Id05a38c8ba510f673594774e57d418f8259779f5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2444174
Commit-Queue: Adam Langley <agl@chromium.org>
Auto-Submit: Adam Langley <agl@chromium.org>
Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#813897}
parent b22f6b41
...@@ -438,7 +438,7 @@ class CableAuthenticator { ...@@ -438,7 +438,7 @@ class CableAuthenticator {
* Called to indicate that a QR code was scanned by the user. * Called to indicate that a QR code was scanned by the user.
* *
* @param value contents of the QR code, which will be a valid caBLE * @param value contents of the QR code, which will be a valid caBLE
* URL, i.e. "fido://c1/"... * URL, i.e. "fido://"...
*/ */
public void onQRCode(String value) { public void onQRCode(String value) {
mTaskRunner.postTask(() -> { mTaskRunner.postTask(() -> {
......
...@@ -36,7 +36,7 @@ public class QRScanDialog extends DialogFragment implements Camera.PreviewCallba ...@@ -36,7 +36,7 @@ public class QRScanDialog extends DialogFragment implements Camera.PreviewCallba
* FIDO QR codes begin with this prefix. This class will ignore QR codes that don't match * FIDO QR codes begin with this prefix. This class will ignore QR codes that don't match
* this. * this.
*/ */
public static final String FIDO_QR_PREFIX = "fido://c1/"; public static final String FIDO_QR_PREFIX = "fido://";
private static final String TAG = "QRScanDialog"; private static final String TAG = "QRScanDialog";
/** /**
* Receives a single call containing the decoded QR value. It will * Receives a single call containing the decoded QR value. It will
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
#include "base/android/jni_array.h" #include "base/android/jni_array.h"
#include "base/android/jni_string.h" #include "base/android/jni_string.h"
#include "base/base64url.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/callback.h" #include "base/callback.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
...@@ -19,8 +18,6 @@ ...@@ -19,8 +18,6 @@
#include "device/fido/cable/v2_handshake.h" #include "device/fido/cable/v2_handshake.h"
#include "device/fido/cable/v2_registration.h" #include "device/fido/cable/v2_registration.h"
#include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_parsing_utils.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/obj.h"
// These "headers" actually contain several function definitions and thus can // These "headers" actually contain several function definitions and thus can
// only be included once across Chromium. // only be included once across Chromium.
...@@ -75,79 +72,6 @@ NewState() { ...@@ -75,79 +72,6 @@ NewState() {
return std::make_tuple(root_secret, std::move(*bytes)); return std::make_tuple(root_secret, std::move(*bytes));
} }
// DecodeQR represents the values in a scanned QR code.
struct DecodedQR {
std::array<uint8_t, 16> secret;
std::array<uint8_t, device::kP256X962Length> peer_identity;
};
// DecompressPublicKey converts a compressed public key (from a scanned QR
// code) into a standard, uncompressed one.
base::Optional<std::array<uint8_t, device::kP256X962Length>>
DecompressPublicKey(
base::span<const uint8_t, device::cablev2::kCompressedPublicKeySize>
compressed_public_key) {
bssl::UniquePtr<EC_GROUP> p256(
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get()));
if (!EC_POINT_oct2point(p256.get(), point.get(), compressed_public_key.data(),
compressed_public_key.size(), /*ctx=*/nullptr)) {
return base::nullopt;
}
std::array<uint8_t, device::kP256X962Length> ret;
CHECK_EQ(
ret.size(),
EC_POINT_point2oct(p256.get(), point.get(), POINT_CONVERSION_UNCOMPRESSED,
ret.data(), ret.size(), /*ctx=*/nullptr));
return ret;
}
// DecodeQR converts the textual form of a scanned QR code into a |DecodedQR|.
base::Optional<DecodedQR> DecodeQR(const std::string& qr_url) {
static const char kPrefix[] = "fido://c1/";
// The scanning code should have filtered out any unrelated URLs.
if (qr_url.find(kPrefix) != 0) {
NOTREACHED();
return base::nullopt;
}
base::StringPiece qr_url_base64(qr_url);
qr_url_base64 = qr_url_base64.substr(sizeof(kPrefix) - 1);
std::string qr_data_str;
if (!base::Base64UrlDecode(qr_url_base64,
base::Base64UrlDecodePolicy::DISALLOW_PADDING,
&qr_data_str) ||
qr_data_str.size() != device::cablev2::kQRDataSize) {
FIDO_LOG(ERROR) << "QR decoding failed: " << qr_url;
return base::nullopt;
}
base::span<const uint8_t, device::cablev2::kQRDataSize> qr_data(
reinterpret_cast<const uint8_t*>(qr_data_str.data()), qr_data_str.size());
static_assert(EXTENT(qr_data) == device::cablev2::kCompressedPublicKeySize +
device::cablev2::kQRSecretSize,
"");
base::span<const uint8_t, device::cablev2::kCompressedPublicKeySize>
compressed_public_key(qr_data.data(),
device::cablev2::kCompressedPublicKeySize);
auto qr_secret = qr_data.subspan(device::cablev2::kCompressedPublicKeySize);
DecodedQR ret;
DCHECK_EQ(qr_secret.size(), ret.secret.size());
std::copy(qr_secret.begin(), qr_secret.end(), ret.secret.begin());
base::Optional<std::array<uint8_t, device::kP256X962Length>> peer_identity =
DecompressPublicKey(compressed_public_key);
if (!peer_identity) {
FIDO_LOG(ERROR) << "Invalid compressed public key in QR data";
return base::nullopt;
}
ret.peer_identity = *peer_identity;
return ret;
}
// JavaByteArrayToSpan returns a span that aliases |data|. Be aware that the // JavaByteArrayToSpan returns a span that aliases |data|. Be aware that the
// reference for |data| must outlive the span. // reference for |data| must outlive the span.
base::span<const uint8_t> JavaByteArrayToSpan( base::span<const uint8_t> JavaByteArrayToSpan(
...@@ -466,9 +390,11 @@ static jboolean JNI_CableAuthenticator_StartQR( ...@@ -466,9 +390,11 @@ static jboolean JNI_CableAuthenticator_StartQR(
const JavaParamRef<jstring>& authenticator_name, const JavaParamRef<jstring>& authenticator_name,
const JavaParamRef<jstring>& qr_url) { const JavaParamRef<jstring>& qr_url) {
GlobalData& global_data = GetGlobalData(); GlobalData& global_data = GetGlobalData();
base::Optional<DecodedQR> decoded_qr( const std::string& qr_string = ConvertJavaStringToUTF8(qr_url);
DecodeQR(ConvertJavaStringToUTF8(qr_url))); base::Optional<device::cablev2::qr::Components> decoded_qr(
device::cablev2::qr::Parse(qr_string));
if (!decoded_qr) { if (!decoded_qr) {
FIDO_LOG(ERROR) << "Failed to decode QR: " << qr_string;
return false; return false;
} }
......
...@@ -9,9 +9,6 @@ ...@@ -9,9 +9,6 @@
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "chrome/common/qr_code_generator/dino_image.h" #include "chrome/common/qr_code_generator/dino_image.h"
#include "chrome/common/qr_code_generator/qr_code_generator.h" #include "chrome/common/qr_code_generator/qr_code_generator.h"
#include "third_party/boringssl/src/include/openssl/ec.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/obj.h"
#include "ui/gfx/canvas.h" #include "ui/gfx/canvas.h"
#include "ui/views/layout/box_layout.h" #include "ui/views/layout/box_layout.h"
#include "ui/views/view.h" #include "ui/views/view.h"
...@@ -39,8 +36,11 @@ class QRView : public views::View { ...@@ -39,8 +36,11 @@ class QRView : public views::View {
static constexpr int kDinoY = static constexpr int kDinoY =
kMid - (dino_image::kDinoHeight * kDinoTilePixels) / 2; kMid - (dino_image::kDinoHeight * kDinoTilePixels) / 2;
explicit QRView(base::span<const uint8_t> qr_data) { explicit QRView(const std::string& qr_string) {
base::Optional<QRCode::GeneratedCode> code = qr_.Generate(qr_data); CHECK_LE(qr_string.size(), QRCodeGenerator::V5::kInputBytes);
base::Optional<QRCode::GeneratedCode> code =
qr_.Generate(base::as_bytes(base::make_span(qr_string)));
DCHECK(code); DCHECK(code);
// The QR Encoder supports dynamic sizing but we expect our data to fit in // The QR Encoder supports dynamic sizing but we expect our data to fit in
// a version five code. // a version five code.
...@@ -50,10 +50,12 @@ class QRView : public views::View { ...@@ -50,10 +50,12 @@ class QRView : public views::View {
~QRView() override = default; ~QRView() override = default;
void RefreshQRCode(base::span<const uint8_t> new_qr_data) { void RefreshQRCode(const std::string& qr_string) {
CHECK_LE(qr_string.size(), QRCodeGenerator::V5::kInputBytes);
state_ = (state_ + 1) % 6; state_ = (state_ + 1) % 6;
base::Optional<QRCode::GeneratedCode> code = base::Optional<QRCode::GeneratedCode> code = qr_.Generate(
qr_.Generate(new_qr_data, /*mask=*/state_); base::as_bytes(base::make_span(qr_string)), /*mask=*/state_);
DCHECK(code); DCHECK(code);
qr_tiles_ = code->data; qr_tiles_ = code->data;
SchedulePaint(); SchedulePaint();
...@@ -170,77 +172,11 @@ class QRView : public views::View { ...@@ -170,77 +172,11 @@ class QRView : public views::View {
DISALLOW_COPY_AND_ASSIGN(QRView); DISALLOW_COPY_AND_ASSIGN(QRView);
}; };
// kCompressedPublicKeySize is the size of an X9.62 compressed P-256 public key.
constexpr size_t kCompressedPublicKeySize = 33;
std::array<uint8_t, kCompressedPublicKeySize> SeedToCompressedPublicKey(
base::span<const uint8_t, 32> seed) {
bssl::UniquePtr<EC_GROUP> p256(
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
bssl::UniquePtr<EC_KEY> key(
EC_KEY_derive_from_secret(p256.get(), seed.data(), seed.size()));
const EC_POINT* public_key = EC_KEY_get0_public_key(key.get());
std::array<uint8_t, kCompressedPublicKeySize> ret;
CHECK_EQ(ret.size(), EC_POINT_point2oct(
p256.get(), public_key, POINT_CONVERSION_COMPRESSED,
ret.data(), ret.size(), /*ctx=*/nullptr));
return ret;
}
// Base64EncodedSize returns the number of bytes required to base64 encode an
// input of |input_length| bytes, without padding.
constexpr size_t Base64EncodedSize(size_t input_length) {
return ((input_length * 4) + 2) / 3;
}
// BuildQRData writes a URL suitable for encoding as a QR to |out_buf|
// and returns a span pointing into that buffer. The URL is generated based on
// |qr_generator_key|.
base::span<uint8_t> BuildQRData(
uint8_t out_buf[QRCode::V5::kInputBytes],
base::span<const uint8_t, device::cablev2::kQRKeySize> qr_generator_key) {
static_assert(device::cablev2::kQRSeedSize <= device::cablev2::kQRKeySize,
"");
const std::array<uint8_t, kCompressedPublicKeySize> compressed_public_key =
SeedToCompressedPublicKey(
base::span<const uint8_t, device::cablev2::kQRSeedSize>(
qr_generator_key.data(), device::cablev2::kQRSeedSize));
uint8_t
qr_data[EXTENT(compressed_public_key) + device::cablev2::kQRSecretSize];
memcpy(qr_data, compressed_public_key.data(), compressed_public_key.size());
static_assert(EXTENT(qr_generator_key) == device::cablev2::kQRSeedSize +
device::cablev2::kQRSecretSize,
"");
memcpy(qr_data + compressed_public_key.size(),
&qr_generator_key[device::cablev2::kQRSeedSize],
device::cablev2::kQRSecretSize);
std::string base64_qr_data;
base::Base64UrlEncode(
base::StringPiece(reinterpret_cast<const char*>(qr_data),
sizeof(qr_data)),
base::Base64UrlEncodePolicy::OMIT_PADDING, &base64_qr_data);
static constexpr size_t kEncodedDataLength =
Base64EncodedSize(sizeof(qr_data));
DCHECK_EQ(kEncodedDataLength, base64_qr_data.size());
static constexpr char kPrefix[] = "fido://c1/";
static constexpr size_t kPrefixLength = sizeof(kPrefix) - 1;
static_assert(QRCode::V5::kInputBytes >= kPrefixLength + kEncodedDataLength,
"unexpected QR input length");
memcpy(out_buf, kPrefix, kPrefixLength);
memcpy(&out_buf[kPrefixLength], base64_qr_data.data(), kEncodedDataLength);
return base::span<uint8_t>(out_buf, kPrefixLength + kEncodedDataLength);
}
} // anonymous namespace } // anonymous namespace
class AuthenticatorQRViewCentered : public views::View { class AuthenticatorQRViewCentered : public views::View {
public: public:
explicit AuthenticatorQRViewCentered(base::span<const uint8_t> qr_data) { explicit AuthenticatorQRViewCentered(const std::string& qr_data) {
views::BoxLayout* layout = views::BoxLayout* layout =
SetLayoutManager(std::make_unique<views::BoxLayout>( SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal)); views::BoxLayout::Orientation::kHorizontal));
...@@ -252,7 +188,7 @@ class AuthenticatorQRViewCentered : public views::View { ...@@ -252,7 +188,7 @@ class AuthenticatorQRViewCentered : public views::View {
AddChildView(qr_view_); AddChildView(qr_view_);
} }
void RefreshQRCode(base::span<const uint8_t> new_qr_data) { void RefreshQRCode(const std::string& new_qr_data) {
qr_view_->RefreshQRCode(new_qr_data); qr_view_->RefreshQRCode(new_qr_data);
} }
...@@ -262,17 +198,15 @@ class AuthenticatorQRViewCentered : public views::View { ...@@ -262,17 +198,15 @@ class AuthenticatorQRViewCentered : public views::View {
AuthenticatorQRSheetView::AuthenticatorQRSheetView( AuthenticatorQRSheetView::AuthenticatorQRSheetView(
std::unique_ptr<AuthenticatorQRSheetModel> sheet_model) std::unique_ptr<AuthenticatorQRSheetModel> sheet_model)
: AuthenticatorRequestSheetView(std::move(sheet_model)), : AuthenticatorRequestSheetView(std::move(sheet_model)),
qr_generator_key_(static_cast<AuthenticatorQRSheetModel*>(model()) qr_string_(static_cast<AuthenticatorQRSheetModel*>(model())
->dialog_model() ->dialog_model()
->qr_generator_key()) {} ->cable_qr_string()) {}
AuthenticatorQRSheetView::~AuthenticatorQRSheetView() = default; AuthenticatorQRSheetView::~AuthenticatorQRSheetView() = default;
std::unique_ptr<views::View> std::unique_ptr<views::View>
AuthenticatorQRSheetView::BuildStepSpecificContent() { AuthenticatorQRSheetView::BuildStepSpecificContent() {
uint8_t qr_data_buf[QRCode::V5::kInputBytes]; auto qr_view = std::make_unique<AuthenticatorQRViewCentered>(qr_string_);
auto qr_view = std::make_unique<AuthenticatorQRViewCentered>(
BuildQRData(qr_data_buf, qr_generator_key_));
qr_view_ = qr_view.get(); qr_view_ = qr_view.get();
timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(600), this, timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(600), this,
...@@ -281,6 +215,5 @@ AuthenticatorQRSheetView::BuildStepSpecificContent() { ...@@ -281,6 +215,5 @@ AuthenticatorQRSheetView::BuildStepSpecificContent() {
} }
void AuthenticatorQRSheetView::Update() { void AuthenticatorQRSheetView::Update() {
uint8_t qr_data_buf[QRCode::V5::kInputBytes]; qr_view_->RefreshQRCode(qr_string_);
qr_view_->RefreshQRCode(BuildQRData(qr_data_buf, qr_generator_key_));
} }
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
#include "base/timer/timer.h" #include "base/timer/timer.h"
#include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h" #include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
#include "chrome/browser/ui/webauthn/sheet_models.h" #include "chrome/browser/ui/webauthn/sheet_models.h"
#include "device/fido/cable/v2_constants.h"
class AuthenticatorQRViewCentered; class AuthenticatorQRViewCentered;
...@@ -32,7 +31,7 @@ class AuthenticatorQRSheetView : public AuthenticatorRequestSheetView { ...@@ -32,7 +31,7 @@ class AuthenticatorQRSheetView : public AuthenticatorRequestSheetView {
void Update(); void Update();
AuthenticatorQRViewCentered* qr_view_ = nullptr; AuthenticatorQRViewCentered* qr_view_ = nullptr;
base::span<const uint8_t, device::cablev2::kQRKeySize> qr_generator_key_; const std::string qr_string_;
base::RepeatingTimer timer_; base::RepeatingTimer timer_;
DISALLOW_COPY_AND_ASSIGN(AuthenticatorQRSheetView); DISALLOW_COPY_AND_ASSIGN(AuthenticatorQRSheetView);
......
...@@ -40,9 +40,9 @@ class AuthenticatorDialogTest : public DialogBrowserTest { ...@@ -40,9 +40,9 @@ class AuthenticatorDialogTest : public DialogBrowserTest {
AuthenticatorTransport::kUsbHumanInterfaceDevice, AuthenticatorTransport::kUsbHumanInterfaceDevice,
AuthenticatorTransport::kInternal, AuthenticatorTransport::kInternal,
AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy}; AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy};
std::array<uint8_t, device::cablev2::kQRKeySize> qr_key = {0};
model->set_cable_transport_info(/*cable_extension_provided=*/true, model->set_cable_transport_info(/*cable_extension_provided=*/true,
/*have_paired_phones=*/false, qr_key); /*have_paired_phones=*/false,
"fido://qrcode");
model->StartFlow(std::move(transport_availability), base::nullopt); model->StartFlow(std::move(transport_availability), base::nullopt);
// The dialog should immediately close as soon as it is displayed. // The dialog should immediately close as soon as it is displayed.
......
...@@ -226,7 +226,7 @@ void AuthenticatorRequestDialogModel::StartWinNativeApi() { ...@@ -226,7 +226,7 @@ void AuthenticatorRequestDialogModel::StartWinNativeApi() {
} }
void AuthenticatorRequestDialogModel::StartPhonePairing() { void AuthenticatorRequestDialogModel::StartPhonePairing() {
DCHECK(qr_generator_key_); DCHECK(cable_qr_string_);
SetCurrentStep(Step::kCableV2QRCode); SetCurrentStep(Step::kCableV2QRCode);
} }
...@@ -585,11 +585,10 @@ void AuthenticatorRequestDialogModel::RequestAttestationPermission( ...@@ -585,11 +585,10 @@ void AuthenticatorRequestDialogModel::RequestAttestationPermission(
void AuthenticatorRequestDialogModel::set_cable_transport_info( void AuthenticatorRequestDialogModel::set_cable_transport_info(
bool cable_extension_provided, bool cable_extension_provided,
bool have_paired_phones, bool have_paired_phones,
const base::Optional<std::array<uint8_t, device::cablev2::kQRKeySize>>& const base::Optional<std::string>& cable_qr_string) {
qr_generator_key) {
cable_extension_provided_ = cable_extension_provided; cable_extension_provided_ = cable_extension_provided;
have_paired_phones_ = have_paired_phones; have_paired_phones_ = have_paired_phones;
qr_generator_key_ = qr_generator_key; cable_qr_string_ = cable_qr_string;
} }
base::WeakPtr<AuthenticatorRequestDialogModel> base::WeakPtr<AuthenticatorRequestDialogModel>
......
...@@ -20,8 +20,6 @@ ...@@ -20,8 +20,6 @@
#include "chrome/browser/webauthn/authenticator_reference.h" #include "chrome/browser/webauthn/authenticator_reference.h"
#include "chrome/browser/webauthn/authenticator_transport.h" #include "chrome/browser/webauthn/authenticator_transport.h"
#include "chrome/browser/webauthn/observable_authenticator_list.h" #include "chrome/browser/webauthn/observable_authenticator_list.h"
#include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/cable/v2_constants.h"
#include "device/fido/fido_request_handler_base.h" #include "device/fido/fido_request_handler_base.h"
#include "device/fido/fido_transport_protocol.h" #include "device/fido/fido_transport_protocol.h"
...@@ -366,10 +364,7 @@ class AuthenticatorRequestDialogModel { ...@@ -366,10 +364,7 @@ class AuthenticatorRequestDialogModel {
return transport_availability_.available_transports; return transport_availability_.available_transports;
} }
base::span<const uint8_t, device::cablev2::kQRKeySize> qr_generator_key() const std::string& cable_qr_string() const { return *cable_qr_string_; }
const {
return *qr_generator_key_;
}
void CollectPIN(base::Optional<int> attempts, void CollectPIN(base::Optional<int> attempts,
base::OnceCallback<void(std::string)> provide_pin_cb); base::OnceCallback<void(std::string)> provide_pin_cb);
...@@ -413,8 +408,7 @@ class AuthenticatorRequestDialogModel { ...@@ -413,8 +408,7 @@ class AuthenticatorRequestDialogModel {
void set_cable_transport_info( void set_cable_transport_info(
bool cable_extension_provided, bool cable_extension_provided,
bool has_paired_phones, bool has_paired_phones,
const base::Optional<std::array<uint8_t, device::cablev2::kQRKeySize>>& const base::Optional<std::string>& cable_qr_string);
qr_generator_key);
bool win_native_api_enabled() const { bool win_native_api_enabled() const {
return transport_availability_.has_win_native_api_authenticator; return transport_availability_.has_win_native_api_authenticator;
...@@ -509,8 +503,7 @@ class AuthenticatorRequestDialogModel { ...@@ -509,8 +503,7 @@ class AuthenticatorRequestDialogModel {
// have_paired_phones_ indicates whether this profile knows of any paired // have_paired_phones_ indicates whether this profile knows of any paired
// phones. // phones.
bool have_paired_phones_ = false; bool have_paired_phones_ = false;
base::Optional<std::array<uint8_t, device::cablev2::kQRKeySize>> base::Optional<std::string> cable_qr_string_;
qr_generator_key_;
// win_native_api_already_tried_ is true if the Windows-native UI has been // win_native_api_already_tried_ is true if the Windows-native UI has been
// displayed already and the user cancelled it. In this case, we shouldn't // displayed already and the user cancelled it. In this case, we shouldn't
// jump straight to showing it again. // jump straight to showing it again.
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "crypto/random.h" #include "crypto/random.h"
#include "device/fido/cable/v2_handshake.h"
#include "device/fido/features.h" #include "device/fido/features.h"
#include "device/fido/fido_authenticator.h" #include "device/fido/fido_authenticator.h"
#include "device/fido/fido_discovery_factory.h" #include "device/fido/fido_discovery_factory.h"
...@@ -359,11 +360,13 @@ void ChromeAuthenticatorRequestDelegate::ConfigureCable( ...@@ -359,11 +360,13 @@ void ChromeAuthenticatorRequestDelegate::ConfigureCable(
base::Optional<std::array<uint8_t, device::cablev2::kQRKeySize>> base::Optional<std::array<uint8_t, device::cablev2::kQRKeySize>>
qr_generator_key; qr_generator_key;
base::Optional<std::string> qr_string;
bool have_paired_phones = false; bool have_paired_phones = false;
std::vector<std::unique_ptr<device::cablev2::Pairing>> paired_phones; std::vector<std::unique_ptr<device::cablev2::Pairing>> paired_phones;
if (base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport)) { if (base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport)) {
qr_generator_key.emplace(); qr_generator_key.emplace();
crypto::RandBytes(*qr_generator_key); crypto::RandBytes(*qr_generator_key);
qr_string = device::cablev2::qr::Encode(*qr_generator_key);
paired_phones = GetCablePairings(); paired_phones = GetCablePairings();
have_paired_phones = !paired_phones.empty(); have_paired_phones = !paired_phones.empty();
...@@ -379,8 +382,8 @@ void ChromeAuthenticatorRequestDelegate::ConfigureCable( ...@@ -379,8 +382,8 @@ void ChromeAuthenticatorRequestDelegate::ConfigureCable(
return; return;
} }
weak_dialog_model_->set_cable_transport_info( weak_dialog_model_->set_cable_transport_info(cable_extension_provided,
cable_extension_provided, have_paired_phones, qr_generator_key); have_paired_phones, qr_string);
discovery_factory->set_cable_data(std::move(pairings), qr_generator_key, discovery_factory->set_cable_data(std::move(pairings), qr_generator_key,
std::move(paired_phones)); std::move(paired_phones));
......
...@@ -34,9 +34,6 @@ constexpr size_t kQRKeySize = kQRSeedSize + kQRSecretSize; ...@@ -34,9 +34,6 @@ constexpr size_t kQRKeySize = kQRSeedSize + kQRSecretSize;
// kCompressedPublicKeySize is the size of a compressed X9.62 public key. // kCompressedPublicKeySize is the size of a compressed X9.62 public key.
constexpr size_t kCompressedPublicKeySize = constexpr size_t kCompressedPublicKeySize =
/* type byte */ 1 + /* field element */ (256 / 8); /* type byte */ 1 + /* field element */ (256 / 8);
// kQRDataSize is the size of the (unencoded) QR payload. It's a compressed
// public key followed by the QR secret.
constexpr size_t kQRDataSize = kCompressedPublicKeySize + kQRSecretSize;
} // namespace cablev2 } // namespace cablev2
} // namespace device } // namespace device
......
...@@ -183,6 +183,126 @@ Components ToComponents(const CableEidArray& eid) { ...@@ -183,6 +183,126 @@ Components ToComponents(const CableEidArray& eid) {
} // namespace eid } // namespace eid
namespace qr {
constexpr char kPrefix[] = "fido://";
// DecompressPublicKey converts a compressed public key (from a scanned QR
// code) into a standard, uncompressed one.
static base::Optional<std::array<uint8_t, device::kP256X962Length>>
DecompressPublicKey(base::span<const uint8_t> compressed_public_key) {
if (compressed_public_key.size() !=
device::cablev2::kCompressedPublicKeySize) {
return base::nullopt;
}
bssl::UniquePtr<EC_GROUP> p256(
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get()));
if (!EC_POINT_oct2point(p256.get(), point.get(), compressed_public_key.data(),
compressed_public_key.size(), /*ctx=*/nullptr)) {
return base::nullopt;
}
std::array<uint8_t, device::kP256X962Length> ret;
CHECK_EQ(
ret.size(),
EC_POINT_point2oct(p256.get(), point.get(), POINT_CONVERSION_UNCOMPRESSED,
ret.data(), ret.size(), /*ctx=*/nullptr));
return ret;
}
static std::array<uint8_t, device::cablev2::kCompressedPublicKeySize>
SeedToCompressedPublicKey(base::span<const uint8_t, 32> seed) {
bssl::UniquePtr<EC_GROUP> p256(
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
bssl::UniquePtr<EC_KEY> key(
EC_KEY_derive_from_secret(p256.get(), seed.data(), seed.size()));
const EC_POINT* public_key = EC_KEY_get0_public_key(key.get());
std::array<uint8_t, device::cablev2::kCompressedPublicKeySize> ret;
CHECK_EQ(ret.size(), EC_POINT_point2oct(
p256.get(), public_key, POINT_CONVERSION_COMPRESSED,
ret.data(), ret.size(), /*ctx=*/nullptr));
return ret;
}
// static
base::Optional<Components> Parse(const std::string& qr_url) {
if (qr_url.find(kPrefix) != 0) {
return base::nullopt;
}
base::StringPiece qr_url_base64(qr_url);
qr_url_base64 = qr_url_base64.substr(sizeof(kPrefix) - 1);
std::string qr_data_str;
if (!base::Base64UrlDecode(qr_url_base64,
base::Base64UrlDecodePolicy::DISALLOW_PADDING,
&qr_data_str)) {
return base::nullopt;
}
base::Optional<cbor::Value> qr_contents =
cbor::Reader::Read(base::span<const uint8_t>(
reinterpret_cast<const uint8_t*>(qr_data_str.data()),
qr_data_str.size()));
if (!qr_contents || !qr_contents->is_map()) {
return base::nullopt;
}
const cbor::Value::MapValue& qr_contents_map(qr_contents->GetMap());
base::span<const uint8_t> values[2];
for (size_t i = 0; i < base::size(values); i++) {
const cbor::Value::MapValue::const_iterator it =
qr_contents_map.find(cbor::Value(static_cast<int>(i)));
if (it == qr_contents_map.end() || !it->second.is_bytestring()) {
return base::nullopt;
}
values[i] = it->second.GetBytestring();
}
base::span<const uint8_t> compressed_public_key = values[0];
base::span<const uint8_t> qr_secret = values[1];
Components ret;
if (qr_secret.size() != ret.secret.size()) {
return base::nullopt;
}
std::copy(qr_secret.begin(), qr_secret.end(), ret.secret.begin());
base::Optional<std::array<uint8_t, device::kP256X962Length>> peer_identity =
DecompressPublicKey(compressed_public_key);
if (!peer_identity) {
FIDO_LOG(ERROR) << "Invalid compressed public key in QR data";
return base::nullopt;
}
ret.peer_identity = *peer_identity;
return ret;
}
std::string Encode(base::span<const uint8_t, kQRKeySize> qr_key) {
cbor::Value::MapValue qr_contents;
qr_contents.emplace(
0, SeedToCompressedPublicKey(
base::span<const uint8_t, device::cablev2::kQRSeedSize>(
qr_key.data(), device::cablev2::kQRSeedSize)));
qr_contents.emplace(1, qr_key.subspan(device::cablev2::kQRSeedSize));
const base::Optional<std::vector<uint8_t>> qr_data =
cbor::Writer::Write(cbor::Value(std::move(qr_contents)));
std::string base64_qr_data;
base::Base64UrlEncode(
base::StringPiece(reinterpret_cast<const char*>(qr_data->data()),
qr_data->size()),
base::Base64UrlEncodePolicy::OMIT_PADDING, &base64_qr_data);
return std::string(kPrefix) + base64_qr_data;
}
} // namespace qr
namespace internal { namespace internal {
void Derive(uint8_t* out, void Derive(uint8_t* out,
......
...@@ -116,6 +116,23 @@ Components ToComponents(const CableEidArray& eid); ...@@ -116,6 +116,23 @@ Components ToComponents(const CableEidArray& eid);
} // namespace eid } // namespace eid
namespace qr {
// Components contains the parsed elements of a QR code.
struct COMPONENT_EXPORT(DEVICE_FIDO) Components {
std::array<uint8_t, device::kP256X962Length> peer_identity;
std::array<uint8_t, 16> secret;
};
COMPONENT_EXPORT(DEVICE_FIDO)
base::Optional<Components> Parse(const std::string& qr_url);
// Encode returns the contents of a QR code that represents |qr_key|.
COMPONENT_EXPORT(DEVICE_FIDO)
std::string Encode(base::span<const uint8_t, kQRKeySize> qr_key);
} // namespace qr
// DerivedValueType enumerates the different types of values that might be // DerivedValueType enumerates the different types of values that might be
// derived in caBLEv2 from some secret. The values this this enum are protocol // derived in caBLEv2 from some secret. The values this this enum are protocol
// constants and thus must not change over time. // constants and thus must not change over time.
......
...@@ -46,6 +46,24 @@ TEST(CableV2Encoding, EIDs) { ...@@ -46,6 +46,24 @@ TEST(CableV2Encoding, EIDs) {
EXPECT_FALSE(eid::IsValid(eid)); EXPECT_FALSE(eid::IsValid(eid));
} }
TEST(CableV2Encoding, QRs) {
std::array<uint8_t, kQRKeySize> qr_key;
crypto::RandBytes(qr_key);
std::string url = qr::Encode(qr_key);
EXPECT_LE(url.size(), 81u) << "QR code doesn't fit into version five";
const base::Optional<qr::Components> decoded = qr::Parse(url);
ASSERT_TRUE(decoded.has_value());
static_assert(EXTENT(qr_key) >= EXTENT(decoded->secret), "");
EXPECT_EQ(memcmp(decoded->secret.data(),
&qr_key[qr_key.size() - decoded->secret.size()],
decoded->secret.size()),
0);
url[0] ^= 4;
EXPECT_FALSE(qr::Parse(url));
EXPECT_FALSE(qr::Parse("nonsense"));
}
TEST(CableV2Encoding, PaddedCBOR) { TEST(CableV2Encoding, PaddedCBOR) {
cbor::Value::MapValue map; cbor::Value::MapValue map;
base::Optional<std::vector<uint8_t>> encoded = base::Optional<std::vector<uint8_t>> encoded =
......
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