thingsboard icon indicating copy to clipboard operation
thingsboard copied to clipboard

[Feature Request] Two-way SSL: parse device ID from client certificate's subject

Open poelstra-sioux opened this issue 2 years ago • 1 comments

Is your feature request related to a problem? Please describe.

We're setting up devices that need to talk to several backend services, including our own REST API's and ThingsBoard's MQTT. Therefore, we're giving each device its own client certificate, and have the device's ID encoded into the certificate's subject (CN). Every service is set up to trust the root / intermediate signing authority, such that we don't need each service to include a list of all client certificates: they can simply look at the subject, and parse the relevant ID out of it.

ThingsBoard currently does support two-way SSL, but (as far as I can tell) requires each certificate to be explicitly added to each TB device.

Note that we want to regularly rotate device certificates (and their private keys), similar to how LetsEncrypt works. When the certificate changes, the certificate in TB currently also needs to be updated explicitly. See downsides in workarounds, below.

Describe the solution you'd like

Have an option in TB to trust all client certificates from a specific trust anchor(s), i.e. our intermediate or root certificate. Then, have an option of specifying a regex (or other simple pattern matcher) to parse the device ID out of the subject's CN.

Current workarounds

  1. Update TB certificates from the certificate authority every time the certificate changes.
  • Downsides: a. Additional machinery in certificate authority to keep TB in sync. If this sync fails, devices can't connect. b. In case the new certificate was generated, but not yet actually received by the device due to e.g. network glitch, the device can't access TB until it eventually receives the new cert (but cert retries may be relatively low-frequency, esp if its current certificate is still valid for some time).
  1. Have additional TLS front-end verify the client certificate (using the root cert), to provide some layer of security, yet have the actual login to TB done using the 'old' mechanism of access token.
  • Downsides: a. Allows someone to connect to the TLS endpoint using client cert for device A, but still login in TB using access token for device B

Additional context

For example, the CN of our client certs will look like cda15b12-ba52-11ec-8422-0242ac120002@IMEI-356482109825238, where the part before the @ would be the TB device ID, so the regex would be something like ^([^@]+)@.

poelstra-sioux avatar Jun 16 '22 14:06 poelstra-sioux

Valid feature request.

Uploading a root/intermediate CA public key cert to trust devices authenticating with client certs signed by this CA (or it's intermediates) is standard for many iot platforms (eg Azure IoT Central, Cumulocity IoT).

Maybe it would be nice to add this feature to a device profile in the (provisioning) settings so it applies to all devices using this profile.

Kind Regards Dominic

flexarts avatar Sep 12 '22 06:09 flexarts

This is a valid and doable request. We will consider adding this to 3.5. @ShvaykaD will review and provide a design for your comments.

ashvayka avatar Nov 03 '22 11:11 ashvayka

Adding to @poelstra-sioux suggestion to parse out the TB deviceID from the CN of the certificate, I would also suggest to allow identification via Device "Attributes" in the CN instead of the TB internal deviceID.

flexarts avatar Nov 03 '22 11:11 flexarts

@flexarts Yes, that's probably an even better idea. We can always have a simple rulechain that adds the device's UUID to a server attribute of that same device.

Still, it would be useful (for our case) to be able to configure that only a part of the CN would then be matched against that attribute.

poelstra-sioux avatar Nov 04 '22 13:11 poelstra-sioux

Hi @poelstra-sioux, could you clarify if the second part of CN (after @) may change during certificate rotation?

ShvaykaD avatar Nov 11 '22 13:11 ShvaykaD

@ShvaykaD In our case it won't change due to certificate rotation, but it may change due to other reasons.

In our case, the subject is composed as <assetId>@<hardwareId>.

  • <assetId> corresponds to the Thingsboard Device (we call it 'asset' in our domain).
  • <hardwareId> corresponds to a unique ID of the physical device currently assigned to this <assetId>

It may be that the physical hardware e.g. breaks down at a certain point and needs to be replaced by another. In that case, that hardware will get a new certificate, with the same <assetId> but a different <hardwareId>.

So this is why we can't just use the subject as-is, but need to parse a specific piece out of it. (Alternatively, we'd need to update the device's attribute when such replacement happens, but I'd like to keep these concepts separated.)

