1601 lines
69 KiB
Python
1601 lines
69 KiB
Python
"""
|
|
MAIN CLASS FOR TLS LITE (START HERE!).
|
|
"""
|
|
from __future__ import generators
|
|
|
|
import socket
|
|
from utils.compat import formatExceptionTrace
|
|
from TLSRecordLayer import TLSRecordLayer
|
|
from Session import Session
|
|
from constants import *
|
|
from utils.cryptomath import getRandomBytes
|
|
from errors import *
|
|
from messages import *
|
|
from mathtls import *
|
|
from HandshakeSettings import HandshakeSettings
|
|
|
|
|
|
class TLSConnection(TLSRecordLayer):
|
|
"""
|
|
This class wraps a socket and provides TLS handshaking and data
|
|
transfer.
|
|
|
|
To use this class, create a new instance, passing a connected
|
|
socket into the constructor. Then call some handshake function.
|
|
If the handshake completes without raising an exception, then a TLS
|
|
connection has been negotiated. You can transfer data over this
|
|
connection as if it were a socket.
|
|
|
|
This class provides both synchronous and asynchronous versions of
|
|
its key functions. The synchronous versions should be used when
|
|
writing single-or multi-threaded code using blocking sockets. The
|
|
asynchronous versions should be used when performing asynchronous,
|
|
event-based I/O with non-blocking sockets.
|
|
|
|
Asynchronous I/O is a complicated subject; typically, you should
|
|
not use the asynchronous functions directly, but should use some
|
|
framework like asyncore or Twisted which TLS Lite integrates with
|
|
(see
|
|
L{tlslite.integration.TLSAsyncDispatcherMixIn.TLSAsyncDispatcherMixIn} or
|
|
L{tlslite.integration.TLSTwistedProtocolWrapper.TLSTwistedProtocolWrapper}).
|
|
"""
|
|
|
|
|
|
def __init__(self, sock):
|
|
"""Create a new TLSConnection instance.
|
|
|
|
@param sock: The socket data will be transmitted on. The
|
|
socket should already be connected. It may be in blocking or
|
|
non-blocking mode.
|
|
|
|
@type sock: L{socket.socket}
|
|
"""
|
|
TLSRecordLayer.__init__(self, sock)
|
|
|
|
def handshakeClientSRP(self, username, password, session=None,
|
|
settings=None, checker=None, async=False):
|
|
"""Perform an SRP handshake in the role of client.
|
|
|
|
This function performs a TLS/SRP handshake. SRP mutually
|
|
authenticates both parties to each other using only a
|
|
username and password. This function may also perform a
|
|
combined SRP and server-certificate handshake, if the server
|
|
chooses to authenticate itself with a certificate chain in
|
|
addition to doing SRP.
|
|
|
|
TLS/SRP is non-standard. Most TLS implementations don't
|
|
support it. See
|
|
U{http://www.ietf.org/html.charters/tls-charter.html} or
|
|
U{http://trevp.net/tlssrp/} for the latest information on
|
|
TLS/SRP.
|
|
|
|
Like any handshake function, this can be called on a closed
|
|
TLS connection, or on a TLS connection that is already open.
|
|
If called on an open connection it performs a re-handshake.
|
|
|
|
If the function completes without raising an exception, the
|
|
TLS connection will be open and available for data transfer.
|
|
|
|
If an exception is raised, the connection will have been
|
|
automatically closed (if it was ever open).
|
|
|
|
@type username: str
|
|
@param username: The SRP username.
|
|
|
|
@type password: str
|
|
@param password: The SRP password.
|
|
|
|
@type session: L{tlslite.Session.Session}
|
|
@param session: A TLS session to attempt to resume. This
|
|
session must be an SRP session performed with the same username
|
|
and password as were passed in. If the resumption does not
|
|
succeed, a full SRP handshake will be performed.
|
|
|
|
@type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
|
|
@param settings: Various settings which can be used to control
|
|
the ciphersuites, certificate types, and SSL/TLS versions
|
|
offered by the client.
|
|
|
|
@type checker: L{tlslite.Checker.Checker}
|
|
@param checker: A Checker instance. This instance will be
|
|
invoked to examine the other party's authentication
|
|
credentials, if the handshake completes succesfully.
|
|
|
|
@type async: bool
|
|
@param async: If False, this function will block until the
|
|
handshake is completed. If True, this function will return a
|
|
generator. Successive invocations of the generator will
|
|
return 0 if it is waiting to read from the socket, 1 if it is
|
|
waiting to write to the socket, or will raise StopIteration if
|
|
the handshake operation is completed.
|
|
|
|
@rtype: None or an iterable
|
|
@return: If 'async' is True, a generator object will be
|
|
returned.
|
|
|
|
@raise socket.error: If a socket error occurs.
|
|
@raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
|
|
without a preceding alert.
|
|
@raise tlslite.errors.TLSAlert: If a TLS alert is signalled.
|
|
@raise tlslite.errors.TLSAuthenticationError: If the checker
|
|
doesn't like the other party's authentication credentials.
|
|
"""
|
|
handshaker = self._handshakeClientAsync(srpParams=(username, password),
|
|
session=session, settings=settings, checker=checker)
|
|
if async:
|
|
return handshaker
|
|
for result in handshaker:
|
|
pass
|
|
|
|
def handshakeClientCert(self, certChain=None, privateKey=None,
|
|
session=None, settings=None, checker=None,
|
|
async=False):
|
|
"""Perform a certificate-based handshake in the role of client.
|
|
|
|
This function performs an SSL or TLS handshake. The server
|
|
will authenticate itself using an X.509 or cryptoID certificate
|
|
chain. If the handshake succeeds, the server's certificate
|
|
chain will be stored in the session's serverCertChain attribute.
|
|
Unless a checker object is passed in, this function does no
|
|
validation or checking of the server's certificate chain.
|
|
|
|
If the server requests client authentication, the
|
|
client will send the passed-in certificate chain, and use the
|
|
passed-in private key to authenticate itself. If no
|
|
certificate chain and private key were passed in, the client
|
|
will attempt to proceed without client authentication. The
|
|
server may or may not allow this.
|
|
|
|
Like any handshake function, this can be called on a closed
|
|
TLS connection, or on a TLS connection that is already open.
|
|
If called on an open connection it performs a re-handshake.
|
|
|
|
If the function completes without raising an exception, the
|
|
TLS connection will be open and available for data transfer.
|
|
|
|
If an exception is raised, the connection will have been
|
|
automatically closed (if it was ever open).
|
|
|
|
@type certChain: L{tlslite.X509CertChain.X509CertChain} or
|
|
L{cryptoIDlib.CertChain.CertChain}
|
|
@param certChain: The certificate chain to be used if the
|
|
server requests client authentication.
|
|
|
|
@type privateKey: L{tlslite.utils.RSAKey.RSAKey}
|
|
@param privateKey: The private key to be used if the server
|
|
requests client authentication.
|
|
|
|
@type session: L{tlslite.Session.Session}
|
|
@param session: A TLS session to attempt to resume. If the
|
|
resumption does not succeed, a full handshake will be
|
|
performed.
|
|
|
|
@type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
|
|
@param settings: Various settings which can be used to control
|
|
the ciphersuites, certificate types, and SSL/TLS versions
|
|
offered by the client.
|
|
|
|
@type checker: L{tlslite.Checker.Checker}
|
|
@param checker: A Checker instance. This instance will be
|
|
invoked to examine the other party's authentication
|
|
credentials, if the handshake completes succesfully.
|
|
|
|
@type async: bool
|
|
@param async: If False, this function will block until the
|
|
handshake is completed. If True, this function will return a
|
|
generator. Successive invocations of the generator will
|
|
return 0 if it is waiting to read from the socket, 1 if it is
|
|
waiting to write to the socket, or will raise StopIteration if
|
|
the handshake operation is completed.
|
|
|
|
@rtype: None or an iterable
|
|
@return: If 'async' is True, a generator object will be
|
|
returned.
|
|
|
|
@raise socket.error: If a socket error occurs.
|
|
@raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
|
|
without a preceding alert.
|
|
@raise tlslite.errors.TLSAlert: If a TLS alert is signalled.
|
|
@raise tlslite.errors.TLSAuthenticationError: If the checker
|
|
doesn't like the other party's authentication credentials.
|
|
"""
|
|
handshaker = self._handshakeClientAsync(certParams=(certChain,
|
|
privateKey), session=session, settings=settings,
|
|
checker=checker)
|
|
if async:
|
|
return handshaker
|
|
for result in handshaker:
|
|
pass
|
|
|
|
def handshakeClientUnknown(self, srpCallback=None, certCallback=None,
|
|
session=None, settings=None, checker=None,
|
|
async=False):
|
|
"""Perform a to-be-determined type of handshake in the role of client.
|
|
|
|
This function performs an SSL or TLS handshake. If the server
|
|
requests client certificate authentication, the
|
|
certCallback will be invoked and should return a (certChain,
|
|
privateKey) pair. If the callback returns None, the library
|
|
will attempt to proceed without client authentication. The
|
|
server may or may not allow this.
|
|
|
|
If the server requests SRP authentication, the srpCallback
|
|
will be invoked and should return a (username, password) pair.
|
|
If the callback returns None, the local implementation will
|
|
signal a user_canceled error alert.
|
|
|
|
After the handshake completes, the client can inspect the
|
|
connection's session attribute to determine what type of
|
|
authentication was performed.
|
|
|
|
Like any handshake function, this can be called on a closed
|
|
TLS connection, or on a TLS connection that is already open.
|
|
If called on an open connection it performs a re-handshake.
|
|
|
|
If the function completes without raising an exception, the
|
|
TLS connection will be open and available for data transfer.
|
|
|
|
If an exception is raised, the connection will have been
|
|
automatically closed (if it was ever open).
|
|
|
|
@type srpCallback: callable
|
|
@param srpCallback: The callback to be used if the server
|
|
requests SRP authentication. If None, the client will not
|
|
offer support for SRP ciphersuites.
|
|
|
|
@type certCallback: callable
|
|
@param certCallback: The callback to be used if the server
|
|
requests client certificate authentication.
|
|
|
|
@type session: L{tlslite.Session.Session}
|
|
@param session: A TLS session to attempt to resume. If the
|
|
resumption does not succeed, a full handshake will be
|
|
performed.
|
|
|
|
@type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
|
|
@param settings: Various settings which can be used to control
|
|
the ciphersuites, certificate types, and SSL/TLS versions
|
|
offered by the client.
|
|
|
|
@type checker: L{tlslite.Checker.Checker}
|
|
@param checker: A Checker instance. This instance will be
|
|
invoked to examine the other party's authentication
|
|
credentials, if the handshake completes succesfully.
|
|
|
|
@type async: bool
|
|
@param async: If False, this function will block until the
|
|
handshake is completed. If True, this function will return a
|
|
generator. Successive invocations of the generator will
|
|
return 0 if it is waiting to read from the socket, 1 if it is
|
|
waiting to write to the socket, or will raise StopIteration if
|
|
the handshake operation is completed.
|
|
|
|
@rtype: None or an iterable
|
|
@return: If 'async' is True, a generator object will be
|
|
returned.
|
|
|
|
@raise socket.error: If a socket error occurs.
|
|
@raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
|
|
without a preceding alert.
|
|
@raise tlslite.errors.TLSAlert: If a TLS alert is signalled.
|
|
@raise tlslite.errors.TLSAuthenticationError: If the checker
|
|
doesn't like the other party's authentication credentials.
|
|
"""
|
|
handshaker = self._handshakeClientAsync(unknownParams=(srpCallback,
|
|
certCallback), session=session, settings=settings,
|
|
checker=checker)
|
|
if async:
|
|
return handshaker
|
|
for result in handshaker:
|
|
pass
|
|
|
|
def handshakeClientSharedKey(self, username, sharedKey, settings=None,
|
|
checker=None, async=False):
|
|
"""Perform a shared-key handshake in the role of client.
|
|
|
|
This function performs a shared-key handshake. Using shared
|
|
symmetric keys of high entropy (128 bits or greater) mutually
|
|
authenticates both parties to each other.
|
|
|
|
TLS with shared-keys is non-standard. Most TLS
|
|
implementations don't support it. See
|
|
U{http://www.ietf.org/html.charters/tls-charter.html} for the
|
|
latest information on TLS with shared-keys. If the shared-keys
|
|
Internet-Draft changes or is superceded, TLS Lite will track
|
|
those changes, so the shared-key support in later versions of
|
|
TLS Lite may become incompatible with this version.
|
|
|
|
Like any handshake function, this can be called on a closed
|
|
TLS connection, or on a TLS connection that is already open.
|
|
If called on an open connection it performs a re-handshake.
|
|
|
|
If the function completes without raising an exception, the
|
|
TLS connection will be open and available for data transfer.
|
|
|
|
If an exception is raised, the connection will have been
|
|
automatically closed (if it was ever open).
|
|
|
|
@type username: str
|
|
@param username: The shared-key username.
|
|
|
|
@type sharedKey: str
|
|
@param sharedKey: The shared key.
|
|
|
|
@type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
|
|
@param settings: Various settings which can be used to control
|
|
the ciphersuites, certificate types, and SSL/TLS versions
|
|
offered by the client.
|
|
|
|
@type checker: L{tlslite.Checker.Checker}
|
|
@param checker: A Checker instance. This instance will be
|
|
invoked to examine the other party's authentication
|
|
credentials, if the handshake completes succesfully.
|
|
|
|
@type async: bool
|
|
@param async: If False, this function will block until the
|
|
handshake is completed. If True, this function will return a
|
|
generator. Successive invocations of the generator will
|
|
return 0 if it is waiting to read from the socket, 1 if it is
|
|
waiting to write to the socket, or will raise StopIteration if
|
|
the handshake operation is completed.
|
|
|
|
@rtype: None or an iterable
|
|
@return: If 'async' is True, a generator object will be
|
|
returned.
|
|
|
|
@raise socket.error: If a socket error occurs.
|
|
@raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
|
|
without a preceding alert.
|
|
@raise tlslite.errors.TLSAlert: If a TLS alert is signalled.
|
|
@raise tlslite.errors.TLSAuthenticationError: If the checker
|
|
doesn't like the other party's authentication credentials.
|
|
"""
|
|
handshaker = self._handshakeClientAsync(sharedKeyParams=(username,
|
|
sharedKey), settings=settings, checker=checker)
|
|
if async:
|
|
return handshaker
|
|
for result in handshaker:
|
|
pass
|
|
|
|
def _handshakeClientAsync(self, srpParams=(), certParams=(),
|
|
unknownParams=(), sharedKeyParams=(),
|
|
session=None, settings=None, checker=None,
|
|
recursive=False):
|
|
|
|
handshaker = self._handshakeClientAsyncHelper(srpParams=srpParams,
|
|
certParams=certParams, unknownParams=unknownParams,
|
|
sharedKeyParams=sharedKeyParams, session=session,
|
|
settings=settings, recursive=recursive)
|
|
for result in self._handshakeWrapperAsync(handshaker, checker):
|
|
yield result
|
|
|
|
|
|
def _handshakeClientAsyncHelper(self, srpParams, certParams, unknownParams,
|
|
sharedKeyParams, session, settings, recursive):
|
|
if not recursive:
|
|
self._handshakeStart(client=True)
|
|
|
|
#Unpack parameters
|
|
srpUsername = None # srpParams
|
|
password = None # srpParams
|
|
clientCertChain = None # certParams
|
|
privateKey = None # certParams
|
|
srpCallback = None # unknownParams
|
|
certCallback = None # unknownParams
|
|
#session # sharedKeyParams (or session)
|
|
#settings # settings
|
|
|
|
if srpParams:
|
|
srpUsername, password = srpParams
|
|
elif certParams:
|
|
clientCertChain, privateKey = certParams
|
|
elif unknownParams:
|
|
srpCallback, certCallback = unknownParams
|
|
elif sharedKeyParams:
|
|
session = Session()._createSharedKey(*sharedKeyParams)
|
|
|
|
if not settings:
|
|
settings = HandshakeSettings()
|
|
settings = settings._filter()
|
|
|
|
#Validate parameters
|
|
if srpUsername and not password:
|
|
raise ValueError("Caller passed a username but no password")
|
|
if password and not srpUsername:
|
|
raise ValueError("Caller passed a password but no username")
|
|
|
|
if clientCertChain and not privateKey:
|
|
raise ValueError("Caller passed a certChain but no privateKey")
|
|
if privateKey and not clientCertChain:
|
|
raise ValueError("Caller passed a privateKey but no certChain")
|
|
|
|
if clientCertChain:
|
|
foundType = False
|
|
try:
|
|
import cryptoIDlib.CertChain
|
|
if isinstance(clientCertChain, cryptoIDlib.CertChain.CertChain):
|
|
if "cryptoID" not in settings.certificateTypes:
|
|
raise ValueError("Client certificate doesn't "\
|
|
"match Handshake Settings")
|
|
settings.certificateTypes = ["cryptoID"]
|
|
foundType = True
|
|
except ImportError:
|
|
pass
|
|
if not foundType and isinstance(clientCertChain,
|
|
X509CertChain):
|
|
if "x509" not in settings.certificateTypes:
|
|
raise ValueError("Client certificate doesn't match "\
|
|
"Handshake Settings")
|
|
settings.certificateTypes = ["x509"]
|
|
foundType = True
|
|
if not foundType:
|
|
raise ValueError("Unrecognized certificate type")
|
|
|
|
|
|
if session:
|
|
if not session.valid():
|
|
session = None #ignore non-resumable sessions...
|
|
elif session.resumable and \
|
|
(session.srpUsername != srpUsername):
|
|
raise ValueError("Session username doesn't match")
|
|
|
|
#Add Faults to parameters
|
|
if srpUsername and self.fault == Fault.badUsername:
|
|
srpUsername += "GARBAGE"
|
|
if password and self.fault == Fault.badPassword:
|
|
password += "GARBAGE"
|
|
if sharedKeyParams:
|
|
identifier = sharedKeyParams[0]
|
|
sharedKey = sharedKeyParams[1]
|
|
if self.fault == Fault.badIdentifier:
|
|
identifier += "GARBAGE"
|
|
session = Session()._createSharedKey(identifier, sharedKey)
|
|
elif self.fault == Fault.badSharedKey:
|
|
sharedKey += "GARBAGE"
|
|
session = Session()._createSharedKey(identifier, sharedKey)
|
|
|
|
|
|
#Initialize locals
|
|
serverCertChain = None
|
|
cipherSuite = 0
|
|
certificateType = CertificateType.x509
|
|
premasterSecret = None
|
|
|
|
#Get client nonce
|
|
clientRandom = getRandomBytes(32)
|
|
|
|
#Initialize acceptable ciphersuites
|
|
cipherSuites = []
|
|
if srpParams:
|
|
cipherSuites += CipherSuite.getSrpRsaSuites(settings.cipherNames)
|
|
cipherSuites += CipherSuite.getSrpSuites(settings.cipherNames)
|
|
elif certParams:
|
|
cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames)
|
|
elif unknownParams:
|
|
if srpCallback:
|
|
cipherSuites += \
|
|
CipherSuite.getSrpRsaSuites(settings.cipherNames)
|
|
cipherSuites += \
|
|
CipherSuite.getSrpSuites(settings.cipherNames)
|
|
cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames)
|
|
elif sharedKeyParams:
|
|
cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames)
|
|
else:
|
|
cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames)
|
|
|
|
#Initialize acceptable certificate types
|
|
certificateTypes = settings._getCertificateTypes()
|
|
|
|
#Tentatively set the version to the client's minimum version.
|
|
#We'll use this for the ClientHello, and if an error occurs
|
|
#parsing the Server Hello, we'll use this version for the response
|
|
self.version = settings.maxVersion
|
|
|
|
#Either send ClientHello (with a resumable session)...
|
|
if session:
|
|
#If it's a resumable (i.e. not a shared-key session), then its
|
|
#ciphersuite must be one of the acceptable ciphersuites
|
|
if (not sharedKeyParams) and \
|
|
session.cipherSuite not in cipherSuites:
|
|
raise ValueError("Session's cipher suite not consistent "\
|
|
"with parameters")
|
|
else:
|
|
clientHello = ClientHello()
|
|
clientHello.create(settings.maxVersion, clientRandom,
|
|
session.sessionID, cipherSuites,
|
|
certificateTypes, session.srpUsername)
|
|
|
|
#Or send ClientHello (without)
|
|
else:
|
|
clientHello = ClientHello()
|
|
clientHello.create(settings.maxVersion, clientRandom,
|
|
createByteArraySequence([]), cipherSuites,
|
|
certificateTypes, srpUsername)
|
|
for result in self._sendMsg(clientHello):
|
|
yield result
|
|
|
|
#Get ServerHello (or missing_srp_username)
|
|
for result in self._getMsg((ContentType.handshake,
|
|
ContentType.alert),
|
|
HandshakeType.server_hello):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
msg = result
|
|
|
|
if isinstance(msg, ServerHello):
|
|
serverHello = msg
|
|
elif isinstance(msg, Alert):
|
|
alert = msg
|
|
|
|
#If it's not a missing_srp_username, re-raise
|
|
if alert.description != AlertDescription.missing_srp_username:
|
|
self._shutdown(False)
|
|
raise TLSRemoteAlert(alert)
|
|
|
|
#If we're not in SRP callback mode, we won't have offered SRP
|
|
#without a username, so we shouldn't get this alert
|
|
if not srpCallback:
|
|
for result in self._sendError(\
|
|
AlertDescription.unexpected_message):
|
|
yield result
|
|
srpParams = srpCallback()
|
|
#If the callback returns None, cancel the handshake
|
|
if srpParams == None:
|
|
for result in self._sendError(AlertDescription.user_canceled):
|
|
yield result
|
|
|
|
#Recursively perform handshake
|
|
for result in self._handshakeClientAsyncHelper(srpParams,
|
|
None, None, None, None, settings, True):
|
|
yield result
|
|
return
|
|
|
|
#Get the server version. Do this before anything else, so any
|
|
#error alerts will use the server's version
|
|
self.version = serverHello.server_version
|
|
|
|
#Future responses from server must use this version
|
|
self._versionCheck = True
|
|
|
|
#Check ServerHello
|
|
if serverHello.server_version < settings.minVersion:
|
|
for result in self._sendError(\
|
|
AlertDescription.protocol_version,
|
|
"Too old version: %s" % str(serverHello.server_version)):
|
|
yield result
|
|
if serverHello.server_version > settings.maxVersion:
|
|
for result in self._sendError(\
|
|
AlertDescription.protocol_version,
|
|
"Too new version: %s" % str(serverHello.server_version)):
|
|
yield result
|
|
if serverHello.cipher_suite not in cipherSuites:
|
|
for result in self._sendError(\
|
|
AlertDescription.illegal_parameter,
|
|
"Server responded with incorrect ciphersuite"):
|
|
yield result
|
|
if serverHello.certificate_type not in certificateTypes:
|
|
for result in self._sendError(\
|
|
AlertDescription.illegal_parameter,
|
|
"Server responded with incorrect certificate type"):
|
|
yield result
|
|
if serverHello.compression_method != 0:
|
|
for result in self._sendError(\
|
|
AlertDescription.illegal_parameter,
|
|
"Server responded with incorrect compression method"):
|
|
yield result
|
|
|
|
#Get the server nonce
|
|
serverRandom = serverHello.random
|
|
|
|
#If the server agrees to resume
|
|
if session and session.sessionID and \
|
|
serverHello.session_id == session.sessionID:
|
|
|
|
#If a shared-key, we're flexible about suites; otherwise the
|
|
#server-chosen suite has to match the session's suite
|
|
if sharedKeyParams:
|
|
session.cipherSuite = serverHello.cipher_suite
|
|
elif serverHello.cipher_suite != session.cipherSuite:
|
|
for result in self._sendError(\
|
|
AlertDescription.illegal_parameter,\
|
|
"Server's ciphersuite doesn't match session"):
|
|
yield result
|
|
|
|
#Set the session for this connection
|
|
self.session = session
|
|
|
|
#Calculate pending connection states
|
|
self._calcPendingStates(clientRandom, serverRandom,
|
|
settings.cipherImplementations)
|
|
|
|
#Exchange ChangeCipherSpec and Finished messages
|
|
for result in self._getFinished():
|
|
yield result
|
|
for result in self._sendFinished():
|
|
yield result
|
|
|
|
#Mark the connection as open
|
|
self._handshakeDone(resumed=True)
|
|
|
|
#If server DOES NOT agree to resume
|
|
else:
|
|
|
|
if sharedKeyParams:
|
|
for result in self._sendError(\
|
|
AlertDescription.user_canceled,
|
|
"Was expecting a shared-key resumption"):
|
|
yield result
|
|
|
|
#We've already validated these
|
|
cipherSuite = serverHello.cipher_suite
|
|
certificateType = serverHello.certificate_type
|
|
|
|
#If the server chose an SRP suite...
|
|
if cipherSuite in CipherSuite.srpSuites:
|
|
#Get ServerKeyExchange, ServerHelloDone
|
|
for result in self._getMsg(ContentType.handshake,
|
|
HandshakeType.server_key_exchange, cipherSuite):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
serverKeyExchange = result
|
|
|
|
for result in self._getMsg(ContentType.handshake,
|
|
HandshakeType.server_hello_done):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
serverHelloDone = result
|
|
|
|
#If the server chose an SRP+RSA suite...
|
|
elif cipherSuite in CipherSuite.srpRsaSuites:
|
|
#Get Certificate, ServerKeyExchange, ServerHelloDone
|
|
for result in self._getMsg(ContentType.handshake,
|
|
HandshakeType.certificate, certificateType):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
serverCertificate = result
|
|
|
|
for result in self._getMsg(ContentType.handshake,
|
|
HandshakeType.server_key_exchange, cipherSuite):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
serverKeyExchange = result
|
|
|
|
for result in self._getMsg(ContentType.handshake,
|
|
HandshakeType.server_hello_done):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
serverHelloDone = result
|
|
|
|
#If the server chose an RSA suite...
|
|
elif cipherSuite in CipherSuite.rsaSuites:
|
|
#Get Certificate[, CertificateRequest], ServerHelloDone
|
|
for result in self._getMsg(ContentType.handshake,
|
|
HandshakeType.certificate, certificateType):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
serverCertificate = result
|
|
|
|
for result in self._getMsg(ContentType.handshake,
|
|
(HandshakeType.server_hello_done,
|
|
HandshakeType.certificate_request)):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
msg = result
|
|
|
|
certificateRequest = None
|
|
if isinstance(msg, CertificateRequest):
|
|
certificateRequest = msg
|
|
for result in self._getMsg(ContentType.handshake,
|
|
HandshakeType.server_hello_done):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
serverHelloDone = result
|
|
elif isinstance(msg, ServerHelloDone):
|
|
serverHelloDone = msg
|
|
else:
|
|
raise AssertionError()
|
|
|
|
|
|
#Calculate SRP premaster secret, if server chose an SRP or
|
|
#SRP+RSA suite
|
|
if cipherSuite in CipherSuite.srpSuites + \
|
|
CipherSuite.srpRsaSuites:
|
|
#Get and check the server's group parameters and B value
|
|
N = serverKeyExchange.srp_N
|
|
g = serverKeyExchange.srp_g
|
|
s = serverKeyExchange.srp_s
|
|
B = serverKeyExchange.srp_B
|
|
|
|
if (g,N) not in goodGroupParameters:
|
|
for result in self._sendError(\
|
|
AlertDescription.untrusted_srp_parameters,
|
|
"Unknown group parameters"):
|
|
yield result
|
|
if numBits(N) < settings.minKeySize:
|
|
for result in self._sendError(\
|
|
AlertDescription.untrusted_srp_parameters,
|
|
"N value is too small: %d" % numBits(N)):
|
|
yield result
|
|
if numBits(N) > settings.maxKeySize:
|
|
for result in self._sendError(\
|
|
AlertDescription.untrusted_srp_parameters,
|
|
"N value is too large: %d" % numBits(N)):
|
|
yield result
|
|
if B % N == 0:
|
|
for result in self._sendError(\
|
|
AlertDescription.illegal_parameter,
|
|
"Suspicious B value"):
|
|
yield result
|
|
|
|
#Check the server's signature, if server chose an
|
|
#SRP+RSA suite
|
|
if cipherSuite in CipherSuite.srpRsaSuites:
|
|
#Hash ServerKeyExchange/ServerSRPParams
|
|
hashBytes = serverKeyExchange.hash(clientRandom,
|
|
serverRandom)
|
|
|
|
#Extract signature bytes from ServerKeyExchange
|
|
sigBytes = serverKeyExchange.signature
|
|
if len(sigBytes) == 0:
|
|
for result in self._sendError(\
|
|
AlertDescription.illegal_parameter,
|
|
"Server sent an SRP ServerKeyExchange "\
|
|
"message without a signature"):
|
|
yield result
|
|
|
|
#Get server's public key from the Certificate message
|
|
for result in self._getKeyFromChain(serverCertificate,
|
|
settings):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
publicKey, serverCertChain = result
|
|
|
|
#Verify signature
|
|
if not publicKey.verify(sigBytes, hashBytes):
|
|
for result in self._sendError(\
|
|
AlertDescription.decrypt_error,
|
|
"Signature failed to verify"):
|
|
yield result
|
|
|
|
|
|
#Calculate client's ephemeral DH values (a, A)
|
|
a = bytesToNumber(getRandomBytes(32))
|
|
A = powMod(g, a, N)
|
|
|
|
#Calculate client's static DH values (x, v)
|
|
x = makeX(bytesToString(s), srpUsername, password)
|
|
v = powMod(g, x, N)
|
|
|
|
#Calculate u
|
|
u = makeU(N, A, B)
|
|
|
|
#Calculate premaster secret
|
|
k = makeK(N, g)
|
|
S = powMod((B - (k*v)) % N, a+(u*x), N)
|
|
|
|
if self.fault == Fault.badA:
|
|
A = N
|
|
S = 0
|
|
premasterSecret = numberToBytes(S)
|
|
|
|
#Send ClientKeyExchange
|
|
for result in self._sendMsg(\
|
|
ClientKeyExchange(cipherSuite).createSRP(A)):
|
|
yield result
|
|
|
|
|
|
#Calculate RSA premaster secret, if server chose an RSA suite
|
|
elif cipherSuite in CipherSuite.rsaSuites:
|
|
|
|
#Handle the presence of a CertificateRequest
|
|
if certificateRequest:
|
|
if unknownParams and certCallback:
|
|
certParamsNew = certCallback()
|
|
if certParamsNew:
|
|
clientCertChain, privateKey = certParamsNew
|
|
|
|
#Get server's public key from the Certificate message
|
|
for result in self._getKeyFromChain(serverCertificate,
|
|
settings):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
publicKey, serverCertChain = result
|
|
|
|
|
|
#Calculate premaster secret
|
|
premasterSecret = getRandomBytes(48)
|
|
premasterSecret[0] = settings.maxVersion[0]
|
|
premasterSecret[1] = settings.maxVersion[1]
|
|
|
|
if self.fault == Fault.badPremasterPadding:
|
|
premasterSecret[0] = 5
|
|
if self.fault == Fault.shortPremasterSecret:
|
|
premasterSecret = premasterSecret[:-1]
|
|
|
|
#Encrypt premaster secret to server's public key
|
|
encryptedPreMasterSecret = publicKey.encrypt(premasterSecret)
|
|
|
|
#If client authentication was requested, send Certificate
|
|
#message, either with certificates or empty
|
|
if certificateRequest:
|
|
clientCertificate = Certificate(certificateType)
|
|
|
|
if clientCertChain:
|
|
#Check to make sure we have the same type of
|
|
#certificates the server requested
|
|
wrongType = False
|
|
if certificateType == CertificateType.x509:
|
|
if not isinstance(clientCertChain, X509CertChain):
|
|
wrongType = True
|
|
elif certificateType == CertificateType.cryptoID:
|
|
if not isinstance(clientCertChain,
|
|
cryptoIDlib.CertChain.CertChain):
|
|
wrongType = True
|
|
if wrongType:
|
|
for result in self._sendError(\
|
|
AlertDescription.handshake_failure,
|
|
"Client certificate is of wrong type"):
|
|
yield result
|
|
|
|
clientCertificate.create(clientCertChain)
|
|
|
|
for result in self._sendMsg(clientCertificate):
|
|
yield result
|
|
else:
|
|
#The server didn't request client auth, so we
|
|
#zeroize these so the clientCertChain won't be
|
|
#stored in the session.
|
|
privateKey = None
|
|
clientCertChain = None
|
|
|
|
#Send ClientKeyExchange
|
|
clientKeyExchange = ClientKeyExchange(cipherSuite,
|
|
self.version)
|
|
clientKeyExchange.createRSA(encryptedPreMasterSecret)
|
|
for result in self._sendMsg(clientKeyExchange):
|
|
yield result
|
|
|
|
#If client authentication was requested and we have a
|
|
#private key, send CertificateVerify
|
|
if certificateRequest and privateKey:
|
|
if self.version == (3,0):
|
|
#Create a temporary session object, just for the
|
|
#purpose of creating the CertificateVerify
|
|
session = Session()
|
|
session._calcMasterSecret(self.version,
|
|
premasterSecret,
|
|
clientRandom,
|
|
serverRandom)
|
|
verifyBytes = self._calcSSLHandshakeHash(\
|
|
session.masterSecret, "")
|
|
elif self.version in ((3,1), (3,2)):
|
|
verifyBytes = stringToBytes(\
|
|
self._handshake_md5.digest() + \
|
|
self._handshake_sha.digest())
|
|
if self.fault == Fault.badVerifyMessage:
|
|
verifyBytes[0] = ((verifyBytes[0]+1) % 256)
|
|
signedBytes = privateKey.sign(verifyBytes)
|
|
certificateVerify = CertificateVerify()
|
|
certificateVerify.create(signedBytes)
|
|
for result in self._sendMsg(certificateVerify):
|
|
yield result
|
|
|
|
|
|
#Create the session object
|
|
self.session = Session()
|
|
self.session._calcMasterSecret(self.version, premasterSecret,
|
|
clientRandom, serverRandom)
|
|
self.session.sessionID = serverHello.session_id
|
|
self.session.cipherSuite = cipherSuite
|
|
self.session.srpUsername = srpUsername
|
|
self.session.clientCertChain = clientCertChain
|
|
self.session.serverCertChain = serverCertChain
|
|
|
|
#Calculate pending connection states
|
|
self._calcPendingStates(clientRandom, serverRandom,
|
|
settings.cipherImplementations)
|
|
|
|
#Exchange ChangeCipherSpec and Finished messages
|
|
for result in self._sendFinished():
|
|
yield result
|
|
for result in self._getFinished():
|
|
yield result
|
|
|
|
#Mark the connection as open
|
|
self.session._setResumable(True)
|
|
self._handshakeDone(resumed=False)
|
|
|
|
|
|
|
|
def handshakeServer(self, sharedKeyDB=None, verifierDB=None,
|
|
certChain=None, privateKey=None, reqCert=False,
|
|
sessionCache=None, settings=None, checker=None):
|
|
"""Perform a handshake in the role of server.
|
|
|
|
This function performs an SSL or TLS handshake. Depending on
|
|
the arguments and the behavior of the client, this function can
|
|
perform a shared-key, SRP, or certificate-based handshake. It
|
|
can also perform a combined SRP and server-certificate
|
|
handshake.
|
|
|
|
Like any handshake function, this can be called on a closed
|
|
TLS connection, or on a TLS connection that is already open.
|
|
If called on an open connection it performs a re-handshake.
|
|
This function does not send a Hello Request message before
|
|
performing the handshake, so if re-handshaking is required,
|
|
the server must signal the client to begin the re-handshake
|
|
through some other means.
|
|
|
|
If the function completes without raising an exception, the
|
|
TLS connection will be open and available for data transfer.
|
|
|
|
If an exception is raised, the connection will have been
|
|
automatically closed (if it was ever open).
|
|
|
|
@type sharedKeyDB: L{tlslite.SharedKeyDB.SharedKeyDB}
|
|
@param sharedKeyDB: A database of shared symmetric keys
|
|
associated with usernames. If the client performs a
|
|
shared-key handshake, the session's sharedKeyUsername
|
|
attribute will be set.
|
|
|
|
@type verifierDB: L{tlslite.VerifierDB.VerifierDB}
|
|
@param verifierDB: A database of SRP password verifiers
|
|
associated with usernames. If the client performs an SRP
|
|
handshake, the session's srpUsername attribute will be set.
|
|
|
|
@type certChain: L{tlslite.X509CertChain.X509CertChain} or
|
|
L{cryptoIDlib.CertChain.CertChain}
|
|
@param certChain: The certificate chain to be used if the
|
|
client requests server certificate authentication.
|
|
|
|
@type privateKey: L{tlslite.utils.RSAKey.RSAKey}
|
|
@param privateKey: The private key to be used if the client
|
|
requests server certificate authentication.
|
|
|
|
@type reqCert: bool
|
|
@param reqCert: Whether to request client certificate
|
|
authentication. This only applies if the client chooses server
|
|
certificate authentication; if the client chooses SRP or
|
|
shared-key authentication, this will be ignored. If the client
|
|
performs a client certificate authentication, the sessions's
|
|
clientCertChain attribute will be set.
|
|
|
|
@type sessionCache: L{tlslite.SessionCache.SessionCache}
|
|
@param sessionCache: An in-memory cache of resumable sessions.
|
|
The client can resume sessions from this cache. Alternatively,
|
|
if the client performs a full handshake, a new session will be
|
|
added to the cache.
|
|
|
|
@type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
|
|
@param settings: Various settings which can be used to control
|
|
the ciphersuites and SSL/TLS version chosen by the server.
|
|
|
|
@type checker: L{tlslite.Checker.Checker}
|
|
@param checker: A Checker instance. This instance will be
|
|
invoked to examine the other party's authentication
|
|
credentials, if the handshake completes succesfully.
|
|
|
|
@raise socket.error: If a socket error occurs.
|
|
@raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
|
|
without a preceding alert.
|
|
@raise tlslite.errors.TLSAlert: If a TLS alert is signalled.
|
|
@raise tlslite.errors.TLSAuthenticationError: If the checker
|
|
doesn't like the other party's authentication credentials.
|
|
"""
|
|
for result in self.handshakeServerAsync(sharedKeyDB, verifierDB,
|
|
certChain, privateKey, reqCert, sessionCache, settings,
|
|
checker):
|
|
pass
|
|
|
|
|
|
def handshakeServerAsync(self, sharedKeyDB=None, verifierDB=None,
|
|
certChain=None, privateKey=None, reqCert=False,
|
|
sessionCache=None, settings=None, checker=None):
|
|
"""Start a server handshake operation on the TLS connection.
|
|
|
|
This function returns a generator which behaves similarly to
|
|
handshakeServer(). Successive invocations of the generator
|
|
will return 0 if it is waiting to read from the socket, 1 if it is
|
|
waiting to write to the socket, or it will raise StopIteration
|
|
if the handshake operation is complete.
|
|
|
|
@rtype: iterable
|
|
@return: A generator; see above for details.
|
|
"""
|
|
handshaker = self._handshakeServerAsyncHelper(\
|
|
sharedKeyDB=sharedKeyDB,
|
|
verifierDB=verifierDB, certChain=certChain,
|
|
privateKey=privateKey, reqCert=reqCert,
|
|
sessionCache=sessionCache, settings=settings)
|
|
for result in self._handshakeWrapperAsync(handshaker, checker):
|
|
yield result
|
|
|
|
|
|
def _handshakeServerAsyncHelper(self, sharedKeyDB, verifierDB,
|
|
certChain, privateKey, reqCert, sessionCache,
|
|
settings):
|
|
|
|
self._handshakeStart(client=False)
|
|
|
|
if (not sharedKeyDB) and (not verifierDB) and (not certChain):
|
|
raise ValueError("Caller passed no authentication credentials")
|
|
if certChain and not privateKey:
|
|
raise ValueError("Caller passed a certChain but no privateKey")
|
|
if privateKey and not certChain:
|
|
raise ValueError("Caller passed a privateKey but no certChain")
|
|
|
|
if not settings:
|
|
settings = HandshakeSettings()
|
|
settings = settings._filter()
|
|
|
|
#Initialize acceptable cipher suites
|
|
cipherSuites = []
|
|
if verifierDB:
|
|
if certChain:
|
|
cipherSuites += \
|
|
CipherSuite.getSrpRsaSuites(settings.cipherNames)
|
|
cipherSuites += CipherSuite.getSrpSuites(settings.cipherNames)
|
|
if sharedKeyDB or certChain:
|
|
cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames)
|
|
|
|
#Initialize acceptable certificate type
|
|
certificateType = None
|
|
if certChain:
|
|
try:
|
|
import cryptoIDlib.CertChain
|
|
if isinstance(certChain, cryptoIDlib.CertChain.CertChain):
|
|
certificateType = CertificateType.cryptoID
|
|
except ImportError:
|
|
pass
|
|
if isinstance(certChain, X509CertChain):
|
|
certificateType = CertificateType.x509
|
|
if certificateType == None:
|
|
raise ValueError("Unrecognized certificate type")
|
|
|
|
#Initialize locals
|
|
clientCertChain = None
|
|
serverCertChain = None #We may set certChain to this later
|
|
postFinishedError = None
|
|
|
|
#Tentatively set version to most-desirable version, so if an error
|
|
#occurs parsing the ClientHello, this is what we'll use for the
|
|
#error alert
|
|
self.version = settings.maxVersion
|
|
|
|
#Get ClientHello
|
|
for result in self._getMsg(ContentType.handshake,
|
|
HandshakeType.client_hello):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
clientHello = result
|
|
|
|
#If client's version is too low, reject it
|
|
if clientHello.client_version < settings.minVersion:
|
|
self.version = settings.minVersion
|
|
for result in self._sendError(\
|
|
AlertDescription.protocol_version,
|
|
"Too old version: %s" % str(clientHello.client_version)):
|
|
yield result
|
|
|
|
#If client's version is too high, propose my highest version
|
|
elif clientHello.client_version > settings.maxVersion:
|
|
self.version = settings.maxVersion
|
|
|
|
else:
|
|
#Set the version to the client's version
|
|
self.version = clientHello.client_version
|
|
|
|
#Get the client nonce; create server nonce
|
|
clientRandom = clientHello.random
|
|
serverRandom = getRandomBytes(32)
|
|
|
|
#Calculate the first cipher suite intersection.
|
|
#This is the 'privileged' ciphersuite. We'll use it if we're
|
|
#doing a shared-key resumption or a new negotiation. In fact,
|
|
#the only time we won't use it is if we're resuming a non-sharedkey
|
|
#session, in which case we use the ciphersuite from the session.
|
|
#
|
|
#Given the current ciphersuite ordering, this means we prefer SRP
|
|
#over non-SRP.
|
|
for cipherSuite in cipherSuites:
|
|
if cipherSuite in clientHello.cipher_suites:
|
|
break
|
|
else:
|
|
for result in self._sendError(\
|
|
AlertDescription.handshake_failure):
|
|
yield result
|
|
|
|
#If resumption was requested...
|
|
if clientHello.session_id and (sharedKeyDB or sessionCache):
|
|
session = None
|
|
|
|
#Check in the sharedKeys container
|
|
if sharedKeyDB and len(clientHello.session_id)==16:
|
|
try:
|
|
#Trim off zero padding, if any
|
|
for x in range(16):
|
|
if clientHello.session_id[x]==0:
|
|
break
|
|
self.allegedSharedKeyUsername = bytesToString(\
|
|
clientHello.session_id[:x])
|
|
session = sharedKeyDB[self.allegedSharedKeyUsername]
|
|
if not session.sharedKey:
|
|
raise AssertionError()
|
|
#use privileged ciphersuite
|
|
session.cipherSuite = cipherSuite
|
|
except KeyError:
|
|
pass
|
|
|
|
#Then check in the session cache
|
|
if sessionCache and not session:
|
|
try:
|
|
session = sessionCache[bytesToString(\
|
|
clientHello.session_id)]
|
|
if session.sharedKey:
|
|
raise AssertionError()
|
|
if not session.resumable:
|
|
raise AssertionError()
|
|
#Check for consistency with ClientHello
|
|
if session.cipherSuite not in cipherSuites:
|
|
for result in self._sendError(\
|
|
AlertDescription.handshake_failure):
|
|
yield result
|
|
if session.cipherSuite not in clientHello.cipher_suites:
|
|
for result in self._sendError(\
|
|
AlertDescription.handshake_failure):
|
|
yield result
|
|
if clientHello.srp_username:
|
|
if clientHello.srp_username != session.srpUsername:
|
|
for result in self._sendError(\
|
|
AlertDescription.handshake_failure):
|
|
yield result
|
|
except KeyError:
|
|
pass
|
|
|
|
#If a session is found..
|
|
if session:
|
|
#Set the session
|
|
self.session = session
|
|
|
|
#Send ServerHello
|
|
serverHello = ServerHello()
|
|
serverHello.create(self.version, serverRandom,
|
|
session.sessionID, session.cipherSuite,
|
|
certificateType)
|
|
for result in self._sendMsg(serverHello):
|
|
yield result
|
|
|
|
#From here on, the client's messages must have the right version
|
|
self._versionCheck = True
|
|
|
|
#Calculate pending connection states
|
|
self._calcPendingStates(clientRandom, serverRandom,
|
|
settings.cipherImplementations)
|
|
|
|
#Exchange ChangeCipherSpec and Finished messages
|
|
for result in self._sendFinished():
|
|
yield result
|
|
for result in self._getFinished():
|
|
yield result
|
|
|
|
#Mark the connection as open
|
|
self._handshakeDone(resumed=True)
|
|
return
|
|
|
|
|
|
#If not a resumption...
|
|
|
|
#TRICKY: we might have chosen an RSA suite that was only deemed
|
|
#acceptable because of the shared-key resumption. If the shared-
|
|
#key resumption failed, because the identifier wasn't recognized,
|
|
#we might fall through to here, where we have an RSA suite
|
|
#chosen, but no certificate.
|
|
if cipherSuite in CipherSuite.rsaSuites and not certChain:
|
|
for result in self._sendError(\
|
|
AlertDescription.handshake_failure):
|
|
yield result
|
|
|
|
#If an RSA suite is chosen, check for certificate type intersection
|
|
#(We do this check down here because if the mismatch occurs but the
|
|
# client is using a shared-key session, it's okay)
|
|
if cipherSuite in CipherSuite.rsaSuites + \
|
|
CipherSuite.srpRsaSuites:
|
|
if certificateType not in clientHello.certificate_types:
|
|
for result in self._sendError(\
|
|
AlertDescription.handshake_failure,
|
|
"the client doesn't support my certificate type"):
|
|
yield result
|
|
|
|
#Move certChain -> serverCertChain, now that we're using it
|
|
serverCertChain = certChain
|
|
|
|
|
|
#Create sessionID
|
|
if sessionCache:
|
|
sessionID = getRandomBytes(32)
|
|
else:
|
|
sessionID = createByteArraySequence([])
|
|
|
|
#If we've selected an SRP suite, exchange keys and calculate
|
|
#premaster secret:
|
|
if cipherSuite in CipherSuite.srpSuites + CipherSuite.srpRsaSuites:
|
|
|
|
#If there's no SRP username...
|
|
if not clientHello.srp_username:
|
|
|
|
#Ask the client to re-send ClientHello with one
|
|
for result in self._sendMsg(Alert().create(\
|
|
AlertDescription.missing_srp_username,
|
|
AlertLevel.warning)):
|
|
yield result
|
|
|
|
#Get ClientHello
|
|
for result in self._getMsg(ContentType.handshake,
|
|
HandshakeType.client_hello):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
clientHello = result
|
|
|
|
#Check ClientHello
|
|
#If client's version is too low, reject it (COPIED CODE; BAD!)
|
|
if clientHello.client_version < settings.minVersion:
|
|
self.version = settings.minVersion
|
|
for result in self._sendError(\
|
|
AlertDescription.protocol_version,
|
|
"Too old version: %s" % str(clientHello.client_version)):
|
|
yield result
|
|
|
|
#If client's version is too high, propose my highest version
|
|
elif clientHello.client_version > settings.maxVersion:
|
|
self.version = settings.maxVersion
|
|
|
|
else:
|
|
#Set the version to the client's version
|
|
self.version = clientHello.client_version
|
|
|
|
#Recalculate the privileged cipher suite, making sure to
|
|
#pick an SRP suite
|
|
cipherSuites = [c for c in cipherSuites if c in \
|
|
CipherSuite.srpSuites + \
|
|
CipherSuite.srpRsaSuites]
|
|
for cipherSuite in cipherSuites:
|
|
if cipherSuite in clientHello.cipher_suites:
|
|
break
|
|
else:
|
|
for result in self._sendError(\
|
|
AlertDescription.handshake_failure):
|
|
yield result
|
|
|
|
#Get the client nonce; create server nonce
|
|
clientRandom = clientHello.random
|
|
serverRandom = getRandomBytes(32)
|
|
|
|
#The username better be there, this time
|
|
if not clientHello.srp_username:
|
|
for result in self._sendError(\
|
|
AlertDescription.illegal_parameter,
|
|
"Client resent a hello, but without the SRP"\
|
|
" username"):
|
|
yield result
|
|
|
|
|
|
#Get username
|
|
self.allegedSrpUsername = clientHello.srp_username
|
|
|
|
#Get parameters from username
|
|
try:
|
|
entry = verifierDB[self.allegedSrpUsername]
|
|
except KeyError:
|
|
for result in self._sendError(\
|
|
AlertDescription.unknown_srp_username):
|
|
yield result
|
|
(N, g, s, v) = entry
|
|
|
|
#Calculate server's ephemeral DH values (b, B)
|
|
b = bytesToNumber(getRandomBytes(32))
|
|
k = makeK(N, g)
|
|
B = (powMod(g, b, N) + (k*v)) % N
|
|
|
|
#Create ServerKeyExchange, signing it if necessary
|
|
serverKeyExchange = ServerKeyExchange(cipherSuite)
|
|
serverKeyExchange.createSRP(N, g, stringToBytes(s), B)
|
|
if cipherSuite in CipherSuite.srpRsaSuites:
|
|
hashBytes = serverKeyExchange.hash(clientRandom,
|
|
serverRandom)
|
|
serverKeyExchange.signature = privateKey.sign(hashBytes)
|
|
|
|
#Send ServerHello[, Certificate], ServerKeyExchange,
|
|
#ServerHelloDone
|
|
msgs = []
|
|
serverHello = ServerHello()
|
|
serverHello.create(self.version, serverRandom, sessionID,
|
|
cipherSuite, certificateType)
|
|
msgs.append(serverHello)
|
|
if cipherSuite in CipherSuite.srpRsaSuites:
|
|
certificateMsg = Certificate(certificateType)
|
|
certificateMsg.create(serverCertChain)
|
|
msgs.append(certificateMsg)
|
|
msgs.append(serverKeyExchange)
|
|
msgs.append(ServerHelloDone())
|
|
for result in self._sendMsgs(msgs):
|
|
yield result
|
|
|
|
#From here on, the client's messages must have the right version
|
|
self._versionCheck = True
|
|
|
|
#Get and check ClientKeyExchange
|
|
for result in self._getMsg(ContentType.handshake,
|
|
HandshakeType.client_key_exchange,
|
|
cipherSuite):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
clientKeyExchange = result
|
|
A = clientKeyExchange.srp_A
|
|
if A % N == 0:
|
|
postFinishedError = (AlertDescription.illegal_parameter,
|
|
"Suspicious A value")
|
|
#Calculate u
|
|
u = makeU(N, A, B)
|
|
|
|
#Calculate premaster secret
|
|
S = powMod((A * powMod(v,u,N)) % N, b, N)
|
|
premasterSecret = numberToBytes(S)
|
|
|
|
|
|
#If we've selected an RSA suite, exchange keys and calculate
|
|
#premaster secret:
|
|
elif cipherSuite in CipherSuite.rsaSuites:
|
|
|
|
#Send ServerHello, Certificate[, CertificateRequest],
|
|
#ServerHelloDone
|
|
msgs = []
|
|
msgs.append(ServerHello().create(self.version, serverRandom,
|
|
sessionID, cipherSuite, certificateType))
|
|
msgs.append(Certificate(certificateType).create(serverCertChain))
|
|
if reqCert:
|
|
msgs.append(CertificateRequest())
|
|
msgs.append(ServerHelloDone())
|
|
for result in self._sendMsgs(msgs):
|
|
yield result
|
|
|
|
#From here on, the client's messages must have the right version
|
|
self._versionCheck = True
|
|
|
|
#Get [Certificate,] (if was requested)
|
|
if reqCert:
|
|
if self.version == (3,0):
|
|
for result in self._getMsg((ContentType.handshake,
|
|
ContentType.alert),
|
|
HandshakeType.certificate,
|
|
certificateType):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
msg = result
|
|
|
|
if isinstance(msg, Alert):
|
|
#If it's not a no_certificate alert, re-raise
|
|
alert = msg
|
|
if alert.description != \
|
|
AlertDescription.no_certificate:
|
|
self._shutdown(False)
|
|
raise TLSRemoteAlert(alert)
|
|
elif isinstance(msg, Certificate):
|
|
clientCertificate = msg
|
|
if clientCertificate.certChain and \
|
|
clientCertificate.certChain.getNumCerts()!=0:
|
|
clientCertChain = clientCertificate.certChain
|
|
else:
|
|
raise AssertionError()
|
|
elif self.version in ((3,1), (3,2)):
|
|
for result in self._getMsg(ContentType.handshake,
|
|
HandshakeType.certificate,
|
|
certificateType):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
clientCertificate = result
|
|
if clientCertificate.certChain and \
|
|
clientCertificate.certChain.getNumCerts()!=0:
|
|
clientCertChain = clientCertificate.certChain
|
|
else:
|
|
raise AssertionError()
|
|
|
|
#Get ClientKeyExchange
|
|
for result in self._getMsg(ContentType.handshake,
|
|
HandshakeType.client_key_exchange,
|
|
cipherSuite):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
clientKeyExchange = result
|
|
|
|
#Decrypt ClientKeyExchange
|
|
premasterSecret = privateKey.decrypt(\
|
|
clientKeyExchange.encryptedPreMasterSecret)
|
|
|
|
randomPreMasterSecret = getRandomBytes(48)
|
|
versionCheck = (premasterSecret[0], premasterSecret[1])
|
|
if not premasterSecret:
|
|
premasterSecret = randomPreMasterSecret
|
|
elif len(premasterSecret)!=48:
|
|
premasterSecret = randomPreMasterSecret
|
|
elif versionCheck != clientHello.client_version:
|
|
if versionCheck != self.version: #Tolerate buggy IE clients
|
|
premasterSecret = randomPreMasterSecret
|
|
|
|
#Get and check CertificateVerify, if relevant
|
|
if clientCertChain:
|
|
if self.version == (3,0):
|
|
#Create a temporary session object, just for the purpose
|
|
#of checking the CertificateVerify
|
|
session = Session()
|
|
session._calcMasterSecret(self.version, premasterSecret,
|
|
clientRandom, serverRandom)
|
|
verifyBytes = self._calcSSLHandshakeHash(\
|
|
session.masterSecret, "")
|
|
elif self.version in ((3,1), (3,2)):
|
|
verifyBytes = stringToBytes(self._handshake_md5.digest() +\
|
|
self._handshake_sha.digest())
|
|
for result in self._getMsg(ContentType.handshake,
|
|
HandshakeType.certificate_verify):
|
|
if result in (0,1):
|
|
yield result
|
|
else:
|
|
break
|
|
certificateVerify = result
|
|
publicKey = clientCertChain.getEndEntityPublicKey()
|
|
if len(publicKey) < settings.minKeySize:
|
|
postFinishedError = (AlertDescription.handshake_failure,
|
|
"Client's public key too small: %d" % len(publicKey))
|
|
if len(publicKey) > settings.maxKeySize:
|
|
postFinishedError = (AlertDescription.handshake_failure,
|
|
"Client's public key too large: %d" % len(publicKey))
|
|
|
|
if not publicKey.verify(certificateVerify.signature,
|
|
verifyBytes):
|
|
postFinishedError = (AlertDescription.decrypt_error,
|
|
"Signature failed to verify")
|
|
|
|
|
|
#Create the session object
|
|
self.session = Session()
|
|
self.session._calcMasterSecret(self.version, premasterSecret,
|
|
clientRandom, serverRandom)
|
|
self.session.sessionID = sessionID
|
|
self.session.cipherSuite = cipherSuite
|
|
self.session.srpUsername = self.allegedSrpUsername
|
|
self.session.clientCertChain = clientCertChain
|
|
self.session.serverCertChain = serverCertChain
|
|
|
|
#Calculate pending connection states
|
|
self._calcPendingStates(clientRandom, serverRandom,
|
|
settings.cipherImplementations)
|
|
|
|
#Exchange ChangeCipherSpec and Finished messages
|
|
for result in self._getFinished():
|
|
yield result
|
|
|
|
#If we were holding a post-finished error until receiving the client
|
|
#finished message, send it now. We delay the call until this point
|
|
#because calling sendError() throws an exception, and our caller might
|
|
#shut down the socket upon receiving the exception. If he did, and the
|
|
#client was still sending its ChangeCipherSpec or Finished messages, it
|
|
#would cause a socket error on the client side. This is a lot of
|
|
#consideration to show to misbehaving clients, but this would also
|
|
#cause problems with fault-testing.
|
|
if postFinishedError:
|
|
for result in self._sendError(*postFinishedError):
|
|
yield result
|
|
|
|
for result in self._sendFinished():
|
|
yield result
|
|
|
|
#Add the session object to the session cache
|
|
if sessionCache and sessionID:
|
|
sessionCache[bytesToString(sessionID)] = self.session
|
|
|
|
#Mark the connection as open
|
|
self.session._setResumable(True)
|
|
self._handshakeDone(resumed=False)
|
|
|
|
|
|
def _handshakeWrapperAsync(self, handshaker, checker):
|
|
if not self.fault:
|
|
try:
|
|
for result in handshaker:
|
|
yield result
|
|
if checker:
|
|
try:
|
|
checker(self)
|
|
except TLSAuthenticationError:
|
|
alert = Alert().create(AlertDescription.close_notify,
|
|
AlertLevel.fatal)
|
|
for result in self._sendMsg(alert):
|
|
yield result
|
|
raise
|
|
except:
|
|
self._shutdown(False)
|
|
raise
|
|
else:
|
|
try:
|
|
for result in handshaker:
|
|
yield result
|
|
if checker:
|
|
try:
|
|
checker(self)
|
|
except TLSAuthenticationError:
|
|
alert = Alert().create(AlertDescription.close_notify,
|
|
AlertLevel.fatal)
|
|
for result in self._sendMsg(alert):
|
|
yield result
|
|
raise
|
|
except socket.error, e:
|
|
raise TLSFaultError("socket error!")
|
|
except TLSAbruptCloseError, e:
|
|
raise TLSFaultError("abrupt close error!")
|
|
except TLSAlert, alert:
|
|
if alert.description not in Fault.faultAlerts[self.fault]:
|
|
raise TLSFaultError(str(alert))
|
|
else:
|
|
pass
|
|
except:
|
|
self._shutdown(False)
|
|
raise
|
|
else:
|
|
raise TLSFaultError("No error!")
|
|
|
|
|
|
def _getKeyFromChain(self, certificate, settings):
|
|
#Get and check cert chain from the Certificate message
|
|
certChain = certificate.certChain
|
|
if not certChain or certChain.getNumCerts() == 0:
|
|
for result in self._sendError(AlertDescription.illegal_parameter,
|
|
"Other party sent a Certificate message without "\
|
|
"certificates"):
|
|
yield result
|
|
|
|
#Get and check public key from the cert chain
|
|
publicKey = certChain.getEndEntityPublicKey()
|
|
if len(publicKey) < settings.minKeySize:
|
|
for result in self._sendError(AlertDescription.handshake_failure,
|
|
"Other party's public key too small: %d" % len(publicKey)):
|
|
yield result
|
|
if len(publicKey) > settings.maxKeySize:
|
|
for result in self._sendError(AlertDescription.handshake_failure,
|
|
"Other party's public key too large: %d" % len(publicKey)):
|
|
yield result
|
|
|
|
yield publicKey, certChain
|