Skip to content
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

Add support for private key operations based on hardware security devices #299

Open
hko-s opened this issue Feb 27, 2024 · 6 comments
Open

Comments

@hko-s
Copy link
Contributor

hko-s commented Feb 27, 2024

Currently, there is no mechanism to conveniently use hardware cryptographic devices with rpgp. Adding one would be nice.

Potential use cases include:

@wiktor-k has signaled interest in co-thinking about this

@wiktor-k
Copy link
Contributor

I think this is closely related to #286.

If rpgp could accept signers and decryptor implementing objects (using standard RustCrypto interfaces) we could potentially plug other projects such as PKCS#11.

One tricky bit is that for PGP signing a couple of things from the PGP "public key" (certificate) are needed, e.g. signing key ID, for embedded that in subpackets (which is not available for any generic keys and couldn't be computed from the raw public key only).

For decryption, up until now, the certificate of the recipient was also needed due to the flexibility in ECDH design (which allowed to put KDF params in the certificate). This has been fixed in v6 (the ticket was from times when it was called v5) but I guess there are still keys in the wild that use non-optimal/standard combinations.

Phew, this is kind of random babbling from my side... I hope that it didn't confuse anyone (too much) 😅

@dignifiedquire
Copy link
Member

I think there might be an opportunity to refactor the internal crypto code to be abstract over the rust crypto traits, given all backing impls are using it effectively

@hko-s
Copy link
Contributor Author

hko-s commented Feb 27, 2024

One tricky bit is that for PGP signing a couple of things from the PGP "public key" (certificate) are needed, e.g. signing key ID, for embedded that in subpackets (which is not available for any generic keys and couldn't be computed from the raw public key only).

It's definitely inconvenient that calculating the hash digest for an OpenPGP signature requires additional data from the public key packet (that is not available on all HSM devices).

However, as I understand it, that's an orthogonal issue to providing a mechanism that allows performing raw signing operation on hardware devices?

So I think we have two somewhat separate design tasks:

  • Low level signature and decryption operations (e.g. signing a raw hash digest)
  • Mechanisms in rpgp that combine these low level operations with the surrounding OpenPGP operation (e.g. calculating that hash digest. With some HSM, all relevant information can be obtained from the HSM, while in other cases an additional copy of the public key data is required to calculate the hash digest)

@wiktor-k
Copy link
Contributor

I think there might be an opportunity to refactor the internal crypto code to be abstract over the rust crypto traits, given all backing impls are using it effectively

That'd be really nice. On the lowest-level there is no difference if one plugs the RSA keypair from RustCrypto or any other implementing the same traits.

Btw, cool to see so much work being done on rpgp! I think I'm not the only one seeing that: https://chaos.social/@kubikpixel/111998260496315248

@hko-s
Copy link
Contributor Author

hko-s commented Feb 27, 2024

For the low level cryptographic operations, I see one (multifaceted) concern that needs to be addressed somehow:

Authorization on the device. In some cases, a password needs to be provided to the device from the host computer, in other cases, a card reader may require pin entry on an external pin pad, or a device may require touch confirmation.

It's currently unclear to me if these requirements have any implications for the design we're discussing here. However, doing this "right" would be nice (the missing notification facility for touch confirmation in particular strikes me as an unnecessary papercut in existing OpenPGP card UX)

@wiktor-k
Copy link
Contributor

wiktor-k commented Mar 15, 2024

Hi folks 👋

I've spent a bit of time and got the card signer to work with rpgp. Lo and behold: https://github.com/wiktor-k/test-rpgp/blob/main/src/bin/sign.rs and... it works with my key!

(If you want to try it out note that it signs the DATA constant and uses my KeyID in SubpacketData::Issuer and passes the PIN as a command line argument. Oh, did I mention it supports only ed25519? 😅 ).

A couple of notes:

  • it'd be cool if the SecretKeyTrait was a bit leaner. Just check out the amount of functions I had to mock (via #[unimpl]) as the code only invokes create_signature (or a separate trait is introduced that has only that function),
  • I didn't use key_pw but maybe that'd be a good way to read the PIN from the user,
  • I didn't use hash but if it was an RSA key it could be necessary.

And that's it!

Btw, I started writing the signing code based on this example but only after a while noticed the The following is NOT compliant warning 😅 I think it'd be better if the example worked (eg by using SignatureConfig as I did) not to proliferate broken code... or maybe it's just me not paying attention 🙄

Edit: I've found the other example which is packet based... so... maybe the first one should be removed, at least from the root doc. 🤔

Edit 2: I've experimented with hardware decryption a bit and it's slightly more involved: wiktor-k@217543f (this is adding additional variant to minimize the amount of changes but it's not my preferred design, see below).

The complexity stems from the fact that the top-level decrypt function asks for structs representing secret keys. And then down the stack it calls SecretKeyUnlock.unlock which passes SecretKeyRepr which is an enum. I think it should be a trait with just two functions: rsa_decrypt and ecdh_derive. Where ecdh_derive should be called from ecdh::decrypt to do just the part of computing diffie_hellman. Then for software keys we should have a struct implementing these two functions using software libraries (just like we have now).

One wrinkle is the "ECDH params" which currently come from the packet. We could expose them alongside these two functions or fix them.

(Unlock is also called for signing but we could have unlock_decryptor and unlock_signer returning different objects).

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

No branches or pull requests

3 participants