poelstra-sioux avatar Nov 11 '22 13:11 poelstra-sioux

Hi @poelstra-sioux, thanks for the update. We reviewed provided design proposal with the team. Please check my comments below for both approaches:

  1. Using a device profile to store the regex and TB deviceID as authentification identifier <assetId>:

    In the authentication stage, we don't know from which device profile we should fetch the regex and even which tenant we should use for device profile lookup. In this approach, we might have performance issues in the case of a multitenancy solution and authentication failure if we will use incorrect regex.

  2. Using a device profile to store the regex and device attributes to store the authentification identifier<assetId>.

The same issue as for device profiles, but it might be even more complex cause the number of devices usually far exceeds the number of device profiles. As a result, we will need to iterate over all the devices in the system and, for each of them, look up the attribute with an authentification identifier and then, using regex check the CN. This approach will be a performance killer.

Let me suggest you our design idea:

Instead of using device profiles, we will use the .yml configuration parameter(Environment variable), which will act as a map between tenantId and a regular expression that should be used to look up device in that tenant. So, as a result, we will need to iterate over the map and, on each iteration, do a lookup of the device by tenant id map key and device id extracted by regex from the CN. If the device is found on some iteration, we will break the loop and send the connection accepted response, otherwise, if the device lookup failed for all iterations the connection refused response will be sent.

ShvaykaD avatar Nov 15 '22 12:11 ShvaykaD

@ShvaykaD What i am missing in the yaml solution is:

  1. a way to do it as a tenant admin inside the platform in an multitenant solution
  2. a plan where the public certificate(s) of the CA or intermediate CA is configured and stored? (i would suggest at the tenant level)

cheers dominic

flexarts avatar Nov 15 '22 15:11 flexarts

@ShvaykaD Thanks! Sorry for the slow response...

We're not using the multi-tenant functionality, so I didn't really think of that. I suppose it makes sense indeed to store the regex at tenant level.

An issue I see here is that we probably cannot/shouldn't be applying the regex to just any CN, only to the CN's that are trusted by a specific tenant, and that we should only apply the regex after the certificate has already been fully verified (against a trust anchor).

Taking @flexarts comment into account, I think for multi-tenancy it should probably work something like:

  • Each tenant has a trust anchor configured (i.e. CA certificate)
  • Client connects using a client cert, matching CA cert is looked up
    • Could be a cached map, similar to what c_rehash does, so performance should be fine?
  • Client cert is fully verified against the matching CA
  • Matching CA cert determines the tenant: CN regex is fetched from the tenant
  • Client CN is matched against the regex, extract <assetId>
  • Lookup TB device matching the <assetId>
    • In our case this would be the TB device UUID, but perhaps other customers would like to match on another attribute of a device? Could be a Tenant property then to configure which field should be used?

In this proposal, the CA cert would need to be unique per tenant (which is not unreasonable to expect, I suppose). Alternatively, you could find all the tenants that have the same CA configured, apply their regexes one-by-one (as in your proposal), and stop when you found a matching device. But I'd find that a bit tricky (and you could allow this feature later if needed).

Note that in addition to validating the certificate's trust chain and validity, I'd also like it to check a Certificate Revocation List. So in terms of configuring the trust anchor, it should be both the (CA) certificate and (optionally) its CRL.

poelstra-sioux avatar Dec 01 '22 15:12 poelstra-sioux

Hi @flexarts and @poelstra-sioux, thanks for the updates and design proposals. We are currently working on investigating the most flexible way of configuration to cover any use cases. We are back to the original requirements to have the ability to specify the CA certificate per device profile along with the regex and device field(device name or deviceId) that we need to extract from CN to identify the device entity.

