Skip to content

Commit

Permalink
support trustroot/certificate in WAMP-cryptosign (#1569)
Browse files Browse the repository at this point in the history
see changelog
  • Loading branch information
oberstet committed Jun 2, 2022
1 parent 0090f30 commit 91014f8
Show file tree
Hide file tree
Showing 24 changed files with 1,862 additions and 634 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ include requirements-dev.txt
include autobahn/nvx/_utf8validator.c
recursive-include autobahn/wamp/gen/schema *
recursive-include autobahn/asset *
recursive-include autobahn/xbr/templates *
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ upload_exe:
aws cloudfront create-invalidation \
--distribution-id E2QIG9LNGCJSP9 --paths "/xbrnetwork/linux-amd64/*"

mypy:
mypy --install-types --non-interactive autobahn

# WEB3_INFURA_PROJECT_ID must be defined for this
test_infura:
time -f "%e" python -c "from web3.auto.infura import w3; print(w3.isConnected())"
Expand Down Expand Up @@ -253,7 +256,8 @@ test_cs1:
USE_ASYNCIO=1 python -m pytest -s -v autobahn/wamp/test/test_cryptosign.py

test1:
USE_TWISTED=1 trial autobahn.wamp.test.test_auth
USE_TWISTED=1 trial autobahn.wamp.test.test_wamp_uri_pattern
# USE_TWISTED=1 trial autobahn.wamp.test.test_auth
# USE_TWISTED=1 python -m pytest -s -v autobahn/wamp/test/test_auth.py
# USE_TWISTED=1 python -m pytest -s -v autobahn/wamp/test/test_router.py
# USE_ASYNCIO=1 python -m pytest -s -v autobahn/wamp/test/test_router.py
Expand Down
2 changes: 1 addition & 1 deletion autobahn/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@
#
###############################################################################

__version__ = '22.5.1.dev1'
__version__ = '22.5.1.dev2'

__build__ = '00000000-0000000'
4 changes: 2 additions & 2 deletions autobahn/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -962,8 +962,8 @@ def hluserid(oid):
return hl('"{}"'.format(oid), color='yellow', bold=True)


def hlval(val, color='white'):
return hl('{}'.format(val), color=color, bold=True)
def hlval(val, color='white', bold=True):
return hl('{}'.format(val), color=color, bold=bold)


def hlcontract(oid):
Expand Down
328 changes: 326 additions & 2 deletions autobahn/wamp/cryptosign.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@
###############################################################################

import binascii
from binascii import a2b_hex, b2a_hex
import struct
from typing import Callable, Optional, Union
from typing import Callable, Optional, Union, Dict, Any

import txaio

from autobahn import util
from autobahn.wamp.interfaces import ISecurityModule, ICryptosignKey, ISession
from autobahn.wamp.types import Challenge
from autobahn.wamp.message import _URI_PAT_REALM_NAME_ETH

__all__ = [
'HAS_CRYPTOSIGN',
Expand Down Expand Up @@ -512,6 +514,9 @@ def sign_challenge(self, session: ISession, challenge: Challenge,
"""
Implements :meth:`autobahn.wamp.interfaces.ICryptosignKey.sign_challenge`.
"""
assert challenge.method in ['cryptosign', 'cryptosign-proxy'], \
'unexpected cryptosign challenge with method "{}"'.format(challenge.method)

# get the TLS channel ID of the underlying TLS connection
if channel_id_type in session._transport.transport_details.channel_id:
channel_id = session._transport.transport_details.channel_id.get(channel_id_type, None)
Expand Down Expand Up @@ -649,4 +654,323 @@ def from_seedphrase(cls, seedphrase: str, index: int = 0) -> 'CryptosignKey':

ICryptosignKey.register(CryptosignKey)

__all__.extend(['CryptosignKey', 'format_challenge', 'sign_challenge'])
class CryptosignAuthextra(object):
"""
WAMP-Cryptosign authextra object.
"""
__slots__ = [
'_pubkey',
'_trustroot',
'_challenge',
'_channel_binding',
'_channel_id',
'_realm',
'_chain_id',
'_block_no',
'_delegate',
'_seeder',
'_bandwidth',
'_signature',
]

def __init__(self,
pubkey: Optional[bytes] = None,
challenge: Optional[bytes] = None,
channel_binding: Optional[str] = None,
channel_id: Optional[bytes] = None,

# domain address, certificates are verified against owner of the domain
trustroot: Optional[bytes] = None,

# FIXME: add delegate address
# FIXME: add certificates
# FIXME: remove reservation
realm: Optional[bytes] = None,
chain_id: Optional[int] = None,
block_no: Optional[int] = None,
delegate: Optional[bytes] = None,
seeder: Optional[bytes] = None,
bandwidth: Optional[int] = None,

signature: Optional[bytes] = None,
):
if pubkey:
assert len(pubkey) == 32
if trustroot:
assert len(trustroot) == 20
if challenge:
assert len(challenge) == 32
if channel_binding:
assert channel_binding in ['tls-unique']
if channel_id:
assert len(channel_id) == 32
if realm:
assert len(realm) == 20
if delegate:
assert len(delegate) == 20
if seeder:
assert len(seeder) == 20
if signature:
assert len(signature) == 65
self._pubkey = pubkey
self._trustroot = trustroot
self._challenge = challenge
self._channel_binding = channel_binding
self._channel_id = channel_id
self._realm = realm
self._chain_id = chain_id
self._block_no = block_no
self._delegate = delegate
self._seeder = seeder
self._bandwidth = bandwidth
self._signature = signature

@property
def pubkey(self) -> Optional[bytes]:
return self._pubkey

@pubkey.setter
def pubkey(self, value: Optional[bytes]):
assert value is None or len(value) == 20
self._pubkey = value

@property
def trustroot(self) -> Optional[bytes]:
return self._trustroot

@trustroot.setter
def trustroot(self, value: Optional[bytes]):
assert value is None or len(value) == 20
self._trustroot = value

@property
def challenge(self) -> Optional[bytes]:
return self._challenge

@challenge.setter
def challenge(self, value: Optional[bytes]):
assert value is None or len(value) == 32
self._challenge = value

@property
def channel_binding(self) -> Optional[str]:
return self._channel_binding

@channel_binding.setter
def channel_binding(self, value: Optional[str]):
assert value is None or value in ['tls-unique']
self._channel_binding = value

@property
def channel_id(self) -> Optional[bytes]:
return self._channel_id

@channel_id.setter
def channel_id(self, value: Optional[bytes]):
assert value is None or len(value) == 32
self._channel_id = value

@property
def realm(self) -> Optional[bytes]:
return self._realm

@realm.setter
def realm(self, value: Optional[bytes]):
assert value is None or len(value) == 20
self._realm = value

@property
def chain_id(self) -> Optional[int]:
return self._chain_id

@chain_id.setter
def chain_id(self, value: Optional[int]):
assert value is None or value > 0
self._chain_id = value

@property
def block_no(self) -> Optional[int]:
return self._block_no

@block_no.setter
def block_no(self, value: Optional[int]):
assert value is None or value > 0
self._block_no = value

@property
def delegate(self) -> Optional[bytes]:
return self._delegate

@delegate.setter
def delegate(self, value: Optional[bytes]):
assert value is None or len(value) == 20
self._delegate = value

@property
def seeder(self) -> Optional[bytes]:
return self._seeder

@seeder.setter
def seeder(self, value: Optional[bytes]):
assert value is None or len(value) == 20
self._seeder = value

@property
def bandwidth(self) -> Optional[int]:
return self._bandwidth

@bandwidth.setter
def bandwidth(self, value: Optional[int]):
assert value is None or value > 0
self._bandwidth = value

@property
def signature(self) -> Optional[bytes]:
return self._signature

@signature.setter
def signature(self, value: Optional[bytes]):
assert value is None or len(value) == 65
self._signature = value

@staticmethod
def parse(data: Dict[str, Any]) -> 'CryptosignAuthextra':
obj = CryptosignAuthextra()

pubkey = data.get('pubkey', None)
if pubkey is not None:
if type(pubkey) != str:
raise ValueError('invalid type {} for pubkey'.format(type(pubkey)))
if len(pubkey) != 32 * 2:
raise ValueError('invalid length {} of pubkey'.format(len(pubkey)))
obj._pubkey = a2b_hex(pubkey)

challenge = data.get('challenge', None)
if challenge is not None:
if type(challenge) != str:
raise ValueError('invalid type {} for challenge'.format(type(challenge)))
if len(challenge) != 32 * 2:
raise ValueError('invalid length {} of challenge'.format(len(challenge)))
obj._challenge = a2b_hex(challenge)

channel_binding = data.get('channel_binding', None)
if channel_binding is not None:
if type(channel_binding) != str:
raise ValueError('invalid type {} for channel_binding'.format(type(channel_binding)))
if channel_binding not in ['tls-unique']:
raise ValueError('invalid value "{}" for channel_binding'.format(channel_binding))
obj._channel_binding = channel_binding

channel_id = data.get('channel_id', None)
if channel_id is not None:
if type(channel_id) != str:
raise ValueError('invalid type {} for channel_id'.format(type(channel_id)))
if len(channel_id) != 32 * 2:
raise ValueError('invalid length {} of channel_id'.format(len(channel_id)))
obj._channel_id = a2b_hex(channel_id)

trustroot = data.get('trustroot', None)
if trustroot is not None:
if type(trustroot) != str:
raise ValueError('invalid type {} for trustroot - expected a string'.format(type(trustroot)))
if not _URI_PAT_REALM_NAME_ETH.match(trustroot):
raise ValueError('invalid value "{}" for trustroot - expected an Ethereum address'.format(type(trustroot)))
obj._trustroot = a2b_hex(trustroot[2:])

reservation = data.get('reservation', None)
if reservation is not None:
if type(reservation) != dict:
raise ValueError('invalid type {} for reservation'.format(type(reservation)))

chain_id = reservation.get('chain_id', None)
if chain_id is not None:
if type(chain_id) != int:
raise ValueError('invalid type {} for reservation.chain_id - expected an integer'.format(type(chain_id)))
obj._chain_id = chain_id

block_no = reservation.get('block_no', None)
if block_no is not None:
if type(block_no) != int:
raise ValueError('invalid type {} for reservation.block_no - expected an integer'.format(type(block_no)))
obj._block_no = block_no

realm = reservation.get('realm', None)
if realm is not None:
if type(realm) != str:
raise ValueError('invalid type {} for reservation.realm - expected a string'.format(type(realm)))
if not _URI_PAT_REALM_NAME_ETH.match(realm):
raise ValueError('invalid value "{}" for reservation.realm - expected an Ethereum address'.format(type(realm)))
obj._realm = a2b_hex(realm[2:])

delegate = reservation.get('delegate', None)
if delegate is not None:
if type(delegate) != str:
raise ValueError('invalid type {} for reservation.delegate - expected a string'.format(type(delegate)))
if not _URI_PAT_REALM_NAME_ETH.match(delegate):
raise ValueError('invalid value "{}" for reservation.delegate - expected an Ethereum address'.format(type(delegate)))
obj._delegate = a2b_hex(delegate[2:])

seeder = reservation.get('seeder', None)
if seeder is not None:
if type(seeder) != str:
raise ValueError('invalid type {} for reservation.seeder - expected a string'.format(type(seeder)))
if not _URI_PAT_REALM_NAME_ETH.match(seeder):
raise ValueError('invalid value "{}" for reservation.seeder - expected an Ethereum address'.format(type(seeder)))
obj._seeder = a2b_hex(seeder[2:])

bandwidth = reservation.get('bandwidth', None)
if bandwidth is not None:
if type(bandwidth) != int:
raise ValueError('invalid type {} for reservation.bandwidth - expected an integer'.format(type(bandwidth)))
obj._bandwidth = bandwidth

signature = data.get('signature', None)
if signature is not None:
if type(signature) != str:
raise ValueError('invalid type {} for signature'.format(type(signature)))
if len(signature) != 65 * 2:
raise ValueError('invalid length {} of signature'.format(len(signature)))
obj._signature = a2b_hex(signature)

return obj

def marshal(self) -> Dict[str, Any]:
res = {}

# FIXME: marshal check-summed eth addresses

if self._pubkey is not None:
res['pubkey'] = b2a_hex(self._pubkey).decode()

if self._challenge is not None:
res['challenge'] = b2a_hex(self._challenge).decode()
if self._channel_binding is not None:
res['channel_binding'] = self._channel_binding
if self._channel_id is not None:
res['channel_id'] = b2a_hex(self._channel_id).decode()

if self._trustroot is not None:
res['trustroot'] = '0x' + b2a_hex(self._trustroot).decode()

reservation = {}
if self._chain_id is not None:
reservation['chain_id'] = self._chain_id
if self._block_no is not None:
reservation['block_no'] = self._block_no
if self._realm is not None:
reservation['realm'] = '0x' + b2a_hex(self._realm).decode()
if self._delegate is not None:
reservation['delegate'] = '0x' + b2a_hex(self._delegate).decode()
if self._seeder is not None:
reservation['seeder'] = '0x' + b2a_hex(self._seeder).decode()
if self._bandwidth is not None:
reservation['bandwidth'] = self._bandwidth
if reservation:
res['reservation'] = reservation

if self._signature is not None:
res['signature'] = b2a_hex(self._signature).decode()

return res

__all__.extend(['CryptosignKey', 'format_challenge', 'sign_challenge', 'CryptosignAuthextra'])

0 comments on commit 91014f8

Please sign in to comment.