Skip to content

feat: PKCS11 PIN per request#3809

Merged
Bravo555 merged 7 commits intothin-edge:mainfrom
Bravo555:feat/pkcs11-pin-per-request
Oct 13, 2025
Merged

feat: PKCS11 PIN per request#3809
Bravo555 merged 7 commits intothin-edge:mainfrom
Bravo555:feat/pkcs11-pin-per-request

Conversation

@Bravo555
Copy link
Copy Markdown
Member

@Bravo555 Bravo555 commented Oct 6, 2025

TODO

  • documentation expanded a bit on what the options are doing and what happens when both cryptoki.pin and device.key_pin are set

Proposed changes

Allows the user to provide a PKCS#11 User PIN value to be used when logging into PKCS11 token using a new device.key_pin config setting.

tedge config set device.key_pin my-pin

Additionally, pin also may be provided in device.key_uri using a standard PKCS#11 URI pin-value query attribute:

pkcs11:token=mytoken;object=mykey?pin-value=my-pin

tedge-p11-server will then attempt to login to the token using my-pin instead of the default PIN tedge-p11-server was started with.

When multiple pin values are present, we use: URI -> pin field -> provider pin.

Because it is sensitive, there are security considerations, of interest to us being this part of the RFC7512:

Section 7.5 of [RFC3986] applies since a PKCS #11 URI may be used in world-readable command-line arguments to run applications, stored in public configuration files, or otherwise used in clear text. For that reason, the "pin-value" attribute should only be used if the URI string itself is protected with the same level of security as the token PIN itself otherwise is.

When using the pin-value attribute, because URIs are just strings, parts of the code that just pass these URIs may log them, potentially exposing secrets in the logs. On tedge-p11-server side as soon as we're parsing URI we can put in in a secrecy wrapper, to not be able to print it from then on, but there's nothing preventing printing the value before it's parsed on the server, or on the client making the request. For this reason, using pin-value is not recommended, although it's supported when setting device.key_pin is for some reason not possible, as pin-value is a standardised attribute so there's no reason for socket or module provider to not support it.

