bug 557113 - sort out crash report certificate issues on Maemo. r=mfinkle,johnath, a=stuart
authorTed Mielczarek <ted.mielczarek@gmail.com>
Fri, 09 Apr 2010 16:52:04 -0400
changeset 34247 1bad18064ab6acaf2460e817303816aae73c7204
parent 34246 67e4791a40a6627624606afb73c54f5b5b2b01a5
child 34252 1a10bd9cd0c1c452eaa0ac8a1ec982adbe1b8630
push id1351
push usertmielczarek@mozilla.com
push dateTue, 25 May 2010 12:49:44 +0000
reviewersmfinkle, johnath, stuart
bugs557113
milestone1.9.2.5pre
bug 557113 - sort out crash report certificate issues on Maemo. r=mfinkle,johnath, a=stuart (transplanted from 9cbe87222f44cf863c3ca3822394252d9690b06d)
toolkit/crashreporter/client/Makefile.in
toolkit/crashreporter/client/certdata2pem.py
toolkit/crashreporter/client/crashreporter_gtk_common.cpp
toolkit/crashreporter/client/crashreporter_gtk_common.h
toolkit/crashreporter/client/crashreporter_maemo_gtk.cpp
toolkit/crashreporter/client/maemo-unit/crashreports.crt
toolkit/crashreporter/client/maemo-unit/opensslverify.sh
toolkit/crashreporter/client/maemo-unit/test_maemo_certs.js
toolkit/crashreporter/google-breakpad/src/common/linux/http_upload.cc
toolkit/crashreporter/google-breakpad/src/common/linux/http_upload.h
--- a/toolkit/crashreporter/client/Makefile.in
+++ b/toolkit/crashreporter/client/Makefile.in
@@ -39,16 +39,18 @@
 
 DEPTH		= ../../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
+MODULE = crashreporter
+
 PROGRAM = crashreporter$(BIN_SUFFIX)
 DIST_PROGRAM = crashreporter$(BIN_SUFFIX)
 
 LOCAL_INCLUDES = -I$(srcdir)/../google-breakpad/src
 
 CPPSRCS = crashreporter.cpp
 
 ifeq ($(OS_ARCH),WINNT)
@@ -73,18 +75,31 @@ LIBS += \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/common/mac/$(LIB_PREFIX)breakpad_mac_common_s.$(LIB_SUFFIX) \
   $(NULL)
 
 LOCAL_INCLUDES += -I$(srcdir) -I$(srcdir)/../google-breakpad/src/common/mac/
 endif
 
 ifeq ($(OS_ARCH),Linux)
 CPPSRCS += crashreporter_gtk_common.cpp crashreporter_unix_common.cpp
+
 ifdef MOZ_PLATFORM_MAEMO
 CPPSRCS += crashreporter_maemo_gtk.cpp
+
+# Maemo's libcurl doesn't ship with a set of CA certificates,
+# so we have to ship our own.
+libs:: $(DIST)/bin/crashreporter.crt
+
+$(DIST)/bin/crashreporter.crt: $(topsrcdir)/security/nss/lib/ckfw/builtins/certdata.txt certdata2pem.py
+	$(PYTHON) $(srcdir)/certdata2pem.py < $< > $@
+
+# The xpcshell test case here verifies that the CA certificate list
+# works with OpenSSL.
+XPCSHELL_TESTS = maemo-unit
+
 else
 CPPSRCS += crashreporter_linux.cpp
 endif
 
 LIBS += \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/common/linux/$(LIB_PREFIX)breakpad_linux_common_s.$(LIB_SUFFIX) \
   $(NULL)
 LOCAL_INCLUDES += -I$(srcdir)
@@ -116,8 +131,13 @@ libs::
 	$(NSINSTALL) $(DIST)/bin/crashreporter $(DIST)/bin/crashreporter.app/Contents/MacOS
 	rm -f $(DIST)/bin/crashreporter
 endif
 
 ifeq (,$(filter-out Linux SunOS,$(OS_ARCH)))
 libs:: $(topsrcdir)/toolkit/themes/winstripe/global/throbber/Throbber-small.gif
 	$(INSTALL) $^ $(DIST)/bin
 endif
