wintrust: Implement SoftpubCleanup.
[wine] / dlls / crypt32 / rootstore.c
1 /*
2  * Copyright 2007 Juan Lang
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 #include "config.h"
19 #include <stdarg.h>
20 #include <stdio.h>
21 #ifdef HAVE_SYS_TYPES_H
22 #include <sys/types.h>
23 #endif
24 #ifdef HAVE_SYS_STAT_H
25 #include <sys/stat.h>
26 #endif
27 #include <dirent.h>
28 #include <fcntl.h>
29 #ifdef HAVE_UNISTD_H
30 #include <unistd.h>
31 #endif
32 #include <errno.h>
33 #include <limits.h>
34 #include "ntstatus.h"
35 #define WIN32_NO_STATUS
36 #include "windef.h"
37 #include "winbase.h"
38 #include "winreg.h"
39 #include "wincrypt.h"
40 #include "winternl.h"
41 #include "wine/debug.h"
42 #include "crypt32_private.h"
43
44 WINE_DEFAULT_DEBUG_CHANNEL(crypt);
45
46 #define INITIAL_CERT_BUFFER 1024
47
48 struct DynamicBuffer
49 {
50     DWORD allocated;
51     DWORD used;
52     BYTE *data;
53 };
54
55 static inline void reset_buffer(struct DynamicBuffer *buffer)
56 {
57     buffer->used = 0;
58     if (buffer->data) buffer->data[0] = 0;
59 }
60
61 static BOOL add_line_to_buffer(struct DynamicBuffer *buffer, LPCSTR line)
62 {
63     BOOL ret;
64
65     if (buffer->used + strlen(line) + 1 > buffer->allocated)
66     {
67         if (!buffer->allocated)
68         {
69             buffer->data = CryptMemAlloc(INITIAL_CERT_BUFFER);
70             if (buffer->data)
71             {
72                 buffer->data[0] = 0;
73                 buffer->allocated = INITIAL_CERT_BUFFER;
74             }
75         }
76         else
77         {
78             DWORD new_size = max(buffer->allocated * 2,
79              buffer->used + strlen(line) + 1);
80
81             buffer->data = CryptMemRealloc(buffer->data, new_size);
82             if (buffer->data)
83                 buffer->allocated = new_size;
84         }
85     }
86     if (buffer->data)
87     {
88         strcpy((char *)buffer->data + strlen((char *)buffer->data), line);
89         /* Not strlen + 1, otherwise we'd count the NULL for every line's
90          * addition (but we overwrite the previous NULL character.)  Not an
91          * overrun, we allocate strlen + 1 bytes above.
92          */
93         buffer->used += strlen(line);
94         ret = TRUE;
95     }
96     else
97         ret = FALSE;
98     return ret;
99 }
100
101 /* Reads any base64-encoded certificates present in fp and adds them to store.
102  * Returns TRUE if any certifcates were successfully imported.
103  */
104 static BOOL import_base64_certs_from_fp(FILE *fp, HCERTSTORE store)
105 {
106     char line[1024];
107     BOOL in_cert = FALSE;
108     struct DynamicBuffer saved_cert = { 0, 0, NULL };
109     int num_certs = 0;
110
111     TRACE("\n");
112     while (fgets(line, sizeof(line), fp))
113     {
114         static const char header[] = "-----BEGIN CERTIFICATE-----";
115         static const char trailer[] = "-----END CERTIFICATE-----";
116
117         if (!strncmp(line, header, strlen(header)))
118         {
119             TRACE("begin new certificate\n");
120             in_cert = TRUE;
121             reset_buffer(&saved_cert);
122         }
123         else if (!strncmp(line, trailer, strlen(trailer)))
124         {
125             DWORD size;
126
127             TRACE("end of certificate, adding cert\n");
128             in_cert = FALSE;
129             if (CryptStringToBinaryA((char *)saved_cert.data, saved_cert.used,
130              CRYPT_STRING_BASE64, NULL, &size, NULL, NULL))
131             {
132                 LPBYTE buf = CryptMemAlloc(size);
133
134                 if (buf)
135                 {
136                     CryptStringToBinaryA((char *)saved_cert.data,
137                      saved_cert.used, CRYPT_STRING_BASE64, buf, &size, NULL,
138                      NULL);
139                     if (CertAddEncodedCertificateToStore(store,
140                      X509_ASN_ENCODING, buf, size, CERT_STORE_ADD_NEW, NULL))
141                         num_certs++;
142                 }
143             }
144         }
145         else if (in_cert)
146             add_line_to_buffer(&saved_cert, line);
147     }
148     CryptMemFree(saved_cert.data);
149     TRACE("Read %d certs\n", num_certs);
150     return num_certs > 0;
151 }
152
153 static const char *trust_status_to_str(DWORD status)
154 {
155     static char buf[1024];
156     int pos = 0;
157
158     if (status & CERT_TRUST_IS_NOT_TIME_VALID)
159         pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\texpired");
160     if (status & CERT_TRUST_IS_NOT_TIME_NESTED)
161         pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\tbad time nesting");
162     if (status & CERT_TRUST_IS_REVOKED)
163         pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\trevoked");
164     if (status & CERT_TRUST_IS_NOT_SIGNATURE_VALID)
165         pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\tbad signature");
166     if (status & CERT_TRUST_IS_NOT_VALID_FOR_USAGE)
167         pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\tbad usage");
168     if (status & CERT_TRUST_IS_UNTRUSTED_ROOT)
169         pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\tuntrusted root");
170     if (status & CERT_TRUST_REVOCATION_STATUS_UNKNOWN)
171         pos += snprintf(buf + pos, sizeof(buf) - pos,
172          "\n\tunknown revocation status");
173     if (status & CERT_TRUST_IS_CYCLIC)
174         pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\tcyclic chain");
175     if (status & CERT_TRUST_INVALID_EXTENSION)
176         pos += snprintf(buf + pos, sizeof(buf) - pos,
177          "\n\tunsupported critical extension");
178     if (status & CERT_TRUST_INVALID_POLICY_CONSTRAINTS)
179         pos += snprintf(buf + pos, sizeof(buf) - pos, "\n\tbad policy");
180     if (status & CERT_TRUST_INVALID_BASIC_CONSTRAINTS)
181         pos += snprintf(buf + pos, sizeof(buf) - pos,
182          "\n\tbad basic constraints");
183     if (status & CERT_TRUST_INVALID_NAME_CONSTRAINTS)
184         pos += snprintf(buf + pos, sizeof(buf) - pos,
185          "\n\tbad name constraints");
186     if (status & CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT)
187         pos += snprintf(buf + pos, sizeof(buf) - pos,
188          "\n\tunsuported name constraint");
189     if (status & CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT)
190         pos += snprintf(buf + pos, sizeof(buf) - pos,
191          "\n\tundefined name constraint");
192     if (status & CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT)
193         pos += snprintf(buf + pos, sizeof(buf) - pos,
194          "\n\tdisallowed name constraint");
195     if (status & CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT)
196         pos += snprintf(buf + pos, sizeof(buf) - pos,
197          "\n\texcluded name constraint");
198     if (status & CERT_TRUST_IS_OFFLINE_REVOCATION)
199         pos += snprintf(buf + pos, sizeof(buf) - pos,
200          "\n\trevocation server offline");
201     if (status & CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY)
202         pos += snprintf(buf + pos, sizeof(buf) - pos,
203          "\n\tno issuance policy");
204     return buf;
205 }
206
207 static const char *get_cert_common_name(PCCERT_CONTEXT cert)
208 {
209     static char buf[1024];
210     const char *name = NULL;
211     CERT_NAME_INFO *nameInfo;
212     DWORD size;
213     BOOL ret = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_NAME,
214      cert->pCertInfo->Subject.pbData, cert->pCertInfo->Subject.cbData,
215      CRYPT_DECODE_NOCOPY_FLAG | CRYPT_DECODE_ALLOC_FLAG, NULL, &nameInfo,
216      &size);
217
218     if (ret)
219     {
220         PCERT_RDN_ATTR commonName = CertFindRDNAttr(szOID_COMMON_NAME,
221          nameInfo);
222
223         if (commonName)
224         {
225             CertRDNValueToStrA(commonName->dwValueType,
226              &commonName->Value, buf, sizeof(buf));
227             name = buf;
228         }
229         LocalFree(nameInfo);
230     }
231     return name;
232 }
233
234 static void check_and_store_certs(HCERTSTORE from, HCERTSTORE to)
235 {
236     PCCERT_CONTEXT cert = NULL;
237     DWORD root_count = 0;
238
239     TRACE("\n");
240
241     do {
242         cert = CertEnumCertificatesInStore(from, cert);
243         if (cert)
244         {
245             CERT_CHAIN_ENGINE_CONFIG chainEngineConfig =
246              { sizeof(chainEngineConfig), 0 };
247             HCERTCHAINENGINE engine = CRYPT_CreateChainEngine(to,
248              &chainEngineConfig);
249
250             if (engine)
251             {
252                 CERT_CHAIN_PARA chainPara = { sizeof(chainPara), { 0 } };
253                 PCCERT_CHAIN_CONTEXT chain;
254                 BOOL ret = CertGetCertificateChain(engine, cert, NULL, from,
255                  &chainPara, 0, NULL, &chain);
256
257                 if (!ret)
258                     TRACE("rejecting %s: %s\n", get_cert_common_name(cert),
259                      "chain creation failed");
260                 else
261                 {
262                     /* The only allowed error is CERT_TRUST_IS_UNTRUSTED_ROOT */
263                     if (chain->TrustStatus.dwErrorStatus &
264                      ~CERT_TRUST_IS_UNTRUSTED_ROOT)
265                         TRACE("rejecting %s: %s\n", get_cert_common_name(cert),
266                          trust_status_to_str(chain->TrustStatus.dwErrorStatus &
267                          ~CERT_TRUST_IS_UNTRUSTED_ROOT));
268                     else
269                     {
270                         DWORD i, j;
271
272                         for (i = 0; i < chain->cChain; i++)
273                             for (j = 0; j < chain->rgpChain[i]->cElement; j++)
274                                 if (CertAddCertificateContextToStore(to,
275                                  chain->rgpChain[i]->rgpElement[j]->pCertContext,
276                                  CERT_STORE_ADD_NEW, NULL))
277                                     root_count++;
278                     }
279                 }
280                 CertFreeCertificateChainEngine(engine);
281             }
282         }
283     } while (cert);
284     TRACE("Added %d root certificates\n", root_count);
285 }
286
287 /* Reads the file fd, and imports any certificates in it into store.
288  * Returns TRUE if any certificates were successfully imported.
289  */
290 static BOOL import_certs_from_file(int fd, HCERTSTORE store)
291 {
292     BOOL ret = FALSE;
293     FILE *fp;
294
295     TRACE("\n");
296
297     fp = fdopen(fd, "r");
298     if (fp)
299     {
300         ret = import_base64_certs_from_fp(fp, store);
301         fclose(fp);
302     }
303     return ret;
304 }
305
306 static BOOL import_certs_from_path(LPCSTR path, HCERTSTORE store,
307  BOOL allow_dir);
308
309 /* Opens path, which must be a directory, and imports certificates from every
310  * file in the directory into store.
311  * Returns TRUE if any certificates were successfully imported.
312  */
313 static BOOL import_certs_from_dir(LPCSTR path, HCERTSTORE store)
314 {
315     BOOL ret = FALSE;
316     DIR *dir;
317
318     TRACE("(%s, %p)\n", debugstr_a(path), store);
319
320     dir = opendir(path);
321     if (dir)
322     {
323         size_t bufsize = strlen(path) + 1 + PATH_MAX + 1;
324         char *filebuf = CryptMemAlloc(bufsize);
325
326         if (filebuf)
327         {
328             struct dirent *entry;
329             while ((entry = readdir(dir)))
330             {
331                 if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, ".."))
332                 {
333                     snprintf(filebuf, bufsize, "%s/%s", path, entry->d_name);
334                     if (import_certs_from_path(filebuf, store, FALSE) && !ret)
335                         ret = TRUE;
336                 }
337             }
338             closedir(dir);
339             CryptMemFree(filebuf);
340         }
341     }
342     return ret;
343 }
344
345 /* Opens path, which may be a file or a directory, and imports any certificates
346  * it finds into store.
347  * Returns TRUE if any certificates were successfully imported.
348  */
349 static BOOL import_certs_from_path(LPCSTR path, HCERTSTORE store,
350  BOOL allow_dir)
351 {
352     BOOL ret = FALSE;
353     int fd;
354
355     TRACE("(%s, %p, %d)\n", debugstr_a(path), store, allow_dir);
356
357     fd = open(path, O_RDONLY);
358     if (fd != -1)
359     {
360         struct stat st;
361
362         if (fstat(fd, &st) == 0)
363         {
364             if (S_ISREG(st.st_mode))
365                 ret = import_certs_from_file(fd, store);
366             else if (S_ISDIR(st.st_mode))
367             {
368                 if (allow_dir)
369                     ret = import_certs_from_dir(path, store);
370                 else
371                     WARN("%s is a directory and directories are disallowed\n",
372                      debugstr_a(path));
373             }
374             else
375                 ERR("%s: invalid file type\n", path);
376         }
377         close(fd);
378     }
379     return ret;
380 }
381
382 static BOOL WINAPI CRYPT_RootWriteCert(HCERTSTORE hCertStore,
383  PCCERT_CONTEXT cert, DWORD dwFlags)
384 {
385     /* The root store can't have certs added */
386     return FALSE;
387 }
388
389 static BOOL WINAPI CRYPT_RootDeleteCert(HCERTSTORE hCertStore,
390  PCCERT_CONTEXT cert, DWORD dwFlags)
391 {
392     /* The root store can't have certs deleted */
393     return FALSE;
394 }
395
396 static BOOL WINAPI CRYPT_RootWriteCRL(HCERTSTORE hCertStore,
397  PCCRL_CONTEXT crl, DWORD dwFlags)
398 {
399     /* The root store can have CRLs added.  At worst, a malicious application
400      * can DoS itself, as the changes aren't persisted in any way.
401      */
402     return TRUE;
403 }
404
405 static BOOL WINAPI CRYPT_RootDeleteCRL(HCERTSTORE hCertStore,
406  PCCRL_CONTEXT crl, DWORD dwFlags)
407 {
408     /* The root store can't have CRLs deleted */
409     return FALSE;
410 }
411
412 static void *rootProvFuncs[] = {
413     NULL, /* CERT_STORE_PROV_CLOSE_FUNC */
414     NULL, /* CERT_STORE_PROV_READ_CERT_FUNC */
415     CRYPT_RootWriteCert,
416     CRYPT_RootDeleteCert,
417     NULL, /* CERT_STORE_PROV_SET_CERT_PROPERTY_FUNC */
418     NULL, /* CERT_STORE_PROV_READ_CRL_FUNC */
419     CRYPT_RootWriteCRL,
420     CRYPT_RootDeleteCRL,
421     NULL, /* CERT_STORE_PROV_SET_CRL_PROPERTY_FUNC */
422     NULL, /* CERT_STORE_PROV_READ_CTL_FUNC */
423     NULL, /* CERT_STORE_PROV_WRITE_CTL_FUNC */
424     NULL, /* CERT_STORE_PROV_DELETE_CTL_FUNC */
425     NULL, /* CERT_STORE_PROV_SET_CTL_PROPERTY_FUNC */
426     NULL, /* CERT_STORE_PROV_CONTROL_FUNC */
427 };
428
429 static const char * const CRYPT_knownLocations[] = {
430  "/etc/ssl/certs/ca-certificates.crt",
431  "/etc/ssl/certs",
432  "/etc/pki/tls/certs/ca-bundle.crt",
433 };
434
435 /* Reads certificates from the list of known locations.  Stops when any
436  * location contains any certificates, to prevent spending unnecessary time
437  * adding redundant certificates, e.g. when both a certificate bundle and
438  * individual certificates exist in the same directory.
439  */
440 static PWINECRYPT_CERTSTORE CRYPT_RootOpenStoreFromKnownLocations(void)
441 {
442     HCERTSTORE root = NULL;
443     HCERTSTORE from = CertOpenStore(CERT_STORE_PROV_MEMORY,
444      X509_ASN_ENCODING, 0, CERT_STORE_CREATE_NEW_FLAG, NULL);
445     HCERTSTORE to = CertOpenStore(CERT_STORE_PROV_MEMORY,
446      X509_ASN_ENCODING, 0, CERT_STORE_CREATE_NEW_FLAG, NULL);
447
448     if (from && to)
449     {
450         CERT_STORE_PROV_INFO provInfo = {
451          sizeof(CERT_STORE_PROV_INFO),
452          sizeof(rootProvFuncs) / sizeof(rootProvFuncs[0]),
453          rootProvFuncs,
454          NULL,
455          0,
456          NULL
457         };
458         DWORD i;
459         BOOL ret = FALSE;
460
461         for (i = 0; !ret &&
462          i < sizeof(CRYPT_knownLocations) / sizeof(CRYPT_knownLocations[0]);
463          i++)
464             ret = import_certs_from_path(CRYPT_knownLocations[i], from, TRUE);
465         check_and_store_certs(from, to);
466         root = CRYPT_ProvCreateStore(0, to, &provInfo);
467     }
468     CertCloseStore(from, 0);
469     TRACE("returning %p\n", root);
470     return root;
471 }
472
473 static PWINECRYPT_CERTSTORE CRYPT_rootStore;
474
475 PWINECRYPT_CERTSTORE CRYPT_RootOpenStore(HCRYPTPROV hCryptProv, DWORD dwFlags)
476 {
477     TRACE("(%ld, %08x)\n", hCryptProv, dwFlags);
478
479     if (dwFlags & CERT_STORE_DELETE_FLAG)
480     {
481         WARN("root store can't be deleted\n");
482         SetLastError(ERROR_ACCESS_DENIED);
483         return NULL;
484     }
485     switch (dwFlags & CERT_SYSTEM_STORE_LOCATION_MASK)
486     {
487     case CERT_SYSTEM_STORE_LOCAL_MACHINE:
488     case CERT_SYSTEM_STORE_CURRENT_USER:
489         break;
490     default:
491         TRACE("location %08x unsupported\n",
492          dwFlags & CERT_SYSTEM_STORE_LOCATION_MASK);
493         SetLastError(E_INVALIDARG);
494         return NULL;
495     }
496     if (!CRYPT_rootStore)
497     {
498         HCERTSTORE root = CRYPT_RootOpenStoreFromKnownLocations();
499
500         InterlockedCompareExchangePointer((PVOID *)&CRYPT_rootStore, root,
501          NULL);
502         if (CRYPT_rootStore != root)
503             CertCloseStore(root, 0);
504     }
505     CertDuplicateStore(CRYPT_rootStore);
506     return CRYPT_rootStore;
507 }