Skip to content

Commit aabede6

Browse files
tbushnellFiloSottile
authored andcommitted
openpgp/clearsign: add ability to sign with more than one key.
Change-Id: I34036514435d365adb2b9da4ac66673be466a34b Reviewed-on: https://go-review.googlesource.com/129655 Reviewed-by: Filippo Valsorda <[email protected]> Run-TryBot: Filippo Valsorda <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent de07523 commit aabede6

File tree

2 files changed

+118
-27
lines changed

2 files changed

+118
-27
lines changed

openpgp/clearsign/clearsign.go

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"bufio"
1414
"bytes"
1515
"crypto"
16+
"fmt"
1617
"hash"
1718
"io"
1819
"net/textproto"
@@ -177,17 +178,18 @@ func Decode(data []byte) (b *Block, rest []byte) {
177178
// message.
178179
type dashEscaper struct {
179180
buffered *bufio.Writer
180-
h hash.Hash
181+
hashers []hash.Hash // one per key in privateKeys
181182
hashType crypto.Hash
183+
toHash io.Writer // writes to all the hashes in hashers
182184

183185
atBeginningOfLine bool
184186
isFirstLine bool
185187

186188
whitespace []byte
187189
byteBuf []byte // a one byte buffer to save allocations
188190

189-
privateKey *packet.PrivateKey
190-
config *packet.Config
191+
privateKeys []*packet.PrivateKey
192+
config *packet.Config
191193
}
192194

193195
func (d *dashEscaper) Write(data []byte) (n int, err error) {
@@ -198,7 +200,7 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) {
198200
// The final CRLF isn't included in the hash so we have to wait
199201
// until this point (the start of the next line) before writing it.
200202
if !d.isFirstLine {
201-
d.h.Write(crlf)
203+
d.toHash.Write(crlf)
202204
}
203205
d.isFirstLine = false
204206
}
@@ -219,12 +221,12 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) {
219221
if _, err = d.buffered.Write(dashEscape); err != nil {
220222
return
221223
}
222-
d.h.Write(d.byteBuf)
224+
d.toHash.Write(d.byteBuf)
223225
d.atBeginningOfLine = false
224226
} else if b == '\n' {
225227
// Nothing to do because we delay writing CRLF to the hash.
226228
} else {
227-
d.h.Write(d.byteBuf)
229+
d.toHash.Write(d.byteBuf)
228230
d.atBeginningOfLine = false
229231
}
230232
if err = d.buffered.WriteByte(b); err != nil {
@@ -245,13 +247,13 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) {
245247
// Any buffered whitespace wasn't at the end of the line so
246248
// we need to write it out.
247249
if len(d.whitespace) > 0 {
248-
d.h.Write(d.whitespace)
250+
d.toHash.Write(d.whitespace)
249251
if _, err = d.buffered.Write(d.whitespace); err != nil {
250252
return
251253
}
252254
d.whitespace = d.whitespace[:0]
253255
}
254-
d.h.Write(d.byteBuf)
256+
d.toHash.Write(d.byteBuf)
255257
if err = d.buffered.WriteByte(b); err != nil {
256258
return
257259
}
@@ -269,25 +271,29 @@ func (d *dashEscaper) Close() (err error) {
269271
return
270272
}
271273
}
272-
sig := new(packet.Signature)
273-
sig.SigType = packet.SigTypeText
274-
sig.PubKeyAlgo = d.privateKey.PubKeyAlgo
275-
sig.Hash = d.hashType
276-
sig.CreationTime = d.config.Now()
277-
sig.IssuerKeyId = &d.privateKey.KeyId
278-
279-
if err = sig.Sign(d.h, d.privateKey, d.config); err != nil {
280-
return
281-
}
282274

283275
out, err := armor.Encode(d.buffered, "PGP SIGNATURE", nil)
284276
if err != nil {
285277
return
286278
}
287279

