File: sign_handle.go

package info (click to toggle)
golang-github-protonmail-gopenpgp-v3 3.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,028 kB
  • sloc: sh: 87; makefile: 2
file content (234 lines) | stat: -rw-r--r-- 6,438 bytes parent folder | download | duplicates (2)
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
package crypto

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"time"
	"unicode/utf8"

	"github.com/ProtonMail/go-crypto/openpgp/armor"
	"github.com/ProtonMail/go-crypto/openpgp/clearsign"
	"github.com/ProtonMail/go-crypto/openpgp/packet"
	openpgp "github.com/ProtonMail/go-crypto/openpgp/v2"
	"github.com/ProtonMail/gopenpgp/v3/constants"
	"github.com/ProtonMail/gopenpgp/v3/internal"
)

type signatureHandle struct {
	SignKeyRing  *KeyRing
	SignContext  *SigningContext
	IsUTF8       bool
	Detached     bool
	ArmorHeaders map[string]string
	profile      SignProfile
	clock        Clock
}

// --- Default signature handle to build from

func defaultSignatureHandle(profile SignProfile, clock Clock) *signatureHandle {
	return &signatureHandle{
		profile:      profile,
		ArmorHeaders: internal.ArmorHeaders,
		clock:        clock,
	}
}

// --- Implements the signature handle methods

// SigningWriter returns a wrapper around underlying output Writer,
// such that any write-operation via the wrapper results in a write to a detached or inline signature message.
// The encoding argument defines the output encoding, i.e., Bytes or Armored
// Once close is called on the returned WriteCloser the final signature is written to the output.
// Thus, the returned WriteCloser must be closed after the plaintext has been written.
func (sh *signatureHandle) SigningWriter(outputWriter Writer, encoding int8) (messageWriter WriteCloser, err error) {
	var armorWriter WriteCloser
	armorOutput := armorOutput(encoding)
	if armorOutput {
		writeChecksum := sh.armorChecksumRequired()
		var err error
		header := constants.PGPMessageHeader
		if sh.Detached {
			header = constants.PGPSignatureHeader
		}
		armorWriter, err = armor.EncodeWithChecksumOption(outputWriter, header, sh.ArmorHeaders, writeChecksum)
		if err != nil {
			return nil, err
		}
		outputWriter = armorWriter
	}
	if sh.Detached {
		// Detached signature
		messageWriter, err = signMessageDetachedWriter(
			sh.SignKeyRing,
			outputWriter,
			sh.IsUTF8,
			sh.SignContext,
			sh.clock,
			sh.profile.SignConfig(),
		)
	} else {
		// Inline signature
		messageWriter, err = sh.signingWriter(outputWriter, nil)
	}
	if err != nil {
		return nil, err
	}
	if armorOutput {
		// Ensure that close is called on the armor writer for the armor suffix
		messageWriter = &armoredWriteCloser{
			armorWriter:   armorWriter,
			messageWriter: messageWriter,
		}
	}
	if sh.IsUTF8 {
		messageWriter = internal.NewUtf8CheckWriteCloser(
			openpgp.NewCanonicalTextWriteCloser(messageWriter),
		)
	}
	return messageWriter, nil
}

// Sign creates a detached or inline signature from the provided byte slice.
// The encoding argument defines the output encoding, i.e., Bytes or Armored.
func (sh *signatureHandle) Sign(message []byte, encoding int8) ([]byte, error) {
	var writer bytes.Buffer
	ptWriter, err := sh.SigningWriter(&writer, encoding)
	if err != nil {
		return nil, err
	}
	_, err = ptWriter.Write(message)
	if err != nil {
		return nil, err
	}
	err = ptWriter.Close()
	if err != nil {
		return nil, err
	}
	return writer.Bytes(), nil
}

// SignCleartext produces an armored cleartext message according to the specification.
// Returns an armored message even if the PGPSign is not configured for armored output.
func (sh *signatureHandle) SignCleartext(message []byte) ([]byte, error) {
	return sh.signCleartext(message)
}

// ClearPrivateParams clears all secret key material contained in the PGPSign from memory.
func (sh *signatureHandle) ClearPrivateParams() {
	if sh.SignKeyRing != nil {
		sh.SignKeyRing.ClearPrivateParams()
	}
}

// --- Private signature handle logic

func (sh *signatureHandle) validate() error {
	if sh.SignKeyRing == nil {
		return errors.New("gopenpgp: no signing key provided")
	}
	return nil
}

func (sh *signatureHandle) armorChecksumRequired() bool {
	if !constants.ArmorChecksumEnabled {
		// If the default behavior is no checksum, we can ignore
		// the logic for the RFC9580 check.
		return false
	}
	if sh.SignKeyRing == nil {
		return true
	}
	for _, signer := range sh.SignKeyRing.entities {
		if signer.PrimaryKey.Version != 6 {
			return true
		}
	}
	return false
}

func (sh *signatureHandle) signCleartext(message []byte) ([]byte, error) {
	config := sh.profile.SignConfig()
	config.Time = NewConstantClock(sh.clock().Unix())
	var buffer bytes.Buffer
	var privateKeys []*packet.PrivateKey
	if !utf8.Valid(message) {
		return nil, internal.ErrIncorrectUtf8
	}
	for _, entity := range sh.SignKeyRing.entities {
		key, ok := entity.SigningKey(config.Now(), config)
		if ok &&
			key.PrivateKey != nil &&
			!key.PrivateKey.Encrypted {
			privateKeys = append(privateKeys, key.PrivateKey)
		} else {
			return nil, errors.New("gopenpgp: no signing key found for entity")
		}
	}
	writer, err := clearsign.EncodeMultiWithHeader(&buffer, privateKeys, config, sh.ArmorHeaders)
	if err != nil {
		return nil, err
	}
	_, err = writer.Write(message)
	if err != nil {
		return nil, err
	}
	err = writer.Close()
	if err != nil {
		return nil, err
	}
	return buffer.Bytes(), nil
}

func (sh *signatureHandle) signingWriter(messageWriter Writer, literalData *LiteralMetadata) (WriteCloser, error) {
	config := sh.profile.SignConfig()
	config.Time = NewConstantClock(sh.clock().Unix())
	signers, err := sh.SignKeyRing.signingEntities()
	if err != nil {
		return nil, err
	}
	hints := &openpgp.FileHints{
		FileName: literalData.Filename(),
		IsUTF8:   sh.IsUTF8,
		ModTime:  time.Unix(literalData.Time(), 0),
	}
	if sh.SignContext != nil {
		config.SignatureNotations = append(config.SignatureNotations, sh.SignContext.getNotation())
	}
	return openpgp.SignWithParams(messageWriter, signers, &openpgp.SignParams{
		Hints:   hints,
		TextSig: sh.IsUTF8,
		Config:  config,
	})
}

func signMessageDetachedWriter(
	signKeyRing *KeyRing,
	outputWriter io.Writer,
	isUTF8 bool,
	context *SigningContext,
	clock Clock,
	config *packet.Config,
) (ptWriter io.WriteCloser, err error) {
	config.Time = NewConstantClock(clock().Unix())

	signers, err := signKeyRing.signingEntities()
	if err != nil {
		return nil, err
	}

	if context != nil {
		config.SignatureNotations = append(config.SignatureNotations, context.getNotation())
	}

	ptWriter, err = openpgp.DetachSignWriter(outputWriter, signers, &openpgp.SignParams{
		TextSig: isUTF8,
		Config:  config,
	})
	if err != nil {
		return nil, fmt.Errorf("gopenpgp: error in signing: %w", err)
	}
	return ptWriter, nil
}