Add X509_V_FLAG_NO_CHECK_TIME. This was added in OpenSSL 1.1.0. cryptography.io binds it. They don't actually use it, but this is a useful feature to have anyway. Projects like Envoy currently implement such a mode with X509_STORE_set_verify_cb, which is a very problematic API to support. Add this so we can move them to something more sustainable. Change-Id: Iaff2d08daa743e0b5f4be261cb785fdcd26a8281 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/53965 Commit-Queue: Adam Langley <agl@google.com> Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc index 8343fee..58fdd26 100644 --- a/crypto/x509/x509_test.cc +++ b/crypto/x509/x509_test.cc
@@ -1470,6 +1470,23 @@ Verify(leaf.get(), {root.get()}, {root.get()}, {algorithm_mismatch_crl2.get()}, X509_V_FLAG_CRL_CHECK)); + // The CRL is valid for a month. + EXPECT_EQ(X509_V_ERR_CRL_HAS_EXPIRED, + Verify(leaf.get(), {root.get()}, {root.get()}, {basic_crl.get()}, + X509_V_FLAG_CRL_CHECK, [](X509_VERIFY_PARAM *param) { + X509_VERIFY_PARAM_set_time( + param, kReferenceTime + 2 * 30 * 24 * 3600); + })); + + // X509_V_FLAG_NO_CHECK_TIME suppresses the validity check. + EXPECT_EQ(X509_V_OK, + Verify(leaf.get(), {root.get()}, {root.get()}, {basic_crl.get()}, + X509_V_FLAG_CRL_CHECK | X509_V_FLAG_NO_CHECK_TIME, + [](X509_VERIFY_PARAM *param) { + X509_VERIFY_PARAM_set_time( + param, kReferenceTime + 2 * 30 * 24 * 3600); + })); + // Parsing kBadExtensionCRL should fail. EXPECT_FALSE(CRLFromPEM(kBadExtensionCRL)); } @@ -3566,6 +3583,95 @@ })); } +// Test that notBefore and notAfter checks work correctly. +TEST(X509Test, Expiry) { + bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); + ASSERT_TRUE(key); + + // The following are measured in seconds relative to kReferenceTime. The + // validity periods are staggered so we can independently test both leaf and + // root time checks. + const time_t kSecondsInDay = 24 * 3600; + const time_t kRootStart = -30 * kSecondsInDay; + const time_t kIntermediateStart = -20 * kSecondsInDay; + const time_t kLeafStart = -10 * kSecondsInDay; + const time_t kIntermediateEnd = 10 * kSecondsInDay; + const time_t kLeafEnd = 20 * kSecondsInDay; + const time_t kRootEnd = 30 * kSecondsInDay; + + bssl::UniquePtr<X509> root = + MakeTestCert("Root", "Root", key.get(), /*is_ca=*/true); + ASSERT_TRUE(root); + ASSERT_TRUE(ASN1_TIME_adj(X509_getm_notBefore(root.get()), kReferenceTime, + /*offset_day=*/0, + /*offset_sec=*/kRootStart)); + ASSERT_TRUE(ASN1_TIME_adj(X509_getm_notAfter(root.get()), kReferenceTime, + /*offset_day=*/0, + /*offset_sec=*/kRootEnd)); + ASSERT_TRUE(X509_sign(root.get(), key.get(), EVP_sha256())); + + bssl::UniquePtr<X509> intermediate = + MakeTestCert("Root", "Intermediate", key.get(), /*is_ca=*/true); + ASSERT_TRUE(intermediate); + ASSERT_TRUE(ASN1_TIME_adj(X509_getm_notBefore(intermediate.get()), + kReferenceTime, + /*offset_day=*/0, + /*offset_sec=*/kIntermediateStart)); + ASSERT_TRUE(ASN1_TIME_adj(X509_getm_notAfter(intermediate.get()), + kReferenceTime, + /*offset_day=*/0, + /*offset_sec=*/kIntermediateEnd)); + ASSERT_TRUE(X509_sign(intermediate.get(), key.get(), EVP_sha256())); + + bssl::UniquePtr<X509> leaf = + MakeTestCert("Intermediate", "Leaf", key.get(), /*is_ca=*/false); + ASSERT_TRUE(leaf); + ASSERT_TRUE(ASN1_TIME_adj(X509_getm_notBefore(leaf.get()), kReferenceTime, + /*offset_day=*/0, + /*offset_sec=*/kLeafStart)); + ASSERT_TRUE(ASN1_TIME_adj(X509_getm_notAfter(leaf.get()), kReferenceTime, + /*offset_day=*/0, + /*offset_sec=*/kLeafEnd)); + ASSERT_TRUE(X509_sign(leaf.get(), key.get(), EVP_sha256())); + + struct VerifyAt { + time_t time; + void operator()(X509_VERIFY_PARAM *param) const { + X509_VERIFY_PARAM_set_time(param, time); + } + }; + + for (bool check_time : {true, false}) { + SCOPED_TRACE(check_time); + unsigned long flags = check_time ? 0 : X509_V_FLAG_NO_CHECK_TIME; + int not_yet_valid = check_time ? X509_V_ERR_CERT_NOT_YET_VALID : X509_V_OK; + int has_expired = check_time ? X509_V_ERR_CERT_HAS_EXPIRED : X509_V_OK; + + EXPECT_EQ(not_yet_valid, + Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, flags, + VerifyAt{kReferenceTime + kRootStart - 1})); + EXPECT_EQ(not_yet_valid, + Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, flags, + VerifyAt{kReferenceTime + kIntermediateStart - 1})); + EXPECT_EQ(not_yet_valid, + Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, flags, + VerifyAt{kReferenceTime + kLeafStart - 1})); + + EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()}, {intermediate.get()}, + {}, flags, VerifyAt{kReferenceTime})); + + EXPECT_EQ(has_expired, + Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, flags, + VerifyAt{kReferenceTime + kRootEnd + 1})); + EXPECT_EQ(has_expired, + Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, flags, + VerifyAt{kReferenceTime + kIntermediateEnd + 1})); + EXPECT_EQ(has_expired, + Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, flags, + VerifyAt{kReferenceTime + kLeafEnd + 1})); + } +} + // kConstructedBitString is an X.509 certificate where the signature is encoded // as a BER constructed BIT STRING. Note that, while OpenSSL's parser accepts // this input, it interprets the value incorrectly.
diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c index f6089cd..eca2d1e 100644 --- a/crypto/x509/x509_vfy.c +++ b/crypto/x509/x509_vfy.c
@@ -971,18 +971,21 @@ // Check CRL times against values in X509_STORE_CTX static int check_crl_time(X509_STORE_CTX *ctx, X509_CRL *crl, int notify) { - time_t *ptime; - int i; + if (ctx->param->flags & X509_V_FLAG_NO_CHECK_TIME) { + return 1; + } + if (notify) { ctx->current_crl = crl; } + time_t *ptime; if (ctx->param->flags & X509_V_FLAG_USE_CHECK_TIME) { ptime = &ctx->param->check_time; } else { ptime = NULL; } - i = X509_cmp_time(X509_CRL_get0_lastUpdate(crl), ptime); + int i = X509_cmp_time(X509_CRL_get0_lastUpdate(crl), ptime); if (i == 0) { if (!notify) { return 0; @@ -1739,16 +1742,18 @@ } static int check_cert_time(X509_STORE_CTX *ctx, X509 *x) { - time_t *ptime; - int i; + if (ctx->param->flags & X509_V_FLAG_NO_CHECK_TIME) { + return 1; + } + time_t *ptime; if (ctx->param->flags & X509_V_FLAG_USE_CHECK_TIME) { ptime = &ctx->param->check_time; } else { ptime = NULL; } - i = X509_cmp_time(X509_get_notBefore(x), ptime); + int i = X509_cmp_time(X509_get_notBefore(x), ptime); if (i == 0) { ctx->error = X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD; ctx->current_cert = x;
diff --git a/include/openssl/x509.h b/include/openssl/x509.h index 96b7495..57eb002 100644 --- a/include/openssl/x509.h +++ b/include/openssl/x509.h
@@ -2582,6 +2582,10 @@ // will force the behaviour to match that of previous versions. #define X509_V_FLAG_NO_ALT_CHAINS 0x100000 +// X509_V_FLAG_NO_CHECK_TIME disables all time checks in certificate +// verification. +#define X509_V_FLAG_NO_CHECK_TIME 0x200000 + #define X509_VP_FLAG_DEFAULT 0x1 #define X509_VP_FLAG_OVERWRITE 0x2 #define X509_VP_FLAG_RESET_FLAGS 0x4