Skip to content

Investigate support for multiple providers of the same type #404

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

Open
paulhowardarm opened this issue Apr 26, 2021 · 5 comments
Open

Investigate support for multiple providers of the same type #404

paulhowardarm opened this issue Apr 26, 2021 · 5 comments
Labels
enhancement New feature or request platforms Compatibility with different secure services or hardware platforms

Comments

@paulhowardarm
Copy link
Collaborator

Summary

This is an investigation/design ticket - how would the service be enhanced so that multiple providers of the same type could be running in the service and used effectively by clients without breaking stability? The service is currently limited to running only a single provider of any given type.

Rationale

Providers are conceptually classes rather than instances. A provider instance is a combination of its class/type and its specific configuration, which points it towards a back-end software/hardware component. In the service today, this class/instance distinction can't be exploited. All providers are effectively singletons because they are addressed with an inflexible integer ID.

There are some valid situations where we might want to deploy Parsec with multiple instances of the same provider. This is particularly the case with the PKCS#11 provider, where we might want to use multiple slots on an HSM, or even more than one PKCS#11 library - such as one that talks to a local HSM, while another talks to an HSM in the Cloud or over a LAN.

Of the providers that exist today, PKCS#11 is probably the one that offers the strongest rationale for multiple instances. The benefits are less clear-cut for the others, but we should aim for a general-purpose solution and not favour PKCS#11 specifically.

Design Considerations

Allowing multiple providers of the same type to run within the service is, by itself, relatively straightforward. What is much less straightforward is designing a mechanism that allows clients to use them effectively, especially given that we need to preserve backwards compatibility and not introduce a breaking change to the way that clients select providers, either explicitly by ID or implicitly through self-configuration and preferential mechanisms.

It is not just about backwards-compatibility. It is also about giving clients an ergonomic experience that is sensitive to their expected workflow and use case.

There might not be a one-size-fits-all answer to this. It might be about supporting multiple mechanisms over time.

Some possibilities - not necessarily mutually exclusive, and these are only enabling mechanisms that do not represent a complete design.

  • Make use of "sessions" in Wire Protocol 1.0, with new opcodes to open a session with parameters to refine the provider selection beyond just the integer ID. In subsequent requests, the session can be used to target the correct provider if the ID by itself is ambiguous.
  • Give the authenticator a role in provider selection. For example, in an HSM with multiple slots, one could imagine assigning different client identities to different slots (with our without globbing patterns). These mappings could be part of Parsec configuration, such that it would be entirely transparent to clients. But this only enables a particular use case, and doesn't really cover the situation where we have multiple PKCS#11 providers with different implementations and completely different purposes.
  • Allow the client to declare its purpose or intent in some semantic way. A combination of client intent and client identity would be a good pair of coordinates for targeting the correct provider. What would "intent" look like? It's really about getting the client to declare what it is trying to do, rather than burdening the client with explicit provider selection (because explicit provider selection is bad for both portability and ergonomics). We want the clients to self-configure as much as possible, so a mapping from "intent" to provider would be done on the service side. The "intent" could be similar to the identity in that the client could provide it on each request, and it could just be an arbitrary string value, but - unlike identity - there would be no need to validate it. It would just be used as a key in a lookup table. Intent strings could be simple tokens like "cloud" and "local", allowing a client to specify a desire to use a remote or local secure back-end.
  • Introduce a Wire Protocol 1.1 and add extra request header field(s) to allow the target provider to be specified using more than just a single integer ID. The service would have to continue supporting WP 1.0 for older clients.

Definition of Done

This ticket is to agree a design - we can consider it "done" once we have a design proposal in place with a suitable level of community review, and tickets have been raised to cover the implementation (or part of it, if this can be phased in some way).

@paulhowardarm paulhowardarm added enhancement New feature or request platforms Compatibility with different secure services or hardware platforms labels Apr 26, 2021
@hug-dev
Copy link
Member

hug-dev commented Apr 26, 2021

What is much less straightforward is designing a mechanism that allows clients to use them effectively, especially given that we need to preserve backwards compatibility and not introduce a breaking change to the way that clients select providers, either explicitly by ID or implicitly through self-configuration and preferential mechanisms.

As well as clients communication another affected area of using multiple providers of the same tyme is the key mappings. Currently the static key ID itself is used to identify a provider but that would not work with two providers with the same ID.

While investigating on #394, I came up with two other possibilities that could solve both areas. I like the first one much more but give the other for reference:

Move to dynamic provider IDs

As you said providers are classes of the same UUID. As written in the "Selecting Providers" section, ideally clients should use the ListProviders operation to determine the provider ID they need to use on each request. I think that we started with static provider IDs for simplicity of coding the first Parsec uses cases. Introducing dynamic provider IDs now might be good solution to support multiple providers of the same type and also in the future dynamic loading of providers.
When the service starts up, IDs would be assigned to all providers present in the configuration file starting from 1 for the one with biggest priority.
To make it work, a new optional field would be added on each provider: name. That would be a short human-readable name, chosen by the admin writing the configuration, describing the instance of the provider class. For example: nitrokey.
The name field would only be mandatory if more than one provider of the same type are entered in the configuration. Ideally, name would also be in ListProviders response. If not specified name would have the value default.
For the mappings, the tuple name + UUID would be needed to select the appropriate keys. That means that the same name could be given to two different providers. That also means that if name is changed, then the old keys would not be visible. If a new provider of the same type is added to an existing working provider storing keys, the old provider would have to be named with the default name (default).
As the attribution of provider ID would be deterministic, clients can still expect to use the same IDs accross service reloads that do not modify providers' order.

Add multiple provider "copies"

We could add one copy (or multiple) of our existing providers. For example Pkcs11Supp where everything is the same except that it has a new UUID (and a new static provider ID if we keep that).

@wiktor-k
Copy link

The benefits are less clear-cut for the others, but we should aim for a general-purpose solution and not favour PKCS#11 specifically.

Agreed. Actually it's not that inconceivable to have two TPM modules. Especially for tests it would be very beneficial two have it (e.g. testing TPM key migration requires two TPM "instances"). Another example: KMIP.

I'm just starting to take a look at Parsec so excuse my ignorance but it would be cool if I could tell the library "please sign using key 3 on token with serial number 327923 via PKCS#11 library libykcs11.so" and it would work even if I plug the token to a different port or restart the computer. I believe the PKCS#11 library should be instantiated using "library libykcs11.so + serial number 327923" and detect which slot is it. There is a simpler version that just says "slot #1" but that doesn't take into account replugging devices in different ports.

On the other hand C_GetTokenInfo is not yet implemented in the cryptoki crate but that's just a minor technical issue.

Subscribed to this issue, thanks for considering this! 👋

@hug-dev
Copy link
Member

hug-dev commented Jun 14, 2021

So right now the instantiation of the PKCS 11 provider takes as inputs: the path to the .so, the slot number and the PIN.

If I understood correctly, you are saying that the slot number will change if you replug the HSM or restart the computer but the serial number will stay the same so a better way to instantiate a PKCS11 provider would be via: .so, serial number and PIN.

That makes sense to me I think!

Also related: #375 which makes serial number or slot not needed if there is only one anyway.

A few questions:

  • how does one find the serial number of its device?
  • would it be common (at least in Parsec use cases) for the device to be unplugged and replugged? Restarting the machine could maybe happen.

Our config file needs to stay stable. That means that if we add a new serial_number field it has to be optional.

@wiktor-k
Copy link

how does one find the serial number of its device?

I actually have code for that (based on C_GetTokenInfo) but I'm submitting one PR at a time not to scare you off :)

Currently I'm looping through all slots getting token info that contains the serial number to find the slot number with the given token. I've got quite some number of tokens and serial numbers are printed on them so it's more convenient in my case. I understand this may not hold for parsec in general.

@ionut-arm
Copy link
Member

Coming back to the original topic, part of the requirement was already implemented in #491 , allowing providers to be identified through names. When we finish the move to the SQLite provider we can start work on this to enable (psuedo-)randomized provider IDs, and thus multiple providers of the same type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request platforms Compatibility with different secure services or hardware platforms
Projects
None yet
Development

No branches or pull requests

4 participants