1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package ctfe
16
17 import (
18 "context"
19 "crypto"
20 "crypto/sha256"
21 "encoding/base64"
22 "encoding/json"
23 "errors"
24 "flag"
25 "fmt"
26 "io"
27 "net/http"
28 "strconv"
29 "strings"
30 "sync"
31 "time"
32
33 "github.com/google/certificate-transparency-go/asn1"
34 "github.com/google/certificate-transparency-go/tls"
35 "github.com/google/certificate-transparency-go/trillian/util"
36 "github.com/google/certificate-transparency-go/x509"
37 "github.com/google/certificate-transparency-go/x509util"
38 "github.com/google/trillian"
39 "github.com/google/trillian/monitoring"
40 "github.com/google/trillian/types"
41 "google.golang.org/grpc/codes"
42 "google.golang.org/grpc/status"
43 "google.golang.org/protobuf/encoding/prototext"
44 "k8s.io/klog/v2"
45
46 ct "github.com/google/certificate-transparency-go"
47 )
48
49 var (
50 alignGetEntries = flag.Bool("align_getentries", true, "Enable get-entries request alignment")
51 getEntriesMetrics = flag.Bool("getentries_metrics", false, "Export get-entries distribution metrics")
52 )
53
54 const (
55
56 cacheControlHeader = "Cache-Control"
57
58 cacheControlImmutable = "public, max-age=86400"
59
60 contentTypeHeader string = "Content-Type"
61
62 contentTypeJSON string = "application/json"
63
64 jsonMapKeyCertificates string = "certificates"
65
66 getEntriesParamStart = "start"
67
68 getEntriesParamEnd = "end"
69
70 getProofParamHash = "hash"
71
72 getProofParamTreeSize = "tree_size"
73
74 getSTHConsistencyParamFirst = "first"
75
76 getSTHConsistencyParamSecond = "second"
77
78 getEntryAndProofParamLeafIndex = "leaf_index"
79
80 getEntryAndProofParamTreeSize = "tree_size"
81 )
82
83 var (
84
85 MaxGetEntriesAllowed int64 = 1000
86
87
88
89 emptyProof = make([][]byte, 0)
90 )
91
92
93 type EntrypointName string
94
95
96 const (
97 AddChainName = EntrypointName("AddChain")
98 AddPreChainName = EntrypointName("AddPreChain")
99 GetSTHName = EntrypointName("GetSTH")
100 GetSTHConsistencyName = EntrypointName("GetSTHConsistency")
101 GetProofByHashName = EntrypointName("GetProofByHash")
102 GetEntriesName = EntrypointName("GetEntries")
103 GetRootsName = EntrypointName("GetRoots")
104 GetEntryAndProofName = EntrypointName("GetEntryAndProof")
105 )
106
107 var (
108
109
110 once sync.Once
111 knownLogs monitoring.Gauge
112 isMirrorLog monitoring.Gauge
113 maxMergeDelay monitoring.Gauge
114 expMergeDelay monitoring.Gauge
115 lastSCTTimestamp monitoring.Gauge
116 lastSTHTimestamp monitoring.Gauge
117 lastSTHTreeSize monitoring.Gauge
118 frozenSTHTimestamp monitoring.Gauge
119 reqsCounter monitoring.Counter
120 rspsCounter monitoring.Counter
121 rspLatency monitoring.Histogram
122 alignedGetEntries monitoring.Counter
123 getEntriesStartPercentiles monitoring.Histogram
124 )
125
126
127 func setupMetrics(mf monitoring.MetricFactory) {
128 knownLogs = mf.NewGauge("known_logs", "Set to 1 for known logs", "logid")
129 isMirrorLog = mf.NewGauge("is_mirror", "Set to 1 for mirror logs", "logid")
130 maxMergeDelay = mf.NewGauge("max_merge_delay", "Maximum Merge Delay in seconds", "logid")
131 expMergeDelay = mf.NewGauge("expected_merge_delay", "Expected Merge Delay in seconds", "logid")
132 lastSCTTimestamp = mf.NewGauge("last_sct_timestamp", "Time of last SCT in ms since epoch", "logid")
133 lastSTHTimestamp = mf.NewGauge("last_sth_timestamp", "Time of last STH in ms since epoch", "logid")
134 lastSTHTreeSize = mf.NewGauge("last_sth_treesize", "Size of tree at last STH", "logid")
135 frozenSTHTimestamp = mf.NewGauge("frozen_sth_timestamp", "Time of the frozen STH in ms since epoch", "logid")
136 reqsCounter = mf.NewCounter("http_reqs", "Number of requests", "logid", "ep")
137 rspsCounter = mf.NewCounter("http_rsps", "Number of responses", "logid", "ep", "rc")
138 rspLatency = mf.NewHistogram("http_latency", "Latency of responses in seconds", "logid", "ep", "rc")
139 alignedGetEntries = mf.NewCounter("aligned_get_entries", "Number of get-entries requests which were aligned to size limit boundaries", "logid", "aligned")
140 getEntriesStartPercentiles = mf.NewHistogramWithBuckets(
141 "get_leaves_start_percentiles",
142 "Start index of GetLeavesByRange request using percentage of current log size at the time",
143 monitoring.PercentileBuckets(5),
144 "logid",
145 )
146 }
147
148
149 var Entrypoints = []EntrypointName{AddChainName, AddPreChainName, GetSTHName, GetSTHConsistencyName, GetProofByHashName, GetEntriesName, GetRootsName, GetEntryAndProofName}
150
151
152 type PathHandlers map[string]AppHandler
153
154
155
156 type AppHandler struct {
157 Info *logInfo
158 Handler func(context.Context, *logInfo, http.ResponseWriter, *http.Request) (int, error)
159 Name EntrypointName
160 Method string
161 }
162
163
164
165 func (a AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
166 var statusCode int
167 label0 := strconv.FormatInt(a.Info.logID, 10)
168 label1 := string(a.Name)
169 reqsCounter.Inc(label0, label1)
170 startTime := a.Info.TimeSource.Now()
171 logCtx := a.Info.RequestLog.Start(r.Context())
172 a.Info.RequestLog.LogPrefix(logCtx, a.Info.LogPrefix)
173 defer func() {
174 latency := a.Info.TimeSource.Now().Sub(startTime).Seconds()
175 rspLatency.Observe(latency, label0, label1, strconv.Itoa(statusCode))
176 }()
177 klog.V(2).Infof("%s: request %v %q => %s", a.Info.LogPrefix, r.Method, r.URL, a.Name)
178 if r.Method != a.Method {
179 klog.Warningf("%s: %s wrong HTTP method: %v", a.Info.LogPrefix, a.Name, r.Method)
180 a.Info.SendHTTPError(w, http.StatusMethodNotAllowed, fmt.Errorf("method not allowed: %s", r.Method))
181 a.Info.RequestLog.Status(logCtx, http.StatusMethodNotAllowed)
182 return
183 }
184
185
186
187 if r.Method == http.MethodGet {
188 if err := r.ParseForm(); err != nil {
189 a.Info.SendHTTPError(w, http.StatusBadRequest, fmt.Errorf("failed to parse form data: %s", err))
190 a.Info.RequestLog.Status(logCtx, http.StatusBadRequest)
191 return
192 }
193 }
194
195
196
197 ctx, cancel := context.WithDeadline(logCtx, getRPCDeadlineTime(a.Info))
198 defer cancel()
199
200 var err error
201 statusCode, err = a.Handler(ctx, a.Info, w, r)
202 a.Info.RequestLog.Status(ctx, statusCode)
203 klog.V(2).Infof("%s: %s <= st=%d", a.Info.LogPrefix, a.Name, statusCode)
204 rspsCounter.Inc(label0, label1, strconv.Itoa(statusCode))
205 if err != nil {
206 klog.Warningf("%s: %s handler error: %v", a.Info.LogPrefix, a.Name, err)
207 a.Info.SendHTTPError(w, statusCode, err)
208 return
209 }
210
211
212 if statusCode != http.StatusOK {
213 klog.Warningf("%s: %s handler non 200 without error: %d %v", a.Info.LogPrefix, a.Name, statusCode, err)
214 a.Info.SendHTTPError(w, http.StatusInternalServerError, fmt.Errorf("http handler misbehaved, st: %d", statusCode))
215 return
216 }
217 }
218
219
220 type CertValidationOpts struct {
221
222 trustedRoots *x509util.PEMCertPool
223
224
225 currentTime time.Time
226
227 rejectExpired bool
228
229 rejectUnexpired bool
230
231
232 notAfterStart *time.Time
233
234
235
236 notAfterLimit *time.Time
237
238 acceptOnlyCA bool
239
240 extKeyUsages []x509.ExtKeyUsage
241
242 rejectExtIds []asn1.ObjectIdentifier
243 }
244
245
246 func NewCertValidationOpts(trustedRoots *x509util.PEMCertPool, currentTime time.Time, rejectExpired bool, rejectUnexpired bool, notAfterStart *time.Time, notAfterLimit *time.Time, acceptOnlyCA bool, extKeyUsages []x509.ExtKeyUsage) CertValidationOpts {
247 var vOpts CertValidationOpts
248 vOpts.trustedRoots = trustedRoots
249 vOpts.currentTime = currentTime
250 vOpts.rejectExpired = rejectExpired
251 vOpts.rejectUnexpired = rejectUnexpired
252 vOpts.notAfterStart = notAfterStart
253 vOpts.notAfterLimit = notAfterLimit
254 vOpts.acceptOnlyCA = acceptOnlyCA
255 vOpts.extKeyUsages = extKeyUsages
256 return vOpts
257 }
258
259
260 type logInfo struct {
261
262 LogPrefix string
263
264 TimeSource util.TimeSource
265
266
267 RequestLog RequestLog
268
269
270 instanceOpts InstanceOptions
271
272 logID int64
273
274 validationOpts CertValidationOpts
275
276 rpcClient trillian.TrillianLogClient
277
278 signer crypto.Signer
279
280 sthGetter STHGetter
281 }
282
283
284 func newLogInfo(
285 instanceOpts InstanceOptions,
286 validationOpts CertValidationOpts,
287 signer crypto.Signer,
288 timeSource util.TimeSource,
289 ) *logInfo {
290 vCfg := instanceOpts.Validated
291 cfg := vCfg.Config
292
293 logID, prefix := cfg.LogId, cfg.Prefix
294 li := &logInfo{
295 logID: logID,
296 LogPrefix: fmt.Sprintf("%s{%d}", prefix, logID),
297 rpcClient: instanceOpts.Client,
298 signer: signer,
299 TimeSource: timeSource,
300 instanceOpts: instanceOpts,
301 validationOpts: validationOpts,
302 RequestLog: instanceOpts.RequestLog,
303 }
304
305 once.Do(func() { setupMetrics(instanceOpts.MetricFactory) })
306 label := strconv.FormatInt(logID, 10)
307 knownLogs.Set(1.0, label)
308
309 switch {
310 case vCfg.FrozenSTH != nil:
311 li.sthGetter = &FrozenSTHGetter{sth: vCfg.FrozenSTH}
312 frozenSTHTimestamp.Set(float64(vCfg.FrozenSTH.Timestamp), label)
313
314 case cfg.IsMirror:
315 st := instanceOpts.STHStorage
316 if st == nil {
317 st = DefaultMirrorSTHStorage{}
318 }
319 li.sthGetter = &MirrorSTHGetter{li: li, st: st}
320
321 default:
322 li.sthGetter = &LogSTHGetter{li: li}
323 }
324
325 if cfg.IsMirror {
326 isMirrorLog.Set(1.0, label)
327 } else {
328 isMirrorLog.Set(0.0, label)
329 }
330 maxMergeDelay.Set(float64(cfg.MaxMergeDelaySec), label)
331 expMergeDelay.Set(float64(cfg.ExpectedMergeDelaySec), label)
332
333 return li
334 }
335
336
337
338 func (li *logInfo) Handlers(prefix string) PathHandlers {
339 if !strings.HasPrefix(prefix, "/") {
340 prefix = "/" + prefix
341 }
342 prefix = strings.TrimRight(prefix, "/")
343
344
345 ph := PathHandlers{
346 prefix + ct.AddChainPath: AppHandler{Info: li, Handler: addChain, Name: AddChainName, Method: http.MethodPost},
347 prefix + ct.AddPreChainPath: AppHandler{Info: li, Handler: addPreChain, Name: AddPreChainName, Method: http.MethodPost},
348 prefix + ct.GetSTHPath: AppHandler{Info: li, Handler: getSTH, Name: GetSTHName, Method: http.MethodGet},
349 prefix + ct.GetSTHConsistencyPath: AppHandler{Info: li, Handler: getSTHConsistency, Name: GetSTHConsistencyName, Method: http.MethodGet},
350 prefix + ct.GetProofByHashPath: AppHandler{Info: li, Handler: getProofByHash, Name: GetProofByHashName, Method: http.MethodGet},
351 prefix + ct.GetEntriesPath: AppHandler{Info: li, Handler: getEntries, Name: GetEntriesName, Method: http.MethodGet},
352 prefix + ct.GetRootsPath: AppHandler{Info: li, Handler: getRoots, Name: GetRootsName, Method: http.MethodGet},
353 prefix + ct.GetEntryAndProofPath: AppHandler{Info: li, Handler: getEntryAndProof, Name: GetEntryAndProofName, Method: http.MethodGet},
354 }
355
356 if li.instanceOpts.Validated.Config.IsReadonly || li.instanceOpts.Validated.Config.IsMirror {
357 delete(ph, prefix+ct.AddChainPath)
358 delete(ph, prefix+ct.AddPreChainPath)
359 }
360
361 return ph
362 }
363
364
365 func (li *logInfo) SendHTTPError(w http.ResponseWriter, statusCode int, err error) {
366 errorBody := http.StatusText(statusCode)
367 if !li.instanceOpts.MaskInternalErrors || statusCode != http.StatusInternalServerError {
368 errorBody += fmt.Sprintf("\n%v", err)
369 }
370 http.Error(w, errorBody, statusCode)
371 }
372
373
374
375 func (li *logInfo) getSTH(ctx context.Context) (*ct.SignedTreeHead, error) {
376 sth, err := li.sthGetter.GetSTH(ctx)
377 if err != nil {
378 return nil, err
379 }
380 logID := strconv.FormatInt(li.logID, 10)
381 lastSTHTimestamp.Set(float64(sth.Timestamp), logID)
382 lastSTHTreeSize.Set(float64(sth.TreeSize), logID)
383 return sth, nil
384 }
385
386
387 func ParseBodyAsJSONChain(r *http.Request) (ct.AddChainRequest, error) {
388 body, err := io.ReadAll(r.Body)
389 if err != nil {
390 klog.V(1).Infof("Failed to read request body: %v", err)
391 return ct.AddChainRequest{}, err
392 }
393
394 var req ct.AddChainRequest
395 if err := json.Unmarshal(body, &req); err != nil {
396 klog.V(1).Infof("Failed to parse request body: %v", err)
397 return ct.AddChainRequest{}, err
398 }
399
400
401 if len(req.Chain) == 0 {
402 klog.V(1).Infof("Request chain is empty: %q", body)
403 return ct.AddChainRequest{}, errors.New("cert chain was empty")
404 }
405
406 return req, nil
407 }
408
409
410
411
412
413 func appendUserCharge(a *trillian.ChargeTo, user string) *trillian.ChargeTo {
414 if a == nil {
415 a = &trillian.ChargeTo{}
416 }
417 a.User = append(a.User, user)
418 return a
419 }
420
421
422
423 func (li *logInfo) chargeUser(r *http.Request) *trillian.ChargeTo {
424 if li.instanceOpts.RemoteQuotaUser != nil {
425 return &trillian.ChargeTo{User: []string{li.instanceOpts.RemoteQuotaUser(r)}}
426 }
427 return nil
428 }
429
430
431
432 func addChainInternal(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request, isPrecert bool) (int, error) {
433 var method EntrypointName
434 var etype ct.LogEntryType
435 if isPrecert {
436 method = AddPreChainName
437 etype = ct.PrecertLogEntryType
438 } else {
439 method = AddChainName
440 etype = ct.X509LogEntryType
441 }
442
443
444 addChainReq, err := ParseBodyAsJSONChain(r)
445 if err != nil {
446 return http.StatusBadRequest, fmt.Errorf("%s: failed to parse add-chain body: %s", li.LogPrefix, err)
447 }
448
449 for _, der := range addChainReq.Chain {
450 li.RequestLog.AddDERToChain(ctx, der)
451 }
452 chain, err := verifyAddChain(li, addChainReq, isPrecert)
453 if err != nil {
454 return http.StatusBadRequest, fmt.Errorf("failed to verify add-chain contents: %s", err)
455 }
456 for _, cert := range chain {
457 li.RequestLog.AddCertToChain(ctx, cert)
458 }
459
460
461 timeMillis := uint64(li.TimeSource.Now().UnixNano() / millisPerNano)
462
463
464 merkleLeaf, err := ct.MerkleTreeLeafFromChain(chain, etype, timeMillis)
465 if err != nil {
466 return http.StatusBadRequest, fmt.Errorf("failed to build MerkleTreeLeaf: %s", err)
467 }
468 leaf, err := buildLogLeafForAddChain(li, *merkleLeaf, chain, isPrecert)
469 if err != nil {
470 return http.StatusInternalServerError, fmt.Errorf("failed to build LogLeaf: %s", err)
471 }
472
473
474 req := trillian.QueueLeafRequest{
475 LogId: li.logID,
476 Leaf: &leaf,
477 ChargeTo: li.chargeUser(r),
478 }
479 if li.instanceOpts.CertificateQuotaUser != nil {
480
481 for _, cert := range chain[1:] {
482 req.ChargeTo = appendUserCharge(req.ChargeTo, li.instanceOpts.CertificateQuotaUser(cert))
483 }
484 }
485
486 klog.V(2).Infof("%s: %s => grpc.QueueLeaves", li.LogPrefix, method)
487 rsp, err := li.rpcClient.QueueLeaf(ctx, &req)
488 klog.V(2).Infof("%s: %s <= grpc.QueueLeaves err=%v", li.LogPrefix, method, err)
489 if err != nil {
490 return li.toHTTPStatus(err), fmt.Errorf("backend QueueLeaves request failed: %s", err)
491 }
492 if rsp == nil {
493 return http.StatusInternalServerError, errors.New("missing QueueLeaves response")
494 }
495 if rsp.QueuedLeaf == nil {
496 return http.StatusInternalServerError, errors.New("QueueLeaf did not return the leaf")
497 }
498
499
500 var loggedLeaf ct.MerkleTreeLeaf
501 if rest, err := tls.Unmarshal(rsp.QueuedLeaf.Leaf.LeafValue, &loggedLeaf); err != nil {
502 return http.StatusInternalServerError, fmt.Errorf("failed to reconstruct MerkleTreeLeaf: %s", err)
503 } else if len(rest) > 0 {
504 return http.StatusInternalServerError, fmt.Errorf("extra data (%d bytes) on reconstructing MerkleTreeLeaf", len(rest))
505 }
506
507
508
509 sct, err := buildV1SCT(li.signer, &loggedLeaf)
510 if err != nil {
511 return http.StatusInternalServerError, fmt.Errorf("failed to generate SCT: %s", err)
512 }
513 sctBytes, err := tls.Marshal(*sct)
514 if err != nil {
515 return http.StatusInternalServerError, fmt.Errorf("failed to marshall SCT: %s", err)
516 }
517
518 li.RequestLog.IssueSCT(ctx, sctBytes)
519 err = marshalAndWriteAddChainResponse(sct, li.signer, w)
520 if err != nil {
521
522 return http.StatusInternalServerError, fmt.Errorf("failed to write response: %s", err)
523 }
524 klog.V(3).Infof("%s: %s <= SCT", li.LogPrefix, method)
525 if sct.Timestamp == timeMillis {
526 lastSCTTimestamp.Set(float64(sct.Timestamp), strconv.FormatInt(li.logID, 10))
527 }
528
529 return http.StatusOK, nil
530 }
531
532 func addChain(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) {
533 return addChainInternal(ctx, li, w, r, false)
534 }
535
536 func addPreChain(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) {
537 return addChainInternal(ctx, li, w, r, true)
538 }
539
540 func getSTH(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) {
541 qctx := ctx
542 if li.instanceOpts.RemoteQuotaUser != nil {
543 rqu := li.instanceOpts.RemoteQuotaUser(r)
544 qctx = context.WithValue(qctx, remoteQuotaCtxKey, rqu)
545 }
546 sth, err := li.getSTH(qctx)
547 if err != nil {
548 return li.toHTTPStatus(err), err
549 }
550 if err := writeSTH(sth, w); err != nil {
551 return http.StatusInternalServerError, err
552 }
553 return http.StatusOK, nil
554 }
555
556
557 func writeSTH(sth *ct.SignedTreeHead, w http.ResponseWriter) error {
558 jsonRsp := ct.GetSTHResponse{
559 TreeSize: sth.TreeSize,
560 SHA256RootHash: sth.SHA256RootHash[:],
561 Timestamp: sth.Timestamp,
562 }
563 var err error
564 jsonRsp.TreeHeadSignature, err = tls.Marshal(sth.TreeHeadSignature)
565 if err != nil {
566 return fmt.Errorf("failed to tls.Marshal signature: %s", err)
567 }
568
569 w.Header().Set(contentTypeHeader, contentTypeJSON)
570 jsonData, err := json.Marshal(&jsonRsp)
571 if err != nil {
572 return fmt.Errorf("failed to marshal response: %s", err)
573 }
574
575 _, err = w.Write(jsonData)
576 if err != nil {
577
578
579 return fmt.Errorf("failed to write response data: %s", err)
580 }
581
582 return nil
583 }
584
585
586 func getSTHConsistency(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) {
587 first, second, err := parseGetSTHConsistencyRange(r)
588 if err != nil {
589 return http.StatusBadRequest, fmt.Errorf("failed to parse consistency range: %s", err)
590 }
591 li.RequestLog.FirstAndSecond(ctx, first, second)
592 var jsonRsp ct.GetSTHConsistencyResponse
593 if first != 0 {
594 req := trillian.GetConsistencyProofRequest{
595 LogId: li.logID,
596 FirstTreeSize: first,
597 SecondTreeSize: second,
598 ChargeTo: li.chargeUser(r),
599 }
600
601 klog.V(2).Infof("%s: GetSTHConsistency(%d, %d) => grpc.GetConsistencyProof %+v", li.LogPrefix, first, second, prototext.Format(&req))
602 rsp, err := li.rpcClient.GetConsistencyProof(ctx, &req)
603 klog.V(2).Infof("%s: GetSTHConsistency <= grpc.GetConsistencyProof err=%v", li.LogPrefix, err)
604 if err != nil {
605 return li.toHTTPStatus(err), fmt.Errorf("backend GetConsistencyProof request failed: %s", err)
606 }
607
608 var currentRoot types.LogRootV1
609 if err := currentRoot.UnmarshalBinary(rsp.GetSignedLogRoot().GetLogRoot()); err != nil {
610 return http.StatusInternalServerError, fmt.Errorf("failed to unmarshal root: %v", rsp.GetSignedLogRoot().GetLogRoot())
611 }
612
613 if currentRoot.TreeSize < uint64(second) {
614 return http.StatusBadRequest, fmt.Errorf("need tree size: %d for proof but only got: %d", second, currentRoot.TreeSize)
615 }
616
617
618 if !checkAuditPath(rsp.Proof.Hashes) {
619 return http.StatusInternalServerError, fmt.Errorf("backend returned invalid proof: %v", rsp.Proof)
620 }
621
622
623 jsonRsp.Consistency = rsp.Proof.Hashes
624 if jsonRsp.Consistency == nil {
625 jsonRsp.Consistency = emptyProof
626 }
627 } else {
628 klog.V(2).Infof("%s: GetSTHConsistency(%d, %d) starts from 0 so return empty proof", li.LogPrefix, first, second)
629 jsonRsp.Consistency = emptyProof
630 }
631
632 w.Header().Set(cacheControlHeader, cacheControlImmutable)
633 w.Header().Set(contentTypeHeader, contentTypeJSON)
634 jsonData, err := json.Marshal(&jsonRsp)
635 if err != nil {
636 return http.StatusInternalServerError, fmt.Errorf("failed to marshal get-sth-consistency resp: %s", err)
637 }
638
639 _, err = w.Write(jsonData)
640 if err != nil {
641
642 return http.StatusInternalServerError, fmt.Errorf("failed to write get-sth-consistency resp: %s", err)
643 }
644
645 return http.StatusOK, nil
646 }
647
648
649 func getProofByHash(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) {
650
651 hash := r.FormValue(getProofParamHash)
652 if len(hash) == 0 {
653 return http.StatusBadRequest, errors.New("get-proof-by-hash: missing / empty hash param for get-proof-by-hash")
654 }
655 leafHash, err := base64.StdEncoding.DecodeString(hash)
656 if err != nil {
657 return http.StatusBadRequest, fmt.Errorf("get-proof-by-hash: invalid base64 hash: %s", err)
658 }
659
660 treeSize, err := strconv.ParseInt(r.FormValue(getProofParamTreeSize), 10, 64)
661 if err != nil || treeSize < 1 {
662 return http.StatusBadRequest, fmt.Errorf("get-proof-by-hash: missing or invalid tree_size: %v", r.FormValue(getProofParamTreeSize))
663 }
664 li.RequestLog.LeafHash(ctx, leafHash)
665 li.RequestLog.TreeSize(ctx, treeSize)
666
667
668
669
670 req := trillian.GetInclusionProofByHashRequest{
671 LogId: li.logID,
672 LeafHash: leafHash,
673 TreeSize: treeSize,
674 OrderBySequence: true,
675 ChargeTo: li.chargeUser(r),
676 }
677 rsp, err := li.rpcClient.GetInclusionProofByHash(ctx, &req)
678 if err != nil {
679 return li.toHTTPStatus(err), fmt.Errorf("backend GetInclusionProofByHash request failed: %s", err)
680 }
681
682 var currentRoot types.LogRootV1
683 if err := currentRoot.UnmarshalBinary(rsp.GetSignedLogRoot().GetLogRoot()); err != nil {
684 return http.StatusInternalServerError, fmt.Errorf("failed to unmarshal root: %v", rsp.GetSignedLogRoot().GetLogRoot())
685 }
686
687
688 if currentRoot.TreeSize < uint64(treeSize) {
689 return http.StatusNotFound, fmt.Errorf("log returned tree size: %d but we expected: %d", currentRoot.TreeSize, treeSize)
690 }
691
692
693 if len(rsp.Proof) == 0 {
694
695
696 return http.StatusNotFound, errors.New("get-proof-by-hash: backend did not return a proof")
697 }
698 if !checkAuditPath(rsp.Proof[0].Hashes) {
699 return http.StatusInternalServerError, fmt.Errorf("get-proof-by-hash: backend returned invalid proof: %v", rsp.Proof[0])
700 }
701
702
703 proofRsp := ct.GetProofByHashResponse{
704 LeafIndex: rsp.Proof[0].LeafIndex,
705 AuditPath: rsp.Proof[0].Hashes,
706 }
707 if proofRsp.AuditPath == nil {
708 proofRsp.AuditPath = emptyProof
709 }
710
711 w.Header().Set(cacheControlHeader, cacheControlImmutable)
712 w.Header().Set(contentTypeHeader, contentTypeJSON)
713 jsonData, err := json.Marshal(&proofRsp)
714 if err != nil {
715 klog.Warningf("%s: Failed to marshal get-proof-by-hash resp: %v", li.LogPrefix, proofRsp)
716 return http.StatusInternalServerError, fmt.Errorf("failed to marshal get-proof-by-hash resp: %s", err)
717 }
718
719 _, err = w.Write(jsonData)
720 if err != nil {
721
722 return http.StatusInternalServerError, fmt.Errorf("failed to write get-proof-by-hash resp: %s", err)
723 }
724
725 return http.StatusOK, nil
726 }
727
728
729 func getEntries(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) {
730
731
732
733 start, end, err := parseGetEntriesRange(r, MaxGetEntriesAllowed, li.logID)
734 if err != nil {
735 return http.StatusBadRequest, fmt.Errorf("bad range on get-entries request: %s", err)
736 }
737 li.RequestLog.StartAndEnd(ctx, start, end)
738
739
740 var leaves []*trillian.LogLeaf
741 count := end + 1 - start
742 req := trillian.GetLeavesByRangeRequest{
743 LogId: li.logID,
744 StartIndex: start,
745 Count: count,
746 ChargeTo: li.chargeUser(r),
747 }
748 rsp, err := li.rpcClient.GetLeavesByRange(ctx, &req)
749 if err != nil {
750 return li.toHTTPStatus(err), fmt.Errorf("backend GetLeavesByRange request failed: %s", err)
751 }
752 var currentRoot types.LogRootV1
753 if err := currentRoot.UnmarshalBinary(rsp.GetSignedLogRoot().GetLogRoot()); err != nil {
754 return http.StatusInternalServerError, fmt.Errorf("failed to unmarshal root: %v", rsp.GetSignedLogRoot().GetLogRoot())
755 }
756 if currentRoot.TreeSize <= uint64(start) {
757
758
759 return http.StatusBadRequest, fmt.Errorf("need tree size: %d to get leaves but only got: %d", start+1, currentRoot.TreeSize)
760 }
761 if *getEntriesMetrics {
762 label := strconv.FormatInt(req.LogId, 10)
763 recordStartPercent(start, currentRoot.TreeSize, label)
764 }
765
766 if len(rsp.Leaves) > int(count) {
767 return http.StatusInternalServerError, fmt.Errorf("backend returned too many leaves: %d vs [%d,%d]", len(rsp.Leaves), start, end)
768 }
769 for i, leaf := range rsp.Leaves {
770 if leaf.LeafIndex != start+int64(i) {
771 return http.StatusInternalServerError, fmt.Errorf("backend returned unexpected leaf index: rsp.Leaves[%d].LeafIndex=%d for range [%d,%d]", i, leaf.LeafIndex, start, end)
772 }
773 }
774 leaves = rsp.Leaves
775
776
777
778
779
780 jsonRsp, err := marshalGetEntriesResponse(li, leaves)
781 if err != nil {
782 return http.StatusInternalServerError, fmt.Errorf("failed to process leaves returned from backend: %s", err)
783 }
784
785 w.Header().Set(cacheControlHeader, cacheControlImmutable)
786 w.Header().Set(contentTypeHeader, contentTypeJSON)
787 jsonData, err := json.Marshal(&jsonRsp)
788 if err != nil {
789 return http.StatusInternalServerError, fmt.Errorf("failed to marshal get-entries resp: %s", err)
790 }
791
792 _, err = w.Write(jsonData)
793 if err != nil {
794
795 return http.StatusInternalServerError, fmt.Errorf("failed to write get-entries resp: %s", err)
796 }
797
798 return http.StatusOK, nil
799 }
800
801 func getRoots(_ context.Context, li *logInfo, w http.ResponseWriter, _ *http.Request) (int, error) {
802
803 rawCerts := make([][]byte, 0, len(li.validationOpts.trustedRoots.RawCertificates()))
804 for _, cert := range li.validationOpts.trustedRoots.RawCertificates() {
805 rawCerts = append(rawCerts, cert.Raw)
806 }
807
808 jsonMap := make(map[string]interface{})
809 jsonMap[jsonMapKeyCertificates] = rawCerts
810 enc := json.NewEncoder(w)
811 err := enc.Encode(jsonMap)
812 if err != nil {
813 klog.Warningf("%s: get_roots failed: %v", li.LogPrefix, err)
814 return http.StatusInternalServerError, fmt.Errorf("get-roots failed with: %s", err)
815 }
816
817 return http.StatusOK, nil
818 }
819
820
821
822 func getEntryAndProof(ctx context.Context, li *logInfo, w http.ResponseWriter, r *http.Request) (int, error) {
823
824 leafIndex, treeSize, err := parseGetEntryAndProofParams(r)
825 if err != nil {
826 return http.StatusBadRequest, fmt.Errorf("failed to parse get-entry-and-proof params: %s", err)
827 }
828 li.RequestLog.LeafIndex(ctx, leafIndex)
829 li.RequestLog.TreeSize(ctx, treeSize)
830
831 req := trillian.GetEntryAndProofRequest{
832 LogId: li.logID,
833 LeafIndex: leafIndex,
834 TreeSize: treeSize,
835 ChargeTo: li.chargeUser(r),
836 }
837 rsp, err := li.rpcClient.GetEntryAndProof(ctx, &req)
838 if err != nil {
839 return li.toHTTPStatus(err), fmt.Errorf("backend GetEntryAndProof request failed: %s", err)
840 }
841
842 var currentRoot types.LogRootV1
843 if err := currentRoot.UnmarshalBinary(rsp.GetSignedLogRoot().GetLogRoot()); err != nil {
844 return http.StatusInternalServerError, fmt.Errorf("failed to unmarshal root: %v", rsp.GetSignedLogRoot().GetLogRoot())
845 }
846 if currentRoot.TreeSize < uint64(treeSize) {
847
848
849 return http.StatusBadRequest, fmt.Errorf("need tree size: %d for proof but only got: %d", req.TreeSize, currentRoot.TreeSize)
850 }
851
852
853 if rsp.Leaf == nil || len(rsp.Leaf.LeafValue) == 0 || rsp.Proof == nil {
854 return http.StatusInternalServerError, fmt.Errorf("got RPC bad response, possible extra info: %v", rsp)
855 }
856 if treeSize > 1 && len(rsp.Proof.Hashes) == 0 {
857 return http.StatusInternalServerError, fmt.Errorf("got RPC bad response (missing proof), possible extra info: %v", rsp)
858 }
859
860
861 jsonRsp := ct.GetEntryAndProofResponse{
862 LeafInput: rsp.Leaf.LeafValue,
863 ExtraData: rsp.Leaf.ExtraData,
864 AuditPath: rsp.Proof.Hashes,
865 }
866
867 w.Header().Set(cacheControlHeader, cacheControlImmutable)
868 w.Header().Set(contentTypeHeader, contentTypeJSON)
869 jsonData, err := json.Marshal(&jsonRsp)
870 if err != nil {
871 return http.StatusInternalServerError, fmt.Errorf("failed to marshal get-entry-and-proof resp: %s", err)
872 }
873
874 _, err = w.Write(jsonData)
875 if err != nil {
876
877
878 return http.StatusInternalServerError, fmt.Errorf("failed to write get-entry-and-proof resp: %s", err)
879 }
880
881 return http.StatusOK, nil
882 }
883
884
885 func getRPCDeadlineTime(li *logInfo) time.Time {
886 return li.TimeSource.Now().Add(li.instanceOpts.Deadline)
887 }
888
889
890
891 func verifyAddChain(li *logInfo, req ct.AddChainRequest, expectingPrecert bool) ([]*x509.Certificate, error) {
892
893 validPath, err := ValidateChain(req.Chain, li.validationOpts)
894 if err != nil {
895
896
897 return nil, fmt.Errorf("chain failed to verify: %s", err)
898 }
899
900 isPrecert, err := IsPrecertificate(validPath[0])
901 if err != nil {
902 return nil, fmt.Errorf("precert test failed: %s", err)
903 }
904
905
906 if isPrecert != expectingPrecert {
907 if expectingPrecert {
908 klog.Warningf("%s: Cert (or precert with invalid CT ext) submitted as precert chain: %q", li.LogPrefix, req.Chain)
909 } else {
910 klog.Warningf("%s: Precert (or cert with invalid CT ext) submitted as cert chain: %q", li.LogPrefix, req.Chain)
911 }
912 return nil, fmt.Errorf("cert / precert mismatch: %T", expectingPrecert)
913 }
914
915 return validPath, nil
916 }
917
918 func extractRawCerts(chain []*x509.Certificate) []ct.ASN1Cert {
919 raw := make([]ct.ASN1Cert, len(chain))
920 for i, cert := range chain {
921 raw[i] = ct.ASN1Cert{Data: cert.Raw}
922 }
923 return raw
924 }
925
926
927
928 func buildLogLeafForAddChain(li *logInfo,
929 merkleLeaf ct.MerkleTreeLeaf, chain []*x509.Certificate, isPrecert bool,
930 ) (trillian.LogLeaf, error) {
931 raw := extractRawCerts(chain)
932 return util.BuildLogLeaf(li.LogPrefix, merkleLeaf, 0, raw[0], raw[1:], isPrecert)
933 }
934
935
936
937 func marshalAndWriteAddChainResponse(sct *ct.SignedCertificateTimestamp, signer crypto.Signer, w http.ResponseWriter) error {
938 logID, err := GetCTLogID(signer.Public())
939 if err != nil {
940 return fmt.Errorf("failed to marshal logID: %s", err)
941 }
942 sig, err := tls.Marshal(sct.Signature)
943 if err != nil {
944 return fmt.Errorf("failed to marshal signature: %s", err)
945 }
946
947 rsp := ct.AddChainResponse{
948 SCTVersion: sct.SCTVersion,
949 Timestamp: sct.Timestamp,
950 ID: logID[:],
951 Extensions: base64.StdEncoding.EncodeToString(sct.Extensions),
952 Signature: sig,
953 }
954
955 w.Header().Set(contentTypeHeader, contentTypeJSON)
956 jsonData, err := json.Marshal(&rsp)
957 if err != nil {
958 return fmt.Errorf("failed to marshal add-chain: %s", err)
959 }
960
961 _, err = w.Write(jsonData)
962 if err != nil {
963 return fmt.Errorf("failed to write add-chain resp: %s", err)
964 }
965
966 return nil
967 }
968
969 func parseGetEntriesRange(r *http.Request, maxRange, logID int64) (int64, int64, error) {
970 start, err := strconv.ParseInt(r.FormValue(getEntriesParamStart), 10, 64)
971 if err != nil {
972 return 0, 0, err
973 }
974
975 end, err := strconv.ParseInt(r.FormValue(getEntriesParamEnd), 10, 64)
976 if err != nil {
977 return 0, 0, err
978 }
979
980 if start < 0 || end < 0 {
981 return 0, 0, fmt.Errorf("start (%d) and end (%d) parameters must be >= 0", start, end)
982 }
983 if start > end {
984 return 0, 0, fmt.Errorf("start (%d) and end (%d) is not a valid range", start, end)
985 }
986
987 count := end - start + 1
988 if count > maxRange {
989 end = start + maxRange - 1
990 }
991 if *alignGetEntries && count >= maxRange {
992
993
994
995
996
997 d := (end + 1) % maxRange
998 end = end - d
999 alignedGetEntries.Inc(strconv.FormatInt(logID, 10), strconv.FormatBool(d == 0))
1000 }
1001
1002 return start, end, nil
1003 }
1004
1005 func parseGetEntryAndProofParams(r *http.Request) (int64, int64, error) {
1006 leafIndex, err := strconv.ParseInt(r.FormValue(getEntryAndProofParamLeafIndex), 10, 64)
1007 if err != nil {
1008 return 0, 0, err
1009 }
1010
1011 treeSize, err := strconv.ParseInt(r.FormValue(getEntryAndProofParamTreeSize), 10, 64)
1012 if err != nil {
1013 return 0, 0, err
1014 }
1015
1016 if treeSize <= 0 {
1017 return 0, 0, fmt.Errorf("tree_size must be > 0, got: %d", treeSize)
1018 }
1019 if leafIndex < 0 {
1020 return 0, 0, fmt.Errorf("leaf_index must be >= 0, got: %d", treeSize)
1021 }
1022 if leafIndex >= treeSize {
1023 return 0, 0, fmt.Errorf("leaf_index %d out of range for tree of size %d", leafIndex, treeSize)
1024 }
1025
1026 return leafIndex, treeSize, nil
1027 }
1028
1029 func parseGetSTHConsistencyRange(r *http.Request) (int64, int64, error) {
1030 firstVal := r.FormValue(getSTHConsistencyParamFirst)
1031 secondVal := r.FormValue(getSTHConsistencyParamSecond)
1032 if firstVal == "" {
1033 return 0, 0, errors.New("parameter 'first' is required")
1034 }
1035 if secondVal == "" {
1036 return 0, 0, errors.New("parameter 'second' is required")
1037 }
1038
1039 first, err := strconv.ParseInt(firstVal, 10, 64)
1040 if err != nil {
1041 return 0, 0, errors.New("parameter 'first' is malformed")
1042 }
1043
1044 second, err := strconv.ParseInt(secondVal, 10, 64)
1045 if err != nil {
1046 return 0, 0, errors.New("parameter 'second' is malformed")
1047 }
1048
1049 if first < 0 || second < 0 {
1050 return 0, 0, fmt.Errorf("first and second params cannot be <0: %d %d", first, second)
1051 }
1052 if second < first {
1053 return 0, 0, fmt.Errorf("invalid first, second params: %d %d", first, second)
1054 }
1055
1056 return first, second, nil
1057 }
1058
1059
1060
1061 func marshalGetEntriesResponse(li *logInfo, leaves []*trillian.LogLeaf) (ct.GetEntriesResponse, error) {
1062 jsonRsp := ct.GetEntriesResponse{}
1063
1064 for _, leaf := range leaves {
1065
1066
1067
1068
1069 var treeLeaf ct.MerkleTreeLeaf
1070 if rest, err := tls.Unmarshal(leaf.LeafValue, &treeLeaf); err != nil {
1071 klog.Errorf("%s: Failed to deserialize Merkle leaf from backend: %d", li.LogPrefix, leaf.LeafIndex)
1072 } else if len(rest) > 0 {
1073 klog.Errorf("%s: Trailing data after Merkle leaf from backend: %d", li.LogPrefix, leaf.LeafIndex)
1074 }
1075
1076 extraData := leaf.ExtraData
1077 if len(extraData) == 0 {
1078 klog.Errorf("%s: Missing ExtraData for leaf %d", li.LogPrefix, leaf.LeafIndex)
1079 }
1080 jsonRsp.Entries = append(jsonRsp.Entries, ct.LeafEntry{
1081 LeafInput: leaf.LeafValue,
1082 ExtraData: extraData,
1083 })
1084 }
1085
1086 return jsonRsp, nil
1087 }
1088
1089
1090
1091 func checkAuditPath(path [][]byte) bool {
1092 for _, node := range path {
1093 if len(node) != sha256.Size {
1094 return false
1095 }
1096 }
1097 return true
1098 }
1099
1100 func (li *logInfo) toHTTPStatus(err error) int {
1101 if li.instanceOpts.ErrorMapper != nil {
1102 if status, ok := li.instanceOpts.ErrorMapper(err); ok {
1103 return status
1104 }
1105 }
1106
1107 rpcStatus, ok := status.FromError(err)
1108 if !ok {
1109 return http.StatusInternalServerError
1110 }
1111
1112 switch rpcStatus.Code() {
1113 case codes.OK:
1114 return http.StatusOK
1115 case codes.Canceled, codes.DeadlineExceeded:
1116 return http.StatusGatewayTimeout
1117 case codes.InvalidArgument, codes.OutOfRange, codes.AlreadyExists:
1118 return http.StatusBadRequest
1119 case codes.NotFound:
1120 return http.StatusNotFound
1121 case codes.PermissionDenied, codes.ResourceExhausted:
1122 return http.StatusForbidden
1123 case codes.Unauthenticated:
1124 return http.StatusUnauthorized
1125 case codes.FailedPrecondition:
1126 return http.StatusPreconditionFailed
1127 case codes.Aborted:
1128 return http.StatusConflict
1129 case codes.Unimplemented:
1130 return http.StatusNotImplemented
1131 case codes.Unavailable:
1132 return http.StatusServiceUnavailable
1133 default:
1134 return http.StatusInternalServerError
1135 }
1136 }
1137
1138
1139
1140 func recordStartPercent(leafIndex int64, treeSize uint64, labelVals ...string) {
1141 if treeSize > 0 {
1142 percent := float64(leafIndex) / float64(treeSize) * 100.0
1143 getEntriesStartPercentiles.Observe(percent, labelVals...)
1144 }
1145 }
1146
View as plain text