package crypto import ( "bytes" "errors" "github.com/gogo/protobuf/proto" "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. type SignedEnvelope struct { // The public key that can be used to verify the signature and derive the peer id of the signer. PublicKey PubKey // A binary identifier that indicates what kind of data is contained in the payload. // TODO(yusef): enforce multicodec prefix PayloadType []byte // The envelope payload. Payload []byte // The signature of the domain string, type hint, and payload. signature []byte } 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. // // 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) { if len(domain) == 0 { return nil, errEmptyDomain } toSign, err := makeSigBuffer(domain, payloadType, payload) if err != nil { return nil, err } sig, err := privateKey.Sign(toSign) if err != nil { return nil, err } return &SignedEnvelope{ PublicKey: privateKey.GetPublic(), PayloadType: payloadType, Payload: payload, signature: sig, }, 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) if err != nil { return nil, err } return &SignedEnvelope{ PublicKey: key, PayloadType: e.PayloadType, Payload: e.Payload, signature: e.Signature, }, nil } // 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 { return nil, err } msg := pb.SignedEnvelope{ PublicKey: key, PayloadType: e.PayloadType, Payload: e.Payload, Signature: e.signature, } 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. 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) if err != nil { return err } if !valid { 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} // 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]) } b := pool.NewBuffer(nil) b.Grow(bufSize) 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 } } return b.Bytes(), nil }