I plan to talk once more with @ashvayka the following week to confirm the design, and then I will get back to you.

Regards, Dmytro

ShvaykaD avatar Dec 02 '22 15:12 ShvaykaD

Hi @flexarts and @poelstra-sioux, sorry for the delayed response.

We have talked with @ashvayka and @volodymyr-babak and finally, we are ready to provide you design overview of the requested feature:

Main improvement - add the ability to authorize devices into Thingsboard (TB) with X.509 client certificates signed by pre-uploaded CA certificates to the Device Profiles. Identify devices based on a deviceName extracted from CN of the X.509 client certificates.

Device Profile Entity improvements:

  1. Add the CA certificate field. General validation requirements for CA certificates:
    • CA certificate can be a self-signed certificate.
    • Uploaded certificate is not a root certificate provided by some well-known CA authorities. E.g., IdenTrust or Let's Encrypt.
  2. Add RegExp pattern field - expression variable to fetch Device Name from Common Name (CN).

Important Note: we plan to support only deviceName as a field that we will try to extract from the client certificate CN. The reason is that deviceName is a unique field in the scope of one Tenant, while deviceId is unique per the system. In this way, we will protect our system from cases when the user may mistakenly add the device id of the device from another tenant to the CN of the client certificate that is linked to the device profile from our Tenant.

General requirements for the certificates:

  • TB supports only X.509 certificates of ‘3’ version.
  • The Certificates used by the client must contain the full certificate chain, including the uploaded CA certificate.
  • The Certificates used by devices must be signed either by uploaded CA certificates or by a chain of certificates signed by uploaded CA certificates.

Flow example:

  • Tenant administrator creates device profiles and sets CA certificates and RegExp patterns. TB will store the hash of the CA certificate as part of the device profile data. Additionally, on the device profile update action, TB will notify TB Transport to cache the mapping between the CA certificate hash and deviceProfileId.

  • Client connects to the platform using X.509 client certificate. This client certificate must be signed by the pre-uploaded CA certificates that were added to the device profile.

  • TB checks that the connected client X.509 certificate exists in the system as device credentials (this is already implemented). You must set the device credentials type to X.509 certificate during device creation. Suppose the client certificate is updated after it was registered in the device credentials. In that case, TB will automatically update the device credentials on the next device connect request to the system after device identification.

    • If TB is not able to find device credentials for the provided certificate, look up against pre-uploaded CA certificates should happen:
      • TB iterates over a chain of client certificates from the bottom up to the root. Once any of the pre-uploaded CA certificates (hashes) matches the one from the device X.509 certificate chain TB transport fetches the device profile from the cache for the CA certificate that was found.

      • Next, TB tries to use RegExp from the device profile and get the device's name from the client certificate CN. Then TB will do a lookup of the device by name that was extracted from the certificate CN.

      • If the device already exists:

        • and device credentials are not X.509 certificate - return an error to the client.
        • otherwise, If the device credentials are X.509 certificate - update device X.509 credentials with the new certificate that the client is used to connect to.
      • Additionally, we plan to implement the feature requested in the following ticket: https://github.com/thingsboard/thingsboard/issues/7668. Namely, add a new type of device provisioning called: X.509 certificate chain. In case when the device doesn't exists we will check for provision type that is set in the device profile:

        • If the provision is set to ‘Disabled’ - return an error to the client.
        • If the provision is set to ‘X.509 certificate chain’ - create a new device, set device credentials as connected certificate, and send OK response to the client.

    Regards, Dmytro

ShvaykaD avatar Dec 21 '22 10:12 ShvaykaD

Hi @ShvaykaD , Thanks for the detailed design proposal. It seems to cover all the features I have wished for. Last question for me, when do you think it will this feature enter the roadmap and for which release? Alex told us (ÖBB) that it will be planned for v3.5 (April 2023). Is that still valid? We really need this feature when we go live in June 2023. Thanks in advance

