diff options
| author | Thomas Bushnell, BSG <tbushnell@google.com> | 2018-08-16 14:30:07 -0700 |
|---|---|---|
| committer | Filippo Valsorda <filippo@golang.org> | 2018-08-16 22:57:34 +0000 |
| commit | aabede6cba87e37f413b3e60ebfc214f8eeca1b0 (patch) | |
| tree | 1e3ef18c4e82cc3673d87cce799875f85eaf2179 /openpgp | |
| parent | de0752318171da717af4ce24d0a2e8626afaeb11 (diff) | |
| download | go-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.go | 75 | ||||
| -rw-r--r-- | openpgp/clearsign/clearsign_test.go | 70 |
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 |
