Complete refactor using protobuff

This commit is contained in:
ycc 2022-01-15 22:19:29 +01:00
parent 60b14db80c
commit c07cdff3de
29 changed files with 1088 additions and 131 deletions

View File

@ -1,2 +0,0 @@
# meowlib

106
asymcrypt.go Normal file
View File

@ -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
}

View File

@ -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")
}

10
clean.sh Executable file
View File

@ -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

15
doc/archi01.puml Normal file
View File

@ -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

15
doc/archi02.puml Normal file
View File

@ -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

39
doc/archi03.puml Normal file
View File

@ -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

0
doc/architecture.tex Normal file
View File

83
doc/class_messages01.puml Normal file
View File

@ -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

193
doc/protocol.tex Normal file
View File

@ -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}

8
doc/sq_msg01.puml Normal file
View File

@ -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

70
endtoend_test.go Normal file
View File

@ -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()
}

5
go.mod
View File

@ -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
)

20
go.sum
View File

@ -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=

50
https.go Normal file
View File

@ -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 {
}
}

View File

@ -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
}

30
identity_test.go Normal file
View File

@ -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")
}
}

42
keys.go
View File

@ -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
}

View File

@ -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
}

9
network.go Normal file
View File

@ -0,0 +1,9 @@
package meowlib
type Network interface {
Configure(url string)
Send(msg []byte)
Receive(request []byte) []byte
Start()
Stop()
}

90
pb/messages.proto Normal file
View File

@ -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<string,ConversationResponse> 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;
}

4
pb/protogen.sh Executable file
View File

@ -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

59
peer.go
View File

@ -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)
}

10
peer_test.go Normal file
View File

@ -0,0 +1,10 @@
package meowlib
import (
"testing"
)
func TestGetFromPublicKey(t *testing.T) {
id := CreateIdentity("test")
id.Save("test.id")
}

36
proto_test.go Normal file
View File

@ -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.")
}

View File

@ -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))
}
}

54
servermessage.go Normal file
View File

@ -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)
}

60
usermessage.go Normal file
View File

@ -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
}

37
usermessage_test.go Normal file
View File

@ -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")
}
}