From: Ondrej Lichtner olichtne@redhat.com
Added 3 new classes: SecureSocket, CtlSecSocket and SlaveSecSocket. The SecureSocket class encapsulates a normal socket object and adds what is basically a TLS Record Layer - adds a signature, padding and encrypts the entire message.
The CtlSecSocket and SlaveSecSocket classes are child classes inheriting from the SecureSocket class and implement the handshake protocols used from the Controller or from the Slave side. The important method is handshake() that takes a sec_params parameter which is a dictionary describing how to do the handshake.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Common/SecureSocket.py | 377 ++++++++++++++++++++++++++++++++++++++++ lnst/Controller/CtlSecSocket.py | 318 +++++++++++++++++++++++++++++++++ lnst/Slave/SlaveSecSocket.py | 323 ++++++++++++++++++++++++++++++++++ 3 files changed, 1018 insertions(+) create mode 100644 lnst/Common/SecureSocket.py create mode 100644 lnst/Controller/CtlSecSocket.py create mode 100644 lnst/Slave/SlaveSecSocket.py
diff --git a/lnst/Common/SecureSocket.py b/lnst/Common/SecureSocket.py new file mode 100644 index 0000000..d319c54 --- /dev/null +++ b/lnst/Common/SecureSocket.py @@ -0,0 +1,377 @@ +""" +This module defines a SecureSocket class that wraps the normal socket by adding +TLS-like functionality of providing data integrity, confidentiality and +authenticity. The reason why we're not using TLS is because the Python +implementation enforces the use of certificates and we want to also allow +password based authentication. This implements the common class, and the Slave +and Controller implement their sides of the handshake algorithms. + +Copyright 2016 Red Hat, Inc. +Licensed under the GNU General Public License, version 2 as +published by the Free Software Foundation; see COPYING for details. +""" + +__author__ = """ +olichtne@redhat.com (Ondrej Lichtner) +""" + +import os +import cPickle +import hashlib +import hmac +import cryptography.exceptions +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.asymmetric import padding, ec +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from cryptography.hazmat.primitives.asymmetric.dsa import DSAPrivateKey +from cryptography.hazmat.primitives.asymmetric.dsa import DSAPublicKey +from cryptography.hazmat.backends import default_backend + +DH_GROUP = {"p": int("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"\ + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"\ + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"\ + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"\ + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"\ + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"\ + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"\ + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"\ + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"\ + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"\ + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16), + "g": 2} + +DH_GROUP["q"] = (DH_GROUP["p"]-1)/2 +DH_GROUP["q_size"] = DH_GROUP["q"].bit_length()/8 +if DH_GROUP["q"].bit_length()%8: + DH_GROUP["q_size"] += 1 +DH_GROUP["p_size"] = DH_GROUP["p"].bit_length()/8 +if DH_GROUP["p"].bit_length()%8: + DH_GROUP["p_size"] += 1 + +SRP_GROUP = {"p": int("0xAC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC" + "3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312" + "AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A" + "163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975E" + "EAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA" + "97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32" + "E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF5" + "2FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308" + "D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435" + "DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73", 16), + "g": 2} + +SRP_GROUP["q"] = (SRP_GROUP["p"]-1)/2 +SRP_GROUP["q_size"] = SRP_GROUP["q"].bit_length()/8 +if SRP_GROUP["q"].bit_length()%8: + SRP_GROUP["q_size"] += 1 +SRP_GROUP["p_size"] = SRP_GROUP["p"].bit_length()/8 +if SRP_GROUP["p"].bit_length()%8: + SRP_GROUP["p_size"] += 1 + +class SecSocketException(Exception): + pass + +class SecureSocket(object): + def __init__(self, soc): + self._role = None + self._socket = soc + + self._master_secret = "" + + self._ctl_random = None + self._slave_random = None + + self._current_write_spec = {"enc_key": None, + "mac_key": None, + "seq_num": 0} + self._current_read_spec = {"enc_key": None, + "mac_key": None, + "seq_num": 0} + self._next_write_spec = {"enc_key": None, + "mac_key": None, + "seq_num": 0} + self._next_read_spec = {"enc_key": None, + "mac_key": None, + "seq_num": 0} + + def send_msg(self, msg): + pickled_msg = cPickle.dumps(msg) + return self.send(pickled_msg) + + def recv_msg(self): + pickled_msg = self.recv() + if pickled_msg == "": + raise SecSocketException("Disconnected") + msg = cPickle.loads(pickled_msg) + return msg + + def _add_mac_sign(self, data): + if not self._current_write_spec["mac_key"]: + return data + + msg = str(self._current_write_spec["seq_num"]) + str(len(data)) + data + signature = hmac.new(self._current_write_spec["mac_key"], + msg, + hashlib.sha256) + signed_msg = {"data": data, + "signature": signature.digest()} + return cPickle.dumps(signed_msg) + + def _del_mac_sign(self, signed_data): + if not self._current_read_spec["mac_key"]: + return signed_data + + signed_msg = cPickle.loads(signed_data) + data = signed_msg["data"] + msg = str(self._current_read_spec["seq_num"]) + str(len(data)) + data + + signature = hmac.new(self._current_read_spec["mac_key"], + msg, + hashlib.sha256) + + if signature.digest() != signed_msg["signature"]: + return None + return data + + def _add_padding(self, data): + if not self._current_write_spec["enc_key"]: + return data + + block_size = algorithms.AES.block_size/8 + pad_length = block_size - (len(data) % block_size) + pad_char = ("%02x" % pad_length).decode("hex") + padding = pad_length * pad_char + + padded_data = data+padding + return padded_data + + def _del_padding(self, data): + if not self._current_read_spec["enc_key"]: + return data + + pad_length = int(data[-1].encode("hex"), 16) + for char in data[-pad_length]: + if int(char.encode("hex"), 16) != pad_length: + return None + + return data[:-pad_length] + + def _add_encrypt(self, data): + if not self._current_write_spec["enc_key"]: + return data + + iv = os.urandom(algorithms.AES.block_size/8) + mode = modes.CBC(iv) + key = self._current_write_spec["enc_key"] + cipher = Cipher(algorithms.AES(key), mode, default_backend()) + encryptor = cipher.encryptor() + + encrypted_data = encryptor.update(data) + encryptor.finalize() + + encrypted_msg = {"iv": iv, + "enc_data": encrypted_data} + + return cPickle.dumps(encrypted_msg) + + def _del_encrypt(self, data): + if not self._current_read_spec["enc_key"]: + return data + + encrypted_msg = cPickle.loads(data) + encrypted_data = encrypted_msg["enc_data"] + + iv = encrypted_msg["iv"] + mode = modes.CBC(iv) + key = self._current_read_spec["enc_key"] + cipher = Cipher(algorithms.AES(key), mode, default_backend()) + decryptor = cipher.decryptor() + + decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize() + + return decrypted_data + + def _protect_data(self, data): + signed = self._add_mac_sign(data) + padded = self._add_padding(signed) + encrypted = self._add_encrypt(padded) + + self._current_write_spec["seq_num"] += 1 + return encrypted + + def _uprotect_data(self, encrypted): + padded = self._del_encrypt(encrypted) + signed = self._del_padding(padded) + + if signed is None: + #preventing timing attacks + self._del_mac_sign(padded) + return None + + data = self._del_mac_sign(signed) + + self._current_read_spec["seq_num"] += 1 + return data + + def send(self, data): + protected_data = self._protect_data(data) + + transmit_data = str(len(protected_data)) + " " + protected_data + + return self._socket.sendall(transmit_data) + + def recv(self): + length = "" + while True: + c = self._socket.recv(1) + if c == ' ': + length = int(length) + break + elif c == "": + return "" + else: + length += c + data = "" + + while len(data) < length: + c = self._socket.recv(length - len(data)) + if c == "": + return "" + else: + data += c + + msg = self._uprotect_data(data) + if msg is None: + return self.recv() + return self._handle_internal(msg) + + def _handle_internal(self, orig_msg): + try: + msg = cPickle.loads(orig_msg) + except: + return orig_msg + if "type" in msg and msg["type"] == "change_cipher_spec": + self._change_read_cipher_spec() + return self.recv() + else: + return orig_msg + + def _send_change_cipher_spec(self): + change_cipher_spec_msg = {"type": "change_cipher_spec"} + self.send_msg(change_cipher_spec_msg) + self._change_write_cipher_spec() + return + + def fileno(self): + """needed to work with select()""" + return self._socket.fileno() + + def close(self): + return self._socket.close() + + def shutdown(self, how): + return self._socket.shutdown(how) + + def _change_read_cipher_spec(self): + self._current_read_spec = self._next_read_spec + self._next_read_spec = {"enc_key": None, + "mac_key": None, + "seq_num": 0} + return + + def _change_write_cipher_spec(self): + self._current_write_spec = self._next_write_spec + self._next_write_spec = {"enc_key": None, + "mac_key": None, + "seq_num": 0} + return + + def p_SHA256(self, secret, seed, length): + prev_a = seed + result = "" + while len(result) < length: + a = hmac.new(secret, msg=prev_a, digestmod=hashlib.sha256) + prev_a = a.digest() + hmac_hash = hmac.new(secret, + msg=a.digest()+seed, + digestmod=hashlib.sha256) + result += hmac_hash.digest() + return result[:length] + + def PRF(self, secret, label, seed, length): + return self.p_SHA256(secret, label+seed, length) + + def _init_cipher_spec(self): + if self._role == "server": + client_spec = self._next_read_spec + server_spec = self._next_write_spec + elif self._role == "client": + client_spec = self._next_write_spec + server_spec = self._next_read_spec + else: + raise SecSocketException("Socket without a role!") + + aes_keysize = max(algorithms.AES.key_sizes)/8 + mac_keysize = hashlib.sha256().block_size + + prf_seq = self.PRF(self._master_secret, + "key expansion", + self._slave_random + self._ctl_random, + 2*aes_keysize + 2*mac_keysize) + + client_spec["enc_key"] = prf_seq[:aes_keysize] + prf_seq = prf_seq[aes_keysize:] + server_spec["enc_key"] = prf_seq[:aes_keysize] + prf_seq = prf_seq[aes_keysize:] + + client_spec["mac_key"] = prf_seq[:mac_keysize] + prf_seq = prf_seq[mac_keysize:] + server_spec["mac_key"] = prf_seq[:mac_keysize] + prf_seq = prf_seq[mac_keysize:] + return + + def _sign_data(self, data, privkey): + if isinstance(privkey, DSAPrivateKey): + signer = privkey.signer(hashes.SHA256()) + elif isinstance(privkey, RSAPrivateKey): + signer = privkey.signer(padding.PSS(padding.MGF1(hashes.SHA256()), + padding.PSS.MAX_LENGTH), + hashes.SHA256()) + elif isinstance(privkey, EllipticCurvePrivateKey): + signer = privkey.signer(ec.ECDSA(hashes.SHA256())) + else: + raise SecSocketException("Unsupported Assymetric Key!") + + signer.update(data) + return signer.finalize() + + def _verify_signature(self, pubkey, data, signature): + if isinstance(pubkey, DSAPublicKey): + verifier = pubkey.verifier(signature, hashes.SHA256()) + elif isinstance(pubkey, RSAPublicKey): + verifier = pubkey.verifier(signature, + padding.PSS(padding.MGF1(hashes.SHA256()), + padding.PSS.MAX_LENGTH), + hashes.SHA256()) + elif isinstance(pubkey, EllipticCurvePublicKey): + verifier = pubkey.verifier(signature, ec.ECDSA(hashes.SHA256())) + else: + raise SecSocketException("Unsupported Assymetric Key!") + + verifier.update(data) + try: + verifier.verify() + except cryptography.exceptions.InvalidSignature: + return False + except: + return False + return True + + def _cmp_pub_keys(self, first, second): + if first.public_numbers() != second.public_numbers(): + return False + else: + return True diff --git a/lnst/Controller/CtlSecSocket.py b/lnst/Controller/CtlSecSocket.py new file mode 100644 index 0000000..db27289 --- /dev/null +++ b/lnst/Controller/CtlSecSocket.py @@ -0,0 +1,318 @@ +""" +The CtlSecSocket implements the controller (client) side of the handshake +protocols. + +Copyright 2016 Red Hat, Inc. +Licensed under the GNU General Public License, version 2 as +published by the Free Software Foundation; see COPYING for details. +""" + +__author__ = """ +olichtne@redhat.com (Ondrej Lichtner) +""" + +import os +import hashlib +import math +from lnst.Common.SecureSocket import SecureSocket +from lnst.Common.SecureSocket import DH_GROUP, SRP_GROUP +from lnst.Common.SecureSocket import SecSocketException +from lnst.Common.Config import lnst_config +from cryptography.hazmat.primitives import serialization as ser +from cryptography.hazmat.primitives.serialization import load_pem_private_key +from cryptography.hazmat.primitives.serialization import load_pem_public_key +from cryptography.hazmat.primitives.serialization import load_ssh_public_key +from cryptography.hazmat.backends import default_backend + +backend = default_backend() + +class CtlSecSocket(SecureSocket): + def __init__(self, soc): + super(CtlSecSocket, self).__init__(soc) + self._role = "client" + + def handshake(self, sec_params): + self._ctl_random = os.urandom(28) + + ctl_hello = {"type": "ctl_hello", + "ctl_random": self._ctl_random} + self.send_msg(ctl_hello) + slave_hello = self.recv_msg() + + if slave_hello["type"] != "slave_hello": + raise SecSocketException("Handshake failed.") + + self._slave_random = slave_hello["slave_random"] + + if sec_params["auth_type"] == "none": + self._dh_handshake() + elif sec_params["auth_type"] == "ssh": + self._ssh_handshake() + elif sec_params["auth_type"] == "pubkey": + ctl_identity = sec_params["identity"] + ctl_key_path = sec_params["privkey"] + try: + with open(ctl_key_path, 'r') as f: + ctl_key = load_pem_private_key(f.read(), None, backend) + except: + ctl_key = None + + srv_key_path = sec_params["srv_pubkey_path"] + try: + with open(srv_key_path, 'r') as f: + srv_key = load_pem_public_key(f.read(), backend) + except: + srv_key = None + + if srv_key is None or ctl_key is None: + raise SecSocketException("Handshake failed.") + + self._pubkey_handshake(ctl_identity, ctl_key, srv_key) + elif sec_params["auth_type"] == "password": + self._passwd_handshake(sec_params["auth_passwd"]) + else: + raise SecSocketException("Unknown authentication method.") + + def _validate_secret(self, handshake_data): + hashed_handshake_data = hashlib.sha256() + hashed_handshake_data.update(handshake_data) + + ctl_verify_data = self.PRF(self._master_secret, + "ctl finished", + hashed_handshake_data.digest(), + 12) + + finished_msg = {"type": "ctl finished", + "verify_data": ctl_verify_data} + self.send_msg(finished_msg) + + server_reply = self.recv_msg() + if server_reply["type"] != "server finished": + raise SecSocketException("Handshake failed.") + + srv_verify_data = self.PRF(self._master_secret, + "server finished", + hashed_handshake_data.digest(), + 12) + + if srv_verify_data != server_reply["verify_data"]: + raise SecSocketException("Handshake failed.") + return + + def _dh_handshake(self): + modp_group = DH_GROUP + #private exponent + ctl_privkey = int(os.urandom(modp_group["q_size"]+1).encode('hex'), 16) + ctl_privkey = ctl_privkey % modp_group["q"] + #public key + ctl_pubkey = pow(modp_group["g"], ctl_privkey, modp_group["p"]) + + msg = {"type": "pub_dh", + "value": ctl_pubkey} + self.send_msg(msg) + + reply = self.recv_msg() + if reply["type"] != "pub_dh": + raise SecSocketException("Handshake failed.") + + srv_pubkey = reply["value"] + + ZZ = pow(srv_pubkey, ctl_privkey, modp_group["p"]) + ZZ = "{1:0{0}x}".format(modp_group['p_size']*2, ZZ) + ZZ = self._master_secret.decode('hex') + + self._master_secret = self.PRF(ZZ, + "master secret", + self._ctl_random + self._slave_random, + 48) + + handshake_data = "" + handshake_data += ("{1:0{0}x}".format(modp_group['p_size']*2, + ctl_pubkey)).decode('hex') + handshake_data += ("{1:0{0}x}".format(modp_group['p_size']*2, + srv_pubkey)).decode('hex') + + self._init_cipher_spec() + self._send_change_cipher_spec() + self._validate_secret(handshake_data) + + def _ssh_handshake(self): + ctl_ssh_key = None + known_hosts = [] + ssh_dir_path = os.path.expanduser("~/.ssh") + with open(ssh_dir_path+"/known_hosts", 'r') as f: + for line in f.readlines(): + key = line[line.find(' ')+1:] + known_hosts.append(load_ssh_public_key(key, backend)) + + with open(ssh_dir_path+"/id_rsa", 'r') as f: + ctl_ssh_key = load_pem_private_key(f.read(), None, backend) + + if not ctl_ssh_key: + raise SecSocketException("Handshake failed.") + + ctl_ssh_pubkey = ctl_ssh_key.public_key() + ctl_ssh_pubkey_pem = ctl_ssh_pubkey.public_bytes( + encoding=ser.Encoding.PEM, + format=ser.PublicFormat.SubjectPublicKeyInfo) + msg = {"type": "ssh_client_hello", + "ctl_ssh_pubkey": ctl_ssh_pubkey_pem} + + self.send_msg(msg) + msg = self.recv_msg() + if msg["type"] != "ssh_server_hello": + raise SecSocketException("Handshake failed.") + srv_ssh_pubkeys = [] + for key in msg["srv_ssh_pubkeys"]: + srv_ssh_pubkeys.append(load_pem_public_key(key, backend)) + + srv_ssh_pubkey = None + i = 0 + for key in srv_ssh_pubkeys: + for host in known_hosts: + if self._cmp_pub_keys(key, host): + srv_ssh_pubkey = host + break + if srv_ssh_pubkey is not None: + break + i += 1 + if not srv_ssh_pubkey: + raise SecSocketException("Handshake failed.") + + msg = {"type": "ssh_client_key_select", + "index": i, + "signature": self._sign_data(str(i), ctl_ssh_key)} + self.send_msg(msg) + + self._pubkey_handshake("ssh", ctl_ssh_key, srv_ssh_pubkey) + + def _pubkey_handshake(self, ctl_identity, ctl_privkey, local_srv_pubkey): + modp_group = DH_GROUP + ctl_dh_privkey = int(os.urandom(modp_group["q_size"]+1).encode('hex'), + 16) + ctl_dh_privkey = ctl_dh_privkey % modp_group["q"] + #public key + ctl_dh_pubkey_int = pow(modp_group["g"], + ctl_dh_privkey, + modp_group["p"]) + ctl_dh_pubkey = "{1:0{0}x}".format(modp_group['p_size']*2, + ctl_dh_pubkey_int) + ctl_dh_pubkey = ctl_dh_pubkey.decode('hex') + + ctl_pubkey = ctl_privkey.public_key() + ctl_pubkey_pem = ctl_pubkey.public_bytes( + encoding=ser.Encoding.PEM, + format=ser.PublicFormat.SubjectPublicKeyInfo) + + signature = self._sign_data(ctl_dh_pubkey, ctl_privkey) + msg = {"type": "pubkey_client_hello", + "identity": ctl_identity, + "ctl_pubkey": ctl_pubkey_pem, + "ctl_pub_dh": ctl_dh_pubkey, + "signature": signature} + + self.send_msg(msg) + + msg = self.recv_msg() + if msg["type"] != "pubkey_server_hello": + raise SecSocketException("Handshake failed.") + + srv_pubkey = load_pem_public_key(msg["srv_pubkey"], backend) + if not self._cmp_pub_keys(local_srv_pubkey, srv_pubkey): + raise SecSocketException("Handshake failed.") + + srv_dh_pubkey = msg["srv_pub_dh"] + if not self._verify_signature(local_srv_pubkey, + srv_dh_pubkey, + msg["signature"]): + raise SecSocketException("Handshake failed.") + + srv_dh_pubkey_int = int(srv_dh_pubkey.encode('hex'), 16) + + ZZ = pow(srv_dh_pubkey_int, ctl_dh_privkey, modp_group["p"]) + ZZ = "{1:0{0}x}".format(modp_group['p_size']*2, ZZ) + ZZ = self._master_secret.decode('hex') + + self._master_secret = self.PRF(ZZ, + "master secret", + self._ctl_random + self._slave_random, + 48) + + self._init_cipher_spec() + self._send_change_cipher_spec() + + def _passwd_handshake(self, auth_passwd): + srp_group = SRP_GROUP + p_bytes = "{1:0{0}x}".format(srp_group['p_size']*2, srp_group['p']) + p_bytes = p_bytes.decode('hex') + g_bytes = "{0:02x}".format(srp_group['g']) + g_bytes = g_bytes.decode('hex') + k = hashlib.sha256(p_bytes + g_bytes).digest() + k = int(k.encode('hex'), 16) + + username = "lnst_user" + + msg = {"type": "srp_client_begin", + "username": username} + self.send_msg(msg) + + reply = self.recv_msg() + if reply["type"] != "srp_server_salt": + raise SecSocketException("Handshake failed.") + + salt = reply["salt"] + + x = hashlib.sha256(salt + username + auth_passwd).digest() + x_int = int(x.encode('hex'), 16) + + ctl_privkey = os.urandom(srp_group["q_size"]+1) + ctl_privkey_int = int(ctl_privkey.encode('hex'), 16) % srp_group["q"] + + ctl_pubkey_int = pow(srp_group["g"], ctl_privkey_int, srp_group["p"]) + ctl_pubkey = "{1:0{0}x}".format(srp_group['p_size']*2, ctl_pubkey_int) + ctl_pubkey = ctl_pubkey.decode('hex') + + msg = {"type": "srp_client_pub", + "ctl_pubkey": ctl_pubkey} + self.send_msg(msg) + + reply = self.recv_msg() + if reply["type"] != "srp_server_pub": + raise SecSocketException("Handshake failed.") + + srv_pubkey = reply["srv_pubkey"] + srv_pubkey_int = int(srv_pubkey.encode('hex'), 16) + + if (srv_pubkey_int % srp_group["p"]) == 0: + raise SecSocketException("Handshake failed.") + + u = hashlib.sha256(ctl_pubkey + srv_pubkey).digest() + u_int = int(u.encode('hex'), 16) + + S_int = srv_pubkey_int - k * pow(srp_group['g'], x_int, srp_group['p']) + S_int = pow(S_int, ctl_privkey_int + u_int * x_int, srp_group['p']) + S = "{1:0{0}x}".format(srp_group['p_size']*2, S_int) + S = S.decode('hex') + + m1 = hashlib.sha256(ctl_pubkey + srv_pubkey + S).digest() + msg = {"type": "srp_client_m1", + "m1": m1} + self.send_msg(msg) + + reply = self.recv_msg() + if reply["type"] != "srp_server_m2": + raise SecSocketException("Handshake failed.") + srv_m2 = reply["m2"] + + client_m2 = hashlib.sha256(ctl_pubkey + m1 + S).digest() + if srv_m2 != client_m2: + raise SecSocketException("Handshake failed.") + + K = hashlib.sha256(S).digest() + self._master_secret = self.PRF(K, + "master secret", + self._ctl_random + self._slave_random, + 48) + + self._init_cipher_spec() + self._send_change_cipher_spec() diff --git a/lnst/Slave/SlaveSecSocket.py b/lnst/Slave/SlaveSecSocket.py new file mode 100644 index 0000000..263b9de --- /dev/null +++ b/lnst/Slave/SlaveSecSocket.py @@ -0,0 +1,323 @@ +""" +The SlaveSecSocket implements the slave (server) side of the handshake +protocols. + +Copyright 2016 Red Hat, Inc. +Licensed under the GNU General Public License, version 2 as +published by the Free Software Foundation; see COPYING for details. +""" + +__author__ = """ +olichtne@redhat.com (Ondrej Lichtner) +""" + +import os +import hashlib +import math +import re +from lnst.Common.SecureSocket import SecureSocket +from lnst.Common.SecureSocket import DH_GROUP, SRP_GROUP +from lnst.Common.SecureSocket import SecSocketException +from cryptography.hazmat.primitives import serialization as ser +from cryptography.hazmat.primitives.serialization import load_pem_private_key +from cryptography.hazmat.primitives.serialization import load_pem_public_key +from cryptography.hazmat.primitives.serialization import load_ssh_public_key +from cryptography.hazmat.backends import default_backend + +backend = default_backend() + +class SlaveSecSocket(SecureSocket): + def __init__(self, soc): + super(SlaveSecSocket, self).__init__(soc) + self._role = "server" + + def handshake(self, sec_params): + ctl_hello = self.recv_msg() + if ctl_hello["type"] != "ctl_hello": + raise SecSocketException("Handshake failed.") + + self._ctl_random = ctl_hello["ctl_random"] + self._slave_random = os.urandom(28) + + slave_hello = {"type": "slave_hello", + "slave_random": self._slave_random} + self.send_msg(slave_hello) + + if sec_params["auth_types"] == "none": + self._dh_handshake() + elif sec_params["auth_types"] == "ssh": + self._ssh_handshake() + elif sec_params["auth_types"] == "pubkey": + srv_key = None + with open(sec_params["privkey"], 'r') as f: + srv_key = load_pem_private_key(f.read(), None, backend) + + ctl_pubkeys = {} + for fname in os.listdir(sec_params["ctl_pubkeys"]): + path = os.path.join(sec_params["ctl_pubkeys"], fname) + if not os.path.isfile(path): + continue + with open(path, 'r') as f: + ctl_pubkeys[fname] = load_pem_public_key(f.read(), backend) + + self._pubkey_handshake(srv_key, ctl_pubkeys) + elif sec_params["auth_types"] == "password": + self._passwd_handshake(sec_params["auth_password"]) + else: + raise SecSocketException("Unknown authentication method.") + + def _validate_secret(self, handshake_data): + hashed_handshake_data = hashlib.sha256() + hashed_handshake_data.update(handshake_data) + + srv_verify_data = self.PRF(self._master_secret, + "server finished", + hashed_handshake_data.digest(), + 12) + + finished_msg = {"type": "server finished", + "verify_data": srv_verify_data} + self.send_msg(finished_msg) + + ctl_reply = self.recv_msg() + if ctl_reply["type"] != "ctl finished": + raise SecSocketException("Handshake failed.") + + ctl_verify_data = self.PRF(self._master_secret, + "ctl finished", + hashed_handshake_data.digest(), + 12) + + if ctl_verify_data != ctl_reply["verify_data"]: + raise SecSocketException("Handshake failed.") + return + + def _dh_handshake(self): + modp_group = DH_GROUP + #private exponent + srv_privkey = int(os.urandom(modp_group["q_size"]+1).encode('hex'), 16) + srv_privkey = srv_privkey % modp_group["q"] + #public key + srv_pubkey = pow(modp_group["g"], srv_privkey, modp_group["p"]) + + msg = {"type": "pub_dh", + "value": srv_pubkey} + self.send_msg(msg) + + reply = self.recv_msg() + if reply["type"] != "pub_dh": + raise SecSocketException("Handshake failed.") + + ctl_pubkey = reply["value"] + + ZZ = pow(ctl_pubkey, srv_privkey, modp_group["p"]) + ZZ = "{1:0{0}x}".format(modp_group['p_size']*2, ZZ) + ZZ = self._master_secret.decode('hex') + + self._master_secret = self.PRF(ZZ, + "master secret", + self._ctl_random + self._slave_random, + 48) + + handshake_data = "" + handshake_data += ("{1:0{0}x}".format(modp_group['p_size']*2, + ctl_pubkey)).decode('hex') + handshake_data += ("{1:0{0}x}".format(modp_group['p_size']*2, + srv_pubkey)).decode('hex') + + self._init_cipher_spec() + self._send_change_cipher_spec() + self._validate_secret(handshake_data) + + def _ssh_handshake(self): + srv_keys = [] + srv_pubkeys = [] + authorized_keys = [] + sshd_key_paths = ["/etc/ssh/ssh_host_rsa_key", + "/etc/ssh/ssh_host_ecdsa_key"] + ssh_dir_path = os.path.expanduser("~/.ssh") + for f_name in sshd_key_paths: + with open(f_name, 'r') as f: + srv_keys.append(load_pem_private_key(f.read(), None, backend)) + srv_pubkeys.append(srv_keys[-1].public_key()) + + with open(ssh_dir_path+"/authorized_keys", 'r') as f: + for line in f.readlines(): + authorized_keys.append(load_ssh_public_key(line, backend)) + + msg = self.recv_msg() + if msg["type"] != "ssh_client_hello": + raise SecSocketException("Handshake failed.") + ctl_ssh_pubkey = load_pem_public_key(msg["ctl_ssh_pubkey"], backend) + + authorized = False + for key in authorized_keys: + if self._cmp_pub_keys(key, ctl_ssh_pubkey): + authorized = True + break + if not authorized: + raise SecSocketException("Handshake failed.") + + pem_pubkeys = [] + for key in srv_pubkeys: + pem_key = key.public_bytes( + encoding=ser.Encoding.PEM, + format=ser.PublicFormat.SubjectPublicKeyInfo) + pem_pubkeys.append(pem_key) + + msg = {"type": "ssh_server_hello", + "srv_ssh_pubkeys": pem_pubkeys} + self.send_msg(msg) + + msg = self.recv_msg() + if msg["type"] != "ssh_client_key_select": + raise SecSocketException("Handshake failed.") + + if not self._verify_signature(ctl_ssh_pubkey, + str(msg["index"]), + msg["signature"]): + raise SecSocketException("Handshake failed.") + + self._pubkey_handshake(srv_keys[msg["index"]], {"ssh": ctl_ssh_pubkey}) + + def _pubkey_handshake(self, srv_privkey, client_pubkeys): + modp_group = DH_GROUP + #private exponent + srv_dh_privkey = int(os.urandom(modp_group["q_size"]+1).encode('hex'), + 16) + srv_dh_privkey = srv_dh_privkey % modp_group["q"] + #public key + srv_dh_pubkey_int = pow(modp_group["g"], + srv_dh_privkey, + modp_group["p"]) + srv_dh_pubkey = "{1:0{0}x}".format(modp_group['p_size']*2, + srv_dh_pubkey_int) + srv_dh_pubkey = srv_dh_pubkey.decode('hex') + + msg = self.recv_msg() + if msg["type"] != "pubkey_client_hello": + raise SecSocketException("Handshake failed.") + ctl_identity = msg["identity"] + ctl_pubkey = load_pem_public_key(msg["ctl_pubkey"], backend) + + local_ctl_pubkey = client_pubkeys[ctl_identity] + + if not self._cmp_pub_keys(local_ctl_pubkey, ctl_pubkey): + raise SecSocketException("Handshake failed.") + + ctl_dh_pubkey = msg["ctl_pub_dh"] + signature = msg["signature"] + if not self._verify_signature(local_ctl_pubkey, + ctl_dh_pubkey, + signature): + raise SecSocketException("Handshake failed.") + + ctl_dh_pubkey_int = int(ctl_dh_pubkey.encode('hex'), 16) + + srv_pubkey = srv_privkey.public_key() + srv_pubkey_pem = srv_pubkey.public_bytes( + encoding=ser.Encoding.PEM, + format=ser.PublicFormat.SubjectPublicKeyInfo) + + signature = self._sign_data(srv_dh_pubkey, srv_privkey) + msg = {"type": "pubkey_server_hello", + "srv_pubkey": srv_pubkey_pem, + "srv_pub_dh": srv_dh_pubkey, + "signature": signature} + self.send_msg(msg) + + ZZ = pow(ctl_dh_pubkey_int, srv_dh_privkey, modp_group["p"]) + ZZ = "{1:0{0}x}".format(modp_group['p_size']*2, ZZ) + ZZ = self._master_secret.decode('hex') + + self._master_secret = self.PRF(ZZ, + "master secret", + self._ctl_random + self._slave_random, + 48) + + self._init_cipher_spec() + self._send_change_cipher_spec() + + def _passwd_handshake(self, auth_passwd): + msg = self.recv_msg() + if msg["type"] != "srp_client_begin": + raise SecSocketException("Handshake failed.") + + if msg["username"] != "lnst_user": + raise SecSocketException("Handshake failed.") + + srp_group = SRP_GROUP + p_bytes = "{1:0{0}x}".format(srp_group['p_size']*2, srp_group['p']) + p_bytes = p_bytes.decode('hex') + g_bytes = "{0:02x}".format(srp_group['g']) + g_bytes = g_bytes.decode('hex') + k = hashlib.sha256(p_bytes + g_bytes).digest() + k = int(k.encode('hex'), 16) + username = msg["username"] + + salt = os.urandom(16) + + x = hashlib.sha256(salt + username + auth_passwd).digest() + + x_int = int(x.encode('hex'), 16) + + v = pow(srp_group["g"], x_int, srp_group["p"]) + + msg = {"type": "srp_server_salt", + "salt": salt} + + self.send_msg(msg) + + reply = self.recv_msg() + if reply["type"] != "srp_client_pub": + raise SecSocketException("Handshake failed.") + + ctl_pubkey = reply["ctl_pubkey"] + ctl_pubkey_int = int(ctl_pubkey.encode('hex'), 16) + + if (ctl_pubkey_int % srp_group["p"]) == 0: + raise SecSocketException("Handshake failed.") + + srv_privkey = os.urandom(srp_group["q_size"]+1) + srv_privkey_int = int(srv_privkey.encode('hex'), 16) % srp_group["q"] + + srv_pubkey_int = pow(srp_group["g"], srv_privkey_int, srp_group["p"]) + srv_pubkey_int = (srv_pubkey_int + k*v) % srp_group["p"] + srv_pubkey = "{1:0{0}x}".format(srp_group['p_size']*2, srv_pubkey_int) + srv_pubkey = srv_pubkey.decode('hex') + + msg = {"type": "srp_server_pub", + "srv_pubkey": srv_pubkey} + self.send_msg(msg) + + u = hashlib.sha256(ctl_pubkey + srv_pubkey).digest() + u_int = int(u.encode('hex'), 16) + + S_int = pow(v, u_int, srp_group['p'])*ctl_pubkey_int + S_int = pow(S_int, srv_privkey_int, srp_group["p"]) + S = "{1:0{0}x}".format(srp_group['p_size']*2, S_int) + S = S.decode('hex') + + msg = self.recv_msg() + if msg["type"] != "srp_client_m1": + raise SecSocketException("Handshake failed.") + + client_m1 = msg["m1"] + + srv_m1 = hashlib.sha256(ctl_pubkey + srv_pubkey + S).digest() + if client_m1 != srv_m1: + raise SecSocketException("Handshake failed.") + + srv_m2 = hashlib.sha256(ctl_pubkey + srv_m1 + S).digest() + msg = {"type": "srp_server_m2", + "m2": srv_m2} + self.send_msg(msg) + + K = hashlib.sha256(S).digest() + self._master_secret = self.PRF(K, + "master secret", + self._ctl_random + self._slave_random, + 48) + + self._init_cipher_spec() + self._send_change_cipher_spec()