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 by CitadelCore, 09 October 2016 - 07:38 PM.