tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 1 | // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
khorimoto | 999e934c | 2016-11-18 20:10:42 | [diff] [blame] | 5 | #include "components/cryptauth/cryptauth_enroller_impl.h" |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 6 | |
dcheng | 51ace48a | 2015-12-26 22:45:17 | [diff] [blame] | 7 | #include <utility> |
| 8 | |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 9 | #include "base/bind.h" |
James Hawkins | 31399f9 | 2018-03-28 17:16:35 | [diff] [blame] | 10 | #include "base/metrics/histogram_macros.h" |
James Hawkins | 813085e | 2018-03-30 18:56:41 | [diff] [blame] | 11 | #include "chromeos/components/proximity_auth/logging/logging.h" |
khorimoto | 999e934c | 2016-11-18 20:10:42 | [diff] [blame] | 12 | #include "components/cryptauth/cryptauth_client_impl.h" |
| 13 | #include "components/cryptauth/cryptauth_enrollment_utils.h" |
| 14 | #include "components/cryptauth/secure_message_delegate.h" |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 15 | #include "crypto/sha2.h" |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 16 | |
khorimoto | 999e934c | 2016-11-18 20:10:42 | [diff] [blame] | 17 | namespace cryptauth { |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 18 | |
| 19 | namespace { |
| 20 | |
| 21 | // A successful SetupEnrollment or FinishEnrollment response should contain this |
| 22 | // status string. |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 23 | const char kResponseStatusOk[] = "ok"; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 24 | |
| 25 | // The name of the "gcmV1" protocol that the enrolling device supports. |
| 26 | const char kSupportedEnrollmentTypeGcmV1[] = "gcmV1"; |
| 27 | |
| 28 | // The version field of the GcmMetadata message. |
| 29 | const int kGCMMetadataVersion = 1; |
| 30 | |
| 31 | // Returns true if |device_info| contains the required fields for enrollment. |
khorimoto | f7a6aff | 2017-01-19 22:50:47 | [diff] [blame] | 32 | bool ValidateDeviceInfo(const GcmDeviceInfo& device_info) { |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 33 | if (!device_info.has_long_device_id()) { |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 34 | PA_LOG(ERROR) << "Expected long_device_id field in GcmDeviceInfo."; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 35 | return false; |
| 36 | } |
| 37 | |
| 38 | if (!device_info.has_device_type()) { |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 39 | PA_LOG(ERROR) << "Expected device_type field in GcmDeviceInfo."; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 40 | return false; |
| 41 | } |
| 42 | |
| 43 | return true; |
| 44 | } |
| 45 | |
| 46 | // Creates the public metadata to put in the SecureMessage that is sent to the |
| 47 | // server with the FinishEnrollment request. |
| 48 | std::string CreateEnrollmentPublicMetadata() { |
khorimoto | f7a6aff | 2017-01-19 22:50:47 | [diff] [blame] | 49 | GcmMetadata metadata; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 50 | metadata.set_version(kGCMMetadataVersion); |
khorimoto | f7a6aff | 2017-01-19 22:50:47 | [diff] [blame] | 51 | metadata.set_type(MessageType::ENROLLMENT); |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 52 | return metadata.SerializeAsString(); |
| 53 | } |
| 54 | |
| 55 | } // namespace |
| 56 | |
| 57 | CryptAuthEnrollerImpl::CryptAuthEnrollerImpl( |
dcheng | 2f01269 | 2016-04-21 00:19:34 | [diff] [blame] | 58 | std::unique_ptr<CryptAuthClientFactory> client_factory, |
| 59 | std::unique_ptr<SecureMessageDelegate> secure_message_delegate) |
dcheng | 51ace48a | 2015-12-26 22:45:17 | [diff] [blame] | 60 | : client_factory_(std::move(client_factory)), |
| 61 | secure_message_delegate_(std::move(secure_message_delegate)), |
| 62 | weak_ptr_factory_(this) {} |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 63 | |
| 64 | CryptAuthEnrollerImpl::~CryptAuthEnrollerImpl() { |
| 65 | } |
| 66 | |
| 67 | void CryptAuthEnrollerImpl::Enroll( |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 68 | const std::string& user_public_key, |
| 69 | const std::string& user_private_key, |
khorimoto | f7a6aff | 2017-01-19 22:50:47 | [diff] [blame] | 70 | const GcmDeviceInfo& device_info, |
| 71 | InvocationReason invocation_reason, |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 72 | const EnrollmentFinishedCallback& callback) { |
| 73 | if (!callback_.is_null()) { |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 74 | PA_LOG(ERROR) << "Enroll() already called. Do not reuse."; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 75 | callback.Run(false); |
| 76 | return; |
| 77 | } |
| 78 | |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 79 | user_public_key_ = user_public_key; |
| 80 | user_private_key_ = user_private_key; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 81 | device_info_ = device_info; |
| 82 | invocation_reason_ = invocation_reason; |
| 83 | callback_ = callback; |
| 84 | |
| 85 | if (!ValidateDeviceInfo(device_info)) { |
| 86 | callback.Run(false); |
| 87 | return; |
| 88 | } |
| 89 | |
| 90 | secure_message_delegate_->GenerateKeyPair( |
| 91 | base::Bind(&CryptAuthEnrollerImpl::OnKeyPairGenerated, |
| 92 | weak_ptr_factory_.GetWeakPtr())); |
| 93 | } |
| 94 | |
| 95 | void CryptAuthEnrollerImpl::OnKeyPairGenerated(const std::string& public_key, |
| 96 | const std::string& private_key) { |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 97 | PA_LOG(INFO) << "Ephemeral key pair generated, calling SetupEnrollment API."; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 98 | session_public_key_ = public_key; |
| 99 | session_private_key_ = private_key; |
| 100 | |
| 101 | cryptauth_client_ = client_factory_->CreateInstance(); |
khorimoto | f7a6aff | 2017-01-19 22:50:47 | [diff] [blame] | 102 | SetupEnrollmentRequest request; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 103 | request.add_types(kSupportedEnrollmentTypeGcmV1); |
| 104 | request.set_invocation_reason(invocation_reason_); |
| 105 | cryptauth_client_->SetupEnrollment( |
| 106 | request, base::Bind(&CryptAuthEnrollerImpl::OnSetupEnrollmentSuccess, |
| 107 | weak_ptr_factory_.GetWeakPtr()), |
| 108 | base::Bind(&CryptAuthEnrollerImpl::OnSetupEnrollmentFailure, |
| 109 | weak_ptr_factory_.GetWeakPtr())); |
| 110 | } |
| 111 | |
| 112 | void CryptAuthEnrollerImpl::OnSetupEnrollmentSuccess( |
khorimoto | f7a6aff | 2017-01-19 22:50:47 | [diff] [blame] | 113 | const SetupEnrollmentResponse& response) { |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 114 | if (response.status() != kResponseStatusOk) { |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 115 | PA_LOG(WARNING) << "Unexpected status for SetupEnrollment: " |
| 116 | << response.status(); |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 117 | callback_.Run(false); |
| 118 | return; |
| 119 | } |
| 120 | |
| 121 | if (response.infos_size() == 0) { |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 122 | PA_LOG(ERROR) << "No response info returned by server for SetupEnrollment"; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 123 | callback_.Run(false); |
| 124 | return; |
| 125 | } |
| 126 | |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 127 | PA_LOG(INFO) << "SetupEnrollment request succeeded: deriving symmetric key."; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 128 | setup_info_ = response.infos(0); |
| 129 | device_info_.set_enrollment_session_id(setup_info_.enrollment_session_id()); |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 130 | |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 131 | secure_message_delegate_->DeriveKey( |
| 132 | session_private_key_, setup_info_.server_ephemeral_key(), |
| 133 | base::Bind(&CryptAuthEnrollerImpl::OnKeyDerived, |
| 134 | weak_ptr_factory_.GetWeakPtr())); |
| 135 | } |
| 136 | |
| 137 | void CryptAuthEnrollerImpl::OnSetupEnrollmentFailure(const std::string& error) { |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 138 | PA_LOG(WARNING) << "SetupEnrollment API failed with error: " << error; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 139 | callback_.Run(false); |
| 140 | } |
| 141 | |
| 142 | void CryptAuthEnrollerImpl::OnKeyDerived(const std::string& symmetric_key) { |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 143 | PA_LOG(INFO) << "Derived symmetric key, " |
| 144 | << "encrypting enrollment data for upload."; |
| 145 | |
| 146 | // Make sure we're enrolling the same public key used below to sign the |
| 147 | // secure message. |
| 148 | device_info_.set_user_public_key(user_public_key_); |
| 149 | device_info_.set_key_handle(user_public_key_); |
| 150 | |
| 151 | // Hash the symmetric key and add it to the |device_info_| to be uploaded. |
| 152 | device_info_.set_device_master_key_hash( |
| 153 | crypto::SHA256HashString(symmetric_key)); |
| 154 | |
| 155 | // The server verifies that the access token set here and in the header |
| 156 | // of the FinishEnrollment() request are the same. |
| 157 | device_info_.set_oauth_token(cryptauth_client_->GetAccessTokenUsed()); |
| 158 | PA_LOG(INFO) << "Using access token: " << device_info_.oauth_token(); |
| 159 | |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 160 | symmetric_key_ = symmetric_key; |
| 161 | SecureMessageDelegate::CreateOptions options; |
| 162 | options.encryption_scheme = securemessage::NONE; |
| 163 | options.signature_scheme = securemessage::ECDSA_P256_SHA256; |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 164 | options.verification_key_id = user_public_key_; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 165 | |
| 166 | // The inner message contains the signed device information that will be |
| 167 | // sent to CryptAuth. |
| 168 | secure_message_delegate_->CreateSecureMessage( |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 169 | device_info_.SerializeAsString(), user_private_key_, options, |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 170 | base::Bind(&CryptAuthEnrollerImpl::OnInnerSecureMessageCreated, |
| 171 | weak_ptr_factory_.GetWeakPtr())); |
| 172 | } |
| 173 | |
| 174 | void CryptAuthEnrollerImpl::OnInnerSecureMessageCreated( |
| 175 | const std::string& inner_message) { |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 176 | if (inner_message.empty()) { |
| 177 | PA_LOG(ERROR) << "Error creating inner message"; |
| 178 | callback_.Run(false); |
| 179 | return; |
| 180 | } |
| 181 | |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 182 | SecureMessageDelegate::CreateOptions options; |
| 183 | options.encryption_scheme = securemessage::AES_256_CBC; |
| 184 | options.signature_scheme = securemessage::HMAC_SHA256; |
| 185 | options.public_metadata = CreateEnrollmentPublicMetadata(); |
| 186 | |
| 187 | // The outer message encrypts and signs the inner message with the derived |
| 188 | // symmetric session key. |
| 189 | secure_message_delegate_->CreateSecureMessage( |
| 190 | inner_message, symmetric_key_, options, |
| 191 | base::Bind(&CryptAuthEnrollerImpl::OnOuterSecureMessageCreated, |
| 192 | weak_ptr_factory_.GetWeakPtr())); |
| 193 | } |
| 194 | |
| 195 | void CryptAuthEnrollerImpl::OnOuterSecureMessageCreated( |
| 196 | const std::string& outer_message) { |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 197 | PA_LOG(INFO) << "SecureMessage created, calling FinishEnrollment API."; |
| 198 | |
khorimoto | f7a6aff | 2017-01-19 22:50:47 | [diff] [blame] | 199 | FinishEnrollmentRequest request; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 200 | request.set_enrollment_session_id(setup_info_.enrollment_session_id()); |
| 201 | request.set_enrollment_message(outer_message); |
| 202 | request.set_device_ephemeral_key(session_public_key_); |
| 203 | request.set_invocation_reason(invocation_reason_); |
| 204 | |
| 205 | cryptauth_client_ = client_factory_->CreateInstance(); |
| 206 | cryptauth_client_->FinishEnrollment( |
| 207 | request, base::Bind(&CryptAuthEnrollerImpl::OnFinishEnrollmentSuccess, |
| 208 | weak_ptr_factory_.GetWeakPtr()), |
| 209 | base::Bind(&CryptAuthEnrollerImpl::OnFinishEnrollmentFailure, |
| 210 | weak_ptr_factory_.GetWeakPtr())); |
| 211 | } |
| 212 | |
| 213 | void CryptAuthEnrollerImpl::OnFinishEnrollmentSuccess( |
khorimoto | f7a6aff | 2017-01-19 22:50:47 | [diff] [blame] | 214 | const FinishEnrollmentResponse& response) { |
James Hawkins | 31399f9 | 2018-03-28 17:16:35 | [diff] [blame] | 215 | const bool success = response.status() == kResponseStatusOk; |
| 216 | |
| 217 | if (!success) { |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 218 | PA_LOG(WARNING) << "Unexpected status for FinishEnrollment: " |
| 219 | << response.status(); |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 220 | } |
James Hawkins | 31399f9 | 2018-03-28 17:16:35 | [diff] [blame] | 221 | |
| 222 | UMA_HISTOGRAM_BOOLEAN("CryptAuth.Enrollment.Result", success); |
| 223 | callback_.Run(success); |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 224 | } |
| 225 | |
| 226 | void CryptAuthEnrollerImpl::OnFinishEnrollmentFailure( |
| 227 | const std::string& error) { |
tengs | 7555b0b | 2015-06-29 18:47:15 | [diff] [blame] | 228 | PA_LOG(WARNING) << "FinishEnrollment API failed with error: " << error; |
tengs | 28cb8d6 | 2015-04-13 19:46:41 | [diff] [blame] | 229 | callback_.Run(false); |
| 230 | } |
| 231 | |
khorimoto | 999e934c | 2016-11-18 20:10:42 | [diff] [blame] | 232 | } // namespace cryptauth |