bug 557113 - sort out crash report certificate issues on Maemo. r=mfinkle,johnath
authorTed Mielczarek <ted.mielczarek@gmail.com>
Fri, 09 Apr 2010 16:52:04 -0400
changeset 41647 9cbe87222f44cf863c3ca3822394252d9690b06d
parent 41646 37cd6605aea2986813eb6a98e2dac7d7964c4c98
child 41648 dd7e4ca433aa367eb08dedace605a71ec6650a78
push idunknown
push userunknown
push dateunknown
reviewersmfinkle, johnath
bugs557113
milestone1.9.3a5pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
bug 557113 - sort out crash report certificate issues on Maemo. r=mfinkle,johnath
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
+
 # Don't use the STL wrappers in the crashreporter clients; they don't
 # link with -lmozalloc, and it really doesn't matter here anyway.
 STL_FLAGS =
 
 PROGRAM = crashreporter$(BIN_SUFFIX)
 DIST_PROGRAM = crashreporter$(BIN_SUFFIX)
 
 LOCAL_INCLUDES = -I$(srcdir)/../google-breakpad/src
@@ -77,18 +79,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)
@@ -120,8 +135,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);