1
0
mirror of https://github.com/AR2000AR/openComputers_codes.git synced 2025-09-08 06:31:14 +02:00
Files
openComputers_codes/libCB/lib/libCB.lua
2023-02-01 21:16:58 +01:00

208 lines
7.5 KiB
Lua

--libCB by AR2000AR=(AR2000)==========
--manage credit card (unmanaged floppy)
--=====================================
---@class libcb
local cb = {} --table returned by require
local data = require("component").data --data component used for cryptocraphic stuf
local serialization = require("serialization") --data serialization and unserialization
local event = require("event")
local proxy = require("component").proxy
local computer = require("computer")
local lastMagRead = {}
---@class cardData
---@field uuid string account UUID
---@field cbUUID string card uuid
---@field type "signed" | "unsinged" card's component type ("signed" or "unsigned")
---@field sig? string card signature made form uuid & card's drive component's uuid. Absent on magData
---@class encryptedCardData
---@field data string the encrypted data
---@field uuid string the card uuid
---@field type "driveData" | "magData"
local componentSwitch = {
__call = function(self, componentProxy, ...)
checkArg(1, componentProxy, "table")
if self[componentProxy.type] then
return self[componentProxy.type](componentProxy, ...)
else
error("{cbDrive.type} is not a valid component", 2)
end
end
}
---Get the most recent read of a magnetic card (max age 10s)
---@param os_magreader ComponentOsMagReader
---@return encryptedCardData|nil rawCardData encrypted card data
local function getRecentMagRead(os_magreader)
checkArg(1, os_magreader, "table")
if (not lastMagRead[os_magreader.address]) then return nil end
if (computer.uptime() - 10 <= lastMagRead[os_magreader.address][1]) then
lastMagRead[os_magreader.address][1] = 0 --prevent using it again
return lastMagRead[os_magreader.address][2]
end
return nil
end
---wait for any compatible card format.
---@param timeout number
---@return Component|boolean proxy
function cb.waitForCB(timeout)
local eventDetails = table.pack(event.pullFiltered(timeout, function(a, b, c, d, e, f)
if (a == "component_added" and c == "drive") then return true end
if (a == "magData") then return true end
end))
if (eventDetails[1]) then return proxy(eventDetails[2])
else return false end
end
cb.loadCB = {}
setmetatable(cb.loadCB, componentSwitch)
---read the data from a card
---@param cbDrive ComponentDrive
---@return encryptedCardData
function cb.loadCB.drive(cbDrive)
--get the data from the card
--the card is in unmanaged mode (binary) and the data is writen at the start of
--the floppy disk
--read the floppy
local cbData = cbDrive.readSector(1):gsub("\0", "") --clean null byte after the data
return {data = cbData, uuid = cbDrive.address, type = "driveData"}
end
---read the data from a magnetic card
---@param os_magreader ComponentOsMagReader
---@param timeout number
---@return encryptedCardData
function cb.loadCB.os_magreader(os_magreader, timeout)
checkArg(1, os_magreader, "table")
local recent = getRecentMagRead(os_magreader)
if (recent) then return recent end
--read the card
local _, _, _, rawData, cardUUID, _, _ = event.pull(timeout, "magData", os_magreader.address)
return {data = rawData, uuid = cardUUID, type = "magDataData"}
end
event.listen("magData", function(eName, eAddr, player, rawData, cardUUID, locked, color) lastMagRead[eAddr] = {computer.uptime(), {data = rawData, uuid = cardUUID, type = "magDataData"}} end)
cb.getCB = {}
setmetatable(cb.getCB, componentSwitch)
---decrypt the card's raw data
---@param rawData encryptedCardData data returned by loadCB
---@param pin string card's pin
---@return cardData|nil, string|nil reason
function cb.getCB.driveData(rawData, pin)
--decrypt it's content
local clearData = data.decrypt(data.decode64(rawData.data), data.md5(pin), data.md5(rawData.uuid))
if (not clearData) then return nil, "Pin might be wrong" end
--unserialize the data to use it later
local cbData = serialization.unserialize(clearData)
if (not cbData or not cbData.uuid) then return nil, "Pin might be wrong" end
--add the missing card uuid to the data
cbData.cbUUID = rawData.uuid
cbData.type = "signed"
return cbData
end
---decrypt the card's raw data
---@param rawData encryptedCardData data returned by loadCB
---@param pin string card's pin
---@return cardData|nil, string|nil reason
function cb.getCB.magDataData(rawData, pin)
--data format :
--aesIV(24)data(64)
--decrypted :
--uuid(36)"PIN"(3)
--eventName,componentAddress,Player,data,cardUUID,locked,color
--decrypt the data
local clearData = data.decrypt(data.decode64(rawData.data:sub(25)), data.md5(pin), data.decode64(rawData.data:sub(1, 24)))
if (clearData and #clearData == 39 and clearData:sub(37) == "PIN") then
return {uuid = clearData:sub(1, 36), cbUUID = rawData.uuid, type = "unsigned"}
else
return nil, "Data invalid"
end
end
---read the credit card data and return it
---@param cbDrive ComponentDrive the floppy's component
---@param pin string pin used to decrypt the data
---@return cardData
function cb.getCB.drive(cbDrive, pin)
return cb.getCB(cb.loadCB(cbDrive), pin)
end
---read the credit card data and return it
---@param cbDrive ComponentOsMagReader the floppy's component
---@param pin string pin used to decrypt the data
---@return cardData|nil
function cb.getCB.os_magreader(cbDrive, pin)
return cb.getCB.magDataData(cb.loadCB(cbDrive))
end
---check if the cb is valide. A unsinged card will always return true
---@param cbData cardData (see man libCB for more info about cbData)
---@param publicKey EcKeyPublic
---@return boolean| "unsigned"
function cb.checkCBdata(cbData, publicKey)
return cbData.type == "unsigned" or data.ecdsa(cbData.uuid .. cbData.cbUUID, publicKey, data.decode64(cbData.sig))
end
---generate a random 4 digit pin
---@return string
local function randomPin()
local pin = "" .. math.floor(math.random(9999))
while (#pin < 4) do
pin = "0" .. pin
end
return pin
end
---create a new rawCBdata
---@param uuid string account uuid
---@param cbUUID string new cb's uuid
---@param privateKey EcKeyPrivate used to sign the cb
---@return string rawCardData,string pin
function cb.createNew(uuid, cbUUID, privateKey)
if (cbUUID) then --for floppy
local pin = randomPin()
local aesKey = data.md5(pin)
local aesIV = data.md5(cbUUID)
local newCB = {}
newCB.uuid = uuid
newCB.sig = data.encode64(data.ecdsa(uuid .. cbUUID, privateKey)--[[@as string]] )
---@diagnostic disable-next-line: cast-local-type
newCB = data.encode64(data.encrypt(serialization.serialize(newCB), aesKey, aesIV))
return newCB, pin
else --magCard
local pin = randomPin()
local aesKey = data.md5(pin)
local aesIV = data.md5(randomPin())
local newCB = data.encode64(aesIV) .. data.encode64(data.encrypt(uuid .. "PIN", aesKey, aesIV))
return newCB, pin
end
end
---write rawCBdata to a unmanaged floppy or a magcard
---@param rawCBdata string data provided by cb.createNew(...)
---@param cbDrive ComponentDrive|ComponentOsCardWriter the floppy's component proxy
---@return boolean cardWritten
function cb.writeCB(rawCBdata, cbDrive)
if (cbDrive.type == "drive") then
---@cast cbDrive ComponentDrive
local buffer = serialization.serialize(rawCBdata)
cbDrive.writeSector(1, buffer)
cbDrive.setLabel("CB")
return true
elseif (cbDrive.type == "os_cardwriter") then
---@cast cbDrive ComponentOsCardWriter
return cbDrive.write(rawCBdata, "CB", true, 11) --11 == color.blue
end
return false
end
return cb --return the table containing all the public functions