blob: 3dd03d3315c1fe307fd1c89bd31455425ef5b5b7 [file] [log] [blame]
Louis Collardf59aa942019-02-25 17:50:14 +08001// Copyright 2019 The Chromium OS 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
5#include "u2fd/user_state.h"
6
Martin Kreichgauer5eae00c2021-05-05 15:07:40 -07007#include <algorithm>
Yicheng Li1090c902020-11-10 11:31:43 -08008#include <utility>
Louis Collardf59aa942019-02-25 17:50:14 +08009
10#include <base/bind.h>
11#include <base/strings/string_number_conversions.h>
12#include <base/strings/stringprintf.h>
13#include <base/sys_byteorder.h>
14#include <brillo/file_utils.h>
15#include <dbus/login_manager/dbus-constants.h>
16#include <openssl/rand.h>
17
Louis Collard2bf43192019-04-03 17:09:47 +080018#include "u2fd/user_state.pb.h"
Louis Collardf59aa942019-02-25 17:50:14 +080019#include "u2fd/util.h"
20
21namespace u2f {
22
23namespace {
24
25constexpr const char kSessionStateStarted[] = "started";
Louis Collard2bf43192019-04-03 17:09:47 +080026constexpr const char kUserSecretPath[] = "/run/daemon-store/u2f/%s/secret_db";
27constexpr const char kCounterPath[] = "/run/daemon-store/u2f/%s/counter_db";
Louis Collardf59aa942019-02-25 17:50:14 +080028constexpr const int kUserSecretSizeBytes = 32;
29
Martin Kreichgauer5eae00c2021-05-05 15:07:40 -070030// Due to b/186431345, U2F credentials were asserted by the WebAuthn platform
31// authenticator with a timestamp-based signature counter. After this was
32// rectified, the UserState signature counter needed to be bumped up to be
33// larger than the most recent timestamp counter that the WebAuthn platform
34// authenticator reported.
35constexpr uint32_t kCounterMinB186431345 =
36 1640995200; // 2022-01-01 00:00:00 UTC
37
Louis Collardf59aa942019-02-25 17:50:14 +080038void OnSignalConnected(const std::string& interface,
39 const std::string& signal,
40 bool success) {
41 if (!success) {
42 LOG(ERROR) << "Could not connect to signal " << signal << " on interface "
43 << interface;
44 }
45}
46
47} // namespace
48
Louis Collardd0c23612019-05-24 10:04:48 +080049UserState::UserState(org::chromium::SessionManagerInterfaceProxy* sm_proxy,
50 uint32_t counter_min)
Martin Kreichgauer5eae00c2021-05-05 15:07:40 -070051 : sm_proxy_(sm_proxy),
52 weak_ptr_factory_(this),
53 counter_min_(std::max(kCounterMinB186431345, counter_min)) {
Louis Collardd0c23612019-05-24 10:04:48 +080054 sm_proxy_->RegisterSessionStateChangedSignalHandler(
Louis Collardf59aa942019-02-25 17:50:14 +080055 base::Bind(&UserState::OnSessionStateChanged,
56 weak_ptr_factory_.GetWeakPtr()),
57 base::Bind(&OnSignalConnected));
58
59 LoadState();
60}
61
Louis Collardaef4b482019-08-30 14:57:20 +080062UserState::UserState() : weak_ptr_factory_(this), counter_min_(0) {}
63
Yicheng Li30b6abc2020-11-13 14:51:15 -080064bool UserState::HasUser() {
65 return user_.has_value() && sanitized_user_.has_value();
66}
67
Yicheng Li67abd182020-11-18 15:31:41 -080068base::Optional<std::string> UserState::GetUser() {
69 if (user_.has_value()) {
70 return *user_;
71 } else {
72 LOG(ERROR) << "User requested but not available.";
73 return base::nullopt;
74 }
75}
76
Yicheng Li1090c902020-11-10 11:31:43 -080077base::Optional<std::string> UserState::GetSanitizedUser() {
78 if (sanitized_user_.has_value()) {
79 return *sanitized_user_;
80 } else {
81 LOG(ERROR) << "Sanitized user requested but not available.";
82 return base::nullopt;
83 }
84}
85
Louis Collardf59aa942019-02-25 17:50:14 +080086base::Optional<brillo::SecureBlob> UserState::GetUserSecret() {
87 if (user_secret_.has_value()) {
88 return *user_secret_;
89 } else {
90 LOG(ERROR) << "User secret requested but not available.";
91 return base::nullopt;
92 }
93}
94
95base::Optional<std::vector<uint8_t>> UserState::GetCounter() {
96 if (!counter_.has_value()) {
97 LOG(ERROR) << "Counter requested but not available.";
98 return base::nullopt;
99 }
100
101 std::vector<uint8_t> counter_bytes;
102 util::AppendToVector(base::HostToNet32(*counter_), &counter_bytes);
103
Louis Collarda98adda2019-08-01 17:08:29 +0800104 return counter_bytes;
105}
106
107bool UserState::IncrementCounter() {
Louis Collardf59aa942019-02-25 17:50:14 +0800108 (*counter_)++;
109
110 if (!PersistCounter()) {
111 LOG(ERROR) << "Failed to persist updated counter. Attempting to re-load.";
112 LoadCounter();
Louis Collarda98adda2019-08-01 17:08:29 +0800113 return false;
Louis Collardf59aa942019-02-25 17:50:14 +0800114 }
115
Louis Collarda98adda2019-08-01 17:08:29 +0800116 return true;
Louis Collardf59aa942019-02-25 17:50:14 +0800117}
118
119void UserState::LoadState() {
120 UpdatePrimarySessionSanitizedUser();
121 if (sanitized_user_.has_value()) {
122 LoadOrCreateUserSecret();
123 LoadCounter();
Yicheng Li9e902962020-11-01 11:07:11 -0800124 if (session_started_callback_ && user_.has_value()) {
125 session_started_callback_.Run(*user_);
126 }
Louis Collardf59aa942019-02-25 17:50:14 +0800127 }
128}
129
130void UserState::OnSessionStateChanged(const std::string& state) {
131 if (state == kSessionStateStarted) {
132 LoadState();
133 } else {
Yicheng Li9e902962020-11-01 11:07:11 -0800134 user_.reset();
Louis Collardf59aa942019-02-25 17:50:14 +0800135 sanitized_user_.reset();
136 user_secret_.reset();
137 counter_.reset();
Yicheng Li9e902962020-11-01 11:07:11 -0800138 if (session_stopped_callback_) {
139 session_stopped_callback_.Run();
140 }
Louis Collardf59aa942019-02-25 17:50:14 +0800141 }
142}
143
144void UserState::UpdatePrimarySessionSanitizedUser() {
145 dbus::MethodCall method_call(
146 login_manager::kSessionManagerInterface,
147 login_manager::kSessionManagerRetrievePrimarySession);
148 std::string user;
149 std::string sanitized_user;
150 brillo::ErrorPtr error;
151
Louis Collardd0c23612019-05-24 10:04:48 +0800152 if (!sm_proxy_->RetrievePrimarySession(&user, &sanitized_user, &error) ||
Louis Collardf59aa942019-02-25 17:50:14 +0800153 sanitized_user.empty()) {
154 LOG(ERROR) << "Failed to retreive current user. This is expected on "
155 "startup if no user is logged in.";
Yicheng Li9e902962020-11-01 11:07:11 -0800156 user_.reset();
Louis Collardf59aa942019-02-25 17:50:14 +0800157 sanitized_user_.reset();
158 } else {
Yicheng Li9e902962020-11-01 11:07:11 -0800159 user_ = user;
Louis Collardf59aa942019-02-25 17:50:14 +0800160 sanitized_user_ = sanitized_user;
161 }
162}
163
164void UserState::LoadOrCreateUserSecret() {
165 base::FilePath path(
166 base::StringPrintf(kUserSecretPath, sanitized_user_->c_str()));
167
168 if (base::PathExists(path)) {
169 LoadUserSecret(path);
170 } else {
171 CreateUserSecret(path);
172 }
173}
174
Louis Collard2bf43192019-04-03 17:09:47 +0800175namespace {
176
177// Wraps the specified proto in a message that includes a hash for integrity
178// checking.
179template <typename Proto, typename Blob>
180bool WrapUserData(const Proto& user_data, Blob* out) {
181 brillo::SecureBlob user_data_bytes(user_data.ByteSizeLong());
182 if (!user_data.SerializeToArray(user_data_bytes.data(),
183 user_data_bytes.size())) {
184 return false;
185 }
186
187 std::vector<uint8_t> hash = util::Sha256(user_data_bytes);
188
189 // TODO(louiscollard): Securely delete proto.
190 UserDataContainer container;
191 container.set_data(user_data_bytes.data(), user_data_bytes.size());
192 container.set_sha256(hash.data(), hash.size());
193
194 out->resize(container.ByteSizeLong());
195 return container.SerializeToArray(out->data(), out->size());
196}
197
198template <typename Proto>
199bool UnwrapUserData(const std::string& container, Proto* out) {
200 UserDataContainer container_pb;
201 if (!container_pb.ParseFromString(container)) {
202 LOG(ERROR) << "Failed to parse user data container";
203 return false;
204 }
205
206 std::vector<uint8_t> hash;
207 util::AppendToVector(container_pb.sha256(), &hash);
208
209 std::vector<uint8_t> hash_verify = util::Sha256(container_pb.data());
210
211 return hash == hash_verify && out->ParseFromString(container_pb.data());
212}
213
214} // namespace
215
Louis Collardf59aa942019-02-25 17:50:14 +0800216void UserState::LoadUserSecret(const base::FilePath& path) {
Louis Collard2bf43192019-04-03 17:09:47 +0800217 // TODO(louiscollard): Securely delete these.
Louis Collardf59aa942019-02-25 17:50:14 +0800218 std::string secret_str;
Louis Collard2bf43192019-04-03 17:09:47 +0800219 UserSecret secret_pb;
220
221 if (base::ReadFileToString(path, &secret_str) &&
222 UnwrapUserData<UserSecret>(secret_str, &secret_pb)) {
223 user_secret_ = brillo::SecureBlob(secret_pb.secret());
Louis Collardf59aa942019-02-25 17:50:14 +0800224 } else {
225 LOG(ERROR) << "Failed to load user secret from: " << path.value();
226 user_secret_.reset();
227 }
228}
229
230void UserState::CreateUserSecret(const base::FilePath& path) {
231 user_secret_ = brillo::SecureBlob(kUserSecretSizeBytes);
232
233 if (RAND_bytes(&user_secret_->front(), user_secret_->size()) != 1) {
234 LOG(ERROR) << "Failed to create new user secret";
235 user_secret_.reset();
236 return;
237 }
238
Louis Collard2bf43192019-04-03 17:09:47 +0800239 // TODO(louiscollard): Securely delete proto.
240 UserSecret secret_proto;
241 secret_proto.set_secret(user_secret_->data(), user_secret_->size());
242
243 brillo::SecureBlob secret_proto_wrapped;
244 if (!WrapUserData(secret_proto, &secret_proto_wrapped) ||
245 !brillo::WriteBlobToFileAtomic(path, secret_proto_wrapped, 0600)) {
Louis Collardf59aa942019-02-25 17:50:14 +0800246 LOG(INFO) << "Failed to persist new user secret to disk.";
247 user_secret_.reset();
Louis Collardf59aa942019-02-25 17:50:14 +0800248 }
249}
250
251void UserState::LoadCounter() {
252 base::FilePath path(
253 base::StringPrintf(kCounterPath, sanitized_user_->c_str()));
254
255 if (!base::PathExists(path)) {
Louis Collard243acd62019-04-25 15:42:57 +0800256 counter_ = counter_min_;
257 LOG(INFO) << "U2F counter missing, initializing counter with value of "
258 << *counter_;
Louis Collardf59aa942019-02-25 17:50:14 +0800259 return;
260 }
261
262 std::string counter;
Louis Collard2bf43192019-04-03 17:09:47 +0800263 U2fCounter counter_pb;
Louis Collardf59aa942019-02-25 17:50:14 +0800264 if (base::ReadFileToString(path, &counter) &&
Louis Collard2bf43192019-04-03 17:09:47 +0800265 UnwrapUserData<U2fCounter>(counter, &counter_pb)) {
Louis Collard243acd62019-04-25 15:42:57 +0800266 uint32_t persistent_counter = counter_pb.counter();
267 if (persistent_counter < counter_min_) {
268 LOG(INFO) << "Overriding persisted counter value of "
269 << counter_pb.counter() << " with minimum value "
270 << counter_min_;
271 counter_ = counter_min_;
272 } else {
273 counter_ = persistent_counter;
274 }
Louis Collardf59aa942019-02-25 17:50:14 +0800275 } else {
276 LOG(ERROR) << "Failed to load counter from: " << path.value();
277 counter_.reset();
278 }
279}
280
281bool UserState::PersistCounter() {
282 base::FilePath path(
283 base::StringPrintf(kCounterPath, sanitized_user_->c_str()));
Louis Collardf59aa942019-02-25 17:50:14 +0800284
Louis Collard2bf43192019-04-03 17:09:47 +0800285 U2fCounter counter_pb;
286 counter_pb.set_counter(*counter_);
287
288 std::vector<uint8_t> counter_wrapped;
289
290 return WrapUserData(counter_pb, &counter_wrapped) &&
291 brillo::WriteBlobToFileAtomic(path, counter_wrapped, 0600);
Louis Collardf59aa942019-02-25 17:50:14 +0800292}
293
Yicheng Li9e902962020-11-01 11:07:11 -0800294void UserState::SetSessionStartedCallback(
295 base::RepeatingCallback<void(const std::string&)> callback) {
296 session_started_callback_ = std::move(callback);
297}
298
299void UserState::SetSessionStoppedCallback(
300 base::RepeatingCallback<void()> callback) {
301 session_stopped_callback_ = std::move(callback);
302}
303
Louis Collardf59aa942019-02-25 17:50:14 +0800304} // namespace u2f