-
Notifications
You must be signed in to change notification settings - Fork 16
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
Implement OpenPGP Signature generation using PGPainless #48
base: master
Are you sure you want to change the base?
Conversation
core/src/main/java/org/eclipse/packager/security/pgp/SigningStream.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks nice. There's a typo in the PR title.
rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpHeaderSignatureProcessor.java
Outdated
Show resolved
Hide resolved
Thanks for the PR. IIRC I had a quick exchange with "Neal" on the fediverse (https://mastodon.dentrassi.de/@[email protected]/110379136551193171) about such a PR. There are two aspects IMHO: a) making it easier to work with crypto (thus reducing errors) … b) reducing the number of dependencies (thus reducing the code base which might contain bugs). To my understanding, this PR (using pgpainless) adds a new layer on top of bouncycastle (BC) not replacing it. There's a bunch of tasks which we have to do here:
So just to be clear, I am still not 100% convinced that adding this out-weights adding a new dependency. I think it makes sense to have an open discussion around it, being able to prove/document why this makes sense. I hope you understand this. If you want, you can also join the Matrix room that we have (https://matrix.to/#/#packager:matrix.eclipse.org), which might make a conversion (or pinging) easier than on GitHub. |
I did a first test just to see what the impact is. I did this in combination with the
I think it might make sense to combine this PR with a PR on the rpm-builder. I know the rpm-builder isn't part of this project, but it is one of the main users. |
Hey, Benefits of using PGPainless vs. Bouncycastle:Using Bouncycastle directly leaves bigger room for errorWhile I see that you are using the BC API mostly correctly in the existing codebase, there are some places where the existing implementation is not acting ideal:
PGP is complex
Arguably, all these issues are fixable in code here, since producing signatures is way easier than verifying them, but I still think it is worthwhile relying on a library whose sole purpose is to get the PGP part right :D Regarding OSGI: I need to look into this, as honestly I never dealt with OSGI bundles before. If it isn't too hard though, I can see what I can do to make PGPainless compliant. I think you are right with regards to the API breakage with PGPPrivateKeys. I will restore the old methods and mark them as deprecated instead, like you proposed. There is no need to rush this PR, just merge it once you are happy with it :) |
I added back some of the old signing logic and moved some new logic to dedicated classes. I looked into OSGI. Unfortunately there doesn't appear to be an official Gradle plugin to build OSGI bundles, so the process would be very manual-ish. Do you know of a place to look that I might have missed? Another question that crossed my mind: The current implementation tags signatures using the I made some changes to PGPainless which this PR will benefit from and which will make it into a new release I will be doing soon :) |
No worries about delays. I am the one not finding enough time for this :) Btw, I will be out for a week and a half, so don't expect too much during that time. Sounds good. I also think that it may be ok to trim down old APIs. As long as there's an alternative patch and we still can support the RPM builder use case. Regarding OSGi (and Gradle), stackoverflow claims it is as easy as
And again, not having OSGi wouldn't be a roadblock for me. Maybe someone asks for it, then we would need to figure it out. Just noted that this would be the case. Following up on the "why": Tell me about SHA1 and GPG 😁 The reason I don't have too much time for this is SHA1, v3 signatures,
I agree there. Every library should have a focus/scope and PGP isn't the focus of "packager", so that makes sense. And to be honest, the more I see in Sequoia, the less I want to do myself in this area :) Regarding the compatibility with the RPM builder, I was hoping to move forward to the new implementation. Which requires some changes in the RPM builder too. Otherwise there wouldn't be too much usage of the new feature. So kicking out the old APIs is fine. And I am not a real fan of the naming of I will try to adapt this on the RPM builder, but I just don't know how/when I will find time for this. |
I will follow up with a few things I notice as I progress (if that's not your style, then please just ignore it, I will try to sum it up later):
|
Ok, I guess I figured it out: I assumed "key ring" would contain multiple keys. But that seems to be |
I created a PR which should adopt those changed in RPM packager: ctron/rpm-builder#78 @vanitasvitae While the tests run fine, I would appreciate second pair of eyes. |
Yeah, the naming is unfortunate. I did it because I did not want to remove the old SigningStream to keep the old API.
I will remove the RSA classes then :)
Good point!
Magic :) |
What if there are multiple candidates? (this is an age-old question) |
PGPainless currently signs with all available signing keys, if no specific key-id is given. |
I believe that is the correct behavior for a general purpose implementation 👍 I'm not sure if it is the correct one in this case, but I would suggest if only one signature is desired the correct way to address that would be to not have the other signing subkeys available for signing in this context in the first place. |
Good point, thanks for bringing this up. Edit: And the ugly |
"The first one" is likely what a BC implementation did before as well, but it's not exactly a well defined criteria. Then again, any signing subkey is as good as the next so it's not "wrong" to do like that per se 🤷 |
They way I use it now in the RPM builder is to load the full key chain collection, pick the key by ID (as provided by the user). That results in a "key ring", which to my understanding now still could have multiple keys for signing. Couldn't we simply allow the user to select this (via an Optional, falling back the the first suitable)? If the user provides a key ID, then the "key ring" containing this key should be picked, and when signing this specific key should be used. If that's not possible, it should fail. That way we would have both: sane defaults & control by the caller |
Yes, that's right. The only issue is that "the first" is essentially picking one (deterministically) at random. As is any other criteria - the newest, the one with the longest expiration, etc, none of those qualify as "least surprising" to the user. If there is already an option to pick, then requiring that argument if there are multiple choices would avoid any ambiguity, which sounds like a good thing for me. Most folks don't have two signing subkeys on their certificate, and those who do for one reason or another would just have to pick. If that sounds too bothersome for users of such keys, it would be good to at least output a hint to the user that this is happening when it is 👌 |
rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpHeaderSignatureProcessor.java
Show resolved
Hide resolved
rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpHeaderSignatureProcessor.java
Outdated
Show resolved
Hide resolved
rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpHeaderSignatureProcessor.java
Show resolved
Hide resolved
OK, I still see some issue. We have So, maybe we can just make |
My idea behind introducing |
So, are we deprecating the old classes? I don't see |
Deprecating them would be an option, yes. I'll create another commit :) |
@ctron So I am OK with this, but do you want to test against rpm-builder first? |
<version>${bouncycastle.version}</version> | ||
<groupId>org.pgpainless</groupId> | ||
<artifactId>pgpainless-core</artifactId> | ||
<version>1.6.7</version> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be provided as a variable.
@@ -8,8 +8,8 @@ | |||
* SPDX-License-Identifier: EPL-2.0 | |||
* | |||
* Contributors: | |||
* mat1e, Groupe EDF - initial API and implementation | |||
* Jens Reimann, Red Hat, Inc | |||
* mat1e, Groupe EDF - initial API and implementation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't change existing contributor lines, but add yourself as one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This must have been some spotless apply
removing the whitespace, sorry.
Yes, I do think we need a change that works for RPM builder too. Otherwise we're just breaking the current API without having a way to prove that the new API supports existing applications (rpm-builder being one of them). I am also still not a fan of And just dropping BC for a new dependency, handling all the signing stuff, without some proper reasoning just feels wrong. I agree that that there are some things to clean up in the current code (like the SHA1 -> SHA256 stuff, and more). But maybe we should split this up. And maybe it makes more sense to provide an additional option (using pgpainless) rather than swapping out the current approach. As said before, I am ok with a breaking release. I am ok dropping OSGi (and things like Java 8). And I also think it good to enable the use of pgpainless. But I am not so convinced on simply replacing the current code and breaking the API at the same time is the best approach. My proposal would be:
I personally don't have time to actually do the implementation. But I can definitely help, test, and review. |
I'll create another PR with the small improvements separated out :) Some background for why I chose to create SigningStream2: PGPainless follows another paradigm. Its philosophy is to take the burden of "key interpretation" (that is decide which algorithms to use, etc.) off the user. PGPainless' signing API therefore takes a PGPSecretKeyRing (the whole secret key + subkeys), where the public key components and signatures are accessible. It then evaluates the signatures and deduces the preferred algorithms, which subkey to use for signing etc. So the user does not need to know anything about the inner workings of OpenPGP, they just need to supply the key and passphrase if needed. |
I see the benefit of What I am concerned about is adding a new dependency handling quite sensitive material, for the benefit of convenience. I raised that concern initially, and I think last week we saw a good reason of "why". On the other side, I also see the benefit of making pgp less painful, I really do. So I think it makes more sense to let the user choose. |
I understand your points. Especially given the xz debacle, I expect scrutiny for my PRs :D I'm struggling a bit getting a good idea of how you'd like me to proceed now though. Do you want me to move the new classes into a separate module? My first impulse was to have a factory class, which can be overridden by the PGPainless implementation, which would swap in the PGPainless classes, so that the user can chose between implementations by swapping out the factory. However, such a class currently doesn't exist and it appears as if the user needs to assemble their signing pipeline by hand. How would you proceed? Edit: I would like to work with an API like "Here is a key (file/input stream), here is a passphrase, here is an optional digest algorithm and optional key-id/fingerprint, now give me back some SignatureProcessors / SigningStream". |
I agree, it could use a more descriptive name |
I'm working on a new PR which introduces some factories :) |
The way I understood it, PGPainless would be an optional dependency A factory is more of a design issue, but the API should not need to change from a user perspective in order to use PGPainless. If possible, rpm-builder can get support without any changes except adding PGPainless to its own |
I see.
|
I think I misunderstood the use of the key-id argument. After taking a look at how rpm-builder sets up the signing, I now understand that the file the user passes in may contain many keys (i.e. their GnuPG key ring) and that the key-id is used to identify the particular OpenPGP key that shall be used for signing. This makes much more sense. But now I wonder: Lets say the user has the following key on their key ring (ignore the non-hex fingerprint):
It should be obvious, that the only key usable for signing in this case is the subkey 0xSIGNING (though the existing implementation would erroneously use 0xPRIMARY if the user passed in 0xPRIMARY). My proposal would be: If the user passes in 0xPRIMARY or 0xSIGNING, use the subkey 0xSIGNING for signing. Alternatively, I could imagine to still use 0xSIGNING in this case, although this might obfuscate some configuration errors. Now the last question is, how to handle the following key:
Should the implementation always use the newest signing key here? |
I created #56 as an alternative to this PR. |
Hey!
I noticed, that packager uses Bouncycastle to sign packages.
Using Bouncycastle for OpenPGP operations has the caveat that BC is pretty low-level and error prone.
For example, it will not warn you if your OpenPGP key is expired.
PGPainless is a wrapper around BC that is designed with ease of use and correctness in mind (see these test results).
It abstracts away the hard parts of BC behind a simple API that is designed to be sane and secure by default.
This PR replaces the use of BC for signing with PGPainless. Since PGPainless internally uses BC, this PR implicitly bumps BC from 1.71 to 1.73.
The API of some methods has changed a bit. PGPainless works on
PGPSecretKeyRing
objects instead ofPGPPrivateKey
s. The reason is, that an OpenPGP key could have more than one subkey. PGPainless will automatically select the appropriate signing subkey, validating the whole key.Further, I removed the HashAlgorithm argument, since PGPainless will automatically select a signature hash algorithm based on the keys preferences and an algorithm policy, which prevents the use of weak algorithms (such as SHA1).
Let me know what you think of the patch :)