Integrate new macaroon library

Implement validation of auth tokens (no session id check yet).

BUG: 26292014

Change-Id: I55c9c8249f6355132486b2be8628c3538d504c5d
Reviewed-on: https://weave-review.googlesource.com/2375
Reviewed-by: Alex Vakulenko <avakulenko@google.com>
diff --git a/src/privet/auth_manager.cc b/src/privet/auth_manager.cc
index 66d04c4..fd15c21 100644
--- a/src/privet/auth_manager.cc
+++ b/src/privet/auth_manager.cc
@@ -18,6 +18,7 @@
 
 extern "C" {
 #include "third_party/libuweave/src/macaroon.h"
+#include "third_party/libuweave/src/macaroon_caveat_internal.h"
 }
 
 namespace weave {
@@ -25,10 +26,19 @@
 
 namespace {
 
+const time_t kJ2000ToTimeT = 946684800;
 const size_t kMaxMacaroonSize = 1024;
 const size_t kMaxPendingClaims = 10;
 const char kInvalidTokenError[] = "invalid_token";
 
+uint32_t ToJ2000Time(const base::Time& time) {
+  return std::max(time.ToTimeT(), kJ2000ToTimeT) - kJ2000ToTimeT;
+}
+
+base::Time FromJ2000Time(uint32_t time) {
+  return base::Time::FromTimeT(time + kJ2000ToTimeT);
+}
+
 template <class T>
 void AppendToArray(T value, std::vector<uint8_t>* array) {
   auto begin = reinterpret_cast<const uint8_t*>(&value);
@@ -37,78 +47,86 @@
 
 class Caveat {
  public:
-  // TODO(vitalybuka): Use _get_buffer_size_ when available.
-  Caveat(UwMacaroonCaveatType type, uint32_t value) : buffer(8) {
-    CHECK(uw_macaroon_caveat_create_with_uint_(type, value, buffer.data(),
-                                               buffer.size(), &caveat));
+  Caveat(UwMacaroonCaveatType type, size_t str_len)
+      : buffer_(uw_macaroon_caveat_creation_get_buffsize_(type, str_len)) {
+    CHECK(!buffer_.empty());
   }
+  const UwMacaroonCaveat& GetCaveat() const { return caveat_; }
 
-  // TODO(vitalybuka): Use _get_buffer_size_ when available.
-  Caveat(UwMacaroonCaveatType type, const std::string& value)
-      : buffer(std::max<size_t>(value.size(), 32u) * 2) {
-    CHECK(uw_macaroon_caveat_create_with_str_(
-        type, reinterpret_cast<const uint8_t*>(value.data()), value.size(),
-        buffer.data(), buffer.size(), &caveat));
-  }
-
-  const UwMacaroonCaveat& GetCaveat() const { return caveat; }
-
- private:
-  UwMacaroonCaveat caveat;
-  std::vector<uint8_t> buffer;
+ protected:
+  UwMacaroonCaveat caveat_{};
+  std::vector<uint8_t> buffer_;
 
   DISALLOW_COPY_AND_ASSIGN(Caveat);
 };
 
-bool CheckCaveatType(const UwMacaroonCaveat& caveat,
-                     UwMacaroonCaveatType type,
-                     ErrorPtr* error) {
-  UwMacaroonCaveatType caveat_type{};
-  if (!uw_macaroon_caveat_get_type_(&caveat, &caveat_type)) {
-    return Error::AddTo(error, FROM_HERE, kInvalidTokenError,
-                        "Unable to get type");
+class ScopeCaveat : public Caveat {
+ public:
+  explicit ScopeCaveat(UwMacaroonCaveatScopeType scope)
+      : Caveat(kUwMacaroonCaveatTypeScope, 0) {
+    CHECK(uw_macaroon_caveat_create_scope_(scope, buffer_.data(),
+                                           buffer_.size(), &caveat_));
   }
 
-  if (caveat_type != type) {
-    return Error::AddTo(error, FROM_HERE, kInvalidTokenError,
-                        "Unexpected caveat type");
+  DISALLOW_COPY_AND_ASSIGN(ScopeCaveat);
+};
+
+class TimestampCaveat : public Caveat {
+ public:
+  explicit TimestampCaveat(const base::Time& timestamp)
+      : Caveat(kUwMacaroonCaveatTypeDelegationTimestamp, 0) {
+    CHECK(uw_macaroon_caveat_create_delegation_timestamp_(
+        ToJ2000Time(timestamp), buffer_.data(), buffer_.size(), &caveat_));
   }
 
-  return true;
-}
+  DISALLOW_COPY_AND_ASSIGN(TimestampCaveat);
+};
 
-bool ReadCaveat(const UwMacaroonCaveat& caveat,
-                UwMacaroonCaveatType type,
-                uint32_t* value,
-                ErrorPtr* error) {
-  if (!CheckCaveatType(caveat, type, error))
-    return false;
-
-  if (!uw_macaroon_caveat_get_value_uint_(&caveat, value)) {
-    return Error::AddTo(error, FROM_HERE, kInvalidTokenError,
-                        "Unable to read caveat");
+class ExpirationCaveat : public Caveat {
+ public:
+  explicit ExpirationCaveat(const base::Time& timestamp)
+      : Caveat(kUwMacaroonCaveatTypeExpirationAbsolute, 0) {
+    CHECK(uw_macaroon_caveat_create_expiration_absolute_(
+        ToJ2000Time(timestamp), buffer_.data(), buffer_.size(), &caveat_));
   }
 
-  return true;
-}
+  DISALLOW_COPY_AND_ASSIGN(ExpirationCaveat);
+};
 
-bool ReadCaveat(const UwMacaroonCaveat& caveat,
-                UwMacaroonCaveatType type,
-                std::string* value,
-                ErrorPtr* error) {
-  if (!CheckCaveatType(caveat, type, error))
-    return false;
-
-  const uint8_t* start{nullptr};
-  size_t size{0};
-  if (!uw_macaroon_caveat_get_value_str_(&caveat, &start, &size)) {
-    return Error::AddTo(error, FROM_HERE, kInvalidTokenError,
-                        "Unable to read caveat");
+class UserIdCaveat : public Caveat {
+ public:
+  explicit UserIdCaveat(const std::string& user_id)
+      : Caveat(kUwMacaroonCaveatTypeDelegateeUser, user_id.size()) {
+    CHECK(uw_macaroon_caveat_create_delegatee_user_(
+        reinterpret_cast<const uint8_t*>(user_id.data()), user_id.size(),
+        buffer_.data(), buffer_.size(), &caveat_));
   }
 
-  value->assign(reinterpret_cast<const char*>(start), size);
-  return true;
-}
+  DISALLOW_COPY_AND_ASSIGN(UserIdCaveat);
+};
+
+// class ServiceCaveat : public Caveat {
+//  public:
+//   ServiceCaveat() : Caveat(kUwMacaroonCaveatTypeDelegateeService, 0) {
+//     // TODO: Replace with service delegatee.
+//     CHECK(uw_macaroon_caveat_create_delegatee_user_(
+//         nullptr, 0, buffer_.data(), buffer_.size(),
+//         &caveat_));
+//   }
+
+//   DISALLOW_COPY_AND_ASSIGN(ServiceCaveat);
+// };
+
+class ClientAuthTokenCaveat : public Caveat {
+ public:
+  ClientAuthTokenCaveat()
+      : Caveat(kUwMacaroonCaveatTypeClientAuthorizationTokenV1, 0) {
+    CHECK(uw_macaroon_caveat_create_client_authorization_token_(
+        nullptr, 0, buffer_.data(), buffer_.size(), &caveat_));
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(ClientAuthTokenCaveat);
+};
 
 std::vector<uint8_t> CreateSecret() {
   std::vector<uint8_t> secret(kSha256OutputSize);
@@ -122,18 +140,53 @@
 
 std::vector<uint8_t> CreateMacaroonToken(
     const std::vector<uint8_t>& secret,
-    const std::vector<UwMacaroonCaveat>& caveats) {
+    const base::Time& time,
+    const std::vector<const UwMacaroonCaveat*>& caveats) {
   CHECK_EQ(kSha256OutputSize, secret.size());
+
+  UwMacaroonContext context{};
+  CHECK(uw_macaroon_context_create_(ToJ2000Time(time), nullptr, 0, &context));
+
   UwMacaroon macaroon{};
-  CHECK(uw_macaroon_new_from_root_key_(&macaroon, secret.data(), secret.size(),
-                                       caveats.data(), caveats.size()));
+  CHECK(uw_macaroon_create_from_root_key_(&macaroon, secret.data(),
+                                          secret.size(), &context,
+                                          caveats.data(), caveats.size()));
 
-  std::vector<uint8_t> token(kMaxMacaroonSize);
+  std::vector<uint8_t> serialized_token(kMaxMacaroonSize);
   size_t len = 0;
-  CHECK(uw_macaroon_dump_(&macaroon, token.data(), token.size(), &len));
-  token.resize(len);
+  CHECK(uw_macaroon_serialize_(&macaroon, serialized_token.data(),
+                               serialized_token.size(), &len));
+  serialized_token.resize(len);
 
-  return token;
+  return serialized_token;
+}
+
+std::vector<uint8_t> ExtendMacaroonToken(
+    const UwMacaroon& macaroon,
+    const base::Time& time,
+    const std::vector<const UwMacaroonCaveat*>& caveats) {
+  UwMacaroonContext context{};
+  CHECK(uw_macaroon_context_create_(ToJ2000Time(time), nullptr, 0, &context));
+
+  UwMacaroon prev_macaroon = macaroon;
+  std::vector<uint8_t> prev_buffer(kMaxMacaroonSize);
+  std::vector<uint8_t> new_buffer(kMaxMacaroonSize);
+
+  for (auto caveat : caveats) {
+    UwMacaroon new_macaroon{};
+    CHECK(uw_macaroon_extend_(&prev_macaroon, &new_macaroon, &context, caveat,
+                              new_buffer.data(), new_buffer.size()));
+    new_buffer.swap(prev_buffer);
+    prev_macaroon = new_macaroon;
+  }
+
+  std::vector<uint8_t> serialized_token(kMaxMacaroonSize);
+  size_t len = 0;
+  CHECK(uw_macaroon_serialize_(&prev_macaroon, serialized_token.data(),
+                               serialized_token.size(), &len));
+  serialized_token.resize(len);
+
+  return serialized_token;
 }
 
 bool LoadMacaroon(const std::vector<uint8_t>& token,
@@ -141,8 +194,8 @@
                   UwMacaroon* macaroon,
                   ErrorPtr* error) {
   buffer->resize(kMaxMacaroonSize);
-  if (!uw_macaroon_load_(token.data(), token.size(), buffer->data(),
-                         buffer->size(), macaroon)) {
+  if (!uw_macaroon_deserialize_(token.data(), token.size(), buffer->data(),
+                                buffer->size(), macaroon)) {
     return Error::AddTo(error, FROM_HERE, kInvalidTokenError,
                         "Invalid token format");
   }
@@ -151,10 +204,16 @@
 
 bool VerifyMacaroon(const std::vector<uint8_t>& secret,
                     const UwMacaroon& macaroon,
+                    const base::Time& time,
+                    UwMacaroonValidationResult* result,
                     ErrorPtr* error) {
   CHECK_EQ(kSha256OutputSize, secret.size());
-  if (!uw_macaroon_verify_(&macaroon, secret.data(), secret.size())) {
-    return Error::AddTo(error, FROM_HERE, "invalid_signature",
+  UwMacaroonContext context = {};
+  CHECK(uw_macaroon_context_create_(ToJ2000Time(time), nullptr, 0, &context));
+
+  if (!uw_macaroon_validate_(&macaroon, secret.data(), secret.size(), &context,
+                             result)) {
+    return Error::AddTo(error, FROM_HERE, "invalid_token",
                         "Invalid token signature");
   }
   return true;
@@ -239,15 +298,16 @@
 
 std::vector<uint8_t> AuthManager::CreateAccessToken(const UserInfo& user_info,
                                                     base::TimeDelta ttl) const {
-  Caveat scope{kUwMacaroonCaveatTypeScope, ToMacaroonScope(user_info.scope())};
-  Caveat user{kUwMacaroonCaveatTypeIdentifier, user_info.user_id()};
-  Caveat issued{kUwMacaroonCaveatTypeExpiration,
-                static_cast<uint32_t>((Now() + ttl).ToTimeT())};
+  ScopeCaveat scope{ToMacaroonScope(user_info.scope())};
+  UserIdCaveat user{user_info.user_id()};
+  const base::Time now = Now();
+  ExpirationCaveat expiration{now + ttl};
   return CreateMacaroonToken(
-      access_secret_,
+      access_secret_, now,
       {
-          scope.GetCaveat(), user.GetCaveat(), issued.GetCaveat(),
+          &scope.GetCaveat(), &user.GetCaveat(), &expiration.GetCaveat(),
       });
+  return {};
 }
 
 bool AuthManager::ParseAccessToken(const std::vector<uint8_t>& token,
@@ -256,35 +316,28 @@
   std::vector<uint8_t> buffer;
   UwMacaroon macaroon{};
 
-  uint32_t scope{0};
-  std::string user_id;
-  uint32_t expiration{0};
-
+  UwMacaroonValidationResult result{};
+  const base::Time now = Now();
   if (!LoadMacaroon(token, &buffer, &macaroon, error) ||
-      !VerifyMacaroon(access_secret_, macaroon, error) ||
       macaroon.num_caveats != 3 ||
-      !ReadCaveat(macaroon.caveats[0], kUwMacaroonCaveatTypeScope, &scope,
-                  error) ||
-      !ReadCaveat(macaroon.caveats[1], kUwMacaroonCaveatTypeIdentifier,
-                  &user_id, error) ||
-      !ReadCaveat(macaroon.caveats[2], kUwMacaroonCaveatTypeExpiration,
-                  &expiration, error)) {
+      !VerifyMacaroon(access_secret_, macaroon, now, &result, error)) {
     return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization,
                         "Invalid token");
   }
 
-  AuthScope auth_scope{FromMacaroonScope(scope)};
+  AuthScope auth_scope{FromMacaroonScope(result.granted_scope)};
   if (auth_scope == AuthScope::kNone) {
     return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization,
                         "Invalid token data");
   }
 
-  base::Time time{base::Time::FromTimeT(expiration)};
-  if (time < clock_->Now()) {
-    return Error::AddTo(error, FROM_HERE, errors::kAuthorizationExpired,
-                        "Token is expired");
-  }
-
+  // If token is valid and token was not extended, it should has precisely this
+  // values.
+  CHECK_GE(FromJ2000Time(result.expiration_time), now);
+  CHECK_EQ(1u, result.num_delegatees);
+  CHECK(!result.delegatees[0].is_app);
+  std::string user_id{reinterpret_cast<const char*>(result.delegatees[0].id),
+                      result.delegatees[0].id_len};
   if (user_info)
     *user_info = UserInfo{auth_scope, user_id};
 
@@ -309,7 +362,7 @@
       std::unique_ptr<AuthManager>{new AuthManager{nullptr, {}}}, owner));
   if (pending_claims_.size() > kMaxPendingClaims)
     pending_claims_.pop_front();
-  return pending_claims_.back().first->GetRootClientAuthToken();
+  return pending_claims_.back().first->GetRootClientAuthToken(owner);
 }
 
 bool AuthManager::ConfirmClientAuthToken(const std::vector<uint8_t>& token,
@@ -332,14 +385,22 @@
   return true;
 }
 
-std::vector<uint8_t> AuthManager::GetRootClientAuthToken() const {
-  Caveat scope{kUwMacaroonCaveatTypeScope, kUwMacaroonCaveatScopeTypeOwner};
-  Caveat issued{kUwMacaroonCaveatTypeIssued,
-                static_cast<uint32_t>(Now().ToTimeT())};
-  return CreateMacaroonToken(auth_secret_,
-                             {
-                                 scope.GetCaveat(), issued.GetCaveat(),
-                             });
+std::vector<uint8_t> AuthManager::GetRootClientAuthToken(
+    RootClientTokenOwner owner) const {
+  CHECK(RootClientTokenOwner::kNone != owner);
+  ClientAuthTokenCaveat auth_token;
+  const base::Time now = Now();
+  TimestampCaveat issued{now};
+
+  UserIdCaveat client{""};
+  // TODO: service caveat when available.
+  // ServiceCaveat cloud;
+
+  return CreateMacaroonToken(
+      auth_secret_, now,
+      {
+          &auth_token.GetCaveat(), &issued.GetCaveat(), &client.GetCaveat(),
+      });
 }
 
 base::Time AuthManager::Now() const {
@@ -350,8 +411,9 @@
                                    ErrorPtr* error) const {
   std::vector<uint8_t> buffer;
   UwMacaroon macaroon{};
+  UwMacaroonValidationResult result{};
   if (!LoadMacaroon(token, &buffer, &macaroon, error) ||
-      !VerifyMacaroon(auth_secret_, macaroon, error)) {
+      !VerifyMacaroon(auth_secret_, macaroon, Now(), &result, error)) {
     return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthCode,
                         "Invalid token");
   }
@@ -365,19 +427,49 @@
     AuthScope* access_token_scope,
     base::TimeDelta* access_token_ttl,
     ErrorPtr* error) const {
-  // TODO(vitalybuka): implement token validation.
-  if (!IsValidAuthToken(auth_token, error))
-    return false;
+  std::vector<uint8_t> buffer;
+  UwMacaroon macaroon{};
+  UwMacaroonValidationResult result{};
+  const base::Time now = Now();
+  if (!LoadMacaroon(auth_token, &buffer, &macaroon, error) ||
+      !VerifyMacaroon(auth_secret_, macaroon, now, &result, error)) {
+    return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthCode,
+                        "Invalid token");
+  }
+
+  AuthScope auth_scope{FromMacaroonScope(result.granted_scope)};
+  if (auth_scope == AuthScope::kNone) {
+    return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization,
+                        "Invalid token data");
+  }
+
+  // TODO: Integrate black list checks.
+  // TODO: Check session id.
+  auto delegates_rbegin = std::reverse_iterator<const UwMacaroonDelegateeInfo*>(
+      result.delegatees + result.num_delegatees);
+  auto delegates_rend =
+      std::reverse_iterator<const UwMacaroonDelegateeInfo*>(result.delegatees);
+  auto last_user_id =
+      std::find_if(delegates_rbegin, delegates_rend,
+                   [](const UwMacaroonDelegateeInfo& delegatee) {
+                     return !delegatee.is_app;
+                   });
+
+  if (last_user_id == delegates_rend || !last_user_id->id_len) {
+    return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization,
+                        "User ID is missing");
+  }
+
+  CHECK_GE(FromJ2000Time(result.expiration_time), now);
 
   if (!access_token)
     return true;
 
-  // TODO(vitalybuka): User and scope must be parsed from auth_token.
-  UserInfo info{config_ ? config_->GetSettings().local_anonymous_access_role
-                        : AuthScope::kViewer,
-                base::GenerateGUID()};
+  std::string user_id{reinterpret_cast<const char*>(last_user_id->id),
+                      last_user_id->id_len};
+  UserInfo info{auth_scope, user_id};
 
-  // TODO(vitalybuka): TTL also should be reduced in accordance with auth_token.
+  ttl = std::min(ttl, FromJ2000Time(result.expiration_time) - now);
   *access_token = CreateAccessToken(info, ttl);
 
   if (access_token_scope)
@@ -395,5 +487,21 @@
   return result;
 }
 
+std::vector<uint8_t> AuthManager::DelegateToUser(
+    const std::vector<uint8_t>& token,
+    const UserInfo& user_info) const {
+  std::vector<uint8_t> buffer;
+  UwMacaroon macaroon{};
+  CHECK(LoadMacaroon(token, &buffer, &macaroon, nullptr));
+
+  ScopeCaveat scope{ToMacaroonScope(user_info.scope())};
+  UserIdCaveat user_caveat{user_info.user_id()};
+
+  return ExtendMacaroonToken(macaroon, Now(),
+                             {
+                                 &scope.GetCaveat(), &user_caveat.GetCaveat(),
+                             });
+}
+
 }  // namespace privet
 }  // namespace weave
diff --git a/src/privet/auth_manager.h b/src/privet/auth_manager.h
index 309d80e..0fa90a7 100644
--- a/src/privet/auth_manager.h
+++ b/src/privet/auth_manager.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include <base/gtest_prod_util.h>
 #include <base/time/default_clock.h>
 #include <base/time/time.h>
 #include <weave/error.h>
@@ -54,7 +55,7 @@
   bool ConfirmClientAuthToken(const std::vector<uint8_t>& token,
                               ErrorPtr* error);
 
-  std::vector<uint8_t> GetRootClientAuthToken() const;
+  std::vector<uint8_t> GetRootClientAuthToken(RootClientTokenOwner owner) const;
   bool IsValidAuthToken(const std::vector<uint8_t>& token,
                         ErrorPtr* error) const;
   bool CreateAccessTokenFromAuth(const std::vector<uint8_t>& auth_token,
@@ -70,6 +71,12 @@
   std::vector<uint8_t> CreateSessionId();
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(AuthManagerClaimTest, CreateAccessTokenFromAuth);
+
+  // Test helper.
+  std::vector<uint8_t> DelegateToUser(const std::vector<uint8_t>& token,
+                                      const UserInfo& user_info) const;
+
   Config* config_{nullptr};  // Can be nullptr for tests.
   base::DefaultClock default_clock_;
   base::Clock* clock_{&default_clock_};
diff --git a/src/privet/auth_manager_unittest.cc b/src/privet/auth_manager_unittest.cc
index 70750ad..c5fba3c 100644
--- a/src/privet/auth_manager_unittest.cc
+++ b/src/privet/auth_manager_unittest.cc
@@ -64,18 +64,18 @@
 }
 
 TEST_F(AuthManagerTest, CreateAccessToken) {
-  EXPECT_EQ("UABRUHgcSZDry0bvIsoJv+WDQgEURQJjMjM0RgUaVArkgA==",
+  EXPECT_EQ("WCKDQgEURQlDMjM0RgUaG52hAFA3hFh7TexW1jC96sU4CxvN",
             Base64Encode(auth_.CreateAccessToken(
                 UserInfo{AuthScope::kViewer, "234"}, {})));
-  EXPECT_EQ("UL7YEruLg5QQRDIp2+u1cqCDQgEIRQJjMjU3RgUaVArkgA==",
+  EXPECT_EQ("WCKDQgEIRQlDMjU3RgUaG52hAFD3dEHl3Y9Y28uoUESiYuLq",
             Base64Encode(auth_.CreateAccessToken(
                 UserInfo{AuthScope::kManager, "257"}, {})));
-  EXPECT_EQ("UPFGeZRanR1wLGYLP5ZDkXiDQgECRQJjNDU2RgUaVArkgA==",
+  EXPECT_EQ("WCKDQgECRQlDNDU2RgUaG52hAFBy35bQdtvlqYf+Y/ANyxLU",
             Base64Encode(auth_.CreateAccessToken(
                 UserInfo{AuthScope::kOwner, "456"}, {})));
   auto new_time = clock_.Now() + base::TimeDelta::FromDays(11);
   EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time));
-  EXPECT_EQ("UMm9KlF3OEtZFBmhScJpl4uDQgEORQJjMzQ1RgUaVBllAA==",
+  EXPECT_EQ("WCKDQgEORQlDMzQ1RgUaG6whgFD1HGVxL8+FPaf/U0bOkXr8",
             Base64Encode(auth_.CreateAccessToken(
                 UserInfo{AuthScope::kUser, "345"}, {})));
 }
@@ -137,30 +137,40 @@
 }
 
 TEST_F(AuthManagerTest, GetRootClientAuthToken) {
-  EXPECT_EQ("UK1ACOc3cWGjGBoTIX2bd3qCQgECRgMaVArkgA==",
-            Base64Encode(auth_.GetRootClientAuthToken()));
+  EXPECT_EQ("WCCDQxkgAUYIGhudoQBCCUBQn9rT/8iUzwKa0ZIAgCNxyg==",
+            Base64Encode(
+                auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient)));
+}
+
+TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentOwner) {
+  EXPECT_EQ(
+      "WCCDQxkgAUYIGhudoQBCCUBQn9rT/8iUzwKa0ZIAgCNxyg==",
+      Base64Encode(auth_.GetRootClientAuthToken(RootClientTokenOwner::kCloud)));
 }
 
 TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentTime) {
   auto new_time = clock_.Now() + base::TimeDelta::FromDays(15);
   EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time));
-  EXPECT_EQ("UBpNF8g/GbNUmAyHg1qqJr+CQgECRgMaVB6rAA==",
-            Base64Encode(auth_.GetRootClientAuthToken()));
+  EXPECT_EQ("WCCDQxkgAUYIGhuxZ4BCCUBQmNBWA9KdLzxHUCMqzonDZw==",
+            Base64Encode(
+                auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient)));
 }
 
 TEST_F(AuthManagerTest, GetRootClientAuthTokenDifferentSecret) {
   AuthManager auth{kSecret2, {}, kSecret1, &clock_};
-  EXPECT_EQ("UFTBUcgd9d0HnPRnLeroN2mCQgECRgMaVArkgA==",
-            Base64Encode(auth.GetRootClientAuthToken()));
+  EXPECT_EQ(
+      "WCCDQxkgAUYIGhudoQBCCUBQQ/BSJs7FEI260RnwjlJrVw==",
+      Base64Encode(auth.GetRootClientAuthToken(RootClientTokenOwner::kClient)));
 }
 
 TEST_F(AuthManagerTest, IsValidAuthToken) {
-  EXPECT_TRUE(auth_.IsValidAuthToken(auth_.GetRootClientAuthToken(), nullptr));
+  EXPECT_TRUE(auth_.IsValidAuthToken(
+      auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient), nullptr));
   // Multiple attempts with random secrets.
   for (size_t i = 0; i < 1000; ++i) {
     AuthManager auth{{}, {}, {}, &clock_};
 
-    auto token = auth.GetRootClientAuthToken();
+    auto token = auth.GetRootClientAuthToken(RootClientTokenOwner::kClient);
     EXPECT_FALSE(auth_.IsValidAuthToken(token, nullptr));
     EXPECT_TRUE(auth.IsValidAuthToken(token, nullptr));
   }
@@ -245,13 +255,17 @@
   std::vector<uint8_t> access_token;
   AuthScope scope;
   base::TimeDelta ttl;
-  EXPECT_TRUE(auth_.CreateAccessTokenFromAuth(
-      auth_.GetRootClientAuthToken(), base::TimeDelta::FromDays(1),
-      &access_token, &scope, &ttl, nullptr));
+  auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient);
+  auto extended = auth_.DelegateToUser(root, UserInfo{AuthScope::kUser, "234"});
+  EXPECT_TRUE(
+      auth_.CreateAccessTokenFromAuth(extended, base::TimeDelta::FromDays(1),
+                                      &access_token, &scope, &ttl, nullptr));
   UserInfo user_info;
   EXPECT_TRUE(auth_.ParseAccessToken(access_token, &user_info, nullptr));
   EXPECT_EQ(scope, user_info.scope());
