diff --git a/certificates.go b/certificates.go index 4ed0ff02d..af8f6c3a2 100644 --- a/certificates.go +++ b/certificates.go @@ -16,7 +16,6 @@ import ( "crypto/x509/pkix" "encoding/pem" "fmt" - "io/ioutil" "math/big" "net" "os" @@ -24,6 +23,7 @@ import ( "text/template" "time" + "github.com/arduino/go-paths-helper" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" ) @@ -133,12 +133,38 @@ func generateSingleCertificate(isCa bool) (*x509.Certificate, error) { return &template, nil } -func generateCertificates() { +// migrateCertificatesGeneratedWithOldAgentVersions checks if certificates generated +// with an old version of the Agent needs to be migrated to the current certificates +// directory, and performs the migration if needed. +func migrateCertificatesGeneratedWithOldAgentVersions(certsDir *paths.Path) { + if certsDir.Join("ca.cert.pem").Exist() { + // The new certificates are already set-up, nothing to do + return + } + + fileList := []string{ + "ca.key.pem", + "ca.cert.pem", + "ca.cert.cer", + "key.pem", + "cert.pem", + "cert.cer", + } + oldCertsDirPath, _ := os.Executable() + oldCertsDir := paths.New(oldCertsDirPath) + for _, fileName := range fileList { + oldCert := oldCertsDir.Join(fileName) + if oldCert.Exist() { + oldCert.CopyTo(certsDir.Join(fileName)) + } + } +} - os.Remove("ca.cert.pem") - os.Remove("ca.key.pem") - os.Remove("cert.pem") - os.Remove("key.pem") +func generateCertificates(certsDir *paths.Path) { + certsDir.Join("ca.cert.pem").Remove() + certsDir.Join("ca.key.pem").Remove() + certsDir.Join("cert.pem").Remove() + certsDir.Join("key.pem").Remove() // Create the key for the certification authority caKey, err := generateKey("P256") @@ -147,18 +173,20 @@ func generateCertificates() { os.Exit(1) } - keyOut, err := os.OpenFile("ca.key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - log.Error(err.Error()) - os.Exit(1) + { + keyOutPath := certsDir.Join("ca.key.pem").String() + keyOut, err := os.OpenFile(keyOutPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) // Save key with user-only permission 0600 + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + pem.Encode(keyOut, pemBlockForKey(caKey)) + keyOut.Close() + log.Printf("written %s", keyOutPath) } - pem.Encode(keyOut, pemBlockForKey(caKey)) - keyOut.Close() - log.Println("written ca.key.pem") // Create the certification authority caTemplate, err := generateSingleCertificate(true) - if err != nil { log.Error(err.Error()) os.Exit(1) @@ -166,17 +194,23 @@ func generateCertificates() { derBytes, _ := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, publicKey(caKey), caKey) - certOut, err := os.Create("ca.cert.pem") - if err != nil { - log.Error(err.Error()) - os.Exit(1) + { + caCertOutPath := certsDir.Join("ca.cert.pem") + caCertOut, err := caCertOutPath.Create() + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + pem.Encode(caCertOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + caCertOut.Close() + log.Printf("written %s", caCertOutPath) } - pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - certOut.Close() - log.Print("written ca.cert.pem") - ioutil.WriteFile("ca.cert.cer", derBytes, 0644) - log.Print("written ca.cert.cer") + { + caCertPath := certsDir.Join("ca.cert.cer") + caCertPath.WriteFile(derBytes) + log.Printf("written %s", caCertPath) + } // Create the key for the final certificate key, err := generateKey("P256") @@ -185,18 +219,20 @@ func generateCertificates() { os.Exit(1) } - keyOut, err = os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - log.Error(err.Error()) - os.Exit(1) + { + keyOutPath := certsDir.Join("key.pem").String() + keyOut, err := os.OpenFile(keyOutPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) // Save key with user-only permission 0600 + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + pem.Encode(keyOut, pemBlockForKey(key)) + keyOut.Close() + log.Printf("written %s", keyOutPath) } - pem.Encode(keyOut, pemBlockForKey(key)) - keyOut.Close() - log.Println("written key.pem") // Create the final certificate template, err := generateSingleCertificate(false) - if err != nil { log.Error(err.Error()) os.Exit(1) @@ -204,18 +240,23 @@ func generateCertificates() { derBytes, _ = x509.CreateCertificate(rand.Reader, template, caTemplate, publicKey(key), caKey) - certOut, err = os.Create("cert.pem") - if err != nil { - log.Error(err.Error()) - os.Exit(1) + { + certOutPath := certsDir.Join("cert.pem").String() + certOut, err := os.Create(certOutPath) + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + certOut.Close() + log.Printf("written %s", certOutPath) } - pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - certOut.Close() - log.Print("written cert.pem") - - ioutil.WriteFile("cert.cer", derBytes, 0644) - log.Print("written cert.cer") + { + certPath := certsDir.Join("cert.cer") + certPath.WriteFile(derBytes) + log.Printf("written %s", certPath) + } } func certHandler(c *gin.Context) { @@ -230,14 +271,14 @@ func certHandler(c *gin.Context) { } func deleteCertHandler(c *gin.Context) { - DeleteCertificates() + DeleteCertificates(getCertificatesDir()) } // DeleteCertificates will delete the certificates -func DeleteCertificates() { - os.Remove("ca.cert.pem") - os.Remove("ca.cert.cer") - os.Remove("ca.key.pem") +func DeleteCertificates(certDir *paths.Path) { + certDir.Join("ca.cert.pem").Remove() + certDir.Join("ca.cert.cer").Remove() + certDir.Join("ca.key.pem").Remove() } const noFirefoxTemplateHTML = ` diff --git a/config.go b/config.go index e3225681d..182e24c52 100644 --- a/config.go +++ b/config.go @@ -17,15 +17,41 @@ package main import ( _ "embed" - "fmt" "os" "github.com/arduino/go-paths-helper" log "github.com/sirupsen/logrus" ) -// getDefaultArduinoCreateConfigDir returns the full path to the default arduino create agent data directory -func getDefaultArduinoCreateConfigDir() (*paths.Path, error) { +// getCertificatesDir return the directory where SSL certificates are saved +func getCertificatesDir() *paths.Path { + return getDataDir() +} + +// getDataDir returns the full path to the default Arduino Create Agent data directory. +func getDataDir() *paths.Path { + userDir, err := os.UserHomeDir() + if err != nil { + log.Panicf("Could not get user dir: %s", err) + } + dataDir := paths.New(userDir, ".arduino-create") + if err := dataDir.MkdirAll(); err != nil { + log.Panicf("Could not create data dir: %s", err) + } + return dataDir +} + +// getLogsDir return the directory where logs are saved +func getLogsDir() *paths.Path { + logsDir := getDataDir().Join("logs") + if err := logsDir.MkdirAll(); err != nil { + log.Panicf("Can't create logs dir: %s", err) + } + return logsDir +} + +// getDefaultConfigDir returns the full path to the default Arduino Create Agent configuration directory. +func getDefaultConfigDir() *paths.Path { // UserConfigDir returns the default root directory to use // for user-specific configuration data. Users should create // their own application-specific subdirectory within this @@ -43,14 +69,14 @@ func getDefaultArduinoCreateConfigDir() (*paths.Path, error) { // is not defined), then it will return an error. configDir, err := os.UserConfigDir() if err != nil { - return nil, err + log.Panicf("Can't get user home dir: %s", err) } agentConfigDir := paths.New(configDir, "ArduinoCreateAgent") if err := agentConfigDir.MkdirAll(); err != nil { - return nil, fmt.Errorf("cannot create config dir: %s", err) + log.Panicf("Can't create config dir: %s", err) } - return agentConfigDir, nil + return agentConfigDir } //go:embed config.ini diff --git a/hub.go b/hub.go index 205c00d34..f59c6460e 100755 --- a/hub.go +++ b/hub.go @@ -182,7 +182,7 @@ func checkCmd(m []byte) { } else if strings.HasPrefix(sl, "downloadtool") { // Always delete root certificates when we receive a downloadtool command // Useful if the install procedure was not followed strictly (eg. manually) - DeleteCertificates() + DeleteCertificates(getCertificatesDir()) go func() { args := strings.Split(s, " ") var tool, toolVersion, pack, behaviour string diff --git a/main.go b/main.go index 723ab7e19..943cfdf79 100755 --- a/main.go +++ b/main.go @@ -128,18 +128,17 @@ func main() { // Generate certificates if *genCert { - generateCertificates() + generateCertificates(getCertificatesDir()) os.Exit(0) } + // Check if certificates made with Agent <=1.2.7 needs to be moved over the new location + migrateCertificatesGeneratedWithOldAgentVersions(getCertificatesDir()) // Launch main loop in a goroutine go loop() // SetupSystray is the main thread - configDir, err := getDefaultArduinoCreateConfigDir() - if err != nil { - log.Panicf("Can't open defaul configuration dir: %s", err) - } + configDir := getDefaultConfigDir() Systray = systray.Systray{ Hibernate: *hibernate, Version: version + "-" + commit, @@ -150,25 +149,19 @@ func main() { ConfigDir: configDir, } - path, err := os.Executable() - if err != nil { - panic(err) - } - // If the executable is temporary, copy it to the full path, then restart - if strings.Contains(path, "-temp") { - newPath := updater.BinPath(path) - err := copyExe(path, newPath) - if err != nil { + if src, err := os.Executable(); err != nil { + panic(err) + } else if strings.Contains(src, "-temp") { + newPath := updater.BinPath(src) + if err := copyExe(src, newPath); err != nil { log.Println("Copy error: ", err) panic(err) } - Systray.Update(newPath) } else { // Otherwise copy to a path with -temp suffix - err := copyExe(path, updater.TempPath(path)) - if err != nil { + if err := copyExe(src, updater.TempPath(src)); err != nil { panic(err) } Systray.Start() @@ -197,15 +190,9 @@ func loop() { log.SetLevel(log.InfoLevel) log.SetOutput(os.Stdout) - // the important folders of the agent - src, _ := os.Executable() - srcPath := paths.New(src) // The path of the agent's binary - srcDir := srcPath.Parent() // The directory of the agent's binary - agentDir, err := getDefaultArduinoCreateConfigDir() - // Instantiate Tools Tools = tools.Tools{ - Directory: agentDir.String(), + Directory: getDataDir().String(), IndexURL: *indexURL, Logger: func(msg string) { mapD := map[string]string{"DownloadStatus": "Pending", "Msg": msg} @@ -216,6 +203,7 @@ func loop() { Tools.Init(requiredToolsAPILevel) // Let's handle the config + configDir := getDefaultConfigDir() var configPath *paths.Path // see if the env var is defined, if it is take the config from there, this will override the default path @@ -225,13 +213,14 @@ func loop() { log.Panicf("config from env var %s does not exists", envConfig) } log.Infof("using config from env variable: %s", configPath) - } else if defaultConfigPath := agentDir.Join("config.ini"); defaultConfigPath.Exist() { + } else if defaultConfigPath := configDir.Join("config.ini"); defaultConfigPath.Exist() { // by default take the config from the ~/.arduino-create/config.ini file configPath = defaultConfigPath log.Infof("using config from default: %s", configPath) } else { - // take the config from the old folder where the agent's binary sits - oldConfigPath := srcDir.Join("config.ini") + // Fall back to the old config.ini location + src, _ := os.Executable() + oldConfigPath := paths.New(src).Parent().Join("config.ini") if oldConfigPath.Exist() { err := oldConfigPath.CopyTo(defaultConfigPath) if err != nil { @@ -243,7 +232,7 @@ func loop() { } } if configPath == nil { - configPath = generateConfig(agentDir) + configPath = generateConfig(configDir) } // Parse the config.ini @@ -352,10 +341,7 @@ func loop() { if *crashreport { logFilename := "crashreport_" + time.Now().Format("20060102150405") + ".log" // handle logs directory creation - logsDir := agentDir.Join("logs") - if logsDir.NotExist() { - logsDir.Mkdir() - } + logsDir := getLogsDir() logFile, err := os.OpenFile(logsDir.Join(logFilename).String(), os.O_WRONLY|os.O_CREATE|os.O_SYNC|os.O_APPEND, 0644) if err != nil { log.Print("Cannot create file used for crash-report") @@ -415,12 +401,13 @@ func loop() { r.POST("/update", updateHandler) // Mount goa handlers - goa := v2.Server(agentDir.String()) + goa := v2.Server(getDataDir().String()) r.Any("/v2/*path", gin.WrapH(goa)) go func() { // check if certificates exist; if not, use plain http - if srcDir.Join("cert.pem").NotExist() { + certsDir := getCertificatesDir() + if certsDir.Join("cert.pem").NotExist() { log.Error("Could not find HTTPS certificate. Using plain HTTP only.") return } @@ -431,7 +418,7 @@ func loop() { for i < end { i = i + 1 portSSL = ":" + strconv.Itoa(i) - if err := r.RunTLS(*address+portSSL, srcDir.Join("cert.pem").String(), srcDir.Join("key.pem").String()); err != nil { + if err := r.RunTLS(*address+portSSL, certsDir.Join("cert.pem").String(), certsDir.Join("key.pem").String()); err != nil { log.Printf("Error trying to bind to port: %v, so exiting...", err) continue } else {