This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
CitadelCore's profile picture

Need help with authentication controller.

Started by CitadelCore, 09 October 2016 - 06:11 AM
CitadelCore #1
Posted 09 October 2016 - 08:11 AM
Hello,
I'm currently writing two programs, one to run on a computer which sits next to a BioLock from Gopher's Peripherals. The other program serves as the authentication server. All communications are public-key encrypted with keys being generated and negotiated on-the-fly with the Advanced Cipher Block from Computronics.

However, when I try to authenticate myself, the script on the authentication server crashes with this error:


textutils:295: attempt to concatentate string and table

I've been over the code numerous times and tried a lot of different fixes. None work. If you were wondering, no, it's not a problem with the encryption, that has already been confirmed as working. The crash occurs sometime between when the server receives the biolock's data from the client and tries to authenticate it against the database, although I can't work out where.

I'm not very experienced with Lua, so some assistance would be great.

Here are the two programs (receive and access keys redacted for obvious reasons)

Server:

Spoiler

recvkey = "[redacted]" -- Key used for authenticating recieved messages
recvkey = textutils.serialize(recvkey) -- Serialize the key
accesskey = "[redacted]"
looplisten = true
logfile = fs.open("/fusionauth.log", "w")
computerWhitelist = {"16", "17", "18", "19"}
cipher = peripheral.wrap("right")
users = {"citadelcore", "jonaharagon"}
hashes = {"nothinghereyet", "nothinghereyet"}
biohashes = {"[redacted]", "nothinghereyet"}
codehashes = {"[redacted]", "nothinghereyet"}
securitylevels = {"5", "4"}
function hash(str)
	local s = 0
	local p = ""
	for c in str:gmatch(".") do
		s = s + string.byte(c)
	end
	s = bit.bxor(65432895, s)
	while s > 0 do
		p = p .. string.char(s % 94 + 33)
		s = bit.brshift(s, 1)
	end
	return string.sub(p, 1, p:len() - 1)
end
function isComputerInWhitelist(computerid, whitelist)
--  for i,v in ipairs(whitelist) do
--   if v == computerid then
--	return true
--   end
--   break
--  end
return true
end
function authUser(purpose, username, hash, securitylevel)
if purpose == "bioauth" then
  for k,v in pairs(biohashes) do
	if hash == v then
	  for k,v in pairs(securitylevels) do
		if securitylevel == v then
		 return true
	 else
	   return false
	 end
	 end
	break
  else
	return false
   end
  end
elseif purpose == "keypadauth" then
for i,v in ipairs(codehashes) do
if v == hash then
   for i,v in ipairs(securitylevels) do
	if v == securitylevel then
	   return true
   else
	 return false
	end
   end
else
  return false
end
break
end
return false
elseif purpose == "rfidauth" then
-- unimplemented
return false
elseif purpose == "magstripeauth" then
-- unimplemented
return false
else
  logfile.writeLine("Unrecognized purpose\n")
  logfile.flush()
  return false
end
end
term.clear()
term.setTextColor(1)
term.setCursorPos(1,1)
term.write("Starting AuthServer 1.0 by CitadelCore.")
term.setCursorPos(1,2)
term.write("Server Module: AuthServer")
logfile.writeLine("Generating keypair...")
logfile.flush()
keySet = cipher.createRandomKeySet()
os.sleep(3)
logfile.writeLine("Done.")
logfile.flush()
rsaPublicKey, rsaPrivateKey = keySet.getKeys()
term.setCursorPos(1,3)
rednet.open("top")
rednet.host("authsec", "authserver")
term.write("Server is ready to recieve authentication requests.")
term.setCursorPos(1,4)
function sendSecureRednet(senderID, message, securePublicKey)
  message = textutils.serialize(message)
  message = cipher.encrypt(message, securePublicKey)
  rednet.send(senderID, message, "authsec")
end
function recieveSecureRednet(securePrivateKey)
  senderID, commandData, protocol = rednet.receive("authsec")
  commandData = cipher.decrypt(commandData, securePrivateKey)
  commandData = textutils.unserialize(commandData)
  return senderID, commandData
end
while looplisten == true do
  senderID, clientPublicKey, protocol = rednet.receive("authsec")
   logfile.writeLine("Got PUBKEY packet from client.")
   logfile.flush()
   computerInWhitelist = isComputerInWhitelist(senderID, computerWhitelist)
   if computerInWhitelist == true then
	rednet.send(senderID, rsaPublicKey, "authsec")
	logfile.writeLine("Sent our public key packet to the client")
	logfile.flush()
	logfile.writeLine("Requesting client to authenticate.")
	logfile.flush()
	-- Request credentials from client
	sendSecureRednet(senderID, "authenticate", clientPublicKey)
	senderID, authkey = recieveSecureRednet(rsaPrivateKey)
	-- Authkey checking
	if authkey == recvkey then
	 logfile.writeLine("Client's authentication packet is valid.")
	 logfile.flush()
	  sendSecureRednet(senderID, "authsuccess", clientPublicKey)
	   logfile.writeLine("Sent authentication success packet.")
		logfile.writeLine("Waiting for control packet.")
		logfile.flush()
		-- Wait for control packet.
	   senderID, keyCompare = recieveSecureRednet(rsaPrivateKey)
	  -- Custom auth logic here
	   logfile.writeLine("Got data from client.")
	   logfile.writeLine("Checking for a parameter match.")
	   logfile.flush()
	   if keyCompare == accesskey then
		 logfile.writeLine("Got accesskey parameter.")
		 logfile.flush()
		sendSecureRednet(senderID, "correctkey", clientPublicKey)
	   elseif keyCompare == "bioauth" then
		 logfile.writeLine("Got bioauth parameter.")
		 logfile.flush()
		sendSecureRednet(senderID, "sendbiodata", clientPublicKey)
		logfile.writeLine("Requested data from client.")
		logfile.flush()
		senderID, biodata = recieveSecureRednet(rsaPrivateKey)
		logfile.writeLine("Recieved data from client")
		textutils.unserialize(biodata)
		secureBiohash = textutils.serialize(biodata.biohash())
		secureAccessLevel = textutils.serialize(biodata.accesslevel())
		authResult = authUser("bioauth", "null", secureBiohash, secureAccessLevel)
		 if authResult == true then
		  sendSecureRednet(senderID, true, clientPublicKey)
		 elseif authResult == false then
		  sendSecureRednet(senderID, false, clientPublicKey)
		 else
		  sendSecureRednet(senderID, false, clientPublicKey)
		 end
	   elseif keyCompare == "keypadauth" then
		 logfile.writeLine("Got keypadauth parameter.\n")
		 logfile.flush()
		 sendSecureRednet(senderID, "sendkeypaddata", clientPublicKey)
		 logfile.writeLine("Requested data from client.\n")
		 senderID, keypaddata = recieveSecureRednet(rsaPrivateKey)
		 logfile.writeLine("Received data from client.\n")
		 logfile.flush()
		 textutils.unserialize(keypaddata)
		 codehash = keypaddata["codehash"]
		 accessLevel = keypaddata["accesslevel"]
		 authResult = authUser("keypadauth", nil, codehash, accessLevel)
		 if authResult == true then
		  sendSecureRednet(senderID, true, clientPublicKey)
		  elseif authResult == false then
		  sendSecureRednet(senderID, false, clientPublicKey)
		  else
		  sendSecureRednet(senderID, false, clientPublicKey)
		  end
	   elseif keyCompare == "rfidauth" then
		 logfile.writeLine("Got rfidauth parameter.\n")
		 logfile.flush()
		 -- RFID authentication methods. Unimplemented.
		 sendSecureRednet(senderID, false, clientPublicKey)
	   elseif keyCompare == "magstripeauth" then
		 logfile.writeLine("Got magstripeauth parameter.\n")
		 logfile.flush()
		 -- Magstripe authentication methods. Unimplemented.
		 sendSecureRednet(senderID, false, clientPublicKey)
	  else
		--logfile.writeLine("Client sent invalid authentication key and was not authenticated. Authentication key: " .. keyCompare)
		logfile.writeLine("Client sent invalid authentication key and was not authenticated.")
		logfile.flush()
		sendSecureRednet(senderID, "invalidkey", clientPublicKey)
	 end
	else
	   -- Client sent an invalid Authkey
	   sendSecureRednet(senderID, "authfail", clientPublicKey)
	   --logfile.writeLine("Authentication from a remote server has failed! Authkey: " .. authkey)
	   logfile.writeLine("Authentication from a remote server has failed!")
	   logfile.flush()
	end
   else
	sendSecureRednet(senderID, "notwhitelisted", clientPublicKey)
	--logfile.writeLine("Client is not on the whitelist and was blocked. Client ID: " .. senderID)
	logfile.writeLine("Client is not on the whitelist and was blocked.")
	logfile.flush()
   end
end
logfile.close()

Client:

Spoiler

modem = peripheral.wrap("top") -- Wrap the modem
authpass = "[redacted]" -- Key used for connecting to the authentication database
authpass = textutils.serialize(authpass) -- Serialize the key
recvkey = "[redacted]" -- Key used for authenticating recieved messages
recvkey = textutils.serialize(recvkey) -- Serialize the key
looplisten = true
logfile = fs.open("/fusionserver.log", "w")
cipher = peripheral.wrap("left")
modem.closeAll() -- Reset the modem, for integrity
lockPurpose = "biolock" -- The lock purpose. Can be biolock, keypad, rfid, or magstripe.
biolock = peripheral.wrap("right") -- Change this to your biolock's side
accessLevelRequired = 4 -- The access level required to enter.
redstoneSide = "back" -- Set this to your redstone side
redstone.setOutput(redstoneSide, true)
-- keypad = peripheral.wrap("right")
-- rfid = peripheral.wrap("right")
-- magstripe = peripheral.wrap("right")
function sendSecureRednet(senderID, message, cryptkey)
  message = textutils.serialize(message)
  message = cipher.encrypt(message, cryptkey)
  rednet.send(senderID, message, "authsec")
