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 GPGKey and use with GPGSigner #488

Merged
merged 13 commits into from
Mar 9, 2023

Conversation

lukpueh
Copy link
Member

@lukpueh lukpueh commented Dec 16, 2022

EDIT 2023/01/20: remove key id param in private key URI

EDIT 2023/03/06:

  • move min code coverage back to 90%, gpg signer is at 100%, nothing more we can do for coverage in this PR
  • remove optional fields from GPGKey, we shouldn't use advanced PKI concepts from GnuPG -- subkeys, key expiration, etc. -- in tuf/in-toto metadata, which use these concepts themselves, instead assert that passed and returned key id match on import_ and sign
  • remove obsolete "hashes" field from GPGKey (this is static for existing schemes, and should be encoded in the scheme for other schemes)

see #488 (comment) pp. and commit messages for more details

Closes #450

Kept as draft to,

  • rebase on merged #486
  • figure out GPGKey main/sub-key id mapping to signer (see discussion)
  • add more tests

Description of the changes being introduced by the pull request:

GPGKey is a regular Key with additional GnuPG specific key fields,
and verification method. It also has conversion helpers to translate
from and to a non-in-toto/tuf-spec compliant key format, which is
still used by the underlying securesystemslib.gpg subpackage.

GPGSigner is updated to:

  • take a GPGKey as constructor argument, and implement
  • from_priv_key_uri, to load signer from gnupg:[<GnuPG homedir>]
  • import_, to import a public key from a GnuPG keyring and return it along with a uri to create the signer.

Please verify and check that the pull request fulfils the following requirements:

  • The code follows the Code Style Guidelines
  • Tests have been added for the bug fix or new feature
  • Docs have been added for the bug fix or new feature

Copy link
Collaborator

@jku jku left a comment

Choose a reason for hiding this comment

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

Looks pretty good to me: it's obviously a bit of a mess with going back and forth beween legacy gpg format and and shared one, but that's all hidden behind the signer API now...

I think requiring secretshandler to be none is wrong but that's the only complaint really.

Is there a test that ensures that the client usage (desdrialize + verify) works without gnupg being installed?

Comment on lines 193 to 197
exceptions.FormatError,
gpg_exceptions.KeyExpirationError,
Copy link
Collaborator

Choose a reason for hiding this comment

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

UnsupportedLibraryError too maybe as this will run in the client?

Comment on lines 251 to 253
if secrets_handler is not None:
raise ValueError("GPGSigner does not support a secrets handler")

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is not useful -- code that uses from_priv_key_uri() is aiming to work with as many signers as it can, using the shared API. This seems to force me to write two code paths for no gain.

Copy link
Member Author

Choose a reason for hiding this comment

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

@jku, do you think we should just ignore it? Maybe log a warning/info? With current implementation of securesystemslib.gpg we cannot pass any secrets to the gpg command.

Copy link
Collaborator

Choose a reason for hiding this comment

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

No need for warning as far as I am concerned: the way I imagine this being used (and the way I am using it) is that the signing code doesn't care which signers are used or what they do:

  • online signing app likely does not have a secrets handler
  • an offline signing tool does provide a secrets handler
  • even the offline signing tool does not care whether the secrets handler is used or not: a gpg signing might not use it but a yubikey signing will

@lukpueh
Copy link
Member Author

lukpueh commented Jan 13, 2023

rebased without changes

@lukpueh
Copy link
Member Author

lukpueh commented Jan 13, 2023

Thanks for the review.

it's obviously a bit of a mess with going back and forth beween legacy gpg format and and shared one, but that's all hidden behind the signer API now...

Agreed. This is not nice but can be easily fixed, once in-toto also transitions to the signer API.

I think requiring secretshandler to be none is wrong but that's the only complaint really.

Will change.

Is there a test that ensures that the client usage (desdrialize + verify) works without gnupg being installed?

Will (fix and) add tests before un-drafting this PR.

@lukpueh
Copy link
Member Author

lukpueh commented Jan 20, 2023

Thanks for the comments, @jku! I've addressed your concerns and added plenty of test, which also revealed a serialization bug (fixed in 3d140d5).

Beyond that, I removed the keyid field from GPGSigner, given that it also has a GPGKey attached, which has the same user-assigned keyid.

I am still unsure how to best resolve the issue that this signer keyid may differ from the keyid of a signature generated by that signer. This is because gpg may use a subkey of the key identified by the passed keyid and we assign the subkey keyid to the signature, so that we know, at verification time, which key or subkey of the public key bundle we should use.

I have a feeling that the signer API should just not support the subkey-masterkey relationship. It adds, for no benefit, a PKI hierarchy to tuf/in-toto, which already have their own PKI hierarchies.

