blob: 57a0e8c4e41a5874273136344a6b1f3498503628 [file] [log] [blame]
// Copyright 2015 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 "chromeos/services/secure_channel/device_to_device_initiator_helper.h"
#include "base/bind.h"
#include "base/callback.h"
#include "chromeos/components/proximity_auth/logging/logging.h"
#include "chromeos/services/device_sync/proto/cryptauth_api.pb.h"
#include "components/cryptauth/secure_message_delegate.h"
namespace chromeos {
namespace secure_channel {
namespace {
// TODO(tengs): Due to a bug with the ChromeOS secure message daemon, we cannot
// create SecureMessages with empty payloads. To workaround this bug, this value
// is put into the payload if it would otherwise be empty.
// See crbug.com/512894.
const char kPayloadFiller[] = "\xae";
// The version to put in the cryptauth::GcmMetadata field.
const int kGcmMetadataVersion = 1;
// The D2D protocol version.
const int kD2DProtocolVersion = 1;
} // namespace
DeviceToDeviceInitiatorHelper::DeviceToDeviceInitiatorHelper()
: weak_ptr_factory_(this) {}
DeviceToDeviceInitiatorHelper::~DeviceToDeviceInitiatorHelper() {}
void DeviceToDeviceInitiatorHelper::CreateHelloMessage(
const std::string& session_public_key,
const std::string& persistent_symmetric_key,
cryptauth::SecureMessageDelegate* secure_message_delegate,
const MessageCallback& callback) {
// Decode public key into the |initator_hello| proto.
securemessage::InitiatorHello initiator_hello;
if (!initiator_hello.mutable_public_dh_key()->ParseFromString(
session_public_key)) {
PA_LOG(ERROR) << "Unable to parse user's public key";
callback.Run(std::string());
return;
}
initiator_hello.set_protocol_version(kD2DProtocolVersion);
// The [Hello] message has the structure:
// {
// header: <session_public_key>,
// Sig(<session_public_key>, persistent_symmetric_key)
// payload: ""
// }
cryptauth::SecureMessageDelegate::CreateOptions create_options;
create_options.encryption_scheme = securemessage::NONE;
create_options.signature_scheme = securemessage::HMAC_SHA256;
initiator_hello.SerializeToString(&create_options.public_metadata);
secure_message_delegate->CreateSecureMessage(
kPayloadFiller, persistent_symmetric_key, create_options, callback);
}
void DeviceToDeviceInitiatorHelper::ValidateResponderAuthMessage(
const std::string& responder_auth_message,
const std::string& persistent_responder_public_key,
const std::string& persistent_symmetric_key,
const std::string& session_private_key,
const std::string& hello_message,
cryptauth::SecureMessageDelegate* secure_message_delegate,
const ValidateResponderAuthCallback& callback) {
// The [Responder Auth] message has the structure:
// {
// header: <responder_public_key>,
// Sig(<responder_public_key> + payload1,
// session_symmetric_key),
// payload1: Enc({
// header: Sig(payload2 + <hello_message>, persistent_symmetric_key),
// payload2: {
// sequence_number: 1,
// body: Enc({
// header: Sig(payload3 + <hello_message>,
// persistent_responder_public_key),
// payload3: ""
// }, persistent_symmetric_key)
// }
// }, session_symmetric_key),
// }
ValidateResponderAuthMessageContext context(
responder_auth_message, persistent_responder_public_key,
persistent_symmetric_key, session_private_key, hello_message,
secure_message_delegate, callback);
BeginResponderAuthValidation(context);
}
void DeviceToDeviceInitiatorHelper::CreateInitiatorAuthMessage(
const SessionKeys& session_keys,
const std::string& persistent_symmetric_key,
const std::string& responder_auth_message,
cryptauth::SecureMessageDelegate* secure_message_delegate,
const MessageCallback& callback) {
// The [Initiator Auth] message has the structure:
// {
// header: Sig(payload1, session_symmetric_key)
// payload1: Enc({
// sequence_number: 2,
// body: {
// header: Sig(payload2 + responder_auth_message,
// persistent_symmetric_key)
// payload2: ""
// }
// }, session_symmetric_key)
// }
cryptauth::SecureMessageDelegate::CreateOptions create_options;
create_options.encryption_scheme = securemessage::AES_256_CBC;
create_options.signature_scheme = securemessage::HMAC_SHA256;
create_options.associated_data = responder_auth_message;
secure_message_delegate->CreateSecureMessage(
kPayloadFiller, persistent_symmetric_key, create_options,
base::Bind(
&DeviceToDeviceInitiatorHelper::OnInnerMessageCreatedForInitiatorAuth,
weak_ptr_factory_.GetWeakPtr(), session_keys, secure_message_delegate,
callback));
}
DeviceToDeviceInitiatorHelper::ValidateResponderAuthMessageContext ::
ValidateResponderAuthMessageContext(
const std::string& responder_auth_message,
const std::string& persistent_responder_public_key,
const std::string& persistent_symmetric_key,
const std::string& session_private_key,
const std::string& hello_message,
cryptauth::SecureMessageDelegate* secure_message_delegate,
const ValidateResponderAuthCallback& callback)
: responder_auth_message(responder_auth_message),
persistent_responder_public_key(persistent_responder_public_key),
persistent_symmetric_key(persistent_symmetric_key),
session_private_key(session_private_key),
hello_message(hello_message),
secure_message_delegate(secure_message_delegate),
callback(callback) {}
DeviceToDeviceInitiatorHelper::ValidateResponderAuthMessageContext ::
ValidateResponderAuthMessageContext(
const ValidateResponderAuthMessageContext& other)
: responder_auth_message(other.responder_auth_message),
persistent_responder_public_key(other.persistent_responder_public_key),
persistent_symmetric_key(other.persistent_symmetric_key),
session_private_key(other.session_private_key),
hello_message(other.hello_message),
secure_message_delegate(other.secure_message_delegate),
callback(other.callback),
responder_session_public_key(other.responder_session_public_key),
session_symmetric_key(other.session_symmetric_key) {}
DeviceToDeviceInitiatorHelper::ValidateResponderAuthMessageContext ::
~ValidateResponderAuthMessageContext() {}
void DeviceToDeviceInitiatorHelper::OnInnerMessageCreatedForInitiatorAuth(
const SessionKeys& session_keys,
cryptauth::SecureMessageDelegate* secure_message_delegate,
const DeviceToDeviceInitiatorHelper::MessageCallback& callback,
const std::string& inner_message) {
if (inner_message.empty()) {
PA_LOG(VERBOSE) << "Failed to create inner message for [Initiator Auth].";
callback.Run(std::string());
return;
}
cryptauth::GcmMetadata gcm_metadata;
gcm_metadata.set_type(cryptauth::DEVICE_TO_DEVICE_MESSAGE);
gcm_metadata.set_version(kGcmMetadataVersion);
// Store the inner message inside a DeviceToDeviceMessage proto.
securemessage::DeviceToDeviceMessage device_to_device_message;
device_to_device_message.set_message(inner_message);
device_to_device_message.set_sequence_number(1);
// Create and return the outer message, which wraps the inner message.
cryptauth::SecureMessageDelegate::CreateOptions create_options;
create_options.encryption_scheme = securemessage::AES_256_CBC;
create_options.signature_scheme = securemessage::HMAC_SHA256;
gcm_metadata.SerializeToString(&create_options.public_metadata);
secure_message_delegate->CreateSecureMessage(
device_to_device_message.SerializeAsString(),
session_keys.initiator_encode_key(), create_options, callback);
}
void DeviceToDeviceInitiatorHelper::BeginResponderAuthValidation(
ValidateResponderAuthMessageContext context) {
// Parse the encrypted SecureMessage so we can get plaintext data from the
// header. Note that the payload will be encrypted.
securemessage::SecureMessage encrypted_message;
securemessage::HeaderAndBody header_and_body;
if (!encrypted_message.ParseFromString(context.responder_auth_message) ||
!header_and_body.ParseFromString(encrypted_message.header_and_body())) {
PA_LOG(WARNING) << "Failed to parse [Responder Hello] message";
context.callback.Run(false, SessionKeys());
return;
}
// Check that header public_metadata contains the correct metadata fields.
securemessage::Header header = header_and_body.header();
cryptauth::GcmMetadata gcm_metadata;
if (!gcm_metadata.ParseFromString(header.public_metadata()) ||
gcm_metadata.type() !=
cryptauth::DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD ||
gcm_metadata.version() != kGcmMetadataVersion) {
PA_LOG(WARNING) << "Failed to validate cryptauth::GcmMetadata in "
<< "[Responder Auth] header.";
context.callback.Run(false, SessionKeys());
return;
}
// Extract responder session public key from |decryption_key_id| field.
securemessage::ResponderHello responder_hello;
if (!responder_hello.ParseFromString(header.decryption_key_id()) ||
!responder_hello.public_dh_key().SerializeToString(
&context.responder_session_public_key)) {
PA_LOG(VERBOSE) << "Failed to extract responder session public key in "
<< "[Responder Auth] header.";
context.callback.Run(false, SessionKeys());
return;
}
// Perform a Diffie-Helmann key exchange to get the session symmetric key.
context.secure_message_delegate->DeriveKey(
context.session_private_key, context.responder_session_public_key,
base::Bind(&DeviceToDeviceInitiatorHelper::OnSessionSymmetricKeyDerived,
weak_ptr_factory_.GetWeakPtr(), context));
}
void DeviceToDeviceInitiatorHelper::OnSessionSymmetricKeyDerived(
ValidateResponderAuthMessageContext context,
const std::string& session_symmetric_key) {
context.session_symmetric_key = session_symmetric_key;
// Unwrap the outer message, using symmetric key encryption and signature.
cryptauth::SecureMessageDelegate::UnwrapOptions unwrap_options;
unwrap_options.encryption_scheme = securemessage::AES_256_CBC;
unwrap_options.signature_scheme = securemessage::HMAC_SHA256;
context.secure_message_delegate->UnwrapSecureMessage(
context.responder_auth_message,
SessionKeys(session_symmetric_key).responder_encode_key(), unwrap_options,
base::Bind(&DeviceToDeviceInitiatorHelper::
OnOuterMessageUnwrappedForResponderAuth,
weak_ptr_factory_.GetWeakPtr(), context));
}
void DeviceToDeviceInitiatorHelper::OnOuterMessageUnwrappedForResponderAuth(
const ValidateResponderAuthMessageContext& context,
bool verified,
const std::string& payload,
const securemessage::Header& header) {
if (!verified) {
PA_LOG(VERBOSE) << "Failed to unwrap outer [Responder Auth] message.";
context.callback.Run(false, SessionKeys());
return;
}
// Parse the decrypted payload.
securemessage::DeviceToDeviceMessage device_to_device_message;
if (!device_to_device_message.ParseFromString(payload) ||
device_to_device_message.sequence_number() != 1) {
PA_LOG(VERBOSE) << "Failed to validate DeviceToDeviceMessage payload.";
context.callback.Run(false, SessionKeys());
return;
}
// Unwrap the middle level SecureMessage, using symmetric key encryption and
// signature.
cryptauth::SecureMessageDelegate::UnwrapOptions unwrap_options;
unwrap_options.encryption_scheme = securemessage::AES_256_CBC;
unwrap_options.signature_scheme = securemessage::HMAC_SHA256;
unwrap_options.associated_data = context.hello_message;
context.secure_message_delegate->UnwrapSecureMessage(
device_to_device_message.message(), context.persistent_symmetric_key,
unwrap_options,
base::Bind(&DeviceToDeviceInitiatorHelper::
OnMiddleMessageUnwrappedForResponderAuth,
weak_ptr_factory_.GetWeakPtr(), context));
}
void DeviceToDeviceInitiatorHelper::OnMiddleMessageUnwrappedForResponderAuth(
const ValidateResponderAuthMessageContext& context,
bool verified,
const std::string& payload,
const securemessage::Header& header) {
if (!verified) {
PA_LOG(VERBOSE) << "Failed to unwrap middle [Responder Auth] message.";
context.callback.Run(false, SessionKeys());
return;
}
// Unwrap the inner-most SecureMessage, using no encryption and an asymmetric
// key signature.
cryptauth::SecureMessageDelegate::UnwrapOptions unwrap_options;
unwrap_options.encryption_scheme = securemessage::NONE;
unwrap_options.signature_scheme = securemessage::ECDSA_P256_SHA256;
unwrap_options.associated_data = context.hello_message;
context.secure_message_delegate->UnwrapSecureMessage(
payload, context.persistent_responder_public_key, unwrap_options,
base::Bind(&DeviceToDeviceInitiatorHelper::
OnInnerMessageUnwrappedForResponderAuth,
weak_ptr_factory_.GetWeakPtr(), context));
}
// Called after the inner-most layer of [Responder Auth] is unwrapped.
void DeviceToDeviceInitiatorHelper::OnInnerMessageUnwrappedForResponderAuth(
const ValidateResponderAuthMessageContext& context,
bool verified,
const std::string& payload,
const securemessage::Header& header) {
if (!verified)
PA_LOG(VERBOSE) << "Failed to unwrap inner [Responder Auth] message.";
// Note: The GMS Core implementation does not properly set the metadata
// version, so we only check that the type is UNLOCK_KEY_SIGNED_CHALLENGE.
cryptauth::GcmMetadata gcm_metadata;
if (!gcm_metadata.ParseFromString(header.public_metadata()) ||
gcm_metadata.type() != cryptauth::UNLOCK_KEY_SIGNED_CHALLENGE) {
PA_LOG(WARNING)
<< "Failed to validate cryptauth::GcmMetadata in inner-most "
<< "[Responder Auth] message.";
context.callback.Run(false, SessionKeys());
return;
}
context.callback.Run(verified, SessionKeys(context.session_symmetric_key));
}
} // namespace secure_channel
} // namespace chromeos