go-libp2p-core/record/envelope_test.go

315 lines
7.8 KiB
Go
Raw Normal View History

Signed envelopes & routing records (#73) * add SignedEnvelope type * use struct for SignedEnvelope instead of exposing protobuf directly * doc comments for envelopes * tests for SignedEnvelopes * add helpers to make routing records for Host * fix doc comment * go fmt * add method to peerstore to retrieve signed routing records * update to match spec changes * just use nanoseconds * use proto3 & rename fields to match spec changes * use proto3 for routing records * make envelope fields private & validate on unmarshal * use buffer pool for envelope signatures * tests for RoutingState * go fmt * rename Equals -> Equal, add some comments * use test helpers * get rid of unsigned RoutingState struct, only expose SignedRoutingState * rm batching SignedRoutingStates accessor in peerstore the datastore peerstore implementation doesn't support batched reads, so it's no more efficient to get a bunch of states at once than it is to call SignedRoutingState multiple times. * whitespace * expose struct fields & remove accessors * use camelCase in protos for consistency * use multiformats uvarint for length-prefixes * remove payloadType check when unmarhaling * rm stray ref to golang/protobuf * define CertifiedAddrBook to avoid breaking API change * add events for updated addresses and routing state * remove SignedRoutingStateFromHost helper moving this to go-libp2p * add routing state records, extend peerstore API * fix: rebuild protos with new gogofaster generator * filter private addrs from signed routing records * envelope: use byte slices from pool; adjust interface. * move envelope to record package. * move protobuf files; adjust imports everywhere. * rename RoutingStateRecord -> PeerRecord also removes embedded reference to Envelope from the record, as that was confusing. as a result, the CertifiedAddrBook now accepts/returns record.SignedEnvelope instead of a specialized type. * hoist Seq from PeerRecord to SignedEnvelope * test that PeerRecords can't be signed by wrong key * commit go.sum * add Seq field to envelope signature * fix proto_path in Makefile * fix import ordering * comments for PeerRecord proto message also removes the seq field from PeerMessage proto, since it was moved to the SignedEnvelope * use Record type for envelope payloads * rename SignedEnvelope -> Envelope, unmarshal payload in ConsumeEnvelope * return buffer to pool before early return * doc comments * rename CertifiedAddrBook methods, update comments * cache unmarshalled Record payload inside Envelope * doc comments * store reflect.Type when registering Record * Revert "return buffer to pool before early return" 8d8da386f26482e06dc21989a6b5ade69f0a46d9 misread this - unsigned will be nil if there's an error, so it was right the way it was * use a DefaultRecord for unregistered PayloadTypes instead of returning an error if we don't have a registered Record for a given PayloadType, we can have a catch-all DefaultRecord type that just preserves the original payload as a []byte * cleanup DefaultRecord code a bit - removes unused error return from blankRecordForPayloadType - just references instead of copying in DefaultRecord.UnmarshalRecord I figure this is likely safe, since we'll be unmarshalling from the payload of an Envelope, which shouldn't get altered after it's created. * use explicit payloadType in MakeEnvelopeWithRecord * Revert DefaultRecord commits ae3bc7bdfb657c232229229706854a56effca80b a26c845a766b45ceabd87c17c0801d191650f0d4 * doc comments * move Seq field back to PeerRecord * make diffs optional in EvtLocalAddressesUpdated * more envelope tests * replace MakeEnvelope with record.Seal also: - add Domain and Codec fields to Record interface * fix import * add interface check * rename ProcessPeerRecord -> ConsumePeerRecord also, adds bool `accepted` return value * rename event field, add doc comment * peer record protobuf: fix field casing. * record protobuf: add docs and fix casing. * cleanup: group imports. * nit: split test/utils.go => test/{addrs,errors}.go. Co-authored-by: Raúl Kripalani <raul.kripalani@gmail.com>
2020-02-11 03:53:24 +08:00
package record_test
import (
"bytes"
"errors"
"testing"
crypto "github.com/libp2p/go-libp2p-core/crypto"
. "github.com/libp2p/go-libp2p-core/record"
pb "github.com/libp2p/go-libp2p-core/record/pb"
"github.com/libp2p/go-libp2p-core/test"
"github.com/gogo/protobuf/proto"
)
type simpleRecord struct {
testDomain *string
testCodec []byte
message string
}
func (r *simpleRecord) Domain() string {
if r.testDomain != nil {
return *r.testDomain
}
return "libp2p-testing"
}
func (r *simpleRecord) Codec() []byte {
if r.testCodec != nil {
return r.testCodec
}
return []byte("/libp2p/testdata")
}
func (r *simpleRecord) MarshalRecord() ([]byte, error) {
return []byte(r.message), nil
}
func (r *simpleRecord) UnmarshalRecord(buf []byte) error {
r.message = string(buf)
return nil
}
// Make an envelope, verify & open it, marshal & unmarshal it
func TestEnvelopeHappyPath(t *testing.T) {
var (
rec = &simpleRecord{message: "hello world!"}
priv, pub, err = test.RandTestKeyPair(crypto.Ed25519, 256)
)
test.AssertNilError(t, err)
payload, err := rec.MarshalRecord()
test.AssertNilError(t, err)
envelope, err := Seal(rec, priv)
test.AssertNilError(t, err)
if !envelope.PublicKey.Equals(pub) {
t.Error("envelope has unexpected public key")
}
if !bytes.Equal(rec.Codec(), envelope.PayloadType) {
Signed envelopes & routing records (#73) * add SignedEnvelope type * use struct for SignedEnvelope instead of exposing protobuf directly * doc comments for envelopes * tests for SignedEnvelopes * add helpers to make routing records for Host * fix doc comment * go fmt * add method to peerstore to retrieve signed routing records * update to match spec changes * just use nanoseconds * use proto3 & rename fields to match spec changes * use proto3 for routing records * make envelope fields private & validate on unmarshal * use buffer pool for envelope signatures * tests for RoutingState * go fmt * rename Equals -> Equal, add some comments * use test helpers * get rid of unsigned RoutingState struct, only expose SignedRoutingState * rm batching SignedRoutingStates accessor in peerstore the datastore peerstore implementation doesn't support batched reads, so it's no more efficient to get a bunch of states at once than it is to call SignedRoutingState multiple times. * whitespace * expose struct fields & remove accessors * use camelCase in protos for consistency * use multiformats uvarint for length-prefixes * remove payloadType check when unmarhaling * rm stray ref to golang/protobuf * define CertifiedAddrBook to avoid breaking API change * add events for updated addresses and routing state * remove SignedRoutingStateFromHost helper moving this to go-libp2p * add routing state records, extend peerstore API * fix: rebuild protos with new gogofaster generator * filter private addrs from signed routing records * envelope: use byte slices from pool; adjust interface. * move envelope to record package. * move protobuf files; adjust imports everywhere. * rename RoutingStateRecord -> PeerRecord also removes embedded reference to Envelope from the record, as that was confusing. as a result, the CertifiedAddrBook now accepts/returns record.SignedEnvelope instead of a specialized type. * hoist Seq from PeerRecord to SignedEnvelope * test that PeerRecords can't be signed by wrong key * commit go.sum * add Seq field to envelope signature * fix proto_path in Makefile * fix import ordering * comments for PeerRecord proto message also removes the seq field from PeerMessage proto, since it was moved to the SignedEnvelope * use Record type for envelope payloads * rename SignedEnvelope -> Envelope, unmarshal payload in ConsumeEnvelope * return buffer to pool before early return * doc comments * rename CertifiedAddrBook methods, update comments * cache unmarshalled Record payload inside Envelope * doc comments * store reflect.Type when registering Record * Revert "return buffer to pool before early return" 8d8da386f26482e06dc21989a6b5ade69f0a46d9 misread this - unsigned will be nil if there's an error, so it was right the way it was * use a DefaultRecord for unregistered PayloadTypes instead of returning an error if we don't have a registered Record for a given PayloadType, we can have a catch-all DefaultRecord type that just preserves the original payload as a []byte * cleanup DefaultRecord code a bit - removes unused error return from blankRecordForPayloadType - just references instead of copying in DefaultRecord.UnmarshalRecord I figure this is likely safe, since we'll be unmarshalling from the payload of an Envelope, which shouldn't get altered after it's created. * use explicit payloadType in MakeEnvelopeWithRecord * Revert DefaultRecord commits ae3bc7bdfb657c232229229706854a56effca80b a26c845a766b45ceabd87c17c0801d191650f0d4 * doc comments * move Seq field back to PeerRecord * make diffs optional in EvtLocalAddressesUpdated * more envelope tests * replace MakeEnvelope with record.Seal also: - add Domain and Codec fields to Record interface * fix import * add interface check * rename ProcessPeerRecord -> ConsumePeerRecord also, adds bool `accepted` return value * rename event field, add doc comment * peer record protobuf: fix field casing. * record protobuf: add docs and fix casing. * cleanup: group imports. * nit: split test/utils.go => test/{addrs,errors}.go. Co-authored-by: Raúl Kripalani <raul.kripalani@gmail.com>
2020-02-11 03:53:24 +08:00
t.Error("PayloadType does not match record Codec")
}
serialized, err := envelope.Marshal()
test.AssertNilError(t, err)
RegisterType(&simpleRecord{})
deserialized, rec2, err := ConsumeEnvelope(serialized, rec.Domain())
test.AssertNilError(t, err)
if !bytes.Equal(deserialized.RawPayload, payload) {
Signed envelopes & routing records (#73) * add SignedEnvelope type * use struct for SignedEnvelope instead of exposing protobuf directly * doc comments for envelopes * tests for SignedEnvelopes * add helpers to make routing records for Host * fix doc comment * go fmt * add method to peerstore to retrieve signed routing records * update to match spec changes * just use nanoseconds * use proto3 & rename fields to match spec changes * use proto3 for routing records * make envelope fields private & validate on unmarshal * use buffer pool for envelope signatures * tests for RoutingState * go fmt * rename Equals -> Equal, add some comments * use test helpers * get rid of unsigned RoutingState struct, only expose SignedRoutingState * rm batching SignedRoutingStates accessor in peerstore the datastore peerstore implementation doesn't support batched reads, so it's no more efficient to get a bunch of states at once than it is to call SignedRoutingState multiple times. * whitespace * expose struct fields & remove accessors * use camelCase in protos for consistency * use multiformats uvarint for length-prefixes * remove payloadType check when unmarhaling * rm stray ref to golang/protobuf * define CertifiedAddrBook to avoid breaking API change * add events for updated addresses and routing state * remove SignedRoutingStateFromHost helper moving this to go-libp2p * add routing state records, extend peerstore API * fix: rebuild protos with new gogofaster generator * filter private addrs from signed routing records * envelope: use byte slices from pool; adjust interface. * move envelope to record package. * move protobuf files; adjust imports everywhere. * rename RoutingStateRecord -> PeerRecord also removes embedded reference to Envelope from the record, as that was confusing. as a result, the CertifiedAddrBook now accepts/returns record.SignedEnvelope instead of a specialized type. * hoist Seq from PeerRecord to SignedEnvelope * test that PeerRecords can't be signed by wrong key * commit go.sum * add Seq field to envelope signature * fix proto_path in Makefile * fix import ordering * comments for PeerRecord proto message also removes the seq field from PeerMessage proto, since it was moved to the SignedEnvelope * use Record type for envelope payloads * rename SignedEnvelope -> Envelope, unmarshal payload in ConsumeEnvelope * return buffer to pool before early return * doc comments * rename CertifiedAddrBook methods, update comments * cache unmarshalled Record payload inside Envelope * doc comments * store reflect.Type when registering Record * Revert "return buffer to pool before early return" 8d8da386f26482e06dc21989a6b5ade69f0a46d9 misread this - unsigned will be nil if there's an error, so it was right the way it was * use a DefaultRecord for unregistered PayloadTypes instead of returning an error if we don't have a registered Record for a given PayloadType, we can have a catch-all DefaultRecord type that just preserves the original payload as a []byte * cleanup DefaultRecord code a bit - removes unused error return from blankRecordForPayloadType - just references instead of copying in DefaultRecord.UnmarshalRecord I figure this is likely safe, since we'll be unmarshalling from the payload of an Envelope, which shouldn't get altered after it's created. * use explicit payloadType in MakeEnvelopeWithRecord * Revert DefaultRecord commits ae3bc7bdfb657c232229229706854a56effca80b a26c845a766b45ceabd87c17c0801d191650f0d4 * doc comments * move Seq field back to PeerRecord * make diffs optional in EvtLocalAddressesUpdated * more envelope tests * replace MakeEnvelope with record.Seal also: - add Domain and Codec fields to Record interface * fix import * add interface check * rename ProcessPeerRecord -> ConsumePeerRecord also, adds bool `accepted` return value * rename event field, add doc comment * peer record protobuf: fix field casing. * record protobuf: add docs and fix casing. * cleanup: group imports. * nit: split test/utils.go => test/{addrs,errors}.go. Co-authored-by: Raúl Kripalani <raul.kripalani@gmail.com>
2020-02-11 03:53:24 +08:00
t.Error("payload of envelope does not match input")
}
if !envelope.Equal(deserialized) {
t.Error("round-trip serde results in unequal envelope structures")
}
typedRec, ok := rec2.(*simpleRecord)
if !ok {
t.Error("expected ConsumeEnvelope to return record with type registered for payloadType")
}
if typedRec.message != "hello world!" {
t.Error("unexpected alteration of record")
}
}
func TestConsumeTypedEnvelope(t *testing.T) {
var (
rec = simpleRecord{message: "hello world!"}
priv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)
)
envelope, err := Seal(&rec, priv)
test.AssertNilError(t, err)
envelopeBytes, err := envelope.Marshal()
test.AssertNilError(t, err)
rec2 := &simpleRecord{}
_, err = ConsumeTypedEnvelope(envelopeBytes, rec2)
test.AssertNilError(t, err)
if rec2.message != "hello world!" {
t.Error("unexpected alteration of record")
}
}
func TestMakeEnvelopeFailsWithEmptyDomain(t *testing.T) {
var (
rec = simpleRecord{message: "hello world!"}
domain = ""
priv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)
)
if err != nil {
t.Fatal(err)
}
// override domain with empty string
rec.testDomain = &domain
_, err = Seal(&rec, priv)
test.ExpectError(t, err, "making an envelope with an empty domain should fail")
}
func TestMakeEnvelopeFailsWithEmptyPayloadType(t *testing.T) {
var (
rec = simpleRecord{message: "hello world!"}
priv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)
)
if err != nil {
t.Fatal(err)
}
// override payload with empty slice
rec.testCodec = []byte{}
_, err = Seal(&rec, priv)
test.ExpectError(t, err, "making an envelope with an empty payloadType should fail")
}
type failingRecord struct {
allowMarshal bool
allowUnmarshal bool
}
func (r failingRecord) Domain() string {
return "testing"
}
func (r failingRecord) Codec() []byte {
return []byte("doesn't matter")
}
func (r failingRecord) MarshalRecord() ([]byte, error) {
if r.allowMarshal {
return []byte{}, nil
}
return nil, errors.New("marshal failed")
}
func (r failingRecord) UnmarshalRecord(data []byte) error {
if r.allowUnmarshal {
return nil
}
return errors.New("unmarshal failed")
}
func TestSealFailsIfRecordMarshalFails(t *testing.T) {
var (
priv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)
)
if err != nil {
t.Fatal(err)
}
rec := failingRecord{}
_, err = Seal(rec, priv)
test.ExpectError(t, err, "Seal should fail if Record fails to marshal")
}
func TestConsumeEnvelopeFailsIfEnvelopeUnmarshalFails(t *testing.T) {
_, _, err := ConsumeEnvelope([]byte("not an Envelope protobuf"), "doesn't-matter")
test.ExpectError(t, err, "ConsumeEnvelope should fail if Envelope fails to unmarshal")
}
func TestConsumeEnvelopeFailsIfRecordUnmarshalFails(t *testing.T) {
var (
priv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)
)
if err != nil {
t.Fatal(err)
}
RegisterType(failingRecord{})
rec := failingRecord{allowMarshal: true}
env, err := Seal(rec, priv)
test.AssertNilError(t, err)
envBytes, err := env.Marshal()
test.AssertNilError(t, err)
_, _, err = ConsumeEnvelope(envBytes, rec.Domain())
test.ExpectError(t, err, "ConsumeEnvelope should fail if Record fails to unmarshal")
}
func TestConsumeTypedEnvelopeFailsIfRecordUnmarshalFails(t *testing.T) {
var (
priv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)
)
if err != nil {
t.Fatal(err)
}
RegisterType(failingRecord{})
rec := failingRecord{allowMarshal: true}
env, err := Seal(rec, priv)
test.AssertNilError(t, err)
envBytes, err := env.Marshal()
test.AssertNilError(t, err)
rec2 := failingRecord{}
_, err = ConsumeTypedEnvelope(envBytes, rec2)
test.ExpectError(t, err, "ConsumeTypedEnvelope should fail if Record fails to unmarshal")
}
func TestEnvelopeValidateFailsForDifferentDomain(t *testing.T) {
var (
rec = &simpleRecord{message: "hello world"}
priv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)
)
test.AssertNilError(t, err)
envelope, err := Seal(rec, priv)
test.AssertNilError(t, err)
serialized, err := envelope.Marshal()
test.AssertNilError(t, err)
Signed envelopes & routing records (#73) * add SignedEnvelope type * use struct for SignedEnvelope instead of exposing protobuf directly * doc comments for envelopes * tests for SignedEnvelopes * add helpers to make routing records for Host * fix doc comment * go fmt * add method to peerstore to retrieve signed routing records * update to match spec changes * just use nanoseconds * use proto3 & rename fields to match spec changes * use proto3 for routing records * make envelope fields private & validate on unmarshal * use buffer pool for envelope signatures * tests for RoutingState * go fmt * rename Equals -> Equal, add some comments * use test helpers * get rid of unsigned RoutingState struct, only expose SignedRoutingState * rm batching SignedRoutingStates accessor in peerstore the datastore peerstore implementation doesn't support batched reads, so it's no more efficient to get a bunch of states at once than it is to call SignedRoutingState multiple times. * whitespace * expose struct fields & remove accessors * use camelCase in protos for consistency * use multiformats uvarint for length-prefixes * remove payloadType check when unmarhaling * rm stray ref to golang/protobuf * define CertifiedAddrBook to avoid breaking API change * add events for updated addresses and routing state * remove SignedRoutingStateFromHost helper moving this to go-libp2p * add routing state records, extend peerstore API * fix: rebuild protos with new gogofaster generator * filter private addrs from signed routing records * envelope: use byte slices from pool; adjust interface. * move envelope to record package. * move protobuf files; adjust imports everywhere. * rename RoutingStateRecord -> PeerRecord also removes embedded reference to Envelope from the record, as that was confusing. as a result, the CertifiedAddrBook now accepts/returns record.SignedEnvelope instead of a specialized type. * hoist Seq from PeerRecord to SignedEnvelope * test that PeerRecords can't be signed by wrong key * commit go.sum * add Seq field to envelope signature * fix proto_path in Makefile * fix import ordering * comments for PeerRecord proto message also removes the seq field from PeerMessage proto, since it was moved to the SignedEnvelope * use Record type for envelope payloads * rename SignedEnvelope -> Envelope, unmarshal payload in ConsumeEnvelope * return buffer to pool before early return * doc comments * rename CertifiedAddrBook methods, update comments * cache unmarshalled Record payload inside Envelope * doc comments * store reflect.Type when registering Record * Revert "return buffer to pool before early return" 8d8da386f26482e06dc21989a6b5ade69f0a46d9 misread this - unsigned will be nil if there's an error, so it was right the way it was * use a DefaultRecord for unregistered PayloadTypes instead of returning an error if we don't have a registered Record for a given PayloadType, we can have a catch-all DefaultRecord type that just preserves the original payload as a []byte * cleanup DefaultRecord code a bit - removes unused error return from blankRecordForPayloadType - just references instead of copying in DefaultRecord.UnmarshalRecord I figure this is likely safe, since we'll be unmarshalling from the payload of an Envelope, which shouldn't get altered after it's created. * use explicit payloadType in MakeEnvelopeWithRecord * Revert DefaultRecord commits ae3bc7bdfb657c232229229706854a56effca80b a26c845a766b45ceabd87c17c0801d191650f0d4 * doc comments * move Seq field back to PeerRecord * make diffs optional in EvtLocalAddressesUpdated * more envelope tests * replace MakeEnvelope with record.Seal also: - add Domain and Codec fields to Record interface * fix import * add interface check * rename ProcessPeerRecord -> ConsumePeerRecord also, adds bool `accepted` return value * rename event field, add doc comment * peer record protobuf: fix field casing. * record protobuf: add docs and fix casing. * cleanup: group imports. * nit: split test/utils.go => test/{addrs,errors}.go. Co-authored-by: Raúl Kripalani <raul.kripalani@gmail.com>
2020-02-11 03:53:24 +08:00
// try to open our modified envelope
_, _, err = ConsumeEnvelope(serialized, "wrong-domain")
test.ExpectError(t, err, "should not be able to open envelope with incorrect domain")
}
func TestEnvelopeValidateFailsIfPayloadTypeIsAltered(t *testing.T) {
var (
rec = &simpleRecord{message: "hello world!"}
domain = "libp2p-testing"
priv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)
)
test.AssertNilError(t, err)
envelope, err := Seal(rec, priv)
test.AssertNilError(t, err)
serialized := alterMessageAndMarshal(t, envelope, func(msg *pb.Envelope) {
msg.PayloadType = []byte("foo")
})
// try to open our modified envelope
_, _, err = ConsumeEnvelope(serialized, domain)
test.ExpectError(t, err, "should not be able to open envelope with modified PayloadType")
}
func TestEnvelopeValidateFailsIfContentsAreAltered(t *testing.T) {
var (
rec = &simpleRecord{message: "hello world!"}
domain = "libp2p-testing"
priv, _, err = test.RandTestKeyPair(crypto.Ed25519, 256)
)
test.AssertNilError(t, err)
envelope, err := Seal(rec, priv)
test.AssertNilError(t, err)
serialized := alterMessageAndMarshal(t, envelope, func(msg *pb.Envelope) {
msg.Payload = []byte("totally legit, trust me")
})
// try to open our modified envelope
_, _, err = ConsumeEnvelope(serialized, domain)
test.ExpectError(t, err, "should not be able to open envelope with modified payload")
}
// Since we're outside of the crypto package (to avoid import cycles with test package),
// we can't alter the fields in a Envelope directly. This helper marshals
// the envelope to a protobuf and calls the alterMsg function, which should
// alter the protobuf message.
// Returns the serialized altered protobuf message.
func alterMessageAndMarshal(t *testing.T, envelope *Envelope, alterMsg func(*pb.Envelope)) []byte {
t.Helper()
serialized, err := envelope.Marshal()
test.AssertNilError(t, err)
msg := pb.Envelope{}
err = proto.Unmarshal(serialized, &msg)
test.AssertNilError(t, err)
alterMsg(&msg)
serialized, err = msg.Marshal()
test.AssertNilError(t, err)
return serialized
}