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>
This commit is contained in:
committed by
Filippo Valsorda
parent
de07523181
commit
aabede6cba
@@ -13,6 +13,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
@@ -177,8 +178,9 @@ func Decode(data []byte) (b *Block, rest []byte) {
|
|||||||
// message.
|
// message.
|
||||||
type dashEscaper struct {
|
type dashEscaper struct {
|
||||||
buffered *bufio.Writer
|
buffered *bufio.Writer
|
||||||
h hash.Hash
|
hashers []hash.Hash // one per key in privateKeys
|
||||||
hashType crypto.Hash
|
hashType crypto.Hash
|
||||||
|
toHash io.Writer // writes to all the hashes in hashers
|
||||||
|
|
||||||
atBeginningOfLine bool
|
atBeginningOfLine bool
|
||||||
isFirstLine bool
|
isFirstLine bool
|
||||||
@@ -186,8 +188,8 @@ type dashEscaper struct {
|
|||||||
whitespace []byte
|
whitespace []byte
|
||||||
byteBuf []byte // a one byte buffer to save allocations
|
byteBuf []byte // a one byte buffer to save allocations
|
||||||
|
|
||||||
privateKey *packet.PrivateKey
|
privateKeys []*packet.PrivateKey
|
||||||
config *packet.Config
|
config *packet.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dashEscaper) Write(data []byte) (n int, err error) {
|
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
|
// 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.
|
// until this point (the start of the next line) before writing it.
|
||||||
if !d.isFirstLine {
|
if !d.isFirstLine {
|
||||||
d.h.Write(crlf)
|
d.toHash.Write(crlf)
|
||||||
}
|
}
|
||||||
d.isFirstLine = false
|
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 {
|
if _, err = d.buffered.Write(dashEscape); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d.h.Write(d.byteBuf)
|
d.toHash.Write(d.byteBuf)
|
||||||
d.atBeginningOfLine = false
|
d.atBeginningOfLine = false
|
||||||
} else if b == '\n' {
|
} else if b == '\n' {
|
||||||
// Nothing to do because we delay writing CRLF to the hash.
|
// Nothing to do because we delay writing CRLF to the hash.
|
||||||
} else {
|
} else {
|
||||||
d.h.Write(d.byteBuf)
|
d.toHash.Write(d.byteBuf)
|
||||||
d.atBeginningOfLine = false
|
d.atBeginningOfLine = false
|
||||||
}
|
}
|
||||||
if err = d.buffered.WriteByte(b); err != nil {
|
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
|
// Any buffered whitespace wasn't at the end of the line so
|
||||||
// we need to write it out.
|
// we need to write it out.
|
||||||
if len(d.whitespace) > 0 {
|
if len(d.whitespace) > 0 {
|
||||||
d.h.Write(d.whitespace)
|
d.toHash.Write(d.whitespace)
|
||||||
if _, err = d.buffered.Write(d.whitespace); err != nil {
|
if _, err = d.buffered.Write(d.whitespace); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d.whitespace = d.whitespace[:0]
|
d.whitespace = d.whitespace[:0]
|
||||||
}
|
}
|
||||||
d.h.Write(d.byteBuf)
|
d.toHash.Write(d.byteBuf)
|
||||||
if err = d.buffered.WriteByte(b); err != nil {
|
if err = d.buffered.WriteByte(b); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -269,25 +271,29 @@ func (d *dashEscaper) Close() (err error) {
|
|||||||
return
|
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)
|
out, err := armor.Encode(d.buffered, "PGP SIGNATURE", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = sig.Serialize(out); err != nil {
|
t := d.config.Now()
|
||||||
return
|
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 {
|
if err = out.Close(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -300,8 +306,17 @@ func (d *dashEscaper) Close() (err error) {
|
|||||||
// Encode returns a WriteCloser which will clear-sign a message with privateKey
|
// 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.
|
// 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) {
|
func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
||||||
if privateKey.Encrypted {
|
return EncodeMulti(w, []*packet.PrivateKey{privateKey}, config)
|
||||||
return nil, errors.InvalidArgumentError("signing key is encrypted")
|
}
|
||||||
|
|
||||||
|
// 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()
|
hashType := config.Hash()
|
||||||
@@ -313,7 +328,14 @@ func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (
|
|||||||
if !hashType.Available() {
|
if !hashType.Available() {
|
||||||
return nil, errors.UnsupportedError("unsupported hash type: " + strconv.Itoa(int(hashType)))
|
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)
|
buffered := bufio.NewWriter(w)
|
||||||
// start has a \n at the beginning that we don't want here.
|
// 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{
|
plaintext = &dashEscaper{
|
||||||
buffered: buffered,
|
buffered: buffered,
|
||||||
h: h,
|
hashers: hashers,
|
||||||
hashType: hashType,
|
hashType: hashType,
|
||||||
|
toHash: toHash,
|
||||||
|
|
||||||
atBeginningOfLine: true,
|
atBeginningOfLine: true,
|
||||||
isFirstLine: true,
|
isFirstLine: true,
|
||||||
|
|
||||||
byteBuf: make([]byte, 1),
|
byteBuf: make([]byte, 1),
|
||||||
|
|
||||||
privateKey: privateKey,
|
privateKeys: privateKeys,
|
||||||
config: config,
|
config: config,
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ package clearsign
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"golang.org/x/crypto/openpgp"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/openpgp"
|
||||||
|
"golang.org/x/crypto/openpgp/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testParse(t *testing.T, input []byte, expected, expectedPlaintext string) {
|
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(`
|
var clearsignInput = []byte(`
|
||||||
;lasjlkfdsa
|
;lasjlkfdsa
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user