2018-03-09 20:44:13 +00:00
|
|
|
package model
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
2018-03-30 21:16:51 +00:00
|
|
|
"encoding/asn1"
|
2018-03-09 20:44:13 +00:00
|
|
|
"encoding/json"
|
|
|
|
"git.mascherari.press/cwtch/protocol"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
"github.com/s-rah/go-ricochet/utils"
|
|
|
|
"golang.org/x/crypto/ed25519"
|
|
|
|
"io/ioutil"
|
2018-04-02 21:10:29 +00:00
|
|
|
// "log"
|
2018-03-15 20:53:22 +00:00
|
|
|
"strconv"
|
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-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
|
|
|
|
Contacts map[string]PublicProfile
|
|
|
|
Ed25519PrivateKey ed25519.PrivateKey
|
|
|
|
OnionPrivateKey *rsa.PrivateKey
|
|
|
|
Groups map[string]*Group
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
|
|
|
p.OnionPrivateKey, _ = utils.GeneratePrivateKey()
|
2018-03-15 20:53:22 +00:00
|
|
|
// DER Encode the Public Key
|
|
|
|
publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
|
|
|
|
N: p.OnionPrivateKey.PublicKey.N,
|
|
|
|
E: p.OnionPrivateKey.PublicKey.E,
|
|
|
|
})
|
|
|
|
p.Onion = utils.GetTorHostname(publicKeyBytes)
|
2018-03-09 20:44:13 +00:00
|
|
|
|
|
|
|
p.Contacts = make(map[string]PublicProfile)
|
|
|
|
p.Groups = make(map[string]*Group)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddCwtchIdentity takes a wire message and if it is a CwtchIdentity message adds the identity as a contact
|
|
|
|
// otherwise returns an error
|
2018-03-12 18:43:51 +00:00
|
|
|
func (p *Profile) AddCwtchIdentity(onion string, ci *protocol.CwtchIdentity) {
|
2018-05-01 20:44:45 +00:00
|
|
|
p.AddContact(onion, PublicProfile{Name: ci.GetName(), Ed25519PublicKey: ci.GetEd25519PublicKey(), Onion: onion})
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddContact allows direct manipulation of cwtch contacts
|
|
|
|
func (p *Profile) AddContact(onion string, profile PublicProfile) {
|
|
|
|
p.Contacts[onion] = profile
|
|
|
|
}
|
|
|
|
|
2018-03-30 21:16:51 +00:00
|
|
|
// VerifyMessage confirms the authenticity of a message given an onion, message and signature.
|
|
|
|
func (p *Profile) VerifyGroupMessage(onion string, groupID string, message string, timestamp int32, signature []byte) bool {
|
2018-05-03 04:12:45 +00:00
|
|
|
|
|
|
|
if onion == p.Onion {
|
|
|
|
m := message + groupID + strconv.Itoa(int(timestamp))
|
|
|
|
return ed25519.Verify(p.Ed25519PublicKey, []byte(m), signature)
|
|
|
|
}
|
|
|
|
|
2018-03-31 19:33:32 +00:00
|
|
|
contact, found := p.Contacts[onion]
|
|
|
|
if found {
|
|
|
|
m := message + groupID + strconv.Itoa(int(timestamp))
|
|
|
|
return ed25519.Verify(contact.Ed25519PublicKey, []byte(m), signature)
|
|
|
|
}
|
|
|
|
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-03-15 16:33:26 +00:00
|
|
|
//StartGroup when given a server, creates a new Group under this profile and returns the group id an a precomputed
|
|
|
|
// invite which can be sent on the wire.
|
2018-03-09 20:44:13 +00:00
|
|
|
func (p *Profile) StartGroup(server string) (groupID string, invite []byte) {
|
|
|
|
group := NewGroup(server)
|
|
|
|
groupID = group.GroupID
|
2018-03-30 21:16:51 +00:00
|
|
|
signedGroupId := p.SignMessage(groupID)
|
|
|
|
group.SignGroup(signedGroupId)
|
2018-03-15 20:53:22 +00:00
|
|
|
invite = group.Invite()
|
2018-03-30 21:16:51 +00:00
|
|
|
p.AddGroup(group)
|
2018-03-09 20:44:13 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-03-30 21:16:51 +00:00
|
|
|
func (p *Profile) GetGroupByGroupId(groupID string) *Group {
|
2018-03-15 20:53:22 +00:00
|
|
|
return p.Groups[groupID]
|
|
|
|
}
|
|
|
|
|
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-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-04-02 21:10:29 +00:00
|
|
|
// owned := ed25519.Verify(p.Contacts[group.Owner].Ed25519PublicKey,[]byte(group.GroupID),group.SignedGroupID)
|
2018-03-15 20:53:22 +00:00
|
|
|
p.Groups[group.GroupID] = group
|
2018-04-02 21:10:29 +00:00
|
|
|
} else if exists && existingGroup.Owner == group.Owner {
|
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.
|
|
|
|
|
|
|
|
// FIXME: This opens up an attack vector!!
|
|
|
|
|
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-03-31 19:33:32 +00:00
|
|
|
func (p *Profile) AttemptDecryption(ciphertext []byte) (bool, *Message) {
|
2018-03-15 20:53:22 +00:00
|
|
|
for _, group := range p.Groups {
|
|
|
|
success, dgm := group.DecryptMessage(ciphertext)
|
2018-04-02 21:10:29 +00:00
|
|
|
//log.Printf("Decrypt Attempt %v %v", success, dgm)
|
2018-03-09 20:44:13 +00:00
|
|
|
if success {
|
2018-03-30 21:16:51 +00:00
|
|
|
verified := p.VerifyGroupMessage(dgm.GetOnion(), group.GroupID, dgm.GetText(), dgm.GetTimestamp(), dgm.GetSignature())
|
2018-03-31 19:33:32 +00:00
|
|
|
return true, group.AddMessage(dgm, verified)
|
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-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-03-31 19:33:32 +00:00
|
|
|
func (p *Profile) EncryptMessageToGroup(message string, groupID string) (ciphertext []byte) {
|
2018-03-15 20:53:22 +00:00
|
|
|
group := p.Groups[groupID]
|
|
|
|
timestamp := time.Now().Unix()
|
2018-03-31 19:33:32 +00:00
|
|
|
signature := p.SignMessage(message + groupID + strconv.Itoa(int(timestamp)))
|
2018-04-02 21:10:29 +00:00
|
|
|
var prevSig []byte
|
|
|
|
if len(group.Timeline.Messages) > 0 {
|
|
|
|
prevSig = group.Timeline.Messages[len(group.Timeline.Messages)-1].Signature
|
|
|
|
} else {
|
|
|
|
prevSig = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
|
|
|
}
|
2018-03-30 21:16:51 +00:00
|
|
|
dm := &protocol.DecryptedGroupMessage{
|
2018-04-02 21:10:29 +00:00
|
|
|
Onion: proto.String(p.Onion),
|
|
|
|
Text: proto.String(message),
|
|
|
|
SignedGroupId: group.SignedGroupID[:],
|
|
|
|
Timestamp: proto.Int32(int32(timestamp)),
|
|
|
|
Signature: signature,
|
|
|
|
PreviousMessageSig: prevSig,
|
2018-03-15 20:53:22 +00:00
|
|
|
}
|
|
|
|
ciphertext = group.EncryptMessage(dm)
|
2018-03-09 20:44:13 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-03-15 16:33:26 +00:00
|
|
|
// Save makes a opy of the profile in the given file
|
2018-03-09 20:44:13 +00:00
|
|
|
func (p *Profile) Save(profilefile string) error {
|
|
|
|
bytes, _ := json.Marshal(p)
|
|
|
|
return ioutil.WriteFile(profilefile, bytes, 0600)
|
|
|
|
}
|
|
|
|
|
2018-03-15 16:33:26 +00:00
|
|
|
// LoadProfile loads a saved profile from a file.
|
2018-03-09 20:44:13 +00:00
|
|
|
func LoadProfile(profilefile string) (*Profile, error) {
|
|
|
|
bytes, _ := ioutil.ReadFile(profilefile)
|
|
|
|
profile := new(Profile)
|
|
|
|
err := json.Unmarshal(bytes, &profile)
|
|
|
|
return profile, err
|
|
|
|
}
|