-  EXPECT_FALSE(user_info.user_id().empty());
+  EXPECT_EQ(AuthScope::kUser, user_info.scope());
+
+  EXPECT_EQ("234", user_info.user_id());
 }
 
 }  // namespace privet
diff --git a/src/privet/openssl_utils.cc b/src/privet/openssl_utils.cc
index f38fd1a..17ebf70 100644
--- a/src/privet/openssl_utils.cc
+++ b/src/privet/openssl_utils.cc
@@ -18,13 +18,9 @@
 std::vector<uint8_t> HmacSha256(const std::vector<uint8_t>& key,
                                 const std::vector<uint8_t>& data) {
   std::vector<uint8_t> mac(kSha256OutputSize);
-  uint8_t hmac_state[uw_crypto_hmac_required_buffer_size_()];
-  CHECK(uw_crypto_hmac_init_(hmac_state, sizeof(hmac_state), key.data(),
-                             key.size()));
-  CHECK(uw_crypto_hmac_update_(hmac_state, sizeof(hmac_state), data.data(),
-                               data.size()));
-  CHECK(uw_crypto_hmac_final_(hmac_state, sizeof(hmac_state), mac.data(),
-                              mac.size()));
+  const UwCryptoHmacMsg messages[] = {{data.data(), data.size()}};
+  CHECK(uw_crypto_hmac_(key.data(), key.size(), messages, arraysize(messages),
+                        mac.data(), mac.size()));
   return mac;
 }
 
diff --git a/third_party/libuweave/src/crypto_hmac.c b/third_party/libuweave/src/crypto_hmac.c
index 8b75133..d3dca65 100644
--- a/third_party/libuweave/src/crypto_hmac.c
+++ b/third_party/libuweave/src/crypto_hmac.c
@@ -11,41 +11,24 @@
 #include <openssl/evp.h>
 #include <openssl/hmac.h>
 
-size_t uw_crypto_hmac_required_buffer_size_() {
-  return sizeof(HMAC_CTX);
-}
-
-bool uw_crypto_hmac_init_(uint8_t* state_buffer,
-                          size_t state_buffer_len,
-                          const uint8_t* key,
-                          size_t key_len) {
-  if (sizeof(HMAC_CTX) > state_buffer_len) {
+bool uw_crypto_hmac_(const uint8_t* key,
+                     size_t key_len,
+                     const UwCryptoHmacMsg messages[],
+                     size_t num_messages,
+                     uint8_t* truncated_digest,
+                     size_t truncated_digest_len) {
+  HMAC_CTX context = {0};
+  HMAC_CTX_init(&context);
+  if (!HMAC_Init(&context, key, key_len, EVP_sha256()))
     return false;
-  }
-  HMAC_CTX* context = (HMAC_CTX*)state_buffer;
-  HMAC_CTX_init(context);
-  return HMAC_Init(context, key, key_len, EVP_sha256());
-}
 
-bool uw_crypto_hmac_update_(uint8_t* state_buffer,
-                            size_t state_buffer_len,
-                            const uint8_t* data,
-                            size_t data_len) {
-  if (sizeof(HMAC_CTX) > state_buffer_len) {
-    return false;
+  for (size_t i = 0; i < num_messages; ++i) {
+    if (messages[i].num_bytes &&
+        (!messages[i].bytes ||
+         !HMAC_Update(&context, messages[i].bytes, messages[i].num_bytes))) {
+      return false;
+    }
   }
-  HMAC_CTX* context = (HMAC_CTX*)state_buffer;
-  return HMAC_Update(context, data, data_len);
-}
-
-bool uw_crypto_hmac_final_(uint8_t* state_buffer,
-                           size_t state_buffer_len,
-                           uint8_t* truncated_digest,
-                           size_t truncated_digest_len) {
-  if (sizeof(HMAC_CTX) > state_buffer_len) {
-    return false;
-  }
-  HMAC_CTX* context = (HMAC_CTX*)state_buffer;
 
   const size_t kFullDigestLen = (size_t)EVP_MD_size(EVP_sha256());
   if (truncated_digest_len > kFullDigestLen) {
@@ -55,8 +38,8 @@
   uint8_t digest[kFullDigestLen];
   uint32_t len = kFullDigestLen;
 
-  bool result = HMAC_Final(context, digest, &len) && kFullDigestLen == len;
-  HMAC_CTX_cleanup(context);
+  bool result = HMAC_Final(&context, digest, &len) && kFullDigestLen == len;
+  HMAC_CTX_cleanup(&context);
   if (result) {
     memcpy(truncated_digest, digest, truncated_digest_len);
   }