aboutsummaryrefslogtreecommitdiff
path: root/openpgp
diff options
context:
space:
mode:
authorThomas Bushnell, BSG <tbushnell@google.com>2018-08-16 14:30:07 -0700
committerFilippo Valsorda <filippo@golang.org>2018-08-16 22:57:34 +0000
commitaabede6cba87e37f413b3e60ebfc214f8eeca1b0 (patch)
tree1e3ef18c4e82cc3673d87cce799875f85eaf2179 /openpgp
parentde0752318171da717af4ce24d0a2e8626afaeb11 (diff)
downloadgo-x-crypto-aabede6cba87e37f413b3e60ebfc214f8eeca1b0.tar.xz
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>
Diffstat (limited to 'openpgp')
-rw-r--r--openpgp/clearsign/clearsign.go75
-rw-r--r--openpgp/clearsign/clearsign_test.go70
2 files changed, 118 insertions, 27 deletions
diff --git a/openpgp/clearsign/clearsign.go b/openpgp/clearsign/clearsign.go
index def4cab..a9437dc 100644
--- a/openpgp/clearsign/clearsign.go
+++ b/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
diff --git a/openpgp/clearsign/clearsign_test.go b/openpgp/clearsign/clearsign_test.go
index 2c09480..96f5d78 100644
--- a/openpgp/clearsign/clearsign_test.go
+++ b/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