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