github.com/google/trillian-examples@v0.0.0-20240520080811-0d40d35cef0e/binary_transparency/firmware/cmd/publisher/impl/publish.go (about) 1 // Copyright 2020 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 // Package impl is a the implementation of a tool to put firmware metadata into the log. 16 package impl 17 18 import ( 19 "context" 20 "crypto/sha512" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "net/url" 25 "os" 26 "time" 27 28 "github.com/golang/glog" 29 "github.com/google/trillian-examples/binary_transparency/firmware/api" 30 dummy_common "github.com/google/trillian-examples/binary_transparency/firmware/devices/dummy/common" 31 "github.com/google/trillian-examples/binary_transparency/firmware/devices/usbarmory" 32 "github.com/google/trillian-examples/binary_transparency/firmware/internal/client" 33 "github.com/google/trillian-examples/binary_transparency/firmware/internal/crypto" 34 "golang.org/x/mod/sumdb/note" 35 ) 36 37 // PublishOpts encapsulates parameters for the publish Main below. 38 type PublishOpts struct { 39 LogURL string 40 LogSigVerifier note.Verifier 41 DeviceID string 42 Revision uint64 43 BinaryPath string 44 Timestamp string 45 OutputPath string 46 } 47 48 // Main is the entrypoint for the implementation of the publisher. 49 func Main(ctx context.Context, opts PublishOpts) error { 50 logURL, err := url.Parse(opts.LogURL) 51 if err != nil { 52 return fmt.Errorf("LogURL is invalid: %w", err) 53 } 54 55 metadata, fw, err := createManifest(opts) 56 if err != nil { 57 return fmt.Errorf("failed to create manifest: %w", err) 58 } 59 60 glog.Infof("Measurement: %x", metadata.ExpectedFirmwareMeasurement) 61 62 js, err := createStatementJSON(metadata) 63 if err != nil { 64 return fmt.Errorf("failed to marshal statement: %w", err) 65 } 66 67 c := &client.SubmitClient{ 68 ReadonlyClient: &client.ReadonlyClient{ 69 LogURL: logURL, 70 LogSigVerifier: opts.LogSigVerifier, 71 }, 72 } 73 74 initialCP, err := c.GetCheckpoint() 75 if err != nil { 76 return fmt.Errorf("failed to get a pre-submission checkpoint from log: %w", err) 77 } 78 79 glog.Info("Submitting entry...") 80 if err := c.PublishFirmware(js, fw); err != nil { 81 return fmt.Errorf("couldn't submit statement: %w", err) 82 } 83 84 glog.Info("Successfully submitted entry, waiting for inclusion...") 85 cp, consistency, ip, err := client.AwaitInclusion(ctx, c.ReadonlyClient, *initialCP, js) 86 if err != nil { 87 glog.Errorf("Failed while waiting for inclusion: %v", err) 88 glog.Warningf("Failed checkpoint: %s", cp) 89 glog.Warningf("Failed consistency proof: %x", consistency) 90 glog.Warningf("Failed inclusion proof: %x", ip) 91 return fmt.Errorf("bailing: %w", err) 92 } 93 94 glog.Infof("Successfully logged %s", js) 95 96 if len(opts.OutputPath) > 0 { 97 glog.Infof("Creating update package file %q...", opts.OutputPath) 98 pb, err := json.Marshal( 99 api.ProofBundle{ 100 ManifestStatement: js, 101 Checkpoint: cp.Envelope, 102 InclusionProof: ip, 103 }) 104 if err != nil { 105 return fmt.Errorf("failed to marshal ProofBundle: %w", err) 106 } 107 108 bundle := api.UpdatePackage{ 109 FirmwareImage: fw, 110 ProofBundle: pb, 111 } 112 113 f, err := os.OpenFile(opts.OutputPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) 114 if err != nil { 115 return fmt.Errorf("failed to create output package file %q: %w", opts.OutputPath, err) 116 } 117 defer func() { 118 if err := f.Close(); err != nil { 119 glog.Errorf("f.Close(): %v", err) 120 } 121 }() 122 123 if err := json.NewEncoder(f).Encode(bundle); err != nil { 124 return fmt.Errorf("failed to encode output package JSON: %w", err) 125 } 126 glog.Infof("Successfully created update package file %q", opts.OutputPath) 127 } 128 return nil 129 } 130 131 func createManifest(opts PublishOpts) (api.FirmwareMetadata, []byte, error) { 132 var measure func([]byte) ([]byte, error) 133 switch opts.DeviceID { 134 case "armory": 135 measure = usbarmory.ExpectedMeasurement 136 case "dummy": 137 measure = dummy_common.ExpectedMeasurement 138 default: 139 return api.FirmwareMetadata{}, nil, errors.New("DeviceID must be one of: 'dummy', 'armory'") 140 } 141 142 fw, err := os.ReadFile(opts.BinaryPath) 143 if err != nil { 144 return api.FirmwareMetadata{}, nil, fmt.Errorf("failed to read %q: %w", opts.BinaryPath, err) 145 } 146 147 h := sha512.Sum512(fw) 148 149 m, err := measure(fw) 150 if err != nil { 151 return api.FirmwareMetadata{}, nil, fmt.Errorf("failed to calculate expected measurement for firmware: %w", err) 152 } 153 154 buildTime := opts.Timestamp 155 if buildTime == "" { 156 buildTime = time.Now().Format(time.RFC3339) 157 } 158 metadata := api.FirmwareMetadata{ 159 DeviceID: opts.DeviceID, 160 FirmwareRevision: opts.Revision, 161 FirmwareImageSHA512: h[:], 162 ExpectedFirmwareMeasurement: m, 163 BuildTimestamp: buildTime, 164 } 165 166 return metadata, fw, nil 167 } 168 169 func createStatementJSON(m api.FirmwareMetadata) ([]byte, error) { 170 js, err := json.Marshal(m) 171 if err != nil { 172 return nil, fmt.Errorf("failed to marshal metadata: %w", err) 173 } 174 sig, err := crypto.Publisher.SignMessage(api.FirmwareMetadataType, js) 175 if err != nil { 176 return nil, fmt.Errorf("failed to generate signature: %w", err) 177 } 178 179 statement := api.SignedStatement{ 180 Type: api.FirmwareMetadataType, 181 Statement: js, 182 Signature: sig, 183 } 184 185 return json.Marshal(statement) 186 }