github.com/transparency-dev/armored-witness-os@v0.1.3-0.20240514084412-27eef7325168/cmd/proofbundle/main.go (about)

     1  // Copyright 2023 The Armored Witness authors. 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  // The proofbundle tool builds serialised proof bundles for use when
    16  // embedding the appled into the OS build, only useful for development work.
    17  package main
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/gob"
    23  	"encoding/json"
    24  	"flag"
    25  	"fmt"
    26  	"io"
    27  	"net/http"
    28  	"net/url"
    29  	"os"
    30  
    31  	"github.com/transparency-dev/armored-witness-common/release/firmware"
    32  	"github.com/transparency-dev/armored-witness-common/release/firmware/ftlog"
    33  	"github.com/transparency-dev/merkle/rfc6962"
    34  	"github.com/transparency-dev/serverless-log/client"
    35  	"golang.org/x/mod/sumdb/note"
    36  	"k8s.io/klog"
    37  )
    38  
    39  var (
    40  	outputFile         = flag.String("output_file", "", "File to write the bundle to.")
    41  	logBaseURL         = flag.String("log_url", "", "Base URL for the firmware transparency log to use.")
    42  	logOrigin          = flag.String("log_origin", "", "FT log origin string.")
    43  	logPubKeyFile      = flag.String("log_pubkey_file", "", "File containing the FT log's public key in Note verifier format.")
    44  	appletFile         = flag.String("applet_file", "", "Applet firmware image to build bundle for.")
    45  	manifestFile       = flag.String("manifest_file", "", "Manifest to build a bundle for.")
    46  	manifestPubKeyFile = flag.String("manifest_pubkey_file", "", "File containing a Note verifier string to verify manifest signatures.")
    47  )
    48  
    49  func main() {
    50  	flag.Parse()
    51  	ctx := context.Background()
    52  
    53  	mv := verifierOrDie(*manifestPubKeyFile, "manifest")
    54  	manifest, _ := loadManifestOrDie(*manifestFile, mv)
    55  	fwBin, err := os.ReadFile(*appletFile)
    56  	if err != nil {
    57  		klog.Exitf("Failed to read applet %q: %v", *appletFile, err)
    58  	}
    59  
    60  	logFetcher := newFetcherOrDie(*logBaseURL)
    61  	logHasher := rfc6962.DefaultHasher
    62  	logVerifier := verifierOrDie(*logPubKeyFile, "log")
    63  	lst, err := client.NewLogStateTracker(
    64  		ctx,
    65  		logFetcher,
    66  		logHasher,
    67  		nil,
    68  		logVerifier,
    69  		*logOrigin,
    70  		client.UnilateralConsensus(logFetcher),
    71  	)
    72  	if _, _, _, err := lst.Update(ctx); err != nil {
    73  		klog.Exitf("Update: %v", err)
    74  	}
    75  
    76  	idx, err := client.LookupIndex(ctx, logFetcher, logHasher.HashLeaf(manifest))
    77  	if err != nil {
    78  		klog.Exitf("LookupIndex: %v", err)
    79  	}
    80  	klog.Infof("Found manifest at index %d", idx)
    81  
    82  	incP, err := lst.ProofBuilder.InclusionProof(ctx, idx)
    83  	if err != nil {
    84  		klog.Exitf("InclusionProof: %v", err)
    85  	}
    86  
    87  	bundle := firmware.Bundle{
    88  		Checkpoint:     lst.LatestConsistentRaw,
    89  		Index:          idx,
    90  		InclusionProof: incP,
    91  		Manifest:       manifest,
    92  		Firmware:       fwBin,
    93  	}
    94  	v := firmware.BundleVerifier{
    95  		LogOrigin:         *logOrigin,
    96  		LogVerifer:        logVerifier,
    97  		ManifestVerifiers: []note.Verifier{mv},
    98  	}
    99  	if _, err := v.Verify(bundle); err != nil {
   100  		klog.Exitf("Failed to verify proof bundle: %v", err)
   101  	}
   102  
   103  	// We don't want the firmware in the encoded config, we only
   104  	// needed it to verify the bundle above.
   105  	bundle.Firmware = nil
   106  	jsn, _ := json.MarshalIndent(&bundle, "", " ")
   107  	klog.Infof("ProofBundle:\n%s", string(jsn))
   108  
   109  	b := &bytes.Buffer{}
   110  	enc := gob.NewEncoder(b)
   111  	if enc.Encode(bundle); err != nil {
   112  		klog.Exitf("Failed to encode bundle: %v", err)
   113  	}
   114  
   115  	if err := os.WriteFile(*outputFile, b.Bytes(), 0o644); err != nil {
   116  		klog.Exitf("WriteFile: %v", err)
   117  	}
   118  
   119  	klog.Infof("Wrote %d bytes of proof bundle to %q", b.Len(), *outputFile)
   120  }
   121  
   122  // newFetcherOrDie creates a Fetcher for the log at the given root location.
   123  func newFetcherOrDie(logURL string) client.Fetcher {
   124  	root, err := url.Parse(logURL)
   125  	if err != nil {
   126  		klog.Exitf("Couldn't parse log_url: %v", err)
   127  	}
   128  
   129  	get := getByScheme[root.Scheme]
   130  	if get == nil {
   131  		klog.Exitf("Unsupported URL scheme %s", root.Scheme)
   132  	}
   133  
   134  	r := func(ctx context.Context, p string) ([]byte, error) {
   135  		u, err := root.Parse(p)
   136  		if err != nil {
   137  			return nil, err
   138  		}
   139  		return get(ctx, u)
   140  	}
   141  	return r
   142  }
   143  
   144  var getByScheme = map[string]func(context.Context, *url.URL) ([]byte, error){
   145  	"http":  readHTTP,
   146  	"https": readHTTP,
   147  	"file": func(_ context.Context, u *url.URL) ([]byte, error) {
   148  		return os.ReadFile(u.Path)
   149  	},
   150  }
   151  
   152  func readHTTP(ctx context.Context, u *url.URL) ([]byte, error) {
   153  	req, err := http.NewRequest("GET", u.String(), nil)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	resp, err := http.DefaultClient.Do(req.WithContext(ctx))
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	switch resp.StatusCode {
   162  	case http.StatusNotFound:
   163  		klog.Infof("Not found: %q", u.String())
   164  		return nil, os.ErrNotExist
   165  	case http.StatusOK:
   166  		break
   167  	default:
   168  		return nil, fmt.Errorf("unexpected http status %q", resp.Status)
   169  	}
   170  	defer func() {
   171  		if err := resp.Body.Close(); err != nil {
   172  			klog.Errorf("resp.Body.Close(): %v", err)
   173  		}
   174  	}()
   175  	return io.ReadAll(resp.Body)
   176  }
   177  
   178  func verifierOrDie(p string, thing string) note.Verifier {
   179  	vs, err := os.ReadFile(p)
   180  	if err != nil {
   181  		klog.Exitf("Failed to read %s pub key file %q: %v", thing, p, err)
   182  	}
   183  	v, err := note.NewVerifier(string(vs))
   184  	if err != nil {
   185  		klog.Exitf("Invalid %s note verifier string %q: %v", thing, vs, err)
   186  	}
   187  	return v
   188  }
   189  
   190  func loadManifestOrDie(p string, v note.Verifier) ([]byte, ftlog.FirmwareRelease) {
   191  	b, err := os.ReadFile(p)
   192  	if err != nil {
   193  		klog.Exitf("Failed to read manifest %q: %v", p, err)
   194  	}
   195  	n, err := note.Open(b, note.VerifierList(v))
   196  	if err != nil {
   197  		klog.Exitf("Failed to verify manifest: %v", err)
   198  	}
   199  	var fr ftlog.FirmwareRelease
   200  	if err := json.Unmarshal([]byte(n.Text), &fr); err != nil {
   201  		klog.Exitf("Invalid manifest contents %q: %v", n.Text, err)
   202  	}
   203  	return b, fr
   204  }