From 64bb34650ea3d50d828212ea543e7af1436a8103 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Tue, 23 Apr 2024 11:07:14 +0200 Subject: [PATCH 01/19] Add function to retrieve certificates expiration date --- certificates/install_darwin.go | 46 +++++++++++++++++++++++++++++++++ certificates/install_default.go | 6 +++++ 2 files changed, 52 insertions(+) diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index 2c84d7dc..8dc48423 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -89,11 +89,46 @@ const char *uninstallCert() { } return ""; } + +const char *getExpirationDate(){ + // Create a key-value dictionary used to query the Keychain and look for the "Arduino" root certificate. + NSDictionary *getquery = @{ + (id)kSecClass: (id)kSecClassCertificate, + (id)kSecAttrLabel: @"Arduino", + (id)kSecReturnRef: @YES, + }; + + OSStatus err = noErr; + SecCertificateRef cert = NULL; + + // Use this function to check for errors + err = SecItemCopyMatching((CFDictionaryRef)getquery, (CFTypeRef *)&cert); + + if (err != errSecItemNotFound && err != noErr){ + NSString *errString = [@"Error: " stringByAppendingFormat:@"%d", err]; + NSLog(@"%@", errString); + return ""; + } + + // Get data from the certificate. We just need the "invalidity date" property. + CFDictionaryRef valuesDict = SecCertificateCopyValues(cert, (__bridge CFArrayRef)@[(__bridge id)kSecOIDInvalidityDate], NULL); + + // TODO: Error checking. + CFDictionaryRef invalidityDateDictionaryRef = CFDictionaryGetValue(valuesDict, kSecOIDInvalidityDate); + CFTypeRef invalidityRef = CFDictionaryGetValue(invalidityDateDictionaryRef, kSecPropertyKeyValue); + id expirationDateValue = CFBridgingRelease(invalidityRef); + + CFRelease(valuesDict); + + NSString *outputString = [@"" stringByAppendingFormat:@"%@", expirationDateValue]; + return [outputString cStringUsingEncoding:[NSString defaultCStringEncoding]]; +} */ import "C" import ( "errors" "os/exec" + "strings" "unsafe" log "github.com/sirupsen/logrus" @@ -131,3 +166,14 @@ func UninstallCertificates() error { } return nil } + +// GetExpirationDate returns the expiration date of a certificate stored in the keychain +func GetExpirationDate() (string, error) { + log.Infof("Retrieving certificate's expiration date") + p := C.getExpirationDate() + s := strings.ReplaceAll(C.GoString(p), " +0000", "") + if len(s) != 0 { + return s, nil + } + return "", nil +} diff --git a/certificates/install_default.go b/certificates/install_default.go index 1b7f24bb..ab10e194 100644 --- a/certificates/install_default.go +++ b/certificates/install_default.go @@ -36,3 +36,9 @@ func UninstallCertificates() error { log.Warn("platform not supported for the certificates uninstall") return errors.New("platform not supported for the certificates uninstall") } + +// GetExpirationDate won't do anything on unsupported Operative Systems +func GetExpirationDate() (string, error) { + log.Warn("platform not supported for retrieving certificates expiration date") + return "", errors.New("platform not supported for retrieving certificates expiration date") +} From 0cb308aacea43a1e0430b331a00774959d75eeb2 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Tue, 23 Apr 2024 11:07:42 +0200 Subject: [PATCH 02/19] Check the certificate expiration date --- certificates/certificates.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/certificates/certificates.go b/certificates/certificates.go index 990fa2e0..cef2138e 100644 --- a/certificates/certificates.go +++ b/certificates/certificates.go @@ -267,3 +267,12 @@ func DeleteCertificates(certDir *paths.Path) { certDir.Join("cert.pem").Remove() certDir.Join("cert.cer").Remove() } + +// isExpired checks if a certificate is expired or about to expire (less than 1 month) +func isExpired() bool { + bound := time.Now().AddDate(0, 1, 0) + // TODO: manage errors + dateS, _ := GetExpirationDate() + date, _ := time.Parse(time.DateTime, dateS) + return date.Before(bound) +} From 431dc589ecfcdf4a9d574e3a7eae183581e3e106 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Wed, 24 Apr 2024 10:50:29 +0200 Subject: [PATCH 03/19] Obtain certificates info using the systray icon --- systray/systray_real.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/systray/systray_real.go b/systray/systray_real.go index 62e52e21..4a3c732d 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -21,6 +21,7 @@ package systray import ( "os" + "os/exec" "runtime" "fyne.io/systray" @@ -65,11 +66,13 @@ func (s *Systray) start() { mGenCerts := systray.AddMenuItem("Generate and Install HTTPS certificates", "HTTPS Certs") mRemoveCerts := systray.AddMenuItem("Remove HTTPS certificates", "") + mCertsInfo := systray.AddMenuItem("Show HTTPS certificates info", "") // On linux/windows chrome/firefox/edge(chromium) the agent works without problems on plain HTTP, // so we disable the menuItem to generate/install the certificates if runtime.GOOS != "darwin" { s.updateMenuItem(mGenCerts, true) s.updateMenuItem(mRemoveCerts, true) + s.updateMenuItem(mCertsInfo, true) } else { s.updateMenuItem(mGenCerts, config.CertsExist()) s.updateMenuItem(mRemoveCerts, !config.CertsExist()) @@ -115,6 +118,19 @@ func (s *Systray) start() { cert.DeleteCertificates(certDir) } s.Restart() + case <-mCertsInfo.ClickedCh: + infoMsg := "The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\n\nYour HTTPS certificate status:\n" + if config.CertsExist() { + expDate, err := cert.GetExpirationDate() + if err != nil { + log.Errorf("cannot get certificates expiration date, something went wrong: %s", err) + } + infoMsg = infoMsg + "- Certificate installed: Yes\n- Certificate trusted: Yes\n- Certificate expiration date: " + expDate + } else { + infoMsg = infoMsg + "- Certificate installed: No\n- Certificate trusted: N/A\n- Certificate expiration date: N/A" + } + oscmd := exec.Command("osascript", "-e", "display dialog \""+infoMsg+"\" buttons \"OK\" with title \"Arduino Agent: certificates info\"") + _ = oscmd.Run() case <-mPause.ClickedCh: s.Pause() case <-mQuit.ClickedCh: From 046397668cfa1051d53bf86621aed3889bc4c4c8 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Wed, 24 Apr 2024 15:10:50 +0200 Subject: [PATCH 04/19] Manage errors that may occur retrieving certificates expiration date --- certificates/certificates.go | 10 ++++--- certificates/install_darwin.go | 48 ++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/certificates/certificates.go b/certificates/certificates.go index cef2138e..2287fe43 100644 --- a/certificates/certificates.go +++ b/certificates/certificates.go @@ -269,10 +269,12 @@ func DeleteCertificates(certDir *paths.Path) { } // isExpired checks if a certificate is expired or about to expire (less than 1 month) -func isExpired() bool { +func isExpired() (bool, error) { bound := time.Now().AddDate(0, 1, 0) - // TODO: manage errors - dateS, _ := GetExpirationDate() + dateS, err := GetExpirationDate() + if err != nil { + return false, err + } date, _ := time.Parse(time.DateTime, dateS) - return date.Before(bound) + return date.Before(bound), nil } diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index 8dc48423..b059515a 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -90,7 +90,7 @@ const char *uninstallCert() { return ""; } -const char *getExpirationDate(){ +const char *getExpirationDate(char *expirationDate){ // Create a key-value dictionary used to query the Keychain and look for the "Arduino" root certificate. NSDictionary *getquery = @{ (id)kSecClass: (id)kSecClassCertificate, @@ -104,24 +104,39 @@ const char *getExpirationDate(){ // Use this function to check for errors err = SecItemCopyMatching((CFDictionaryRef)getquery, (CFTypeRef *)&cert); - if (err != errSecItemNotFound && err != noErr){ + if (err != noErr){ NSString *errString = [@"Error: " stringByAppendingFormat:@"%d", err]; NSLog(@"%@", errString); - return ""; + return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; } // Get data from the certificate. We just need the "invalidity date" property. CFDictionaryRef valuesDict = SecCertificateCopyValues(cert, (__bridge CFArrayRef)@[(__bridge id)kSecOIDInvalidityDate], NULL); - // TODO: Error checking. - CFDictionaryRef invalidityDateDictionaryRef = CFDictionaryGetValue(valuesDict, kSecOIDInvalidityDate); - CFTypeRef invalidityRef = CFDictionaryGetValue(invalidityDateDictionaryRef, kSecPropertyKeyValue); - id expirationDateValue = CFBridgingRelease(invalidityRef); - - CFRelease(valuesDict); + id expirationDateValue; + if(valuesDict){ + CFDictionaryRef invalidityDateDictionaryRef = CFDictionaryGetValue(valuesDict, kSecOIDInvalidityDate); + if(invalidityDateDictionaryRef){ + CFTypeRef invalidityRef = CFDictionaryGetValue(invalidityDateDictionaryRef, kSecPropertyKeyValue); + if(invalidityRef){ + expirationDateValue = CFBridgingRelease(invalidityRef); + } + } + CFRelease(valuesDict); + } NSString *outputString = [@"" stringByAppendingFormat:@"%@", expirationDateValue]; - return [outputString cStringUsingEncoding:[NSString defaultCStringEncoding]]; + if([outputString isEqualToString:@""]){ + NSString *errString = @"Error: the expiration date of the certificate could not be found"; + NSLog(@"%@", errString); + return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; + } + + // This workaround allows to obtain the expiration date alongside the error message + strncpy(expirationDate, [outputString cStringUsingEncoding:[NSString defaultCStringEncoding]], 32); + expirationDate[32-1] = 0; + + return ""; } */ import "C" @@ -170,10 +185,15 @@ func UninstallCertificates() error { // GetExpirationDate returns the expiration date of a certificate stored in the keychain func GetExpirationDate() (string, error) { log.Infof("Retrieving certificate's expiration date") - p := C.getExpirationDate() - s := strings.ReplaceAll(C.GoString(p), " +0000", "") + dateString := C.CString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") // 32 characters string + defer C.free(unsafe.Pointer(dateString)) + p := C.getExpirationDate(dateString) + s := C.GoString(p) if len(s) != 0 { - return s, nil + oscmd := exec.Command("osascript", "-e", "display dialog \""+s+"\" buttons \"OK\" with title \"Arduino Agent: Error retrieving expiration date\"") + _ = oscmd.Run() + return "", errors.New(s) } - return "", nil + date := C.GoString(dateString) + return strings.ReplaceAll(date, " +0000", ""), nil } From 6a0017fe2152b4df41ce8c2195914b2cfaee705d Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Tue, 30 Apr 2024 09:38:29 +0200 Subject: [PATCH 05/19] Obtain default browser name on macOS --- certificates/install_darwin.go | 19 +++++++++++++++++++ certificates/install_default.go | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index b059515a..8c999c40 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -138,6 +138,18 @@ const char *getExpirationDate(char *expirationDate){ return ""; } + +const char *getDefaultBrowserName() { + NSURL *defaultBrowserURL = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:[NSURL URLWithString:@"http://"]]; + if (defaultBrowserURL) { + NSBundle *defaultBrowserBundle = [NSBundle bundleWithURL:defaultBrowserURL]; + NSString *defaultBrowser = [defaultBrowserBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + + return [defaultBrowser cStringUsingEncoding:[NSString defaultCStringEncoding]]; + } + + return ""; +} */ import "C" import ( @@ -197,3 +209,10 @@ func GetExpirationDate() (string, error) { date := C.GoString(dateString) return strings.ReplaceAll(date, " +0000", ""), nil } + +// GetDefaultBrowserName returns the name of the default browser +func GetDefaultBrowserName() string { + log.Infof("Retrieving default browser name") + p := C.getDefaultBrowserName() + return C.GoString(p) +} diff --git a/certificates/install_default.go b/certificates/install_default.go index ab10e194..8013c018 100644 --- a/certificates/install_default.go +++ b/certificates/install_default.go @@ -42,3 +42,9 @@ func GetExpirationDate() (string, error) { log.Warn("platform not supported for retrieving certificates expiration date") return "", errors.New("platform not supported for retrieving certificates expiration date") } + +// GetDefaultBrowserName won't do anything on unsupported Operative Systems +func GetDefaultBrowserName() string { + log.Warn("platform not supported for retrieving default browser name") + return "" +} From f80791d84cb9a7b7c8bb0c0d06b55087c31a8bf6 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Tue, 30 Apr 2024 15:22:04 +0200 Subject: [PATCH 06/19] Prompt Safari users to install HTTPS certificates and check if they are outdated when the Agent is started --- certificates/certificates.go | 35 +++++++++++++++++++++ main.go | 60 ++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/certificates/certificates.go b/certificates/certificates.go index 2287fe43..18a300fa 100644 --- a/certificates/certificates.go +++ b/certificates/certificates.go @@ -30,6 +30,7 @@ import ( "math/big" "net" "os" + "os/exec" "time" "github.com/arduino/go-paths-helper" @@ -278,3 +279,37 @@ func isExpired() (bool, error) { date, _ := time.Parse(time.DateTime, dateS) return date.Before(bound), nil } + +// PromptInstallCertsSafari prompts the user to install the HTTPS certificates if they are using Safari +func PromptInstallCertsSafari() bool { + if GetDefaultBrowserName() != "Safari" { + return false + } + oscmd := exec.Command("osascript", "-e", "display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nIf you use Safari, you need to install it.\" buttons {\"Do not install\", \"Install the certificate for Safari\"} default button 2 with title \"Install Certificates\"") + pressed, _ := oscmd.Output() + return string(pressed) == "button returned:Install the certificate for Safari" +} + +// PromptExpiredCerts prompts the user to update the HTTPS certificates if they are using Safari +func PromptExpiredCerts(certDir *paths.Path) { + if expired, err := isExpired(); err != nil { + log.Errorf("cannot check if certificates are expired something went wrong: %s", err) + } else if expired { + oscmd := exec.Command("osascript", "-e", "display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nYour certificate is expired or close to expiration. Do you want to update it?\" buttons {\"Do not update\", \"Update the certificate for Safari\"} default button 2 with title \"Update Certificates\"") + if pressed, _ := oscmd.Output(); string(pressed) == "button returned:Update the certificate for Safari" { + err := UninstallCertificates() + if err != nil { + log.Errorf("cannot uninstall certificates something went wrong: %s", err) + } else { + DeleteCertificates(certDir) + GenerateCertificates(certDir) + err := InstallCertificate(certDir.Join("ca.cert.cer")) + // if something goes wrong during the cert install we remove them, so the user is able to retry + if err != nil { + log.Errorf("cannot install certificates something went wrong: %s", err) + DeleteCertificates(certDir) + } + } + } + } +} diff --git a/main.go b/main.go index 45ae5259..245d4247 100755 --- a/main.go +++ b/main.go @@ -86,6 +86,7 @@ var ( verbose = iniConf.Bool("v", true, "show debug logging") crashreport = iniConf.Bool("crashreport", false, "enable crashreport logging") autostartMacOS = iniConf.Bool("autostartMacOS", true, "the Arduino Create Agent is able to start automatically after login on macOS (launchd agent)") + installCerts = iniConf.Bool("installCerts", false, "install the HTTPS certificate for Safari and keep it updated") ) // the ports filter provided by the user via the -regex flag, if any @@ -220,6 +221,34 @@ func loop() { configPath = config.GenerateConfig(configDir) } + // if the default browser is Safari, prompt the user to install HTTPS certificates + // and eventually install them + if runtime.GOOS == "darwin" { + if exist, err := installCertsKeyExists(configPath.String()); err != nil { + log.Panicf("config.ini cannot be parsed: %s", err) + } else if !exist { + if cert.PromptInstallCertsSafari() { + err = modifyIni(configPath.String(), "true") + if err != nil { + log.Panicf("config.ini cannot be parsed: %s", err) + } + certDir := config.GetCertificatesDir() + cert.GenerateCertificates(certDir) + err := cert.InstallCertificate(certDir.Join("ca.cert.cer")) + // if something goes wrong during the cert install we remove them, so the user is able to retry + if err != nil { + log.Errorf("cannot install certificates something went wrong: %s", err) + cert.DeleteCertificates(certDir) + } + } else { + err = modifyIni(configPath.String(), "false") + if err != nil { + log.Panicf("config.ini cannot be parsed: %s", err) + } + } + } + } + // Parse the config.ini args, err := parseIni(configPath.String()) if err != nil { @@ -342,6 +371,13 @@ func loop() { } } + // check if the HTTPS certificates are expired and prompt the user to update them on macOS + if runtime.GOOS == "darwin" { + if *installCerts && config.CertsExist() { + cert.PromptExpiredCerts(config.GetCertificatesDir()) + } + } + // launch the discoveries for the running system go serialPorts.Run() // launch the hub routine which is the singleton for the websocket server @@ -487,3 +523,27 @@ func parseIni(filename string) (args []string, err error) { return args, nil } + +func modifyIni(filename string, value string) error { + cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: false, AllowPythonMultilineValues: true}, filename) + if err != nil { + return err + } + _, err = cfg.Section("").NewKey("installCerts", value) + if err != nil { + return err + } + err = cfg.SaveTo(filename) + if err != nil { + return err + } + return nil +} + +func installCertsKeyExists(filename string) (bool, error) { + cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: false, AllowPythonMultilineValues: true}, filename) + if err != nil { + return false, err + } + return cfg.Section("").HasKey("installCerts"), nil +} From 40f50f469c829ffb10024d1b9afdd16c548a7227 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Thu, 2 May 2024 15:18:16 +0200 Subject: [PATCH 07/19] Skip some tests on macOS because the user is prompted to install certificates --- tests/test_info.py | 6 ++++++ tests/test_v2.py | 6 ++++++ tests/test_ws.py | 9 +++++++++ 3 files changed, 21 insertions(+) diff --git a/tests/test_info.py b/tests/test_info.py index 6982ca35..efda3bce 100644 --- a/tests/test_info.py +++ b/tests/test_info.py @@ -15,8 +15,14 @@ import re import requests +import pytest +from sys import platform +@pytest.mark.skipif( + platform == "darwin", + reason="on macOS the user is prompted to install certificates", +) def test_version(base_url, agent): resp = requests.get(f"{base_url}/info") diff --git a/tests/test_v2.py b/tests/test_v2.py index 9a377802..5fa44034 100644 --- a/tests/test_v2.py +++ b/tests/test_v2.py @@ -14,8 +14,14 @@ # along with this program. If not, see . import requests +import pytest +from sys import platform +@pytest.mark.skipif( + platform == "darwin", + reason="on macOS the user is prompted to install certificates", +) def test_get_tools(base_url, agent): resp = requests.get(f"{base_url}/v2/pkgs/tools/installed") diff --git a/tests/test_ws.py b/tests/test_ws.py index c2623da5..b8004649 100644 --- a/tests/test_ws.py +++ b/tests/test_ws.py @@ -17,16 +17,25 @@ import json import base64 import pytest +from sys import platform from common import running_on_ci message = [] +@pytest.mark.skipif( + platform == "darwin", + reason="on macOS the user is prompted to install certificates", +) def test_ws_connection(socketio): print('my sid is', socketio.sid) assert socketio.sid is not None +@pytest.mark.skipif( + platform == "darwin", + reason="on macOS the user is prompted to install certificates", +) def test_list(socketio, message): socketio.emit('command', 'list') time.sleep(.2) From f1f76a3f8e94f1e03763e63e4b02486a934a297e Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Thu, 2 May 2024 15:41:45 +0200 Subject: [PATCH 08/19] Set installCerts value in config.ini if certicates are manually installed --- config/config.go | 18 ++++++++++++++++++ main.go | 20 ++------------------ systray/systray_real.go | 4 ++++ 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/config/config.go b/config/config.go index 437437e5..69d29eee 100644 --- a/config/config.go +++ b/config/config.go @@ -21,6 +21,7 @@ import ( "os" "github.com/arduino/go-paths-helper" + "github.com/go-ini/ini" log "github.com/sirupsen/logrus" ) @@ -124,3 +125,20 @@ func GenerateConfig(destDir *paths.Path) *paths.Path { log.Infof("generated config in %s", configPath) return configPath } + +// SetInstallCertsIni sets installCerts value to true in the config +func SetInstallCertsIni(filename string, value string) error { + cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: false, AllowPythonMultilineValues: true}, filename) + if err != nil { + return err + } + _, err = cfg.Section("").NewKey("installCerts", value) + if err != nil { + return err + } + err = cfg.SaveTo(filename) + if err != nil { + return err + } + return nil +} diff --git a/main.go b/main.go index 245d4247..6d8f72aa 100755 --- a/main.go +++ b/main.go @@ -228,7 +228,7 @@ func loop() { log.Panicf("config.ini cannot be parsed: %s", err) } else if !exist { if cert.PromptInstallCertsSafari() { - err = modifyIni(configPath.String(), "true") + err = config.SetInstallCertsIni(configPath.String(), "true") if err != nil { log.Panicf("config.ini cannot be parsed: %s", err) } @@ -241,7 +241,7 @@ func loop() { cert.DeleteCertificates(certDir) } } else { - err = modifyIni(configPath.String(), "false") + err = config.SetInstallCertsIni(configPath.String(), "false") if err != nil { log.Panicf("config.ini cannot be parsed: %s", err) } @@ -524,22 +524,6 @@ func parseIni(filename string) (args []string, err error) { return args, nil } -func modifyIni(filename string, value string) error { - cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: false, AllowPythonMultilineValues: true}, filename) - if err != nil { - return err - } - _, err = cfg.Section("").NewKey("installCerts", value) - if err != nil { - return err - } - err = cfg.SaveTo(filename) - if err != nil { - return err - } - return nil -} - func installCertsKeyExists(filename string) (bool, error) { cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: false, AllowPythonMultilineValues: true}, filename) if err != nil { diff --git a/systray/systray_real.go b/systray/systray_real.go index 4a3c732d..4d70b703 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -108,6 +108,10 @@ func (s *Systray) start() { log.Errorf("cannot install certificates something went wrong: %s", err) cert.DeleteCertificates(certDir) } + err = config.SetInstallCertsIni(s.currentConfigFilePath.String(), "true") + if err != nil { + log.Errorf("cannot set installCerts value in config.ini: %s", err) + } s.Restart() case <-mRemoveCerts.ClickedCh: err := cert.UninstallCertificates() From 88495ca2395468e41d476b4206622fdabb768d1a Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Thu, 2 May 2024 16:40:21 +0200 Subject: [PATCH 09/19] Always set installCerts if the certificates exist --- main.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 6d8f72aa..07ab5598 100755 --- a/main.go +++ b/main.go @@ -227,7 +227,12 @@ func loop() { if exist, err := installCertsKeyExists(configPath.String()); err != nil { log.Panicf("config.ini cannot be parsed: %s", err) } else if !exist { - if cert.PromptInstallCertsSafari() { + if config.CertsExist() { + err = config.SetInstallCertsIni(configPath.String(), "true") + if err != nil { + log.Panicf("config.ini cannot be parsed: %s", err) + } + } else if cert.PromptInstallCertsSafari() { err = config.SetInstallCertsIni(configPath.String(), "true") if err != nil { log.Panicf("config.ini cannot be parsed: %s", err) From a438fed65194575146e4748fe980cf70fd6e04da Mon Sep 17 00:00:00 2001 From: Xayton <30591904+Xayton@users.noreply.github.com> Date: Thu, 2 May 2024 17:31:08 +0200 Subject: [PATCH 10/19] Add "Arduino Agent" to the title of dialogs --- certificates/certificates.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/certificates/certificates.go b/certificates/certificates.go index 18a300fa..d1c31697 100644 --- a/certificates/certificates.go +++ b/certificates/certificates.go @@ -285,7 +285,7 @@ func PromptInstallCertsSafari() bool { if GetDefaultBrowserName() != "Safari" { return false } - oscmd := exec.Command("osascript", "-e", "display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nIf you use Safari, you need to install it.\" buttons {\"Do not install\", \"Install the certificate for Safari\"} default button 2 with title \"Install Certificates\"") + oscmd := exec.Command("osascript", "-e", "display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nIf you use Safari, you need to install it.\" buttons {\"Do not install\", \"Install the certificate for Safari\"} default button 2 with title \"Arduino Agent: Install Certificates\"") pressed, _ := oscmd.Output() return string(pressed) == "button returned:Install the certificate for Safari" } @@ -295,7 +295,7 @@ func PromptExpiredCerts(certDir *paths.Path) { if expired, err := isExpired(); err != nil { log.Errorf("cannot check if certificates are expired something went wrong: %s", err) } else if expired { - oscmd := exec.Command("osascript", "-e", "display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nYour certificate is expired or close to expiration. Do you want to update it?\" buttons {\"Do not update\", \"Update the certificate for Safari\"} default button 2 with title \"Update Certificates\"") + oscmd := exec.Command("osascript", "-e", "display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nYour certificate is expired or close to expiration. Do you want to update it?\" buttons {\"Do not update\", \"Update the certificate for Safari\"} default button 2 with title \"Arduino Agent: Update Certificates\"") if pressed, _ := oscmd.Output(); string(pressed) == "button returned:Update the certificate for Safari" { err := UninstallCertificates() if err != nil { From 66ba136928bff6e5f9fd32057d14e3062ee0830b Mon Sep 17 00:00:00 2001 From: Xayton <30591904+Xayton@users.noreply.github.com> Date: Thu, 2 May 2024 17:56:56 +0200 Subject: [PATCH 11/19] Fix check for pressed buttons --- certificates/certificates.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/certificates/certificates.go b/certificates/certificates.go index d1c31697..e2256699 100644 --- a/certificates/certificates.go +++ b/certificates/certificates.go @@ -287,7 +287,7 @@ func PromptInstallCertsSafari() bool { } oscmd := exec.Command("osascript", "-e", "display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nIf you use Safari, you need to install it.\" buttons {\"Do not install\", \"Install the certificate for Safari\"} default button 2 with title \"Arduino Agent: Install Certificates\"") pressed, _ := oscmd.Output() - return string(pressed) == "button returned:Install the certificate for Safari" + return strings.Contains(string(pressed), "button returned:Install the certificate for Safari") } // PromptExpiredCerts prompts the user to update the HTTPS certificates if they are using Safari @@ -296,7 +296,7 @@ func PromptExpiredCerts(certDir *paths.Path) { log.Errorf("cannot check if certificates are expired something went wrong: %s", err) } else if expired { oscmd := exec.Command("osascript", "-e", "display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nYour certificate is expired or close to expiration. Do you want to update it?\" buttons {\"Do not update\", \"Update the certificate for Safari\"} default button 2 with title \"Arduino Agent: Update Certificates\"") - if pressed, _ := oscmd.Output(); string(pressed) == "button returned:Update the certificate for Safari" { + if pressed, _ := oscmd.Output(); strings.Contains(string(pressed), "button returned:Update the certificate for Safari") { err := UninstallCertificates() if err != nil { log.Errorf("cannot uninstall certificates something went wrong: %s", err) From 52961e28e121d93a3c3bb8ea8c1d7610656a180f Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Mon, 6 May 2024 13:00:49 +0200 Subject: [PATCH 12/19] Move osascript execution function to Utilities to avoid code duplication --- certificates/certificates.go | 12 ++++++------ certificates/install_darwin.go | 13 +++++-------- main.go | 10 ++-------- systray/systray_real.go | 5 ++--- utilities/utilities.go | 7 +++++++ 5 files changed, 22 insertions(+), 25 deletions(-) diff --git a/certificates/certificates.go b/certificates/certificates.go index e2256699..f612d0cc 100644 --- a/certificates/certificates.go +++ b/certificates/certificates.go @@ -30,9 +30,10 @@ import ( "math/big" "net" "os" - "os/exec" + "strings" "time" + "github.com/arduino/arduino-create-agent/utilities" "github.com/arduino/go-paths-helper" log "github.com/sirupsen/logrus" ) @@ -285,9 +286,8 @@ func PromptInstallCertsSafari() bool { if GetDefaultBrowserName() != "Safari" { return false } - oscmd := exec.Command("osascript", "-e", "display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nIf you use Safari, you need to install it.\" buttons {\"Do not install\", \"Install the certificate for Safari\"} default button 2 with title \"Arduino Agent: Install Certificates\"") - pressed, _ := oscmd.Output() - return strings.Contains(string(pressed), "button returned:Install the certificate for Safari") + buttonPressed := utilities.UserPrompt("display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nIf you use Safari, you need to install it.\" buttons {\"Do not install\", \"Install the certificate for Safari\"} default button 2 with title \"Arduino Agent: Install Certificates\"") + return strings.Contains(string(buttonPressed), "button returned:Install the certificate for Safari") } // PromptExpiredCerts prompts the user to update the HTTPS certificates if they are using Safari @@ -295,8 +295,8 @@ func PromptExpiredCerts(certDir *paths.Path) { if expired, err := isExpired(); err != nil { log.Errorf("cannot check if certificates are expired something went wrong: %s", err) } else if expired { - oscmd := exec.Command("osascript", "-e", "display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nYour certificate is expired or close to expiration. Do you want to update it?\" buttons {\"Do not update\", \"Update the certificate for Safari\"} default button 2 with title \"Arduino Agent: Update Certificates\"") - if pressed, _ := oscmd.Output(); strings.Contains(string(pressed), "button returned:Update the certificate for Safari") { + buttonPressed := utilities.UserPrompt("display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nYour certificate is expired or close to expiration. Do you want to update it?\" buttons {\"Do not update\", \"Update the certificate for Safari\"} default button 2 with title \"Arduino Agent: Update Certificates\"") + if strings.Contains(string(buttonPressed), "button returned:Update the certificate for Safari") { err := UninstallCertificates() if err != nil { log.Errorf("cannot uninstall certificates something went wrong: %s", err) diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index 8c999c40..892c390b 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -154,12 +154,12 @@ const char *getDefaultBrowserName() { import "C" import ( "errors" - "os/exec" "strings" "unsafe" log "github.com/sirupsen/logrus" + "github.com/arduino/arduino-create-agent/utilities" "github.com/arduino/go-paths-helper" ) @@ -172,9 +172,8 @@ func InstallCertificate(cert *paths.Path) error { p := C.installCert(ccert) s := C.GoString(p) if len(s) != 0 { - oscmd := exec.Command("osascript", "-e", "display dialog \""+s+"\" buttons \"OK\" with title \"Arduino Agent: Error installing certificates\"") - _ = oscmd.Run() - _ = UninstallCertificates() + utilities.UserPrompt("display dialog \"" + s + "\" buttons \"OK\" with title \"Arduino Agent: Error installing certificates\"") + UninstallCertificates() return errors.New(s) } return nil @@ -187,8 +186,7 @@ func UninstallCertificates() error { p := C.uninstallCert() s := C.GoString(p) if len(s) != 0 { - oscmd := exec.Command("osascript", "-e", "display dialog \""+s+"\" buttons \"OK\" with title \"Arduino Agent: Error uninstalling certificates\"") - _ = oscmd.Run() + utilities.UserPrompt("display dialog \"" + s + "\" buttons \"OK\" with title \"Arduino Agent: Error uninstalling certificates\"") return errors.New(s) } return nil @@ -202,8 +200,7 @@ func GetExpirationDate() (string, error) { p := C.getExpirationDate(dateString) s := C.GoString(p) if len(s) != 0 { - oscmd := exec.Command("osascript", "-e", "display dialog \""+s+"\" buttons \"OK\" with title \"Arduino Agent: Error retrieving expiration date\"") - _ = oscmd.Run() + utilities.UserPrompt("display dialog \"" + s + "\" buttons \"OK\" with title \"Arduino Agent: Error retrieving expiration date\"") return "", errors.New(s) } date := C.GoString(dateString) diff --git a/main.go b/main.go index 07ab5598..8d3f7c33 100755 --- a/main.go +++ b/main.go @@ -25,7 +25,6 @@ import ( "html/template" "io" "os" - "os/exec" "regexp" "runtime" "runtime/debug" @@ -40,6 +39,7 @@ import ( "github.com/arduino/arduino-create-agent/systray" "github.com/arduino/arduino-create-agent/tools" "github.com/arduino/arduino-create-agent/updater" + "github.com/arduino/arduino-create-agent/utilities" v2 "github.com/arduino/arduino-create-agent/v2" paths "github.com/arduino/go-paths-helper" cors "github.com/gin-contrib/cors" @@ -178,7 +178,7 @@ func loop() { // If we are updating manually from 1.2.7 to 1.3.0 we have to uninstall the old agent manually first. // This check will inform the user if he needs to run the uninstall first if runtime.GOOS == "darwin" && oldInstallExists() { - printDialog("Old agent installation of the Arduino Create Agent found, please uninstall it before launching the new one") + utilities.UserPrompt("display dialog \"Old agent installation of the Arduino Create Agent found, please uninstall it before launching the new one\" buttons \"OK\" with title \"Error\"") os.Exit(0) } @@ -498,12 +498,6 @@ func oldInstallExists() bool { return oldAgentPath.Join("ArduinoCreateAgent.app").Exist() } -// printDialog will print a GUI error dialog on macos -func printDialog(dialogText string) { - oscmd := exec.Command("osascript", "-e", "display dialog \""+dialogText+"\" buttons \"OK\" with title \"Error\"") - _ = oscmd.Run() -} - func parseIni(filename string) (args []string, err error) { cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: false, AllowPythonMultilineValues: true}, filename) if err != nil { diff --git a/systray/systray_real.go b/systray/systray_real.go index 4d70b703..57e2e9e7 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -21,13 +21,13 @@ package systray import ( "os" - "os/exec" "runtime" "fyne.io/systray" cert "github.com/arduino/arduino-create-agent/certificates" "github.com/arduino/arduino-create-agent/config" "github.com/arduino/arduino-create-agent/icon" + "github.com/arduino/arduino-create-agent/utilities" "github.com/go-ini/ini" log "github.com/sirupsen/logrus" "github.com/skratchdot/open-golang/open" @@ -133,8 +133,7 @@ func (s *Systray) start() { } else { infoMsg = infoMsg + "- Certificate installed: No\n- Certificate trusted: N/A\n- Certificate expiration date: N/A" } - oscmd := exec.Command("osascript", "-e", "display dialog \""+infoMsg+"\" buttons \"OK\" with title \"Arduino Agent: certificates info\"") - _ = oscmd.Run() + utilities.UserPrompt("display dialog \"" + infoMsg + "\" buttons \"OK\" with title \"Arduino Agent: certificates info\"") case <-mPause.ClickedCh: s.Pause() case <-mQuit.ClickedCh: diff --git a/utilities/utilities.go b/utilities/utilities.go index 4f40aaf7..63f09103 100644 --- a/utilities/utilities.go +++ b/utilities/utilities.go @@ -149,3 +149,10 @@ func VerifyInput(input string, signature string) error { d := h.Sum(nil) return rsa.VerifyPKCS1v15(rsaKey, crypto.SHA256, d, sign) } + +// UserPrompt executes an osascript and returns the pressed button +func UserPrompt(dialog string) string { + oscmd := exec.Command("osascript", "-e", dialog) + pressedButton, _ := oscmd.Output() + return string(pressedButton) +} From a759046a13069c82508e9e611b56339fb2eb6f0e Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Mon, 6 May 2024 14:36:17 +0200 Subject: [PATCH 13/19] Modify certificate management from the systray menu --- systray/systray_real.go | 67 ++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/systray/systray_real.go b/systray/systray_real.go index 57e2e9e7..31ea460c 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -22,6 +22,7 @@ package systray import ( "os" "runtime" + "strings" "fyne.io/systray" cert "github.com/arduino/arduino-create-agent/certificates" @@ -64,18 +65,11 @@ func (s *Systray) start() { mRmCrashes := systray.AddMenuItem("Remove crash reports", "") s.updateMenuItem(mRmCrashes, config.LogsIsEmpty()) - mGenCerts := systray.AddMenuItem("Generate and Install HTTPS certificates", "HTTPS Certs") - mRemoveCerts := systray.AddMenuItem("Remove HTTPS certificates", "") - mCertsInfo := systray.AddMenuItem("Show HTTPS certificates info", "") + mManageCerts := systray.AddMenuItem("Manage HTTPS certificates", "HTTPS Certs") // On linux/windows chrome/firefox/edge(chromium) the agent works without problems on plain HTTP, // so we disable the menuItem to generate/install the certificates if runtime.GOOS != "darwin" { - s.updateMenuItem(mGenCerts, true) - s.updateMenuItem(mRemoveCerts, true) - s.updateMenuItem(mCertsInfo, true) - } else { - s.updateMenuItem(mGenCerts, config.CertsExist()) - s.updateMenuItem(mRemoveCerts, !config.CertsExist()) + s.updateMenuItem(mManageCerts, true) } // Add pause/quit @@ -99,41 +93,46 @@ func (s *Systray) start() { case <-mRmCrashes.ClickedCh: RemoveCrashes() s.updateMenuItem(mRmCrashes, config.LogsIsEmpty()) - case <-mGenCerts.ClickedCh: - certDir := config.GetCertificatesDir() - cert.GenerateCertificates(certDir) - err := cert.InstallCertificate(certDir.Join("ca.cert.cer")) - // if something goes wrong during the cert install we remove them, so the user is able to retry - if err != nil { - log.Errorf("cannot install certificates something went wrong: %s", err) - cert.DeleteCertificates(certDir) - } - err = config.SetInstallCertsIni(s.currentConfigFilePath.String(), "true") - if err != nil { - log.Errorf("cannot set installCerts value in config.ini: %s", err) - } - s.Restart() - case <-mRemoveCerts.ClickedCh: - err := cert.UninstallCertificates() - if err != nil { - log.Errorf("cannot uninstall certificates something went wrong: %s", err) - } else { - certDir := config.GetCertificatesDir() - cert.DeleteCertificates(certDir) - } - s.Restart() - case <-mCertsInfo.ClickedCh: + case <-mManageCerts.ClickedCh: infoMsg := "The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\n\nYour HTTPS certificate status:\n" + buttons := "{\"OK\", \"Install certificate for Safari\"}" + certDir := config.GetCertificatesDir() if config.CertsExist() { expDate, err := cert.GetExpirationDate() if err != nil { log.Errorf("cannot get certificates expiration date, something went wrong: %s", err) } infoMsg = infoMsg + "- Certificate installed: Yes\n- Certificate trusted: Yes\n- Certificate expiration date: " + expDate + buttons = "{\"OK\", \"Uninstall certificate for Safari\"}" } else { infoMsg = infoMsg + "- Certificate installed: No\n- Certificate trusted: N/A\n- Certificate expiration date: N/A" } - utilities.UserPrompt("display dialog \"" + infoMsg + "\" buttons \"OK\" with title \"Arduino Agent: certificates info\"") + pressedButton := utilities.UserPrompt("display dialog \"" + infoMsg + "\" buttons " + buttons + " with title \"Arduino Agent: manage HTTPS certificates\"") + if strings.Contains(pressedButton, "Install certificate for Safari") { + cert.GenerateCertificates(certDir) + err := cert.InstallCertificate(certDir.Join("ca.cert.cer")) + // if something goes wrong during the cert install we remove them, so the user is able to retry + if err != nil { + log.Errorf("cannot install certificates something went wrong: %s", err) + cert.DeleteCertificates(certDir) + } + err = config.SetInstallCertsIni(s.currentConfigFilePath.String(), "true") + if err != nil { + log.Errorf("cannot set installCerts value in config.ini: %s", err) + } + } else if strings.Contains(pressedButton, "Uninstall certificate for Safari") { + err := cert.UninstallCertificates() + if err != nil { + log.Errorf("cannot uninstall certificates something went wrong: %s", err) + } else { + cert.DeleteCertificates(certDir) + err = config.SetInstallCertsIni(s.currentConfigFilePath.String(), "false") + if err != nil { + log.Errorf("cannot set installCerts value in config.ini: %s", err) + } + } + } + s.Restart() case <-mPause.ClickedCh: s.Pause() case <-mQuit.ClickedCh: From 46ceb5d16227900e3a6c41312c90ee616fe87cf4 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Tue, 7 May 2024 11:16:31 +0200 Subject: [PATCH 14/19] Install certificates if they are missing and the flag inside the config is set to true --- main.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 8d3f7c33..4e4f371b 100755 --- a/main.go +++ b/main.go @@ -378,8 +378,26 @@ func loop() { // check if the HTTPS certificates are expired and prompt the user to update them on macOS if runtime.GOOS == "darwin" { - if *installCerts && config.CertsExist() { - cert.PromptExpiredCerts(config.GetCertificatesDir()) + if *installCerts { + if config.CertsExist() { + cert.PromptExpiredCerts(config.GetCertificatesDir()) + } else if cert.PromptInstallCertsSafari() { + // installing the certificates from scratch at this point should only happen if + // something went wrong during previous installation attempts + certDir := config.GetCertificatesDir() + cert.GenerateCertificates(certDir) + err := cert.InstallCertificate(certDir.Join("ca.cert.cer")) + // if something goes wrong during the cert install we remove them, so the user is able to retry + if err != nil { + log.Errorf("cannot install certificates something went wrong: %s", err) + cert.DeleteCertificates(certDir) + } + } else { + err = config.SetInstallCertsIni(configPath.String(), "false") + if err != nil { + log.Panicf("config.ini cannot be parsed: %s", err) + } + } } } From 144515b135088a2c51cf3190ccb1451d69bb1fb6 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Tue, 7 May 2024 11:56:36 +0200 Subject: [PATCH 15/19] Avoid code duplication --- certificates/certificates.go | 19 ++++++++++++------- main.go | 18 ++---------------- systray/systray_real.go | 10 ++-------- 3 files changed, 16 insertions(+), 31 deletions(-) diff --git a/certificates/certificates.go b/certificates/certificates.go index f612d0cc..a3a236a2 100644 --- a/certificates/certificates.go +++ b/certificates/certificates.go @@ -302,14 +302,19 @@ func PromptExpiredCerts(certDir *paths.Path) { log.Errorf("cannot uninstall certificates something went wrong: %s", err) } else { DeleteCertificates(certDir) - GenerateCertificates(certDir) - err := InstallCertificate(certDir.Join("ca.cert.cer")) - // if something goes wrong during the cert install we remove them, so the user is able to retry - if err != nil { - log.Errorf("cannot install certificates something went wrong: %s", err) - DeleteCertificates(certDir) - } + GenerateAndInstallCertificates(certDir) } } } } + +// GenerateAndInstallCertificates generates and installs the certificates +func GenerateAndInstallCertificates(certDir *paths.Path) { + GenerateCertificates(certDir) + err := InstallCertificate(certDir.Join("ca.cert.cer")) + // if something goes wrong during the cert install we remove them, so the user is able to retry + if err != nil { + log.Errorf("cannot install certificates something went wrong: %s", err) + DeleteCertificates(certDir) + } +} diff --git a/main.go b/main.go index 4e4f371b..8cefa995 100755 --- a/main.go +++ b/main.go @@ -237,14 +237,7 @@ func loop() { if err != nil { log.Panicf("config.ini cannot be parsed: %s", err) } - certDir := config.GetCertificatesDir() - cert.GenerateCertificates(certDir) - err := cert.InstallCertificate(certDir.Join("ca.cert.cer")) - // if something goes wrong during the cert install we remove them, so the user is able to retry - if err != nil { - log.Errorf("cannot install certificates something went wrong: %s", err) - cert.DeleteCertificates(certDir) - } + cert.GenerateAndInstallCertificates(config.GetCertificatesDir()) } else { err = config.SetInstallCertsIni(configPath.String(), "false") if err != nil { @@ -384,14 +377,7 @@ func loop() { } else if cert.PromptInstallCertsSafari() { // installing the certificates from scratch at this point should only happen if // something went wrong during previous installation attempts - certDir := config.GetCertificatesDir() - cert.GenerateCertificates(certDir) - err := cert.InstallCertificate(certDir.Join("ca.cert.cer")) - // if something goes wrong during the cert install we remove them, so the user is able to retry - if err != nil { - log.Errorf("cannot install certificates something went wrong: %s", err) - cert.DeleteCertificates(certDir) - } + cert.GenerateAndInstallCertificates(config.GetCertificatesDir()) } else { err = config.SetInstallCertsIni(configPath.String(), "false") if err != nil { diff --git a/systray/systray_real.go b/systray/systray_real.go index 31ea460c..d4ff48da 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -109,14 +109,8 @@ func (s *Systray) start() { } pressedButton := utilities.UserPrompt("display dialog \"" + infoMsg + "\" buttons " + buttons + " with title \"Arduino Agent: manage HTTPS certificates\"") if strings.Contains(pressedButton, "Install certificate for Safari") { - cert.GenerateCertificates(certDir) - err := cert.InstallCertificate(certDir.Join("ca.cert.cer")) - // if something goes wrong during the cert install we remove them, so the user is able to retry - if err != nil { - log.Errorf("cannot install certificates something went wrong: %s", err) - cert.DeleteCertificates(certDir) - } - err = config.SetInstallCertsIni(s.currentConfigFilePath.String(), "true") + cert.GenerateAndInstallCertificates(certDir) + err := config.SetInstallCertsIni(s.currentConfigFilePath.String(), "true") if err != nil { log.Errorf("cannot set installCerts value in config.ini: %s", err) } From e9c71b34f1be5be6fc07d488cb4db55d8d6c6902 Mon Sep 17 00:00:00 2001 From: Xayton <30591904+Xayton@users.noreply.github.com> Date: Tue, 7 May 2024 14:47:25 +0200 Subject: [PATCH 16/19] Fix button order and title --- systray/systray_real.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/systray/systray_real.go b/systray/systray_real.go index d4ff48da..41f27bd2 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -65,7 +65,7 @@ func (s *Systray) start() { mRmCrashes := systray.AddMenuItem("Remove crash reports", "") s.updateMenuItem(mRmCrashes, config.LogsIsEmpty()) - mManageCerts := systray.AddMenuItem("Manage HTTPS certificates", "HTTPS Certs") + mManageCerts := systray.AddMenuItem("Manage HTTPS certificate", "HTTPS Certs") // On linux/windows chrome/firefox/edge(chromium) the agent works without problems on plain HTTP, // so we disable the menuItem to generate/install the certificates if runtime.GOOS != "darwin" { @@ -95,7 +95,7 @@ func (s *Systray) start() { s.updateMenuItem(mRmCrashes, config.LogsIsEmpty()) case <-mManageCerts.ClickedCh: infoMsg := "The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\n\nYour HTTPS certificate status:\n" - buttons := "{\"OK\", \"Install certificate for Safari\"}" + buttons := "{\"Install certificate for Safari\", \"OK\"} default button \"OK\"" certDir := config.GetCertificatesDir() if config.CertsExist() { expDate, err := cert.GetExpirationDate() @@ -103,11 +103,11 @@ func (s *Systray) start() { log.Errorf("cannot get certificates expiration date, something went wrong: %s", err) } infoMsg = infoMsg + "- Certificate installed: Yes\n- Certificate trusted: Yes\n- Certificate expiration date: " + expDate - buttons = "{\"OK\", \"Uninstall certificate for Safari\"}" + buttons = "{\"Uninstall certificate for Safari\", \"OK\"} default button \"OK\"" } else { infoMsg = infoMsg + "- Certificate installed: No\n- Certificate trusted: N/A\n- Certificate expiration date: N/A" } - pressedButton := utilities.UserPrompt("display dialog \"" + infoMsg + "\" buttons " + buttons + " with title \"Arduino Agent: manage HTTPS certificates\"") + pressedButton := utilities.UserPrompt("display dialog \"" + infoMsg + "\" buttons " + buttons + " with title \"Arduino Agent: Manage HTTPS certificate\"") if strings.Contains(pressedButton, "Install certificate for Safari") { cert.GenerateAndInstallCertificates(certDir) err := config.SetInstallCertsIni(s.currentConfigFilePath.String(), "true") From 1ba5ed19b47a62af03e35acba197c6d77d9f8026 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Tue, 7 May 2024 14:49:40 +0200 Subject: [PATCH 17/19] Do not restart the Agent if no action is performed on the certificate --- systray/systray_real.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/systray/systray_real.go b/systray/systray_real.go index 41f27bd2..2b06b24c 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -114,6 +114,7 @@ func (s *Systray) start() { if err != nil { log.Errorf("cannot set installCerts value in config.ini: %s", err) } + s.Restart() } else if strings.Contains(pressedButton, "Uninstall certificate for Safari") { err := cert.UninstallCertificates() if err != nil { @@ -125,8 +126,8 @@ func (s *Systray) start() { log.Errorf("cannot set installCerts value in config.ini: %s", err) } } + s.Restart() } - s.Restart() case <-mPause.ClickedCh: s.Pause() case <-mQuit.ClickedCh: From 1ec717132cd610049f11db3d7db052319ba37cd9 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Tue, 7 May 2024 15:07:13 +0200 Subject: [PATCH 18/19] Do not modify the config if the default browser is not Safari --- certificates/certificates.go | 3 --- main.go | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/certificates/certificates.go b/certificates/certificates.go index a3a236a2..34ca9865 100644 --- a/certificates/certificates.go +++ b/certificates/certificates.go @@ -283,9 +283,6 @@ func isExpired() (bool, error) { // PromptInstallCertsSafari prompts the user to install the HTTPS certificates if they are using Safari func PromptInstallCertsSafari() bool { - if GetDefaultBrowserName() != "Safari" { - return false - } buttonPressed := utilities.UserPrompt("display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nIf you use Safari, you need to install it.\" buttons {\"Do not install\", \"Install the certificate for Safari\"} default button 2 with title \"Arduino Agent: Install Certificates\"") return strings.Contains(string(buttonPressed), "button returned:Install the certificate for Safari") } diff --git a/main.go b/main.go index 8cefa995..0231548d 100755 --- a/main.go +++ b/main.go @@ -223,7 +223,7 @@ func loop() { // if the default browser is Safari, prompt the user to install HTTPS certificates // and eventually install them - if runtime.GOOS == "darwin" { + if runtime.GOOS == "darwin" && cert.GetDefaultBrowserName() == "Safari" { if exist, err := installCertsKeyExists(configPath.String()); err != nil { log.Panicf("config.ini cannot be parsed: %s", err) } else if !exist { @@ -370,7 +370,7 @@ func loop() { } // check if the HTTPS certificates are expired and prompt the user to update them on macOS - if runtime.GOOS == "darwin" { + if runtime.GOOS == "darwin" && cert.GetDefaultBrowserName() == "Safari" { if *installCerts { if config.CertsExist() { cert.PromptExpiredCerts(config.GetCertificatesDir()) From 27d8b765568bbe4125893c42293ef6cd844fd724 Mon Sep 17 00:00:00 2001 From: Xayton <30591904+Xayton@users.noreply.github.com> Date: Tue, 7 May 2024 17:41:49 +0200 Subject: [PATCH 19/19] Small messages/titles fixes --- certificates/certificates.go | 4 ++-- systray/systray_real.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/certificates/certificates.go b/certificates/certificates.go index 34ca9865..baac7c33 100644 --- a/certificates/certificates.go +++ b/certificates/certificates.go @@ -283,7 +283,7 @@ func isExpired() (bool, error) { // PromptInstallCertsSafari prompts the user to install the HTTPS certificates if they are using Safari func PromptInstallCertsSafari() bool { - buttonPressed := utilities.UserPrompt("display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nIf you use Safari, you need to install it.\" buttons {\"Do not install\", \"Install the certificate for Safari\"} default button 2 with title \"Arduino Agent: Install Certificates\"") + buttonPressed := utilities.UserPrompt("display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nIf you use Safari, you need to install it.\" buttons {\"Do not install\", \"Install the certificate for Safari\"} default button 2 with title \"Arduino Agent: Install certificate\"") return strings.Contains(string(buttonPressed), "button returned:Install the certificate for Safari") } @@ -292,7 +292,7 @@ func PromptExpiredCerts(certDir *paths.Path) { if expired, err := isExpired(); err != nil { log.Errorf("cannot check if certificates are expired something went wrong: %s", err) } else if expired { - buttonPressed := utilities.UserPrompt("display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nYour certificate is expired or close to expiration. Do you want to update it?\" buttons {\"Do not update\", \"Update the certificate for Safari\"} default button 2 with title \"Arduino Agent: Update Certificates\"") + buttonPressed := utilities.UserPrompt("display dialog \"The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nYour certificate is expired or close to expiration. Do you want to update it?\" buttons {\"Do not update\", \"Update the certificate for Safari\"} default button 2 with title \"Arduino Agent: Update certificate\"") if strings.Contains(string(buttonPressed), "button returned:Update the certificate for Safari") { err := UninstallCertificates() if err != nil { diff --git a/systray/systray_real.go b/systray/systray_real.go index 2b06b24c..503373d8 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -95,7 +95,7 @@ func (s *Systray) start() { s.updateMenuItem(mRmCrashes, config.LogsIsEmpty()) case <-mManageCerts.ClickedCh: infoMsg := "The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\n\nYour HTTPS certificate status:\n" - buttons := "{\"Install certificate for Safari\", \"OK\"} default button \"OK\"" + buttons := "{\"Install the certificate for Safari\", \"OK\"} default button \"OK\"" certDir := config.GetCertificatesDir() if config.CertsExist() { expDate, err := cert.GetExpirationDate() @@ -103,7 +103,7 @@ func (s *Systray) start() { log.Errorf("cannot get certificates expiration date, something went wrong: %s", err) } infoMsg = infoMsg + "- Certificate installed: Yes\n- Certificate trusted: Yes\n- Certificate expiration date: " + expDate - buttons = "{\"Uninstall certificate for Safari\", \"OK\"} default button \"OK\"" + buttons = "{\"Uninstall the certificate for Safari\", \"OK\"} default button \"OK\"" } else { infoMsg = infoMsg + "- Certificate installed: No\n- Certificate trusted: N/A\n- Certificate expiration date: N/A" }