1package record 2 3import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "sync" 8 9 "github.com/libp2p/go-libp2p-core/crypto" 10 pb "github.com/libp2p/go-libp2p-core/record/pb" 11 12 pool "github.com/libp2p/go-buffer-pool" 13 14 "github.com/gogo/protobuf/proto" 15 "github.com/multiformats/go-varint" 16) 17 18// Envelope contains an arbitrary []byte payload, signed by a libp2p peer. 19// 20// Envelopes are signed in the context of a particular "domain", which is a 21// string specified when creating and verifying the envelope. You must know the 22// domain string used to produce the envelope in order to verify the signature 23// and access the payload. 24type Envelope struct { 25 // The public key that can be used to verify the signature and derive the peer id of the signer. 26 PublicKey crypto.PubKey 27 28 // A binary identifier that indicates what kind of data is contained in the payload. 29 // TODO(yusef): enforce multicodec prefix 30 PayloadType []byte 31 32 // The envelope payload. 33 RawPayload []byte 34 35 // The signature of the domain string :: type hint :: payload. 36 signature []byte 37 38 // the unmarshalled payload as a Record, cached on first access via the Record accessor method 39 cached Record 40 unmarshalError error 41 unmarshalOnce sync.Once 42} 43 44var ErrEmptyDomain = errors.New("envelope domain must not be empty") 45var ErrEmptyPayloadType = errors.New("payloadType must not be empty") 46var ErrInvalidSignature = errors.New("invalid signature or incorrect domain") 47 48// Seal marshals the given Record, places the marshaled bytes inside an Envelope, 49// and signs with the given private key. 50func Seal(rec Record, privateKey crypto.PrivKey) (*Envelope, error) { 51 payload, err := rec.MarshalRecord() 52 if err != nil { 53 return nil, fmt.Errorf("error marshaling record: %v", err) 54 } 55 56 domain := rec.Domain() 57 payloadType := rec.Codec() 58 if domain == "" { 59 return nil, ErrEmptyDomain 60 } 61 62 if len(payloadType) == 0 { 63 return nil, ErrEmptyPayloadType 64 } 65 66 unsigned, err := makeUnsigned(domain, payloadType, payload) 67 if err != nil { 68 return nil, err 69 } 70 defer pool.Put(unsigned) 71 72 sig, err := privateKey.Sign(unsigned) 73 if err != nil { 74 return nil, err 75 } 76 77 return &Envelope{ 78 PublicKey: privateKey.GetPublic(), 79 PayloadType: payloadType, 80 RawPayload: payload, 81 signature: sig, 82 }, nil 83} 84 85// ConsumeEnvelope unmarshals a serialized Envelope and validates its 86// signature using the provided 'domain' string. If validation fails, an error 87// is returned, along with the unmarshalled envelope so it can be inspected. 88// 89// On success, ConsumeEnvelope returns the Envelope itself, as well as the inner payload, 90// unmarshalled into a concrete Record type. The actual type of the returned Record depends 91// on what has been registered for the Envelope's PayloadType (see RegisterType for details). 92// 93// You can type assert on the returned Record to convert it to an instance of the concrete 94// Record type: 95// 96// envelope, rec, err := ConsumeEnvelope(envelopeBytes, peer.PeerRecordEnvelopeDomain) 97// if err != nil { 98// handleError(envelope, err) // envelope may be non-nil, even if errors occur! 99// return 100// } 101// peerRec, ok := rec.(*peer.PeerRecord) 102// if ok { 103// doSomethingWithPeerRecord(peerRec) 104// } 105// 106// Important: you MUST check the error value before using the returned Envelope. In some error 107// cases, including when the envelope signature is invalid, both the Envelope and an error will 108// be returned. This allows you to inspect the unmarshalled but invalid Envelope. As a result, 109// you must not assume that any non-nil Envelope returned from this function is valid. 110// 111// If the Envelope signature is valid, but no Record type is registered for the Envelope's 112// PayloadType, ErrPayloadTypeNotRegistered will be returned, along with the Envelope and 113// a nil Record. 114func ConsumeEnvelope(data []byte, domain string) (envelope *Envelope, rec Record, err error) { 115 e, err := UnmarshalEnvelope(data) 116 if err != nil { 117 return nil, nil, fmt.Errorf("failed when unmarshalling the envelope: %w", err) 118 } 119 120 err = e.validate(domain) 121 if err != nil { 122 return e, nil, fmt.Errorf("failed to validate envelope: %w", err) 123 } 124 125 rec, err = e.Record() 126 if err != nil { 127 return e, nil, fmt.Errorf("failed to unmarshal envelope payload: %w", err) 128 } 129 return e, rec, nil 130} 131 132// ConsumeTypedEnvelope unmarshals a serialized Envelope and validates its 133// signature. If validation fails, an error is returned, along with the unmarshalled 134// envelope so it can be inspected. 135// 136// Unlike ConsumeEnvelope, ConsumeTypedEnvelope does not try to automatically determine 137// the type of Record to unmarshal the Envelope's payload into. Instead, the caller provides 138// a destination Record instance, which will unmarshal the Envelope payload. It is the caller's 139// responsibility to determine whether the given Record type is able to unmarshal the payload 140// correctly. 141// 142// rec := &MyRecordType{} 143// envelope, err := ConsumeTypedEnvelope(envelopeBytes, rec) 144// if err != nil { 145// handleError(envelope, err) 146// } 147// doSomethingWithRecord(rec) 148// 149// Important: you MUST check the error value before using the returned Envelope. In some error 150// cases, including when the envelope signature is invalid, both the Envelope and an error will 151// be returned. This allows you to inspect the unmarshalled but invalid Envelope. As a result, 152// you must not assume that any non-nil Envelope returned from this function is valid. 153func ConsumeTypedEnvelope(data []byte, destRecord Record) (envelope *Envelope, err error) { 154 e, err := UnmarshalEnvelope(data) 155 if err != nil { 156 return nil, fmt.Errorf("failed when unmarshalling the envelope: %w", err) 157 } 158 159 err = e.validate(destRecord.Domain()) 160 if err != nil { 161 return e, fmt.Errorf("failed to validate envelope: %w", err) 162 } 163 164 err = destRecord.UnmarshalRecord(e.RawPayload) 165 if err != nil { 166 return e, fmt.Errorf("failed to unmarshal envelope payload: %w", err) 167 } 168 e.cached = destRecord 169 return e, nil 170} 171 172// UnmarshalEnvelope unmarshals a serialized Envelope protobuf message, 173// without validating its contents. Most users should use ConsumeEnvelope. 174func UnmarshalEnvelope(data []byte) (*Envelope, error) { 175 var e pb.Envelope 176 if err := proto.Unmarshal(data, &e); err != nil { 177 return nil, err 178 } 179 180 key, err := crypto.PublicKeyFromProto(e.PublicKey) 181 if err != nil { 182 return nil, err 183 } 184 185 return &Envelope{ 186 PublicKey: key, 187 PayloadType: e.PayloadType, 188 RawPayload: e.Payload, 189 signature: e.Signature, 190 }, nil 191} 192 193// Marshal returns a byte slice containing a serialized protobuf representation 194// of a Envelope. 195func (e *Envelope) Marshal() ([]byte, error) { 196 key, err := crypto.PublicKeyToProto(e.PublicKey) 197 if err != nil { 198 return nil, err 199 } 200 201 msg := pb.Envelope{ 202 PublicKey: key, 203 PayloadType: e.PayloadType, 204 Payload: e.RawPayload, 205 Signature: e.signature, 206 } 207 return proto.Marshal(&msg) 208} 209 210// Equal returns true if the other Envelope has the same public key, 211// payload, payload type, and signature. This implies that they were also 212// created with the same domain string. 213func (e *Envelope) Equal(other *Envelope) bool { 214 if other == nil { 215 return e == nil 216 } 217 return e.PublicKey.Equals(other.PublicKey) && 218 bytes.Compare(e.PayloadType, other.PayloadType) == 0 && 219 bytes.Compare(e.signature, other.signature) == 0 && 220 bytes.Compare(e.RawPayload, other.RawPayload) == 0 221} 222 223// Record returns the Envelope's payload unmarshalled as a Record. 224// The concrete type of the returned Record depends on which Record 225// type was registered for the Envelope's PayloadType - see record.RegisterType. 226// 227// Once unmarshalled, the Record is cached for future access. 228func (e *Envelope) Record() (Record, error) { 229 e.unmarshalOnce.Do(func() { 230 if e.cached != nil { 231 return 232 } 233 e.cached, e.unmarshalError = unmarshalRecordPayload(e.PayloadType, e.RawPayload) 234 }) 235 return e.cached, e.unmarshalError 236} 237 238// TypedRecord unmarshals the Envelope's payload to the given Record instance. 239// It is the caller's responsibility to ensure that the Record type is capable 240// of unmarshalling the Envelope payload. Callers can inspect the Envelope's 241// PayloadType field to determine the correct type of Record to use. 242// 243// This method will always unmarshal the Envelope payload even if a cached record 244// exists. 245func (e *Envelope) TypedRecord(dest Record) error { 246 return dest.UnmarshalRecord(e.RawPayload) 247} 248 249// validate returns nil if the envelope signature is valid for the given 'domain', 250// or an error if signature validation fails. 251func (e *Envelope) validate(domain string) error { 252 unsigned, err := makeUnsigned(domain, e.PayloadType, e.RawPayload) 253 if err != nil { 254 return err 255 } 256 defer pool.Put(unsigned) 257 258 valid, err := e.PublicKey.Verify(unsigned, e.signature) 259 if err != nil { 260 return fmt.Errorf("failed while verifying signature: %w", err) 261 } 262 if !valid { 263 return ErrInvalidSignature 264 } 265 return nil 266} 267 268// makeUnsigned is a helper function that prepares a buffer to sign or verify. 269// It returns a byte slice from a pool. The caller MUST return this slice to the 270// pool. 271func makeUnsigned(domain string, payloadType []byte, payload []byte) ([]byte, error) { 272 var ( 273 fields = [][]byte{[]byte(domain), payloadType, payload} 274 275 // fields are prefixed with their length as an unsigned varint. we 276 // compute the lengths before allocating the sig buffer so we know how 277 // much space to add for the lengths 278 flen = make([][]byte, len(fields)) 279 size = 0 280 ) 281 282 for i, f := range fields { 283 l := len(f) 284 flen[i] = varint.ToUvarint(uint64(l)) 285 size += l + len(flen[i]) 286 } 287 288 b := pool.Get(size) 289 290 var s int 291 for i, f := range fields { 292 s += copy(b[s:], flen[i]) 293 s += copy(b[s:], f) 294 } 295 296 return b[:s], nil 297} 298