Use initializer lists to specify cipher rule tests.

This is significantly less of a nuisance than having to explicitly type out
kRule5, kExpected5.

Change-Id: I61820c26a159c71e09000fbe0bf91e30da42205e
Reviewed-on: https://boringssl-review.googlesource.com/7000
Reviewed-by: Adam Langley <agl@google.com>
This commit is contained in:
David Benjamin 2015-12-16 19:34:22 -05:00 committed by Adam Langley
parent 894a47df24
commit fb974e6cb3

View File

@ -39,215 +39,178 @@ struct ExpectedCipher {
struct CipherTest {
// The rule string to apply.
const char *rule;
// The list of expected ciphers, in order, terminated with -1.
const ExpectedCipher *expected;
// The list of expected ciphers, in order.
std::vector<ExpectedCipher> expected;
};
// Selecting individual ciphers should work.
static const char kRule1[] =
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256";
static const ExpectedCipher kExpected1[] = {
{ TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0 },
{ TLS1_CK_ECDHE_ECDSA_CHACHA20_POLY1305_OLD, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0 },
{ TLS1_CK_ECDHE_RSA_CHACHA20_POLY1305_OLD, 0 },
{ TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0 },
{ 0, 0 },
};
// + reorders selected ciphers to the end, keeping their relative
// order.
static const char kRule2[] =
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"+aRSA";
static const ExpectedCipher kExpected2[] = {
{ TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0 },
{ TLS1_CK_ECDHE_ECDSA_CHACHA20_POLY1305_OLD, 0 },
{ TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0 },
{ TLS1_CK_ECDHE_RSA_CHACHA20_POLY1305_OLD, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0 },
{ 0, 0 },
};
// ! banishes ciphers from future selections.
static const char kRule3[] =
"!aRSA:"
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256";
static const ExpectedCipher kExpected3[] = {
{ TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0 },
{ TLS1_CK_ECDHE_ECDSA_CHACHA20_POLY1305_OLD, 0 },
{ TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0 },
{ 0, 0 },
};
// Multiple masks can be ANDed in a single rule.
static const char kRule4[] = "kRSA+AESGCM+AES128";
static const ExpectedCipher kExpected4[] = {
{ TLS1_CK_RSA_WITH_AES_128_GCM_SHA256, 0 },
{ 0, 0 },
};
// - removes selected ciphers, but preserves their order for future
// selections. Select AES_128_GCM, but order the key exchanges RSA,
// DHE_RSA, ECDHE_RSA.
static const char kRule5[] =
"ALL:-kECDHE:-kDHE:-kRSA:-ALL:"
"AESGCM+AES128+aRSA";
static const ExpectedCipher kExpected5[] = {
{ TLS1_CK_RSA_WITH_AES_128_GCM_SHA256, 0 },
{ TLS1_CK_DHE_RSA_WITH_AES_128_GCM_SHA256, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0 },
{ 0, 0 },
};
// Unknown selectors are no-ops.
static const char kRule6[] =
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"BOGUS1:-BOGUS2:+BOGUS3:!BOGUS4";
static const ExpectedCipher kExpected6[] = {
{ TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0 },
{ TLS1_CK_ECDHE_ECDSA_CHACHA20_POLY1305_OLD, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0 },
{ TLS1_CK_ECDHE_RSA_CHACHA20_POLY1305_OLD, 0 },
{ TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0 },
{ 0, 0 },
};
// Square brackets specify equi-preference groups.
static const char kRule7[] =
"[ECDHE-ECDSA-CHACHA20-POLY1305|ECDHE-ECDSA-AES128-GCM-SHA256]:"
"[ECDHE-RSA-CHACHA20-POLY1305]:"
"ECDHE-RSA-AES128-GCM-SHA256";
static const ExpectedCipher kExpected7[] = {
{ TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 1 },
{ TLS1_CK_ECDHE_ECDSA_CHACHA20_POLY1305_OLD, 1 },
{ TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 1 },
{ TLS1_CK_ECDHE_RSA_CHACHA20_POLY1305_OLD, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0 },
{ 0, 0 },
};
// @STRENGTH performs a stable strength-sort of the selected
// ciphers and only the selected ciphers.
static const char kRule8[] =
// To simplify things, banish all but {ECDHE_RSA,RSA} x
// {CHACHA20,AES_256_CBC,AES_128_CBC,RC4} x SHA1.
"!kEDH:!AESGCM:!3DES:!SHA256:!MD5:!SHA384:"
// Order some ciphers backwards by strength.
"ALL:-CHACHA20:-AES256:-AES128:-RC4:-ALL:"
// Select ECDHE ones and sort them by strength. Ties should resolve
// based on the order above.
"kECDHE:@STRENGTH:-ALL:"
// Now bring back everything uses RSA. ECDHE_RSA should be first,
// sorted by strength. Then RSA, backwards by strength.
"aRSA";
static const ExpectedCipher kExpected8[] = {
{ TLS1_CK_ECDHE_RSA_WITH_AES_256_CBC_SHA, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0 },
{ TLS1_CK_ECDHE_RSA_CHACHA20_POLY1305_OLD, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_RC4_128_SHA, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_AES_128_CBC_SHA, 0 },
{ SSL3_CK_RSA_RC4_128_SHA, 0 },
{ TLS1_CK_RSA_WITH_AES_128_SHA, 0 },
{ TLS1_CK_RSA_WITH_AES_256_SHA, 0 },
{ 0, 0 },
};
// Exact ciphers may not be used in multi-part rules; they are treated
// as unknown aliases.
static const char kRule9[] =
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"!ECDHE-RSA-AES128-GCM-SHA256+RSA:"
"!ECDSA+ECDHE-ECDSA-AES128-GCM-SHA256";
static const ExpectedCipher kExpected9[] = {
{ TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0 },
{ 0, 0 },
};
// SSLv3 matches everything that existed before TLS 1.2.
static const char kRule10[] = "AES128-SHA:AES128-SHA256:!SSLv3";
static const ExpectedCipher kExpected10[] = {
{ TLS1_CK_RSA_WITH_AES_128_SHA256, 0 },
{ 0, 0 },
};
// TLSv1.2 matches everything added in TLS 1.2.
static const char kRule11[] = "AES128-SHA:AES128-SHA256:!TLSv1.2";
static const ExpectedCipher kExpected11[] = {
{ TLS1_CK_RSA_WITH_AES_128_SHA, 0 },
{ 0, 0 },
};
// The two directives have no intersection.
static const char kRule12[] = "AES128-SHA:AES128-SHA256:!TLSv1.2+SSLv3";
static const ExpectedCipher kExpected12[] = {
{ TLS1_CK_RSA_WITH_AES_128_SHA, 0 },
{ TLS1_CK_RSA_WITH_AES_128_SHA256, 0 },
{ 0, 0 },
};
// The shared name of the CHACHA20_POLY1305 variants behaves like a cipher name
// and not an alias. It may not be used in a multipart rule. (That the shared
// name works is covered by the standard tests.)
static const char kRule13[] =
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"!ECDHE-RSA-CHACHA20-POLY1305+RSA:"
"!ECDSA+ECDHE-ECDSA-CHACHA20-POLY1305";
static const ExpectedCipher kExpected13[] = {
{ TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0 },
{ TLS1_CK_ECDHE_ECDSA_CHACHA20_POLY1305_OLD, 0 },
{ TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0 },
{ TLS1_CK_ECDHE_RSA_CHACHA20_POLY1305_OLD, 0 },
{ 0, 0 },
};
static CipherTest kCipherTests[] = {
{ kRule1, kExpected1 },
{ kRule2, kExpected2 },
{ kRule3, kExpected3 },
{ kRule4, kExpected4 },
{ kRule5, kExpected5 },
{ kRule6, kExpected6 },
{ kRule7, kExpected7 },
{ kRule8, kExpected8 },
{ kRule9, kExpected9 },
{ kRule10, kExpected10 },
{ kRule11, kExpected11 },
{ kRule12, kExpected12 },
{ kRule13, kExpected13 },
{ NULL, NULL },
static const CipherTest kCipherTests[] = {
// Selecting individual ciphers should work.
{
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256",
{
{TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_ECDSA_CHACHA20_POLY1305_OLD, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_RSA_CHACHA20_POLY1305_OLD, 0},
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
},
// + reorders selected ciphers to the end, keeping their relative order.
{
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"+aRSA",
{
{TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_ECDSA_CHACHA20_POLY1305_OLD, 0},
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_RSA_CHACHA20_POLY1305_OLD, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
},
// ! banishes ciphers from future selections.
{
"!aRSA:"
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256",
{
{TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_ECDSA_CHACHA20_POLY1305_OLD, 0},
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
},
},
// Multiple masks can be ANDed in a single rule.
{
"kRSA+AESGCM+AES128",
{
{TLS1_CK_RSA_WITH_AES_128_GCM_SHA256, 0},
},
},
// - removes selected ciphers, but preserves their order for future
// selections. Select AES_128_GCM, but order the key exchanges RSA, DHE_RSA,
// ECDHE_RSA.
{
"ALL:-kECDHE:-kDHE:-kRSA:-ALL:"
"AESGCM+AES128+aRSA",
{
{TLS1_CK_RSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_DHE_RSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
},
// Unknown selectors are no-ops.
{
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"BOGUS1:-BOGUS2:+BOGUS3:!BOGUS4",
{
{TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_ECDSA_CHACHA20_POLY1305_OLD, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_RSA_CHACHA20_POLY1305_OLD, 0},
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
},
// Square brackets specify equi-preference groups.
{
"[ECDHE-ECDSA-CHACHA20-POLY1305|ECDHE-ECDSA-AES128-GCM-SHA256]:"
"[ECDHE-RSA-CHACHA20-POLY1305]:"
"ECDHE-RSA-AES128-GCM-SHA256",
{
{TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 1},
{TLS1_CK_ECDHE_ECDSA_CHACHA20_POLY1305_OLD, 1},
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 1},
{TLS1_CK_ECDHE_RSA_CHACHA20_POLY1305_OLD, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
},
// @STRENGTH performs a stable strength-sort of the selected ciphers and
// only the selected ciphers.
{
// To simplify things, banish all but {ECDHE_RSA,RSA} x
// {CHACHA20,AES_256_CBC,AES_128_CBC,RC4} x SHA1.
"!kEDH:!AESGCM:!3DES:!SHA256:!MD5:!SHA384:"
// Order some ciphers backwards by strength.
"ALL:-CHACHA20:-AES256:-AES128:-RC4:-ALL:"
// Select ECDHE ones and sort them by strength. Ties should resolve
// based on the order above.
"kECDHE:@STRENGTH:-ALL:"
// Now bring back everything uses RSA. ECDHE_RSA should be first, sorted
// by strength. Then RSA, backwards by strength.
"aRSA",
{
{TLS1_CK_ECDHE_RSA_WITH_AES_256_CBC_SHA, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_RSA_CHACHA20_POLY1305_OLD, 0},
{TLS1_CK_ECDHE_RSA_WITH_RC4_128_SHA, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_CBC_SHA, 0},
{SSL3_CK_RSA_RC4_128_SHA, 0},
{TLS1_CK_RSA_WITH_AES_128_SHA, 0},
{TLS1_CK_RSA_WITH_AES_256_SHA, 0},
},
},
// Exact ciphers may not be used in multi-part rules; they are treated
// as unknown aliases.
{
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"!ECDHE-RSA-AES128-GCM-SHA256+RSA:"
"!ECDSA+ECDHE-ECDSA-AES128-GCM-SHA256",
{
{TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
{TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
},
},
// SSLv3 matches everything that existed before TLS 1.2.
{
"AES128-SHA:AES128-SHA256:!SSLv3",
{
{TLS1_CK_RSA_WITH_AES_128_SHA256, 0},
},
},
// TLSv1.2 matches everything added in TLS 1.2.
{
"AES128-SHA:AES128-SHA256:!TLSv1.2",
{
{TLS1_CK_RSA_WITH_AES_128_SHA, 0},
},
},
// The two directives have no intersection.
{
"AES128-SHA:AES128-SHA256:!TLSv1.2+SSLv3",
{
{TLS1_CK_RSA_WITH_AES_128_SHA, 0},
{TLS1_CK_RSA_WITH_AES_128_SHA256, 0},
},
},
// The shared name of the CHACHA20_POLY1305 variants behaves like a cipher
// name and not an alias. It may not be used in a multipart rule. (That the
// shared name works is covered by the standard tests.)
{
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"!ECDHE-RSA-CHACHA20-POLY1305+RSA:"
"!ECDSA+ECDHE-ECDSA-CHACHA20-POLY1305",
{
{TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_ECDSA_CHACHA20_POLY1305_OLD, 0},
{TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 0},
{TLS1_CK_ECDHE_RSA_CHACHA20_POLY1305_OLD, 0},
},
},
};
static const char *kBadRules[] = {
@ -271,7 +234,6 @@ static const char *kBadRules[] = {
"[ECDHE-RSA-CHACHA20-POLY1305|ECDHE-RSA-AES128-GCM-SHA256]:!FOO",
"[ECDHE-RSA-CHACHA20-POLY1305|ECDHE-RSA-AES128-GCM-SHA256]:-FOO",
"[ECDHE-RSA-CHACHA20-POLY1305|ECDHE-RSA-AES128-GCM-SHA256]:@STRENGTH",
NULL,
};
static const char *kMustNotIncludeNull[] = {
@ -288,7 +250,6 @@ static const char *kMustNotIncludeNull[] = {
"SSLv3",
"TLSv1",
"TLSv1.2",
NULL
};
static void PrintCipherPreferenceList(ssl_cipher_preference_list_st *list) {
@ -311,36 +272,35 @@ static void PrintCipherPreferenceList(ssl_cipher_preference_list_st *list) {
}
}
static bool TestCipherRule(CipherTest *t) {
static bool TestCipherRule(const CipherTest &t) {
ScopedSSL_CTX ctx(SSL_CTX_new(TLS_method()));
if (!ctx) {
return false;
}
if (!SSL_CTX_set_cipher_list(ctx.get(), t->rule)) {
fprintf(stderr, "Error testing cipher rule '%s'\n", t->rule);
if (!SSL_CTX_set_cipher_list(ctx.get(), t.rule)) {
fprintf(stderr, "Error testing cipher rule '%s'\n", t.rule);
return false;
}
// Compare the two lists.
size_t i;
for (i = 0; i < sk_SSL_CIPHER_num(ctx->cipher_list->ciphers); i++) {
if (sk_SSL_CIPHER_num(ctx->cipher_list->ciphers) != t.expected.size()) {
fprintf(stderr, "Error: cipher rule '%s' evaluated to:\n", t.rule);
PrintCipherPreferenceList(ctx->cipher_list);
return false;
}
for (size_t i = 0; i < t.expected.size(); i++) {
const SSL_CIPHER *cipher =
sk_SSL_CIPHER_value(ctx->cipher_list->ciphers, i);
if (t->expected[i].id != SSL_CIPHER_get_id(cipher) ||
t->expected[i].in_group_flag != ctx->cipher_list->in_group_flags[i]) {
fprintf(stderr, "Error: cipher rule '%s' evaluated to:\n", t->rule);
if (t.expected[i].id != SSL_CIPHER_get_id(cipher) ||
t.expected[i].in_group_flag != ctx->cipher_list->in_group_flags[i]) {
fprintf(stderr, "Error: cipher rule '%s' evaluated to:\n", t.rule);
PrintCipherPreferenceList(ctx->cipher_list);
return false;
}
}
if (t->expected[i].id != 0) {
fprintf(stderr, "Error: cipher rule '%s' evaluated to:\n", t->rule);
PrintCipherPreferenceList(ctx->cipher_list);
return false;
}
return true;
}
@ -363,26 +323,26 @@ static bool TestRuleDoesNotIncludeNull(const char *rule) {
}
static bool TestCipherRules() {
for (size_t i = 0; kCipherTests[i].rule != NULL; i++) {
if (!TestCipherRule(&kCipherTests[i])) {
for (const CipherTest &test : kCipherTests) {
if (!TestCipherRule(test)) {
return false;
}
}
for (size_t i = 0; kBadRules[i] != NULL; i++) {
for (const char *rule : kBadRules) {
ScopedSSL_CTX ctx(SSL_CTX_new(SSLv23_server_method()));
if (!ctx) {
return false;
}
if (SSL_CTX_set_cipher_list(ctx.get(), kBadRules[i])) {
fprintf(stderr, "Cipher rule '%s' unexpectedly succeeded\n", kBadRules[i]);
if (SSL_CTX_set_cipher_list(ctx.get(), rule)) {
fprintf(stderr, "Cipher rule '%s' unexpectedly succeeded\n", rule);
return false;
}
ERR_clear_error();
}
for (size_t i = 0; kMustNotIncludeNull[i] != NULL; i++) {
if (!TestRuleDoesNotIncludeNull(kMustNotIncludeNull[i])) {
for (const char *rule : kMustNotIncludeNull) {
if (!TestRuleDoesNotIncludeNull(rule)) {
return false;
}
}