Group Experiment: Do not require GroupID to be Secret
This commit is contained in:
parent
e099f1bf29
commit
026a00f171
|
@ -23,7 +23,7 @@ import (
|
|||
)
|
||||
|
||||
// CurrentGroupVersion is used to set the version of newly created groups and make sure group structs stored are correct and up to date
|
||||
const CurrentGroupVersion = 3
|
||||
const CurrentGroupVersion = 4
|
||||
|
||||
// GroupInvitePrefix identifies a particular string as being a serialized group invite.
|
||||
const GroupInvitePrefix = "torv3"
|
||||
|
@ -58,13 +58,6 @@ func NewGroup(server string) (*Group, error) {
|
|||
|
||||
group.GroupServer = server
|
||||
|
||||
var groupID [16]byte
|
||||
if _, err := io.ReadFull(rand.Reader, groupID[:]); err != nil {
|
||||
log.Errorf("Cannot read from random: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
group.GroupID = fmt.Sprintf("%x", groupID)
|
||||
|
||||
var groupKey [32]byte
|
||||
if _, err := io.ReadFull(rand.Reader, groupKey[:]); err != nil {
|
||||
log.Errorf("Error: Cannot read from random: %v\n", err)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"cwtch.im/cwtch/model/attr"
|
||||
"cwtch.im/cwtch/protocol/groups"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
@ -322,7 +323,7 @@ func (p *Profile) GetContact(onion string) (*PublicProfile, bool) {
|
|||
return contact, ok
|
||||
}
|
||||
|
||||
// VerifyGroupMessage confirms the authenticity of a message given an sender onion, ciphertext and signature.
|
||||
// VerifyGroupMessage confirms the authenticity of a message given an sender onion, message and signature.
|
||||
// The goal of this function is 2-fold:
|
||||
// 1. We confirm that the sender referenced in the group text is the actual sender of the message (or at least
|
||||
// knows the senders private key)
|
||||
|
@ -334,7 +335,7 @@ func (p *Profile) GetContact(onion string) (*PublicProfile, bool) {
|
|||
// on two different servers with the same key and then forwards messages between them to convince the parties in
|
||||
// each group that they are actually in one big group (with the intent to later censor and/or selectively send messages
|
||||
// to each group).
|
||||
func (p *Profile) VerifyGroupMessage(onion string, groupID string, ciphertext []byte, signature []byte) bool {
|
||||
func (p *Profile) VerifyGroupMessage(onion string, groupID string, message string, signature []byte) bool {
|
||||
|
||||
group := p.GetGroup(groupID)
|
||||
if group == nil {
|
||||
|
@ -342,7 +343,7 @@ func (p *Profile) VerifyGroupMessage(onion string, groupID string, ciphertext []
|
|||
}
|
||||
|
||||
// We use our group id, a known reference server and the ciphertext of the message.
|
||||
m := groupID + group.GroupServer + string(ciphertext)
|
||||
m := groupID + group.GroupServer + message
|
||||
|
||||
// If the message is ostensibly from us then we check it against our public key...
|
||||
if onion == p.Onion {
|
||||
|
@ -425,7 +426,33 @@ func (p *Profile) AttemptDecryption(ciphertext []byte, signature []byte) (bool,
|
|||
for _, group := range p.Groups {
|
||||
success, dgm := group.DecryptMessage(ciphertext)
|
||||
if success {
|
||||
verified := p.VerifyGroupMessage(dgm.Onion, group.GroupID, ciphertext, signature)
|
||||
|
||||
// Attempt to serialize this message
|
||||
serialized, err := json.Marshal(dgm)
|
||||
|
||||
// Someone send a message that isn't a valid Decrypted Group Message. Since we require this struct in orer
|
||||
// to verify the message, we simply ignore it.
|
||||
if err != nil {
|
||||
return false, group.GroupID, nil, false
|
||||
}
|
||||
|
||||
// This now requires knowledge of the Sender, the Onion and the Specific Decrypted Group Message (which should only
|
||||
// be derivable from the cryptographic key) which contains many unique elements such as the time and random padding
|
||||
verified := p.VerifyGroupMessage(dgm.Onion, group.GroupID, base64.StdEncoding.EncodeToString(serialized), signature)
|
||||
|
||||
if !verified {
|
||||
// An earlier version of this protocol mistakenly signed the ciphertext of the message
|
||||
// instead of the serialized decrypted group message.
|
||||
// This has 2 issues:
|
||||
// 1. A server with knowledge of group members public keys AND the Group ID would be able to detect valid messages
|
||||
// 2. It made the metadata-security of a group dependent on keeping the cryptographically derived Group ID secret.
|
||||
// While not awful, it also isn't good. For Version 3 groups only we permit Cwtch to check this older signature
|
||||
// structure in a backwards compatible way for the duration of the Groups Experiment.
|
||||
// TODO: Delete this check when Groups are no long Experimental
|
||||
if group.Version == 3 {
|
||||
verified = p.VerifyGroupMessage(dgm.Onion, group.GroupID, string(ciphertext), signature)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
@ -494,7 +521,8 @@ func (p *Profile) EncryptMessageToGroup(message string, groupID string) ([]byte,
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
signature := p.SignMessage(groupID + group.GroupServer + string(ciphertext))
|
||||
serialized, _ := json.Marshal(dm)
|
||||
signature := p.SignMessage(groupID + group.GroupServer + base64.StdEncoding.EncodeToString(serialized))
|
||||
group.AddSentMessage(dm, signature)
|
||||
return ciphertext, signature, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue