From 3b942f8a1c371142a8e20cbf3dd5817c68f50024 Mon Sep 17 00:00:00 2001 From: Martin/Geno Date: Mon, 9 Sep 2019 13:14:38 +0200 Subject: [PATCH] add image+audio support + refactory group --- blob.go | 6 +-- message_audio.go | 92 ++++++++++++++++++++++++++++++++++++++++++++ message_image.go | 87 +++++++++++++++++++++++++++++++++++++++++ message_text.go | 46 +++++++++++++++++----- messagegroup.go | 27 +++++++++++++ messagegroup_text.go | 81 -------------------------------------- 6 files changed, 244 insertions(+), 95 deletions(-) create mode 100644 message_audio.go create mode 100644 message_image.go create mode 100644 messagegroup.go delete mode 100644 messagegroup_text.go diff --git a/blob.go b/blob.go index 30fe1dc..b7d1492 100644 --- a/blob.go +++ b/blob.go @@ -74,9 +74,8 @@ func uploadBlob(blob []byte) ([16]byte, error) { } // encryptAsymAndUpload encrypts a blob with recipients PK and the sc owners SK -func encryptAsymAndUpload(sc SessionContext, plainImage []byte, recipientName string) (blobNonce nonce, ServerID byte, size uint32, blobID [16]byte, err error) { +func encryptAsymAndUpload(threemaID *ThreemaID, plainImage []byte, recipientName string) (blobNonce nonce, ServerID byte, size uint32, blobID [16]byte, err error) { // Get contact public key - threemaID := sc.ID recipient, inContacts := threemaID.Contacts.Get(recipientName) if !inContacts { var tr ThreemaRest @@ -157,14 +156,13 @@ func downloadBlob(blobID [16]byte) ([]byte, error) { return ciphertext, nil } -func downloadAndDecryptAsym(sc SessionContext, blobID [16]byte, senderName string, blobNonce nonce) (plaintext []byte, err error) { +func downloadAndDecryptAsym(threemaID *ThreemaID, blobID [16]byte, senderName string, blobNonce nonce) (plaintext []byte, err error) { ciphertext, err := downloadBlob(blobID) if err != nil { return []byte{}, err } var sender ThreemaContact - threemaID := sc.ID sender, inContacts := threemaID.Contacts.Get(senderName) if !inContacts { var tr ThreemaRest diff --git a/message_audio.go b/message_audio.go new file mode 100644 index 0000000..c924cc6 --- /dev/null +++ b/message_audio.go @@ -0,0 +1,92 @@ +package o3 + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" +) + +// MsgType mock enum +const MessageTypeAudio MsgType = 0x14 + +//AudioMessage represents a image message as sent e2e encrypted to other threema users +type AudioMessage struct { + *MessageHeader + Duration uint16 + BlobID [16]byte + ServerID byte + Size uint32 + Key [32]byte +} + +// String returns the message text as string +func (m AudioMessage) String() string { + return fmt.Sprintf("AudioMSG: https://%2x.blob.threema.ch/%16x, Size: %d, Key: %24x", m.ServerID, m.BlobID, m.Size, m.Key) +} + +// GetData return the decrypted Audio needs the recipients secret key +func (m AudioMessage) GetData() ([]byte, error) { + return downloadAndDecryptSym(m.BlobID, m.Key) +} + +// SetAudio encrypts and uploads the image by file. Sets the blob info in the AudioMessage. Needs the recipients public key. +func (m *AudioMessage) SetDataByFile(filename string) error { + data, err := ioutil.ReadFile(filename) + if err != nil { + return errors.New("could not load image") + } + return m.SetData(data) +} + +// SetAudioData encrypts and uploads the image. Sets the blob info in the AudioMessage. Needs the recipients public key. +func (m *AudioMessage) SetData(data []byte) (err error) { + // TODO: Should we have a whole media lib as dependency just to set this to the proper value? + m.Duration = 0xFF + m.Key, m.ServerID, m.Size, m.BlobID, err = encryptSymAndUpload(data) + return +} + +//Serialize returns a fully serialized byte slice of a TextMessage +func (m AudioMessage) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + bufMarshal("msg-type", buf, MessageTypeAudio) + bufMarshal("duration", buf, 0xFFFF) + bufMarshal("blob-id", buf, m.BlobID) + bufMarshal("size", buf, m.Size) + bufMarshal("nonce", buf, m.Key) + bufMarshalPadding(buf) + + return buf.Bytes(), nil +} + +func (m *AudioMessage) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + var t MsgType + bufUnmarshal("read message type", buf, &t) + if t != MessageTypeText { + return errors.New("not correct type") + } + stripPadding(buf) + + bufUnmarshal("duration", buf, &m.Duration) + bufUnmarshal("blob-id", buf, &m.BlobID) + bufUnmarshal("size", buf, &m.Size) + bufUnmarshal("key", buf, &m.Key) + + m.ServerID = m.BlobID[0] + + return nil +} + +func init() { + messageUnmarshal[MessageTypeAudio] = func(mh *MessageHeader, data []byte) (Message, error) { + m := &AudioMessage{ + MessageHeader: mh, + } + if err := m.UnmarshalBinary(data); err != nil { + return nil, err + } + return m, nil + } +} diff --git a/message_image.go b/message_image.go new file mode 100644 index 0000000..a3ff4bc --- /dev/null +++ b/message_image.go @@ -0,0 +1,87 @@ +package o3 + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" +) + +// MsgType mock enum +const MessageTypeImage MsgType = 0x2 + +//ImageMessage represents a image message as sent e2e encrypted to other threema users +type ImageMessage struct { + *MessageHeader + BlobID [16]byte + ServerID byte + Size uint32 + Nonce nonce +} + +// String returns the message text as string +func (m ImageMessage) String() string { + return fmt.Sprintf("ImageMSG: https://%2x.blob.threema.ch/%16x, Size: %d, Nonce: %24x", m.ServerID, m.BlobID, m.Size, m.Nonce.nonce) +} + +// GetImageData return the decrypted Image needs the recipients secret key +func (m ImageMessage) GetData(threemaID *ThreemaID) ([]byte, error) { + return downloadAndDecryptAsym(threemaID, m.BlobID, m.Sender.String(), m.Nonce) +} + +// SetDataByFile encrypts and uploads the image by file. Sets the blob info in the ImageMessage. Needs the recipients public key. +func (m *ImageMessage) SetDataByFile(threemaID *ThreemaID, filename string) error { + data, err := ioutil.ReadFile(filename) + if err != nil { + return errors.New("could not load image") + } + return m.SetData(threemaID, data) +} + +// SetData encrypts and uploads the image. Sets the blob info in the ImageMessage. Needs the recipients public key. +func (m *ImageMessage) SetData(threemaID *ThreemaID, data []byte) (err error) { + m.Nonce, m.ServerID, m.Size, m.BlobID, err = encryptAsymAndUpload(threemaID, data, m.Recipient.String()) + return +} + +//Serialize returns a fully serialized byte slice of a TextMessage +func (m ImageMessage) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + bufMarshal("msg-type", buf, MessageTypeImage) + bufMarshal("blob-id", buf, m.BlobID) + bufMarshal("size", buf, m.Size) + bufMarshal("nonce", buf, m.Nonce.nonce) + bufMarshalPadding(buf) + + return buf.Bytes(), nil +} + +func (m *ImageMessage) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + var t MsgType + bufUnmarshal("read message type", buf, &t) + if t != MessageTypeText { + return errors.New("not correct type") + } + stripPadding(buf) + + bufUnmarshal("blob-id", buf, &m.BlobID) + bufUnmarshal("size", buf, &m.Size) + bufUnmarshal("nonce", buf, &m.Nonce.nonce) + + m.ServerID = m.BlobID[0] + + return nil +} + +func init() { + messageUnmarshal[MessageTypeImage] = func(mh *MessageHeader, data []byte) (Message, error) { + m := &ImageMessage{ + MessageHeader: mh, + } + if err := m.UnmarshalBinary(data); err != nil { + return nil, err + } + return m, nil + } +} diff --git a/message_text.go b/message_text.go index 3bc4541..dbacbe4 100644 --- a/message_text.go +++ b/message_text.go @@ -6,11 +6,15 @@ import ( ) // MsgType mock enum -const MessageTypeText MsgType = 0x1 +const ( + MessageTypeText MsgType = 0x1 + MessageTypeGroupText MsgType = 0x41 +) //TextMessage represents a text message as sent e2e encrypted to other threema users type TextMessage struct { *MessageHeader + *GroupMessageHeader Body string } @@ -28,36 +32,58 @@ func (tm TextMessage) String() string { } //Serialize returns a fully serialized byte slice of a TextMessage -func (tm TextMessage) MarshalBinary() ([]byte, error) { +func (m TextMessage) MarshalBinary() ([]byte, error) { buf := new(bytes.Buffer) - bufMarshal("msg-type", buf, MessageTypeText) - bufMarshal("body", buf, []byte(tm.Body)) + if m.GroupMessageHeader == nil { + bufMarshal("msg-type", buf, MessageTypeText) + } else { + bufMarshal("msg-type", buf, uint8(MessageTypeGroupText)) + data, err := m.GroupMessageHeader.MarshalBinary() + bufMarshal("msg-group-header", buf, data) + if err != nil { + return nil, err + } + } + bufMarshal("body", buf, []byte(m.Body)) bufMarshalPadding(buf) return buf.Bytes(), nil } -func (tm *TextMessage) UnmarshalBinary(data []byte) error { +func (m *TextMessage) UnmarshalBinary(data []byte) error { buf := bytes.NewBuffer(data) var t MsgType bufUnmarshal("read message type", buf, &t) - if t != MessageTypeText { + if t == MessageTypeGroupText { + m.GroupMessageHeader.UnmarshalBinary(data[1 : GroupMessageHeaderLenght+1]) + } else if t != MessageTypeText { return errors.New("not correct type") } stripPadding(buf) - tm.Body = string(buf.Bytes()) + m.Body = string(buf.Bytes()) return nil } func init() { messageUnmarshal[MessageTypeText] = func(mh *MessageHeader, data []byte) (Message, error) { - tm := &TextMessage{ + m := &TextMessage{ MessageHeader: mh, } - if err := tm.UnmarshalBinary(data); err != nil { + if err := m.UnmarshalBinary(data); err != nil { + return nil, err + } + return m, nil + } + messageUnmarshal[MessageTypeGroupText] = func(mh *MessageHeader, data []byte) (Message, error) { + m := &TextMessage{ + MessageHeader: mh, + GroupMessageHeader: &GroupMessageHeader{}, + } + + if err := m.UnmarshalBinary(data); err != nil { return nil, err } - return tm, nil + return m, nil } } diff --git a/messagegroup.go b/messagegroup.go new file mode 100644 index 0000000..21312a1 --- /dev/null +++ b/messagegroup.go @@ -0,0 +1,27 @@ +package o3 + +import ( + "bytes" +) + +type GroupMessageHeader struct { + CreatorID IDString + GroupID [8]byte +} + +const GroupMessageHeaderLenght = 16 + +func (msg GroupMessageHeader) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + bufMarshal("gh-creator id", buf, msg.CreatorID) + bufMarshal("gh-group id", buf, msg.GroupID) + return buf.Bytes(), nil +} + +func (msg *GroupMessageHeader) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + bufUnmarshal("read group creator", buf, &msg.CreatorID) + bufUnmarshal("read group id", buf, &msg.GroupID) + + return nil +} diff --git a/messagegroup_text.go b/messagegroup_text.go deleted file mode 100644 index 8a28503..0000000 --- a/messagegroup_text.go +++ /dev/null @@ -1,81 +0,0 @@ -package o3 - -import ( - "bytes" - "errors" -) - -// MsgType mock enum -const MessageTypeGroupText MsgType = 0x41 - -type GroupMessageHeader struct { - CreatorID IDString - GroupID [8]byte -} - -const GroupMessageHeaderLenght = 16 - -func (msg GroupMessageHeader) MarshalBinary() ([]byte, error) { - buf := new(bytes.Buffer) - bufMarshal("gh-creator id", buf, msg.CreatorID) - bufMarshal("gh-group id", buf, msg.GroupID) - return buf.Bytes(), nil -} -func (msg *GroupMessageHeader) UnmarshalBinary(data []byte) error { - buf := bytes.NewBuffer(data) - bufUnmarshal("read group creator", buf, &msg.CreatorID) - bufUnmarshal("read group id", buf, &msg.GroupID) - - return nil -} - -//GroupTextMessage represents a text message as sent e2e encrypted to other threema users -type GroupTextMessage struct { - *GroupMessageHeader - *TextMessage -} - -//Serialize returns a fully serialized byte slice of a TextMessage -func (msg GroupTextMessage) MarshalBinary() ([]byte, error) { - - buf := new(bytes.Buffer) - bufMarshal("msg-type", buf, uint8(MessageTypeGroupText)) - data, err := msg.GroupMessageHeader.MarshalBinary() - bufMarshal("msg-group-header", buf, data) - if err != nil { - return nil, err - } - data, err = msg.TextMessage.MarshalBinary() - if err != nil { - return nil, err - } - bufMarshal("text-msg", buf, data[1:]) - return buf.Bytes(), nil -} - -func (msg *GroupTextMessage) UnmarshalBinary(data []byte) error { - buf := bytes.NewBuffer(data) - var t MsgType - bufUnmarshal("read message type", buf, &t) - if t != MessageTypeGroupText { - return errors.New("not correct type") - } - - return nil -} - -func init() { - messageUnmarshal[MessageTypeGroupText] = func(mh *MessageHeader, data []byte) (Message, error) { - tm := &GroupTextMessage{ - GroupMessageHeader: &GroupMessageHeader{}, - TextMessage: &TextMessage{ - MessageHeader: mh, - }, - } - data[0] = byte(MessageTypeText) - tm.GroupMessageHeader.UnmarshalBinary(data[1 : GroupMessageHeaderLenght+1]) - data = append(data[:1], data[GroupMessageHeaderLenght+1:]...) - tm.TextMessage.UnmarshalBinary(data) - return tm, nil - } -}