-
Notifications
You must be signed in to change notification settings - Fork 50
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
Design key import/generation for the signer API #466
Comments
Adds basic Signer and Key implementations to generate signatures on a hardware security module (HSMSigner) and to export the corresponding public key (HSMKey). Supported keys are ecdsa on SECG curves secp256r1 (NIST P-256) or secp384r1 (NIST P-384), this corresponds to securesystemslib signing schemes "ecdsa-sha2-nistp256" and "ecdsa-sha2-nistp384". Tests are performed on virtual hsm (SoftHSM). **Caveat** HSMSigner and HSMKey use the token from the passed "PyKCS11" session. This means that users must identify the correct slot, token and key, open a "PyKCS11.Session", and optionally login (for signing), and also log out and close the session afterwards. This is not user-friendly. Ideally, the user only identifies the correct slot, token and key out-of-band (e.g. with pkcs11-tool, yubico-piv-tool or ykman) and then passes a stable identifier to HSMSigner. Maybe labels? Slot id is not stable. **Other ideas** - Implement `HSMKey.from_hsm` to a different key/import generation API (secure-systems-lab#466) and remove `HSMKey`. - Move HSMSigner to a dedicated _hsm_signer.py module Signed-off-by: Lukas Puehringer <[email protected]>
Adds basic Signer and Key implementations to generate signatures on a hardware security module (HSMSigner) and to export the corresponding public key (HSMKey). Supported keys are ecdsa on SECG curves secp256r1 (NIST P-256) or secp384r1 (NIST P-384), which correspond to securesystemslib signing schemes "ecdsa-sha2-nistp256" and "ecdsa-sha2-nistp384". Tests are performed on SoftHSM (virtual hsm). **Caveat** HSMSigner and HSMKey use the token from a passed PyKCS11 session. This means that users must identify the correct slot, token and key, open a session, optionally log in (for signing), and also log out and close the session afterwards. This is not user-friendly. Ideally, the user only identifies the correct slot, token and key out-of-band (e.g. with pkcs11-tool, yubico-piv-tool or ykman) and then passes a stable identifier to HSMSigner. Maybe labels? Slot id is not stable. **Other ideas** - HSMKey is an SSlibKey with an *import key from HSM* method. The method could be moved to different import API (see secure-systems-lab#466), and HSMKey could be removed. - HSMSigner could live in a dedicated _hsm_signer.py, this would better hide the conditional imports. Signed-off-by: Lukas Puehringer <[email protected]>
Adds basic Signer and Key implementations to generate signatures on a hardware security module (HSMSigner) and to export the corresponding public key (HSMKey). Supported keys are ecdsa on SECG curves secp256r1 (NIST P-256) or secp384r1 (NIST P-384), which correspond to securesystemslib signing schemes "ecdsa-sha2-nistp256" and "ecdsa-sha2-nistp384". Tests are performed on SoftHSM (virtual hsm). **Caveat** HSMSigner and HSMKey use the token from a passed PyKCS11 session. This means that users must identify the correct slot, token and key, open a session, optionally log in (for signing), and also log out and close the session afterwards. This is not user-friendly. Ideally, the user only identifies the correct slot, token and key out-of-band (e.g. with pkcs11-tool, yubico-piv-tool or ykman) and then passes a stable identifier to HSMSigner. Maybe labels? Slot id is not stable. **Other ideas** - HSMKey is an SSlibKey with an *import key from HSM* method. The method could be moved to different import API (see secure-systems-lab#466), and HSMKey could be removed. - HSMSigner could live in a dedicated _hsm_signer.py, this would better hide the conditional imports. Signed-off-by: Lukas Puehringer <[email protected]>
Adds basic Signer and Key implementations to generate signatures on a hardware security module (HSMSigner) and to export the corresponding public key (HSMKey). Supported keys are ecdsa on SECG curves secp256r1 (NIST P-256) or secp384r1 (NIST P-384), which correspond to securesystemslib signing schemes "ecdsa-sha2-nistp256" and "ecdsa-sha2-nistp384". Tests are performed on SoftHSM (virtual hsm). **Caveat** HSMSigner and HSMKey use the token from a passed PyKCS11 session. This means that users must identify the correct slot, token and key, open a session, optionally log in (for signing), and also log out and close the session afterwards. This is not user-friendly. Ideally, the user only identifies the correct slot, token and key out-of-band (e.g. with pkcs11-tool, yubico-piv-tool or ykman) and then passes a stable identifier to HSMSigner. Maybe labels? Slot id is not stable. **Other ideas** - HSMKey is an SSlibKey with an *import key from HSM* method. The method could be moved to different import API (see secure-systems-lab#466), and HSMKey could be removed. - HSMSigner could live in a dedicated _hsm_signer.py, this would better hide the conditional imports. Signed-off-by: Lukas Puehringer <[email protected]>
Adds basic Signer and Key implementations to generate signatures on a hardware security module (HSMSigner) and to export the corresponding public key (HSMKey). Supported keys are ecdsa on SECG curves secp256r1 (NIST P-256) or secp384r1 (NIST P-384), which correspond to securesystemslib signing schemes "ecdsa-sha2-nistp256" and "ecdsa-sha2-nistp384". Tests are performed on SoftHSM (virtual hsm). **Caveat** HSMSigner and HSMKey use the token from a passed PyKCS11 session. This means that users must identify the correct slot, token and key, open a session, optionally log in (for signing), and also log out and close the session afterwards. This is not user-friendly. Ideally, the user only identifies the correct slot, token and key out-of-band (e.g. with pkcs11-tool, yubico-piv-tool or ykman) and then passes a stable identifier to HSMSigner. Maybe labels? Slot id is not stable. **Other ideas** - HSMKey is an SSlibKey with an *import key from HSM* method. The method could be moved to different import API (see secure-systems-lab#466), and HSMKey could be removed. - HSMSigner could live in a dedicated _hsm_signer.py, this would better hide the conditional imports. Signed-off-by: Lukas Puehringer <[email protected]>
Adds basic Signer and Key implementations to generate signatures on a hardware security module (HSMSigner) and to export the corresponding public key (HSMKey). Supported keys are ecdsa on SECG curves secp256r1 (NIST P-256) or secp384r1 (NIST P-384), which correspond to securesystemslib signing schemes "ecdsa-sha2-nistp256" and "ecdsa-sha2-nistp384". Tests are performed on SoftHSM (virtual hsm). **Caveat** HSMSigner and HSMKey use the token from a passed PyKCS11 session. This means that users must identify the correct slot, token and key, open a session, optionally log in (for signing), and also log out and close the session afterwards. This is not user-friendly. Ideally, the user only identifies the correct slot, token and key out-of-band (e.g. with pkcs11-tool, yubico-piv-tool or ykman) and then passes a stable identifier to HSMSigner. Maybe labels? Slot id is not stable. **Other ideas** - HSMKey is an SSlibKey with an *import key from HSM* method. The method could be moved to different import API (see secure-systems-lab#466), and HSMKey could be removed. - HSMSigner could live in a dedicated _hsm_signer.py, this would better hide the conditional imports. Signed-off-by: Lukas Puehringer <[email protected]>
Adds basic Signer and Key implementations to generate signatures on a hardware security module (HSMSigner) and to export the corresponding public key (HSMKey). Supported keys are ecdsa on SECG curves secp256r1 (NIST P-256) or secp384r1 (NIST P-384), which correspond to securesystemslib signing schemes "ecdsa-sha2-nistp256" and "ecdsa-sha2-nistp384". Tests are performed on SoftHSM (virtual hsm). **Caveat** HSMSigner and HSMKey use the token from a passed PyKCS11 session. This means that users must identify the correct slot, token and key, open a session, optionally log in (for signing), and also log out and close the session afterwards. This is not user-friendly. Ideally, the user only identifies the correct slot, token and key out-of-band (e.g. with pkcs11-tool, yubico-piv-tool or ykman) and then passes a stable identifier to HSMSigner. Maybe labels? Slot id is not stable. **Other ideas** - HSMKey is an SSlibKey with an *import key from HSM* method. The method could be moved to different import API (see secure-systems-lab#466), and HSMKey could be removed. - HSMSigner could live in a dedicated _hsm_signer.py, this would better hide the conditional imports. Signed-off-by: Lukas Puehringer <[email protected]>
Adds basic Signer and Key implementations to generate signatures on a hardware security module (HSMSigner) and to export the corresponding public key (HSMKey). Supported keys are ecdsa on SECG curves secp256r1 (NIST P-256) or secp384r1 (NIST P-384), which correspond to securesystemslib signing schemes "ecdsa-sha2-nistp256" and "ecdsa-sha2-nistp384". Tests are performed on SoftHSM (virtual hsm). **Caveat** HSMSigner and HSMKey use the token from a passed PyKCS11 session. This means that users must identify the correct slot, token and key, open a session, optionally log in (for signing), and also log out and close the session afterwards. This is not user-friendly. Ideally, the user only identifies the correct slot, token and key out-of-band (e.g. with pkcs11-tool, yubico-piv-tool or ykman) and then passes a stable identifier to HSMSigner. Maybe labels? Slot id is not stable. **Other ideas** - HSMKey is an SSlibKey with an *import key from HSM* method. The method could be moved to different import API (see secure-systems-lab#466), and HSMKey could be removed. - HSMSigner could live in a dedicated _hsm_signer.py, this would better hide the conditional imports. Signed-off-by: Lukas Puehringer <[email protected]>
As background for why the public_key should be a Signer property, and why I think import/generate-methods should be Signer implementation specific constructors: In real-world scenarios, if you generate/import a Key, you always want to "connect" that to a specific signer, or at least the private key uri: you never want to just generate a Key because then how would you know how to sign for that Key? Somehow you need to be able to connect a signer private key uri to a keyid: we can leave that to the user of course but if both are properties of the Signer, that gets solved neatly As an example if you import a public key from a Yubikey, you then always want to store configuration like Constructing a Signer is not the only solution but I think it makes sense. Something like this # import signer and key from a yubikey
signer = Signer.import("hsm:")
# store the whole public key in metadata, and private key access details
# (private key uri, keyid, maybe other things) in application configuration
repo.set_targets_key(signer.public_key)
configuration.save_signer(signer) |
So I guess the signer implementation specific APIs look roughly like this -- probably no signer is going to implement all three types of methods (and some signers might implement multiple versions of some methods): Possible implementation specific methods:
Common properties I think Signer interface should require:
|
The major question I have is WRT A minor question is on SSlibSigner: the generate examples show how it gets a bit cumbersome: we have multiple signing schemes and multiple storage models in the same class so the constructor needs to handle a lot of cases... |
Making note of a comment from Lukas:
|
In the spirit of that finding:
|
I've tried this in a limited manner for SSlibKey in repository-editor-for-tuf and I think it works: it won't be pretty if you support all the different schemes and keytypes but that can't be avoided. For me the only case I'm not sure about is import() for SSlibKey -- does it even make sense, is it important to hash out now, and what would it look like? Maybe something like this: SSlibSigner.import_envvar(sslib_key_dict: Dict) -> Tuple[str, str, Key]
# returns
# * the environment variable string to use: "SSLIB_KEY_abdcf1234=dcba09876"
# * private key uri: "envvar:SSLIB_KEY_abdcf1234"
# * the public key
SSlibSigner.import_file(sslib_key_dict: Dict, priv_key_path: str, passphrase: Optional[str]) -> Tuple[str, str, Key]
# Returns
# * secret (possibly encrypted)
# * private key uri: "file:path/to/secret"
# * the public key Note that neither of them actually stores the secret anywhere, just returns it: this is because we cannot know if the secret is actually meant to be used on this machine or not: writing to disk does not make sense if it's not |
Now each private key material is in its own file. There is a keys.json file but it is no longer a secret: it's the configuration file that defines how to sign for specific roles This essentially makes the local keys compatible with the "generate()" idea in secure-systems-lab/securesystemslib#466
New PoC for securesystemslib/securesystemslib/signer/_gpg_signer.py Lines 274 to 294 in c95e3c7
Returning signer URI and public key does feel ergonomic. |
Also not sure about this. Import functions that take an For an SSlibSigner the location of public key and location of private key, as well as location at import time and location at signing time are more independent from each other, compared to HSM, GPG, GCP signers. |
I'm convinced this is close to a good solution. "Signing system initialization" really does mean 3 separate things:
We don't always have to (and can't) handle all three on our code but all three need to happen before signing is useful. This is why the import process either has 3 return values or gets something as given (in HSM and and KMS cases the secrets storage has been setup in an external process, and we get public key from there). I guess the above begs the question: would import make more sense for SSlibSigner if we don't try to cram key creation in there -- then the API would look more like the other signers. Essentially, should there be a SSlibPrivateKey (that would handle the key generation and would be the SSlibSigner import argument)? This way no signer would try to "generate keys", they are always imported?
I think thinking of signing time and import time as independent is very useful for others too: in the GCP case for example, I don't have signer permissions on my personal account (which I authenticate with to add a GCP key to metadata). So the situation is 100% the same: code knows where "secret storage" is going to be later but can't actually expect it is available right now. |
I'll reiterate that I think import/generate are internal details of the specific signing system, and that SSlibSigner is not even that interesting now that we have HSM and KMS... but to ensure that SSlibSigner can be implemented in a somewhat reasonable way, here's a proposal for that: class SSlibSigner():
@classmethod
def generate_file(cls, scheme: str, signing_time_priv_key_path: str, encrypted: bool) -> Tuple[str, Key, bytes]
@classmethod
def import_file(cls, key_dict: Dict, signing_time_priv_key_path: str, encrypted: bool) -> Tuple[str, Key, bytes]
# Returns:
# * signer uri containing the signing time path and encrypted status
# * public key
# * the private key bytes that should be written to signing time location
@classmethod
def generate_envvar(cls, scheme: str) -> Tuple[str, Key, str]
@classmethod
def import_envvar(cls, key_dict: Dict) -> Tuple[str, Key, str]
# Returns:
# * signer uri containing the variable name
# * public key
# * the environment variable declaration ("PRIVATE_KEY_abcdef=0a1b2c3d") Some comments:
|
Following is just related thinking out loud: It's a little worrying that we don't have a good story for how to handle e.g. keys in files in a general way but that's not this PRs fault... maybe Signer.from_priv_key_uri() actually could select the signer implementation also based on the public key -- it seem like in some cases that would be the smart thing. So in case of "file:" URIs, it would do another lookup based on the keytype to find out if it should use SPXSigner or something else? Originally posted by @jku in #568 (review) |
I've also thought about this, but probably in a different way, although I am not sure I understand your proposal. In my mind, the goal is to make the This design is not very flexible, that is a signer won't be able to use a custom and a re-usable transport at the same time. But that could surely be solved. The big question is, is it worth the effort / do we foresee a lot of demand for re-usable transports? Originally posted by @lukpueh in #568 (comment) |
Yeah I think we're talking about roughly the same thing. The core issues are A) dispatch and B) signer construction: I was trying to say
It's entirely possible that being less rigid about the private key URI meaning would be less work... Originally posted by @jku in #568 (comment) |
With #456 the signer API comes a lot closer to being a "fully featured API". The missing part is key generation and import (and maybe storing private keys)
Related issues: #446, #408, #280, #451 (and probably more)
Current status (after #456):
Proposal
keys
module from users, and makesSSlibKey.from_securesystemslib_key
unneededThe part I have not 100% figured out is private key storage: how and when do you store the private key content in a way that you can then use the
from_priv_key_uri()
to load it again. This is somewhat different depending on the key type and in many cases does not even make sense (you can't really store environment variables)... possibly it should happen during the constructor, soSSlibSigner.new_ed25519()
could actually take a priv_key_uri argument and and the generated key would immediately get stored ?The text was updated successfully, but these errors were encountered: