Vanilla 1.1.4 is a product of Lussumo. More Information: Documentation, Community Support.
I have been thinking about security for object-to-object communication within SL. Whilst the normal practice is to simply issue commands from one object to another via an obscure channel, this is not secure - channels can be discovered without that much bother, and any password transmitted clearly on that channel will be vulnerable.
Of course in many cases one can just check that the key of the object owner is the same in all touch, listen etc events:
if (llGetOwnerKey(llDetectedKey(0)) != llGetOwner()) return;
but that does not work with an open system, say *cough* a combat system, where projectiles must be able to trigger damage effects in objects owned by others.
This afternoon I did some work on a basic encrypted challenge-response system, and I would be interested to know what people think. I do not have a lot of experience with this sort of thing, but it seems to me that this should be quite secure and un-listenable-in-on. This is a demonstration of course - paste it into two objects near each other, touch one, and the other should say "something or other". In practice a projectile or explosive, say, would use the send_command() function to issue a damage command, and the damaged object listen for that and behave appropriately.
// Inworld challenge-response password system
// Ordinal Malaprop
// 2007-10-23
//--------------------------------------------------------------------
// Globals and constants
// Just for test purposes
string TESTNAME = "Challenge-response tester";
// These must be the same between all objects wishing to ask each other
// to do things. Either could be reset dynamically.
integer CHANNEL = -123899093;
// A pretty strong password here.
string PASSWORD = "we8hespu8epheqabefreHejAtruchabu";
// Key that we are challenging
key gAuthChallengeKey = NULL_KEY;
// Current key that is authorised to give us commands
key gAuthObjectKey = NULL_KEY;
// Challenge again this long after authorisation
float AUTH_TIMEOUT = 60.0;
// The current challenge salt that we have -
// if 0 we haven't issued a challenge
integer gSalt = 0;
// Remember some details as to the last command we sent
// in case we are challenged, and have to repeat it
key gLastCommandTo = NULL_KEY;
string gLastMsg = "";
//--------------------------------------------------------------------
// Functions
// Send a command - this is like sending a message except we remember
// the command and destination in case it needs repeating.
send_command(key id, string msg)
{
// Save the last values we used here
// in case the recipient challenges us
gLastCommandTo = id;
gLastMsg = msg;
// Now send it all off
send(id, msg);
}
// Send a message to the object with key id
send(key id, string msg)
{
llSay(CHANNEL, (string)id + "|" + msg);
}
// This function is called with a list of the command elements
// parsed from the listen() event, and interprets them.
process_command(list elements)
{
string command = llList2String(elements, 0);
if (command == "say") {
llSay(0, llList2String(elements, 1));
}
else {
llSay(0, "Pardon? I didn't understand '" + llList2CSV(elements) + "'");
}
}
//--------------------------------------------------------------------
// Main
default
{
state_entry()
{
llListen(CHANNEL, "", NULL_KEY, "");
// For test purposes...
llSetObjectName(TESTNAME);
}
listen(integer channel, string name, key id, string msg)
{
// Command string elements are separated with a pipe
list elements = llParseString2List(msg, ["|"], []);
// Message must always begin with this object's key
if ((key)llList2String(elements, 0) != llGetKey()) return;
string command = llList2String(elements, 1);
// Have we just sent a message and gotten a challenge
// or authorisation from the object?
if (command == "challenge" && gLastCommandTo == id) {
// The salt is the second parameter
// Respond to the challenge to get authorisation
send(id, "response|"
+ llMD5String((string)llGetKey() + PASSWORD, (integer)llList2String(elements, 2)));
}
else if (command == "auth-success" && gLastCommandTo == id) {
// We have been authorised
// Repeat what we tried to do to start with
send_command(gLastCommandTo, gLastMsg);
}
else if (command == "auth-fail" && gLastCommandTo == id) {
// We have failed authorisation!
// In practice we probably wouldn't want to say anything here.
llOwnerSay("Failed authorisation from '" + name + "'");
}
// Is it a response to a challenge that we issued?
else if (command == "response" && gSalt != 0) {
if (llList2String(elements, 2) == llMD5String((string)gAuthChallengeKey + PASSWORD, gSalt)) {
// Correct password
// Change the currently authenticated object to the new one
gAuthObjectKey = id;
// Set the timeout for the auth to expire
llSetTimerEvent(AUTH_TIMEOUT);
// Tell them they are authorised
send(id, "auth-success");
}
// Otherwise they got the password wrong
else {
send(id, "auth-fail");
}
}
// If not any of that, it's a command of some sort for us
// Check whether we've authorised that object to command us already
// and the auth has not timed out
else if (id == gAuthObjectKey && gAuthObjectKey != NULL_KEY) {
// If so, do what it says
process_command(llList2List(elements, 1, -1));
}
else {
// If not, issue a challenge to whoever this is
gSalt = llCeil(llFrand(2000000000.0));
gAuthChallengeKey = id;
send(id, "challenge|" + (string)gSalt);
}
}
touch_start(integer n)
{
// Part of the test function - this finds the nearest object
// which also contains this script
llSensor(TESTNAME, NULL_KEY, SCRIPTED, 10.0, PI);
}
sensor(integer n)
{
// If a nearby test object is found, send it a command
send_command(llDetectedKey(0), "say|something or other");
}
timer()
{
// After the timeout period, remove any authorisation
gAuthObjectKey = NULL_KEY;
gSalt = 0;
llSetTimerEvent(0.0);
}
}
I have modified this to account for a "man in the middle" attack specified in the comments on my post.
1 to 4 of 4