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"
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"
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 {
Name string
Ed25519PublicKey ed25519 . PublicKey
2018-03-30 21:16:51 +00:00
Trusted bool
Blocked bool
2018-05-01 20:44:45 +00:00
Onion string
2018-10-31 23:24:12 +00:00
Attributes map [ string ] string
lock sync . Mutex
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-09-21 17:45:25 +00:00
Custom map [ string ] string
2018-05-30 17:41:02 +00:00
lock sync . Mutex
2018-03-09 20:44:13 +00:00
}
2018-10-31 23:24:12 +00:00
func ( p * PublicProfile ) init ( ) {
p . Attributes = make ( map [ string ] string )
}
// 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 )
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 )
2018-09-21 17:45:25 +00:00
p . Custom = make ( map [ string ] string )
2018-03-09 20:44:13 +00:00
return p
}
2018-03-15 16:33:26 +00:00
// GetCwtchIdentityPacket returns the wire message for conveying this profiles identity.
2018-03-09 20:44:13 +00:00
func ( p * Profile ) GetCwtchIdentityPacket ( ) ( message [ ] byte ) {
ci := & protocol . CwtchIdentity {
Name : p . Name ,
Ed25519PublicKey : p . Ed25519PublicKey ,
}
cpp := & protocol . CwtchPeerPacket {
CwtchIdentify : ci ,
}
message , err := proto . Marshal ( cpp )
utils . CheckError ( err )
return
}
// 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 ( )
}
// RejectInvite rejects and removes a group invite
func ( p * Profile ) RejectInvite ( groupID string ) {
p . lock . Lock ( )
delete ( p . Groups , groupID )
p . lock . Unlock ( )
}
// 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
}
2018-09-21 17:45:25 +00:00
// SetCustomAttribute allows applications to store arbitrary configuration info at the profile level.
func ( p * Profile ) SetCustomAttribute ( name string , value string ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
p . Custom [ name ] = value
}
// GetCustomAttribute returns the value of a value set with SetCustomAttribute. If no such value has been set exists is set to false.
func ( p * Profile ) GetCustomAttribute ( name string ) ( value string , exists bool ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
value , exists = p . Custom [ name ]
return
}
2018-06-19 22:28:44 +00:00
// 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
}
// 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 {
group := p . GetGroupByGroupID ( groupID )
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 ) )
if err == nil {
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
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
}
2018-05-16 20:53:09 +00:00
// GetGroupByGroupID a pointer to a Group by the group Id, returns nil if no group found.
2018-05-30 17:41:02 +00:00
func ( p * Profile ) GetGroupByGroupID ( groupID string ) ( g * Group ) {
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
}
2018-03-15 16:33:26 +00:00
// ProcessInvite adds a new group invite to the profile.
2018-03-15 20:53:22 +00:00
func ( p * Profile ) ProcessInvite ( gci * protocol . GroupChatInvite , peerHostname string ) {
2018-03-09 20:44:13 +00:00
group := new ( Group )
group . GroupID = gci . GetGroupName ( )
2018-03-30 21:16:51 +00:00
group . SignedGroupID = gci . GetSignedGroupId ( )
2018-03-09 20:44:13 +00:00
copy ( group . GroupKey [ : ] , gci . GetGroupSharedKey ( ) [ : ] )
group . GroupServer = gci . GetServerHost ( )
2018-09-21 18:02:46 +00:00
group . InitialMessage = gci . GetInitialMessage ( ) [ : ]
2018-03-15 20:53:22 +00:00
group . Accepted = false
group . Owner = peerHostname
2018-03-09 20:44:13 +00:00
p . AddGroup ( group )
}
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 ) {
2018-03-15 20:53:22 +00:00
existingGroup , exists := p . Groups [ group . GroupID ]
if ! exists {
2018-10-31 23:24:12 +00:00
// TODO More robust error handling (confirm this onion checksum is correct)
decodedPub , _ := base32 . StdEncoding . DecodeString ( strings . ToUpper ( group . Owner [ : 56 ] ) )
valid := ed25519 . Verify ( ed25519 . PublicKey ( decodedPub [ : 32 ] ) , [ ] byte ( group . GroupID + group . GroupServer ) , group . SignedGroupID )
if valid {
p . lock . Lock ( )
defer p . lock . Unlock ( )
p . Groups [ group . GroupID ] = group
2018-05-09 19:09:00 +00:00
}
2018-04-02 21:10:29 +00:00
} else if exists && existingGroup . Owner == group . Owner {
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
}
// If we are sent an invite or group update by someone who is not an owner
// then we reject the 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.
2018-06-22 18:11:23 +00:00
func ( p * Profile ) AttemptDecryption ( ciphertext [ ] byte , signature [ ] byte ) ( bool , * Message ) {
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-05-20 18:38:56 +00:00
2018-05-20 19:58:16 +00:00
// Assert that we know the owner of the group
2018-05-20 18:38:56 +00:00
owner , ok := p . Contacts [ group . Owner ]
if ok {
valid := ed25519 . Verify ( owner . Ed25519PublicKey , [ ] byte ( group . GroupID + group . GroupServer ) , dgm . SignedGroupId )
// If we can decrypt the message, but the group id is wrong that means that
// this message is from someone who was not invited to the group.
// As such this group has been compromised, probably by one of the other members.
// We set the flag to be handled by the UX and reject the message.
if ! valid {
group . Compromised ( )
return false , nil
}
}
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 ( )
return false , nil
}
return true , group . AddMessage ( dgm , signature )
2018-03-09 20:44:13 +00:00
}
}
2018-03-31 19:33:32 +00:00
return false , nil
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-05-30 17:41:02 +00:00
group := p . GetGroupByGroupID ( 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
if len ( group . Timeline . Messages ) > 0 {
prevSig = group . Timeline . Messages [ len ( group . Timeline . Messages ) - 1 ] . Signature
} 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
lenPadding := 1024 - len ( message )
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
}
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 ) )
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
}