-
Notifications
You must be signed in to change notification settings - Fork 24
/
marshal-v1.go
190 lines (180 loc) · 5.4 KB
/
marshal-v1.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
package macaroon
import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"unicode/utf8"
)
// macaroonJSONV1 defines the V1 JSON format for macaroons.
type macaroonJSONV1 struct {
Caveats []caveatJSONV1 `json:"caveats"`
Location string `json:"location"`
Identifier string `json:"identifier"`
Signature string `json:"signature"` // hex-encoded
}
// caveatJSONV1 defines the V1 JSON format for caveats within a macaroon.
type caveatJSONV1 struct {
CID string `json:"cid"`
VID string `json:"vid,omitempty"`
Location string `json:"cl,omitempty"`
}
// marshalJSONV1 marshals the macaroon to the V1 JSON format.
func (m *Macaroon) marshalJSONV1() ([]byte, error) {
if !utf8.Valid(m.id) {
return nil, fmt.Errorf("macaroon id is not valid UTF-8")
}
mjson := macaroonJSONV1{
Location: m.location,
Identifier: string(m.id),
Signature: hex.EncodeToString(m.sig[:]),
Caveats: make([]caveatJSONV1, len(m.caveats)),
}
for i, cav := range m.caveats {
if !utf8.Valid(cav.Id) {
return nil, fmt.Errorf("caveat id is not valid UTF-8")
}
mjson.Caveats[i] = caveatJSONV1{
Location: cav.Location,
CID: string(cav.Id),
VID: base64.RawURLEncoding.EncodeToString(cav.VerificationId),
}
}
data, err := json.Marshal(mjson)
if err != nil {
return nil, fmt.Errorf("cannot marshal json data: %v", err)
}
return data, nil
}
// initJSONV1 initializes m from the JSON-unmarshaled data
// held in mjson.
func (m *Macaroon) initJSONV1(mjson *macaroonJSONV1) error {
m.init([]byte(mjson.Identifier), mjson.Location, V1)
sig, err := hex.DecodeString(mjson.Signature)
if err != nil {
return fmt.Errorf("cannot decode macaroon signature %q: %v", m.sig, err)
}
if len(sig) != hashLen {
return fmt.Errorf("signature has unexpected length %d", len(sig))
}
copy(m.sig[:], sig)
m.caveats = m.caveats[:0]
for _, cav := range mjson.Caveats {
vid, err := Base64Decode([]byte(cav.VID))
if err != nil {
return fmt.Errorf("cannot decode verification id %q: %v", cav.VID, err)
}
m.appendCaveat([]byte(cav.CID), vid, cav.Location)
}
return nil
}
// The original (v1) binary format of a macaroon is as follows.
// Each identifier represents a v1 packet.
//
// location
// identifier
// (
// caveatId?
// verificationId?
// caveatLocation?
// )*
// signature
// parseBinaryV1 parses the given data in V1 format into the macaroon. The macaroon's
// internal data structures will retain references to the data. It
// returns the data after the end of the macaroon.
func (m *Macaroon) parseBinaryV1(data []byte) ([]byte, error) {
var err error
loc, err := expectPacketV1(data, fieldNameLocation)
if err != nil {
return nil, err
}
data = data[loc.totalLen:]
id, err := expectPacketV1(data, fieldNameIdentifier)
if err != nil {
return nil, err
}
data = data[id.totalLen:]
m.init(id.data, string(loc.data), V1)
var cav Caveat
for {
p, err := parsePacketV1(data)
if err != nil {
return nil, err
}
data = data[p.totalLen:]
switch field := string(p.fieldName); field {
case fieldNameSignature:
// At the end of the caveats we find the signature.
if cav.Id != nil {
m.caveats = append(m.caveats, cav)
}
if len(p.data) != hashLen {
return nil, fmt.Errorf("signature has unexpected length %d", len(p.data))
}
copy(m.sig[:], p.data)
return data, nil
case fieldNameCaveatId:
if cav.Id != nil {
m.caveats = append(m.caveats, cav)
cav = Caveat{}
}
cav.Id = p.data
case fieldNameVerificationId:
if cav.VerificationId != nil {
return nil, fmt.Errorf("repeated field %q in caveat", fieldNameVerificationId)
}
cav.VerificationId = p.data
case fieldNameCaveatLocation:
if cav.Location != "" {
return nil, fmt.Errorf("repeated field %q in caveat", fieldNameLocation)
}
cav.Location = string(p.data)
default:
return nil, fmt.Errorf("unexpected field %q", field)
}
}
}
func expectPacketV1(data []byte, kind string) (packetV1, error) {
p, err := parsePacketV1(data)
if err != nil {
return packetV1{}, err
}
if field := string(p.fieldName); field != kind {
return packetV1{}, fmt.Errorf("unexpected field %q; expected %s", field, kind)
}
return p, nil
}
// appendBinaryV1 appends the binary encoding of m to data.
func (m *Macaroon) appendBinaryV1(data []byte) ([]byte, error) {
var ok bool
data, ok = appendPacketV1(data, fieldNameLocation, []byte(m.location))
if !ok {
return nil, fmt.Errorf("failed to append location to macaroon, packet is too long")
}
data, ok = appendPacketV1(data, fieldNameIdentifier, m.id)
if !ok {
return nil, fmt.Errorf("failed to append identifier to macaroon, packet is too long")
}
for _, cav := range m.caveats {
data, ok = appendPacketV1(data, fieldNameCaveatId, cav.Id)
if !ok {
return nil, fmt.Errorf("failed to append caveat id to macaroon, packet is too long")
}
if cav.VerificationId == nil {
continue
}
data, ok = appendPacketV1(data, fieldNameVerificationId, cav.VerificationId)
if !ok {
return nil, fmt.Errorf("failed to append verification id to macaroon, packet is too long")
}
data, ok = appendPacketV1(data, fieldNameCaveatLocation, []byte(cav.Location))
if !ok {
return nil, fmt.Errorf("failed to append verification id to macaroon, packet is too long")
}
}
data, ok = appendPacketV1(data, fieldNameSignature, m.sig[:])
if !ok {
return nil, fmt.Errorf("failed to append signature to macaroon, packet is too long")
}
return data, nil
}