Merge pull request #48 from jaywink/new-magic-envelope

New style Diaspora Magic Envelope support
merge-requests/130/head
Jason Robinson 2016-09-13 11:35:22 +03:00 zatwierdzone przez GitHub
commit 8e29497e91
6 zmienionych plików z 223 dodań i 4 usunięć

Wyświetl plik

@ -1,7 +1,11 @@
## [unreleased]
## Changed
* Deprecate receiving user `key` attribute for Diaspora protocol. Instead correct attribute is now `private_key` for any user passed to `federation.inbound.handle_receive`. We already use `private_key` in the message creation code so this is just to unify the user related required attributes. There is a fallback with `key` for user objects in the receiving payload part of the Diaspora protocol until 0.8.0.
### Added
* New style Diaspora Magic Envelope support. The magic envelope can be created using the class `federation.protocols.diaspora.magic_envelope.MagicEnvelope`. By default this will not wrap the payload message in `<XML><post></post></XML>`. To provide that functionality the class should be initialized with `wrap_payload=True`. No changes are made to the protocol send methods yet, if you need this new magic envelope you can initialize and render it directly.
### Changed
* Deprecate receiving user `key` attribute for Diaspora protocol. Instead correct attribute is now `private_key` for any user passed to `federation.inbound.handle_receive`. We already use `private_key` in the message creation code so this is just to unify the user related required attributes.
* DEPRECATION: There is a fallback with `key` for user objects in the receiving payload part of the Diaspora protocol until 0.8.0.
## [0.5.0] - 2016-09-05

Wyświetl plik

@ -0,0 +1,83 @@
from base64 import urlsafe_b64encode, b64encode
from Crypto.Hash import SHA256
from Crypto.Signature import PKCS1_v1_5 as PKCSSign
from lxml import etree
class MagicEnvelope(object):
"""Diaspora protocol magic envelope.
See: http://diaspora.github.io/diaspora_federation/federation/magicsig.html
"""
nsmap = {
'me': 'http://salmon-protocol.org/ns/magic-env'
}
def __init__(self, message, private_key, author_handle, wrap_payload=False):
"""
Args:
wrap_payload (bool) - Whether to wrap the message in <XML><post></post></XML>.
This is part of the legacy Diaspora protocol which will be removed in the future. (default False)
"""
self.message = message
self.private_key = private_key
self.author_handle = author_handle
self.wrap_payload = wrap_payload
self.doc = None
self.payload = None
def _encode_payload(self):
"""Encode the payload and wrap it to 60 char lines."""
self.payload = urlsafe_b64encode(self.payload).decode("ascii")
self.payload = '\n'.join(
[self.payload[start:start + 60] for start in range(0, len(self.payload), 60)]
)
self.payload += "\n"
return self.payload
def create_payload(self):
"""Create the payload doc.
Returns:
bytes
"""
doc = etree.fromstring(self.message)
if self.wrap_payload:
wrap = etree.Element("XML")
post = etree.SubElement(wrap, "post")
post.append(doc)
doc = wrap
self.payload = etree.tostring(doc, encoding="utf-8")
return self.payload
def _build_signature(self):
"""Create the signature using the private key."""
sig_contents = \
self.payload + "." + \
b64encode(b"application/xml").decode("ascii") + "." + \
b64encode(b"base64url").decode("ascii") + "." + \
b64encode(b"RSA-SHA256").decode("ascii")
sig_hash = SHA256.new(sig_contents.encode("ascii"))
cipher = PKCSSign.new(self.private_key)
sig = urlsafe_b64encode(cipher.sign(sig_hash))
key_id = urlsafe_b64encode(bytes(self.author_handle, encoding="utf-8"))
return sig, key_id
def build(self):
self.doc = etree.Element("{%s}env" % self.nsmap["me"], nsmap=self.nsmap)
etree.SubElement(self.doc, "{%s}encoding" % self.nsmap["me"]).text = 'base64url'
etree.SubElement(self.doc, "{%s}alg" % self.nsmap["me"]).text = 'RSA-SHA256'
self.create_payload()
self._encode_payload()
etree.SubElement(self.doc, "{%s}data" % self.nsmap["me"],
{"type": "application/xml"}).text = self.payload
signature, key_id = self._build_signature()
etree.SubElement(self.doc, "{%s}sig" % self.nsmap["me"], key_id=key_id).text = signature
return self.doc
def render(self):
if self.doc is None:
self.build()
return etree.tostring(self.doc, encoding="unicode")

Wyświetl plik

@ -335,8 +335,11 @@ class Protocol(BaseProtocol):
encrypted header. Selected elements are signed by the author so that
tampering can be detected.
Note, this corresponds to the old Diaspora protocol which will slowly be replaced by the
new version. See PR https://github.com/diaspora/diaspora_federation/issues/30
Args:
recipient - Recipient object which must have public key as `key`
recipient - Recipient object which must have public key as `key` (private messages only)
Returns:
XML document as string

