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

RFE: add support for multiple OpenPGP signatures per package #3385

Closed
pmatilai opened this issue Oct 17, 2024 · 38 comments
Closed

RFE: add support for multiple OpenPGP signatures per package #3385

pmatilai opened this issue Oct 17, 2024 · 38 comments
Labels
crypto Signatures, keys, hashes and their verification fileformat Matters concerning package (file) format RFE v6 Related to rpm v6 (readiness)
Milestone

Comments

@pmatilai
Copy link
Member

pmatilai commented Oct 17, 2024

Initially requested in #189 and one possible implementation drafted in #1050, but lacking direction and motivation at the time. The topic rose again as in the context of Post Quantum signatures in #3363 - something rpm would rather not know anything about. #1050 added labels to each signature but this is apparently a goofy idea (I think I stole it from Debian at the time), and back then rpm v3 header+payload signatures complicated the backwards compatibility store quite a bit, which is where I ran out of steam in the face of lack of general interest. The discussion in #3363 provided us with a nice clear path ahead now, with multiple benefits, so much so that it'd be stupid not to do this now:

  • support for multiple signatures has generic benefits and use-cases
  • puts a further layer of insulation between rpm and crypto, something we have been actively driving for a couple of years now
  • adds provisions for PQC without us getting directly involved in it
  • fixes up a long-standing terminology mixup with tag names
  • lines up nicely with the rpm v6 theme and timing
  • most of it is already implemented

