summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/ssl.rst34
-rw-r--r--Lib/ssl.py32
-rw-r--r--Lib/test/test_ssl.py71
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS4
5 files changed, 126 insertions, 16 deletions
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index 422cf56..79cbbdc 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -372,20 +372,32 @@ Certificate handling
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
index d6a63db..9c91096 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -879,12 +879,34 @@ def wrap_socket(sock, keyfile=None, certfile=None,
# 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
index a2ab795..a23d263 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -86,6 +86,12 @@ def have_verify_flags():
# 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 @@ class BasicSocketTests(unittest.TestCase):
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
index 035e525..f27e8f8 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -23,6 +23,7 @@ Jim Ahlstrom
Farhan Ahmad
Matthew Ahrens
Nir Aides
+Akira
Yaniv Aknin
Jyrki Alakuijala
Steve Alexander
diff --git a/Misc/NEWS b/Misc/NEWS
index 8ce9c77..5cc1c58 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -57,6 +57,10 @@ Core and Builtins
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.