...

Source file src/github.com/google/certificate-transparency-go/internal/witness/cmd/client/main.go

Documentation: github.com/google/certificate-transparency-go/internal/witness/cmd/client

     1  // Copyright 2022 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  // client fetches and verifies new STHs for a set of logs from a single witness.
    16  package main
    17  
    18  import (
    19  	"context"
    20  	"crypto/x509"
    21  	"encoding/base64"
    22  	"encoding/json"
    23  	"flag"
    24  	"fmt"
    25  	"io"
    26  	"net/http"
    27  	"net/url"
    28  	"os"
    29  	"sync"
    30  	"time"
    31  
    32  	ct "github.com/google/certificate-transparency-go"
    33  	wit_api "github.com/google/certificate-transparency-go/internal/witness/api"
    34  	wh "github.com/google/certificate-transparency-go/internal/witness/client/http"
    35  	"github.com/google/certificate-transparency-go/internal/witness/verifier"
    36  	"github.com/google/certificate-transparency-go/loglist3"
    37  	"k8s.io/klog/v2"
    38  )
    39  
    40  var (
    41  	logList   = flag.String("log_list_url", "https://www.gstatic.com/ct/log_list/v3/log_list.json", "The location of the log list")
    42  	witness   = flag.String("witness_url", "", "The endpoint of the witness HTTP API")
    43  	witnessPK = flag.String("witness_pk", "", "The base64-encoded witness public key")
    44  	interval  = flag.Duration("poll", 10*time.Second, "How frequently to poll to get new witnessed STHs")
    45  )
    46  
    47  // WitnessSigVerifier verifies the witness' signature on a cosigned STH.
    48  type WitnessSigVerifier interface {
    49  	VerifySignature(cosigned wit_api.CosignedSTH) error
    50  }
    51  
    52  // Witness consists of the witness' URL and signature verifier.
    53  type Witness struct {
    54  	Client   *wh.Witness
    55  	Verifier WitnessSigVerifier
    56  }
    57  
    58  type ctLog struct {
    59  	id       string
    60  	name     string
    61  	wsth     *wit_api.CosignedSTH
    62  	verifier *ct.SignatureVerifier
    63  }
    64  
    65  func main() {
    66  	klog.InitFlags(nil)
    67  	flag.Parse()
    68  	if *witness == "" {
    69  		klog.Exit("--witness_url must not be empty")
    70  	}
    71  	if *witnessPK == "" {
    72  		klog.Exit("--witness_pk must not be empty")
    73  	}
    74  	ctx := context.Background()
    75  	// Set up the witness client.
    76  	wURL, err := url.Parse(*witness)
    77  	if err != nil {
    78  		klog.Exitf("Failed to parse witness URL: %v", err)
    79  	}
    80  	pk, err := ct.PublicKeyFromB64(*witnessPK)
    81  	if err != nil {
    82  		klog.Exitf("Failed to create witness public key: %v", err)
    83  	}
    84  	wv, err := verifier.NewWitnessVerifier(pk)
    85  	if err != nil {
    86  		klog.Exitf("Failed to create witness signature verifier: %v", err)
    87  	}
    88  	w := Witness{
    89  		Client: &wh.Witness{
    90  			URL: wURL,
    91  		},
    92  		Verifier: wv,
    93  	}
    94  	// Set up the log data.
    95  	ctLogs, err := populateLogs(*logList)
    96  	if err != nil {
    97  		klog.Exitf("Failed to set up log data: %v", err)
    98  	}
    99  	// Now poll the witness for each log.
   100  	wg := &sync.WaitGroup{}
   101  	for _, log := range ctLogs {
   102  		wg.Add(1)
   103  		go func(witness *Witness, log ctLog) {
   104  			defer wg.Done()
   105  			if err := log.getSTH(ctx, witness, *interval); err != nil {
   106  				klog.Errorf("getSTH: %v", err)
   107  			}
   108  		}(&w, log)
   109  	}
   110  	wg.Wait()
   111  }
   112  
   113  // populateLogs populates a list of ctLogs based on the log list.
   114  func populateLogs(logListURL string) ([]ctLog, error) {
   115  	u, err := url.Parse(logListURL)
   116  	if err != nil {
   117  		return nil, fmt.Errorf("failed to parse URL: %v", err)
   118  	}
   119  	body, err := readURL(u)
   120  	if err != nil {
   121  		return nil, fmt.Errorf("failed to get log list data: %v", err)
   122  	}
   123  	// Get data for all usable logs.
   124  	logList, err := loglist3.NewFromJSON(body)
   125  	if err != nil {
   126  		return nil, fmt.Errorf("failed to parse JSON: %v", err)
   127  	}
   128  	usable := logList.SelectByStatus([]loglist3.LogStatus{loglist3.UsableLogStatus})
   129  	var logs []ctLog
   130  	for _, operator := range usable.Operators {
   131  		for _, log := range operator.Logs {
   132  			logID := base64.StdEncoding.EncodeToString(log.LogID)
   133  			//logPK := base64.StdEncoding.EncodeToString(log.Key)
   134  			//pk, err := ct.PublicKeyFromB64(logPK)
   135  			pk, err := x509.ParsePKIXPublicKey(log.Key)
   136  			if err != nil {
   137  				return nil, fmt.Errorf("failed to create public key for %s: %v", log.Description, err)
   138  			}
   139  			v, err := ct.NewSignatureVerifier(pk)
   140  			if err != nil {
   141  				return nil, fmt.Errorf("failed to create signature verifier: %v", err)
   142  			}
   143  			l := ctLog{
   144  				id:       logID,
   145  				name:     log.Description,
   146  				verifier: v,
   147  			}
   148  			logs = append(logs, l)
   149  		}
   150  	}
   151  	return logs, nil
   152  }
   153  
   154  // getSTH gets cosigned STHs for a given log continuously from the witness,
   155  // returning only when the context is done.
   156  func (l *ctLog) getSTH(ctx context.Context, witness *Witness, interval time.Duration) error {
   157  	tik := time.NewTicker(interval)
   158  	defer tik.Stop()
   159  	for {
   160  		func() {
   161  			ctx, cancel := context.WithTimeout(ctx, interval)
   162  			defer cancel()
   163  
   164  			klog.V(2).Infof("Requesting STH for %s from witness", l.name)
   165  			if err := l.getOnce(ctx, witness); err != nil {
   166  				klog.Warningf("Failed to retrieve STH for %s: %v", l.name, err)
   167  			} else {
   168  				klog.Infof("Verified the STH for %s at size %d!", l.name, l.wsth.TreeSize)
   169  			}
   170  		}()
   171  
   172  		select {
   173  		case <-ctx.Done():
   174  			return ctx.Err()
   175  		case <-tik.C:
   176  		}
   177  	}
   178  }
   179  
   180  // getOnce gets a new cosigned STH once and verifies it, replacing the stored STH
   181  // in the case that it does verify.
   182  func (l *ctLog) getOnce(ctx context.Context, witness *Witness) error {
   183  	// Get and parse the latest cosigned STH from the witness.
   184  	var cSTH wit_api.CosignedSTH
   185  	sthRaw, err := witness.Client.GetLatestSTH(ctx, l.id)
   186  	if err != nil {
   187  		return fmt.Errorf("failed to get STH: %v", err)
   188  	}
   189  	if err := json.Unmarshal(sthRaw, &cSTH); err != nil {
   190  		return fmt.Errorf("failed to unmarshal STH: %v", err)
   191  	}
   192  	// First verify the witness signature(s).
   193  	if err := witness.Verifier.VerifySignature(cSTH); err != nil {
   194  		return fmt.Errorf("failed to verify witness signature: %v", err)
   195  	}
   196  	// Then verify the log signature.
   197  	plainSTH := cSTH.SignedTreeHead
   198  	if err := l.verifier.VerifySTHSignature(plainSTH); err != nil {
   199  		return fmt.Errorf("failed to verify log signature: %v", err)
   200  	}
   201  	l.wsth = &cSTH
   202  	return nil
   203  }
   204  
   205  var getByScheme = map[string]func(*url.URL) ([]byte, error){
   206  	"http":  readHTTP,
   207  	"https": readHTTP,
   208  	"file": func(u *url.URL) ([]byte, error) {
   209  		return os.ReadFile(u.Path)
   210  	},
   211  }
   212  
   213  // readHTTP fetches and reads data from an HTTP-based URL.
   214  func readHTTP(u *url.URL) ([]byte, error) {
   215  	resp, err := http.Get(u.String())
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  	defer resp.Body.Close()
   220  	return io.ReadAll(resp.Body)
   221  }
   222  
   223  // readURL fetches and reads data from an HTTP-based or filesystem URL.
   224  func readURL(u *url.URL) ([]byte, error) {
   225  	s := u.Scheme
   226  	queryFn, ok := getByScheme[s]
   227  	if !ok {
   228  		return nil, fmt.Errorf("failed to identify suitable scheme for the URL %q", u.String())
   229  	}
   230  	return queryFn(u)
   231  }
   232  

View as plain text