github.com/transparency-dev/armored-witness-applet@v0.1.1/trusted_applet/update.go (about)

     1  // Copyright 2023 The Armored Witness Applet 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  package main
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"io"
    22  	"net/http"
    23  	"net/url"
    24  	"os"
    25  	"time"
    26  
    27  	"github.com/machinebox/progress"
    28  	"github.com/transparency-dev/armored-witness-applet/trusted_applet/internal/update/rpc"
    29  	"github.com/transparency-dev/armored-witness-common/release/firmware"
    30  	"github.com/transparency-dev/armored-witness-common/release/firmware/ftlog"
    31  	"github.com/transparency-dev/armored-witness-common/release/firmware/update"
    32  	"github.com/transparency-dev/serverless-log/client"
    33  	"golang.org/x/mod/sumdb/note"
    34  	"k8s.io/klog/v2"
    35  )
    36  
    37  // These vars are set at compile time using the -X flag, see the Makefile.
    38  var (
    39  	updateBinariesURL                    string
    40  	updateLogURL                         string
    41  	updateLogOrigin                      string
    42  	updateLogVerifier                    string
    43  	updateAppletVerifier                 string
    44  	updateOSVerifier1, updateOSVerifier2 string
    45  )
    46  
    47  // updater returns an updater struct configured from the compiled-in
    48  // parameters above.
    49  func updater(ctx context.Context) (*update.Fetcher, *update.Updater, error) {
    50  	if updateLogURL[len(updateLogURL)-1] != '/' {
    51  		updateLogURL += "/"
    52  	}
    53  	logBaseURL, err := url.Parse(updateLogURL)
    54  	if err != nil {
    55  		return nil, nil, fmt.Errorf("firmware log URL invalid: %v", err)
    56  	}
    57  
    58  	logVerifier, err := note.NewVerifier(updateLogVerifier)
    59  	if err != nil {
    60  		return nil, nil, fmt.Errorf("invalid firmware log verifier: %v", err)
    61  	}
    62  	appletVerifier, err := note.NewVerifier(updateAppletVerifier)
    63  	if err != nil {
    64  		return nil, nil, fmt.Errorf("invalid applet verifier: %v", err)
    65  	}
    66  	osVerifier1, err := note.NewVerifier(updateOSVerifier1)
    67  	if err != nil {
    68  		return nil, nil, fmt.Errorf("invalid OS verifier 1: %v", err)
    69  	}
    70  	osVerifier2, err := note.NewVerifier(updateOSVerifier2)
    71  	if err != nil {
    72  		return nil, nil, fmt.Errorf("invalid OS verifier 2: %v", err)
    73  	}
    74  
    75  	if updateBinariesURL[len(updateBinariesURL)-1] != '/' {
    76  		updateBinariesURL += "/"
    77  	}
    78  	binBaseURL, err := url.Parse(updateBinariesURL)
    79  	if err != nil {
    80  		return nil, nil, fmt.Errorf("binaries URL invalid: %v", err)
    81  	}
    82  	bf := newFetcher(binBaseURL, 5*time.Minute, true)
    83  	binFetcher := func(ctx context.Context, r ftlog.FirmwareRelease) ([]byte, []byte, error) {
    84  		p, err := update.BinaryPath(r)
    85  		if err != nil {
    86  			return nil, nil, fmt.Errorf("BinaryPath: %v", err)
    87  		}
    88  		klog.Infof("Fetching %v bin from %q", r.Component, p)
    89  		// We don't auto-update the bootloader, so no need to fetch HAB signatures.
    90  		bin, err := bf(ctx, p)
    91  		return bin, nil, err
    92  	}
    93  
    94  	updateFetcher, err := update.NewFetcher(ctx,
    95  		update.FetcherOpts{
    96  			LogFetcher:     newFetcher(logBaseURL, 30*time.Second, false),
    97  			LogOrigin:      updateLogOrigin,
    98  			LogVerifier:    logVerifier,
    99  			BinaryFetcher:  binFetcher,
   100  			AppletVerifier: appletVerifier,
   101  			OSVerifiers:    [2]note.Verifier{osVerifier1, osVerifier2},
   102  			// Note that we leave BootVerifier and RecoveryVerifier unset as we
   103  			// cannot update those components.
   104  		})
   105  	if err != nil {
   106  		return nil, nil, fmt.Errorf("NewFetcher: %v", err)
   107  	}
   108  
   109  	fwVerifier := newFWVerifier(updateLogOrigin, logVerifier, appletVerifier, []note.Verifier{osVerifier1, osVerifier2})
   110  	updater, err := update.NewUpdater(&rpc.Client{}, updateFetcher, fwVerifier)
   111  	if err != nil {
   112  		return nil, nil, fmt.Errorf("NewUdater: %v", err)
   113  	}
   114  	return updateFetcher, updater, nil
   115  }
   116  
   117  type fwVerifier struct {
   118  	logOrigin            string
   119  	logVerifier          note.Verifier
   120  	appletBundleVerifier firmware.BundleVerifier
   121  	osBundleVerifier     firmware.BundleVerifier
   122  }
   123  
   124  func newFWVerifier(logOrigin string, logVerifier note.Verifier, appletVerifier note.Verifier, osVerifiers []note.Verifier) fwVerifier {
   125  	return fwVerifier{
   126  		logOrigin:   logOrigin,
   127  		logVerifier: logVerifier,
   128  		appletBundleVerifier: firmware.BundleVerifier{
   129  			LogOrigin:         logOrigin,
   130  			LogVerifer:        logVerifier,
   131  			ManifestVerifiers: []note.Verifier{appletVerifier},
   132  		},
   133  		osBundleVerifier: firmware.BundleVerifier{
   134  			LogOrigin:         logOrigin,
   135  			LogVerifer:        logVerifier,
   136  			ManifestVerifiers: osVerifiers,
   137  		},
   138  	}
   139  }
   140  
   141  func (fw fwVerifier) Verify(b firmware.Bundle) error {
   142  	allVerifiers := append(append([]note.Verifier{}, fw.appletBundleVerifier.ManifestVerifiers...), fw.osBundleVerifier.ManifestVerifiers...)
   143  	m, err := note.Open(b.Manifest, note.VerifierList(allVerifiers...))
   144  	if err != nil {
   145  		return fmt.Errorf("failed to open manifest: %v", err)
   146  	}
   147  	r := ftlog.FirmwareRelease{}
   148  	if err := json.Unmarshal([]byte(m.Text), &r); err != nil {
   149  		return fmt.Errorf("failed to unmarshal manifest: %v", err)
   150  	}
   151  	switch r.Component {
   152  	case ftlog.ComponentApplet:
   153  		_, err := fw.appletBundleVerifier.Verify(b)
   154  		return err
   155  	case ftlog.ComponentOS:
   156  		_, err := fw.osBundleVerifier.Verify(b)
   157  		return err
   158  	default:
   159  		return fmt.Errorf("non updatable component %q", r.Component)
   160  	}
   161  }
   162  
   163  // New creates a Fetcher for the log at the given root location.
   164  func newFetcher(root *url.URL, httpTimeout time.Duration, logProgress bool) client.Fetcher {
   165  	return func(ctx context.Context, p string) ([]byte, error) {
   166  		u, err := root.Parse(p)
   167  		if err != nil {
   168  			return nil, err
   169  		}
   170  		return readHTTP(ctx, u, httpTimeout, logProgress)
   171  	}
   172  }
   173  
   174  func readHTTP(ctx context.Context, u *url.URL, timeout time.Duration, logProgress bool) ([]byte, error) {
   175  	ctx, cancel := context.WithTimeout(ctx, timeout)
   176  	defer cancel()
   177  
   178  	req, err := http.NewRequest("GET", u.String(), nil)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  	// Clone DefaultClient and set a timeout.
   183  	dc := *http.DefaultClient
   184  	hc := &dc
   185  	hc.Timeout = timeout
   186  	resp, err := hc.Do(req.WithContext(ctx))
   187  	if err != nil {
   188  		return nil, fmt.Errorf("http.Client.Do(): %v", err)
   189  	}
   190  	switch resp.StatusCode {
   191  	case http.StatusNotFound:
   192  		klog.Infof("Not found: %q", u.String())
   193  		return nil, os.ErrNotExist
   194  	case http.StatusOK:
   195  		break
   196  	default:
   197  		return nil, fmt.Errorf("unexpected http status %q", resp.Status)
   198  	}
   199  	defer func() {
   200  		if err := resp.Body.Close(); err != nil {
   201  			klog.Errorf("resp.Body.Close(): %v", err)
   202  		}
   203  	}()
   204  
   205  	pr := progress.NewReader(resp.Body)
   206  	if logProgress && resp.ContentLength > 0 {
   207  		go func() {
   208  			progressChan := progress.NewTicker(ctx, pr, resp.ContentLength, 1*time.Second)
   209  			for p := range progressChan {
   210  				klog.Infof("Downloading %q: %d%%, %v remaining...", u.String(), int(p.Percent()), p.Remaining().Round(time.Second))
   211  			}
   212  		}()
   213  	}
   214  	b, err := io.ReadAll(pr)
   215  	if logProgress {
   216  		klog.Infof("Downloading %q: finished", u.String())
   217  	}
   218  
   219  	return b, nil
   220  }