+
+ifdef MOZ_PLATFORM_MAEMO
+libs::
+	$(INSTALL) $(DIST)/bin/crashreporter.crt $(DEPTH)/_tests/xpcshell/$(MODULE)/maemo-unit/
+endif
new file mode 100755
--- /dev/null
+++ b/toolkit/crashreporter/client/certdata2pem.py
@@ -0,0 +1,111 @@
+#!/usr/bin/python
+# vim:set et sw=4:
+#
+# Originally from:
+# http://cvs.fedoraproject.org/viewvc/F-13/ca-certificates/certdata2pem.py?revision=1.1&content-type=text%2Fplain&view=co
+#
+# certdata2pem.py - converts certdata.txt into PEM format. 
+#
+# Copyright (C) 2009 Philipp Kern <pkern@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+import base64
+import os.path
+import re
+import sys
+import textwrap
+
+objects = []
+
+# Dirty file parser.
+in_data, in_multiline, in_obj = False, False, False
+field, type, value, obj = None, None, None, dict()
+for line in sys.stdin: 
+    # Ignore the file header.
+    if not in_data:
+        if line.startswith('BEGINDATA'):
+            in_data = True
+        continue
+    # Ignore comment lines.
+    if line.startswith('#'):
+        continue
+    # Empty lines are significant if we are inside an object.
+    if in_obj and len(line.strip()) == 0:
+        objects.append(obj)
+        obj = dict()
+        in_obj = False
+        continue
+    if len(line.strip()) == 0:
+        continue
+    if in_multiline:
+        if not line.startswith('END'):
+            if type == 'MULTILINE_OCTAL':
+                line = line.strip()
+                for i in re.finditer(r'\\([0-3][0-7][0-7])', line):
+                    value += chr(int(i.group(1), 8))
+            else:
+                value += line
+            continue
+        obj[field] = value
+        in_multiline = False
+        continue
+    if line.startswith('CKA_CLASS'):
+        in_obj = True
+    line_parts = line.strip().split(' ', 2)
+    if len(line_parts) > 2:
+        field, type = line_parts[0:2]
+        value = ' '.join(line_parts[2:])
+    elif len(line_parts) == 2:
+        field, type = line_parts
+        value = None
+    else:
+        raise NotImplementedError, 'line_parts < 2 not supported.'
+    if type == 'MULTILINE_OCTAL':
+        in_multiline = True
+        value = ""
+        continue
+    obj[field] = value
+if len(obj.items()) > 0:
+    objects.append(obj)
+
+# Build up trust database.
+trust = dict()
+for obj in objects:
+    if obj['CKA_CLASS'] != 'CKO_NETSCAPE_TRUST':
+        continue
+    # For some reason, OpenSSL on Maemo has a bug where if we include
+    # this certificate, and it winds up as the last certificate in the file,
+    # then OpenSSL is unable to verify the server certificate. For now,
+    # we'll just omit this particular CA cert, since it's not one we need
+    # for crash reporting.
+    # This is likely to be fragile if the NSS certdata.txt changes.
+    # The bug is filed upstream:
+    # https://bugs.maemo.org/show_bug.cgi?id=10069
+    if obj['CKA_LABEL'] == '"ACEDICOM Root"':
+        continue
+    # We only want certs that are trusted for SSL server auth
+    if obj['CKA_TRUST_SERVER_AUTH'] == 'CKT_NETSCAPE_TRUSTED_DELEGATOR':
+        trust[obj['CKA_LABEL']] = True
+
+for obj in objects:
+    if obj['CKA_CLASS'] == 'CKO_CERTIFICATE':
+        if not obj['CKA_LABEL'] in trust or not trust[obj['CKA_LABEL']]:
+            continue
+        sys.stdout.write("-----BEGIN CERTIFICATE-----\n")
+        sys.stdout.write("\n".join(textwrap.wrap(base64.b64encode(obj['CKA_VALUE']), 64)))
+        sys.stdout.write("\n-----END CERTIFICATE-----\n\n")
+
--- a/toolkit/crashreporter/client/crashreporter_gtk_common.cpp
+++ b/toolkit/crashreporter/client/crashreporter_gtk_common.cpp
@@ -72,16 +72,17 @@ GtkWidget* gCloseButton = 0;
 GtkWidget* gRestartButton = 0;
 
 bool gInitialized = false;
 bool gDidTrySend = false;
 string gDumpFile;
 StringTable gQueryParameters;
 string gHttpProxy;
 string gAuth;
+string gCACertificateFile;
 string gSendURL;
 string gURLParameter;
 vector<string> gRestartArgs;
 GThread* gSendThreadID;
 
 // From crashreporter_linux.cpp or crashreporter_maemo_gtk.cpp
 void SaveSettings();
 void SendReport();
@@ -217,16 +218,17 @@ gpointer SendThread(gpointer args)
   string response, error;
 
   bool success = google_breakpad::HTTPUpload::SendRequest
     (gSendURL,
      gQueryParameters,
      gDumpFile,
      "upload_file_minidump",
      gHttpProxy, gAuth,
+     gCACertificateFile,
      &response,
      &error);
   if (success) {
     LogMessage("Crash report submitted successfully");
   }
   else {
     LogMessage("Crash report submission failed: " + error);
   }
--- a/toolkit/crashreporter/client/crashreporter_gtk_common.h
+++ b/toolkit/crashreporter/client/crashreporter_gtk_common.h
@@ -21,16 +21,17 @@ extern std::vector<std::string> gRestart
 extern GThread* gSendThreadID;
 
 extern bool gInitialized;
 extern bool gDidTrySend;
 extern std::string gDumpFile;
 extern StringTable gQueryParameters;
 extern std::string gHttpProxy;
 extern std::string gAuth;
+extern std::string gCACertificateFile;
 extern std::string gSendURL;
 extern std::string gURLParameter;
 
 void LoadProxyinfo();
 gpointer SendThread(gpointer args);
 gboolean WindowDeleted(GtkWidget* window,
                        GdkEvent* event,
                        gpointer userData);
--- a/toolkit/crashreporter/client/crashreporter_maemo_gtk.cpp
+++ b/toolkit/crashreporter/client/crashreporter_maemo_gtk.cpp
@@ -95,16 +95,31 @@ void SaveSettings()
   settings["SubmitReport"] =
     gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gSubmitReportCheck))
     ? "1" : "0";
 
   WriteStringsToFile(gSettingsPath + "/" + kIniFile,
                      "Crash Reporter", settings, true);
 }
 
+/*
+ * Check if a crashreporter.crt file exists next
+ * to the crashreporter binary, and if so set gCACertificateFile
+ * to its path. The CA cert will then be used by libcurl to authenticate
+ * the server's SSL certificate.
+ */
+static void FindCACertificateFile()
+{
+  string path = gArgv[0];
+  path += ".crt";
+  if (UIFileExists(path)) {
+    gCACertificateFile = path;
+  }
+}
+
 void SendReport()
 {
   // disable all our gui controls, show the throbber + change the progress text
   gtk_widget_set_sensitive(gSubmitReportCheck, FALSE);
   if (gIncludeURLCheck)
     gtk_widget_set_sensitive(gIncludeURLCheck, FALSE);
   gtk_widget_set_sensitive(gCloseButton, FALSE);
   if (gRestartButton)
@@ -112,16 +127,18 @@ void SendReport()
   gtk_widget_show_all(gThrobber);
   gtk_label_set_text(GTK_LABEL(gProgressLabel),
                      gStrings[ST_REPORTDURINGSUBMIT].c_str());
 
 #ifdef MOZ_ENABLE_GCONF
   LoadProxyinfo();
 #endif
 
+  FindCACertificateFile();
+
   // and spawn a thread to do the sending
   GError* err;
   gSendThreadID = g_thread_create(SendThread, NULL, TRUE, &err);
 }
 
 void UpdateSubmit()
 {
   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gSubmitReportCheck))) {
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/client/maemo-unit/crashreports.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDPTCCAqagAwIBAgIDDjAwMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkxMjAyMDY0MzE1WhcNMTIwMjAyMTIwMDI1
+WjCBxzEpMCcGA1UEBRMgbG5qYnUvcVJXL2p3UC9EUXFHNEFOTDNDUWdlZHg2d24x
+CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3Vu
+dGFpbiBWaWV3MRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMR4wHAYDVQQL
+ExVNb3ppbGxhIENyYXNoIFJlcG9ydHMxIjAgBgNVBAMTGWNyYXNoLXJlcG9ydHMu
+bW96aWxsYS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMrrjxQWtgh6
+xJkFb6DebjldmLr0IU3SUymHaMcos6ISJ4w8IkkGGJS+59sMpLGR6bMZmioH4dpS
+ZqwQqCYPpMQxi8XjdkeIzxP8Q2+01lYYK/fTVqp2jh3TWwOk1gbiNBzYYYxxkhXO
+R5yjj6pfSHdWBJZZJRajYo/xddzmq5p9AgMBAAGjga4wgaswDgYDVR0PAQH/BAQD
+AgTwMB0GA1UdDgQWBBTd2p7ARtLT3nVeh8a/M5NdAUWyTzA6BgNVHR8EMzAxMC+g
+LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDAf
+BgNVHSMEGDAWgBRI5mj5K9KylddH2CMgEE8zmJCf1DAdBgNVHSUEFjAUBggrBgEF
+BQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADgYEAoV0j092DMZXaVlNXT9vr
+Fmt6lrVQcTgvYJlutFa9vnnXFqYt4i5VVrRPo+BigZN8p1KGdD/dIgYQM+JubrnA
+qEWAyoBHropuEpiR8Fa0qcZHPVQOCWBfK1PB5W6CvUiDNOYl89mBqzuwSMKzojsT
+yiI0JD1SVgnoTumvkZd5w+I=
+-----END CERTIFICATE-----
new file mode 100755
--- /dev/null
+++ b/toolkit/crashreporter/client/maemo-unit/opensslverify.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+# Run as:
+# opensslverify.sh <ca certificate file> <server certificate file>
+#
+# `openssl verify` doesn't return an error code if the cert fails
+# to verify, so we have to grep the output, and we can't do that via
+# nsIProcess, so we use a shell script.
+
+if openssl verify -CAfile $1 -purpose sslserver $2 2>&1 | grep -q "^error"; then
+  exit 1;
+else
+  exit 0;
+fi
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/client/maemo-unit/test_maemo_certs.js
@@ -0,0 +1,31 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+/*
+ * This test validates that OpenSSL running on this system can
+ * validate the server certificate in crashreports.crt against the
+ * list of CA certificates in crashreporter.crt. On Maemo systems,
+ * OpenSSL has a bug where the ordering of certain certificates
+ * in the CA certificate file can cause validation to fail.
+ * This test is intended to catch that condition in case the NSS
+ * certificate list changes in a way that would trigger the bug.
+ */
+function run_test() {
+  let file = Components.classes["@mozilla.org/file/local;1"]
+                       .createInstance(Components.interfaces.nsILocalFile);
+  file.initWithPath("/bin/sh");
+
+  let process = Components.classes["@mozilla.org/process/util;1"]
+                        .createInstance(Components.interfaces.nsIProcess);
+  process.init(file);
+
+  let shscript = do_get_file("opensslverify.sh");
+  let cacerts = do_get_file("crashreporter.crt");
+  let servercert = do_get_file("crashreports.crt");
+  let args = [shscript.path, cacerts.path, servercert.path];
+  process.run(true, args, args.length);
+
+  dump('If the following test fails, the logic in toolkit/crashreporter/client/certdata2pem.py needs to be fixed, otherwise crash report submission on Maemo will fail.\n');
+  do_check_eq(process.exitValue, 0);
+}
--- a/toolkit/crashreporter/google-breakpad/src/common/linux/http_upload.cc
+++ b/toolkit/crashreporter/google-breakpad/src/common/linux/http_upload.cc
@@ -57,16 +57,17 @@ static const char kUserAgent[] = "Breakp
 
 // static
 bool HTTPUpload::SendRequest(const string &url,
                              const map<string, string> &parameters,
                              const string &upload_file,
                              const string &file_part_name,
                              const string &proxy,
                              const string &proxy_user_pwd,
+                             const string &ca_certificate_file,
                              string *response_body,
                              string *error_description) {
   if (!CheckParameters(parameters))
     return false;
 
   void *curl_lib = dlopen("libcurl.so", RTLD_NOW);
   if (!curl_lib) {
     if (error_description != NULL)
@@ -102,16 +103,19 @@ bool HTTPUpload::SendRequest(const strin
   (*curl_easy_setopt)(curl, CURLOPT_URL, url.c_str());
   (*curl_easy_setopt)(curl, CURLOPT_USERAGENT, kUserAgent);
   // Set proxy information if necessary.
   if (!proxy.empty())
     (*curl_easy_setopt)(curl, CURLOPT_PROXY, proxy.c_str());
   if (!proxy_user_pwd.empty())
     (*curl_easy_setopt)(curl, CURLOPT_PROXYUSERPWD, proxy_user_pwd.c_str());
 
+  if (!ca_certificate_file.empty())
+    (*curl_easy_setopt)(curl, CURLOPT_CAINFO, ca_certificate_file.c_str());
+
   struct curl_httppost *formpost = NULL;
   struct curl_httppost *lastptr = NULL;
   // Add form data.
   CURLFORMcode (*curl_formadd)(struct curl_httppost **, struct curl_httppost **, ...);
   *(void**) (&curl_formadd) = dlsym(curl_lib, "curl_formadd");
   map<string, string>::const_iterator iter = parameters.begin();
   for (; iter != parameters.end(); ++iter)
     (*curl_formadd)(&formpost, &lastptr,
--- a/toolkit/crashreporter/google-breakpad/src/common/linux/http_upload.h
+++ b/toolkit/crashreporter/google-breakpad/src/common/linux/http_upload.h
@@ -56,16 +56,17 @@ class HTTPUpload {
   // If the send fails, a description of the error will be
   // returned in error_description.
   static bool SendRequest(const string &url,
                           const map<string, string> &parameters,
                           const string &upload_file,
                           const string &file_part_name,
                           const string &proxy,
                           const string &proxy_user_pwd,
+                          const string &ca_certificate_file,
                           string *response_body,
                           string *error_description);
 
  private:
   // Checks that the given list of parameters has only printable
   // ASCII characters in the parameter name, and does not contain
   // any quote (") characters.  Returns true if so.
   static bool CheckParameters(const map<string, string> &parameters);