Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WAMPCRA does not support salting #2120

Closed
Mahad-10 opened this issue Jan 7, 2025 · 10 comments
Closed

WAMPCRA does not support salting #2120

Mahad-10 opened this issue Jan 7, 2025 · 10 comments

Comments

@Mahad-10
Copy link
Contributor

Mahad-10 commented Jan 7, 2025

As mentioned in the wamp proto specification: https://wamp-proto.org/wamp_ap_latest_ietf.html#section-5.2-31, wampcra should support salting but crossbar doesn't support it.

Steps to reproduce:

My config.json file looks like this:

{
  "version": 2,
  "controller": {},
  "workers": [
    {
      "type": "router",
      "realms": [
        {
          "name": "realm1",
          "roles": [
            {
              "name": "anonymous",
              "permissions": [
                {
                  "uri": "*",
                  "allow": {
                    "publish": true,
                    "subscribe": true,
                    "call": true,
                    "register": true
                  }
                }
              ]
            }
          ]
        }
      ],
      "transports": [
        {
          "type": "websocket",
          "endpoint": {
            "type": "tcp",
            "port": 8080
          },
          "auth": {
            "wampcra": {
              "type": "static",
              "users": {
                "bob": {
                  "secret": "bob-secret",
                  "role": "anonymous",
                  "salt": "salt123",
                  "iterations": 1000,
                  "keylen": 32
                }
              }
            }
          }
        }
      ]
    }
  ]
}

The client code to test it:

from twisted.internet.defer import inlineCallbacks

import txaio

txaio.use_twisted()

from autobahn.wamp.serializer import JsonSerializer
from autobahn.twisted.wamp import ApplicationSession
from autobahn.twisted.util import sleep
from autobahn.wamp import auth
from autobahn.twisted.wamp import ApplicationRunner

USER = "bob"
USER_SECRET = "bob-secret"


class ClientSession(ApplicationSession):

    def onConnect(self):
        self.join(self.config.realm, ["wampcra"], USER)

    def onChallenge(self, challenge):
        if challenge.method == "wampcra":
            signature = auth.compute_wcs(USER_SECRET, challenge.extra['challenge'])

            return signature

        else:
            raise Exception("Invalid authmethod {}".format(challenge.method))

    @inlineCallbacks
    def onJoin(self, details):
        print("Client session joined: {}".format(details))
        yield sleep(1)

        res = yield self.publish('com.example.add2', 2, 3)

        self.leave()

    def onLeave(self, details):
        self.disconnect()

if __name__ == '__main__':
    serializers = [JsonSerializer(batched=False)]

    runner = ApplicationRunner(url='ws://localhost:8080/ws', realm='realm1', serializers=serializers)
    runner.run(ClientSession)

This should result in error as i have not derived key from salt and passed it directly. But it works fine.

@om26er
Copy link
Contributor

om26er commented Jan 11, 2025

Yeah, I think would be a good first contribution to this project

@oberstet
Copy link
Contributor

oberstet commented Jan 12, 2025

Crossbar.io does support salting with WAMP-CRA. pls see

https://github.com/crossbario/crossbar-examples/blob/master/authentication/wampcra/static/client.py

and and other salting examples, e.g. WAMP-SCRAM does feature salting as well (and is also supported in Crossbar.io) https://github.com/crossbario/crossbar-examples/tree/master/authentication#crossbario-authentication

@Mahad-10
Copy link
Contributor Author

@oberstet The provided example doesn't work with salt. I have created a PR to address the issue. #2121

@oberstet
Copy link
Contributor

The provided example doesn't work with salt

it does, pls see the logs there

I have created a PR to address the issue

sorry, I can't see any issue

@Mahad-10
Copy link
Contributor Author

Here's my setup:
Crossbar version:

