diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 2c0c501a5..b0d7e96ba 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -49,6 +49,80 @@ PSK_MODES = ["psk_dhe_ke", "psk_ke"] +class Keypair(object): + """ + Key, certificate and related data. + + Stores also certificate associate data like OCSPs and transparency info. + TODO: add the above + + First certificate in certificates needs to match key, remaining should + build a trust path to a root CA. + + :vartype key: RSAKey or ECDSAKey + :ivar key: private key + + :vartype certificates: list of X509 + :ivar certificates: the certificates to send to peer if the key is selected + for use. The first one MUST include the public key of the ``key`` + """ + def __init__(self, key=None, certificates=tuple()): + self.key = key + self.certificates = certificates + + def validate(self): + """Sanity check the keypair.""" + if not self.key or not self.certificates: + raise ValueError("Key or certificate missing in Keypair") + + +class VirtualHost(object): + """ + Configuration of keys and certs for a single virual server. + + This class encapsulates keys and certificates for hosts specified by + server_name (SNI) and ALPN extensions. + + TODO: support SRP as alternative to certificates + TODO: support PSK as alternative to certificates + + :vartype keys: list of :ref:`~Keypair` + :ivar keys: List of certificates and keys to be used in this + virtual host. First keypair able to server ClientHello will be used. + + :vartype hostnames: set of bytes + :ivar hostnames: all the hostnames that server supports + please use :ref:`matches_hostname` to verify if the VirtualHost + can serve a request to a given hostname as that allows wildcard hosts + that always reply True. + + :vartype trust_anchors: list of X509 + :ivar trust_anchors: list of CA certificates supported for client + certificate authentication, sent in CertificateRequest + + :ivar app_protocols: all the application protocols that the server supports + (for ALPN) + """ + + def __init__(self): + """Set up default configuration.""" + self.keys = [] + self.hostnames = set() + self.trust_anchors = [] + self.app_protocols = [] + + def matches_hostname(self, hostname): + """Checks if the virtual host can serve hostname""" + return hostname in self.hostnames + + def validate(self): + """Sanity check the settings""" + if not self.keys: + raise ValueError("Virtual host missing keys") + for i in self.keys: + i.validate() + + class HandshakeSettings(object): """ This class encapsulates various parameters that can be used with @@ -254,6 +328,7 @@ def _init_key_settings(self): self.maxKeySize = 8193 self.rsaSigHashes = list(RSA_SIGNATURE_HASHES) self.rsaSchemes = list(RSA_SCHEMES) + self.virtual_hosts = [] # DH key settings self.eccCurves = list(CURVE_NAMES) self.dhParams = None @@ -312,6 +387,9 @@ def _sanityCheckKeySizes(other): raise ValueError("maxKeySize too large") if other.maxKeySize < other.minKeySize: raise ValueError("maxKeySize smaller than minKeySize") + # check also keys of virtual hosts + for i in other.virtual_hosts: + i.validate() @staticmethod def _not_matching(values, sieve): @@ -530,6 +608,7 @@ def _copy_extension_settings(self, other): """Copy values of settings related to extensions.""" other.useExtendedMasterSecret = self.useExtendedMasterSecret other.requireExtendedMasterSecret = self.requireExtendedMasterSecret + other.useExperimentalTackExtension = self.useExperimentalTackExtension other.sendFallbackSCSV = self.sendFallbackSCSV other.useEncryptThenMAC = self.useEncryptThenMAC other.usePaddingExtension = self.usePaddingExtension @@ -574,6 +653,7 @@ def _copy_key_settings(self, other): other.rsaSigHashes = self.rsaSigHashes other.rsaSchemes = self.rsaSchemes other.ecdsaSigHashes = self.ecdsaSigHashes + other.virtual_hosts = self.virtual_hosts # DH key params other.eccCurves = self.eccCurves other.dhParams = self.dhParams diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 8ca80d5dd..75bd35fd8 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1910,11 +1910,13 @@ def handshakeServer(self, verifierDB=None, :type certChain: ~tlslite.x509certchain.X509CertChain :param certChain: The certificate chain to be used if the - client requests server certificate authentication. + client requests server certificate authentication and no virtual + host defined in HandshakeSettings matches ClientHello. :type privateKey: ~tlslite.utils.rsakey.RSAKey :param privateKey: The private key to be used if the client - requests server certificate authentication. + requests server certificate authentication and no virtual host + defined in HandshakeSettings matches ClientHello. :type reqCert: bool :param reqCert: Whether to request client certificate @@ -1941,21 +1943,23 @@ def handshakeServer(self, verifierDB=None, :type reqCAs: list of bytearray :param reqCAs: A collection of DER-encoded DistinguishedNames that - will be sent along with a certificate request. This does not affect - verification. + will be sent along with a certificate request to help client pick + a certificates. This does not affect verification. :type nextProtos: list of str :param nextProtos: A list of upper layer protocols to expose to the clients through the Next-Protocol Negotiation Extension, - if they support it. + if they support it. Deprecated, use the `virtual_hosts` in + HandshakeSettings. :type alpn: list of bytearray :param alpn: names of application layer protocols supported. Note that it will be used instead of NPN if both were advertised by - client. + client. Deprecated, use the `virtual_hosts` in HandshakeSettings. :type sni: bytearray - :param sni: expected virtual name hostname. + :param sni: expected virtual name hostname. Deprecated, use the + `virtual_hosts` in HandshakeSettings. :raises socket.error: If a socket error occurs. :raises tlslite.errors.TLSAbruptCloseError: If the socket is closed @@ -2009,8 +2013,12 @@ def _handshakeServerAsyncHelper(self, verifierDB, self._handshakeStart(client=False) + if not settings: + settings = HandshakeSettings() + settings = settings.validate() + if (not verifierDB) and (not cert_chain) and not anon and \ - not settings.pskConfigs: + not settings.pskConfigs and not settings.virtual_hosts: raise ValueError("Caller passed no authentication credentials") if cert_chain and not privateKey: raise ValueError("Caller passed a cert_chain but no privateKey") @@ -2030,16 +2038,14 @@ def _handshakeServerAsyncHelper(self, verifierDB, if alpn is not None and not alpn: raise ValueError("Empty list of ALPN protocols") - if not settings: - settings = HandshakeSettings() - settings = settings.validate() self.sock.padding_cb = settings.padding_cb # OK Start exchanging messages # ****************************** # Handle ClientHello and resumption - for result in self._serverGetClientHello(settings, cert_chain, + for result in self._serverGetClientHello(settings, privateKey, + cert_chain, verifierDB, sessionCache, anon, alpn, sni): if result in (0,1): yield result @@ -2047,7 +2053,7 @@ def _handshakeServerAsyncHelper(self, verifierDB, self._handshakeDone(resumed=True) return # Handshake was resumed, we're done else: break - (clientHello, cipherSuite, version, scheme) = result + (clientHello, version, cipherSuite, sig_scheme) = result # in TLS 1.3 the handshake is completely different # (extensions go into different messages, format of messages is @@ -2056,7 +2062,7 @@ def _handshakeServerAsyncHelper(self, verifierDB, for result in self._serverTLS13Handshake(settings, clientHello, cipherSuite, privateKey, cert_chain, - version, scheme, + version, sig_scheme, alpn, reqCert): if result in (0, 1): yield result @@ -2823,7 +2829,8 @@ def _serverTLS13Handshake(self, settings, clientHello, cipherSuite, yield "finished" - def _serverGetClientHello(self, settings, cert_chain, verifierDB, + def _serverGetClientHello(self, settings, private_key, cert_chain, + verifierDB, sessionCache, anon, alpn, sni): # Tentatively set version to most-desirable version, so if an error # occurs parsing the ClientHello, this will be the version we'll use @@ -3070,6 +3077,7 @@ def _serverGetClientHello(self, settings, cert_chain, verifierDB, self._recordLayer.max_early_data = settings.max_early_data self._recordLayer.early_data_ok = True + # negotiate the protocol version for the connection high_ver = None if ver_ext: high_ver = getFirstMatching(settings.versions, @@ -3107,13 +3115,13 @@ def _serverGetClientHello(self, settings, cert_chain, verifierDB, # TODO when TLS 1.3 is final, check the client hello random for # downgrade too - scheme = None + sig_scheme = None if version >= (3, 4): try: - scheme = self._pickServerKeyExchangeSig(settings, - clientHello, - cert_chain, - version) + sig_scheme = self._pickServerKeyExchangeSig(settings, + clientHello, + cert_chain, + version) except TLSHandshakeFailure as alert: for result in self._sendError( AlertDescription.handshake_failure, @@ -3628,7 +3636,7 @@ def _serverGetClientHello(self, settings, cert_chain, verifierDB, # we have no session cache, or # the client's session_id was not found in cache: #pylint: disable = undefined-loop-variable - yield (clientHello, cipherSuite, version, scheme) + yield (clientHello, version, cipherSuite, sig_scheme) #pylint: enable = undefined-loop-variable def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB,