Coverage Report

Created: 2023-06-07 07:04

/proc/self/cwd/src/jwt.cc
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2018 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//    https://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include "jwt_verify_lib/jwt.h"
16
17
#include <algorithm>
18
19
#include "absl/container/flat_hash_set.h"
20
#include "absl/strings/escaping.h"
21
#include "absl/strings/str_split.h"
22
#include "absl/time/clock.h"
23
#include "google/protobuf/util/json_util.h"
24
#include "jwt_verify_lib/struct_utils.h"
25
26
namespace google {
27
namespace jwt_verify {
28
29
namespace {
30
31
2.70k
bool isImplemented(absl::string_view alg) {
32
2.70k
  static const absl::flat_hash_set<absl::string_view> implemented_algs = {
33
2.70k
      {"ES256"}, {"ES384"}, {"ES512"}, {"HS256"}, {"HS384"},
34
2.70k
      {"HS512"}, {"RS256"}, {"RS384"}, {"RS512"}, {"PS256"},
35
2.70k
      {"PS384"}, {"PS512"}, {"EdDSA"},
36
2.70k
  };
37
38
2.70k
  return implemented_algs.find(alg) != implemented_algs.end();
39
2.70k
}
40
41
}  // namespace
42
43
0
Jwt::Jwt(const Jwt& instance) { *this = instance; }
44
45
0
Jwt& Jwt::operator=(const Jwt& rhs) {
46
0
  parseFromString(rhs.jwt_);
47
0
  return *this;
48
0
}
49
50
10.4k
Status Jwt::parseFromString(const std::string& jwt) {
51
  // jwt must have exactly 2 dots with 3 sections.
52
10.4k
  jwt_ = jwt;
53
10.4k
  std::vector<absl::string_view> jwt_split =
54
10.4k
      absl::StrSplit(jwt, '.', absl::SkipEmpty());
55
10.4k
  if (jwt_split.size() != 3) {
56
7.28k
    return Status::JwtBadFormat;
57
7.28k
  }
58
59
  // Parse header json
60
3.19k
  header_str_base64url_ = std::string(jwt_split[0]);
61
3.19k
  if (!absl::WebSafeBase64Unescape(header_str_base64url_, &header_str_)) {
62
183
    return Status::JwtHeaderParseErrorBadBase64;
63
183
  }
64
65
3.01k
  ::google::protobuf::util::JsonParseOptions options;
66
3.01k
  const auto header_status = ::google::protobuf::util::JsonStringToMessage(
67
3.01k
      header_str_, &header_pb_, options);
68
3.01k
  if (!header_status.ok()) {
69
299
    return Status::JwtHeaderParseErrorBadJson;
70
299
  }
71
72
2.71k
  StructUtils header_getter(header_pb_);
73
  // Header should contain "alg" and should be a string.
74
2.71k
  if (header_getter.GetString("alg", &alg_) != StructUtils::OK) {
75
14
    return Status::JwtHeaderBadAlg;
76
14
  }
77
78
2.70k
  if (!isImplemented(alg_)) {
79
86
    return Status::JwtHeaderNotImplementedAlg;
80
86
  }
81
82
  // Header may contain "kid", should be a string if exists.
83
2.61k
  if (header_getter.GetString("kid", &kid_) == StructUtils::WRONG_TYPE) {
84
1
    return Status::JwtHeaderBadKid;
85
1
  }
86
87
  // Parse payload json
88
2.61k
  payload_str_base64url_ = std::string(jwt_split[1]);
89
2.61k
  if (!absl::WebSafeBase64Unescape(payload_str_base64url_, &payload_str_)) {
90
36
    return Status::JwtPayloadParseErrorBadBase64;
91
36
  }
92
93
2.57k
  const auto payload_status = ::google::protobuf::util::JsonStringToMessage(
94
2.57k
      payload_str_, &payload_pb_, options);
95
2.57k
  if (!payload_status.ok()) {
96
49
    return Status::JwtPayloadParseErrorBadJson;
97
49
  }
98
99
2.52k
  StructUtils payload_getter(payload_pb_);
100
2.52k
  if (payload_getter.GetString("iss", &iss_) == StructUtils::WRONG_TYPE) {
101
0
    return Status::JwtPayloadParseErrorIssNotString;
102
0
  }
103
2.52k
  if (payload_getter.GetString("sub", &sub_) == StructUtils::WRONG_TYPE) {
104
1
    return Status::JwtPayloadParseErrorSubNotString;
105
1
  }
106
107
2.52k
  auto result = payload_getter.GetUInt64("iat", &iat_);
108
2.52k
  if (result == StructUtils::WRONG_TYPE) {
109
1
    return Status::JwtPayloadParseErrorIatNotInteger;
110
2.52k
  } else if (result == StructUtils::OUT_OF_RANGE) {
111
0
    return Status::JwtPayloadParseErrorIatOutOfRange;
112
0
  }
113
114
2.52k
  result = payload_getter.GetUInt64("nbf", &nbf_);
115
2.52k
  if (result == StructUtils::WRONG_TYPE) {
116
0
    return Status::JwtPayloadParseErrorNbfNotInteger;
117
2.52k
  } else if (result == StructUtils::OUT_OF_RANGE) {
118
0
    return Status::JwtPayloadParseErrorNbfOutOfRange;
119
0
  }
120
121
2.52k
  result = payload_getter.GetUInt64("exp", &exp_);
122
2.52k
  if (result == StructUtils::WRONG_TYPE) {
123
1
    return Status::JwtPayloadParseErrorExpNotInteger;
124
2.52k
  } else if (result == StructUtils::OUT_OF_RANGE) {
125
3
    return Status::JwtPayloadParseErrorExpOutOfRange;
126
3
  }
127
128
2.52k
  if (payload_getter.GetString("jti", &jti_) == StructUtils::WRONG_TYPE) {
129
0
    return Status::JwtPayloadParseErrorJtiNotString;
130
0
  }
131
132
  // "aud" can be either string array or string.
133
  // GetStringList function will try to read as string, if fails,
134
  // try to read as string array.
135
2.52k
  if (payload_getter.GetStringList("aud", &audiences_) ==
136
2.52k
      StructUtils::WRONG_TYPE) {
137
2
    return Status::JwtPayloadParseErrorAudNotString;
138
2
  }
139
140
  // Set up signature
141
2.52k
  if (!absl::WebSafeBase64Unescape(jwt_split[2], &signature_)) {
142
    // Signature is a bad Base64url input.
143
27
    return Status::JwtSignatureParseErrorBadBase64;
144
27
  }
145
2.49k
  return Status::Ok;
146
2.52k
}
147
148
2.39k
Status Jwt::verifyTimeConstraint(uint64_t now, uint64_t clock_skew) const {
149
  // Check Jwt is active (nbf).
150
2.39k
  if (now + clock_skew < nbf_) {
151
0
    return Status::JwtNotYetValid;
152
0
  }
153
  // Check JWT has not expired (exp).
154
2.39k
  if (exp_ && now > exp_ + clock_skew) {
155
11
    return Status::JwtExpired;
156
11
  }
157
2.37k
  return Status::Ok;
158
2.39k
}
159
160
}  // namespace jwt_verify
161
}  // namespace google