What the implementation will do:

  • add a string array RPMTAG_OPENPGP tag and RPMSIGTAG_OPENPGP alias
  • RPMTAG_OPENPGP may contain one or more independent OpenPGP signatures base64-encoded (the header doesn't support binary arrays)
  • the RPMTAG_OPENPGP signatures are always header-only, and we'll call them RPM v6 signatures
  • extend :pgpsig format to handle new format
  • add the new tag to rpm -qi Signature: output
  • rpmsign --addsign appends a new signature to the RPMTAG_OPENPGP tag
  • rpmsign --delsign deletes all signatures from the package
  • rpmsign --resign deletes all signatures before adding a new one
  • verification will:
    • ignore unknown algorithms
    • require all known (and enabled) signatures to pass for a positive verification
  • backwards compatibility is handled as follows:
    • when signing v4 packages and --rpmv6 is specified, the first added RSA/DSA/EcDSA signature is additionally stored in binary format to RPMTAG_RSAHEADER/RPMTAG_DSAHEADER as appropriate
    • v6 packages get only RPMTAG_OPENPGP signatures, unless --rpmv4 switch (added by the PR) is used - this allows rpm v4 to verify such packages, in which case it behaves the same as signing v4 packages

Allowing to replace or delete a specific signature would be out of the initial scope but can be done later. Additional controls for verification policies can/will be added later.

Edit: add default verification policy

@pmatilai pmatilai added RFE fileformat Matters concerning package (file) format crypto Signatures, keys, hashes and their verification labels Oct 17, 2024
@pmatilai pmatilai added this to the 6.0.0 alpha milestone Oct 17, 2024
@pmatilai
Copy link
Member Author

@simo5 @nwalfield @mlschroe @ffesti thoughts - did I miss some finer details, are there elephants in the room etc?

@pmatilai
Copy link
Member Author

One potential open question is --resign: right now it's just an alias for --addsign because there's no practical difference between the two. With this, it could mean something else, probably delete any existing signatures and then sign. Of course this just a minor non-critical detail and can be dealt with later as well.

@pmatilai pmatilai added the v6 Related to rpm v6 (readiness) label Oct 17, 2024
@simo5
Copy link

simo5 commented Oct 17, 2024

Sounds reasonable that --resign will drop all signatures and add new ones.
I think the only potentially missing case here is the desire to drop only a specific signature.

The reason to do that is if you have a package with multiple signatures and you want to replace only one that had a signing key compromised while the others did not.

The use case is packages re-distributed by a 3rd party that wants to retain the original signatures and can't recreate them because they have no access to those keys.

I wonder if --resign could be enhanced to be able to specify a signature to replace, in which case it would only replace the specific signature and not drop them all ?

This is really a corner case and if it is complicated it can definitely be deferred or even not made available.

@JanZerebecki

This comment was marked as off-topic.

@simo5

This comment was marked as off-topic.

@simo5

This comment was marked as off-topic.

@pmatilai
Copy link
Member Author

Yeah --delete and --resign deleting everything is basically just the simplest possible semantics to move forward. I certainly see use-cases for deleting or replacing a specific signature instead, and since that doesn't require any format changes it can be done later once the more critical stuff is out of the way. So I think if we move ahead with this plan, I'd just file another ticket for deleting/replacing a specific signature in some point in the future.

@JanZerebecki

This comment was marked as off-topic.

@simo5

This comment was marked as off-topic.

@pmatilai

This comment was marked as off-topic.

@jcpunk

This comment was marked as off-topic.

@DemiMarie
Copy link
Contributor

I recommend against using base64. Just use length-prefixed binary blobs. If base64 is used, the decoder should be very strict and only accept data that round-trips correctly (so no whitespace, etc).

@pmatilai
Copy link
Member Author

We don't want to add a "proprietary" format for such a thing because standard header APIs are then no longer usable for accessing and modifying it, and then you only have more code to worry about. We could also just use hex strings, size is not a concern here. But, if we can't be trusted to decode base64 then how are we expected to read the rest of the rpm? That's like being too afraid to leave the house because something bad might happen. I'll note that this is something that could be easily outsourced to rpm-sequoia.

Binary arrays could be sort of handled with existinging code u,sing an embedded header (it's just another binary blob afterall), using tag number as the index. But this gets weird and wacky and it's not like the header itself is a trivial structure to parse.

@dmnks dmnks added this to RPM Oct 22, 2024
@github-project-automation github-project-automation bot moved this to Backlog in RPM Oct 22, 2024
@Conan-Kudo

This comment was marked as off-topic.

@pmatilai
Copy link
Member Author

pmatilai commented Nov 5, 2024

Folks wanting to discuss pros and cons of detached signatures are welcome to do so in the relevant topic but is off-topic here, and has now been flagged such. This ticket is about a new form of embedded signatures in rpm 6.0. Thank you.

@pmatilai
Copy link
Member Author

pmatilai commented Nov 5, 2024

One thing the description doesn't currently cover is the verbose level verification messages, in particular the enforcing mode where it spews out everything it looked at. For example with an unsigned package in enforcing mode, you'd get something like (the last two non-prefixed items stand for legacy Header+payload signatures):

/data/RPMS/hello-2.0-1.x86_64:
    Header RSA signature: NOTFOUND
    Header DSA signature: NOTFOUND
    Header SHA256 digest: OK
    Payload SHA256 digest: OK
    RSA signature: NOTFOUND
    DSA signature: NOTFOUND

I think we need to lump all the OpenPGP signatures under one label per range to make any sense out of this, ie:

/data/RPMS/hello-2.0-1.x86_64:
    Header OpenPGP signature: NOTFOUND
    Header SHA256 digest: OK
    Payload SHA256 digest: OK
    Header+payload OpenPGP signature: NOTFOUND

I'm tempted to add "Legacy" in front of the last item because that's what it is, and multiple signatures wont be supported for those. It's a dying breed already in v4, and I'm tempted to drop support for creating them at all in 6.0. We'll need to verify them to properly support v4 but we probably shouldn't even look for them in v6 packages. rpmsign will not create those entries for v6 packages anyhow, but it seems these days rpmsign is the last tool anybody uses for signing...

A possible sample output from a package with multiple signatures:

/tmp/hello-2.0-1.x86_64.rpm:
    Header OpenPGP V4 ECDSA/SHA512 signature, key fingerprint: e8a62c0512b06b5d2183ba207f1c21f95f65bbe8: OK
    Header OpenPGP V4 RSA/SHA512 signature, key ID 4344591e1964c5fc: NOKEY
    Header OpenPGP V4 EdDSA/SHA512 signature, key fingerprint: 152bb32fd9ca982797e835cfb0645aec757bf69e: OK
    Header SHA256 digest: OK
    Payload SHA256 digest: OK

pmatilai added a commit to pmatilai/rpm that referenced this issue Nov 6, 2024
This clarifies what these things are - not raw DSA/RSA signatures but
OpenPGP signatures. It also opens the door for other types of signatures
somewhere in the future, even though no such things are in the plans
just now.

Most importantly though, this will be needed to make sense of these
messages with the multiple OpenPGP signature support where we no longer
know such algorithm details.

Related: rpm-software-management#3385
pmatilai added a commit to pmatilai/rpm that referenced this issue Nov 6, 2024
Header+payload signatures and digests are rpm v3 era stuff, we still
process them but let people know what they are.

What a joyous exercise in sed...

Related: rpm-software-management#3385
pmatilai added a commit to pmatilai/rpm that referenced this issue Nov 6, 2024
This clarifies what these things are - not raw DSA/RSA signatures but
OpenPGP signatures. It also opens the door for other types of signatures
somewhere in the future, even though no such things are in the plans
just now.

Most importantly though, this will be needed to make sense of these
messages with the multiple OpenPGP signature support where we no longer
know such algorithm details.

Related: rpm-software-management#3385
pmatilai added a commit to pmatilai/rpm that referenced this issue Nov 6, 2024
Header+payload signatures and digests are rpm v3 era stuff, we still
process them but let people know what they are.

What a joyous exercise in sed...

Related: rpm-software-management#3385
@Conan-Kudo
Copy link
Member

@Conan-Kudo the simplest policy is that signatures must all verify (why would you put multiple of them otherwise?).

Multiple signatures aren't necessarily for users installing to process, so it would make sense to ignore them in that case. For example, the signatures may be used to indicate something passed through certain stages. You may have a policy to validate them all, but it may not actually be a required policy. Some signatures may only be for some systems to validate but not others.

I can think of a variety of reasons for it. But regardless, I think it does make sense to have some way to indicate a primary/key signature to validate.

@JanZerebecki
Copy link
Contributor

One important point of my suggestion is that the list of keys that are associated with a repo is signed and verifiable with the same list, but is distinct from the keys that are trusted to sign repos. Trusted keys are a superset of keys used in a repo.

This makes verifying parts of a repo more deterministic. If your config verified the repo, the uncompromised rpms will always verify. You do not suddenly get one rpm that has only a new signature that you always skipped on the other rpms because they had also an old one.

@pmatilai
Copy link
Member Author

pmatilai commented Nov 7, 2024

@pmatilai How do we decide when a package "fails" verification with multiple signatures? Would we have a policy tunable? Some kind of indicator as a "primary" signature? Or something else?

Hmm, I thought it was in the description as it's been discussed elsewhere but apparently not - will fix. The initial implementation will indeed simply require all signatures to pass. I expect us to have various extra controls later.

Rpm currently has disablers like RPMVSF_NORSAHEADER that operate on the tag level because that's how the signatures are spread out per algorithm, I think we'd extend this to simply operate on algorithm level instead, which means you can explicitly disable eg an algorithm considered compromised and if that's the only thing there was, you fail to get a positive verification.

As for unknown signatures, I hadn't really gotten there yet. But there is indeed only one possible default: to ignore anything unknown, because that's the only way to deal with forward compatibility - like @simo5 said. If in doubt, think about this: we add this new RPMTAG_OPENPGP signature tag into rpm now. Older rpm versions simply do not know about this tag, so they will not look there, much less try to verify anything in there. And that's exactly what allows forward compatibility to exist: older rpm versions can still verify the packages to the best of their abilities, we cannot expect them to do anything more. And that's exactly what we must do with the new signatures too - just ignore if not known. If there are no known signatures at all then you fail to get a positive verification, and that's again how it should be.

Note all the talk about positive verification: as a reminder, rpm 6.0 will ship with enforcing signature checking on by default (#1573). So you need to make that assumption when talking about this stuff now, otherwise none of it makes any sense. Just like rpm 4.x default signature behavior makes no sense whatsoever.

@pmatilai
Copy link
Member Author

pmatilai commented Nov 7, 2024

Also note that dnf (or otherwise) repositories are way out of scope and topic here, this is strictly an rpm level matter. How multiple signatures are dealt with on repo level is a repo tooling headache to be discussed elsewhere.

@pmatilai
Copy link
Member Author

pmatilai commented Nov 7, 2024

The point was not about the correctness of our implementation of base64, but that the format should have only one canonical encoding any alternate encodings being rejected. It also makes the format more reproducible.

When incorporating existing formats, it is suggested to use a format whose normal spec is strict in that regard.

If base64 is bad, what is good then? Plain hex better? @simo5 - thoughts on the encoding? I'm not particularly in love with base64, it's just a format we already have to deal with, and one that isn't as dumb as plain hex space-wise. For traditional signatures, space isn't critical because we're not expecting a single package to have hundreds of thousands of signatures. Are PQ signatures significantly bigger? (I've never seen one, I've no idea)

pmatilai added a commit to pmatilai/rpm that referenced this issue Nov 7, 2024
This clarifies what these things are - not raw DSA/RSA signatures but
OpenPGP signatures. It also opens the door for other types of signatures
somewhere in the future, even though no such things are in the plans
just now.

Most importantly though, this will be needed to make sense of these
messages with the multiple OpenPGP signature support where we no longer
know such algorithm details.

Related: rpm-software-management#3385
pmatilai added a commit to pmatilai/rpm that referenced this issue Nov 7, 2024
Header+payload signatures and digests are rpm v3 era stuff, we still
process them but let people know what they are.

What a joyous exercise in sed...

Related: rpm-software-management#3385
ffesti pushed a commit that referenced this issue Nov 7, 2024
This clarifies what these things are - not raw DSA/RSA signatures but
OpenPGP signatures. It also opens the door for other types of signatures
somewhere in the future, even though no such things are in the plans
just now.

Most importantly though, this will be needed to make sense of these
messages with the multiple OpenPGP signature support where we no longer
know such algorithm details.

Related: #3385
ffesti pushed a commit that referenced this issue Nov 7, 2024
Header+payload signatures and digests are rpm v3 era stuff, we still
process them but let people know what they are.

What a joyous exercise in sed...

Related: #3385
@simo5
Copy link

simo5 commented Nov 7, 2024

The encoding does not affect the feature for me so I have no opinion.
Some PQ signatures can be big.

The scheme that has the biggest signatures for now is SLH-DSA (formerly known as SPHINCS+) and the stronger variant has a signature size of 50KiB.

ML-DSA's (aka Dilithium) biggest signature is ~ 4600 bytes.

pmatilai added a commit to pmatilai/rpm that referenced this issue Nov 11, 2024
Add support for multiple OpenPGP header signatures per package, base64
encoded in a string array, also known as rpm v6 signatures.

--addsign no longer deletes any signatures, it only creates and adds a
new signature if possible. --delete and --resign behave as before: they
delete ALL signatures on the package, and the latter then creates and
adds a new one.

For v6 packages this is the default signature type, but if requested,
one v4 compat signature can be created for compatible algorithms. v6
signatures on v4 packages are also supported, but have to be explicitly
requested. In that case, v3/v4 signatures are only added if none already
exist and a v4 compatible algorithm is used. v3 signatures on v6
packages are not supported (out of principle, not a technical
limitation)

On verification, if RPMTAG_OPENPGP exists then other signature tags are
ignored because they're expected to only contain compat copies of the
same content. As of now, all existing signatures must validate for
signature checking of a package to pass, further policies are to be
added later.

Besides the concrete RPMTAG_OPENPGP signature tag, add an extension by
the same name to handle compatibility with v3/v4 signatures: a user will
only need to query the RPMTAG_OPENPGP extension to get all the
signatures at once. Extend :pgpsig tag format to handle the new variant.

Update --info/-i query to output all existing signatures, one per line.
The no-signature case of "Signature  : (none)" is preserved as-is to
help backwards compatibility with scripts parsing the output.

Fixes: rpm-software-management#3385
pmatilai added a commit to pmatilai/rpm that referenced this issue Nov 11, 2024
Add support for multiple OpenPGP header signatures per package, base64
encoded in a string array, also known as rpm v6 signatures.

--addsign no longer deletes any signatures, it only creates and adds a
new signature if possible. --delete and --resign behave as before: they
delete ALL signatures on the package, and the latter then creates and
adds a new one.

For v6 packages this is the default signature type, but if requested,
one v4 compat signature can be created for compatible algorithms. v6
signatures on v4 packages are also supported, but have to be explicitly
requested. In that case, v3/v4 signatures are only added if none already
exist and a v4 compatible algorithm is used. v3 signatures on v6
packages are not supported (out of principle, not a technical
limitation)

On verification, if RPMTAG_OPENPGP exists then other signature tags are
ignored because they're expected to only contain compat copies of the
same content. As of now, all existing signatures must validate for
signature checking of a package to pass, further policies are to be
added later.

Besides the concrete RPMTAG_OPENPGP signature tag, add an extension by
the same name to handle compatibility with v3/v4 signatures: a user will
only need to query the RPMTAG_OPENPGP extension to get all the
signatures at once. Extend :pgpsig tag format to handle the new variant.

Update --info/-i query to output all existing signatures, one per line.
The no-signature case of "Signature  : (none)" is preserved as-is to
help backwards compatibility with scripts parsing the output.

Fixes: rpm-software-management#3385
pmatilai added a commit to pmatilai/rpm that referenced this issue Nov 18, 2024
Add support for multiple OpenPGP header signatures per package, base64
encoded in a string array, also known as rpm v6 signatures.

--addsign no longer deletes any signatures, it only creates and adds a
new signature if possible. --delete and --resign behave as before: they
delete ALL signatures on the package, and the latter then creates and
adds a new one.

For v6 packages this is the default signature type, but if requested,
one v4 compat signature can be created for compatible algorithms. v6
signatures on v4 packages are also supported, but have to be explicitly
requested. In that case, v3/v4 signatures are only added if none already
exist and a v4 compatible algorithm is used. v3 signatures on v6
packages are not supported (out of principle, not a technical
limitation)

On verification, if RPMTAG_OPENPGP exists then other signature tags are
ignored because they're expected to only contain compat copies of the
same content. As of now, all existing signatures must validate for
signature checking of a package to pass, further policies are to be
added later.

Fixes: rpm-software-management#3385
pmatilai added a commit to pmatilai/rpm that referenced this issue Nov 18, 2024
Add a tag extension for RPMTAG_OPENPGP (on top of the concrete tag) to
handle compatibility with v3/v4 signatures: the extension collects all
legacy signatures under the same umbrella so users don't need to query
multiple different tags, you just query for RPMTAG_OPENPGP to get all
them at once. Extend :pgpsig tag format to handle the new string
array/base64 variant.

Update --info/-i query to use the extension and output all existing
signatures, one per line. The no-signature case of "Signature  : (none)"
is preserved as-is to help backwards compatibility with scripts parsing
the output.

Related: rpm-software-management#3385
@ffesti ffesti closed this as completed in 5630cf4 Nov 19, 2024
ffesti pushed a commit that referenced this issue Nov 19, 2024
Add a tag extension for RPMTAG_OPENPGP (on top of the concrete tag) to
handle compatibility with v3/v4 signatures: the extension collects all
legacy signatures under the same umbrella so users don't need to query
multiple different tags, you just query for RPMTAG_OPENPGP to get all
them at once. Extend :pgpsig tag format to handle the new string
array/base64 variant.

Update --info/-i query to use the extension and output all existing
signatures, one per line. The no-signature case of "Signature  : (none)"
is preserved as-is to help backwards compatibility with scripts parsing
the output.

Related: #3385
@github-project-automation github-project-automation bot moved this from Backlog to Done in RPM Nov 19, 2024
dmnks pushed a commit to dmnks/rpm that referenced this issue Nov 21, 2024
Add support for multiple OpenPGP header signatures per package, base64
encoded in a string array, also known as rpm v6 signatures.

--addsign no longer deletes any signatures, it only creates and adds a
new signature if possible. --delete and --resign behave as before: they
delete ALL signatures on the package, and the latter then creates and
adds a new one.

For v6 packages this is the default signature type, but if requested,
one v4 compat signature can be created for compatible algorithms. v6
signatures on v4 packages are also supported, but have to be explicitly
requested. In that case, v3/v4 signatures are only added if none already
exist and a v4 compatible algorithm is used. v3 signatures on v6
packages are not supported (out of principle, not a technical
limitation)

On verification, if RPMTAG_OPENPGP exists then other signature tags are
ignored because they're expected to only contain compat copies of the
same content. As of now, all existing signatures must validate for
signature checking of a package to pass, further policies are to be
added later.

Fixes: rpm-software-management#3385
dmnks pushed a commit to dmnks/rpm that referenced this issue Nov 21, 2024
Add a tag extension for RPMTAG_OPENPGP (on top of the concrete tag) to
handle compatibility with v3/v4 signatures: the extension collects all
legacy signatures under the same umbrella so users don't need to query
multiple different tags, you just query for RPMTAG_OPENPGP to get all
them at once. Extend :pgpsig tag format to handle the new string
array/base64 variant.

Update --info/-i query to use the extension and output all existing
signatures, one per line. The no-signature case of "Signature  : (none)"
is preserved as-is to help backwards compatibility with scripts parsing
the output.

Related: rpm-software-management#3385
@pmatilai
Copy link
Member Author

pmatilai commented Nov 28, 2024

Hmm. Just realized that we're accidentally using ASCII-armored signatures when signing with Sequoia, whereas GPG always used binary signatures. And when we then base64 encode that, it gets silly. But that also made me realize that we could use ASCII armored signatures as-is in the RPMTAG_OPENPGP tag, somehow this option never even crossed my mind, I was so fixated on the gpg-era default of "signatures are binary". We could still change this of course - thoughts?

@nwalfield
Copy link
Contributor

I'm mostly ambivalent. I think that forensics (i.e., manually extracting the signature and examining it) are a tiny bit easier when using ASCII armor than using a "custom" encoding.

@pmatilai
Copy link
Member Author

pmatilai commented Nov 28, 2024

That's pretty much my view on it too: if it's an OpenPGP signature in a string array, it might as well just say so without having to run it through various extra formatting/processing 😅

@DemiMarie
Copy link
Contributor

I strongly oppose ASCII armor, as it injects a lot of additional malliability.

@simo5
Copy link

simo5 commented Nov 30, 2024

I strongly oppose ASCII armor, as it injects a lot of additional malliability.

What does that even mean?
It is just a format with no ambiguity in representation.

Given the current code wants ASCII I would definitely just use the ASCII Armored signature directly and avoid the silly step. I see no need to force a binary representation.

@DemiMarie
Copy link
Contributor

Malliability means the ability to modify the signature in a way that still produces a valid signature. Base64 with suitable constraints is 1-to-1. ASCII armor is not.

@simo5
Copy link

simo5 commented Dec 2, 2024

I know what malleability means, what makes no sense to me is your claim, out of the blue, that the official way to release PGP signatures in printable form is somehow vulnerable to confounding attacks, and how they would be relevant here.

If the signature validates it is a valid signature and there is no fudging with the ASCII text that will make it more or less so.

I am aware of no relevant issues with armored files, do you have any published analysis that support your stance and would show it being relevant to the RPM situation ?

@DemiMarie
Copy link
Contributor

There is no relevant security issue in this application, since RPM doesn’t rely on non-malleability and the actual parsing is done by Sequoia.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
crypto Signatures, keys, hashes and their verification fileformat Matters concerning package (file) format RFE v6 Related to rpm v6 (readiness)
Projects
Status: Done
Development

No branches or pull requests

7 participants