Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

signed_certificate_timestamp extension from RFC 6962 #133

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tlslite/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class ExtensionType(TLSEnum):
srp = 12 # RFC 5054
signature_algorithms = 13 # RFC 5246
alpn = 16 # RFC 7301
signed_certificate_timestamp = 18 # RFC 6962
client_hello_padding = 21 # RFC 7685
encrypt_then_mac = 22 # RFC 7366
extended_master_secret = 23 # RFC 7627
Expand Down
73 changes: 73 additions & 0 deletions tlslite/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,78 @@ def parse(self, parser):
return self


class SCTExtension(TLSExtension):
"""
Client and Server Hello extension from Certificate Transparency.

Extension containing a list of serialised SignedCertificateTimestamp
objects.

See RFC 6962
"""

def __init__(self):
"""Create instance of class"""
extType = ExtensionType.signed_certificate_timestamp
super(SCTExtension, self).__init__(extType=extType)
self.sct_list = None

def create(self, sct_list):
"""
Set the list of signed certificate timestamps

@type sct_list: list of bytearrays
@param sct_list: list of serialised certificate time stamps
"""
self.sct_list = sct_list
return self

@property
def extData(self):
"""
Return raw encoding of the extension

@rtype: bytearray
"""
if self.sct_list is None:
return bytearray(0)

writer = Writer()
# elements have 2 byte header lengths
for sct in self.sct_list:
writer.add(len(sct), 2)
writer.bytes += sct

writer2 = Writer()
writer2.add(len(writer.bytes), 2)
return writer2.bytes + writer.bytes

def parse(self, parser):
"""
Deserialise extension from on the wire data.

@type parser: L{tlslite.util.codec.Parser}
@param parser: data to be parsed

@rtype: L{SCTExtension}
"""
if parser.getRemainingLength() == 0:
self.sct_list = None
return self

self.sct_list = []

parser.startLengthCheck(2)
while not parser.atLengthCheck():
self.sct_list.append(parser.getVarBytes(2))
parser.stopLengthCheck()

if parser.getRemainingLength() != 0:
raise SyntaxError("Trailing data in SCTExtension")

return self


class StatusRequestExtension(TLSExtension):
"""
Handling of the Certificate Status Request extension from RFC 6066.
Expand Down Expand Up @@ -1367,6 +1439,7 @@ def parse(self, parser):
ExtensionType.srp: SRPExtension,
ExtensionType.signature_algorithms: SignatureAlgorithmsExtension,
ExtensionType.alpn: ALPNExtension,
ExtensionType.signed_certificate_timestamp: SCTExtension,
ExtensionType.supports_npn: NPNExtension,
ExtensionType.client_hello_padding: PaddingExtension,
ExtensionType.renegotiation_info: RenegotiationInfoExtension}
Expand Down
2 changes: 2 additions & 0 deletions tlslite/handshakesettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ def __init__(self):
self.usePaddingExtension = True
self.useExtendedMasterSecret = True
self.requireExtendedMasterSecret = False
self.signedCertificateTimestamp = None

@staticmethod
def _sanityCheckKeySizes(other):
Expand Down Expand Up @@ -267,6 +268,7 @@ def validate(self):
other.eccCurves = self.eccCurves
other.useExtendedMasterSecret = self.useExtendedMasterSecret
other.requireExtendedMasterSecret = self.requireExtendedMasterSecret
other.signedCertificateTimestamp = self.signedCertificateTimestamp

if not cipherfactory.tripleDESPresent:
other.cipherNames = [i for i in self.cipherNames if i != "3des"]
Expand Down
87 changes: 86 additions & 1 deletion unit_tests/test_tlslite_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\
TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\
SignatureAlgorithmsExtension, PaddingExtension, VarListExtension, \
RenegotiationInfoExtension, ALPNExtension, StatusRequestExtension
RenegotiationInfoExtension, ALPNExtension, StatusRequestExtension, \
SCTExtension
from tlslite.utils.codec import Parser
from tlslite.constants import NameType, ExtensionType, GroupName,\
ECPointFormat, HashAlgorithm, SignatureAlgorithm, \
Expand Down Expand Up @@ -1588,6 +1589,90 @@ def test_parse_from_TLSExtension(self):
bytearray(b'spdy/1')])


class TestSCTExtension(unittest.TestCase):
def setUp(self):
self.ext = SCTExtension()

def test___int__(self):
self.assertIsNotNone(self.ext)
self.assertEqual(self.ext.extType, 18)
self.assertEqual(self.ext.extData, bytearray())
self.assertIsNone(self.ext.sct_list)

def test_create(self):
ext2 = self.ext.create([bytearray(b'SCT number 1'),
bytearray(b'SCT number 2')])

self.assertIs(self.ext, ext2)
self.assertEqual(self.ext.sct_list, [bytearray(b'SCT number 1'),
bytearray(b'SCT number 2')])

def test_extData_with_empty_array(self):
self.ext.create([])

self.assertEqual(self.ext.extData, bytearray(b'\x00\x00'))

def test_extData_with_empty_SCTs(self):
self.ext.create([bytearray(), bytearray()])

self.assertEqual(self.ext.extData, bytearray(b'\x00\x04'
b'\x00\x00'
b'\x00\x00'))

def test_extData(self):
self.ext.create([bytearray(b'test'), bytearray(b'example')])

self.assertEqual(self.ext.extData, bytearray(b'\x00\x0f'
b'\x00\x04test'
b'\x00\x07example'))

def test_parse_with_empty_data(self):
parser = Parser(bytearray(b''))

ret = self.ext.parse(parser)

self.assertIs(ret, self.ext)
self.assertIsNone(self.ext.sct_list)

def test_parse_with_empty_array(self):
parser = Parser(bytearray(b'\x00\x00'))

ret = self.ext.parse(parser)

self.assertIs(ret, self.ext)
self.assertEqual(self.ext.sct_list, [])

def test_parse_with_empty_elements(self):
parser = Parser(bytearray(b'\x00\x04\x00\x00\x00\x00'))

self.ext.parse(parser)

self.assertEqual(self.ext.sct_list, [bytearray(), bytearray()])

def test_parse_with_value(self):
parser = Parser(bytearray(b'\x00\x06\x00\x04test'))

self.ext.parse(parser)

self.assertEqual(self.ext.sct_list, [bytearray(b'test')])

def test_parse_with_overflowing_data(self):
parser = Parser(bytearray(b'\x00\x00test'))

with self.assertRaises(SyntaxError):
self.ext.parse(parser)

def test_parse_from_TLSExtension(self):
ext = TLSExtension()

parser = Parser(bytearray(b'\x00\x12\x00\x08'
b'\x00\x06\x00\x04test'))

ret = ext.parse(parser)
self.assertIsInstance(ret, SCTExtension)
self.assertEqual(ret.sct_list, [bytearray(b'test')])


class TestStatusRequestExtension(unittest.TestCase):
def setUp(self):
self.ext = StatusRequestExtension()
Expand Down