mirror of
https://github.com/libp2p/go-libp2p-core.git
synced 2025-02-23 08:30:07 +08:00
make envelope fields private & validate on unmarshal
This commit is contained in:
parent
a8a530e24f
commit
3724a31efe
@ -16,14 +16,13 @@ import (
|
|||||||
type SignedEnvelope struct {
|
type SignedEnvelope struct {
|
||||||
|
|
||||||
// The public key that can be used to verify the signature and derive the peer id of the signer.
|
// The public key that can be used to verify the signature and derive the peer id of the signer.
|
||||||
PublicKey PubKey
|
publicKey PubKey
|
||||||
|
|
||||||
// A binary identifier that indicates what kind of data is contained in the payload.
|
// A binary identifier that indicates what kind of data is contained in the payload.
|
||||||
// TODO(yusef): enforce multicodec prefix
|
// TODO(yusef): enforce multicodec prefix
|
||||||
PayloadType []byte
|
payloadType []byte
|
||||||
|
|
||||||
// The envelope payload. This is private to discourage accessing the payload without verifying the signature.
|
// The envelope payload.
|
||||||
// To access, use the Open method.
|
|
||||||
payload []byte
|
payload []byte
|
||||||
|
|
||||||
// The signature of the domain string, type hint, and payload.
|
// The signature of the domain string, type hint, and payload.
|
||||||
@ -31,6 +30,7 @@ type SignedEnvelope struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var errEmptyDomain = errors.New("envelope domain must not be empty")
|
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.
|
// MakeEnvelope constructs a new SignedEnvelope using the given privateKey.
|
||||||
//
|
//
|
||||||
@ -38,27 +38,42 @@ var errEmptyDomain = errors.New("envelope domain must not be empty")
|
|||||||
// and must be supplied when verifying the signature.
|
// and must be supplied when verifying the signature.
|
||||||
//
|
//
|
||||||
// The 'payloadType' field indicates what kind of data is contained and may be empty.
|
// The 'payloadType' field indicates what kind of data is contained and may be empty.
|
||||||
func MakeEnvelope(privateKey PrivKey, domain string, payloadType []byte, contents []byte) (*SignedEnvelope, error) {
|
func MakeEnvelope(privateKey PrivKey, domain string, payloadType []byte, payload []byte) (*SignedEnvelope, error) {
|
||||||
if len(domain) == 0 {
|
if len(domain) == 0 {
|
||||||
return nil, errEmptyDomain
|
return nil, errEmptyDomain
|
||||||
}
|
}
|
||||||
toSign := makeSigBuffer(domain, payloadType, contents)
|
toSign := makeSigBuffer(domain, payloadType, payload)
|
||||||
sig, err := privateKey.Sign(toSign)
|
sig, err := privateKey.Sign(toSign)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &SignedEnvelope{
|
return &SignedEnvelope{
|
||||||
PublicKey: privateKey.GetPublic(),
|
publicKey: privateKey.GetPublic(),
|
||||||
PayloadType: payloadType,
|
payloadType: payloadType,
|
||||||
payload: contents,
|
payload: payload,
|
||||||
signature: sig,
|
signature: sig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalEnvelope converts a serialized protobuf representation of an envelope
|
// OpenEnvelope unmarshals a serialized SignedEnvelope, validating its signature
|
||||||
// into a SignedEnvelope struct.
|
// using the provided 'domain' string.
|
||||||
func UnmarshalEnvelope(serializedEnvelope []byte) (*SignedEnvelope, error) {
|
func OpenEnvelope(envelopeBytes []byte, domain string) (*SignedEnvelope, error) {
|
||||||
|
e, err := UnmarshalEnvelopeWithoutValidating(envelopeBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = e.validate(domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
e := pb.SignedEnvelope{}
|
e := pb.SignedEnvelope{}
|
||||||
if err := proto.Unmarshal(serializedEnvelope, &e); err != nil {
|
if err := proto.Unmarshal(serializedEnvelope, &e); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -68,57 +83,63 @@ func UnmarshalEnvelope(serializedEnvelope []byte) (*SignedEnvelope, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &SignedEnvelope{
|
return &SignedEnvelope{
|
||||||
PublicKey: key,
|
publicKey: key,
|
||||||
PayloadType: e.PayloadType,
|
payloadType: e.PayloadType,
|
||||||
payload: e.Payload,
|
payload: e.Payload,
|
||||||
signature: e.Signature,
|
signature: e.Signature,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate returns true if the envelope signature is valid for the given 'domain',
|
// PublicKey returns the public key that can be used to verify the signature and derive the peer id of the signer.
|
||||||
// or false if it is invalid. May return an error if signature validation fails.
|
func (e *SignedEnvelope) PublicKey() PubKey {
|
||||||
func (e *SignedEnvelope) Validate(domain string) (bool, error) {
|
return e.publicKey
|
||||||
toVerify := makeSigBuffer(domain, e.PayloadType, e.payload)
|
}
|
||||||
return e.PublicKey.Verify(toVerify, e.signature)
|
|
||||||
|
// PayloadType returns a binary identifier that indicates what kind of data is contained in the payload.
|
||||||
|
func (e *SignedEnvelope) PayloadType() []byte {
|
||||||
|
return e.payloadType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payload returns the binary payload of a SignedEnvelope.
|
||||||
|
func (e *SignedEnvelope) Payload() []byte {
|
||||||
|
return e.payload
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal returns a []byte containing a serialized protobuf representation of
|
|
||||||
// the SignedEnvelope.
|
|
||||||
func (e *SignedEnvelope) Marshal() ([]byte, error) {
|
func (e *SignedEnvelope) Marshal() ([]byte, error) {
|
||||||
key, err := PublicKeyToProto(e.PublicKey)
|
key, err := PublicKeyToProto(e.publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
msg := pb.SignedEnvelope{
|
msg := pb.SignedEnvelope{
|
||||||
PublicKey: key,
|
PublicKey: key,
|
||||||
PayloadType: e.PayloadType,
|
PayloadType: e.payloadType,
|
||||||
Payload: e.payload,
|
Payload: e.payload,
|
||||||
Signature: e.signature,
|
Signature: e.signature,
|
||||||
}
|
}
|
||||||
return proto.Marshal(&msg)
|
return proto.Marshal(&msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open validates the signature (within the given 'domain') and returns
|
|
||||||
// the payload of the envelope. Will fail with an error if the signature
|
|
||||||
// is invalid.
|
|
||||||
func (e *SignedEnvelope) Open(domain string) ([]byte, error) {
|
|
||||||
valid, err := e.Validate(domain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !valid {
|
|
||||||
return nil, errors.New("invalid signature or incorrect domain")
|
|
||||||
}
|
|
||||||
return e.payload, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *SignedEnvelope) Equals(other *SignedEnvelope) bool {
|
func (e *SignedEnvelope) Equals(other *SignedEnvelope) bool {
|
||||||
return e.PublicKey.Equals(other.PublicKey) &&
|
return e.publicKey.Equals(other.publicKey) &&
|
||||||
bytes.Compare(e.PayloadType, other.PayloadType) == 0 &&
|
bytes.Compare(e.payloadType, other.payloadType) == 0 &&
|
||||||
bytes.Compare(e.payload, other.payload) == 0 &&
|
bytes.Compare(e.payload, other.payload) == 0 &&
|
||||||
bytes.Compare(e.signature, other.signature) == 0
|
bytes.Compare(e.signature, other.signature) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 := makeSigBuffer(domain, e.payloadType, e.payload)
|
||||||
|
valid, err := e.publicKey.Verify(toVerify, e.signature)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
return errInvalidSignature
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// makeSigBuffer is a helper function that prepares a buffer to sign or verify.
|
// makeSigBuffer is a helper function that prepares a buffer to sign or verify.
|
||||||
func makeSigBuffer(domain string, typeHint []byte, content []byte) []byte {
|
func makeSigBuffer(domain string, typeHint []byte, content []byte) []byte {
|
||||||
b := bytes.Buffer{}
|
b := bytes.Buffer{}
|
||||||
|
@ -2,7 +2,8 @@ package crypto_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
. "github.com/libp2p/go-libp2p-core/crypto"
|
. "github.com/libp2p/go-libp2p-core/crypto"
|
||||||
pb "github.com/libp2p/go-libp2p-core/crypto/pb"
|
pb "github.com/libp2p/go-libp2p-core/crypto/pb"
|
||||||
"github.com/libp2p/go-libp2p-core/test"
|
"github.com/libp2p/go-libp2p-core/test"
|
||||||
@ -23,39 +24,27 @@ func TestEnvelopeHappyPath(t *testing.T) {
|
|||||||
t.Errorf("error constructing envelope: %v", err)
|
t.Errorf("error constructing envelope: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !envelope.PublicKey.Equals(pub) {
|
if !envelope.PublicKey().Equals(pub) {
|
||||||
t.Error("envelope has unexpected public key")
|
t.Error("envelope has unexpected public key")
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Compare(payloadType, envelope.PayloadType) != 0 {
|
if bytes.Compare(payloadType, envelope.PayloadType()) != 0 {
|
||||||
t.Error("PayloadType does not match payloadType used to construct envelope")
|
t.Error("PayloadType does not match payloadType used to construct envelope")
|
||||||
}
|
}
|
||||||
|
|
||||||
valid, err := envelope.Validate(domain)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error validating envelope: %v", err)
|
|
||||||
}
|
|
||||||
if !valid {
|
|
||||||
t.Error("envelope should be valid, but Valid returns false")
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := envelope.Open(domain)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error opening envelope: %v", err)
|
|
||||||
}
|
|
||||||
if bytes.Compare(c, payload) != 0 {
|
|
||||||
t.Error("payload of envelope do not match input")
|
|
||||||
}
|
|
||||||
|
|
||||||
serialized, err := envelope.Marshal()
|
serialized, err := envelope.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error serializing envelope: %v", err)
|
t.Errorf("error serializing envelope: %v", err)
|
||||||
}
|
}
|
||||||
deserialized, err := UnmarshalEnvelope(serialized)
|
deserialized, err := OpenEnvelope(serialized, domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error deserializing envelope: %v", err)
|
t.Errorf("error deserializing envelope: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bytes.Compare(deserialized.Payload(), payload) != 0 {
|
||||||
|
t.Error("payload of envelope does not match input")
|
||||||
|
}
|
||||||
|
|
||||||
if !envelope.Equals(deserialized) {
|
if !envelope.Equals(deserialized) {
|
||||||
t.Error("round-trip serde results in unequal envelope structures")
|
t.Error("round-trip serde results in unequal envelope structures")
|
||||||
}
|
}
|
||||||
@ -73,13 +62,11 @@ func TestEnvelopeValidateFailsForDifferentDomain(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error constructing envelope: %v", err)
|
t.Errorf("error constructing envelope: %v", err)
|
||||||
}
|
}
|
||||||
|
serialized, err := envelope.Marshal()
|
||||||
valid, err := envelope.Validate("other-domain")
|
// try to open our modified envelope
|
||||||
if err != nil {
|
_, err = OpenEnvelope(serialized, "wrong-domain")
|
||||||
t.Errorf("error validating envelope: %v", err)
|
if err == nil {
|
||||||
}
|
t.Error("should not be able to open envelope with incorrect domain")
|
||||||
if valid {
|
|
||||||
t.Error("envelope should be invalid, but Valid returns true")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,13 +82,13 @@ func TestEnvelopeValidateFailsIfTypeHintIsAltered(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error constructing envelope: %v", err)
|
t.Errorf("error constructing envelope: %v", err)
|
||||||
}
|
}
|
||||||
envelope.PayloadType = []byte("foo")
|
serialized := alterMessageAndMarshal(t, envelope, func(msg *pb.SignedEnvelope) {
|
||||||
valid, err := envelope.Validate("other-domain")
|
msg.PayloadType = []byte("foo")
|
||||||
if err != nil {
|
})
|
||||||
t.Errorf("error validating envelope: %v", err)
|
// try to open our modified envelope
|
||||||
}
|
_, err = OpenEnvelope(serialized, domain)
|
||||||
if valid {
|
if err == nil {
|
||||||
t.Error("envelope should be invalid, but Valid returns true")
|
t.Error("should not be able to open envelope with modified payloadType")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,33 +105,35 @@ func TestEnvelopeValidateFailsIfContentsAreAltered(t *testing.T) {
|
|||||||
t.Errorf("error constructing envelope: %v", err)
|
t.Errorf("error constructing envelope: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// since the payload field is private, we'll serialize and alter the
|
serialized := alterMessageAndMarshal(t, envelope, func(msg *pb.SignedEnvelope) {
|
||||||
// serialized protobuf
|
msg.Payload = []byte("totally legit, trust me")
|
||||||
|
})
|
||||||
|
// try to open our modified envelope
|
||||||
|
_, err = OpenEnvelope(serialized, domain)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("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 SignedEnvelope 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 *SignedEnvelope, alterMsg func(*pb.SignedEnvelope)) []byte {
|
||||||
serialized, err := envelope.Marshal()
|
serialized, err := envelope.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error serializing envelope: %v", err)
|
t.Errorf("error marshaling envelope: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := pb.SignedEnvelope{}
|
msg := pb.SignedEnvelope{}
|
||||||
err = proto.Unmarshal(serialized, &msg)
|
err = proto.Unmarshal(serialized, &msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error deserializing envelope: %v", err)
|
t.Errorf("error unmarshaling envelope: %v", err)
|
||||||
}
|
}
|
||||||
msg.Payload = []byte("totally legit, trust me")
|
alterMsg(&msg)
|
||||||
serialized, err = proto.Marshal(&msg)
|
serialized, err = msg.Marshal()
|
||||||
|
|
||||||
// unmarshal our altered envelope
|
|
||||||
deserialized, err := UnmarshalEnvelope(serialized)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error deserializing envelope: %v", err)
|
t.Errorf("error marshaling envelope: %v", err)
|
||||||
}
|
|
||||||
|
|
||||||
// verify that it's now invalid
|
|
||||||
valid, err := deserialized.Validate(domain)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error validating envelope: %v", err)
|
|
||||||
}
|
|
||||||
if valid {
|
|
||||||
t.Error("envelope should be invalid, but Valid returns true")
|
|
||||||
}
|
}
|
||||||
|
return serialized
|
||||||
}
|
}
|
||||||
|
@ -97,8 +97,8 @@ type AddrBook interface {
|
|||||||
AddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration)
|
AddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration)
|
||||||
|
|
||||||
// AddCertifiedAddrs adds addresses from a routing.RoutingState record
|
// AddCertifiedAddrs adds addresses from a routing.RoutingState record
|
||||||
// contained in the given SignedEnvelope.
|
// contained in a serialized SignedEnvelope.
|
||||||
AddCertifiedAddrs(envelope *ic.SignedEnvelope, ttl time.Duration) error
|
AddCertifiedAddrs(envelopeBytes []byte, ttl time.Duration) error
|
||||||
|
|
||||||
// SetAddr calls mgr.SetAddrs(p, addr, ttl)
|
// SetAddr calls mgr.SetAddrs(p, addr, ttl)
|
||||||
SetAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration)
|
SetAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration)
|
||||||
@ -130,9 +130,17 @@ type AddrBook interface {
|
|||||||
// PeersWithAddrs returns all of the peer IDs stored in the AddrBook
|
// PeersWithAddrs returns all of the peer IDs stored in the AddrBook
|
||||||
PeersWithAddrs() peer.IDSlice
|
PeersWithAddrs() peer.IDSlice
|
||||||
|
|
||||||
// SignedRoutingState returns a SignedEnvelope containing a RoutingState
|
// SignedRoutingState returns a signed RoutingState record for the
|
||||||
// record, if one exists for the given peer.
|
// given peer id, if one exists in the peerstore. The record is
|
||||||
SignedRoutingState(p peer.ID) *ic.SignedEnvelope
|
// returned as a byte slice containing a serialized SignedEnvelope.
|
||||||
|
// Returns nil if no routing state exists for the peer.
|
||||||
|
SignedRoutingState(p peer.ID) []byte
|
||||||
|
|
||||||
|
// SignedRoutingStates returns signed RoutingState records for each of
|
||||||
|
// the given peer ids, if one exists in the peerstore.
|
||||||
|
// Returns a map of peer ids to serialized SignedEnvelope messages. If
|
||||||
|
// no routing state exists for a peer, their map entry will be nil.
|
||||||
|
SignedRoutingStates(peers ...peer.ID) map[peer.ID][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyBook tracks the keys of Peers.
|
// KeyBook tracks the keys of Peers.
|
||||||
|
@ -75,16 +75,19 @@ func UnmarshalRoutingState(serialized []byte) (*RoutingState, error) {
|
|||||||
// RoutingStateFromEnvelope unwraps a peer RoutingState record from a SignedEnvelope.
|
// RoutingStateFromEnvelope unwraps a peer RoutingState record from a SignedEnvelope.
|
||||||
// This method will fail if the signature is invalid, or if the record
|
// This method will fail if the signature is invalid, or if the record
|
||||||
// belongs to a peer other than the one that signed the envelope.
|
// belongs to a peer other than the one that signed the envelope.
|
||||||
func RoutingStateFromEnvelope(envelope *crypto.SignedEnvelope) (*RoutingState, error) {
|
func RoutingStateFromEnvelope(envelopeBytes []byte) (*RoutingState, error) {
|
||||||
msgBytes, err := envelope.Open(StateEnvelopeDomain)
|
envelope, err := crypto.OpenEnvelope(envelopeBytes, StateEnvelopeDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
state, err := UnmarshalRoutingState(msgBytes)
|
if bytes.Compare(envelope.PayloadType(), StateEnvelopePayloadType) != 0 {
|
||||||
|
return nil, errors.New("unexpected envelope payload type")
|
||||||
|
}
|
||||||
|
state, err := UnmarshalRoutingState(envelope.Payload())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !state.PeerID.MatchesPublicKey(envelope.PublicKey) {
|
if !state.PeerID.MatchesPublicKey(envelope.PublicKey()) {
|
||||||
return nil, errors.New("peer id in routing state record does not match signing key")
|
return nil, errors.New("peer id in routing state record does not match signing key")
|
||||||
}
|
}
|
||||||
return state, nil
|
return state, nil
|
||||||
|
Loading…
Reference in New Issue
Block a user