// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "context" "crypto/sha256" "errors" "fmt" ct "github.com/google/certificate-transparency-go" "github.com/google/trillian" "github.com/google/trillian/types" "google.golang.org/protobuf/encoding/prototext" "k8s.io/klog/v2" ) type contextKey string // remoteQuotaCtxKey is the key used to attach a Trillian quota user to // context.Context passed in to STH getters. var remoteQuotaCtxKey = contextKey("quotaUser") // MirrorSTHStorage provides STHs of a source log to be served from a mirror. type MirrorSTHStorage interface { // GetMirrorSTH returns an STH of TreeSize <= maxTreeSize. It does best // effort to maximize the returned STH's TreeSize and/or Timestamp. GetMirrorSTH(ctx context.Context, maxTreeSize int64) (*ct.SignedTreeHead, error) } // STHGetter provides latest STHs for a log. type STHGetter interface { // GetSTH returns the latest STH for the log, as required by the RFC-6962 // get-sth endpoint: https://tools.ietf.org/html/rfc6962#section-4.3. GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) } // FrozenSTHGetter is an STHGetter implementation returning a constant STH. type FrozenSTHGetter struct { sth *ct.SignedTreeHead } // GetSTH returns the frozen STH. func (sg *FrozenSTHGetter) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) { return sg.sth, nil } // LogSTHGetter is an STHGetter implementation for regular (non-mirror) logs, // i.e. logs that have their own key and actively sign STHs. type LogSTHGetter struct { li *logInfo cache SignatureCache } // GetSTH retrieves and builds a tree head structure for the given log. // nolint:staticcheck func (sg *LogSTHGetter) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) { currentRoot, err := getSignedLogRoot(ctx, sg.li.rpcClient, sg.li.logID, sg.li.LogPrefix) if err != nil { return nil, err } // Build the CT STH object, except the signature. sth := &ct.SignedTreeHead{ Version: ct.V1, TreeSize: uint64(currentRoot.TreeSize), Timestamp: uint64(currentRoot.TimestampNanos / 1000 / 1000), } // Note: The size was checked in getSignedLogRoot. copy(sth.SHA256RootHash[:], currentRoot.RootHash) // Add the signature over the STH contents. err = signV1TreeHead(sg.li.signer, sth, &sg.cache) if err != nil || len(sth.TreeHeadSignature.Signature) == 0 { return nil, fmt.Errorf("failed to sign tree head: %v", err) } return sth, nil } // MirrorSTHGetter is an STHGetter implementation for mirror logs. It assumes // no knowledge of the key, and returns STHs obtained from an external source // represented by the MirrorSTHStorage interface. type MirrorSTHGetter struct { li *logInfo st MirrorSTHStorage } // GetSTH returns a known source log's STH with as large TreeSize and/or // timestamp as possible, but such that TreeSize <= Trillian log size. This is // to ensure that the mirror doesn't expose a "future" state of the log before // it is properly stored in Trillian. func (sg *MirrorSTHGetter) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) { currentRoot, err := getSignedLogRoot(ctx, sg.li.rpcClient, sg.li.logID, sg.li.LogPrefix) if err != nil { return nil, err } sth, err := sg.st.GetMirrorSTH(ctx, int64(currentRoot.TreeSize)) // nolint:staticcheck if err != nil { return nil, err } // TODO(pavelkalinnikov): Check sth signature. // TODO(pavelkalinnikov): Check consistency between slr and sth. return sth, nil } // getSignedLogRoot obtains the latest LogRootV1 from Trillian log. // nolint:staticcheck func getSignedLogRoot(ctx context.Context, client trillian.TrillianLogClient, logID int64, prefix string) (*types.LogRootV1, error) { req := trillian.GetLatestSignedLogRootRequest{LogId: logID} if q := ctx.Value(remoteQuotaCtxKey); q != nil { quotaUser, ok := q.(string) if !ok { return nil, fmt.Errorf("incorrect quota value: %v, type %T", q, q) } req.ChargeTo = appendUserCharge(req.ChargeTo, quotaUser) } klog.V(2).Infof("%s: GetSTH => grpc.GetLatestSignedLogRoot %+v", prefix, prototext.Format(&req)) rsp, err := client.GetLatestSignedLogRoot(ctx, &req) klog.V(2).Infof("%s: GetSTH <= grpc.GetLatestSignedLogRoot err=%v", prefix, err) if err != nil { return nil, err } // Check over the response. slr := rsp.SignedLogRoot if slr == nil { return nil, errors.New("no log root returned") } klog.V(3).Infof("%s: GetSTH <= slr=%+v", prefix, slr) var currentRoot types.LogRootV1 if err := currentRoot.UnmarshalBinary(slr.GetLogRoot()); err != nil { return nil, fmt.Errorf("failed to unmarshal root: %v", slr) } if hashSize := len(currentRoot.RootHash); hashSize != sha256.Size { return nil, fmt.Errorf("bad hash size from backend expecting: %d got %d", sha256.Size, hashSize) } return ¤tRoot, nil } // DefaultMirrorSTHFactory creates DefaultMirrorSTHStorage instances. type DefaultMirrorSTHFactory struct{} // NewStorage creates a dummy STH storage. func (f DefaultMirrorSTHFactory) NewStorage(logID [sha256.Size]byte) (MirrorSTHStorage, error) { return DefaultMirrorSTHStorage{}, nil } // DefaultMirrorSTHStorage is a dummy STH storage that always returns an error. type DefaultMirrorSTHStorage struct{} // GetMirrorSTH returns an error. func (st DefaultMirrorSTHStorage) GetMirrorSTH(ctx context.Context, maxTreeSize int64) (*ct.SignedTreeHead, error) { return nil, errors.New("not implemented") }