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...