Switch to macaroon library to generate and parse access tokens

We need access tokens to store signed time, user id and scope.
Macaroon library provides exactly this functionality. So we can reuse
that and remove some of our generating/parsing code.

Access token is not expected to be parsed by clients. Client can't
remove caveats from macaroon. Adding new caveats is possible, but code
will reject such extended tokens because number of caveat was changed.

Change-Id: I3f3a4a972cad061fe6ac75eb906a5b299e75d13d
Reviewed-on: https://weave-review.googlesource.com/2084
Reviewed-by: Vitaly Buka <vitalybuka@google.com>
diff --git a/src/privet/auth_manager.cc b/src/privet/auth_manager.cc
index 7306e20..8373a7e 100644
--- a/src/privet/auth_manager.cc
+++ b/src/privet/auth_manager.cc
@@ -25,7 +25,6 @@
 
 namespace {
 
-const char kTokenDelimeter[] = ":";
 const size_t kMaxMacaroonSize = 1024;
 const size_t kMaxPendingClaims = 10;
 const char kInvalidTokenError[] = "invalid_token";
@@ -36,36 +35,6 @@
   array->insert(array->end(), begin, begin + sizeof(value));
 }
 
-// Returns "scope:time:id".
-std::string CreateTokenData(const UserInfo& user_info, const base::Time& time) {
-  return base::IntToString(static_cast<int>(user_info.scope())) +
-         kTokenDelimeter + std::to_string(time.ToTimeT()) + kTokenDelimeter +
-         user_info.user_id();
-}
-
-// Splits string of "scope:time:id" format.
-UserInfo SplitTokenData(const std::string& token, base::Time* time) {
-  const UserInfo kNone;
-  auto parts = SplitAtFirst(token, kTokenDelimeter, false);
-  if (parts.second.empty())
-    return kNone;
-  int scope = 0;
-  if (!base::StringToInt(parts.first, &scope) ||
-      scope < static_cast<int>(AuthScope::kNone) ||
-      scope > static_cast<int>(AuthScope::kOwner)) {
-    return kNone;
-  }
-
-  parts = SplitAtFirst(parts.second, kTokenDelimeter, false);
-  int64_t timestamp{0};
-  if (parts.second.empty() || !base::StringToInt64(parts.first, &timestamp))
-    return kNone;
-
-  if (time)
-    *time = base::Time::FromTimeT(timestamp);
-  return UserInfo{static_cast<AuthScope>(scope), parts.second};
-}
-
 class Caveat {
  public:
   // TODO(vitalybuka): Use _get_buffer_size_ when available.
@@ -91,6 +60,60 @@
   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)) {
+    Error::AddTo(error, FROM_HERE, errors::kDomain, kInvalidTokenError,
+                 "Unable to get type");
+    return false;
+  }
+
+  if (caveat_type != type) {
+    Error::AddTo(error, FROM_HERE, errors::kDomain, kInvalidTokenError,
+                 "Unexpected caveat type");
+    return false;
+  }
+
+  return true;
+}
+
+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)) {
+    Error::AddTo(error, FROM_HERE, errors::kDomain, kInvalidTokenError,
+                 "Unable to read caveat");
+    return false;
+  }
+
+  return true;
+}
+
+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)) {
+    Error::AddTo(error, FROM_HERE, errors::kDomain, kInvalidTokenError,
+                 "Unable to read caveat");
+    return false;
+  }
+
+  value->assign(reinterpret_cast<const char*>(start), size);
+  return true;
+}
+
 std::vector<uint8_t> CreateSecret() {
   std::vector<uint8_t> secret(kSha256OutputSize);
   base::RandBytes(secret.data(), secret.size());
@@ -143,6 +166,34 @@
   return true;
 }
 
