github.com/arduino/arduino-cloud-cli@v0.0.0-20240517070944-e7a449561083/internal/binary/index.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 binary 19 20 import ( 21 "bytes" 22 "context" 23 "encoding/json" 24 "fmt" 25 "io/ioutil" 26 27 "compress/gzip" 28 29 "github.com/arduino/arduino-cloud-cli/internal/binary/gpgkey" 30 "golang.org/x/crypto/openpgp" 31 ) 32 33 const ( 34 // URL of cloud-team binary index. 35 IndexGZURL = "https://cloud-downloads.arduino.cc/binaries/index.json.gz" 36 IndexSigURL = "https://cloud-downloads.arduino.cc/binaries/index.json.sig" 37 ) 38 39 // Index contains details about all the binaries 40 // loaded in 'cloud-downloads'. 41 type Index struct { 42 Boards []IndexBoard `json:"boards"` 43 } 44 45 // IndexBoard describes all the binaries available for a specific board. 46 type IndexBoard struct { 47 FQBN string `json:"fqbn"` 48 Provision *IndexBin `json:"provision"` 49 } 50 51 // IndexBin contains the details needed to retrieve a binary file from the cloud. 52 type IndexBin struct { 53 URL string `json:"url"` 54 Checksum string `json:"checksum"` 55 Size json.Number `json:"size"` 56 } 57 58 // LoadIndex downloads and verifies the index of binaries 59 // contained in 'cloud-downloads'. 60 func LoadIndex(ctx context.Context) (*Index, error) { 61 indexGZ, err := download(ctx, IndexGZURL) 62 if err != nil { 63 return nil, fmt.Errorf("cannot download index: %w", err) 64 } 65 66 indexReader, err := gzip.NewReader(bytes.NewReader(indexGZ)) 67 if err != nil { 68 return nil, fmt.Errorf("cannot decompress index: %w", err) 69 } 70 index, err := ioutil.ReadAll(indexReader) 71 if err != nil { 72 return nil, fmt.Errorf("cannot read downloaded index: %w", err) 73 } 74 75 sig, err := download(ctx, IndexSigURL) 76 if err != nil { 77 return nil, fmt.Errorf("cannot download index signature: %w", err) 78 } 79 80 keyRing, err := openpgp.ReadKeyRing(bytes.NewReader(gpgkey.IndexPublicKey)) 81 if err != nil { 82 return nil, fmt.Errorf("cannot retrieve Arduino public GPG key: %w", err) 83 } 84 85 signer, err := openpgp.CheckDetachedSignature(keyRing, bytes.NewReader(index), bytes.NewReader(sig)) 86 if signer == nil || err != nil { 87 return nil, fmt.Errorf("invalid signature for index downloaded from %s", IndexGZURL) 88 } 89 90 i := &Index{} 91 if err = json.Unmarshal(index, &i); err != nil { 92 return nil, fmt.Errorf("cannot unmarshal index json: %w", err) 93 } 94 return i, nil 95 } 96 97 // FindProvisionBin looks for the provisioning binary corresponding 98 // to the passed fqbn in the index. 99 // Returns nil if the binary is not found. 100 func (i *Index) FindProvisionBin(fqbn string) *IndexBin { 101 for _, b := range i.Boards { 102 if b.FQBN == fqbn { 103 return b.Provision 104 } 105 } 106 return nil 107 }