def update_DA(self, s: socket.socket): """ Send in the socket s, all certificates in personal CA and DA """ self.DA.update(self.CA) for autorities in (self.CA, self.DA): # json is better with string, not bytes -> encode() / decode() _set = {} for autority in autorities: k = KeyPair.pubkey_pem(autority.issuer_key).decode() v = autority.cert_from_issuer.cert_pem().decode() _set[k] = v sendall(s, json.dumps(_set).encode()) _received_json = recv_json(s) for (key, value) in _received_json.items(): self.DA.add( AutorityProof( issuer_key=KeyPair.load_pub_from_pem(key.encode()), cert_from_issuer=X509Certificate.load_from_pem( value.encode()), )) print("DA update finished")
def test_chain_0_element(self): """ Should raise an error cause there is no chain """ chain = [] kp = KeyPair() with pytest.raises(ValueError): X509Certificate.verify_chain(kp.public_key(), chain, kp.public_key())
def test_selfsigned_cert(self): kp = KeyPair() cert = X509Certificate( issuer="issuer", subject="subject", public_key=kp.public_key(), private_key=kp.private_key(), validity_days=10, ) X509Certificate.verify(cert, kp.public_key())
def test_cert_to_pem_to_cert(self): kp = KeyPair() cert1 = X509Certificate( issuer="issuer", subject="subject", public_key=kp.public_key(), private_key=kp.private_key(), validity_days=10, ) cert2 = X509Certificate.load_from_pem(cert1.cert_pem()) assert cert1 == cert2
def _fix_parameters(self, sock): """Receive the DH p, g parameters to generate a key pair""" raw = sock.recv(self.BUFFER_SIZE) recv_msg = Message.from_buffer(raw) assert recv_msg['code'] == Message.FIX_PARAMS, \ 'Unexpected message code during key exchange: %d' % recv_msg['code'] dh_p = mpz(recv_msg['dh_p']) dh_g = mpz(recv_msg['dh_g']) self._keys = KeyPair(dh_p, dh_g)
def test_wrong_pubkey(self): pubkey_to_signed = KeyPair().public_key() kp_root = KeyPair() cert = X509Certificate( issuer="issuer", subject="subject", public_key=pubkey_to_signed, private_key=kp_root.private_key(), validity_days=10, ) assert not X509Certificate.verify(cert, pubkey_to_signed)
def __init__(self): """Initialize a DH key pair and start listening for client connections""" self._keys = KeyPair() self._session_secret = None self._echo_msg = None sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((self.TCP_IP, self.TCP_PORT)) sock.listen(1) conn, addr = sock.accept() while True: self._handle(conn)
def __init__(self, name: str, port: int): self.name = name self.port = port self.CA: Set[AutorityProof] = set() # Certification autorities self.DA: Set[AutorityProof] = set() # Derived autorities self.key_pair = KeyPair() self.certificate = X509Certificate( self.name, self.name, self.key_pair.public_key(), self.key_pair.private_key(), 10, ) print("Is my cert valid ? {} ".format( X509Certificate.verify(self.certificate, self.key_pair.public_key()))) print(self)
def test_gen_good_chain_line(self): """ tests weither equipment A can create a chain to B via C B -> C -> A """ equipmentA = Equipment(name="name", port=8888) chain = set() kp_B = KeyPair() kp_C = KeyPair() kp_A = equipmentA.key_pair cert_BC = X509Certificate( issuer="B", subject="C", public_key=kp_C.public_key(), private_key=kp_B.private_key(), validity_days=10, ) cert_CA = X509Certificate( issuer="C", subject="A", public_key=kp_A.public_key(), private_key=kp_C.private_key(), validity_days=10, ) equipmentA.DA.add( AutorityProof(issuer_key=kp_B.public_key(), cert_from_issuer=cert_BC) ) equipmentA.DA.add( AutorityProof(issuer_key=kp_C.public_key(), cert_from_issuer=cert_CA) ) assert equipmentA.create_cert_chain(kp_B.public_key()) == [cert_BC, cert_CA]
def test_chain_X_elements(self, cert_count): """ Chain with X valid certificates """ chain = [] kp_root = KeyPair() privkey = kp_root.private_key() for i in range(cert_count): kp_next = KeyPair() pubkey = kp_next.public_key() cert = X509Certificate( issuer="issuer", subject="subject", public_key=kp_next.public_key(), private_key=privkey, validity_days=10, ) privkey = kp_next.private_key() chain.append(cert) X509Certificate.verify_chain(kp_root.public_key(), chain, pubkey)
def test_certEqcert_kp(self): kp1 = KeyPair() cert1 = X509Certificate( issuer="issuer", subject="subject", public_key=kp1.public_key(), private_key=kp1.private_key(), validity_days=10, ) kp2 = KeyPair() cert2 = X509Certificate( issuer="issuer", subject="subject", public_key=kp2.public_key(), private_key=kp2.private_key(), validity_days=10, ) with pytest.raises(AssertionError): assert cert1 == cert2 assert cert1 != cert2
def test_chain_1_element(self): """" An equipment automaticaly verify a chain with only it inside """ chain = [] kp = KeyPair() cert = X509Certificate( issuer="issuer", subject="subject", public_key=kp.public_key(), private_key=kp.private_key(), validity_days=10, ) chain.append(cert) X509Certificate.verify_chain(kp.public_key(), chain, kp.public_key())
class MessageServer(object): """Simple message server for encrypted communications using Diffie-Hellman key exchange""" TCP_IP = '127.0.0.1' TCP_PORT = 9090 BUFFER_SIZE = 1024 AES_IV_SIZE = 16 def __init__(self): """Initialize a DH key pair and start listening for client connections""" self._keys = KeyPair() self._session_secret = None self._echo_msg = None sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((self.TCP_IP, self.TCP_PORT)) sock.listen(1) conn, addr = sock.accept() while True: self._handle(conn) def _handle(self, conn): # Run through protocol... # 1) Fix parameters self._fix_parameters(conn) # 2) Exchange keys self._key_exchange(conn) # 3) Read encrypted msg self._read_msg(conn) # 4) Echo encrypted reply self._send_echo(conn) conn.close() def _fix_parameters(self, conn): """Fix the DH p, g parameters to use in key exchange""" fixed_p = KeyPair.P_NIST.digits(16) fixed_g = KeyPair.G_NIST.digits(16) # Create the message object with our DH parameters msg = Message(Message.FIX_PARAMS, dh_p=fixed_p, dh_g=fixed_g) # Send the message buffer conn.send(msg.buffer) def _key_exchange(self, conn): """Initiate a DH key exchange (send/receive public keys)""" public_key = self._keys.get_public().to_hex() # Create the message object with server public key msg = Message(Message.KEY_EXCHG, public_key=public_key) # Send the message buffer conn.send(msg.buffer) # Read the client public key raw = conn.recv(self.BUFFER_SIZE) recv_msg = Message.from_buffer(raw) assert recv_msg['code'] == Message.KEY_EXCHG, \ 'Unexpected message code during key exchange: %d' % recv_msg['code'] # Create the shared secret buf = Buffer.from_hex(recv_msg['public_key']) self._session_secret = self._keys.session_key(buf) def _read_msg(self, conn): """Receive the encrypted client response""" raw = conn.recv(self.BUFFER_SIZE) recv_msg = Message.from_buffer(raw) assert recv_msg['code'] == Message.RECV_ENC, \ 'Unexpected message code during receive: %d' % recv_msg['code'] ciphertext = recv_msg['client_msg'] assert len(ciphertext) > self.AES_IV_SIZE, 'Invalid client message size' # Decrypt the received ciphertext using AES-CBC (16-bit IV prepended) iv = ciphertext[0:self.AES_IV_SIZE] ciphertext = ciphertext[self.AES_IV_SIZE:] cipher = AES.new(self._session_secret, AES.MODE_CBC, iv) # Store the decrypted message self._echo_msg = cipher.decrypt(ciphertext) def _send_echo(self, conn): """Send an encrypted server echo response""" # Re-encrypt the echo message iv = Random.new().read(self.AES_IV_SIZE) cipher = AES.new(self._session_secret, AES.MODE_CBC, iv) ciphertext = iv ciphertext += cipher.encrypt(self._echo_msg) # Create the message object with encoded echo message echo_msg = Message(Message.SEND_ENC, server_msg=ciphertext) # Send the message buffer conn.send(echo_msg.buffer)
def load(cls, tupl: Tuple[bytes, bytes]) -> "AutorityProof": return cls(KeyPair.load_pub_from_pem(tupl[0]), X509Certificate.load_from_pem(tupl[1]))
def pem(self) -> Tuple[bytes, bytes]: return (KeyPair.pubkey_pem(self.issuer_key), self.cert_from_issuer.cert_pem())
def test_gen_good_chain_dual(self): r""" tests weither equipment A can create a chain to B and then to D B -> C -> A \ / D -> E """ equipmentA = Equipment(name="name", port=8888) chain = set() kp_B = KeyPair() kp_C = KeyPair() kp_A = equipmentA.key_pair kp_D = KeyPair() kp_E = KeyPair() cert_BC = X509Certificate( issuer="B", subject="C", public_key=kp_C.public_key(), private_key=kp_B.private_key(), validity_days=10, ) equipmentA.DA.add(AutorityProof(kp_B.public_key(), cert_BC)) cert_BD = X509Certificate( issuer="B", subject="D", public_key=kp_D.public_key(), private_key=kp_B.private_key(), validity_days=10, ) equipmentA.DA.add(AutorityProof(kp_B.public_key(), cert_BD)) cert_DE = X509Certificate( issuer="D", subject="E", public_key=kp_E.public_key(), private_key=kp_D.private_key(), validity_days=10, ) equipmentA.DA.add(AutorityProof(kp_D.public_key(), cert_DE)) cert_EA = X509Certificate( issuer="E", subject="A", public_key=kp_A.public_key(), private_key=kp_E.private_key(), validity_days=10, ) equipmentA.DA.add(AutorityProof(kp_E.public_key(), cert_EA)) cert_CA = X509Certificate( issuer="C", subject="A", public_key=kp_A.public_key(), private_key=kp_C.private_key(), validity_days=10, ) equipmentA.DA.add(AutorityProof(kp_C.public_key(), cert_CA)) assert equipmentA.create_cert_chain(kp_B.public_key()) == [cert_BC, cert_CA] assert equipmentA.create_cert_chain(kp_D.public_key()) == [cert_DE, cert_EA]
class Equipment: def __init__(self, name: str, port: int): self.name = name self.port = port self.CA: Set[AutorityProof] = set() # Certification autorities self.DA: Set[AutorityProof] = set() # Derived autorities self.key_pair = KeyPair() self.certificate = X509Certificate( self.name, self.name, self.key_pair.public_key(), self.key_pair.private_key(), 10, ) print("Is my cert valid ? {} ".format( X509Certificate.verify(self.certificate, self.key_pair.public_key()))) print(self) def __repr__(self): s = "My name is {} and my port is {}\n".format(self.name, self.port) s += "My public key is {}\n".format( self.key_pair.public_key().public_numbers()) s += "My certificate is:\n{}\n".format(self.certificate) return s def server(self): print("I play server and wait on port {}".format(self.port)) with socket.socket() as s: s.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # from python doc do reuse socket in TIME-WAIT state s.bind(("localhost", self.port)) s.listen() conn, addr = s.accept() # conn is a socket connected to addr with conn: print("Connected by", addr) self.a_la_pgp_process(conn=conn) print("Closing connection") def client(self, addr: str, port: int): print("I play client and want to acces {} on port {}".format( addr, port)) with socket.socket() as conn: conn.connect((addr, port)) print("Connected to", (addr, port)) self.a_la_pgp_process(conn=conn) print("Closing connection") def show_certs(self): CA_certs = {autority.cert_from_issuer for autority in self.CA} DA_certs = {autority.cert_from_issuer for autority in self.DA} print("#" * 8 + " CA " + "#" * 8 + "\n{}".format(CA_certs)) print("#" * 8 + " DA " + "#" * 8 + "\n{}".format(DA_certs)) def show_certs_couple(self): print("#" * 8 + " CA " + "#" * 8 + "\n{}".format(self.CA)) print("#" * 8 + " DA " + "#" * 8 + "\n{}".format(self.DA)) def update_CA(self, s: socket.socket, subject, pubkey): """ Equipments don't know each other and they need to exchange their certificate. The auth by DA failed. :pubkey is the pubkey of the other equipment """ # send our certificate on its public key cert_to_sent = X509Certificate( issuer=self.name, subject=subject, public_key=pubkey, private_key=self.key_pair.private_key(), validity_days=10, ) sendall(s, cert_to_sent.cert_pem()) # received its certificate on our public key cert_received = X509Certificate.load_from_pem(recv_all(s)) if not X509Certificate.verify(cert_received, pubkey): print("update_CA FAILS cause the cert received is not valid") return self.CA.add(AutorityProof(pubkey, cert_received)) print("CA update finished") def update_DA(self, s: socket.socket): """ Send in the socket s, all certificates in personal CA and DA """ self.DA.update(self.CA) for autorities in (self.CA, self.DA): # json is better with string, not bytes -> encode() / decode() _set = {} for autority in autorities: k = KeyPair.pubkey_pem(autority.issuer_key).decode() v = autority.cert_from_issuer.cert_pem().decode() _set[k] = v sendall(s, json.dumps(_set).encode()) _received_json = recv_json(s) for (key, value) in _received_json.items(): self.DA.add( AutorityProof( issuer_key=KeyPair.load_pub_from_pem(key.encode()), cert_from_issuer=X509Certificate.load_from_pem( value.encode()), )) print("DA update finished") def hand_shake(self, s: socket.socket) -> X509Certificate: """ Exchanges selfsigned certificate with another equipment and verifies it """ self.clean_sets() # send selfsigned certificate sendall(s, self.certificate.cert_pem()) # received the other's selfsigned certificate cert_selfsigned_received = X509Certificate.load_from_pem(recv_all(s)) other_cert_pubkey = cert_selfsigned_received.public_key() if not X509Certificate.verify(cert_selfsigned_received, other_cert_pubkey): raise NotValidCertificate( "hand_shake FAILS cause of invalid certificate") return cert_selfsigned_received def is_in_CA(self, issuer_key) -> bool: """ check if the a issuer_key is a known issuer_key of an AutorityProof is CA and if the associated cert is good """ certs = [ autority.cert_from_issuer for autority in self.CA if autority.issuer_key.public_numbers() == issuer_key.public_numbers() ] if len(certs) == 0: return False else: cert = certs.pop() if not X509Certificate.verify(cert, issuer_key): return False return True def create_cert_chain(self, pubkey_other) -> List[X509Certificate]: graph = Graph([]) for autority in self.DA: graph.add_edge( n1=autority.issuer_key.public_numbers(), n2=autority.cert_from_issuer.public_key().public_numbers(), cost=1, both_ends=False, ) dq = graph.dijkstra( source=pubkey_other.public_numbers(), dest=self.certificate.public_key().public_numbers(), ) if len(dq) <= 1: # no chain return [] cert_chain: List[X509Certificate] = [] n1 = dq.popleft() while len(dq) != 0: n2 = dq.popleft() for autority in self.DA: if (autority.issuer_key.public_numbers() == n1 and autority. cert_from_issuer.public_key().public_numbers() == n2): cert_chain.append(autority.cert_from_issuer) break n1 = n2 return cert_chain def is_known_by_DA(self, s: socket.socket, pubkey_other) -> bool: cert_chain = self.create_cert_chain(pubkey_other) set_to_send = [] for cert in cert_chain: d = cert.cert_pem().decode() set_to_send.append(d) sendall(s, json.dumps(set_to_send).encode()) cert_chain_received = recv_json(s) cert_chain_received = [ X509Certificate.load_from_pem(cert.encode()) for cert in cert_chain_received ] print("Cert chain exchanged") if len(cert_chain) != 0: print("I CAN reach the other one, no more question on my side") return True elif len(cert_chain_received) == 0: print("NONE of us can reach the other one with a cert chain") return False elif not X509Certificate.verify_chain( self.key_pair.public_key(), cert_chain_received, pubkey_other): # the other one a cert chain. Is it valid ? print( "I cannot generate a cert chain and the cert chain received is NOT valid" ) return False print( "I cannot generate a cert chain BUT the cert chain received IS valid" ) return True def a_la_pgp_process(self, conn: socket.socket): """ Define the action taken by both server and equipment. They do exactly the same """ cert_received = self.hand_shake(conn) if self.is_in_CA(cert_received.public_key()): print("I already know you (CA)") self.update_DA(s=conn) elif self.is_known_by_DA(s=conn, pubkey_other=cert_received.public_key()): self.update_CA( s=conn, subject=cert_received.issuer(), pubkey=cert_received.public_key(), ) self.update_DA(s=conn) print("One of us show that we belong to the same network (DA)") else: while True: resp = input("Connect a new device ? (y/n)") if resp == "y": self.update_CA( s=conn, subject=cert_received.issuer(), pubkey=cert_received.public_key(), ) self.update_DA(s=conn) break elif resp == "n": print("Nothing happened") break else: print("Invalid answer") def clean_sets(self): """ clean CA and DA sets before any communications with other equipment. Out of date certificates are deleted """ for autority_set in (self.CA, self.DA): autority_set = { autority for autority in autority_set if X509Certificate.verify( autority.cert_from_issuer, autority.issuer_key) }
class MessageClient(object): """Simple message client for encrypted communications using Diffie-Hellman key exchange """ SERVER_IP = '127.0.0.1' SERVER_PORT = 9090 BUFFER_SIZE = 1024 AES_IV_SIZE = 16 def __init__(self, message): """Connect to the message server and perform a secure echo exchange""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((self.SERVER_IP, self.SERVER_PORT)) self._message = message self._keys = None self._session_secret = None self._check_echo(sock) def _check_echo(self, sock): # Run through protocol... # 1) Fix parameters self._fix_parameters(sock) # 2) Exchange keys self._key_exchange(sock) # 3) Send an encrypted message self._send_msg(sock) # 4) Read the encrypted echo reply self._read_echo(sock) sock.close() def _fix_parameters(self, sock): """Receive the DH p, g parameters to generate a key pair""" raw = sock.recv(self.BUFFER_SIZE) recv_msg = Message.from_buffer(raw) assert recv_msg['code'] == Message.FIX_PARAMS, \ 'Unexpected message code during key exchange: %d' % recv_msg['code'] dh_p = mpz(recv_msg['dh_p']) dh_g = mpz(recv_msg['dh_g']) self._keys = KeyPair(dh_p, dh_g) def _key_exchange(self, sock): """Initiate a DH key exchange (receive/send public keys)""" # Read the server public key raw = sock.recv(self.BUFFER_SIZE) recv_msg = Message.from_buffer(raw) assert recv_msg['code'] == Message.KEY_EXCHG, \ 'Unexpected message code during key exchange: %d' % recv_msg['code'] # Create the shared secret buf = Buffer.from_hex(recv_msg['public_key']) self._session_secret = self._keys.session_key(buf) # Send back client public key public_key = self._keys.get_public().to_hex() # Create the message object with client public key msg = Message(Message.KEY_EXCHG, public_key=public_key) # Send the message buffer sock.send(msg.buffer) def _send_msg(self, sock): """Send an encrypted message to the server""" # Encrypt the message with AES iv = Random.new().read(self.AES_IV_SIZE) cipher = AES.new(self._session_secret, AES.MODE_CBC, iv) ciphertext = iv ciphertext += cipher.encrypt(self._message) # Create the message object with encoded message msg = Message(Message.RECV_ENC, client_msg=ciphertext) # Send the message buffer sock.send(msg.buffer) def _read_echo(self, sock): """Receive the encrypted server echo response""" raw = sock.recv(self.BUFFER_SIZE) recv_msg = Message.from_buffer(raw) assert recv_msg['code'] == Message.SEND_ENC, \ 'Unexpected message code during receive: %d' % recv_msg['code'] ciphertext = recv_msg['server_msg'] assert len(ciphertext) > self.AES_IV_SIZE, 'Invalid server message size' # Decrypt the received ciphertext using AES-CBC (16-bit IV prepended) iv = ciphertext[0:self.AES_IV_SIZE] ciphertext = ciphertext[self.AES_IV_SIZE:] cipher = AES.new(self._session_secret, AES.MODE_CBC, iv) # Check the decrypted message matches echo_msg = cipher.decrypt(ciphertext) assert echo_msg == self._message, "Decrypted echo response did not match" print "[*] Successful echo..."