dia_message.go 5.26 KB
Newer Older
zauberstuhl's avatar
zauberstuhl committed
1 2
package federation
//
Ghost User's avatar
Ghost User committed
3 4
// GangGo Federation Library
// Copyright (C) 2017-2018 Lukas Matt <lukas@zauberstuhl.de>
zauberstuhl's avatar
zauberstuhl committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

20 21
import (
  "errors"
zauberstuhl's avatar
zauberstuhl committed
22 23
  "github.com/Zauberstuhl/go-xml"
  "encoding/base64"
24
  "strings"
Ghost User's avatar
Ghost User committed
25
  "crypto/rsa"
26
)
zauberstuhl's avatar
zauberstuhl committed
27

Ghost User's avatar
Ghost User committed
28
type DiasporaMessage struct {
zauberstuhl's avatar
zauberstuhl committed
29
  XMLName xml.Name `xml:"me:env"`
30 31
  Me string `xml:"me,attr"`
  Data struct {
zauberstuhl's avatar
zauberstuhl committed
32
    XMLName xml.Name `xml:"me:data"`
33 34
    Type string `xml:"type,attr"`
    Data string `xml:",chardata"`
zauberstuhl's avatar
zauberstuhl committed
35
  }
zauberstuhl's avatar
zauberstuhl committed
36 37
  Encoding string `xml:"me:encoding"`
  Alg string `xml:"me:alg"`
38
  Sig struct {
zauberstuhl's avatar
zauberstuhl committed
39
    XMLName xml.Name `xml:"me:sig"`
40 41
    Sig string `xml:",chardata"`
    KeyId string `xml:"key_id,attr,omitempty"`
zauberstuhl's avatar
zauberstuhl committed
42
  }
Ghost User's avatar
Ghost User committed
43
  entity DiasporaEntity `xml:"-"`
zauberstuhl's avatar
zauberstuhl committed
44 45
}

Ghost User's avatar
Ghost User committed
46
type DiasporaEntity struct {
47 48 49 50
  XMLName xml.Name
  // Use custom unmarshaler for xml fetch XMLName
  // and decide which entity to use
  Type string `xml:"-"`
51
  SignatureOrder string `xml:"-"`
Ghost User's avatar
Ghost User committed
52 53
  Data MessageBase `xml:"-"`
  DataRaw []byte `xml:"-"`
54 55
}

Ghost User's avatar
Ghost User committed
56 57 58
func (message DiasporaMessage) ValidSignature(pub *rsa.PublicKey) bool {
  signature := (&signature{}).New(message.signatureText(), SignatureDelimiter)
  return signature.Verify(pub, message.Sig.Sig)
59 60
}

Ghost User's avatar
Ghost User committed
61
func (m DiasporaMessage) signatureText() []string {
zauberstuhl's avatar
zauberstuhl committed
62 63
  return []string{
    m.Data.Data,
Ghost User's avatar
Ghost User committed
64 65 66 67 68 69 70 71 72
    base64.URLEncoding.EncodeToString([]byte(m.Data.Type)),
    base64.URLEncoding.EncodeToString([]byte(m.Encoding)),
    base64.URLEncoding.EncodeToString([]byte(m.Alg)),
  }
}

func (m DiasporaMessage) Type() MessageType {
  return MessageType{
    Proto: DiasporaProtocol,
zauberstuhl's avatar
zauberstuhl committed
73 74 75
  }
}

Ghost User's avatar
Ghost User committed
76 77 78 79 80
func (m DiasporaMessage) Entity() MessageBase {
  return m.entity.Data
}