Wyświetl plik

@ -0,0 +1,34 @@
from Crypto.PublicKey import RSA
PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n" \
"MIIEogIBAAKCAQEAiY2JBgMV90ULt0btku198l6wGuzn3xCcHs+eBZHL2C+XWRA3\n" \
"BVDThSBj19dKXehfDphQ5u/Omfm76ImajEPHGBiYtZT7AgcO15zvm+JCpbREbdOV\n" \
"QkST3ANyqCzi+Fk0ZWRwXQTR9m64ML++42iK0BESUbbrVnKipZJ1tE73xs1XBM8J\n" \
"DCOIdM2VBVdDArNJZHGzqugEbDzwh0SqEsKYLE7uzst+eY9vIAbyX80pNzC/d1J8\n" \
"3Pia5WvRV0gtllkMXlGnTIortDJuEr496a8UqfPWDWNg4scCca6aSk/13Q8ClEbP\n" \
"X1sdW4s9yW9OmGg0VMZj+Tca3Jls/3FJosH0yQIDAQABAoIBADVdDGihr9bjGX17\n" \
"7dUPf8oUg/ueJwJ5/idR4ntEqbFwHSY3TTEpvzWpcDKfWkF+UcpmuxQsupkvsn+v\n" \
"Sp7Z+JZXjH79kjeiJ1bskmSGbda9TcLRz9kKo9Y6HDQ0XcV9Tf977L+ZjB8vqxN2\n" \
"gAbXWusHhHThIwHBrWnQnQtbi3K7SzVT3OK0WFfsoAZgYSzfS+4LE0Gs9+ZcK8q7\n" \
"So4BE7/jSjf+Baux92Hes5spi73ltx/BsyEYR5XQVzWfIUg4sX3VDRbpBTW+DBqA\n" \
"G0kUh3CjlsPkZeRSiPrAfk610hQr4HLInGxPkaK+8Fuui2ofM0qYwOeGkNXqlY4Z\n" \
"huhXcFUCgYEAtX0/KoF9k52FbSJdl+2ekeBluU9fJyB3SpGyk5MTKeoAo9I82KyJ\n" \
"tens+5ebj8rUZYHTQfjHsm0ihy4F3GH+huPw4B+RQ8h5BLkU5+KC6pT60M+eMj13\n" \
"bJZkm9n4bInDx9f8Aj4XSG+P2g8h9dBSSm4Ewiqp4CtFTY58uujvMu8CgYEAwgaE\n" \
"5vanfxfk08qvZ7WSxUGfZxp6R2sLjfyB2qL4XJk/8ZpLB17kpYdGhhpk5qWRNmlH\n" \
"vetLp3RZoZRB0JJYq++IkiIq1gfnghgKcSbM8sMXvIT0icBXZU/XTzBVReeRYf9P\n" \
"Sjc+zD/W6L2lXhdZ7z1rGHHvEH/bMQEj3vIQc8cCgYAN6awN9h9KUakI1LmYC/87\n" \
"75fcvNjuhu6eKM0nwv6VF/s0k8lWUuO7rlMcdmLWgxYFMg6f4BJu+y7KbhzE6D46\n" \
"2P5+L+1S5OtiEU4o+JRQp1sS5teZwlyFVoIf8HW63FTF3SjUgy4Fv4enj8Fqtq2Y\n" \
"RxbWS676IFcPuvyU14Z+wQKBgARZWw9GRhjeMz3gFDBx7HlJcEZCXK1PI/Ipz8tT\n" \
"zdddhAZpW/ctVFi1gIou+0YEPg4HLBmAtbBqNjwd85+2OBCajOghpe4oPTM4ULua\n" \
"kAt8/gI2xLh1vD/EG2JmBfNMLoEQ1Pkn5dt0LuAGqDdEtLpdGRJyM1aeVw5xJRmx\n" \
"OVcvAoGAO2keIaA0uB9SszdgovK22pzmkluCIB7ldcjuf/zkjt62nSOOa3mtEAue\n" \
"t/b5Jw+yQVBqNkfJwOMykCxcYs4IEuJelbOYSCp3GmW014nDxYbe5y1Q40drdTro\n" \
"w6Y5FnjFw022w+M3exyH6ZtxcmG6buDbp2F/SPD/FnYy5IFCDig=\n" \
"-----END RSA PRIVATE KEY-----"
def get_dummy_private_key():
return RSA.importKey(PRIVATE_KEY)

Wyświetl plik

@ -8,7 +8,6 @@ import pytest
from federation.exceptions import EncryptedMessageError, NoSenderKeyFoundError, NoHeaderInMessageError
from federation.protocols.diaspora.protocol import Protocol, identify_payload
from federation.tests.factories.entities import DiasporaPostFactory
from federation.tests.fixtures.payloads import ENCRYPTED_DIASPORA_PAYLOAD, UNENCRYPTED_DIASPORA_PAYLOAD

