SERVER-94767 Expand testing of Certificate Revocation Lists (#27819)

GitOrigin-RevId: bacc7b90914150c845321184ad2ea66d17066bec
This commit is contained in:
Erwin Pe 2024-10-07 16:38:43 -04:00 committed by MongoDB Bot
parent 8040ebd63e
commit 8236b73d96
11 changed files with 286 additions and 12 deletions

View File

@ -0,0 +1,12 @@
-----BEGIN X509 CRL-----
MIIBvTCBpjANBgkqhkiG9w0BAQsFADB3MQswCQYDVQQGEwJVUzERMA8GA1UECAwI
TmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAOBgNVBAoMB01vbmdv
REIxDzANBgNVBAsMBktlcm5lbDEaMBgGA1UEAwwRSW50ZXJtZWRpYXRlIENBIEIX
DTI0MTAwNDE1MzM1NVoXDTI3MDEwNTE1MzM1NVowDQYJKoZIhvcNAQELBQADggEB
ALHascvQiwxAIYAMuE2kP/SnEErR7uMxVteHN8HNUbfAX5k070mnGKlixsJ0I26Q
/ZcERRIGFZRrpp0u4m1Sak6Kb1oGjrM91d+8N4szjgj4FSFD07siY6kuIbYVet6M
ALR+Ww5uOqhOv54di1xvsETvuLOakvV7BTy2jv13SvC7+59EzamVBXUNgWUI8DX9
tpew9KH7JXYzvjMvVbdr0QfoH/PYDKZmlK7vPG14VpBc4BvO6jpclKjmvOxO7rhA
+11Vp8Bij3IiHLTZtvofk7Fn+HiI5Zd5sLPHLQjRWWDt6g0FmuIqebJJTMGzzFtv
bIDCUryP2KJ0BsxQG/5Rumc=
-----END X509 CRL-----

View File

@ -0,0 +1 @@
0A41FB5894DAB67FB8D5B57506FE35DE926BE5B9

View File

@ -0,0 +1 @@
5A78E0DB3DA5C54F97E83B1C041095EF36DFCBCBC7B6BEB1B28986CD8268ADDD

View File

@ -0,0 +1,12 @@
-----BEGIN X509 CRL-----
MIIBwjCBqzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzERMA8GA1UECAwI
TmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAOBgNVBAoMB01vbmdv
REIxDzANBgNVBAsMBktlcm5lbDEfMB0GA1UEAwwWVHJ1c3RlZCBLZXJuZWwgVGVz
dCBDQRcNMjQxMDA0MTUzMzU1WhcNMjcwMTA1MTUzMzU1WjANBgkqhkiG9w0BAQsF
AAOCAQEAJ+Tz2mzrlYMH0C6csdRwpG3xAMzvn5nFOuWdHDDWbtBKGYx0Jsds1fc1
ccCthhYAvEFzCBCjiNm5VVplMGaSkxGZ+yHqXjssDslD7KDQ/4zj2nGme+p1kXHE
ai5Q+gvHY1dJ4wDqtELQugDy737XHnPm98DqLuZjD31PhdOJt/NaO93L6A1kFrvQ
fpL9XuCumhk1z7kCZiU/4ysjBshWAkx+9vGwlcxXkwq2zEYulf4FqsV2hShklp7z
AdI2AQPkWov47h08XDWRbC/aQnCJyC3UXn/pqzwPtEH1gBE/ry2Fv0OUxrvHfB2q
ACDic7GrdkKOrE8/m7RJPhsmd8VwTQ==
-----END X509 CRL-----

View File

@ -0,0 +1 @@
520F782BCF4CF6DB791C0420A71E4C9127F59039

View File

@ -0,0 +1 @@
42CA4384684DCDFC82D9A22BAA5485661E0849F00E42E8E1CEE6F83A96D57A4C

View File

@ -0,0 +1,12 @@
-----BEGIN X509 CRL-----
MIIB0zCBvDANBgkqhkiG9w0BAQsFADB0MQswCQYDVQQGEwJVUzERMA8GA1UECAwI
TmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAOBgNVBAoMB01vbmdv
REIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwOS2VybmVsIFRlc3QgQ0EXDTI0
MTAwMTE3MDQwMVoXDTI3MDEwMjE3MDQwMVowFzAVAgQQP6lYFw0yNDEwMDExNzA0
MDFaMA0GCSqGSIb3DQEBCwUAA4IBAQC6XLhZfKBhniiRGvZ5W6OI5IjW436WWzoA
tfF3uheYqSL6CSfHKzmHBUcdQHv63qOgUTW6axRZmTY/7nTFi/XU93ICAdQaqSo6
Hb/8XkgzAP6uElH/e9UA3H6EN8Htr/FqcszcRsn0DhZzVgt4lGjZ3sZOKyIXfH+X
4/SkHR5vTAl3WsjhsXv1Vgcpa4K8FXocm27nTzByu3/nAM7dVg5Tbv4NZ43aChRD
e5Hbaha5IO/vOR/p+JqzLcztjMibVWE49kTu83YhJgBmXHmMVvxu6Or3DWBP31S6
8WpuVZgFDJ6WIEQo2jRwSQxVXf+NXu18/U6Ky6akCR/mpzHNRbPj
-----END X509 CRL-----

View File

@ -0,0 +1 @@
EB6AC7435EEC30A81FBCD1D9964245F0F24EE089

View File

@ -0,0 +1 @@
2928A1FCB9268902A63005544ADAC00A228B008C21703E116145524E34DB9143

View File

@ -4,12 +4,26 @@ set -e
# This script uses the openssl command line tool to create CRLs.
OUTPUT_PATH="jstests/libs/"
CA_PEM_PATH="${OUTPUT_PATH}/ca.pem"
TRUSTED_CA_PEM_PATH="${OUTPUT_PATH}/trusted-ca.pem"
die () {
[ $# -gt 0 ] && [ ! -z "$1" ] && echo "$1" >&2
exit 1
}
# Usage:
# crl <ca_pem_file> <output_crl_file> {empty|expired|revoked} [pem_file_to_revoke]
crl() {
[ $# -lt 3 ] && die "Error: too few arguments"
[ -z "$1" ] && die "Error: must supply a CA file"
[ -z "$2" ] && die "Error: must supply an output filename"
[ "$3" = "revoked" ] && [ -z "$4" ] && die "Error: must supply a certificate file to revoke"
CADB=$(mktemp -d)
CA="jstests/libs/ca.pem"
CA="$1"
CONFIG="${CADB}/config"
DEST="${OUTPUT_PATH}/$1"
DEST="${OUTPUT_PATH}/$2"
echo '01' > "$CADB/serial"
touch "$CADB/index.txt" "$CADB/index.txt.attr"
echo -e "[ ca ]\ndefault_ca = CA_default\n" > "$CONFIG"
@ -17,18 +31,22 @@ crl() {
echo -e "certificate = $CA\nprivate_key = $CA\ndefault_md = sha256" >> "$CONFIG"
VALIDITY_OPTIONS="-days 824 -crldays 823"
if [ "$2" = "expired" ]; then
# -enddate 010101000000Z = expires on 0:00:00, Jan 1, 2000.
# -crlsec 1 = valid for 1 second from now.
if [ "$3" = "expired" ]; then
# -enddate 010101000000Z = expires on 0:00:00, Jan 1, 2000.
# -crlsec 1 = valid for 1 second from now.
# i.e. this certificate will be completely invalid very soon.
VALIDITY_OPTIONS="-enddate 010101000000Z -crlsec 1"
elif [ "$2" = "revoked" ]; then
openssl ca -config "$CADB/config" -revoke "jstests/libs/client_revoked.pem"
elif [ "$3" = "revoked" ]; then
openssl ca -config "$CADB/config" -revoke "$4"
fi
openssl ca -config "$CADB/config" -gencrl -out "$DEST" -md sha256 $VALIDITY_OPTIONS
jstests/ssl/x509/mkdigest.py crl sha256 "$DEST"
jstests/ssl/x509/mkdigest.py crl sha1 "$DEST"
}
crl crl.pem empty
crl crl_expired.pem expired
crl crl_client_revoked.pem revoked
crl $CA_PEM_PATH crl.pem empty
crl $CA_PEM_PATH crl_expired.pem expired
crl $CA_PEM_PATH crl_client_revoked.pem revoked "jstests/libs/client_revoked.pem"
crl $CA_PEM_PATH crl_intermediate_ca_B_revoked.pem revoked "jstests/libs/intermediate-ca-B.pem"
crl $TRUSTED_CA_PEM_PATH crl_from_trusted_ca.pem empty
crl "jstests/libs/intermediate-ca-B.pem" crl_from_intermediate_ca_B.pem empty

View File

@ -62,20 +62,27 @@ namespace mongo {
namespace {
#define TEST_CERTS_DIR "jstests/libs/"
// certs rooted in ca.pem
// certs & CRLs rooted in ca.pem
constexpr const char* caFile = TEST_CERTS_DIR "ca.pem";
constexpr const char* serverKeyFile = TEST_CERTS_DIR "server.pem";
constexpr const char* clientKeyFile = TEST_CERTS_DIR "client.pem";
constexpr const char* revokedClientKeyFile = TEST_CERTS_DIR "client_revoked.pem";
constexpr const char* intermediateACaFile = TEST_CERTS_DIR "intermediate-ca.pem";
constexpr const char* intermediateALeafKeyFile = TEST_CERTS_DIR "server-intermediate-leaf.pem";
constexpr const char* intermediateBCaFile = TEST_CERTS_DIR "intermediate-ca-B.pem";
constexpr const char* intermediateBLeafKeyFile = TEST_CERTS_DIR "intermediate-ca-B-leaf.pem";
constexpr const char* emptyCRL = TEST_CERTS_DIR "crl.pem";
constexpr const char* expiredCRL = TEST_CERTS_DIR "crl_expired.pem";
constexpr const char* clientRevokedCRL = TEST_CERTS_DIR "crl_client_revoked.pem";
constexpr const char* intermediateBRevokedCRL = TEST_CERTS_DIR "crl_intermediate_ca_B_revoked.pem";
constexpr const char* intermediateBCRL = TEST_CERTS_DIR "crl_from_intermediate_ca_B.pem";
// certs rooted in trusted-ca.pem
// certs & CRLs rooted in trusted-ca.pem
constexpr const char* trustedCaFile = TEST_CERTS_DIR "trusted-ca.pem";
constexpr const char* trustedServerKeyFile = TEST_CERTS_DIR "trusted-server.pem";
constexpr const char* trustedClientKeyFile = TEST_CERTS_DIR "trusted-client.pem";
constexpr const char* trustedEmptyCRL = TEST_CERTS_DIR "crl_from_trusted_ca.pem";
// Test implementation needed by ASIO transport.
class SessionManagerUtil : public transport::SessionManager {
@ -1582,6 +1589,213 @@ TEST(SSLManager, intermediateCATests) {
}
}
// Tests that validation fails if configured CRL for the issuer of the peer certificate being
// validated has expired.
// Caveats:
// - Apple: CRL unsupported; test disabled
// - Windows: validation fails, but with misleading error message
#if MONGO_CONFIG_SSL_PROVIDER != MONGO_CONFIG_SSL_PROVIDER_APPLE
TEST(SSLManager, expiredCRLTest) {
SSLParams clientParams;
clientParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
clientParams.sslAllowInvalidHostnames = true;
clientParams.sslCAFile = caFile;
clientParams.sslPEMKeyFile = clientKeyFile;
clientParams.sslCRLFile = expiredCRL;
SSLParams serverParams;
serverParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
serverParams.sslAllowInvalidHostnames = true;
serverParams.sslCAFile = caFile;
serverParams.sslPEMKeyFile = serverKeyFile;
serverParams.sslCRLFile = expiredCRL;
SSLTestFixture tf(serverParams, clientParams);
tf.doHandshake();
auto result = tf.runIngressEgressValidation();
checkValidationResults(result, false /*expectIngressPass*/, false /*expectEgressPass*/);
#if MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_WINDOWS
constexpr const char* cause = "revocation server was offline";
#else
constexpr const char* cause = "expired";
#endif
ASSERT_NE(result.ingress.getStatus().reason().find(cause), std::string::npos);
ASSERT_NE(result.egress.getStatus().reason().find(cause), std::string::npos);
}
// Tests peer cert validation behavior if multiple CRLs from the same issuer are included
// in the CRL PEM file.
// Caveats:
// - Apple: CRL unsupported; test disabled
// - Windows: not allowed; setup fails with "The object or property already exists"
// - OpenSSL: allowed, even with some CRLs having already expired
TEST(SSLManager, multipleCRLsFromSameIssuerTests) {
// Combine two CRLs from the same issuer, one is valid and the other is expired.
const auto expiredCRLWithNonExpiredCRL = combinePEMFiles({{clientRevokedCRL}, {expiredCRL}});
SSLParams serverParams;
serverParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
serverParams.sslAllowInvalidHostnames = true;
serverParams.sslCAFile = caFile;
serverParams.sslPEMKeyFile = serverKeyFile;
serverParams.sslCRLFile = expiredCRLWithNonExpiredCRL;
SSLParams clientParams;
clientParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
clientParams.sslAllowInvalidHostnames = true;
clientParams.sslCAFile = caFile;
#if MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_WINDOWS
ASSERT_THROWS_CODE_AND_WHAT(
SSLManagerInterface::create(serverParams, true),
DBException,
ErrorCodes::InvalidSSLConfiguration,
"CertAddCRLContextToStore Failed The object or property already exists.");
#else
{
clientParams.sslPEMKeyFile = clientKeyFile;
LOGV2(9476700, "Running with client key file", "keyfile"_attr = clientKeyFile);
SSLTestFixture tf(serverParams, clientParams);
tf.doHandshake();
auto result = tf.runIngressEgressValidation();
checkValidationResults(result, true /*expectIngressPass*/, true);
}
{
clientParams.sslPEMKeyFile = revokedClientKeyFile;
LOGV2(9476701, "Running with client key file", "keyfile"_attr = revokedClientKeyFile);
SSLTestFixture tf(serverParams, clientParams);
tf.doHandshake();
auto result = tf.runIngressEgressValidation();
checkValidationResults(result, false, true);
ASSERT_NE(result.ingress.getStatus().reason().find("revoked"), std::string::npos);
}
#endif
}
// Tests basic CRL revocation works on ingress if the client is configured with a revoked key.
// Caveats:
// - Apple: CRL unsupported; test disabled
TEST(SSLManager, basicCRLRevocationTests) {
struct TestCase {
std::string serverCRLFile;
bool serverPass;
void serialize(BSONObjBuilder* bob) const {
bob->append("serverCRLFile", serverCRLFile);
bob->append("serverPass", serverPass);
}
};
SSLParams clientParams;
clientParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
clientParams.sslAllowInvalidHostnames = true;
clientParams.sslCAFile = trustedCaFile;
clientParams.sslPEMKeyFile = revokedClientKeyFile;
SSLParams serverParams;
serverParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
serverParams.sslAllowInvalidHostnames = true;
serverParams.sslCAFile = caFile;
serverParams.sslPEMKeyFile = trustedServerKeyFile;
{
serverParams.sslCRLFile = emptyCRL;
LOGV2(9476702, "Running test case", "CRLFile"_attr = emptyCRL, "pass"_attr = true);
SSLTestFixture tf(serverParams, clientParams);
tf.doHandshake();
auto result = tf.runIngressEgressValidation();
checkValidationResults(result, true, true /*expectEgressPass*/);
}
{
serverParams.sslCRLFile = clientRevokedCRL;
LOGV2(9476703, "Running test case", "CRLFile"_attr = clientRevokedCRL, "pass"_attr = false);
SSLTestFixture tf(serverParams, clientParams);
tf.doHandshake();
auto result = tf.runIngressEgressValidation();
checkValidationResults(result, false, true /*expectEgressPass*/);
ASSERT_NE(result.ingress.getStatus().reason().find("revoked"), std::string::npos);
}
}
// Tests validation behavior on ingress (or egress) if CRL checking is enabled, but no suitable
// CRL is found from same issuer of the peer cert being validated.
// Caveats:
// - Apple: CRL unsupported; test disabled
// - Windows: validation passes if no CRL is found
// - OpenSSL: validation fails if no CRL is found
TEST(SSLManager, noCRLFoundTests) {
SSLParams clientParams;
clientParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
clientParams.sslAllowInvalidHostnames = true;
clientParams.sslCAFile = caFile;
clientParams.sslPEMKeyFile = clientKeyFile;
clientParams.sslCRLFile = trustedEmptyCRL; // CRL issued by trusted-ca.pem, not ca.pem
SSLParams serverParams;
serverParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
serverParams.sslAllowInvalidHostnames = true;
serverParams.sslCAFile = caFile;
serverParams.sslPEMKeyFile = serverKeyFile;
serverParams.sslCRLFile = trustedEmptyCRL;
SSLTestFixture tf(serverParams, clientParams);
tf.doHandshake();
auto result = tf.runIngressEgressValidation();
#if MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_WINDOWS
checkValidationResults(result, true, true);
#else
checkValidationResults(result, false, false);
constexpr const char* expectedError = "unable to get certificate CRL";
ASSERT_NE(result.ingress.getStatus().reason().find(expectedError), std::string::npos);
ASSERT_NE(result.egress.getStatus().reason().find(expectedError), std::string::npos);
#endif
}
// Tests whether validation passes if an intermediate CA issuer cert is revoked, but
// the end-entity cert is not.
// Caveats:
// - Apple: CRL unsupported; test disabled
// - Windows: multiple CRLs (root CRL + intermediate CRL) is not allowed
// TODO: SERVER-95583 investigate why windows behaves like this.
// - OpenSSL: passes; only leaf cert is checked against CRL
// TODO: SERVER-95445 should fix this openssl issue.
TEST(SSLManager, revocationWithCRLsIntermediateTests) {
// intermediate-ca-B.pem + intermediate-ca-B-leaf.pem bundle
const std::string intermediateBLeafWithIssuerCertKeyFile = combinePEMFiles(
{{intermediateBLeafKeyFile, true /*includePrivKey*/}, {intermediateBCaFile}});
// crl_from_intermediate_ca_B.pem + crl_intermediate_ca_B_revoked.pem
const std::string crlsFromRootAndIntermediateB =
combinePEMFiles({{intermediateBRevokedCRL}, {intermediateBCRL}});
SSLParams clientParams;
clientParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
clientParams.sslAllowInvalidHostnames = true;
clientParams.sslCAFile = caFile;
clientParams.sslPEMKeyFile = clientKeyFile;
clientParams.sslCRLFile = crlsFromRootAndIntermediateB;
SSLParams serverParams;
serverParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
serverParams.sslAllowInvalidHostnames = true;
serverParams.sslCAFile = caFile;
serverParams.sslPEMKeyFile = intermediateBLeafWithIssuerCertKeyFile;
#if MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_WINDOWS
ASSERT_THROWS_CODE_AND_WHAT(
SSLManagerInterface::create(clientParams, true),
DBException,
ErrorCodes::InvalidSSLConfiguration,
"CertAddCRLContextToStore Failed The object or property already exists.");
#else
SSLTestFixture tf(serverParams, clientParams);
tf.doHandshake();
auto result = tf.runIngressEgressValidation();
checkValidationResults(result, true, true);
#endif
}
#endif // MONGO_CONFIG_SSL_PROVIDER != MONGO_CONFIG_SSL_PROVIDER_APPLE
#endif // MONGO_CONFIG_SSL
} // namespace
} // namespace mongo