[Python-checkins] cpython: Issue #19940: ssl.cert_time_to_seconds() now interprets the given time string

antoine.pitrou python-checkins at python.org
Mon Apr 28 20:57:47 CEST 2014


http://hg.python.org/cpython/rev/7191c37238d5
changeset:   90484:7191c37238d5
user:        Antoine Pitrou <solipsis at pitrou.net>
date:        Mon Apr 28 20:57:36 2014 +0200
summary:
  Issue #19940: ssl.cert_time_to_seconds() now interprets the given time string in the UTC timezone (as specified in RFC 5280), not the local timezone.

Patch by Akira.

files:
  Doc/library/ssl.rst  |  34 ++++++++++----
  Lib/ssl.py           |  32 ++++++++++++--
  Lib/test/test_ssl.py |  71 ++++++++++++++++++++++++++++++++
  Misc/ACKS            |   1 +
  Misc/NEWS            |   4 +
  5 files changed, 126 insertions(+), 16 deletions(-)


diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -372,20 +372,32 @@
       IDN A-labels such as ``www*.xn--pthon-kva.org`` are still supported,
       but ``x*.python.org`` no longer matches ``xn--tda.python.org``.
 
-.. function:: cert_time_to_seconds(timestring)
+.. function:: cert_time_to_seconds(cert_time)
 
-   Returns a floating-point value containing a normal seconds-after-the-epoch
-   time value, given the time-string representing the "notBefore" or "notAfter"
-   date from a certificate.
+   Return the time in seconds since the Epoch, given the ``cert_time``
+   string representing the "notBefore" or "notAfter" date from a
+   certificate in ``"%b %d %H:%M:%S %Y %Z"`` strptime format (C
+   locale).
 
-   Here's an example::
+   Here's an example:
 
-     >>> import ssl
-     >>> ssl.cert_time_to_seconds("May  9 00:00:00 2007 GMT")
-     1178694000.0
-     >>> import time
-     >>> time.ctime(ssl.cert_time_to_seconds("May  9 00:00:00 2007 GMT"))
-     'Wed May  9 00:00:00 2007'
+   .. doctest:: newcontext
+
+      >>> import ssl
+      >>> timestamp = ssl.cert_time_to_seconds("Jan  5 09:34:43 2018 GMT")
+      >>> timestamp
+      1515144883
+      >>> from datetime import datetime
+      >>> print(datetime.utcfromtimestamp(timestamp))
+      2018-01-05 09:34:43
+
+   "notBefore" or "notAfter" dates must use GMT (:rfc:`5280`).
+
+   .. versionchanged:: 3.5
+      Interpret the input time as a time in UTC as specified by 'GMT'
+      timezone in the input string. Local timezone was used
+      previously. Return an integer (no fractions of a second in the
+      input format)
 
 .. function:: get_server_certificate(addr, ssl_version=PROTOCOL_SSLv23, ca_certs=None)
 
diff --git a/Lib/ssl.py b/Lib/ssl.py
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -879,12 +879,34 @@
 # some utility functions
 
 def cert_time_to_seconds(cert_time):
-    """Takes a date-time string in standard ASN1_print form
-    ("MON DAY 24HOUR:MINUTE:SEC YEAR TIMEZONE") and return
-    a Python time value in seconds past the epoch."""
+    """Return the time in seconds since the Epoch, given the timestring
+    representing the "notBefore" or "notAfter" date from a certificate
+    in ``"%b %d %H:%M:%S %Y %Z"`` strptime format (C locale).
 
-    import time
-    return time.mktime(time.strptime(cert_time, "%b %d %H:%M:%S %Y GMT"))
+    "notBefore" or "notAfter" dates must use UTC (RFC 5280).
+
+    Month is one of: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
+    UTC should be specified as GMT (see ASN1_TIME_print())
+    """
+    from time import strptime
+    from calendar import timegm
+
+    months = (
+        "Jan","Feb","Mar","Apr","May","Jun",
+        "Jul","Aug","Sep","Oct","Nov","Dec"
+    )
+    time_format = ' %d %H:%M:%S %Y GMT' # NOTE: no month, fixed GMT
+    try:
+        month_number = months.index(cert_time[:3].title()) + 1
+    except ValueError:
+        raise ValueError('time data %r does not match '
+                         'format "%%b%s"' % (cert_time, time_format))
+    else:
+        # found valid month
+        tt = strptime(cert_time[3:], time_format)
+        # return an integer, the previous mktime()-based implementation
+        # returned a float (fractional seconds are always zero here).
+        return timegm((tt[0], month_number) + tt[2:6])
 
 PEM_HEADER = "-----BEGIN CERTIFICATE-----"
 PEM_FOOTER = "-----END CERTIFICATE-----"
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -86,6 +86,12 @@
     # 0.9.8 or higher
     return ssl.OPENSSL_VERSION_INFO >= (0, 9, 8, 0, 15)
 
