Skip to content

Conversation

@MauriceHendrix
Copy link
Contributor

@MauriceHendrix MauriceHendrix commented Mar 4, 2025

Started S2 pairing implementation

@MauriceHendrix MauriceHendrix marked this pull request as draft March 4, 2025 14:45
@lfse-slafleur
Copy link
Member

Hea @MauriceHendrix ! Thank you for adding this novel item to the library. Note that this library is being used by others in running systems that do not know about S2Pairing. Could you keep an eye on your implementation that it doesn't break existing features?

@MauriceHendrix
Copy link
Contributor Author

Sure @lfse-slafleur. This PR is just a draft still.
I don't think there should be any need to make any breaking changes to anything existing, I guess we can add the required tokens as optional parameter to S2Connection

by the way could you give TNO (@wcoenraads, @jorritn @MauriceHendrix) access to pypi?

@dolfandringa
Copy link

dolfandringa commented Mar 7, 2025

I was looking at ideas for this too. I think we could combine OAuth2 for authentication with device discovery. Authentication is going to be necessary with websockets in production in any case. And by using the OAuth2 Device Authorization Flow, existing users can authorize devices quite easily. I think the way would be for them to have a mobile app for instance (outside S2 scope) with the hardware communicating over http or bluetooth or NFC, where the user authorizes the device to the oauth2 provider. Any required data the CEM needs for the device can then be committed to the CEM that way as well (again, out-of-scope) for S2.
Whatever the oauth2 provider is, all S2/CEM need to care about then is whether the device authenticates with a valid token.

# via pip-tools
zipp==3.20.1
# via importlib-metadata
jwskate==0.11.1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PyJWT also does JWKS https://pyjwt.readthedocs.io/en/stable/usage.html#retrieve-rsa-signing-keys-from-a-jwks-endpoint. I'd generally go with the library that hast most community adoption. PyJWT also has sponsorship from Auth0. I don't have experience with jwskate, but judging from the github repositories, pyjwt has much wider spread adoption and support.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did start by looking at PyJWT but everything I've read indicates that it can only sign with the private key and verify with the public key (or use the same key to sign & verify). What we need is the opposite, according to the spec:

  • The server gets public key from client, uses it to encrypt challenge and client decrypts and uses it.

image

Copy link

@dolfandringa dolfandringa Mar 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we need is the opposite, according to the spec

Is this already part of the S2 standard?

I think there are industry standard solutions that don't require us to invent custom logic here.

What I would suggest is to put OAuth2 authentication on the websocket endpoint. The resulting JWT token that is then issued by the authentication provider contains the data that is needed to start the S2 session and can be read as-is. All the websocket endpoint needs to do is verify the jwt token was issued by a valid issuer. There is no custom S2 code necessary before the HTTPUpgrade. All the websocket endpoint needs to do is verify that the jwt token that the RM provides was signed by a trusted issuer (which JWKS can be used for).
Encryption isn't really necessary either unless we need to put privacy/security sensitive stuff in the jwt token.

Further encryption of course can be handled with HTTPS/TLS.

As for the discovery side:
The only assumption when implementing OAuth for this is: The new device that starts communicating with the CEM is owned by a person/company/etc that has some type of existing relation with the CEM supplier.

With the OAuth Device Authorization flow, during hardware setup, the user would authenticate with the CEM provider (in a mobile app for instance), "add" a device, receive the required data for the new device, and store it in the hardware. This could be done over bluetooth, local network http, cloud api, nfc, etc in a mobile app either from the hardware supplier, or from the CEM provider. The device then retrieves its own token from the oauth provider (which is unique for the hardware and can be revoked by the user) and uses that from then on to authenticate when making an S2 connection to the websocket endpoint.

The elegance of this is that S2 does not need to have any knowledge of any of it. It only needs to make sure it trusts the right authorization provider.

image
Source: https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow

@lfse-slafleur
Copy link
Member

Sure @lfse-slafleur. This PR is just a draft still. I don't think there should be any need to make any breaking changes to anything existing, I guess we can add the required tokens as optional parameter to S2Connection

by the way could you give TNO (@wcoenraads, @jorritn @MauriceHendrix) access to pypi?

@MauriceHendrix Much appreciated! Just requested your pypi account names through e-mail (send it to Jorrit & Wester as I do not have your e-mail yet).

added example using first paring and then using the details, connection uri and token in the S2 websocket connection
s2_connection.stop()

def start_s2_session(url, client_node_id=str(uuid.uuid4())):
def start_s2_session(url, client_node_id=str(uuid.uuid4()), bearer_token=None):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solution to the challenge

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@VladIftime one thing to keep in mind: we'd like it to be backwards compatible so that existing code will keep working when the S2 pairing is eventually put into the main library. That's why I added the default of None.

+ '/' + connection_details.connectionUri.lstrip('/')
logger.info('connectionUri %s ', connection_details.connectionUri)

challenge: Mapping[str, Any] = json.loads(JweCompact(connection_details.challenge).decrypt(rsa_key_pair))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the 'solving' of the challenge

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

Successfully merging this pull request may close these issues.

5 participants