Browse Source

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 <filippo@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Thomas Bushnell, BSG 7 năm trước cách đây
mục cha
commit
aabede6cba
2 tập tin đã thay đổi với 118 bổ sung27 xóa
  1. 49 26
      openpgp/clearsign/clearsign.go
  2. 69 1
      openpgp/clearsign/clearsign_test.go

+ 49 - 26
openpgp/clearsign/clearsign.go

@@ -13,6 +13,7 @@ import (
 	"bufio"
 	"bytes"
 	"crypto"
+	"fmt"
 	"hash"
 	"io"
 	"net/textproto"
@@ -177,8 +178,9 @@ func Decode(data []byte) (b *Block, rest []byte) {
 // message.
 type dashEscaper struct {
 	buffered *bufio.Writer
-	h        hash.Hash
+	hashers  []hash.Hash // one per key in privateKeys
 	hashType crypto.Hash
+	toHash   io.Writer // writes to all the hashes in hashers
 
 	atBeginningOfLine bool
 	isFirstLine       bool
@@ -186,8 +188,8 @@ type dashEscaper struct {
 	whitespace []byte
 	byteBuf    []byte // a one byte buffer to save allocations
 
-	privateKey *packet.PrivateKey
-	config     *packet.Config
+	privateKeys []*packet.PrivateKey
+	config      *packet.Config
 }
 
 func (d *dashEscaper) Write(data []byte) (n int, err error) {
@@ -198,7 +200,7 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) {
 			// The final CRLF isn't included in the hash so we have to wait
 			// until this point (the start of the next line) before writing it.
 			if !d.isFirstLine {
-				d.h.Write(crlf)
+				d.toHash.Write(crlf)
 			}
 			d.isFirstLine = false
 		}
@@ -219,12 +221,12 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) {
 				if _, err = d.buffered.Write(dashEscape); err != nil {
 					return
 				}
-				d.h.Write(d.byteBuf)
+				d.toHash.Write(d.byteBuf)
 				d.atBeginningOfLine = false
 			} else if b == '\n' {
 				// Nothing to do because we delay writing CRLF to the hash.
 			} else {
-				d.h.Write(d.byteBuf)
+				d.toHash.Write(d.byteBuf)
 				d.atBeginningOfLine = false
 			}
 			if err = d.buffered.WriteByte(b); err != nil {
@@ -245,13 +247,13 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) {
 				// Any buffered whitespace wasn't at the end of the line so
 				// we need to write it out.
 				if len(d.whitespace) > 0 {
-					d.h.Write(d.whitespace)
+					d.toHash.Write(d.whitespace)
 					if _, err = d.buffered.Write(d.whitespace); err != nil {
 						return
 					}
 					d.whitespace = d.whitespace[:0]
 				}
-				d.h.Write(d.byteBuf)
+				d.toHash.Write(d.byteBuf)
 				if err = d.buffered.WriteByte(b); err != nil {
 					return
 				}
@@ -269,25 +271,29 @@ func (d *dashEscaper) Close() (err error) {
 			return
 		}
 	}
-	sig := new(packet.Signature)
-	sig.SigType = packet.SigTypeText
-	sig.PubKeyAlgo = d.privateKey.PubKeyAlgo
-	sig.Hash = d.hashType
-	sig.CreationTime = d.config.Now()
-	sig.IssuerKeyId = &d.privateKey.KeyId
-
-	if err = sig.Sign(d.h, d.privateKey, d.config); err != nil {
-		return
-	}
 
 	out, err := armor.Encode(d.buffered, "PGP SIGNATURE", nil)
 	if err != nil {
 		return
 	}
 
-	if err = sig.Serialize(out); err != nil {
-		return
+	t := d.config.Now()
+	for i, k := range d.privateKeys {
+		sig := new(packet.Signature)
+		sig.SigType = packet.SigTypeText
+		sig.PubKeyAlgo = k.PubKeyAlgo
+		sig.Hash = d.hashType
+		sig.CreationTime = t
+		sig.IssuerKeyId = &k.KeyId
+
+		if err = sig.Sign(d.hashers[i], k, d.config); err != nil {
+			return
+		}
+		if err = sig.Serialize(out); err != nil {
+			return
+		}
 	}
+
 	if err = out.Close(); err != nil {
 		return
 	}
@@ -300,8 +306,17 @@ func (d *dashEscaper) Close() (err error) {
 // Encode returns a WriteCloser which will clear-sign a message with privateKey
 // and write it to w. If config is nil, sensible defaults are used.
 func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
-	if privateKey.Encrypted {
-		return nil, errors.InvalidArgumentError("signing key is encrypted")
+	return EncodeMulti(w, []*packet.PrivateKey{privateKey}, config)
+}
+
+// EncodeMulti returns a WriteCloser which will clear-sign a message with all the
+// private keys indicated and write it to w. If config is nil, sensible defaults
+// are used.
+func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
+	for _, k := range privateKeys {
+		if k.Encrypted {
+			return nil, errors.InvalidArgumentError(fmt.Sprintf("signing key %s is encrypted", k.KeyIdString()))
+		}
 	}
 
 	hashType := config.Hash()
@@ -313,7 +328,14 @@ func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (
 	if !hashType.Available() {
 		return nil, errors.UnsupportedError("unsupported hash type: " + strconv.Itoa(int(hashType)))
 	}
-	h := hashType.New()
+	var hashers []hash.Hash
+	var ws []io.Writer
+	for range privateKeys {
+		h := hashType.New()
+		hashers = append(hashers, h)
+		ws = append(ws, h)
+	}
+	toHash := io.MultiWriter(ws...)
 
 	buffered := bufio.NewWriter(w)
 	// 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) (
 
 	plaintext = &dashEscaper{
 		buffered: buffered,
-		h:        h,
+		hashers:  hashers,
 		hashType: hashType,
+		toHash:   toHash,
 
 		atBeginningOfLine: true,
 		isFirstLine:       true,
 
 		byteBuf: make([]byte, 1),
 
-		privateKey: privateKey,
-		config:     config,
+		privateKeys: privateKeys,
+		config:      config,
 	}
 
 	return

+ 69 - 1
openpgp/clearsign/clearsign_test.go

@@ -6,8 +6,11 @@ package clearsign
 
 import (
 	"bytes"
-	"golang.org/x/crypto/openpgp"
+	"fmt"
 	"testing"
+
+	"golang.org/x/crypto/openpgp"
+	"golang.org/x/crypto/openpgp/packet"
 )
 
 func testParse(t *testing.T, input []byte, expected, expectedPlaintext string) {
@@ -125,6 +128,71 @@ func TestSigning(t *testing.T) {
 	}
 }
 
+// We use this to make test keys, so that they aren't all the same.
+type quickRand byte
+
+func (qr *quickRand) Read(p []byte) (int, error) {
+	for i := range p {
+		p[i] = byte(*qr)
+	}
+	*qr++
+	return len(p), nil
+}
+
+func TestMultiSign(t *testing.T) {
+	zero := quickRand(0)
+	config := packet.Config{Rand: &zero}
+
+	for nKeys := 0; nKeys < 4; nKeys++ {
+	nextTest:
+		for nExtra := 0; nExtra < 4; nExtra++ {
+			var signKeys []*packet.PrivateKey
+			var verifyKeys openpgp.EntityList
+
+			desc := fmt.Sprintf("%d keys; %d of which will be used to verify", nKeys+nExtra, nKeys)
+			for i := 0; i < nKeys+nExtra; i++ {
+				e, err := openpgp.NewEntity("name", "comment", "email", &config)
+				if err != nil {
+					t.Errorf("cannot create key: %v", err)
+					continue nextTest
+				}
+				if i < nKeys {
+					verifyKeys = append(verifyKeys, e)
+				}
+				signKeys = append(signKeys, e.PrivateKey)
+			}
+
+			input := []byte("this is random text\r\n4 17")
+			var output bytes.Buffer
+			w, err := EncodeMulti(&output, signKeys, nil)
+			if err != nil {
+				t.Errorf("EncodeMulti (%s) failed: %v", desc, err)
+			}
+			if _, err := w.Write(input); err != nil {
+				t.Errorf("Write(%q) to signer (%s) failed: %v", string(input), desc, err)
+			}
+			if err := w.Close(); err != nil {
+				t.Errorf("Close() of signer (%s) failed: %v", desc, err)
+			}
+
+			block, _ := Decode(output.Bytes())
+			if string(block.Bytes) != string(input) {
+				t.Errorf("Inline data didn't match original; got %q want %q", string(block.Bytes), string(input))
+			}
+			_, err = openpgp.CheckDetachedSignature(verifyKeys, bytes.NewReader(block.Bytes), block.ArmoredSignature.Body)
+			if nKeys == 0 {
+				if err == nil {
+					t.Errorf("verifying inline (%s) succeeded; want failure", desc)
+				}
+			} else {
+				if err != nil {
+					t.Errorf("verifying inline (%s) failed (%v); want success", desc, err)
+				}
+			}
+		}
+	}
+}
+
 var clearsignInput = []byte(`
 ;lasjlkfdsa