Wyświetl plik

@ -0,0 +1,96 @@
from Crypto import Random
from Crypto.PublicKey import RSA
from lxml.etree import _Element
from federation.protocols.diaspora.magic_envelope import MagicEnvelope
from federation.tests.fixtures.keys import get_dummy_private_key
class TestMagicEnvelope(object):
@staticmethod
def generate_rsa_private_key():
"""Generate a new RSA private key."""
rand = Random.new().read
return RSA.generate(2048, rand)
def test_build(self):
env = MagicEnvelope(
message="<status_message><foo>bar</foo></status_message>",
private_key=get_dummy_private_key(),
author_handle="foobar@example.com"
)
doc = env.build()
assert isinstance(doc, _Element)
def test_create_payload_wrapped(self):
env = MagicEnvelope(
message="<status_message><foo>bar</foo></status_message>",
private_key="key",
author_handle="foobar@example.com",
wrap_payload=True,
)
payload = env.create_payload()
assert payload == b"<XML><post><status_message><foo>bar</foo></status_message></post></XML>"
def test_create_payload(self):
env = MagicEnvelope(
message="<status_message><foo>bar</foo></status_message>",
private_key="key",
author_handle="foobar@example.com"
)
payload = env.create_payload()
assert payload == b"<status_message><foo>bar</foo></status_message>"
def test_encode_payload(self):
env = MagicEnvelope(
message="<status_message><foo>bar</foo></status_message>",
private_key="key",
author_handle="foobar@example.com"
)
env.create_payload()
payload = env._encode_payload()
assert payload == "PHN0YXR1c19tZXNzYWdlPjxmb28-YmFyPC9mb28-PC9zdGF0dXNfbWVzc2Fn\nZT4=\n"
def test_build_signature(self):
env = MagicEnvelope(
message="<status_message><foo>bar</foo></status_message>",
private_key=get_dummy_private_key(),
author_handle="foobar@example.com"
)
env.create_payload()
env._encode_payload()
signature, key_id = env._build_signature()
assert signature == b"RAfiBBrk0OzPbmh6xE7wMRe7ir-qprZ7zk5VDGfopc6rfATFNbNB2FWH" \
b"FdvJfoky9ORNvfUoiFmtbMG7kmmFHgpQdUl_OU81lKb7NG6-aq2ZRVDQ" \
b"T46UYat1ssdqkkynqywowdyEGVUxxalFkOHWuYajmpc7ajt_G8xXjMDU" \
b"Ctt0VUFXepxshd24ZWRXO1RQK4bFr7X9-d26Ho3kLuB1VB_pYYbxJQCZl" \
b"m0EDlFj7vktl0zibswMFyRqiacwu8zec_HR4x8yMkF_zSNJsnnLq6ch4ad6" \
b"r83LOVk3Yvdxinb61spHEjr2zvPWExEgUt4Jcpc07aZRUKCJVfFXFYAGnA=="
assert key_id == b"Zm9vYmFyQGV4YW1wbGUuY29t"
def test_render(self):
env = MagicEnvelope(
message="<status_message><foo>bar</foo></status_message>",
private_key=get_dummy_private_key(),
author_handle="foobar@example.com"
)
env.build()
output = env.render()
assert output == '<me:env xmlns:me="http://salmon-protocol.org/ns/magic-env">' \
'<me:encoding>base64url</me:encoding><me:alg>RSA-SHA256</me:alg>' \
'<me:data type="application/xml">PHN0YXR1c19tZXNzYWdlPjxmb28-Ym' \
'FyPC9mb28-PC9zdGF0dXNfbWVzc2Fn\nZT4=\n</me:data>' \
'<me:sig key_id="Zm9vYmFyQGV4YW1wbGUuY29t">RAfiBBrk0OzPbmh6xE7wMRe' \
'7ir-qprZ7zk5VDGfopc6rfATFNbNB2FWHFdvJfoky9ORNvfUoiFmtbMG7kmmFHgp' \
'QdUl_OU81lKb7NG6-aq2ZRVDQT46UYat1ssdqkkynqywo' \
'wdyEGVUxxalFkOHWuYajmpc7ajt_G8xXjMDUCtt0VUFXepxshd24ZWRX' \
'O1RQK4bFr7X9-d26Ho3kLuB1VB_pYYbxJQCZlm0EDlFj7vktl0zibs' \
'wMFyRqiacwu8zec_HR4x8yMkF_zSNJsnnLq6ch4ad6r83LOVk3Yvdxin' \
'b61spHEjr2zvPWExEgUt4Jcpc07aZRUKCJVfFXFYAGnA==</me:sig></me:env>'
env2 = MagicEnvelope(
message="<status_message><foo>bar</foo></status_message>",
private_key=get_dummy_private_key(),
author_handle="foobar@example.com"
)
output2 = env2.render()
assert output2 == output