File: mechanism_openpgp.go

package info (click to toggle)
golang-github-containers-image 5.28.0-4
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 5,104 kB
  • sloc: sh: 194; makefile: 73
file content (179 lines) | stat: -rw-r--r-- 7,072 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//go:build containers_image_openpgp
// +build containers_image_openpgp

package signature

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"path"
	"strings"
	"time"

	"github.com/containers/image/v5/signature/internal"
	"github.com/containers/storage/pkg/homedir"
	// This is a fallback code; the primary recommendation is to use the gpgme mechanism
	// implementation, which is out-of-process and more appropriate for handling long-term private key material
	// than any Go implementation.
	// For this verify-only fallback, we haven't reviewed any of the
	// existing alternatives to choose; so, for now, continue to
	// use this frozen deprecated implementation.
	//lint:ignore SA1019 See above
	"golang.org/x/crypto/openpgp" //nolint:staticcheck
)

// A GPG/OpenPGP signing mechanism, implemented using x/crypto/openpgp.
type openpgpSigningMechanism struct {
	keyring openpgp.EntityList
}

// 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) {
	m := &openpgpSigningMechanism{
		keyring: openpgp.EntityList{},
	}

	gpgHome := optionalDir
	if gpgHome == "" {
		gpgHome = os.Getenv("GNUPGHOME")
		if gpgHome == "" {
			gpgHome = path.Join(homedir.Get(), ".gnupg")
		}
	}

	pubring, err := os.ReadFile(path.Join(gpgHome, "pubring.gpg"))
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}
	} else {
		_, err := m.importKeysFromBytes(pubring)
		if err != nil {
			return nil, err
		}
	}
	return m, nil
}

// newEphemeralGPGSigningMechanism returns a new GPG/OpenPGP signing mechanism which
// recognizes _only_ public keys from the supplied blob, and returns the identities
// of these keys.
// The caller must call .Close() on the returned SigningMechanism.
func newEphemeralGPGSigningMechanism(blobs [][]byte) (signingMechanismWithPassphrase, []string, error) {
	m := &openpgpSigningMechanism{
		keyring: openpgp.EntityList{},
	}
	keyIdentities := []string{}
	for _, blob := range blobs {
		ki, err := m.importKeysFromBytes(blob)
		if err != nil {
			return nil, nil, err
		}
		keyIdentities = append(keyIdentities, ki...)
	}

	return m, keyIdentities, nil
}

func (m *openpgpSigningMechanism) Close() error {
	return nil
}

// importKeysFromBytes imports public keys from the supplied blob and returns their identities.
// The blob is assumed to have an appropriate format (the caller is expected to know which one).
func (m *openpgpSigningMechanism) importKeysFromBytes(blob []byte) ([]string, error) {
	keyring, err := openpgp.ReadKeyRing(bytes.NewReader(blob))
	if err != nil {
		k, e2 := openpgp.ReadArmoredKeyRing(bytes.NewReader(blob))
		if e2 != nil {
			return nil, err // The original error  -- FIXME: is this better?
		}
		keyring = k
	}

	keyIdentities := []string{}
	for _, entity := range keyring {
		if entity.PrimaryKey == nil {
			// Coverage: This should never happen, openpgp.ReadEntity fails with a
			// openpgp.errors.StructuralError instead of returning an entity with this
			// field set to nil.
			continue
		}
		// Uppercase the fingerprint to be compatible with gpgme
		keyIdentities = append(keyIdentities, strings.ToUpper(fmt.Sprintf("%x", entity.PrimaryKey.Fingerprint)))
		m.keyring = append(m.keyring, entity)
	}
	return keyIdentities, nil
}

// SupportsSigning returns nil if the mechanism supports signing, or a SigningNotSupportedError.
func (m *openpgpSigningMechanism) SupportsSigning() error {
	return SigningNotSupportedError("signing is not supported in github.com/containers/image built with the containers_image_openpgp build tag")
}

// Sign creates a (non-detached) signature of input using keyIdentity.
// Fails with a SigningNotSupportedError if the mechanism does not support signing.
func (m *openpgpSigningMechanism) SignWithPassphrase(input []byte, keyIdentity string, passphrase string) ([]byte, error) {
	return nil, SigningNotSupportedError("signing is not supported in github.com/containers/image built with the containers_image_openpgp build tag")
}

// Sign creates a (non-detached) signature of input using keyIdentity.
// Fails with a SigningNotSupportedError if the mechanism does not support signing.
func (m *openpgpSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte, error) {
	return m.SignWithPassphrase(input, keyIdentity, "")
}

// Verify parses unverifiedSignature and returns the content and the signer's identity
func (m *openpgpSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error) {
	md, err := openpgp.ReadMessage(bytes.NewReader(unverifiedSignature), m.keyring, nil, nil)
	if err != nil {
		return nil, "", err
	}
	if !md.IsSigned {
		return nil, "", errors.New("not signed")
	}
	content, err := io.ReadAll(md.UnverifiedBody)
	if err != nil {
		// Coverage: md.UnverifiedBody.Read only fails if the body is encrypted
		// (and possibly also signed, but it _must_ be encrypted) and the signing
		// “modification detection code” detects a mismatch. But in that case,
		// we would expect the signature verification to fail as well, and that is checked
		// first.  Besides, we are not supplying any decryption keys, so we really
		// can never reach this “encrypted data MDC mismatch” path.
		return nil, "", err
	}
	if md.SignatureError != nil {
		return nil, "", fmt.Errorf("signature error: %v", md.SignatureError)
	}
	if md.SignedBy == nil {
		return nil, "", internal.NewInvalidSignatureError(fmt.Sprintf("Invalid GPG signature: %#v", md.Signature))
	}
	if md.Signature != nil {
		if md.Signature.SigLifetimeSecs != nil {
			expiry := md.Signature.CreationTime.Add(time.Duration(*md.Signature.SigLifetimeSecs) * time.Second)
			if time.Now().After(expiry) {
				return nil, "", internal.NewInvalidSignatureError(fmt.Sprintf("Signature expired on %s", expiry))
			}
		}
	} else if md.SignatureV3 == nil {
		// Coverage: If md.SignedBy != nil, the final md.UnverifiedBody.Read() either sets one of md.Signature or md.SignatureV3,
		// or sets md.SignatureError.
		return nil, "", internal.NewInvalidSignatureError("Unexpected openpgp.MessageDetails: neither Signature nor SignatureV3 is set")
	}

	// Uppercase the fingerprint to be compatible with gpgme
	return content, strings.ToUpper(fmt.Sprintf("%x", md.SignedBy.PublicKey.Fingerprint)), nil
}

// 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 *openpgpSigningMechanism) UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error) {
	return gpgUntrustedSignatureContents(untrustedSignature)
}