| // 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/cryptauth/cryptauth_enroller_impl.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "components/cryptauth/cryptauth_client_impl.h" |
| #include "components/cryptauth/cryptauth_enrollment_utils.h" |
| #include "components/cryptauth/secure_message_delegate.h" |
| #include "components/proximity_auth/logging/logging.h" |
| #include "crypto/sha2.h" |
| |
| namespace cryptauth { |
| |
| 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( |
| std::unique_ptr<CryptAuthClientFactory> client_factory, |
| std::unique_ptr<SecureMessageDelegate> secure_message_delegate) |
| : client_factory_(std::move(client_factory)), |
| secure_message_delegate_(std::move(secure_message_delegate)), |
| 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 cryptauth |