...

Source file src/github.com/google/certificate-transparency-go/trillian/migrillian/core/trillian.go

Documentation: github.com/google/certificate-transparency-go/trillian/migrillian/core

     1  // Copyright 2018 Google LLC. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package core
    16  
    17  import (
    18  	"context"
    19  	"crypto/sha256"
    20  	"encoding/binary"
    21  	"errors"
    22  	"fmt"
    23  	"time"
    24  
    25  	ct "github.com/google/certificate-transparency-go"
    26  	"github.com/google/certificate-transparency-go/scanner"
    27  	"github.com/google/certificate-transparency-go/trillian/migrillian/configpb"
    28  	"github.com/google/certificate-transparency-go/x509"
    29  	"github.com/google/trillian"
    30  	"github.com/google/trillian/client/backoff"
    31  	"github.com/google/trillian/types"
    32  	"google.golang.org/grpc/codes"
    33  	"google.golang.org/grpc/status"
    34  	"k8s.io/klog/v2"
    35  )
    36  
    37  var errRetry = errors.New("retry")
    38  
    39  // PreorderedLogClient is a means of communicating with a single Trillian
    40  // pre-ordered log tree.
    41  type PreorderedLogClient struct {
    42  	cli    trillian.TrillianLogClient
    43  	treeID int64
    44  	idFunc func(int64, *ct.RawLogEntry) []byte
    45  	prefix string // TODO(pavelkalinnikov): Get rid of this.
    46  }
    47  
    48  // NewPreorderedLogClient creates and initializes a pre-ordered log client.
    49  func NewPreorderedLogClient(
    50  	cli trillian.TrillianLogClient,
    51  	tree *trillian.Tree,
    52  	idFuncType configpb.IdentityFunction,
    53  	prefix string,
    54  ) (*PreorderedLogClient, error) {
    55  	if tree == nil {
    56  		return nil, errors.New("missing Tree")
    57  	}
    58  	if got, want := tree.TreeType, trillian.TreeType_PREORDERED_LOG; got != want {
    59  		return nil, fmt.Errorf("tree %d is %v, want %v", tree.TreeId, got, want)
    60  	}
    61  	ret := PreorderedLogClient{cli: cli, treeID: tree.TreeId, prefix: prefix}
    62  
    63  	switch idFuncType {
    64  	case configpb.IdentityFunction_SHA256_CERT_DATA:
    65  		ret.idFunc = idHashCertData
    66  	case configpb.IdentityFunction_SHA256_LEAF_INDEX:
    67  		ret.idFunc = idHashLeafIndex
    68  	default:
    69  		return nil, fmt.Errorf("unknown identity function: %v", idFuncType)
    70  	}
    71  
    72  	return &ret, nil
    73  }
    74  
    75  // getRoot returns the current root of the Trillian tree.
    76  func (c *PreorderedLogClient) getRoot(ctx context.Context) (uint64, []byte, error) {
    77  	req := trillian.GetLatestSignedLogRootRequest{LogId: c.treeID}
    78  	rsp, err := c.cli.GetLatestSignedLogRoot(ctx, &req)
    79  	if err != nil {
    80  		return 0, nil, err
    81  	} else if rsp == nil || rsp.SignedLogRoot == nil {
    82  		return 0, nil, errors.New("missing SignedLogRoot")
    83  	}
    84  	var logRoot types.LogRootV1
    85  	if err := logRoot.UnmarshalBinary(rsp.SignedLogRoot.LogRoot); err != nil {
    86  		return 0, nil, err
    87  	}
    88  	return logRoot.TreeSize, logRoot.RootHash, nil
    89  }
    90  
    91  // addSequencedLeaves converts a batch of CT log entries into Trillian log
    92  // leaves and submits them to Trillian via AddSequencedLeaves API.
    93  //
    94  // If and while Trillian returns "quota exceeded" errors, the function will
    95  // retry the request with a limited exponential back-off.
    96  //
    97  // Returns an error if Trillian replies with a severe/unknown error.
    98  func (c *PreorderedLogClient) addSequencedLeaves(ctx context.Context, b *scanner.EntryBatch) error {
    99  	// TODO(pavelkalinnikov): Verify range inclusion against the remote STH.
   100  	leaves := make([]*trillian.LogLeaf, len(b.Entries))
   101  	for i, e := range b.Entries {
   102  		var err error
   103  		if leaves[i], err = c.buildLogLeaf(b.Start+int64(i), &e); err != nil {
   104  			return err
   105  		}
   106  	}
   107  	req := trillian.AddSequencedLeavesRequest{LogId: c.treeID, Leaves: leaves}
   108  
   109  	// TODO(pavelkalinnikov): Make this strategy configurable.
   110  	bo := backoff.Backoff{
   111  		Min:    1 * time.Second,
   112  		Max:    1 * time.Minute,
   113  		Factor: 3,
   114  		Jitter: true,
   115  	}
   116  
   117  	var err error
   118  	boerr := bo.Retry(ctx, func() error {
   119  		var rsp *trillian.AddSequencedLeavesResponse
   120  		rsp, err = c.cli.AddSequencedLeaves(ctx, &req)
   121  		switch status.Code(err) {
   122  		case codes.ResourceExhausted: // There was (probably) a quota error.
   123  			end := b.Start + int64(len(b.Entries))
   124  			klog.Errorf("%d: retrying batch [%d, %d) due to error: %v", c.treeID, b.Start, end, err)
   125  			return errRetry
   126  		case codes.OK:
   127  			if rsp == nil {
   128  				err = errors.New("missing AddSequencedLeaves response")
   129  			}
   130  			// TODO(pavelkalinnikov): Check rsp.Results statuses.
   131  			return nil
   132  		default: // There was another (probably serious) error.
   133  			return nil // Stop backing off, and return err as is below.
   134  		}
   135  	})
   136  	if err != nil {
   137  		// Return the more specific error if available
   138  		return err
   139  	}
   140  	// Return the timeout, or nil on success
   141  	return boerr
   142  }
   143  
   144  func (c *PreorderedLogClient) buildLogLeaf(index int64, entry *ct.LeafEntry) (*trillian.LogLeaf, error) {
   145  	rle, err := ct.RawLogEntryFromLeaf(index, entry)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	// Don't return on x509 parsing errors because we want to migrate this log
   151  	// entry as is. But log the error so that it can be flagged by monitoring.
   152  	if _, err = rle.ToLogEntry(); x509.IsFatal(err) {
   153  		klog.Errorf("%s: index=%d: x509 fatal error: %v", c.prefix, index, err)
   154  	} else if err != nil {
   155  		klog.Infof("%s: index=%d: x509 non-fatal error: %v", c.prefix, index, err)
   156  	}
   157  	// TODO(pavelkalinnikov): Verify cert chain if error is nil or non-fatal.
   158  
   159  	leafIDHash := c.idFunc(index, rle)
   160  	return &trillian.LogLeaf{
   161  		LeafValue:        entry.LeafInput,
   162  		ExtraData:        entry.ExtraData,
   163  		LeafIndex:        index,
   164  		LeafIdentityHash: leafIDHash[:],
   165  	}, nil
   166  }
   167  
   168  func idHashCertData(_ int64, entry *ct.RawLogEntry) []byte {
   169  	hash := sha256.Sum256(entry.Cert.Data)
   170  	return hash[:]
   171  }
   172  
   173  func idHashLeafIndex(index int64, _ *ct.RawLogEntry) []byte {
   174  	data := make([]byte, 8)
   175  	binary.LittleEndian.PutUint64(data, uint64(index))
   176  	hash := sha256.Sum256(data)
   177  	return hash[:]
   178  }
   179  

View as plain text