Skip to content

Commit e5741d0

Browse files
committed
signed_certificate_timestamp extension from RFC 6962
1 parent 0358247 commit e5741d0

File tree

3 files changed

+159
-1
lines changed

3 files changed

+159
-1
lines changed

tlslite/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ class ExtensionType: # RFC 6066 / 4366
119119
srp = 12 # RFC 5054
120120
signature_algorithms = 13 # RFC 5246
121121
alpn = 16 # RFC 7301
122+
signed_certificate_timestamp = 18 # RFC 6962
122123
client_hello_padding = 21 # RFC 7685
123124
encrypt_then_mac = 22 # RFC 7366
124125
extended_master_secret = 23 # RFC 7627

tlslite/extensions.py

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

12501250

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

unit_tests/test_tlslite_extensions.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\
1313
TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\
1414
SignatureAlgorithmsExtension, PaddingExtension, VarListExtension, \
15-
RenegotiationInfoExtension, ALPNExtension
15+
RenegotiationInfoExtension, ALPNExtension, SCTExtension
1616
from tlslite.utils.codec import Parser
1717
from tlslite.constants import NameType, ExtensionType, GroupName,\
1818
ECPointFormat, HashAlgorithm, SignatureAlgorithm
@@ -1587,5 +1587,89 @@ def test_parse_from_TLSExtension(self):
15871587
bytearray(b'spdy/1')])
15881588

15891589

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

0 commit comments

Comments
 (0)