+UwMacaroonCaveatScopeType ToMacaroonScope(AuthScope scope) {
+  switch (scope) {
+    case AuthScope::kViewer:
+      return kUwMacaroonCaveatScopeTypeViewer;
+    case AuthScope::kUser:
+      return kUwMacaroonCaveatScopeTypeUser;
+    case AuthScope::kManager:
+      return kUwMacaroonCaveatScopeTypeManager;
+    case AuthScope::kOwner:
+      return kUwMacaroonCaveatScopeTypeOwner;
+    default:
+      NOTREACHED() << EnumToString(scope);
+  }
+  return kUwMacaroonCaveatScopeTypeViewer;
+}
+
+AuthScope FromMacaroonScope(uint32_t scope) {
+  if (scope <= kUwMacaroonCaveatScopeTypeOwner)
+    return AuthScope::kOwner;
+  if (scope <= kUwMacaroonCaveatScopeTypeManager)
+    return AuthScope::kManager;
+  if (scope <= kUwMacaroonCaveatScopeTypeUser)
+    return AuthScope::kUser;
+  if (scope <= kUwMacaroonCaveatScopeTypeViewer)
+    return AuthScope::kViewer;
+  return AuthScope::kNone;
+}
+
 }  // namespace
 
 AuthManager::AuthManager(Config* config,
@@ -192,44 +243,51 @@
 
 AuthManager::~AuthManager() {}
 
-// Returns "[hmac]scope:expiration_time:id".
 std::vector<uint8_t> AuthManager::CreateAccessToken(const UserInfo& user_info,
                                                     base::TimeDelta ttl) const {
-  std::string data_str{CreateTokenData(user_info, Now() + ttl)};
-  std::vector<uint8_t> data{data_str.begin(), data_str.end()};
-  std::vector<uint8_t> hash{HmacSha256(access_secret_, data)};
-  hash.insert(hash.end(), data.begin(), data.end());
-
-  return hash;
+  Caveat scope{kUwMacaroonCaveatTypeScope, ToMacaroonScope(user_info.scope())};
+  Caveat user{kUwMacaroonCaveatTypeIdentifier, user_info.user_id()};
+  Caveat issued{kUwMacaroonCaveatTypeExpiration,
+                static_cast<uint32_t>((Now() + ttl).ToTimeT())};
+  return CreateMacaroonToken(
+      access_secret_,
+      {
+          scope.GetCaveat(), user.GetCaveat(), issued.GetCaveat(),
+      });
 }
 
-// TODO(vitalybuka): Switch to Macaroon?
-// Parses "base64([hmac]scope:id:expriration_time)".
 bool AuthManager::ParseAccessToken(const std::vector<uint8_t>& token,
                                    UserInfo* user_info,
                                    ErrorPtr* error) const {
-  if (token.size() <= kSha256OutputSize) {
-    Error::AddToPrintf(error, FROM_HERE, errors::kDomain,
-                       errors::kInvalidAuthorization, "Invalid token size: %zu",
-                       token.size());
-    return false;
-  }
-  std::vector<uint8_t> hash(token.begin(), token.begin() + kSha256OutputSize);
-  std::vector<uint8_t> data(token.begin() + kSha256OutputSize, token.end());
-  if (hash != HmacSha256(access_secret_, data)) {
+  std::vector<uint8_t> buffer;
+  UwMacaroon macaroon{};
+
+  uint32_t scope{0};
+  std::string user_id;
+  uint32_t expiration{0};
+
+  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)) {
     Error::AddTo(error, FROM_HERE, errors::kDomain,
-                 errors::kInvalidAuthorization, "Invalid signature");
+                 errors::kInvalidAuthorization, "Invalid token");
     return false;
   }
 
-  base::Time time;
-  UserInfo info = SplitTokenData(std::string(data.begin(), data.end()), &time);
-  if (info.scope() == AuthScope::kNone) {
+  AuthScope auth_scope{FromMacaroonScope(scope)};
+  if (auth_scope == AuthScope::kNone) {
     Error::AddTo(error, FROM_HERE, errors::kDomain,
                  errors::kInvalidAuthorization, "Invalid token data");
     return false;
   }
 
+  base::Time time{base::Time::FromTimeT(expiration)};
   if (time < clock_->Now()) {
     Error::AddTo(error, FROM_HERE, errors::kDomain,
                  errors::kAuthorizationExpired, "Token is expired");
@@ -237,7 +295,7 @@
   }
 
   if (user_info)
-    *user_info = info;
+    *user_info = UserInfo{auth_scope, user_id};
 
   return true;
 }
diff --git a/src/privet/auth_manager_unittest.cc b/src/privet/auth_manager_unittest.cc
index ce142c0..70750ad 100644
--- a/src/privet/auth_manager_unittest.cc
+++ b/src/privet/auth_manager_unittest.cc
@@ -64,21 +64,18 @@
 }
 
 TEST_F(AuthManagerTest, CreateAccessToken) {
-  EXPECT_EQ("xy58xYHWQUp5VT2tDmYLNIgdRiJ5ZQbgyCQuOGyQa5AwOjE0MTAwMDAwMDA6",
-            Base64Encode(auth_.CreateAccessToken(
-                UserInfo{AuthScope::kNone, "123"}, {})));
-  EXPECT_EQ("U0U8NQ4J0btA4kwPkG8deFkDLQOPPANbw53gGmcTUMUxOjE0MTAwMDAwMDA6MjM0",
+  EXPECT_EQ("UABRUHgcSZDry0bvIsoJv+WDQgEURQJjMjM0RgUaVArkgA==",
             Base64Encode(auth_.CreateAccessToken(
                 UserInfo{AuthScope::kViewer, "234"}, {})));
-  EXPECT_EQ("BooVCAQZ+ectnq2RYrzL3qymLfGm5YLGp9NMCuXAM3EzOjE0MTAwMDAwMDA6MjU3",
+  EXPECT_EQ("UL7YEruLg5QQRDIp2+u1cqCDQgEIRQJjMjU3RgUaVArkgA==",
             Base64Encode(auth_.CreateAccessToken(
                 UserInfo{AuthScope::kManager, "257"}, {})));
-  EXPECT_EQ("YjgJgNR2uDzfTxHokbuDfguwOB72/F9mbzDO2PehIS80OjE0MTAwMDAwMDA6NDU2",
+  EXPECT_EQ("UPFGeZRanR1wLGYLP5ZDkXiDQgECRQJjNDU2RgUaVArkgA==",
             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("d3t7075JF0Vb/c/Ihunk+xn2gxzUkHotdaIS9vc+A6kyOjE0MTA5NTA0MDA6MzQ1",
+  EXPECT_EQ("UMm9KlF3OEtZFBmhScJpl4uDQgEORQJjMzQ1RgUaVBllAA==",
             Base64Encode(auth_.CreateAccessToken(
                 UserInfo{AuthScope::kUser, "345"}, {})));
 }