aboutsummaryrefslogtreecommitdiff
path: root/reqres.go
blob: 0c14d89c4e2e92f83f226c5de524ba7157af9e79 (plain)
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
package stfe

import (
	"fmt"
	"strconv"

	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"io/ioutil"
	"net/http"

	"github.com/google/certificate-transparency-go/tls"
	"github.com/google/trillian"
)

// AddEntryRequest is a collection of add-entry input parameters
type AddEntryRequest struct {
	Item        string `json:"item"`        // base64-encoded StItem
	Signature   string `json:"signature"`   // base64-encoded DigitallySigned
	Certificate string `json:"certificate"` // base64-encoded X.509 certificate
}

// GetEntriesRequest is a collection of get-entry input parameters
type GetEntriesRequest struct {
	Start int64 `json:"start"` // 0-based and inclusive start-index
	End   int64 `json:"end"`   // 0-based and inclusive end-index
}

// GetProofByHashRequest is a collection of get-proof-by-hash input parameters
type GetProofByHashRequest struct {
	Hash     []byte `json:"hash"`      // base64-encoded leaf hash
	TreeSize int64  `json:"tree_size"` // Tree head size to base proof on
}

// GetConsistencyProofRequest is a collection of get-consistency-proof input
// parameters
type GetConsistencyProofRequest struct {
	First  int64 `json:"first"`
	Second int64 `json:"second"`
}

// AddEntryResponse is an assembled add-entry response
type AddEntryResponse struct {
	SignedDebugInfo string `json:"sdi"`
}

// GetEntryResponse is an assembled log entry and its associated appendix
type GetEntryResponse struct {
	Leaf      string   `json:"leaf"`      // base64-encoded StItem
	Signature string   `json:"signature"` // base64-encoded signature
	Chain     []string `json:"chain"`     // base64-encoded X.509 certificates
}

// GetEntriesResponse is an assembled get-entries responses
type GetEntriesResponse struct {
	Entries []GetEntryResponse `json:"entries"`
}

// GetProofByHashResponse is an assembled inclusion proof response
type GetProofByHashResponse struct {
	InclusionProof string `json:"inclusion_proof"` // base64-encoded StItem
}

type GetConsistencyProofResponse struct {
	ConsistencyProof string `json:"consistency_proof"` // base64-encoded StItem
}

// GetAnchorsResponse is an assembled get-anchor response
type GetAnchorsResponse struct {
	Certificates []string `json:"certificates"`
}

// GetSthResponse is an assembled get-sth response
type GetSthResponse struct {
	SignedTreeHead string `json:"sth"` // base64-encoded StItem
}

// NewAddEntryRequest parses and sanitizes the JSON-encoded add-entry
// parameters from an incoming HTTP post.  The resulting AddEntryRequest is
// well-formed, but not necessarily trusted (further sanitization is needed).
func NewAddEntryRequest(r *http.Request) (AddEntryRequest, error) {
	var ret AddEntryRequest
	if err := UnpackJsonPost(r, &ret); err != nil {
		return ret, err
	}

	item, err := StItemFromB64(ret.Item)
	if err != nil {
		return ret, fmt.Errorf("failed decoding StItem: %v", err)
	}
	if item.Format != StFormatChecksumV1 {
		return ret, fmt.Errorf("invalid StItem format: %s", item.Format)
	}
	// TODO: verify that we got a checksum length
	// TODO: verify that we got a signature and certificate
	return ret, nil
}

// NewGetEntriesRequest parses and sanitizes the URL-encoded get-entries
// parameters from an incoming HTTP request.
func NewGetEntriesRequest(httpRequest *http.Request) (GetEntriesRequest, error) {
	start, err := strconv.ParseInt(httpRequest.FormValue("start"), 10, 64)
	if err != nil {
		return GetEntriesRequest{}, fmt.Errorf("bad start parameter: %v", err)
	}
	end, err := strconv.ParseInt(httpRequest.FormValue("end"), 10, 64)
	if err != nil {
		return GetEntriesRequest{}, fmt.Errorf("bad end parameter: %v", err)
	}

	if start < 0 {
		return GetEntriesRequest{}, fmt.Errorf("bad parameters: start(%v) must have a non-negative value", start)
	}
	if start > end {
		return GetEntriesRequest{}, fmt.Errorf("bad parameters: start(%v) must be less than or equal to end(%v)", start, end)
	}
	// TODO: check that range is not larger than the max range. Yes -> truncate
	// TODO: check that end is not past the most recent STH. Yes -> truncate
	return GetEntriesRequest{Start: start, End: end}, nil
}

// NewGetProofByHashRequest parses and sanitizes the URL-encoded
// get-proof-by-hash parameters from an incoming HTTP request.
func NewGetProofByHashRequest(httpRequest *http.Request) (GetProofByHashRequest, error) {
	treeSize, err := strconv.ParseInt(httpRequest.FormValue("tree_size"), 10, 64)
	if err != nil {
		return GetProofByHashRequest{}, fmt.Errorf("bad tree_size parameter: %v", err)
	}
	if treeSize < 0 {
		return GetProofByHashRequest{}, fmt.Errorf("bad tree_size parameter: negative value")
	}
	// TODO: check that tree size is not past STH.tree_size

	hash, err := base64.StdEncoding.DecodeString(httpRequest.FormValue("hash"))
	if err != nil {
		return GetProofByHashRequest{}, fmt.Errorf("bad hash parameter: %v", err)
	}
	return GetProofByHashRequest{TreeSize: treeSize, Hash: hash}, nil
}

func NewGetConsistencyProofRequest(httpRequest *http.Request) (GetConsistencyProofRequest, error) {
	first, err := strconv.ParseInt(httpRequest.FormValue("first"), 10, 64)
	if err != nil {
		return GetConsistencyProofRequest{}, fmt.Errorf("bad first parameter: %v", err)
	}
	second, err := strconv.ParseInt(httpRequest.FormValue("second"), 10, 64)
	if err != nil {
		return GetConsistencyProofRequest{}, fmt.Errorf("bad second parameter: %v", err)
	}

	if first < 1 {
		return GetConsistencyProofRequest{}, fmt.Errorf("bad parameters: first(%v) must be a natural number", first)
	}
	if first >= second {
		return GetConsistencyProofRequest{}, fmt.Errorf("bad parameters: second(%v) must be larger than first(%v)", first, second)
	}

	return GetConsistencyProofRequest{First: first, Second: second}, nil
}

// NewAddEntryResponse assembles an add-entry response from an SDI
func NewAddEntryResponse(sdi StItem) (AddEntryResponse, error) {
	b, err := tls.Marshal(sdi)
	if err != nil {
		return AddEntryResponse{}, fmt.Errorf("tls marshal failed: %v", err)
	}
	return AddEntryResponse{
		SignedDebugInfo: base64.StdEncoding.EncodeToString(b),
	}, nil
}

// NewGetEntryResponse assembles a log entry and its appendix
func NewGetEntryResponse(leaf, appendix []byte) (GetEntryResponse, error) {
	var app Appendix
	extra, err := tls.Unmarshal(appendix, &app)
	if err != nil {
		return GetEntryResponse{}, fmt.Errorf("failed tls unmarshaling appendix: %v (%v)", err, extra)
	} else if len(extra) > 0 {
		return GetEntryResponse{}, fmt.Errorf("tls umarshal found extra data for appendix: %v", extra)
	}

	chain := make([]string, 0, len(app.Chain))
	for _, c := range app.Chain {
		chain = append(chain, base64.StdEncoding.EncodeToString(c.Data))
	}

	return GetEntryResponse{
		Leaf:      base64.StdEncoding.EncodeToString(leaf),
		Signature: base64.StdEncoding.EncodeToString(app.Signature),
		Chain:     chain,
	}, nil
}

// NewGetEntriesResponse assembles a get-entries response
func NewGetEntriesResponse(leaves []*trillian.LogLeaf) (GetEntriesResponse, error) {
	entries := make([]GetEntryResponse, 0, len(leaves))
	for _, leaf := range leaves {
		entry, err := NewGetEntryResponse(leaf.GetLeafValue(), leaf.GetExtraData())
		if err != nil {
			return GetEntriesResponse{}, err
		}
		entries = append(entries, entry)
	}
	return GetEntriesResponse{entries}, nil
}

