diff --git a/go.mod b/go.mod index 3f0569da99..cbc41f9abe 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 2ea3be6991..8749714e3a 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 2b2a7ad866..63bc934569 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 82ca998937..181d414b08 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 0000000000..948cc3a372 --- /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 ef67db6b99..09e875114c 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"