github.com/arduino/arduino-cloud-cli@v0.0.0-20240517070944-e7a449561083/command/device/provision.go (about) 1 // This file is part of arduino-cloud-cli. 2 // 3 // Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/) 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published 7 // by the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18 package device 19 20 import ( 21 "context" 22 "encoding/hex" 23 "fmt" 24 "path/filepath" 25 "strconv" 26 "time" 27 28 "github.com/arduino/arduino-cloud-cli/arduino" 29 "github.com/arduino/arduino-cloud-cli/internal/binary" 30 "github.com/arduino/arduino-cloud-cli/internal/serial" 31 "github.com/arduino/go-paths-helper" 32 iotclient "github.com/arduino/iot-client-go" 33 "github.com/sirupsen/logrus" 34 ) 35 36 // downloadProvisioningFile downloads and returns the absolute path 37 // of the provisioning binary corresponding to the passed fqbn. 38 func downloadProvisioningFile(ctx context.Context, fqbn string) (string, error) { 39 index, err := binary.LoadIndex(ctx) 40 if err != nil { 41 return "", err 42 } 43 bin := index.FindProvisionBin(fqbn) 44 if bin == nil { 45 return "", fmt.Errorf("provisioning binary for board %s not found", fqbn) 46 } 47 bytes, err := binary.Download(ctx, bin) 48 if err != nil { 49 return "", fmt.Errorf("downloading provisioning binary: %w", err) 50 } 51 52 // Save provision binary always in the same temporary folder to 53 // avoid wasting user's storage. 54 filename := filepath.Base(bin.URL) 55 path := paths.TempDir().Join("cloud-cli").Join(filename) 56 path.Parent().MkdirAll() 57 if err = path.WriteFile(bytes); err != nil { 58 return "", fmt.Errorf("writing provisioning binary: %w", err) 59 } 60 p, err := path.Abs() 61 if err != nil { 62 return "", fmt.Errorf("cannot retrieve absolute path of downloaded binary: %w", err) 63 } 64 return p.String(), nil 65 } 66 67 type certificateCreator interface { 68 CertificateCreate(ctx context.Context, id, csr string) (*iotclient.ArduinoCompressedv2, error) 69 } 70 71 // provision is responsible for running the provisioning 72 // procedures for boards with crypto-chip. 73 type provision struct { 74 arduino.Commander 75 cert certificateCreator 76 ser *serial.Serial 77 board *board 78 id string 79 } 80 81 // run provisioning procedure for boards with crypto-chip. 82 func (p provision) run(ctx context.Context) error { 83 bin, err := downloadProvisioningFile(ctx, p.board.fqbn) 84 if err != nil { 85 return err 86 } 87 88 // Try to upload the provisioning sketch 89 logrus.Infof("%s\n", "Uploading provisioning sketch on the board") 90 if err = sleepCtx(ctx, 500*time.Millisecond); err != nil { 91 return err 92 } 93 errMsg := "Error while uploading the provisioning sketch" 94 err = retry(ctx, 5, time.Millisecond*1000, errMsg, func() error { 95 //serialutils.Reset(dev.port, true, nil) 96 return p.UploadBin(ctx, p.board.fqbn, bin, p.board.address, p.board.protocol) 97 }) 98 if err != nil { 99 return err 100 } 101 102 // Try to connect to board through the serial port 103 logrus.Infof("%s\n", "Connecting to the board through serial port") 104 if err = sleepCtx(ctx, 1500*time.Millisecond); err != nil { 105 return err 106 } 107 p.ser = serial.NewSerial() 108 errMsg = "Error while connecting to the board" 109 err = retry(ctx, 5, time.Millisecond*1000, errMsg, func() error { 110 return p.ser.Connect(p.board.address) 111 }) 112 if err != nil { 113 return err 114 } 115 defer p.ser.Close() 116 logrus.Infof("%s\n\n", "Connected to the board") 117 118 // Wait some time before using the serial port 119 if err = sleepCtx(ctx, 2000*time.Millisecond); err != nil { 120 return err 121 } 122 123 // Send configuration commands to the board 124 if err = p.configBoard(ctx); err != nil { 125 return err 126 } 127 128 logrus.Infof("%s\n\n", "Device provisioning successful") 129 return nil 130 } 131 132 func (p provision) configBoard(ctx context.Context) error { 133 logrus.Info("Receiving the certificate") 134 csr, err := p.ser.SendReceive(ctx, serial.CSR, []byte(p.id)) 135 if err != nil { 136 return err 137 } 138 cert, err := p.cert.CertificateCreate(ctx, p.id, string(csr)) 139 if err != nil { 140 return err 141 } 142 143 logrus.Info("Requesting begin storage") 144 if err = p.ser.Send(ctx, serial.BeginStorage, nil); err != nil { 145 return err 146 } 147 148 s := strconv.Itoa(cert.NotBefore.Year()) 149 logrus.Info("Sending year: ", s) 150 if err = p.ser.Send(ctx, serial.SetYear, []byte(s)); err != nil { 151 return err 152 } 153 154 s = fmt.Sprintf("%02d", int(cert.NotBefore.Month())) 155 logrus.Info("Sending month: ", s) 156 if err = p.ser.Send(ctx, serial.SetMonth, []byte(s)); err != nil { 157 return err 158 } 159 160 s = fmt.Sprintf("%02d", cert.NotBefore.Day()) 161 logrus.Info("Sending day: ", s) 162 if err = p.ser.Send(ctx, serial.SetDay, []byte(s)); err != nil { 163 return err 164 } 165 166 s = fmt.Sprintf("%02d", cert.NotBefore.Hour()) 167 logrus.Info("Sending hour: ", s) 168 if err = p.ser.Send(ctx, serial.SetHour, []byte(s)); err != nil { 169 return err 170 } 171 172 s = strconv.Itoa(31) 173 logrus.Info("Sending validity: ", s) 174 if err = p.ser.Send(ctx, serial.SetValidity, []byte(s)); err != nil { 175 return err 176 } 177 178 logrus.Info("Sending certificate serial") 179 b, err := hex.DecodeString(cert.Serial) 180 if err != nil { 181 return fmt.Errorf("decoding certificate serial: %w", err) 182 } 183 if err = p.ser.Send(ctx, serial.SetCertSerial, b); err != nil { 184 return err 185 } 186 187 logrus.Info("Sending certificate authority key") 188 b, err = hex.DecodeString(cert.AuthorityKeyIdentifier) 189 if err != nil { 190 return fmt.Errorf("decoding certificate authority key id: %w", err) 191 } 192 if err = p.ser.Send(ctx, serial.SetAuthKey, b); err != nil { 193 return err 194 } 195 196 logrus.Info("Sending certificate signature") 197 b, err = hex.DecodeString(cert.SignatureAsn1X + cert.SignatureAsn1Y) 198 if err != nil { 199 err = fmt.Errorf("decoding certificate signature: %w", err) 200 return err 201 } 202 if err = p.ser.Send(ctx, serial.SetSignature, b); err != nil { 203 return err 204 } 205 206 if err := sleepCtx(ctx, 1*time.Second); err != nil { 207 return err 208 } 209 210 logrus.Info("Requesting end storage") 211 if err = p.ser.Send(ctx, serial.EndStorage, nil); err != nil { 212 return err 213 } 214 215 if err := sleepCtx(ctx, 2*time.Second); err != nil { 216 return err 217 } 218 219 logrus.Info("Requesting certificate reconstruction") 220 if err = p.ser.Send(ctx, serial.ReconstructCert, nil); err != nil { 221 return err 222 } 223 224 return nil 225 } 226 227 func retry(ctx context.Context, tries int, sleep time.Duration, errMsg string, fun func() error) error { 228 if err := ctx.Err(); err != nil { 229 return err 230 } 231 232 var err error 233 for n := 0; n < tries; n++ { 234 err = fun() 235 if err == nil { 236 break 237 } 238 logrus.Warningf("%s: %s: %s", errMsg, err.Error(), "\nTrying again...") 239 if err := sleepCtx(ctx, sleep); err != nil { 240 return err 241 } 242 } 243 return err 244 } 245 246 func sleepCtx(ctx context.Context, tm time.Duration) error { 247 select { 248 case <-ctx.Done(): 249 return ctx.Err() 250 case <-time.After(tm): 251 return nil 252 } 253 }