Need help on keypairs for static authorization

Dear all!

I am using “subset” -version of the autobahn-python/examples/router/.crossbar/config.json file:

{
      "version": 2,
      "workers": [
            {
                  "type": "router",
                  "realms": [
                        {
                              "name": "crossbardemo",
                              "store": {
                                    "type": "memory",
                                    "event-history": [
                                          {
                                                "uri": "com.example.history",
                                                "limit": 100
                                          }
                                    ]
                              },
                              "roles": [
                                    {
                                          "name": "authenticated",
                                          "permissions": [
                                                {
                                                      "uri": "",
                                                      "match": "prefix",
                                                      "allow": {
                                                            "call": true,
                                                            "register": true,
                                                            "publish": true,
                                                            "subscribe": true
                                                      },
                                                      "disclose": {
                                                            "caller": false,
                                                            "publisher": false
                                                      },
                                                      "cache": true
                                                }]}]}],
                  "transports": [
                        {
                              "type": "universal",
                              "endpoint": {
                                    "type": "tcp",
                                    "port": 8080
                              },
                              "websocket": {
                                    "ws": {
                                          "type": "websocket",
                                          "serializers": [
                                                "cbor",
                                                "msgpack",
                                                "ubjson",
                                                "json"
                                          ],
                                          "options": {
                                                "enable_webstatus": true,
                                                "max_frame_size": 1048576,
                                                "max_message_size": 1048576,
                                                "auto_fragment_size": 65536,
                                                "fail_by_drop": true,
                                                "open_handshake_timeout": 2500,
                                                "close_handshake_timeout": 1000,
                                                "auto_ping_interval": 10000,
                                                "auto_ping_timeout": 5000,
                                                "auto_ping_size": 4,
                                                "compression": {
                                                      "deflate": {
                                                            "request_no_context_takeover": false,
                                                            "request_max_window_bits": 13,
                                                            "no_context_takeover": false,
                                                            "max_window_bits": 13,
                                                            "memory_level": 5
                                                      }}}},
                                    "auth_ws": {
                                          "type": "websocket",
                                          "auth": {
                                                "cryptosign": {
                                                      "type": "static",
                                                      "principals": {
                                                            "alice": {
                                                                  "realm": "crossbardemo",
                                                                  "role": "authenticated",
                                                                  "authorized_keys": [                                                                      "020b13239ca0f10a1c65feaf26e8dfca6e84c81d2509a2b7b75a7e5ee5ce4b66"
                                                                  ] }}}}}}}]}]}

I got and build crossbar from git (HEAD on v21.2.1)

And as a client the example from https://autobahn.readthedocs.io/en/latest/wamp/programming.html#patterns-for-more-complicated-applications :

#---------------------------------------------------------------------------------------------------
from autobahn.asyncio.component import Component, run
from asyncio import sleep
from autobahn.wamp.types import RegisterOptions

# to see how this works on the Crossbar.io side, see the example
# router configuration in:
# https://github.com/crossbario/autobahn-python/blob/master/examples/router/.crossbar/config.json

component = Component(
    # you can configure multiple transports; here we use two different
    # transports which both exist in the demo router
    transports=[
        {
            "type": "websocket",
            "url": "ws://localhost:8080/auth_ws",
            "endpoint": {
                "type": "tcp",
                "host": "localhost",
                "port": 8080,
            },
            # you can set various websocket options here if you want
            "options": {
                "open_handshake_timeout": 100,
            }
        },
    ],
    # authentication can also be configured (this will only work on
    # the demo router on the first transport above)
    authentication={
        "cryptosign": {
            'authid': 'alice',
            # this key should be loaded from disk, database etc never burned into code like this...
            'privkey': '6e3a302aa67d55ffc2059efeb5cf679470b37a26ae9ac18693b56ea3d0cd331c',
        }
    },
    # must provide a realm
    realm="crossbardemo",
)
@component.on_join
async def join(session, details):
    print("joined {}: {}".format(session, details))
    await sleep(1)
    print("Calling 'com.example'")
    res = await session.call("example.foo", 42, something="nothing")
    print("Result: {}".format(res))
    await session.leave()
