Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c695c95

Browse files
committed
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.
1 parent 3a74ce2 commit c695c95

5 files changed

Lines changed: 126 additions & 16 deletions

File tree

Doc/library/ssl.rst

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -372,20 +372,32 @@ Certificate handling
372372
IDN A-labels such as ``www*.xn--pthon-kva.org`` are still supported,
373373
but ``x*.python.org`` no longer matches ``xn--tda.python.org``.
374374

375-
.. function:: cert_time_to_seconds(timestring)
375+
.. function:: cert_time_to_seconds(cert_time)
376376

377-
Returns a floating-point value containing a normal seconds-after-the-epoch
378-
time value, given the time-string representing the "notBefore" or "notAfter"
379-
date from a certificate.
377+
Return the time in seconds since the Epoch, given the ``cert_time``
378+
string representing the "notBefore" or "notAfter" date from a
379+
certificate in ``"%b %d %H:%M:%S %Y %Z"`` strptime format (C
380+
locale).
380381

381-
Here's an example::
382+
Here's an example:
382383

383-
>>> import ssl
384-
>>> ssl.cert_time_to_seconds("May 9 00:00:00 2007 GMT")
385-
1178694000.0
386-
>>> import time
387-
>>> time.ctime(ssl.cert_time_to_seconds("May 9 00:00:00 2007 GMT"))
388-
'Wed May 9 00:00:00 2007'
384+
.. doctest:: newcontext
385+
386+
>>> import ssl
387+
>>> timestamp = ssl.cert_time_to_seconds("Jan 5 09:34:43 2018 GMT")
388+
>>> timestamp
389+
1515144883
390+
>>> from datetime import datetime
391+
>>> print(datetime.utcfromtimestamp(timestamp))
392+
2018-01-05 09:34:43
393+
394+
"notBefore" or "notAfter" dates must use GMT (:rfc:`5280`).
395+
396+
.. versionchanged:: 3.5
397+
Interpret the input time as a time in UTC as specified by 'GMT'
398+
timezone in the input string. Local timezone was used
399+
previously. Return an integer (no fractions of a second in the
400+
input format)
389401

390402
.. function:: get_server_certificate(addr, ssl_version=PROTOCOL_SSLv23, ca_certs=None)
391403

