Patchwork [2,of,2,v2] ssl: on OS X, use a dummy cert to trick Python/OpenSSL to use system CA certs

login
register
mail settings
Submitter Mads Kiilerich
Date Sept. 26, 2014, 12:20 a.m.
Message ID <afdbe4646db8f7a9d0d9.1411690858@localhost.localdomain>
Download mbox | patch
Permalink /patch/5988/
State Accepted
Headers show

Comments

Mads Kiilerich - Sept. 26, 2014, 12:20 a.m.
# HG changeset patch
# User Mads Kiilerich <madski@unity3d.com>
# Date 1411690788 -7200
#      Fri Sep 26 02:19:48 2014 +0200
# Node ID afdbe4646db8f7a9d0d9eaa623c021083f20a93a
# Parent  6e888ceda9e9b358e1bcb45eef2555ceb93e0874
ssl: on OS X, use a dummy cert to trick Python/OpenSSL to use system CA certs

This will give PKI-secure behaviour out of the box, without any configuration.

Setting web.cacerts to any value or empty will disable this trick.

This dummy cert trick only works on OS X 10.6+, but 10.5 had Python 2.5 which
didn't have certificate validation at all.
Augie Fackler - Sept. 29, 2014, 7:10 p.m.
On Fri, Sep 26, 2014 at 02:20:58AM +0200, Mads Kiilerich wrote:
> # HG changeset patch
> # User Mads Kiilerich <madski@unity3d.com>
> # Date 1411690788 -7200
> #      Fri Sep 26 02:19:48 2014 +0200
> # Node ID afdbe4646db8f7a9d0d9eaa623c021083f20a93a
> # Parent  6e888ceda9e9b358e1bcb45eef2555ceb93e0874
> ssl: on OS X, use a dummy cert to trick Python/OpenSSL to use system CA certs

LGTM, queued.

(Will edit DISAPLE -> DISABLE.)