@component.register(
    "example.foo",
    options=RegisterOptions(details_arg='details'),
)
async def foo(*args, **kw):
    print("foo called: {}, {}".format(args, kw))
    for x in range(5, 0, -1):
        print("  returning in {}".format(x))
        await sleep(1)
    print("returning '42'")
    return 42
if __name__ == "__main__":
    run([component])

#---------------------------------------------------------------------------------------------------

Starting crossbar and running the python script as posted above it works as expected. However, when exchanging the private-public-keypair with a pair generated by running

crossbar keygen

I receive:

2021-02-16T20:07:09 wamp.error.not_authorized: extra.pubkey provided does not match any one of authorized_keys for the principal
2021-02-16T20:07:12 session leaving 'wamp.error.not_authorized' 

I am new to crossbar (and autobahn) and spent the last two days with searching the internet for a solution, but without success.
Therefore I am very much looking for help from here!

Thanks a lot in advance!
Markus

Hi!

Quick note to say I edited your post so it formats correctly; you need triple-backticks at the beginning and end of multi-line formatted text. I’ll try to answer (but in a separate post).

thanks,
meejah

Okay, so when you run crossbar keygen it spits out the private key and the (corresponding) public key. You’d put the private-key in the privkey attribute of your Component (the client) and the corresponding public key in the authorized_keys list. This can also be dynamic (e.g. from a database).

These are just normal ed25519 keys formatted in hex (so the privkey is 32 bytes of random data, e.g. from /dev/urandom and the public key is computed from it).

Does that help? (I didn’t try to check if the keys in your example do in fact correspond).

Yes, that what I did. Used crossbar keygen to get a keypair (in Hex-format) and put each part into the respective files.

In addition, I used

import os
from nacl.public import PrivateKey
from nacl.encoding import HexEncoder
priv = os.urandom(32)
print(PrivateKey(priv).encode(HexEncoder))
print(PrivateKey(priv).public_key.encode(HexEncoder))

to generate a key pair, which may be identically to crossbar keygen, though.

Neither way worked. I reckon, it might be something simple that would not come to mind of the experts here, as I have no experience (yet) using crossbar and autobahn. :slight_smile:

Thanks a lot!
Markus

p.s.:
Thank you for editing my initial post!

We have a complete example here that you might want to try to assure yourself everything is working as expected before inserting your new keys:

This also show how to load 32 random bytes as a raw key, and print the public key corresponding to that:

        # load the client private key (raw format)
        try:
            self._key = cryptosign.SigningKey.from_raw_key(config.extra['key'])
        except Exception as e:
            self.log.error(
                "could not load client private key: {log_failure}", log_failure=e)
            self.leave()
        else:
            self.log.info("client public key loaded: {}".format(
                self._key.public_key()))

I’ve just retried it myself … works:

Hmm, I am pretty sure that I did this in a similar fashion. I used the code cited on top, which stems from the example/router - folder (github, crossbar.io) and the component-example as described in the autobahn documentation. The code posted above is 1:1 as I used it. If you like, I could demonstrate it live in a (off-forum) jitsi session. I am really puzzled as I do not doubt your screen shots, too.

Best wishes and thanks a lot
Markus

the advantage of doing exactly the same as me in above would be: if that fails for you, it must be sth in library/installation/environment. if it works, your code is where to look further.

anyways, hope you find it=)

Thank you. Your example worked, of course. When looking at the script I saw that a SigningKey is used. When converting to raw format, or when generating keys from os.urandom(32), respectively I used pynacl’s PrivateKey class which I reckon is different from the SigningKey class.

However, upon storing a new 32-Byte raw private key in a file and loading this file by the client_tx.py script shows the corresponding public key. Putting this ascii-hex code in the config.json configuration of the router enables communication with the new key. Hurray!

So I’ll stick with this workflow. :slight_smile:

Many thanks for the discussion which helped me finding the clue.

Best wishes and a nice weekend
Markus

great! good to hear you got it working.

pynacl’s PrivateKey class which I reckon is different from the SigningKey class

yes, the code in client_tx ultimately ends up using nacl.signing.SigningKey(keydata) to create a private key object that can be used for signing (required for the client to answer the challenge it gets sent during authentication handshake).