// TODO: Remove #include "pkix/pkixnss.h", #include "cert.h", // #include "ScopedPtr.h", etc. when this is rewritten to be independent of // NSS. Result CheckNameConstraints(Input encodedNameConstraints, const BackCert& firstChild, KeyPurposeId requiredEKUIfPresent) { ScopedPtr<PLArenaPool, PORT_FreeArena_false> arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); if (!arena) { return Result::FATAL_ERROR_NO_MEMORY; } SECItem encodedNameConstraintsSECItem = UnsafeMapInputToSECItem(encodedNameConstraints); // Owned by arena const CERTNameConstraints* constraints = CERT_DecodeNameConstraintsExtension(arena.get(), &encodedNameConstraintsSECItem); if (!constraints) { return MapPRErrorCodeToResult(PR_GetError()); } for (const BackCert* child = &firstChild; child; child = child->childCert) { SECItem childCertDER = UnsafeMapInputToSECItem(child->GetDER()); ScopedPtr<CERTCertificate, CERT_DestroyCertificate> nssCert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &childCertDER, nullptr, false, true)); if (!nssCert) { return MapPRErrorCodeToResult(PR_GetError()); } bool includeCN = child->endEntityOrCA == EndEntityOrCA::MustBeEndEntity && requiredEKUIfPresent == KeyPurposeId::id_kp_serverAuth; // owned by arena const CERTGeneralName* names(CERT_GetConstrainedCertificateNames(nssCert.get(), arena.get(), includeCN)); if (!names) { return MapPRErrorCodeToResult(PR_GetError()); } CERTGeneralName* currentName = const_cast<CERTGeneralName*>(names); do { if (CERT_CheckNameSpace(arena.get(), constraints, currentName) != SECSuccess) { // XXX: It seems like CERT_CheckNameSpace doesn't always call // PR_SetError when it fails, so we ignore what PR_GetError would // return. NSS's cert_VerifyCertChainOld does something similar. return Result::ERROR_CERT_NOT_IN_NAME_SPACE; } currentName = CERT_GetNextGeneralName(currentName); } while (currentName != names); } return Success; }
// 4.2.1.10. Name Constraints Result CheckNameConstraints(BackCert& cert) { if (!cert.encodedNameConstraints) { return Success; } PLArenaPool* arena = cert.GetArena(); if (!arena) { return FatalError; } // Owned by arena const CERTNameConstraints* constraints = CERT_DecodeNameConstraintsExtension(arena, cert.encodedNameConstraints); if (!constraints) { return MapSECStatus(SECFailure); } for (BackCert* prev = cert.childCert; prev; prev = prev->childCert) { const CERTGeneralName* names = nullptr; Result rv = prev->GetConstrainedNames(&names); if (rv != Success) { return rv; } PORT_Assert(names); CERTGeneralName* currentName = const_cast<CERTGeneralName*>(names); do { if (CERT_CheckNameSpace(arena, constraints, currentName) != SECSuccess) { // XXX: It seems like CERT_CheckNameSpace doesn't always call // PR_SetError when it fails. We set the error code here, though this // may be papering over some fatal errors. NSS's // cert_VerifyCertChainOld does something similar. PR_SetError(SEC_ERROR_CERT_NOT_IN_NAME_SPACE, 0); return RecoverableError; } currentName = CERT_GetNextGeneralName(currentName); } while (currentName != names); } return Success; }
// returns TRUE if SAN was used to produce names // return FALSE if nothing was produced // names => a single name or a list of names // multipleNames => whether multiple names were delivered static bool GetSubjectAltNames(CERTCertificate *nssCert, nsINSSComponent *component, nsString &allNames, uint32_t &nameCount) { allNames.Truncate(); nameCount = 0; SECItem altNameExtension = {siBuffer, nullptr, 0 }; CERTGeneralName *sanNameList = nullptr; SECStatus rv = CERT_FindCertExtension(nssCert, SEC_OID_X509_SUBJECT_ALT_NAME, &altNameExtension); if (rv != SECSuccess) { return false; } ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); if (!arena) { return false; } sanNameList = CERT_DecodeAltNameExtension(arena.get(), &altNameExtension); if (!sanNameList) { return false; } SECITEM_FreeItem(&altNameExtension, false); CERTGeneralName *current = sanNameList; do { nsAutoString name; switch (current->type) { case certDNSName: { nsDependentCSubstring nameFromCert(reinterpret_cast<char*> (current->name.other.data), current->name.other.len); // dNSName fields are defined as type IA5String and thus should // be limited to ASCII characters. if (IsASCII(nameFromCert)) { name.Assign(NS_ConvertASCIItoUTF16(nameFromCert)); if (!allNames.IsEmpty()) { allNames.AppendLiteral(", "); } ++nameCount; allNames.Append(name); } } break; case certIPAddress: { char buf[INET6_ADDRSTRLEN]; PRNetAddr addr; if (current->name.other.len == 4) { addr.inet.family = PR_AF_INET; memcpy(&addr.inet.ip, current->name.other.data, current->name.other.len); PR_NetAddrToString(&addr, buf, sizeof(buf)); name.AssignASCII(buf); } else if (current->name.other.len == 16) { addr.ipv6.family = PR_AF_INET6; memcpy(&addr.ipv6.ip, current->name.other.data, current->name.other.len); PR_NetAddrToString(&addr, buf, sizeof(buf)); name.AssignASCII(buf); } else { /* invalid IP address */ } if (!name.IsEmpty()) { if (!allNames.IsEmpty()) { allNames.AppendLiteral(", "); } ++nameCount; allNames.Append(name); } break; } default: // all other types of names are ignored break; } current = CERT_GetNextGeneralName(current); } while (current != sanNameList); // double linked return true; }
bool cert_VerifySubjectAltName(const CERTCertificate *cert, const char *name) { SECStatus rv; SECItem subAltName; PLArenaPool *arena = NULL; CERTGeneralName *nameList = NULL; CERTGeneralName *current = NULL; bool san_ip = FALSE; unsigned int len = strlen(name); ip_address myip; rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME, &subAltName); if (rv != SECSuccess) { DBG(DBG_X509, DBG_log("certificate contains no subjectAltName extension")); return FALSE; } if (tnatoaddr(name, 0, AF_UNSPEC, &myip) == NULL) san_ip = TRUE; arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); passert(arena != NULL); nameList = current = CERT_DecodeAltNameExtension(arena, &subAltName); passert(current != NULL); do { switch (current->type) { case certDNSName: case certRFC822Name: if (san_ip) break; if (current->name.other.len == len) { if (memcmp(current->name.other.data, name, len) == 0) { DBG(DBG_X509, DBG_log("subjectAltname %s found in certificate", name)); PORT_FreeArena(arena, PR_FALSE); return TRUE; } } if (current->name.other.len != 0 && current->name.other.len < IDTOA_BUF) { char osan[IDTOA_BUF]; memcpy(osan,current->name.other.data, current->name.other.len); osan[current->name.other.len] = '\0'; DBG(DBG_X509, DBG_log("subjectAltname (len=%d) %s not match %s", current->name.other.len, osan, name)); } else { DBG(DBG_X509, DBG_log("subjectAltname <TOO BIG TO PRINT> does not match %s", name)); } break; case certIPAddress: if (!san_ip) break; if ((current->name.other.len == 4) && (addrtypeof(&myip) == AF_INET)) { if (memcmp(current->name.other.data, &myip.u.v4.sin_addr.s_addr, 4) == 0) { DBG(DBG_X509, DBG_log("subjectAltname IPv4 matches %s", name)); PORT_FreeArena(arena, PR_FALSE); return TRUE; } else { DBG(DBG_X509, DBG_log("subjectAltname IPv4 does not match %s", name)); break; } } if ((current->name.other.len == 16) && (addrtypeof(&myip) == AF_INET6)) { if (memcmp(current->name.other.data, &myip.u.v6.sin6_addr.s6_addr, 16) == 0) { DBG(DBG_X509, DBG_log("subjectAltname IPv6 matches %s", name)); PORT_FreeArena(arena, PR_FALSE); return TRUE; } else { DBG(DBG_X509, DBG_log("subjectAltname IPv6 does not match %s", name)); break; } } DBG(DBG_X509, DBG_log("subjectAltnamea IP address family mismatch for %s", name)); break; default: break; } current = CERT_GetNextGeneralName(current); } while (current != nameList); loglog(RC_LOG_SERIOUS, "No matching subjectAltName found"); /* Don't free nameList, it's part of the arena. */ PORT_FreeArena(arena, PR_FALSE); return FALSE; }