end
function recieveSecureRednet(cryptkey)
  senderID, commandData, protocol = rednet.receive("authsec")
  commandData = cipher.decrypt(commandData, cryptkey)
  commandData = textutils.unserialize(commandData)
  return senderID, commandData
end
function authCommand(destinationhostname, password, functionCommand, publicKey, privateKey, logindata)
	 rednet.open("top")
	 destID = rednet.lookup("authsec", destinationhostname)
	 -- Send HELLO message to any listening FusionServer
	 rednet.send(destID, publicKey, "authsec")
	 logfile.writeLine("Sent our public key packet to the server\n")
	 logfile.flush()
	 -- Wait for command reply and instruction
	 senderID, clientPublicKey, protocol = rednet.receive("authsec")
	 senderID, commandReply = recieveSecureRednet(privateKey)
	  -- Is the command reply AUTHENTICATE?
	  if commandReply == "authenticate" then
	  logfile.writeLine("Got reply: AUTHENTICATE\n")
	  logfile.flush()
	  -- Send back the authentication passkey
	  sendSecureRednet(destID, password, clientPublicKey)
	  logfile.writeLine("Sending authentication packet to server\n")
	  logfile.flush()
	  -- Wait for authentication adknowlegement
	  senderID, authReply = recieveSecureRednet(privateKey)
	   if authReply == "authsuccess" then
	   logfile.writeLine("Got reply: AUTHSUCCESS\n")
	   logfile.writeLine("Requesting data from server\n")
	   logfile.flush()
		-- Request auth from server
		sendSecureRednet(destID, functionCommand, clientPublicKey)
		senderID, returnData = recieveSecureRednet(privateKey)
		if returnData == "sendbiodata" then
		  logfile.writeLine("Got reply: SENDBIODATA\n")
		  logfile.writeLine("Sending data\n")
		  logfile.flush()
		  sendSecureRednet(destID, logindata, clientPublicKey)
		  senderID, returnData = recieveSecureRednet(privateKey)
		  logfile.writeLine("Got reply:\n" .. returnData)
		  logfile.flush()
		  return returnData
		elseif returnData == "sendkeypaddata" then
		  logfile.writeLine("Got reply: SENDKEYPADDATA\n")
		  logfile.writeLine("Sending data\n")
		  logfile.flush()
		  sendSecureRednet(destID, logindata, clientPublicKey)
		  senderID, returnData = recieveSecureRednet(privateKey)
		  logfile.writeLine("Got reply:\n" .. returnData)
		  logfile.flush()
		  return returnData
		elseif returnData == "sendrfiddata" then
		  logfile.writeLine("Got reply: SENDRFIDDATA\n")
		  logfile.writeLine("Sending data\n")
		  logfile.flush()
		  sendSecureRednet(destID, logindata, clientPublicKey)
		  senderID, returnData = recieveSecureRednet(privateKey)
		  logfile.writeLine("Got reply:\n" .. returnData)
		  logfile.flush()
		  return returnData
		elseif returnData == "sendmagstripedata" then
		  logfile.writeLine("Got reply: SENDMAGSTRIPEDATA\n")
		  logfile.writeLine("Sending data\n")
		  logfile.flush()
		  sendSecureRednet(destID, logindata, clientPublicKey)
		  senderID, returnData = recieveSecureRednet(privateKey)
		  logfile.writeLine("Got reply:\n" .. returnData)
		  logfile.flush()
		  return returnData
		else
		return returnData
		end
	   elseif authReply == "authfail" then
		-- Server gave invalid key message
		logfile.writeLine("Got reply: AUTHFAIL\n")
		logfile.writeLine("Authentication server denied our key!\n")
		logfile.flush()
	   else
		-- Server gave invalid response to key adknowlegement
		logfile.writeLine("Got invalid parameter from authentication server!\n")
		logfile.writeLine("Parameter:" .. authReply)
		logfile.flush()
	   end
	  else
	  -- Server gave invalid response to command reply
	  logfile.writeLine("Got invalid parameter from authentication server!\n")
	  logfile.writeLine("Parameter:" .. commandReply)
	  logfile.flush()
	 end
	 -- Finish up communication with Auth Server
	 rednet.close("top")
	 term.redirect(rmon.restoreTo)
	 logfile.writeLine("Finished communication with server\n")
	 logfile.flush()
	 term.redirect(rmon)
