static const WCHAR rootW[] = { 'R','o','o','t',0 };
+/* Finds cert in store by comparing the cert's hashes. */
+static PCCERT_CONTEXT CRYPT_FindCertInStore(HCERTSTORE store,
+ PCCERT_CONTEXT cert)
+{
+ PCCERT_CONTEXT matching = NULL;
+ BYTE hash[20];
+ DWORD size = sizeof(hash);
+
+ if (CertGetCertificateContextProperty(cert, CERT_HASH_PROP_ID, hash, &size))
+ {
+ CRYPT_HASH_BLOB blob = { sizeof(hash), hash };
+
+ matching = CertFindCertificateInStore(store, cert->dwCertEncodingType,
+ 0, CERT_FIND_SHA1_HASH, &blob, NULL);
+ }
+ return matching;
+}
+
static BOOL CRYPT_CheckRestrictedRoot(HCERTSTORE store)
{
BOOL ret = TRUE;
{
HCERTSTORE rootStore = CertOpenSystemStoreW(0, rootW);
PCCERT_CONTEXT cert = NULL, check;
- BYTE hash[20];
- DWORD size;
do {
cert = CertEnumCertificatesInStore(store, cert);
if (cert)
{
- size = sizeof(hash);
-
- ret = CertGetCertificateContextProperty(cert, CERT_HASH_PROP_ID,
- hash, &size);
- if (ret)
- {
- CRYPT_HASH_BLOB blob = { sizeof(hash), hash };
-
- check = CertFindCertificateInStore(rootStore,
- cert->dwCertEncodingType, 0, CERT_FIND_SHA1_HASH, &blob,
- NULL);
- if (!check)
- ret = FALSE;
- else
- CertFreeCertificateContext(check);
- }
+ if (!(check = CRYPT_FindCertInStore(rootStore, cert)))
+ ret = FALSE;
+ else
+ CertFreeCertificateContext(check);
}
} while (ret && cert);
if (cert)
LONG ref;
} CertificateChain, *PCertificateChain;
-static inline BOOL CRYPT_IsCertificateSelfSigned(PCCERT_CONTEXT cert)
+static BOOL CRYPT_IsCertificateSelfSigned(PCCERT_CONTEXT cert)
{
- return CertCompareCertificateName(cert->dwCertEncodingType,
- &cert->pCertInfo->Subject, &cert->pCertInfo->Issuer);
+ PCERT_EXTENSION ext;
+ DWORD size;
+ BOOL ret;
+
+ if ((ext = CertFindExtension(szOID_AUTHORITY_KEY_IDENTIFIER2,
+ cert->pCertInfo->cExtension, cert->pCertInfo->rgExtension)))
+ {
+ CERT_AUTHORITY_KEY_ID2_INFO *info;
+
+ ret = CryptDecodeObjectEx(cert->dwCertEncodingType,
+ X509_AUTHORITY_KEY_ID2, ext->Value.pbData, ext->Value.cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, NULL,
+ &info, &size);
+ if (ret)
+ {
+ if (info->AuthorityCertIssuer.cAltEntry &&
+ info->AuthorityCertSerialNumber.cbData)
+ {
+ PCERT_ALT_NAME_ENTRY directoryName = NULL;
+ DWORD i;
+
+ for (i = 0; !directoryName &&
+ i < info->AuthorityCertIssuer.cAltEntry; i++)
+ if (info->AuthorityCertIssuer.rgAltEntry[i].dwAltNameChoice
+ == CERT_ALT_NAME_DIRECTORY_NAME)
+ directoryName =
+ &info->AuthorityCertIssuer.rgAltEntry[i];
+ if (directoryName)
+ {
+ ret = CertCompareCertificateName(cert->dwCertEncodingType,
+ &directoryName->u.DirectoryName, &cert->pCertInfo->Issuer)
+ && CertCompareIntegerBlob(&info->AuthorityCertSerialNumber,
+ &cert->pCertInfo->SerialNumber);
+ }
+ else
+ {
+ FIXME("no supported name type in authority key id2\n");
+ ret = FALSE;
+ }
+ }
+ else if (info->KeyId.cbData)
+ {
+ ret = CertGetCertificateContextProperty(cert,
+ CERT_KEY_IDENTIFIER_PROP_ID, NULL, &size);
+ if (ret && size == info->KeyId.cbData)
+ {
+ LPBYTE buf = CryptMemAlloc(size);
+
+ if (buf)
+ {
+ CertGetCertificateContextProperty(cert,
+ CERT_KEY_IDENTIFIER_PROP_ID, buf, &size);
+ ret = !memcmp(buf, info->KeyId.pbData, size);
+ CryptMemFree(buf);
+ }
+ }
+ else
+ ret = FALSE;
+ }
+ LocalFree(info);
+ }
+ }
+ else if ((ext = CertFindExtension(szOID_AUTHORITY_KEY_IDENTIFIER,
+ cert->pCertInfo->cExtension, cert->pCertInfo->rgExtension)))
+ {
+ CERT_AUTHORITY_KEY_ID_INFO *info;
+
+ ret = CryptDecodeObjectEx(cert->dwCertEncodingType,
+ X509_AUTHORITY_KEY_ID, ext->Value.pbData, ext->Value.cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, NULL,
+ &info, &size);
+ if (ret)
+ {
+ if (info->CertIssuer.cbData && info->CertSerialNumber.cbData)
+ {
+ ret = CertCompareCertificateName(cert->dwCertEncodingType,
+ &info->CertIssuer, &cert->pCertInfo->Issuer) &&
+ CertCompareIntegerBlob(&info->CertSerialNumber,
+ &cert->pCertInfo->SerialNumber);
+ }
+ else if (info->KeyId.cbData)
+ {
+ ret = CertGetCertificateContextProperty(cert,
+ CERT_KEY_IDENTIFIER_PROP_ID, NULL, &size);
+ if (ret && size == info->KeyId.cbData)
+ {
+ LPBYTE buf = CryptMemAlloc(size);
+
+ if (buf)
+ {
+ CertGetCertificateContextProperty(cert,
+ CERT_KEY_IDENTIFIER_PROP_ID, buf, &size);
+ ret = !memcmp(buf, info->KeyId.pbData, size);
+ CryptMemFree(buf);
+ }
+ else
+ ret = FALSE;
+ }
+ else
+ ret = FALSE;
+ }
+ else
+ ret = FALSE;
+ LocalFree(info);
+ }
+ }
+ else
+ ret = CertCompareCertificateName(cert->dwCertEncodingType,
+ &cert->pCertInfo->Subject, &cert->pCertInfo->Issuer);
+ return ret;
}
static void CRYPT_FreeChainElement(PCERT_CHAIN_ELEMENT element)
static void CRYPT_CheckTrustedStatus(HCERTSTORE hRoot,
PCERT_CHAIN_ELEMENT rootElement)
{
- BYTE hash[20];
- DWORD size = sizeof(hash);
- CRYPT_HASH_BLOB blob = { sizeof(hash), hash };
- PCCERT_CONTEXT trustedRoot;
-
- CertGetCertificateContextProperty(rootElement->pCertContext,
- CERT_HASH_PROP_ID, hash, &size);
- trustedRoot = CertFindCertificateInStore(hRoot,
- rootElement->pCertContext->dwCertEncodingType, 0, CERT_FIND_SHA1_HASH,
- &blob, NULL);
+ PCCERT_CONTEXT trustedRoot = CRYPT_FindCertInStore(hRoot,
+ rootElement->pCertContext);
+
if (!trustedRoot)
rootElement->TrustStatus.dwErrorStatus |=
CERT_TRUST_IS_UNTRUSTED_ROOT;
}
/* Checks element's basic constraints to see if it can act as a CA, with
- * remainingCAs CAs left in this chain. A root certificate is assumed to be
- * allowed to be a CA whether or not the basic constraints extension is present,
- * whereas an intermediate CA cert is not. This matches the expected usage in
- * RFC 3280: a conforming intermediate CA MUST contain the basic constraints
- * extension. It also appears to match Microsoft's implementation.
+ * remainingCAs CAs left in this chain. In general, a cert must include the
+ * basic constraints extension, with the CA flag asserted, in order to be
+ * allowed to be a CA. A V1 or V2 cert, which has no extensions, is also
+ * allowed to be a CA if it's installed locally (in the engine's world store.)
+ * This matches the expected usage in RFC 5280, section 4.2.1.9: a conforming
+ * CA MUST include the basic constraints extension in all certificates that are
+ * used to validate digital signatures on certificates. It also matches
+ * section 6.1.4(k): "If a certificate is a v1 or v2 certificate, then the
+ * application MUST either verify that the certificate is a CA certificate
+ * through out-of-band means or reject the certificate." Rejecting the
+ * certificate prohibits a large number of commonly used certificates, so
+ * accepting locally installed ones is a compromise.
+ * Root certificates are also allowed to be CAs even without a basic
+ * constraints extension. This is implied by RFC 5280, section 6.1: the
+ * root of a certificate chain's only requirement is that it was used to issue
+ * the next certificate in the chain.
* Updates chainConstraints with the element's constraints, if:
* 1. chainConstraints doesn't have a path length constraint, or
* 2. element's path length constraint is smaller than chainConstraints's
* Returns TRUE if the element can be a CA, and the length of the remaining
* chain is valid.
*/
-static BOOL CRYPT_CheckBasicConstraintsForCA(PCCERT_CONTEXT cert,
- CERT_BASIC_CONSTRAINTS2_INFO *chainConstraints, DWORD remainingCAs,
- BOOL isRoot, BOOL *pathLengthConstraintViolated)
+static BOOL CRYPT_CheckBasicConstraintsForCA(PCertificateChainEngine engine,
+ PCCERT_CONTEXT cert, CERT_BASIC_CONSTRAINTS2_INFO *chainConstraints,
+ DWORD remainingCAs, BOOL isRoot, BOOL *pathLengthConstraintViolated)
{
- BOOL validBasicConstraints;
+ BOOL validBasicConstraints, implicitCA = FALSE;
CERT_BASIC_CONSTRAINTS2_INFO constraints;
+ if (isRoot)
+ implicitCA = TRUE;
+ else if (cert->pCertInfo->dwVersion == CERT_V1 ||
+ cert->pCertInfo->dwVersion == CERT_V2)
+ {
+ BYTE hash[20];
+ DWORD size = sizeof(hash);
+
+ if (CertGetCertificateContextProperty(cert, CERT_HASH_PROP_ID,
+ hash, &size))
+ {
+ CRYPT_HASH_BLOB blob = { sizeof(hash), hash };
+ PCCERT_CONTEXT localCert = CertFindCertificateInStore(
+ engine->hWorld, cert->dwCertEncodingType, 0, CERT_FIND_SHA1_HASH,
+ &blob, NULL);
+
+ if (localCert)
+ {
+ CertFreeCertificateContext(localCert);
+ implicitCA = TRUE;
+ }
+ }
+ }
if ((validBasicConstraints = CRYPT_DecodeBasicConstraints(cert,
- &constraints, isRoot)))
+ &constraints, implicitCA)))
{
+ chainConstraints->fCA = constraints.fCA;
if (!constraints.fCA)
{
TRACE_(chain)("chain element %d can't be a CA\n", remainingCAs + 1);
return validBasicConstraints;
}
+static BOOL domain_name_matches(LPCWSTR constraint, LPCWSTR name)
+{
+ BOOL match;
+
+ /* RFC 5280, section 4.2.1.10:
+ * "For URIs, the constraint applies to the host part of the name...
+ * When the constraint begins with a period, it MAY be expanded with one
+ * or more labels. That is, the constraint ".example.com" is satisfied by
+ * both host.example.com and my.host.example.com. However, the constraint
+ * ".example.com" is not satisfied by "example.com". When the constraint
+ * does not begin with a period, it specifies a host."
+ * and for email addresses,
+ * "To indicate all Internet mail addresses on a particular host, the
+ * constraint is specified as the host name. For example, the constraint
+ * "example.com" is satisfied by any mail address at the host
+ * "example.com". To specify any address within a domain, the constraint
+ * is specified with a leading period (as with URIs)."
+ */
+ if (constraint[0] == '.')
+ {
+ /* Must be strictly greater than, a name can't begin with '.' */
+ if (lstrlenW(name) > lstrlenW(constraint))
+ match = !lstrcmpiW(name + lstrlenW(name) - lstrlenW(constraint),
+ constraint);
+ else
+ {
+ /* name is too short, no match */
+ match = FALSE;
+ }
+ }
+ else
+ match = !lstrcmpiW(name, constraint);
+ return match;
+}
+
static BOOL url_matches(LPCWSTR constraint, LPCWSTR name,
DWORD *trustErrorStatus)
{
*trustErrorStatus |= CERT_TRUST_INVALID_NAME_CONSTRAINTS;
else if (!name)
; /* no match */
- else if (constraint[0] == '.')
+ else
{
- if (lstrlenW(name) > lstrlenW(constraint))
- match = !lstrcmpiW(name + lstrlenW(name) - lstrlenW(constraint),
- constraint);
+ LPCWSTR colon, authority_end, at, hostname = NULL;
+ /* The maximum length for a hostname is 254 in the DNS, see RFC 1034 */
+ WCHAR hostname_buf[255];
+
+ /* RFC 5280: only the hostname portion of the URL is compared. From
+ * section 4.2.1.10:
+ * "For URIs, the constraint applies to the host part of the name.
+ * The constraint MUST be specified as a fully qualified domain name
+ * and MAY specify a host or a domain."
+ * The format for URIs is in RFC 2396.
+ *
+ * First, remove any scheme that's present. */
+ colon = strchrW(name, ':');
+ if (colon && *(colon + 1) == '/' && *(colon + 2) == '/')
+ name = colon + 3;
+ /* Next, find the end of the authority component. (The authority is
+ * generally just the hostname, but it may contain a username or a port.
+ * Those are removed next.)
+ */
+ authority_end = strchrW(name, '/');
+ if (!authority_end)
+ authority_end = strchrW(name, '?');
+ if (!authority_end)
+ authority_end = name + strlenW(name);
+ /* Remove any port number from the authority. The userinfo portion
+ * of an authority may contain a colon, so stop if a userinfo portion
+ * is found (indicated by '@').
+ */
+ for (colon = authority_end; colon >= name && *colon != ':' &&
+ *colon != '@'; colon--)
+ ;
+ if (*colon == ':')
+ authority_end = colon;
+ /* Remove any username from the authority */
+ if ((at = strchrW(name, '@')))
+ name = at;
+ /* Ignore any path or query portion of the URL. */
+ if (*authority_end)
+ {
+ if (authority_end - name < sizeof(hostname_buf) /
+ sizeof(hostname_buf[0]))
+ {
+ memcpy(hostname_buf, name,
+ (authority_end - name) * sizeof(WCHAR));
+ hostname_buf[authority_end - name] = 0;
+ hostname = hostname_buf;
+ }
+ /* else: Hostname is too long, not a match */
+ }
+ else
+ hostname = name;
+ if (hostname)
+ match = domain_name_matches(constraint, hostname);
}
- else
- match = !lstrcmpiW(constraint, name);
return match;
}
*trustErrorStatus |= CERT_TRUST_INVALID_NAME_CONSTRAINTS;
else if (!name)
; /* no match */
- else if ((at = strchrW(constraint, '@')))
+ else if (strchrW(constraint, '@'))
match = !lstrcmpiW(constraint, name);
else
{
if ((at = strchrW(name, '@')))
- match = url_matches(constraint, at + 1, trustErrorStatus);
+ match = domain_name_matches(constraint, at + 1);
else
match = !lstrcmpiW(constraint, name);
}
*trustErrorStatus |= CERT_TRUST_INVALID_NAME_CONSTRAINTS;
else if (!name)
; /* no match */
- else if (lstrlenW(name) >= lstrlenW(constraint))
+ /* RFC 5280, section 4.2.1.10:
+ * "DNS name restrictions are expressed as host.example.com. 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. For example,
+ * www.host.example.com would satisfy the constraint but host1.example.com
+ * would not."
+ */
+ else if (lstrlenW(name) == lstrlenW(constraint))
+ match = !lstrcmpiW(name, constraint);
+ else if (lstrlenW(name) > lstrlenW(constraint))
+ {
match = !lstrcmpiW(name + lstrlenW(name) - lstrlenW(constraint),
constraint);
+ if (match)
+ {
+ BOOL dot = FALSE;
+ LPCWSTR ptr;
+
+ /* This only matches if name is a subdomain of constraint, i.e.
+ * there's a '.' between the beginning of the name and the
+ * matching portion of the name.
+ */
+ for (ptr = name + lstrlenW(name) - lstrlenW(constraint);
+ !dot && ptr >= name; ptr--)
+ if (*ptr == '.')
+ dot = TRUE;
+ match = dot;
+ }
+ }
/* else: name is too short, no match */
return match;
TRACE("(%d, %p), (%d, %p)\n", constraint->cbData, constraint->pbData,
name->cbData, name->pbData);
- if (constraint->cbData != sizeof(DWORD) * 2)
+ /* RFC5280, section 4.2.1.10, iPAddress syntax: either 8 or 32 bytes, for
+ * IPv4 or IPv6 addresses, respectively.
+ */
+ if (constraint->cbData != sizeof(DWORD) * 2 && constraint->cbData != 32)
*trustErrorStatus |= CERT_TRUST_INVALID_NAME_CONSTRAINTS;
- else if (name->cbData == sizeof(DWORD))
+ else if (name->cbData == sizeof(DWORD) &&
+ constraint->cbData == sizeof(DWORD) * 2)
{
DWORD subnet, mask, addr;
*/
match = (subnet & mask) == (addr & mask);
}
+ else if (name->cbData == 16 && constraint->cbData == 32)
+ {
+ const BYTE *subnet, *mask, *addr;
+ DWORD i;
+
+ subnet = constraint->pbData;
+ mask = constraint->pbData + 16;
+ addr = name->pbData;
+ match = TRUE;
+ for (i = 0; match && i < 16; i++)
+ if ((subnet[i] & mask[i]) != (addr[i] & mask[i]))
+ match = FALSE;
+ }
/* else: name is wrong size, no match */
return match;
}
-static void CRYPT_FindMatchingNameEntry(const CERT_ALT_NAME_ENTRY *constraint,
- const CERT_ALT_NAME_INFO *subjectName, DWORD *trustErrorStatus,
- DWORD errorIfFound, DWORD errorIfNotFound)
+static BOOL directory_name_matches(const CERT_NAME_BLOB *constraint,
+ const CERT_NAME_BLOB *name)
+{
+ CERT_NAME_INFO *constraintName;
+ DWORD size;
+ BOOL match = FALSE;
+
+ if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_NAME, constraint->pbData,
+ constraint->cbData, CRYPT_DECODE_ALLOC_FLAG, NULL, &constraintName, &size))
+ {
+ DWORD i;
+
+ match = TRUE;
+ for (i = 0; match && i < constraintName->cRDN; i++)
+ match = CertIsRDNAttrsInCertificateName(X509_ASN_ENCODING,
+ CERT_CASE_INSENSITIVE_IS_RDN_ATTRS_FLAG,
+ (CERT_NAME_BLOB *)name, &constraintName->rgRDN[i]);
+ LocalFree(constraintName);
+ }
+ return match;
+}
+
+static BOOL alt_name_matches(const CERT_ALT_NAME_ENTRY *name,
+ const CERT_ALT_NAME_ENTRY *constraint, DWORD *trustErrorStatus, BOOL *present)
+{
+ BOOL match = FALSE;
+
+ if (name->dwAltNameChoice == constraint->dwAltNameChoice)
+ {
+ if (present)
+ *present = TRUE;
+ switch (constraint->dwAltNameChoice)
+ {
+ case CERT_ALT_NAME_RFC822_NAME:
+ match = rfc822_name_matches(constraint->u.pwszURL,
+ name->u.pwszURL, trustErrorStatus);
+ break;
+ case CERT_ALT_NAME_DNS_NAME:
+ match = dns_name_matches(constraint->u.pwszURL,
+ name->u.pwszURL, trustErrorStatus);
+ break;
+ case CERT_ALT_NAME_URL:
+ match = url_matches(constraint->u.pwszURL,
+ name->u.pwszURL, trustErrorStatus);
+ break;
+ case CERT_ALT_NAME_IP_ADDRESS:
+ match = ip_address_matches(&constraint->u.IPAddress,
+ &name->u.IPAddress, trustErrorStatus);
+ break;
+ case CERT_ALT_NAME_DIRECTORY_NAME:
+ match = directory_name_matches(&constraint->u.DirectoryName,
+ &name->u.DirectoryName);
+ break;
+ default:
+ ERR("name choice %d unsupported in this context\n",
+ constraint->dwAltNameChoice);
+ *trustErrorStatus |=
+ CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT;
+ }
+ }
+ else if (present)
+ *present = FALSE;
+ return match;
+}
+
+static BOOL alt_name_matches_excluded_name(const CERT_ALT_NAME_ENTRY *name,
+ const CERT_NAME_CONSTRAINTS_INFO *nameConstraints, DWORD *trustErrorStatus)
+{
+ DWORD i;
+ BOOL match = FALSE;
+
+ for (i = 0; !match && i < nameConstraints->cExcludedSubtree; i++)
+ match = alt_name_matches(name,
+ &nameConstraints->rgExcludedSubtree[i].Base, trustErrorStatus, NULL);
+ return match;
+}
+
+static BOOL alt_name_matches_permitted_name(const CERT_ALT_NAME_ENTRY *name,
+ const CERT_NAME_CONSTRAINTS_INFO *nameConstraints, DWORD *trustErrorStatus,
+ BOOL *present)
{
DWORD i;
BOOL match = FALSE;
- for (i = 0; i < subjectName->cAltEntry; i++)
+ for (i = 0; !match && i < nameConstraints->cPermittedSubtree; i++)
+ match = alt_name_matches(name,
+ &nameConstraints->rgPermittedSubtree[i].Base, trustErrorStatus,
+ present);
+ return match;
+}
+
+static inline PCERT_EXTENSION get_subject_alt_name_ext(const CERT_INFO *cert)
+{
+ PCERT_EXTENSION ext;
+
+ ext = CertFindExtension(szOID_SUBJECT_ALT_NAME2,
+ cert->cExtension, cert->rgExtension);
+ if (!ext)
+ ext = CertFindExtension(szOID_SUBJECT_ALT_NAME,
+ cert->cExtension, cert->rgExtension);
+ return ext;
+}
+
+static void compare_alt_name_with_constraints(const CERT_EXTENSION *altNameExt,
+ const CERT_NAME_CONSTRAINTS_INFO *nameConstraints, DWORD *trustErrorStatus)
+{
+ CERT_ALT_NAME_INFO *subjectAltName;
+ DWORD size;
+
+ if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_ALTERNATE_NAME,
+ altNameExt->Value.pbData, altNameExt->Value.cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, NULL,
+ &subjectAltName, &size))
{
- if (subjectName->rgAltEntry[i].dwAltNameChoice ==
- constraint->dwAltNameChoice)
+ DWORD i;
+
+ for (i = 0; i < subjectAltName->cAltEntry; i++)
{
- switch (constraint->dwAltNameChoice)
+ BOOL nameFormPresent;
+
+ /* A name constraint only applies if the name form is present.
+ * From RFC 5280, section 4.2.1.10:
+ * "Restrictions apply only when the specified name form is
+ * present. If no name of the type is in the certificate,
+ * the certificate is acceptable."
+ */
+ if (alt_name_matches_excluded_name(
+ &subjectAltName->rgAltEntry[i], nameConstraints,
+ trustErrorStatus))
{
- case CERT_ALT_NAME_RFC822_NAME:
- match = rfc822_name_matches(constraint->u.pwszURL,
- subjectName->rgAltEntry[i].u.pwszURL, trustErrorStatus);
- break;
- case CERT_ALT_NAME_DNS_NAME:
- match = dns_name_matches(constraint->u.pwszURL,
- subjectName->rgAltEntry[i].u.pwszURL, trustErrorStatus);
- break;
- case CERT_ALT_NAME_URL:
- match = url_matches(constraint->u.pwszURL,
- subjectName->rgAltEntry[i].u.pwszURL, trustErrorStatus);
- break;
- case CERT_ALT_NAME_IP_ADDRESS:
- match = ip_address_matches(&constraint->u.IPAddress,
- &subjectName->rgAltEntry[i].u.IPAddress, trustErrorStatus);
- break;
- case CERT_ALT_NAME_DIRECTORY_NAME:
- default:
- ERR("name choice %d unsupported in this context\n",
- constraint->dwAltNameChoice);
+ TRACE_(chain)("subject alternate name form %d excluded\n",
+ subjectAltName->rgAltEntry[i].dwAltNameChoice);
*trustErrorStatus |=
- CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT;
+ CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT;
+ }
+ nameFormPresent = FALSE;
+ if (!alt_name_matches_permitted_name(
+ &subjectAltName->rgAltEntry[i], nameConstraints,
+ trustErrorStatus, &nameFormPresent) && nameFormPresent)
+ {
+ TRACE_(chain)("subject alternate name form %d not permitted\n",
+ subjectAltName->rgAltEntry[i].dwAltNameChoice);
+ *trustErrorStatus |=
+ CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT;
}
}
+ LocalFree(subjectAltName);
}
- *trustErrorStatus |= match ? errorIfFound : errorIfNotFound;
+ else
+ *trustErrorStatus |=
+ CERT_TRUST_INVALID_EXTENSION | CERT_TRUST_INVALID_NAME_CONSTRAINTS;
}
-static void CRYPT_CheckNameConstraints(
- const CERT_NAME_CONSTRAINTS_INFO *nameConstraints, const CERT_INFO *cert,
- DWORD *trustErrorStatus)
+static BOOL rfc822_attr_matches_excluded_name(const CERT_RDN_ATTR *attr,
+ const CERT_NAME_CONSTRAINTS_INFO *nameConstraints, DWORD *trustErrorStatus)
{
- /* If there aren't any existing constraints, don't bother checking */
- if (nameConstraints->cPermittedSubtree || nameConstraints->cExcludedSubtree)
+ DWORD i;
+ BOOL match = FALSE;
+
+ for (i = 0; !match && i < nameConstraints->cExcludedSubtree; i++)
+ {
+ const CERT_ALT_NAME_ENTRY *constraint =
+ &nameConstraints->rgExcludedSubtree[i].Base;
+
+ if (constraint->dwAltNameChoice == CERT_ALT_NAME_RFC822_NAME)
+ match = rfc822_name_matches(constraint->u.pwszRfc822Name,
+ (LPCWSTR)attr->Value.pbData, trustErrorStatus);
+ }
+ return match;
+}
+
+static BOOL rfc822_attr_matches_permitted_name(const CERT_RDN_ATTR *attr,
+ const CERT_NAME_CONSTRAINTS_INFO *nameConstraints, DWORD *trustErrorStatus,
+ BOOL *present)
+{
+ DWORD i;
+ BOOL match = FALSE;
+
+ for (i = 0; !match && i < nameConstraints->cPermittedSubtree; i++)
{
- CERT_EXTENSION *ext;
+ const CERT_ALT_NAME_ENTRY *constraint =
+ &nameConstraints->rgPermittedSubtree[i].Base;
- if ((ext = CertFindExtension(szOID_SUBJECT_ALT_NAME, cert->cExtension,
- cert->rgExtension)))
+ if (constraint->dwAltNameChoice == CERT_ALT_NAME_RFC822_NAME)
{
- CERT_ALT_NAME_INFO *subjectName;
- DWORD size;
+ *present = TRUE;
+ match = rfc822_name_matches(constraint->u.pwszRfc822Name,
+ (LPCWSTR)attr->Value.pbData, trustErrorStatus);
+ }
+ }
+ return match;
+}
- if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_ALTERNATE_NAME,
- ext->Value.pbData, ext->Value.cbData,
- CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, NULL,
- &subjectName, &size))
- {
- DWORD i;
+static void compare_subject_with_email_constraints(
+ const CERT_NAME_BLOB *subjectName,
+ const CERT_NAME_CONSTRAINTS_INFO *nameConstraints, DWORD *trustErrorStatus)
+{
+ CERT_NAME_INFO *name;
+ DWORD size;
+
+ if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_UNICODE_NAME,
+ subjectName->pbData, subjectName->cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, NULL, &name, &size))
+ {
+ DWORD i, j;
- for (i = 0; i < nameConstraints->cExcludedSubtree; i++)
- CRYPT_FindMatchingNameEntry(
- &nameConstraints->rgExcludedSubtree[i].Base, subjectName,
- trustErrorStatus,
- CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT, 0);
- for (i = 0; i < nameConstraints->cPermittedSubtree; i++)
- CRYPT_FindMatchingNameEntry(
- &nameConstraints->rgPermittedSubtree[i].Base, subjectName,
- trustErrorStatus,
- 0, CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT);
- LocalFree(subjectName);
+ for (i = 0; i < name->cRDN; i++)
+ for (j = 0; j < name->rgRDN[i].cRDNAttr; j++)
+ if (!strcmp(name->rgRDN[i].rgRDNAttr[j].pszObjId,
+ szOID_RSA_emailAddr))
+ {
+ BOOL nameFormPresent;
+
+ /* A name constraint only applies if the name form is
+ * present. From RFC 5280, section 4.2.1.10:
+ * "Restrictions apply only when the specified name form is
+ * present. If no name of the type is in the certificate,
+ * the certificate is acceptable."
+ */
+ if (rfc822_attr_matches_excluded_name(
+ &name->rgRDN[i].rgRDNAttr[j], nameConstraints,
+ trustErrorStatus))
+ {
+ TRACE_(chain)(
+ "email address in subject name is excluded\n");
+ *trustErrorStatus |=
+ CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT;
+ }
+ nameFormPresent = FALSE;
+ if (!rfc822_attr_matches_permitted_name(
+ &name->rgRDN[i].rgRDNAttr[j], nameConstraints,
+ trustErrorStatus, &nameFormPresent) && nameFormPresent)
+ {
+ TRACE_(chain)(
+ "email address in subject name is not permitted\n");
+ *trustErrorStatus |=
+ CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT;
+ }
+ }
+ LocalFree(name);
+ }
+ else
+ *trustErrorStatus |=
+ CERT_TRUST_INVALID_EXTENSION | CERT_TRUST_INVALID_NAME_CONSTRAINTS;
+}
+
+static BOOL CRYPT_IsEmptyName(const CERT_NAME_BLOB *name)
+{
+ BOOL empty;
+
+ if (!name->cbData)
+ empty = TRUE;
+ else if (name->cbData == 2 && name->pbData[1] == 0)
+ {
+ /* An empty sequence is also empty */
+ empty = TRUE;
+ }
+ else
+ empty = FALSE;
+ return empty;
+}
+
+static void compare_subject_with_constraints(const CERT_NAME_BLOB *subjectName,
+ const CERT_NAME_CONSTRAINTS_INFO *nameConstraints, DWORD *trustErrorStatus)
+{
+ BOOL hasEmailConstraint = FALSE;
+ DWORD i;
+
+ /* In general, a subject distinguished name only matches a directory name
+ * constraint. However, an exception exists for email addresses.
+ * From RFC 5280, section 4.2.1.6:
+ * "Legacy implementations exist where an electronic mail address is
+ * embedded in the subject distinguished name as an emailAddress
+ * attribute [RFC2985]."
+ * If an email address constraint exists, check that constraint separately.
+ */
+ for (i = 0; !hasEmailConstraint && i < nameConstraints->cExcludedSubtree;
+ i++)
+ if (nameConstraints->rgExcludedSubtree[i].Base.dwAltNameChoice ==
+ CERT_ALT_NAME_RFC822_NAME)
+ hasEmailConstraint = TRUE;
+ for (i = 0; !hasEmailConstraint && i < nameConstraints->cPermittedSubtree;
+ i++)
+ if (nameConstraints->rgPermittedSubtree[i].Base.dwAltNameChoice ==
+ CERT_ALT_NAME_RFC822_NAME)
+ hasEmailConstraint = TRUE;
+ if (hasEmailConstraint)
+ compare_subject_with_email_constraints(subjectName, nameConstraints,
+ trustErrorStatus);
+ for (i = 0; i < nameConstraints->cExcludedSubtree; i++)
+ {
+ CERT_ALT_NAME_ENTRY *constraint =
+ &nameConstraints->rgExcludedSubtree[i].Base;
+
+ if (constraint->dwAltNameChoice == CERT_ALT_NAME_DIRECTORY_NAME &&
+ directory_name_matches(&constraint->u.DirectoryName, subjectName))
+ {
+ TRACE_(chain)("subject name is excluded\n");
+ *trustErrorStatus |=
+ CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT;
+ }
+ }
+ /* RFC 5280, section 4.2.1.10:
+ * "Restrictions apply only when the specified name form is present.
+ * If no name of the type is in the certificate, the certificate is
+ * acceptable."
+ * An empty name can't have the name form present, so don't check it.
+ */
+ if (nameConstraints->cPermittedSubtree && !CRYPT_IsEmptyName(subjectName))
+ {
+ BOOL match = FALSE, hasDirectoryConstraint = FALSE;
+
+ for (i = 0; !match && i < nameConstraints->cPermittedSubtree; i++)
+ {
+ CERT_ALT_NAME_ENTRY *constraint =
+ &nameConstraints->rgPermittedSubtree[i].Base;
+
+ if (constraint->dwAltNameChoice == CERT_ALT_NAME_DIRECTORY_NAME)
+ {
+ hasDirectoryConstraint = TRUE;
+ match = directory_name_matches(&constraint->u.DirectoryName,
+ subjectName);
}
}
- else
+ if (hasDirectoryConstraint && !match)
{
- if (nameConstraints->cPermittedSubtree)
- *trustErrorStatus |=
- CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT;
- if (nameConstraints->cExcludedSubtree)
- *trustErrorStatus |=
- CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT;
+ TRACE_(chain)("subject name is not permitted\n");
+ *trustErrorStatus |= CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT;
}
}
}
+static void CRYPT_CheckNameConstraints(
+ const CERT_NAME_CONSTRAINTS_INFO *nameConstraints, const CERT_INFO *cert,
+ DWORD *trustErrorStatus)
+{
+ CERT_EXTENSION *ext = get_subject_alt_name_ext(cert);
+
+ if (ext)
+ compare_alt_name_with_constraints(ext, nameConstraints,
+ trustErrorStatus);
+ /* Name constraints apply to the subject alternative name as well as the
+ * subject name. From RFC 5280, section 4.2.1.10:
+ * "Restrictions apply to the subject distinguished name and apply to
+ * subject alternative names."
+ */
+ compare_subject_with_constraints(&cert->Subject, nameConstraints,
+ trustErrorStatus);
+}
+
/* Gets cert's name constraints, if any. Free with LocalFree. */
static CERT_NAME_CONSTRAINTS_INFO *CRYPT_GetNameConstraints(CERT_INFO *cert)
{
return info;
}
+static BOOL CRYPT_IsValidNameConstraint(const CERT_NAME_CONSTRAINTS_INFO *info)
+{
+ DWORD i;
+ BOOL ret = TRUE;
+
+ /* Make sure at least one permitted or excluded subtree is present. From
+ * RFC 5280, section 4.2.1.10:
+ * "Conforming CAs MUST NOT issue certificates where name constraints is an
+ * empty sequence. That is, either the permittedSubtrees field or the
+ * excludedSubtrees MUST be present."
+ */
+ if (!info->cPermittedSubtree && !info->cExcludedSubtree)
+ {
+ WARN_(chain)("constraints contain no permitted nor excluded subtree\n");
+ ret = FALSE;
+ }
+ /* Check that none of the constraints specifies a minimum or a maximum.
+ * See RFC 5280, section 4.2.1.10:
+ * "Within this profile, the minimum and maximum fields are not used with
+ * any name forms, thus, the minimum MUST be zero, and maximum MUST be
+ * absent. However, if an application encounters a critical name
+ * constraints extension that specifies other values for minimum or
+ * maximum for a name form that appears in a subsequent certificate, the
+ * application MUST either process these fields or reject the
+ * certificate."
+ * Since it gives no guidance as to how to process these fields, we
+ * reject any name constraint that contains them.
+ */
+ for (i = 0; ret && i < info->cPermittedSubtree; i++)
+ if (info->rgPermittedSubtree[i].dwMinimum ||
+ info->rgPermittedSubtree[i].fMaximum)
+ {
+ TRACE_(chain)("found a minimum or maximum in permitted subtrees\n");
+ ret = FALSE;
+ }
+ for (i = 0; ret && i < info->cExcludedSubtree; i++)
+ if (info->rgExcludedSubtree[i].dwMinimum ||
+ info->rgExcludedSubtree[i].fMaximum)
+ {
+ TRACE_(chain)("found a minimum or maximum in excluded subtrees\n");
+ ret = FALSE;
+ }
+ return ret;
+}
+
static void CRYPT_CheckChainNameConstraints(PCERT_SIMPLE_CHAIN chain)
{
int i, j;
if ((nameConstraints = CRYPT_GetNameConstraints(
chain->rgpElement[i]->pCertContext->pCertInfo)))
{
- for (j = i - 1; j >= 0; j--)
+ if (!CRYPT_IsValidNameConstraint(nameConstraints))
+ chain->rgpElement[i]->TrustStatus.dwErrorStatus |=
+ CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT;
+ else
{
- DWORD errorStatus = 0;
-
- /* According to RFC 3280, self-signed certs don't have name
- * constraints checked unless they're the end cert.
- */
- if (j == 0 || !CRYPT_IsCertificateSelfSigned(
- chain->rgpElement[j]->pCertContext))
+ for (j = i - 1; j >= 0; j--)
{
- CRYPT_CheckNameConstraints(nameConstraints,
- chain->rgpElement[i]->pCertContext->pCertInfo,
- &errorStatus);
- chain->rgpElement[i]->TrustStatus.dwErrorStatus |=
- errorStatus;
+ DWORD errorStatus = 0;
+
+ /* According to RFC 3280, self-signed certs don't have name
+ * constraints checked unless they're the end cert.
+ */
+ if (j == 0 || !CRYPT_IsCertificateSelfSigned(
+ chain->rgpElement[j]->pCertContext))
+ {
+ CRYPT_CheckNameConstraints(nameConstraints,
+ chain->rgpElement[j]->pCertContext->pCertInfo,
+ &errorStatus);
+ if (errorStatus)
+ {
+ chain->rgpElement[i]->TrustStatus.dwErrorStatus |=
+ errorStatus;
+ CRYPT_CombineTrustStatus(&chain->TrustStatus,
+ &chain->rgpElement[i]->TrustStatus);
+ }
+ else
+ chain->rgpElement[i]->TrustStatus.dwInfoStatus |=
+ CERT_TRUST_HAS_VALID_NAME_CONSTRAINTS;
+ }
}
}
LocalFree(nameConstraints);
}
}
+static LPWSTR name_value_to_str(const CERT_NAME_BLOB *name)
+{
+ DWORD len = cert_name_to_str_with_indent(X509_ASN_ENCODING, 0, name,
+ CERT_SIMPLE_NAME_STR, NULL, 0);
+ LPWSTR str = NULL;
+
+ if (len)
+ {
+ str = CryptMemAlloc(len * sizeof(WCHAR));
+ if (str)
+ cert_name_to_str_with_indent(X509_ASN_ENCODING, 0, name,
+ CERT_SIMPLE_NAME_STR, str, len);
+ }
+ return str;
+}
+
+static void dump_alt_name_entry(const CERT_ALT_NAME_ENTRY *entry)
+{
+ LPWSTR str;
+
+ switch (entry->dwAltNameChoice)
+ {
+ case CERT_ALT_NAME_OTHER_NAME:
+ TRACE_(chain)("CERT_ALT_NAME_OTHER_NAME, oid = %s\n",
+ debugstr_a(entry->u.pOtherName->pszObjId));
+ break;
+ case CERT_ALT_NAME_RFC822_NAME:
+ TRACE_(chain)("CERT_ALT_NAME_RFC822_NAME: %s\n",
+ debugstr_w(entry->u.pwszRfc822Name));
+ break;
+ case CERT_ALT_NAME_DNS_NAME:
+ TRACE_(chain)("CERT_ALT_NAME_DNS_NAME: %s\n",
+ debugstr_w(entry->u.pwszDNSName));
+ break;
+ case CERT_ALT_NAME_DIRECTORY_NAME:
+ str = name_value_to_str(&entry->u.DirectoryName);
+ TRACE_(chain)("CERT_ALT_NAME_DIRECTORY_NAME: %s\n", debugstr_w(str));
+ CryptMemFree(str);
+ break;
+ case CERT_ALT_NAME_URL:
+ TRACE_(chain)("CERT_ALT_NAME_URL: %s\n", debugstr_w(entry->u.pwszURL));
+ break;
+ case CERT_ALT_NAME_IP_ADDRESS:
+ TRACE_(chain)("CERT_ALT_NAME_IP_ADDRESS: %d bytes\n",
+ entry->u.IPAddress.cbData);
+ break;
+ case CERT_ALT_NAME_REGISTERED_ID:
+ TRACE_(chain)("CERT_ALT_NAME_REGISTERED_ID: %s\n",
+ debugstr_a(entry->u.pszRegisteredID));
+ break;
+ default:
+ TRACE_(chain)("dwAltNameChoice = %d\n", entry->dwAltNameChoice);
+ }
+}
+
+static void dump_alt_name(LPCSTR type, const CERT_EXTENSION *ext)
+{
+ CERT_ALT_NAME_INFO *name;
+ DWORD size;
+
+ TRACE_(chain)("%s:\n", type);
+ if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_ALTERNATE_NAME,
+ ext->Value.pbData, ext->Value.cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, NULL, &name, &size))
+ {
+ DWORD i;
+
+ TRACE_(chain)("%d alt name entries:\n", name->cAltEntry);
+ for (i = 0; i < name->cAltEntry; i++)
+ dump_alt_name_entry(&name->rgAltEntry[i]);
+ LocalFree(name);
+ }
+}
+
static void dump_basic_constraints(const CERT_EXTENSION *ext)
{
CERT_BASIC_CONSTRAINTS_INFO *info;
}
}
+static void dump_key_usage(const CERT_EXTENSION *ext)
+{
+ CRYPT_BIT_BLOB usage;
+ DWORD size = sizeof(usage);
+
+ if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_BITS, ext->Value.pbData,
+ ext->Value.cbData, CRYPT_DECODE_NOCOPY_FLAG, NULL, &usage, &size))
+ {
+#define trace_usage_bit(bits, bit) \
+ if ((bits) & (bit)) TRACE_(chain)("%s\n", #bit)
+ if (usage.cbData)
+ {
+ trace_usage_bit(usage.pbData[0], CERT_DIGITAL_SIGNATURE_KEY_USAGE);
+ trace_usage_bit(usage.pbData[0], CERT_NON_REPUDIATION_KEY_USAGE);
+ trace_usage_bit(usage.pbData[0], CERT_KEY_ENCIPHERMENT_KEY_USAGE);
+ trace_usage_bit(usage.pbData[0], CERT_DATA_ENCIPHERMENT_KEY_USAGE);
+ trace_usage_bit(usage.pbData[0], CERT_KEY_AGREEMENT_KEY_USAGE);
+ trace_usage_bit(usage.pbData[0], CERT_KEY_CERT_SIGN_KEY_USAGE);
+ trace_usage_bit(usage.pbData[0], CERT_CRL_SIGN_KEY_USAGE);
+ trace_usage_bit(usage.pbData[0], CERT_ENCIPHER_ONLY_KEY_USAGE);
+ }
+#undef trace_usage_bit
+ if (usage.cbData > 1 && usage.pbData[1] & CERT_DECIPHER_ONLY_KEY_USAGE)
+ TRACE_(chain)("CERT_DECIPHER_ONLY_KEY_USAGE\n");
+ }
+}
+
+static void dump_general_subtree(const CERT_GENERAL_SUBTREE *subtree)
+{
+ dump_alt_name_entry(&subtree->Base);
+ TRACE_(chain)("dwMinimum = %d, fMaximum = %d, dwMaximum = %d\n",
+ subtree->dwMinimum, subtree->fMaximum, subtree->dwMaximum);
+}
+
+static void dump_name_constraints(const CERT_EXTENSION *ext)
+{
+ CERT_NAME_CONSTRAINTS_INFO *nameConstraints;
+ DWORD size;
+
+ if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_NAME_CONSTRAINTS,
+ ext->Value.pbData, ext->Value.cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, NULL, &nameConstraints,
+ &size))
+ {
+ DWORD i;
+
+ TRACE_(chain)("%d permitted subtrees:\n",
+ nameConstraints->cPermittedSubtree);
+ for (i = 0; i < nameConstraints->cPermittedSubtree; i++)
+ dump_general_subtree(&nameConstraints->rgPermittedSubtree[i]);
+ TRACE_(chain)("%d excluded subtrees:\n",
+ nameConstraints->cExcludedSubtree);
+ for (i = 0; i < nameConstraints->cExcludedSubtree; i++)
+ dump_general_subtree(&nameConstraints->rgExcludedSubtree[i]);
+ LocalFree(nameConstraints);
+ }
+}
+
+static void dump_cert_policies(const CERT_EXTENSION *ext)
+{
+ CERT_POLICIES_INFO *policies;
+ DWORD size;
+
+ if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_CERT_POLICIES,
+ ext->Value.pbData, ext->Value.cbData, CRYPT_DECODE_ALLOC_FLAG, NULL,
+ &policies, &size))
+ {
+ DWORD i, j;
+
+ TRACE_(chain)("%d policies:\n", policies->cPolicyInfo);
+ for (i = 0; i < policies->cPolicyInfo; i++)
+ {
+ TRACE_(chain)("policy identifier: %s\n",
+ debugstr_a(policies->rgPolicyInfo[i].pszPolicyIdentifier));
+ TRACE_(chain)("%d policy qualifiers:\n",
+ policies->rgPolicyInfo[i].cPolicyQualifier);
+ for (j = 0; j < policies->rgPolicyInfo[i].cPolicyQualifier; j++)
+ TRACE_(chain)("%s\n", debugstr_a(
+ policies->rgPolicyInfo[i].rgPolicyQualifier[j].
+ pszPolicyQualifierId));
+ }
+ LocalFree(policies);
+ }
+}
+
+static void dump_enhanced_key_usage(const CERT_EXTENSION *ext)
+{
+ CERT_ENHKEY_USAGE *usage;
+ DWORD size;
+
+ if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_ENHANCED_KEY_USAGE,
+ ext->Value.pbData, ext->Value.cbData, CRYPT_DECODE_ALLOC_FLAG, NULL,
+ &usage, &size))
+ {
+ DWORD i;
+
+ TRACE_(chain)("%d usages:\n", usage->cUsageIdentifier);
+ for (i = 0; i < usage->cUsageIdentifier; i++)
+ TRACE_(chain)("%s\n", usage->rgpszUsageIdentifier[i]);
+ LocalFree(usage);
+ }
+}
+
+static void dump_netscape_cert_type(const CERT_EXTENSION *ext)
+{
+ CRYPT_BIT_BLOB usage;
+ DWORD size = sizeof(usage);
+
+ if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_BITS, ext->Value.pbData,
+ ext->Value.cbData, CRYPT_DECODE_NOCOPY_FLAG, NULL, &usage, &size))
+ {
+#define trace_cert_type_bit(bits, bit) \
+ if ((bits) & (bit)) TRACE_(chain)("%s\n", #bit)
+ if (usage.cbData)
+ {
+ trace_cert_type_bit(usage.pbData[0],
+ NETSCAPE_SSL_CLIENT_AUTH_CERT_TYPE);
+ trace_cert_type_bit(usage.pbData[0],
+ NETSCAPE_SSL_SERVER_AUTH_CERT_TYPE);
+ trace_cert_type_bit(usage.pbData[0], NETSCAPE_SMIME_CERT_TYPE);
+ trace_cert_type_bit(usage.pbData[0], NETSCAPE_SIGN_CERT_TYPE);
+ trace_cert_type_bit(usage.pbData[0], NETSCAPE_SSL_CA_CERT_TYPE);
+ trace_cert_type_bit(usage.pbData[0], NETSCAPE_SMIME_CA_CERT_TYPE);
+ trace_cert_type_bit(usage.pbData[0], NETSCAPE_SIGN_CA_CERT_TYPE);
+ }
+#undef trace_cert_type_bit
+ }
+}
+
static void dump_extension(const CERT_EXTENSION *ext)
{
TRACE_(chain)("%s (%scritical)\n", debugstr_a(ext->pszObjId),
ext->fCritical ? "" : "not ");
- if (!strcmp(ext->pszObjId, szOID_BASIC_CONSTRAINTS))
+ if (!strcmp(ext->pszObjId, szOID_SUBJECT_ALT_NAME))
+ dump_alt_name("subject alt name", ext);
+ else if (!strcmp(ext->pszObjId, szOID_ISSUER_ALT_NAME))
+ dump_alt_name("issuer alt name", ext);
+ else if (!strcmp(ext->pszObjId, szOID_BASIC_CONSTRAINTS))
dump_basic_constraints(ext);
+ else if (!strcmp(ext->pszObjId, szOID_KEY_USAGE))
+ dump_key_usage(ext);
+ else if (!strcmp(ext->pszObjId, szOID_SUBJECT_ALT_NAME2))
+ dump_alt_name("subject alt name 2", ext);
+ else if (!strcmp(ext->pszObjId, szOID_ISSUER_ALT_NAME2))
+ dump_alt_name("issuer alt name 2", ext);
else if (!strcmp(ext->pszObjId, szOID_BASIC_CONSTRAINTS2))
dump_basic_constraints2(ext);
+ else if (!strcmp(ext->pszObjId, szOID_NAME_CONSTRAINTS))
+ dump_name_constraints(ext);
+ else if (!strcmp(ext->pszObjId, szOID_CERT_POLICIES))
+ dump_cert_policies(ext);
+ else if (!strcmp(ext->pszObjId, szOID_ENHANCED_KEY_USAGE))
+ dump_enhanced_key_usage(ext);
+ else if (!strcmp(ext->pszObjId, szOID_NETSCAPE_CERT_TYPE))
+ dump_netscape_cert_type(ext);
}
static LPCWSTR filetime_to_str(const FILETIME *time)
LPWSTR name = NULL;
DWORD len, i;
- TRACE_(chain)("%p\n", cert);
+ TRACE_(chain)("%p: version %d\n", cert, cert->pCertInfo->dwVersion);
len = CertGetNameStringW(cert, CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG, NULL, NULL, 0);
name = CryptMemAlloc(len * sizeof(WCHAR));
dump_extension(&cert->pCertInfo->rgExtension[i]);
}
+static BOOL CRYPT_KeyUsageValid(PCertificateChainEngine engine,
+ PCCERT_CONTEXT cert, BOOL isRoot, BOOL isCA, DWORD index)
+{
+ PCERT_EXTENSION ext;
+ BOOL ret;
+ BYTE usageBits = 0;
+
+ ext = CertFindExtension(szOID_KEY_USAGE, cert->pCertInfo->cExtension,
+ cert->pCertInfo->rgExtension);
+ if (ext)
+ {
+ CRYPT_BIT_BLOB usage;
+ DWORD size = sizeof(usage);
+
+ ret = CryptDecodeObjectEx(cert->dwCertEncodingType, X509_BITS,
+ ext->Value.pbData, ext->Value.cbData, CRYPT_DECODE_NOCOPY_FLAG, NULL,
+ &usage, &size);
+ if (!ret)
+ return FALSE;
+ else if (usage.cbData > 2)
+ {
+ /* The key usage extension only defines 9 bits => no more than 2
+ * bytes are needed to encode all known usages.
+ */
+ return FALSE;
+ }
+ else
+ {
+ /* The only bit relevant to chain validation is the keyCertSign
+ * bit, which is always in the least significant byte of the
+ * key usage bits.
+ */
+ usageBits = usage.pbData[usage.cbData - 1];
+ }
+ }
+ if (isCA)
+ {
+ if (!ext)
+ {
+ /* MS appears to violate RFC 5280, section 4.2.1.3 (Key Usage)
+ * here. Quoting the RFC:
+ * "This [key usage] extension MUST appear in certificates that
+ * contain public keys that are used to validate digital signatures
+ * on other public key certificates or CRLs."
+ * MS appears to accept certs that do not contain key usage
+ * extensions as CA certs. V1 and V2 certificates did not have
+ * extensions, and many root certificates are V1 certificates, so
+ * perhaps this is prudent. On the other hand, MS also accepts V3
+ * certs without key usage extensions. We are more restrictive:
+ * we accept locally installed V1 or V2 certs as CA certs.
+ * We also accept a lack of key usage extension on root certs,
+ * which is implied in RFC 5280, section 6.1: the trust anchor's
+ * only requirement is that it was used to issue the next
+ * certificate in the chain.
+ */
+ if (isRoot)
+ ret = TRUE;
+ else if (cert->pCertInfo->dwVersion == CERT_V1 ||
+ cert->pCertInfo->dwVersion == CERT_V2)
+ {
+ PCCERT_CONTEXT localCert = CRYPT_FindCertInStore(
+ engine->hWorld, cert);
+
+ ret = localCert != NULL;
+ CertFreeCertificateContext(localCert);
+ }
+ else
+ ret = FALSE;
+ if (!ret)
+ WARN_(chain)("no key usage extension on a CA cert\n");
+ }
+ else
+ {
+ if (!(usageBits & CERT_KEY_CERT_SIGN_KEY_USAGE))
+ {
+ WARN_(chain)("keyCertSign not asserted on a CA cert\n");
+ ret = FALSE;
+ }
+ else
+ ret = TRUE;
+ }
+ }
+ else
+ {
+ if (ext && (usageBits & CERT_KEY_CERT_SIGN_KEY_USAGE))
+ {
+ WARN_(chain)("keyCertSign asserted on a non-CA cert\n");
+ ret = FALSE;
+ }
+ else
+ ret = TRUE;
+ }
+ return ret;
+}
+
static BOOL CRYPT_CriticalExtensionsSupported(PCCERT_CONTEXT cert)
{
BOOL ret = TRUE;
else if (!strcmp(oid, szOID_NAME_CONSTRAINTS))
ret = TRUE;
else if (!strcmp(oid, szOID_KEY_USAGE))
- {
- static int warned;
-
- if (!warned++)
- FIXME("key usage extension unsupported, ignoring\n");
ret = TRUE;
- }
else if (!strcmp(oid, szOID_SUBJECT_ALT_NAME))
ret = TRUE;
+ else if (!strcmp(oid, szOID_SUBJECT_ALT_NAME2))
+ ret = TRUE;
+ else if (!strcmp(oid, szOID_ENHANCED_KEY_USAGE))
+ ret = TRUE;
else
{
FIXME("unsupported critical extension %s\n",
return ret;
}
+static BOOL CRYPT_IsCertVersionValid(PCCERT_CONTEXT cert)
+{
+ BOOL ret = TRUE;
+
+ /* Checks whether the contents of the cert match the cert's version. */
+ switch (cert->pCertInfo->dwVersion)
+ {
+ case CERT_V1:
+ /* A V1 cert may not contain unique identifiers. See RFC 5280,
+ * section 4.1.2.8:
+ * "These fields MUST only appear if the version is 2 or 3 (Section
+ * 4.1.2.1). These fields MUST NOT appear if the version is 1."
+ */
+ if (cert->pCertInfo->IssuerUniqueId.cbData ||
+ cert->pCertInfo->SubjectUniqueId.cbData)
+ ret = FALSE;
+ /* A V1 cert may not contain extensions. See RFC 5280, section 4.1.2.9:
+ * "This field MUST only appear if the version is 3 (Section 4.1.2.1)."
+ */
+ if (cert->pCertInfo->cExtension)
+ ret = FALSE;
+ break;
+ case CERT_V2:
+ /* A V2 cert may not contain extensions. See RFC 5280, section 4.1.2.9:
+ * "This field MUST only appear if the version is 3 (Section 4.1.2.1)."
+ */
+ if (cert->pCertInfo->cExtension)
+ ret = FALSE;
+ break;
+ case CERT_V3:
+ /* Do nothing, all fields are allowed for V3 certs */
+ break;
+ default:
+ WARN_(chain)("invalid cert version %d\n", cert->pCertInfo->dwVersion);
+ ret = FALSE;
+ }
+ return ret;
+}
+
static void CRYPT_CheckSimpleChain(PCertificateChainEngine engine,
PCERT_SIMPLE_CHAIN chain, LPFILETIME time)
{
PCERT_CHAIN_ELEMENT rootElement = chain->rgpElement[chain->cElement - 1];
int i;
BOOL pathLengthConstraintViolated = FALSE;
- CERT_BASIC_CONSTRAINTS2_INFO constraints = { TRUE, FALSE, 0 };
+ CERT_BASIC_CONSTRAINTS2_INFO constraints = { FALSE, FALSE, 0 };
TRACE_(chain)("checking chain with %d elements for time %s\n",
chain->cElement, debugstr_w(filetime_to_str(time)));
for (i = chain->cElement - 1; i >= 0; i--)
{
+ BOOL isRoot;
+
if (TRACE_ON(chain))
dump_element(chain->rgpElement[i]->pCertContext);
+ if (i == chain->cElement - 1)
+ isRoot = CRYPT_IsCertificateSelfSigned(
+ chain->rgpElement[i]->pCertContext);
+ else
+ isRoot = FALSE;
+ if (!CRYPT_IsCertVersionValid(chain->rgpElement[i]->pCertContext))
+ {
+ /* MS appears to accept certs whose versions don't match their
+ * contents, so there isn't an appropriate error code.
+ */
+ chain->rgpElement[i]->TrustStatus.dwErrorStatus |=
+ CERT_TRUST_INVALID_EXTENSION;
+ }
if (CertVerifyTimeValidity(time,
chain->rgpElement[i]->pCertContext->pCertInfo) != 0)
chain->rgpElement[i]->TrustStatus.dwErrorStatus |=
CERT_TRUST_IS_NOT_TIME_VALID;
if (i != 0)
{
- BOOL isRoot;
-
- if (i == chain->cElement - 1)
- isRoot = CRYPT_IsCertificateSelfSigned(
- chain->rgpElement[i]->pCertContext);
- else
- isRoot = FALSE;
/* Check the signature of the cert this issued */
if (!CryptVerifyCertificateSignatureEx(0, X509_ASN_ENCODING,
CRYPT_VERIFY_CERT_SIGN_SUBJECT_CERT,
if (pathLengthConstraintViolated)
chain->rgpElement[i]->TrustStatus.dwErrorStatus |=
CERT_TRUST_INVALID_BASIC_CONSTRAINTS;
- else if (!CRYPT_CheckBasicConstraintsForCA(
- chain->rgpElement[i]->pCertContext, &constraints, i - 1,
- isRoot, &pathLengthConstraintViolated))
+ else if (!CRYPT_CheckBasicConstraintsForCA(engine,
+ chain->rgpElement[i]->pCertContext, &constraints, i - 1, isRoot,
+ &pathLengthConstraintViolated))
chain->rgpElement[i]->TrustStatus.dwErrorStatus |=
CERT_TRUST_INVALID_BASIC_CONSTRAINTS;
else if (constraints.fPathLenConstraint &&
chain->rgpElement[i]->TrustStatus.dwErrorStatus |=
CERT_TRUST_INVALID_BASIC_CONSTRAINTS;
}
+ if (!CRYPT_KeyUsageValid(engine, chain->rgpElement[i]->pCertContext,
+ isRoot, constraints.fCA, i))
+ chain->rgpElement[i]->TrustStatus.dwErrorStatus |=
+ CERT_TRUST_IS_NOT_VALID_FOR_USAGE;
if (CRYPT_IsSimpleChainCyclic(chain))
{
/* If the chain is cyclic, then the path length constraints
CERT_TRUST_IS_PARTIAL_CHAIN |
CERT_TRUST_INVALID_BASIC_CONSTRAINTS;
}
- /* FIXME: check valid usages */
/* Check whether every critical extension is supported */
if (!CRYPT_CriticalExtensionsSupported(
chain->rgpElement[i]->pCertContext))
subject->dwCertEncodingType, 0, CERT_FIND_CERT_ID, &id,
prevIssuer);
if (issuer)
+ {
+ TRACE_(chain)("issuer found by issuer/serial number\n");
*infoStatus = CERT_TRUST_HAS_EXACT_MATCH_ISSUER;
+ }
}
else if (info->KeyId.cbData)
{
subject->dwCertEncodingType, 0, CERT_FIND_CERT_ID, &id,
prevIssuer);
if (issuer)
+ {
+ TRACE_(chain)("issuer found by key id\n");
*infoStatus = CERT_TRUST_HAS_KEY_MATCH_ISSUER;
+ }
}
LocalFree(info);
}
subject->dwCertEncodingType, 0, CERT_FIND_CERT_ID, &id,
prevIssuer);
if (issuer)
+ {
+ TRACE_(chain)("issuer found by directory name\n");
*infoStatus = CERT_TRUST_HAS_EXACT_MATCH_ISSUER;
+ }
}
else
FIXME("no supported name type in authority key id2\n");
subject->dwCertEncodingType, 0, CERT_FIND_CERT_ID, &id,
prevIssuer);
if (issuer)
+ {
+ TRACE_(chain)("issuer found by key id\n");
*infoStatus = CERT_TRUST_HAS_KEY_MATCH_ISSUER;
+ }
}
LocalFree(info);
}
issuer = CertFindCertificateInStore(store,
subject->dwCertEncodingType, 0, CERT_FIND_SUBJECT_NAME,
&subject->pCertInfo->Issuer, prevIssuer);
+ TRACE_(chain)("issuer found by name\n");
*infoStatus = CERT_TRUST_HAS_NAME_MATCH_ISSUER;
}
return issuer;
return alternate;
}
-#define CHAIN_QUALITY_SIGNATURE_VALID 8
-#define CHAIN_QUALITY_TIME_VALID 4
-#define CHAIN_QUALITY_COMPLETE_CHAIN 2
-#define CHAIN_QUALITY_TRUSTED_ROOT 1
+#define CHAIN_QUALITY_SIGNATURE_VALID 0x16
+#define CHAIN_QUALITY_TIME_VALID 8
+#define CHAIN_QUALITY_COMPLETE_CHAIN 4
+#define CHAIN_QUALITY_BASIC_CONSTRAINTS 2
+#define CHAIN_QUALITY_TRUSTED_ROOT 1
#define CHAIN_QUALITY_HIGHEST \
CHAIN_QUALITY_SIGNATURE_VALID | CHAIN_QUALITY_TIME_VALID | \
- CHAIN_QUALITY_COMPLETE_CHAIN | CHAIN_QUALITY_TRUSTED_ROOT
+ CHAIN_QUALITY_COMPLETE_CHAIN | CHAIN_QUALITY_BASIC_CONSTRAINTS | \
+ CHAIN_QUALITY_TRUSTED_ROOT
#define IS_TRUST_ERROR_SET(TrustStatus, bits) \
(TrustStatus)->dwErrorStatus & (bits)
if (IS_TRUST_ERROR_SET(&chain->context.TrustStatus,
CERT_TRUST_IS_UNTRUSTED_ROOT))
quality &= ~CHAIN_QUALITY_TRUSTED_ROOT;
+ if (IS_TRUST_ERROR_SET(&chain->context.TrustStatus,
+ CERT_TRUST_INVALID_BASIC_CONSTRAINTS))
+ quality &= ~CHAIN_QUALITY_BASIC_CONSTRAINTS;
if (IS_TRUST_ERROR_SET(&chain->context.TrustStatus,
CERT_TRUST_IS_PARTIAL_CHAIN))
- if (chain->context.TrustStatus.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN)
quality &= ~CHAIN_QUALITY_COMPLETE_CHAIN;
if (IS_TRUST_ERROR_SET(&chain->context.TrustStatus,
CERT_TRUST_IS_NOT_TIME_VALID | CERT_TRUST_IS_NOT_TIME_NESTED))
if (cContext)
{
PCCERT_CONTEXT *contexts =
- CryptMemAlloc(cContext * sizeof(PCCERT_CONTEXT *));
+ CryptMemAlloc(cContext * sizeof(PCCERT_CONTEXT));
if (contexts)
{
case CRYPT_E_NO_REVOCATION_CHECK:
case CRYPT_E_NO_REVOCATION_DLL:
case CRYPT_E_NOT_IN_REVOCATION_DATABASE:
- error = CERT_TRUST_REVOCATION_STATUS_UNKNOWN;
+ /* If the revocation status is unknown, it's assumed to be
+ * offline too.
+ */
+ error = CERT_TRUST_REVOCATION_STATUS_UNKNOWN |
+ CERT_TRUST_IS_OFFLINE_REVOCATION;
break;
case CRYPT_E_REVOCATION_OFFLINE:
error = CERT_TRUST_IS_OFFLINE_REVOCATION;
}
}
+static void CRYPT_CheckUsages(PCERT_CHAIN_CONTEXT chain,
+ const CERT_CHAIN_PARA *pChainPara)
+{
+ if (pChainPara->cbSize >= sizeof(CERT_CHAIN_PARA_NO_EXTRA_FIELDS) &&
+ pChainPara->RequestedUsage.Usage.cUsageIdentifier)
+ {
+ PCCERT_CONTEXT endCert;
+ PCERT_EXTENSION ext;
+ BOOL validForUsage;
+
+ /* A chain, if created, always includes the end certificate */
+ endCert = chain->rgpChain[0]->rgpElement[0]->pCertContext;
+ /* The extended key usage extension specifies how a certificate's
+ * public key may be used. From RFC 5280, section 4.2.1.12:
+ * "This extension indicates one or more purposes for which the
+ * certified public key may be used, in addition to or in place of the
+ * basic purposes indicated in the key usage extension."
+ * If the extension is present, it only satisfies the requested usage
+ * if that usage is included in the extension:
+ * "If the extension is present, then the certificate MUST only be used
+ * for one of the purposes indicated."
+ * There is also the special anyExtendedKeyUsage OID, but it doesn't
+ * have to be respected:
+ * "Applications that require the presence of a particular purpose
+ * MAY reject certificates that include the anyExtendedKeyUsage OID
+ * but not the particular OID expected for the application."
+ * For now, I'm being more conservative and ignoring the presence of
+ * the anyExtendedKeyUsage OID.
+ */
+ if ((ext = CertFindExtension(szOID_ENHANCED_KEY_USAGE,
+ endCert->pCertInfo->cExtension, endCert->pCertInfo->rgExtension)))
+ {
+ const CERT_ENHKEY_USAGE *requestedUsage =
+ &pChainPara->RequestedUsage.Usage;
+ CERT_ENHKEY_USAGE *usage;
+ DWORD size;
+
+ if (CryptDecodeObjectEx(X509_ASN_ENCODING,
+ X509_ENHANCED_KEY_USAGE, ext->Value.pbData, ext->Value.cbData,
+ CRYPT_DECODE_ALLOC_FLAG, NULL, &usage, &size))
+ {
+ if (pChainPara->RequestedUsage.dwType == USAGE_MATCH_TYPE_AND)
+ {
+ DWORD i, j;
+
+ /* For AND matches, all usages must be present */
+ validForUsage = TRUE;
+ for (i = 0; validForUsage &&
+ i < requestedUsage->cUsageIdentifier; i++)
+ {
+ BOOL match = FALSE;
+
+ for (j = 0; !match && j < usage->cUsageIdentifier; j++)
+ match = !strcmp(usage->rgpszUsageIdentifier[j],
+ requestedUsage->rgpszUsageIdentifier[i]);
+ if (!match)
+ validForUsage = FALSE;
+ }
+ }
+ else
+ {
+ DWORD i, j;
+
+ /* For OR matches, any matching usage suffices */
+ validForUsage = FALSE;
+ for (i = 0; !validForUsage &&
+ i < requestedUsage->cUsageIdentifier; i++)
+ {
+ for (j = 0; !validForUsage &&
+ j < usage->cUsageIdentifier; j++)
+ validForUsage =
+ !strcmp(usage->rgpszUsageIdentifier[j],
+ requestedUsage->rgpszUsageIdentifier[i]);
+ }
+ }
+ LocalFree(usage);
+ }
+ else
+ validForUsage = FALSE;
+ }
+ else
+ {
+ /* If the extension isn't present, any interpretation is valid:
+ * "Certificate using applications MAY require that the extended
+ * key usage extension be present and that a particular purpose
+ * be indicated in order for the certificate to be acceptable to
+ * that application."
+ * Not all web sites include the extended key usage extension, so
+ * accept chains without it.
+ */
+ TRACE_(chain)("requested usage from certificate with no usages\n");
+ validForUsage = TRUE;
+ }
+ if (!validForUsage)
+ {
+ chain->TrustStatus.dwErrorStatus |=
+ CERT_TRUST_IS_NOT_VALID_FOR_USAGE;
+ chain->rgpChain[0]->rgpElement[0]->TrustStatus.dwErrorStatus |=
+ CERT_TRUST_IS_NOT_VALID_FOR_USAGE;
+ }
+ }
+ if (pChainPara->cbSize >= sizeof(CERT_CHAIN_PARA) &&
+ pChainPara->RequestedIssuancePolicy.Usage.cUsageIdentifier)
+ FIXME("unimplemented for RequestedIssuancePolicy\n");
+}
+
+static void dump_usage_match(LPCSTR name, const CERT_USAGE_MATCH *usageMatch)
+{
+ if (usageMatch->Usage.cUsageIdentifier)
+ {
+ DWORD i;
+
+ TRACE_(chain)("%s: %s\n", name,
+ usageMatch->dwType == USAGE_MATCH_TYPE_AND ? "AND" : "OR");
+ for (i = 0; i < usageMatch->Usage.cUsageIdentifier; i++)
+ TRACE_(chain)("%s\n", usageMatch->Usage.rgpszUsageIdentifier[i]);
+ }
+}
+
+static void dump_chain_para(const CERT_CHAIN_PARA *pChainPara)
+{
+ TRACE_(chain)("%d\n", pChainPara->cbSize);
+ if (pChainPara->cbSize >= sizeof(CERT_CHAIN_PARA_NO_EXTRA_FIELDS))
+ dump_usage_match("RequestedUsage", &pChainPara->RequestedUsage);
+ if (pChainPara->cbSize >= sizeof(CERT_CHAIN_PARA))
+ {
+ dump_usage_match("RequestedIssuancePolicy",
+ &pChainPara->RequestedIssuancePolicy);
+ TRACE_(chain)("%d\n", pChainPara->dwUrlRetrievalTimeout);
+ TRACE_(chain)("%d\n", pChainPara->fCheckRevocationFreshnessTime);
+ TRACE_(chain)("%d\n", pChainPara->dwRevocationFreshnessTime);
+ }
+}
+
BOOL WINAPI CertGetCertificateChain(HCERTCHAINENGINE hChainEngine,
PCCERT_CONTEXT pCertContext, LPFILETIME pTime, HCERTSTORE hAdditionalStore,
PCERT_CHAIN_PARA pChainPara, DWORD dwFlags, LPVOID pvReserved,
if (!hChainEngine)
hChainEngine = CRYPT_GetDefaultChainEngine();
+ if (TRACE_ON(chain))
+ dump_chain_para(pChainPara);
/* FIXME: what about HCCE_LOCAL_MACHINE? */
ret = CRYPT_BuildCandidateChainFromCert(hChainEngine, pCertContext, pTime,
hAdditionalStore, &chain);
if (!(dwFlags & CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS))
CRYPT_FreeLowerQualityChains(chain);
pChain = (PCERT_CHAIN_CONTEXT)chain;
- CRYPT_VerifyChainRevocation(pChain, pTime, pChainPara, dwFlags);
+ if (!pChain->TrustStatus.dwErrorStatus)
+ CRYPT_VerifyChainRevocation(pChain, pTime, pChainPara, dwFlags);
+ CRYPT_CheckUsages(pChain, pChainPara);
+ TRACE_(chain)("error status: %08x\n",
+ pChain->TrustStatus.dwErrorStatus);
if (ppChainContext)
*ppChainContext = pChain;
else
return TRUE;
}
+static BOOL match_dns_to_subject_alt_name(PCERT_EXTENSION ext,
+ LPCWSTR server_name)
+{
+ BOOL matches = FALSE;
+ CERT_ALT_NAME_INFO *subjectName;
+ DWORD size;
+
+ TRACE_(chain)("%s\n", debugstr_w(server_name));
+ /* This could be spoofed by the embedded NULL vulnerability, since the
+ * returned CERT_ALT_NAME_INFO doesn't have a way to indicate the
+ * encoded length of a name. Fortunately CryptDecodeObjectEx fails if
+ * the encoded form of the name contains a NULL.
+ */
+ if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_ALTERNATE_NAME,
+ ext->Value.pbData, ext->Value.cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, NULL,
+ &subjectName, &size))
+ {
+ DWORD i;
+
+ /* RFC 5280 states that multiple instances of each name type may exist,
+ * in section 4.2.1.6:
+ * "Multiple name forms, and multiple instances of each name form,
+ * MAY be included."
+ * It doesn't specify the behavior in such cases, but both RFC 2818
+ * and RFC 2595 explicitly accept a certificate if any name matches.
+ */
+ for (i = 0; !matches && i < subjectName->cAltEntry; i++)
+ {
+ if (subjectName->rgAltEntry[i].dwAltNameChoice ==
+ CERT_ALT_NAME_DNS_NAME)
+ {
+ TRACE_(chain)("dNSName: %s\n", debugstr_w(
+ subjectName->rgAltEntry[i].u.pwszDNSName));
+ if (!strcmpiW(server_name,
+ subjectName->rgAltEntry[i].u.pwszDNSName))
+ matches = TRUE;
+ }
+ }
+ LocalFree(subjectName);
+ }
+ return matches;
+}
+
+static BOOL find_matching_domain_component(CERT_NAME_INFO *name,
+ LPCWSTR component)
+{
+ BOOL matches = FALSE;
+ DWORD i, j;
+
+ for (i = 0; !matches && i < name->cRDN; i++)
+ for (j = 0; j < name->rgRDN[i].cRDNAttr; j++)
+ if (!strcmp(szOID_DOMAIN_COMPONENT,
+ name->rgRDN[i].rgRDNAttr[j].pszObjId))
+ {
+ PCERT_RDN_ATTR attr;
+
+ attr = &name->rgRDN[i].rgRDNAttr[j];
+ /* Compare with memicmpW rather than strcmpiW in order to avoid
+ * a match with a string with an embedded NULL. The component
+ * must match one domain component attribute's entire string
+ * value with a case-insensitive match.
+ */
+ matches = !memicmpW(component, (LPWSTR)attr->Value.pbData,
+ attr->Value.cbData / sizeof(WCHAR));
+ }
+ return matches;
+}
+
+static BOOL match_domain_component(LPCWSTR allowed_component, DWORD allowed_len,
+ LPCWSTR server_component, DWORD server_len, BOOL allow_wildcards,
+ BOOL *see_wildcard)
+{
+ LPCWSTR allowed_ptr, server_ptr;
+ BOOL matches = TRUE;
+
+ *see_wildcard = FALSE;
+ if (server_len < allowed_len)
+ {
+ WARN_(chain)("domain component %s too short for %s\n",
+ debugstr_wn(server_component, server_len),
+ debugstr_wn(allowed_component, allowed_len));
+ /* A domain component can't contain a wildcard character, so a domain
+ * component shorter than the allowed string can't produce a match.
+ */
+ return FALSE;
+ }
+ for (allowed_ptr = allowed_component, server_ptr = server_component;
+ matches && allowed_ptr - allowed_component < allowed_len;
+ allowed_ptr++, server_ptr++)
+ {
+ if (*allowed_ptr == '*')
+ {
+ if (allowed_ptr - allowed_component < allowed_len - 1)
+ {
+ WARN_(chain)("non-wildcard characters after wildcard not supported\n");
+ matches = FALSE;
+ }
+ else if (!allow_wildcards)
+ {
+ WARN_(chain)("wildcard after non-wildcard component\n");
+ matches = FALSE;
+ }
+ else
+ {
+ /* the preceding characters must have matched, so the rest of
+ * the component also matches.
+ */
+ *see_wildcard = TRUE;
+ break;
+ }
+ }
+ matches = tolowerW(*allowed_ptr) == tolowerW(*server_ptr);
+ }
+ if (matches && server_ptr - server_component < server_len)
+ {
+ /* If there are unmatched characters in the server domain component,
+ * the server domain only matches if the allowed string ended in a '*'.
+ */
+ matches = *allowed_ptr == '*';
+ }
+ return matches;
+}
+
+static BOOL match_common_name(LPCWSTR server_name, PCERT_RDN_ATTR nameAttr)
+{
+ LPCWSTR allowed = (LPCWSTR)nameAttr->Value.pbData;
+ LPCWSTR allowed_component = allowed;
+ DWORD allowed_len = nameAttr->Value.cbData / sizeof(WCHAR);
+ LPCWSTR server_component = server_name;
+ DWORD server_len = strlenW(server_name);
+ BOOL matches = TRUE, allow_wildcards = TRUE;
+
+ TRACE_(chain)("CN = %s\n", debugstr_wn(allowed_component, allowed_len));
+
+ /* From RFC 2818 (HTTP over TLS), section 3.1:
+ * "Names may contain the wildcard character * which is considered to match
+ * any single domain name component or component fragment. E.g.,
+ * *.a.com matches foo.a.com but not bar.foo.a.com. f*.com matches foo.com
+ * but not bar.com."
+ *
+ * And from RFC 2595 (Using TLS with IMAP, POP3 and ACAP), section 2.4:
+ * "A "*" wildcard character MAY be used as the left-most name component in
+ * the certificate. For example, *.example.com would match a.example.com,
+ * foo.example.com, etc. but would not match example.com."
+ *
+ * There are other protocols which use TLS, and none of them is
+ * authoritative. This accepts certificates in common usage, e.g.
+ * *.domain.com matches www.domain.com but not domain.com, and
+ * www*.domain.com matches www1.domain.com but not mail.domain.com.
+ */
+ do {
+ LPCWSTR allowed_dot, server_dot;
+
+ allowed_dot = memchrW(allowed_component, '.',
+ allowed_len - (allowed_component - allowed));
+ server_dot = memchrW(server_component, '.',
+ server_len - (server_component - server_name));
+ /* The number of components must match */
+ if ((!allowed_dot && server_dot) || (allowed_dot && !server_dot))
+ {
+ if (!allowed_dot)
+ WARN_(chain)("%s: too many components for CN=%s\n",
+ debugstr_w(server_name), debugstr_wn(allowed, allowed_len));
+ else
+ WARN_(chain)("%s: not enough components for CN=%s\n",
+ debugstr_w(server_name), debugstr_wn(allowed, allowed_len));
+ matches = FALSE;
+ }
+ else
+ {
+ LPCWSTR allowed_end, server_end;
+ BOOL has_wildcard;
+
+ allowed_end = allowed_dot ? allowed_dot : allowed + allowed_len;
+ server_end = server_dot ? server_dot : server_name + server_len;
+ matches = match_domain_component(allowed_component,
+ allowed_end - allowed_component, server_component,
+ server_end - server_component, allow_wildcards, &has_wildcard);
+ /* Once a non-wildcard component is seen, no wildcard components
+ * may follow
+ */
+ if (!has_wildcard)
+ allow_wildcards = FALSE;
+ if (matches)
+ {
+ allowed_component = allowed_dot ? allowed_dot + 1 : allowed_end;
+ server_component = server_dot ? server_dot + 1 : server_end;
+ }
+ }
+ } while (matches && allowed_component &&
+ allowed_component - allowed < allowed_len &&
+ server_component && server_component - server_name < server_len);
+ TRACE_(chain)("returning %d\n", matches);
+ return matches;
+}
+
+static BOOL match_dns_to_subject_dn(PCCERT_CONTEXT cert, LPCWSTR server_name)
+{
+ BOOL matches = FALSE;
+ CERT_NAME_INFO *name;
+ DWORD size;
+
+ TRACE_(chain)("%s\n", debugstr_w(server_name));
+ if (CryptDecodeObjectEx(X509_ASN_ENCODING, X509_UNICODE_NAME,
+ cert->pCertInfo->Subject.pbData, cert->pCertInfo->Subject.cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, NULL,
+ &name, &size))
+ {
+ /* If the subject distinguished name contains any name components,
+ * make sure all of them are present.
+ */
+ if (CertFindRDNAttr(szOID_DOMAIN_COMPONENT, name))
+ {
+ LPCWSTR ptr = server_name;
+
+ matches = TRUE;
+ do {
+ LPCWSTR dot = strchrW(ptr, '.'), end;
+ /* 254 is the maximum DNS label length, see RFC 1035 */
+ WCHAR component[255];
+ DWORD len;
+
+ end = dot ? dot : ptr + strlenW(ptr);
+ len = end - ptr;
+ if (len >= sizeof(component) / sizeof(component[0]))
+ {
+ WARN_(chain)("domain component %s too long\n",
+ debugstr_wn(ptr, len));
+ matches = FALSE;
+ }
+ else
+ {
+ memcpy(component, ptr, len * sizeof(WCHAR));
+ component[len] = 0;
+ matches = find_matching_domain_component(name, component);
+ }
+ ptr = dot ? dot + 1 : end;
+ } while (matches && ptr && *ptr);
+ }
+ else
+ {
+ PCERT_RDN_ATTR attr;
+
+ /* If the certificate isn't using a DN attribute in the name, make
+ * make sure the common name matches.
+ */
+ if ((attr = CertFindRDNAttr(szOID_COMMON_NAME, name)))
+ matches = match_common_name(server_name, attr);
+ }
+ LocalFree(name);
+ }
+ return matches;
+}
+
+static BOOL WINAPI verify_ssl_policy(LPCSTR szPolicyOID,
+ PCCERT_CHAIN_CONTEXT pChainContext, PCERT_CHAIN_POLICY_PARA pPolicyPara,
+ PCERT_CHAIN_POLICY_STATUS pPolicyStatus)
+{
+ pPolicyStatus->lChainIndex = pPolicyStatus->lElementIndex = -1;
+ if (pChainContext->TrustStatus.dwErrorStatus &
+ CERT_TRUST_IS_NOT_SIGNATURE_VALID)
+ {
+ pPolicyStatus->dwError = TRUST_E_CERT_SIGNATURE;
+ find_element_with_error(pChainContext,
+ CERT_TRUST_IS_NOT_SIGNATURE_VALID, &pPolicyStatus->lChainIndex,
+ &pPolicyStatus->lElementIndex);
+ }
+ else if (pChainContext->TrustStatus.dwErrorStatus &
+ CERT_TRUST_IS_UNTRUSTED_ROOT)
+ {
+ pPolicyStatus->dwError = CERT_E_UNTRUSTEDROOT;
+ find_element_with_error(pChainContext,
+ CERT_TRUST_IS_UNTRUSTED_ROOT, &pPolicyStatus->lChainIndex,
+ &pPolicyStatus->lElementIndex);
+ }
+ else if (pChainContext->TrustStatus.dwErrorStatus & CERT_TRUST_IS_CYCLIC)
+ {
+ pPolicyStatus->dwError = CERT_E_UNTRUSTEDROOT;
+ find_element_with_error(pChainContext,
+ CERT_TRUST_IS_CYCLIC, &pPolicyStatus->lChainIndex,
+ &pPolicyStatus->lElementIndex);
+ /* For a cyclic chain, which element is a cycle isn't meaningful */
+ pPolicyStatus->lElementIndex = -1;
+ }
+ else if (pChainContext->TrustStatus.dwErrorStatus &
+ CERT_TRUST_IS_NOT_TIME_VALID)
+ {
+ pPolicyStatus->dwError = CERT_E_EXPIRED;
+ find_element_with_error(pChainContext,
+ CERT_TRUST_IS_NOT_TIME_VALID, &pPolicyStatus->lChainIndex,
+ &pPolicyStatus->lElementIndex);
+ }
+ else
+ pPolicyStatus->dwError = NO_ERROR;
+ /* We only need bother checking whether the name in the end certificate
+ * matches if the chain is otherwise okay.
+ */
+ if (!pPolicyStatus->dwError && pPolicyPara &&
+ pPolicyPara->cbSize >= sizeof(CERT_CHAIN_POLICY_PARA))
+ {
+ HTTPSPolicyCallbackData *sslPara = pPolicyPara->pvExtraPolicyPara;
+
+ if (sslPara && sslPara->u.cbSize >= sizeof(HTTPSPolicyCallbackData))
+ {
+ if (sslPara->dwAuthType == AUTHTYPE_SERVER &&
+ sslPara->pwszServerName)
+ {
+ PCCERT_CONTEXT cert;
+ PCERT_EXTENSION altNameExt;
+ BOOL matches;
+
+ cert = pChainContext->rgpChain[0]->rgpElement[0]->pCertContext;
+ altNameExt = get_subject_alt_name_ext(cert->pCertInfo);
+ /* If the alternate name extension exists, the name it contains
+ * is bound to the certificate, so make sure the name matches
+ * it. Otherwise, look for the server name in the subject
+ * distinguished name. RFC5280, section 4.2.1.6:
+ * "Whenever such identities are to be bound into a
+ * certificate, the subject alternative name (or issuer
+ * alternative name) extension MUST be used; however, a DNS
+ * name MAY also be represented in the subject field using the
+ * domainComponent attribute."
+ */
+ if (altNameExt)
+ matches = match_dns_to_subject_alt_name(altNameExt,
+ sslPara->pwszServerName);
+ else
+ matches = match_dns_to_subject_dn(cert,
+ sslPara->pwszServerName);
+ if (!matches)
+ {
+ pPolicyStatus->dwError = CERT_E_CN_NO_MATCH;
+ pPolicyStatus->lChainIndex = 0;
+ pPolicyStatus->lElementIndex = 0;
+ }
+ }
+ }
+ }
+ return TRUE;
+}
+
static BYTE msPubKey1[] = {
0x30,0x82,0x01,0x0a,0x02,0x82,0x01,0x01,0x00,0xdf,0x08,0xba,0xe3,0x3f,0x6e,
0x64,0x9b,0xf5,0x89,0xaf,0x28,0x96,0x4a,0x07,0x8f,0x1b,0x2e,0x8b,0x3e,0x1d,
TRACE("(%s, %p, %p, %p)\n", debugstr_a(szPolicyOID), pChainContext,
pPolicyPara, pPolicyStatus);
- if (!HIWORD(szPolicyOID))
+ if (IS_INTOID(szPolicyOID))
{
switch (LOWORD(szPolicyOID))
{
case LOWORD(CERT_CHAIN_POLICY_AUTHENTICODE):
verifyPolicy = verify_authenticode_policy;
break;
+ case LOWORD(CERT_CHAIN_POLICY_SSL):
+ verifyPolicy = verify_ssl_policy;
+ break;
case LOWORD(CERT_CHAIN_POLICY_BASIC_CONSTRAINTS):
verifyPolicy = verify_basic_constraints_policy;
break;
pPolicyStatus);
if (hFunc)
CryptFreeOIDFunctionAddress(hFunc, 0);
+ TRACE("returning %d (%08x)\n", ret, pPolicyStatus->dwError);
return ret;
}