diff --git a/tests/t_verify_cert.c b/tests/t_verify_cert.c index b52a9901..7cb544a9 100644 --- a/tests/t_verify_cert.c +++ b/tests/t_verify_cert.c @@ -36,6 +36,8 @@ int main (int argc, char *argv[]) { struct clsrvconf conf; + // X509 + X509 /* /CN=test */ *certsimple = getcert("-----BEGIN CERTIFICATE-----\n\ @@ -64,16 +66,6 @@ tCGaM1KbqRZA/3VgQt+6iEFuoxkwFzAVBgNVHREEDjAMggp0ZXN0LmxvY2FsMAkG\n\ ByqGSM49BAEDJAAwIQIPAId8FJW00y8XSFmd2lBvAg5K6WAMIFgjhtwcRFcfQg==\n\ -----END CERTIFICATE-----"), -/* /CN=other, SAN DNS:other.local */ -*certsandnsother = getcert("-----BEGIN CERTIFICATE-----\n\ -MIHuMIG6oAMCAQICFAiFPNqpXcSIwxS0bfJZs8KDDafVMAkGByqGSM49BAEwEDEO\n\ -MAwGA1UEAwwFb3RoZXIwHhcNMjAwOTI5MTYxMTM2WhcNMjAxMDA5MTYxMTM2WjAQ\n\ -MQ4wDAYDVQQDDAVvdGhlcjAyMBAGByqGSM49AgEGBSuBBAAGAx4ABJxnszX24oQM\n\ -NcK0IZozUpupFkD/dWBC37qIQW6jGjAYMBYGA1UdEQQPMA2CC290aGVyLmxvY2Fs\n\ -MAkGByqGSM49BAEDJAAwIQIOTrQCgOkGcknZEchJFDgCDwCY84F0R2BnNEba95o9\n\ -NA==\n\ ------END CERTIFICATE-----"), - /* /CN=test, SAN IP Address:192.0.2.1 */ *certsanip = getcert("-----BEGIN CERTIFICATE-----\n\ MIHlMIGxoAMCAQICFEukd75rE75+qB95Bo7fcb9wXlA9MAkGByqGSM49BAEwDzEN\n\ @@ -92,6 +84,25 @@ NcK0IZozUpupFkD/dWBC37qIQW6jEzARMA8GA1UdEQQIMAaHBMAAAgIwCQYHKoZI\n\ zj0EAQMjADAgAg5trehJeRpM04SJJZ6XnAIOFfzRRnQtm5rnsP+QBe8=\n\ -----END CERTIFICATE-----"), +/* /CN=test, SAN DNS:192.0.2.1 */ +*certsanipindns = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHqMIG2oAMCAQICFFUjZGG96kpFI2fu90+jAhWsTr8YMAkGByqGSM49BAEwDzEN\n\ +MAsGA1UEAwwEdGVzdDAeFw0yMDA5MjkxNTU4NDBaFw0yMDEwMDkxNTU4NDBaMA8x\n\ +DTALBgNVBAMMBHRlc3QwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKEDDXC\n\ +tCGaM1KbqRZA/3VgQt+6iEFuoxgwFjAUBgNVHREEDTALggkxOTIuMC4yLjEwCQYH\n\ +KoZIzj0EAQMkADAhAg5BngyplTbRlQ8o/oWWwQIPAL9SfgIaXi/gD6YlQCOU\n\ +-----END CERTIFICATE-----"), + +/* /CN=test, DNS:somethinglese, DNS:test.local, IP Address:192.0.2.1, IP Address:2001:DB8:0:0:0:0:0:1 */ +*certcomplex = getcert("-----BEGIN CERTIFICATE-----\n\ +MIIBEjCB3qADAgECAhRgxyW7klgZvTf9isALCvwlVwvRtDAJBgcqhkjOPQQBMA8x\n\ +DTALBgNVBAMMBHRlc3QwHhcNMjAwOTMwMDU1MjIyWhcNMjAxMDEwMDU1MjIyWjAP\n\ +MQ0wCwYDVQQDDAR0ZXN0MDIwEAYHKoZIzj0CAQYFK4EEAAYDHgAEnGezNfbihAw1\n\ +wrQhmjNSm6kWQP91YELfuohBbqNAMD4wPAYDVR0RBDUwM4INc29tZXRoaW5nbGVz\n\ +ZYIKdGVzdC5sb2NhbIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATAJBgcqhkjOPQQB\n\ +AyQAMCECDlTfJfMJElZZgvUdkatdAg8ApiXkPXLXXrV6OMRG9us=\n\ +-----END CERTIFICATE-----"), + /* /CN=test, SAN IP Address:2001:DB8:0:0:0:0:0:1 */ *certsanipv6 = getcert("-----BEGIN CERTIFICATE-----\n\ MIHxMIG9oAMCAQICFGhkABYXfolor1EF6Li3hDQeEVU+MAkGByqGSM49BAEwDzEN\n\ @@ -102,15 +113,6 @@ AAABMAkGByqGSM49BAEDJAAwIQIPAKsn++FWaDIcpnNBOFTuAg5C7gs7DxaNWgEu\n\ OrBTXA==\n\ -----END CERTIFICATE-----"), -/* /CN=test, SAN DNS:192.0.2.1 */ -*certsanipindns = getcert("-----BEGIN CERTIFICATE-----\n\ -MIHqMIG2oAMCAQICFFUjZGG96kpFI2fu90+jAhWsTr8YMAkGByqGSM49BAEwDzEN\n\ -MAsGA1UEAwwEdGVzdDAeFw0yMDA5MjkxNTU4NDBaFw0yMDEwMDkxNTU4NDBaMA8x\n\ -DTALBgNVBAMMBHRlc3QwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKEDDXC\n\ -tCGaM1KbqRZA/3VgQt+6iEFuoxgwFjAUBgNVHREEDTALggkxOTIuMC4yLjEwCQYH\n\ -KoZIzj0EAQMkADAhAg5BngyplTbRlQ8o/oWWwQIPAL9SfgIaXi/gD6YlQCOU\n\ ------END CERTIFICATE-----"), - /* /CN=test, SAN DNS:2001:DB8::1 */ *certsanipv6indns = getcert("-----BEGIN CERTIFICATE-----\n\ MIHsMIG4oAMCAQICFFgnXltbOEGcWsS0vCv6Lsj4FhO3MAkGByqGSM49BAEwDzEN\n\ @@ -120,28 +122,18 @@ tCGaM1KbqRZA/3VgQt+6iEFuoxowGDAWBgNVHREEDzANggsyMDAxOmRiODo6MTAJ\n\ BgcqhkjOPQQBAyQAMCECDlWFhJxpHRgt93ZzN9k7Ag8Ag0YN+dL3MEIo2sqgRWg=\n\ -----END CERTIFICATE-----"), -/* /CN=test, DNS:somethinglese, DNS:test.local, IP Address:192.0.2.1, IP Address:2001:DB8:0:0:0:0:0:1 */ -*certcomplex = getcert("-----BEGIN CERTIFICATE-----\n\ -MIIBEjCB3qADAgECAhRgxyW7klgZvTf9isALCvwlVwvRtDAJBgcqhkjOPQQBMA8x\n\ -DTALBgNVBAMMBHRlc3QwHhcNMjAwOTMwMDU1MjIyWhcNMjAxMDEwMDU1MjIyWjAP\n\ -MQ0wCwYDVQQDDAR0ZXN0MDIwEAYHKoZIzj0CAQYFK4EEAAYDHgAEnGezNfbihAw1\n\ -wrQhmjNSm6kWQP91YELfuohBbqNAMD4wPAYDVR0RBDUwM4INc29tZXRoaW5nbGVz\n\ -ZYIKdGVzdC5sb2NhbIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATAJBgcqhkjOPQQB\n\ -AyQAMCECDlTfJfMJElZZgvUdkatdAg8ApiXkPXLXXrV6OMRG9us=\n\ +/* /CN=other, SAN DNS:other.local */ +*certsandnsother = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHuMIG6oAMCAQICFAiFPNqpXcSIwxS0bfJZs8KDDafVMAkGByqGSM49BAEwEDEO\n\ +MAwGA1UEAwwFb3RoZXIwHhcNMjAwOTI5MTYxMTM2WhcNMjAxMDA5MTYxMTM2WjAQ\n\ +MQ4wDAYDVQQDDAVvdGhlcjAyMBAGByqGSM49AgEGBSuBBAAGAx4ABJxnszX24oQM\n\ +NcK0IZozUpupFkD/dWBC37qIQW6jGjAYMBYGA1UdEQQPMA2CC290aGVyLmxvY2Fs\n\ +MAkGByqGSM49BAEDJAAwIQIOTrQCgOkGcknZEchJFDgCDwCY84F0R2BnNEba95o9\n\ +NA==\n\ -----END CERTIFICATE-----"), -/* /CN=test, DNS:somethinglese, DNS:other.local, IP Address:192.0.2.2, IP Address:2001:DB8:0:0:0:0:0:2 */ -*certcomplexother = getcert("-----BEGIN CERTIFICATE-----\n\ -MIIBFTCB4aADAgECAhR0GSgeV7pqQnbHRgv5y5plz/6+NjAJBgcqhkjOPQQBMBAx\n\ -DjAMBgNVBAMMBW90aGVyMB4XDTIwMDkzMDA1NTI1NVoXDTIwMTAxMDA1NTI1NVow\n\ -EDEOMAwGA1UEAwwFb3RoZXIwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKE\n\ -DDXCtCGaM1KbqRZA/3VgQt+6iEFuo0EwPzA9BgNVHREENjA0gg1zb21ldGhpbmds\n\ -ZXNlggtvdGhlci5sb2NhbIcEwAACAocQIAENuAAAAAAAAAAAAAAAAjAJBgcqhkjO\n\ -PQQBAyQAMCECDwCEaHL6oHT4zwH6jD91YwIOYO3L8cHIzmnGCOJYIQ4=\n\ ------END CERTIFICATE-----"), - - /* /CN=test, URI:https://test.local/profile#me */ - *certsanuri = getcert("-----BEGIN CERTIFICATE-----\n\ +/* /CN=test, URI:https://test.local/profile#me */ +*certsanuri = getcert("-----BEGIN CERTIFICATE-----\n\ MIH9MIHKoAMCAQICFHsSOjcYexRKQpNlH1oHV1cxvdgHMAkGByqGSM49BAEwDzEN\n\ MAsGA1UEAwwEdGVzdDAeFw0yMDEwMDYwODU5MzRaFw0yMDEwMTYwODU5MzRaMA8x\n\ DTALBgNVBAMMBHRlc3QwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKEDDXC\n\ @@ -238,8 +230,7 @@ vY/uPjA=\n\ while(list_shift(conf.hostports)); } - - /* test no check if prefixlen != 255 (CIDR) */ + /* test no check if prefixlen != 255 (CIDR) */ { struct hostportres hp; @@ -253,7 +244,6 @@ vY/uPjA=\n\ while(list_shift(conf.hostports)); } - /* test simple match for CN=test */ { struct hostportres hp; @@ -272,7 +262,6 @@ vY/uPjA=\n\ while(list_shift(conf.hostports)); } - /* test literal ip match to SAN IP */ { struct hostportres hp; @@ -288,12 +277,11 @@ vY/uPjA=\n\ ok(0,verifyconfcert(certsanipother, &conf, &hp),"wrong san ip"); ok(0,verifyconfcert(certsimple, &conf, &hp), "negative san ip"); ok(1,verifyconfcert(certsanipindns, &conf, &hp),"san ip in dns"); - ok(1,verifyconfcert(certcomplex,&conf, &hp),"san ip in complex cert"); + ok(1,verifyconfcert(certcomplex, &conf, &hp),"san ip in complex cert"); freeaddrinfo(hp.addrinfo); while(list_shift(conf.hostports)); } - /* test literal ipv6 match to SAN IP */ { struct hostportres hp; @@ -315,7 +303,6 @@ vY/uPjA=\n\ freeaddrinfo(hp.addrinfo); while(list_shift(conf.hostports)); } - /* test simple match for SAN DNS:test.local */ { struct hostportres hp; @@ -333,7 +320,6 @@ vY/uPjA=\n\ while(list_shift(conf.hostports)); } - /* test multiple hostports san dns(match in second) */ { struct hostportres hp1, hp2; @@ -352,7 +338,6 @@ vY/uPjA=\n\ while(list_shift(conf.hostports)); } - /* test multiple hostports san dns(match in second) */ { struct hostportres hp1, hp2; @@ -377,7 +362,6 @@ vY/uPjA=\n\ while(list_shift(conf.hostports)); } - /* test explicit CN regex */ { conf.name = "test"; @@ -391,7 +375,6 @@ vY/uPjA=\n\ freematchcertattr(&conf); } - /* test explicit ip match to SAN IP */ { conf.name = "test"; @@ -406,7 +389,6 @@ vY/uPjA=\n\ freematchcertattr(&conf); } - /* test explicit ipv6 match to SAN IP */ { conf.name = "test"; @@ -421,7 +403,6 @@ vY/uPjA=\n\ freematchcertattr(&conf); } - /* test explicit SAN DNS regex */ { conf.name = "test"; @@ -436,7 +417,6 @@ vY/uPjA=\n\ freematchcertattr(&conf); } - /* test explicit SAN URI regex */ { conf.name = "test"; @@ -450,7 +430,6 @@ vY/uPjA=\n\ freematchcertattr(&conf); } - /* test explicit SAN rID */ { conf.name = "test"; @@ -464,7 +443,6 @@ vY/uPjA=\n\ freematchcertattr(&conf); } - /* test explicit SAN otherNAME */ { conf.name = "test"; @@ -478,7 +456,6 @@ vY/uPjA=\n\ freematchcertattr(&conf); } - /* test valid config syntax */ { conf.name = "test"; @@ -489,7 +466,6 @@ vY/uPjA=\n\ freematchcertattr(&conf); } - /* test invalid config syntax */ { conf.name = "test"; @@ -510,7 +486,6 @@ vY/uPjA=\n\ ok(0,addmatchcertattr(&conf, "SubjectAltName:rID:1:2"),"test invalid syntax rID"); freematchcertattr(&conf); } - /* test explicit & implicit combined */ { struct hostportres hp; @@ -531,7 +506,6 @@ vY/uPjA=\n\ while(list_shift(conf.hostports)); freematchcertattr(&conf); } - /* test multiple explicit checks*/ { struct hostportres hp; @@ -556,6 +530,7 @@ vY/uPjA=\n\ freematchcertattr(&conf); } + printf("1..%d\n", numtests); list_free(conf.hostports); X509_free(certsimple); @@ -568,7 +543,6 @@ vY/uPjA=\n\ X509_free(certsanipv6); X509_free(certsanipv6indns); X509_free(certcomplex); - X509_free(certcomplexother); X509_free(certsanuri); X509_free(certsanuriother); X509_free(certsanrid); @@ -577,6 +551,5 @@ vY/uPjA=\n\ X509_free(certsanothernameother); X509_free(certmulti); X509_free(certmultiother); - return 0; } diff --git a/tlscommon.c b/tlscommon.c index b033c6ad..3b892aff 100644 --- a/tlscommon.c +++ b/tlscommon.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include #include @@ -626,13 +628,227 @@ static int certattr_matchip(GENERAL_NAME *gn, struct certattrmatch *match){ && !memcmp(ASN1_STRING_get0_data(gn->d.iPAddress), &match->ipaddr, l)) ? 1 : 0 ; } +static char* concatenate(char* s1, char* s2) { + char* result = NULL; + if(s1 == NULL && s2 == NULL) { + return NULL; + } + if(s1 == NULL) { + result = (char*) calloc(strlen(s2) + 1, sizeof(char)); + strcpy(result, s2); + return result; + } + if(s2 == NULL) { + result = (char*) calloc(strlen(s1) + 1, sizeof(char)); + strcpy(result, s1); + return result; + } + result = (char*) calloc(strlen(s1) + strlen(s2) + 1, sizeof(char)); + if(result == NULL) { + return NULL; + } + strcpy(result, s1); + memcpy(result + strlen(s1), s2, strlen(s2)); + return result; +} + +static char* append(int n, ...) { + char* val = NULL; + va_list ap; + int i; + + va_start(ap, n); + char* result = NULL; + for(i = 0; i < n; i++) { + val = va_arg(ap, char*); + result = concatenate(result, val); + } + va_end(ap); + return result; +} + +static char* extractFirstPortion(const char* dns, const char c) { + char* result = NULL; + char *firstPos = strchr(dns, c); + if(firstPos == NULL) { + return result; + } + int lenOfFirstPortion = strlen(dns) - strlen(firstPos); + result = (char*) calloc(lenOfFirstPortion + 1, sizeof(char)); + if(result == NULL) { + return NULL; + } + memcpy(result, dns, lenOfFirstPortion); + return result; +} + +static int regexMatcher(char* pattern, char* str) { + regex_t regex; + char buffer[100]; + + int reti = regcomp(®ex, pattern, REG_EXTENDED); + if (reti) { + regerror(reti, ®ex, buffer, sizeof(buffer)); + debug(DBG_ERR, "regcomp failed with '%s'\n", buffer); + return 0; + } + reti = regexec(®ex, str, 0, NULL, 0); + if(reti) { + return 0; + } + return 1; +} + +static char* convertLenToString(int len) { + int length = snprintf(NULL, 0, "%d", len); + char* str = (char*) calloc(length+1, sizeof(char)); + if(str == NULL) { + return NULL; + } + snprintf(str, length+1, "%d", len); + char* result = append(3, "{0,", str, "}"); + free(str); + return result; +} + +// compareWithModifiedHostname generates a regular expression based on the first label present +// in certificate DNS. Matches the regular expression with the first label of hostname and +// returns result - 1 - matched, 0 - unmatched +// Refer https://datatracker.ietf.org/doc/html/rfc6125#section-6.4 +// buzz* => ^buzz[a-zA-Z0-9-]{0,59}$ +// *buzz => ^[a-z0-9A-Z][a-z0-9A-Z-]{0,59}buzz$ +// b*uzz => ^b[a-zA-Z0-9-]{0,59}uzz$ +// 63 - Maximum length of a label +// Wiki: A domain name consists of one or more labels, each of which is formed from the set of ASCII letters, +// digits, and hyphens (a-z, A-Z, 0–9, -), but not starting or ending with a hyphen. +// The labels are case-insensitive +// Internationlized Domain Names: www.äöü.com converted to www.xn--4ca0bs.com. xn--4ca0bs is considered +// a valid DN and let through. diseñolatinoamericano.com => xn--diseolatinoamericano-66b.com will also be +// let through. So IDNs are passed in Punycode encoded and xn-- says everything that follows is unicode encoded. +static int compareWithModifiedHostname(char* certDNS, char* hostname) { + + int retCode = 0; + char* firstPorCertDNS = extractFirstPortion(certDNS, '.'); + char* pattern = NULL; + char* lenRegex = NULL; + + if(firstPorCertDNS == NULL) { + return retCode; + } + char* firstPorHostName = extractFirstPortion(hostname, '.'); + if(firstPorHostName == NULL) { + goto cleanup; + } + + char* regex2 = "[a-zA-Z0-9-]"; + char* startRegex = "^"; + char* endRregex = "$"; + char* wildcard = "*"; + + // Handles *.3af521.net (cert DNS) and idp.3af521.net (host name) case + if(strlen(firstPorCertDNS) == 1 && firstPorCertDNS[0] == '*') { + + char* lastPorHostName = strchr(hostname, '.'); + if(lastPorHostName == NULL) { + goto cleanup; + } + char* modifiedHostName = append(2, wildcard, lastPorHostName); + if(modifiedHostName == NULL) { + goto cleanup; + } + if(strlen(modifiedHostName) == strlen(certDNS) && memcmp(modifiedHostName, certDNS, strlen(certDNS)) == 0) { + retCode = 1; + goto cleanupBlock; + } + goto cleanupBlock; + + cleanupBlock: + if(firstPorCertDNS != NULL) { + free(firstPorCertDNS); + } + if(firstPorHostName != NULL) { + free(firstPorHostName); + } + if(modifiedHostName != NULL) { + free(modifiedHostName); + } + return retCode; + } + + char* asteriskPos = strchr(firstPorCertDNS, '*'); + if(asteriskPos == NULL) { + goto cleanup; + } + + lenRegex = convertLenToString(63 - strlen(firstPorCertDNS) - 1); + if(lenRegex == NULL) { + goto cleanup; + } + + if(firstPorCertDNS[0] == '*') { + char* regex1 = "^[a-z0-9A-Z][a-z0-9A-Z-]"; + pattern = append(4, regex1, lenRegex, asteriskPos + 1, endRregex); + if(pattern == NULL) { + goto cleanup; + } + } else if(firstPorCertDNS[strlen(firstPorCertDNS)-1] == '*') { + char* regex1 = extractFirstPortion(firstPorCertDNS, '*'); + if(regex1 == NULL) { + goto cleanup; + } + pattern = append(5, startRegex, regex1, regex2, lenRegex, endRregex); + if(pattern == NULL) { + free(regex1); + goto cleanup; + } + free(regex1); + } else { + char* regex1 = extractFirstPortion(firstPorCertDNS, '*'); + if(regex1 == NULL) { + goto cleanup; + } + pattern = append(6, startRegex, regex1, regex2, lenRegex, asteriskPos + 1, endRregex); + if(pattern == NULL) { + free(regex1); + goto cleanup; + } + free(regex1); + } + + if(regexMatcher(pattern, firstPorHostName)) { + retCode = 1; + } + + goto cleanup; + + cleanup: + if(firstPorCertDNS != NULL) { + free(firstPorCertDNS); + } + if(firstPorHostName != NULL) { + free(firstPorHostName); + } + if(lenRegex != NULL) { + free(lenRegex); + } + if(pattern != NULL) { + free(pattern); + } + return retCode; +} + static int _general_name_regex_match(char *v, int l, struct certattrmatch *match) { char *s; if (l <= 0 ) return 0; + + // Found DNS if (match->exact) { if (l == strlen(match->exact) && memcmp(v, match->exact, l) == 0) return 1; + if(compareWithModifiedHostname(v, match->exact)) { + return 1; + } return 0; }