| /* | |
| * Copyright 2007 Juan Lang | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | |
| */ | |
| #include "config.h" | |
| #include <stdarg.h> | |
| #include <stdio.h> | |
| #include <sys/types.h> | |
| #ifdef HAVE_SYS_STAT_H | |
| #include <sys/stat.h> | |
| #endif | |
| #include <dirent.h> | |
| #include <fcntl.h> | |
| #ifdef HAVE_UNISTD_H | |
| #include <unistd.h> | |
| #endif | |
| #include <errno.h> | |
| #include <limits.h> | |
| #include "ntstatus.h" | |
| #define WIN32_NO_STATUS | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "winreg.h" | |
| #include "wincrypt.h" | |
| #include "winternl.h" | |
| #include "wine/debug.h" | |
| #include "crypt32_private.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(crypt); | |
| #define INITIAL_CERT_BUFFER 1024 | |
| struct DynamicBuffer | |
| { | |
| DWORD allocated; | |
| DWORD used; | |
| BYTE *data; | |
| }; | |
| static inline void reset_buffer(struct DynamicBuffer *buffer) | |
| { | |
| buffer->used = 0; | |
| if (buffer->data) buffer->data[0] = 0; | |
| } | |
| static BOOL add_line_to_buffer(struct DynamicBuffer *buffer, LPCSTR line) | |
| { | |
| BOOL ret; | |
| if (buffer->used + strlen(line) + 1 > buffer->allocated) | |
| { | |
| if (!buffer->allocated) | |
| { | |
| buffer->data = CryptMemAlloc(INITIAL_CERT_BUFFER); | |
| if (buffer->data) | |
| { | |
| buffer->data[0] = 0; | |
| buffer->allocated = INITIAL_CERT_BUFFER; | |
| } | |
| } | |
| else | |
| { | |
| DWORD new_size = max(buffer->allocated * 2, | |
| buffer->used + strlen(line) + 1); | |
| buffer->data = CryptMemRealloc(buffer->data, new_size); | |
| if (buffer->data) | |
| buffer->allocated = new_size; | |
| } | |
| } | |
| if (buffer->data) | |
| { | |
| strcpy((char *)buffer->data + strlen((char *)buffer->data), line); | |
| /* Not strlen + 1, otherwise we'd count the NULL for every line's | |
| * addition (but we overwrite the previous NULL character.) Not an | |
| * overrun, we allocate strlen + 1 bytes above. | |
| */ | |
| buffer->used += strlen(line); | |
| ret = TRUE; | |
| } | |
| else | |
| ret = FALSE; | |
| return ret; | |
| } | |
| /* Reads any base64-encoded certificates present in fp and adds them to store. | |
| * Returns TRUE if any certifcates were successfully imported. | |
| */ | |
| static BOOL import_base64_certs_from_fp(FILE *fp, HCERTSTORE store) | |
| { | |
| char line[1024]; | |
| BOOL in_cert = FALSE; | |
| struct DynamicBuffer saved_cert = { 0, 0, NULL }; | |
| int num_certs = 0; | |
| TRACE("\n"); | |
| while (fgets(line, sizeof(line), fp)) | |
| { | |
| static const char header[] = "-----BEGIN CERTIFICATE-----"; | |
| static const char trailer[] = "-----END CERTIFICATE-----"; | |
| if (!strncmp(line, header, strlen(header))) | |
| { | |
| TRACE("begin new certificate\n"); | |
| in_cert = TRUE; | |
| reset_buffer(&saved_cert); | |
| } | |
| else if (!strncmp(line, trailer, strlen(trailer))) | |
| { | |
| DWORD size; | |
| TRACE("end of certificate, adding cert\n"); | |
| in_cert = FALSE; | |
| if (CryptStringToBinaryA((char *)saved_cert.data, saved_cert.used, | |
| CRYPT_STRING_BASE64, NULL, &size, NULL, NULL)) | |
| { | |
| LPBYTE buf = CryptMemAlloc(size); | |
| if (buf) | |
| { | |
| CryptStringToBinaryA((char *)saved_cert.data, | |
| saved_cert.used, CRYPT_STRING_BASE64, buf, &size, NULL, | |
| NULL); | |
| if (CertAddEncodedCertificateToStore(store, | |
| X509_ASN_ENCODING, buf, size, CERT_STORE_ADD_NEW, NULL)) | |
| num_certs++; | |
| CryptMemFree(buf); | |
| } | |
| } | |
| } | |
| else if (in_cert) | |
| add_line_to_buffer(&saved_cert, line); | |
| } | |
| CryptMemFree(saved_cert.data); | |
| TRACE("Read %d certs\n", num_certs); | |
| return num_certs > 0; | |
| } | |
| static const char *trust_status_to_str(DWORD status) | |
| { | |
| static char buf[1024]; | |
| int pos = 0; | |
| if (status & CERT_TRUST_IS_NOT_TIME_VALID) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\texpired"); | |
| if (status & CERT_TRUST_IS_NOT_TIME_NESTED) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\tbad time nesting"); | |
| if (status & CERT_TRUST_IS_REVOKED) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\trevoked"); | |
| if (status & CERT_TRUST_IS_NOT_SIGNATURE_VALID) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\tbad signature"); | |
| if (status & CERT_TRUST_IS_NOT_VALID_FOR_USAGE) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\tbad usage"); | |
| if (status & CERT_TRUST_IS_UNTRUSTED_ROOT) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\tuntrusted root"); | |
| if (status & CERT_TRUST_REVOCATION_STATUS_UNKNOWN) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, | |
| "\n\tunknown revocation status"); | |
| if (status & CERT_TRUST_IS_CYCLIC) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\tcyclic chain"); | |
| if (status & CERT_TRUST_INVALID_EXTENSION) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, | |
| "\n\tunsupported critical extension"); | |
| if (status & CERT_TRUST_INVALID_POLICY_CONSTRAINTS) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\tbad policy"); | |
| if (status & CERT_TRUST_INVALID_BASIC_CONSTRAINTS) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, | |
| "\n\tbad basic constraints"); | |
| if (status & CERT_TRUST_INVALID_NAME_CONSTRAINTS) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, | |
| "\n\tbad name constraints"); | |
| if (status & CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, | |
| "\n\tunsuported name constraint"); | |
| if (status & CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, | |
| "\n\tundefined name constraint"); | |
| if (status & CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, | |
| "\n\tdisallowed name constraint"); | |
| if (status & CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, | |
| "\n\texcluded name constraint"); | |
| if (status & CERT_TRUST_IS_OFFLINE_REVOCATION) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, | |
| "\n\trevocation server offline"); | |
| if (status & CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY) | |
| pos += snprintf(buf + pos, sizeof(buf) - pos, | |
| "\n\tno issuance policy"); | |
| return buf; | |
| } | |
| static const char *get_cert_common_name(PCCERT_CONTEXT cert) | |
| { | |
| static char buf[1024]; | |
| const char *name = NULL; | |
| CERT_NAME_INFO *nameInfo; | |
| DWORD size; | |
| BOOL ret = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_NAME, | |
| cert->pCertInfo->Subject.pbData, cert->pCertInfo->Subject.cbData, | |
| CRYPT_DECODE_NOCOPY_FLAG | CRYPT_DECODE_ALLOC_FLAG, NULL, &nameInfo, | |
| &size); | |
| if (ret) | |
| { | |
| PCERT_RDN_ATTR commonName = CertFindRDNAttr(szOID_COMMON_NAME, | |
| nameInfo); | |
| if (commonName) | |
| { | |
| CertRDNValueToStrA(commonName->dwValueType, | |
| &commonName->Value, buf, sizeof(buf)); | |
| name = buf; | |
| } | |
| LocalFree(nameInfo); | |
| } | |
| return name; | |
| } | |
| static void check_and_store_certs(HCERTSTORE from, HCERTSTORE to) | |
| { | |
| PCCERT_CONTEXT cert = NULL; | |
| DWORD root_count = 0; | |
| TRACE("\n"); | |
| do { | |
| cert = CertEnumCertificatesInStore(from, cert); | |
| if (cert) | |
| { | |
| CERT_CHAIN_ENGINE_CONFIG chainEngineConfig = | |
| { sizeof(chainEngineConfig), 0 }; | |
| HCERTCHAINENGINE engine = CRYPT_CreateChainEngine(to, | |
| &chainEngineConfig); | |
| if (engine) | |
| { | |
| CERT_CHAIN_PARA chainPara = { sizeof(chainPara), { 0 } }; | |
| PCCERT_CHAIN_CONTEXT chain; | |
| BOOL ret = CertGetCertificateChain(engine, cert, NULL, from, | |
| &chainPara, 0, NULL, &chain); | |
| if (!ret) | |
| TRACE("rejecting %s: %s\n", get_cert_common_name(cert), | |
| "chain creation failed"); | |
| else | |
| { | |
| /* The only allowed error is CERT_TRUST_IS_UNTRUSTED_ROOT */ | |
| if (chain->TrustStatus.dwErrorStatus & | |
| ~CERT_TRUST_IS_UNTRUSTED_ROOT) | |
| TRACE("rejecting %s: %s\n", get_cert_common_name(cert), | |
| trust_status_to_str(chain->TrustStatus.dwErrorStatus & | |
| ~CERT_TRUST_IS_UNTRUSTED_ROOT)); | |
| else | |
| { | |
| DWORD i, j; | |
| for (i = 0; i < chain->cChain; i++) | |
| for (j = 0; j < chain->rgpChain[i]->cElement; j++) | |
| if (CertAddCertificateContextToStore(to, | |
| chain->rgpChain[i]->rgpElement[j]->pCertContext, | |
| CERT_STORE_ADD_NEW, NULL)) | |
| root_count++; | |
| } | |
| } | |
| CertFreeCertificateChainEngine(engine); | |
| } | |
| } | |
| } while (cert); | |
| TRACE("Added %d root certificates\n", root_count); | |
| } | |
| /* Reads the file fd, and imports any certificates in it into store. | |
| * Returns TRUE if any certificates were successfully imported. | |
| */ | |
| static BOOL import_certs_from_file(int fd, HCERTSTORE store) | |
| { | |
| BOOL ret = FALSE; | |
| FILE *fp; | |
| TRACE("\n"); | |
| fp = fdopen(fd, "r"); | |
| if (fp) | |
| { | |
| ret = import_base64_certs_from_fp(fp, store); | |
| fclose(fp); | |
| } | |
| return ret; | |
| } | |
| static BOOL import_certs_from_path(LPCSTR path, HCERTSTORE store, | |
| BOOL allow_dir); | |
| /* Opens path, which must be a directory, and imports certificates from every | |
| * file in the directory into store. | |
| * Returns TRUE if any certificates were successfully imported. | |
| */ | |
| static BOOL import_certs_from_dir(LPCSTR path, HCERTSTORE store) | |
| { | |
| BOOL ret = FALSE; | |
| DIR *dir; | |
| TRACE("(%s, %p)\n", debugstr_a(path), store); | |
| dir = opendir(path); | |
| if (dir) | |
| { | |
| size_t bufsize = strlen(path) + 1 + PATH_MAX + 1; | |
| char *filebuf = CryptMemAlloc(bufsize); | |
| if (filebuf) | |
| { | |
| struct dirent *entry; | |
| while ((entry = readdir(dir))) | |
| { | |
| if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) | |
| { | |
| snprintf(filebuf, bufsize, "%s/%s", path, entry->d_name); | |
| if (import_certs_from_path(filebuf, store, FALSE) && !ret) | |
| ret = TRUE; | |
| } | |
| } | |
| closedir(dir); | |
| CryptMemFree(filebuf); | |
| } | |
| } | |
| return ret; | |
| } | |
| /* Opens path, which may be a file or a directory, and imports any certificates | |
| * it finds into store. | |
| * Returns TRUE if any certificates were successfully imported. | |
| */ | |
| static BOOL import_certs_from_path(LPCSTR path, HCERTSTORE store, | |
| BOOL allow_dir) | |
| { | |
| BOOL ret = FALSE; | |
| int fd; | |
| TRACE("(%s, %p, %d)\n", debugstr_a(path), store, allow_dir); | |
| fd = open(path, O_RDONLY); | |
| if (fd != -1) | |
| { | |
| struct stat st; | |
| if (fstat(fd, &st) == 0) | |
| { | |
| if (S_ISREG(st.st_mode)) | |
| ret = import_certs_from_file(fd, store); | |
| else if (S_ISDIR(st.st_mode)) | |
| { | |
| if (allow_dir) | |
| ret = import_certs_from_dir(path, store); | |
| else | |
| WARN("%s is a directory and directories are disallowed\n", | |
| debugstr_a(path)); | |
| } | |
| else | |
| ERR("%s: invalid file type\n", path); | |
| } | |
| close(fd); | |
| } | |
| return ret; | |
| } | |
| static BOOL WINAPI CRYPT_RootWriteCert(HCERTSTORE hCertStore, | |
| PCCERT_CONTEXT cert, DWORD dwFlags) | |
| { | |
| /* The root store can't have certs added */ | |
| return FALSE; | |
| } | |
| static BOOL WINAPI CRYPT_RootDeleteCert(HCERTSTORE hCertStore, | |
| PCCERT_CONTEXT cert, DWORD dwFlags) | |
| { | |
| /* The root store can't have certs deleted */ | |
| return FALSE; | |
| } | |
| static BOOL WINAPI CRYPT_RootWriteCRL(HCERTSTORE hCertStore, | |
| PCCRL_CONTEXT crl, DWORD dwFlags) | |
| { | |
| /* The root store can have CRLs added. At worst, a malicious application | |
| * can DoS itself, as the changes aren't persisted in any way. | |
| */ | |
| return TRUE; | |
| } | |
| static BOOL WINAPI CRYPT_RootDeleteCRL(HCERTSTORE hCertStore, | |
| PCCRL_CONTEXT crl, DWORD dwFlags) | |
| { | |
| /* The root store can't have CRLs deleted */ | |
| return FALSE; | |
| } | |
| static void *rootProvFuncs[] = { | |
| NULL, /* CERT_STORE_PROV_CLOSE_FUNC */ | |
| NULL, /* CERT_STORE_PROV_READ_CERT_FUNC */ | |
| CRYPT_RootWriteCert, | |
| CRYPT_RootDeleteCert, | |
| NULL, /* CERT_STORE_PROV_SET_CERT_PROPERTY_FUNC */ | |
| NULL, /* CERT_STORE_PROV_READ_CRL_FUNC */ | |
| CRYPT_RootWriteCRL, | |
| CRYPT_RootDeleteCRL, | |
| NULL, /* CERT_STORE_PROV_SET_CRL_PROPERTY_FUNC */ | |
| NULL, /* CERT_STORE_PROV_READ_CTL_FUNC */ | |
| NULL, /* CERT_STORE_PROV_WRITE_CTL_FUNC */ | |
| NULL, /* CERT_STORE_PROV_DELETE_CTL_FUNC */ | |
| NULL, /* CERT_STORE_PROV_SET_CTL_PROPERTY_FUNC */ | |
| NULL, /* CERT_STORE_PROV_CONTROL_FUNC */ | |
| }; | |
| static const char * const CRYPT_knownLocations[] = { | |
| "/etc/ssl/certs/ca-certificates.crt", | |
| "/etc/ssl/certs", | |
| "/etc/pki/tls/certs/ca-bundle.crt", | |
| }; | |
| /* Reads certificates from the list of known locations. Stops when any | |
| * location contains any certificates, to prevent spending unnecessary time | |
| * adding redundant certificates, e.g. when both a certificate bundle and | |
| * individual certificates exist in the same directory. | |
| */ | |
| static PWINECRYPT_CERTSTORE CRYPT_RootOpenStoreFromKnownLocations(void) | |
| { | |
| HCERTSTORE root = NULL; | |
| HCERTSTORE from = CertOpenStore(CERT_STORE_PROV_MEMORY, | |
| X509_ASN_ENCODING, 0, CERT_STORE_CREATE_NEW_FLAG, NULL); | |
| HCERTSTORE to = CertOpenStore(CERT_STORE_PROV_MEMORY, | |
| X509_ASN_ENCODING, 0, CERT_STORE_CREATE_NEW_FLAG, NULL); | |
| if (from && to) | |
| { | |
| CERT_STORE_PROV_INFO provInfo = { | |
| sizeof(CERT_STORE_PROV_INFO), | |
| sizeof(rootProvFuncs) / sizeof(rootProvFuncs[0]), | |
| rootProvFuncs, | |
| NULL, | |
| 0, | |
| NULL | |
| }; | |
| DWORD i; | |
| BOOL ret = FALSE; | |
| for (i = 0; !ret && | |
| i < sizeof(CRYPT_knownLocations) / sizeof(CRYPT_knownLocations[0]); | |
| i++) | |
| ret = import_certs_from_path(CRYPT_knownLocations[i], from, TRUE); | |
| check_and_store_certs(from, to); | |
| root = CRYPT_ProvCreateStore(0, to, &provInfo); | |
| } | |
| CertCloseStore(from, 0); | |
| TRACE("returning %p\n", root); | |
| return root; | |
| } | |
| static PWINECRYPT_CERTSTORE CRYPT_rootStore; | |
| PWINECRYPT_CERTSTORE CRYPT_RootOpenStore(HCRYPTPROV hCryptProv, DWORD dwFlags) | |
| { | |
| TRACE("(%ld, %08x)\n", hCryptProv, dwFlags); | |
| if (dwFlags & CERT_STORE_DELETE_FLAG) | |
| { | |
| WARN("root store can't be deleted\n"); | |
| SetLastError(ERROR_ACCESS_DENIED); | |
| return NULL; | |
| } | |
| switch (dwFlags & CERT_SYSTEM_STORE_LOCATION_MASK) | |
| { | |
| case CERT_SYSTEM_STORE_LOCAL_MACHINE: | |
| case CERT_SYSTEM_STORE_CURRENT_USER: | |
| break; | |
| default: | |
| TRACE("location %08x unsupported\n", | |
| dwFlags & CERT_SYSTEM_STORE_LOCATION_MASK); | |
| SetLastError(E_INVALIDARG); | |
| return NULL; | |
| } | |
| if (!CRYPT_rootStore) | |
| { | |
| HCERTSTORE root = CRYPT_RootOpenStoreFromKnownLocations(); | |
| InterlockedCompareExchangePointer((PVOID *)&CRYPT_rootStore, root, | |
| NULL); | |
| if (CRYPT_rootStore != root) | |
| CertCloseStore(root, 0); | |
| } | |
| CertDuplicateStore(CRYPT_rootStore); | |
| return CRYPT_rootStore; | |
| } |