+def utc_offset(): #NOTE: ignore issues like #1647654
+    # local time = utc time + utc offset
+    if time.daylight and time.localtime().tm_isdst > 0:
+        return -time.altzone  # seconds
+    return -time.timezone
+
 def asn1time(cert_time):
     # Some versions of OpenSSL ignore seconds, see #18207
     # 0.9.8.i
@@ -651,6 +657,71 @@
             ctx.wrap_socket(s)
         self.assertEqual(str(cx.exception), "only stream sockets are supported")
 
+    def cert_time_ok(self, timestring, timestamp):
+        self.assertEqual(ssl.cert_time_to_seconds(timestring), timestamp)
+
+    def cert_time_fail(self, timestring):
+        with self.assertRaises(ValueError):
+            ssl.cert_time_to_seconds(timestring)
+
+    @unittest.skipUnless(utc_offset(),
+                         'local time needs to be different from UTC')
+    def test_cert_time_to_seconds_timezone(self):
+        # Issue #19940: ssl.cert_time_to_seconds() returns wrong
+        #               results if local timezone is not UTC
+        self.cert_time_ok("May  9 00:00:00 2007 GMT", 1178668800.0)
+        self.cert_time_ok("Jan  5 09:34:43 2018 GMT", 1515144883.0)
+
+    def test_cert_time_to_seconds(self):
+        timestring = "Jan  5 09:34:43 2018 GMT"
+        ts = 1515144883.0
+        self.cert_time_ok(timestring, ts)
+        # accept keyword parameter, assert its name
+        self.assertEqual(ssl.cert_time_to_seconds(cert_time=timestring), ts)
+        # accept both %e and %d (space or zero generated by strftime)
+        self.cert_time_ok("Jan 05 09:34:43 2018 GMT", ts)
+        # case-insensitive
+        self.cert_time_ok("JaN  5 09:34:43 2018 GmT", ts)
+        self.cert_time_fail("Jan  5 09:34 2018 GMT")     # no seconds
+        self.cert_time_fail("Jan  5 09:34:43 2018")      # no GMT
+        self.cert_time_fail("Jan  5 09:34:43 2018 UTC")  # not GMT timezone
+        self.cert_time_fail("Jan 35 09:34:43 2018 GMT")  # invalid day
+        self.cert_time_fail("Jon  5 09:34:43 2018 GMT")  # invalid month
+        self.cert_time_fail("Jan  5 24:00:00 2018 GMT")  # invalid hour
+        self.cert_time_fail("Jan  5 09:60:43 2018 GMT")  # invalid minute
+
+        newyear_ts = 1230768000.0
+        # leap seconds
+        self.cert_time_ok("Dec 31 23:59:60 2008 GMT", newyear_ts)
+        # same timestamp
+        self.cert_time_ok("Jan  1 00:00:00 2009 GMT", newyear_ts)
+
+        self.cert_time_ok("Jan  5 09:34:59 2018 GMT", 1515144899)
+        #  allow 60th second (even if it is not a leap second)
+        self.cert_time_ok("Jan  5 09:34:60 2018 GMT", 1515144900)
+        #  allow 2nd leap second for compatibility with time.strptime()
+        self.cert_time_ok("Jan  5 09:34:61 2018 GMT", 1515144901)
+        self.cert_time_fail("Jan  5 09:34:62 2018 GMT")  # invalid seconds
+
+        # no special treatement for the special value:
+        #   99991231235959Z (rfc 5280)
+        self.cert_time_ok("Dec 31 23:59:59 9999 GMT", 253402300799.0)
+
+    @support.run_with_locale('LC_ALL', '')
+    def test_cert_time_to_seconds_locale(self):
+        # `cert_time_to_seconds()` should be locale independent
+
+        def local_february_name():
+            return time.strftime('%b', (1, 2, 3, 4, 5, 6, 0, 0, 0))
+
+        if local_february_name().lower() == 'feb':
+            self.skipTest("locale-specific month name needs to be "
+                          "different from C locale")
+
+        # locale-independent
+        self.cert_time_ok("Feb  9 00:00:00 2007 GMT", 1170979200.0)
+        self.cert_time_fail(local_february_name() + "  9 00:00:00 2007 GMT")
+
 
 class ContextTests(unittest.TestCase):
 
diff --git a/Misc/ACKS b/Misc/ACKS
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -23,6 +23,7 @@
 Farhan Ahmad
 Matthew Ahrens
 Nir Aides
+Akira
 Yaniv Aknin
 Jyrki Alakuijala
 Steve Alexander
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -57,6 +57,10 @@
 Library
 -------
 
+- Issue #19940: ssl.cert_time_to_seconds() now interprets the given time
+  string in the UTC timezone (as specified in RFC 5280), not the local
+  timezone.
+
 - Issue #13204: Calling sys.flags.__new__ would crash the interpreter,
   now it raises a TypeError.
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list