Support the Windows system certificate store.
authorTimothy B. Terriberry <tterribe@xiph.org>
Sun, 10 Feb 2013 04:09:41 +0000 (20:09 -0800)
committerRalph Giles <giles@thaumas.net>
Thu, 28 Feb 2013 02:12:53 +0000 (18:12 -0800)
OpenSSL on Windows does not pull certificates from any well-known
 location (in fact most binaries continue to use the default Unix
 path, which usually doesn't even exist).
We could ship our own set of certificates (e.g., cloned from the
 Mozilla root list), but I don't want to be responsible for
 releasing libopusfile updates when things like DigiNotar
 fiasco [1] happen.
That approach also means that we would need to load, parse, and
 keep a copy of every certificate in the system for every SSL
 session.

OpenSSL has had patches sitting in their bugtracker which load
 certificates from the Crypto API's system certificate store.
However, those patches have been sitting around for several years,
 so movement on that front in the near future seems unlikely.
We don't care about using OpenSSL's builtin CAPI engine, though, so
 we can do the same thing with less than 200 lines of code.
This puts the maintenance burden on Windows Update, which will be
 far more timely and effective than getting people to upgrade
 libopusfile, and gets us on-demand loading of just the
 certificates we need.

[1] <https://bugzilla.mozilla.org/show_bug.cgi?id=682927>

Makefile.am
src/http.c
src/wincerts.c [new file with mode: 0644]

index 5d0a169..1a3cb73 100644 (file)
@@ -17,7 +17,8 @@ libopusfile_la_LDFLAGS = -no-undefined \
  -version-info @OP_LT_CURRENT@:@OP_LT_REVISION@:@OP_LT_AGE@
 if OP_ENABLE_HTTP
 if OP_WIN32
-libopusfile_la_LIBADD += -lws2_32
+libopusfile_la_SOURCES += src/wincerts.c
+libopusfile_la_LIBADD += -lws2_32 -lcrypt32
 endif
 endif
 
index 16b50a2..a92ad1e 100644 (file)
@@ -208,6 +208,7 @@ static const char *op_parse_file_url(const char *_src){
 # if defined(_WIN32)
 #  include <winsock2.h>
 #  include <ws2tcpip.h>
+#  include <openssl/ssl.h>
 #  include "winerrno.h"
 
 typedef SOCKET op_sock;
@@ -314,6 +315,11 @@ static int op_poll_win32(struct pollfd *_fds,nfds_t _nfds,int _timeout){
 typedef ptrdiff_t ssize_t;
 #  endif
 
+/*Load certificates from the built-in certificate store.*/
+int SSL_CTX_set_default_verify_paths_win32(SSL_CTX *_ssl_ctx);
+#  define SSL_CTX_set_default_verify_paths \
+ SSL_CTX_set_default_verify_paths_win32
+
 # else
 /*Normal Berkeley sockets.*/
 #  include <sys/ioctl.h>
@@ -326,6 +332,7 @@ typedef ptrdiff_t ssize_t;
 #  include <netdb.h>
 #  include <poll.h>
 #  include <unistd.h>
+#  include <openssl/ssl.h>
 
 typedef int op_sock;
 
@@ -336,7 +343,6 @@ typedef int op_sock;
 
 # endif
 # include <sys/timeb.h>
-# include <openssl/ssl.h>
 # include <openssl/x509v3.h>
 
 /*The maximum number of simultaneous connections.
diff --git a/src/wincerts.c b/src/wincerts.c
new file mode 100644 (file)
index 0000000..5d0c322
--- /dev/null
@@ -0,0 +1,167 @@
+/********************************************************************
+ *                                                                  *
+ * THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. *
+ * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS     *
+ * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
+ * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING.       *
+ *                                                                  *
+ * THE libopusfile SOURCE CODE IS (C) COPYRIGHT 2013                *
+ * by the Xiph.Org Foundation and contributors http://www.xiph.org/ *
+ *                                                                  *
+ ********************************************************************/
+
+/*This should really be part of OpenSSL, but there's been a patch [1] sitting
+   in their bugtracker for over two years that implements this, without any
+   action, so I'm giving up and re-implementing it locally.
+
+  [1] <http://rt.openssl.org/Ticket/Display.html?id=2158>*/
+
+#include "internal.h"
+#if defined(OP_ENABLE_HTTP)&&defined(_WIN32)
+/*You must include windows.h before wincrypt.h and x509.h.*/
+# define WIN32_LEAN_AND_MEAN
+# define WIN32_EXTRA_LEAN
+# include <windows.h>
+/*You must include wincrypt.h before x509.h, too, or X509_NAME doesn't get
+   defined properly.*/
+# include <wincrypt.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/x509.h>
+
+static int op_capi_new(X509_LOOKUP *_lu){
+  HCERTSTORE h_store;
+  h_store=CertOpenStore(CERT_STORE_PROV_SYSTEM_A,0,0,
+   CERT_STORE_OPEN_EXISTING_FLAG|CERT_STORE_READONLY_FLAG|
+   CERT_SYSTEM_STORE_CURRENT_USER|CERT_STORE_SHARE_CONTEXT_FLAG,"ROOT");
+  if(h_store!=NULL){
+    _lu->method_data=(char *)h_store;
+    return 1;
+  }
+  return 0;
+}
+
+static void op_capi_free(X509_LOOKUP *_lu){
+  HCERTSTORE h_store;
+  h_store=(HCERTSTORE)_lu->method_data;
+# if defined(OP_ENABLE_ASSERTIONS)
+  OP_ALWAYS_TRUE(CertCloseStore(h_store,CERT_CLOSE_STORE_CHECK_FLAG));
+# else
+  CertCloseStore(h_store,0);
+# endif
+}
+
+static int op_capi_retrieve_by_subject(X509_LOOKUP *_lu,int _type,
+ X509_NAME *_name,X509_OBJECT *_ret){
+  X509_OBJECT *obj;
+  CRYPTO_w_lock(CRYPTO_LOCK_X509_STORE);
+  obj=X509_OBJECT_retrieve_by_subject(_lu->store_ctx->objs,_type,_name);
+  CRYPTO_w_unlock(CRYPTO_LOCK_X509_STORE);
+  if(obj!=NULL){
+    _ret->type=obj->type;
+    memcpy(&_ret->data,&obj->data,sizeof(_ret->data));
+    return 1;
+  }
+  return 0;
+}
+
+static int op_capi_get_by_subject(X509_LOOKUP *_lu,int _type,X509_NAME *_name,
+ X509_OBJECT *_ret){
+  HCERTSTORE h_store;
+  if(_name==NULL)return 0;
+  if(_name->bytes==NULL||_name->bytes->length<=0||_name->modified){
+    if(i2d_X509_NAME(_name,NULL)<0)return 0;
+    OP_ASSERT(_name->bytes->length>0);
+  }
+  h_store=(HCERTSTORE)_lu->method_data;
+  switch(_type){
+    case X509_LU_X509:{
+      CERT_NAME_BLOB  find_para;
+      PCCERT_CONTEXT  cert;
+      X509           *x;
+      int             ret;
+      /*Although X509_NAME contains a canon_enc field, that "canonical" [1]
+         encoding was just made up by OpenSSL.
+        It doesn't correspond to any actual standard, and since it drops the
+         initial sequence header, won't be recognized by the Crypto API.
+        The assumption here is that CertFindCertificateInStore() will allow any
+         appropriate variations in the encoding when it does its comparison.
+        This is, however, emphatically not true under Wine, which just compares
+         the encodings with memcmp().
+        Most of the time things work anyway, though, and there isn't really
+         anything we can do to make the situation better.
+
+        [1] A "canonical form" is defined as the one where, if you locked 10
+         mathematicians in a room and asked them to come up with a
+         representation for something, it's the answer that 9 of them would
+         give you back.
+        I don't think OpenSSL's encoding qualifies.*/
+      find_para.cbData=_name->bytes->length;
+      find_para.pbData=(unsigned char *)_name->bytes->data;
+      cert=CertFindCertificateInStore(h_store,X509_ASN_ENCODING,0,
+       CERT_FIND_SUBJECT_NAME,&find_para,NULL);
+      if(cert==NULL)return 0;
+      x=d2i_X509(NULL,(const unsigned char **)&cert->pbCertEncoded,
+       cert->cbCertEncoded);
+      CertFreeCertificateContext(cert);
+      if(x==NULL)return 0;
+      ret=X509_STORE_add_cert(_lu->store_ctx,x);
+      X509_free(x);
+      if(ret)return op_capi_retrieve_by_subject(_lu,_type,_name,_ret);
+    }break;
+    case X509_LU_CRL:{
+      CERT_INFO      cert_info;
+      CERT_CONTEXT   find_para;
+      PCCRL_CONTEXT  crl;
+      X509_CRL      *x;
+      int            ret;
+      ret=op_capi_retrieve_by_subject(_lu,_type,_name,_ret);
+      if(ret>0)return ret;
+      memset(&cert_info,0,sizeof(cert_info));
+      cert_info.Issuer.cbData=_name->bytes->length;
+      cert_info.Issuer.pbData=(unsigned char *)_name->bytes->data;
+      memset(&find_para,0,sizeof(find_para));
+      find_para.pCertInfo=&cert_info;
+      crl=CertFindCRLInStore(h_store,0,0,CRL_FIND_ISSUED_BY,&find_para,NULL);
+      if(crl==NULL)return 0;
+      x=d2i_X509_CRL(NULL,(const unsigned char **)&crl->pbCrlEncoded,
+       crl->cbCrlEncoded);
+      CertFreeCRLContext(crl);
+      if(x==NULL)return 0;
+      ret=X509_STORE_add_crl(_lu->store_ctx,x);
+      X509_CRL_free(x);
+      if(ret)return op_capi_retrieve_by_subject(_lu,_type,_name,_ret);
+    }break;
+  }
+  return 0;
+}
+
+/*This is not const because OpenSSL doesn't allow it, even though it won't
+   write to it.*/
+static X509_LOOKUP_METHOD X509_LOOKUP_CAPI={
+  "Load Crypto API store into cache",
+  op_capi_new,
+  op_capi_free,
+  NULL,
+  NULL,
+  NULL,
+  op_capi_get_by_subject,
+  NULL,
+  NULL,
+  NULL
+};
+
+int SSL_CTX_set_default_verify_paths_win32(SSL_CTX *_ssl_ctx){
+  X509_STORE  *store;
+  X509_LOOKUP *lu;
+  /*We intentionally do not add the normal default paths, as they are usually
+     wrong, and are just asking to be used as an exploit vector.*/
+  store=SSL_CTX_get_cert_store(_ssl_ctx);
+  OP_ASSERT(store!=NULL);
+  lu=X509_STORE_add_lookup(store,&X509_LOOKUP_CAPI);
+  if(lu==NULL)return 0;
+  ERR_clear_error();
+  return 1;
+}
+
+#endif