diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index 794029da171..c3907b394e2 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -23,6 +23,8 @@ #include +#include + #if defined(WC_ENABLE_ASYM_KEY_EXPORT) && defined(HAVE_ED25519) static int test_SetAsymKeyDer_once(byte* privKey, word32 privKeySz, byte* pubKey, word32 pubKeySz, byte* trueDer, word32 trueDerSz) @@ -638,3 +640,150 @@ int test_wc_IndexSequenceOf(void) return EXPECT_RESULT(); } + +int test_wolfssl_local_MatchBaseName(void) +{ + EXPECT_DECLS; + +#if !defined(NO_CERTS) && !defined(NO_ASN) && !defined(IGNORE_NAME_CONSTRAINTS) + /* + * Tests for DNS type (ASN_DNS_TYPE = 0x02) + */ + + /* Positive tests - should match */ + /* Exact match */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "domain.com", 10, "domain.com", 10), 1); + /* Case insensitive match */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "DOMAIN.COM", 10, "domain.com", 10), 1); + /* Subdomain match (RFC 5280: adding labels to the left) */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "sub.domain.com", 14, "domain.com", 10), 1); + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "a.b.domain.com", 14, "domain.com", 10), 1); + /* Leading dot constraint with subdomain (not RFC 5280 compliant for DNS, + * but kept for backwards compatibility) */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "sub.domain.com", 14, ".domain.com", 11), 1); + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "a.b.domain.com", 14, ".domain.com", 11), 1); + + /* Negative tests - should NOT match */ + /* Bug #3: fakedomain.com should NOT match domain.com (no dot boundary) */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "fakedomain.com", 14, "domain.com", 10), 0); + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "notdomain.com", 13, "domain.com", 10), 0); + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "xexample.com", 12, "example.com", 11), 0); + /* Bug #3: fakedomain.com should NOT match .domain.com */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "fakedomain.com", 14, ".domain.com", 11), 0); + /* domain.com should NOT match .domain.com (leading dot requires subdomain) */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "domain.com", 10, ".domain.com", 11), 0); + /* Different domain */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "other.com", 9, "domain.com", 10), 0); + /* Name starting with dot */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + ".domain.com", 11, "domain.com", 10), 0); + + /* + * Tests for email type (ASN_RFC822_TYPE = 0x01) + */ + + /* Positive tests - should match */ + /* Exact email match */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "user@domain.com", 15, "user@domain.com", 15), 1); + /* Email with domain constraint (leading dot) - subdomain present */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "user@sub.domain.com", 19, ".domain.com", 11), 1); + /* Email with domain constraint (no leading dot) - exact domain */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "user@domain.com", 15, "domain.com", 10), 1); + + /* Negative tests - should NOT match */ + /* user@domain.com should NOT match .domain.com (subdomain required) */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "user@domain.com", 15, ".domain.com", 11), 0); + /* user@sub.domain.com should NOT match domain.com (exact domain only) */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "user@sub.domain.com", 19, "domain.com", 10), 0); + /* @ at start is invalid */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "@domain.com", 11, ".domain.com", 11), 0); + /* @ at end is invalid */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "user@", 5, ".domain.com", 11), 0); + /* double @ is invalid */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "user@@domain.com", 16, ".domain.com", 11), 0); + /* multiple @ is invalid */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "user@domain@extra.com", 21, ".domain.com", 11), 0); + /* No @ in email name */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "userdomain.com", 14, ".domain.com", 11), 0); + /* Email domain doesn't match constraint */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "user@other.com", 14, ".domain.com", 11), 0); + /* Email suffix without dot boundary (fakedomain) */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "user@fakedomain.com", 19, ".domain.com", 11), 0); + /* Base constraint with invalid @ position */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "user@domain.com", 15, "@domain.com", 11), 0); + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, + "user@domain.com", 15, "user@", 5), 0); + + /* + * Tests for directory type (ASN_DIR_TYPE = 0x04) + */ + + /* Positive tests - should match */ + /* Exact match */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DIR_TYPE, + "CN=test", 7, "CN=test", 7), 1); + /* Prefix match (name longer than base) */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DIR_TYPE, + "CN=test,O=org", 13, "CN=test", 7), 1); + + /* Negative tests - should NOT match */ + /* Different content */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DIR_TYPE, + "CN=other", 8, "CN=test", 7), 0); + /* Case sensitive for directory */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DIR_TYPE, + "CN=TEST", 7, "CN=test", 7), 0); + + /* + * Edge cases and error handling + */ + + /* NULL pointers */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + NULL, 10, "domain.com", 10), 0); + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "domain.com", 10, NULL, 10), 0); + /* Empty/zero size */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "", 0, "domain.com", 10), 0); + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "domain.com", 10, "", 0), 0); + /* Invalid type */ + ExpectIntEQ(wolfssl_local_MatchBaseName(0xFF, + "domain.com", 10, "domain.com", 10), 0); + /* Name starting with dot */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + ".", 1, ".", 1), 0); + /* Name shorter than base */ + ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE, + "a.com", 5, "domain.com", 10), 0); + +#endif /* !NO_CERTS && !NO_ASN && !IGNORE_NAME_CONSTRAINTS */ + + return EXPECT_RESULT(); +} diff --git a/tests/api/test_asn.h b/tests/api/test_asn.h index 1d2e20b2f4f..e78bb145bbe 100644 --- a/tests/api/test_asn.h +++ b/tests/api/test_asn.h @@ -27,10 +27,12 @@ int test_SetAsymKeyDer(void); int test_GetSetShortInt(void); int test_wc_IndexSequenceOf(void); +int test_wolfssl_local_MatchBaseName(void); #define TEST_ASN_DECLS \ TEST_DECL_GROUP("asn", test_SetAsymKeyDer), \ TEST_DECL_GROUP("asn", test_GetSetShortInt), \ - TEST_DECL_GROUP("asn", test_wc_IndexSequenceOf) + TEST_DECL_GROUP("asn", test_wc_IndexSequenceOf), \ + TEST_DECL_GROUP("asn", test_wolfssl_local_MatchBaseName) #endif /* WOLFCRYPT_TEST_ASN_H */ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index dc17a3357a0..3e5703c4143 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -19315,8 +19315,8 @@ int ConfirmSignature(SignatureCtx* sigCtx, #ifndef IGNORE_NAME_CONSTRAINTS -static int MatchBaseName(int type, const char* name, int nameSz, - const char* base, int baseSz) +int wolfssl_local_MatchBaseName(int type, const char* name, int nameSz, + const char* base, int baseSz) { if (base == NULL || baseSz <= 0 || name == NULL || nameSz <= 0 || name[0] == '.' || nameSz < baseSz || @@ -19333,6 +19333,8 @@ static int MatchBaseName(int type, const char* name, int nameSz, if (type == ASN_RFC822_TYPE) { const char* p = NULL; int count = 0; + int baseIsEmail = 0; + int atPos = -1; if (base[0] != '.') { p = base; @@ -19344,25 +19346,36 @@ static int MatchBaseName(int type, const char* name, int nameSz, p++; } - /* No '@' in base, reset p to NULL */ - if (count >= baseSz) - p = NULL; + if (count < baseSz) { + /* '@' found in base - validate it's not at start/end and only one */ + if (count == 0 || count == baseSz - 1) + return 0; /* '@' at start or end of base is invalid */ + baseIsEmail = 1; + } } - if (p == NULL) { - /* Base isn't an email address, it is a domain name, - * wind the name forward one character past its '@'. */ - p = name; - count = 0; - while (*p != '@' && count < baseSz) { - count++; - p++; + /* verify that name is a valid email address, store @ position */ + p = name; + count = 0; + while (count < nameSz) { + if (*p == '@') { + if (atPos >= 0) + return 0; /* Multiple '@' in name is invalid */ + atPos = count; } + count++; + p++; + } - if (count < baseSz && *p == '@') { - name = p + 1; - nameSz -= count + 1; - } + /* Validate '@' exists and is not at start or end */ + if (atPos < 0 || atPos == 0 || atPos == nameSz - 1) + return 0; + + if (!baseIsEmail) { + /* Base isn't an email address but a domain or host. + * wind the name forward one character past its '@'. */ + name = name + atPos + 1; + nameSz -= atPos + 1; } } @@ -19370,13 +19383,24 @@ static int MatchBaseName(int type, const char* name, int nameSz, * "...Any DNS name that can be constructed by simply adding zero or more * labels to the left-hand side of the name satisfies the name constraint." * i.e www.host.example.com works for host.example.com name constraint and - * host1.example.com does not. */ + * host1.example.com does not. + * + * Note: For DNS type, RFC 5280 does not allow leading dot in constraint. + * However, we accept it here for backwards compatibility. */ if (type == ASN_DNS_TYPE || (type == ASN_RFC822_TYPE && base[0] == '.')) { int szAdjust = nameSz - baseSz; + /* Check dot boundary: if there's a prefix and base doesn't start with + * '.', the character before the matched suffix must be '.'. + * When base starts with '.', the dot is included in the comparison. */ + if (szAdjust > 0 && base[0] != '.' && name[szAdjust - 1] != '.') + return 0; name += szAdjust; nameSz -= szAdjust; } + if (nameSz != baseSz) + return 0; + while (nameSz > 0) { if (XTOLOWER((unsigned char)*name) != XTOLOWER((unsigned char)*base)) @@ -19408,8 +19432,8 @@ static int PermittedListOk(DNS_entry* name, Base_entry* dnsList, byte nameType) if (current->type == nameType) { need = 1; /* restriction on permitted names is set for this type */ if (name->len >= current->nameSz && - MatchBaseName(nameType, name->name, name->len, - current->name, current->nameSz)) { + wolfssl_local_MatchBaseName(nameType, name->name, name->len, + current->name, current->nameSz)) { match = 1; /* found the current name in the permitted list*/ break; } @@ -19439,8 +19463,8 @@ static int IsInExcludedList(DNS_entry* name, Base_entry* dnsList, byte nameType) while (current != NULL) { if (current->type == nameType) { if (name->len >= current->nameSz && - MatchBaseName(nameType, name->name, name->len, - current->name, current->nameSz)) { + wolfssl_local_MatchBaseName(nameType, name->name, name->len, + current->name, current->nameSz)) { ret = 1; break; } diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 99da1e84fa3..a12a3ef5b19 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -2789,6 +2789,12 @@ WOLFSSL_LOCAL int VerifyX509Acert(const byte* cert, word32 certSz, #endif /* WOLFSSL_ACERT */ +#ifndef IGNORE_NAME_CONSTRAINTS +WOLFSSL_TEST_VIS int wolfssl_local_MatchBaseName(int type, const char* name, + int nameSz, const char* base, + int baseSz); +#endif + #if ((defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_IMPORT)) \ || (defined(HAVE_CURVE25519) && defined(HAVE_CURVE25519_KEY_IMPORT)) \ || (defined(HAVE_ED448) && defined(HAVE_ED448_KEY_IMPORT)) \