1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
"""ASN.1 UTCTime and GeneralizedTime, as understood by RFC 5280."""
import abc
import time
from ct.crypto import error
from ct.crypto.asn1 import tag
from ct.crypto.asn1 import types
class BaseTime(types.ASN1String):
"""Base class for time types."""
def __init__(self, value=None, serialized_value=None, strict=True):
super(BaseTime, self).__init__(value=value,
serialized_value=serialized_value,
strict=strict)
self._gmtime = self._decode_gmtime(strict=strict)
# This is a lenient "strict": if we were able to decode the time,
# even if it didn't fully conform to the standard, then we'll allow it.
# If the time string is garbage then we raise.
if strict and self._gmtime is None:
raise error.ASN1Error("Corrupt time: %s" % self._value)
def gmtime(self):
"""GMT time.
Returns:
a time.struct_time struct.
Raises:
error.ASN1Error: the ASN.1 string does not represent a valid time.
"""
if self._gmtime is None:
raise error.ASN1Error("Corrupt time: %s" % self._value)
return self._gmtime
@abc.abstractmethod
def _decode_gmtime(self, strict):
pass
def __str__(self):
try:
return time.strftime("%c GMT", self.gmtime())
except error.ASN1Error:
return str(self.value)
@types.Universal(23, tag.PRIMITIVE)
class UTCTime(BaseTime):
"""UTCTime, as understood by RFC 5280."""
# YYMMDDHHMMSSZ
_ASN1_LENGTH = 13
# YYMMDDHHMMZ
_UTC_NO_SECONDS_LENGTH = 11
# YYMMDDHHMMSS+HHMM
_UTC_TZ_OFFSET_LENGTH = 17
# YYMMDDHHMMSS
_UTC_NO_Z_LENGTH = 12
def _decode_gmtime(self, strict):
"""GMT time.
Returns:
a time.struct_time struct, or None if the string does not represent
a valid time.
"""
# From RFC 5280:
# For the purposes of this profile, UTCTime values MUST be expressed in
# Greenwich Mean Time (Zulu) and MUST include seconds (i.e., times are
# YYMMDDHHMMSSZ), even where the number of seconds is zero. Conforming
# systems MUST interpret the year field (YY) as follows:
#
# Where YY is greater than or equal to 50, the year SHALL be
# interpreted as 19YY; and
#
# Where YY is less than 50, the year SHALL be interpreted as 20YY.
#
# In addition, there are a number of older certificates
# that exclude the seconds, e.g. 0001010000Z and others than use
# an alternative timezone format 360526194526+0000
string_time = self.value
if len(string_time) == self._ASN1_LENGTH and string_time[-1] == "Z":
format = "%Y%m%d%H%M%S%Z"
elif (len(string_time) == self._UTC_NO_SECONDS_LENGTH and
string_time[-1] == "Z"):
format = "%Y%m%d%H%M%Z"
elif (len(string_time) == self._UTC_TZ_OFFSET_LENGTH and
string_time[self._UTC_NO_Z_LENGTH] in ('+','-')):
# note according to http://docs.python.org/2/library/time.html
# "%z" is not supported on all platforms.
#
# TBD: in next patch, parse this correctly
#
# Given that it's very infrequent and non-standard,
# we'll ignore time zone for now.
#
# convert the +HHMM to a timedelta and add to timestruct
# One could also special case the "+0000" which should be the same
# as GMT (without DST).
#
format = "%Y%m%d%H%M%S%Z"
string_time = string_time[0:self._ASN1_LENGTH]
elif (len(string_time) == self._UTC_NO_Z_LENGTH) and not strict:
string_time += "Z"
format = "%Y%m%d%H%M%S%Z"
else:
return None
try:
year = int(string_time[:2])
except ValueError:
return None
if 0 <= year < 50:
century = "20"
elif 50 <= year <= 99:
century = "19"
else:
return None
try:
# Adding GMT clears the daylight saving flag.
return time.strptime(century + string_time[:-1] + "GMT", format)
except ValueError:
return None
@types.Universal(24, tag.PRIMITIVE)
class GeneralizedTime(BaseTime):
"""Generalized time, as understood by RFC 5280."""
# YYYYMMDDHHMMSSZ
_ASN1_LENGTH = 15
def _decode_gmtime(self, strict):
"""GMT time.
Returns:
a time.struct_time struct, or None if the string does not represent
a valid time.
"""
# From RFC 5280:
# For the purposes of this profile, GeneralizedTime values MUST be
# expressed in Greenwich Mean Time (Zulu) and MUST include seconds
# (i.e., times are YYYYMMDDHHMMSSZ), even where the number of seconds
# is zero. GeneralizedTime values MUST NOT include fractional seconds.
if len(self._value) != self._ASN1_LENGTH or self._value[-1] != "Z":
return None
try:
# Adding GMT clears the daylight saving flag.
return time.strptime(self._value[:-1] + "GMT", "%Y%m%d%H%M%S%Z")
except ValueError:
return None
class Time(types.Choice):
print_labels = False
components = {"utcTime": UTCTime,
"generalTime": GeneralizedTime}
class Validity(types.Sequence):
components = (
(types.Component("notBefore", Time)),
(types.Component("notAfter", Time))
)
|