288-
if err = sig.Serialize(out); err != nil {
289-
return
280+
t := d.config.Now()
281+
for i, k := range d.privateKeys {
282+
sig := new(packet.Signature)
283+
sig.SigType = packet.SigTypeText
284+
sig.PubKeyAlgo = k.PubKeyAlgo
285+
sig.Hash = d.hashType
286+
sig.CreationTime = t
287+
sig.IssuerKeyId = &k.KeyId
288+
289+
if err = sig.Sign(d.hashers[i], k, d.config); err != nil {
290+
return
291+
}
292+
if err = sig.Serialize(out); err != nil {
293+
return
294+
}
290295
}
296+
291297
if err = out.Close(); err != nil {
292298
return
293299
}
@@ -300,8 +306,17 @@ func (d *dashEscaper) Close() (err error) {
300306
// Encode returns a WriteCloser which will clear-sign a message with privateKey
301307
// and write it to w. If config is nil, sensible defaults are used.
302308
func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
303-
if privateKey.Encrypted {
304-
return nil, errors.InvalidArgumentError("signing key is encrypted")
309+
return EncodeMulti(w, []*packet.PrivateKey{privateKey}, config)
310+
}
311+
312+
// EncodeMulti returns a WriteCloser which will clear-sign a message with all the
313+
// private keys indicated and write it to w. If config is nil, sensible defaults
314+
// are used.
315+
func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
316+
for _, k := range privateKeys {
317+
if k.Encrypted {
318+
return nil, errors.InvalidArgumentError(fmt.Sprintf("signing key %s is encrypted", k.KeyIdString()))
319+
}
305320
}
306321

307322
hashType := config.Hash()
@@ -313,7 +328,14 @@ func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (
313328
if !hashType.Available() {
314329
return nil, errors.UnsupportedError("unsupported hash type: " + strconv.Itoa(int(hashType)))
315330
}
316-
h := hashType.New()
331+
var hashers []hash.Hash
332+
var ws []io.Writer
333+
for range privateKeys {
334+
h := hashType.New()
335+
hashers = append(hashers, h)
336+
ws = append(ws, h)
337+
}
338+
toHash := io.MultiWriter(ws...)
317339

318340
buffered := bufio.NewWriter(w)
319341
// start has a \n at the beginning that we don't want here.
@@ -338,16 +360,17 @@ func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (
338360

339361
plaintext = &dashEscaper{
340362
buffered: buffered,
341-
h: h,
363+
hashers: hashers,
342364
hashType: hashType,
365+
toHash: toHash,
343366

344367
atBeginningOfLine: true,
345368
isFirstLine: true,
346369

347370
byteBuf: make([]byte, 1),
348371

349-
privateKey: privateKey,
350-
config: config,
372+
privateKeys: privateKeys,
373+
config: config,
351374
}
352375

353376
return

openpgp/clearsign/clearsign_test.go

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ package clearsign
66

77
import (
88
"bytes"
9-
"golang.org/x/crypto/openpgp"
9+
"fmt"
1010
"testing"
11+
12+
"golang.org/x/crypto/openpgp"
13+
"golang.org/x/crypto/openpgp/packet"
1114
)
1215

1316
func testParse(t *testing.T, input []byte, expected, expectedPlaintext string) {
@@ -125,6 +128,71 @@ func TestSigning(t *testing.T) {
125128
}
126129
}
127130

131+
// We use this to make test keys, so that they aren't all the same.
132+
type quickRand byte
133+
134+
func (qr *quickRand) Read(p []byte) (int, error) {
135+
for i := range p {
136+
p[i] = byte(*qr)
137+
}
138+
*qr++
139+
return len(p), nil
140+
}
141+
142+
func TestMultiSign(t *testing.T) {
143+
zero := quickRand(0)
144+
config := packet.Config{Rand: &zero}
145+
146+
for nKeys := 0; nKeys < 4; nKeys++ {
147+
nextTest:
148+
for nExtra := 0; nExtra < 4; nExtra++ {
149+
var signKeys []*packet.PrivateKey
150+
var verifyKeys openpgp.EntityList
151+
152+
desc := fmt.Sprintf("%d keys; %d of which will be used to verify", nKeys+nExtra, nKeys)
153+
for i := 0; i < nKeys+nExtra; i++ {
154+
e, err := openpgp.NewEntity("name", "comment", "email", &config)
155+
if err != nil {
156+
t.Errorf("cannot create key: %v", err)
157+
continue nextTest
158+
}
159+
if i < nKeys {
160+
verifyKeys = append(verifyKeys, e)
161+
}
162+
signKeys = append(signKeys, e.PrivateKey)
163+
}
164+
165+
input := []byte("this is random text\r\n4 17")
166+
var output bytes.Buffer
167+
w, err := EncodeMulti(&output, signKeys, nil)
168+
if err != nil {
169+
t.Errorf("EncodeMulti (%s) failed: %v", desc, err)
170+
}
171+
if _, err := w.Write(input); err != nil {
172+
t.Errorf("Write(%q) to signer (%s) failed: %v", string(input), desc, err)
173+
}
174+
if err := w.Close(); err != nil {
175+
t.Errorf("Close() of signer (%s) failed: %v", desc, err)
176+
}
177+
178+
block, _ := Decode(output.Bytes())
179+
if string(block.Bytes) != string(input) {
180+
t.Errorf("Inline data didn't match original; got %q want %q", string(block.Bytes), string(input))
181+
}
182+
_, err = openpgp.CheckDetachedSignature(verifyKeys, bytes.NewReader(block.Bytes), block.ArmoredSignature.Body)
183+
if nKeys == 0 {
184+
if err == nil {
185+
t.Errorf("verifying inline (%s) succeeded; want failure", desc)
186+
}
187+
} else {
188+
if err != nil {
189+
t.Errorf("verifying inline (%s) failed (%v); want success", desc, err)
190+
}
191+
}
192+
}
193+
}
194+
}
195+
128196
var clearsignInput = []byte(`
129197
;lasjlkfdsa
130198

0 commit comments

Comments
 (0)