REVO7000 # 2Advanced Encrypted Traffic Server Framework w Chat3 ˆ 'global ConnectedClients global cList ##openstack and startup on openstack global ConnectedClients global cList pass openstack end openstack function CheckRegistered what ##use hash function when not testing--for testing you can just use a simple string -- put "your secret string here" into tSecret -- put messageAuthenticationCode(what,tSecret,"HMAC-SHA-256") into tHMAC -- put binaryDecode("H*",tHMAC,theHex) into test -- put theHex into tHMAC --return tHMAC return what end CheckRegistered ## you might want to use encrypted read and write functions to encrypt your user list --which does not contain user info or passwords--just the HMACs of accounts you "authorize" ## or you could use those functions with some other task your server performs--just an example to tickle the imagination function securedatawrite whatdata encrypt whatdata using "aes-256" with password "replacethiswithyourlongcomplexpassphrase" put base64encode(whatdata) into whatdata return whatdata end securedatawrite function securedataread whatdata put base64decode(whatdata) into whatdata decrypt whatdata using "aes-256" with password "replacethiswithyourlongcomplexpassphrase)" return whatdata end securedataread command newClist put refreshClientList()into temp repeat for each line x in temp put the time into cList[x] end repeat end newClist command makeExampleClientList create folder specialfolderpath("Documents")& "/LCHostFramework/" put specialfolderpath("Documents") & "/LCHostFramework/securefw.txt" into tFilePath open file tFilePath for binary write write "normallyanhmacorhash" to file tFilePath close file tFilePath end makeExampleClientList private function refreshClientList put empty into cList ##change path to match your requirements; we are using docs folder as a convenience --yeah you can put it into a db but it isn't required put specialfolderpath("Documents") & "/LCHostFramework/securefw.txt" into tFilePath open file tFilePath for binary read read from file tFilePath until eof put it into tResponse close file tFilePath return tResponse end refreshClientList function makePassPhrase put randombytes(20) into thebytes --40 chars ( 2 x randombytes) = passphrase length put randomdigits(thebytes) into what return what end makePassPhrase function randomdigits what put what into hold repeat for each byte x in hold get binarydecode("H*",x,thetext) put thetext after test end repeat return test end randomdigits € Ž ŽÑ¨ : 6LiveCode Server Ephemeral Keys (AES Encryption) w Chat ÿÿÿÿ U (Text),unicode U (Message),unicode cRevOnline id 992 cRevStandaloneSettings = askDialog OSX,longVersion #License-Registration Server 1.0.0.0 OSX,signature ???? Solaris SPARC false Windows,ProductName License-Registration Server Windows,FileDescription /License-Registration Server 1.0.0.0 for Windows OSX,smallappicon 210008 Windows,UUID MacOS,shortVersion 1.0.0.0 extensions –com.livecode.widget.spinner com.livecode.library.json com.livecode.library.mime com.livecode.library.oauth2 com.livecode.library.messageauthentication OSX,info LC Host Framework android,billingProvider Google Solaris Intel false Windows false Windows,productversion4 0 OSX,appicon 210002 UNIX,pageSetup Windows,companyname Windows,fileversion1 1 Linux false MacOSX x86-64 OSX,documentExtension scriptLibraries SSL & Encryption Internet pdfPrinter inclusions select password howdy ios,active menu Basic Settings Windows x86-64 false Windows,documenticonFile z/Applications/LiveCode Business 9.6.2 (rc 2).app/Contents/Tools/Runtime/Windows/x86-32/Support/Sample Icons/genericdoc.ico Windows,productversion3 0 name Advanced Encrypted Host OSX,documentType Windows,productversion2 0 OSX,name LC Host UNIX,colorChooser UNIX,printerChooser brushes MacOS,longVersion #License-Registration Server 1.0.0.0 answerDialog externals tsNet Linux x64 false databaseDrivers ios,ipad intial orientations Portrait Windows,productversion1 1 OSX,copyright #2021 All rights reserved worldwide UNIX,fileSelector OSX,identifier com..lchostframework revolutionPrintDialogs Windows,fileversion2 0 Windows,copyright #2021 All rights reserved worldwide Windows,iconFile z/Applications/LiveCode Business 9.6.2 (rc 2).app/Contents/Tools/Runtime/Windows/x86-32/Support/Sample Icons/genericapp.ico defaultBuildFolder /Users/markclark/Desktop OSX,shortVersion 1.0.0.0 cursors false Windows,fileversion4 0 magnify Windows,LegalCopyright #2021 All rights reserved worldwide MacOS,creator ???? Linux ARMv6-HF false MacOS,documentType Windows,fileversion3 0 cREVGeometryCache stackID # cREVGeneral breakpointstates breakpoints breakpointconditions scripteditorselection 1749 scalefactor ð? scripteditorvscroll A ê Main @ ÿÿÿÿÿÿ € Ѩ: cREVGeneral scripteditorvscroll scripteditorselection 0 cREVGeometrycache total order cREVGeometryCacheIDs 1619888803936 # 1162390083001 ì 1611692244235 1612028594030 1611070248682 1614376341124 1614376337457 ! ì ! # ì clientrequest ‚)` € ¡ ¼ cREVGeneral revUniqueID 1162390083001 scripteditorvscroll scripteditorselection 0 ê ' í ` Ÿ º stopserverâe›pî global ConnectedClients globaL Wannabe ## might want to send a broadcast msg that host is going down on mouseup put fld "serverport" into sPort set the sockettimeoutinterval to 10 repeat for each key x in wannabe close socket x end repeat repeat for each key x in connectedClients close socket x if the first line of fld "clientrequest" is not empty then put x & ":" && "Connection closed." & return before the first line of fld "clientrequest" else put x & ":" && "Connection closed." into the first line of field "clientrequest" end repeat put empty into ConnectedClients put empty into wannabe put empty into chatroom1 put empty into chatroom2 close socket sPort if the first line of fld "clientrequest" is not empty then put myTime() & ": Server is no longer accepting connections on port" && sPort & "." & return before the first line of fld "clientrequest" else put myTime() & ": Server is no longer accepting connections on port" && sPort & "." into the first line of field "clientrequest" disable me enable btn "startserver" end mouseup function myTime put the time && "on" && the long date into tMc return tMc end myTime € ñ Ö o ï Stop Server cREVGeneral scripteditorvscroll / scripteditorselection 734 revUniqueID °ÐtwB startserveràe‹pÈ• ## In this implementation each client connection creates an ephemeral RSA keypair and transmits the public key to the server along with a simple "HELO". ## The server creates a complex passphrase for the subsequent session AES encryption, encrypts that with the client public key and sends it back to the client. ## The client then gets this key and creates an "AUTH" request with its server account credentials, encrypts that with the session AES key provided ## by the host and sends this back to the server. If the creds check out the client is connected, the ephemeral key var is cleared from memory, etc. ## The client could also generate a new AES passphrase and include it in the auth details, but this is not terribly useful IMO, but it's plumbed ## in and available if it's of interest. ## What you will need to adapt this for your own purposes: ## Create a simple account/authentication creation stack-- the server auth framework is already in place and pluggable. ## That stack should use an HMAC secret for authenticating your client. I use a separate secret for locally registering an application. This might seem overkill ## but if a bad actor managed to get your HMAC secret from a client they could not use that secret to generate an HMAC for generating arbitrary ## client accounts that they somehow slip into your client list file (or DB) on the server side. Here we distribute identifying data vs having users generate passwords. ## You can do it however you like, of course. ## Once you get the basics down think about including a message integrity code, timestamp and byte counts for all messages and adding additional output ## from the server side to the outgoing messages; in other words you can have parts of the return message to the client indicate ## if it's a broadcast message, a chat message with support multiple chat rooms, etc, or a response from some command or function. ## That would make it trivial to handle messages on the client end with a more global listening facility. ## ## It's up to you, you can also just handle things with each client request when you are doing things that are more command-response stuff, ## which is what is outlined in the client framework. ## The world's your oyster--just watch those timeoutintervals on the client side. global wannabe global ConnectedClients global cList global chatroom1 global chatroom2 on mouseUp put fld "serverport" into sPort if sPort is not a number then exit mouseup newClist set the sockettimeoutinterval to 1500 accept connections on port sPort with message "clientRequest" disable me enable btn "stopserver" if the first line of fld "clientrequest" is not empty then put myTime() & ": Now accepting connections on port" && sPort & return before the first line of field "clientrequest" else put myTime() & ": Now accepting connections on port" && sPort into the first line of field "clientrequest" end mouseUp ## I am using a variable named "theIP" because it's short and sweet--in reality it's a bit more than just IP, but it's easy to type and read on socketclosed theIP put theIP & ":" && ConnectedClients[theIP]["user"] && "has disconnected." into tResponse if the first line of fld "clientrequest" is not empty then put tResponse & return before the first line of fld "clientrequest" else put tResponse into the first line of fld "clientrequest" delete variable ConnectedClients[theIP] delete variable wannabe[theIP] end socketclosed on clientRequest theIP, theCrequest set the sockettimeoutinterval to 4000 --depends on your use case--this is just a number representing milliseconds ## check first to see if the IP-socket is among the keys of connectedClients ## follow a path to authorize the client and read for x chars if theIP is not among the keys of connectedClients then ### Who's Knocking put "Attempting to connect..." into tK if the first line of fld "clientrequest" is not empty then put theIP & ":" && tK & return before the first line of field "clientrequest" else put theIP & ":" && tK into the first line of field "clientrequest" ## IP filter ## Optionally Block connections ## Add ranges or individual IPs into your good or bad list ## Sure you have a firewall--why not also put rules here while you are at it? Especially if you don't manage the firewall put "bad" into tIP --default is nobody can be trusted ##example --if theIP contains "192.168.1" then put "good" into tIP --only people in your private range can connect put "good" into tIP --everyone can connect if tIP is "bad" then if the first line of fld "clientrequest" is not empty then put theIP & ":" && "Client IP is not allowed. Closing socket without notice." & return before the first line of field "clientrequest" else put theIP & ":" && "Client IP is not allowed. Closing socket without notice." into the first line of field "clientrequest" close socket theIP exit clientRequest end if read from socket theIP for 4 chars put it into tQ ## Display and log request if the first line of fld "clientrequest" is not empty then put theIP & ":" && word 1 of tQ & return before the first line of field "clientrequest" else put theIP & ":" && word 1 of tQ into the first line of field "clientrequest" ## If this is not an "AUTH" or "HELO" request you might want to potentially add this requester ## IP to a list of IPs that may need to be blocked (see filter above; create new array for blacklist) if tQ contains "AUTH" is false and tQ contains "HELO" is false then put theIP & ":" && "Bad client connection request. Session terminated." into tLog if the first line of fld "clientrequest" is not empty then put tLog & return before the first line of fld "clientrequest" else put tLog into the first line of fld "clientrequest" close socket theIP exit clientRequest end if if tQ contains "HELO" then read from socket theIP for 903 chars --pub key component plus hex overhead + comma is 907 - 4 chars read above put item 2 of it into tM put binaryencode("H*",tM ) into test --you might want to test for pub key format put test into wannabe[theIP]["pubkey"] put makePassPhrase() into tPass put tPass into wannabe[theIP]["skey"] encrypt tPass using RSA with public key wannabe[theIP]["pubkey"] put it into tPass put binarydecode("H*",tPass, theHex) into hold write theHex & return to socket theIP end if if tQ contains "AUTH" then read from socket theIP until return put it into tM replace return with empty in tM ## Simple hex put binaryencode("H*",tM ) into hold decrypt hold using "aes-256-cbc" with password wannabe[theIP]["skey"] put it into tUser ##Confirm client has credentials to use the system ##Compute-verify hash or HMAC of creds then add to connected or close connection ##item 1 is (may be complex) string to reconstruct and check the pattern/hash against put item 1 of tUser into tCheck ##HMAC secrets are in the stack script--easy to encrypt when building a standalone put CheckRegistered(tCheck) into tHMAC if tHMAC is not among the keys of cList then put "Sorry, you aren't on the list" into tNew encrypt tNew using "aes-256-cbc" with password item 3 of tUser put it into tResponse put binarydecode("H*",tResponse, theHex) into hold write theHex & return to socket theIP put theIP &":" && "Client not authorized." into tLog if the first line of fld "clientrequest" is not empty then put tLog & return before the first line of fld "clientrequest" else put tLog into the first line of fld "clientrequest" close socket theIP exit clientRequest end if if tHMAC is among the keys of cList then ##store things of value to your solution, these are just examples put the time into ConnectedClients[theIP]["connectTime"] put item 2 of tUser into ConnectedClients[theIP]["user"] ## use line below to set session encryption after confirming client is entitled; optional client substituted passphrase for server generated put item 3 of tUser into ConnectedClients[theIP]["skey"] put item 4 of tUser into ConnectedClients[theIP]["email"] delete variable wannabe[theIP] ## encrypt welcome msg with skey put myWelcome(theMessage) & comma && ConnectedClients[theIP]["user"] into tNew encrypt tNew using "aes-256-cbc" with password ConnectedClients[theIP]["skey"] put it into tResponse put binarydecode("H*",tResponse, theHex) into hold write theHex & return to socket theIP if the first line of fld "clientrequest" is not empty then put theIP & ":" && ConnectedClients[theIP]["user"] && "has connected." & return before the first line of field "clientrequest" else put theIP & ":" && ConnectedClients[theIP]["user"] && "has connected." into the first line of field "clientrequest" end if end if end if ## This is the section where you let your imagination run free--connect to databases, create a game, host chat sessions, check ## the temperature in your lab, send a tempurl from your object store or another host host so you dont tie this server up serving big files etc, etc if theIP is among the keys of connectedClients then ##Decode-Decrypt Request put ConnectedClients[theIP]["skey"] into tKey replace return with empty in theCrequest put binaryencode("H*",theCrequest ) into theCrequest decrypt theCrequest using "aes-256-cbc" with password tKey put it into theCrequest --consider an error path ### Handle Client Request --you can make it look neater with switch-case statements but if-then works too if item 1 of theCrequest is "time" then if the first line of fld "clientrequest" is not empty then put theIP & ":" && item 1 of theCrequest & return before the first line of field "clientrequest" else put theIP & ":" && item 1 of theCrequest into the first line of field "clientrequest" ##this could be easily handled "inline," but remember that any calls to shell should be function calls outside this handler ## to avoid race conditions put myTime() into tResponse encrypt tResponse using "aes-256-cbc" with password tKey put it into tResponse put binarydecode("H*",tResponse, theHex) into hold --returns 1 here if good write theHex & return to socket theIP ##debug put empty into theCrequest end if ## the old Who's Online question from back in the day if item 1 of theCrequest contains "whosonline" then if the last line of fld "clientrequest" is not empty then put theIP & ":" && item 1 of theCrequest & return before the first line of field "clientrequest" else put theIP & ":" && item 1 of theCrequest into the first line of field "clientrequest" repeat for each key x in connectedClients put connectedClients[x]["user"] & return after theGangsallHere end repeat delete last char of theGangsallHere encrypt theGangsallHere using "aes-256-cbc" with password tKey put it into tResponse put binarydecode("H*",tResponse, theHex) into hold write theHex & return to socket theIP ##debug put empty into theCrequest end if ##Chat Room 1 if item 1 of theCrequest contains "chat1enter" then if the last line of fld "clientrequest" is not empty then put theIP & ":" && item 1 of theCrequest & return before the first line of field "clientrequest" else put theIP & ":" && item 1 of theCrequest into the first line of field "clientrequest" put theIP into chatroom1[theIP] --chatroom1 members put connectedClients[theIP]["user"] & " has entered the room." into theArrived repeat for each key x in chatroom1 put ConnectedClients[x]["skey"] into tKey encrypt theArrived using "aes-256-cbc" with password tKey put it into tResponse put binarydecode("H*",tResponse, theHex) into hold write theHex & return to socket chatroom1[x] end repeat put empty into theCrequest end if if item 1 of theCrequest contains "chat1text" then if the last line of fld "clientrequest" is not empty then put theIP & ":" && item 1 of theCrequest & return before the first line of field "clientrequest" else put theIP & ":" && item 1 of theCrequest into the first line of field "clientrequest" put connectedClients[theIP]["user"] & ": " & item 2 of theCrequest into theText repeat for each key x in chatroom1 put ConnectedClients[x]["skey"] into tKey encrypt theText using "aes-256-cbc" with password tKey put it into tResponse put binarydecode("H*",tResponse, theHex) into hold write theHex & return to socket chatroom1[x] end repeat put empty into theCrequest end if if item 1 of theCrequest contains "chat1exit" then if the last line of fld "clientrequest" is not empty then put theIP & ":" && item 1 of theCrequest & return before the first line of field "clientrequest" else put theIP & ":" && item 1 of theCrequest into the first line of field "clientrequest" -- put "Goodbye" into lastText -- encrypt theDeparted using "aes-256-cbc" with password connectedClients[theIP]["user"] -- put it into tResponse -- put binarydecode("H*",tResponse, theHex) into hold -- write theHex & return to socket ConnectedClients[theIP] put connectedClients[theIP]["user"] & " has left the room." into theDeparted --delete local chatroom1[theIP] repeat for each key x in chatroom1 put ConnectedClients[x]["skey"] into tKey encrypt theDeparted using "aes-256-cbc" with password tKey put it into tResponse put binarydecode("H*",tResponse, theHex) into hold write theHex & return to socket chatroom1[x] end repeat delete local chatroom1[theIP] put empty into theCrequest end if ### Chat Room 2 if item 1 of theCrequest contains "chat2enter" then if the last line of fld "clientrequest" is not empty then put theIP & ":" && item 1 of theCrequest & return before the first line of field "clientrequest" else put theIP & ":" && item 1 of theCrequest into the first line of field "clientrequest" put theIP into chatroom2[theIP] put connectedClients[theIP]["user"] & " has entered the room." into theArrived repeat for each key x in chatroom2 put connectedClients[x]["skey"] into tKey encrypt theArrived using "aes-256-cbc" with password tKey put it into tResponse put binarydecode("H*",tResponse, theHex) into hold write theHex & return to socket chatroom2[x] end repeat put empty into theCrequest end if if item 1 of theCrequest contains "chat2text" then if the last line of fld "clientrequest" is not empty then put theIP & ":" && item 1 of theCrequest & return before the first line of field "clientrequest" else put theIP & ":" && item 1 of theCrequest into the first line of field "clientrequest" put connectedClients[theIP]["user"] & ": " & item 2 of theCrequest into theText repeat for each key x in chatroom2 put connectedClients[x]["skey"] into tKey encrypt theText using "aes-256-cbc" with password tKey put it into tResponse put binarydecode("H*",tResponse, theHex) into hold write theHex & return to socket chatroom2[x] end repeat put empty into theCrequest end if if item 1 of theCrequest contains "chat2exit" then if the last line of fld "clientrequest" is not empty then put theIP & ":" && item 1 of theCrequest & return before the first line of field "clientrequest" else put theIP & ":" && item 1 of theCrequest into the first line of field "clientrequest" put connectedClients[theIP]["user"] & " has left the room." into theDeparted repeat for each key x in chatroom2 put connectedClients[x]["sKey"] into tKey encrypt theDeparted using "aes-256-cbc" with password tKey put it into tResponse put binarydecode("H*",tResponse, theHex) into hold write theHex & return to socket chatroom2[x] end repeat delete local chatroom2[theIP] put empty into theCrequest end if ## end is connected end if read from socket theIP until return with message "clientRequest" end clientRequest function myTime put the time && "on" && the long date into tMc return tMc end myTime function myWelcome put "Welcome to the world of 1989 online services" into tMc return tMc end myWelcome € Ö Î ù Start Server cREVGeneral scriptChecksum ÿÉ—|¯«Ú©?æQixë tempScript scripteditorselection 16048 scripteditorvscroll script!
on mouseUp
accept connections on port 8080 with message "someoneConnected"
end mouseUp
on someoneConnected theIP
read from socket theIP until return with message "newMessage"
end someoneConnected
on newMessage theIp theMessage
put theIP & ":" && theMessage & return after field "text"
read from socket theIP until return with message "newMessage"
end newMessage