class SslClient(object): """ High level API implementing an insecure SSL client. """ def __init__(self, sock=None, sslVersion=SSLV23, sslVerify=SSL_VERIFY_PEER, sslVerifyLocations=None): # A Python socket handles transmission of the data self._sock = sock self._handshakeDone = False # OpenSSL objects # SSL_CTX self._sslCtx = SSL_CTX(sslVersion) self._sslCtx.set_verify(sslVerify) if sslVerifyLocations: self._sslCtx.load_verify_locations(sslVerifyLocations) # SSL self._ssl = SSL(self._sslCtx) self._ssl.set_connect_state() # Specific servers do not reply to a client hello that is bigger than 255 bytes # See http://rt.openssl.org/Ticket/Display.html?id=2771&user=guest&pass=guest # So we make the default cipher list smaller (to make the client hello smaller) if sslVersion != SSLV2: # This makes SSLv2 fail self._ssl.set_cipher_list('HIGH:-aNULL:-eNULL:-3DES:-SRP:-PSK:-CAMELLIA') else: # Handshake workaround for SSL2 + IIS 7 self.do_handshake = self.do_ssl2_iis_handshake # BIOs self._internalBio = BIO() self._networkBio = BIO() # http://www.openssl.org/docs/crypto/BIO_s_bio.html BIO.make_bio_pair(self._internalBio, self._networkBio) self._ssl.set_bio(self._internalBio) def do_handshake(self): if self._sock is None: # TODO: Auto create a socket ? raise IOError('Internal socket set to None; cannot perform handshake.') while True: try: if self._ssl.do_handshake() == 1: self._handshakeDone = True return True # Handshake was successful except WantReadError: # OpenSSL is expecting more data from the peer # Send available handshake data to the peer lenToRead = self._networkBio.pending() while lenToRead: # Get the data from the SSL engine handshakeDataOut = self._networkBio.read(lenToRead) # Send it to the peer self._sock.send(handshakeDataOut) lenToRead = self._networkBio.pending() # Recover the peer's encrypted response handshakeDataIn = self._sock.recv(DEFAULT_BUFFER_SIZE) if len(handshakeDataIn) == 0: raise IOError('Nassl SSL handshake failed: peer did not send data back.') # Pass the data to the SSL engine self._networkBio.write(handshakeDataIn) except WantX509LookupError: # Server asked for a client certificate and we didn't provide one raise ClientCertificateRequested(self._ssl.get_client_CA_list()) def do_ssl2_iis_handshake(self): if self._sock is None: # TODO: Auto create a socket ? raise IOError('Internal socket set to None; cannot perform handshake.') while True: try: if self._ssl.do_handshake() == 1: self._handshakeDone = True return True # Handshake was successful except WantReadError: # OpenSSL is expecting more data from the peer # Send available handshake data to the peer lenToRead = self._networkBio.pending() while lenToRead: # Get the data from the SSL engine handshakeDataOut = self._networkBio.read(lenToRead) if 'SSLv2 read server verify A' in self._ssl.state_string_long(): # Awful h4ck for SSLv2 when connecting to IIS7 (like in the 90s) # OpenSSL sends the client's CMK and data message in the same packet without # waiting for the server's response, causing IIS 7 to hang on the connection. # This workaround forces our client to send the CMK message, then wait for the server's # response, and then send the data packet if '\x02' in handshakeDataOut[2]: # Make sure we're looking at the CMK message cmkSize = handshakeDataOut[0:2] test1 = int(handshakeDataOut[0].encode('hex'), base=16) test2 = int(handshakeDataOut[1].encode('hex'), base=16) test1 = (test1 & 0x7f) << 8 size = test1 + test2 # Manually split the two records to force them to be sent separately cmkPacket = handshakeDataOut[0:size+2] dataPacket = handshakeDataOut[size+2::] self._sock.send(cmkPacket) handshakeDataIn = self._sock.recv(DEFAULT_BUFFER_SIZE) #print repr(handshakeDataIn) if len(handshakeDataIn) == 0: raise IOError('Nassl SSL handshake failed: peer did not send data back.') # Pass the data to the SSL engine self._networkBio.write(handshakeDataIn) handshakeDataOut = dataPacket # Send it to the peer self._sock.send(handshakeDataOut) lenToRead = self._networkBio.pending() handshakeDataIn = self._sock.recv(DEFAULT_BUFFER_SIZE) if len(handshakeDataIn) == 0: raise IOError('Nassl SSL handshake failed: peer did not send data back.') # Pass the data to the SSL engine self._networkBio.write(handshakeDataIn) except WantX509LookupError: # Server asked for a client certificate and we didn't provide one raise ClientCertificateRequested(self._ssl.get_client_CA_list()) def read(self, size): if not self._handshakeDone: raise IOError('SSL Handshake was not completed; cannot receive data.') while True: # Receive available encrypted data from the peer encData = self._sock.recv(DEFAULT_BUFFER_SIZE) # Pass it to the SSL engine self._networkBio.write(encData) try: # Try to read the decrypted data decData = self._ssl.read(size) return decData except WantReadError: # The SSL engine needs more data # before it can decrypt the whole message pass def write(self, data): """ Returns the number of (encrypted) bytes sent. """ if not self._handshakeDone: raise IOError('SSL Handshake was not completed; cannot send data.') # Pass the cleartext data to the SSL engine self._ssl.write(data) # Recover the corresponding encrypted data lenToRead = self._networkBio.pending() finalLen = lenToRead while lenToRead: encData = self._networkBio.read(lenToRead) # Send the encrypted data to the peer self._sock.send(encData) lenToRead = self._networkBio.pending() finalLen += lenToRead return finalLen def shutdown(self): self._handshakeDone = False try: self._ssl.shutdown() except OpenSSLError as e: # Ignore "uninitialized" exception if 'SSL_shutdown:uninitialized' not in str(e): raise def set_verify(self, verifyMode): """Set the OpenSSL verify mode.""" return self._ssl.set_verify(verifyMode) def set_tlsext_host_name(self, nameIndication): """Set the hostname within the Server Name Indication extension in the client SSL Hello.""" return self._ssl.set_tlsext_host_name(nameIndication) def get_peer_certificate(self): _x509 = self._ssl.get_peer_certificate() if _x509: return X509Certificate(_x509) else: return None def get_peer_cert_chain(self): """ See the OpenSSL documentation for differences between get_peer_cert_chain() and get_peer_certificate(). https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html """ _x509_list = self._ssl.get_peer_cert_chain() final_list = [] if _x509_list: for _x509 in _x509_list: final_list.append(X509Certificate(_x509)) return final_list def set_cipher_list(self, cipherList): return self._ssl.set_cipher_list(cipherList) def get_cipher_list(self): return self._ssl.get_cipher_list() def get_current_cipher_name(self): return self._ssl.get_cipher_name() def get_current_cipher_bits(self): return self._ssl.get_cipher_bits() def use_private_key(self, certFile, certType, keyFile, keyType, keyPassword=''): self._ssl.use_certificate_file(certFile, certType) if isinstance(keyPassword, basestring): self._sslCtx.set_private_key_password(keyPassword) else: raise TypeError('keyPassword is not a string') self._ssl.use_PrivateKey_file(keyFile, keyType) return self._ssl.check_private_key() def get_certificate_chain_verify_result(self): verifyResult = self._ssl.get_verify_result() verifyResultStr = X509.verify_cert_error_string(verifyResult) return verifyResult, verifyResultStr def set_tlsext_status_ocsp(self): """Enable the OCSP Stapling extension.""" return self._ssl.set_tlsext_status_type(TLSEXT_STATUSTYPE_ocsp) def get_tlsext_status_ocsp_resp(self): """Retrieve the server's OCSP Stapling status.""" ocspResp = self._ssl.get_tlsext_status_ocsp_resp() if ocspResp: return OcspResponse(ocspResp) else: return None
class SslClient(object): """ High level API implementing an insecure SSL client. """ def __init__(self, sock=None, sslVersion=SSLV23, sslVerify=SSL_VERIFY_PEER, sslVerifyLocations=None): # A Python socket handles transmission of the data self._sock = sock self._handshakeDone = False # OpenSSL objects # SSL_CTX self._sslCtx = SSL_CTX(sslVersion) self._sslCtx.set_verify(sslVerify) if sslVerifyLocations: self._sslCtx.load_verify_locations(sslVerifyLocations) # SSL self._ssl = SSL(self._sslCtx) self._ssl.set_connect_state() # Specific servers do not reply to a client hello that is bigger than 255 bytes # See http://rt.openssl.org/Ticket/Display.html?id=2771&user=guest&pass=guest # So we make the default cipher list smaller (to make the client hello smaller) if sslVersion != SSLV2: # This makes SSLv2 fail self._ssl.set_cipher_list( 'HIGH:-aNULL:-eNULL:-3DES:-SRP:-PSK:-CAMELLIA') # BIOs self._internalBio = BIO() self._networkBio = BIO() # http://www.openssl.org/docs/crypto/BIO_s_bio.html BIO.make_bio_pair(self._internalBio, self._networkBio) self._ssl.set_bio(self._internalBio) def do_handshake(self): if (self._sock == None): # TODO: Auto create a socket ? raise IOError( 'Internal socket set to None; cannot perform handshake.') while True: try: if self._ssl.do_handshake() == 1: self._handshakeDone = True return True # Handshake was successful except WantReadError: # OpenSSL is expecting more data from the peer # Send available handshake data to the peer lenToRead = self._networkBio.pending() while lenToRead: # Get the data from the SSL engine handshakeDataOut = self._networkBio.read(lenToRead) # Send it to the peer self._sock.send(handshakeDataOut) lenToRead = self._networkBio.pending() # Recover the peer's encrypted response handshakeDataIn = self._sock.recv(DEFAULT_BUFFER_SIZE) if len(handshakeDataIn) == 0: raise IOError( 'Nassl SSL handshake failed: peer did not send data back.' ) # Pass the data to the SSL engine self._networkBio.write(handshakeDataIn) except WantX509LookupError: # Server asked for a client certificate and we didn't provide one raise ClientCertificateRequested( self._ssl.get_client_CA_list()) def read(self, size): if (self._handshakeDone == False): raise IOError( 'SSL Handshake was not completed; cannot receive data.') while True: # Receive available encrypted data from the peer encData = self._sock.recv(DEFAULT_BUFFER_SIZE) # Pass it to the SSL engine self._networkBio.write(encData) try: # Try to read the decrypted data decData = self._ssl.read(size) return decData except WantReadError: # The SSL engine needs more data # before it can decrypt the whole message pass def write(self, data): """ Returns the number of (encrypted) bytes sent. """ if (self._handshakeDone == False): raise IOError('SSL Handshake was not completed; cannot send data.') # Pass the cleartext data to the SSL engine self._ssl.write(data) # Recover the corresponding encrypted data lenToRead = self._networkBio.pending() finalLen = lenToRead while lenToRead: encData = self._networkBio.read(lenToRead) # Send the encrypted data to the peer self._sock.send(encData) lenToRead = self._networkBio.pending() finalLen += lenToRead return finalLen def shutdown(self): self._handshakeDone = False try: self._ssl.shutdown() except OpenSSLError as e: # Ignore "uninitialized" exception if 'SSL_shutdown:uninitialized' not in str(e): raise def get_secure_renegotiation_support(self): return self._ssl.get_secure_renegotiation_support() def get_current_compression_method(self): return self._ssl.get_current_compression_method() @staticmethod def get_available_compression_methods(): """ Returns the list of SSL compression methods supported by SslClient. """ return SSL.get_available_compression_methods() def set_verify(self, verifyMode): return self._ssl.set_verify(verifyMode) def set_tlsext_host_name(self, nameIndication): return self._ssl.set_tlsext_host_name(nameIndication) def get_peer_certificate(self): _x509 = self._ssl.get_peer_certificate() if _x509: return X509Certificate(_x509) else: return None def set_cipher_list(self, cipherList): return self._ssl.set_cipher_list(cipherList) def get_cipher_list(self): return self._ssl.get_cipher_list() def get_current_cipher_name(self): return self._ssl.get_cipher_name() def get_current_cipher_bits(self): return self._ssl.get_cipher_bits() def use_private_key(self, certFile, certType, keyFile, keyType, keyPassword=''): self._ssl.use_certificate_file(certFile, certType) if isinstance(keyPassword, basestring): self._sslCtx.set_private_key_password(keyPassword) else: raise TypeError('keyPassword is not a string') self._ssl.use_PrivateKey_file(keyFile, keyType) return self._ssl.check_private_key() def get_certificate_chain_verify_result(self): verifyResult = self._ssl.get_verify_result() verifyResultStr = X509.verify_cert_error_string(verifyResult) return (verifyResult, verifyResultStr) def do_renegotiate(self): if (self._handshakeDone == False): raise IOError( 'SSL Handshake was not completed; cannot renegotiate.') self._ssl.renegotiate() return self.do_handshake() def get_session(self): return self._ssl.get_session() def set_session(self, sslSession): return self._ssl.set_session(sslSession) def set_options(self, options): return self._ssl.set_options(options) def set_tlsext_status_ocsp(self): return self._ssl.set_tlsext_status_type(TLSEXT_STATUSTYPE_ocsp) def get_tlsext_status_ocsp_resp(self): ocspResp = self._ssl.get_tlsext_status_ocsp_resp() if ocspResp: return OcspResponse(ocspResp) else: return None
class SslClient(object): """High level API implementing an SSL client. """ _DEFAULT_BUFFER_SIZE = 4096 def __init__( self, sock=None, # type: Optional[socket.socket] ssl_version=OpenSslVersionEnum.SSLV23, # type: OpenSslVersionEnum ssl_verify=OpenSslVerifyEnum.PEER, # type: OpenSslVerifyEnum ssl_verify_locations=None, # type: Optional[Text] client_certchain_file=None, # type: Optional[Text] client_key_file=None, # type: Optional[Text] client_key_type=OpenSslFileTypeEnum.PEM, # type: OpenSslFileTypeEnum client_key_password='', # type: Text ignore_client_authentication_requests=False # type: bool ): # type: (...) -> None # A Python socket handles transmission of the data self._sock = sock self._is_handshake_completed = False self._client_CA_list = [] # OpenSSL objects # SSL_CTX self._ssl_ctx = SSL_CTX(ssl_version.value) self._ssl_ctx.set_verify(ssl_verify.value) if ssl_verify_locations: # Ensure the file exists with open(ssl_verify_locations): pass self._ssl_ctx.load_verify_locations(ssl_verify_locations) if client_certchain_file is not None: self._use_private_key(client_certchain_file, client_key_file, client_key_type.value, client_key_password) if ignore_client_authentication_requests: if client_certchain_file: raise ValueError( 'Cannot enable both client_certchain_file and ignore_client_authentication_requests' ) self._ssl_ctx.set_client_cert_cb_NULL() # SSL self._ssl = SSL(self._ssl_ctx) self._ssl.set_connect_state() # Specific servers do not reply to a client hello that is bigger than 255 bytes # See http://rt.openssl.org/Ticket/Display.html?id=2771&user=guest&pass=guest # So we make the default cipher list smaller (to make the client hello smaller) if ssl_version != OpenSslVersionEnum.SSLV2: # This makes SSLv2 fail self._ssl.set_cipher_list( 'HIGH:-aNULL:-eNULL:-3DES:-SRP:-PSK:-CAMELLIA') else: # Handshake workaround for SSL2 + IIS 7 self.do_handshake = self.do_ssl2_iis_handshake # BIOs self._internal_bio = BIO() self._network_bio = BIO() # http://www.openssl.org/docs/crypto/BIO_s_bio.html BIO.make_bio_pair(self._internal_bio, self._network_bio) self._ssl.set_bio(self._internal_bio) def do_handshake(self): # type: () -> None if self._sock is None: # TODO: Auto create a socket ? raise IOError( 'Internal socket set to None; cannot perform handshake.') while True: try: self._ssl.do_handshake() self._is_handshake_completed = True # Handshake was successful return except WantReadError: # OpenSSL is expecting more data from the peer # Send available handshake data to the peer self._flush_ssl_engine() # Recover the peer's encrypted response handshake_data_in = self._sock.recv(self._DEFAULT_BUFFER_SIZE) if len(handshake_data_in) == 0: raise IOError( 'Nassl SSL handshake failed: peer did not send data back.' ) # Pass the data to the SSL engine self._network_bio.write(handshake_data_in) except WantX509LookupError: # Server asked for a client certificate and we didn't provide one raise ClientCertificateRequested(self.get_client_CA_list()) def do_ssl2_iis_handshake(self): # type: () -> None if self._sock is None: # TODO: Auto create a socket ? raise IOError( 'Internal socket set to None; cannot perform handshake.') while True: try: self._ssl.do_handshake() self._is_handshake_completed = True # Handshake was successful return except WantReadError: # OpenSSL is expecting more data from the peer # Send available handshake data to the peer lengh_to_read = self._network_bio.pending() while lengh_to_read: # Get the data from the SSL engine handshake_data_out = self._network_bio.read(lengh_to_read) if 'SSLv2 read server verify A' in self._ssl.state_string_long( ): # Awful h4ck for SSLv2 when connecting to IIS7 (like in the 90s) # OpenSSL sends the client's CMK and data message in the same packet without # waiting for the server's response, causing IIS 7 to hang on the connection. # This workaround forces our client to send the CMK message, then wait for the server's # response, and then send the data packet if '\x02' in handshake_data_out[ 2]: # Make sure we're looking at the CMK message # cmk_size = handshake_data_out[0:2] test1 = int(handshake_data_out[0].encode('hex'), base=16) test2 = int(handshake_data_out[1].encode('hex'), base=16) test1 = (test1 & 0x7f) << 8 size = test1 + test2 # Manually split the two records to force them to be sent separately cmk_packet = handshake_data_out[0:size + 2] data_packet = handshake_data_out[size + 2::] self._sock.send(cmk_packet) handshake_data_in = self._sock.recv( self._DEFAULT_BUFFER_SIZE) # print repr(handshake_data_in) if len(handshake_data_in) == 0: raise IOError( 'Nassl SSL handshake failed: peer did not send data back.' ) # Pass the data to the SSL engine self._network_bio.write(handshake_data_in) handshake_data_out = data_packet # Send it to the peer self._sock.send(handshake_data_out) lengh_to_read = self._network_bio.pending() handshake_data_in = self._sock.recv(self._DEFAULT_BUFFER_SIZE) if len(handshake_data_in) == 0: raise IOError( 'Nassl SSL handshake failed: peer did not send data back.' ) # Pass the data to the SSL engine self._network_bio.write(handshake_data_in) except WantX509LookupError: # Server asked for a client certificate and we didn't provide one raise ClientCertificateRequested(self.get_client_CA_list()) def read(self, size): # type: (int) -> bytes if not self._is_handshake_completed: raise IOError( 'SSL Handshake was not completed; cannot receive data.') while True: # Receive available encrypted data from the peer encrypted_data = self._sock.recv(self._DEFAULT_BUFFER_SIZE) if len(encrypted_data) == 0: raise IOError('Could not read() - peer closed the connection.') # Pass it to the SSL engine self._network_bio.write(encrypted_data) try: # Try to read the decrypted data decrypted_data = self._ssl.read(size) return decrypted_data except WantReadError: # The SSL engine needs more data # before it can decrypt the whole message pass def write(self, data): # type: (bytes) -> int """Returns the number of (encrypted) bytes sent. """ if not self._is_handshake_completed: raise IOError('SSL Handshake was not completed; cannot send data.') # Pass the cleartext data to the SSL engine self._ssl.write(data) # Recover the corresponding encrypted data final_length = self._flush_ssl_engine() return final_length def _flush_ssl_engine(self): # type: () -> int length_to_read = self._network_bio.pending() final_length = length_to_read while length_to_read: encrypted_data = self._network_bio.read(length_to_read) # Send the encrypted data to the peer self._sock.send(encrypted_data) length_to_read = self._network_bio.pending() final_length += length_to_read return final_length def shutdown(self): # type: () -> None self._is_handshake_completed = False try: self._ssl.shutdown() self._flush_ssl_engine() except OpenSSLError as e: # Ignore "uninitialized" exception if 'SSL_shutdown:uninitialized' not in str( e) and 'shutdown while in init' not in str(e): raise def set_tlsext_host_name(self, name_indication): # type: (Text) -> None """Set the hostname within the Server Name Indication extension in the client SSL Hello. """ self._ssl.set_tlsext_host_name(name_indication) def get_peer_certificate(self): # type: () -> Optional[X509Certificate] _x509 = self._ssl.get_peer_certificate() if _x509: return X509Certificate(_x509) else: return None def get_peer_cert_chain(self): # type: () -> List[X509Certificate] """See the OpenSSL documentation for differences between get_peer_cert_chain() and get_peer_certificate(). https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html """ x509_list = self._ssl.get_peer_cert_chain() final_list = [] if x509_list: for x509_cert in x509_list: final_list.append(X509Certificate(x509_cert)) return final_list def set_cipher_list(self, cipher_list): # type: (Text) -> None self._ssl.set_cipher_list(cipher_list) def get_cipher_list(self): # type: () -> List[Text] return self._ssl.get_cipher_list() def get_current_cipher_name(self): # type: () -> Text return self._ssl.get_cipher_name() def get_current_cipher_bits(self): # type: () -> int return self._ssl.get_cipher_bits() def _use_private_key(self, client_certchain_file, client_key_file, client_key_type, client_key_password): # type: (Text, Text, OpenSslFileTypeEnum, Text) -> None """The certificate chain file must be in PEM format. Private method because it should be set via the constructor. """ # Ensure the files exist with open(client_certchain_file): pass with open(client_key_file): pass self._ssl_ctx.use_certificate_chain_file(client_certchain_file) self._ssl_ctx.set_private_key_password(client_key_password) try: self._ssl_ctx.use_PrivateKey_file(client_key_file, client_key_type.value) except OpenSSLError as e: if 'bad password read' in str(e) or 'bad decrypt' in str(e): raise ValueError('Invalid Private Key') else: raise self._ssl_ctx.check_private_key() def get_certificate_chain_verify_result(self): # type: () -> Tuple[int, Text] verify_result = self._ssl.get_verify_result() verify_result_str = X509.verify_cert_error_string(verify_result) return verify_result, verify_result_str _TLSEXT_STATUSTYPE_ocsp = 1 def set_tlsext_status_ocsp(self): # type: () -> None """Enable the OCSP Stapling extension. """ self._ssl.set_tlsext_status_type(self._TLSEXT_STATUSTYPE_ocsp) def get_tlsext_status_ocsp_resp(self): # type: () -> Optional[OcspResponse] """Retrieve the server's OCSP Stapling status. """ ocsp_response = self._ssl.get_tlsext_status_ocsp_resp() if ocsp_response: return OcspResponse(ocsp_response) else: return None def get_client_CA_list(self): # type: () -> List[Text] if not self._client_CA_list: self._client_CA_list = self._ssl.get_client_CA_list() return self._client_CA_list
class SslClient(object): """ High level API implementing an insecure SSL client. """ def __init__(self, sock=None, sslVersion=SSLV23, sslVerify=SSL_VERIFY_PEER, sslVerifyLocations=None): # A Python socket handles transmission of the data self._sock = sock self._handshakeDone = False # OpenSSL objects # SSL_CTX self._sslCtx = SSL_CTX(sslVersion) self._sslCtx.set_verify(sslVerify) if sslVerifyLocations: self._sslCtx.load_verify_locations(sslVerifyLocations) # SSL self._ssl = SSL(self._sslCtx) self._ssl.set_connect_state() # Specific servers do not reply to a client hello that is bigger than 255 bytes # See http://rt.openssl.org/Ticket/Display.html?id=2771&user=guest&pass=guest # So we make the default cipher list smaller (to make the client hello smaller) if sslVersion != SSLV2: # This makes SSLv2 fail self._ssl.set_cipher_list('HIGH:-aNULL:-eNULL:-3DES:-SRP:-PSK:-CAMELLIA') # BIOs self._internalBio = BIO() self._networkBio = BIO() # http://www.openssl.org/docs/crypto/BIO_s_bio.html BIO.make_bio_pair(self._internalBio, self._networkBio) self._ssl.set_bio(self._internalBio) def do_handshake(self): if self._sock is None: # TODO: Auto create a socket ? raise IOError('Internal socket set to None; cannot perform handshake.') while True: try: if self._ssl.do_handshake() == 1: self._handshakeDone = True return True # Handshake was successful except WantReadError: # OpenSSL is expecting more data from the peer # Send available handshake data to the peer lenToRead = self._networkBio.pending() while lenToRead: # Get the data from the SSL engine handshakeDataOut = self._networkBio.read(lenToRead) # Send it to the peer self._sock.send(handshakeDataOut) lenToRead = self._networkBio.pending() # Recover the peer's encrypted response handshakeDataIn = self._sock.recv(DEFAULT_BUFFER_SIZE) if len(handshakeDataIn) == 0: raise IOError('Nassl SSL handshake failed: peer did not send data back.') # Pass the data to the SSL engine self._networkBio.write(handshakeDataIn) except WantX509LookupError: # Server asked for a client certificate and we didn't provide one raise ClientCertificateRequested(self._ssl.get_client_CA_list()) def read(self, size): if not self._handshakeDone: raise IOError('SSL Handshake was not completed; cannot receive data.') while True: # Receive available encrypted data from the peer encData = self._sock.recv(DEFAULT_BUFFER_SIZE) # Pass it to the SSL engine self._networkBio.write(encData) try: # Try to read the decrypted data decData = self._ssl.read(size) return decData except WantReadError: # The SSL engine needs more data # before it can decrypt the whole message pass def write(self, data): """ Returns the number of (encrypted) bytes sent. """ if not self._handshakeDone: raise IOError('SSL Handshake was not completed; cannot send data.') # Pass the cleartext data to the SSL engine self._ssl.write(data) # Recover the corresponding encrypted data lenToRead = self._networkBio.pending() finalLen = lenToRead while lenToRead: encData = self._networkBio.read(lenToRead) # Send the encrypted data to the peer self._sock.send(encData) lenToRead = self._networkBio.pending() finalLen += lenToRead return finalLen def shutdown(self): self._handshakeDone = False try: self._ssl.shutdown() except OpenSSLError as e: # Ignore "uninitialized" exception if 'SSL_shutdown:uninitialized' not in str(e): raise def get_secure_renegotiation_support(self): return self._ssl.get_secure_renegotiation_support() def get_current_compression_method(self): return self._ssl.get_current_compression_method() @staticmethod def get_available_compression_methods(): """ Returns the list of SSL compression methods supported by SslClient. """ return SSL.get_available_compression_methods() def set_verify(self, verifyMode): return self._ssl.set_verify(verifyMode) def set_tlsext_host_name(self, nameIndication): return self._ssl.set_tlsext_host_name(nameIndication) def get_peer_certificate(self): _x509 = self._ssl.get_peer_certificate() if _x509: return X509Certificate(_x509) else: return None def set_cipher_list(self, cipherList): return self._ssl.set_cipher_list(cipherList) def get_cipher_list(self): return self._ssl.get_cipher_list() def get_current_cipher_name(self): return self._ssl.get_cipher_name() def get_current_cipher_bits(self): return self._ssl.get_cipher_bits() def use_private_key(self, certFile, certType, keyFile, keyType, keyPassword=''): self._ssl.use_certificate_file(certFile, certType) if isinstance(keyPassword, basestring): self._sslCtx.set_private_key_password(keyPassword) else: raise TypeError('keyPassword is not a string') self._ssl.use_PrivateKey_file(keyFile, keyType) return self._ssl.check_private_key() def get_certificate_chain_verify_result(self): verifyResult = self._ssl.get_verify_result() verifyResultStr = X509.verify_cert_error_string(verifyResult) return verifyResult, verifyResultStr def do_renegotiate(self): if not self._handshakeDone: raise IOError('SSL Handshake was not completed; cannot renegotiate.') self._ssl.renegotiate() return self.do_handshake() def get_session(self): return self._ssl.get_session() def set_session(self, sslSession): return self._ssl.set_session(sslSession) def set_options(self, options): return self._ssl.set_options(options) def set_tlsext_status_ocsp(self): return self._ssl.set_tlsext_status_type(TLSEXT_STATUSTYPE_ocsp) def get_tlsext_status_ocsp_resp(self): ocspResp = self._ssl.get_tlsext_status_ocsp_resp() if ocspResp: return OcspResponse(ocspResp) else: return None
class SslClient(object): """High level API implementing an SSL client. """ def __init__(self, sock=None, ssl_version=SSLV23, ssl_verify=SSL_VERIFY_PEER, ssl_verify_locations=None, client_certchain_file=None, client_key_file=None, client_key_type=SSL_FILETYPE_PEM, client_key_password='', ignore_client_authentication_requests=False): # type: (socket.socket, int, int, str, str, str, int, str, bool) -> None # A Python socket handles transmission of the data self._sock = sock self._is_handshake_completed = False self._client_CA_list = [] # OpenSSL objects # SSL_CTX self._ssl_ctx = SSL_CTX(ssl_version) self._ssl_ctx.set_verify(ssl_verify) if ssl_verify_locations: self._ssl_ctx.load_verify_locations(ssl_verify_locations) if client_certchain_file is not None: self._use_private_key(client_certchain_file, client_key_file, client_key_type, client_key_password) if ignore_client_authentication_requests: if client_certchain_file: raise ValueError('Cannot enable both client_certchain_file and ignore_client_authentication_requests') self._ssl_ctx.set_client_cert_cb_NULL() # SSL self._ssl = SSL(self._ssl_ctx) self._ssl.set_connect_state() # Specific servers do not reply to a client hello that is bigger than 255 bytes # See http://rt.openssl.org/Ticket/Display.html?id=2771&user=guest&pass=guest # So we make the default cipher list smaller (to make the client hello smaller) if ssl_version != SSLV2: # This makes SSLv2 fail self._ssl.set_cipher_list('HIGH:-aNULL:-eNULL:-3DES:-SRP:-PSK:-CAMELLIA') else: # Handshake workaround for SSL2 + IIS 7 self.do_handshake = self.do_ssl2_iis_handshake # BIOs self._internal_bio = BIO() self._network_bio = BIO() # http://www.openssl.org/docs/crypto/BIO_s_bio.html BIO.make_bio_pair(self._internal_bio, self._network_bio) self._ssl.set_bio(self._internal_bio) def do_handshake(self): # type: () -> None if self._sock is None: # TODO: Auto create a socket ? raise IOError('Internal socket set to None; cannot perform handshake.') while True: try: self._ssl.do_handshake() self._is_handshake_completed = True # Handshake was successful return except WantReadError: # OpenSSL is expecting more data from the peer # Send available handshake data to the peer self._flush_ssl_engine() # Recover the peer's encrypted response handshake_data_in = self._sock.recv(DEFAULT_BUFFER_SIZE) if len(handshake_data_in) == 0: raise IOError('Nassl SSL handshake failed: peer did not send data back.') # Pass the data to the SSL engine self._network_bio.write(handshake_data_in) except WantX509LookupError: # Server asked for a client certificate and we didn't provide one raise ClientCertificateRequested(self.get_client_CA_list()) def do_ssl2_iis_handshake(self): # type: () -> None if self._sock is None: # TODO: Auto create a socket ? raise IOError('Internal socket set to None; cannot perform handshake.') while True: try: self._ssl.do_handshake() self._is_handshake_completed = True # Handshake was successful return except WantReadError: # OpenSSL is expecting more data from the peer # Send available handshake data to the peer lengh_to_read = self._network_bio.pending() while lengh_to_read: # Get the data from the SSL engine handshake_data_out = self._network_bio.read(lengh_to_read) if 'SSLv2 read server verify A' in self._ssl.state_string_long(): # Awful h4ck for SSLv2 when connecting to IIS7 (like in the 90s) # OpenSSL sends the client's CMK and data message in the same packet without # waiting for the server's response, causing IIS 7 to hang on the connection. # This workaround forces our client to send the CMK message, then wait for the server's # response, and then send the data packet if '\x02' in handshake_data_out[2]: # Make sure we're looking at the CMK message # cmk_size = handshake_data_out[0:2] test1 = int(handshake_data_out[0].encode('hex'), base=16) test2 = int(handshake_data_out[1].encode('hex'), base=16) test1 = (test1 & 0x7f) << 8 size = test1 + test2 # Manually split the two records to force them to be sent separately cmk_packet = handshake_data_out[0:size+2] data_packet = handshake_data_out[size+2::] self._sock.send(cmk_packet) handshake_data_in = self._sock.recv(DEFAULT_BUFFER_SIZE) # print repr(handshake_data_in) if len(handshake_data_in) == 0: raise IOError('Nassl SSL handshake failed: peer did not send data back.') # Pass the data to the SSL engine self._network_bio.write(handshake_data_in) handshake_data_out = data_packet # Send it to the peer self._sock.send(handshake_data_out) lengh_to_read = self._network_bio.pending() handshake_data_in = self._sock.recv(DEFAULT_BUFFER_SIZE) if len(handshake_data_in) == 0: raise IOError('Nassl SSL handshake failed: peer did not send data back.') # Pass the data to the SSL engine self._network_bio.write(handshake_data_in) except WantX509LookupError: # Server asked for a client certificate and we didn't provide one raise ClientCertificateRequested(self.get_client_CA_list()) def read(self, size): # type: (int) -> str if not self._is_handshake_completed: raise IOError('SSL Handshake was not completed; cannot receive data.') while True: # Receive available encrypted data from the peer encrypted_data = self._sock.recv(DEFAULT_BUFFER_SIZE) if len(encrypted_data) == 0: raise IOError('Could not read() - peer closed the connection.') # Pass it to the SSL engine self._network_bio.write(encrypted_data) try: # Try to read the decrypted data decrypted_data = self._ssl.read(size) return decrypted_data except WantReadError: # The SSL engine needs more data # before it can decrypt the whole message pass def write(self, data): # type: (str) -> int """Returns the number of (encrypted) bytes sent. """ if not self._is_handshake_completed: raise IOError('SSL Handshake was not completed; cannot send data.') # Pass the cleartext data to the SSL engine self._ssl.write(data) # Recover the corresponding encrypted data final_length = self._flush_ssl_engine() return final_length def _flush_ssl_engine(self): # type: () -> int length_to_read = self._network_bio.pending() final_length = length_to_read while length_to_read: encrypted_data = self._network_bio.read(length_to_read) # Send the encrypted data to the peer self._sock.send(encrypted_data) length_to_read = self._network_bio.pending() final_length += length_to_read return final_length def shutdown(self): # type: () -> None self._is_handshake_completed = False try: self._ssl.shutdown() self._flush_ssl_engine() except OpenSSLError as e: # Ignore "uninitialized" exception if 'SSL_shutdown:uninitialized' not in str(e) and 'shutdown while in init' not in str(e): raise def _set_verify(self, verify_mode): # type: (int) -> None """Set the OpenSSL verify mode. Private method because it should be set via the constructor. """ self._ssl._set_verify(verify_mode) def set_tlsext_host_name(self, name_indication): # type: (str) -> None """Set the hostname within the Server Name Indication extension in the client SSL Hello. """ self._ssl.set_tlsext_host_name(name_indication) def get_peer_certificate(self): # type: () -> Optional[X509Certificate] _x509 = self._ssl.get_peer_certificate() if _x509: return X509Certificate(_x509) else: return None def get_peer_cert_chain(self): # type: () -> List[X509Certificate] """See the OpenSSL documentation for differences between get_peer_cert_chain() and get_peer_certificate(). https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html """ x509_list = self._ssl.get_peer_cert_chain() final_list = [] if x509_list: for x509_cert in x509_list: final_list.append(X509Certificate(x509_cert)) return final_list def set_cipher_list(self, cipher_list): # type: (str) -> None self._ssl.set_cipher_list(cipher_list) def get_cipher_list(self): # type: () -> List[str] return self._ssl.get_cipher_list() def get_current_cipher_name(self): # type: () -> str return self._ssl.get_cipher_name() def get_current_cipher_bits(self): # type: () -> int return self._ssl.get_cipher_bits() def _use_private_key(self, client_certchain_file, client_key_file, client_key_type, client_key_password): # type: (str, str, int, str) -> None """The certificate chain file must be in PEM format. Private method because it should be set via the constructor. """ self._ssl_ctx.use_certificate_chain_file(client_certchain_file) if isinstance(client_key_password, basestring): self._ssl_ctx.set_private_key_password(client_key_password) else: raise TypeError('client_key_password is not a string') try: self._ssl_ctx.use_PrivateKey_file(client_key_file, client_key_type) except OpenSSLError as e: if 'bad password read' in str(e) or 'bad decrypt' in str(e): raise ValueError('Invalid Private Key') else: raise self._ssl_ctx.check_private_key() def get_certificate_chain_verify_result(self): # type: () -> Tuple[int, str] verify_result = self._ssl.get_verify_result() verify_result_str = X509.verify_cert_error_string(verify_result) return verify_result, verify_result_str def set_tlsext_status_ocsp(self): # type: () -> None """Enable the OCSP Stapling extension. """ self._ssl.set_tlsext_status_type(TLSEXT_STATUSTYPE_ocsp) def get_tlsext_status_ocsp_resp(self): # type: () -> Optional[OcspResponse] """Retrieve the server's OCSP Stapling status. """ ocsp_response = self._ssl.get_tlsext_status_ocsp_resp() if ocsp_response: return OcspResponse(ocsp_response) else: return None def get_client_CA_list(self): # type: () -> List[str] if not self._client_CA_list: self._client_CA_list = self._ssl.get_client_CA_list() return self._client_CA_list def set_mode(self, mode): # type: (int) -> None self._ssl.set_mode(mode)