Since there wasn't a clear consensus about how to get CA certificates into the registry, I decided to do what Mono does: punt. So I've written a tool that can load certificates from a file or from a URL and stick them in the registry.
By default it assumes you want to download them from Mozilla's CVS front-end, and does so. A patch that adds it is attached. Comments? Thanks, --Juan
From e91714bca7dc58e3159a91ae6ac91341751b9eed Mon Sep 17 00:00:00 2001 From: Juan Lang <[EMAIL PROTECTED]> Date: Tue, 14 Aug 2007 10:04:27 -0700 Subject: [PATCH] Add winerootcerts, a program to install root certificates into registry --- programs/winerootcerts/Makefile.in | 16 + programs/winerootcerts/winerootcerts.c | 607 ++++++++++++++++++++++++++++++++ 2 files changed, 623 insertions(+), 0 deletions(-) diff --git a/programs/winerootcerts/Makefile.in b/programs/winerootcerts/Makefile.in new file mode 100644 index 0000000..261cd01 --- /dev/null +++ b/programs/winerootcerts/Makefile.in @@ -0,0 +1,16 @@ +TOPSRCDIR = @top_srcdir@ +TOPOBJDIR = ../.. +SRCDIR = @srcdir@ +VPATH = @srcdir@ +MODULE = winerootcerts.exe +APPMODE = -mconsole +IMPORTS = advapi32 kernel32 crypt32 +DELAYIMPORTS = wininet + +C_SRCS = \ + winerootcerts.c + [EMAIL PROTECTED]@ + + [EMAIL PROTECTED]@ # everything below this line is overwritten by make depend diff --git a/programs/winerootcerts/winerootcerts.c b/programs/winerootcerts/winerootcerts.c new file mode 100644 index 0000000..9951a30 --- /dev/null +++ b/programs/winerootcerts/winerootcerts.c @@ -0,0 +1,607 @@ +/* + * an application for importing root CA certificates into Wine's registry + * + * 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 <stdarg.h> +#include <windef.h> +#include <winbase.h> +#include <wincrypt.h> +#include <wininet.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(winerootcerts); + +#define INITIAL_CERT_BUFFER 1024 + +static const char DefaultURL[] = "http://lxr.mozilla.org/seamonkey/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1"; +static const char DefaultStore[] = "Root"; + +typedef enum _CertFileFormat { + FileFormatBase64, + FileFormatDER, + FileFormatMozilla, +} CertFileFormat; + +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_byte_to_buffer(struct DynamicBuffer *buffer, BYTE byte) +{ + BOOL ret; + + if (buffer->used + 1 > buffer->allocated) + { + if (!buffer->allocated) + { + buffer->data = HeapAlloc(GetProcessHeap(), 0, INITIAL_CERT_BUFFER); + if (buffer->data) + { + buffer->data[0] = 0; + buffer->allocated = INITIAL_CERT_BUFFER; + } + } + else + { + buffer->data = HeapReAlloc(GetProcessHeap(), 0, buffer->data, + buffer->allocated * 2); + if (buffer->data) + buffer->allocated *= 2; + } + } + if (buffer->data) + { + buffer->data[buffer->used++] = byte; + ret = TRUE; + } + else + ret = FALSE; + return ret; +} + +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 = HeapAlloc(GetProcessHeap(), 0, 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 = HeapReAlloc(GetProcessHeap(), 0, 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; +} + +static void import_mozilla_from_fp(FILE *fp, HCERTSTORE store) +{ + char line[1024]; + BOOL in_cert = FALSE, in_value = FALSE, error = FALSE; + struct DynamicBuffer saved_cert = { 0, 0, NULL }; + int num_certs = 0; + + while (fgets(line, sizeof(line), fp)) + { + char *tok; + + if (strlen(line) == 0) + { + WINE_TRACE("empty line, resetting\n"); + error = in_cert = in_value = FALSE; + } + else if (strlen(line) >= 2 && line[strlen(line) - 2] == '\r' && + line[strlen(line) - 1] == '\n') + line[strlen(line) - 2] = '\0'; + else if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + + tok = strtok(line, " \t"); + if (!tok || !strcmp(tok, "#")) + continue; + else if (in_value && !strcmp(tok, "END")) + { + WINE_TRACE("end of value, adding cert\n"); + in_cert = in_value = FALSE; + if (!error) + { + if (CertAddEncodedCertificateToStore(store, X509_ASN_ENCODING, + saved_cert.data, saved_cert.used, CERT_STORE_ADD_NEW, NULL)) + num_certs++; + else + fprintf(stderr, + "CertAddEncodedCertificateToStore failed: %08x\n", + GetLastError()); + } + } + else if (!strcmp(tok, "CKA_CLASS")) + { + tok += strlen(tok) + 1; + tok = strtok(tok, " \t"); + if (!strcmp(tok, "CK_OBJECT_CLASS")) + { + tok += strlen(tok) + 1; + tok = strtok(tok, " \t"); + if (!strcmp(tok, "CKO_CERTIFICATE")) + { + WINE_TRACE("begin new certificate\n"); + in_cert = TRUE; + } + } + } + else if (in_cert && !strcmp(tok, "CKA_VALUE")) + { + tok += strlen(tok) + 1; + tok = strtok(tok, " \t"); + if (!strcmp(tok, "MULTILINE_OCTAL")) + { + WINE_TRACE("begin cert value\n"); + in_value = TRUE; + reset_buffer(&saved_cert); + } + else + WINE_WARN("unknown format %s\n", wine_dbgstr_a(tok)); + } + else if (in_cert && in_value) + { + char *ptr = line; + unsigned int byte; + + while (ptr && sscanf(ptr, "\\%o", &byte) == 1) + { + if (byte > 0xff) + error = TRUE; + else + add_byte_to_buffer(&saved_cert, byte); + ptr = strchr(ptr + 1, '\\'); + } + if (ptr && *ptr) + WINE_TRACE("bad char: %c\n", *ptr); + } + } + printf("Read %d certs\n", num_certs); +} + +static void import_base64_from_fp(FILE *fp, HCERTSTORE store) +{ + char line[1024]; + BOOL in_cert = FALSE; + struct DynamicBuffer saved_cert = { 0, 0, NULL }; + int num_certs = 0; + + WINE_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))) + { + WINE_TRACE("begin new certificate\n"); + in_cert = TRUE; + reset_buffer(&saved_cert); + } + else if (!strncmp(line, trailer, strlen(trailer))) + { + DWORD size; + + WINE_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 = HeapAlloc(GetProcessHeap(), 0, 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++; + else + fprintf(stderr, "Cert decoding failed: %08x\n", + GetLastError()); + } + } + else + fprintf(stderr, "base64 decoding failed: %08x\n", + GetLastError()); + } + else if (in_cert) + add_line_to_buffer(&saved_cert, line); + } + printf("Read %d certs\n", num_certs); +} + +static void import_der_from_fp(FILE *fp, HCERTSTORE store) +{ + long size; + BYTE *buf; + + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + buf = HeapAlloc(GetProcessHeap(), 0, size); + if (buf) + { + if (fread(buf, 1, size, fp) == size) + { + if (!CertAddEncodedCertificateToStore(store, X509_ASN_ENCODING, + buf, size, CERT_STORE_ADD_NEW, NULL)) + fprintf(stderr, "Cert decoding failed: %08x\n", GetLastError()); + } + else + perror("fread"); + HeapFree(GetProcessHeap(), 0, buf); + } +} + +static FILE *download_url(const char *url) +{ + static BYTE buf[4096]; + HINTERNET internet = NULL, opened_url = NULL; + FILE *fp = NULL; + DWORD bytes_read; + BOOL ret; + + printf("Downloading from %s\n", url); + + internet = InternetOpenA("Wine Root Cert tool", + INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); + if (!internet) + goto error; + opened_url = InternetOpenUrlA(internet, url, NULL, 0, 0, 0); + if (!opened_url) + goto error; + fp = tmpfile(); + if (!fp) + goto error; + + do { + ret = InternetReadFile(opened_url, buf, sizeof(buf), &bytes_read); + if (ret && bytes_read) + { + size_t bytes_written = fwrite(buf, 1, bytes_read, fp); + + if (bytes_written != bytes_read) + ret = FALSE; + } + } while (ret && bytes_read); + if (!ret) + { + fclose(fp); + fp = NULL; + } + else + rewind(fp); + +error: + InternetCloseHandle(opened_url); + InternetCloseHandle(internet); + return fp; +} + +static void print_cert(PCCERT_CONTEXT cert) +{ + DWORD len = CertNameToStrA(X509_ASN_ENCODING, &cert->pCertInfo->Subject, + CERT_SIMPLE_NAME_STR, NULL, 0); + + if (len) + { + char *name = HeapAlloc(GetProcessHeap(), 0, len); + + if (name) + { + CertNameToStrA(X509_ASN_ENCODING, &cert->pCertInfo->Subject, + CERT_SIMPLE_NAME_STR, name, len); + printf("%s", name); + HeapFree(GetProcessHeap(), 0, name); + } + } +} + +static void reject_cert(PCCERT_CONTEXT cert, PCCERT_CONTEXT issuer, + LPCSTR reason) +{ + printf("Rejecting "); + print_cert(cert); + if (issuer) + { + printf(",\nissued by "); + print_cert(issuer); + } + printf(":\n%s\n", reason); +} + +static BOOL can_be_ca(PCCERT_CONTEXT cert) +{ + BOOL ret = FALSE; + PCERT_EXTENSION ext = CertFindExtension(szOID_BASIC_CONSTRAINTS, + cert->pCertInfo->cExtension, cert->pCertInfo->rgExtension); + + if (ext) + { + CERT_BASIC_CONSTRAINTS_INFO *info; + DWORD size = 0; + + ret = CryptDecodeObjectEx(X509_ASN_ENCODING, szOID_BASIC_CONSTRAINTS, + ext->Value.pbData, ext->Value.cbData, CRYPT_DECODE_ALLOC_FLAG, + NULL, (LPBYTE)&info, &size); + if (ret) + { + if (info->SubjectType.cbData == 1) + ret = info->SubjectType.pbData[0] & CERT_CA_SUBJECT_FLAG; + LocalFree(info); + } + } + else + { + ext = CertFindExtension(szOID_BASIC_CONSTRAINTS2, + cert->pCertInfo->cExtension, cert->pCertInfo->rgExtension); + if (ext) + { + CERT_BASIC_CONSTRAINTS2_INFO *info; + DWORD size = 0; + + ret = CryptDecodeObjectEx(X509_ASN_ENCODING, + szOID_BASIC_CONSTRAINTS2, ext->Value.pbData, ext->Value.cbData, + CRYPT_DECODE_ALLOC_FLAG, NULL, (LPBYTE)&info, &size); + if (ret) + { + ret = info->fCA; + LocalFree(info); + } + } + else + ret = TRUE; + } + return ret; +} + +static void check_and_store_certs(HCERTSTORE store, HCERTSTORE root, + LPCSTR store_name) +{ + PCCERT_CONTEXT cert = NULL; + HCERTSTORE roots; + DWORD root_count = 0; + + WINE_TRACE("%s\n", wine_dbgstr_a(store_name)); + + roots = CertOpenStore(CERT_STORE_PROV_SYSTEM_A, 0, 0, + CERT_SYSTEM_STORE_LOCAL_MACHINE, store_name); + do { + cert = CertEnumCertificatesInStore(store, cert); + if (cert) + { + PCCERT_CONTEXT issuer = NULL, prev_issuer = NULL; + DWORD flags; + + /* Find a valid issuer for this cert. First check our existing + * roots: + */ + do { + flags = + CERT_STORE_SIGNATURE_FLAG | CERT_STORE_TIME_VALIDITY_FLAG; + issuer = CertGetIssuerCertificateFromStore(root, cert, issuer, + &flags); + if (issuer) + prev_issuer = CertDuplicateCertificateContext(issuer); + } while (issuer && flags); + /* If that doesn't work, try the store we're building up: */ + if (!prev_issuer) + { + do { + flags = + CERT_STORE_SIGNATURE_FLAG | CERT_STORE_TIME_VALIDITY_FLAG; + issuer = CertGetIssuerCertificateFromStore(store, cert, + issuer, &flags); + if (issuer) + prev_issuer = CertDuplicateCertificateContext(issuer); + } while (issuer && flags); + } + + if (!prev_issuer) + { + if (CertCompareCertificateName(cert->dwCertEncodingType, + &cert->pCertInfo->Subject, &cert->pCertInfo->Issuer)) + { + /* Self-signed cert, check its signature */ + if (!CryptVerifyCertificateSignatureEx(0, X509_ASN_ENCODING, + CRYPT_VERIFY_CERT_SIGN_SUBJECT_CERT, (void *)cert, + CRYPT_VERIFY_CERT_SIGN_ISSUER_CERT, (void *)cert, 0, NULL)) + reject_cert(cert, NULL, "Bad self signature"); + else if (!can_be_ca(cert)) + reject_cert(cert, NULL, "Can't act as CA"); + else + { + CertAddCertificateContextToStore(roots, cert, + CERT_STORE_ADD_NEW, NULL); + root_count++; + } + } + else + reject_cert(cert, NULL, "No issuer"); + } + else + { + if (flags & CERT_STORE_SIGNATURE_FLAG) + reject_cert(cert, prev_issuer, "Bad signature"); + else if (flags & CERT_STORE_TIME_VALIDITY_FLAG) + reject_cert(cert, prev_issuer, "Expired"); + else if (!can_be_ca(cert)) + reject_cert(cert, NULL, "Can't act as CA"); + else + { + CertAddCertificateContextToStore(roots, cert, + CERT_STORE_ADD_NEW, NULL); + root_count++; + } + CertFreeCertificateContext(prev_issuer); + } + } + } while (cert); + CertCloseStore(roots, 0); + printf("Added %d root certificates\n", root_count); +} + +static void print_usage(void) +{ + printf("Usage: winerootcerts -h | [options]\n"); + printf("\n"); + printf("Options:\n"); + printf(" -format mozilla|base64|der\n"); + printf(" specifies the file format.\n"); + printf(" Default: mozilla\n"); + printf(" -file certfile\n"); + printf(" specifies a local file to import.\n"); + printf(" -url url\n"); + printf(" specifies a URL to download certificates from.\n"); + printf(" Default: Mozilla's web CVS front end\n"); + printf(" -store store\n"); + printf(" specifies the certificate store to save the certificates in.\n"); + printf(" Default: root\n"); +} + +int main(int argc, char *argv[]) +{ + const char *url = NULL; + const char *file = NULL; + const char *store_name = NULL; + FILE *fp = NULL; + CertFileFormat format = FileFormatMozilla; + int i; + + for (i = 1; i < argc; i++) + { + if (!lstrcmpi(argv[i], "-?") || !lstrcmpi(argv[i], "-h") || + !lstrcmpi(argv[i], "-help")) + { + print_usage(); + exit(0); + } + else if (!lstrcmpi(argv[i], "-file")) + { + file = argv[i + 1]; + i++; + } + else if (!lstrcmpi(argv[i], "-url")) + { + url = argv[i + 1]; + i++; + } + else if (!lstrcmpi(argv[i], "-format")) + { + if (!lstrcmpi(argv[i + 1], "mozilla")) + format = FileFormatMozilla; + else if (!lstrcmpi(argv[i + 1], "base64")) + format = FileFormatBase64; + else if (!lstrcmpi(argv[i + 1], "der")) + format = FileFormatDER; + else + { + fprintf(stderr, "Unknown file format: %s\n", argv[i + 1]); + print_usage(); + exit(1); + } + i++; + } + else if (!lstrcmpi(argv[i], "-store")) + { + store_name = argv[i + 1]; + i++; + } + } + + if (!url && !file) + url = DefaultURL; + if (url) + fp = download_url(url); + else + fp = fopen(file, "r"); + if (!store_name) + store_name = DefaultStore; + if (fp) + { + HCERTSTORE store = CertOpenStore(CERT_STORE_PROV_MEMORY, + X509_ASN_ENCODING, 0, CERT_STORE_CREATE_NEW_FLAG, NULL); + HCERTSTORE root = CertOpenSystemStoreA(0, "Root"); + + if (store) + { + switch (format) + { + case FileFormatMozilla: + import_mozilla_from_fp(fp, store); + break; + case FileFormatBase64: + import_base64_from_fp(fp, store); + break; + case FileFormatDER: + import_der_from_fp(fp, store); + } + check_and_store_certs(store, root, store_name); + CertCloseStore(store, 0); + CertCloseStore(root, 0); + } + fclose(fp); + } + else + perror(file); + return 0; +} -- 1.4.1