summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAdrian Gonzalez <adriangonzalezmontemayor@gmail.com>2022-10-27 18:23:41 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-12-07 18:15:07 +0000
commitedf22af7c49d3649b33d5ee0c8f1332356586521 (patch)
treebbe6464e63d2806a6f2ac3b0e02b89b7f23e034f /src
parent8128da8bf941bc5f53bc9ce85c11dad030322fd7 (diff)
downloadmongo-edf22af7c49d3649b33d5ee0c8f1332356586521.tar.gz
SERVER-70954 Implement JWSValidator
Diffstat (limited to 'src')
-rw-r--r--src/mongo/base/error_codes.yml4
-rw-r--r--src/mongo/crypto/SConscript24
-rw-r--r--src/mongo/crypto/jwk_manager.cpp49
-rw-r--r--src/mongo/crypto/jwk_manager.h28
-rw-r--r--src/mongo/crypto/jws_validated_token.cpp106
-rw-r--r--src/mongo/crypto/jws_validated_token.h80
-rw-r--r--src/mongo/crypto/jws_validated_token_test.cpp183
-rw-r--r--src/mongo/crypto/jws_validator.h60
-rw-r--r--src/mongo/crypto/jws_validator_apple.cpp40
-rw-r--r--src/mongo/crypto/jws_validator_none.cpp40
-rw-r--r--src/mongo/crypto/jws_validator_openssl.cpp166
-rw-r--r--src/mongo/crypto/jws_validator_test.cpp171
-rw-r--r--src/mongo/crypto/jws_validator_windows.cpp40
-rw-r--r--src/mongo/crypto/jwt_test.cpp4
-rw-r--r--src/mongo/crypto/jwt_types.idl58
15 files changed, 1032 insertions, 21 deletions
diff --git a/src/mongo/base/error_codes.yml b/src/mongo/base/error_codes.yml
index 01243f93ea5..de82197cb07 100644
--- a/src/mongo/base/error_codes.yml
+++ b/src/mongo/base/error_codes.yml
@@ -505,10 +505,12 @@ error_codes:
- {code: 386, name: DuplicateKeyId}
- {code: 387, name: EncounteredFLEPayloadWhileRedacting}
-
+
- {code: 388, name: TransactionTooLargeForCache}
- {code: 389, name: LibmongocryptError}
+ - {code: 390, name: InvalidSignature}
+
# Error codes 4000-8999 are reserved.
# Non-sequential error codes for compatibility only)
diff --git a/src/mongo/crypto/SConscript b/src/mongo/crypto/SConscript
index 59c8955dd9b..71bfd40a48b 100644
--- a/src/mongo/crypto/SConscript
+++ b/src/mongo/crypto/SConscript
@@ -138,7 +138,7 @@ env.CppUnitTest(
'aead_encryption_test.cpp',
'encryption_fields_util_test.cpp',
'encryption_fields_validation_test.cpp',
- 'jwt_test.cpp',
+ 'jwt_test.cpp' if ssl_provider == 'openssl' else [],
'fle_crypto_test.cpp',
'fle_crypto_test_vectors.cpp',
'mechanism_scram_test.cpp',
@@ -150,7 +150,7 @@ env.CppUnitTest(
LIBDEPS=[
'$BUILD_DIR/mongo/base',
'$BUILD_DIR/mongo/base/secure_allocator',
- '$BUILD_DIR/mongo/util/net/openssl_init' if ssl_provider == 'openssl' else '',
+ '$BUILD_DIR/mongo/util/net/openssl_init' if ssl_provider == 'openssl' else [],
'aead_encryption',
'encrypted_field_config',
'fle_crypto',
@@ -162,15 +162,31 @@ env.CppUnitTest(
env.Library(
target='jwt',
source=[
- 'jwt_types.idl',
'jwk_manager.cpp',
+ 'jwt_types.idl',
+ 'jws_validated_token.cpp',
+ 'jws_validator_{}.cpp'.format(ssl_provider),
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
- "$BUILD_DIR/mongo/client/sasl_client",
+ '$BUILD_DIR/mongo/client/sasl_client',
'$BUILD_DIR/mongo/util/net/http_client_impl',
],
LIBDEPS_PRIVATE=[
'$BUILD_DIR/mongo/idl/idl_parser',
],
)
+
+if ssl_provider == 'openssl':
+ env.CppUnitTest(
+ target='jws_validator_test',
+ source=[
+ 'jws_validator_test.cpp',
+ 'jws_validated_token_test.cpp',
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/util/net/openssl_init',
+ 'jwt',
+ ],
+ )
diff --git a/src/mongo/crypto/jwk_manager.cpp b/src/mongo/crypto/jwk_manager.cpp
index 5d5937c93b6..71e2619f33e 100644
--- a/src/mongo/crypto/jwk_manager.cpp
+++ b/src/mongo/crypto/jwk_manager.cpp
@@ -30,6 +30,7 @@
#include "mongo/crypto/jwk_manager.h"
#include "mongo/bson/json.h"
+#include "mongo/crypto/jws_validator.h"
#include "mongo/crypto/jwt_types_gen.h"
#include "mongo/logv2/log.h"
#include "mongo/util/base64.h"
@@ -40,6 +41,8 @@
namespace mongo::crypto {
namespace {
constexpr auto kMinKeySizeBytes = 2048 >> 3;
+using SharedValidator = std::shared_ptr<JWSValidator>;
+using SharedMap = std::map<std::string, SharedValidator>;
// Strip insignificant leading zeroes to determine the key's true size.
StringData reduceInt(StringData value) {
@@ -63,9 +66,37 @@ JWKManager::JWKManager(StringData source) : _keyURI(source) {
cdr.readInto<StringData>(&str);
BSONObj data = fromjson(str);
+ _setAndValidateKeys(data);
+}
+
+JWKManager::JWKManager(BSONObj keys) {
+ _setAndValidateKeys(keys);
+}
+
+const BSONObj& JWKManager::getKey(StringData keyId) const {
+ auto it = _keyMaterial.find(keyId.toString());
+ uassert(ErrorCodes::NoSuchKey,
+ str::stream() << "Unknown key '" << keyId << "'",
+ it != _keyMaterial.end());
+ return it->second;
+}
+
+SharedValidator JWKManager::getValidator(StringData keyId) const {
+ auto it = _validators->find(keyId.toString());
+
+ // TODO: SERVER-71195, refresh keys from the endpoint and try to get the validator again.
+ // If still no key is found throw a uassert.
+ uassert(ErrorCodes::NoSuchKey,
+ str::stream() << "Unknown key '" << keyId << "'",
+ it != _validators->end());
+ return it->second;
+}
+
+void JWKManager::_setAndValidateKeys(const BSONObj& keys) {
+ _validators = std::make_shared<SharedMap>();
+ auto keysParsed = JWKSet::parse(IDLParserContext("JWKSet"), keys);
- auto keys = JWKSet::parse(IDLParserContext("JWKSet"), data);
- for (const auto& key : keys.getKeys()) {
+ for (const auto& key : keysParsed.getKeys()) {
auto JWK = JWK::parse(IDLParserContext("JWK"), key);
uassert(ErrorCodes::BadValue,
str::stream() << "Only RSA key types are accepted at this time",
@@ -95,15 +126,13 @@ JWKManager::JWKManager(StringData source) : _keyURI(source) {
LOGV2_DEBUG(6766000, 5, "Loaded JWK Key", "kid"_attr = RSAKey.getKeyId());
_keyMaterial.insert({keyId, key.copy()});
- }
-}
-const BSONObj& JWKManager::getKey(StringData keyId) const {
- auto it = _keyMaterial.find(keyId.toString());
- uassert(ErrorCodes::NoSuchKey,
- str::stream() << "Unknown key '" << keyId << "'",
- it != _keyMaterial.end());
- return it->second;
+ auto swValidator = JWSValidator::create(JWK.getType(), key);
+ uassertStatusOK(swValidator.getStatus());
+ SharedValidator shValidator = std::move(swValidator.getValue());
+
+ _validators->insert({keyId, shValidator});
+ }
}
} // namespace mongo::crypto
diff --git a/src/mongo/crypto/jwk_manager.h b/src/mongo/crypto/jwk_manager.h
index b4728d817e8..47aa8fc753a 100644
--- a/src/mongo/crypto/jwk_manager.h
+++ b/src/mongo/crypto/jwk_manager.h
@@ -34,12 +34,15 @@
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonobj.h"
+#include "mongo/crypto/jws_validator.h"
+
namespace mongo::crypto {
class JWKManager {
public:
- JWKManager() = default;
+ using SharedValidator = std::shared_ptr<JWSValidator>;
+
/**
Fetch a JWKS file from the specified URL, parse them as keys,
and instantiate JWSValidator instances.
@@ -47,15 +50,38 @@ public:
explicit JWKManager(StringData source);
/**
+ Parse a BSONObj array of keys, and instantiate JWSValidator instances.
+ This was added for testing purposes.
+ */
+ explicit JWKManager(BSONObj keys);
+
+ /**
Given a unique keyId it will return the matching JWK.
If no key is found for the given keyId, a uassert will be thrown.
*/
const BSONObj& getKey(StringData keyId) const;
+ /**
+ * Fetch a specific JWSValidator from the JWKManager by keyId.
+ * If the keyId does not exist,
+ * a rate-limited refresh of the JWK endpoint will be
+ * triggered.
+ * If the keyId still does not exist, return ptr will be empty.
+ */
+ SharedValidator getValidator(StringData keyId) const;
+
+ std::size_t size() const {
+ return _validators->size();
+ }
+
+private:
+ void _setAndValidateKeys(const BSONObj& keys);
+
private:
// Map<keyId, JWKRSA>
std::map<std::string, BSONObj> _keyMaterial; // From last load
std::string _keyURI;
+ std::shared_ptr<std::map<std::string, SharedValidator>> _validators;
};
} // namespace mongo::crypto
diff --git a/src/mongo/crypto/jws_validated_token.cpp b/src/mongo/crypto/jws_validated_token.cpp
new file mode 100644
index 00000000000..ba5b4fce3c2
--- /dev/null
+++ b/src/mongo/crypto/jws_validated_token.cpp
@@ -0,0 +1,106 @@
+/**
+ * Copyright (C) 2022-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/crypto/jws_validated_token.h"
+
+#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/bson/json.h"
+#include "mongo/crypto/jws_validator.h"
+#include "mongo/db/basic_types_gen.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/base64.h"
+
+namespace mongo::crypto {
+namespace {
+struct ParsedToken {
+ StringData token[3];
+ StringData payload;
+};
+
+// Split "header.body.signature" into {"header", "body", "signature", "header.body"}
+ParsedToken parseSignedToken(StringData token) {
+ ParsedToken pt;
+ std::size_t split, pos = 0;
+ for (int i = 0; i < 3; ++i) {
+ split = token.find('.', pos);
+ pt.token[i] = token.substr(pos, split - pos);
+ pos = split + 1;
+
+ if (i == 1) {
+ // Payload: encoded header + '.' + encoded body
+ pt.payload = token.substr(0, split);
+ }
+ }
+
+ uassert(7095400, "Unknown format of token", split == std::string::npos);
+ return pt;
+}
+} // namespace
+
+Status JWSValidatedToken::validate(const JWKManager& keyMgr) const {
+ const auto nowEpoch = Date_t::now().toMillisSinceEpoch() / 1000;
+ if (_body.getExpirationEpoch() < nowEpoch) {
+ return Status{ErrorCodes::BadValue, "Token is expired"};
+ }
+
+ if (_body.getIssuedAtEpoch() > nowEpoch) {
+ return Status{ErrorCodes::BadValue, "Token not yet valid"};
+ }
+
+ auto tokenSplit = parseSignedToken(_originalToken);
+ auto signature = base64url::decode(tokenSplit.token[2]);
+ auto payload = tokenSplit.payload;
+
+ auto sValidator = keyMgr.getValidator(_header.getKeyId());
+ if (!sValidator) {
+ return Status{ErrorCodes::BadValue,
+ str::stream() << "Unknown JWT keyId << '" << _header.getKeyId() << "'"};
+ }
+
+ return sValidator.get()->validate(_header.getAlgorithm(), payload, signature);
+}
+
+JWSValidatedToken::JWSValidatedToken(const JWKManager& keyMgr, StringData token)
+ : _originalToken(token.toString()) {
+ auto tokenSplit = parseSignedToken(token);
+
+ auto headerString = base64url::decode(tokenSplit.token[0]);
+ _headerBSON = fromjson(headerString);
+ _header = JWSHeader::parse(IDLParserContext("JWSHeader"), _headerBSON);
+ uassert(7095401, "Unknown type of token", !_header.getType() || _header.getType() == "JWT"_sd);
+
+ auto bodyString = base64url::decode(tokenSplit.token[1]);
+ _bodyBSON = fromjson(bodyString);
+ _body = JWT::parse(IDLParserContext("JWT"), _bodyBSON);
+
+ uassertStatusOK(validate(keyMgr));
+};
+
+} // namespace mongo::crypto
diff --git a/src/mongo/crypto/jws_validated_token.h b/src/mongo/crypto/jws_validated_token.h
new file mode 100644
index 00000000000..61493978763
--- /dev/null
+++ b/src/mongo/crypto/jws_validated_token.h
@@ -0,0 +1,80 @@
+/**
+ * Copyright (C) 2022-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/crypto/jwk_manager.h"
+#include "mongo/crypto/jwt_types_gen.h"
+
+namespace mongo::crypto {
+
+class JWSValidatedToken {
+public:
+ JWSValidatedToken() = delete;
+
+ /**
+ * Constructs an instance by parsing a JWS Compact Serialization,
+ * extracting key name and signature type from the header,
+ * and selecting an appropriate key from the key manager.
+ * Upon completion, header and body payloads
+ * and parsed structs are available.
+ */
+ JWSValidatedToken(const JWKManager& keyMgr, StringData token);
+
+ /**
+ * Validates token is not expired or issued on a later date,
+ * verifies it has a validator matching its keyId and finally
+ * it calls validate from the validator, returning the status.
+ */
+ Status validate(const JWKManager& keyMgr) const;
+
+ // General read-only accessors.
+ const BSONObj& getHeaderBSON() const {
+ return _headerBSON;
+ };
+ const JWSHeader& getHeader() const {
+ return _header;
+ };
+ const BSONObj& getBodyBSON() const {
+ return _bodyBSON;
+ };
+ const JWT& getBody() const {
+ return _body;
+ };
+
+private:
+ BSONObj _headerBSON;
+ JWSHeader _header;
+ BSONObj _bodyBSON;
+ JWT _body;
+ std::string _originalToken;
+};
+
+} // namespace mongo::crypto
diff --git a/src/mongo/crypto/jws_validated_token_test.cpp b/src/mongo/crypto/jws_validated_token_test.cpp
new file mode 100644
index 00000000000..84c67e3461b
--- /dev/null
+++ b/src/mongo/crypto/jws_validated_token_test.cpp
@@ -0,0 +1,183 @@
+/**
+ * Copyright (C) 2022-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/crypto/jws_validated_token.h"
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/bson/json.h"
+#include "mongo/config.h"
+#include "mongo/crypto/jwk_manager.h"
+#include "mongo/crypto/jws_validator.h"
+#include "mongo/unittest/assert.h"
+#include "mongo/unittest/bson_test_util.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/base64.h"
+
+#if MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_OPENSSL
+
+namespace mongo::crypto {
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER >= 0x2070000fL)
+
+// Serialization Header: { typ: 'JWT', alg: 'RS256', kid: 'custom-key-1' }
+constexpr auto modifiedTokenHeader =
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImN1c3RvbS1rZXktMSJ9";
+
+// Serialization Header: { typ: 'JWT', alg: 'RS256', kid: 'custom-key-2' }
+// Serialization Body: { iss: "JWSCompactParserTest", sub: "jwsParserTest1",
+// iat: 1661374077, exp: 2147483647,
+// aud: ["jwt@kernel.mongodb.com"],
+// nonce: "gdfhjj324ehj23k4", auth_time: 1661374077 }
+// Expires 01/18/2038
+constexpr auto validTokenHeader =
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImN1c3RvbS1rZXktMiJ9";
+constexpr auto validTokenBody =
+ "eyJpc3MiOiJKV1NDb21wYWN0UGFyc2VyVGVzdCIsInN1YiI6Imp3c1BhcnNlclRlc3QxIiwiaWF0IjoxNjYxMzc0MDc3LC"
+ "JleHAiOjIxNDc0ODM2NDcsImF1ZCI6WyJqd3RAa2VybmVsLm1vbmdvZGIuY29tIl0sIm5vbmNlIjoiZ2RmaGpqMzI0ZWhq"
+ "MjNrNCIsImF1dGhfdGltZSI6MTY2MTM3NDA3N30";
+constexpr auto validTokenSignature =
+ "E6wxDxFrxpzt-zxjhTbtslT_T5UlMMZDfqxnoIyeDBb1d7VD9ced_"
+ "yH192qfldjuR8Q4Wv5YLkjMTQ8KNXIjN313EAomd2jBxHo9zHgXd9jenVIWxF7WLI4hqWZYaO630bhoRFeQYIF4J-"
+ "7fgJ9xQEJTWWi8peqXpYaCUcw2rEP-vA0oPfJhTIY67DLaTwPUExQ37kNn58Ei0ey4VWokGeY16aeyLVI-aLbh_xzwt_"
+ "DEPq4Ifjj1mab4hg1m7QYfKFpezoldmC-"
+ "0WJqqae9IhucUYK4T1nrAR4PBQreunIutajv0j8kMu3Mb7fBdFrfxhAzm7oeCrwPEIHRk-rDsiw";
+
+// Serialization Header: { typ: 'JWT', alg: 'RS256', kid: 'custom-key-2' }
+// Serialization Body: { iss: "JWSCompactParserTest", sub: "jwsParserTest1",
+// iat: 1661374077, exp: 1661374677,
+// aud: ["jwt@kernel.mongodb.com"],
+// nonce: "gdfhjj324ehj23k4", auth_time: 1661374077 }
+constexpr auto expiredTokenHeader =
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImN1c3RvbS1rZXktMiJ9";
+constexpr auto expiredTokenBody =
+ "eyJpc3MiOiJKV1NDb21wYWN0UGFyc2VyVGVzdCIsInN1YiI6Imp3c1BhcnNlclRlc3QxIiwiaWF0IjoxNjYxMzc0MDc3LC"
+ "JleHAiOjE2NjEzNzQ2NzcsImF1ZCI6WyJqd3RAa2VybmVsLm1vbmdvZGIuY29tIl0sIm5vbmNlIjoiZ2RmaGpqMzI0ZWhq"
+ "MjNrNCIsImF1dGhfdGltZSI6MTY2MTM3NDA3N30";
+constexpr auto expiredTokenSignature =
+ "xNcutFSVjDdHQ2U1xZMb6eghjNXrozJrtWl58dk-bJhatqMotbF1OecurgqQPru2KOhY_IT4rba0F1m403Pp10WiC5-"
+ "zpyhElKiBktB7U3XamVZYKDFPpr6iFGxlfBEwzbA39Y6akjEqFExhQ0wr3kR4oqVGiCoG8prPWV39-"
+ "MUpgtWg8XaJK65wK3jmEHWfr2QE5mLNpLQBzifBKqhXCqR69VWyFm9FSKyYXLMgk-yH3mIFNBdZxutvWg_PZFECwdcjl-"
+ "rtZX-VvzXUtNZl7Dcnn7PbtOEmSISpCdd797we4iwfAHduf5tUykiYn7_NwHD_fxCyfI8HgtRJ9VmVEQ";
+
+BSONObj getTestJWKSet() {
+ BSONObjBuilder set;
+ BSONArrayBuilder keys(set.subarrayStart("keys"_sd));
+
+ {
+ BSONObjBuilder key(keys.subobjStart());
+ key.append("kty", "RSA");
+ key.append("kid", "custom-key-1");
+ key.append("e", "AQAB");
+ key.append(
+ "n",
+ "ALtUlNS31SzxwqMzMR9jKOJYDhHj8zZtLUYHi3s1en3wLdILp1Uy8O6Jy0Z66tPyM1u8lke0JK5gS-40yhJ-"
+ "bvqioW8CnwbLSLPmzGNmZKdfIJ08Si8aEtrRXMxpDyz4Is7JLnpjIIUZ4lmqC3MnoZHd6qhhJb1v1Qy-"
+ "QGlk4NJy1ZI0aPc_uNEUM7lWhPAJABZsWc6MN8flSWCnY8pJCdIk_cAktA0U17tuvVduuFX_"
+ "94763nWYikZIMJS_cTQMMVxYNMf1xcNNOVFlUSJHYHClk46QT9nT8FWeFlgvvWhlXfhsp9aNAi3pX-"
+ "KxIxqF2wABIAKnhlMa3CJW41323Js");
+ key.doneFast();
+ }
+ {
+ BSONObjBuilder key(keys.subobjStart());
+ key.append("kty", "RSA");
+ key.append("kid", "custom-key-2");
+ key.append("e", "AQAB");
+ key.append(
+ "n",
+ "4Amo26gLJITvt62AXI7z224KfvfQjwpyREjtpA2DU2mN7pnlz-"
+ "ZDu0sygwkhGcAkRPVbzpEiliXtVo2dYN4vMKLSd5BVBXhtB41bZ6OUxni48uP5txm7w8BUWv8MxzPkzyW_"
+ "3dd8rOfzECdLCF5G3aA4u_XRu2ODUSAMcrxXngnNtAuC-"
+ "OdqgYmvZfgFwqbU0VKNR4bbkhSrw6p9Tct6CUW04Ml4HMacZUovJKXRvNqnHcx3sy4PtVe3CyKlbb4KhBtkj1U"
+ "U_"
+ "cwiosz8uboBbchp7wsATieGVF8x3BUtf0ry94BGYXKbCGY_Mq-TSxcM_3afZiJA1COVZWN7d4GTEw");
+ key.doneFast();
+ }
+
+ keys.doneFast();
+ return set.obj();
+}
+
+TEST(JWKManager, validateTokenFromKeys) {
+ auto validToken = validTokenHeader + "."_sd + validTokenBody + "."_sd + validTokenSignature;
+ BSONObj keys = getTestJWKSet();
+
+ JWKManager manager(keys);
+ JWSValidatedToken validatedToken(manager, validToken);
+
+ auto headerString = base64url::decode(validTokenHeader);
+ BSONObj headerBSON = fromjson(headerString);
+ auto header = JWSHeader::parse(IDLParserContext("JWTHeader"), headerBSON);
+
+ ASSERT_BSONOBJ_EQ(validatedToken.getHeaderBSON(), headerBSON);
+ ASSERT_BSONOBJ_EQ(validatedToken.getHeader().toBSON(), header.toBSON());
+
+ auto bodyString = base64url::decode(validTokenBody);
+ BSONObj bodyBSON = fromjson(bodyString);
+ auto body = JWT::parse(IDLParserContext("JWT"), bodyBSON);
+
+ ASSERT_BSONOBJ_EQ(validatedToken.getBodyBSON(), bodyBSON);
+ ASSERT_BSONOBJ_EQ(validatedToken.getBody().toBSON(), body.toBSON());
+}
+
+TEST(JWKManager, failsWithExpiredToken) {
+ auto expiredToken =
+ expiredTokenHeader + "."_sd + expiredTokenBody + "."_sd + expiredTokenSignature;
+ BSONObj keys = getTestJWKSet();
+
+ JWKManager manager(keys);
+ ASSERT_THROWS(JWSValidatedToken(manager, expiredToken), DBException);
+}
+
+TEST(JWKManager, failsWithModifiedToken) {
+ auto modifiedToken =
+ validTokenHeader + "."_sd + validTokenBody + "."_sd + validTokenSignature + "a"_sd;
+ BSONObj keys = getTestJWKSet();
+
+ JWKManager manager(keys);
+ ASSERT_THROWS(JWSValidatedToken(manager, modifiedToken), DBException);
+}
+
+TEST(JWKManager, failsWithModifiedHeaderForADifferentKey) {
+ auto modifiedToken =
+ modifiedTokenHeader + "."_sd + validTokenBody + "."_sd + validTokenSignature;
+ BSONObj keys = getTestJWKSet();
+
+ JWKManager manager(keys);
+ ASSERT_THROWS(JWSValidatedToken(manager, modifiedToken), DBException);
+}
+#endif
+} // namespace mongo::crypto
+
+#endif
diff --git a/src/mongo/crypto/jws_validator.h b/src/mongo/crypto/jws_validator.h
new file mode 100644
index 00000000000..3bb599d4643
--- /dev/null
+++ b/src/mongo/crypto/jws_validator.h
@@ -0,0 +1,60 @@
+/**
+ * Copyright (C) 2022-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/base/status.h"
+#include "mongo/base/status_with.h"
+
+namespace mongo::crypto {
+
+class JWSValidator {
+public:
+ virtual ~JWSValidator() = default;
+
+ /**
+ * Validate the provided payload/signature pair
+ * using the algorithm provided.
+ * In practice, this will be:
+ * validate('RS256', 'headerb64url.bodyb64url', 'signatureoctets')
+ */
+ virtual Status validate(StringData algorithm,
+ StringData payload,
+ StringData signature) const = 0;
+
+ /**
+ * Instantiate a new JWSValidator for the given
+ * key type (e.g. 'RSA') and JWK key data.
+ * Returns, for example, JWSValidatorOpenSSLRSA
+ */
+ static StatusWith<std::unique_ptr<JWSValidator>> create(StringData algorithm,
+ const BSONObj& key);
+};
+
+} // namespace mongo::crypto
diff --git a/src/mongo/crypto/jws_validator_apple.cpp b/src/mongo/crypto/jws_validator_apple.cpp
new file mode 100644
index 00000000000..5cf05bc777b
--- /dev/null
+++ b/src/mongo/crypto/jws_validator_apple.cpp
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2022-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/base/error_codes.h"
+#include "mongo/crypto/jws_validator.h"
+
+namespace mongo::crypto {
+
+// TODO: SERVER-68518, remove or implement this class
+StatusWith<std::unique_ptr<JWSValidator>> JWSValidator::create(StringData algorithm,
+ const BSONObj& key) {
+ return {ErrorCodes::OperationFailed, "Signature Verification Not Available"};
+}
+} // namespace mongo::crypto
diff --git a/src/mongo/crypto/jws_validator_none.cpp b/src/mongo/crypto/jws_validator_none.cpp
new file mode 100644
index 00000000000..5cf05bc777b
--- /dev/null
+++ b/src/mongo/crypto/jws_validator_none.cpp
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2022-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/base/error_codes.h"
+#include "mongo/crypto/jws_validator.h"
+
+namespace mongo::crypto {
+
+// TODO: SERVER-68518, remove or implement this class
+StatusWith<std::unique_ptr<JWSValidator>> JWSValidator::create(StringData algorithm,
+ const BSONObj& key) {
+ return {ErrorCodes::OperationFailed, "Signature Verification Not Available"};
+}
+} // namespace mongo::crypto
diff --git a/src/mongo/crypto/jws_validator_openssl.cpp b/src/mongo/crypto/jws_validator_openssl.cpp
new file mode 100644
index 00000000000..ab2f43d34bb
--- /dev/null
+++ b/src/mongo/crypto/jws_validator_openssl.cpp
@@ -0,0 +1,166 @@
+/**
+ * Copyright (C) 2022-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/base/status.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/crypto/jws_validator.h"
+#include "mongo/crypto/jwt_types_gen.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/net/ssl_manager.h"
+
+#include <openssl/bn.h>
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+namespace {
+// Copies of OpenSSL 1.1.0 and later define new EVP digest routines. We must
+// polyfill used definitions to interact with older OpenSSL versions.
+EVP_MD_CTX* EVP_MD_CTX_new() {
+ return EVP_MD_CTX_create();
+}
+
+void EVP_MD_CTX_free(EVP_MD_CTX* ctx) {
+ EVP_MD_CTX_destroy(ctx);
+}
+
+int RSA_set0_key(RSA* r, BIGNUM* n, BIGNUM* e, BIGNUM* d) {
+ /* If the fields n and e in r are NULL, the corresponding input
+ * parameters MUST be non-NULL for n and e. d may be
+ * left NULL (in case only the public key is used).
+ */
+ if ((r->n == NULL && n == NULL) || (r->e == NULL && e == NULL))
+ return 0;
+
+ if (n != NULL) {
+ BN_free(r->n);
+ r->n = n;
+ }
+ if (e != NULL) {
+ BN_free(r->e);
+ r->e = e;
+ }
+ if (d != NULL) {
+ BN_free(r->d);
+ r->d = d;
+ }
+
+ return 1;
+}
+
+} // namespace
+#endif
+
+namespace mongo::crypto {
+namespace {
+
+using UniqueRSA = std::unique_ptr<RSA, OpenSSLDeleter<decltype(RSA_free), RSA_free>>;
+using UniqueEVPPKey =
+ std::unique_ptr<EVP_PKEY, OpenSSLDeleter<decltype(EVP_PKEY_free), EVP_PKEY_free>>;
+using UniqueBIGNUM = std::unique_ptr<BIGNUM, OpenSSLDeleter<decltype(BN_free), BN_free>>;
+
+class JWSValidatorOpenSSLRSA : public JWSValidator {
+public:
+ JWSValidatorOpenSSLRSA(StringData algorithm, const BSONObj& key)
+ : _verificationCtx(EVP_MD_CTX_new()) {
+ auto RSAKey = JWKRSA::parse(IDLParserContext("JWKRSA"), key);
+
+ const auto* pubKeyNData =
+ reinterpret_cast<const unsigned char*>(RSAKey.getModulus().rawData());
+ UniqueBIGNUM n(BN_bin2bn(pubKeyNData, RSAKey.getModulus().size(), nullptr));
+ uassertOpenSSL("Failed creating modulus", n.get() != nullptr);
+
+ const auto* pubKeyEData =
+ reinterpret_cast<const unsigned char*>(RSAKey.getPublicExponent().rawData());
+ UniqueBIGNUM e(BN_bin2bn(pubKeyEData, RSAKey.getPublicExponent().size(), nullptr));
+ uassertOpenSSL("Failed creating exponent", e.get() != nullptr);
+
+ UniqueRSA rsa(RSA_new());
+ uassertOpenSSL("Failed creating RSAKey", rsa.get() != nullptr);
+ uassertOpenSSL("RSA key setup failed",
+ RSA_set0_key(rsa.get(), n.get(), e.get(), nullptr) == 1);
+ n.release(); // Now owned by rsa
+ e.release(); // Now owned by rsa
+
+ UniqueEVPPKey evpKey(EVP_PKEY_new());
+ uassertOpenSSL("Failed creating EVP_PKey", evpKey.get() != nullptr);
+ uassertOpenSSL("EVP_PKEY assignment failed",
+ EVP_PKEY_assign_RSA(evpKey.get(), rsa.get()) == 1);
+ rsa.release(); // Now owned by evpKey
+
+ uassert(7095402, "Unknown hashing algorithm", algorithm == "RSA");
+ uassertOpenSSL("DigestVerifyInit failed",
+ EVP_DigestVerifyInit(
+ _verificationCtx.get(), nullptr, EVP_sha256(), nullptr, evpKey.get()) ==
+ 1);
+ }
+
+ Status validate(StringData algorithm, StringData payload, StringData signature) const final {
+ uassert(7095403, "Unknown hashing algorithm", algorithm == "RSA" || algorithm == "RS256");
+
+ uassertOpenSSL(
+ "DigestVerifyUpdate failed",
+ EVP_DigestVerifyUpdate(_verificationCtx.get(),
+ reinterpret_cast<const unsigned char*>(payload.rawData()),
+ payload.size()) == 1);
+
+ int verifyRes =
+ EVP_DigestVerifyFinal(_verificationCtx.get(),
+ reinterpret_cast<const unsigned char*>(signature.rawData()),
+ signature.size());
+ if (verifyRes == 0) {
+ return {ErrorCodes::InvalidSignature, "OpenSSL: Signature is invalid"};
+ } else if (verifyRes != 1) {
+ return {ErrorCodes::UnknownError,
+ SSLManagerInterface::getSSLErrorMessage(ERR_get_error())};
+ }
+ return Status::OK();
+ }
+
+private:
+ std::unique_ptr<EVP_MD_CTX, OpenSSLDeleter<decltype(EVP_MD_CTX_free), ::EVP_MD_CTX_free>>
+ _verificationCtx;
+
+ static void uassertOpenSSL(StringData context, bool success) {
+ uassert(ErrorCodes::OperationFailed,
+ str::stream() << context << ": "
+ << SSLManagerInterface::getSSLErrorMessage(ERR_get_error()),
+ success);
+ }
+};
+} // namespace
+
+StatusWith<std::unique_ptr<JWSValidator>> JWSValidator::create(StringData algorithm,
+ const BSONObj& key) try {
+ return std::make_unique<JWSValidatorOpenSSLRSA>(algorithm, key);
+} catch (const DBException& e) {
+ return e.toStatus();
+}
+} // namespace mongo::crypto
diff --git a/src/mongo/crypto/jws_validator_test.cpp b/src/mongo/crypto/jws_validator_test.cpp
new file mode 100644
index 00000000000..b9c8bc4f96a
--- /dev/null
+++ b/src/mongo/crypto/jws_validator_test.cpp
@@ -0,0 +1,171 @@
+/**
+ * Copyright (C) 2022-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/crypto/jws_validator.h"
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "mongo/base/data_range.h"
+#include "mongo/base/status.h"
+#include "mongo/bson/bsontypes.h"
+#include "mongo/config.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/base64.h"
+#include "mongo/util/hex.h"
+
+#if MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_OPENSSL
+
+namespace mongo::crypto {
+
+class AsymmetricCryptoTestVectors : public unittest::Test {
+public:
+ class RSAKeySignatureVerificationVector {
+ public:
+ RSAKeySignatureVerificationVector(StringData keyID,
+ StringData e,
+ StringData n,
+ StringData msg,
+ StringData signature,
+ bool shouldPass) {
+ this->keyID = keyID.toString();
+
+ std::string strE = hexblob::decode(e);
+ std::string base64E = base64url::encode(StringData(strE.data(), strE.length()));
+ this->e = base64E;
+
+ std::string strN = hexblob::decode(n);
+ std::string base64N = base64url::encode(StringData(strN.data(), strN.length()));
+ this->n = base64N;
+
+ this->msg = hexblob::decode(msg);
+ this->signature = hexblob::decode(signature);
+ this->shouldPass = shouldPass;
+ }
+
+ std::string keyID;
+ std::string e;
+ std::string n;
+ std::string msg;
+ std::string signature;
+ bool shouldPass;
+ };
+
+ void evaluate(RSAKeySignatureVerificationVector test) {
+ std::string algorithm = "RSA";
+ BSONObjBuilder rsaKey;
+
+ rsaKey.append("kty", algorithm);
+ rsaKey.append("kid", test.keyID);
+ rsaKey.append("e", test.e);
+ rsaKey.append("n", test.n);
+
+ auto asymmetricKey = uassertStatusOK(JWSValidator::create(algorithm, rsaKey.obj()));
+ Status result = asymmetricKey->validate(
+ algorithm,
+ ConstDataRange(test.msg.data(), test.msg.length()).data(),
+ ConstDataRange(test.signature.data(), test.signature.length()).data());
+ if (test.shouldPass) {
+ ASSERT_OK(result);
+ } else {
+ ASSERT_NOT_OK(result);
+ }
+ }
+};
+
+// /**
+// * RSA test vectors are otained from FIPS 186-4 RSA:
+// https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/Digital-Signatures#rsa2vs
+// */
+
+TEST_F(AsymmetricCryptoTestVectors, RSASignatureVerificationTest1) {
+ evaluate(RSAKeySignatureVerificationVector(
+ "0UhWwyvtfIdxPvR9zCWYJB5_AM0LE2qc6RGOcI0cQjw"_sd,
+ "49d2a1"_sd,
+ "c47abacc2a84d56f3614d92fd62ed36ddde459664b9301dcd1d61781cfcc026bcb2399bee7e75681a80b7bf500e2d08ceae1c42ec0b707927f2b2fe92ae852087d25f1d260cc74905ee5f9b254ed05494a9fe06732c3680992dd6f0dc634568d11542a705f83ae96d2a49763d5fbb24398edf3702bc94bc168190166492b8671de874bb9cecb058c6c8344aa8c93754d6effcd44a41ed7de0a9dcd9144437f212b18881d042d331a4618a9e630ef9bb66305e4fdf8f0391b3b2313fe549f0189ff968b92f33c266a4bc2cffc897d1937eeb9e406f5d0eaa7a14782e76af3fce98f54ed237b4a04a4159a5f6250a296a902880204e61d891c4da29f2d65f34cbb"_sd,
+ "95123c8d1b236540b86976a11cea31f8bd4e6c54c235147d20ce722b03a6ad756fbd918c27df8ea9ce3104444c0bbe877305bc02e35535a02a58dcda306e632ad30b3dc3ce0ba97fdf46ec192965dd9cd7f4a71b02b8cba3d442646eeec4af590824ca98d74fbca934d0b6867aa1991f3040b707e806de6e66b5934f05509bea"_sd,
+ "51265d96f11ab338762891cb29bf3f1d2b3305107063f5f3245af376dfcc7027d39365de70a31db05e9e10eb6148cb7f6425f0c93c4fb0e2291adbd22c77656afc196858a11e1c670d9eeb592613e69eb4f3aa501730743ac4464486c7ae68fd509e896f63884e9424f69c1c5397959f1e52a368667a598a1fc90125273d9341295d2f8e1cc4969bf228c860e07a3546be2eeda1cde48ee94d062801fe666e4a7ae8cb9cd79262c017b081af874ff00453ca43e34efdb43fffb0bb42a4e2d32a5e5cc9e8546a221fe930250e5f5333e0efe58ffebf19369a3b8ae5a67f6a048bc9ef915bda25160729b508667ada84a0c27e7e26cf2abca413e5e4693f4a9405"_sd,
+ true));
+}
+
+TEST_F(AsymmetricCryptoTestVectors, RSASignatureVerificationTest2) {
+ evaluate(RSAKeySignatureVerificationVector(
+ "0UhWwyvtfIdxPvR9zCWYJB5_AM0LE2qc6RGOcI0cQjw"_sd,
+ "49d2a1"_sd,
+ "c47abacc2a84d56f3614d92fd62ed36ddde459664b9301dcd1d61781cfcc026bcb2399bee7e75681a80b7bf500e2d08ceae1c42ec0b707927f2b2fe92ae852087d25f1d260cc74905ee5f9b254ed05494a9fe06732c3680992dd6f0dc634568d11542a705f83ae96d2a49763d5fbb24398edf3702bc94bc168190166492b8671de874bb9cecb058c6c8344aa8c93754d6effcd44a41ed7de0a9dcd9144437f212b18881d042d331a4618a9e630ef9bb66305e4fdf8f0391b3b2313fe549f0189ff968b92f33c266a4bc2cffc897d1937eeb9e406f5d0eaa7a14782e76af3fce98f54ed237b4a04a4159a5f6250a296a902880204e61d891c4da29f2d65f34cbb"_sd,
+ "f89fd2f6c45a8b5066a651410b8e534bfec0d9a36f3e2b887457afd44dd651d1ec79274db5a455f182572fceea5e9e39c3c7c5d9e599e4fe31c37c34d253b419c3e8fb6b916aef6563f87d4c37224a456e5952698ba3d01b38945d998a795bd285d69478e3131f55117284e27b441f16095dca7ce9c5b68890b09a2bfbb010a5"_sd,
+ "ba48538708512d45c0edcac57a9b4fb637e9721f72003c60f13f5c9a36c968cef9be8f54665418141c3d9ecc02a5bf952cfc055fb51e18705e9d8850f4e1f5a344af550de84ffd0805e27e557f6aa50d2645314c64c1c71aa6bb44faf8f29ca6578e2441d4510e36052f46551df341b2dcf43f761f08b946ca0b7081dadbb88e955e820fd7f657c4dd9f4554d167dd7c9a487ed41ced2b40068098deedc951060faf7e15b1f0f80ae67ff2ee28a238d80bf72dd71c8d95c79bc156114ece8ec837573a4b66898d45b45a5eacd0b0e41447d8fa08a367f437645e50c9920b88a16bc0880147acfb9a79de9e351b3fa00b3f4e9f182f45553dffca55e393c5eab6"_sd,
+ false));
+}
+
+TEST_F(AsymmetricCryptoTestVectors, RSASignatureVerificationTest3) {
+ evaluate(RSAKeySignatureVerificationVector(
+ "0UhWwyvtfIdxPvR9zCWYJB5_AM0LE2qc6RGOcI0cQjw"_sd,
+ "49d2a1"_sd,
+ "c47abacc2a84d56f3614d92fd62ed36ddde459664b9301dcd1d61781cfcc026bcb2399bee7e75681a80b7bf500e2d08ceae1c42ec0b707927f2b2fe92ae852087d25f1d260cc74905ee5f9b254ed05494a9fe06732c3680992dd6f0dc634568d11542a705f83ae96d2a49763d5fbb24398edf3702bc94bc168190166492b8671de874bb9cecb058c6c8344aa8c93754d6effcd44a41ed7de0a9dcd9144437f212b18881d042d331a4618a9e630ef9bb66305e4fdf8f0391b3b2313fe549f0189ff968b92f33c266a4bc2cffc897d1937eeb9e406f5d0eaa7a14782e76af3fce98f54ed237b4a04a4159a5f6250a296a902880204e61d891c4da29f2d65f34cbb"_sd,
+ "915c5e4c16acfa0f49de43d6491f0060a944034475ba518572c08366a8d36c7f1e6afc11e5e4649757bf7b9da10a61d57f1d626847871d8a2948e551b54167c79de88d3ebd40a3e35809b996a53348f98a9918c7a7ec606896ed30c271e00c51953dd97aa6a8fe1cd423c3695c83fcf45120ec0a9cd1644642182b60e599a246"_sd,
+ "3d57ea5961db8fc144301ca4278f799911229d865ea3e992c7fbc4d03c6551729e26034e95dd71da312340e4051c9dd9b12f7700a821fe3b7c37785d5106350b667ac255a57c13da5842d90bcadea9e6b1f720c607d6893a2caa3c5f3c4074e914451a45380a767c291a67cac3f1cab1fbd05adc37036856a8404e7cea3654019466de449ad6e92b27254f3d25949b1b860065406455a13db7c5fe25d1af7a84cddf7792c64e16260c950d60bd86d005924148ad097c126b84947ab6e89d48f61e711d62522b6e48f16186d1339e6ab3f58c359eb24cb68043737591cd7d9390a468c0022b3b253be52f1a7fc408f84e9ffb4c34fa9e01605851d6583aa13032"_sd,
+ false));
+}
+
+TEST_F(AsymmetricCryptoTestVectors, RSASignatureVerificationTest4) {
+ evaluate(RSAKeySignatureVerificationVector(
+ "0UhWwyvtfIdxPvR9zCWYJB5_AM0LE2qc6RGOcI0cQjw"_sd,
+ "7485b2"_sd,
+ "c47abacc2a84d56f3614d92fd62ed36ddde459664b9301dcd1d61781cfcc026bcb2399bee7e75681a80b7bf500e2d08ceae1c42ec0b707927f2b2fe92ae852087d25f1d260cc74905ee5f9b254ed05494a9fe06732c3680992dd6f0dc634568d11542a705f83ae96d2a49763d5fbb24398edf3702bc94bc168190166492b8671de874bb9cecb058c6c8344aa8c93754d6effcd44a41ed7de0a9dcd9144437f212b18881d042d331a4618a9e630ef9bb66305e4fdf8f0391b3b2313fe549f0189ff968b92f33c266a4bc2cffc897d1937eeb9e406f5d0eaa7a14782e76af3fce98f54ed237b4a04a4159a5f6250a296a902880204e61d891c4da29f2d65f34cbb"_sd,
+ "3d2f0693517cffb2b724c1f30502c5359c051c1bcd88dc1dd54b89e6981009d275a813b2bf016b74d0f6ed0d91e62d0884785c9afd8fd1fb7e99246cd4005cdda71a39cb649197a996d8ad2d23fdfb6bb015f24ec3d7f88af64fb83b4b525eb06607d133eec834cf7d6c9ab817b4c0dda370459d9cfba05ad0c1adc86a909fe1"_sd,
+ "511abd82218cab344979b2887b02600d2427f1eb12ac01d97684c2a443a9272834c3f79cded07a39dbee3770dde827a74dc994b17bfd8a26d07b239d26d58c42f79d560264c31b7e1c3dddef6d7556f228c394414f4cec561c3da2686a8eebec7702f32850809a93deeb84b2a02fcdba224d2fd9efb8e056e796f49b57d56e9f3e90d0b49b08bdee93a2e12e676fb4d4fa838c5bd88eda008f1b592a72465587be0ae17d9b156b904f44a7e04d3b58d24ad67b71b0f4c699fa51639546b62b9f83597ff03d465f1bb396ae15e92d0e92e85647d5df113e2c7518d0e3ad2e7aa7dac720c98347aa151e4f37fea081dbed350cc9c93f606b38f21a3e5de6d140d2"_sd,
+ false));
+}
+
+TEST_F(AsymmetricCryptoTestVectors, RSASignatureVerificationTest5) {
+ evaluate(RSAKeySignatureVerificationVector(
+ "0UhWwyvtfIdxPvR9zCWYJB5_AM0LE2qc6RGOcI0cQjw"_sd,
+ "49d2a1"_sd,
+ "c47abacc2a84d56f3614d92fd62ed36ddde459664b9301dcd1d61781cfcc026bcb2399bee7e75681a80b7bf500e2d08ceae1c42ec0b707927f2b2fe92ae852087d25f1d260cc74905ee5f9b254ed05494a9fe06732c3680992dd6f0dc634568d11542a705f83ae96d2a49763d5fbb24398edf3702bc94bc168190166492b8671de874bb9cecb058c6c8344aa8c93754d6effcd44a41ed7de0a9dcd9144437f212b18881d042d331a4618a9e630ef9bb66305e4fdf8f0391b3b2313fe549f0189ff968b92f33c266a4bc2cffc897d1937eeb9e406f5d0eaa7a14782e76af3fce98f54ed237b4a04a4159a5f6250a296a902880204e61d891c4da29f2d65f34cbb"_sd,
+ "dffe42bfda886e1a73fe8a8dfcf71c9fb44deb054588a9bb9199d554aecce08f2ff88f2aa6f8a0fb675fb03c8e685c27432ca7c33c189bfd849d34fa7b2979ac1f57eca389632426bae0b98398ad60a3342557e14e96041c1bf4d90b46cf7ad1348322d28caf43c4f7e86c0924ae703c109ec50a84ea2a43df078c3015a52b28"_sd,
+ "8f4dd479239f2d08dc05d7d40539288b67c4d77210ecb16be76f0b1925e8b088570831e361a1ca57893135f8af64b8e2996b8d635899da4e04c68acb9b1b3813697d57da90c57f18509e0ab6705c704feb448cca5c07d258ecd884ab93f508cefdb25f2bc3061c4006099e2e33b27972c3edb0a0a33114d381c82ab506d041ff680af595ef3400a8bb6774030d2e38dd304272092bd32a553017f7bda4b998b27aa8aca12def327b1f11063a5342b0d55738183417d321c5682fc4ab64e79174216feebb989521e1e3d827647068003be34fe1d093964d28f4877c49b4065672448597a89b91919cfb55ca13836e7e6f3b3fd04f417cf1c16d9872538bf4e87a"_sd,
+ false));
+}
+
+TEST_F(AsymmetricCryptoTestVectors, RSASignatureVerificationTest6) {
+ evaluate(RSAKeySignatureVerificationVector(
+ "0UhWwyvtfIdxPvR9zCWYJB5_AM0LE2qc6RGOcI0cQjw"_sd,
+ "49d2a1"_sd,
+ "c47abacc2a84d56f3614d92fd62ed36ddde459664b9301dcd1d61781cfcc026bcb2399bee7e75681a80b7bf500e2d08ceae1c42ec0b707927f2b2fe92ae852087d25f1d260cc74905ee5f9b254ed05494a9fe06732c3680992dd6f0dc634568d11542a705f83ae96d2a49763d5fbb24398edf3702bc94bc168190166492b8671de874bb9cecb058c6c8344aa8c93754d6effcd44a41ed7de0a9dcd9144437f212b18881d042d331a4618a9e630ef9bb66305e4fdf8f0391b3b2313fe549f0189ff968b92f33c266a4bc2cffc897d1937eeb9e406f5d0eaa7a14782e76af3fce98f54ed237b4a04a4159a5f6250a296a902880204e61d891c4da29f2d65f34cbb"_sd,
+ "cfe99788f55ec6944942bd0a187d51b80fd8bd4051bd4f07c73e614eb75a8b9f997b176b2642b5f1b1877061ba9ce142c1d2a311583f072b7cbe08ed253681191c209d7b0d438fcdddc284d93d59d6dd80e48333a921dd31c9b6834f88768f8701e01102d3e8bdf074fbe0b8c93d9951f41545ef6eeb3be35530babc079f1fb3"_sd,
+ "9fd6f6107e838107f906c26cb2910704599f175b6a84db485fbc30776eb7fd53bfe20c38c537b154a3e519b662bd9fdc8e3045e21f6e5ae97d0ff6a9d8632825544525d84f99f80e3ed4e69dc5e219d59ccfbb37c23c84fe3b3e6fb22f402f94e5225c6387fdf8bcdb3508f8832908fe05771521e92234348004e8fe19a8f24bebcab9f074327c88d066bc12081748d696be6135c6aea32220ea786ebd7800e6936365ff25831c28cb6c8a59237ff84f5cf89036cff188ee0f9a6195f2b1aca2e4442af8369f1b49322fa2f891b83a14a97b60c6aeafd6c2928047affda9c8d869ff5294bb5943ad14a6d64e784d126c469d51e292b9ce33e1d8371ba5f467b3"_sd,
+ false));
+}
+
+} // namespace mongo::crypto
+
+#endif
diff --git a/src/mongo/crypto/jws_validator_windows.cpp b/src/mongo/crypto/jws_validator_windows.cpp
new file mode 100644
index 00000000000..5cf05bc777b
--- /dev/null
+++ b/src/mongo/crypto/jws_validator_windows.cpp
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2022-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/base/error_codes.h"
+#include "mongo/crypto/jws_validator.h"
+
+namespace mongo::crypto {
+
+// TODO: SERVER-68518, remove or implement this class
+StatusWith<std::unique_ptr<JWSValidator>> JWSValidator::create(StringData algorithm,
+ const BSONObj& key) {
+ return {ErrorCodes::OperationFailed, "Signature Verification Not Available"};
+}
+} // namespace mongo::crypto
diff --git a/src/mongo/crypto/jwt_test.cpp b/src/mongo/crypto/jwt_test.cpp
index 18433f7f977..41e8059bbfe 100644
--- a/src/mongo/crypto/jwt_test.cpp
+++ b/src/mongo/crypto/jwt_test.cpp
@@ -57,6 +57,10 @@ TEST(JWKManager, parseJWKSetBasicFromSource) {
auto keyFromKid = manager.getKey(key["kid"_sd].str());
ASSERT_BSONOBJ_EQ(key.Obj(), keyFromKid);
}
+
+ for (const auto& key : data["keys"_sd].Obj()) {
+ ASSERT(manager.getValidator(key["kid"_sd].str()));
+ }
}
} // namespace
diff --git a/src/mongo/crypto/jwt_types.idl b/src/mongo/crypto/jwt_types.idl
index 3256a23e396..d4f5f15e418 100644
--- a/src/mongo/crypto/jwt_types.idl
+++ b/src/mongo/crypto/jwt_types.idl
@@ -51,14 +51,14 @@ structs:
strict: false
inline_chained_structs: true
chained_structs:
- JWK: JWK
+ JWK: JWK
fields:
- n:
- description: Modulus of the RSA Key
+ n:
+ description: Modulus of the RSA Key
type: base64urlstring
cpp_name: modulus
- e:
- description: Public key component of the RSA key
+ e:
+ description: Public key component of the RSA key
type: base64urlstring
cpp_name: publicExponent
@@ -72,4 +72,52 @@ structs:
# Non-specific sub-type to accommodate future key types
type: array<object>
+ JWSHeader:
+ description: Describes the contents of a JWT token body and
+ any cryptographic operations applied to the token.
+ strict: false
+ fields:
+ typ:
+ description: The type of the token, e.g. 'JWT'.
+ type: string
+ cpp_name: type
+ optional: true
+ alg:
+ description: Algorithm used to secure the JWT, e.g. 'RS256'.
+ type: string
+ cpp_name: algorithm
+ kid:
+ description: The ID identifying the key used.
+ type: string
+ cpp_name: keyId
+ JWT:
+ description: A set of claims relating to the bearer of the token.
+ strict: false
+ fields:
+ iss:
+ description: Issuer; CS string identifying the issuing provider.
+ type: string
+ cpp_name: issuer
+ sub:
+ description: Subject; CS string identifying the subject.
+ type: string
+ cpp_name: subject
+ aud:
+ description: Audience; array of CS strings identifying
+ intended token consumer(s).
+ type:
+ variant: [string, array<string>]
+ cpp_name: audience
+ iat:
+ description: Time at which the JWT was issued. (Unix Epoch)
+ type: safeInt64
+ cpp_name: issuedAtEpoch
+ exp:
+ description: Time at which the JWT expires. (Unix Epoch)
+ type: safeInt64
+ cpp_name: expirationEpoch
+ auth_time:
+ description: Time when the authentication occurred. (Unix Epoch)
+ type: safeInt64
+ optional: true