Commit 8cf9d860 authored by Lukas Matt's avatar Lukas Matt
parent af31bac8
......@@ -23,4 +23,13 @@ const (
APPLICATION_XML = "application/xml"
BASE64_URL = "base64url"
RSA_SHA256 = "RSA-SHA256"
// entity names
Retraction = "retraction"
Profile = "profile"
StatusMessage = "status_message"
Reshare = "reshare"
Comment = "comment"
Like = "like"
Contact = "contact"
)
......@@ -17,56 +17,109 @@ package federation
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import "encoding/xml"
import (
"errors"
"encoding/xml"
"reflect"
"strings"
)
// NOTE I had huge problems with marshal
// and unmarshal with the same structs
// apperently namespaces in tags are a problem
// for the go xml implementation
type DiasporaUnmarshal struct {
XMLName xml.Name `xml:"diaspora"`
Xmlns string `xml:"xmlns,attr"`
XmlnsMe string `xml:"me,attr"`
Header struct {
XMLName xml.Name `xml:"header"`
AuthorId string `xml:"author_id"`
type Message struct {
XMLName xml.Name `xml:"env"`
Me string `xml:"me,attr"`
Data struct {
XMLName xml.Name `xml:"data"`
Type string `xml:"type,attr"`
Data string `xml:",chardata"`
}
EncryptedHeader string `xml:"encrypted_header,omitempty"`
DecryptedHeader *XmlDecryptedHeader `xml:",omitempty"`
Env struct {
XMLName xml.Name `xml:"env"`
Me string `xml:"me,attr"`
Data struct {
XMLName xml.Name `xml:"data"`
Type string `xml:"type,attr"`
Data string `xml:",chardata"`
}
Encoding string `xml:"encoding"`
Alg string `xml:"alg"`
Sig struct {
XMLName xml.Name `xml:"sig"`
Sig string `xml:",chardata"`
KeyId string `xml:"key_id,attr,omitempty"`
}
Encoding string `xml:"encoding"`
Alg string `xml:"alg"`
Sig struct {
XMLName xml.Name `xml:"sig"`
Sig string `xml:",chardata"`
KeyId string `xml:"key_id,attr,omitempty"`
}
Entity Entity `xml:"-"`
}
type Entity struct {
XMLName xml.Name `xml:"XML"`
Post EntityPost `xml:"post"`
XMLName xml.Name
// Use custom unmarshaler for xml fetch XMLName
// and decide which entity to use
Type string `xml:"-"`
SignatureOrder string `xml:"-"`
Data interface{} `xml:"-"`
}
func (e *Entity) LocalSignatureOrder() (order string) {
val := reflect.TypeOf(e.Data)
for i := 0; i < val.NumField(); i++ {
params := strings.Split(val.Field(i).Tag.Get("xml"), ",")
if len(params) > 0 {
switch tagName := params[0]; tagName {
case "":
case "-":
case "author_signature":
case "parent_author_signature":
default:
order += params[0] + " "
}
}
}
return order[:len(order)-1] // trim space
}
type EntityPost struct {
XMLName xml.Name `xml:"post,omitempty"`
Request *EntityRequest `xml:"request,omitempty"`
Retraction *EntityRetraction `xml:"retraction,omitempty"`
Profile *EntityProfile `xml:"profile,omitempty"`
Reshare *EntityStatusMessage `xml:"reshare,omitempty"`
StatusMessage *EntityStatusMessage `xml:"status_message,omitempty"`
Comment *EntityComment `xml:"comment,omitempty"`
Like *EntityLike `xml:"like,omitempty"`
SignedRetraction *EntityRelayableSignedRetraction `xml:"signed_retraction,omitempty"`
RelayableRetraction *EntityRelayableSignedRetraction `xml:"relayable_retraction,omitempty"`
func (e *Entity) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
// 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
switch local := start.Name.Local; local {
case Retraction:
content := EntityRetraction{}
if err := d.DecodeElement(&content, &start); err != nil {
return err
}
(*e).Type = local
(*e).Data = content
case Profile:
content := EntityProfile{}
if err := d.DecodeElement(&content, &start); err != nil {
return err
}
(*e).Type = local
(*e).Data = content
case StatusMessage:
fallthrough
case Reshare:
content := EntityStatusMessage{}
if err := d.DecodeElement(&content, &start); err != nil {
return err
}
(*e).Type = local
(*e).Data = content
case Comment:
content := EntityComment{}
if err := d.DecodeElement(&content, &start); err != nil {
return err
}
(*e).Type = local
(*e).Data = content
case Like:
content := EntityLike{}
if err := d.DecodeElement(&content, &start); err != nil {
return err
}
(*e).Type = local
(*e).Data = content
case Contact:
content := EntityContact{}
if err := d.DecodeElement(&content, &start); err != nil {
return err
}
(*e).Type = local
(*e).Data = content
default:
return errors.New("Entity '" + local + "' not known!")
}
return nil
}
......@@ -21,10 +21,11 @@ import "encoding/xml"
type EntityComment struct {
XMLName xml.Name `xml:"comment"`
DiasporaHandle string `xml:"diaspora_handle"`
Author string `xml:"author"`
Guid string `xml:"guid"`
ParentGuid string `xml:"parent_guid"`
Text string `xml:"text"`
AuthorSignature string `xml:"author_signature"`
ParentAuthorSignature string `xml:"parent_author_signature"`
SignatureOrder string `xml:"-"`
}
......@@ -17,7 +17,9 @@ package federation
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
type EntityRequest struct {
Sender string `xml:"sender_handle"`
Recipient string `xml:"recipient_handle"`
type EntityContact struct {
Author string `xml:"author"`
Recipient string `xml:"recipient"`
Following bool `xml:"following"`
Sharing bool `xml:"sharing"`
}
......@@ -17,12 +17,15 @@ package federation
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import "encoding/xml"
type EntityLike struct {
XMLName xml.Name `xml:"like"`
Positive bool `xml:"positive"`
Guid string `xml:"guid"`
ParentGuid string `xml:"parent_guid"`
TargetType string `xml:"target_type"`
DiasporaHandle string `xml:"diaspora_handle"`
TargetType string `xml:"parent_type"`
Author string `xml:"author"`
AuthorSignature string `xml:"author_signature"`
ParentAuthorSignature string `xml:"parent_author_signature"`
}
......@@ -17,14 +17,18 @@ package federation
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import "time"
import (
"time"
"encoding/xml"
)
type EntityStatusMessage struct {
DiasporaHandle string `xml:"diaspora_handle"`
XMLName xml.Name `xml:"status_message"`
Author string `xml:"author"`
Guid string `xml:"guid"`
CreatedAt time.Time `xml:"created_at"`
ProviderName string `xml:"provider_display_name"`
RawMessage string `xml:"raw_message,omitempty"`
Text string `xml:"text,omitempty"`
Photo *EntityPhotos `xml:"photo,omitempty"`
Location *EntityLocation `xml:"location,omitempty"`
Poll *EntityPoll `xml:"poll,omitempty"`
......
......@@ -18,7 +18,7 @@ package federation
//
type EntityProfile struct {
DiasporaHandle string `xml:"diaspora_handle"`
Author string `xml:"author"`
FirstName string `xml:"first_name"`
LastName string `xml:"last_name"`
ImageUrl string `xml:"image_url"`
......@@ -29,6 +29,7 @@ type EntityProfile struct {
Bio string `xml:"bio"`
Location string `xml:"location"`
Searchable bool `xml:"searchable"`
Public bool `xml:"public"`
Nsfw bool `xml:"nsfw"`
TagString string `xml:"tag_string"`
}
......@@ -17,16 +17,8 @@ package federation
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
type EntityRelayableSignedRetraction struct {
type EntityRetraction struct {
Author string `xml:"author"`
TargetGuid string `xml:"target_guid"`
TargetType string `xml:"target_type"`
SenderHandle string `xml:"sender_handle"`
TargetAuthorSignature string `xml:"target_author_signature"`
ParentAuthorSignature string `xml:"parent_author_signature"`
}
type EntityRetraction struct {
DiasporaHandle string `xml:"diaspora_handle"`
PostGuid string `xml:"post_guid"`
Type string `xml:"type"`
}
package federation
//
// GangGo Diaspora Federation Library
// Copyright (C) 2017 Lukas Matt <lukas@zauberstuhl.de>
//
// 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/>.
//
import(
"encoding/base64"
"encoding/json"
"encoding/xml"
"crypto/rsa"
"crypto/rand"
)
/* Header
<?xml version="1.0" encoding="UTF-8"?>
<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env">
<header>
<author_id>dia@192.168.0.173:3000</author_id>
</header>
*/
type Header struct {
XMLName xml.Name `xml:"header"`
AuthorId string `xml:"author_id"`
}
/* Decrypted Header
<?xml version="1.0" encoding="UTF-8"?>
<decrypted_header>
<iv>...</iv>
<aes_key>...</aes_key>
<author_id>one@two.tld</author_id>
</decrypted_header>
*/
type XmlDecryptedHeader struct {
XMLName xml.Name `xml:"decrypted_header"`
Iv string `xml:"iv"`
AesKey string `xml:"aes_key"`
AuthorId string `xml:"author_id"`
}
type JsonEnvHeader struct {
AesKey string `json:"aes_key"`
Ciphertext string `json:"ciphertext"`
}
func (request *DiasporaUnmarshal) DecryptHeader(serialized []byte) error {
header := JsonEnvHeader{}
decryptedHeader := XmlDecryptedHeader{}
decoded, err := base64.StdEncoding.DecodeString(request.EncryptedHeader)
if err != nil {
return err
}
err = json.Unmarshal(decoded, &header)
if err != nil {
return err
}
privkey, err := ParseRSAPrivKey(serialized)
if err != nil {
return err
}
decoded, err = base64.StdEncoding.DecodeString(header.AesKey)
if err != nil {
return err
}
aesKeyJson, err := rsa.DecryptPKCS1v15(rand.Reader, privkey, decoded)
if err != nil {
return err
}
var aesKeySet Aes
err = json.Unmarshal(aesKeyJson, &aesKeySet)
if err != nil {
return err
}
aesKeySet.Data = header.Ciphertext
ciphertext, err := aesKeySet.Decrypt()
if err != nil {
return err
}
err = xml.Unmarshal(ciphertext, &decryptedHeader)
if err != nil {
return err
}
(*request).DecryptedHeader = &decryptedHeader
return nil
}
......@@ -23,11 +23,32 @@ import (
"errors"
)
func FetchEntityOrder(entityXML string) (order string) {
re := regexp.MustCompile(`<([^>]+?)>[^<]+?</[^>]+?>`)
elements := re.FindAllStringSubmatch(entityXML, -1)
for _, element := range elements {
if len(element) == 2 {
switch element[1] {
case "author_signature":
case "parent_author_signature":
default:
order += element[1] + " "
}
}
}
return order[:len(order)-1] // trim space
}
// This is a workaround for sorting xml elements. Diaspora requires
// a specific order otherwise the signature check will fail and
// ignore the entity. This should be a TODO since we could implement
// this kind of logic in a custom xml marshaller
func SortByEntityOrder(order string, entity []byte) (sorted []byte) {
// if we do not require sorting skip it
if order == "" {
return entity
}
// remove all newline character
entity = []byte(strings.Replace(string(entity), "\n", "", -1))
entity = []byte(strings.Replace(string(entity), "\r", "", -1))
......@@ -117,14 +138,6 @@ func ExtractEntityOrder(entity string) string {
return strings.Join(order, " ")
}
func ParseDiasporaHandle(handle string) (string, string, error) {
parts, err := parseStringHelper(handle, `^(.+?)@(.+?)$`, 2)
if err != nil {
return "", "", err
}
return parts[1], parts[2], nil
}
func ParseWebfingerHandle(handle string) (string, error) {
parts, err := parseStringHelper(handle, `^acct:(.+?)@.+?$`, 1)
if err != nil {
......
......@@ -20,76 +20,58 @@ package federation
import (
"encoding/xml"
"encoding/base64"
"strings"
)
func PreparePublicRequest(body string) (request DiasporaUnmarshal, err error) {
err = xml.Unmarshal([]byte(body), &request)
func ParseDecryptedRequest(entityXML []byte) (message Message, err error) {
err = xml.Unmarshal(entityXML, &message)
if err != nil {
warn(err)
fatal(err)
return
}
return
}
func (request *DiasporaUnmarshal) Parse(pubkey []byte) (entity Entity, err error) {
err = request.VerifySignature(pubkey)
if err != nil {
warn(err)
if !strings.EqualFold(message.Encoding, BASE64_URL) {
fatal(err)
return
}
xmlPayload, err := base64.URLEncoding.DecodeString(request.Env.Data.Data)
if err != nil {
warn(err)
if !strings.EqualFold(message.Alg, RSA_SHA256) {
fatal(err)
return
}
return _parse(xmlPayload)
}
func PreparePrivateRequest(body string, privkey []byte) (request DiasporaUnmarshal, err error) {
err = xml.Unmarshal([]byte(body), &request)
keyId, err := base64.StdEncoding.DecodeString(message.Sig.KeyId)
if err != nil {
warn(err)
fatal(err)
return
}
message.Sig.KeyId = string(keyId)
err = request.DecryptHeader(privkey)
data, err := base64.URLEncoding.DecodeString(message.Data.Data)
if err != nil {
warn(err)
fatal(err)
return
}
return
}
func (request *DiasporaUnmarshal) ParsePrivate(pubkey []byte) (entity Entity, err error) {
err = request.VerifySignature(pubkey)
if err != nil {
warn(err)
return
}
aesKeySet := Aes{
Key: request.DecryptedHeader.AesKey,
Iv: request.DecryptedHeader.Iv,
Data: request.Env.Data.Data,
var entity = Entity{
SignatureOrder: FetchEntityOrder(string(data)),
}
xmlPayload, err := aesKeySet.Decrypt()
err = xml.Unmarshal(data, &entity)
if err != nil {
warn(err)
fatal(err)
return
}
return _parse(xmlPayload)
message.Entity = entity
return
}
func _parse(payload []byte) (entity Entity, err error) {
info("received payload", string(payload))
entity.SignatureOrder = ExtractEntityOrder(string(payload))
err = xml.Unmarshal(payload, &entity)
func ParseEncryptedRequest(wrapper AesWrapper, privkey []byte) (message Message, err error) {
entityXML, err := wrapper.Decrypt(privkey)
if err != nil {
warn(err)
fatal(err)
return
}
return
return ParseDecryptedRequest(entityXML)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment