-
Notifications
You must be signed in to change notification settings - Fork 400
signature: add OpenPGP signing mechanism based on Sequoia #2569
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
base: main
Are you sure you want to change the base?
Conversation
0f4142d
to
5003e75
Compare
(I'm not an "official" reviewer here, FWIW) I skimmed your code and I have to say it looks generally great. But very similarly to your PR in ostreedev/ostree#3278 it'd be quite nice I think to list out why we're making this change. Especially questions like: Do we still need the openpgp backend too going forward? I do wonder whether it makes sense for such a thing to live separately from this repository. Would it have a stable API/ABI? Of course adding it to this repository suddenly grows the scope of things here from "Go" to "Go, Rust and C" which is not at all a small thing. But, I personally also do like the idea of opening up to thinking about how we use Rust code in this stack too. (To be clear, I am not speaking in any way for the other people who have done 99% of the work in this repository that are not me) |
Hey @ueno , I’m very sorry I couldn’t get back to this discussion since … April?, due to a different RHEL priority (… possibly relevant here: sigstore signatures, using the Go standard-library The code in this PR is all good. We do need to figure out packaging, and RH package ownership.
There’s a product discussion motivating the inclusion of this — and I assume product concerns would drive the continued existence of OpenPGP for a number of years. The default … should be kept up to date, but also needs to be somewhat practical to use.
This repository is currently consumed by referencing it in *shrug* That means that adding an entirely new built artifact into this repo would probably not hurt any existing packaging. But one other implication is that different versions of “Podman & friends” can be concurrently installed and include different versions of c/image, i.e. the C-API shared library (in a single system-wide location) will have to maintain a stable ABI to support all of those versions; directly including the Rust+C part in the c/image repo would not allow us to develop the server and client in immediate lockstep, so there’s no direct benefit of using one repo for all. One thing I’m unsure about is assumptions of the Go tooling about repository contents — we have a Podman etc. developers who don’t directly work on signing do need some reasonably convenient way to get an environment which doesn’t panic during process start. For GPGME, we tell them to install existing packages, on both Linux and macOS: https://github.com/containers/image/tree/main?tab=readme-ov-file#building . Something at about that level of hassle (or less) should exist for https://github.com/containers/image/tree/main?tab=readme-ov-file#building , or those developers are going to start sticking |
In the discussion in April, a couple of motivations raised are:
As for the future of the openpgp backend, I agree with @mtrmac that it still has some value to maintain it as a legacy/standalone backend, even if sigstore signatures are preferred.
Not sure if this is an ideal solution, but one option might be to merge mechanism_sequoia.go into mechanism_gpgme.go (rename it accordingly), and make it use the existing GPGME backend as a fallback if it fails to load |
fa356ee
to
23fec2d
Compare
That’s interesting … that would certainly eliminate concerns about developers not being able to work on other things. OTOH we’d then need some other mode to run in tests, and in production, to ensure we are using the right backend. |
23fec2d
to
995e8aa
Compare
@mtrmac I've reworked this and incorporated all the required code into the same tree, instead of vendoring an external repository. The rust code is now hosted in signatures/sequoia/rust, which can be packaged something like rust-rpm-sequoia; we (RHEL Crypto team) are happy to own the downstream package. As for the CI coverage, the base CI image needs to be updated to include the Rust toolchain and OpenSSL development files. Could you help with that? |
995e8aa
to
f8df8c8
Compare
f8df8c8
to
bdfcafc
Compare
To test it locally, I currently do the following on Fedora 41: cd signature/sequoia/rust
PREFIX=/usr LIBDIR="\${prefix}/lib64" cargo build --release
cd -
sudo update-crypto-policies --set LEGACY
LD_LIBRARY_PATH=$PWD/signature/sequoia/rust/target/release make BUILDTAGS="btrfs_noversion containers_image_sequoia" |
bdfcafc
to
344c8ea
Compare
Cc: @jnovy, I’ll probably come asking questions. |
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.
Just a very first look at the code, filing this without trying this or a full understanding.
One highlight is the key migration … and I don’t know what happens about passphrases / workflows that currently assume gpg-agent
. We might have to give up on that?!
for _, keyring := range []string{"pubring.gpg", "secring.gpg"} { | ||
blob, err := os.ReadFile(path.Join(gpgHome, keyring)) |
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.
Would this be copying all keys from ~/.gnupg
into ~/.local/share/sequoia
, on every single podman push --sign-by
? That seems unexpected.
I don’t know what to do here… at a first glance, maybe, if we find such keys, we need to load them only into an ephemeral store.
Or maybe this is fine, for products where users / product owners opt in — probably implying that Sequoia wouldn’t be the default. Well, it isn’t a default in this PR.
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.
It turns out ( https://www.gnupg.org/faq/whats-new-in-2.1.html#nosecring ) that recent GPG implementations are not writing to secring.gpg
at all, so this would not necessarily import all relevant keys.
And after I generated a new ed25519 key pair using the default settings of GnuPG 2.4.8, the sequoia backend imported it successfully, but using it failed with Malformed MPI: Details omitted, parsing secret
; that seems to be a message that applies also when an incorrect passphrase is used ?!
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.
… a rsa3072 key works.
Is there something non-obvious I need to do to support the GnuPG-default ed25519 keys?
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.
Filed https://gitlab.com/sequoia-pgp/sequoia/-/issues/1194 with a reproducer.
signature/sequoia/sequoia_test.go
Outdated
@@ -0,0 +1,228 @@ | |||
//go:build containers_image_sequoia |
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.
FIXME: I didn’t read this yet.
signature/sequoia/rust/build.rs
Outdated
let bindings_dir = build_dir.join("bindings"); | ||
let include = bindings_dir.join("sequoia.h"); | ||
|
||
// Generate ${CARGO_TARGET_DIR}/${PROFILE}/bindings/sequoia.h |
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.
It’s unclear to me how this relates to the code in this PR — nothing uses bindings/…
. Is the sequoia.h
included in this PR auto-generated by this?
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.
Yes, the *.[ch]
files are, originally, generated: https://github.com/ueno/podman-sequoia/blob/ffc01d5b6fdb6032e36e32f899652d9134d3a62a/go/GNUmakefile#L15-L24 .
We should generate them as a part of the build (forcing users to use make
???`), or (preferably??) ensure in CI that the files have not been modified automatically.
Or are we going to freeze the C API, in order to have a stable shared library interface across independently-updated packages?
return nil, err | ||
} | ||
} else if _, err := mech.ImportKeys(blob); err != nil { | ||
return nil, err |
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.
Probably wrap with something that indicates this is a failure to import a specific file.
macOS: (cd signature/sequoia/rust; DYLD_FALLBACK_LIBRARY_PATH="$(xcode-select --print-path)/Toolchains/XcodeDefault.xctoolchain/usr/lib/" LIBDIR="$(pwd)" cargo build --release) where … and more modifications are required in the caller. |
A high-level note: during a personal discussion at DevConf.CZ, it was agreed that the preferred design is to use Sequoia for all signature verifications; but for signing, to expose signing using GnuPG-maintained secret keys, and Sequoia-maintained secret keys, as two separate features in the UI. That way, existing GnuPG keys, and existing |
@nwalfield pointed out that, |
@ueno I think that might well make sense to do, but not as the first step. I don’t think full transparency is achievable: if we want to support PQC algorithms using the Sequoia implementations, users should be manually generating keys using Sequoia directly, without GnuPG. So, users will need to use So, I think the best UI will be to add new CLI features ( In the first version of this, it would be less work to keep the existing And afterwards, after this time-sensitive work is time-sensitive is done, we can consider replacing the @ueno I can work on the Go / c/image-side work to introduce the new Sequoia-only signing features, and handle the other small comments from the earlier review. (I’d be also happy to work on improvements to the Rust side, with the disclaimer that I’m very new to Rust and completely unfamiliar with the Sequoia codebase and API.) The primary thing I’d need help with is for you and @jnovy to come to an agreement on packaging / ownership. Does the offer that the RHEL Crypto team could own and maintain the Rust-side package still stand? In that case, it would probably make most sense for the Rust code to live in its own repository (with c/image containing just a static copy of the generated headers and CGo stubs); and I think it would be simpler for Podman/Buildah/Skopeo to link directly to a shared library, without |
344c8ea
to
1e352d0
Compare
That would be great! I just pushed some fixes (not all) to the issues pointed in the review, FWIW.
Yes, it does.
That would be simpler, though I actually chose this |
I don’t know. @jnovy ? |
1e352d0
to
19cc16f
Compare
This adds a new OpenPGP signing mechanism based Sequoia-PGP[1]. As Sequoia-PGP is written in Rust and doesn't provide a stable C FFI, this integration includes a minimal shared library as a "point solution". To build, first follow the instruction in signature/sequoia/README.md and install `libimage_sequoia.so*` into the library path, and then build with the following command from the top-level directory: $ make BUILDTAGS="btrfs_noversion containers_image_sequoia" Note also that, for testing on Fedora, crypto-policies settings might need to be modified to explictly allow SHA-1 and 1024 bit RSA, as the testing keys in signature/fixtures are using those legacy algorithms. 1. https://sequoia-pgp.org/ Signed-off-by: Daiki Ueno <dueno@redhat.com>
19cc16f
to
a83a195
Compare
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.
@ueno, this looks really good! In my review, I focused on the Rust code, as I don't feel qualified to comment on the rest.
fn from_directory(dir: impl AsRef<Path>) -> Result<Self, anyhow::Error> { | ||
let home_dir = dir.as_ref().to_path_buf(); | ||
|
||
let keystore_dir = home_dir.join("data").join("keystore"); |
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.
If your goal is to be compatible with sq
, then this is corrent when SEQUOIA_HOME
is set. If SEQUOIA_HOME
is not set, then we use the xdg paths. The sequoia-directories
does most of this automatically.
let primary_key_handle: KeyHandle = key_handle.parse()?; | ||
let certs = self | ||
.certstore | ||
.lookup_by_cert_or_subkey(&primary_key_handle) |
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.
If you know you have a primary key handle, then you should use lookup_by_cert
instead of lookup_by_cert_or_subkey
.
|
||
let mut signing_key_handles: Vec<KeyHandle> = vec![]; | ||
for cert in certs { | ||
for ka in cert.keys().with_policy(&self.policy, None).for_signing() { |
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.
You need to check:
- The cert is not revoked
- The cert is live (not expired)
- The subkey is not revoked
- The subkey is live (not expired)
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.
Note to self: Do we need more test coverage?
The primary caller does verification in a ~stateless manner, given a signature and a set of acceptable public keys. That input might contain top-level revocations, but that seems unlikely. Expired keys are definitely a possibility, it’s a bit hard to infer user intent in that case.
Also there’s some earlier history in containers/podman#16406 , which says subkeys don’t work with GPGME at all — and some speculation how key revocation / expiration without a trusted timestamp can be problematic.
}; | ||
let mut policy = ConfiguredStandardPolicy::new(); | ||
policy.parse_default_config()?; | ||
let policy = policy.build(); |
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.
Can't you reuse the policy in self.policy
?
MessageLayer::SignatureGroup { ref results } => { | ||
let result = results.iter().find(|r| r.is_ok()); | ||
if let Some(result) = result { | ||
self.signer = Some(result.as_ref().unwrap().ka.cert().to_owned()); |
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.
As above, you need to check that the certificate and subkey are not revoked and live.
This adds a new OpenPGP signing mechanism based Sequoia-PGP[1]. As
Sequoia-PGP is written in Rust and doesn't provide a stable C FFI,
this integration includes a minimal shared library as a "point
solution".
To build, first follow the instruction in signature/sequoia/README.md
and install
libimage_sequoia.so*
into the library path, and thenbuild with the following command from the top-level directory:
Note also that, for testing on Fedora, crypto-policies settings might
need to be modified to explictly allow SHA-1 and 1024 bit RSA, as the
testing keys in signature/fixtures are using those legacy algorithms.