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