Lib/ssl.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -879,12 +879,34 @@ def wrap_socket(sock, keyfile=None, certfile=None,
879879
# some utility functions
880880

881881
def cert_time_to_seconds(cert_time):
882-
"""Takes a date-time string in standard ASN1_print form
883-
("MON DAY 24HOUR:MINUTE:SEC YEAR TIMEZONE") and return
884-
a Python time value in seconds past the epoch."""
882+
"""Return the time in seconds since the Epoch, given the timestring
883+
representing the "notBefore" or "notAfter" date from a certificate
884+
in ``"%b %d %H:%M:%S %Y %Z"`` strptime format (C locale).
885885
886-
import time
887-
return time.mktime(time.strptime(cert_time, "%b %d %H:%M:%S %Y GMT"))
886+
"notBefore" or "notAfter" dates must use UTC (RFC 5280).
887+
888+
Month is one of: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
889+
UTC should be specified as GMT (see ASN1_TIME_print())
890+
"""
891+
from time import strptime
892+
from calendar import timegm
893+
894+
months = (
895+
"Jan","Feb","Mar","Apr","May","Jun",
896+
"Jul","Aug","Sep","Oct","Nov","Dec"
897+
)
898+
time_format = ' %d %H:%M:%S %Y GMT' # NOTE: no month, fixed GMT
899+
try:
900+
month_number = months.index(cert_time[:3].title()) + 1
901+
except ValueError:
902+
raise ValueError('time data %r does not match '
903+
'format "%%b%s"' % (cert_time, time_format))
904+
else:
905+
# found valid month
906+
tt = strptime(cert_time[3:], time_format)
907+
# return an integer, the previous mktime()-based implementation
908+
# returned a float (fractional seconds are always zero here).
909+
return timegm((tt[0], month_number) + tt[2:6])
888910

889911
PEM_HEADER = "-----BEGIN CERTIFICATE-----"
890912
PEM_FOOTER = "-----END CERTIFICATE-----"

Lib/test/test_ssl.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ def have_verify_flags():
8686
# 0.9.8 or higher
8787
return ssl.OPENSSL_VERSION_INFO >= (0, 9, 8, 0, 15)
8888

89+
def utc_offset(): #NOTE: ignore issues like #1647654
90+
# local time = utc time + utc offset
91+
if time.daylight and time.localtime().tm_isdst > 0:
92+
return -time.altzone # seconds
93+
return -time.timezone
94+
8995
def asn1time(cert_time):
9096
# Some versions of OpenSSL ignore seconds, see #18207
9197
# 0.9.8.i
@@ -651,6 +657,71 @@ def test_unsupported_dtls(self):
651657
ctx.wrap_socket(s)
652658
self.assertEqual(str(cx.exception), "only stream sockets are supported")
653659

660+
def cert_time_ok(self, timestring, timestamp):
661+
self.assertEqual(ssl.cert_time_to_seconds(timestring), timestamp)
662+
663+
def cert_time_fail(self, timestring):
664+
with self.assertRaises(ValueError):
665+
ssl.cert_time_to_seconds(timestring)
666+
667+
@unittest.skipUnless(utc_offset(),
668+
'local time needs to be different from UTC')
669+
def test_cert_time_to_seconds_timezone(self):
670+
# Issue #19940: ssl.cert_time_to_seconds() returns wrong
671+
# results if local timezone is not UTC
672+
self.cert_time_ok("May 9 00:00:00 2007 GMT", 1178668800.0)
673+
self.cert_time_ok("Jan 5 09:34:43 2018 GMT", 1515144883.0)
674+
675+
def test_cert_time_to_seconds(self):
676+
timestring = "Jan 5 09:34:43 2018 GMT"
677+
ts = 1515144883.0
678+
self.cert_time_ok(timestring, ts)
679+
# accept keyword parameter, assert its name
680+
self.assertEqual(ssl.cert_time_to_seconds(cert_time=timestring), ts)
681+
# accept both %e and %d (space or zero generated by strftime)
682+
self.cert_time_ok("Jan 05 09:34:43 2018 GMT", ts)
683+
# case-insensitive
684+
self.cert_time_ok("JaN 5 09:34:43 2018 GmT", ts)
685+
self.cert_time_fail("Jan 5 09:34 2018 GMT") # no seconds
686+
self.cert_time_fail("Jan 5 09:34:43 2018") # no GMT
687+
self.cert_time_fail("Jan 5 09:34:43 2018 UTC") # not GMT timezone
688+
self.cert_time_fail("Jan 35 09:34:43 2018 GMT") # invalid day
689+
self.cert_time_fail("Jon 5 09:34:43 2018 GMT") # invalid month
690+
self.cert_time_fail("Jan 5 24:00:00 2018 GMT") # invalid hour
691+
self.cert_time_fail("Jan 5 09:60:43 2018 GMT") # invalid minute
692+
693+
newyear_ts = 1230768000.0
694+
# leap seconds
695+
self.cert_time_ok("Dec 31 23:59:60 2008 GMT", newyear_ts)
696+
# same timestamp
697+
self.cert_time_ok("Jan 1 00:00:00 2009 GMT", newyear_ts)
698+
699+
self.cert_time_ok("Jan 5 09:34:59 2018 GMT", 1515144899)
700+
# allow 60th second (even if it is not a leap second)
701+
self.cert_time_ok("Jan 5 09:34:60 2018 GMT", 1515144900)
702+
# allow 2nd leap second for compatibility with time.strptime()
703+
self.cert_time_ok("Jan 5 09:34:61 2018 GMT", 1515144901)
704+
self.cert_time_fail("Jan 5 09:34:62 2018 GMT") # invalid seconds
705+
706+
# no special treatement for the special value:
707+
# 99991231235959Z (rfc 5280)
708+
self.cert_time_ok("Dec 31 23:59:59 9999 GMT", 253402300799.0)
709+
710+
@support.run_with_locale('LC_ALL', '')
711+
def test_cert_time_to_seconds_locale(self):
712+
# `cert_time_to_seconds()` should be locale independent
713+
714+
def local_february_name():
715+
return time.strftime('%b', (1, 2, 3, 4, 5, 6, 0, 0, 0))
716+
717+
if local_february_name().lower() == 'feb':
718+
self.skipTest("locale-specific month name needs to be "
719+
"different from C locale")
720+
721+
# locale-independent
722+
self.cert_time_ok("Feb 9 00:00:00 2007 GMT", 1170979200.0)
723+
self.cert_time_fail(local_february_name() + " 9 00:00:00 2007 GMT")
724+
654725

655726
class ContextTests(unittest.TestCase):
656727

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Jim Ahlstrom
2323
Farhan Ahmad
2424
Matthew Ahrens
2525
Nir Aides
26+
Akira
2627
Yaniv Aknin
2728
Jyrki Alakuijala
2829
Steve Alexander

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ Core and Builtins
5757
Library
5858
-------
5959

60+
- Issue #19940: ssl.cert_time_to_seconds() now interprets the given time
61+
string in the UTC timezone (as specified in RFC 5280), not the local
62+
timezone.
63+
6064
- Issue #13204: Calling sys.flags.__new__ would crash the interpreter,
6165
now it raises a TypeError.
6266

0 commit comments

Comments
 (0)