>
> This will give PKI-secure behaviour out of the box, without any configuration.
>
> Setting web.cacerts to any value or empty will disable this trick.
>
> This dummy cert trick only works on OS X 10.6+, but 10.5 had Python 2.5 which
> didn't have certificate validation at all.
>
> diff --git a/mercurial/dummycert.pem b/mercurial/dummycert.pem
> new file mode 100644
> --- /dev/null
> +++ b/mercurial/dummycert.pem
> @@ -0,0 +1,56 @@
> +A dummy certificate that will make OS X 10.6+ Python use the system CA
> +certificate store:
> +
> +-----BEGIN CERTIFICATE-----
> +MIIBIzCBzgIJANjmj39sb3FmMA0GCSqGSIb3DQEBBQUAMBkxFzAVBgNVBAMTDmhn
> +LmV4YW1wbGUuY29tMB4XDTE0MDgzMDA4NDU1OVoXDTE0MDgyOTA4NDU1OVowGTEX
> +MBUGA1UEAxMOaGcuZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEA
> +mh/ZySGlcq0ALNLmA1gZqt61HruywPrRk6WyrLJRgt+X7OP9FFlEfl2tzHfzqvmK
> +CtSQoPINWOdAJMekBYFgKQIDAQABMA0GCSqGSIb3DQEBBQUAA0EAF9h49LkSqJ6a
> +IlpogZuUHtihXeKZBsiktVIDlDccYsNy0RSh9XxUfhk+XMLw8jBlYvcltSXdJ7We
> +aKdQRekuMQ==
> +-----END CERTIFICATE-----
> +
> +This certificate was generated to be syntactically valid but never be usable;
> +it expired before it became valid.
> +
> +Created as:
> +
> +  $ cat > cn.conf << EOT
> +  > [req]
> +  > distinguished_name = req_distinguished_name
> +  > [req_distinguished_name]
> +  > commonName = Common Name
> +  > commonName_default = no.example.com
> +  > EOT
> +  $ openssl req -nodes -new -x509 -keyout /dev/null \
> +  >   -out dummycert.pem -days -1 -config cn.conf -subj '/CN=hg.example.com'
> +
> +To verify the content of this certificate:
> +
> +  $ openssl x509 -in dummycert.pem -noout -text
> +  Certificate:
> +      Data:
> +          Version: 1 (0x0)
> +          Serial Number: 15629337334278746470 (0xd8e68f7f6c6f7166)
> +      Signature Algorithm: sha1WithRSAEncryption
> +          Issuer: CN=hg.example.com
> +          Validity
> +              Not Before: Aug 30 08:45:59 2014 GMT
> +              Not After : Aug 29 08:45:59 2014 GMT
> +          Subject: CN=hg.example.com
> +          Subject Public Key Info:
> +              Public Key Algorithm: rsaEncryption
> +                  Public-Key: (512 bit)
> +                  Modulus:
> +                      00:9a:1f:d9:c9:21:a5:72:ad:00:2c:d2:e6:03:58:
> +                      19:aa:de:b5:1e:bb:b2:c0:fa:d1:93:a5:b2:ac:b2:
> +                      51:82:df:97:ec:e3:fd:14:59:44:7e:5d:ad:cc:77:
> +                      f3:aa:f9:8a:0a:d4:90:a0:f2:0d:58:e7:40:24:c7:
> +                      a4:05:81:60:29
> +                  Exponent: 65537 (0x10001)
> +      Signature Algorithm: sha1WithRSAEncryption
> +           17:d8:78:f4:b9:12:a8:9e:9a:22:5a:68:81:9b:94:1e:d8:a1:
> +           5d:e2:99:06:c8:a4:b5:52:03:94:37:1c:62:c3:72:d1:14:a1:
> +           f5:7c:54:7e:19:3e:5c:c2:f0:f2:30:65:62:f7:25:b5:25:dd:
> +           27:b5:9e:68:a7:50:45:e9:2e:31
> diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
> --- a/mercurial/sslutil.py
> +++ b/mercurial/sslutil.py
> @@ -6,7 +6,7 @@
>  #
>  # This software may be used and distributed according to the terms of the
>  # GNU General Public License version 2 or any later version.
> -import os
> +import os, sys
>
>  from mercurial import util
>  from mercurial.i18n import _
> @@ -104,6 +104,13 @@ def sslkwargs(ui, host):
>          cacerts = util.expandpath(cacerts)
>          if not os.path.exists(cacerts):
>              raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
> +    elif cacerts is None and sys.platform == 'darwin' and not util.mainfrozen():
> +        dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
> +        if os.path.exists(dummycert):
> +            ui.debug('using %s to enable OS X system CA\n' % dummycert)
> +            ui.setconfig('web', 'cacerts', dummycert, 'dummy')
> +            cacerts = dummycert
> +    if cacerts:
>          kws.update({'ca_certs': cacerts,
>                      'cert_reqs': CERT_REQUIRED,
>                      })
> diff --git a/setup.py b/setup.py
> --- a/setup.py
> +++ b/setup.py
> @@ -481,7 +481,8 @@ class HackedMingw32CCompiler(cygwinccomp
>  cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
>
>  packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
> -                             'help/*.txt']}
> +                             'help/*.txt',
> +                             'dummycert.pem']}
>
>  def ordinarypath(p):
>      return p and p[0] != '.' and p[-1] != '~'
> diff --git a/tests/hghave.py b/tests/hghave.py
> --- a/tests/hghave.py
> +++ b/tests/hghave.py
> @@ -332,6 +332,10 @@ def has_msys():
>  def has_aix():
>      return sys.platform.startswith("aix")
>
> +@check("osx", "OS X")
> +def has_osx():
> +    return sys.platform == 'darwin'
> +
>  @check("absimport", "absolute_import in __future__")
>  def has_absimport():
>      import __future__
> diff --git a/tests/test-https.t b/tests/test-https.t
> --- a/tests/test-https.t
> +++ b/tests/test-https.t
> @@ -115,9 +115,20 @@ Test server address cannot be reused
>  #endif
>    $ cd ..
>
> +OS X has a dummy CA cert that enables use of the system CA store
> +
> +  $ DISAPLEOSXDUMMYCERT=
> +#if osx
> +  $ hg clone https://localhost:$HGPORT/ copy-pull
> +  abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
> +  [255]
> +
> +  $ DISAPLEOSXDUMMYCERT="--config=web.cacerts="
> +#endif
> +
>  clone via pull
>
> -  $ hg clone https://localhost:$HGPORT/ copy-pull
> +  $ hg clone https://localhost:$HGPORT/ copy-pull $DISAPLEOSXDUMMYCERT
>    warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
>    requesting all changes
>    adding changesets
> @@ -143,7 +154,7 @@ pull without cacert
>    $ cd copy-pull
>    $ echo '[hooks]' >> .hg/hgrc
>    $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
> -  $ hg pull
> +  $ hg pull $DISAPLEOSXDUMMYCERT
>    warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
>    pulling from https://localhost:$HGPORT/
>    searching for changes
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel

Patch