func (message *DiasporaMessage) Parse() error {
81
  if !strings.EqualFold(message.Encoding, BASE64_URL) {
Ghost User's avatar
Ghost User committed
82
    Log.Error("Encoding doesn't match",
83
      "message", message.Encoding, "lib", BASE64_URL)
Ghost User's avatar
Ghost User committed
84
    return errors.New("Encoding doesn't match")
85 86 87
  }

  if !strings.EqualFold(message.Alg, RSA_SHA256) {
Ghost User's avatar
Ghost User committed
88
    Log.Error("Algorithm doesn't match",
89
      "message", message.Alg, "lib", RSA_SHA256)
Ghost User's avatar
Ghost User committed
90
    return errors.New("Algorithm doesn't match")
91 92 93 94
  }

  keyId, err := base64.StdEncoding.DecodeString(message.Sig.KeyId)
  if err != nil {
Ghost User's avatar
Ghost User committed
95 96
    Log.Error("Cannot decode signature key ID", "err", err)
    return err
97 98
  }
  message.Sig.KeyId = string(keyId)
Ghost User's avatar
Ghost User committed
99
  Log.Info("Entity sender", message.Sig.KeyId)
100 101 102

  data, err := base64.URLEncoding.DecodeString(message.Data.Data)
  if err != nil {
Ghost User's avatar
Ghost User committed
103 104
    Log.Error("Cannot decode message data", "err", err)
    return err
105
  }
Ghost User's avatar
Ghost User committed
106 107
  message.entity.DataRaw = data
  Log.Info("Entity raw", string(data))
108

Ghost User's avatar
Ghost User committed
109
  message.entity.SignatureOrder, err = DiasporaFetchOrder(data)
110
  if err != nil {
Ghost User's avatar
Ghost User committed
111 112
    Log.Error("Cannot fetch entity order", "err", err)
    return err
113
  }
Ghost User's avatar
Ghost User committed
114
  Log.Info("Entity order", message.entity.SignatureOrder)
115

Ghost User's avatar
Ghost User committed
116
  err = xml.Unmarshal(data, &message.entity)
117
  if err != nil {
Ghost User's avatar
Ghost User committed
118 119
    Log.Error("Cannot unmarshal data", "err", err)
    return err
120
  }
Ghost User's avatar
Ghost User committed
121
  return nil
122 123
}

Ghost User's avatar
Ghost User committed
124
func (e *DiasporaEntity) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
125 126 127
  // NOTE since the encoder ignores the interface type
  // (see https://golang.org/src/encoding/xml/read.go#L377)
  // we have to decode on every single step
Ghost User's avatar
Ghost User committed
128
  switch local := EntityType(start.Name.Local); local {
129
  case Retraction:
Ghost User's avatar
Ghost User committed
130
    content := &DiasporaRetraction{}
131 132 133
    if err := d.DecodeElement(&content, &start); err != nil {
      return err
    }
Ghost User's avatar
Ghost User committed
134
    (*e).Type = string(local)
135 136
    (*e).Data = content
  case Profile:
Ghost User's avatar
Ghost User committed
137
    content := &DiasporaProfile{}
138 139 140
    if err := d.DecodeElement(&content, &start); err != nil {
      return err
    }
Ghost User's avatar
Ghost User committed
141
    (*e).Type = string(local)
142 143
    (*e).Data = content
  case StatusMessage:
Ghost User's avatar
Ghost User committed
144
    content := &DiasporaStatusMessage{}
145 146 147
    if err := d.DecodeElement(&content, &start); err != nil {
      return err
    }
Ghost User's avatar
Ghost User committed
148
    (*e).Type = string(local)
149
    (*e).Data = content
150
  case Reshare:
Ghost User's avatar
Ghost User committed
151
    content := &DiasporaReshare{}
152 153 154
    if err := d.DecodeElement(&content, &start); err != nil {
      return err
    }
Ghost User's avatar
Ghost User committed
155
    (*e).Type = string(local)
156
    (*e).Data = content
157
  case Comment:
Ghost User's avatar
Ghost User committed
158 159 160 161
    content := &DiasporaComment{
      EntityRaw: e.DataRaw,
      EntitySignatureOrder: e.SignatureOrder,
    }
162 163 164
    if err := d.DecodeElement(&content, &start); err != nil {
      return err
    }
Ghost User's avatar
Ghost User committed
165
    (*e).Type = string(local)
166 167
    (*e).Data = content
  case Like:
Ghost User's avatar
Ghost User committed
168 169 170 171
    content := &DiasporaLike{
      EntityRaw: e.DataRaw,
      EntitySignatureOrder: e.SignatureOrder,
    }
172 173 174
    if err := d.DecodeElement(&content, &start); err != nil {
      return err
    }
Ghost User's avatar
Ghost User committed
175
    (*e).Type = string(local)
176 177
    (*e).Data = content
  case Contact:
Ghost User's avatar
Ghost User committed
178
    content := &DiasporaContact{}
179 180 181
    if err := d.DecodeElement(&content, &start); err != nil {
      return err
    }
Ghost User's avatar
Ghost User committed
182
    (*e).Type = string(local)
183 184
    (*e).Data = content
  default:
Ghost User's avatar
Ghost User committed
185
    return errors.New("Entity '" + string(local) + "' not known!")
186 187
  }
  return nil
zauberstuhl's avatar
zauberstuhl committed
188
}