From: Ondrej Lichtner olichtne@redhat.com
This method returns all the option values in a specific section. Previously we just had the get_section method which returns the internal dict structure of the Config class which is not very usable in the application.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Common/Config.py | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/lnst/Common/Config.py b/lnst/Common/Config.py index 97a82e4..de7ba4a 100644 --- a/lnst/Common/Config.py +++ b/lnst/Common/Config.py @@ -155,6 +155,16 @@ class Config(): raise ConfigError(msg) return self._options[section]
+ def get_section_values(self, section): + if section not in self._options: + msg = 'Unknow section: %s' % section + raise ConfigError(msg) + + res = {} + for opt_name, opt in self._options[section].items(): + res[opt_name] = opt["value"] + return res + def get_option(self, section, option): sect = self.get_section(section) if option not in sect:
From: Ondrej Lichtner olichtne@redhat.com
This adds some basic security parameters that will be used by the lnst-slave and lnst-ctl applications.
On the Controller it's just 2 options: identity - a name that the controller uses to identify to the Slave, Slave stores public keys of controllers with their identification privkey - the path to the private key of the Controller
Both of options are only used in case we're using the pubkey authetication of the Slave.
On the Slave it's these options: auth_types - the accepted authentication types, at this moment it only takes a single value but in the future it will be a list of authentication types provided to Controllers. Possible values are: none, password, pubkey, ssh auth_password - if auth_types == password then this password will be used for authentication privkey - path to the Slaves private key that will be used when auth_types == pubkey ctl_pubkeys - path to directory where the Slave should look for public keys of Controllers when auth_types == pubkey
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Common/Config.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+)
diff --git a/lnst/Common/Config.py b/lnst/Common/Config.py index de7ba4a..31481b2 100644 --- a/lnst/Common/Config.py +++ b/lnst/Common/Config.py @@ -97,6 +97,18 @@ class Config():
self._options['pools'] = dict()
+ self._options['security'] = dict() + self._options['security']['identity'] = {\ + "value" : "", + "additive" : False, + "action" : self.optionPlain, + "name" : "identity"} + self._options['security']['privkey'] = {\ + "value" : "", + "additive" : False, + "action" : self.optionPath, + "name" : "privkey"} + self.colours_scheme()
def slave_init(self): @@ -132,6 +144,28 @@ class Config(): "action" : self.optionTimeval, "name" : "expiration_period"}
+ self._options['security'] = dict() + self._options['security']['auth_types'] = {\ + "value" : "none", + "additive" : False, + "action" : self.optionPlain, #TODO list?? + "name" : "auth_types"} + self._options['security']['auth_password'] = {\ + "value" : "", + "additive" : False, + "action" : self.optionPlain, + "name" : "auth_password"} + self._options['security']['privkey'] = {\ + "value" : "", + "additive" : False, + "action" : self.optionPath, + "name" : "privkey"} + self._options['security']['ctl_pubkeys'] = {\ + "value" : "", + "additive" : False, + "action" : self.optionPath, + "name" : "ctl_pubkeys"} + self.colours_scheme()
def colours_scheme(self):
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()
From: Ondrej Lichtner olichtne@redhat.com
The slave machine XML files now contain security information used to negotiate a secure channel.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/SlaveMachineParser.py | 31 +++++++++++++++++++++++++++++++ schema-sm.rng | 29 +++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+)
diff --git a/lnst/Controller/SlaveMachineParser.py b/lnst/Controller/SlaveMachineParser.py index 1b87a89..3fee680 100644 --- a/lnst/Controller/SlaveMachineParser.py +++ b/lnst/Controller/SlaveMachineParser.py @@ -11,6 +11,7 @@ __author__ = """ rpazdera@redhat.com (Radek Pazdera) """
+import os from lnst.Controller.XmlParser import XmlParser from lnst.Controller.XmlProcessing import XmlProcessingError, XmlData from lnst.Controller.XmlProcessing import XmlCollection @@ -39,6 +40,8 @@ class SlaveMachineParser(XmlParser): interface = self._process_interface(eth_tag) sm["interfaces"].append(interface)
+ security_tag = sm_tag.find("security") + sm["security"] = self._process_security(security_tag) return sm
def _process_params(self, params_tag): @@ -64,3 +67,31 @@ class SlaveMachineParser(XmlParser): iface["params"] = params
return iface + + def _process_security(self, sec_tag): + sec = XmlData(sec_tag) + + if sec_tag is None: + sec["auth_type"] = "none" + return sec + + auth_type_tag = sec_tag.find("auth_type") + sec["auth_type"] = auth_type_tag.text + + auth_passwd_tag = sec_tag.find("auth_password") + if auth_passwd_tag is not None: + sec["auth_passwd"] = auth_passwd_tag.text + else: + sec["auth_passwd"] = "" + + key_tag = sec_tag.find("pubkey_path") + if key_tag is not None: + path = key_tag.text + exp_path = os.path.expanduser(path) + abs_path = os.path.join(os.path.dirname(self._path), exp_path) + norm_path = os.path.normpath(abs_path) + sec["pubkey_path"] = norm_path + else: + sec["pubkey_path"] = "" + + return sec diff --git a/schema-sm.rng b/schema-sm.rng index f9d5a14..8bd6a35 100644 --- a/schema-sm.rng +++ b/schema-sm.rng @@ -7,6 +7,10 @@ <ref name="define"/> </optional>
+ <optional> + <ref name="security"/> + </optional> + <zeroOrMore> <ref name="params"/> </zeroOrMore> @@ -79,4 +83,29 @@ </interleave> </element> </define> + + <define name="security"> + <element name="security"> + <element name="auth_type"> + <choice> + <value>none</value> + <value>pubkey</value> + <value>ssh</value> + <value>password</value> + </choice> + </element> + + <optional> + <element name="pubkey_path"> + <text/> + </element> + </optional> + + <optional> + <element name="auth_password"> + <text/> + </element> + </optional> + </element> + </define> </grammar>
From: Ondrej Lichtner olichtne@redhat.com
The Machine object now includes the security information of the machine that was parsed from the Slave Machine XML file. To complete the security information it also includes the Controller identification information to the dictionary holding the security information.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/Machine.py | 8 +++++++- lnst/Controller/SlavePool.py | 9 ++++++--- 2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py index 9897580..62393b8 100644 --- a/lnst/Controller/Machine.py +++ b/lnst/Controller/Machine.py @@ -42,13 +42,19 @@ class Machine(object): deconfiguration, and running commands. """
- def __init__(self, m_id, hostname=None, libvirt_domain=None, rpcport=None): + def __init__(self, m_id, hostname=None, libvirt_domain=None, rpcport=None, + security=None): self._id = m_id self._hostname = hostname self._slave_desc = None self._connection = None self._configured = False self._system_config = {} + self._security = security + self._security["identity"] = lnst_config.get_option("security", + "identity") + self._security["privkey"] = lnst_config.get_option("security", + "privkey")
self._domain_ctl = None self._network_bridges = None diff --git a/lnst/Controller/SlavePool.py b/lnst/Controller/SlavePool.py index 87bc850..24a21d8 100644 --- a/lnst/Controller/SlavePool.py +++ b/lnst/Controller/SlavePool.py @@ -167,7 +167,7 @@ class SlavePool: return (None, None)
def _process_machine_xml_data(self, m_id, machine_xml_data): - machine_spec = {"interfaces": {}, "params":{}} + machine_spec = {"interfaces": {}, "params":{}, "security": {}}
# process parameters if "params" in machine_xml_data: @@ -205,6 +205,8 @@ class SlavePool: % m_id raise SlaveMachineError(msg, machine_xml_data)
+ machine_spec["security"] = machine_xml_data["security"] + return machine_spec
def _process_iface_xml_data(self, m_id, iface): @@ -304,7 +306,7 @@ class SlavePool: if "rpc_port" in pm["params"]: rpcport = pm["params"]["rpc_port"]
- machine = Machine(tm_id, hostname, None, rpcport) + machine = Machine(tm_id, hostname, None, rpcport, pm["security"])
used = [] if_map = self._map["machines"][tm_id]["interfaces"] @@ -340,7 +342,8 @@ class SlavePool: if "rpc_port" in pm["params"]: rpcport = pm["params"]["rpc_port"]
- machine = Machine(tm_id, hostname, libvirt_domain, rpcport) + machine = Machine(tm_id, hostname, libvirt_domain, rpcport, + pm["security"])
# make all the existing unused for if_id, if_data in pm["interfaces"].iteritems():
From: Ondrej Lichtner olichtne@redhat.com
This patch replaces the sockets used for the Controller<->Slave communication with CtlSecSocket and SlaveSecSocket. This includes some changes to the ConnectionHandler by adding SecureSocket support and removing the socket support since it's not needed anymore (at least for now).
Caution: Right now this breaks the functionality of the lnst-ctl deconfigure command and the lnst-pool-wizard, which are at the moment not able to use the secured sockets.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Common/ConnectionHandler.py | 39 ++++++++++----------------------------- lnst/Controller/Machine.py | 5 ++++- lnst/Slave/NetTestSlave.py | 13 +++++++++++-- 3 files changed, 25 insertions(+), 32 deletions(-)
diff --git a/lnst/Common/ConnectionHandler.py b/lnst/Common/ConnectionHandler.py index 36349dc..5d3170a 100644 --- a/lnst/Common/ConnectionHandler.py +++ b/lnst/Common/ConnectionHandler.py @@ -12,19 +12,15 @@ olichtne@redhat.com (Ondrej Lichtner) """
import select -import cPickle import socket from _multiprocessing import Connection from pyroute2 import IPRSocket +from lnst.Common.SecureSocket import SecureSocket, SecSocketException
def send_data(s, data): try: - if isinstance(s, socket.SocketType): - pickled_data = cPickle.dumps(data) - length = len(pickled_data) - - data_to_send = str(length) + " " + pickled_data - s.sendall(data_to_send) + if isinstance(s, SecureSocket): + s.send_msg(data) elif isinstance(s, Connection): s.send(data) else: @@ -37,27 +33,11 @@ def recv_data(s): if isinstance(s, IPRSocket): msg = s.get() data = {"type": "netlink", "data": msg} - elif isinstance(s, socket.SocketType): - length = "" - while True: - c = s.recv(1) - if c == ' ': - length = int(length) - break - elif c == "": - return "" - else: - length += c - data = "" - - while len(data)<length: - c = s.recv(length - len(data)) - if c == "": - return "" - else: - data += c - - data = cPickle.loads(data) + elif isinstance(s, SecureSocket): + try: + data = s.recv_msg() + except SecSocketException: + return "" elif isinstance(s, Connection): data = s.recv() else: @@ -95,10 +75,11 @@ class ConnectionHandler(object): f.close() self.remove_connection(f) f_ready = False - else: + elif data is not None: id = self.get_connection_id(f) requests.append((id, data))
+ if f_ready: #poll the file descriptor if there is another message rll, _, _ = select.select([f], [], [], 0) if rll == []: diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py index 62393b8..839ec16 100644 --- a/lnst/Controller/Machine.py +++ b/lnst/Controller/Machine.py @@ -23,6 +23,7 @@ from lnst.Common.NetUtils import normalize_hwaddr from lnst.Common.Utils import wait_for, create_tar_archive from lnst.Common.Utils import check_process_running from lnst.Common.NetTestCommand import DEFAULT_TIMEOUT +from lnst.Controller.CtlSecSocket import CtlSecSocket
# conditional support for libvirt if check_process_running("libvirtd"): @@ -215,7 +216,9 @@ class Machine(object): m_id = self._id
logging.info("Connecting to RPC on machine %s (%s)", m_id, hostname) - connection = socket.create_connection((hostname, port)) + connection = CtlSecSocket(socket.create_connection((hostname, port))) + connection.handshake(self._security) + self._msg_dispatcher.add_slave(self, connection)
hello, slave_desc = self._rpc_call("hello", recipe_name) diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py index 1428497..3b8bf79 100644 --- a/lnst/Slave/NetTestSlave.py +++ b/lnst/Slave/NetTestSlave.py @@ -37,6 +37,7 @@ from lnst.Common.Config import lnst_config from lnst.Common.Config import DefaultRPCPort from lnst.Slave.InterfaceManager import InterfaceManager from lnst.Slave.BridgeTool import BridgeTool +from lnst.Slave.SlaveSecSocket import SlaveSecSocket, SecSocketException
class SlaveMethods: ''' @@ -837,11 +838,19 @@ class ServerHandler(ConnectionHandler): self._netns = None self._c_socket = None
+ self._security = lnst_config.get_section_values("security") + def accept_connection(self): self._c_socket, addr = self._s_socket.accept() - self._c_socket = (self._c_socket, addr[0]) + self._c_socket = (SlaveSecSocket(self._c_socket), addr[0]) logging.info("Recieved connection from %s" % self._c_socket[1])
+ try: + self._c_socket[0].handshake(self._security) + except: + self.close_c_sock() + raise + self.add_connection(self._c_socket[1], self._c_socket[0]) return self._c_socket
@@ -974,7 +983,7 @@ class NetTestSlave: try: logging.info("Waiting for connection.") self._server_handler.accept_connection() - except socket.error: + except (socket.error, SecSocketException): continue self._log_ctl.set_connection( self._server_handler.get_ctl_sock())
From: Ondrej Lichtner olichtne@redhat.com
This name and beggining of description fits better to what the method actually does.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/Machine.py | 4 ++-- lnst/Controller/NetTestController.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py index 839ec16..4112bd0 100644 --- a/lnst/Controller/Machine.py +++ b/lnst/Controller/Machine.py @@ -203,8 +203,8 @@ class Machine(object): return self._rpc_call(method_name, *args) return self._rpc_call_to_netns(netns, method_name, *args)
- def configure(self, recipe_name): - """ Prepare the machine + def init_connection(self, recipe_name): + """ Initialize the slave connection
Calling this method will initialize the rpc connection to the machine and initialize all the interfaces. Note, that it will diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py index 8fdd43e..e1b2054 100644 --- a/lnst/Controller/NetTestController.py +++ b/lnst/Controller/NetTestController.py @@ -258,7 +258,7 @@ class NetTestController: machine.set_network_bridges(self._network_bridges)
recipe_name = os.path.basename(self._recipe_path) - machine.configure(recipe_name) + machine.init_connection(recipe_name)
sync_table = {'module': {}, 'tools': {}} if resource_sync:
From: Ondrej Lichtner olichtne@redhat.com
The sockets should be properly closed after the availability of the lnst-slave is checked. This adds the relevant calls.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/SlavePool.py | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/lnst/Controller/SlavePool.py b/lnst/Controller/SlavePool.py index 24a21d8..e825998 100644 --- a/lnst/Controller/SlavePool.py +++ b/lnst/Controller/SlavePool.py @@ -103,9 +103,12 @@ class SlavePool: m_id = check_sockets[s] if err == 0: pool[m_id]["available"] = True + s.shutdown(socket.SHUT_RDWR) + s.close() del check_sockets[s] else: pool[m_id]["available"] = False + s.close() del check_sockets[s] else: for m_id in pool.keys():
From: Ondrej Lichtner olichtne@redhat.com
This renames the old "none" auth_type to "no-auth" and adds a new "none" auth_type that works the same way as we've had until now - no authentication in place. This also adds some warning log messages indicating the insecurity of the communication channel to both the controller and the slave.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/CtlSecSocket.py | 12 ++++++++++++ lnst/Slave/SlaveSecSocket.py | 11 +++++++++++ 2 files changed, 23 insertions(+)
diff --git a/lnst/Controller/CtlSecSocket.py b/lnst/Controller/CtlSecSocket.py index db27289..b4c857a 100644 --- a/lnst/Controller/CtlSecSocket.py +++ b/lnst/Controller/CtlSecSocket.py @@ -14,6 +14,7 @@ olichtne@redhat.com (Ondrej Lichtner) import os import hashlib import math +import logging from lnst.Common.SecureSocket import SecureSocket from lnst.Common.SecureSocket import DH_GROUP, SRP_GROUP from lnst.Common.SecureSocket import SecSocketException @@ -45,6 +46,17 @@ class CtlSecSocket(SecureSocket): self._slave_random = slave_hello["slave_random"]
if sec_params["auth_type"] == "none": + logging.warning("===================================") + logging.warning("%s:%d" % self._socket.getpeername()) + logging.warning("NO SECURE CHANNEL SETUP IS IN PLACE") + logging.warning(" ALL COMMUNICATION IS IN PLAINTEXT") + logging.warning("===================================") + return True + if sec_params["auth_type"] == "no-auth": + logging.warning("===========================================") + logging.warning(" NO AUTHENTICATION IN PLACE") + logging.warning("SECURE CHANNEL IS VULNERABLE TO MIM ATTACKS") + logging.warning("===========================================") self._dh_handshake() elif sec_params["auth_type"] == "ssh": self._ssh_handshake() diff --git a/lnst/Slave/SlaveSecSocket.py b/lnst/Slave/SlaveSecSocket.py index 263b9de..9e3cd92 100644 --- a/lnst/Slave/SlaveSecSocket.py +++ b/lnst/Slave/SlaveSecSocket.py @@ -15,6 +15,7 @@ import os import hashlib import math import re +import logging from lnst.Common.SecureSocket import SecureSocket from lnst.Common.SecureSocket import DH_GROUP, SRP_GROUP from lnst.Common.SecureSocket import SecSocketException @@ -44,6 +45,16 @@ class SlaveSecSocket(SecureSocket): self.send_msg(slave_hello)
if sec_params["auth_types"] == "none": + logging.warning("===================================") + logging.warning("NO SECURE CHANNEL SETUP IS IN PLACE") + logging.warning(" ALL COMMUNICATION IS IN PLAINTEXT") + logging.warning("===================================") + return True + if sec_params["auth_types"] == "no-auth": + logging.warning("===========================================") + logging.warning(" NO AUTHENTICATION IN PLACE") + logging.warning("SECURE CHANNEL IS VULNERABLE TO MIM ATTACKS") + logging.warning("===========================================") self._dh_handshake() elif sec_params["auth_types"] == "ssh": self._ssh_handshake()
Mon, Feb 29, 2016 at 05:16:47PM CET, olichtne@redhat.com wrote:
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)
try/catch is missing here.
If you end up with a mix of public and private keys in one directory, loading a private key will crash the slave here.
Traceback (most recent call last): File "./a.py", line 10, in <module> print load_pem_public_key(f.read(), backend) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/primitives/serialization.py", line 24, in load_pem_public_key return backend.load_pem_public_key(data) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/multibackend.py", line 291, in load_pem_public_key return b.load_pem_public_key(data) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1632, in load_pem_public_key self._handle_key_loading_error() File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1874, in _handle_key_loading_error raise ValueError("Could not unserialize key data.") ValueError: Could not unserialize key data.
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()
-- 2.7.2 _______________________________________________ LNST-developers mailing list lnst-developers@lists.fedorahosted.org https://lists.fedorahosted.org/admin/lists/lnst-developers@lists.fedorahoste...
Mon, Feb 29, 2016 at 05:16:47PM CET, olichtne@redhat.com wrote:
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"]):
You should check if directory exists here, I renamed mine and got traceback:
Traceback (most recent call last): File "/usr/bin/lnst-slave", line 102, in <module> main() File "/usr/bin/lnst-slave", line 99, in main nettestslave.run() File "/usr/lib/python2.7/site-packages/lnst/Slave/NetTestSlave.py", line 985, in run self._server_handler.accept_connection() File "/usr/lib/python2.7/site-packages/lnst/Slave/NetTestSlave.py", line 849, in accept_connection self._c_socket[0].handshake(self._security) File "/usr/lib/python2.7/site-packages/lnst/Slave/SlaveSecSocket.py", line 67, in handshake for fname in os.listdir(sec_params["ctl_pubkeys"]): OSError: [Errno 2] No such file or directory: '/etc/lnst/keys'
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()
-- 2.7.2 _______________________________________________ LNST-developers mailing list lnst-developers@lists.fedorahosted.org https://lists.fedorahosted.org/admin/lists/lnst-developers@lists.fedorahoste...
On Tue, Mar 01, 2016 at 01:35:32PM +0100, Jan Tluka wrote:
- 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)
try/catch is missing here.
If you end up with a mix of public and private keys in one directory, loading a private key will crash the slave here.
Traceback (most recent call last): File "./a.py", line 10, in <module> print load_pem_public_key(f.read(), backend) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/primitives/serialization.py", line 24, in load_pem_public_key return backend.load_pem_public_key(data) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/multibackend.py", line 291, in load_pem_public_key return b.load_pem_public_key(data) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1632, in load_pem_public_key self._handle_key_loading_error() File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1874, in _handle_key_loading_error raise ValueError("Could not unserialize key data.") ValueError: Could not unserialize key data.
You shouldn't really have private keys in this directory... It's a directory solely for Controller public keys, kind of like the authorized_keys file of SSH... But I agree, I should add a try/except block here to filter out tracebacks.
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.")
Mon, Feb 29, 2016 at 05:16:47PM CET, olichtne@redhat.com wrote:
From: Ondrej Lichtner olichtne@redhat.com 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"]
The 'srv_pubkey_path' key does not exist. In SM XML this is defined as 'pubkey_path' so it should be renamed in the parser/relaxng or here.
lnst-developers@lists.fedorahosted.org