diff --git a/mercurial/dummycert.pem b/mercurial/dummycert.pem
new file mode 100644
--- /dev/null
+++ b/mercurial/dummycert.pem
@@ -0,0 +1,56 @@ 
+A dummy certificate that will make OS X 10.6+ Python use the system CA
+certificate store:
+
+-----BEGIN CERTIFICATE-----
+MIIBIzCBzgIJANjmj39sb3FmMA0GCSqGSIb3DQEBBQUAMBkxFzAVBgNVBAMTDmhn
+LmV4YW1wbGUuY29tMB4XDTE0MDgzMDA4NDU1OVoXDTE0MDgyOTA4NDU1OVowGTEX
+MBUGA1UEAxMOaGcuZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEA
+mh/ZySGlcq0ALNLmA1gZqt61HruywPrRk6WyrLJRgt+X7OP9FFlEfl2tzHfzqvmK
+CtSQoPINWOdAJMekBYFgKQIDAQABMA0GCSqGSIb3DQEBBQUAA0EAF9h49LkSqJ6a
+IlpogZuUHtihXeKZBsiktVIDlDccYsNy0RSh9XxUfhk+XMLw8jBlYvcltSXdJ7We
+aKdQRekuMQ==
+-----END CERTIFICATE-----
+
+This certificate was generated to be syntactically valid but never be usable;
+it expired before it became valid.
+
+Created as:
+
+  $ cat > cn.conf << EOT
+  > [req]
+  > distinguished_name = req_distinguished_name
+  > [req_distinguished_name]
+  > commonName = Common Name
+  > commonName_default = no.example.com
+  > EOT
+  $ openssl req -nodes -new -x509 -keyout /dev/null \
+  >   -out dummycert.pem -days -1 -config cn.conf -subj '/CN=hg.example.com'
+
+To verify the content of this certificate:
+
+  $ openssl x509 -in dummycert.pem -noout -text
+  Certificate:
+      Data:
+          Version: 1 (0x0)
+          Serial Number: 15629337334278746470 (0xd8e68f7f6c6f7166)
+      Signature Algorithm: sha1WithRSAEncryption
+          Issuer: CN=hg.example.com
+          Validity
+              Not Before: Aug 30 08:45:59 2014 GMT
+              Not After : Aug 29 08:45:59 2014 GMT
+          Subject: CN=hg.example.com
+          Subject Public Key Info:
+              Public Key Algorithm: rsaEncryption
+                  Public-Key: (512 bit)
+                  Modulus:
+                      00:9a:1f:d9:c9:21:a5:72:ad:00:2c:d2:e6:03:58:
+                      19:aa:de:b5:1e:bb:b2:c0:fa:d1:93:a5:b2:ac:b2:
+                      51:82:df:97:ec:e3:fd:14:59:44:7e:5d:ad:cc:77:
+                      f3:aa:f9:8a:0a:d4:90:a0:f2:0d:58:e7:40:24:c7:
+                      a4:05:81:60:29
+                  Exponent: 65537 (0x10001)
+      Signature Algorithm: sha1WithRSAEncryption
+           17:d8:78:f4:b9:12:a8:9e:9a:22:5a:68:81:9b:94:1e:d8:a1:
+           5d:e2:99:06:c8:a4:b5:52:03:94:37:1c:62:c3:72:d1:14:a1:
+           f5:7c:54:7e:19:3e:5c:c2:f0:f2:30:65:62:f7:25:b5:25:dd:
+           27:b5:9e:68:a7:50:45:e9:2e:31
diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -6,7 +6,7 @@ 
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-import os
+import os, sys
 
 from mercurial import util
 from mercurial.i18n import _
@@ -104,6 +104,13 @@  def sslkwargs(ui, host):
         cacerts = util.expandpath(cacerts)
         if not os.path.exists(cacerts):
             raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
+    elif cacerts is None and sys.platform == 'darwin' and not util.mainfrozen():
+        dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
+        if os.path.exists(dummycert):
+            ui.debug('using %s to enable OS X system CA\n' % dummycert)
+            ui.setconfig('web', 'cacerts', dummycert, 'dummy')
+            cacerts = dummycert
+    if cacerts:
         kws.update({'ca_certs': cacerts,
                     'cert_reqs': CERT_REQUIRED,
                     })
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -481,7 +481,8 @@  class HackedMingw32CCompiler(cygwinccomp
 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
 
 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
-                             'help/*.txt']}
+                             'help/*.txt',
+                             'dummycert.pem']}
 
 def ordinarypath(p):
     return p and p[0] != '.' and p[-1] != '~'
diff --git a/tests/hghave.py b/tests/hghave.py
--- a/tests/hghave.py
+++ b/tests/hghave.py
@@ -332,6 +332,10 @@  def has_msys():
 def has_aix():
     return sys.platform.startswith("aix")
 
+@check("osx", "OS X")
+def has_osx():
+    return sys.platform == 'darwin'
+
 @check("absimport", "absolute_import in __future__")
 def has_absimport():
     import __future__
diff --git a/tests/test-https.t b/tests/test-https.t
--- a/tests/test-https.t
+++ b/tests/test-https.t
@@ -115,9 +115,20 @@  Test server address cannot be reused
 #endif
   $ cd ..
 
+OS X has a dummy CA cert that enables use of the system CA store
+
+  $ DISAPLEOSXDUMMYCERT=
+#if osx
+  $ hg clone https://localhost:$HGPORT/ copy-pull
+  abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
+  [255]
+
+  $ DISAPLEOSXDUMMYCERT="--config=web.cacerts="
+#endif
+
 clone via pull
 
-  $ hg clone https://localhost:$HGPORT/ copy-pull
+  $ hg clone https://localhost:$HGPORT/ copy-pull $DISAPLEOSXDUMMYCERT
   warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
   requesting all changes
   adding changesets
@@ -143,7 +154,7 @@  pull without cacert
   $ cd copy-pull
   $ echo '[hooks]' >> .hg/hgrc
   $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
-  $ hg pull
+  $ hg pull $DISAPLEOSXDUMMYCERT
   warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
   pulling from https://localhost:$HGPORT/
   searching for changes