Skip to content

Commit

Permalink
signature: add OpenPGP signing mechanism based on Sequoia
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
ueno committed Sep 17, 2024
1 parent 31d4ad1 commit 0f4142d
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 5 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
4 changes: 2 additions & 2 deletions signature/mechanism_gpgme.go
Original file line number Diff line number Diff line change
@@ -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

Expand Down
4 changes: 2 additions & 2 deletions signature/mechanism_gpgme_test.go
Original file line number Diff line number Diff line change
@@ -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

Expand Down
134 changes: 134 additions & 0 deletions signature/mechanism_sequoia.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//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
migratedDir string // If not "", a directory to be removed on Close()
}

// 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) {
// Migrate GnuPG home directory into a Sequoia format.
dir, err := os.MkdirTemp("", "containers-ephemeral-gpg-")
if err != nil {
return nil, err
}
removeDir := true
defer func() {
if removeDir {
os.RemoveAll(dir)
}
}()
mech, err := sequoia.NewMechanismFromDirectory(dir)
if err != nil {
return nil, err
}

gpgHome := optionalDir
if gpgHome == "" {
gpgHome = os.Getenv("GNUPGHOME")
if gpgHome == "" {
gpgHome = path.Join(homedir.Get(), ".gnupg")
}
}
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
}
}

removeDir = false
return &sequoiaSigningMechanism{
inner: mech,
migratedDir: dir,
}, 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,
migratedDir: "",
}, keyIdentities, nil
}

func (m *sequoiaSigningMechanism) Close() error {
err := m.inner.Close()
if err != nil {
return err
}
if m.migratedDir != "" {
os.RemoveAll(m.migratedDir) // Ignore an error, if any
}
return nil
}

// 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")
}
}
2 changes: 1 addition & 1 deletion signature/mechanism_test.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down

0 comments on commit 0f4142d

Please sign in to comment.