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 }