Skip to content
Snippets Groups Projects
Commit 76d3112368a5 authored by Ilya Etingof's avatar Ilya Etingof
Browse files

Improve CER/DER encoding of GeneralizedTime

- Added support for subseconds CER/DER encoding edge cases in
  `GeneralizedTime` codec
- Fixed 3-digit fractional seconds value CER/DER encoding of
  `GeneralizedTime`
parent 8f8c3f312972
No related branches found
No related tags found
No related merge requests found
......@@ -29,6 +29,10 @@
- Added `PyAsn1UnicodeDecodeError`/`PyAsn1UnicodeDecodeError` exceptions
to help the caller treating unicode errors happening internally
to pyasn1 at the upper layers.
- Added support for subseconds CER/DER encoding edge cases in
`GeneralizedTime` codec.
- Fixed 3-digit fractional seconds value CER/DER encoding of
`GeneralizedTime`.
- Fixed `AnyDecoder` to accept possible `TagMap` as `asn1Spec`
to make dumping raw value operational
......
......@@ -31,11 +31,14 @@
# specialized GeneralStringEncoder here
class TimeEncoderMixIn(object):
zchar, = str2octs('Z')
pluschar, = str2octs('+')
minuschar, = str2octs('-')
commachar, = str2octs(',')
minLength = 12
maxLength = 19
Z_CHAR = ord('Z')
PLUS_CHAR = ord('+')
MINUS_CHAR = ord('-')
COMMA_CHAR = ord(',')
DOT_CHAR = ord('.')
ZERO_CHAR = ord('0')
MIN_LENGTH = 12
MAX_LENGTH = 19
def encodeValue(self, value, asn1Spec, encodeFun, **options):
......@@ -40,4 +43,4 @@
def encodeValue(self, value, asn1Spec, encodeFun, **options):
# Encoding constraints:
# CER encoding constraints:
# - minutes are mandatory, seconds are optional
......@@ -43,5 +46,5 @@
# - minutes are mandatory, seconds are optional
# - sub-seconds must NOT be zero
# - sub-seconds must NOT be zero / no meaningless zeros
# - no hanging fraction dot
# - time in UTC (Z)
# - only dot is allowed for fractions
......@@ -49,5 +52,11 @@
if asn1Spec is not None:
value = asn1Spec.clone(value)
octets = value.asOctets()
numbers = value.asNumbers()
if self.PLUS_CHAR in numbers or self.MINUS_CHAR in numbers:
raise error.PyAsn1Error('Must be UTC time: %r' % value)
if numbers[-1] != self.Z_CHAR:
raise error.PyAsn1Error('Missing "Z" time zone specifier: %r' % value)
......@@ -53,4 +62,12 @@
if not self.minLength < len(octets) < self.maxLength:
raise error.PyAsn1Error('Length constraint violated: %r' % value)
if self.COMMA_CHAR in numbers:
raise error.PyAsn1Error('Comma in fractions disallowed: %r' % value)
if self.DOT_CHAR in numbers:
isModified = False
numbers = list(numbers)
searchIndex = min(numbers.index(self.DOT_CHAR) + 4, len(numbers) - 1)
......@@ -56,4 +73,10 @@
if self.pluschar in octets or self.minuschar in octets:
raise error.PyAsn1Error('Must be UTC time: %r' % octets)
while numbers[searchIndex] != self.DOT_CHAR:
if numbers[searchIndex] == self.ZERO_CHAR:
del numbers[searchIndex]
isModified = True
searchIndex -= 1
searchIndex += 1
......@@ -59,4 +82,7 @@
if octets[-1] != self.zchar:
raise error.PyAsn1Error('Missing "Z" time zone specifier: %r' % octets)
if searchIndex < len(numbers):
if numbers[searchIndex] == self.Z_CHAR:
# drop hanging comma
del numbers[searchIndex - 1]
isModified = True
......@@ -62,6 +88,9 @@
if self.commachar in octets:
raise error.PyAsn1Error('Comma in fractions disallowed: %r' % value)
if isModified:
value = value.clone(numbers)
if not self.MIN_LENGTH < len(numbers) < self.MAX_LENGTH:
raise error.PyAsn1Error('Length constraint violated: %r' % value)
options.update(maxChunkSize=1000)
......@@ -71,8 +100,8 @@
class GeneralizedTimeEncoder(TimeEncoderMixIn, encoder.OctetStringEncoder):
minLength = 12
maxLength = 19
MIN_LENGTH = 12
MAX_LENGTH = 20
class UTCTimeEncoder(TimeEncoderMixIn, encoder.OctetStringEncoder):
......@@ -76,8 +105,8 @@
class UTCTimeEncoder(TimeEncoderMixIn, encoder.OctetStringEncoder):
minLength = 10
maxLength = 14
MIN_LENGTH = 10
MAX_LENGTH = 14
class SetEncoder(encoder.SequenceEncoder):
......
......@@ -99,6 +99,26 @@
useful.GeneralizedTime('20170801120112.59Z')
) == ints2octs((24, 18, 50, 48, 49, 55, 48, 56, 48, 49, 49, 50, 48, 49, 49, 50, 46, 53, 57, 90))
def testWithSubsecondsWithZeros(self):
assert encoder.encode(
useful.GeneralizedTime('20170801120112.099Z')
) == ints2octs((24, 18, 50, 48, 49, 55, 48, 56, 48, 49, 49, 50, 48, 49, 49, 50, 46, 57, 57, 90))
def testWithSubsecondsMax(self):
assert encoder.encode(
useful.GeneralizedTime('20170801120112.999Z')
) == ints2octs((24, 19, 50, 48, 49, 55, 48, 56, 48, 49, 49, 50, 48, 49, 49, 50, 46, 57, 57, 57, 90))
def testWithSubsecondsMin(self):
assert encoder.encode(
useful.GeneralizedTime('20170801120112.000Z')
) == ints2octs((24, 15, 50, 48, 49, 55, 48, 56, 48, 49, 49, 50, 48, 49, 49, 50, 90))
def testWithSubsecondsDanglingDot(self):
assert encoder.encode(
useful.GeneralizedTime('20170801120112.Z')
) == ints2octs((24, 15, 50, 48, 49, 55, 48, 56, 48, 49, 49, 50, 48, 49, 49, 50, 90))
def testWithSeconds(self):
assert encoder.encode(
useful.GeneralizedTime('20170801120112Z')
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment