Skip to content

Commit c0211b0

Browse files
committed
signed_certificate_timestamp extension from RFC 6962
1 parent d69c210 commit c0211b0

File tree

3 files changed

+160
-1
lines changed

3 files changed

+160
-1
lines changed

tlslite/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class ExtensionType(TLSEnum):
129129
srp = 12 # RFC 5054
130130
signature_algorithms = 13 # RFC 5246
131131
alpn = 16 # RFC 7301
132+
signed_certificate_timestamp = 18 # RFC 6962
132133
client_hello_padding = 21 # RFC 7685
133134
encrypt_then_mac = 22 # RFC 7366
134135
extended_master_secret = 23 # RFC 7627

tlslite/extensions.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,78 @@ def parse(self, parser):
12491249
return self
12501250

12511251

1252+
class SCTExtension(TLSExtension):
1253+
"""
1254+
Client and Server Hello extension from Certificate Transparency.
1255+
1256+
Extension containing a list of serialised SignedCertificateTimestamp
1257+
objects.
1258+
1259+
See RFC 6962
1260+
"""
1261+
1262+
def __init__(self):
1263+
"""Create instance of class"""
1264+
extType = ExtensionType.signed_certificate_timestamp
1265+
super(SCTExtension, self).__init__(extType=extType)
1266+
self.sct_list = None
1267+
1268+
def create(self, sct_list):
1269+
"""
1270+
Set the list of signed certificate timestamps
1271+
1272+
@type sct_list: list of bytearrays
1273+
@param sct_list: list of serialised certificate time stamps
1274+
"""
1275+
self.sct_list = sct_list
1276+
return self
1277+
1278+
@property
1279+
def extData(self):
1280+
"""
1281+
Return raw encoding of the extension
1282+
1283+
@rtype: bytearray
1284+
"""
1285+
if self.sct_list is None:
1286+
return bytearray(0)
1287+
1288+
writer = Writer()
1289+
# elements have 2 byte header lengths
1290+
for sct in self.sct_list:
1291+
writer.add(len(sct), 2)
1292+
writer.bytes += sct
1293+
1294+
writer2 = Writer()
1295+
writer2.add(len(writer.bytes), 2)
1296+
return writer2.bytes + writer.bytes
1297+
1298+
def parse(self, parser):
1299+
"""
1300+
Deserialise extension from on the wire data.
1301+
1302+
@type parser: L{tlslite.util.codec.Parser}
1303+
@param parser: data to be parsed
1304+
1305+
@rtype: L{SCTExtension}
1306+
"""
1307+
if parser.getRemainingLength() == 0:
1308+
self.sct_list = None
1309+
return self
1310+
1311+
self.sct_list = []
1312+
1313+
parser.startLengthCheck(2)
1314+
while not parser.atLengthCheck():
1315+
self.sct_list.append(parser.getVarBytes(2))
1316+
parser.stopLengthCheck()
1317+
1318+
if parser.getRemainingLength() != 0:
1319+
raise SyntaxError("Trailing data in SCTExtension")
1320+
1321+
return self
1322+
1323+
12521324
class StatusRequestExtension(TLSExtension):
12531325
"""
12541326
Handling of the Certificate Status Request extension from RFC 6066.
@@ -1367,6 +1439,7 @@ def parse(self, parser):
13671439
ExtensionType.srp: SRPExtension,
13681440
ExtensionType.signature_algorithms: SignatureAlgorithmsExtension,
13691441
ExtensionType.alpn: ALPNExtension,
1442+
ExtensionType.signed_certificate_timestamp: SCTExtension,
13701443
ExtensionType.supports_npn: NPNExtension,
13711444
ExtensionType.client_hello_padding: PaddingExtension,
13721445
ExtensionType.renegotiation_info: RenegotiationInfoExtension}

unit_tests/test_tlslite_extensions.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\
1313
TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\
1414
SignatureAlgorithmsExtension, PaddingExtension, VarListExtension, \
15-
RenegotiationInfoExtension, ALPNExtension, StatusRequestExtension
15+
RenegotiationInfoExtension, ALPNExtension, StatusRequestExtension, \
16+
SCTExtension
1617
from tlslite.utils.codec import Parser
1718
from tlslite.constants import NameType, ExtensionType, GroupName,\
1819
ECPointFormat, HashAlgorithm, SignatureAlgorithm, \
@@ -1588,6 +1589,90 @@ def test_parse_from_TLSExtension(self):
15881589
bytearray(b'spdy/1')])
15891590

15901591

1592+
class TestSCTExtension(unittest.TestCase):
1593+
def setUp(self):
1594+
self.ext = SCTExtension()
1595+
1596+
def test___int__(self):
1597+
self.assertIsNotNone(self.ext)
1598+
self.assertEqual(self.ext.extType, 18)
1599+
self.assertEqual(self.ext.extData, bytearray())
1600+
self.assertIsNone(self.ext.sct_list)
1601+
1602+
def test_create(self):
1603+
ext2 = self.ext.create([bytearray(b'SCT number 1'),
1604+
bytearray(b'SCT number 2')])
1605+
1606+
self.assertIs(self.ext, ext2)
1607+
self.assertEqual(self.ext.sct_list, [bytearray(b'SCT number 1'),
1608+
bytearray(b'SCT number 2')])
1609+
1610+
def test_extData_with_empty_array(self):
1611+
self.ext.create([])
1612+
1613+
self.assertEqual(self.ext.extData, bytearray(b'\x00\x00'))
1614+
1615+
def test_extData_with_empty_SCTs(self):
1616+
self.ext.create([bytearray(), bytearray()])
1617+
1618+
self.assertEqual(self.ext.extData, bytearray(b'\x00\x04'
1619+
b'\x00\x00'
1620+
b'\x00\x00'))
1621+
1622+
def test_extData(self):
1623+
self.ext.create([bytearray(b'test'), bytearray(b'example')])
1624+
1625+
self.assertEqual(self.ext.extData, bytearray(b'\x00\x0f'
1626+
b'\x00\x04test'
1627+
b'\x00\x07example'))
1628+
1629+
def test_parse_with_empty_data(self):
1630+
parser = Parser(bytearray(b''))
1631+
1632+
ret = self.ext.parse(parser)
1633+
1634+
self.assertIs(ret, self.ext)
1635+
self.assertIsNone(self.ext.sct_list)
1636+
1637+
def test_parse_with_empty_array(self):
1638+
parser = Parser(bytearray(b'\x00\x00'))
1639+
1640+
ret = self.ext.parse(parser)
1641+
1642+
self.assertIs(ret, self.ext)
1643+
self.assertEqual(self.ext.sct_list, [])
1644+
1645+
def test_parse_with_empty_elements(self):
1646+
parser = Parser(bytearray(b'\x00\x04\x00\x00\x00\x00'))
1647+
1648+
self.ext.parse(parser)
1649+
1650+
self.assertEqual(self.ext.sct_list, [bytearray(), bytearray()])
1651+
1652+
def test_parse_with_value(self):
1653+
parser = Parser(bytearray(b'\x00\x06\x00\x04test'))
1654+
1655+
self.ext.parse(parser)
1656+
1657+
self.assertEqual(self.ext.sct_list, [bytearray(b'test')])
1658+
1659+
def test_parse_with_overflowing_data(self):
1660+
parser = Parser(bytearray(b'\x00\x00test'))
1661+
1662+
with self.assertRaises(SyntaxError):
1663+
self.ext.parse(parser)
1664+
1665+
def test_parse_from_TLSExtension(self):
1666+
ext = TLSExtension()
1667+
1668+
parser = Parser(bytearray(b'\x00\x12\x00\x08'
1669+
b'\x00\x06\x00\x04test'))
1670+
1671+
ret = ext.parse(parser)
1672+
self.assertIsInstance(ret, SCTExtension)
1673+
self.assertEqual(ret.sct_list, [bytearray(b'test')])
1674+
1675+
15911676
class TestStatusRequestExtension(unittest.TestCase):
15921677
def setUp(self):
15931678
self.ext = StatusRequestExtension()

0 commit comments

Comments
 (0)