mirror of
https://dev.narayana.im/narayana/telegabber.git
synced 2026-08-05 04:07:07 +00:00
133 lines
3.2 KiB
Go
133 lines
3.2 KiB
Go
package omemo
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/xml"
|
|
"math/big"
|
|
)
|
|
|
|
// Two dialects of XEP-0420 Stanza Content Encryption, matching what each
|
|
// OMEMO version actually wraps its plaintext in - just enough of each to
|
|
// carry a plain message body, per each dialect's requirement that the
|
|
// envelope MUST contain <rpad/> and SHOULD contain <from/> (MUST contain
|
|
// <to/> for MUC, which this gateway doesn't do OMEMO for - see the plan's
|
|
// scope note on MUC).
|
|
//
|
|
// V0 (Omemo1, XEP-0384 0.4.0-0.7.x) is the OLD XEP-0420 shape, confirmed
|
|
// by reading the archived 0.3.2 spec directly:
|
|
//
|
|
// <content xmlns='urn:xmpp:sce:0'>
|
|
// <payload>
|
|
// <body xmlns='jabber:client'>...</body>
|
|
// </payload>
|
|
// <from jid='...'/>
|
|
// <rpad>...</rpad>
|
|
// </content>
|
|
//
|
|
// V1 (Omemo2, current) renamed content->envelope and payload->content
|
|
// (XEP-0420 0.4.0+, urn:xmpp:sce:1):
|
|
//
|
|
// <envelope xmlns='urn:xmpp:sce:1'>
|
|
// <content>
|
|
// <body xmlns='jabber:client'>...</body>
|
|
// </content>
|
|
// <from jid='...'/>
|
|
// <rpad>...</rpad>
|
|
// </envelope>
|
|
|
|
type sceBody struct {
|
|
XMLName xml.Name `xml:"jabber:client body"`
|
|
Text string `xml:",chardata"`
|
|
}
|
|
|
|
type sceJIDAttr struct {
|
|
JID string `xml:"jid,attr"`
|
|
}
|
|
|
|
// --- V0 (Omemo1) ---
|
|
|
|
type sceContentV0 struct {
|
|
XMLName xml.Name `xml:"urn:xmpp:sce:0 content"`
|
|
Payload scePayloadV0 `xml:"payload"`
|
|
From *sceJIDAttr `xml:"from"`
|
|
RPad string `xml:"rpad"`
|
|
}
|
|
|
|
type scePayloadV0 struct {
|
|
Body sceBody `xml:"body"`
|
|
}
|
|
|
|
func sceEncodeV0(plaintext []byte, fromJID string) ([]byte, error) {
|
|
rpad, err := randomPadding()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
env := sceContentV0{
|
|
Payload: scePayloadV0{Body: sceBody{Text: string(plaintext)}},
|
|
RPad: rpad,
|
|
}
|
|
if fromJID != "" {
|
|
env.From = &sceJIDAttr{JID: fromJID}
|
|
}
|
|
return xml.Marshal(env)
|
|
}
|
|
|
|
func sceDecodeV0(data []byte) ([]byte, error) {
|
|
var env sceContentV0
|
|
if err := xml.Unmarshal(data, &env); err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(env.Payload.Body.Text), nil
|
|
}
|
|
|
|
// --- V1 (Omemo2) ---
|
|
|
|
type sceEnvelopeV1 struct {
|
|
XMLName xml.Name `xml:"urn:xmpp:sce:1 envelope"`
|
|
Content sceContentV1 `xml:"content"`
|
|
From *sceJIDAttr `xml:"from"`
|
|
RPad string `xml:"rpad"`
|
|
}
|
|
|
|
type sceContentV1 struct {
|
|
Body sceBody `xml:"body"`
|
|
}
|
|
|
|
func sceEncodeV1(plaintext []byte, fromJID string) ([]byte, error) {
|
|
rpad, err := randomPadding()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
env := sceEnvelopeV1{
|
|
Content: sceContentV1{Body: sceBody{Text: string(plaintext)}},
|
|
RPad: rpad,
|
|
}
|
|
if fromJID != "" {
|
|
env.From = &sceJIDAttr{JID: fromJID}
|
|
}
|
|
return xml.Marshal(env)
|
|
}
|
|
|
|
func sceDecodeV1(data []byte) ([]byte, error) {
|
|
var env sceEnvelopeV1
|
|
if err := xml.Unmarshal(data, &env); err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(env.Content.Body.Text), nil
|
|
}
|
|
|
|
// randomPadding returns base64-encoded random padding of a random length
|
|
// (1-63 bytes before encoding) to mask plaintext length, per XEP-0420's
|
|
// MUST-contain-<rpad/> requirement (both dialects).
|
|
func randomPadding() (string, error) {
|
|
n, err := rand.Int(rand.Reader, big.NewInt(63))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
buf := make([]byte, n.Int64()+1)
|
|
if _, err := rand.Read(buf); err != nil {
|
|
return "", err
|
|
}
|
|
return base64.StdEncoding.EncodeToString(buf), nil
|
|
}
|