go-libp2p-core/crypto/envelope.go

193 lines
5.4 KiB
Go
Raw Normal View History

2019-11-07 01:24:34 +08:00
package crypto
import (
"bytes"
"encoding/binary"
"errors"
2019-11-22 00:00:38 +08:00
"github.com/gogo/protobuf/proto"
"github.com/libp2p/go-buffer-pool"
2019-11-07 01:24:34 +08:00
pb "github.com/libp2p/go-libp2p-core/crypto/pb"
)
2019-11-07 23:56:28 +08:00
// 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.
type SignedEnvelope struct {
2019-11-07 23:56:28 +08:00
// The public key that can be used to verify the signature and derive the peer id of the signer.
publicKey PubKey
2019-11-07 23:56:28 +08:00
// A binary identifier that indicates what kind of data is contained in the payload.
// TODO(yusef): enforce multicodec prefix
payloadType []byte
2019-11-07 23:56:28 +08:00
// The envelope payload.
payload []byte
2019-11-07 23:56:28 +08:00
// The signature of the domain string, type hint, and payload.
signature []byte
}
2019-11-07 01:24:34 +08:00
2019-11-07 23:56:28 +08:00
var errEmptyDomain = errors.New("envelope domain must not be empty")
var errInvalidSignature = errors.New("invalid signature or incorrect domain")
2019-11-07 23:56:28 +08:00
// MakeEnvelope constructs a new SignedEnvelope using the given privateKey.
//
// The required 'domain' string contextualizes the envelope for a particular purpose,
// and must be supplied when verifying the signature.
//
// 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) {
2019-11-07 23:56:28 +08:00
if len(domain) == 0 {
return nil, errEmptyDomain
}
toSign, err := makeSigBuffer(domain, payloadType, payload)
if err != nil {
return nil, err
}
2019-11-07 01:24:34 +08:00
sig, err := privateKey.Sign(toSign)
if err != nil {
return nil, err
}
return &SignedEnvelope{
publicKey: privateKey.GetPublic(),
payloadType: payloadType,
payload: payload,
signature: sig,
2019-11-07 01:24:34 +08:00
}, 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)
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) {
var e pb.SignedEnvelope
if err := proto.Unmarshal(serializedEnvelope, &e); err != nil {
return nil, err
}
key, err := PublicKeyFromProto(e.PublicKey)
2019-11-07 01:24:34 +08:00
if err != nil {
return nil, err
2019-11-07 01:24:34 +08:00
}
return &SignedEnvelope{
publicKey: key,
payloadType: e.PayloadType,
payload: e.Payload,
signature: e.Signature,
}, nil
2019-11-07 01:24:34 +08:00
}
// PublicKey returns the public key that can be used to verify the signature and derive the peer id of the signer.
func (e *SignedEnvelope) PublicKey() PubKey {
return e.publicKey
}
// 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 slice containing a serailized protobuf representation of a SignedEnvelope.
func (e *SignedEnvelope) Marshal() ([]byte, error) {
key, err := PublicKeyToProto(e.publicKey)
if err != nil {
2019-11-07 01:24:34 +08:00
return nil, err
}
msg := pb.SignedEnvelope{
PublicKey: key,
PayloadType: e.payloadType,
Payload: e.payload,
Signature: e.signature,
}
return proto.Marshal(&msg)
2019-11-07 01:24:34 +08:00
}
// 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 &&
bytes.Compare(e.payload, other.payload) == 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, err := makeSigBuffer(domain, e.payloadType, e.payload)
if err != nil {
return err
}
valid, err := e.publicKey.Verify(toVerify, e.signature)
2019-11-07 01:24:34 +08:00
if err != nil {
return err
2019-11-07 01:24:34 +08:00
}
if !valid {
return errInvalidSignature
2019-11-07 01:24:34 +08:00
}
return nil
2019-11-08 01:01:00 +08:00
}
2019-11-07 23:56:28 +08:00
// makeSigBuffer is a helper function that prepares a buffer to sign or verify.
func makeSigBuffer(domain string, payloadType []byte, payload []byte) ([]byte, error) {
2019-11-07 01:24:34 +08:00
domainBytes := []byte(domain)
fields := [][]byte{domainBytes, payloadType, payload}
const lengthPrefixSize = 8
size := 0
for _, f := range fields {
size += len(f) + lengthPrefixSize
}
b := pool.NewBuffer(nil)
b.Grow(size)
for _, f := range fields {
err := writeField(b, f)
if err != nil {
return nil, err
}
}
return b.Bytes(), nil
}
func writeField(b *pool.Buffer, f []byte) error {
_, err := b.Write(encodedSize(f))
if err != nil {
return err
}
_, err = b.Write(f)
return err
2019-11-07 01:24:34 +08:00
}
func encodedSize(content []byte) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(len(content)))
return b
2019-11-09 00:42:36 +08:00
}