end
function hash(str)
	local s = 0
	local p = ""
	for c in str:gmatch(".") do
		s = s + string.byte(c)
	end
	s = bit.bxor(65432895, s)
	while s > 0 do
		p = p .. string.char(s % 94 + 33)
		s = bit.brshift(s, 1)
	end
	return string.sub(p, 1, p:len() - 1)
end
-- Print terminal information
term.clear()
term.setTextColor(1)
term.setCursorPos(1,1)
term.write("Starting FusionClient 1.0 by CitadelCore.")
term.setCursorPos(1,2)
term.write("Client Module: SmartLock")
term.setCursorPos(1,3)
term.write("Negotiating keypair")
term.setCursorPos(1,4)
logfile.writeLine("Generating keypair...\n")
logfile.flush()
keySet = cipher.createRandomKeySet()
os.sleep(3)
logfile.writeLine("Done.\n")
logfile.flush()
rsaPublicKey, rsaPrivateKey = keySet.getKeys()
term.setCursorPos(1,6)
while looplisten == true do
if lockPurpose == "biolock" then
biodata = {}
event, print, attachName, learnedName, accessLevel = os.pullEvent("biolock")
biodata["biohash"] = print
biodata["accesslevel"] = accessLevelRequired
textutils.serialize(biodata)
authCommand("authserver", authpass, "bioauth", rsaPublicKey, rsaPrivateKey, biodata)
logfile.writeLine("Sent bioauth control parameter to server..\n")
logfile.flush()
senderID, isPrintValid = recieveSecureRednet(rsaPrivateKey)
if isPrintValid == true then
   logfile.writeLine("Bioprint is valid, opening door.\n")
   -- Bioprint is valid, open door
   redstone.setOutput(redstoneSide, false)
   os.sleep(3)
   redstone.setOutput(redstoneSide, true)
elseif isPrintValid == false then
   logfile.writeLine("Server returned invalid bioprint!\n")
   -- Bioprint is not valid, don't open door
else
   -- Returned invalid string
   logfile.writeLine("Got invalid parameter from authentication server!\n")
   logfile.writeLine("Parameter:" .. commandReply)
end
elseif lockPurpose == "keypad" then
keypaddata = {}
event, attachName, button1 = os.pullEvent("keypad_button")
event, attachName, button2 = os.pullEvent("keypad_button")
event, attachName, button3 = os.pullEvent("keypad_button")
event, attachName, button4 = os.pullEvent("keypad_button")
event, attachName, button5 = os.pullEvent("keypad_button")
event, attachName, button6 = os.pullEvent("keypad_button")
keycode = (button1 .. button2 .. button3 .. button4 .. button5 .. button6)
keycode = hash(keycode)
keypaddata["codehash"] = keycode
keypaddata["accesslevel"] = accessLevelRequired
textutils.serialize(keypaddata)
authCommand("authserver", authpass, "keypadauth", rsaPublicKey, rsaPrivateKey, keypaddata)
logfile.writeLine("Sent keypadauth control parameter to server..\n")
logfile.flush()
senderID, isCodeValid = recieveSecureRednet(rsaPrivateKey)
if isCodeValid == true then
  logfile.writeLine("Code is valid, opening door.\n")
  -- Code is valid, open door
  redstone.setOutput(redstoneSide, false)
  os.sleep(3)
  redstone.setOutput(redstoneSide, true)
elseif isCodeValid == false then
  logfile.writeLine("Server returned that the code is invalid!\n")
  -- Code is not valid, don't open door
else
  -- Returned invalid string
  logfile.writeLine("Got invalid parameter from authentication server!\n")
  logfile.writeLine("Parameter:" .. commandReply)
end
elseif lockPurpose == "rfid" then
  -- rfid functions
elseif lockPurpose == "magstripe" then
  -- magstripe functions
else
  term.write("Invalid lock purpose: " .. lockPurpose)
  os.exit()
end
end

logfile.close()

Thanks,
CitadelCore
Edited on 09 October 2016 - 06:38 PM
Bomb Bloke #2
Posted 09 October 2016 - 10:52 AM
The error is being generated by this line (you can also find the ComputerCraft API Lua source files if you extract out the mod archive), which'd put you inside a textutils.unserialise() call, and implies you called that function with a table as an argument.

There are three places where your server script calls textutils.unserialise(); lines 105, 148, and 167. The latter two have you attempting to unserialise what recieveSecureRednet() returned, which, due to line 105 sitting within that function, should indeed already be a table.
Edited on 09 October 2016 - 08:55 AM
CitadelCore #3
Posted 09 October 2016 - 08:37 PM
Thank you! The program's running fine now :)/>