maddy@office-pc:~/scm/crossbar-examples/authentication/wampcra/static$ crossbar version


    :::::::::::::::::
          :::::          _____                 __              _____  __
    :::::   :   :::::   / ___/______  ___ ___ / /  ___ _____  / __/ |/_/
    :::::::   :::::::  / /__/ __/ _ \(_-<(_-</ _ \/ _ `/ __/ / _/_>  <
    :::::   :   :::::  \___/_/  \___/___/___/_.__/\_,_/_/   /_/ /_/|_|
          :::::
    :::::::::::::::::   Crossbar.io FX v22.2.1 [19000101-0000000]

    Copyright (c) 2013-2025 Crossbar.io Technologies GmbH. All rights reserved.

 Crossbar.io        : 22.2.1
   txaio            : 22.2.1
   Autobahn         : 22.2.2
     UTF8 Validator : wsaccel-0.6.2
     XOR Masker     : wsaccel-0.6.2
     JSON Codec     : stdlib
     MsgPack Codec  : msgpack-1.0.3
     CBOR Codec     : cbor2-5.4.2.post1
     UBJSON Codec   : ubjson-0.16.1
     FlatBuffers    : flatbuffers-2.0
   Twisted          : 22.1.0-EPollReactor
   LMDB             : 1.3.0/lmdb-0.9.29
   Python           : 3.8.10/CPython
   PIP              : 
   NumPy            : 1.22.2
   zLMDB            : 22.2.1
   CFXDB            : 22.2.2
   XBR              : 21.2.1
 Frozen executable  : no
 Operating system   : Linux-6.8.0-51-generic-x86_64-with-glibc2.29
 Host machine       : x86_64
 Release key        : RWRXn6E3fKGODnVOfeff9H/E+lc9hRgd+9wrOFbtFzqHcqxqusmUUo41

I have minimized the config https://github.com/crossbario/crossbar-examples/blob/master/authentication/wampcra/static/.crossbar/config.json from examples to this:

{
    "version": 2,
    "workers": [
        {
            "type": "router",
            "id": "test_router1",
            "options": {
                "pythonpath": [
                    ".."
                ]
            },
            "realms": [
                {
                    "name": "realm1",
                    "roles": [
                        {
                            "name": "anonymous",
                            "permissions": [
                                {
                                    "uri": "",
                                    "match": "prefix",
                                    "allow": {
                                        "call": true,
                                        "register": true,
                                        "publish": true,
                                        "subscribe": true
                                    },
                                    "disclose": {
                                        "caller": true,
                                        "publisher": true
                                    },
                                    "cache": true
                                }
                            ]
                        },
                        {
                            "name": "backend",
                            "permissions": [
                                {
                                    "uri": "",
                                    "match": "prefix",
                                    "allow": {
                                        "call": true,
                                        "register": true,
                                        "publish": true,
                                        "subscribe": true
                                    },
                                    "disclose": {
                                        "caller": false,
                                        "publisher": false
                                    },
                                    "cache": true
                                },
                                {
                                    "uri": "com.example.topic2",
                                    "match": "exact",
                                    "allow": {
                                        "call": false,
                                        "register": false,
                                        "publish": false,
                                        "subscribe": false
                                    },
                                    "disclose": {
                                        "caller": false,
                                        "publisher": false
                                    },
                                    "cache": true
                                }
                            ]
                        },
                        {
                            "name": "frontend",
                            "permissions": [
                                {
                                    "uri": "com.example.add2",
                                    "match": "exact",
                                    "allow": {
                                        "call": true,
                                        "register": false,
                                        "publish": false,
                                        "subscribe": false
                                    },
                                    "disclose": {
                                        "caller": false,
                                        "publisher": false
                                    },
                                    "cache": true
                                },
                                {
                                    "uri": "com.example.",
                                    "match": "prefix",
                                    "allow": {
                                        "call": false,
                                        "register": false,
                                        "publish": true,
                                        "subscribe": false
                                    },
                                    "disclose": {
                                        "caller": false,
                                        "publisher": false
                                    },
                                    "cache": true
                                },
                                {
                                    "uri": "com.example.topic2",
                                    "match": "exact",
                                    "allow": {
                                        "call": false,
                                        "register": false,
                                        "publish": false,
                                        "subscribe": false
                                    },
                                    "disclose": {
                                        "caller": false,
                                        "publisher": false
                                    },
                                    "cache": true
                                },
                                {
                                    "uri": "com.foobar.topic1",
                                    "match": "exact",
                                    "allow": {
                                        "call": false,
                                        "register": false,
                                        "publish": true,
                                        "subscribe": false
                                    },
                                    "disclose": {
                                        "caller": false,
                                        "publisher": false
                                    },
                                    "cache": true
                                }
                            ]
                        }
                    ]
                }
            ],
            "transports": [
                {
                    "type": "web",
                    "endpoint": {
                        "type": "tcp",
                        "port": 8080
                    },
                    "paths": {
                        "/": {
                            "type": "static",
                            "directory": "../web"
                        },
                        "info": {
                            "type": "nodeinfo"
                        },
                        "ws": {
                            "type": "websocket",
                            "serializers": ["json", "msgpack", "cbor"],
                            "auth": {
                                "wampcra": {
                                    "type": "static",
                                    "users": {
                                        "client1": {
                                            "secret": "${MYSECRET}",
                                            "role": "frontend"
                                        },
                                        "client2": {
                                            "secret": "${MYSECRET}",
                                            "role": "frontend",
                                            "salt": "salt123",
                                            "iterations": 100,
                                            "keylen": 16
                                        }
                                    }
                                },
                                "cookie": {}
                            }
                        }
                    }
                }
            ],
            "components": [
                {
                    "type": "class",
                    "classname": "backend.BackendSession",
                    "realm": "realm1",
                    "role": "backend"
                }
            ]
        }
    ]
}

The client code is as follow:

import os
import sys

from twisted.internet import reactor
from twisted.internet.defer import inlineCallbacks

import txaio

txaio.use_twisted()

from autobahn.wamp.serializer import JsonSerializer
from autobahn.twisted.wamp import ApplicationSession
from autobahn.twisted.util import sleep
from autobahn.wamp.types import PublishOptions
from autobahn.wamp import auth

if 'MYSECRET' in os.environ and len(sys.argv) > 1:
    USER = sys.argv[1]
    USER_SECRET = os.environ['MYSECRET']
else:
    raise RuntimeError('missing authid or auth secret (from env var MYSECRET)')


class ClientSession(ApplicationSession):

    def onConnect(self):
        print("Client session connected. Starting WAMP-CRA authentication on realm '{}' as user '{}' ..".format(
            self.config.realm, USER))
        self.join(self.config.realm, ["wampcra"], USER)

    def onChallenge(self, challenge):
        if challenge.method == "wampcra":
            print("WAMP-CRA challenge received: {}".format(challenge))

            if 'salt' in challenge.extra:
                # salted secret
                key = auth.derive_key(USER_SECRET,
                                      challenge.extra['salt'],
                                      challenge.extra['iterations'],
                                      challenge.extra['keylen'])
            else:
                # plain, unsalted secret
                key = USER_SECRET

            signature = auth.compute_wcs(key, challenge.extra['challenge'])
            return signature

        else:
            raise Exception("Invalid authmethod {}".format(challenge.method))

    @inlineCallbacks
    def onJoin(self, details):
        print("Client session joined: {}".format(details))
        yield sleep(1)

        try:
            res = yield self.call('com.example.add2', 2, 3)
            print("call result: {}".format(res))
        except Exception as e:
            print("call error: {}".format(e))

        try:
            reg = yield self.register(lambda x, y: x * y, 'com.example.mul2')
        except Exception as e:
            print("registration failed - this is expected: {}".format(e))

        for topic in [
            'com.example.topic1',
            'com.foobar.topic1']:
            try:
                yield self.publish(topic, "hello", options=PublishOptions(acknowledge=True))
                print("ok, event published to topic {}".format(topic))
            except Exception as e:
                print("publication to topic {} failed: {}".format(topic, e))

        for topic in [
            'com.example.topic2',
            'com.foobar.topic2']:
            try:
                yield self.publish(topic, "hello", options=PublishOptions(acknowledge=True))
                print("ok, event published to topic {}".format(topic))
            except Exception as e:
                print("publication to topic {} failed - this is expected: {}".format(topic, e))

        self.leave()

    def onLeave(self, details):
        print("Client session left: {}".format(details))
        self.config.extra['exit_details'] = details
        self.disconnect()

    def onDisconnect(self):
        print("Client session disconnected.")
        reactor.stop()


if __name__ == '__main__':

    from autobahn.twisted.wamp import ApplicationRunner

    extra = {
        'exit_details': None,
    }

    serializers = [JsonSerializer(batched=False)]

    runner = ApplicationRunner(url='ws://localhost:8080/ws', realm='realm1', extra=extra, serializers=serializers)
    runner.run(ClientSession)

    # CloseDetails(reason=<wamp.error.not_authorized>, message='WAMP-CRA signature is invalid')
    print(extra['exit_details'])

    if not extra['exit_details'] or extra['exit_details'].reason != 'wamp.close.normal':
        sys.exit(1)
    else:
        sys.exit(0)

Output:

(.venv) maddy@office-pc:~/scm/crossbar-examples/authentication/wampcra/static$ python3 client.py client2
2025-01-15T15:47:30+0500 Client session connected. Starting WAMP-CRA authentication on realm 'realm1' as user 'client2' ..
2025-01-15T15:47:30+0500 WAMP-CRA challenge received: Challenge(method=wampcra, extra={'challenge': '{"authid": "client2", "authrole": "frontend", "authmethod": "wampcra", "authprovider": "static", "session": 8036499724115709, "nonce": "d7cqdHkl2W1QeE5Kdgza6ceuKd4IkWuUNiGYu2YTRONq9exagCFSoexe85m0jAJm", "timestamp": "2025-01-15T10:47:30.603Z"}', 'salt': 'salt123', 'iterations': 100, 'keylen': 16})
2025-01-15T15:47:30+0500 malok
2025-01-15T15:47:30+0500 Client session left: CloseDetails(reason=<wamp.error.not_authorized>, message='WAMP-CRA signature is invalid')
2025-01-15T15:47:30+0500 Client session disconnected.
2025-01-15T15:47:30+0500 Main loop terminated.
2025-01-15T15:47:30+0500 CloseDetails(reason=<wamp.error.not_authorized>, message='WAMP-CRA signature is invalid')

@om26er
Copy link
Contributor

om26er commented Jan 15, 2025

I need to mention that with the proposed change, the issue indeed gets resolved.

We are writing a WAMP interoperability suite for different languages and came across this issue during that testing.

@oberstet
Copy link
Contributor

the issue indeed gets resolved

what issue? what's the Crossbar.io issue number?

We are writing a WAMP interoperability suite for different languages

sounds great!! definitely welcome.

not sure though:

"a WAMP interoperability suite": where can I look?

do you want to integrate that with or help add to the CI tests here in Crossbar.io?

@Mahad-10
Copy link
Contributor Author

what issue? what's the Crossbar.io issue number?

It's this current issue that i have opened #2120 (salt doesn't work with WAMPCRA), You can test the code that i have pasted.

@oberstet
Copy link
Contributor

well, the issue is closed, I don't have time to run your stuff, but as said, it works for me.

if you think something is wrong, a proper bug report is needed first, which includes both your new failing example with logs and including logs of the existing example I posted shown to either also fail with the data of your example - or not fail in itself (but then it should, as yours?).

once that is there, I would be convinced there is an issue at all.

at that point, what we next need is: an automated test integrated into the Crossbar.io CI/CD which demonstrates the issue and allows us then to track its fixture, as well as continue to monitor that no regressions creap in thereafter.

once we have the new failing CICD test, finally (in a separate PR) a fix can be merged and integrated!

if that sounds like a lot of work and non-trivial: yes, I agree!

however, in my experience and judgement, the alternative of "just merge and move on" wild west approach is not appropriate for Crossbar.io at this point anymore, we need the full shebang.

if you say, "that is all cool, but too much work/effort for me": sad, but I understand, and that'd be perfectly fine!

@Mahad-10
Copy link
Contributor Author

Mahad-10 commented Jan 17, 2025

@oberstet I have opened a new issue #2123 which contains the whole setup details and the logs for failing example & a working one also.

at that point, what we next need is: an automated test integrated into the Crossbar.io CI/CD which demonstrates the issue and allows us then to track its fixture, as well as continue to monitor that no regressions creap in thereafter.

Here's the failing CI/CD test #2122, and it should work after merging #2121

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants