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.
This commit is contained in:
parent
3a74ce2088
commit
c695c95626
|
@ -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:
|
||||
|
||||
.. doctest:: newcontext
|
||||
|
||||
>>> 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'
|
||||
>>> 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)
|
||||
|
||||
|
|
32
Lib/ssl.py
32
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-----"
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ Jim Ahlstrom
|
|||
Farhan Ahmad
|
||||
Matthew Ahrens
|
||||
Nir Aides
|
||||
Akira
|
||||
Yaniv Aknin
|
||||
Jyrki Alakuijala
|
||||
Steve Alexander
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue