Skip to content

Commit 1ff9f25

Browse files
committed
factor out index handling of the index to a separate package
the `.arduino-create` dir is now created by the `index.Init()` func
1 parent 9813a7e commit 1ff9f25

8 files changed

+195
-123
lines changed

tools/hidefile_linux.go renamed to index/hidefile_default.go

+5-15
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,11 @@
1313
// You should have received a copy of the GNU Affero General Public License
1414
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1515

16-
package tools
16+
//go:build !windows
1717

18-
import (
19-
"os/exec"
20-
)
18+
package index
2119

22-
func hideFile(path string) {
23-
24-
}
25-
26-
// TellCommandNotToSpawnShell will now spawn a shell
27-
func TellCommandNotToSpawnShell(_ *exec.Cmd) {
28-
}
29-
30-
// MessageBox will open a dialog
31-
func MessageBox(title, text string) int {
32-
return 6
20+
// hideFile will do nothing in this platform
21+
func hideFile(path string) error {
22+
return nil
3323
}

index/hidefile_windows.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2022 Arduino SA
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU Affero General Public License as published
5+
// by the Free Software Foundation, either version 3 of the License, or
6+
// (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU Affero General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Affero General Public License
14+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
16+
package index
17+
18+
import (
19+
"syscall"
20+
)
21+
22+
// hideFile will set the hidden attribute on the file
23+
func hideFile(path string) error {
24+
filenameW, err := syscall.UTF16PtrFromString(path)
25+
if err != nil {
26+
return err
27+
}
28+
err = syscall.SetFileAttributes(filenameW, syscall.FILE_ATTRIBUTE_HIDDEN)
29+
if err != nil {
30+
return err
31+
}
32+
return nil
33+
}

index/index.go

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright 2022 Arduino SA
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU Affero General Public License as published
5+
// by the Free Software Foundation, either version 3 of the License, or
6+
// (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU Affero General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Affero General Public License
14+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
16+
package index
17+
18+
import (
19+
"bytes"
20+
"encoding/hex"
21+
"io"
22+
"log"
23+
"net/http"
24+
"net/url"
25+
"path"
26+
"time"
27+
28+
"github.com/arduino/go-paths-helper"
29+
"golang.org/x/crypto/openpgp"
30+
)
31+
32+
// IndexResource represent the index of the system
33+
type IndexResource struct {
34+
LastRefresh time.Time // Last time the index was downloaded
35+
IndexURL url.URL // The URL used to host the index.json
36+
IndexFile paths.Path // The location of the index on the filesystem
37+
IndexSignature paths.Path // The location of the signature on the filesystem
38+
}
39+
40+
// gpg --export YOURKEYID --export-options export-minimal,no-export-attributes | hexdump /dev/stdin -v -e '/1 "%02X"'
41+
var publicKeyHex string = "99020D0452FAA2FA011000D0C5604932111750628F171E4E612D599ABEA8E4309888B9B9E87CCBD3AAD014B27454B0AF08E7CDD019DA72D492B6CF882AD7FA8571E985C538582DA096C371E7FCD95B71BC00C0E92BDDC26801F1B11C86814E0EA849E5973F630FC426E6A5F262C22986CB489B5304005202BA729D519725E3E6042C9199C8ECE734052B7376CF40A864679C3594C93203EBFB3F82CD42CD956F961792233B4C7C1A28252360F48F1D6D8662F2CF93F87DB40A99304F61828AF8A3EB07239E984629DC0B1D5C6494C9AFB5C8F8B9A53F1324C254A1AEA4CD9219AB4DF8667653AC9A6E76C3DB37CE8F60B546F78ECA43A90CB82A2278A291D2E98D66753B56F0595280C6E33B274F631846806D97447DD5C9438E7EC85779D9FA2173E088CE6FA156E291FAFD432C4FC2B1EB251DAFD13C721FF6618F696B77C122FB75E3CBCB446FAAA7FFFDD071C81C6C3D2360D495964C623617352915BBB30FA7C903EA096BF01E77FC84C8B51C02EB11BC9F03F19C7E81254F1F786E64A7A9F7F438873583CFA40F49C0F041432EAECCEC7EE9BA465A30320306F0E2E65EBE01E533CBBD8B1C1C04222213D5D05F4B689193DB60A68A6F1FC8B2ADD9E58A104E482AAD3869CCC42236EDC9CBE8561E105837AB6F7A764DCE5D8CB62608E8133F0FDD5F8FAFBE3BC57EE551ADC7386AADD443B331716EC032ACF9C639BF9DFE62301D4F197221F10DEF0011010001B42041726475696E6F204C4C43203C737570706F72744061726475696E6F2E63633E890238041301020022050252FAA2FA021B03060B090807030206150802090A0B0416020301021E01021780000A09107BAF404C2DFAB4AEF8090FFE20C3B36BF786D692969DA2ECFD7BCA3961E735D3CBB5585D7AB04BB8A0B64B64528ED76DB4752FA24523AA1E07B69A6A66CDDAE074A6A572800228194DD5916A956BF22606D866C7FD81F32878E06FEC200DDB0703D805E1A61006EB0B5BDB3AA89C095BB259BD93C7AAE8BDB18468A6DBE30F85BD6A3271F5456EB22BC2BCE99DB3A054D9BCA8F562C01B99E6BF4C2136B62771EEF54CB2AE95F8E2FE543284C37EB77E5104D49939ABAEF323CA5F1A66CA48ED423DBB3A2CFF12792CCA71ACD1E3032186CC7D05A13E0D66A3258E443527AAF921B7EA70C6CC10E2A51FCAB4DD130A10D3D29B1B01FB4207EF6501D3A9186BDB652ECCC9F354599A114DD3F80F9ED3493AC51A5C4F1F3BB59049EE7EC61411E90E02F27789E87B18A860551DFDFFA870E8542F6128E167CE1875C5C5B1128259347B85265487006B173AA631F1CDA1EDC68C54978E1D0FE3B310CC0F49F9AE84F37B1472437B69DA125BAFDC99AE57C2245F70747E1EFD52849C40469247CF13CB679A31AF4700468E09ED1ECFE5A53F67C80C48A0B0C1334FAE9650584DFD406ADA30FFBEED659256D40924432B029BBB24CEF22195D389381F0B1EB964C6494942335E74A373D869D1FB0C7967F30F79D71AB06929CEBB660514C2567284BD9EC32470B263539B3AFF5D3FBA9A275D4665E6B502B4031B63F511C1DFDD16B617A6FB046FCEB018A7A01CEFB9020D0452FAA2FA011000D6DE1747395EB3836103D30FA5CF555F6FBC982FB8B0FD72389CD6E99A88ACA1BCBD8BAD35211929AB5AB7F656BA1AFFA8C9A5AF83436FC8FE36AB403453E3E6EC679371AD81657FA1506956B1165D8887E3FB7EF366EFCCA82EE543E0B22170D0164A6702EF5280398A901CB6262E63C0AE378FD8CA1957EEED9CE48AA3D481BD117A2CA0341C3E16FE20CB6A5C3130A19B364F656CDC45E2216DE7ACFAD429967D71D101CADE10BA64F4075801ED2E9E3A3293114543456A26236CCA459DC7700D2E9C692BADCA9BA0CDE7189CD594B20CA4D1F20A70B02B9B50F70CFC6F7697B1D500702CE29492C7CD28C5D555475788DDE57482BC39E8465A720E25866AC931D5D7030AB61136BF702B25BC850A5089D1E6F0F68B8AE894ADFC3C92BB836888E3DB5A940426DBE7BBC5BDD3DDD6F5123627D1CE6FD1845CC66A920094391BE783069CB05746C0A55DAFC869FDAF0A08F81099E4F4CD07D05C7269C538C341CF1EDB94114B8CD97B44214EA58EEDB93FAB772013A1D77A08B9208082F9617A6CFE39B56F0078406C6267ABF5CF1078C49B1AB9B60EA1451351CF889EF72D7D696B23B22F753B28979AF10237B579A350FA5596A3B22244FA91402562AE530E814EF19A9E3448F465F78C16220DE0663F7B97C7F0EF1629E2F64A76B21BB695A3DE505B22B09B3459A3CE2180424BD67C8482EBD5EBC8128F98634EEE8707001101000189021F041801020009050252FAA2FA021B0C000A09107BAF404C2DFAB4AE050B1000C1434E8CC0D6F8E60E2FB091AA5EA04E7612B29D3823E09914F704DE1835A7B202D3F619183BD3A16439BFA31A6AF342672E8F59184333C4F56D18AF3B7CE8326F655F7C8DD1D4B38A1964E6A4D7550D159CE1B5EC44BC2091B1097CABE724C0E8C4942C2CF82672E3F209322270D133313CF601E07756B705946A45235DAF7294BCD34292D989EFDFDA2F46AF0AEAEC72F55DC6B2940C7C6A409B7FAD3354D5CA30C3E4EE29F9218A56EF8D7FBA2A7BB8E6304110A21DF0C847C4B761CDE408CE156D53091535A800C1C522CA33C71105B11550A145FD0E41B464146B46D46F08DFAEF9B03D313D54A1E4A82E8749895AB78521DAA8E66EEF6F7B17A0CA4B4CBFCB937713B9806269556EBD88AE87996EFAC0846ACBA0D3412FC0A5E90923C261CD443E4D6C1AE93D83166937C5F606A14FD73DB4919A0ED416D4B3163420F57FACCE9C9347BD5501BE3FC830472B64068E5FF5B09E7425030625246720D21608DEE829F84E8365527F764C91DA93372C72AA4054B458104CAFC2BDCED63DC80F36E7BD4BE0D3A19E20E3FED90F80F9E1584853B971B8E847C27027123B9AA19C3E90B41B3A643D3D5BE2FC134ADA8396D072D37E7101B64CE83E1802D0D5DDA9150B6C21564987950C9601FC2147F139C7A9906640A0883981B452F25AF7A0F32FAA2148ECDD9B04B93AFCED00F11AA0E6695C2F92676B8DB9E93172FD7779B9020D04530B05A7011000CAA1A8FF4BF9D0F0AC9EDBCA3B4D26E3E569DFEA04341F3E6ACE23AE5D87D62C2600DFF10B106144A1B52FF8B695A590D65C681F69DEE454800C235160EBE3FC1436193E1278D56C86E2BBB2187BEAAC1E1D04D40A392B1457471D10A2B6BF39CDF35D1A090A9406BCB06BDEF83A12A490C5E17D68884AD2686042669E2B458AD3CC0377DDA9C58D7070CE29A53E0E7C876D61B29A2DE2A9D73F914D0FF3B0E35E2ED361B60A8C3C3D4C7E77E17A939283BFDA2EC5725A2BFAAC18C6A64ACBEC776760D7086EA42BD93031E8B59FB8DFEFF77E5F80DBEB84ADE74B3A6F9E4D0F3140A8D0F576ED00548883C85271AA7F2450D1061F56CB839786038861D5A2473B7F58EBC00D2BB9EFEB1A2DF612A7B9087C326FBB08F2879102253316784272967A886089D61D5AB0FDB33737D35F27C2886ABB4D4E88F541D0BBAD04AEF7BD3ED66A1282B762BD6F8EEDC3760773B157C1A2D4E4586E43B28879C54E7599F9A34E1524E6E7F9B8EA13CC5A2DF5C1920AF74833EDDEC8EB9A8BE33196702DFD656D81ACBBFE3A10DA882EAA3065D9C9476C0A7B66C15D0063CB7AD1A2EB31537CB443F21B81642436943FE6C45E6AF9C2B595D4DFCB64B83F2CA6B4DD536726C6EC4761A340C18E32B2D7210640B9AB1D8E2165C0DD38BC9FD9DB6A30B380DF08C3F10002A6636FDC79CD2312B606F5F116AC665618A56BBE46C494FC7E23C7001101000189043E0418010200090502530B05A7021B02022909107BAF404C2DFAB4AEC15D200419010200060502530B05A7000A091024A26BAD7F29429187700FFE30ED1B7C96B3846AC7B363F9602D2886F7913A9C451C31E043AD75597024D460B59E6A60A6EE3D58E656901237A2465F8402169A816B38170AF550284EB420B7E827386D66852D68125A27FA6770F139EE7FCAEF43000673B7C7D168614877603C875AD593E333AE9237DB77065FB8375CE98FA1BF7FB1733034AAC61F1D23A3EFF8665702C10968C7991458F88D151B3448C7D9334059431A63D30A9C8E636A99D88DA8DB04CB8C64F1183AC873FF0942EF9555B6B3F192AD5F221AC9737F875CCAE21E88EC45CB35E40C0FF1AAF0A8FE44876D93A930A03CC4846A29102C956F39F2AC5808CCBCD7F4868A8E8E8B9A66EA18C275CEF9C371AB0592796ED57D757A3BAB31FF8E3887F6041E61BDA433E7D68CB2D5F28E81F57843D5032D73BF67119C137FC4CE8BEF4F705D690E47A530B1A85B8B6A09A4AE16A2973C11D69031B89BE92B0751DB7FE74F6F1C219C8B93E5C68EC1403856DF28E96E27737A7FB9C80F6EE9EC485A0609DC4EB8DF444F61C76A97F32ADFA2D8B4784DF3ABA4DE1B57894B9CF89934A143451308D73CF79ECC8BF382B8A34F24DC335238D8353767B363F5432D9A81C84F7D2FAB6E36E7188FA911120A905C67342A996251EBECAC13BD543A9B3C2C063AE294FDD15C66D5DD9224F3E936325F525700F2129D0B31CE8CCD4EBA5DEDB89F0A2BFC0C43E732F695161E4F33CE5DED14B1E98654547B110FFF7CBC2BA513721A96DD18964635069343FA8EEF4D492BFA55C930F9C78DF1F7454F1BDD40F4B04BDE9F9B9A9923A303D96D0CBFA361921AFEF13AED098D0CF70E84C0DDB20C58821351D2359B131671AAF5D2484717A4CAF385DB0CC19FBC37A3FC04F4F387D6934C1E84B9C1291231A14F69A1BF6708875C7DE00E3EFE3C7855A2459C96245C5F0D21FC00E87A0C18F80A3B79C0E28EA27493309C535254421BE7CDFBEFB5B44DAEA56B6859430FCCBEE766048F891AD5CB503866B98E521ED69B37E4165012A45E29836E2A0380728C1108E4C8A32EA186E1A855F78DA5506B6CF86DB888A87FAB6E15A90E3416469522DF5BD8872D729B35E6D82C974CD80076C26008015AB216C83FAF64E488F07D2BD01F51B0963F87BE0AB8392B442227BF7215148038B0C55189024D7C1B032DB1B3C56C66953E530C5B323634FC584A476CAD285EF1108011D14D9D180A75A9DFC936AFC7EF9E6C3F3CFEDD894894CE60358E7156B3A65ED7644DEA343A133F5D4DE4D33B74281086A0C20515AC4151CFED93C56DD574E578FDEE72C4115C25CAEC5EAD97C147F27F4EAE67FEFFEA0DC1CDF5D636AC331CB74DF477C9C3B3706F9DAF50C2E13AC8DE8CC9DD3C79E59EC779EE489D915CF22FDC53E3B3C7710FE8368DF11B9ACDF5F3CAE1F43CB7312E5E9F57F248692B3681CBA3E49207878FD33ED2A47CE9CE9B4E4A6EFD8F0AD2CD"
42+
43+
// Init will initialize the IndexResource structure and will return it.
44+
// It will take indexString as a paramenter.
45+
func Init(indexString string, directory *paths.Path) IndexResource {
46+
if directory == nil {
47+
log.Fatalf("configuration directory not provided")
48+
}
49+
if !directory.Exist() {
50+
err := directory.Mkdir()
51+
if err != nil {
52+
log.Fatalf("cannot create config dir: %s", err)
53+
}
54+
err = hideFile(directory.String())
55+
if err != nil {
56+
log.Printf("cannot make config dir hidden: %s", err)
57+
}
58+
}
59+
indexParsed, err := url.Parse(indexString)
60+
if err != nil {
61+
log.Fatalf("cannot parse provided index: %s", indexString)
62+
}
63+
64+
indexFile := path.Base(indexParsed.Path) // == package_index.json
65+
signatureFile := indexFile + ".sig"
66+
67+
var ir = IndexResource{
68+
IndexURL: *indexParsed,
69+
IndexFile: *directory.Join(indexFile),
70+
IndexSignature: *directory.Join(signatureFile),
71+
}
72+
73+
if !ir.IndexFile.Exist() || time.Since(ir.LastRefresh) > 1*time.Hour {
74+
// Download the file again and save it
75+
if err := ir.DownloadAndVerify(); err != nil {
76+
log.Fatalf("cannot download index: %s", err)
77+
}
78+
}
79+
80+
return ir
81+
}
82+
83+
// DownloadAndVerify will download an index file located at IndexURL and verify the signature
84+
// if everything matches the files are overwritten
85+
func (ir *IndexResource) DownloadAndVerify() error {
86+
// Fetch the index
87+
resp, err := http.Get(ir.IndexURL.String())
88+
if err != nil {
89+
return err
90+
}
91+
defer resp.Body.Close()
92+
93+
// Read the index body
94+
body, err := io.ReadAll(resp.Body)
95+
if err != nil {
96+
return err
97+
}
98+
99+
// Fetch the signature
100+
signature, err := http.Get(ir.IndexURL.String() + ".sig")
101+
if err != nil {
102+
return err
103+
}
104+
defer signature.Body.Close()
105+
106+
// Read the signature body
107+
signatureBody, err := io.ReadAll(signature.Body)
108+
if err != nil {
109+
return err
110+
}
111+
112+
err = checkGPGSig(bytes.NewReader(body), bytes.NewReader(signatureBody))
113+
if err != nil {
114+
return err
115+
}
116+
117+
// we overwrite the files if the signature is valid
118+
ir.IndexFile.WriteFile(body)
119+
ir.IndexSignature.WriteFile(signatureBody)
120+
121+
ir.LastRefresh = time.Now()
122+
123+
return nil
124+
}
125+
126+
// checkGPGSign takes a signed io.Reader and a detached signature io.Reader
127+
// and returns if the signature is valid
128+
func checkGPGSig(signed, signature io.Reader) error {
129+
publicKeyBin, err := hex.DecodeString(publicKeyHex)
130+
if err != nil {
131+
return err
132+
}
133+
keyring, _ := openpgp.ReadKeyRing(bytes.NewReader(publicKeyBin))
134+
135+
_, err = openpgp.CheckDetachedSignature(keyring, signed, signature)
136+
return err
137+
}

main.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
cert "github.com/arduino/arduino-create-agent/certificates"
3737
"github.com/arduino/arduino-create-agent/config"
3838
"github.com/arduino/arduino-create-agent/globals"
39+
"github.com/arduino/arduino-create-agent/index"
3940
"github.com/arduino/arduino-create-agent/systray"
4041
"github.com/arduino/arduino-create-agent/tools"
4142
"github.com/arduino/arduino-create-agent/updater"
@@ -48,9 +49,9 @@ import (
4849
)
4950

5051
var (
51-
version = "x.x.x-dev" //don't modify it, Jenkins will take care
52-
commit = "xxxxxxxx" //don't modify it, Jenkins will take care
53-
port string
52+
version = "x.x.x-dev" //don't modify it, Jenkins will take care
53+
commit = "xxxxxxxx" //don't modify it, Jenkins will take care
54+
port string
5455
portSSL string
5556
)
5657

@@ -98,6 +99,7 @@ var homeTemplateHTML string
9899
var (
99100
Tools tools.Tools
100101
Systray systray.Systray
102+
Index index.IndexResource
101103
)
102104

103105
type logWriter struct{}
@@ -175,10 +177,13 @@ func loop() {
175177
os.Exit(0)
176178
}
177179

180+
// Instantiate Index
181+
Index = index.Init(*indexURL, config.GetDataDir())
182+
178183
// Instantiate Tools
179184
Tools = tools.Tools{
180185
Directory: config.GetDataDir().String(),
181-
IndexURL: *indexURL,
186+
Index: Index,
182187
Logger: func(msg string) {
183188
mapD := map[string]string{"DownloadStatus": "Pending", "Msg": msg}
184189
mapB, _ := json.Marshal(mapD)

0 commit comments

Comments
 (0)