1
// Copyright 2018 Google LLC
2
// Copyright Envoy Project Authors
3
// SPDX-License-Identifier: Apache-2.0
4

            
5
#include "source/common/jwt/jwt.h"
6

            
7
#include <algorithm>
8

            
9
#include "source/common/jwt/struct_utils.h"
10
#include "source/common/protobuf/protobuf.h"
11

            
12
#include "absl/container/flat_hash_set.h"
13
#include "absl/strings/escaping.h"
14
#include "absl/strings/str_split.h"
15
#include "absl/time/clock.h"
16

            
17
namespace Envoy {
18
namespace JwtVerify {
19

            
20
namespace {
21

            
22
38595
bool isImplemented(absl::string_view alg) {
23
38595
  static const absl::flat_hash_set<absl::string_view> implemented_algs = {
24
38595
      {"ES256"}, {"ES384"}, {"ES512"}, {"HS256"}, {"HS384"}, {"HS512"}, {"RS256"},
25
38595
      {"RS384"}, {"RS512"}, {"PS256"}, {"PS384"}, {"PS512"}, {"EdDSA"},
26
38595
  };
27

            
28
38595
  return implemented_algs.find(alg) != implemented_algs.end();
29
38595
}
30

            
31
} // namespace
32

            
33
38204
Jwt::Jwt(const Jwt& instance) { *this = instance; }
34

            
35
38205
Jwt& Jwt::operator=(const Jwt& rhs) {
36
38205
  parseFromString(rhs.jwt_);
37
38205
  return *this;
38
38205
}
39

            
40
38670
Status Jwt::parseFromString(const std::string& jwt) {
41
  // jwt must have exactly 2 dots with 3 sections.
42
38670
  jwt_ = jwt;
43
38670
  std::vector<absl::string_view> jwt_split = absl::StrSplit(jwt, '.', absl::SkipEmpty());
44
38670
  if (jwt_split.size() != 3) {
45
71
    return Status::JwtBadFormat;
46
71
  }
47

            
48
  // Parse header json
49
38599
  header_str_base64url_ = std::string(jwt_split[0]);
50
38599
  if (!absl::WebSafeBase64Unescape(header_str_base64url_, &header_str_)) {
51
1
    return Status::JwtHeaderParseErrorBadBase64;
52
1
  }
53

            
54
38598
  Protobuf::util::JsonParseOptions options;
55
38598
  const auto header_status = Protobuf::util::JsonStringToMessage(header_str_, &header_pb_, options);
56
38598
  if (!header_status.ok()) {
57
1
    return Status::JwtHeaderParseErrorBadJson;
58
1
  }
59

            
60
38597
  StructUtils header_getter(header_pb_);
61
  // Header should contain "alg" and should be a string.
62
38597
  if (header_getter.GetString("alg", &alg_) != StructUtils::OK) {
63
2
    return Status::JwtHeaderBadAlg;
64
2
  }
65

            
66
38595
  if (!isImplemented(alg_)) {
67
1
    return Status::JwtHeaderNotImplementedAlg;
68
1
  }
69

            
70
  // Header may contain "kid", should be a string if exists.
71
38594
  if (header_getter.GetString("kid", &kid_) == StructUtils::WRONG_TYPE) {
72
1
    return Status::JwtHeaderBadKid;
73
1
  }
74

            
75
  // Parse payload json
76
38593
  payload_str_base64url_ = std::string(jwt_split[1]);
77
38593
  if (!absl::WebSafeBase64Unescape(payload_str_base64url_, &payload_str_)) {
78
1
    return Status::JwtPayloadParseErrorBadBase64;
79
1
  }
80

            
81
38592
  const auto payload_status =
82
38592
      Protobuf::util::JsonStringToMessage(payload_str_, &payload_pb_, options);
83
38592
  if (!payload_status.ok()) {
84
1
    return Status::JwtPayloadParseErrorBadJson;
85
1
  }
86

            
87
38591
  StructUtils payload_getter(payload_pb_);
88
38591
  if (payload_getter.GetString("iss", &iss_) == StructUtils::WRONG_TYPE) {
89
1
    return Status::JwtPayloadParseErrorIssNotString;
90
1
  }
91
38590
  if (payload_getter.GetString("sub", &sub_) == StructUtils::WRONG_TYPE) {
92
1
    return Status::JwtPayloadParseErrorSubNotString;
93
1
  }
94

            
95
38589
  auto result = payload_getter.GetUInt64("iat", &iat_);
96
38589
  if (result == StructUtils::WRONG_TYPE) {
97
1
    return Status::JwtPayloadParseErrorIatNotInteger;
98
38588
  } else if (result == StructUtils::OUT_OF_RANGE) {
99
2
    return Status::JwtPayloadParseErrorIatOutOfRange;
100
2
  }
101

            
102
38586
  result = payload_getter.GetUInt64("nbf", &nbf_);
103
38586
  if (result == StructUtils::WRONG_TYPE) {
104
1
    return Status::JwtPayloadParseErrorNbfNotInteger;
105
38585
  } else if (result == StructUtils::OUT_OF_RANGE) {
106
2
    return Status::JwtPayloadParseErrorNbfOutOfRange;
107
2
  }
108

            
109
38583
  result = payload_getter.GetUInt64("exp", &exp_);
110
38583
  if (result == StructUtils::WRONG_TYPE) {
111
1
    return Status::JwtPayloadParseErrorExpNotInteger;
112
38582
  } else if (result == StructUtils::OUT_OF_RANGE) {
113
2
    return Status::JwtPayloadParseErrorExpOutOfRange;
114
2
  }
115

            
116
38580
  if (payload_getter.GetString("jti", &jti_) == StructUtils::WRONG_TYPE) {
117
1
    return Status::JwtPayloadParseErrorJtiNotString;
118
1
  }
119

            
120
  // "aud" can be either string array or string.
121
  // GetStringList function will try to read as string, if fails,
122
  // try to read as string array.
123
38579
  if (payload_getter.GetStringList("aud", &audiences_) == StructUtils::WRONG_TYPE) {
124
2
    return Status::JwtPayloadParseErrorAudNotString;
125
2
  }
126

            
127
  // Set up signature
128
38577
  if (!absl::WebSafeBase64Unescape(jwt_split[2], &signature_)) {
129
    // Signature is a bad Base64url input.
130
1
    return Status::JwtSignatureParseErrorBadBase64;
131
1
  }
132
38576
  return Status::Ok;
133
38577
}
134

            
135
38548
Status Jwt::verifyTimeConstraint(uint64_t now, uint64_t clock_skew) const {
136
  // Check Jwt is active (nbf).
137
38548
  if (now + clock_skew < nbf_) {
138
4
    return Status::JwtNotYetValid;
139
4
  }
140
  // Check JWT has not expired (exp).
141
38544
  if (exp_ && now > exp_ + clock_skew) {
142
54
    return Status::JwtExpired;
143
54
  }
144
38490
  return Status::Ok;
145
38544
}
146

            
147
} // namespace JwtVerify
148
} // namespace Envoy