Correct the test of IP addresses in Subject Alternative Name extensions.

bug 103752.
This commit is contained in:
nelsonb%netscape.com 2002-08-01 22:51:56 +00:00
parent 8509821897
commit 8e038c1211

View File

@ -34,7 +34,7 @@
/* /*
* Certificate handling code * Certificate handling code
* *
* $Id: certdb.c,v 1.35 2002/07/30 23:15:43 nelsonb%netscape.com Exp $ * $Id: certdb.c,v 1.36 2002/08/01 22:51:56 nelsonb%netscape.com Exp $
*/ */
#include "nssilock.h" #include "nssilock.h"
@ -1238,24 +1238,25 @@ CERT_AddOKDomainName(CERTCertificate *cert, const char *hn)
return SECSuccess; return SECSuccess;
} }
SECStatus /* returns SECSuccess if hn matches pattern cn,
cert_TestHostName(const char * cn, const char * hn) ** returns SECFailure with SSL_ERROR_BAD_CERT_DOMAIN if no match,
** returns SECFailure with some other error code if another error occurs.
**
** may modify cn, so caller must pass a modifiable copy.
*/
static SECStatus
cert_TestHostName(char * cn, const char * hn)
{ {
char * hndomain; char * hndomain;
int regvalid; int regvalid;
char cnbuf[80];
if ((hndomain = PORT_Strchr(hn, '.')) == NULL) { if ((hndomain = PORT_Strchr(hn, '.')) == NULL) {
/* No domain in URI host name */ /* No domain in URI host name */
char * cndomain; char * cndomain;
long nameLen;
if ((cndomain = PORT_Strchr(cn, '.')) != NULL && if ((cndomain = PORT_Strchr(cn, '.')) != NULL &&
(nameLen = cndomain - cn) < sizeof cnbuf && (cndomain - cn) > 0) {
nameLen > 0) {
/* there is a domain in the cn string, so chop it off */ /* there is a domain in the cn string, so chop it off */
memcpy(cnbuf, cn, nameLen); *cndomain = '\0';
cnbuf[nameLen] = '\0';
cn = (const char *)cnbuf;
} }
} }
@ -1300,10 +1301,13 @@ cert_VerifySubjectAltName(CERTCertificate *cert, const char *hn)
CERTGeneralName * current; CERTGeneralName * current;
char * cn; char * cn;
int cnBufLen; int cnBufLen;
int cnLen;
unsigned int hnLen; unsigned int hnLen;
int DNSextCount = 0;
int IPextCount = 0;
PRBool isIPaddr;
SECStatus rv = SECFailure; SECStatus rv = SECFailure;
SECItem subAltName; SECItem subAltName;
PRNetAddr netAddr;
char cnbuf[128]; char cnbuf[128];
subAltName.data = NULL; subAltName.data = NULL;
@ -1314,10 +1318,9 @@ cert_VerifySubjectAltName(CERTCertificate *cert, const char *hn)
rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME, rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME,
&subAltName); &subAltName);
if (rv != SECSuccess) { if (rv != SECSuccess) {
if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
goto finish; goto finish;
} }
isIPaddr = (PR_SUCCESS == PR_StringToNetAddr(hn, &netAddr));
rv = SECFailure; rv = SECFailure;
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if (!arena) if (!arena)
@ -1330,28 +1333,61 @@ cert_VerifySubjectAltName(CERTCertificate *cert, const char *hn)
do { do {
switch (current->type) { switch (current->type) {
case certDNSName: case certDNSName:
/* DNS name current->name.other.data is not null terminated. if (!isIPaddr) {
** so much copy it. Then downshift it. /* DNS name current->name.other.data is not null terminated.
*/ ** so must copy it.
cnLen = current->name.other.len; */
if (cnLen + 1 > cnBufLen) { int cnLen = current->name.other.len;
cnBufLen = cnLen + 1; if (cnLen + 1 > cnBufLen) {
cn = (char *)PORT_ArenaAlloc(arena, cnBufLen); cnBufLen = cnLen + 1;
if (!cn) cn = (char *)PORT_ArenaAlloc(arena, cnBufLen);
if (!cn)
goto finish;
}
PORT_Memcpy(cn, current->name.other.data,
current->name.other.len);
cn[cnLen] = 0;
rv = cert_TestHostName(cn ,hn);
if (rv == SECSuccess)
goto finish; goto finish;
} }
PORT_Memcpy(cn, current->name.other.data, current->name.other.len); DNSextCount++;
cn[cnLen] = 0;
rv = cert_TestHostName(cn ,hn);
if (rv == SECSuccess)
goto finish;
break; break;
case certIPAddress: case certIPAddress:
if (hnLen == current->name.other.len && if (isIPaddr) {
0 == memcmp(hn, current->name.other.data, hnLen)) { int match = 0;
rv = SECSuccess; PRIPv6Addr v6Addr;
goto finish; if (current->name.other.len == 4 && /* IP v4 address */
netAddr.inet.family == PR_AF_INET) {
match = !memcmp(&netAddr.inet.ip,
current->name.other.data, 4);
} else if (current->name.other.len == 16 && /* IP v6 address */
netAddr.ipv6.family == PR_AF_INET6) {
match = !memcmp(&netAddr.ipv6.ip,
current->name.other.data, 16);
} else if (current->name.other.len == 16 && /* IP v6 address */
netAddr.inet.family == PR_AF_INET) {
/* convert netAddr to ipv6, then compare. */
/* ipv4 must be in Network Byte Order on input. */
PR_ConvertIPv4AddrToIPv6(netAddr.inet.ip, &v6Addr);
match = !memcmp(&v6Addr, current->name.other.data, 16);
} else if (current->name.other.len == 4 && /* IP v4 address */
netAddr.inet.family == PR_AF_INET6) {
/* convert netAddr to ipv6, then compare. */
PRUint32 ipv4 = (current->name.other.data[0] << 24) |
(current->name.other.data[1] << 16) |
(current->name.other.data[2] << 8) |
current->name.other.data[3];
/* ipv4 must be in Network Byte Order on input. */
PR_ConvertIPv4AddrToIPv6(PR_htonl(ipv4), &v6Addr);
match = !memcmp(&netAddr.ipv6.ip, &v6Addr, 16);
}
if (match) {
rv = SECSuccess;
goto finish;
}
} }
IPextCount++;
break; break;
default: default:
break; break;
@ -1359,7 +1395,12 @@ cert_VerifySubjectAltName(CERTCertificate *cert, const char *hn)
current = cert_get_next_general_name(current); current = cert_get_next_general_name(current);
} while (current != nameList); } while (current != nameList);
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); if ((!isIPaddr && !DNSextCount) || (isIPaddr && !IPextCount)) {
/* no relevant value in the extension was found. */
PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND);
} else {
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
}
rv = SECFailure; rv = SECFailure;
finish: finish:
@ -1400,9 +1441,11 @@ CERT_VerifyCertName(CERTCertificate *cert, const char *hn)
} }
} }
/* test the cert's Subject Alternative Name extension, if any */ /* Per RFC 2818, if the SubjectAltName extension is present, it must
** be used as the cert's identity.
*/
rv = cert_VerifySubjectAltName(cert, hn); rv = cert_VerifySubjectAltName(cert, hn);
if (rv == SECSuccess || PORT_GetError() != SSL_ERROR_BAD_CERT_DOMAIN) if (rv == SECSuccess || PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND)
return rv; return rv;
/* try the cert extension first, then the common name */ /* try the cert extension first, then the common name */
@ -1413,7 +1456,8 @@ CERT_VerifyCertName(CERTCertificate *cert, const char *hn)
if ( cn ) { if ( cn ) {
rv = cert_TestHostName(cn, hn); rv = cert_TestHostName(cn, hn);
PORT_Free(cn); PORT_Free(cn);
} } else
PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
return rv; return rv;
} }