1 // Copyright 2021 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 // http://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 ///////////////////////////////////////////////////////////////////////////////
16
17 #include "tink/jwt/internal/jwt_format.h"
18
19 #include <string>
20
21 #include "absl/status/status.h"
22 #include "absl/strings/escaping.h"
23 #include "absl/strings/str_split.h"
24 #include "tink/crypto_format.h"
25 #include "tink/jwt/internal/json_util.h"
26 #include "proto/tink.pb.h"
27
28 namespace crypto {
29 namespace tink {
30 namespace jwt_internal {
31
32 using ::google::crypto::tink::OutputPrefixType;
33
34 namespace {
35
isValidUrlsafeBase64Char(char c)36 bool isValidUrlsafeBase64Char(char c) {
37 return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) ||
38 ((c >= '0') && (c <= '9')) || ((c == '-') || (c == '_')));
39 }
40
StrictWebSafeBase64Unescape(absl::string_view src,std::string * dest)41 bool StrictWebSafeBase64Unescape(absl::string_view src, std::string* dest) {
42 for (char c : src) {
43 if (!isValidUrlsafeBase64Char(c)) {
44 return false;
45 }
46 }
47 return absl::WebSafeBase64Unescape(src, dest);
48 }
49
ValidateKidInHeader(const google::protobuf::Value & kid_in_header,absl::string_view kid)50 util::Status ValidateKidInHeader(const google::protobuf::Value& kid_in_header,
51 absl::string_view kid) {
52 if (kid_in_header.kind_case() != google::protobuf::Value::kStringValue) {
53 return util::Status(absl::StatusCode::kInvalidArgument,
54 "kid header is not a string");
55 }
56 if (kid_in_header.string_value() != kid) {
57 return util::Status(absl::StatusCode::kInvalidArgument,
58 "invalid kid header");
59 }
60 return util::OkStatus();
61 }
62
63 } // namespace
64
EncodeHeader(absl::string_view json_header)65 std::string EncodeHeader(absl::string_view json_header) {
66 return absl::WebSafeBase64Escape(json_header);
67 }
68
DecodeHeader(absl::string_view header,std::string * json_header)69 bool DecodeHeader(absl::string_view header, std::string* json_header) {
70 return StrictWebSafeBase64Unescape(header, json_header);
71 }
72
GetKid(uint32_t key_id,OutputPrefixType output_prefix_type)73 absl::optional<std::string> GetKid(uint32_t key_id,
74 OutputPrefixType output_prefix_type) {
75 if (output_prefix_type != OutputPrefixType::TINK) {
76 return absl::nullopt;
77 }
78 char buffer[4];
79 absl::big_endian::Store32(buffer, key_id);
80 return absl::WebSafeBase64Escape(absl::string_view(buffer, 4));
81 }
82
GetKeyId(absl::string_view kid)83 absl::optional<uint32_t> GetKeyId(absl::string_view kid) {
84 std::string decoded_kid;
85 if (!StrictWebSafeBase64Unescape(kid, &decoded_kid)) {
86 return absl::nullopt;
87 }
88 if (decoded_kid.size() != 4) {
89 return absl::nullopt;
90 }
91
92 return absl::big_endian::Load32(decoded_kid.data());
93 }
94
CreateHeader(absl::string_view algorithm,absl::optional<absl::string_view> type_header,absl::optional<absl::string_view> kid)95 util::StatusOr<std::string> CreateHeader(
96 absl::string_view algorithm, absl::optional<absl::string_view> type_header,
97 absl::optional<absl::string_view> kid) {
98 google::protobuf::Struct header;
99 auto fields = header.mutable_fields();
100 if (kid.has_value()) {
101 google::protobuf::Value kid_value;
102 (*fields)["kid"].set_string_value(std::string(kid.value()));
103 }
104 if (type_header.has_value()) {
105 (*fields)["typ"].set_string_value(std::string(type_header.value()));
106 }
107 (*fields)["alg"].set_string_value(std::string(algorithm));
108 util::StatusOr<std::string> json_header =
109 jwt_internal::ProtoStructToJsonString(header);
110 if (!json_header.ok()) {
111 return json_header.status();
112 }
113 return EncodeHeader(*json_header);
114 }
115
ValidateHeader(const google::protobuf::Struct & header,absl::string_view algorithm,absl::optional<absl::string_view> tink_kid,absl::optional<absl::string_view> custom_kid)116 util::Status ValidateHeader(const google::protobuf::Struct& header,
117 absl::string_view algorithm,
118 absl::optional<absl::string_view> tink_kid,
119 absl::optional<absl::string_view> custom_kid) {
120 auto fields = header.fields();
121 auto it = fields.find("alg");
122 if (it == fields.end()) {
123 return util::Status(absl::StatusCode::kInvalidArgument,
124 "header is missing alg");
125 }
126 const google::protobuf::Value& alg = it->second;
127 if (alg.kind_case() != google::protobuf::Value::kStringValue) {
128 return util::Status(absl::StatusCode::kInvalidArgument,
129 "alg is not a string");
130 }
131 if (alg.string_value() != algorithm) {
132 return util::Status(absl::StatusCode::kInvalidArgument, "invalid alg");
133 }
134 if (fields.find("crit") != fields.end()) {
135 return util::Status(absl::StatusCode::kInvalidArgument,
136 "all tokens with crit headers are rejected");
137 }
138
139 if (tink_kid.has_value() && custom_kid.has_value()) {
140 return util::Status(absl::StatusCode::kInvalidArgument,
141 "custom_kid can only be set for RAW keys");
142 }
143 auto kid_it = fields.find("kid");
144 bool header_has_kid = (kid_it != fields.end());
145 if (tink_kid.has_value()) {
146 if (!header_has_kid) {
147 // for output prefix type TINK, the kid header is required.
148 return util::Status(absl::StatusCode::kInvalidArgument,
149 "missing kid in header");
150 }
151 util::Status status = ValidateKidInHeader(kid_it->second, *tink_kid);
152 if (!status.ok()) {
153 return status;
154 }
155 }
156 if (custom_kid.has_value() && header_has_kid) {
157 util::Status status = ValidateKidInHeader(kid_it->second, *custom_kid);
158 if (!status.ok()) {
159 return status;
160 }
161 }
162 return util::OkStatus();
163 }
164
GetTypeHeader(const google::protobuf::Struct & header)165 absl::optional<std::string> GetTypeHeader(
166 const google::protobuf::Struct& header) {
167 auto it = header.fields().find("typ");
168 if (it == header.fields().end()) {
169 return absl::nullopt;
170 }
171 const auto& value = it->second;
172 if (value.kind_case() != google::protobuf::Value::kStringValue) {
173 return absl::nullopt;
174 }
175 return value.string_value();
176 }
177
EncodePayload(absl::string_view json_payload)178 std::string EncodePayload(absl::string_view json_payload) {
179 return absl::WebSafeBase64Escape(json_payload);
180 }
181
DecodePayload(absl::string_view payload,std::string * json_payload)182 bool DecodePayload(absl::string_view payload, std::string* json_payload) {
183 return StrictWebSafeBase64Unescape(payload, json_payload);
184 }
185
EncodeSignature(absl::string_view signature)186 std::string EncodeSignature(absl::string_view signature) {
187 return absl::WebSafeBase64Escape(signature);
188 }
189
DecodeSignature(absl::string_view encoded_signature,std::string * signature)190 bool DecodeSignature(absl::string_view encoded_signature,
191 std::string* signature) {
192 return StrictWebSafeBase64Unescape(encoded_signature, signature);
193 }
194
FromJson(absl::optional<std::string> type_header,absl::string_view json_payload)195 util::StatusOr<RawJwt> RawJwtParser::FromJson(
196 absl::optional<std::string> type_header, absl::string_view json_payload) {
197 return RawJwt::FromJson(type_header, json_payload);
198 }
199
200 } // namespace jwt_internal
201 } // namespace tink
202 } // namespace crypto
203