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
|
// 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.
|
// GroupInvitePrefix identifies a particular string as being a serialized group invite.
|
||||||
const GroupInvitePrefix = "torv3"
|
const GroupInvitePrefix = "torv3"
|
||||||
|
@ -58,13 +58,6 @@ func NewGroup(server string) (*Group, error) {
|
||||||
|
|
||||||
group.GroupServer = server
|
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
|
var groupKey [32]byte
|
||||||
if _, err := io.ReadFull(rand.Reader, groupKey[:]); err != nil {
|
if _, err := io.ReadFull(rand.Reader, groupKey[:]); err != nil {
|
||||||
log.Errorf("Error: Cannot read from random: %v\n", err)
|
log.Errorf("Error: Cannot read from random: %v\n", err)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"cwtch.im/cwtch/model/attr"
|
"cwtch.im/cwtch/model/attr"
|
||||||
"cwtch.im/cwtch/protocol/groups"
|
"cwtch.im/cwtch/protocol/groups"
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -322,7 +323,7 @@ func (p *Profile) GetContact(onion string) (*PublicProfile, bool) {
|
||||||
return contact, ok
|
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:
|
// 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
|
// 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)
|
// 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
|
// 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
|
// each group that they are actually in one big group (with the intent to later censor and/or selectively send messages
|
||||||
// to each group).
|
// 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)
|
group := p.GetGroup(groupID)
|
||||||
if group == nil {
|
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.
|
// 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 the message is ostensibly from us then we check it against our public key...
|
||||||
if onion == p.Onion {
|
if onion == p.Onion {
|
||||||
|
@ -425,7 +426,33 @@ func (p *Profile) AttemptDecryption(ciphertext []byte, signature []byte) (bool,
|
||||||
for _, group := range p.Groups {
|
for _, group := range p.Groups {
|
||||||
success, dgm := group.DecryptMessage(ciphertext)
|
success, dgm := group.DecryptMessage(ciphertext)
|
||||||
if success {
|
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.
|
// 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)
|
// 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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
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)
|
group.AddSentMessage(dm, signature)
|
||||||
return ciphertext, signature, nil
|
return ciphertext, signature, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue