969 lines
37 KiB
Python
969 lines
37 KiB
Python
|
# 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)]
|