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.
5 #include "components/proximity_auth/cryptauth/cryptauth_enroller_impl.h"
8 #include "components/proximity_auth/cryptauth/base64url.h"
9 #include "components/proximity_auth/cryptauth/cryptauth_client_impl.h"
10 #include "components/proximity_auth/cryptauth/cryptauth_enrollment_utils.h"
11 #include "components/proximity_auth/cryptauth/fake_secure_message_delegate.h"
12 #include "components/proximity_auth/cryptauth/secure_message_delegate.h"
13 #include "components/proximity_auth/logging/logging.h"
14 #include "crypto/sha2.h"
16 namespace proximity_auth
{
20 // A successful SetupEnrollment or FinishEnrollment response should contain this
22 const char kResponseStatusOk
[] = "ok";
24 // The name of the "gcmV1" protocol that the enrolling device supports.
25 const char kSupportedEnrollmentTypeGcmV1
[] = "gcmV1";
27 // The version field of the GcmMetadata message.
28 const int kGCMMetadataVersion
= 1;
30 // Returns true if |device_info| contains the required fields for enrollment.
31 bool ValidateDeviceInfo(const cryptauth::GcmDeviceInfo
& device_info
) {
32 if (!device_info
.has_long_device_id()) {
33 PA_LOG(ERROR
) << "Expected long_device_id field in GcmDeviceInfo.";
37 if (!device_info
.has_device_type()) {
38 PA_LOG(ERROR
) << "Expected device_type field in GcmDeviceInfo.";
45 // Creates the public metadata to put in the SecureMessage that is sent to the
46 // server with the FinishEnrollment request.
47 std::string
CreateEnrollmentPublicMetadata() {
48 cryptauth::GcmMetadata metadata
;
49 metadata
.set_version(kGCMMetadataVersion
);
50 metadata
.set_type(cryptauth::MessageType::ENROLLMENT
);
51 return metadata
.SerializeAsString();
56 CryptAuthEnrollerImpl::CryptAuthEnrollerImpl(
57 scoped_ptr
<CryptAuthClientFactory
> client_factory
,
58 scoped_ptr
<SecureMessageDelegate
> secure_message_delegate
)
59 : client_factory_(client_factory
.Pass()),
60 secure_message_delegate_(secure_message_delegate
.Pass()),
61 weak_ptr_factory_(this) {
64 CryptAuthEnrollerImpl::~CryptAuthEnrollerImpl() {
67 void CryptAuthEnrollerImpl::Enroll(
68 const std::string
& user_public_key
,
69 const std::string
& user_private_key
,
70 const cryptauth::GcmDeviceInfo
& device_info
,
71 cryptauth::InvocationReason invocation_reason
,
72 const EnrollmentFinishedCallback
& callback
) {
73 if (!callback_
.is_null()) {
74 PA_LOG(ERROR
) << "Enroll() already called. Do not reuse.";
79 user_public_key_
= user_public_key
;
80 user_private_key_
= user_private_key
;
81 device_info_
= device_info
;
82 invocation_reason_
= invocation_reason
;
85 if (!ValidateDeviceInfo(device_info
)) {
90 secure_message_delegate_
->GenerateKeyPair(
91 base::Bind(&CryptAuthEnrollerImpl::OnKeyPairGenerated
,
92 weak_ptr_factory_
.GetWeakPtr()));
95 void CryptAuthEnrollerImpl::OnKeyPairGenerated(const std::string
& public_key
,
96 const std::string
& private_key
) {
97 PA_LOG(INFO
) << "Ephemeral key pair generated, calling SetupEnrollment API.";
98 session_public_key_
= public_key
;
99 session_private_key_
= private_key
;
101 cryptauth_client_
= client_factory_
->CreateInstance();
102 cryptauth::SetupEnrollmentRequest request
;
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()));
112 void CryptAuthEnrollerImpl::OnSetupEnrollmentSuccess(
113 const cryptauth::SetupEnrollmentResponse
& response
) {
114 if (response
.status() != kResponseStatusOk
) {
115 PA_LOG(WARNING
) << "Unexpected status for SetupEnrollment: "
116 << response
.status();
117 callback_
.Run(false);
121 if (response
.infos_size() == 0) {
122 PA_LOG(ERROR
) << "No response info returned by server for SetupEnrollment";
123 callback_
.Run(false);
127 PA_LOG(INFO
) << "SetupEnrollment request succeeded: deriving symmetric key.";
128 setup_info_
= response
.infos(0);
129 device_info_
.set_enrollment_session_id(setup_info_
.enrollment_session_id());
131 secure_message_delegate_
->DeriveKey(
132 session_private_key_
, setup_info_
.server_ephemeral_key(),
133 base::Bind(&CryptAuthEnrollerImpl::OnKeyDerived
,
134 weak_ptr_factory_
.GetWeakPtr()));
137 void CryptAuthEnrollerImpl::OnSetupEnrollmentFailure(const std::string
& error
) {
138 PA_LOG(WARNING
) << "SetupEnrollment API failed with error: " << error
;
139 callback_
.Run(false);
142 void CryptAuthEnrollerImpl::OnKeyDerived(const std::string
& symmetric_key
) {
143 PA_LOG(INFO
) << "Derived symmetric key, "
144 << "encrypting enrollment data for upload.";
146 // Make sure we're enrolling the same public key used below to sign the
148 device_info_
.set_user_public_key(user_public_key_
);
149 device_info_
.set_key_handle(user_public_key_
);
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
));
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();
160 symmetric_key_
= symmetric_key
;
161 SecureMessageDelegate::CreateOptions options
;
162 options
.encryption_scheme
= securemessage::NONE
;
163 options
.signature_scheme
= securemessage::ECDSA_P256_SHA256
;
164 options
.verification_key_id
= user_public_key_
;
166 // The inner message contains the signed device information that will be
167 // sent to CryptAuth.
168 secure_message_delegate_
->CreateSecureMessage(
169 device_info_
.SerializeAsString(), user_private_key_
, options
,
170 base::Bind(&CryptAuthEnrollerImpl::OnInnerSecureMessageCreated
,
171 weak_ptr_factory_
.GetWeakPtr()));
174 void CryptAuthEnrollerImpl::OnInnerSecureMessageCreated(
175 const std::string
& inner_message
) {
176 if (inner_message
.empty()) {
177 PA_LOG(ERROR
) << "Error creating inner message";
178 callback_
.Run(false);
182 SecureMessageDelegate::CreateOptions options
;
183 options
.encryption_scheme
= securemessage::AES_256_CBC
;
184 options
.signature_scheme
= securemessage::HMAC_SHA256
;
185 options
.public_metadata
= CreateEnrollmentPublicMetadata();
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()));
195 void CryptAuthEnrollerImpl::OnOuterSecureMessageCreated(
196 const std::string
& outer_message
) {
197 PA_LOG(INFO
) << "SecureMessage created, calling FinishEnrollment API.";
199 cryptauth::FinishEnrollmentRequest request
;
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_
);
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()));
213 void CryptAuthEnrollerImpl::OnFinishEnrollmentSuccess(
214 const cryptauth::FinishEnrollmentResponse
& response
) {
215 if (response
.status() != kResponseStatusOk
) {
216 PA_LOG(WARNING
) << "Unexpected status for FinishEnrollment: "
217 << response
.status();
218 callback_
.Run(false);
224 void CryptAuthEnrollerImpl::OnFinishEnrollmentFailure(
225 const std::string
& error
) {
226 PA_LOG(WARNING
) << "FinishEnrollment API failed with error: " << error
;
227 callback_
.Run(false);
230 } // namespace proximity_auth