def register_node(data, address, keys, dbkeys): #Determine if the public key sent by the node is in the system for i in keys: if str( base64.b64encode( hashlib.sha256(i.key.exportKey("PEM")).digest()), 'ascii') == data[0]: #open connection to node for challenge-response authentication with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((address[0], 44432)) #pick 2 numbers, one for the db key, and one for the decvice key sum1 = str(int.from_bytes(Random.get_random_bytes(4), "big")) sum2 = str(int.from_bytes(Random.get_random_bytes(4), "big")) #encrypt the first number with device public key and the second with db public key pay = aes_crypt.aes_enc(i.key, sum1) pay2 = aes_crypt.aes_enc(dbkeys[data[1]].key, sum2) sum1 = int(sum1) sum2 = int(sum2) #send the two payloads s.send(pay + b'::' + pay2) #recieve and check that valid data was recieved return_sums = aes_crypt.aes_dec( rsa_encrypt.get_priv_key_auth(), s.recv(4096)) #Return error if trouble decrypting message if return_sums == -2 or return_sums == -1: return #Convert the sums to a list return_sums = str(return_sums, 'ascii').split(":") #convert the response to integer check1 = int(return_sums[0]) check2 = int(return_sums[1]) #validate that the node was able to read the data and modify it predictably if (sum1 + 1) == check1 and (sum2 + 1) == check2: #log that the node was registered i.db = data[1] #grab timestamp from node timestamp = aes_crypt.aes_dec( rsa_encrypt.get_priv_key_auth(), s.recv(4096)) #validate data and convert timstamp to float if timestamp == -1 or timestamp == -2: return timestamp = float(str(timestamp, "ascii")) #start node database update and print results when finished shamir_update_client.update(i, timestamp, s) print("Node registered: " + data[1])
def challenge(): #create host object host = Host() #Create a hash of the node's public key to send to the auth node for identity verification keyhash = str( base64.b64encode( hashlib.sha256( rsa_encrypt.get_pub_key().exportKey("PEM")).digest()), 'ascii') #creates a payload of the message that identifies that this is a client node that needs to be updated payload = "imup" + ":" + str( comms_number) + ":" + keyhash + ":" + settings.ID #create a socket to communicate with the auth nodes with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) as s: s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 32) #Create an empty data and address variable and a socket to recieve the data data = "" addr = 0 with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as us: #set a timeout for if there is no auth node ready us.settimeout(1) us.bind(('0.0.0.0', 55551)) #send the challenge tag to the auth nodes along with a public key to encrypt their return message with s.sendto( aes_crypt.aes_enc(rsa_encrypt.get_pub_key_auth(), "who?:" + keyhash), ((host.host, host.port))) #Recv a number from the auth node to connect to try: data, addr = us.recvfrom(4096) #if it fails return an error except socket.timeout: us.close() return -1 #Decrypt the recieved message data = aes_crypt.aes_dec(rsa_encrypt.get_priv_key(), data) #if message is bad return error if data == -1 or data == -2: return -1 #convert data to a string to return to the auth nodes along with the instruction payload data = str(data, 'ascii') #send payload and return expected address s.sendto( aes_crypt.aes_enc(rsa_encrypt.get_pub_key_auth(), "you!:" + data + ":" + payload), ((host.host, host.port))) return addr
def handle_response(data, address, keys, dbkeys): #Decrypt message and validate data = aes_crypt.aes_dec(rsa_encrypt.get_priv_key_auth(), data) #invalid data is ignored if data == -1 or data == -2: return #split the message and determine how to respond to it data = str(data, 'ascii').split(":") #Node is sending share for authentication if data[0] == "auth": add_secret(data[1:]) #Node needs an auth node, so the auth contest is started using a node public key elif data[0] == "who?": contest(address[0], data[1], keys) #An auth node has woken up, so the auth contest is started with the auth public key elif data[0] == "regA": contest_auth(address[0]) #Recieve an update when a user is registered or deleted, unless it comes from this node elif data[0] == "here": #dont recieve a share sent by self if not int(data[1]) == my_number: recv_update(data[2]) #A node has picked an auth node to use, check if it is this server elif data[0] == "you!": if int(data[1]) == my_number: #respond to startup update for client node if data[2] == "imup": print("Sending Update to Client Node") register_node(data[3:], address, keys, dbkeys) #respond to startup update for auth node elif data[2] == "woke": print("Sending Update to Auth Node") auth_update.updater(address[0])
def challenge(my_number, kind): #Creates a host object for use in multicast socket host = Host() #Creates a multicast socket to communicate with all auth nodes at once with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) as s: s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 32) #Creates a socket to recieve a unique number from the first auth node to respond to the contest data = "" with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as us: #sets a small timeout in case this is the first auth node in the system or the response is delayed us.settimeout(1) us.bind(('0.0.0.0', 55551)) #Sends a messafe to the other auth nodes to start a contest s.sendto( aes_crypt.aes_enc(rsa_encrypt.get_pub_key_auth(), "regA:" + str(my_number)), ((host.host, host.port))) #Recieves encrypted number from auth node data, address = us.recvfrom(4096) #Decrypt the recieved number using auth private key data = aes_crypt.aes_dec(rsa_encrypt.get_priv_key_auth(), data) #Check to make sure the message wasnt tampered with, return error if it was if data == -1 or data == -2: return 1 #convert the number to a string data = str(data, 'ascii') #encrypt the number with the auth public key and send it back to the auth nodes, letting them know which one was chosen and what action to preform #in this case the asction is provising updates to the auth server s.sendto( aes_crypt.aes_enc(rsa_encrypt.get_pub_key_auth(), "you!:" + data + ":" + kind), ((host.host, host.port))) return address
def register_node(data, address, keys, dbkeys): #Determine if the public key sent by the node is in the system #register update type comms_node = data[0] data = data[1:] for i in keys: if str( base64.b64encode( hashlib.sha256(i.key.exportKey("PEM")).digest()), 'ascii') == data[0]: #log databse name i.db = data[1] #add database to clients db and share it with wb uis db_send(data[1], comms_node) #open connection to node for challenge-response authentication with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((address[0], 55550)) #pick 2 numbers, one for the db key, and one for the decvice key sum1 = str(int.from_bytes(Random.get_random_bytes(4), "big")) sum2 = str(int.from_bytes(Random.get_random_bytes(4), "big")) #encrypt the first number with device public key and the second with db public key pay = aes_crypt.aes_enc(i.key, sum1) pay2 = aes_crypt.aes_enc(dbkeys[data[1]].key, sum2) sum1 = int(sum1) sum2 = int(sum2) #send the two payloads s.send(pay + b'::' + pay2) #recieve and check that valid data was recieved return_sums = aes_crypt.aes_dec( rsa_encrypt.get_priv_key_auth(), s.recv(4096)) #Return error if trouble decrypting message if return_sums == -2 or return_sums == -1: return #Convert the sums to a list return_sums = str(return_sums, 'ascii').split(":") #convert the response to integer check1 = int(return_sums[0]) check2 = int(return_sums[1]) #validate that the node was able to read the data and modify it predictably if (sum1 + 1) == check1 and (sum2 + 1) == check2: #grab timestamps from node temp = b"" data = b"" try: while 1 == 1: temp = s.recv(4096) if temp and len(temp) == 4096: data += temp else: break data += temp #if connection dies except: return -1 #decrypt timestamps timestamps = aes_crypt.aes_dec( rsa_encrypt.get_priv_key_auth(), data) #validate data and convert timstamp to float if timestamps == -1 or timestamps == -2: return -1 #split timestamps into list timestamps = str(timestamps, "ascii").split("|") #start node database update and print results when finished shamir_update_client.update(i, timestamps, s) print("Node updated: " + i.db)
def updater(address): #Open a socket to send the shares from, connecting to the provided address with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((address, 55552)) #create a random number for the challenge response authentication challenge = int.from_bytes(Random.get_random_bytes(10), 'big') #Send the number to the recieving node s.send( aes_crypt.aes_enc(rsa_encrypt.get_pub_key_auth(), str(challenge))) #Get the number back, along with the updatee's timestamps #Recieve until done response = b"" temp = b"" try: while 1 == 1: temp = s.recv(4096) if temp and len(temp) == 4096: response += temp else: break response += temp #exit if connection breaks except: return -1 #decrypt response response = aes_crypt.aes_dec(rsa_encrypt.get_priv_key_auth(), response) #return error if data is corrupted if response == -1 or response == -2: return -1 #split response into timestamp and the response number response = str(response, 'ascii').split(":") #confirm that the response is correct if (challenge + 1) == int(response[0], 0): #grab timestamp timestamps = response[1].split("|") #create holder for share information data = "" #for each database for i in settings.DBS: #set up connection conn = sqlite3.connect(settings.DBdir + i + ".db") conn.row_factory = sqlite3.Row c = conn.cursor() #Make sure table exists c.execute( "CREATE TABLE IF NOT EXISTS enc_shares(id PRIMARY KEY, share, timestamp DOUBLE)" ) #Grab all shares from the current database with timestamp greater than the client's timestamp c.execute("SELECT * FROM enc_shares") d = c.fetchall() shares = [] #for each share for i in range(len(d)): #Join the components into a string if they are needed by the updatee if not str(d[i]['timestamp']) in timestamps: shares.append(d[i]["id"] + "|" + d[i]["share"] + "|" + str(d[i]["timestamp"])) #Join each share together shares = "::".join(shares) #add the information from this database to the database string data += (shares + ":::") #close connection conn.close() #open the secrets db conn = sqlite3.connect(settings.DBdir + "secrets.db") conn.row_factory = sqlite3.Row c = conn.cursor() #make sure table exists c.execute( "CREATE TABLE IF NOT EXISTS secrets(id PRIMARY KEY, name, secret, timestamp DOUBLE)" ) #Grab all shares past the client timestamp c.execute("SELECT * FROM secrets") d = c.fetchall() secrets = [] #For each secret for i in range(len(d)): #convert the share to a string if it is needed by the updatee if not str(d[i]['timestamp']) in timestamps: secrets.append(d[i]["id"] + "|" + d[i]["name"] + "|" + d[i]["secret"] + "|" + str(d[i]['timestamp'])) #join all the shares and add them to the db string data += "::".join(secrets) #send the databases to the client and exit s.send(aes_crypt.aes_enc(rsa_encrypt.get_pub_key_auth(), data)) return 0
def updateee(my_number): #open socket to recieve shares into with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind(('0.0.0.0', 55552)) s.listen(5) address = 0 #Attempt to update the node try: #make sure that challenge exits succesfully and grab host address address = challenge(my_number, "woke") while address == 1: address = challenge(my_number, "woke") #If socket times out then return, this is likely the first auth node to be activated except socket.timeout: print("No Auth Nodes Found") return #accept connection from the node that shares will be pulled from cli, addr = s.accept() while not addr[0] == address[0]: cli, addr = s.accept() #Challenge response authentication, the node recieves a number from the auth node responsible for the update #and sends the number + 1 to the other node data = cli.recv(1024) #decrypt number and increment it by one data = aes_crypt.aes_dec(rsa_encrypt.get_priv_key_auth(), data) data = str(data, 'ascii') data = str(int(data) + 1) #encrypt number and return it to the host #send the timestamp of the most recent share along with the number timestamps = grab_timestamps() cli.send( aes_crypt.aes_enc(rsa_encrypt.get_pub_key_auth(), data + ":" + timestamps)) #Recv data until the sender is done data = b"" temp = b"" try: while 1 == 1: temp = cli.recv(4096) if temp and len(temp) == 4096: data += temp else: break data += temp #if the sender loses the connection then quit except: print("registered: 0 updates") return #Decrypt the data with the auth private key data = aes_crypt.aes_dec(rsa_encrypt.get_priv_key_auth(), data) #if the data is invalid return error if data == -2 or data == -1: return -1 #if no databases hold data then return if data == b':::' * settings.TOTAL: print("registered: 0 updates") return #split the data into a list of databases data = str(data, 'ascii').split(":::") #for each database split the entries into a list for i in range(len(data)): data[i] = data[i].split("::") #store the list of db's into a disctonary #they are sent in the order they are listed in settings.py updates = {} for i in range(len(settings.DBS)): updates[settings.DBS[i]] = data[i] #store the secrets database into the dictionary updates['secrets'] = data[-1] #fill the databases fill_dbs(updates) #exit, ptinting the number of shares that were updated print("registered: " + str(len(updates['secrets'])) + " updates") return
def register(): #Create socket to recieve updates from with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(5) s.bind(("0.0.0.0", 55550)) s.listen(5) #make sure that challenge executes correctly, else return error print("Looking for Auth Node") address = challenge() if address == -1: return -1 #Create a connection with the auth node, if it is not the #expected address than continue waiting try: cli, addr = s.accept() while not addr[0] == address[0]: cli, addr = s.accept() except socket.timeout: return -1 #Report print("Recieving Updates Now") #Recieve two sums for challenge response authentication #One for the database and one for the public key sums = cli.recv(2048).split(b"::") #Decrypt the sums using the node and db public keys sums[0] = aes_crypt.aes_dec(rsa_encrypt.get_priv_key(), sums[0]) sums[1] = aes_crypt.aes_dec(rsa_encrypt.get_priv_key_db(settings.ID), sums[1]) #if there is an error in the communication of the sums return an error if sums[0] == -1 or sums[0] == -2 or sums[1] == -1 or sums[1] == -2: return 1 #Increment the sums and return them via the auth public key sum1 = str(int(sums[0]) + 1) sum2 = str(int(sums[1]) + 1) #send the incremented sums back to proove node identity payload = aes_crypt.aes_enc(rsa_encrypt.get_pub_key_auth(), sum1 + ":" + sum2) #Fill recv buffer cli.send(payload + b"\x00" * (4096 - len(payload))) #Grab the latest timestamp timestamps = grab_timestamps() #Send the timestamp encrypted with the auth public key cli.send( aes_crypt.aes_enc(rsa_encrypt.get_pub_key_auth(), str(timestamps))) #Run the update process num_updates = shamir_updater.update(cli) #make number presentable if num_updates == -1: num_updates = 0 #Report shares print("Registered: " + str(num_updates) + " updates") #clean up and exit cli.close() return
def update(cli): #open connection to local database conn = sqlite3.connect(settings.DBdir + settings.ID + ".db") conn.row_factory = sqlite3.Row conn.cursor().execute("CREATE TABLE IF NOT EXISTS shares(id PRIMARY KEY, x, y, key, timestamp DOUBLE)") conn.commit() #Create empty data string data = b"" #Recieve until done try: while 1==1: temp = cli.recv(4096) if temp: data += temp else: break #if the connection dies except: #Return no updates return 0 #close the socket cli.close() #get encrypted list of encrypted shares #ENCRYPTED_MESSAGE(ENCRYPTED_SHARES) #The shares are encrypted to prevent the auth node from having all knowledge data = aes_crypt.aes_dec(rsa_encrypt.get_priv_key(), data) #If the data is invalid return an error if data == -1 or data == -2: return -1 #Convert data to a list data = str(data, 'ascii').split(":") #if no data then return if data == ['']: return #For each share for i in data: #Split the share into its content and timestamp d = i.split("|") #If user is marked for deletion if d[0] == "DEL": #Delete the share and commit the action conn.cursor().execute("DELETE FROM shares WHERE id = ?", [d[1]]) conn.commit() continue #decrypt the share and concatenate it with the timestamp temp = rsa_encrypt.get_priv_key_db(settings.ID).decrypt((base64.b64decode(d[0]),)) + b':' + bytes(d[1], 'ascii') #Pass the share into the database update_db(temp, conn) #close the connection and return the number of shares commited conn.close() return(len(data))
def updater(address): #Open a socket to send the shares from, connecting to the provided address with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((address, 44441)) #create a random number for the challenge response authentication challenge = int.from_bytes(Random.get_random_bytes(10), 'big') #Send the number to the recieving node s.send( aes_crypt.aes_enc(rsa_encrypt.get_pub_key_auth(), str(challenge))) #Get the number back, along with the most recent timestamp that the recieving server has response = s.recv(2048) #decrypt response response = aes_crypt.aes_dec(rsa_encrypt.get_priv_key_auth(), response) #return error if data is corrupted if response == -1 or response == -2: return -1 #split response into timestamp and the response number response = str(response, 'ascii').split(":") #confirm that the response is correct if (challenge + 1) == int(response[0], 0): #grab timestamp timestamp = response[1] #create holder for share information data = "" #for each database for i in settings.DBS: #set up connection conn = sqlite3.connect(settings.DBdir + i + ".db") conn.row_factory = sqlite3.Row c = conn.cursor() #Make sure table exists c.execute( "CREATE TABLE IF NOT EXISTS enc_shares(id PRIMARY KEY, share, timestamp DOUBLE)" ) #Grab all shares from the current database with timestamp greater than the client's timestamp c.execute("SELECT * FROM enc_shares WHERE timestamp > ?", [float(timestamp)]) d = c.fetchall() #for each share for i in range(len(d)): #Join the components into a string d[i] = d[i]["id"] + "|" + d[i]["share"] + "|" + str( d[i]["timestamp"]) #Join each share together d = "::".join(d) #add the information from this database to the database string data += (d + ":::") #close connection conn.close() #open the secrets db conn = sqlite3.connect(settings.DBdir + "secrets.db") conn.row_factory = sqlite3.Row c = conn.cursor() #make sure table exists c.execute( "CREATE TABLE IF NOT EXISTS secrets(id PRIMARY KEY, name, secret, timestamp DOUBLE)" ) #Grab all shares past the client timestamp c.execute("SELECT * FROM secrets WHERE timestamp > ?", [float(timestamp)]) d = c.fetchall() #For each share for i in range(len(d)): #convert the share to a string d[i] = d[i]["id"] + "|" + d[i]["name"] + "|" + d[i][ "secret"] + "|" + str(d[i]['timestamp']) #join all the shares and add them to the db string data += "::".join(d) #send the databases to the client and exit s.send(aes_crypt.aes_enc(rsa_encrypt.get_pub_key_auth(), data)) return 0
def update(cli): #open connection to local database conn = sqlite3.connect(settings.DBdir + settings.ID + ".db") conn.row_factory = sqlite3.Row conn.cursor().execute( "CREATE TABLE IF NOT EXISTS shares(id PRIMARY KEY, x, y, key, timestamp DOUBLE)" ) conn.commit() #Create empty data string data = b"" #Recieve until done try: while 1 == 1: temp = cli.recv(4096) if temp and len(temp) == 4096: data += temp else: break data += temp #if the connection dies except: #Return no updates return 0 #close the socket cli.close() #get encrypted list of encrypted shares #ENCRYPTED_MESSAGE(ENCRYPTED_SHARES) #The shares are encrypted to prevent the auth node from having all knowledge data = aes_crypt.aes_dec(rsa_encrypt.get_priv_key(), data) #If the data is invalid return an error if data == -1 or data == -2: print("Error in transmission, updates will be applied shortly") return -1 #Convert data to a list data = str(data, 'ascii').split(":") #if no data then return if data == ['']: return #record number of updates num_updates = len(data) #For each share for i in data: #Split the share into its content and timestamp d = i.split("|") #If user is marked for deletion if d[0] == "DEL": new = delete_user(conn, d) if not new: num_updates -= 1 continue #if data is longer than 344 then it means the data was chunked then concatenated together #need to decrypt each chunk individually if len(d[0]) > 344: msg = b'' curr_len = len(d[0]) start = 0 end = 344 while curr_len > 0: # subtract 344 on each iteration curr_len -= 344 # decrypt a single chunk, the starting and ending index are specified by start and end dec_chunk = rsa_encrypt.get_priv_key_db(settings.ID).decrypt( (base64.b64decode(d[0][start:end]), )) # append the single chunk to the full message msg += dec_chunk # increment start and end to the next chunk start += 344 end += 344 # append the timestamp to the entire message msg += b':' + bytes(d[1], 'ascii') #Pass the share into the database update_db(msg, conn) # if the data was not chunked then pass the entire string else: #decrypt the share and concatenate it with the timestamp temp = rsa_encrypt.get_priv_key_db(settings.ID).decrypt( (base64.b64decode(d[0]), )) + b':' + bytes(d[1], 'ascii') #Pass the share into the database update_db(temp, conn) #close the connection and return the number of shares commited conn.close() return num_updates