// 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 "components/proximity_auth/cryptauth/cryptauth_enroller_impl.h" #include "base/bind.h" #include "components/proximity_auth/cryptauth/base64url.h" #include "components/proximity_auth/cryptauth/cryptauth_client_impl.h" #include "components/proximity_auth/cryptauth/cryptauth_enrollment_utils.h" #include "components/proximity_auth/cryptauth/fake_secure_message_delegate.h" #include "components/proximity_auth/cryptauth/secure_message_delegate.h" #include "components/proximity_auth/logging/logging.h" #include "crypto/sha2.h" namespace proximity_auth { namespace { // A successful SetupEnrollment or FinishEnrollment response should contain this // status string. const char kResponseStatusOk[] = "ok"; // The name of the "gcmV1" protocol that the enrolling device supports. const char kSupportedEnrollmentTypeGcmV1[] = "gcmV1"; // The version field of the GcmMetadata message. const int kGCMMetadataVersion = 1; // Returns true if |device_info| contains the required fields for enrollment. bool ValidateDeviceInfo(const cryptauth::GcmDeviceInfo& device_info) { if (!device_info.has_long_device_id()) { PA_LOG(ERROR) << "Expected long_device_id field in GcmDeviceInfo."; return false; } if (!device_info.has_device_type()) { PA_LOG(ERROR) << "Expected device_type field in GcmDeviceInfo."; return false; } return true; } // Creates the public metadata to put in the SecureMessage that is sent to the // server with the FinishEnrollment request. std::string CreateEnrollmentPublicMetadata() { cryptauth::GcmMetadata metadata; metadata.set_version(kGCMMetadataVersion); metadata.set_type(cryptauth::MessageType::ENROLLMENT); return metadata.SerializeAsString(); } } // namespace CryptAuthEnrollerImpl::CryptAuthEnrollerImpl( scoped_ptr client_factory, scoped_ptr secure_message_delegate) : client_factory_(client_factory.Pass()), secure_message_delegate_(secure_message_delegate.Pass()), weak_ptr_factory_(this) { } CryptAuthEnrollerImpl::~CryptAuthEnrollerImpl() { } void CryptAuthEnrollerImpl::Enroll( const std::string& user_public_key, const std::string& user_private_key, const cryptauth::GcmDeviceInfo& device_info, cryptauth::InvocationReason invocation_reason, const EnrollmentFinishedCallback& callback) { if (!callback_.is_null()) { PA_LOG(ERROR) << "Enroll() already called. Do not reuse."; callback.Run(false); return; } user_public_key_ = user_public_key; user_private_key_ = user_private_key; device_info_ = device_info; invocation_reason_ = invocation_reason; callback_ = callback; if (!ValidateDeviceInfo(device_info)) { callback.Run(false); return; } secure_message_delegate_->GenerateKeyPair( base::Bind(&CryptAuthEnrollerImpl::OnKeyPairGenerated, weak_ptr_factory_.GetWeakPtr())); } void CryptAuthEnrollerImpl::OnKeyPairGenerated(const std::string& public_key, const std::string& private_key) { PA_LOG(INFO) << "Ephemeral key pair generated, calling SetupEnrollment API."; session_public_key_ = public_key; session_private_key_ = private_key; cryptauth_client_ = client_factory_->CreateInstance(); cryptauth::SetupEnrollmentRequest request; request.add_types(kSupportedEnrollmentTypeGcmV1); request.set_invocation_reason(invocation_reason_); cryptauth_client_->SetupEnrollment( request, base::Bind(&CryptAuthEnrollerImpl::OnSetupEnrollmentSuccess, weak_ptr_factory_.GetWeakPtr()), base::Bind(&CryptAuthEnrollerImpl::OnSetupEnrollmentFailure, weak_ptr_factory_.GetWeakPtr())); } void CryptAuthEnrollerImpl::OnSetupEnrollmentSuccess( const cryptauth::SetupEnrollmentResponse& response) { if (response.status() != kResponseStatusOk) { PA_LOG(WARNING) << "Unexpected status for SetupEnrollment: " << response.status(); callback_.Run(false); return; } if (response.infos_size() == 0) { PA_LOG(ERROR) << "No response info returned by server for SetupEnrollment"; callback_.Run(false); return; } PA_LOG(INFO) << "SetupEnrollment request succeeded: deriving symmetric key."; setup_info_ = response.infos(0); device_info_.set_enrollment_session_id(setup_info_.enrollment_session_id()); secure_message_delegate_->DeriveKey( session_private_key_, setup_info_.server_ephemeral_key(), base::Bind(&CryptAuthEnrollerImpl::OnKeyDerived, weak_ptr_factory_.GetWeakPtr())); } void CryptAuthEnrollerImpl::OnSetupEnrollmentFailure(const std::string& error) { PA_LOG(WARNING) << "SetupEnrollment API failed with error: " << error; callback_.Run(false); } void CryptAuthEnrollerImpl::OnKeyDerived(const std::string& symmetric_key) { PA_LOG(INFO) << "Derived symmetric key, " << "encrypting enrollment data for upload."; // Make sure we're enrolling the same public key used below to sign the // secure message. device_info_.set_user_public_key(user_public_key_); device_info_.set_key_handle(user_public_key_); // Hash the symmetric key and add it to the |device_info_| to be uploaded. device_info_.set_device_master_key_hash( crypto::SHA256HashString(symmetric_key)); // The server verifies that the access token set here and in the header // of the FinishEnrollment() request are the same. device_info_.set_oauth_token(cryptauth_client_->GetAccessTokenUsed()); PA_LOG(INFO) << "Using access token: " << device_info_.oauth_token(); symmetric_key_ = symmetric_key; SecureMessageDelegate::CreateOptions options; options.encryption_scheme = securemessage::NONE; options.signature_scheme = securemessage::ECDSA_P256_SHA256; options.verification_key_id = user_public_key_; // The inner message contains the signed device information that will be // sent to CryptAuth. secure_message_delegate_->CreateSecureMessage( device_info_.SerializeAsString(), user_private_key_, options, base::Bind(&CryptAuthEnrollerImpl::OnInnerSecureMessageCreated, weak_ptr_factory_.GetWeakPtr())); } void CryptAuthEnrollerImpl::OnInnerSecureMessageCreated( const std::string& inner_message) { if (inner_message.empty()) { PA_LOG(ERROR) << "Error creating inner message"; callback_.Run(false); return; } SecureMessageDelegate::CreateOptions options; options.encryption_scheme = securemessage::AES_256_CBC; options.signature_scheme = securemessage::HMAC_SHA256; options.public_metadata = CreateEnrollmentPublicMetadata(); // The outer message encrypts and signs the inner message with the derived // symmetric session key. secure_message_delegate_->CreateSecureMessage( inner_message, symmetric_key_, options, base::Bind(&CryptAuthEnrollerImpl::OnOuterSecureMessageCreated, weak_ptr_factory_.GetWeakPtr())); } void CryptAuthEnrollerImpl::OnOuterSecureMessageCreated( const std::string& outer_message) { PA_LOG(INFO) << "SecureMessage created, calling FinishEnrollment API."; cryptauth::FinishEnrollmentRequest request; request.set_enrollment_session_id(setup_info_.enrollment_session_id()); request.set_enrollment_message(outer_message); request.set_device_ephemeral_key(session_public_key_); request.set_invocation_reason(invocation_reason_); cryptauth_client_ = client_factory_->CreateInstance(); cryptauth_client_->FinishEnrollment( request, base::Bind(&CryptAuthEnrollerImpl::OnFinishEnrollmentSuccess, weak_ptr_factory_.GetWeakPtr()), base::Bind(&CryptAuthEnrollerImpl::OnFinishEnrollmentFailure, weak_ptr_factory_.GetWeakPtr())); } void CryptAuthEnrollerImpl::OnFinishEnrollmentSuccess( const cryptauth::FinishEnrollmentResponse& response) { if (response.status() != kResponseStatusOk) { PA_LOG(WARNING) << "Unexpected status for FinishEnrollment: " << response.status(); callback_.Run(false); } else { callback_.Run(true); } } void CryptAuthEnrollerImpl::OnFinishEnrollmentFailure( const std::string& error) { PA_LOG(WARNING) << "FinishEnrollment API failed with error: " << error; callback_.Run(false); } } // namespace proximity_auth