This solution entails the following changes

  • On public key import, users must pass the exact keyid of the key, they want to use for verification. This means, if gpg will use a subkey of a key bundle for signing, they must pass that subkey keyid.
  • Public key import only returns the key identified by the keyid, and not the full public key bundle (as it does now even if a subkey keyid is passed).
  • Signing fails, if gpg uses a different key for signing, than the one attached to the signer public key (e.g. because the user explicitly imported a master key, but the signing capability in gpg is set on a subkey).

caveat: Explicitly identifying a public key before signing requires additional tooling to allow signing with the default key, which is currently possible in in-toto.

An alternative solution, which allows both PKI hierarchies alongside each other, would be to accept public key bundles with subkeys, but always assign the master key keyid to the signature, regardless of what key/subkey was used. However, this would require a different way of matching the correct key in the public key bundle with a signature at verification time.

cc @jku, @adityasaky, @SantiagoTorres

@lukpueh
Copy link
Member Author

lukpueh commented Jan 20, 2023

One more minor thing I thought about is the hashes field of a GPGKey and whether we should merge the info with its scheme field.

For context:

  • The field is currently hardcoded to ["pgp+SHA2"], but ignored at signature creation and verification, which both hard-code sha256 as digest algorithm.
  • This PR already changes the gpg key format in a backwards-incompatible way, to make it in-toto/tuf specification compatible (see #450). Metadata backwards-compatibility will be guaranteed through in-toto/in-toto#534.
  • #308 describes, among other things, how scheme can be used to encode the hash digest scheme. This is also what other supported keys in securesystemslib do.

cc @adityasaky, @SantiagoTorres

@adityasaky
Copy link
Member

adityasaky commented Jan 20, 2023

Public key import only returns the key identified by the keyid, and not the full public key bundle (as it does now even if a subkey keyid is passed).

I haven't had a chance to look through how this is used but would this change impact our notions of subkeys during verification? If only the subkey is returned, would in-toto (for example) have to look elsewhere when verifying that threshold wasn't met using subkeys?

In in-toto, we use securesystemslib.gpg.functions.export_pubkey (https://github.com/in-toto/in-toto/blob/develop/in_toto/models/layout.py#L362) and that in turn calls securesystemslib.gpg.common.get_pubkey_bundle.

@lukpueh
Copy link
Member Author

lukpueh commented Jan 23, 2023

would this change impact our notions of subkeys during verification?

Yes, both of my proposals are not compatible with current in-toto usage of GPG keys and signatures. in-toto currently considers all keys in a public key bundle (masterkey + subkeys), both, to check if a key is authorized via layout to provide a signature, and to identify the exact key or subkey in the bundle to verify the signature.

But this is not a concern, because the key and signature metadata formats of the GPGSigner API in securesystemslib are already incompatible with in-toto,. For in-toto-backwards- and securesystemslib-signer-compatibility, I proposed adding a custom _LegacyGPGSigner API to the in-toto code base (see my other comment above).

@lukpueh
Copy link
Member Author

lukpueh commented Jan 23, 2023

Another thought about parallel PKI: We export gpg keys with creation_time and validity_period fields from the gpg keychain, if present. This information is then considered at signature verification time and may raise KeyExpirationError.

Given that in-toto and TUF already have a mechanism to expire signing keys, that is via an expiration date in the delegating metadata, I wonder, if we should ignore expiration information of gpg keys, when verifying signatures, and leave it to the user to not add gpg keys to delegating metadata that would expire earlier than the metadata.

@jku
Copy link
Collaborator

jku commented Jan 24, 2023

I have a feeling that the signer API should just not support the subkey-masterkey relationship.

I agree with this, and both of your proposed solutions seem solid (although I admit I'm not fully immersed in the problem space of PGP subkeys).

I wonder, if we should ignore expiration information of gpg keys

This is a reasonable point: increasing the complexity of the serialization format doesn't seem useful if the same practical security advantage can be had by making the key-import code just slightly more complex.

GPGKey is a regular Key with additional GnuPG specific key fields,
and verification method. It also has conversion helpers to translate
from and to a non-in-toto/tuf-spec compliant key format, which is
still used by the underlying securesystemslib.gpg subpackage.

GPGSigner is updated to:
- take a GPGKey as constructor argument, and implement
- `from_priv_key_uri`, to load signer from
  `"gnupg:[<GnuPG homedir>][?id=<keyid>]"`
- `import_`, to import a public key from a GnuPG keyring and
  return it along with a uri to create the signer.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
- add expected exception to verify method
- warn on passed secrets handler, don't raise

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
The keyid is redundant with the keyid of the attached public key
instance.

Same goes for the keyid parameter in the related private key uri,
which can also be read from the public key instance passed to
the from_priv_key_uri method.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
Include unrecognized fields in GPGKey.to_dict.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
New tests for:
- `sign` and `import_` failure but successful verification, if
  'gpg' is not available.
- verification failure, if 'cryptography' is not available.
- key de/serialization (also legacy format) and comparison (__eq__)
- expected failures on `from_priv_key` and `verify_signature`

Improvements:
- remove obsolete `assertFalse` in `with self.assertRaises` block
- condense tests in `test_gpg_functions` (use DDT instead of
  copy-paste)

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
Supporting subkeys in a GPGKey and considering them for
verification adds, for no benefit, a PKI hierarchy to tuf/in-toto,
which already have their own PKI hierarchies. In addition it makes
the verification (and delegation) code more complex and error
prone.

This commit drops subkey support, by the following two changes:

- import_: require exact match between passed keyid, and one of
  the keys in the bundle returned by gpg, and return a GPGKey
  only for that key, w/o subkeys

- sign: require exact match between keyid on attached public key
  and keyid on the signature returned by gpg

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
GPGSigner does not support signing with a default key (unlike
the lower level securesystemslib gpg signing function), thus the
test is redundant with the non-default key test.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
- creation_time + validity_period: key validity should be
  determined by the metadata expiration time alone, and not by an
  additional signer-specific key expiration, which is prone to be
  out of sync (we don't use gpg for verification)

- subkeys: dropped support in a previous commit

- hashes: static for currently supported schemes. If other hash
  algorithms are needed, they should be encoded it in the scheme
  string.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
These methods are ugly no matter where they are implemented. I move
them to the signer to keep them together with the legacy signature
format conversion methods.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
Change warning type log statement about exported subkeys to debug.
This is no longer relevant for GPGSigner.import_, which don't
return key bundles to the user, but only a key or a subkey.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
@lukpueh lukpueh marked this pull request as ready for review March 6, 2023 16:16
@lukpueh lukpueh requested a review from jku March 6, 2023 16:16

for key in raw_keys:
if key["keyid"] == keyid:
# TODO: Raise here if key is expired, revoked, incapable, ...
Copy link
Member Author

Choose a reason for hiding this comment

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

I'll create a ticket for this. We can also partially re-use #190)

@lukpueh lukpueh requested a review from adityasaky March 8, 2023 08:15
Copy link
Collaborator

@jku jku left a comment

Choose a reason for hiding this comment

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

👏 looks fine to me. Left a question about the keytype but since pgp key compatibility is mostly a in-toto issue, I'll leave it to you

Comment on lines +51 to +53
("rsa", "pgp+rsa-pkcsv1.5"): GPGKey,
("dsa", "pgp+dsa-fips-180-2"): GPGKey,
("eddsa", "pgp+eddsa-ed25519"): GPGKey,
Copy link
Collaborator

Choose a reason for hiding this comment

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

are these the same keytype/scheme combos that were used before, but the serialization format is now slightly different? Would it make sense sense to use e.g. a different keyname?

I guess in-toto is the only existing user... so do what makes sense there.

Copy link
Member Author

Choose a reason for hiding this comment

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

The thing is, these keytype/scheme combos are completely new. We did use the same values as type/method combos, but I think that's okay.

@lukpueh lukpueh merged commit 7252521 into secure-systems-lab:main Mar 9, 2023
lukpueh added a commit to lukpueh/securesystemslib that referenced this pull request Apr 23, 2024
The `gpg` subpackage provides a vaguely defined API (`gpg.functions`) to
create signatures, export public keys, and verify signatures. This API
and the used formats are incompatible with the securesystemslib signer
API.

For the sake of a consistent API, the `gpg` subpackage is marked as
internal (renamed to `_gpg`) and the above mentioned functionality is
exposed via the new signer API. Replacement methods are:

- `GPGSigner.import_` (replaces `export_pubkey`)
- `GPGSigner.sign`
- `GPGKey.verify_signature`

Note that public key and signature formats also change, in order to match
`Key` and `Signature` interfaces. This means:

- signature field `signature` is renamed to `sig`
- public key fields `type`, `method` and `hashes` are replaced by
  `keytype` and `scheme` fields, and
- public keys no longer include `subkeys` or key expiration infos. This
  means that the signature verification function no longer needs to
  decide, if a key is authorized or valid to verify a given signature.

See discussion for context:
secure-systems-lab#488 (comment)
secure-systems-lab#488 (comment)

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
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

Successfully merging this pull request may close these issues.

GPG public key schemas are incompatible with both TUF and in-toto specifications
3 participants