// NewGetProofByHashResponse assembles a get-proof-by-hash response
func NewGetProofByHashResponse(logId []byte, treeSize uint64, inclusionProof *trillian.Proof) (*GetProofByHashResponse, error) {
	item := NewInclusionProofV1(logId, treeSize, inclusionProof)
	b, err := tls.Marshal(item)
	if err != nil {
		return nil, fmt.Errorf("tls marshal failed: %v", err)
	}
	return &GetProofByHashResponse{
		InclusionProof: base64.StdEncoding.EncodeToString(b),
	}, nil
}

func NewGetConsistencyProofResponse(logId []byte, first, second int64, consistencyProof *trillian.Proof) (*GetConsistencyProofResponse, error) {
	item := NewConsistencyProofV1(logId, first, second, consistencyProof)
	b, err := tls.Marshal(item)
	if err != nil {
		return nil, fmt.Errorf("tls marshal failed: %v", err)
	}
	return &GetConsistencyProofResponse{
		ConsistencyProof: base64.StdEncoding.EncodeToString(b),
	}, nil
}

func NewGetAnchorsResponse(anchors []*x509.Certificate) GetAnchorsResponse {
	certificates := make([]string, 0, len(anchors))
	for _, certificate := range anchors {
		certificates = append(certificates, base64.StdEncoding.EncodeToString(certificate.Raw))
	}
	return GetAnchorsResponse{Certificates: certificates}
}

func NewGetSthResponse(sth StItem) (GetSthResponse, error) {
	b, err := tls.Marshal(sth)
	if err != nil {
		return GetSthResponse{}, fmt.Errorf("tls marshal failed: %v", err)
	}
	return GetSthResponse{
		SignedTreeHead: base64.StdEncoding.EncodeToString(b),
	}, nil
}

// VerifyAddEntryRequest determines whether a well-formed AddEntryRequest should
// be inserted into the log.  The corresponding leaf and appendix is returned.
func VerifyAddEntryRequest(ld *LogParameters, r AddEntryRequest) ([]byte, []byte, error) {
	item, err := StItemFromB64(r.Item)
	if err != nil {
		return nil, nil, fmt.Errorf("failed decoding StItem: %v", err)
	}

	leaf, err := tls.Marshal(item)
	if err != nil {
		return nil, nil, fmt.Errorf("failed tls marshaling StItem: %v", err)
	} // leaf is the serialized data that should be added to the tree

	c, err := base64.StdEncoding.DecodeString(r.Certificate)
	if err != nil {
		return nil, nil, fmt.Errorf("failed decoding certificate: %v", err)
	}
	certificate, err := x509.ParseCertificate(c)
	if err != nil {
		return nil, nil, fmt.Errorf("failed decoding certificate: %v", err)
	} // certificate is the end-entity certificate that signed leaf

	chain, err := VerifyChain(ld, certificate)
	if err != nil {
		return nil, nil, fmt.Errorf("chain verification failed: %v", err)
	} // chain is a valid path to some trust anchor

	signature, err := base64.StdEncoding.DecodeString(r.Signature)
	if err != nil {
		return nil, nil, fmt.Errorf("failed decoding signature: %v", err)
	}
	if err := VerifySignature(leaf, signature, certificate); err != nil {
		return nil, nil, fmt.Errorf("signature verification failed: %v", err)
	} // signature is valid for certificate

	// TODO: update doc of what signature "is", i.e., w/e x509 does
	// TODO: doc in markdown/api.md what signature schemes we expect
	appendix, err := tls.Marshal(NewAppendix(chain, signature))
	if err != nil {
		return nil, nil, fmt.Errorf("failed tls marshaling appendix: %v", err)
	}

	return leaf, appendix, nil
}

// UnpackJsonPost unpacks a json-encoded HTTP POST request into `unpack`
func UnpackJsonPost(r *http.Request, unpack interface{}) error {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return fmt.Errorf("failed reading request body: %v", err)
	}
	if err := json.Unmarshal(body, &unpack); err != nil {
		return fmt.Errorf("failed parsing json body: %v", err)
	}
	return nil
}

func WriteJsonResponse(response interface{}, w http.ResponseWriter) error {
	json, err := json.Marshal(&response)
	if err != nil {
		return fmt.Errorf("json-encoding failed: %v", err)
	}

	w.Header().Set("Content-Type", "application/json")
	_, err = w.Write(json)
	if err != nil {
		return fmt.Errorf("failed writing json response: %v", err)
	}
	return nil
}