class ProtocolVersion(Enum): elem_t = Uint16 SSL3 = Uint16(0x0300) TLS10 = Uint16(0x0301) TLS11 = Uint16(0x0302) TLS12 = Uint16(0x0303) TLS13 = Uint16(0x0304)
class TLSCiphertext(meta.StructMeta): opaque_type: ContentType = ContentType.application_data legacy_record_version: ProtocolVersion = ProtocolVersion(0x0303) length: Uint16 = lambda self: Uint16(len(bytes(self.encrypted_record))) encrypted_record: OpaqueLength def decrypt(self, cipher_instance): encrypted_record = self.encrypted_record.get_raw_bytes() aad = bytes.fromhex('170303') + bytes(Uint16(len(encrypted_record))) plaindata = cipher_instance.decrypt_and_verify(encrypted_record, aad) plaindata, content_type = TLSInnerPlaintext.split_pad(plaindata) # print(hexdump(bytes(plaindata))) if content_type == ContentType.application_data: return self._decrypt_app_data(plaindata, content_type) return TLSPlaintext(type=content_type, fragment=OpaqueLength(bytes(plaindata))) # Application Data を受信したとき def _decrypt_app_data(self, plaindata, content_type): # NewSessionTicketを受け取った場合 if plaindata[:2] == bytes(HandshakeType.new_session_ticket) + b'\x00': return TLSPlaintext(type=content_type, fragment=OpaqueLength(bytes(plaindata))) # それ以外は通信データ else: return TLSPlaintext(type=content_type, length=len(plaindata), fragment=OpaqueLength(bytes(plaindata)))
def test_structmeta_keep_rest_bytes(self): import io OpaqueUint8 = Opaque(size_t=Uint8) ListUint8OpaqueUint8 = List(size_t=Uint8, elem_t=Opaque(size_t=Uint8)) @struct class Sample1(StructMeta): fieldA: Uint16 fieldB: OpaqueUint8 fieldC: ListUint8OpaqueUint8 s = Sample1(fieldA=Uint16(0x1), fieldB=OpaqueUint8(b'\xff'), fieldC=ListUint8OpaqueUint8( [OpaqueUint8(b'\xaa'), OpaqueUint8(b'\xbb')])) deadbeef = bytes.fromhex('deadbeef') fs = io.BytesIO(bytes(s) + deadbeef) s2 = Sample1.from_fs(fs) rest = fs.read() self.assertEqual(rest, deadbeef)
def test_structmeta(self): OpaqueUint8 = Opaque(size_t=Uint8) ListUint8OpaqueUint8 = List(size_t=Uint8, elem_t=Opaque(size_t=Uint8)) @struct class Sample1(StructMeta): fieldA: Uint16 fieldB: OpaqueUint8 fieldC: ListUint8OpaqueUint8 s = Sample1(fieldA=Uint16(0x1), fieldB=OpaqueUint8(b'\xff'), fieldC=ListUint8OpaqueUint8( [OpaqueUint8(b'\xaa'), OpaqueUint8(b'\xbb')])) self.assertTrue(hasattr(s, 'fieldA')) self.assertTrue(isinstance(s.fieldA, Uint16)) self.assertTrue(hasattr(s, 'fieldB')) self.assertTrue(isinstance(s.fieldB, OpaqueUint8)) self.assertTrue(hasattr(s, 'fieldC')) self.assertTrue(isinstance(s.fieldC, ListUint8OpaqueUint8)) self.assertEqual(bytes(s), b'\x00\x01\x01\xff\x04\x01\xaa\x01\xbb') self.assertEqual(Sample1.from_bytes(bytes(s)), s)
def test_structmeta_multiple_parents(self): @struct class Sample1(StructMeta): child_field: Select('Sample3.parent_fieldA', cases={ Uint8(0xaa): Uint8, Uint8(0xbb): Uint16, }) @struct class Sample2(StructMeta): parent_fieldB: Uint8 fragment: Sample1 @struct class Sample3(StructMeta): parent_fieldA: Uint8 fragment: Sample2 s = Sample3(parent_fieldA=Uint8(0xbb), fragment=Sample2( parent_fieldB=Uint8(0x12), fragment=Sample1(child_field=Uint16(0x0101)))) s_byte = bytes.fromhex('bb 12 0101') self.assertEqual(bytes(s), s_byte) self.assertEqual(Sample3.from_bytes(bytes(s)), s)
def test_structmeta_parent(self): @struct class Sample1(StructMeta): child_field: Select('Sample2.parent_field', cases={ Uint8(0xaa): Uint8, Uint8(0xbb): Uint16, }) @struct class Sample2(StructMeta): parent_field: Uint8 fragment: Sample1 s1 = Sample2(parent_field=Uint8(0xaa), fragment=Sample1(child_field=Uint8(0xff))) s1_byte = bytes.fromhex('aa ff') s2 = Sample2(parent_field=Uint8(0xbb), fragment=Sample1(child_field=Uint16(0xffff))) s2_byte = bytes.fromhex('bb ffff') self.assertEqual(bytes(s1), s1_byte) self.assertEqual(bytes(s2), s2_byte) self.assertEqual(Sample2.from_bytes(bytes(s1)), s1) self.assertEqual(Sample2.from_bytes(bytes(s2)), s2)
def encrypt(self, cipher_instance): msg_pad = TLSInnerPlaintext.append_pad(self) tag_size = cipher_instance.__class__.tag_size aad = bytes.fromhex('170303') + bytes( Uint16(len(bytes(msg_pad)) + tag_size)) encrypted_record = cipher_instance.encrypt_and_tag(msg_pad, aad) return TLSCiphertext( encrypted_record=OpaqueLength(bytes(encrypted_record)))
class NamedGroup(Enum): elem_t = Uint16 # Elliptic Curve Groups (ECDHE) #obsolete_RESERVED = Uint16(0x0001)..Uint16(0x0016) secp256r1 = Uint16(0x0017) secp384r1 = Uint16(0x0018) secp521r1 = Uint16(0x0019) #obsolete_RESERVED = Uint16(0x001A)..Uint16(0x001C) x25519 = Uint16(0x001D) x448 = Uint16(0x001E) # Finite Field Groups (DHE) # https://tools.ietf.org/html/rfc7919#appendix-A ffdhe2048 = Uint16(0x0100) ffdhe3072 = Uint16(0x0101) ffdhe4096 = Uint16(0x0102) ffdhe6144 = Uint16(0x0103) ffdhe8192 = Uint16(0x0104)
def test_structmeta_recursive(self): @struct class Sample1(StructMeta): fieldC: Uint16 fieldD: Uint16 @struct class Sample2(StructMeta): fieldA: Uint16 fieldB: Sample1 s = Sample2(fieldA=Uint16(0xaaaa), fieldB=Sample1(fieldC=Uint16(0xbbbb), fieldD=Uint16(0xcccc))) self.assertTrue(isinstance(s.fieldB, Sample1)) self.assertTrue(isinstance(s.fieldB.fieldC, Uint16)) self.assertEqual(bytes(s), b'\xaa\xaa\xbb\xbb\xcc\xcc') self.assertEqual(Sample2.from_bytes(bytes(s)), s)
def decrypt(self, cipher_instance): encrypted_record = self.encrypted_record.get_raw_bytes() aad = bytes.fromhex('170303') + bytes(Uint16(len(encrypted_record))) plaindata = cipher_instance.decrypt_and_verify(encrypted_record, aad) plaindata, content_type = TLSInnerPlaintext.split_pad(plaindata) # print(hexdump(bytes(plaindata))) if content_type == ContentType.application_data: return self._decrypt_app_data(plaindata, content_type) return TLSPlaintext(type=content_type, fragment=OpaqueLength(bytes(plaindata)))
class CipherSuite(Enum): elem_t = Uint16 TLS_AES_128_GCM_SHA256 = Uint16(0x1301) TLS_AES_256_GCM_SHA384 = Uint16(0x1302) TLS_CHACHA20_POLY1305_SHA256 = Uint16(0x1303) TLS_AES_128_CCM_SHA256 = Uint16(0x1304) TLS_AES_128_CCM_8_SHA256 = Uint16(0x1305) TLS_EMPTY_RENEGOTIATION_INFO_SCSV = Uint16(0x00FF) @classmethod def get_cipher_class(cls, cipher_suite): ciphercuite2class = { CipherSuite.TLS_AES_128_GCM_SHA256: None, CipherSuite.TLS_AES_256_GCM_SHA384: None, CipherSuite.TLS_CHACHA20_POLY1305_SHA256: Chacha20Poly1305, CipherSuite.TLS_AES_128_CCM_SHA256: None, CipherSuite.TLS_AES_128_CCM_8_SHA256: None, } return ciphercuite2class.get(cipher_suite) @classmethod def get_hash_name(cls, cipher_suite): if cipher_suite == CipherSuite.TLS_AES_256_GCM_SHA384: return 'sha384' else: return 'sha256' @classmethod def get_hash_size(cls, cipher_suite): if cipher_suite == CipherSuite.TLS_AES_256_GCM_SHA384: return 48 else: return 32
class Extension(meta.StructMeta): extension_type: ExtensionType length: Uint16 = lambda self: Uint16(len(bytes(self.extension_data))) extension_data: meta.Select('extension_type', cases={ ExtensionType.supported_versions: SupportedVersions, ExtensionType.supported_groups: NamedGroupList, ExtensionType.key_share: KeyShareHello, ExtensionType.signature_algorithms: SignatureSchemeList, meta.Otherwise: OpaqueLength, })
def test_structmeta_has_parent_ref(self): @struct class Sample1(StructMeta): child_field: Select('Sample3.parent_fieldA', cases={ Uint8(0xaa): Uint8, Uint8(0xbb): Uint16, }) @struct class Sample2(StructMeta): parent_fieldB: Uint8 fragment: Sample1 Sample2s = List(size_t=Uint8, elem_t=Sample2) @struct class Sample3(StructMeta): parent_fieldA: Uint8 fragment: Sample2s s = Sample3(parent_fieldA=Uint8(0xbb), fragment=Sample2s([ Sample2( parent_fieldB=Uint8(0x12), fragment=Sample1(child_field=Uint16(0x0101))) ])) # コンストラクタで構造体を構築した場合 target = s.fragment.get_array()[0].fragment # 最下の子インスタンス self.assertTrue(isinstance(target, Sample1)) self.assertTrue(isinstance(target.parent, Sample2)) self.assertTrue(isinstance(target.parent.parent, Sample3)) # バイト列から構造体を構築した場合 s2 = Sample3.from_bytes(bytes(s)) target = s.fragment.get_array()[0].fragment # 最下の子インスタンス self.assertTrue(isinstance(target, Sample1)) self.assertTrue(isinstance(target.parent, Sample2)) self.assertTrue(isinstance(target.parent.parent, Sample3))
def test_structmeta_select(self): @struct class Sample1(StructMeta): field: Uint16 @struct class Sample2(StructMeta): type: Uint8 fragment: Select('type', cases={ Uint8(0xaa): Opaque(0), Uint8(0xbb): Sample1, }) s1 = Sample2(type=Uint8(0xaa), fragment=Opaque(0)(b'')) self.assertEqual(bytes(s1), bytes.fromhex('aa')) self.assertEqual(Sample2.from_bytes(bytes(s1)), s1) s2 = Sample2(type=Uint8(0xbb), fragment=Sample1(field=Uint16(0x1212))) self.assertEqual(bytes(s2), bytes.fromhex('bb 1212')) self.assertEqual(Sample2.from_bytes(bytes(s2)), s2)
class TLSPlaintext(meta.StructMeta): type: ContentType legacy_record_version: ProtocolVersion = ProtocolVersion(0x0303) length: Uint16 = lambda self: Uint16(len(bytes(self.fragment))) fragment: OpaqueLength @classmethod def create(self, content_type, *messages): assert isinstance(content_type, ContentType) messages_byte = b''.join(bytes(msg) for msg in messages) return TLSPlaintext(type=content_type, fragment=OpaqueLength(messages_byte)) def encrypt(self, cipher_instance): msg_pad = TLSInnerPlaintext.append_pad(self) tag_size = cipher_instance.__class__.tag_size aad = bytes.fromhex('170303') + bytes( Uint16(len(bytes(msg_pad)) + tag_size)) encrypted_record = cipher_instance.encrypt_and_tag(msg_pad, aad) return TLSCiphertext( encrypted_record=OpaqueLength(bytes(encrypted_record))) def get_messages(self): contenttype2class = { ContentType.handshake: Handshake, ContentType.change_cipher_spec: OpaqueLength, ContentType.alert: Alert, } elem_t = contenttype2class.get(self.type) # 複数のHandshakeメッセージは結合して一つのTLSPlaintextで送ることができる # https://tools.ietf.org/html/rfc8446#section-5.1 messages = [] stream_len = len(self.fragment.get_raw_bytes()) stream = io.BytesIO(self.fragment.get_raw_bytes()) while stream.tell() < stream_len: messages.append(elem_t.from_fs(stream)) return messages
def HKDF_expand_label(secret, label, hash_value, length, hash_name='sha256') -> bytearray: # HKDF-Expand-Label (https://tools.ietf.org/html/rfc8446#section-7.1) # # HKDF-Expand-Label(Secret, Label, Context, Length) = # HKDF-Expand(Secret, HkdfLabel, Length) # # Where HkdfLabel is specified as: # # struct { # uint16 length = Length; # opaque label<7..255> = "tls13 " + Label; # opaque context<0..255> = Context; # } HkdfLabel; # hkdf_label = b'' hkdf_label += bytes(Uint16(length)) hkdf_label += bytes(OpaqueUint8(b'tls13 ' + label)) hkdf_label += bytes(OpaqueUint8(hash_value)) # print('- [-] hkdf_label:', hkdf_label.hex()) out = HKDF_expand(secret, hkdf_label, length, hash_name) # print('- [-] out:', out.hex()) return out
class SignatureScheme(Enum): elem_t = Uint16 # RSASSA-PKCS1-v1_5 algorithms rsa_pkcs1_sha256 = Uint16(0x0401) rsa_pkcs1_sha384 = Uint16(0x0501) rsa_pkcs1_sha512 = Uint16(0x0601) # ECDSA algorithms ecdsa_secp256r1_sha256 = Uint16(0x0403) ecdsa_secp384r1_sha384 = Uint16(0x0503) ecdsa_secp512r1_sha512 = Uint16(0x0603) # RSASSA-PSS algorithms with public key OID rsaEncryption rsa_pss_rsae_sha256 = Uint16(0x0804) rsa_pss_rsae_sha384 = Uint16(0x0805) rsa_pss_rsae_sha512 = Uint16(0x0806) # EdDSA algorithms ed25519 = Uint16(0x0807) ed448 = Uint16(0x0808) # RSASSA-PSS algorithms with public key OID RSASSA-PSS rsa_pss_pss_sha256 = Uint16(0x0809) rsa_pss_pss_sha384 = Uint16(0x080a) rsa_pss_pss_sha512 = Uint16(0x080b) # Legacy algorithms rsa_pkcs1_sha1 = Uint16(0x0201) ecdsa_sha1 = Uint16(0x0203)
class ExtensionType(EnumUnknown): elem_t = Uint16 server_name = Uint16(0) max_fragment_length = Uint16(1) status_request = Uint16(5) supported_groups = Uint16(10) signature_algorithms = Uint16(13) use_srtp = Uint16(14) heartbeat = Uint16(15) application_layer_protocol_negotiation = Uint16(16) signed_certificate_timestamp = Uint16(18) client_certificate_type = Uint16(19) server_certificate_type = Uint16(20) padding = Uint16(21) #RESERVED = Uint16(40) pre_shared_key = Uint16(41) early_data = Uint16(42) supported_versions = Uint16(43) cookie = Uint16(44) psk_key_exchange_modes = Uint16(45) #RESERVED = Uint16(46) certificate_authorities = Uint16(47) oid_filters = Uint16(48) post_handshake_auth = Uint16(49) signature_algorithms_cert = Uint16(50) key_share = Uint16(51)