Not signed in (Sign In)

Vanilla 1.1.4 is a product of Lussumo. More Information: Documentation, Community Support.

  1.  # 1

    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.

  2.  # 2
    // 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); } }
  3.  # 3
  4.  # 4

    I have modified this to account for a "man in the middle" attack specified in the comments on my post.