chrysn / aiocoap

The Python CoAP library
Other
267 stars 120 forks source link

Code example for simple client/server communication with OSCORE #310

Open SignorMercurio opened 1 year ago

SignorMercurio commented 1 year ago

Background

Thanks for the great library! I'm trying to implement a simple client/server communication demo with OSCORE, and have already read the docs here: https://aiocoap.readthedocs.io/en/latest/stateofoscore.html. However, the docs only include instructions on using the CLIs rather than code examples, which would be very helpful if added to the docs.

Attempts

So I tried to write the demo code for the client:

import asyncio
from aiocoap import *
import logging

logging.basicConfig(level=logging.INFO)

async def main():
    client = await Context.create_client_context()

    client.client_credentials.load_from_dict({
        "coap://localhost/*": {"oscore": {"contextfile": "client1/for-fileserver/"}}
    })

    request1 = Message(
        code=GET, uri='coap://localhost/sensor_data')

    print(request1.remote)
    response1 = await client.request(request1).response

    print("Response from Sensor1: %s" % response1.payload)

if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

As well as the server:

import asyncio
from aiocoap.resource import Resource, Site
from aiocoap import *
import logging

logging.basicConfig(level=logging.DEBUG)

class SensorResource(Resource):
    async def render_get(self, request):
        response = Message(payload=b"Sensor 1 data")
        return response

async def main():
    resource = SensorResource()

    site = Site()
    site.add_resource(('sensor_data',), resource)
    server = await Context.create_server_context(site, bind=('localhost', 5683))
    server.server_credentials.load_from_dict({
        ":client1": {"oscore": {"contextfile": "server/from-client1/"}}
    })
    print("Sensor server is running...")
    await asyncio.sleep(3600)

if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

The OSCORE context here is exactly the same with the docs, but I got an error No Object-Security option present. After some debugging, I believe the problem here is that the communication is using simple6 transport rather than the expected oscore transport.

I'm aware that simple6 is used for platforms like OSX so I switched to Linux, and it's still using udp6, not oscore. I've also tried setting request1.opt.object_security = b'', which resolves the above error but still using simple6 transport. In addition, I tried using the provided CLIs client and fileserver and got the same No Object-Security option present error with the same simple6 transport.

I wonder if there's anything wrong with the above code. I'll be happy to contribute a correct code example for simple client/server communication with OSCORE to the current docs.

Output of python3 -m aiocoap.cli.defaults

Python version: 3.8.9 (default, Feb 18 2022, 07:45:33)
[Clang 13.1.6 (clang-1316.0.21.2)]
aiocoap version: 0.4.7
Modules missing for subsystems:
    dtls: everything there
    oscore: everything there
    linkheader: everything there
    prettyprint: missing termcolor
Python platform: darwin
Default server transports:  oscore:tinydtls:tcpserver:tcpclient:tlsserver:tlsclient:simple6:simplesocketserver
Selected server transports: oscore:tinydtls:tcpserver:tcpclient:tlsserver:tlsclient:simple6:simplesocketserver
Default client transports:  oscore:tinydtls:tcpclient:tlsclient:simple6
Selected client transports: oscore:tinydtls:tcpclient:tlsclient:simple6
SO_REUSEPORT available (default, selected): True, True
MariaKrm commented 6 months ago

I don't know if this is the proper way but based on the code on contrib/oscore-plugtest I got OSCORE working using OscoreSiteWrapper. Using the following on the server in the above example.

from aiocoap.oscore_sitewrapper import OscoreSiteWrapper

...

async def main():
    resource = SensorResource()

    site = Site()
    site.add_resource(('sensor_data',), resource)
    credentials = aiocoap.credentials.CredentialsMap()
    credentials.load_from_dict({
        ":client1": {"oscore": {"contextfile": "server/from-client1/"}}
    })
    site = OscoreSiteWrapper(site, credentials)
    server = await Context.create_server_context(site, bind=('localhost', 5683))

    print("Sensor server is running...")
    await asyncio.sleep(3600)

Not sure how to check which transport is being used here but the network requests seem to be using OSCORE. I'd also like to know if there is a suggested way of setting up a server :smile: