1package macaroon
2
3import (
4	"encoding/base64"
5	"encoding/json"
6	"fmt"
7	"unicode/utf8"
8)
9
10// macaroonJSONV2 defines the V2 JSON format for macaroons.
11type macaroonJSONV2 struct {
12	Caveats      []caveatJSONV2 `json:"c,omitempty"`
13	Location     string         `json:"l,omitempty"`
14	Identifier   string         `json:"i,omitempty"`
15	Identifier64 string         `json:"i64,omitempty"`
16	Signature    string         `json:"s,omitempty"`
17	Signature64  string         `json:"s64,omitempty"`
18}
19
20// caveatJSONV2 defines the V2 JSON format for caveats within a macaroon.
21type caveatJSONV2 struct {
22	CID      string `json:"i,omitempty"`
23	CID64    string `json:"i64,omitempty"`
24	VID      string `json:"v,omitempty"`
25	VID64    string `json:"v64,omitempty"`
26	Location string `json:"l,omitempty"`
27}
28
29func (m *Macaroon) marshalJSONV2() ([]byte, error) {
30	mjson := macaroonJSONV2{
31		Location: m.location,
32		Caveats:  make([]caveatJSONV2, len(m.caveats)),
33	}
34	putJSONBinaryField(m.id, &mjson.Identifier, &mjson.Identifier64)
35	putJSONBinaryField(m.sig[:], &mjson.Signature, &mjson.Signature64)
36	for i, cav := range m.caveats {
37		cavjson := caveatJSONV2{
38			Location: cav.Location,
39		}
40		putJSONBinaryField(cav.Id, &cavjson.CID, &cavjson.CID64)
41		putJSONBinaryField(cav.VerificationId, &cavjson.VID, &cavjson.VID64)
42		mjson.Caveats[i] = cavjson
43	}
44	data, err := json.Marshal(mjson)
45	if err != nil {
46		return nil, fmt.Errorf("cannot marshal json data: %v", err)
47	}
48	return data, nil
49}
50
51// initJSONV2 initializes m from the JSON-unmarshaled data
52// held in mjson.
53func (m *Macaroon) initJSONV2(mjson *macaroonJSONV2) error {
54	id, err := jsonBinaryField(mjson.Identifier, mjson.Identifier64)
55	if err != nil {
56		return fmt.Errorf("invalid identifier: %v", err)
57	}
58	m.init(id, mjson.Location, V2)
59	sig, err := jsonBinaryField(mjson.Signature, mjson.Signature64)
60	if err != nil {
61		return fmt.Errorf("invalid signature: %v", err)
62	}
63	if len(sig) != hashLen {
64		return fmt.Errorf("signature has unexpected length %d", len(sig))
65	}
66	copy(m.sig[:], sig)
67	m.caveats = make([]Caveat, 0, len(mjson.Caveats))
68	for _, cav := range mjson.Caveats {
69		cid, err := jsonBinaryField(cav.CID, cav.CID64)
70		if err != nil {
71			return fmt.Errorf("invalid cid in caveat: %v", err)
72		}
73		vid, err := jsonBinaryField(cav.VID, cav.VID64)
74		if err != nil {
75			return fmt.Errorf("invalid vid in caveat: %v", err)
76		}
77		m.appendCaveat(cid, vid, cav.Location)
78	}
79	return nil
80}
81
82// putJSONBinaryField puts the value of x into one
83// of the appropriate fields depending on its value.
84func putJSONBinaryField(x []byte, s, sb64 *string) {
85	if !utf8.Valid(x) {
86		*sb64 = base64.RawURLEncoding.EncodeToString(x)
87		return
88	}
89	// We could use either string or base64 encoding;
90	// choose the most compact of the two possibilities.
91	b64len := base64.RawURLEncoding.EncodedLen(len(x))
92	sx := string(x)
93	if jsonEnc, _ := json.Marshal(sx); len(jsonEnc)-2 <= b64len+2 {
94		// The JSON encoding is smaller than the base 64 encoding.
95		// NB marshaling a string can never return an error;
96		// it always includes the two quote characters;
97		// but using base64 also uses two extra characters for the
98		// "64" suffix on the field name. If all is equal, prefer string
99		// encoding because it's more readable.
100		*s = sx
101		return
102	}
103	*sb64 = base64.RawURLEncoding.EncodeToString(x)
104}
105
106// jsonBinaryField returns the value of a JSON field that may
107// be string, hex or base64-encoded.
108func jsonBinaryField(s, sb64 string) ([]byte, error) {
109	switch {
110	case s != "":
111		if sb64 != "" {
112			return nil, fmt.Errorf("ambiguous field encoding")
113		}
114		return []byte(s), nil
115	case sb64 != "":
116		return Base64Decode([]byte(sb64))
117	}
118	return []byte{}, nil
119}
120
121// The v2 binary format of a macaroon is as follows.
122// All entries other than the version are packets as
123// parsed by parsePacketV2.
124//
125// version [1 byte]
126// location?
127// identifier
128// eos
129// (
130//	location?
131//	identifier
132//	verificationId?
133//	eos
134// )*
135// eos
136// signature
137//
138// See also https://github.com/rescrv/libmacaroons/blob/master/doc/format.txt
139
140// parseBinaryV2 parses the given data in V2 format into the macaroon. The macaroon's
141// internal data structures will retain references to the data. It
142// returns the data after the end of the macaroon.
143func (m *Macaroon) parseBinaryV2(data []byte) ([]byte, error) {
144	// The version has already been checked, so
145	// skip it.
146	data = data[1:]
147
148	data, section, err := parseSectionV2(data)
149	if err != nil {
150		return nil, err
151	}
152	var loc string
153	if len(section) > 0 && section[0].fieldType == fieldLocation {
154		loc = string(section[0].data)
155		section = section[1:]
156	}
157	if len(section) != 1 || section[0].fieldType != fieldIdentifier {
158		return nil, fmt.Errorf("invalid macaroon header")
159	}
160	id := section[0].data
161	m.init(id, loc, V2)
162	for {
163		rest, section, err := parseSectionV2(data)
164		if err != nil {
165			return nil, err
166		}
167		data = rest
168		if len(section) == 0 {
169			break
170		}
171		var cav Caveat
172		if len(section) > 0 && section[0].fieldType == fieldLocation {
173			cav.Location = string(section[0].data)
174			section = section[1:]
175		}
176		if len(section) == 0 || section[0].fieldType != fieldIdentifier {
177			return nil, fmt.Errorf("no identifier in caveat")
178		}
179		cav.Id = section[0].data
180		section = section[1:]
181		if len(section) == 0 {
182			// First party caveat.
183			if cav.Location != "" {
184				return nil, fmt.Errorf("location not allowed in first party caveat")
185			}
186			m.caveats = append(m.caveats, cav)
187			continue
188		}
189		if len(section) != 1 {
190			return nil, fmt.Errorf("extra fields found in caveat")
191		}
192		if section[0].fieldType != fieldVerificationId {
193			return nil, fmt.Errorf("invalid field found in caveat")
194		}
195		cav.VerificationId = section[0].data
196		m.caveats = append(m.caveats, cav)
197	}
198	data, sig, err := parsePacketV2(data)
199	if err != nil {
200		return nil, err
201	}
202	if sig.fieldType != fieldSignature {
203		return nil, fmt.Errorf("unexpected field found instead of signature")
204	}
205	if len(sig.data) != hashLen {
206		return nil, fmt.Errorf("signature has unexpected length")
207	}
208	copy(m.sig[:], sig.data)
209	return data, nil
210}
211
212// appendBinaryV2 appends the binary-encoded macaroon
213// in v2 format to data.
214func (m *Macaroon) appendBinaryV2(data []byte) []byte {
215	// Version byte.
216	data = append(data, 2)
217	if len(m.location) > 0 {
218		data = appendPacketV2(data, packetV2{
219			fieldType: fieldLocation,
220			data:      []byte(m.location),
221		})
222	}
223	data = appendPacketV2(data, packetV2{
224		fieldType: fieldIdentifier,
225		data:      m.id,
226	})
227	data = appendEOSV2(data)
228	for _, cav := range m.caveats {
229		if len(cav.Location) > 0 {
230			data = appendPacketV2(data, packetV2{
231				fieldType: fieldLocation,
232				data:      []byte(cav.Location),
233			})
234		}
235		data = appendPacketV2(data, packetV2{
236			fieldType: fieldIdentifier,
237			data:      cav.Id,
238		})
239		if len(cav.VerificationId) > 0 {
240			data = appendPacketV2(data, packetV2{
241				fieldType: fieldVerificationId,
242				data:      []byte(cav.VerificationId),
243			})
244		}
245		data = appendEOSV2(data)
246	}
247	data = appendEOSV2(data)
248	data = appendPacketV2(data, packetV2{
249		fieldType: fieldSignature,
250		data:      m.sig[:],
251	})
252	return data
253}
254