flexarts avatar Dec 29 '22 07:12 flexarts

Hi @ShvaykaD,

Thanks indeed! Looks very good, and it can definitely solve my use-cases, if my assumptions/questions below are correct ;)

Uploaded certificate is not a root certificate provided by some well-known CA authorities

That's fine by me. However, if I, as a tenant, would use such a CA, then I suppose it would be fine if someone uses such a client certificate, as long as it then matches the device's deviceName? It would perhaps indeed be confusing if two tenants started to use such a CA, and both have a device with the same deviceName.

If you want to prevent that, perhaps the requirement should be that no two tenants can have a shared common ancestor in the CA chain?

To prevent 'first come, first served' kind of stuff for your hosted service, I can indeed imagine you'd want to just block any 'well-known' CA from being used, but perhaps it should be made an option that can be disabled (or the list of 'well-known' can be tweaked).

In this way, we will protect our system from cases when the user may mistakenly add the device id of the device from another tenant to the CN of the client certificate that is linked to the device profile from our Tenant

In our case, we're already rolling out client certificates which have the TB device ID in the CN. I assume/hope I can still just set the device name to the UUID though? (I.e. first create the TB device, obtain its UUID, write that UUID to the deviceName field)

I assume that during the lookup of matching devices (i.e. "Then TB will do a lookup of the device by name that was extracted from the certificate CN" in the steps below), it will only consider devices that have the device profile from which the regexp (and CA) was used?

This way, even if someone happens to use the UUID of another tenant's device, they cannot be accidentally 'mixed up'.

TB will notify TB Transport to cache the mapping between the CA certificate hash and deviceProfileId

What if I want to have two device profiles (in one tenant), but both just using the same PKI infrastructure (thus same signing CA)? I suppose the mapping should be that a single CA can match several device profiles (although indeed perhaps only device profiles of a single tenant, as mentioned above).

You must set the device credentials type to X.509 certificate during device creation

I assume I can just set the type to X.509, but leave the actual client cert empty at that stage? I.e. it will then be populated on the first connection, and they're not a 'required field'?

Namely, add a new type of device provisioning called: X.509 certificate chain

Oh, nice :) In our case, we already need the device in TB before it first connects, but I can definitely see its value for other cases.

poelstra-sioux avatar Jan 10 '23 14:01 poelstra-sioux

Thought about this a bit more.

Because this feature was not yet available, we already had to go live with the 'old' approach of using an access token. Right now, our devices are connecting to MQTTS sending their client certificate (which isn't really used) AND their access token.

We would need to have some smooth transition mechanism from access tokens to X.509 certs.

For example:

  • Right now, devices send client cert + token
  • We add CA cert + regex to the device profile
  • We change each device's Credentials Type to "X.509 certificate"
  • Device still connects using client cert, which successfully authenticates the device.
  • Device also still sends access token, which is then ignored (but not 'rejected')
  • Future firmware update of device can remove the sending of the access token

@ShvaykaD Do you think that it is possible to have a smooth transition to the new mechanism like this? Or do can you suggest another approach?

poelstra-sioux avatar Jan 11 '23 11:01 poelstra-sioux

@ShvaykaD @ikulikov Do you have an ETA for this feature? you initally mentioned to plan to release this feature in 3.5. But i do not see it mentioned on the roadmap: https://thingsboard.io/docs/reference/roadmap/

Thanks in advance!

flexarts avatar Mar 09 '23 05:03 flexarts

The X.509 provisioning feature was implemented and included in the 3.5 release. You can now refer to the updated documentation for detailed information on how to manage and configure X.509 certificate chain in your ThingsBoard environment.

AndriiLandiak avatar May 15 '23 08:05 AndriiLandiak

Thanks for the perfectly professional approach to bring this feature to life!

flexarts avatar May 26 '23 12:05 flexarts