experimental ed25519 support
This commit is contained in:
parent
a98f15b533
commit
c488429922
|
@ -0,0 +1,141 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import json
|
||||
import binascii
|
||||
from types import NoneType
|
||||
|
||||
__all__ = (
|
||||
'KeyRing',
|
||||
)
|
||||
|
||||
try:
|
||||
import nacl # noqa
|
||||
HAS_NACL = True
|
||||
except ImportError:
|
||||
HAS_NACL = False
|
||||
|
||||
|
||||
class EncryptedPayload(object):
|
||||
|
||||
def __init__(self, algo, pkey, serializer, payload):
|
||||
self.algo = algo
|
||||
self.pkey = pkey
|
||||
self.serializer = serializer
|
||||
self.payload = payload
|
||||
|
||||
|
||||
if HAS_NACL:
|
||||
from nacl.encoding import Base64Encoder
|
||||
from nacl.public import PrivateKey, Box
|
||||
from nacl.utils import random
|
||||
|
||||
from pytrie import StringTrie
|
||||
|
||||
class KeyRing(object):
|
||||
"""
|
||||
In WAMP, a WAMP client connected to a router in general will
|
||||
send and receive WAMP messages containing application payload
|
||||
in the "args" and "kwargs" message fields.
|
||||
|
||||
Futher, a WAMP client will send the following WAMP message types:
|
||||
|
||||
* PUBLISH
|
||||
* CALL
|
||||
* YIELD
|
||||
* ERROR
|
||||
|
||||
and will receive the following message types
|
||||
|
||||
* EVENT
|
||||
* RESULT
|
||||
* ERROR
|
||||
* INVOCATION
|
||||
|
||||
A keyring maps an URI to a pair of Ed25519 keys, one for the
|
||||
sending side, and one for the receiving side.
|
||||
|
||||
A client wishing to publish to topic T1 first looks up T1 in
|
||||
a keyring. This will return the pair of keys (Sender_key, Receiver_key)
|
||||
that have been stored under an URI K1 such that K1 is a longest match of T1.
|
||||
|
||||
The client generates a new random message key and encrypt the
|
||||
message. The symmetric message encrypted key is then encrypted using
|
||||
the Pub(Receiver_key) and Priv(Sender_key). This encrypted symmetric key
|
||||
is attached to the WAMP message in `option.message_key`.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._uri_map = StringTrie()
|
||||
|
||||
def add(self, uri_prefix, key_base64=None):
|
||||
if uri_prefix not in self._uri_map:
|
||||
if key_base64:
|
||||
key = PrivateKey(key_base64, encoder=Base64Encoder)
|
||||
box = Box(key, key.public_key)
|
||||
else:
|
||||
box = None
|
||||
self._uri_map[uri_prefix] = box
|
||||
print("key set for {}".format(uri_prefix))
|
||||
|
||||
def check(self, uri):
|
||||
"""
|
||||
Return True if sending a message to the URI should have it's
|
||||
application payload args and kwargs encrypted.
|
||||
"""
|
||||
try:
|
||||
box = self._uri_map.longest_prefix_value(uri)
|
||||
return box is not None
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def encrypt(self, uri, args=None, kwargs=None):
|
||||
box = self._uri_map.longest_prefix_value(uri)
|
||||
payload = {
|
||||
u'uri': uri,
|
||||
u'args': args,
|
||||
u'kwargs': kwargs
|
||||
}
|
||||
nonce = random(Box.NONCE_SIZE)
|
||||
payload_ser = json.dumps(payload)
|
||||
payload_encr = box.encrypt(payload_ser, nonce, encoder=Base64Encoder)
|
||||
payload_bytes = payload_encr.encode().decode('ascii')
|
||||
payload_key = binascii.b2a_base64(os.urandom(32)).strip().decode('ascii')
|
||||
|
||||
return EncryptedPayload(u'ed25519-sha512', payload_key, u'json', payload_bytes)
|
||||
|
||||
def decrypt(self, uri, encrypted_payload):
|
||||
box = self._uri_map.longest_prefix_value(uri)
|
||||
payload_ser = box.decrypt(encrypted_payload.payload, encoder=Base64Encoder)
|
||||
payload = json.loads(payload_ser)
|
||||
return payload[u'uri'], payload[u'args'], payload[u'kwargs']
|
||||
|
||||
else:
|
||||
|
||||
KeyRing = NoneType
|
|
@ -27,6 +27,7 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
import six
|
||||
|
||||
import autobahn
|
||||
|
@ -84,6 +85,9 @@ _URI_PAT_LOOSE_LAST_EMPTY = re.compile(r"^([^\s\.#]+\.)*([^\s\.#]*)$")
|
|||
# custom (=implementation specific) WAMP attributes (used in WAMP message details/options)
|
||||
_CUSTOM_ATTRIBUTE = re.compile(r"^x_([a-z][0-9a-z_]+)?$")
|
||||
|
||||
# value for algo attribute in end-to-end encrypted messages
|
||||
_ED25519_SHA512 = u'ed25519-sha512'
|
||||
|
||||
|
||||
def check_or_raise_uri(value, message=u"WAMP message invalid", strict=False, allowEmptyComponents=False):
|
||||
"""
|
||||
|
@ -967,7 +971,11 @@ class Publish(Message):
|
|||
exclude_me=None,
|
||||
exclude=None,
|
||||
eligible=None,
|
||||
disclose_me=None):
|
||||
disclose_me=None,
|
||||
ep_algo=None,
|
||||
ep_key=None,
|
||||
ep_serializer=None,
|
||||
ep_payload=None):
|
||||
"""
|
||||
|
||||
:param request: The WAMP request ID of this request.
|
||||
|
@ -997,14 +1005,21 @@ class Publish(Message):
|
|||
"""
|
||||
assert(type(request) in six.integer_types)
|
||||
assert(type(topic) == six.text_type)
|
||||
assert(args is None or type(args) in [list, tuple])
|
||||
assert(kwargs is None or type(kwargs) == dict)
|
||||
assert(args is None or type(args) in [list, tuple, six.text_type, six.binary_type])
|
||||
assert(kwargs is None or type(kwargs) in [dict, six.text_type, six.binary_type])
|
||||
assert(acknowledge is None or type(acknowledge) == bool)
|
||||
assert(exclude_me is None or type(exclude_me) == bool)
|
||||
assert(exclude is None or type(exclude) == list)
|
||||
assert(eligible is None or type(eligible) == list)
|
||||
assert(disclose_me is None or type(disclose_me) == bool)
|
||||
|
||||
# end-to-end app payload encryption
|
||||
assert(ep_algo is None or ep_algo in [_ED25519_SHA512])
|
||||
assert(ep_key is None or type(ep_key) in [six.text_type, six.binary_type])
|
||||
assert(ep_serializer is None or ep_serializer in [u'json', u'msgpack', u'cbor'])
|
||||
assert(ep_payload is None or (ep_serializer == u'json' and type(ep_payload) == six.text_type) or (ep_serializer in [u'msgpack', u'cbor'] and type(ep_payload) == six.binary_type))
|
||||
assert((ep_algo is None and ep_key is None and ep_serializer is None and ep_payload is None) or (ep_algo is not None and ep_key is not None and ep_serializer is not None and ep_payload is not None))
|
||||
|
||||
Message.__init__(self)
|
||||
self.request = request
|
||||
self.topic = topic
|
||||
|
@ -1016,6 +1031,12 @@ class Publish(Message):
|
|||
self.eligible = eligible
|
||||
self.disclose_me = disclose_me
|
||||
|
||||
# end-to-end app payload encryption
|
||||
self.ep_algo = ep_algo
|
||||
self.ep_key = ep_key
|
||||
self.ep_serializer = ep_serializer
|
||||
self.ep_payload = ep_payload
|
||||
|
||||
@staticmethod
|
||||
def parse(wmsg):
|
||||
"""
|
||||
|
@ -1038,16 +1059,39 @@ class Publish(Message):
|
|||
topic = check_or_raise_uri(wmsg[3], u"'topic' in PUBLISH")
|
||||
|
||||
args = None
|
||||
if len(wmsg) > 4:
|
||||
args = wmsg[4]
|
||||
if type(args) != list:
|
||||
raise ProtocolError("invalid type {0} for 'args' in PUBLISH".format(type(args)))
|
||||
|
||||
kwargs = None
|
||||
if len(wmsg) > 5:
|
||||
kwargs = wmsg[5]
|
||||
if type(kwargs) != dict:
|
||||
raise ProtocolError("invalid type {0} for 'kwargs' in PUBLISH".format(type(kwargs)))
|
||||
|
||||
ep_algo = None
|
||||
ep_key = None
|
||||
ep_serializer = None
|
||||
ep_payload = None
|
||||
|
||||
if len(wmsg) == 5 and type(wmsg[4]) in [six.text_type, six.binary_type]:
|
||||
|
||||
ep_payload = wmsg[4]
|
||||
|
||||
ep_algo = options.get(u'ep_algo', None)
|
||||
if ep_algo != _ED25519_SHA512:
|
||||
raise ProtocolError("invalid value {0} for 'ep_algo' option in PUBLISH".format(ep_algo))
|
||||
|
||||
ep_key = options.get(u'ep_key', None)
|
||||
if type(ep_key) not in [six.text_type, six.binary_type]:
|
||||
raise ProtocolError("invalid type {0} for 'ep_key' option in PUBLISH".format(type(ep_key)))
|
||||
|
||||
ep_serializer = options.get(u'ep_serializer', None)
|
||||
if ep_serializer not in [u'json', u'msgpack', u'cbor']:
|
||||
raise ProtocolError("invalid value {0} for 'ep_serializer' option in PUBLISH".format(ep_serializer))
|
||||
|
||||
else:
|
||||
if len(wmsg) > 4:
|
||||
args = wmsg[4]
|
||||
if type(args) not in [list, six.text_type, six.binary_type]:
|
||||
raise ProtocolError("invalid type {0} for 'args' in PUBLISH".format(type(args)))
|
||||
|
||||
if len(wmsg) > 5:
|
||||
kwargs = wmsg[5]
|
||||
if type(kwargs) not in [dict, six.text_type, six.binary_type]:
|
||||
raise ProtocolError("invalid type {0} for 'kwargs' in PUBLISH".format(type(kwargs)))
|
||||
|
||||
acknowledge = None
|
||||
exclude_me = None
|
||||
|
@ -1111,7 +1155,11 @@ class Publish(Message):
|
|||
exclude_me=exclude_me,
|
||||
exclude=exclude,
|
||||
eligible=eligible,
|
||||
disclose_me=disclose_me)
|
||||
disclose_me=disclose_me,
|
||||
ep_algo=ep_algo,
|
||||
ep_key=ep_key,
|
||||
ep_serializer=ep_serializer,
|
||||
ep_payload=ep_payload)
|
||||
|
||||
return obj
|
||||
|
||||
|
@ -1134,18 +1182,24 @@ class Publish(Message):
|
|||
if self.disclose_me is not None:
|
||||
options[u'disclose_me'] = self.disclose_me
|
||||
|
||||
if self.kwargs:
|
||||
return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.args, self.kwargs]
|
||||
elif self.args:
|
||||
return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.args]
|
||||
if self.ep_payload:
|
||||
options[u'ep_algo'] = self.ep_algo
|
||||
options[u'ep_key'] = self.ep_key
|
||||
options[u'ep_serializer'] = self.ep_serializer
|
||||
return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.ep_payload]
|
||||
else:
|
||||
return [Publish.MESSAGE_TYPE, self.request, options, self.topic]
|
||||
if self.kwargs:
|
||||
return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.args, self.kwargs]
|
||||
elif self.args:
|
||||
return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.args]
|
||||
else:
|
||||
return [Publish.MESSAGE_TYPE, self.request, options, self.topic]
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Returns string representation of this message.
|
||||
"""
|
||||
return "WAMP PUBLISH Message (request = {0}, topic = {1}, args = {2}, kwargs = {3}, acknowledge = {4}, exclude_me = {5}, exclude = {6}, eligible = {7}, disclose_me = {8})".format(self.request, self.topic, self.args, self.kwargs, self.acknowledge, self.exclude_me, self.exclude, self.eligible, self.disclose_me)
|
||||
return "WAMP PUBLISH Message (request = {0}, topic = {1}, args = {2}, kwargs = {3}, acknowledge = {4}, exclude_me = {5}, exclude = {6}, eligible = {7}, disclose_me = {8}, ep_algo = {9}, ep_key = {10}, ep_serializer = {11}, ep_payload_len = {12})".format(self.request, self.topic, self.args, self.kwargs, self.acknowledge, self.exclude_me, self.exclude, self.eligible, self.disclose_me, self.ep_algo, self.ep_key, self.ep_serializer, len(self.ep_payload) if self.ep_payload else 0)
|
||||
|
||||
|
||||
class Published(Message):
|
||||
|
@ -1555,7 +1609,8 @@ class Event(Message):
|
|||
The WAMP message code for this type of message.
|
||||
"""
|
||||
|
||||
def __init__(self, subscription, publication, args=None, kwargs=None, publisher=None, topic=None):
|
||||
def __init__(self, subscription, publication, args=None, kwargs=None, publisher=None, topic=None,
|
||||
ep_algo=None, ep_key=None, ep_serializer=None, ep_payload=None):
|
||||
"""
|
||||
|
||||
:param subscription: The subscription ID this event is dispatched under.
|
||||
|
@ -1580,6 +1635,13 @@ class Event(Message):
|
|||
assert(publisher is None or type(publisher) in six.integer_types)
|
||||
assert(topic is None or type(topic) == six.text_type)
|
||||
|
||||
# end-to-end app payload encryption
|
||||
assert(ep_algo is None or ep_algo in [_ED25519_SHA512])
|
||||
assert(ep_key is None or type(ep_key) in [six.text_type, six.binary_type])
|
||||
assert(ep_serializer is None or ep_serializer in [u'json', u'msgpack', u'cbor'])
|
||||
assert(ep_payload is None or (ep_serializer == u'json' and type(ep_payload) == six.text_type) or (ep_serializer in [u'msgpack', u'cbor'] and type(ep_payload) == six.binary_type))
|
||||
assert((ep_algo is None and ep_key is None and ep_serializer is None and ep_payload is None) or (ep_algo is not None and ep_key is not None and ep_serializer is not None and ep_payload is not None))
|
||||
|
||||
Message.__init__(self)
|
||||
self.subscription = subscription
|
||||
self.publication = publication
|
||||
|
@ -1588,6 +1650,12 @@ class Event(Message):
|
|||
self.publisher = publisher
|
||||
self.topic = topic
|
||||
|
||||
# end-to-end app payload encryption
|
||||
self.ep_algo = ep_algo
|
||||
self.ep_key = ep_key
|
||||
self.ep_serializer = ep_serializer
|
||||
self.ep_payload = ep_payload
|
||||
|
||||
@staticmethod
|
||||
def parse(wmsg):
|
||||
"""
|
||||
|
@ -1610,16 +1678,38 @@ class Event(Message):
|
|||
details = check_or_raise_extra(wmsg[3], u"'details' in EVENT")
|
||||
|
||||
args = None
|
||||
if len(wmsg) > 4:
|
||||
args = wmsg[4]
|
||||
if type(args) != list:
|
||||
raise ProtocolError("invalid type {0} for 'args' in EVENT".format(type(args)))
|
||||
|
||||
kwargs = None
|
||||
if len(wmsg) > 5:
|
||||
kwargs = wmsg[5]
|
||||
if type(kwargs) != dict:
|
||||
raise ProtocolError("invalid type {0} for 'kwargs' in EVENT".format(type(kwargs)))
|
||||
|
||||
ep_algo = None
|
||||
ep_key = None
|
||||
ep_serializer = None
|
||||
ep_payload = None
|
||||
|
||||
if len(wmsg) == 5 and type(wmsg[4]) in [six.text_type, six.binary_type]:
|
||||
|
||||
ep_payload = wmsg[4]
|
||||
|
||||
ep_algo = details.get(u'ep_algo', None)
|
||||
if ep_algo != _ED25519_SHA512:
|
||||
raise ProtocolError("invalid value {0} for 'ep_algo' detail in EVENT".format(ep_algo))
|
||||
|
||||
ep_key = details.get(u'ep_key', None)
|
||||
if type(ep_key) not in [six.text_type, six.binary_type]:
|
||||
raise ProtocolError("invalid type {0} for 'ep_key' detail in EVENT".format(type(ep_key)))
|
||||
|
||||
ep_serializer = details.get(u'ep_serializer', None)
|
||||
if ep_serializer not in [u'json', u'msgpack', u'cbor']:
|
||||
raise ProtocolError("invalid value {0} for 'ep_serializer' detail in EVENT".format(ep_serializer))
|
||||
|
||||
else:
|
||||
if len(wmsg) > 4:
|
||||
args = wmsg[4]
|
||||
if type(args) not in [list, six.text_type, six.binary_type]:
|
||||
raise ProtocolError("invalid type {0} for 'args' in EVENT".format(type(args)))
|
||||
if len(wmsg) > 5:
|
||||
kwargs = wmsg[5]
|
||||
if type(kwargs) not in [dict]:
|
||||
raise ProtocolError("invalid type {0} for 'kwargs' in EVENT".format(type(kwargs)))
|
||||
|
||||
publisher = None
|
||||
if u'publisher' in details:
|
||||
|
@ -1644,7 +1734,11 @@ class Event(Message):
|
|||
args=args,
|
||||
kwargs=kwargs,
|
||||
publisher=publisher,
|
||||
topic=topic)
|
||||
topic=topic,
|
||||
ep_algo=ep_algo,
|
||||
ep_key=ep_key,
|
||||
ep_serializer=ep_serializer,
|
||||
ep_payload=ep_payload)
|
||||
|
||||
return obj
|
||||
|
||||
|
@ -1662,18 +1756,24 @@ class Event(Message):
|
|||
if self.topic is not None:
|
||||
details[u'topic'] = self.topic
|
||||
|
||||
if self.kwargs:
|
||||
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.args, self.kwargs]
|
||||
elif self.args:
|
||||
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.args]
|
||||
if self.ep_payload:
|
||||
details[u'ep_algo'] = self.ep_algo
|
||||
details[u'ep_key'] = self.ep_key
|
||||
details[u'ep_serializer'] = self.ep_serializer
|
||||
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.ep_payload]
|
||||
else:
|
||||
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details]
|
||||
if self.kwargs:
|
||||
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.args, self.kwargs]
|
||||
elif self.args:
|
||||
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.args]
|
||||
else:
|
||||
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details]
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Returns string representation of this message.
|
||||
"""
|
||||
return "WAMP EVENT Message (subscription = {0}, publication = {1}, args = {2}, kwargs = {3}, publisher = {4}, topic = {5})".format(self.subscription, self.publication, self.args, self.kwargs, self.publisher, self.topic)
|
||||
return "WAMP EVENT Message (subscription = {0}, publication = {1}, args = {2}, kwargs = {3}, publisher = {4}, topic = {5}, ep_algo = {6}, ep_key = {7}, ep_serializer = {8}, ep_payload_len = {9})".format(self.subscription, self.publication, self.args, self.kwargs, self.publisher, self.topic, self.ep_algo, self.ep_key, self.ep_serializer, len(self.ep_payload) if self.ep_payload else 0)
|
||||
|
||||
|
||||
class Call(Message):
|
||||
|
|
|
@ -39,6 +39,7 @@ from autobahn.wamp import exception
|
|||
from autobahn.wamp.exception import ApplicationError, ProtocolError, SessionNotReady, SerializationError
|
||||
from autobahn.wamp.interfaces import IApplicationSession # noqa
|
||||
from autobahn.wamp.types import SessionDetails
|
||||
from autobahn.wamp.keyring import EncryptedPayload
|
||||
from autobahn.util import IdGenerator, ObservableMixin
|
||||
|
||||
from autobahn.wamp.request import \
|
||||
|
@ -227,6 +228,7 @@ class ApplicationSession(BaseSession):
|
|||
self._transport = None
|
||||
self._session_id = None
|
||||
self._realm = None
|
||||
self._keyring = None
|
||||
|
||||
self._goodbye_sent = False
|
||||
self._transport_is_closing = False
|
||||
|
@ -430,6 +432,16 @@ class ApplicationSession(BaseSession):
|
|||
for subscription in self._subscriptions[msg.subscription]:
|
||||
|
||||
handler = subscription.handler
|
||||
topic = msg.topic or subscription.topic
|
||||
|
||||
if msg.ep_payload:
|
||||
if self._keyring:
|
||||
encrypted_payload = EncryptedPayload(msg.ep_algo, msg.ep_key, msg.ep_serializer, msg.ep_payload)
|
||||
decrypted_topic, msg.args, msg.kwargs = self._keyring.decrypt(topic, encrypted_payload)
|
||||
if topic != decrypted_topic:
|
||||
raise Exception("envelope topic URI does not match encrypted one")
|
||||
else:
|
||||
raise Exception("received encrypted payload, but no keyring active")
|
||||
|
||||
invoke_args = (handler.obj,) if handler.obj else tuple()
|
||||
if msg.args:
|
||||
|
@ -437,13 +449,15 @@ class ApplicationSession(BaseSession):
|
|||
|
||||
invoke_kwargs = msg.kwargs if msg.kwargs else dict()
|
||||
if handler.details_arg:
|
||||
invoke_kwargs[handler.details_arg] = types.EventDetails(publication=msg.publication, publisher=msg.publisher, topic=msg.topic or subscription.topic)
|
||||
invoke_kwargs[handler.details_arg] = types.EventDetails(publication=msg.publication, publisher=msg.publisher, topic=topic)
|
||||
|
||||
def _error(e):
|
||||
errmsg = 'While firing {0} subscribed under {1}.'.format(
|
||||
handler.fn, msg.subscription)
|
||||
return self._swallow_error(e, errmsg)
|
||||
|
||||
print("$$", invoke_args)
|
||||
|
||||
future = txaio.as_future(handler.fn, *invoke_args, **invoke_kwargs)
|
||||
txaio.add_callbacks(future, None, _error)
|
||||
|
||||
|
@ -458,7 +472,7 @@ class ApplicationSession(BaseSession):
|
|||
publish_request = self._publish_reqs.pop(msg.request)
|
||||
|
||||
# create a new publication object
|
||||
publication = Publication(msg.publication)
|
||||
publication = Publication(msg.publication, was_encrypted=publish_request.was_encrypted)
|
||||
|
||||
# resolve deferred/future for publishing successfully
|
||||
txaio.resolve(publish_request.on_reply, publication)
|
||||
|
@ -821,19 +835,36 @@ class ApplicationSession(BaseSession):
|
|||
if not self._transport:
|
||||
raise exception.TransportLost()
|
||||
|
||||
request_id = self._request_id_gen.next()
|
||||
|
||||
if 'options' in kwargs and isinstance(kwargs['options'], types.PublishOptions):
|
||||
options = kwargs.pop('options')
|
||||
msg = message.Publish(request_id, topic, args=args, kwargs=kwargs, **options.message_attr())
|
||||
else:
|
||||
options = None
|
||||
msg = message.Publish(request_id, topic, args=args, kwargs=kwargs)
|
||||
|
||||
request_id = self._request_id_gen.next()
|
||||
|
||||
if self._keyring and self._keyring.check(topic):
|
||||
encrypted_payload = self._keyring.encrypt(topic, args, kwargs)
|
||||
else:
|
||||
encrypted_payload = None
|
||||
|
||||
if encrypted_payload:
|
||||
msg = message.Publish(request_id,
|
||||
topic,
|
||||
ep_algo=encrypted_payload.algo,
|
||||
ep_key=encrypted_payload.pkey,
|
||||
ep_serializer=encrypted_payload.serializer,
|
||||
ep_payload=encrypted_payload.payload,
|
||||
**options.message_attr())
|
||||
else:
|
||||
if options:
|
||||
msg = message.Publish(request_id, topic, args=args, kwargs=kwargs, **options.message_attr())
|
||||
else:
|
||||
msg = message.Publish(request_id, topic, args=args, kwargs=kwargs)
|
||||
|
||||
if options and options.acknowledge:
|
||||
# only acknowledged publications expect a reply ..
|
||||
on_reply = txaio.create_future()
|
||||
self._publish_reqs[request_id] = PublishRequest(request_id, on_reply)
|
||||
self._publish_reqs[request_id] = PublishRequest(request_id, on_reply, was_encrypted=(encrypted_payload is not None))
|
||||
else:
|
||||
on_reply = None
|
||||
|
||||
|
|
|
@ -51,13 +51,14 @@ class Publication(object):
|
|||
an acknowledged publish).
|
||||
"""
|
||||
|
||||
__slots__ = ('id')
|
||||
__slots__ = ('id', 'was_encrypted')
|
||||
|
||||
def __init__(self, publication_id):
|
||||
def __init__(self, publication_id, was_encrypted):
|
||||
self.id = publication_id
|
||||
self.was_encrypted = was_encrypted
|
||||
|
||||
def __str__(self):
|
||||
return "Publication(id={0})".format(self.id)
|
||||
return "Publication(id={0}, was_encrypted={1})".format(self.id, self.was_encrypted)
|
||||
|
||||
|
||||
class Subscription(object):
|
||||
|
@ -180,6 +181,12 @@ class PublishRequest(Request):
|
|||
Object representing an outstanding request to publish (acknowledged) an event.
|
||||
"""
|
||||
|
||||
__slots__ = ('was_encrypted')
|
||||
|
||||
def __init__(self, request_id, on_reply, was_encrypted):
|
||||
Request.__init__(self, request_id, on_reply)
|
||||
self.was_encrypted = was_encrypted
|
||||
|
||||
|
||||
class SubscribeRequest(Request):
|
||||
"""
|
||||
|
|
|
@ -53,15 +53,21 @@ class ComponentConfig(object):
|
|||
provided to the constructor of :class:`autobahn.wamp.protocol.ApplicationSession`.
|
||||
"""
|
||||
|
||||
def __init__(self, realm=None, extra=None):
|
||||
def __init__(self, realm=None, extra=None, keyring=None):
|
||||
"""
|
||||
:param realm: The realm the session should join.
|
||||
:type realm: unicode
|
||||
|
||||
:param extra: Optional user-supplied object with extra
|
||||
configuration. This can be any object you like, and is
|
||||
accessible in your `ApplicationSession` subclass via
|
||||
`self.config.extra`. `dict` is a good default choice.
|
||||
:type extra: dict or None
|
||||
:param keyring: A mapper from WAMP URIs to "from"/"to" Ed25519 keys. When using
|
||||
WAMP end-to-end encryption, application payload is encrypted using a
|
||||
symmetric message key, which in turn is encrypted using the "to" URI (topic being
|
||||
published to or procedure being called) public key and the "from" URI
|
||||
private key. In both cases, the key for the longest matching URI is used.
|
||||
:type keyring: obj implementing IKeyRing
|
||||
"""
|
||||
realm = realm or u'default'
|
||||
# FIXME
|
||||
|
@ -71,9 +77,10 @@ class ComponentConfig(object):
|
|||
raise RuntimeError('"realm" must be of type Unicode - was {0}'.format(type(realm)))
|
||||
self.realm = realm
|
||||
self.extra = extra
|
||||
self.keyring = keyring
|
||||
|
||||
def __str__(self):
|
||||
return "ComponentConfig(realm = {0}, extra = {1})".format(self.realm, self.extra)
|
||||
return "ComponentConfig(realm = {0}, extra = {1}, keyring = {2})".format(self.realm, self.extra, self.keyring)
|
||||
|
||||
|
||||
class HelloReturn(object):
|
||||
|
@ -359,7 +366,7 @@ class PublishOptions(object):
|
|||
u'exclude_me': self.exclude_me,
|
||||
u'exclude': self.exclude,
|
||||
u'eligible': self.eligible,
|
||||
u'disclose_me': self.disclose_me
|
||||
u'disclose_me': self.disclose_me,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
from __future__ import print_function
|
||||
from os import environ
|
||||
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
|
||||
from autobahn.twisted.util import sleep
|
||||
from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner
|
||||
from autobahn.wamp.types import PublishOptions
|
||||
|
||||
ENCRYPTION_KEY = u'z1JePdJbQkbRCWjldZYImgj5hpsZ2cEtX7CQmQmdta4='
|
||||
|
||||
from autobahn.wamp.keyring import KeyRing
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
"""
|
||||
An application component that publishes an event every second.
|
||||
"""
|
||||
|
||||
@inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
print("session attached")
|
||||
self._keyring = KeyRing()
|
||||
self._keyring.add(u'com.myapp.topic1', ENCRYPTION_KEY)
|
||||
|
||||
def on_message(*args, **kwargs):
|
||||
print("received: args={}, kwargs={}".format(args, kwargs))
|
||||
|
||||
yield self.subscribe(on_message, u'com.myapp.topic1')
|
||||
yield self.subscribe(on_message, u'com.myapp.topic2')
|
||||
|
||||
options = PublishOptions(acknowledge=True, exclude_me=False)
|
||||
counter = 0
|
||||
while True:
|
||||
msg = u"Hello, world! [{}]".format(counter)
|
||||
yield self.publish(u'com.myapp.topic1', msg, options=options)
|
||||
yield self.publish(u'com.myapp.topic2', msg, options=options)
|
||||
print('published', counter)
|
||||
counter += 1
|
||||
yield sleep(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", u"ws://127.0.0.1:8080/ws"),
|
||||
u"realm1",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
3
setup.py
3
setup.py
|
@ -135,7 +135,8 @@ extras_require_all = extras_require_twisted + extras_require_asyncio + \
|
|||
# development dependencies
|
||||
#
|
||||
extras_require_dev = [
|
||||
"pep8>=1.6.2", # MIT license
|
||||
# flake8 will install the version "it needs"
|
||||
# "pep8>=1.6.2", # MIT license
|
||||
"pep8-naming>=0.3.3", # MIT license
|
||||
"flake8>=2.4.1", # MIT license
|
||||
"pyflakes>=0.9.2", # MIT license
|
||||
|
|
Loading…
Reference in New Issue