-
Notifications
You must be signed in to change notification settings - Fork 24
/
marshal.go
239 lines (220 loc) · 6.32 KB
/
marshal.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
package macaroon
import (
"encoding/base64"
"encoding/json"
"fmt"
)
// Version specifies the version of a macaroon.
// In version 1, the macaroon id and all caveats
// must be UTF-8-compatible strings, and the
// size of any part of the macaroon may not exceed
// approximately 64K. In version 2,
// all field may be arbitrary binary blobs.
type Version uint16
const (
// V1 specifies version 1 macaroons.
V1 Version = 1
// V2 specifies version 2 macaroons.
V2 Version = 2
// LatestVersion holds the latest supported version.
LatestVersion = V2
)
// String returns a string representation of the version;
// for example V1 formats as "v1".
func (v Version) String() string {
return fmt.Sprintf("v%d", v)
}
// Version returns the version of the macaroon.
func (m *Macaroon) Version() Version {
return m.version
}
// MarshalJSON implements json.Marshaler by marshaling the
// macaroon in JSON format. The serialisation format is determined
// by the macaroon's version.
func (m *Macaroon) MarshalJSON() ([]byte, error) {
switch m.version {
case V1:
return m.marshalJSONV1()
case V2:
return m.marshalJSONV2()
default:
return nil, fmt.Errorf("unknown version %v", m.version)
}
}
// UnmarshalJSON implements json.Unmarshaller by unmarshaling
// the given macaroon in JSON format. It accepts both V1 and V2
// forms encoded forms, and also a base64-encoded JSON string
// containing the binary-marshaled macaroon.
//
// After unmarshaling, the macaroon's version will reflect
// the version that it was unmarshaled as.
func (m *Macaroon) UnmarshalJSON(data []byte) error {
if data[0] == '"' {
// It's a string, so it must be a base64-encoded binary form.
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
data, err := Base64Decode([]byte(s))
if err != nil {
return err
}
if err := m.UnmarshalBinary(data); err != nil {
return err
}
return nil
}
// Not a string; try to unmarshal into both kinds of macaroon object.
// This assumes that neither format has any fields in common.
// For subsequent versions we may need to change this approach.
type MacaroonJSONV1 macaroonJSONV1
type MacaroonJSONV2 macaroonJSONV2
var both struct {
*MacaroonJSONV1
*MacaroonJSONV2
}
if err := json.Unmarshal(data, &both); err != nil {
return err
}
switch {
case both.MacaroonJSONV1 != nil && both.MacaroonJSONV2 != nil:
return fmt.Errorf("cannot determine macaroon encoding version")
case both.MacaroonJSONV1 != nil:
if err := m.initJSONV1((*macaroonJSONV1)(both.MacaroonJSONV1)); err != nil {
return err
}
m.version = V1
case both.MacaroonJSONV2 != nil:
if err := m.initJSONV2((*macaroonJSONV2)(both.MacaroonJSONV2)); err != nil {
return err
}
m.version = V2
default:
return fmt.Errorf("invalid JSON macaroon encoding")
}
return nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
// It accepts both V1 and V2 binary encodings.
func (m *Macaroon) UnmarshalBinary(data []byte) error {
// Copy the data to avoid retaining references to it
// in the internal data structures.
data = append([]byte(nil), data...)
_, err := m.parseBinary(data)
return err
}
// parseBinary parses the macaroon in binary format
// from the given data and returns where the parsed data ends.
//
// It retains references to data.
func (m *Macaroon) parseBinary(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, fmt.Errorf("empty macaroon data")
}
v := data[0]
if v == 2 {
// Version 2 binary format.
data, err := m.parseBinaryV2(data)
if err != nil {
return nil, fmt.Errorf("unmarshal v2: %v", err)
}
m.version = V2
return data, nil
}
if isASCIIHex(v) {
// It's a hex digit - version 1 binary format
data, err := m.parseBinaryV1(data)
if err != nil {
return nil, fmt.Errorf("unmarshal v1: %v", err)
}
m.version = V1
return data, nil
}
return nil, fmt.Errorf("cannot determine data format of binary-encoded macaroon")
}
// MarshalBinary implements encoding.BinaryMarshaler by
// formatting the macaroon according to the version specified
// by MarshalAs.
func (m *Macaroon) MarshalBinary() ([]byte, error) {
return m.appendBinary(nil)
}
// appendBinary appends the binary-formatted macaroon to
// the given data, formatting it according to the macaroon's
// version.
func (m *Macaroon) appendBinary(data []byte) ([]byte, error) {
switch m.version {
case V1:
return m.appendBinaryV1(data)
case V2:
return m.appendBinaryV2(data), nil
default:
return nil, fmt.Errorf("bad macaroon version %v", m.version)
}
}
// Slice defines a collection of macaroons. By convention, the
// first macaroon in the slice is a primary macaroon and the rest
// are discharges for its third party caveats.
type Slice []*Macaroon
// MarshalBinary implements encoding.BinaryMarshaler.
func (s Slice) MarshalBinary() ([]byte, error) {
var data []byte
var err error
for _, m := range s {
data, err = m.appendBinary(data)
if err != nil {
return nil, fmt.Errorf("failed to marshal macaroon %q: %v", m.Id(), err)
}
}
return data, nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
// It accepts all known binary encodings for the data - all the
// embedded macaroons need not be encoded in the same format.
func (s *Slice) UnmarshalBinary(data []byte) error {
// Prevent the internal data structures from holding onto the
// slice by copying it first.
data = append([]byte(nil), data...)
*s = (*s)[:0]
for len(data) > 0 {
var m Macaroon
rest, err := m.parseBinary(data)
if err != nil {
return fmt.Errorf("cannot unmarshal macaroon: %v", err)
}
*s = append(*s, &m)
data = rest
}
return nil
}
const (
padded = 1 << iota
stdEncoding
)
var codecs = [4]*base64.Encoding{
0: base64.RawURLEncoding,
padded: base64.URLEncoding,
stdEncoding: base64.RawStdEncoding,
stdEncoding | padded: base64.StdEncoding,
}
// Base64Decode base64-decodes the given data.
// It accepts both standard and URL encodings, both
// padded and unpadded.
func Base64Decode(data []byte) ([]byte, error) {
encoding := 0
if len(data) > 0 && data[len(data)-1] == '=' {
encoding |= padded
}
for _, b := range data {
if b == '/' || b == '+' {
encoding |= stdEncoding
break
}
}
codec := codecs[encoding]
buf := make([]byte, codec.DecodedLen(len(data)))
n, err := codec.Decode(buf, data)
if err == nil {
return buf[0:n], nil
}
return nil, err
}