blob: c56a39938ee7c72148fed665b49645f524393aee [file] [log] [blame]
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////
#include "tink/jwt/jwt_validator.h"
#include <algorithm>
#include <string>
#include "absl/status/status.h"
namespace crypto {
namespace tink {
namespace {
static constexpr absl::Duration kJwtMaxClockSkew = absl::Minutes(10);
}
JwtValidator::JwtValidator(const JwtValidatorBuilder& builder) {
expected_type_header_ = builder.expected_type_header_;
expected_issuer_ = builder.expected_issuer_;
expected_audience_ = builder.expected_audience_;
ignore_type_header_ = builder.ignore_type_header_;
ignore_issuer_ = builder.ignore_issuer_;
ignore_audiences_ = builder.ignore_audiences_;
allow_missing_expiration_ = builder.allow_missing_expiration_;
expect_issued_in_the_past_ = builder.expect_issued_in_the_past_;
clock_skew_ = builder.clock_skew_;
fixed_now_ = builder.fixed_now_;
}
util::Status JwtValidator::ValidateTimestamps(RawJwt const& raw_jwt) const {
absl::Time now;
if (fixed_now_.has_value()) {
now = fixed_now_.value();
} else {
now = absl::Now();
}
if (!raw_jwt.HasExpiration() && !allow_missing_expiration_) {
return util::Status(absl::StatusCode::kInvalidArgument,
"token does not have an expiration set");
}
if (raw_jwt.HasExpiration()) {
util::StatusOr<absl::Time> expiration = raw_jwt.GetExpiration();
if (!expiration.ok()) {
return expiration.status();
}
if (*expiration <= now - clock_skew_) {
return util::Status(absl::StatusCode::kInvalidArgument,
"token has expired");
}
}
if (raw_jwt.HasNotBefore()) {
util::StatusOr<absl::Time> not_before = raw_jwt.GetNotBefore();
if (!not_before.ok()) {
return not_before.status();
}
if (*not_before > now + clock_skew_) {
return util::Status(absl::StatusCode::kInvalidArgument,
"token cannot yet be used");
}
}
if (expect_issued_in_the_past_) {
util::StatusOr<absl::Time> issued_at = raw_jwt.GetIssuedAt();
if (!issued_at.ok()) {
return issued_at.status();
}
if (*issued_at > now + clock_skew_) {
return util::Status(absl::StatusCode::kInvalidArgument,
"token has an invalid iat claim in the future");
}
}
return util::OkStatus();
}
util::Status JwtValidator::ValidateTypeHeader(RawJwt const& raw_jwt) const {
if (expected_type_header_.has_value()) {
if (!raw_jwt.HasTypeHeader()) {
return util::Status(absl::StatusCode::kInvalidArgument,
"missing expected type header");
}
util::StatusOr<std::string> type_header = raw_jwt.GetTypeHeader();
if (!type_header.ok()) {
return type_header.status();
}
if (expected_type_header_.value() != *type_header) {
return util::Status(absl::StatusCode::kInvalidArgument,
"wrong type header");
}
} else {
if (raw_jwt.HasTypeHeader() && !ignore_type_header_) {
return util::Status(
absl::StatusCode::kInvalidArgument,
"invalid JWT; token has type header set, but validator not");
}
}
return util::OkStatus();
}
util::Status JwtValidator::ValidateIssuer(RawJwt const& raw_jwt) const {
if (expected_issuer_.has_value()){
if (!raw_jwt.HasIssuer()) {
return util::Status(absl::StatusCode::kInvalidArgument,
"missing expected issuer");
}
util::StatusOr<std::string> issuer = raw_jwt.GetIssuer();
if (!issuer.ok()) {
return issuer.status();
}
if (expected_issuer_.value() != *issuer) {
return util::Status(absl::StatusCode::kInvalidArgument, "wrong issuer");
}
} else {
if (raw_jwt.HasIssuer() && !ignore_issuer_) {
return util::Status(
absl::StatusCode::kInvalidArgument,
"invalid JWT; token has issuer set, but validator not");
}
}
return util::OkStatus();
}
util::Status JwtValidator::ValidateAudiences(RawJwt const& raw_jwt) const {
if (expected_audience_.has_value()) {
if (!raw_jwt.HasAudiences()) {
return util::Status(absl::StatusCode::kInvalidArgument,
"missing expected audiences");
}
util::StatusOr<std::vector<std::string>> audiences = raw_jwt.GetAudiences();
if (!audiences.ok()) {
return audiences.status();
}
auto it =
std::find(audiences->begin(), audiences->end(), expected_audience_);
if (it == audiences->end()) {
return util::Status(absl::StatusCode::kInvalidArgument,
"audience not found");
}
} else {
if (raw_jwt.HasAudiences() && !ignore_audiences_) {
return util::Status(
absl::StatusCode::kInvalidArgument,
"invalid JWT; token has audience set, but validator not");
}
}
return util::OkStatus();
}
util::Status JwtValidator::Validate(RawJwt const& raw_jwt) const {
util::Status status;
status = ValidateTimestamps(raw_jwt);
if (!status.ok()) {
return status;
}
status = ValidateTypeHeader(raw_jwt);
if (!status.ok()) {
return status;
}
status = ValidateIssuer(raw_jwt);
if (!status.ok()) {
return status;
}
status = ValidateAudiences(raw_jwt);
if (!status.ok()) {
return status;
}
return util::OkStatus();
}
JwtValidatorBuilder::JwtValidatorBuilder() {
ignore_type_header_ = false;
ignore_issuer_ = false;
ignore_audiences_ = false;
allow_missing_expiration_ = false;
expect_issued_in_the_past_ = false;
clock_skew_ = absl::ZeroDuration();
}
JwtValidatorBuilder& JwtValidatorBuilder::ExpectTypeHeader(
absl::string_view type_header) {
expected_type_header_ = std::string(type_header);
return *this;
}
JwtValidatorBuilder& JwtValidatorBuilder::ExpectIssuer(
absl::string_view issuer) {
expected_issuer_ = std::string(issuer);
return *this;
}
JwtValidatorBuilder& JwtValidatorBuilder::ExpectAudience(
absl::string_view audience) {
expected_audience_ = std::string(audience);
return *this;
}
JwtValidatorBuilder& JwtValidatorBuilder::IgnoreTypeHeader() {
ignore_type_header_ = true;
return *this;
}
JwtValidatorBuilder& JwtValidatorBuilder::IgnoreIssuer() {
ignore_issuer_ = true;
return *this;
}
JwtValidatorBuilder& JwtValidatorBuilder::IgnoreAudiences() {
ignore_audiences_ = true;
return *this;
}
JwtValidatorBuilder& JwtValidatorBuilder::AllowMissingExpiration() {
allow_missing_expiration_ = true;
return *this;
}
JwtValidatorBuilder& JwtValidatorBuilder::ExpectIssuedInThePast() {
expect_issued_in_the_past_ = true;
return *this;
}
JwtValidatorBuilder& JwtValidatorBuilder::SetClockSkew(
absl::Duration clock_skew) {
clock_skew_ = clock_skew;
return *this;
}
JwtValidatorBuilder& JwtValidatorBuilder::SetFixedNow(absl::Time fixed_now) {
fixed_now_ = fixed_now;
return *this;
}
util::StatusOr<JwtValidator> JwtValidatorBuilder::Build() {
if (expected_type_header_.has_value() && ignore_type_header_) {
return util::Status(
absl::StatusCode::kInvalidArgument,
"IgnoreTypeHeader() and ExpectTypeHeader() cannot be used together");
}
if (expected_issuer_.has_value() && ignore_issuer_) {
return util::Status(
absl::StatusCode::kInvalidArgument,
"IgnoreIssuer() and ExpectedIssuer() cannot be used together");
}
if (expected_audience_.has_value() && ignore_audiences_) {
return util::Status(
absl::StatusCode::kInvalidArgument,
"IgnoreAudiences() and ExpectAudience() cannot be used together");
}
if (clock_skew_ > kJwtMaxClockSkew) {
return util::Status(absl::StatusCode::kInvalidArgument,
"clock skew too large, max is 10 minutes");
}
JwtValidator validator(*this);
return validator;
}
} // namespace tink
} // namespace crypto