diff --git a/README.md b/README.md deleted file mode 100644 index 4344c55..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# meowlib - diff --git a/asymcrypt.go b/asymcrypt.go new file mode 100644 index 0000000..41556b9 --- /dev/null +++ b/asymcrypt.go @@ -0,0 +1,106 @@ +package meowlib + +import ( + "encoding/base64" + "time" + + "github.com/ProtonMail/gopenpgp/v2/crypto" + "github.com/ProtonMail/gopenpgp/v2/helper" + "github.com/rs/zerolog/log" +) + +type KeyPair struct { + Public string `json:"public,omitempty"` + Private string `json:"private,omitempty"` + Generated time.Time `json:"generated,omitempty"` +} + +type KeysArray []KeyPair + +func NewKeyPair() KeyPair { + var kp KeyPair + keys, err := crypto.GenerateKey("name", "mail", "rsa", 4096) + if err != nil { + log.Error().Msg("Key generation failed") + } + kp.Generated = time.Now() + pub, err := keys.GetArmoredPublicKey() + if err != nil { + log.Error().Msg("Public key extraction failed") + } + kp.Public = base64.StdEncoding.EncodeToString([]byte(pub)) + priv, err := keys.Armor() + if err != nil { + log.Error().Msg("Private key extraction failed") + } + kp.Private = base64.StdEncoding.EncodeToString([]byte(priv)) + return kp +} + +func (keyPair *KeyPair) GetCryptoKeyObject() *crypto.Key { + priv, err := base64.StdEncoding.DecodeString(keyPair.Private) + if err != nil { + log.Error().Msg("Create key from armoured b64 failed") + } + key, err := crypto.NewKeyFromArmored(string(priv)) + if err != nil { + log.Error().Msg("Create key from armoured failed") + } + return key +} + +func Encrypt(publicKey string, data []byte) ([]byte, error) { + pub, err := base64.StdEncoding.DecodeString(publicKey) + if err != nil { + log.Error().Msg("Message encryption b64 failed") + } + armor, err := helper.EncryptBinaryMessageArmored(string(pub), data) + if err != nil { + log.Error().Msg("Message encryption failed") + } + return []byte(armor), err +} + +func Decrypt(privateKey string, data []byte) ([]byte, error) { + priv, err := base64.StdEncoding.DecodeString(privateKey) + if err != nil { + log.Error().Msg("Message decryption b64 failed") + } + decrypted, err := helper.DecryptBinaryMessageArmored(string(priv), []byte(""), string(data)) + if err != nil { + log.Error().Msg("Message decryption failed") + } + return []byte(decrypted), err +} + +func EncryptAndSign(publicKey string, privateKey string, data []byte) ([]byte, []byte, error) { + pub, err := base64.StdEncoding.DecodeString(publicKey) + if err != nil { + log.Error().Msg("Message encryption and sign b64 failed") + } + priv, err := base64.StdEncoding.DecodeString(privateKey) + if err != nil { + log.Error().Msg("Message encryption and sign b64 failed") + } + armor, signature, err := helper.EncryptSignBinaryDetached(string(pub), string(priv), []byte(""), data) + if err != nil { + log.Error().Msg("Message encryption and sign failed") + } + return []byte(armor), []byte(signature), err +} + +func DecryptAndSign(publicKey string, privateKey string, data []byte, signature []byte) ([]byte, error) { + pub, err := base64.StdEncoding.DecodeString(publicKey) + if err != nil { + log.Error().Msg("Message decryption and sign b64 failed") + } + priv, err := base64.StdEncoding.DecodeString(privateKey) + if err != nil { + log.Error().Msg("Message decryption and sign b64 failed") + } + decrypted, err := helper.DecryptVerifyBinaryDetached(string(pub), string(priv), []byte(""), data, string(signature)) + if err != nil { + log.Error().Msg("Message decryption and sign failed") + } + return decrypted, err +} diff --git a/keys_test.go b/asymcrypt_test.go similarity index 72% rename from keys_test.go rename to asymcrypt_test.go index 21bcdb7..8c407f2 100644 --- a/keys_test.go +++ b/asymcrypt_test.go @@ -1,6 +1,7 @@ -package meow +package meowlib import ( + "encoding/base64" "fmt" "log" "testing" @@ -18,7 +19,8 @@ func TestGetKey(t *testing.T) { // fmt.Println(kp.Private) key := kp.GetCryptoKeyObject() // fmt.Println(key.Armor()) - pubkey, _ := key.GetArmoredPublicKey() + Armpubkey, _ := key.GetArmoredPublicKey() + pubkey := base64.StdEncoding.EncodeToString([]byte(Armpubkey)) if kp.Public != pubkey { log.Fatal("error in public key") } diff --git a/clean.sh b/clean.sh new file mode 100755 index 0000000..c68698f --- /dev/null +++ b/clean.sh @@ -0,0 +1,10 @@ +#/usr/bin/bash +rm doc/*.aux +rm doc/*.fdb_latexmk +rm doc/*.fls +rm doc/*.log +rm doc/*.pdf +rm doc/*.synctex.gz +rm test.id +rm id.json +rm messages.pb.go diff --git a/doc/archi01.puml b/doc/archi01.puml new file mode 100644 index 0000000..342d0f3 --- /dev/null +++ b/doc/archi01.puml @@ -0,0 +1,15 @@ +@startuml Simple server based communication + +actor Client1 + +node Server [ +Server + +] + +actor Client2 +Client1 -u-> Server:Send Message +Client2 -u-> Server: Retrieve Message + + +@enduml diff --git a/doc/archi02.puml b/doc/archi02.puml new file mode 100644 index 0000000..3d29e5e --- /dev/null +++ b/doc/archi02.puml @@ -0,0 +1,15 @@ +@startuml + +actor Client1 + +node Server [ +Server + +] + +actor Client2 +Client1 -u-> Server:Send Presence (IP@) +Client2 -u-> Server: Retrieve Presence (IP@) +Client1 <-> Client2: Direct Connect + +@enduml diff --git a/doc/archi03.puml b/doc/archi03.puml new file mode 100644 index 0000000..16d148d --- /dev/null +++ b/doc/archi03.puml @@ -0,0 +1,39 @@ +@startuml Mtrk protocol communication + +actor Client1 + +node Server [ +Server +] +node GW1 [ +GW1 +] +node GW2 [ +GW2 +] +node GW3 [ +GW3 +] +node GW4 [ +GW4 +] +node GW5 [ +GW5 +] +node GW6 [ +GW6 +] +actor Client2 + +Client1 -> GW1:(GK1(GK2(GK3((CK2)Payload)))) +GW1 -> GW2:(GK2(GK3((CK2)Payload))) +GW2 -> GW3:(GK3((CK2)Payload)) +GW3 -d-> Server:((CK2)Payload) + +Client2 -> GW4:(GK4(GK5(GK6(CK2)Request)))) +GW4 -> GW5:(GK5(GK6(CK2)Request))) +GW5 -> GW6:(GK6(CK2)Request)) +GW6 -u-> Server:((CK2)Request) + + +@enduml diff --git a/doc/architecture.tex b/doc/architecture.tex new file mode 100644 index 0000000..e69de29 diff --git a/doc/class_messages01.puml b/doc/class_messages01.puml new file mode 100644 index 0000000..82c0f8c --- /dev/null +++ b/doc/class_messages01.puml @@ -0,0 +1,83 @@ +@startuml Messages sturcture and hierarchy + +class PackedServerMessage { + From string + Payload string + Signature string +} + +class ServerMessage { + Type string +} + + +class PackedForwardMessage { + NextServerKey string + Url string + Payload string +} + +class PackedUserMessage { + DestinationKey + Payload + Signature +} + +class UserMessage +{ + LocalUuid + Destination + From + Type + Data + Sent + NextKey +} + +class InternalMessage { + With + SentByMe + UserMessageData + ServerUuid + Received + Processed + TransferPath +} + +class ConversationRequest { + Destination string + LastUuidOK string + PublishIp bool +} +class PollRequest { + Destinations []ConversationRequest + Time time.Time +} + +class ConversationResponse { + MessageUuids []string + SourceIpAddress string +} + +class PollResponse { + Conversations map[string]ConversationResponse +} + +class MessageBodies { + Messages []Message +} + +PackedServerMessage *-- ServerMessage + +ServerMessage <|--PackedForwardMessage +ServerMessage <|--PollRequest +PollRequest *-- ConversationRequest +PollResponse *--ConversationResponse +ServerMessage <|--PollResponse +ServerMessage <|-- MessageBodies +PackedUserMessage *-- UserMessage +InternalMessage *-- UserMessage +PackedForwardMessage *-- ServerMessage +PackedForwardMessage *-- PackedForwardMessage +MessageBodies *-- PackedUserMessage +@enduml diff --git a/doc/protocol.tex b/doc/protocol.tex new file mode 100644 index 0000000..1a1c67b --- /dev/null +++ b/doc/protocol.tex @@ -0,0 +1,193 @@ +\documentclass{article} +\usepackage{fetamont} +\begin{document} +\title{ + \textffm{Meow} messaging protocol} +\author{Author + \texttt{author@address.net}} +\date{\today} + +\maketitle + +\begin{abstract} +The \textffm{Meow} protocol is a privacy driven instant messaging protocol. +That protocol might be used for creating secure and distributed chat services or allowing machine to machine communication. +This document describes the services provided by the protocol, the messaging structures and the transport protocols that might be used. + +\end{abstract} + + +\section{Services} +\subsection{Unregulated identities} +The only requirement to get a valid \textffm{Meow} identity is to generate a user key pair. +No phone number or email check will be performed, unlike main instant messaging protocols, there is no central administration. + +\subsection{Fine grained privacy control} + +\subsubsection{Trustable server based communication} +Like most widely available messaging softwares, (Whatsapp, Signal, Viber, Telegram...), \textffm{Meow} provides a simple server based messaging. +The main difference is that allows to explicitly choose which server you want to use. +The server code being open source, we strongly encourage you to run your own server at home or in your company. +The server requires very few ressources and will run on any low cost single board computer. + +\subsubsection{Anonymized message transfer} +\textffm{Meow} also provides an anonymizing transfer services very similar to the Tor Onion protocol, we called it the Matriochka protocol. +Any server can be used for building the transfer chain. +Some of them might be marked as trusted. +It is strongly advised to use trusted servers as your first node and message server (the one that holds your incoming messages). + +\subsubsection{Presence protocol for direct messaging} +A presence service associating your conversation keys to your IP address for direct peer to peer connection is also provided. +The presence protocol is simply activated by setting a flag in the message poll requests. +If that flag is set, your encrypted IP will be published on the server, allowing your only your peer(s) to decrypt it and directly communicate with your terminal. + +\subsubsection{Peer based privacy settings} +You might define specific communication privacy preferences for each of your contacts : +\begin{itemize} + \item simple server based communication allowed for Joe, + \item direct communication prefered with Julian, fallback to my own server, + \item matriochka protocol required for Edward, first node is one of my trusted servers, my message node is my own server, randomly switch from trusted server lists for others. + \item ... +\end{itemize} +\subsection{Multiple devices support} +\textffm{Meow} allows you to be connected from multiple devices and offers chat synchronization capability. +A device might be revoqued anytime from an other any one. Proof of your identity (password or other) shall be provided in order to grant device revocation. + +\subsection{Adding contacts} +If you want to add a new contact, keys and uuids will be generated, then a rendez-vous card will be created. +That rendez-vous card might be sent by any trustable communication means, or preferably from hand to hand, as a file on a flash disk or a QR code.\\ +In return your contact will provide the exact same data, encrypted with your public key and delivered to the address specified in the initial rendez-vous card. + +\subsection{Contacts forwarding} +Using the \textffm{Meow} protocol a user won't be able to forward your contact information without your consent. +Each user knows you as a different identity, thus forwarding a known identity to another user is meaningless, any message to that identity signed by another user would be discarded. + + +\subsection{Group conversation} +A very basic group messaging service is available. It allows to exchange group information between users. After that, a message to a group will send a copy of the message to each member. + + + +\subsection{Emergency broadcast} +The + + +\subsection{Public networks shortage resilience} +\textffm{Meow} may run without Internet connection, either on an isolated wifi access point, either on a meshed network of wifi routers or even via serial IOT transport layers (LoRa,...) + + + + +\subsection{User directory service} +This service allows to restore a lost functionality of Internet historic chat services (like ICQ). You could simply set a "Free for chat" status that allowed other people to contact you, either randomly or based on a short description that you might provide. +Why providing that service while the internet is suffocating due to the abundance of social networks ?\\ +Well, that option offered a few advantages : +\begin{itemize} + \item you're still an anonymous user chatting with other anonymous users. + \item no social network algorithm will select people that think/behave/vote/eat... just like you. Diversity makes a better world. + \item a smaller community of users, skilled enough to operate a \textffm{Meow} chat app... that might provide a first filter. + It's a bit like in the old times, when people had to be able to start a win98 computer, connect it to internet, then download and install ICQ... + If you lost some time in social networks, and experienced ICQ in the 2000's, you know what I mean. +\end{itemize} + +\section{Identities and keys} + +\subsection{User identity} +Each \textffm{Meow} user has a unique identity. That identity is strictly private, only used to manage your own data (local encryption, devices, ...) +Let's call that one the User Key Pair (Ukp) + +\subsection{Contact identity} +Each of your contacts will know you as a different identity, we'll call that one the Contact Key Pair (Ckp) +This means that : +\begin{itemize} + \item none of your contacts will be able to forward your id to another person without your consent + \item any message to that Ckp, not signed by the user associated to it, will be discarded. +\end{itemize} + +\subsection{Conversation encryption} +Each conversation with one of your contacts will be encrypted using an encryption keypair (Ekp) +The Ekp might be changed anytime by its owner and the new public key will be sent along the last message. +The Ekp is used to cypher your conversation. + +\subsection{Conversation lookup} +A contact conversation Lookup Key Pair(Lkp) is also associated with your conversation. The Lkp public key is used to identify your conversation on a server. +the private key allows you to sign your tequest and prove the server that you are the legitimate recipient for a message. +This Lkp can be changed anytime by it's owner and the new public key will be sent along the last message. +The Lkp and the Ekp are only changed, once the change has beeen acknowledged by your contact. + +\subsection{Server identity} +Each server has a Server key (Skp). That key allows you to cypher the messages that you're sending to the server. + +\subsection{Device identity} +Each device is identified by a key (Dkp), that device key allows you to perform secured exchanges between your devices for synchronization/revocation purposes. +Communication between devices is achieved using the same principle as the user to user communication. A device might be considered as any another user. The messages content are based on a synchronization protocol. + +\section{Contact management} +\subsection{Adding a contact} +Rendez-vous card, containing : +\begin{itemize} + \item Your public key for that contact + \item An initial conversation public key for getting encrypted messages from that contact + \item An initial conversation uuid that you'll use to lookup for incoming messages on the servers + \item A list of your prefered message servers + \item A signature to prevent transmission of tampered data +\end{itemize} +\subsection{Sharing a contact} +If a user wants to forward one of his contacts to you, that will be handled as a double request : +\begin{enumerate} + \item I'm receiving a contact name, without any key + \item +\end{enumerate} + +\section{Messaging} +\subsection{User messages} +TODO + +\subsection{Server stored message} +TODO + +\subsection{Matriochka message packing} +TODO + +\subsection{Synchronization messages} +TODO + +\section{Transport protocols} +\subsection{URLs} +Server urls do define the protocol used for communicating with the server. +Some of the protocols will be described hereafter, but that list is not exhaustive, and might be extended in the future.\\ +Examples of a valid url: +\begin{verbatim} +http://myserver.com +https://user:pass@myauthenticatedserver.net:8443 +mqtt://mymqttserver:6203 +udp://myudpserver.org:41325 +serial://dev/ttyS0 +\end{verbatim} + +\subsection{HTTP/S} +TODO + +\subsection{UDP} +TODO + +\subsection{Internetless alternative routing} +TODO + +\section{Server Features} +\subsection{Server catalog} +\subsection{Antispam} +\subsection{Self defense} + + + + +\section{Backup} +\section{Recovery} + +\section{Very secure devices} +You don't trust your phone ? + + + +\end{document} \ No newline at end of file diff --git a/doc/sq_msg01.puml b/doc/sq_msg01.puml new file mode 100644 index 0000000..bde2ddd --- /dev/null +++ b/doc/sq_msg01.puml @@ -0,0 +1,8 @@ +@startuml +Client1 -> Server: Send Message (PubKeyClient2 + Payload) +Server --> Client1: Ack Message (Server UUID + DateReceived) +Client2 -> Server: Get Messages (PubKeyClient2 + IpPublish) +Server --> Client2: Available Messages list (UUID list cyphered with PubKeyClient2) +Client2 <- Server: Get Messages (decoded UUID signed with PK) +Server --> Client2: Messages [](PubKeyClient2 + Payload) +@enduml \ No newline at end of file diff --git a/endtoend_test.go b/endtoend_test.go new file mode 100644 index 0000000..328dee5 --- /dev/null +++ b/endtoend_test.go @@ -0,0 +1,70 @@ +package meowlib + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "testing" +) + +func TestEndToEnd(t *testing.T) { + // + // Create my own identity + // + fmt.Println("Trying to load identity from file.") + me, err := LoadIdentity("test.id") + if err != nil { + fmt.Println("Failed : creating New identity...") + me = CreateIdentity("myname") + + // + // define my preferences (servers) + // + me.MessageServers.Name = "Message Servers" + me.MessageServers.AddUrls([]string{"http://127.0.0.1/meow/", "mqtt://127.0.0.1", "meow://127.0.0.1"}) + // + // create an invitation for a friend, I want him/her to know me as Bender + // + fmt.Println("Creating an invitation for the first friend...") + myFirstFriend, invitation := me.InvitePeer("Bender", "myfirstfriend", []int{1, 2}) + // print my invitation + a, _ := json.Marshal(invitation) + fmt.Println(string(a)) + // TODO : Convert invitation to QR Code + + // + // Simulate peer invitation response : generate the friend's keypair + fmt.Println("Simulating first friend answer...") + var receivedContact Contact + firstFriendContactKp := NewKeyPair() + firstFriendEncryptionKp := NewKeyPair() + firstFriendLookupKp := NewKeyPair() + receivedContact.Name = "I'm the friend" + receivedContact.ContactPublicKey = firstFriendContactKp.Public + receivedContact.EncryptionPublicKey = firstFriendEncryptionKp.Public + receivedContact.LookupPublicKey = firstFriendLookupKp.Public + var friendsMessageServers ServerList + friendsMessageServers.AddUrls([]string{"http://myfriend.org/meow/"}) + for _, srv := range friendsMessageServers.Servers { + receivedContact.PullServers = append(receivedContact.PullServers, srv.ServerData) + } + // End simulating concact invitation response + // + + // + // Finalize the contact with the invitation response + // + me.FinalizeInvitation(myFirstFriend, &receivedContact) + err = me.Save("test.id") + if err != nil { + fmt.Println(err.Error()) + } + + a, _ = json.Marshal(me) + ioutil.WriteFile("id.json", a, 0644) + fmt.Println(string(a)) + } + //myFirstFriend.SetComMethod() + //msg := myFirstFriend.SendText() + +} diff --git a/go.mod b/go.mod index 45d8de0..72497a1 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,10 @@ go 1.16 require ( github.com/ProtonMail/gopenpgp/v2 v2.2.4 + github.com/go-resty/resty/v2 v2.6.0 + github.com/google/uuid v1.3.0 github.com/rs/zerolog v1.25.0 + github.com/stretchr/testify v1.4.0 + github.com/tidwall/gjson v1.10.2 + google.golang.org/protobuf v1.27.1 ) diff --git a/go.sum b/go.sum index 2d3b1a2..fac4706 100644 --- a/go.sum +++ b/go.sum @@ -9,7 +9,14 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4= +github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -26,6 +33,12 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/tidwall/gjson v1.10.2 h1:APbLGOM0rrEkd8WBw9C24nllro4ajFuJu0Sc9hRz8Bo= +github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -45,6 +58,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -67,7 +81,13 @@ golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/https.go b/https.go new file mode 100644 index 0000000..2160fd7 --- /dev/null +++ b/https.go @@ -0,0 +1,50 @@ +package meowlib + +import ( + "crypto/tls" + "fmt" + + "github.com/tidwall/gjson" + + "github.com/go-resty/resty/v2" + "github.com/rs/zerolog/log" +) + +var Address string + +func Configure(url string) { + Address = url +} + +func Send(msg []byte) (string, error) { + client := resty.New().SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) + + resp, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetBody(msg). + Post(Address + "/message/add/") + if err != nil { + log.Error().Msg(err.Error()) + } + serverUuid := gjson.Get(resp.String(), "serveruuid").String() + return serverUuid, err +} + +func Receive(key string) []byte { + client := resty.New().SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) + + resp, err := client.R(). + SetHeader("Content-Type", "application/json"). + Get(Address + "/message/" + key) + fmt.Println(" StatusCode :", resp.StatusCode()) + fmt.Println(" Cookies :", resp.Cookies()) + fmt.Println(" Error :", err) + msg := resp.Body() + return msg +} + +func Start(callback *func() []InternalMessage) { + for { + + } +} diff --git a/identity.go b/identity.go index 23d1518..7461475 100644 --- a/identity.go +++ b/identity.go @@ -1,41 +1,86 @@ -package meow +package meowlib + +import ( + "encoding/json" + "io/ioutil" + + "github.com/ProtonMail/gopenpgp/v2/helper" +) + +const key = "3pw0c8#6ZG8{75b5;3?fe80$2" type Identity struct { - nickname string - PublicKey string - PrivateKey string - Status string - Peers []Peer + Nickname string `json:"nickname,omitempty"` + PublicKey string `json:"public_key,omitempty"` + PrivateKey string `json:"private_key,omitempty"` + Status string `json:"status,omitempty"` + Peers PeerList `json:"peers,omitempty"` + KnownServers ServerList `json:"known_servers,omitempty"` + MessageServers ServerList `json:"message_servers,omitempty"` } -func Create(nickname string) Identity { +func CreateIdentity(nickname string) *Identity { var id Identity - id.nickname = nickname + id.Nickname = nickname kp := NewKeyPair() id.PublicKey = kp.Public id.PrivateKey = kp.Private - return id + return &id } -func (*Identity) InvitePeer(name string) *Peer { +func (id *Identity) InvitePeer(myName string, contactName string, messageServerIdxs []int) (*Peer, *Contact) { var peer Peer - peer.Me = append(peer.Me, NewKeyPair()) - peer.Name = name - return &peer + var myContactCard Contact + peer.Me = NewKeyPair() + peer.EncryptionKp = NewKeyPair() + peer.LookupKp = NewKeyPair() + peer.Name = contactName + for _, i := range messageServerIdxs { + myContactCard.PullServers = append(myContactCard.PullServers, id.MessageServers.Servers[i].ServerData) + } + myContactCard.Name = myName + myContactCard.ContactPublicKey = peer.Me.Public + myContactCard.EncryptionPublicKey = peer.EncryptionKp.Public + myContactCard.LookupPublicKey = peer.LookupKp.Public + + id.Peers = append(id.Peers, peer) + + return &id.Peers[len(id.Peers)-1], &myContactCard } -func (*Identity) FinalizeInvitation(peer *Peer, peerPublicKey string) { - peer.PublicKey = peerPublicKey +func (*Identity) FinalizeInvitation(peer *Peer, receivedContact *Contact) { + peer.Contact = *receivedContact + } func (*Identity) AddPeer(name string, peerPublicKey string) string { var peer Peer - peer.Me = append(peer.Me, NewKeyPair()) - peer.Name = name - peer.PublicKey = peerPublicKey - return peer.Me[len(peer.Me)-1].Public + peer.Me = NewKeyPair() + peer.Contact.Name = name + peer.Contact.ContactPublicKey = peerPublicKey + return peer.Me.Public } -func Save() { - +func LoadIdentity(file string) (*Identity, error) { + var id Identity + indata, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + pass, err := helper.DecryptMessageWithPassword([]byte(key), string(indata)) + if err == nil { + err = json.Unmarshal([]byte(pass), &id) + return nil, err + } + return &id, err +} + +func (id *Identity) Save(file string) error { + b, _ := json.Marshal(id) + armor, err := helper.EncryptMessageWithPassword([]byte(key), string(b)) + if err != nil { + return err + } + err = ioutil.WriteFile(file, []byte(armor), 0644) + return err } diff --git a/identity_test.go b/identity_test.go new file mode 100644 index 0000000..adcfb97 --- /dev/null +++ b/identity_test.go @@ -0,0 +1,30 @@ +package meowlib + +import ( + "log" + "testing" +) + +func TestCreate(t *testing.T) { + id := CreateIdentity("myname") + id.Save("test.id") +} + +func TestSave(t *testing.T) { + id := Identity{Nickname: "myname", + PublicKey: "pubk", + PrivateKey: "privk", + Status: "online", + } + id.Save("test.id") +} + +func TestLoad(t *testing.T) { + id, err := LoadIdentity("test.id") + if err != nil { + log.Fatal((err.Error())) + } + if id.Nickname != "myname" { + log.Fatal("failed") + } +} diff --git a/keys.go b/keys.go deleted file mode 100644 index ede2439..0000000 --- a/keys.go +++ /dev/null @@ -1,42 +0,0 @@ -package meow - -import ( - "time" - - "github.com/ProtonMail/gopenpgp/v2/crypto" - "github.com/rs/zerolog/log" -) - -type KeyPair struct { - Public string - Private string - Generated time.Time -} - -type KeysArray []KeyPair - -func NewKeyPair() KeyPair { - var kp KeyPair - keys, err := crypto.GenerateKey("name", "mail", "rsa", 4096) - if err != nil { - log.Debug().Msg("Key generation failed") - } - kp.Generated = time.Now() - kp.Public, err = keys.GetArmoredPublicKey() - if err != nil { - log.Debug().Msg("Public key extraction failed") - } - kp.Private, err = keys.Armor() - if err != nil { - log.Debug().Msg("Private key extraction failed") - } - return kp -} - -func (keyPair *KeyPair) GetCryptoKeyObject() *crypto.Key { - key, err := crypto.NewKeyFromArmored(keyPair.Private) - if err != nil { - log.Debug().Msg("Create key from armoured failed") - } - return key -} diff --git a/message.go b/message.go deleted file mode 100644 index 0c0b9cd..0000000 --- a/message.go +++ /dev/null @@ -1,45 +0,0 @@ -package meow - -import ( - "time" - - "github.com/ProtonMail/gopenpgp/v2/helper" - "github.com/rs/zerolog/log" -) - -type Message struct { - To string - From string - Data []byte - Sent time.Time - Received time.Time - Read time.Time -} - -func CreateFromText(peer Peer, text string) *Message { - var msg Message - var err error - msg.To = peer.PublicKey - msg.From = peer.Me[len(peer.Me)-1].Public - msg.Data = []byte{byte('t')} - armor, err := helper.EncryptMessageArmored(msg.To, text) - if err != nil { - log.Debug().Msg("Message encryption failed") - } - msg.Data = append(msg.Data, []byte(armor)...) - msg.Sent = time.Now() - return &msg -} - -func (*Message) GetType() string { - - return "msg" -} - -func (msg *Message) GetText(peer Peer) string { - decrypted, err := helper.DecryptMessageArmored(peer.Me[len(peer.Me)-1].Private, []byte(""), string(msg.Data)) - if err != nil { - log.Debug().Msg("Message decryption failed") - } - return decrypted -} diff --git a/network.go b/network.go new file mode 100644 index 0000000..7dac336 --- /dev/null +++ b/network.go @@ -0,0 +1,9 @@ +package meowlib + +type Network interface { + Configure(url string) + Send(msg []byte) + Receive(request []byte) []byte + Start() + Stop() +} diff --git a/pb/messages.proto b/pb/messages.proto new file mode 100644 index 0000000..b27520d --- /dev/null +++ b/pb/messages.proto @@ -0,0 +1,90 @@ +syntax = "proto3"; +package meowlib; +option go_package = "forge.redroom.link/yves/meowlib"; + +message PackedServerMessage { + string From = 1; + bytes Payload = 2; + string Signature = 3; +} + +message PackedUserMessage { + string From = 1; + string Destination=2; + bytes Payload=3; + bytes Signature=4; +} + +message ServerMessage { + string Type = 1; + string ServerPubKey = 2 ; + bytes Payload = 3 ; + uint64 ServerReceived = 4 ; + string ServerUuid = 5 ; + + message ConversationRequest { + string ccid = 1; + string LastUuidOK = 2; + bool PublishOnline = 3; + } + + message ConversationResponse { + repeated string MessageUuids = 1; + string SourceIpAddress = 2; + } + + repeated ConversationRequest PollRequest = 7; + map PollResponse = 8; + + message PostedMessage{ + string ccid= 1; + repeated PackedUserMessage Messages = 2; + } + repeated PostedMessage Messages = 9; + string NextServerKey = 10; + string Url = 11; +} + +message Server { + string Name = 1; + string Description=2; + string PublicKey = 3; + string Url = 4; + int32 ConfidenceLevel = 5; +} + +message MinimalContact { + string name=1; + string publicKey=2; + repeated Server TrustedServers = 3; +} + + +message UserMessage { + + string Destination = 1; + string From = 2; + string Type = 3; + bytes Data = 4; + message ConversationStatus { + string LocalUuid = 1; + uint64 LocalSequence = 2 ; + uint64 Sent = 3 ; + uint64 Received = 4; + uint64 Processed = 5; + string NextCcid = 6; + bool NextCcidAck = 7; // false when proposing a new id, true for accepting it + string NextCcpkey = 8; + bool NextKeyCcpkeyAck = 9; // false when proposing a new key, true for accpeting it + } + ConversationStatus Status = 5; + + + MinimalContact contact = 6; + + message Group{ + string name=1; + repeated MinimalContact members = 2; + } + Group group = 7; +} \ No newline at end of file diff --git a/pb/protogen.sh b/pb/protogen.sh new file mode 100755 index 0000000..85b255e --- /dev/null +++ b/pb/protogen.sh @@ -0,0 +1,4 @@ +#!/bin/bash +protoc -I=. --go_out=.. messages.proto +mv ../forge.redroom.link/yves/meowlib/messages.pb.go ../ +rm -rf ../forge.redroom.link \ No newline at end of file diff --git a/peer.go b/peer.go index 69e4cb1..c03f054 100644 --- a/peer.go +++ b/peer.go @@ -1,27 +1,60 @@ -package meow +package meowlib -import "time" +import ( + "fmt" + "time" +) + +type Contact struct { + Name string `json:"name,omitempty"` + ContactPublicKey string `json:"contact_public_key,omitempty"` + EncryptionPublicKey string `json:"encryption_public_key,omitempty"` + LookupPublicKey string `json:"lookup_public_key,omitempty"` + PullServers []Server `json:"pull_servers,omitempty"` +} type Peer struct { - Me KeysArray - Name string - Visible bool - Password string - MessageNotification string - PublicKey string - PushServers []Server - OnionMode bool - Convdersation []Message - LastMessage time.Time + Name string `json:"name,omitempty"` + Me KeyPair `json:"me,omitempty"` + Contact Contact `json:"contact,omitempty"` + Visible bool `json:"visible,omitempty"` + VisiblePassword string `json:"visible_password,omitempty"` + PasswordType string `json:"password_type,omitempty"` + Blocked bool `json:"blocked,omitempty"` + MessageNotification string `json:"message_notification,omitempty"` + OnionMode bool `json:"onion_mode,omitempty"` + Conversation []InternalMessage `json:"convdersation,omitempty"` + LastMessage time.Time `json:"last_message,omitempty"` + EncryptionKp KeyPair `json:"conversation_kp,omitempty"` + LookupKp KeyPair `json:"lookup_kp,omitempty"` } type PeerList []Peer +type Group struct { + Name string `json:"name,omitempty"` + Members []Contact `json:"members,omitempty"` +} + func (pl *PeerList) GetFromPublicKey(publickey string) *Peer { for _, peer := range *pl { - if peer.PublicKey == publickey { + if peer.Contact.ContactPublicKey == publickey { return &peer } } return nil } + +func (pl *PeerList) GetFromName(name string) *Peer { + for _, peer := range *pl { + if peer.Contact.Name == name { + return &peer + } + } + return nil +} + +func (peer *Peer) SendText(text string) { + im := CreateText(*peer, text) + fmt.Println(im.MessageData.Destination) +} diff --git a/peer_test.go b/peer_test.go new file mode 100644 index 0000000..60891a9 --- /dev/null +++ b/peer_test.go @@ -0,0 +1,10 @@ +package meowlib + +import ( + "testing" +) + +func TestGetFromPublicKey(t *testing.T) { + id := CreateIdentity("test") + id.Save("test.id") +} diff --git a/proto_test.go b/proto_test.go new file mode 100644 index 0000000..55c295f --- /dev/null +++ b/proto_test.go @@ -0,0 +1,36 @@ +package meowlib + +import ( + "io/ioutil" + "log" + "testing" + + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/proto" +) + +func TestServerMessageSerialization(t *testing.T) { + var msg PackedServerMessage + msg.From = "toto" + msg.Payload = []byte("mon texte") + msg.Signature = "moi" + out, err := proto.Marshal(&msg) + if err != nil { + log.Fatalln("Failed to encode address book:", err) + } + if err := ioutil.WriteFile("test", out, 0644); err != nil { + log.Fatalln("Failed to write address book:", err) + } + in, err := ioutil.ReadFile("test") + if err != nil { + log.Fatalln("Error reading file:", err) + } + rmsg := &PackedServerMessage{} + if err := proto.Unmarshal(in, rmsg); err != nil { + log.Fatalln("Failed to parse address book:", err) + } + assert.Equal(t, msg.From, rmsg.From, "The two words should be the same.") + assert.Equal(t, msg.Payload, rmsg.Payload, "The two words should be the same.") + assert.Equal(t, msg.Signature, rmsg.Signature, "The two words should be the same.") + +} diff --git a/server.go b/server.go index 3bdc69d..c509f93 100644 --- a/server.go +++ b/server.go @@ -1,8 +1,30 @@ -package meow +package meowlib -type Server struct { - Name string - PublicKey string - Address string - Port string +import "time" + +type InternalServer struct { + ServerData Server `json:"server_data,omitempty"` + Presence bool `json:"presence,omitempty"` + LastCheck time.Time `json:"last_check,omitempty"` + Uptime time.Duration `json:"uptime,omitempty"` + Login string `json:"login,omitempty"` + Password string `json:"password,omitempty"` + Me KeyPair `json:"me,omitempty"` +} + +type ServerList struct { + Name string + Servers []InternalServer +} + +func ServerFromUrl(url string) *InternalServer { + var is InternalServer + is.ServerData.Url = url + return &is +} + +func (sl *ServerList) AddUrls(urls []string) { + for _, url := range urls { + sl.Servers = append(sl.Servers, *ServerFromUrl(url)) + } } diff --git a/servermessage.go b/servermessage.go new file mode 100644 index 0000000..3092927 --- /dev/null +++ b/servermessage.go @@ -0,0 +1,54 @@ +package meowlib + +import ( + "encoding/json" + + "github.com/rs/zerolog/log" +) + +const MessagesType = 1 +const PollRequestType = 1 +const PollResponseType = 1 +const MtrkType = 1 + +func (msg *ServerMessage) Pack() *PackedServerMessage { + var pck PackedServerMessage + + jsonMsg, _ := json.Marshal(msg) + armor, err := Encrypt(msg.ServerPubKey, jsonMsg) + if err != nil { + log.Error().Msg("Message encryption failed") + } + pck.Payload = []byte(armor) + return &pck +} + +func (pck *PackedServerMessage) Unpack(privateKey string) *ServerMessage { + var msg *ServerMessage + + decrypted, err := Decrypt(privateKey, pck.Payload) + if err != nil { + log.Error().Msg("Message decryption failed") + } + err = json.Unmarshal(decrypted, &msg) + if err != nil { + log.Error().Msg("Message encryption failed") + } + return msg +} + +func CreateMtrkChainServerMessage([]Server, []PackedUserMessage) *PackedServerMessage { + var msg PackedServerMessage + return &msg +} + +func (msg *ServerMessage) Parse() { + var pck PackedServerMessage + + jsonMsg, _ := json.Marshal(msg) + armor, err := Encrypt(msg.ServerPubKey, jsonMsg) + if err != nil { + log.Error().Msg("Message encryption failed") + } + pck.Payload = []byte(armor) +} diff --git a/usermessage.go b/usermessage.go new file mode 100644 index 0000000..f5c18da --- /dev/null +++ b/usermessage.go @@ -0,0 +1,60 @@ +package meowlib + +import ( + "encoding/json" + "strings" + "time" + + "github.com/google/uuid" + "github.com/rs/zerolog/log" +) + +type InternalMessage struct { + With Peer `json:"with,omitempty"` + SentByMe bool `json:"sent_by_me,omitempty"` + MessageData UserMessage `json:"message_data,omitempty"` + ServerUuid string `json:"server_uuid,omitempty"` + Received time.Time `json:"received,omitempty"` + Processed time.Time `json:"processed,omitempty"` + TransferPath []Server `json:"transfer_path,omitempty"` +} + +func CreateText(Destination Peer, text string) *InternalMessage { + var msg InternalMessage + msg.With = Destination + msg.SentByMe = true + msg.MessageData.From = Destination.Me.Public + msg.MessageData.Destination = Destination.Contact.ContactPublicKey + msg.MessageData.Status.Sent = uint64(time.Now().Unix()) + msg.MessageData.Status.LocalUuid = strings.Replace(uuid.New().String(), "-", "", -1) + msg.MessageData.Type = "t" + msg.MessageData.Data = []byte(text) + return &msg +} + +func (msg *UserMessage) Pack() *PackedUserMessage { + var pck PackedUserMessage + + jsonMsg, _ := json.Marshal(msg) + armor, err := Encrypt(msg.Destination, jsonMsg) + if err != nil { + log.Error().Msg("Message encryption failed") + } + pck.Destination = msg.Destination + pck.Payload = []byte(armor) + return &pck +} + +func (pck *PackedUserMessage) Unpack(privateKey string) *UserMessage { + var msg *UserMessage + + decrypted, err := Decrypt(privateKey, pck.Payload) + if err != nil { + log.Error().Msg("Message decryption failed") + } + err = json.Unmarshal(decrypted, &msg) + if err != nil { + log.Error().Msg("Message encryption failed") + } + return msg +} diff --git a/usermessage_test.go b/usermessage_test.go new file mode 100644 index 0000000..74160c4 --- /dev/null +++ b/usermessage_test.go @@ -0,0 +1,37 @@ +package meowlib + +import ( + "fmt" + "log" + "testing" +) + +func TestCreateText(t *testing.T) { + kp := NewKeyPair() + fmt.Println(kp.Public) + fmt.Println(kp.Private) +} + +func TestPack(t *testing.T) { + kp := NewKeyPair() + // fmt.Println(kp.Public) + // fmt.Println(kp.Private) + key := kp.GetCryptoKeyObject() + // fmt.Println(key.Armor()) + pubkey, _ := key.GetArmoredPublicKey() + if kp.Public != pubkey { + log.Fatal("error in public key") + } +} + +func TestUnPack(t *testing.T) { + kp := NewKeyPair() + // fmt.Println(kp.Public) + // fmt.Println(kp.Private) + key := kp.GetCryptoKeyObject() + // fmt.Println(key.Armor()) + pubkey, _ := key.GetArmoredPublicKey() + if kp.Public != pubkey { + log.Fatal("error in public key") + } +}