1
0
mirror of https://github.com/libp2p/go-libp2p-core.git synced 2025-04-28 17:10:14 +08:00

envelope: use byte slices from pool; adjust interface.

This commit is contained in:
Raúl Kripalani 2019-12-27 20:07:28 +00:00
parent b57c5023fb
commit 9bbe26ca5f
2 changed files with 117 additions and 81 deletions

View File

@ -3,19 +3,21 @@ package crypto
import (
"bytes"
"errors"
"fmt"
"github.com/gogo/protobuf/proto"
"github.com/libp2p/go-buffer-pool"
pool "github.com/libp2p/go-buffer-pool"
pb "github.com/libp2p/go-libp2p-core/crypto/pb"
"github.com/multiformats/go-varint"
)
// SignedEnvelope contains an arbitrary []byte payload, signed by a libp2p peer.
// Envelopes are signed in the context of a particular "domain", which is a string
// specified when creating and verifying the envelope. You must know the domain
// string used to produce the envelope in order to verify the signature and
// access the payload.
//
// Envelopes are signed in the context of a particular "domain", which is a
// string specified when creating and verifying the envelope. You must know the
// domain string used to produce the envelope in order to verify the signature
// and access the payload.
type SignedEnvelope struct {
// The public key that can be used to verify the signature and derive the peer id of the signer.
PublicKey PubKey
@ -27,12 +29,12 @@ type SignedEnvelope struct {
// The envelope payload.
Payload []byte
// The signature of the domain string, type hint, and payload.
// The signature of the domain string :: type hint :: payload.
signature []byte
}
var errEmptyDomain = errors.New("envelope domain must not be empty")
var errInvalidSignature = errors.New("invalid signature or incorrect domain")
var ErrEmptyDomain = errors.New("envelope domain must not be empty")
var ErrInvalidSignature = errors.New("invalid signature or incorrect domain")
// MakeEnvelope constructs a new SignedEnvelope using the given privateKey.
//
@ -41,14 +43,17 @@ var errInvalidSignature = errors.New("invalid signature or incorrect domain")
//
// The 'PayloadType' field indicates what kind of data is contained and may be empty.
func MakeEnvelope(privateKey PrivKey, domain string, payloadType []byte, payload []byte) (*SignedEnvelope, error) {
if len(domain) == 0 {
return nil, errEmptyDomain
if domain == "" {
return nil, ErrEmptyDomain
}
toSign, err := makeSigBuffer(domain, payloadType, payload)
unsigned, err := makeUnsigned(domain, payloadType, payload)
if err != nil {
return nil, err
}
sig, err := privateKey.Sign(toSign)
defer pool.Put(unsigned)
sig, err := privateKey.Sign(unsigned)
if err != nil {
return nil, err
}
@ -61,32 +66,31 @@ func MakeEnvelope(privateKey PrivKey, domain string, payloadType []byte, payload
}, nil
}
// OpenEnvelope unmarshals a serialized SignedEnvelope, validating its signature
// using the provided 'domain' string.
func OpenEnvelope(envelopeBytes []byte, domain string) (*SignedEnvelope, error) {
e, err := UnmarshalEnvelopeWithoutValidating(envelopeBytes)
// ConsumeEnvelope unmarshals a serialized SignedEnvelope, and validates its
// signature using the provided 'domain' string. If validation fails, an error
// is returned, along with the unmarshalled payload so it can be inspected.
func ConsumeEnvelope(data []byte, domain string) (*SignedEnvelope, error) {
e, err := UnmarshalEnvelope(data)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed when unmarshalling the envelope: %w", err)
}
err = e.validate(domain)
if err != nil {
return nil, err
}
return e, nil
return e, e.validate(domain)
}
// UnmarshalEnvelopeWithoutValidating unmarshals a serialized SignedEnvelope protobuf message,
// without validating its contents. Should not be used unless you have a very good reason
// (e.g. testing)!
func UnmarshalEnvelopeWithoutValidating(serializedEnvelope []byte) (*SignedEnvelope, error) {
// UnmarshalEnvelope unmarshals a serialized SignedEnvelope protobuf message,
// without validating its contents. Most users should use ConsumeEnvelope.
func UnmarshalEnvelope(data []byte) (*SignedEnvelope, error) {
var e pb.SignedEnvelope
if err := proto.Unmarshal(serializedEnvelope, &e); err != nil {
if err := proto.Unmarshal(data, &e); err != nil {
return nil, err
}
key, err := PublicKeyFromProto(e.PublicKey)
if err != nil {
return nil, err
}
return &SignedEnvelope{
PublicKey: key,
PayloadType: e.PayloadType,
@ -95,12 +99,14 @@ func UnmarshalEnvelopeWithoutValidating(serializedEnvelope []byte) (*SignedEnvel
}, nil
}
// Marshal returns a byte slice containing a serailized protobuf representation of a SignedEnvelope.
// Marshal returns a byte slice containing a serialized protobuf representation
// of a SignedEnvelope.
func (e *SignedEnvelope) Marshal() ([]byte, error) {
key, err := PublicKeyToProto(e.PublicKey)
if err != nil {
return nil, err
}
msg := pb.SignedEnvelope{
PublicKey: key,
PayloadType: e.PayloadType,
@ -110,9 +116,9 @@ func (e *SignedEnvelope) Marshal() ([]byte, error) {
return proto.Marshal(&msg)
}
// Equal returns true if the other SignedEnvelope has the same
// public key, payload, payload type, and signature. This
// implies that they were also created with the same domain string.
// Equal returns true if the other SignedEnvelope has the same public key,
// payload, payload type, and signature. This implies that they were also
// created with the same domain string.
func (e *SignedEnvelope) Equal(other *SignedEnvelope) bool {
return e.PublicKey.Equals(other.PublicKey) &&
bytes.Compare(e.PayloadType, other.PayloadType) == 0 &&
@ -123,49 +129,49 @@ func (e *SignedEnvelope) Equal(other *SignedEnvelope) bool {
// validate returns true if the envelope signature is valid for the given 'domain',
// or false if it is invalid. May return an error if signature validation fails.
func (e *SignedEnvelope) validate(domain string) error {
toVerify, err := makeSigBuffer(domain, e.PayloadType, e.Payload)
unsigned, err := makeUnsigned(domain, e.PayloadType, e.Payload)
if err != nil {
return err
}
valid, err := e.PublicKey.Verify(toVerify, e.signature)
defer pool.Put(unsigned)
valid, err := e.PublicKey.Verify(unsigned, e.signature)
if err != nil {
return err
return fmt.Errorf("failed while verifying signature: %w", err)
}
if !valid {
return errInvalidSignature
return ErrInvalidSignature
}
return nil
}
// makeSigBuffer is a helper function that prepares a buffer to sign or verify.
func makeSigBuffer(domain string, payloadType []byte, payload []byte) ([]byte, error) {
domainBytes := []byte(domain)
fields := [][]byte{domainBytes, payloadType, payload}
// makeUnsigned is a helper function that prepares a buffer to sign or verify.
// It returns a byte slice from a pool. The caller MUST return this slice to the
// pool.
func makeUnsigned(domain string, payloadType []byte, payload []byte) ([]byte, error) {
var (
fields = [][]byte{[]byte(domain), payloadType, payload}
// fields are prefixed with their length as an unsigned varint. we
// compute the lengths before allocating the sig buffer so we know how
// much space to add for the lengths
flen = make([][]byte, len(fields))
size = 0
)
// fields are prefixed with their length as an unsigned varint.
// we compute the lengths before allocating the sig
// buffer so we know how much space to add for the lengths
fieldLengths := make([][]byte, len(fields))
bufSize := 0
for i, f := range fields {
l := len(f)
fieldLengths[i] = varint.ToUvarint(uint64(l))
bufSize += l + len(fieldLengths[i])
flen[i] = varint.ToUvarint(uint64(l))
size += l + len(flen[i])
}
b := pool.NewBuffer(nil)
b.Grow(bufSize)
b := pool.Get(size)
var s int
for i, f := range fields {
_, err := b.Write(fieldLengths[i])
if err != nil {
return nil, err
}
_, err = b.Write(f)
if err != nil {
return nil, err
}
s += copy(b[s:], flen[i])
s += copy(b[s:], f)
}
return b.Bytes(), nil
return b[:s], nil
}

View File

@ -12,11 +12,15 @@ import (
// Make an envelope, verify & open it, marshal & unmarshal it
func TestEnvelopeHappyPath(t *testing.T) {
priv, pub, err := test.RandTestKeyPair(Ed25519, 256)
var (
payload = []byte("happy hacking")
domain = "libp2p-testing"
payloadType = []byte("/libp2p/testdata")
priv, pub, err = test.RandTestKeyPair(Ed25519, 256)
)
test.AssertNilError(t, err)
payload := []byte("happy hacking")
domain := "libp2p-testing"
payloadType := []byte("/libp2p/testdata")
envelope, err := MakeEnvelope(priv, domain, payloadType, payload)
test.AssertNilError(t, err)
@ -30,7 +34,8 @@ func TestEnvelopeHappyPath(t *testing.T) {
serialized, err := envelope.Marshal()
test.AssertNilError(t, err)
deserialized, err := OpenEnvelope(serialized, domain)
deserialized, err := ConsumeEnvelope(serialized, domain)
test.AssertNilError(t, err)
if bytes.Compare(deserialized.Payload, payload) != 0 {
@ -43,60 +48,81 @@ func TestEnvelopeHappyPath(t *testing.T) {
}
func TestMakeEnvelopeFailsWithEmptyDomain(t *testing.T) {
priv, _, err := test.RandTestKeyPair(Ed25519, 256)
var (
payload = []byte("happy hacking")
payloadType = []byte("/libp2p/testdata")
priv, _, err = test.RandTestKeyPair(Ed25519, 256)
)
if err != nil {
t.Error(err)
t.Fatal(err)
}
payload := []byte("happy hacking")
payloadType := []byte("/libp2p/testdata")
_, err = MakeEnvelope(priv, "", payloadType, payload)
test.ExpectError(t, err, "making an envelope with an empty domain should fail")
}
func TestEnvelopeValidateFailsForDifferentDomain(t *testing.T) {
priv, _, err := test.RandTestKeyPair(Ed25519, 256)
var (
payload = []byte("happy hacking")
domain = "libp2p-testing"
payloadType = []byte("/libp2p/testdata")
priv, _, err = test.RandTestKeyPair(Ed25519, 256)
)
test.AssertNilError(t, err)
payload := []byte("happy hacking")
domain := "libp2p-testing"
payloadType := []byte("/libp2p/testdata")
envelope, err := MakeEnvelope(priv, domain, payloadType, payload)
test.AssertNilError(t, err)
serialized, err := envelope.Marshal()
// try to open our modified envelope
_, err = OpenEnvelope(serialized, "wrong-domain")
_, err = ConsumeEnvelope(serialized, "wrong-domain")
test.ExpectError(t, err, "should not be able to open envelope with incorrect domain")
}
func TestEnvelopeValidateFailsIfTypeHintIsAltered(t *testing.T) {
priv, _, err := test.RandTestKeyPair(Ed25519, 256)
var (
payload = []byte("happy hacking")
domain = "libp2p-testing"
payloadType = []byte("/libp2p/testdata")
priv, _, err = test.RandTestKeyPair(Ed25519, 256)
)
test.AssertNilError(t, err)
payload := []byte("happy hacking")
domain := "libp2p-testing"
payloadType := []byte("/libp2p/testdata")
envelope, err := MakeEnvelope(priv, domain, payloadType, payload)
test.AssertNilError(t, err)
serialized := alterMessageAndMarshal(t, envelope, func(msg *pb.SignedEnvelope) {
msg.PayloadType = []byte("foo")
})
// try to open our modified envelope
_, err = OpenEnvelope(serialized, domain)
_, err = ConsumeEnvelope(serialized, domain)
test.ExpectError(t, err, "should not be able to open envelope with modified PayloadType")
}
func TestEnvelopeValidateFailsIfContentsAreAltered(t *testing.T) {
priv, _, err := test.RandTestKeyPair(Ed25519, 256)
var (
payload = []byte("happy hacking")
domain = "libp2p-testing"
payloadType = []byte("/libp2p/testdata")
priv, _, err = test.RandTestKeyPair(Ed25519, 256)
)
test.AssertNilError(t, err)
payload := []byte("happy hacking")
domain := "libp2p-testing"
payloadType := []byte("/libp2p/testdata")
envelope, err := MakeEnvelope(priv, domain, payloadType, payload)
test.AssertNilError(t, err)
serialized := alterMessageAndMarshal(t, envelope, func(msg *pb.SignedEnvelope) {
msg.Payload = []byte("totally legit, trust me")
})
// try to open our modified envelope
_, err = OpenEnvelope(serialized, domain)
_, err = ConsumeEnvelope(serialized, domain)
test.ExpectError(t, err, "should not be able to open envelope with modified payload")
}
@ -107,13 +133,17 @@ func TestEnvelopeValidateFailsIfContentsAreAltered(t *testing.T) {
// Returns the serialized altered protobuf message.
func alterMessageAndMarshal(t *testing.T, envelope *SignedEnvelope, alterMsg func(*pb.SignedEnvelope)) []byte {
t.Helper()
serialized, err := envelope.Marshal()
test.AssertNilError(t, err)
msg := pb.SignedEnvelope{}
err = proto.Unmarshal(serialized, &msg)
test.AssertNilError(t, err)
alterMsg(&msg)
serialized, err = msg.Marshal()
test.AssertNilError(t, err)
return serialized
}