Commit c09e58ea authored by Lukas Matt's avatar Lukas Matt

Add Aspect compatibility to federation

Including encrypted magic envelope
Signed-off-by: default avatarLukas Matt <lukas@zauberstuhl.de>
parent ccbf0084
......@@ -18,9 +18,12 @@ package federation
//
import (
"bytes"
"encoding/base64"
"crypto/aes"
"crypto/cipher"
"io"
"crypto/rand"
)
type Aes struct {
......@@ -29,6 +32,71 @@ type Aes struct {
Data string `json:"-"`
}
type AesWrapper struct {
AesKey string `json:"aes_key"`
MagicEnvelope string `json:"encrypted_magic_envelope"`
}
func (a *Aes) Generate() error {
// The key argument should be the AES key,
// either 16, 24, or 32 bytes to select
// AES-128, AES-192, or AES-256.
key := make([]byte, 32)
_, err := io.ReadFull(rand.Reader, key)
if err != nil {
return err
}
a.Key = base64.StdEncoding.EncodeToString(key)
// The IV needs to be unique, but not secure. Therefore it's common to
// include it at the beginning of the ciphertext.
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return err
}
a.Iv = base64.StdEncoding.EncodeToString(iv)
return nil
}
func (a *Aes) Encrypt(data []byte) error {
// CBC mode works on blocks so plaintexts may need to be padded to the
// next whole block. For an example of such padding, see
// https://tools.ietf.org/html/rfc5246#section-6.2.3.2.
if len(data)%aes.BlockSize != 0 {
paddingLen := aes.BlockSize - (len(data)%aes.BlockSize)
paddingText := bytes.Repeat([]byte{byte(paddingLen)}, paddingLen)
//for i := 0; i < paddingLen; i++ {
// data = append(data, 0x20)
//}
data = append(data, paddingText...)
}
key, err := base64.StdEncoding.DecodeString(a.Key)
if err != nil {
return err
}
block, err := aes.NewCipher(key)
if err != nil {
return err
}
ciphertext := make([]byte, len(data))
iv, err := base64.StdEncoding.DecodeString(a.Iv)
if err != nil {
return err
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[:], data)
a.Data = base64.StdEncoding.EncodeToString(ciphertext)
return nil
}
func (a Aes) Decrypt() (ciphertext []byte, err error) {
key, err := base64.StdEncoding.DecodeString(a.Key)
if err != nil {
......@@ -40,16 +108,16 @@ func (a Aes) Decrypt() (ciphertext []byte, err error) {
return ciphertext, err
}
ciphertext, err = base64.URLEncoding.DecodeString(a.Data)
headerText, fail := base64.URLEncoding.DecodeString(a.Data)
if fail == nil {
info("header aes decryption detected")
a.Data = string(headerText)
}
ciphertext, err = base64.StdEncoding.DecodeString(a.Data)
if err != nil {
return ciphertext, err
}
headerText, err := base64.StdEncoding.DecodeString(string(ciphertext))
if err == nil {
// depending on the request
// we have to do it twice
ciphertext = headerText
}
return decryptAES(key, iv, ciphertext)
}
......
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/>.
//
const (
XMLNS = "https://joindiaspora.com/protocol"
XMLNS_ME = "http://salmon-protocol.org/ns/magic-env"
APPLICATION_XML = "application/xml"
BASE64_URL = "base64url"
RSA_SHA256 = "RSA-SHA256"
)
......@@ -25,8 +25,6 @@ import (
"crypto/sha256"
"encoding/base64"
"encoding/pem"
"encoding/xml"
"encoding/json"
"errors"
)
......@@ -119,7 +117,7 @@ func AuthorSignature(data interface{}, privKey string) (string, error) {
return Sign(text, privKey)
}
func (d *DiasporaMarshal) Sign(privKey string) (err error) {
func (d *PrivateEnvMarshal) Sign(privKey string) (err error) {
type64 := base64.StdEncoding.EncodeToString(
[]byte(d.Data.Type),
)
......@@ -156,52 +154,4 @@ func Sign(text, privKey string) (sig string, err error) {
}
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
}
......@@ -19,6 +19,19 @@ package federation
import "encoding/xml"
/* 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"`
}
/* Encrypted Header
<?xml version="1.0" encoding="UTF-8"?>
......@@ -28,6 +41,7 @@ import "encoding/xml"
<author_id>one@two.tld</author_id>
</decrypted_header>
*/
// XXX legacy?
type XmlDecryptedHeader struct {
XMLName xml.Name `xml:"decrypted_header"`
Iv string `xml:"iv"`
......@@ -40,9 +54,21 @@ type JsonEnvHeader struct {
Ciphertext string `json:"ciphertext"`
}
// Marshal Requests Non-Legacy
type DiasporaMarshal struct {
// Private Request
//
// <?xml version="1.0" encoding="UTF-8"?>
// <diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env">
// <encrypted_header>eyJhZXNfa2V5IjoiVTFGbVZ5TE5CT0pyOWViZVpiMUxnSGQzMlJwMzNMa1ViZVRnY3BEaDluZHAyT2cyMUZNeHh2Mm9RUXp0eWxLVjZsOEkzR0wvQlEzcEQxbGNYbjFGQWlEVTBmTHJwRUZwUEJNc1AwT3MvdmhEZ3I3MDJvaWNkMUxFN0ZMZ3ZVc1VKRGxGOHdvTVZUaGtnTmNVWGlCNUZpajcvb1J4ZFZ1QlJxbVpCQ0VXLys4PSIsImNpcGhlcnRleHQiOiJveWhWVjR5bXJoTGxYRVU1WVcxQWdpK29sTkxRSDlvR2NPQnVBOG00a25FZ09GZnJoNzEwUi9XQloyQ1I3WVlMeEhuOEcvUWFyU1UraDFka0VHMGFhcS9FQ1RWQjFHOXNkT2lQMnZSRTRkY1JOclNqb3RINXBsV3F4QVBvNlBpRkdwNUJZcURoYnB2RUNYNFNpM3BTN2hyOTdyWUs3NTFyYnYvRjNwRERJNG13d2NoUFA3S21nN21yVXBCZTBEeXRqQk5xK0k3S2lxSVQrRmVTUUMxc0pQSmd4TVAxck1VcDB6cURpT2ZlR3U2RGVnVG5MRnd5WHM3WEhGQW1FN29iSlZJY2NxS3czNjNWcnBrWXE0N1BDRWc3R3p1bFVlOXA5eFhDN3dVRmNOak91RTVQV1NXU1h3cWM0K2EybHFuOCJ9</encrypted_header>
// <me:env>
// <me:data type="application/xml">WXRPc09JakFleHh0dW5rQytRallnSEg0a08zOGd3ZVQyVVg4NkppVVZzVlJlMFFHbExhSjBtbzBnRUVGSnQyc2pqRG5OOUFETk1nakJ6MThGVnJsRWNEcjBqUDJ2emZPRjlCVHVLNXlvRjZqQ3NIRlh5USt4L3RlMnUxN0xCSUlxSDk4OGNhL1BVLzE2NEhBL0plQU1sMEZCOFBnTTFzYW9qUUJaV3V1RW95Y25ONWVZTm9wRi9adElXU3AxOFVzL05RWG13cktiWVVYYXJpNitOSWo1RWVOdTJrYnJnN2FoWUdhRFJKMVFtcEdhL0pMNmVvMEM1ZHFkS2s1amgrNFU0dnA0ZVY5bkRPK0ZGSkhpd3pDWm84NWJkdUR4T0NRRURpa2QxeGE1OWl4NkZWMTYzQ0MvMXBqdmhTa3cvTjNOTGdHOUZjWExQZDRwdTYzQ2pkS1E4QlE2MTYrMEFGREV0bjF0RldqYU9DV0VXOW5ORzQrTmNVZ0ZKamM0dzZxU3Ruc00rSFdzS0hOTnpRZElRc1pXT3c2bzJUK3ZCOUZmeUY3T3NTNHhwYnF0OW5MZFM1Z0U1M1lFb3FXVmRzK3E1a2xYV3dXdjlBeHRGdlhIVWxyNEE9PQ==</me:data>
// <me:encoding>base64url</me:encoding>
// <me:alg>RSA-SHA256</me:alg>
// <me:sig>sJPN--TJ9IqVwhT_j9mNGrLF4yQvwXUQKo24cPLi5FVXl-tVpyEOxrUI1gwRJ5j5UkkqNJO8mLph2ravlxqt7PNhS9YAOTuo46nXWXyOJjP_ESxq3DaMrYqQt57PDnM29x5yQ0QATbSAs6XneHtxmVKzwKgi4ZpdQ3THFj_iWwac0BI3Or1okt9wLxxl3LTLO9vwfIZaeo-XDNT7JlIfSMZDv1xipjtbl-P0z0q4u2wYOLquvvDjRdI_9vStZK3EOmYARhDXhH0vcJNjVXYTuq16BtXsyfEW3WLBPH67t9Ef3c6cWqU3qPSS3-ddZY5VVq6pPpmtnHuBNzB5hZvZ8asMexc7S0V075ZG-7axUcwXkWKTwCZuxwZNm3VinQze4meWY6vWITtD6zHCguMIWZgxW5z7LGZ04j8_26NbBmZXV52-TFRJExi6H6kUmDb3GrYTlLTOziEUB9pl4NkCX_ghi-Pixbzg7zc_LD_cKXoUyj7iHRNfdfXLck9SOxXkmWAI7hNAswBgx1ngnH6AVyNSsFYVNF1jzc-uTwANfMQqjqq5_XCdE2Z2GFOvFJQ6JK4S-gAEpLygzeltbvYxuK_qqONA9cCTqoJlOxSgQY_lj7h6s__1EuyP9_-Fh4J9MWi9i118ndkmzaswOcxU_VfnHLDgbQbXD5B7zaS1c90=</me:sig>
// </me:env>
// </diaspora>
type PrivateEnvMarshal struct {
XMLName xml.Name `xml:"me:env"`
Me string `xml:"xmlns:me,attr"`
Data struct {
......@@ -59,6 +85,22 @@ type DiasporaMarshal struct {
}
}
type PrivateMarshal struct {
XMLName xml.Name `xml:"diaspora"`
Xmlns string `xml:"xmlns,attr"`
XmlnsMe string `xml:"me,attr"`
EncryptedHeader string `xml:"encrypted_header,omitempty"`
Env PrivateEnvMarshal
}
type PublicMarshal struct {
XMLName xml.Name `xml:"diaspora"`
Xmlns string `xml:"xmlns,attr"`
XmlnsMe string `xml:"me,attr"`
Header Header
Env PrivateEnvMarshal
}
// NOTE I had huge problems with marshal
// and unmarshal with the same structs
// apperently namespaces in tags are a problem
......
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"
)
func (request *PrivateMarshal) EncryptHeader(authorId string, serializedPubKey []byte) (err error) {
// Generate the AES key before you start
// encrypting the plain header
var aesKeySet Aes
err = aesKeySet.Generate()
if err != nil {
return
}
// The actual header
//
// <decrypted_header>
// <iv>...</iv>
// <aes_key>...</aes_key>
// <author_id>one@two.tld</author_id>
// </decrypted_header>
decryptedHeader := XmlDecryptedHeader{
Iv: aesKeySet.Iv,
AesKey: aesKeySet.Key,
AuthorId: authorId,
}
decryptedHeaderXml, err := json.Marshal(decryptedHeader)
if err != nil {
return err
}
err = aesKeySet.Encrypt(decryptedHeaderXml)
if err != nil {
return
}
aesKeySetXml, err := json.Marshal(aesKeySet)
if err != nil {
return err
}
pubKey, err := ParseRSAPubKey(serializedPubKey)
if err != nil {
return err
}
// aes_key
aesKey, err := rsa.EncryptPKCS1v15(rand.Reader, pubKey, aesKeySetXml)
if err != nil {
return err
}
aesKeyEncoded := base64.StdEncoding.EncodeToString(aesKey)
header := JsonEnvHeader{
AesKey: aesKeyEncoded,
Ciphertext: aesKeySet.Data,
}
headerXml, err := json.Marshal(header)
if err != nil {
return err
}
(*request).EncryptedHeader = base64.StdEncoding.EncodeToString(headerXml)
return
}
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
}
......@@ -47,11 +47,14 @@ func pushXml(host, endpoint, proto string, body io.Reader) error {
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/magic-envelope+xml")
client := &http.Client{
Timeout: timeout,
if strings.Contains(endpoint, "public") {
req.Header.Add("Content-Type", "application/magic-envelope+xml")
} else {
req.Header.Add("Content-Type", "application/json")
}
client := &http.Client{Timeout: timeout}
resp, err := client.Do(req)
if err != nil {
if proto == PROTO_HTTPS {
......
......@@ -20,22 +20,27 @@ package federation
import (
"encoding/base64"
"encoding/xml"
//XXX
"encoding/json"
"crypto/rsa"
"crypto/rand"
)
func MagicEnvelope(privkey string, handle, plainXml []byte) (payload []byte, err error) {
func MagicEnvelope(privkey, handle string, plainXml []byte) (payload []byte, err error) {
info("plain xml", string(plainXml))
info("privkey length", len(privkey))
info("handle", string(handle))
info("handle", handle)
data := base64.URLEncoding.EncodeToString(plainXml)
keyId := base64.URLEncoding.EncodeToString(handle)
keyId := base64.URLEncoding.EncodeToString([]byte(handle))
xmlBody := DiasporaMarshal{}
xmlBody.Data.Type = "application/xml"
xmlBody := PrivateEnvMarshal{}
xmlBody.Data.Type = APPLICATION_XML
xmlBody.Data.Data = data
xmlBody.Me = "http://salmon-protocol.org/ns/magic-env"
xmlBody.Encoding = "base64url"
xmlBody.Alg = "RSA-SHA256"
xmlBody.Me = XMLNS_ME
xmlBody.Encoding = BASE64_URL
xmlBody.Alg = RSA_SHA256
xmlBody.Sig.KeyId = keyId
err = xmlBody.Sign(privkey)
......@@ -53,6 +58,98 @@ func MagicEnvelope(privkey string, handle, plainXml []byte) (payload []byte, err
return
}
//func EncryptedMagicEnvelope(privkey, pubkey string, handle, plainXml []byte) (payload []byte, err error) {
//
//}
func EncryptedMagicEnvelope(privkey, pubkey, handle string, serializedXml []byte) (payload []byte, err error) {
info("serialized xml", string(serializedXml))
info("privkey length", len(privkey))
info("pubkey length", len(pubkey))
info("handle", handle)
data := base64.URLEncoding.EncodeToString(serializedXml)
keyId := base64.URLEncoding.EncodeToString([]byte(handle))
// encrypted header
//xmlBody := PrivateMarshal{
// Xmlns: XMLNS,
// XmlnsMe: XMLNS_ME,
xmlBodyEnv := PrivateEnvMarshal{
Me: XMLNS_ME,
Encoding: BASE64_URL,
Alg: RSA_SHA256,
}
//}
xmlBodyEnv.Data.Type = APPLICATION_XML
xmlBodyEnv.Data.Data = data
xmlBodyEnv.Sig.KeyId = keyId
err = xmlBodyEnv.Sign(privkey)
if err != nil {
warn(err)
return
}
// XXX NOTE move below to header file
// Generate a new AES key pair
var (
aesKeySet Aes
aesWrapper AesWrapper
)
err = aesKeySet.Generate()
if err != nil {
warn(err)
return
}
// payload mit aes versch und base64
payload, err = xml.Marshal(xmlBodyEnv)
if err != nil {
warn(err)
return
}
err = aesKeySet.Encrypt(payload)
if err != nil {
warn(err)
return
}
//aesWrapper.MagicEnvelope = base64.StdEncoding.EncodeToString([]byte(aesKeySet.Data))
aesWrapper.MagicEnvelope = aesKeySet.Data
// aes mit pub key versch
aesKeySetXml, err := json.Marshal(aesKeySet)
if err != nil {
warn(err)
return
}
pubKey, err := ParseRSAPubKey([]byte(pubkey))
if err != nil {
warn(err)
return
}
info("aesKeySetXml", string(aesKeySetXml))
// aes_key
aesKey, err := rsa.EncryptPKCS1v15(rand.Reader, pubKey, aesKeySetXml)
if err != nil {
warn(err)
return
}
aesWrapper.AesKey = base64.StdEncoding.EncodeToString(aesKey)
payload, err = json.Marshal(aesWrapper)
if err != nil {
warn(err)
return
}
info("payload", string(payload))
return
}
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