esp32_bluetooth_classic_sni.../libs/scapy/layers/tls/keyexchange.py
Matheus Eduardo Garbelini 86890704fd initial commit
todo: add documentation & wireshark dissector
2021-08-31 19:51:03 +08:00

968 lines
37 KiB
Python
Executable file

# This file is part of Scapy
# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard
# 2015, 2016, 2017 Maxence Tury
# 2019 Romain Perez
# This program is published under a GPLv2 license
"""
TLS key exchange logic.
"""
from __future__ import absolute_import
import math
import struct
from scapy.config import conf, crypto_validator
from scapy.error import warning
from scapy.fields import ByteEnumField, ByteField, EnumField, FieldLenField, \
FieldListField, PacketField, ShortEnumField, ShortField, \
StrFixedLenField, StrLenField
from scapy.compat import orb
from scapy.packet import Packet, Raw, Padding
from scapy.layers.tls.cert import PubKeyRSA, PrivKeyRSA
from scapy.layers.tls.session import _GenericTLSSessionInheritance
from scapy.layers.tls.basefields import _tls_version, _TLSClientVersionField
from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp, pkcs_os2ip
from scapy.layers.tls.crypto.groups import _ffdh_groups, _tls_named_curves
import scapy.modules.six as six
if conf.crypto_valid:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import dh, ec
###############################################################################
# Common Fields #
###############################################################################
_tls_hash_sig = {0x0000: "none+anon", 0x0001: "none+rsa",
0x0002: "none+dsa", 0x0003: "none+ecdsa",
0x0100: "md5+anon", 0x0101: "md5+rsa",
0x0102: "md5+dsa", 0x0103: "md5+ecdsa",
0x0200: "sha1+anon", 0x0201: "sha1+rsa",
0x0202: "sha1+dsa", 0x0203: "sha1+ecdsa",
0x0300: "sha224+anon", 0x0301: "sha224+rsa",
0x0302: "sha224+dsa", 0x0303: "sha224+ecdsa",
0x0400: "sha256+anon", 0x0401: "sha256+rsa",
0x0402: "sha256+dsa", 0x0403: "sha256+ecdsa",
0x0500: "sha384+anon", 0x0501: "sha384+rsa",
0x0502: "sha384+dsa", 0x0503: "sha384+ecdsa",
0x0600: "sha512+anon", 0x0601: "sha512+rsa",
0x0602: "sha512+dsa", 0x0603: "sha512+ecdsa",
0x0804: "sha256+rsaepss", 0x0805: "sha384+rsaepss",
0x0806: "sha512+rsaepss", 0x0807: "ed25519",
0x0808: "ed448", 0x0809: "sha256+rsapss",
0x080a: "sha384+rsapss", 0x080b: "sha512+rsapss"}
def phantom_mode(pkt):
"""
We expect this. If tls_version is not set, this means we did not process
any complete ClientHello, so we're most probably reading/building a
signature_algorithms extension, hence we cannot be in phantom_mode.
However, if the tls_version has been set, we test for TLS 1.2.
"""
if not pkt.tls_session:
return False
if not pkt.tls_session.tls_version:
return False
return pkt.tls_session.tls_version < 0x0303
def phantom_decorate(f, get_or_add):
"""
Decorator for version-dependent fields.
If get_or_add is True (means get), we return s, self.phantom_value.
If it is False (means add), we return s.
"""
def wrapper(*args):
self, pkt, s = args[:3]
if phantom_mode(pkt):
if get_or_add:
return s, self.phantom_value
return s
return f(*args)
return wrapper
class SigAndHashAlgField(EnumField):
"""Used in _TLSSignature."""
phantom_value = None
getfield = phantom_decorate(EnumField.getfield, True)
addfield = phantom_decorate(EnumField.addfield, False)
class SigAndHashAlgsLenField(FieldLenField):
"""Used in TLS_Ext_SignatureAlgorithms and TLSCertificateResquest."""
phantom_value = 0
getfield = phantom_decorate(FieldLenField.getfield, True)
addfield = phantom_decorate(FieldLenField.addfield, False)
class SigAndHashAlgsField(FieldListField):
"""Used in TLS_Ext_SignatureAlgorithms and TLSCertificateResquest."""
phantom_value = []
getfield = phantom_decorate(FieldListField.getfield, True)
addfield = phantom_decorate(FieldListField.addfield, False)
class SigLenField(FieldLenField):
"""There is a trick for SSLv2, which uses implicit lengths..."""
def getfield(self, pkt, s):
v = pkt.tls_session.tls_version
if v and v < 0x0300:
return s, None
return super(SigLenField, self).getfield(pkt, s)
def addfield(self, pkt, s, val):
"""With SSLv2 you will never be able to add a sig_len."""
v = pkt.tls_session.tls_version
if v and v < 0x0300:
return s
return super(SigLenField, self).addfield(pkt, s, val)
class SigValField(StrLenField):
"""There is a trick for SSLv2, which uses implicit lengths..."""
def getfield(self, pkt, m):
s = pkt.tls_session
if s.tls_version and s.tls_version < 0x0300:
if len(s.client_certs) > 0:
sig_len = s.client_certs[0].pubKey.pubkey.key_size // 8
else:
warning("No client certificate provided. "
"We're making a wild guess about the signature size.")
sig_len = 256
return m[sig_len:], self.m2i(pkt, m[:sig_len])
return super(SigValField, self).getfield(pkt, m)
class _TLSSignature(_GenericTLSSessionInheritance):
"""
Prior to TLS 1.2, digitally-signed structure implicitly used the
concatenation of a MD5 hash and a SHA-1 hash.
Then TLS 1.2 introduced explicit SignatureAndHashAlgorithms,
i.e. couples of (hash_alg, sig_alg). See RFC 5246, section 7.4.1.4.1.
By default, the _TLSSignature implements the TLS 1.2 scheme,
but if it is provided a TLS context with a tls_version < 0x0303
at initialization, it will fall back to the implicit signature.
Even more, the 'sig_len' field won't be used with SSLv2.
#XXX 'sig_alg' should be set in __init__ depending on the context.
"""
name = "TLS Digital Signature"
fields_desc = [SigAndHashAlgField("sig_alg", 0x0804, _tls_hash_sig),
SigLenField("sig_len", None, fmt="!H",
length_of="sig_val"),
SigValField("sig_val", None,
length_from=lambda pkt: pkt.sig_len)]
def __init__(self, *args, **kargs):
super(_TLSSignature, self).__init__(*args, **kargs)
if (self.tls_session and
self.tls_session.tls_version):
if self.tls_session.tls_version < 0x0303:
self.sig_alg = None
elif self.tls_session.tls_version == 0x0304:
# For TLS 1.3 signatures, set the signature
# algorithm to RSA-PSS
self.sig_alg = 0x0804
def _update_sig(self, m, key):
"""
Sign 'm' with the PrivKey 'key' and update our own 'sig_val'.
Note that, even when 'sig_alg' is not None, we use the signature scheme
of the PrivKey (neither do we care to compare the both of them).
"""
if self.sig_alg is None:
if self.tls_session.tls_version >= 0x0300:
self.sig_val = key.sign(m, t='pkcs', h='md5-sha1')
else:
self.sig_val = key.sign(m, t='pkcs', h='md5')
else:
h, sig = _tls_hash_sig[self.sig_alg].split('+')
if sig.endswith('pss'):
t = "pss"
else:
t = "pkcs"
self.sig_val = key.sign(m, t=t, h=h)
def _verify_sig(self, m, cert):
"""
Verify that our own 'sig_val' carries the signature of 'm' by the
key associated to the Cert 'cert'.
"""
if self.sig_val:
if self.sig_alg:
h, sig = _tls_hash_sig[self.sig_alg].split('+')
if sig.endswith('pss'):
t = "pss"
else:
t = "pkcs"
return cert.verify(m, self.sig_val, t=t, h=h)
else:
if self.tls_session.tls_version >= 0x0300:
return cert.verify(m, self.sig_val, t='pkcs', h='md5-sha1')
else:
return cert.verify(m, self.sig_val, t='pkcs', h='md5')
return False
def guess_payload_class(self, p):
return Padding
class _TLSSignatureField(PacketField):
"""
Used for 'digitally-signed struct' in several ServerKeyExchange,
and also in CertificateVerify. We can handle the anonymous case.
"""
__slots__ = ["length_from"]
def __init__(self, name, default, length_from=None, remain=0):
self.length_from = length_from
PacketField.__init__(self, name, default, _TLSSignature, remain=remain)
def m2i(self, pkt, m):
tmp_len = self.length_from(pkt)
if tmp_len == 0:
return None
return _TLSSignature(m, tls_session=pkt.tls_session)
def getfield(self, pkt, s):
i = self.m2i(pkt, s)
if i is None:
return s, None
remain = b""
if conf.padding_layer in i:
r = i[conf.padding_layer]
del r.underlayer.payload
remain = r.load
return remain, i
class _TLSServerParamsField(PacketField):
"""
This is a dispatcher for the Server*DHParams below, used in
TLSServerKeyExchange and based on the key_exchange.server_kx_msg_cls.
When this cls is None, it means that we should not see a ServerKeyExchange,
so we grab everything within length_from and make it available using Raw.
When the context has not been set (e.g. when no ServerHello was parsed or
dissected beforehand), we (kinda) clumsily set the cls by trial and error.
XXX We could use Serv*DHParams.check_params() once it has been implemented.
"""
__slots__ = ["length_from"]
def __init__(self, name, default, length_from=None, remain=0):
self.length_from = length_from
PacketField.__init__(self, name, default, None, remain=remain)
def m2i(self, pkt, m):
s = pkt.tls_session
tmp_len = self.length_from(pkt)
if s.prcs:
cls = s.prcs.key_exchange.server_kx_msg_cls(m)
if cls is None:
return None, Raw(m[:tmp_len]) / Padding(m[tmp_len:])
return cls(m, tls_session=s)
else:
try:
p = ServerDHParams(m, tls_session=s)
if pkcs_os2ip(p.load[:2]) not in _tls_hash_sig:
raise Exception
return p
except Exception:
cls = _tls_server_ecdh_cls_guess(m)
p = cls(m, tls_session=s)
if pkcs_os2ip(p.load[:2]) not in _tls_hash_sig:
return None, Raw(m[:tmp_len]) / Padding(m[tmp_len:])
return p
###############################################################################
# Server Key Exchange parameters & value #
###############################################################################
# Finite Field Diffie-Hellman
class ServerDHParams(_GenericTLSSessionInheritance):
"""
ServerDHParams for FFDH-based key exchanges, as defined in RFC 5246/7.4.3.
Either with .fill_missing() or .post_dissection(), the server_kx_privkey or
server_kx_pubkey of the TLS context are updated according to the
parsed/assembled values. It is the user's responsibility to store and
restore the original values if he wants to keep them. For instance, this
could be done between the writing of a ServerKeyExchange and the receiving
of a ClientKeyExchange (which includes secret generation).
"""
name = "Server FFDH parameters"
fields_desc = [FieldLenField("dh_plen", None, length_of="dh_p"),
StrLenField("dh_p", "",
length_from=lambda pkt: pkt.dh_plen),
FieldLenField("dh_glen", None, length_of="dh_g"),
StrLenField("dh_g", "",
length_from=lambda pkt: pkt.dh_glen),
FieldLenField("dh_Yslen", None, length_of="dh_Ys"),
StrLenField("dh_Ys", "",
length_from=lambda pkt: pkt.dh_Yslen)]
@crypto_validator
def fill_missing(self):
"""
We do not want TLSServerKeyExchange.build() to overload and recompute
things every time it is called. This method can be called specifically
to have things filled in a smart fashion.
Note that we do not expect default_params.g to be more than 0xff.
"""
s = self.tls_session
default_params = _ffdh_groups['modp2048'][0].parameter_numbers()
default_mLen = _ffdh_groups['modp2048'][1]
if not self.dh_p:
self.dh_p = pkcs_i2osp(default_params.p, default_mLen // 8)
if self.dh_plen is None:
self.dh_plen = len(self.dh_p)
if not self.dh_g:
self.dh_g = pkcs_i2osp(default_params.g, 1)
if self.dh_glen is None:
self.dh_glen = 1
p = pkcs_os2ip(self.dh_p)
g = pkcs_os2ip(self.dh_g)
real_params = dh.DHParameterNumbers(p, g).parameters(default_backend())
if not self.dh_Ys:
s.server_kx_privkey = real_params.generate_private_key()
pubkey = s.server_kx_privkey.public_key()
y = pubkey.public_numbers().y
self.dh_Ys = pkcs_i2osp(y, pubkey.key_size // 8)
# else, we assume that the user wrote the server_kx_privkey by himself
if self.dh_Yslen is None:
self.dh_Yslen = len(self.dh_Ys)
if not s.client_kx_ffdh_params:
s.client_kx_ffdh_params = real_params
@crypto_validator
def register_pubkey(self):
"""
XXX Check that the pubkey received is in the group.
"""
p = pkcs_os2ip(self.dh_p)
g = pkcs_os2ip(self.dh_g)
pn = dh.DHParameterNumbers(p, g)
y = pkcs_os2ip(self.dh_Ys)
public_numbers = dh.DHPublicNumbers(y, pn)
s = self.tls_session
s.server_kx_pubkey = public_numbers.public_key(default_backend())
if not s.client_kx_ffdh_params:
s.client_kx_ffdh_params = pn.parameters(default_backend())
def post_dissection(self, r):
try:
self.register_pubkey()
except ImportError:
pass
def guess_payload_class(self, p):
"""
The signature after the params gets saved as Padding.
This way, the .getfield() which _TLSServerParamsField inherits
from PacketField will return the signature remain as expected.
"""
return Padding
# Elliptic Curve Diffie-Hellman
_tls_ec_curve_types = {1: "explicit_prime",
2: "explicit_char2",
3: "named_curve"}
_tls_ec_basis_types = {0: "ec_basis_trinomial", 1: "ec_basis_pentanomial"}
class ECCurvePkt(Packet):
name = "Elliptic Curve"
fields_desc = [FieldLenField("alen", None, length_of="a", fmt="B"),
StrLenField("a", "", length_from=lambda pkt: pkt.alen),
FieldLenField("blen", None, length_of="b", fmt="B"),
StrLenField("b", "", length_from=lambda pkt: pkt.blen)]
# Char2 Curves
class ECTrinomialBasis(Packet):
name = "EC Trinomial Basis"
val = 0
fields_desc = [FieldLenField("klen", None, length_of="k", fmt="B"),
StrLenField("k", "", length_from=lambda pkt: pkt.klen)]
def guess_payload_class(self, p):
return Padding
class ECPentanomialBasis(Packet):
name = "EC Pentanomial Basis"
val = 1
fields_desc = [FieldLenField("k1len", None, length_of="k1", fmt="B"),
StrLenField("k1", "", length_from=lambda pkt: pkt.k1len),
FieldLenField("k2len", None, length_of="k2", fmt="B"),
StrLenField("k2", "", length_from=lambda pkt: pkt.k2len),
FieldLenField("k3len", None, length_of="k3", fmt="B"),
StrLenField("k3", "", length_from=lambda pkt: pkt.k3len)]
def guess_payload_class(self, p):
return Padding
_tls_ec_basis_cls = {0: ECTrinomialBasis, 1: ECPentanomialBasis}
class _ECBasisTypeField(ByteEnumField):
__slots__ = ["basis_type_of"]
def __init__(self, name, default, enum, basis_type_of, remain=0):
self.basis_type_of = basis_type_of
EnumField.__init__(self, name, default, enum, "B")
def i2m(self, pkt, x):
if x is None:
fld, fval = pkt.getfield_and_val(self.basis_type_of)
x = fld.i2basis_type(pkt, fval)
return x
class _ECBasisField(PacketField):
__slots__ = ["clsdict", "basis_type_from"]
def __init__(self, name, default, basis_type_from, clsdict, remain=0):
self.clsdict = clsdict
self.basis_type_from = basis_type_from
PacketField.__init__(self, name, default, None, remain=remain)
def m2i(self, pkt, m):
basis = self.basis_type_from(pkt)
cls = self.clsdict[basis]
return cls(m)
def i2basis_type(self, pkt, x):
val = 0
try:
val = x.val
except Exception:
pass
return val
# Distinct ECParameters
##
# To support the different ECParameters structures defined in Sect. 5.4 of
# RFC 4492, we define 3 separates classes for implementing the 3 associated
# ServerECDHParams: ServerECDHNamedCurveParams, ServerECDHExplicitPrimeParams
# and ServerECDHExplicitChar2Params (support for this one is only partial).
# The most frequent encounter of the 3 is (by far) ServerECDHNamedCurveParams.
class ServerECDHExplicitPrimeParams(_GenericTLSSessionInheritance):
"""
We provide parsing abilities for ExplicitPrimeParams, but there is no
support from the cryptography library, hence no context operations.
"""
name = "Server ECDH parameters - Explicit Prime"
fields_desc = [ByteEnumField("curve_type", 1, _tls_ec_curve_types),
FieldLenField("plen", None, length_of="p", fmt="B"),
StrLenField("p", "", length_from=lambda pkt: pkt.plen),
PacketField("curve", None, ECCurvePkt),
FieldLenField("baselen", None, length_of="base", fmt="B"),
StrLenField("base", "",
length_from=lambda pkt: pkt.baselen),
FieldLenField("orderlen", None,
length_of="order", fmt="B"),
StrLenField("order", "",
length_from=lambda pkt: pkt.orderlen),
FieldLenField("cofactorlen", None,
length_of="cofactor", fmt="B"),
StrLenField("cofactor", "",
length_from=lambda pkt: pkt.cofactorlen),
FieldLenField("pointlen", None,
length_of="point", fmt="B"),
StrLenField("point", "",
length_from=lambda pkt: pkt.pointlen)]
def fill_missing(self):
"""
Note that if it is not set by the user, the cofactor will always
be 1. It is true for most, but not all, TLS elliptic curves.
"""
if self.curve_type is None:
self.curve_type = _tls_ec_curve_types["explicit_prime"]
def guess_payload_class(self, p):
return Padding
class ServerECDHExplicitChar2Params(_GenericTLSSessionInheritance):
"""
We provide parsing abilities for Char2Params, but there is no
support from the cryptography library, hence no context operations.
"""
name = "Server ECDH parameters - Explicit Char2"
fields_desc = [ByteEnumField("curve_type", 2, _tls_ec_curve_types),
ShortField("m", None),
_ECBasisTypeField("basis_type", None,
_tls_ec_basis_types, "basis"),
_ECBasisField("basis", ECTrinomialBasis(),
lambda pkt: pkt.basis_type,
_tls_ec_basis_cls),
PacketField("curve", ECCurvePkt(), ECCurvePkt),
FieldLenField("baselen", None, length_of="base", fmt="B"),
StrLenField("base", "",
length_from=lambda pkt: pkt.baselen),
ByteField("order", None),
ByteField("cofactor", None),
FieldLenField("pointlen", None,
length_of="point", fmt="B"),
StrLenField("point", "",
length_from=lambda pkt: pkt.pointlen)]
def fill_missing(self):
if self.curve_type is None:
self.curve_type = _tls_ec_curve_types["explicit_char2"]
def guess_payload_class(self, p):
return Padding
class ServerECDHNamedCurveParams(_GenericTLSSessionInheritance):
name = "Server ECDH parameters - Named Curve"
fields_desc = [ByteEnumField("curve_type", 3, _tls_ec_curve_types),
ShortEnumField("named_curve", None, _tls_named_curves),
FieldLenField("pointlen", None,
length_of="point", fmt="B"),
StrLenField("point", None,
length_from=lambda pkt: pkt.pointlen)]
@crypto_validator
def fill_missing(self):
"""
We do not want TLSServerKeyExchange.build() to overload and recompute
things every time it is called. This method can be called specifically
to have things filled in a smart fashion.
XXX We should account for the point_format (before 'point' filling).
"""
s = self.tls_session
if self.curve_type is None:
self.curve_type = _tls_ec_curve_types["named_curve"]
if self.named_curve is None:
curve = ec.SECP256R1()
s.server_kx_privkey = ec.generate_private_key(curve,
default_backend())
self.named_curve = next((cid for cid, name in six.iteritems(_tls_named_curves) # noqa: E501
if name == curve.name), 0)
else:
curve_name = _tls_named_curves.get(self.named_curve)
if curve_name is None:
# this fallback is arguable
curve = ec.SECP256R1()
else:
curve_cls = ec._CURVE_TYPES.get(curve_name)
if curve_cls is None:
# this fallback is arguable
curve = ec.SECP256R1()
else:
curve = curve_cls()
s.server_kx_privkey = ec.generate_private_key(curve,
default_backend())
if self.point is None:
pubkey = s.server_kx_privkey.public_key()
try:
# cryptography >= 2.5
self.point = pubkey.public_bytes(
serialization.Encoding.X962,
serialization.PublicFormat.UncompressedPoint
)
except TypeError:
# older versions
self.key_exchange = pubkey.public_numbers().encode_point()
# else, we assume that the user wrote the server_kx_privkey by himself
if self.pointlen is None:
self.pointlen = len(self.point)
if not s.client_kx_ecdh_params:
s.client_kx_ecdh_params = curve
@crypto_validator
def register_pubkey(self):
"""
XXX Support compressed point format.
XXX Check that the pubkey received is on the curve.
"""
# point_format = 0
# if self.point[0] in [b'\x02', b'\x03']:
# point_format = 1
curve_name = _tls_named_curves[self.named_curve]
curve = ec._CURVE_TYPES[curve_name]()
s = self.tls_session
try: # cryptography >= 2.5
import_point = ec.EllipticCurvePublicKey.from_encoded_point
s.server_kx_pubkey = import_point(curve, self.point)
except AttributeError:
import_point = ec.EllipticCurvePublicNumbers.from_encoded_point
pubnum = import_point(curve, self.point)
s.server_kx_pubkey = pubnum.public_key(default_backend())
if not s.client_kx_ecdh_params:
s.client_kx_ecdh_params = curve
def post_dissection(self, r):
try:
self.register_pubkey()
except ImportError:
pass
def guess_payload_class(self, p):
return Padding
_tls_server_ecdh_cls = {1: ServerECDHExplicitPrimeParams,
2: ServerECDHExplicitChar2Params,
3: ServerECDHNamedCurveParams}
def _tls_server_ecdh_cls_guess(m):
if not m:
return None
curve_type = orb(m[0])
return _tls_server_ecdh_cls.get(curve_type, None)
# RSA Encryption (export)
class ServerRSAParams(_GenericTLSSessionInheritance):
"""
Defined for RSA_EXPORT kx : it enables servers to share RSA keys shorter
than their principal {>512}-bit key, when it is not allowed for kx.
This should not appear in standard RSA kx negotiation, as the key
has already been advertised in the Certificate message.
"""
name = "Server RSA_EXPORT parameters"
fields_desc = [FieldLenField("rsamodlen", None, length_of="rsamod"),
StrLenField("rsamod", "",
length_from=lambda pkt: pkt.rsamodlen),
FieldLenField("rsaexplen", None, length_of="rsaexp"),
StrLenField("rsaexp", "",
length_from=lambda pkt: pkt.rsaexplen)]
@crypto_validator
def fill_missing(self):
k = PrivKeyRSA()
k.fill_and_store(modulusLen=512)
self.tls_session.server_tmp_rsa_key = k
pubNum = k.pubkey.public_numbers()
if not self.rsamod:
self.rsamod = pkcs_i2osp(pubNum.n, k.pubkey.key_size // 8)
if self.rsamodlen is None:
self.rsamodlen = len(self.rsamod)
rsaexplen = math.ceil(math.log(pubNum.e) / math.log(2) / 8.)
if not self.rsaexp:
self.rsaexp = pkcs_i2osp(pubNum.e, rsaexplen)
if self.rsaexplen is None:
self.rsaexplen = len(self.rsaexp)
@crypto_validator
def register_pubkey(self):
mLen = self.rsamodlen
m = self.rsamod
e = self.rsaexp
self.tls_session.server_tmp_rsa_key = PubKeyRSA((e, m, mLen))
def post_dissection(self, pkt):
try:
self.register_pubkey()
except ImportError:
pass
def guess_payload_class(self, p):
return Padding
# Pre-Shared Key
class ServerPSKParams(Packet):
"""
XXX We provide some parsing abilities for ServerPSKParams, but the
context operations have not been implemented yet. See RFC 4279.
Note that we do not cover the (EC)DHE_PSK key exchange,
which should contain a Server*DHParams after 'psk_identity_hint'.
"""
name = "Server PSK parameters"
fields_desc = [FieldLenField("psk_identity_hint_len", None,
length_of="psk_identity_hint", fmt="!H"),
StrLenField("psk_identity_hint", "",
length_from=lambda pkt: pkt.psk_identity_hint_len)] # noqa: E501
def fill_missing(self):
pass
def post_dissection(self, pkt):
pass
def guess_payload_class(self, p):
return Padding
###############################################################################
# Client Key Exchange value #
###############################################################################
# FFDH/ECDH
class ClientDiffieHellmanPublic(_GenericTLSSessionInheritance):
"""
If the user provides a value for dh_Yc attribute, we assume he will set
the pms and ms accordingly and trigger the key derivation on his own.
XXX As specified in 7.4.7.2. of RFC 4346, we should distinguish the needs
for implicit or explicit value depending on availability of DH parameters
in *client* certificate. For now we can only do ephemeral/explicit DH.
"""
name = "Client DH Public Value"
fields_desc = [FieldLenField("dh_Yclen", None, length_of="dh_Yc"),
StrLenField("dh_Yc", "",
length_from=lambda pkt: pkt.dh_Yclen)]
@crypto_validator
def fill_missing(self):
s = self.tls_session
params = s.client_kx_ffdh_params
s.client_kx_privkey = params.generate_private_key()
pubkey = s.client_kx_privkey.public_key()
y = pubkey.public_numbers().y
self.dh_Yc = pkcs_i2osp(y, pubkey.key_size // 8)
if s.client_kx_privkey and s.server_kx_pubkey:
pms = s.client_kx_privkey.exchange(s.server_kx_pubkey)
s.pre_master_secret = pms
s.compute_ms_and_derive_keys()
def post_build(self, pkt, pay):
if not self.dh_Yc:
try:
self.fill_missing()
except ImportError:
pass
if self.dh_Yclen is None:
self.dh_Yclen = len(self.dh_Yc)
return pkcs_i2osp(self.dh_Yclen, 2) + self.dh_Yc + pay
def post_dissection(self, m):
"""
First we update the client DHParams. Then, we try to update the server
DHParams generated during Server*DHParams building, with the shared
secret. Finally, we derive the session keys and update the context.
"""
s = self.tls_session
# if there are kx params and keys, we assume the crypto library is ok
if s.client_kx_ffdh_params:
y = pkcs_os2ip(self.dh_Yc)
param_numbers = s.client_kx_ffdh_params.parameter_numbers()
public_numbers = dh.DHPublicNumbers(y, param_numbers)
s.client_kx_pubkey = public_numbers.public_key(default_backend())
if s.server_kx_privkey and s.client_kx_pubkey:
ZZ = s.server_kx_privkey.exchange(s.client_kx_pubkey)
s.pre_master_secret = ZZ
s.compute_ms_and_derive_keys()
def guess_payload_class(self, p):
return Padding
class ClientECDiffieHellmanPublic(_GenericTLSSessionInheritance):
"""
Note that the 'len' field is 1 byte longer than with the previous class.
"""
name = "Client ECDH Public Value"
fields_desc = [FieldLenField("ecdh_Yclen", None,
length_of="ecdh_Yc", fmt="B"),
StrLenField("ecdh_Yc", "",
length_from=lambda pkt: pkt.ecdh_Yclen)]
@crypto_validator
def fill_missing(self):
s = self.tls_session
params = s.client_kx_ecdh_params
s.client_kx_privkey = ec.generate_private_key(params,
default_backend())
pubkey = s.client_kx_privkey.public_key()
x = pubkey.public_numbers().x
y = pubkey.public_numbers().y
self.ecdh_Yc = (b"\x04" +
pkcs_i2osp(x, params.key_size // 8) +
pkcs_i2osp(y, params.key_size // 8))
if s.client_kx_privkey and s.server_kx_pubkey:
pms = s.client_kx_privkey.exchange(ec.ECDH(), s.server_kx_pubkey)
s.pre_master_secret = pms
s.compute_ms_and_derive_keys()
def post_build(self, pkt, pay):
if not self.ecdh_Yc:
try:
self.fill_missing()
except ImportError:
pass
if self.ecdh_Yclen is None:
self.ecdh_Yclen = len(self.ecdh_Yc)
return pkcs_i2osp(self.ecdh_Yclen, 1) + self.ecdh_Yc + pay
def post_dissection(self, m):
s = self.tls_session
# if there are kx params and keys, we assume the crypto library is ok
if s.client_kx_ecdh_params:
try: # cryptography >= 2.5
import_point = ec.EllipticCurvePublicKey.from_encoded_point
s.client_kx_pubkey = import_point(s.client_kx_ecdh_params,
self.ecdh_Yc)
except AttributeError:
import_point = ec.EllipticCurvePublicNumbers.from_encoded_point
pub_num = import_point(s.client_kx_ecdh_params, self.ecdh_Yc)
s.client_kx_pubkey = pub_num.public_key(default_backend())
if s.server_kx_privkey and s.client_kx_pubkey:
ZZ = s.server_kx_privkey.exchange(ec.ECDH(), s.client_kx_pubkey)
s.pre_master_secret = ZZ
s.compute_ms_and_derive_keys()
# RSA Encryption (standard & export)
class _UnEncryptedPreMasterSecret(Raw):
"""
When the content of an EncryptedPreMasterSecret could not be deciphered,
we use this class to represent the encrypted data.
"""
name = "RSA Encrypted PreMaster Secret (protected)"
def __init__(self, *args, **kargs):
kargs.pop('tls_session', None)
return super(_UnEncryptedPreMasterSecret, self).__init__(*args, **kargs) # noqa: E501
class EncryptedPreMasterSecret(_GenericTLSSessionInheritance):
"""
Pay attention to implementation notes in section 7.4.7.1 of RFC 5246.
"""
name = "RSA Encrypted PreMaster Secret"
fields_desc = [_TLSClientVersionField("client_version", None,
_tls_version),
StrFixedLenField("random", None, 46)]
@classmethod
def dispatch_hook(cls, _pkt=None, *args, **kargs):
if _pkt and 'tls_session' in kargs:
s = kargs['tls_session']
if s.server_tmp_rsa_key is None and s.server_rsa_key is None:
return _UnEncryptedPreMasterSecret
return EncryptedPreMasterSecret
def pre_dissect(self, m):
s = self.tls_session
tbd = m
tls_version = s.tls_version
if tls_version is None:
tls_version = s.advertised_tls_version
if tls_version >= 0x0301:
if len(m) < 2: # Should not happen
return m
tmp_len = struct.unpack("!H", m[:2])[0]
if len(m) != tmp_len + 2:
err = "TLS 1.0+, but RSA Encrypted PMS with no explicit length"
warning(err)
else:
tbd = m[2:]
if s.server_tmp_rsa_key is not None:
# priority is given to the tmp_key, if there is one
decrypted = s.server_tmp_rsa_key.decrypt(tbd)
pms = decrypted[-48:]
elif s.server_rsa_key is not None:
decrypted = s.server_rsa_key.decrypt(tbd)
pms = decrypted[-48:]
else:
# the dispatch_hook is supposed to prevent this case
pms = b"\x00" * 48
err = "No server RSA key to decrypt Pre Master Secret. Skipping."
warning(err)
s.pre_master_secret = pms
s.compute_ms_and_derive_keys()
return pms
def post_build(self, pkt, pay):
"""
We encrypt the premaster secret (the 48 bytes) with either the server
certificate or the temporary RSA key provided in a server key exchange
message. After that step, we add the 2 bytes to provide the length, as
described in implementation notes at the end of section 7.4.7.1.
"""
enc = pkt
s = self.tls_session
s.pre_master_secret = enc
s.compute_ms_and_derive_keys()
if s.server_tmp_rsa_key is not None:
enc = s.server_tmp_rsa_key.encrypt(pkt, t="pkcs")
elif s.server_certs is not None and len(s.server_certs) > 0:
enc = s.server_certs[0].encrypt(pkt, t="pkcs")
else:
warning("No material to encrypt Pre Master Secret")
tmp_len = b""
tls_version = s.tls_version
if tls_version is None:
tls_version = s.advertised_tls_version
if tls_version >= 0x0301:
tmp_len = struct.pack("!H", len(enc))
return tmp_len + enc + pay
def guess_payload_class(self, p):
return Padding
# Pre-Shared Key
class ClientPSKIdentity(Packet):
"""
XXX We provide parsing abilities for ServerPSKParams, but the context
operations have not been implemented yet. See RFC 4279.
Note that we do not cover the (EC)DHE_PSK nor the RSA_PSK key exchange,
which should contain either an EncryptedPMS or a ClientDiffieHellmanPublic.
"""
name = "Server PSK parameters"
fields_desc = [FieldLenField("psk_identity_len", None,
length_of="psk_identity", fmt="!H"),
StrLenField("psk_identity", "",
length_from=lambda pkt: pkt.psk_identity_len)]