2018-03-09 20:44:13 +00:00
package model
import (
"crypto/rand"
2018-05-28 18:05:06 +00:00
"cwtch.im/cwtch/protocol"
2018-10-05 03:18:34 +00:00
"encoding/base32"
2019-01-29 20:56:59 +00:00
"encoding/hex"
2019-01-21 20:11:40 +00:00
"encoding/json"
2018-05-09 19:09:00 +00:00
"errors"
2018-06-23 16:15:36 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
2018-06-29 19:20:07 +00:00
"github.com/golang/protobuf/proto"
2018-03-09 20:44:13 +00:00
"golang.org/x/crypto/ed25519"
2018-05-28 17:44:47 +00:00
"io"
2019-01-29 20:56:59 +00:00
"path/filepath"
2018-10-05 03:18:34 +00:00
"strings"
2018-05-30 17:41:02 +00:00
"sync"
2018-03-30 21:16:51 +00:00
"time"
2018-03-09 20:44:13 +00:00
)
2018-03-15 16:33:26 +00:00
// PublicProfile is a local copy of a CwtchIdentity
2018-03-09 20:44:13 +00:00
type PublicProfile struct {
2019-10-18 23:56:10 +00:00
Name string
Ed25519PublicKey ed25519 . PublicKey
Trusted bool
Blocked bool
Onion string
Attributes map [ string ] string
Timeline Timeline ` json:"-" `
LocalID string // used by storage engine
State string ` json:"-" `
lock sync . Mutex
unacknowledgedMessages map [ string ] Message
2018-03-09 20:44:13 +00:00
}
2018-03-15 16:33:26 +00:00
// Profile encapsulates all the attributes necessary to be a Cwtch Peer.
2018-03-09 20:44:13 +00:00
type Profile struct {
PublicProfile
2018-05-03 19:23:02 +00:00
Contacts map [ string ] * PublicProfile
2018-03-09 20:44:13 +00:00
Ed25519PrivateKey ed25519 . PrivateKey
Groups map [ string ] * Group
}
2018-11-28 19:50:32 +00:00
// MaxGroupMessageLength is the maximum length of a message posted to a server group.
// TODO: Should this be per server?
2019-01-19 21:09:38 +00:00
const MaxGroupMessageLength = 1800
2018-11-28 19:50:32 +00:00
2019-01-29 20:56:59 +00:00
func generateRandomID ( ) string {
randBytes := make ( [ ] byte , 16 )
rand . Read ( randBytes )
return filepath . Join ( hex . EncodeToString ( randBytes ) )
}
2018-10-31 23:24:12 +00:00
func ( p * PublicProfile ) init ( ) {
2019-02-11 21:40:20 +00:00
if p . Attributes == nil {
p . Attributes = make ( map [ string ] string )
}
2019-10-18 23:56:10 +00:00
p . unacknowledgedMessages = make ( map [ string ] Message )
2019-01-29 20:56:59 +00:00
p . LocalID = generateRandomID ( )
2018-10-31 23:24:12 +00:00
}
// SetAttribute allows applications to store arbitrary configuration info at the profile level.
func ( p * PublicProfile ) SetAttribute ( name string , value string ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
p . Attributes [ name ] = value
}
// GetAttribute returns the value of a value set with SetCustomAttribute. If no such value has been set exists is set to false.
func ( p * PublicProfile ) GetAttribute ( name string ) ( value string , exists bool ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
value , exists = p . Attributes [ name ]
return
}
2018-03-15 16:33:26 +00:00
// GenerateNewProfile creates a new profile, with new encryption and signing keys, and a profile name.
2018-03-09 20:44:13 +00:00
func GenerateNewProfile ( name string ) * Profile {
p := new ( Profile )
2019-01-21 20:11:40 +00:00
p . init ( )
2018-03-09 20:44:13 +00:00
p . Name = name
pub , priv , _ := ed25519 . GenerateKey ( rand . Reader )
p . Ed25519PublicKey = pub
p . Ed25519PrivateKey = priv
2018-10-05 03:18:34 +00:00
p . Onion = utils . GetTorV3Hostname ( pub )
2018-03-09 20:44:13 +00:00
2018-05-03 19:23:02 +00:00
p . Contacts = make ( map [ string ] * PublicProfile )
2018-05-09 19:09:00 +00:00
p . Contacts [ p . Onion ] = & p . PublicProfile
2018-03-09 20:44:13 +00:00
p . Groups = make ( map [ string ] * Group )
return p
}
// AddContact allows direct manipulation of cwtch contacts
2018-05-03 19:23:02 +00:00
func ( p * Profile ) AddContact ( onion string , profile * PublicProfile ) {
2018-05-30 17:41:02 +00:00
p . lock . Lock ( )
2018-10-31 23:24:12 +00:00
profile . init ( )
// TODO: More Robust V3 Onion Handling
decodedPub , _ := base32 . StdEncoding . DecodeString ( strings . ToUpper ( onion [ : 56 ] ) )
profile . Ed25519PublicKey = ed25519 . PublicKey ( decodedPub [ : 32 ] )
2018-03-09 20:44:13 +00:00
p . Contacts [ onion ] = profile
2018-05-30 17:41:02 +00:00
p . lock . Unlock ( )
}
2019-08-07 05:27:11 +00:00
// DeleteContact deletes a peer contact
func ( p * Profile ) DeleteContact ( onion string ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
delete ( p . Contacts , onion )
}
// DeleteGroup deletes a group
func ( p * Profile ) DeleteGroup ( groupID string ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
delete ( p . Groups , groupID )
}
2018-05-30 17:41:02 +00:00
// RejectInvite rejects and removes a group invite
func ( p * Profile ) RejectInvite ( groupID string ) {
p . lock . Lock ( )
delete ( p . Groups , groupID )
p . lock . Unlock ( )
}
2019-10-18 23:56:10 +00:00
// AddSentMessageToContactTimeline allows the saving of a message sent via a direct connection chat to the profile.
func ( p * Profile ) AddSentMessageToContactTimeline ( onion string , messageTxt string , sent time . Time , eventID string ) * Message {
p . lock . Lock ( )
defer p . lock . Unlock ( )
contact , ok := p . Contacts [ onion ]
if ok {
now := time . Now ( )
sig := p . SignMessage ( onion + messageTxt + sent . String ( ) + now . String ( ) )
message := & Message { PeerID : p . Onion , Message : messageTxt , Timestamp : sent , Received : now , Signature : sig , Acknowledged : false }
if contact . unacknowledgedMessages == nil {
contact . unacknowledgedMessages = make ( map [ string ] Message )
}
contact . unacknowledgedMessages [ eventID ] = * message
return message
}
return nil
}
2018-11-21 21:23:59 +00:00
// AddMessageToContactTimeline allows the saving of a message sent via a direct connection chat to the profile.
2019-10-18 23:56:10 +00:00
func ( p * Profile ) AddMessageToContactTimeline ( onion string , messageTxt string , sent time . Time ) ( message * Message ) {
2018-11-21 21:23:59 +00:00
p . lock . Lock ( )
defer p . lock . Unlock ( )
contact , ok := p . Contacts [ onion ]
// We don't really need a Signature here, but we use it to maintain order
now := time . Now ( )
2019-10-18 23:56:10 +00:00
sig := p . SignMessage ( onion + messageTxt + sent . String ( ) + now . String ( ) )
if ok {
message = & Message { PeerID : onion , Message : messageTxt , Timestamp : sent , Received : now , Signature : sig , Acknowledged : true }
contact . Timeline . Insert ( message )
}
return
}
// ErrorSentMessageToPeer sets a sent message's error message and removes it from the unacknowledged list
func ( p * Profile ) ErrorSentMessageToPeer ( onion string , eventID string , error string ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
contact , ok := p . Contacts [ onion ]
2018-11-21 21:23:59 +00:00
if ok {
2019-10-18 23:56:10 +00:00
message , ok := contact . unacknowledgedMessages [ eventID ]
if ok {
message . Error = error
contact . Timeline . Insert ( & message ) // TODO: do we want a non timeline.Insert way to handle errors
delete ( contact . unacknowledgedMessages , eventID )
}
}
}
// AckSentMessageToPeer sets mesage to a peer as acknowledged
func ( p * Profile ) AckSentMessageToPeer ( onion string , eventID string ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
contact , ok := p . Contacts [ onion ]
if ok {
message , ok := contact . unacknowledgedMessages [ eventID ]
if ok {
message . Acknowledged = true
contact . Timeline . Insert ( & message )
delete ( contact . unacknowledgedMessages , eventID )
}
}
}
// AddGroupSentMessageError searches matching groups for the message by sig and marks it as an error
func ( p * Profile ) AddGroupSentMessageError ( groupServer string , signature string , error string ) {
for _ , group := range p . Groups {
if group . GroupServer == groupServer {
if group . ErrorSentMessage ( [ ] byte ( signature ) , error ) {
break
}
2018-11-21 21:23:59 +00:00
}
}
}
2018-05-30 17:41:02 +00:00
// AcceptInvite accepts a group invite
2018-06-03 19:02:42 +00:00
func ( p * Profile ) AcceptInvite ( groupID string ) ( err error ) {
2018-05-30 17:41:02 +00:00
p . lock . Lock ( )
2018-06-03 19:02:42 +00:00
defer p . lock . Unlock ( )
2018-05-30 17:41:02 +00:00
group , ok := p . Groups [ groupID ]
if ok {
group . Accepted = true
2018-06-03 19:02:42 +00:00
} else {
err = errors . New ( "group does not exist" )
2018-05-30 17:41:02 +00:00
}
2018-06-03 19:02:42 +00:00
return
2018-05-30 17:41:02 +00:00
}
2018-06-19 22:28:44 +00:00
// GetGroups returns an unordered list of group IDs associated with this profile.
func ( p * Profile ) GetGroups ( ) [ ] string {
p . lock . Lock ( )
defer p . lock . Unlock ( )
var keys [ ] string
for onion := range p . Groups {
keys = append ( keys , onion )
}
return keys
}
// GetContacts returns an unordered list of contact onions associated with this profile.
func ( p * Profile ) GetContacts ( ) [ ] string {
p . lock . Lock ( )
defer p . lock . Unlock ( )
var keys [ ] string
for onion := range p . Contacts {
if onion != p . Onion {
keys = append ( keys , onion )
}
}
return keys
}
2018-05-30 17:41:02 +00:00
// BlockPeer blocks a contact
2018-06-03 19:02:42 +00:00
func ( p * Profile ) BlockPeer ( onion string ) ( err error ) {
2018-05-30 17:41:02 +00:00
p . lock . Lock ( )
2018-06-03 19:02:42 +00:00
defer p . lock . Unlock ( )
2018-05-30 17:41:02 +00:00
contact , ok := p . Contacts [ onion ]
if ok {
contact . Blocked = true
2018-06-03 19:02:42 +00:00
} else {
err = errors . New ( "peer does not exist" )
2018-05-30 17:41:02 +00:00
}
2018-06-03 19:02:42 +00:00
return
2018-05-30 17:41:02 +00:00
}
2019-08-07 18:49:44 +00:00
// UnblockPeer unblocks a contact
func ( p * Profile ) UnblockPeer ( onion string ) ( err error ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
contact , ok := p . Contacts [ onion ]
if ok {
contact . Blocked = false
} else {
err = errors . New ( "peer does not exist" )
}
return
}
2019-05-15 20:12:11 +00:00
// BlockedPeers calculates a list of Peers who have been Blocked.
func ( p * Profile ) BlockedPeers ( ) [ ] string {
blockedPeers := [ ] string { }
for _ , contact := range p . GetContacts ( ) {
c , _ := p . GetContact ( contact )
if c . Blocked {
blockedPeers = append ( blockedPeers , c . Onion )
}
}
return blockedPeers
}
2018-05-30 17:41:02 +00:00
// TrustPeer sets a contact to trusted
2018-06-03 19:02:42 +00:00
func ( p * Profile ) TrustPeer ( onion string ) ( err error ) {
2018-05-30 17:41:02 +00:00
p . lock . Lock ( )
2018-06-03 19:02:42 +00:00
defer p . lock . Unlock ( )
2018-05-30 17:41:02 +00:00
contact , ok := p . Contacts [ onion ]
if ok {
contact . Trusted = true
2018-06-03 19:02:42 +00:00
} else {
err = errors . New ( "peer does not exist" )
2018-05-30 17:41:02 +00:00
}
2018-06-03 19:02:42 +00:00
return
2018-05-30 17:41:02 +00:00
}
// IsBlocked returns true if the contact has been blocked, false otherwise
func ( p * Profile ) IsBlocked ( onion string ) bool {
contact , ok := p . GetContact ( onion )
if ok {
return contact . Blocked
}
return false
}
2018-06-15 16:21:07 +00:00
// GetContact returns a contact if the profile has it
2018-05-30 17:41:02 +00:00
func ( p * Profile ) GetContact ( onion string ) ( * PublicProfile , bool ) {
p . lock . Lock ( )
2018-06-03 19:02:42 +00:00
defer p . lock . Unlock ( )
2018-05-30 17:41:02 +00:00
contact , ok := p . Contacts [ onion ]
return contact , ok
2018-03-09 20:44:13 +00:00
}
2018-05-16 20:53:09 +00:00
// VerifyGroupMessage confirms the authenticity of a message given an onion, message and signature.
2018-06-22 18:11:23 +00:00
func ( p * Profile ) VerifyGroupMessage ( onion string , groupID string , message string , timestamp int32 , ciphertext [ ] byte , signature [ ] byte ) bool {
2019-10-31 21:39:31 +00:00
group := p . GetGroup ( groupID )
2018-06-22 18:11:23 +00:00
if group == nil {
return false
}
2018-05-03 04:12:45 +00:00
if onion == p . Onion {
2018-06-22 18:11:23 +00:00
m := groupID + group . GroupServer + string ( ciphertext )
2018-05-03 04:12:45 +00:00
return ed25519 . Verify ( p . Ed25519PublicKey , [ ] byte ( m ) , signature )
}
2018-10-05 03:18:34 +00:00
m := groupID + group . GroupServer + string ( ciphertext )
decodedPub , err := base32 . StdEncoding . DecodeString ( strings . ToUpper ( onion ) )
2019-11-08 00:39:27 +00:00
if err == nil && len ( decodedPub ) >= 32 {
2018-10-05 03:18:34 +00:00
return ed25519 . Verify ( decodedPub [ : 32 ] , [ ] byte ( m ) , signature )
2018-03-31 19:33:32 +00:00
}
return false
2018-03-30 21:16:51 +00:00
}
2018-03-15 16:33:26 +00:00
// SignMessage takes a given message and returns an Ed21159 signature
2018-03-09 20:44:13 +00:00
func ( p * Profile ) SignMessage ( message string ) [ ] byte {
sig := ed25519 . Sign ( p . Ed25519PrivateKey , [ ] byte ( message ) )
return sig
}
2018-10-05 03:18:34 +00:00
// StartGroup when given a server, creates a new Group under this profile and returns the group id an a precomputed
2018-03-15 16:33:26 +00:00
// invite which can be sent on the wire.
2018-05-16 20:18:47 +00:00
func ( p * Profile ) StartGroup ( server string ) ( groupID string , invite [ ] byte , err error ) {
2018-09-21 18:02:46 +00:00
return p . StartGroupWithMessage ( server , [ ] byte { } )
}
2018-10-05 03:18:34 +00:00
// StartGroupWithMessage when given a server, and an initial message creates a new Group under this profile and returns the group id an a precomputed
2018-09-21 18:02:46 +00:00
// invite which can be sent on the wire.
func ( p * Profile ) StartGroupWithMessage ( server string , initialMessage [ ] byte ) ( groupID string , invite [ ] byte , err error ) {
2018-09-27 00:08:54 +00:00
group , err := NewGroup ( server )
if err != nil {
return "" , nil , err
}
2018-03-09 20:44:13 +00:00
groupID = group . GroupID
2019-01-19 21:09:38 +00:00
group . Owner = p . Onion
2018-05-16 20:53:09 +00:00
signedGroupID := p . SignMessage ( groupID + server )
group . SignGroup ( signedGroupID )
2018-09-21 18:02:46 +00:00
invite , err = group . Invite ( initialMessage )
2018-05-30 17:41:02 +00:00
p . lock . Lock ( )
2018-06-03 19:02:42 +00:00
defer p . lock . Unlock ( )
2018-05-09 19:09:00 +00:00
p . Groups [ group . GroupID ] = group
2018-03-09 20:44:13 +00:00
return
}
2019-10-31 21:39:31 +00:00
// GetGroup a pointer to a Group by the group Id, returns nil if no group found.
func ( p * Profile ) GetGroup ( groupID string ) ( g * Group ) {
2018-05-30 17:41:02 +00:00
p . lock . Lock ( )
2018-06-03 19:02:42 +00:00
defer p . lock . Unlock ( )
2018-05-30 17:41:02 +00:00
g = p . Groups [ groupID ]
2018-06-03 19:02:42 +00:00
return
2018-03-15 20:53:22 +00:00
}
2019-09-19 23:14:35 +00:00
// ProcessInvite adds a new group invite to the profile. returns the new group ID
func ( p * Profile ) ProcessInvite ( invite string , peerHostname string ) ( string , error ) {
var gci protocol . GroupChatInvite
err := proto . Unmarshal ( [ ] byte ( invite ) , & gci )
if err == nil {
group := new ( Group )
group . GroupID = gci . GetGroupName ( )
group . LocalID = generateRandomID ( )
group . SignedGroupID = gci . GetSignedGroupId ( )
copy ( group . GroupKey [ : ] , gci . GetGroupSharedKey ( ) [ : ] )
group . GroupServer = gci . GetServerHost ( )
group . InitialMessage = gci . GetInitialMessage ( ) [ : ]
group . Accepted = false
group . Owner = peerHostname
group . Attributes = make ( map [ string ] string )
p . AddGroup ( group )
return group . GroupID , nil
}
return "" , err
2018-03-09 20:44:13 +00:00
}
2018-03-31 19:33:32 +00:00
// AddGroup is a convenience method for adding a group to a profile.
2018-03-09 20:44:13 +00:00
func ( p * Profile ) AddGroup ( group * Group ) {
2019-01-19 23:16:38 +00:00
_ , exists := p . Groups [ group . GroupID ]
2018-03-15 20:53:22 +00:00
if ! exists {
2018-05-30 17:41:02 +00:00
p . lock . Lock ( )
2018-06-03 19:02:42 +00:00
defer p . lock . Unlock ( )
2018-03-15 20:53:22 +00:00
p . Groups [ group . GroupID ] = group
}
2018-03-09 20:44:13 +00:00
}
2018-03-15 16:33:26 +00:00
// AttemptDecryption takes a ciphertext and signature and attempts to decrypt it under known groups.
2019-10-18 23:56:10 +00:00
// If successful, adds the message to the group's timeline
2019-02-03 03:24:42 +00:00
func ( p * Profile ) AttemptDecryption ( ciphertext [ ] byte , signature [ ] byte ) ( bool , string , * Message , bool ) {
2018-03-15 20:53:22 +00:00
for _ , group := range p . Groups {
success , dgm := group . DecryptMessage ( ciphertext )
2018-03-09 20:44:13 +00:00
if success {
2018-06-22 18:11:23 +00:00
verified := p . VerifyGroupMessage ( dgm . GetOnion ( ) , group . GroupID , dgm . GetText ( ) , dgm . GetTimestamp ( ) , ciphertext , signature )
2018-10-05 03:18:34 +00:00
// So we have a message that has a valid group key, but the signature can't be verified.
// The most obvious explanation for this is that the group key has been compromised (or we are in an open group and the server is being malicious)
// Either way, someone who has the private key is being detectably bad so we are just going to throw this message away and mark the group as Compromised.
if ! verified {
group . Compromised ( )
2019-02-03 03:24:42 +00:00
return false , group . GroupID , nil , false
2018-10-05 03:18:34 +00:00
}
2019-02-03 03:24:42 +00:00
message , seen := group . AddMessage ( dgm , signature )
return true , group . GroupID , message , seen
2018-03-09 20:44:13 +00:00
}
}
2019-01-19 21:09:38 +00:00
// If we couldn't find a group to decrypt the message with we just return false. This is an expected case
2019-02-03 03:24:42 +00:00
return false , "" , nil , false
2018-03-09 20:44:13 +00:00
}
2018-05-28 17:44:47 +00:00
func getRandomness ( arr * [ ] byte ) {
if _ , err := io . ReadFull ( rand . Reader , ( * arr ) [ : ] ) ; err != nil {
utils . CheckError ( err )
}
}
2018-03-15 16:33:26 +00:00
// EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and
// profile
2018-06-22 18:11:23 +00:00
func ( p * Profile ) EncryptMessageToGroup ( message string , groupID string ) ( [ ] byte , [ ] byte , error ) {
2018-11-28 19:50:32 +00:00
if len ( message ) > MaxGroupMessageLength {
return nil , nil , errors . New ( "group message is too long" )
}
2019-10-31 21:39:31 +00:00
group := p . GetGroup ( groupID )
2018-05-09 19:09:00 +00:00
if group != nil {
timestamp := time . Now ( ) . Unix ( )
2018-06-22 18:11:23 +00:00
2018-05-09 19:09:00 +00:00
var prevSig [ ] byte
2019-02-03 01:18:33 +00:00
if len ( group . Timeline . Messages ) > 0 {
prevSig = group . Timeline . Messages [ len ( group . Timeline . Messages ) - 1 ] . Signature
2018-05-09 19:09:00 +00:00
} else {
2018-05-28 17:44:47 +00:00
prevSig = group . SignedGroupID
2018-05-09 19:09:00 +00:00
}
2018-05-28 17:44:47 +00:00
2018-11-28 19:50:32 +00:00
lenPadding := MaxGroupMessageLength - len ( message )
2018-05-28 17:44:47 +00:00
padding := make ( [ ] byte , lenPadding )
getRandomness ( & padding )
2018-05-09 19:09:00 +00:00
dm := & protocol . DecryptedGroupMessage {
Onion : proto . String ( p . Onion ) ,
Text : proto . String ( message ) ,
SignedGroupId : group . SignedGroupID [ : ] ,
Timestamp : proto . Int32 ( int32 ( timestamp ) ) ,
PreviousMessageSig : prevSig ,
2018-05-28 17:44:47 +00:00
Padding : padding [ : ] ,
2018-05-09 19:09:00 +00:00
}
2019-11-08 00:39:27 +00:00
2018-09-27 00:08:54 +00:00
ciphertext , err := group . EncryptMessage ( dm )
if err != nil {
return nil , nil , err
}
2018-06-22 18:11:23 +00:00
signature := p . SignMessage ( groupID + group . GroupServer + string ( ciphertext ) )
2019-02-20 20:03:04 +00:00
group . AddSentMessage ( dm , signature )
2018-06-22 18:11:23 +00:00
return ciphertext , signature , nil
2018-03-15 20:53:22 +00:00
}
2018-06-22 18:11:23 +00:00
return nil , nil , errors . New ( "group does not exist" )
2018-03-09 20:44:13 +00:00
}
2019-01-21 20:11:40 +00:00
2019-07-19 17:27:50 +00:00
// GetCopy returns a full deep copy of the Profile struct and its members (timeline inclusion control by arg)
func ( p * Profile ) GetCopy ( timeline bool ) * Profile {
2019-01-21 20:11:40 +00:00
p . lock . Lock ( )
defer p . lock . Unlock ( )
newp := new ( Profile )
bytes , _ := json . Marshal ( p )
json . Unmarshal ( bytes , & newp )
2019-07-19 17:27:50 +00:00
if timeline {
for groupID := range newp . Groups {
newp . Groups [ groupID ] . Timeline = * p . Groups [ groupID ] . Timeline . GetCopy ( )
}
}
2019-01-21 20:11:40 +00:00
return newp
}