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 }