When using device.key_uri config setting, the value of the secret appears in tedge.toml, but after reading it will not be printed in the logs.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Improvement (general improvements like code refactoring that doesn't explicitly fix a bug or add any new functionality)
  • Documentation Update (if none of the other choices apply)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Paste Link to the issue

#3716

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA (in all commits with git commit -s. You can activate automatic signing by running just prepare-dev once)
  • I ran just format as mentioned in CODING_GUIDELINES
  • I used just check as mentioned in CODING_GUIDELINES
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Further comments

@Bravo555 Bravo555 temporarily deployed to Test Pull Request October 6, 2025 14:03 — with GitHub Actions Inactive
@Bravo555 Bravo555 added the theme:hsm Hardware Security Module related topics label Oct 6, 2025
@Bravo555 Bravo555 changed the title Feat/pkcs11 pin per request feat: PKCS11 PIN per request Oct 6, 2025
@codecov
Copy link
Copy Markdown

codecov bot commented Oct 6, 2025

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Oct 6, 2025

Robot Results

✅ Passed ❌ Failed ⏭️ Skipped Total Pass % ⏱️ Duration
712 0 3 712 100 2h6m3.869254s

@Bravo555 Bravo555 force-pushed the feat/pkcs11-pin-per-request branch from b265cc2 to 5e8aa94 Compare October 7, 2025 14:08
@Bravo555 Bravo555 temporarily deployed to Test Pull Request October 7, 2025 14:08 — with GitHub Actions Inactive
This version of secrecy is already used by cryptoki, so we don't
actually pull anything more, but need to declare it because cryptoki
doesn't reexport it.

Current version of secrecy is 0.10.0, but cryptoki doesn't use it yet,
so using this old version to not to have to pull in 2 versions. Can
upgrade once cryptoki does as well.

Signed-off-by: Marcel Guzik <marcel.guzik@cumulocity.com>
Parse "pin-value" query attribute if it's present in a PKCS 11 URI.

RFC7512 specifies a "pin-value" query attribute that can be used to
provide the PIN value, which we can use to pass it in the request.

Because it is sensitive, there are security considerations, of interest
to us being this part of the RFC:

> Section 7.5 of [RFC3986] applies since a PKCS #11 URI may be used in
> world-readable command-line arguments to run applications, stored in
> public configuration files, or otherwise used in clear text.  For
> that reason, the "pin-value" attribute should only be used if the URI
> string itself is protected with the same level of security as the
> token PIN itself otherwise is.

In our case, the challenge is in not showing up these values in the
logs. On tedge-p11-server side as soon as we're parsing URI we can put
in in a secrecy wrapper, to not be able to print it from then on, but
there's nothing preventing printing the value before it's parsed on the
server, or on the client making the request.

Signed-off-by: Marcel Guzik <marcel.guzik@cumulocity.com>
Allows the user to provide a PIN value to be used when logging into
PKCS11 token as a query attribute in the URI, e.g. when the following
value is set as `device.key_uri`:

pkcs11:token=mytoken;object=mykey?pin-value=my-pin

tedge-p11-server will attempt to login to the token using `my-pin`
instead of the default PIN tedge-p11-server was started with.

Because it is sensitive, there are security considerations, of interest
to us being this part of the RFC:

> Section 7.5 of [RFC3986] applies since a PKCS #11 URI may be used in
> world-readable command-line arguments to run applications, stored in
> public configuration files, or otherwise used in clear text.  For
> that reason, the "pin-value" attribute should only be used if the URI
> string itself is protected with the same level of security as the
> token PIN itself otherwise is.

In our case, the challenge is in not showing up these values in the
logs. On tedge-p11-server side as soon as we're parsing URI we can put
in in a secrecy wrapper, to not be able to print it from then on, but
there's nothing preventing printing the value before it's parsed on the
server, or on the client making the request.

Signed-off-by: Marcel Guzik <marcel.guzik@cumulocity.com>
Signed-off-by: Marcel Guzik <marcel.guzik@cumulocity.com>
@Bravo555 Bravo555 marked this pull request as ready for review October 9, 2025 08:54
@Bravo555 Bravo555 marked this pull request as draft October 9, 2025 09:00
@Bravo555 Bravo555 force-pushed the feat/pkcs11-pin-per-request branch from 5e8aa94 to 3c5f0a4 Compare October 9, 2025 13:29
@Bravo555 Bravo555 force-pushed the feat/pkcs11-pin-per-request branch from 3c5f0a4 to 8fc58a9 Compare October 9, 2025 13:30
@Bravo555 Bravo555 temporarily deployed to Test Pull Request October 9, 2025 13:30 — with GitHub Actions Inactive
@Bravo555 Bravo555 marked this pull request as ready for review October 9, 2025 13:33
Comment on lines +55 to +61
/// A secret string that should not be printed by accident.
///
/// Rolling our own type because `secrecy::SecretString` doesn't impl Serialize,
/// and we don't need eager zeroizing, we only need to make sure not to print
/// the value.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretString(String);
Copy link
Copy Markdown
Contributor

@didier-wenzek didier-wenzek Oct 9, 2025

Choose a reason for hiding this comment

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

I don't fully get the motivation here.

  • Something that can be serialized is no more something that cannot be printed.
  • A SecretString can be exposed as a string with expose()
  • A SecretString can be cast into a secrecy::SecretString which can then be exposed with expose_secret().
  • The secret is stored in tedge.toml as clear-text. And is displayed by tedge config get

This sounds overly complicated. Why no simply use String? Or secrecy::SecretString with appropriate can to expose_secret() when sending a request over the unix socket?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

  • Something that can be serialized is no more something that cannot be printed.

We just don't want to log it accidentally, i.e. when logging the request with "{:?}" we'd like to see other fields but have PIN be redacted. When serializing, it's only done in connection.rs and when calling .serialize(), one has to provide a serializer, so it's not going to be accidental.

  • The secret is stored in tedge.toml as clear-text. And is displayed by tedge config get

Indeed config may contain secrets (we also have cryptoki.pin in tedge.toml), but we still don't want to have any secrets in the logs if we can help it (similarly to how cryptoki.pin is redacted when starting tedge-p11-server). Having pin as clear text in tedge.toml is probably not ideal and we could handle it like private keys, i.e. secret in a separate file and path to that file in tedge.toml, which we can always do in the future.

Why no simply use String

Requests and Responses implement Debug and can be logged, so when using String we'd log the pin value in the logs

Or secrecy::SecretString with appropriate call to expose_secret() when sending a request over the unix socket?

We're serializing the entire request to write it to the socket, to call expose_secret() one would have to write impl Serialize manually, which we're currently deriving.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we could handle it like private keys, i.e. secret in a separate file and path to that file in tedge.toml, which we can always do in the future.

Why not in this PR? This would makes more sense.

By the way, this is also what is done for c8y.credentials_path.

We just don't want to log it accidentally, i.e. when logging the request with "{:?}" we'd like to see other fields but have PIN be redacted.

I appreciate that. However, I feel the current implementation is over complicated. Do we need the secrecy crate as we have to work around it? Why not simply keep this SecretString wrapper with redacting Display and Debug implementations?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Removed secrecy and replaced with our own SuperString everywhere, in dca30ef.

Why not in this PR? This would makes more sense.

I think then we'd have to add an option for cryptoki.pin_path as well, so both pin options support it, point users to use this new option and keep the old cryptoki.pin_path for backwards compatibility but say it's not recommended, and then in this PR I could change key_pin to key_pin_path. I could do the cryptoki.pin_path changes in a separate PR for readability.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As discussed offline, it has been decided to keep with storing the pin within the tedge.toml. The user can still protect this file by changing the permissions so that it is only readable by the tedge user (e.g. chmod 600 /etc/tedge/tedge.toml).

Copy link
Copy Markdown
Contributor

@didier-wenzek didier-wenzek left a comment

Choose a reason for hiding this comment

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

Approved


// NOTE: combining URI behaviour seems unintuitive and surprising. If a client asks for a key on `token2`,
// but the server only allows accessing `token1` then it probably should receive a KeyNotFound instead of
// getting a signature from key on `token1` and then NotAuthorized because the signature is wrong.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe this doc comment is trying to say too many things, notably when displayed by tedge config list --doc. Yes PKCS#11 is subtel, but I seems to me that a proper doc would do better that a command helper.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is a regular comment, not a doc comment, so it's not displayed in tedge config list --doc. It's related to the behaviour explained in the doc comment, so decided to include it below.

Copy link
Copy Markdown
Member Author

@Bravo555 Bravo555 Oct 13, 2025

Choose a reason for hiding this comment

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

Unless you don't really mean the NOTE, but also the entire doc comment above talking about how the URIs are combined?
In this case I'd agree that if every option had descriptions of this size, then yeah tedge config list --doc output would be a bit large, but still it's nice to have longer descriptions for the options, explaining their behaviour and also potentially interactions with other options, directly in tedge-config instead of only doing that in Markdown documentation.

My preferred solution would be to either tweak a bit the format of tedge config list --doc such that these longer descriptions look better.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My preferred solution would be to either tweak a bit the format of tedge config list --doc such that these longer descriptions look better.

We can see that later.

@reubenmiller reubenmiller modified the milestone: 1.7.0 Oct 13, 2025
Signed-off-by: Marcel Guzik <marcel.guzik@cumulocity.com>
Expand documentation and configuration setting descriptions how PIN and
URIs option can be overridden on a per-key basis.

Signed-off-by: Marcel Guzik <marcel.guzik@cumulocity.com>
Signed-off-by: Marcel Guzik <marcel.guzik@cumulocity.com>
@Bravo555 Bravo555 force-pushed the feat/pkcs11-pin-per-request branch from dca30ef to 1f19693 Compare October 13, 2025 13:36
@Bravo555 Bravo555 temporarily deployed to Test Pull Request October 13, 2025 13:36 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@didier-wenzek didier-wenzek left a comment

Choose a reason for hiding this comment

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

Approved.

@Bravo555 Bravo555 added this pull request to the merge queue Oct 13, 2025
Merged via the queue into thin-edge:main with commit f05255e Oct 13, 2025
34 checks passed
@Bravo555 Bravo555 deleted the feat/pkcs11-pin-per-request branch October 14, 2025 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

theme:hsm Hardware Security Module related topics

Projects

None yet

Development

Successfully merging this pull request may close these issues.

allow clients to provide the PKCS11 pin to the tedge-p11-server when interacting in the socket mode

3 participants