From 23fec2db4643226188df802b0af33ccb6e3ac647 Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Tue, 17 Sep 2024 11:09:22 +0900 Subject: [PATCH] signature: add OpenPGP signing mechanism based on Sequoia This adds a new OpenPGP signing mechanism based Sequoia[1]. As Sequoia is written in Rust and doesn't provide a stable C FFI, this integration uses a minimal shared library as a "point solution". To build, first follow the instruction at [2] and install `libpodman_sequoia.so*` into the library path, and then build with the following command from the top-level directory: $ make BUILDTAGS="btrfs_noversion libdm_no_deferred_remove 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/ 2. https://github.com/ueno/podman-sequoia Signed-off-by: Daiki Ueno --- go.mod | 1 + go.sum | 2 + signature/mechanism_gpgme.go | 4 +- signature/mechanism_gpgme_test.go | 4 +- signature/mechanism_sequoia.go | 131 ++++++++++++++++++++++++++++++ signature/mechanism_test.go | 2 +- 6 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 signature/mechanism_sequoia.go diff --git a/go.mod b/go.mod index 3f0569da9..cbc41f9ab 100644 --- a/go.mod +++ b/go.mod @@ -127,6 +127,7 @@ require ( github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect + github.com/ueno/podman-sequoia/go v0.0.0-20240917014836-c53d571fb375 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect diff --git a/go.sum b/go.sum index 2ea3be699..8749714e3 100644 --- a/go.sum +++ b/go.sum @@ -335,6 +335,8 @@ github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BG github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= +github.com/ueno/podman-sequoia/go v0.0.0-20240917014836-c53d571fb375 h1:k6F/0KdIf86gLWN2WWJkA9Dz+yD24y7k+QZlLKUOHEU= +github.com/ueno/podman-sequoia/go v0.0.0-20240917014836-c53d571fb375/go.mod h1:rL7oFaeDd+3z0KyQI+GTz3ZE0ZvO4mT2+zMAXxZiPvM= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= diff --git a/signature/mechanism_gpgme.go b/signature/mechanism_gpgme.go index 2b2a7ad86..63bc93456 100644 --- a/signature/mechanism_gpgme.go +++ b/signature/mechanism_gpgme.go @@ -1,5 +1,5 @@ -//go:build !containers_image_openpgp -// +build !containers_image_openpgp +//go:build !containers_image_openpgp && !containers_image_sequoia +// +build !containers_image_openpgp,!containers_image_sequoia package signature diff --git a/signature/mechanism_gpgme_test.go b/signature/mechanism_gpgme_test.go index 82ca99893..181d414b0 100644 --- a/signature/mechanism_gpgme_test.go +++ b/signature/mechanism_gpgme_test.go @@ -1,5 +1,5 @@ -//go:build !containers_image_openpgp -// +build !containers_image_openpgp +//go:build !containers_image_openpgp && !containers_image_sequoia +// +build !containers_image_openpgp,!containers_image_sequoia package signature diff --git a/signature/mechanism_sequoia.go b/signature/mechanism_sequoia.go new file mode 100644 index 000000000..948cc3a37 --- /dev/null +++ b/signature/mechanism_sequoia.go @@ -0,0 +1,131 @@ +//go:build containers_image_sequoia +// +build containers_image_sequoia + +package signature + +import ( + "github.com/containers/storage/pkg/homedir" + "github.com/ueno/podman-sequoia/go/sequoia" + "os" + "path" +) + +// A GPG/OpenPGP signing mechanism, implemented using Sequoia. +type sequoiaSigningMechanism struct { + inner *sequoia.SigningMechanism +} + +// newGPGSigningMechanismInDirectory returns a new GPG/OpenPGP signing mechanism, using optionalDir if not empty. +// The caller must call .Close() on the returned SigningMechanism. +func newGPGSigningMechanismInDirectory(optionalDir string) (signingMechanismWithPassphrase, error) { + // For compatibility reasons, we allow both sequoiaHome and + // gpgHome to be the same directory as designated by + // optionalDir or GNUPGHOME. + envHome := os.Getenv("GNUPGHOME") + + gpgHome := optionalDir + if gpgHome == "" { + gpgHome = envHome + if gpgHome == "" { + gpgHome = path.Join(homedir.Get(), ".gnupg") + } + } + + sequoiaHome := optionalDir + if sequoiaHome == "" { + sequoiaHome = envHome + if sequoiaHome == "" { + dataHome, err := homedir.GetDataHome() + if err != nil { + return nil, err + } + sequoiaHome = path.Join(dataHome, "sequoia") + } + } + + mech, err := sequoia.NewMechanismFromDirectory(sequoiaHome) + if err != nil { + return nil, err + } + + // Migrate GnuPG keyrings if exist. + for _, keyring := range []string{"pubring.gpg", "secring.gpg"} { + blob, err := os.ReadFile(path.Join(gpgHome, keyring)) + if err != nil { + if !os.IsNotExist(err) { + return nil, err + } + } else if _, err := mech.ImportKeys(blob); err != nil { + return nil, err + } + } + + return &sequoiaSigningMechanism{ + inner: mech, + }, nil +} + +// newEphemeralGPGSigningMechanism returns a new GPG/OpenPGP signing mechanism which +// recognizes _only_ public keys from the supplied blobs, and returns the identities +// of these keys. +// The caller must call .Close() on the returned SigningMechanism. +func newEphemeralGPGSigningMechanism(blobs [][]byte) (signingMechanismWithPassphrase, []string, error) { + mech, err := sequoia.NewEphemeralMechanism() + if err != nil { + return nil, nil, err + } + keyIdentities := []string{} + for _, blob := range blobs { + ki, err := mech.ImportKeys(blob) + if err != nil { + return nil, nil, err + } + keyIdentities = append(keyIdentities, ki...) + } + + return &sequoiaSigningMechanism{ + inner: mech, + }, keyIdentities, nil +} + +func (m *sequoiaSigningMechanism) Close() error { + return m.inner.Close() +} + +// SupportsSigning returns nil if the mechanism supports signing, or a SigningNotSupportedError. +func (m *sequoiaSigningMechanism) SupportsSigning() error { + return m.inner.SupportsSigning() +} + +// Sign creates a (non-detached) signature of input using keyIdentity and passphrase. +// Fails with a SigningNotSupportedError if the mechanism does not support signing. +func (m *sequoiaSigningMechanism) SignWithPassphrase(input []byte, keyIdentity string, passphrase string) ([]byte, error) { + return m.inner.SignWithPassphrase(input, keyIdentity, passphrase) +} + +// Sign creates a (non-detached) signature of input using keyIdentity. +// Fails with a SigningNotSupportedError if the mechanism does not support signing. +func (m *sequoiaSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte, error) { + return m.inner.Sign(input, keyIdentity) +} + +// Verify parses unverifiedSignature and returns the content and the signer's identity +func (m *sequoiaSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error) { + return m.inner.Verify(unverifiedSignature) +} + +// UntrustedSignatureContents returns UNTRUSTED contents of the signature WITHOUT ANY VERIFICATION, +// along with a short identifier of the key used for signing. +// WARNING: The short key identifier (which corresponds to "Key ID" for OpenPGP keys) +// is NOT the same as a "key identity" used in other calls to this interface, and +// the values may have no recognizable relationship if the public key is not available. +func (m *sequoiaSigningMechanism) UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error) { + return gpgUntrustedSignatureContents(untrustedSignature) +} + +func init() { + err := sequoia.Init() + if err != nil { + panic("sequoia cannot be loaded: " + err.Error()) + } +} diff --git a/signature/mechanism_test.go b/signature/mechanism_test.go index ef67db6b9..09e875114 100644 --- a/signature/mechanism_test.go +++ b/signature/mechanism_test.go @@ -1,6 +1,6 @@ package signature -// These tests are expected to pass unmodified for _both_ mechanism_gpgme.go and mechanism_openpgp.go. +// These tests are expected to pass unmodified for _all_ mechanism_sequoia.go, mechanism_gpgme.go, and mechanism_openpgp.go. import ( "bytes"