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
|
package stfe
import (
"fmt"
"strconv"
"encoding/base64"
"encoding/json"
"io/ioutil"
"net/http"
"github.com/google/certificate-transparency-go/tls"
"github.com/google/certificate-transparency-go/trillian/ctfe"
"github.com/google/certificate-transparency-go/x509"
"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
}
// 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 DigitallySigned
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
}
// GetAnchorsResponse
type GetAnchorsResponse struct {
Certificates []string `json:"certificates"`
}
// 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 larger than 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
}
// NewGetEntryResponse assembles a log entry and its appendix
func NewGetEntryResponse(leaf []byte) GetEntryResponse {
return GetEntryResponse{
Leaf: base64.StdEncoding.EncodeToString(leaf),
// TODO: add signature and chain
}
}
// NewGetEntriesResponse assembles a get-entries response
func NewGetEntriesResponse(leaves []*trillian.LogLeaf) (GetEntriesResponse, error) {
entries := make([]GetEntryResponse, 0, len(leaves))
for _, leaf := range leaves {
entries = append(entries, NewGetEntryResponse(leaf.GetLeafValue())) // TODO: add signature and chain
}
return GetEntriesResponse{entries}, nil
}
// NewGetProofByHashResponse assembles a get-proof-by-hash response
func NewGetProofByHashResponse(treeSize uint64, inclusionProof *trillian.Proof) (*GetProofByHashResponse, error) {
item := NewInclusionProofV1([]byte("TODO: add log ID"), 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 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}
}
// VerifyAddEntryRequest determines whether a well-formed AddEntryRequest should
// be inserted into the log. If so, the serialized leaf value is returned.
func VerifyAddEntryRequest(anchors ctfe.CertValidationOpts, r AddEntryRequest) ([]byte, error) {
item, err := StItemFromB64(r.Item)
if err != nil {
fmt.Errorf("failed decoding StItem: %v", err)
}
leaf, err := tls.Marshal(item)
if err != nil {
return nil, fmt.Errorf("failed tls marshaling StItem: %v", err)
}
certificate, err := base64.StdEncoding.DecodeString(r.Certificate)
if err != nil {
return nil, fmt.Errorf("failed decoding certificate: %v", err)
}
chain := make([][]byte, 0, 1)
chain = append(chain, certificate)
x509chain, err := ctfe.ValidateChain(chain, anchors)
if err != nil {
return nil, fmt.Errorf("chain verification failed: %v", err)
}
c := x509chain[0]
signature, err := base64.StdEncoding.DecodeString(r.Signature)
if err != nil {
return nil, fmt.Errorf("failed decoding signature: %v", err)
}
if err := c.CheckSignature(c.SignatureAlgorithm, leaf, signature); err != nil {
return nil, fmt.Errorf("invalid signature: %v", err)
}
return leaf, 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
}
|