From f43b0e4639e868cab7b44d16cffd0cafef77ae62 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Wed, 8 May 2024 16:42:12 +0200 Subject: [PATCH 01/19] Fix check on buttons returning the correct message --- systray/systray_real.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/systray/systray_real.go b/systray/systray_real.go index 503373d8..1bb2d3c2 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -108,14 +108,14 @@ func (s *Systray) start() { 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 certificate\"") - if strings.Contains(pressedButton, "Install certificate for Safari") { + if strings.Contains(pressedButton, "Install the certificate for Safari") { cert.GenerateAndInstallCertificates(certDir) err := config.SetInstallCertsIni(s.currentConfigFilePath.String(), "true") 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") { + } else if strings.Contains(pressedButton, "Uninstall the certificate for Safari") { err := cert.UninstallCertificates() if err != nil { log.Errorf("cannot uninstall certificates something went wrong: %s", err) From f69980a0bc21d3435d6475bdadb3fee429f39b99 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Wed, 8 May 2024 16:43:38 +0200 Subject: [PATCH 02/19] Update certificates regardless of the default browser --- main.go | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/main.go b/main.go index 0231548d..33698606 100755 --- a/main.go +++ b/main.go @@ -369,20 +369,18 @@ func loop() { } } - // check if the HTTPS certificates are expired and prompt the user to update them on macOS - if runtime.GOOS == "darwin" && cert.GetDefaultBrowserName() == "Safari" { - 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 - cert.GenerateAndInstallCertificates(config.GetCertificatesDir()) - } else { - err = config.SetInstallCertsIni(configPath.String(), "false") - if err != nil { - log.Panicf("config.ini cannot be parsed: %s", err) - } + // check if the HTTPS certificates are expired or expiring and prompt the user to update them on macOS + if runtime.GOOS == "darwin" && *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 + cert.GenerateAndInstallCertificates(config.GetCertificatesDir()) + } else { + err = config.SetInstallCertsIni(configPath.String(), "false") + if err != nil { + log.Panicf("config.ini cannot be parsed: %s", err) } } } From f7bd6e37c8473b64d297c61f358561b3c3930e40 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Wed, 8 May 2024 16:49:31 +0200 Subject: [PATCH 03/19] Set installCerts when the certificate is installed from previous versions of the Agent regardless of the default browser --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 33698606..fe7eabc0 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" && cert.GetDefaultBrowserName() == "Safari" { + if runtime.GOOS == "darwin" { if exist, err := installCertsKeyExists(configPath.String()); err != nil { log.Panicf("config.ini cannot be parsed: %s", err) } else if !exist { @@ -232,7 +232,7 @@ func loop() { if err != nil { log.Panicf("config.ini cannot be parsed: %s", err) } - } else if cert.PromptInstallCertsSafari() { + } else if cert.GetDefaultBrowserName() == "Safari" && cert.PromptInstallCertsSafari() { err = config.SetInstallCertsIni(configPath.String(), "true") if err != nil { log.Panicf("config.ini cannot be parsed: %s", err) From 80063109435a62acff53cdb6e4e1a9b3783cbe89 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Wed, 8 May 2024 16:59:18 +0200 Subject: [PATCH 04/19] Do not set installCerts to false if the default browser is not Safari --- main.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index fe7eabc0..71918dac 100755 --- a/main.go +++ b/main.go @@ -232,16 +232,18 @@ func loop() { if err != nil { log.Panicf("config.ini cannot be parsed: %s", err) } - } else if cert.GetDefaultBrowserName() == "Safari" && cert.PromptInstallCertsSafari() { - err = config.SetInstallCertsIni(configPath.String(), "true") - if err != nil { - log.Panicf("config.ini cannot be parsed: %s", err) - } - cert.GenerateAndInstallCertificates(config.GetCertificatesDir()) - } else { - err = config.SetInstallCertsIni(configPath.String(), "false") - if err != nil { - log.Panicf("config.ini cannot be parsed: %s", err) + } else if cert.GetDefaultBrowserName() == "Safari" { + if cert.PromptInstallCertsSafari() { + err = config.SetInstallCertsIni(configPath.String(), "true") + if err != nil { + log.Panicf("config.ini cannot be parsed: %s", err) + } + cert.GenerateAndInstallCertificates(config.GetCertificatesDir()) + } else { + err = config.SetInstallCertsIni(configPath.String(), "false") + if err != nil { + log.Panicf("config.ini cannot be parsed: %s", err) + } } } } From aa02ed0e11ed4920cda2f6eb84a9bcb840bfb8f5 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Thu, 9 May 2024 09:33:53 +0200 Subject: [PATCH 05/19] Do not ask again to update the certificate if the user refuses once --- certificates/certificates.go | 30 ++----------------------- main.go | 31 ++++++++++++++++++++++---- systray/systray_real.go | 43 +++++++++++++++++++----------------- utilities/utilities.go | 6 ++--- 4 files changed, 55 insertions(+), 55 deletions(-) diff --git a/certificates/certificates.go b/certificates/certificates.go index baac7c33..d1069b7a 100644 --- a/certificates/certificates.go +++ b/certificates/certificates.go @@ -30,10 +30,8 @@ import ( "math/big" "net" "os" - "strings" "time" - "github.com/arduino/arduino-create-agent/utilities" "github.com/arduino/go-paths-helper" log "github.com/sirupsen/logrus" ) @@ -270,8 +268,8 @@ func DeleteCertificates(certDir *paths.Path) { certDir.Join("cert.cer").Remove() } -// isExpired checks if a certificate is expired or about to expire (less than 1 month) -func isExpired() (bool, error) { +// IsExpired checks if a certificate is expired or about to expire (less than 1 month) +func IsExpired() (bool, error) { bound := time.Now().AddDate(0, 1, 0) dateS, err := GetExpirationDate() if err != nil { @@ -281,30 +279,6 @@ func isExpired() (bool, error) { return date.Before(bound), nil } -// 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 certificate\"") - 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 -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 certificate\"") - 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) - } else { - DeleteCertificates(certDir) - GenerateAndInstallCertificates(certDir) - } - } - } -} - // GenerateAndInstallCertificates generates and installs the certificates func GenerateAndInstallCertificates(certDir *paths.Path) { GenerateCertificates(certDir) diff --git a/main.go b/main.go index 71918dac..02e931c4 100755 --- a/main.go +++ b/main.go @@ -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() { - 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\"") + utilities.UserPrompt("Old agent installation of the Arduino Create Agent found, please uninstall it before launching the new one", "\"OK\"", "OK", "Error") os.Exit(0) } @@ -233,7 +233,7 @@ func loop() { log.Panicf("config.ini cannot be parsed: %s", err) } } else if cert.GetDefaultBrowserName() == "Safari" { - if cert.PromptInstallCertsSafari() { + if promptInstallCertsSafari() { err = config.SetInstallCertsIni(configPath.String(), "true") if err != nil { log.Panicf("config.ini cannot be parsed: %s", err) @@ -374,8 +374,27 @@ func loop() { // check if the HTTPS certificates are expired or expiring and prompt the user to update them on macOS if runtime.GOOS == "darwin" && *installCerts { if config.CertsExist() { - cert.PromptExpiredCerts(config.GetCertificatesDir()) - } else if cert.PromptInstallCertsSafari() { + certDir := config.GetCertificatesDir() + if expired, err := cert.IsExpired(); err != nil { + log.Errorf("cannot check if certificates are expired something went wrong: %s", err) + } else if expired { + buttonPressed := utilities.UserPrompt("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?", "{\"Do not update\", \"Update the certificate for Safari\"}", "Update the certificate for Safari", "Arduino Agent: Update certificate") + if buttonPressed { + err := cert.UninstallCertificates() + if err != nil { + log.Errorf("cannot uninstall certificates something went wrong: %s", err) + } else { + cert.DeleteCertificates(certDir) + cert.GenerateAndInstallCertificates(certDir) + } + } else { + err = config.SetInstallCertsIni(configPath.String(), "false") + if err != nil { + log.Panicf("config.ini cannot be parsed: %s", err) + } + } + } + } else if promptInstallCertsSafari() { // installing the certificates from scratch at this point should only happen if // something went wrong during previous installation attempts cert.GenerateAndInstallCertificates(config.GetCertificatesDir()) @@ -534,3 +553,7 @@ func installCertsKeyExists(filename string) (bool, error) { } return cfg.Section("").HasKey("installCerts"), nil } + +func promptInstallCertsSafari() bool { + return utilities.UserPrompt("The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nIf you use Safari, you need to install it.", "{\"Do not install\", \"Install the certificate for Safari\"}", "Install the certificate for Safari", "Arduino Agent: Install certificate") +} diff --git a/systray/systray_real.go b/systray/systray_real.go index 1bb2d3c2..957615e2 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -22,7 +22,6 @@ package systray import ( "os" "runtime" - "strings" "fyne.io/systray" cert "github.com/arduino/arduino-create-agent/certificates" @@ -95,7 +94,8 @@ 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 the certificate for Safari\", \"OK\"} default button \"OK\"" + buttons := "{\"OK\", \"Install the certificate for Safari\"}" + defaultButton := "Install the certificate for Safari" certDir := config.GetCertificatesDir() if config.CertsExist() { expDate, err := cert.GetExpirationDate() @@ -103,30 +103,33 @@ 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 the certificate for Safari\", \"OK\"} default button \"OK\"" + buttons = "{\"OK\", \"Uninstall the certificate for Safari\"}" + defaultButton = "Uninstall the certificate for Safari" + pressedButton := utilities.UserPrompt(infoMsg, buttons, defaultButton, "Arduino Agent: Manage HTTPS certificate") + if pressedButton { + 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() + } } 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 certificate\"") - if strings.Contains(pressedButton, "Install the certificate for Safari") { - cert.GenerateAndInstallCertificates(certDir) - err := config.SetInstallCertsIni(s.currentConfigFilePath.String(), "true") - if err != nil { - log.Errorf("cannot set installCerts value in config.ini: %s", err) - } - s.Restart() - } else if strings.Contains(pressedButton, "Uninstall the 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") + pressedButton := utilities.UserPrompt(infoMsg, buttons, defaultButton, "Arduino Agent: Manage HTTPS certificate") + if pressedButton { + cert.GenerateAndInstallCertificates(certDir) + err := config.SetInstallCertsIni(s.currentConfigFilePath.String(), "true") if err != nil { log.Errorf("cannot set installCerts value in config.ini: %s", err) } + s.Restart() } - s.Restart() } case <-mPause.ClickedCh: s.Pause() diff --git a/utilities/utilities.go b/utilities/utilities.go index 63f09103..c31730b1 100644 --- a/utilities/utilities.go +++ b/utilities/utilities.go @@ -151,8 +151,8 @@ func VerifyInput(input string, signature string) error { } // UserPrompt executes an osascript and returns the pressed button -func UserPrompt(dialog string) string { - oscmd := exec.Command("osascript", "-e", dialog) +func UserPrompt(dialog string, buttons string, defaultButton string, title string) bool { + oscmd := exec.Command("osascript", "-e", "display dialog \""+dialog+"\" buttons "+buttons+" default button\""+defaultButton+"\" with title \""+title+"\"") pressedButton, _ := oscmd.Output() - return string(pressedButton) + return strings.Contains(string(pressedButton), "button returned:"+defaultButton) } From e46bfbf0bf192369ba6572aa603f7e96af039223 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Thu, 9 May 2024 09:44:26 +0200 Subject: [PATCH 06/19] Fix user script on macOS --- certificates/install_darwin.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index 892c390b..a36bec59 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -172,7 +172,7 @@ func InstallCertificate(cert *paths.Path) error { p := C.installCert(ccert) s := C.GoString(p) if len(s) != 0 { - utilities.UserPrompt("display dialog \"" + s + "\" buttons \"OK\" with title \"Arduino Agent: Error installing certificates\"") + utilities.UserPrompt(s, "\"OK\"", "OK", "Arduino Agent: Error installing certificates") UninstallCertificates() return errors.New(s) } @@ -186,7 +186,7 @@ func UninstallCertificates() error { p := C.uninstallCert() s := C.GoString(p) if len(s) != 0 { - utilities.UserPrompt("display dialog \"" + s + "\" buttons \"OK\" with title \"Arduino Agent: Error uninstalling certificates\"") + utilities.UserPrompt(s, "\"OK\"", "OK", "Arduino Agent: Error uninstalling certificates") return errors.New(s) } return nil @@ -200,7 +200,7 @@ func GetExpirationDate() (string, error) { p := C.getExpirationDate(dateString) s := C.GoString(p) if len(s) != 0 { - utilities.UserPrompt("display dialog \"" + s + "\" buttons \"OK\" with title \"Arduino Agent: Error retrieving expiration date\"") + utilities.UserPrompt(s, "\"OK\"", "OK", "Arduino Agent: Error retrieving expiration date") return "", errors.New(s) } date := C.GoString(dateString) From 7f4cdf62bf107df1cbdee8f1f7bc1cde71a5a29f Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Thu, 9 May 2024 14:01:55 +0200 Subject: [PATCH 07/19] Check for the presence of the certificate in the keychain to determine if it is installed --- certificates/install_darwin.go | 30 ++++++++++++++++++++++++++++++ certificates/install_default.go | 6 ++++++ main.go | 4 ++-- systray/systray_real.go | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index a36bec59..79fea5bf 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -150,6 +150,25 @@ const char *getDefaultBrowserName() { return ""; } + +const char *certInKeychain() { + // Each line is a key-value of the dictionary. Note: the the inverted order, value first then key. + NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: + (id)kSecClassCertificate, kSecClass, + CFSTR("Arduino"), kSecAttrLabel, + kSecMatchLimitOne, kSecMatchLimit, + kCFBooleanTrue, kSecReturnAttributes, + nil]; + + OSStatus err = noErr; + // Use this function to check for errors + err = SecItemCopyMatching((CFDictionaryRef)dict, nil); + NSString *exists = @"false"; + if (err == noErr) { + exists = @"true"; + } + return [exists cStringUsingEncoding:[NSString defaultCStringEncoding]];; +} */ import "C" import ( @@ -213,3 +232,14 @@ func GetDefaultBrowserName() string { p := C.getDefaultBrowserName() return C.GoString(p) } + +// CertInKeychain checks if the certificate is stored inside the keychain +func CertInKeychain() bool { + log.Infof("Checking if the Arduino certificate is in the keychain") + p := C.certInKeychain() + s := C.GoString(p) + if s == "true" { + return true + } + return false +} diff --git a/certificates/install_default.go b/certificates/install_default.go index 8013c018..f98efbcd 100644 --- a/certificates/install_default.go +++ b/certificates/install_default.go @@ -48,3 +48,9 @@ func GetDefaultBrowserName() string { log.Warn("platform not supported for retrieving default browser name") return "" } + +// CertInKeychain won't do anything on unsupported Operative Systems +func CertInKeychain() bool { + log.Warn("platform not supported for verifying the certificate existence") + return false +} diff --git a/main.go b/main.go index 02e931c4..8648b363 100755 --- a/main.go +++ b/main.go @@ -227,7 +227,7 @@ func loop() { if exist, err := installCertsKeyExists(configPath.String()); err != nil { log.Panicf("config.ini cannot be parsed: %s", err) } else if !exist { - if config.CertsExist() { + if cert.CertInKeychain() || config.CertsExist() { err = config.SetInstallCertsIni(configPath.String(), "true") if err != nil { log.Panicf("config.ini cannot be parsed: %s", err) @@ -373,7 +373,7 @@ func loop() { // check if the HTTPS certificates are expired or expiring and prompt the user to update them on macOS if runtime.GOOS == "darwin" && *installCerts { - if config.CertsExist() { + if cert.CertInKeychain() || config.CertsExist() { certDir := config.GetCertificatesDir() if expired, err := cert.IsExpired(); err != nil { log.Errorf("cannot check if certificates are expired something went wrong: %s", err) diff --git a/systray/systray_real.go b/systray/systray_real.go index 957615e2..4fabda9a 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -97,7 +97,7 @@ func (s *Systray) start() { buttons := "{\"OK\", \"Install the certificate for Safari\"}" defaultButton := "Install the certificate for Safari" certDir := config.GetCertificatesDir() - if config.CertsExist() { + if cert.CertInKeychain() || config.CertsExist() { expDate, err := cert.GetExpirationDate() if err != nil { log.Errorf("cannot get certificates expiration date, something went wrong: %s", err) From 4285c046a63567986fad235dfbf9e6b7f0e4b4e3 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Thu, 9 May 2024 16:04:37 +0200 Subject: [PATCH 08/19] Fix getExpirationDate breaking when the certificate is expired --- certificates/certificates.go | 3 +-- certificates/install_darwin.go | 13 +++++++------ certificates/install_default.go | 5 +++-- systray/systray_real.go | 3 ++- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/certificates/certificates.go b/certificates/certificates.go index d1069b7a..172ff054 100644 --- a/certificates/certificates.go +++ b/certificates/certificates.go @@ -271,11 +271,10 @@ func DeleteCertificates(certDir *paths.Path) { // IsExpired checks if a certificate is expired or about to expire (less than 1 month) func IsExpired() (bool, error) { bound := time.Now().AddDate(0, 1, 0) - dateS, err := GetExpirationDate() + date, err := GetExpirationDate() if err != nil { return false, err } - date, _ := time.Parse(time.DateTime, dateS) return date.Before(bound), nil } diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index 79fea5bf..bec3b1b4 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -111,11 +111,11 @@ const char *getExpirationDate(char *expirationDate){ } // Get data from the certificate. We just need the "invalidity date" property. - CFDictionaryRef valuesDict = SecCertificateCopyValues(cert, (__bridge CFArrayRef)@[(__bridge id)kSecOIDInvalidityDate], NULL); + CFDictionaryRef valuesDict = SecCertificateCopyValues(cert, (__bridge CFArrayRef)@[(__bridge id)kSecOIDX509V1ValidityNotAfter], NULL); id expirationDateValue; if(valuesDict){ - CFDictionaryRef invalidityDateDictionaryRef = CFDictionaryGetValue(valuesDict, kSecOIDInvalidityDate); + CFDictionaryRef invalidityDateDictionaryRef = CFDictionaryGetValue(valuesDict, kSecOIDX509V1ValidityNotAfter); if(invalidityDateDictionaryRef){ CFTypeRef invalidityRef = CFDictionaryGetValue(invalidityDateDictionaryRef, kSecPropertyKeyValue); if(invalidityRef){ @@ -173,7 +173,8 @@ const char *certInKeychain() { import "C" import ( "errors" - "strings" + "strconv" + "time" "unsafe" log "github.com/sirupsen/logrus" @@ -212,7 +213,7 @@ func UninstallCertificates() error { } // GetExpirationDate returns the expiration date of a certificate stored in the keychain -func GetExpirationDate() (string, error) { +func GetExpirationDate() (time.Time, error) { log.Infof("Retrieving certificate's expiration date") dateString := C.CString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") // 32 characters string defer C.free(unsafe.Pointer(dateString)) @@ -222,8 +223,8 @@ func GetExpirationDate() (string, error) { utilities.UserPrompt(s, "\"OK\"", "OK", "Arduino Agent: Error retrieving expiration date") return "", errors.New(s) } - date := C.GoString(dateString) - return strings.ReplaceAll(date, " +0000", ""), nil + dateValue, _ := strconv.Atoi(C.GoString(dateString)) + return time.Unix(dateValue, 0).AddDate(31, 0, 0), nil } // GetDefaultBrowserName returns the name of the default browser diff --git a/certificates/install_default.go b/certificates/install_default.go index f98efbcd..533574d6 100644 --- a/certificates/install_default.go +++ b/certificates/install_default.go @@ -19,6 +19,7 @@ package certificates import ( "errors" + "time" log "github.com/sirupsen/logrus" @@ -38,9 +39,9 @@ func UninstallCertificates() error { } // GetExpirationDate won't do anything on unsupported Operative Systems -func GetExpirationDate() (string, error) { +func GetExpirationDate() (time.Time, error) { log.Warn("platform not supported for retrieving certificates expiration date") - return "", errors.New("platform not supported for retrieving certificates expiration date") + return time.Time{}, errors.New("platform not supported for retrieving certificates expiration date") } // GetDefaultBrowserName won't do anything on unsupported Operative Systems diff --git a/systray/systray_real.go b/systray/systray_real.go index 4fabda9a..68cbd367 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -22,6 +22,7 @@ package systray import ( "os" "runtime" + "time" "fyne.io/systray" cert "github.com/arduino/arduino-create-agent/certificates" @@ -102,7 +103,7 @@ func (s *Systray) start() { 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 + infoMsg = infoMsg + "- Certificate installed: Yes\n- Certificate trusted: Yes\n- Certificate expiration date: " + expDate.Format(time.DateTime) buttons = "{\"OK\", \"Uninstall the certificate for Safari\"}" defaultButton = "Uninstall the certificate for Safari" pressedButton := utilities.UserPrompt(infoMsg, buttons, defaultButton, "Arduino Agent: Manage HTTPS certificate") From 406454197d9b8cd221d1e0d0fc4b36af318fdfa5 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Thu, 9 May 2024 17:01:13 +0200 Subject: [PATCH 09/19] Fix return value in case of error --- certificates/install_darwin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index bec3b1b4..35387c56 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -221,9 +221,9 @@ func GetExpirationDate() (time.Time, error) { s := C.GoString(p) if len(s) != 0 { utilities.UserPrompt(s, "\"OK\"", "OK", "Arduino Agent: Error retrieving expiration date") - return "", errors.New(s) + return time.Time{}, errors.New(s) } - dateValue, _ := strconv.Atoi(C.GoString(dateString)) + dateValue, _ := strconv.ParseInt(C.GoString(dateString), 10, 64) return time.Unix(dateValue, 0).AddDate(31, 0, 0), nil } From cf315464198033e2915b8212e77532734d5441c3 Mon Sep 17 00:00:00 2001 From: Xayton <30591904+Xayton@users.noreply.github.com> Date: Thu, 9 May 2024 17:55:40 +0200 Subject: [PATCH 10/19] getExpirationDate rewritten to use the correct expiration field. --- certificates/install_darwin.go | 76 +++++++++++++++++----------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index 35387c56..fb531f38 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -24,6 +24,13 @@ package certificates #cgo LDFLAGS: -framework Cocoa #import + +// Used to return error strings (as NSString) as a C-string to the Go code. +const char *toErrorString(NSString *errString) { + NSLog(@"%@", errString); + return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; +} + const char *installCert(const char *path) { NSURL *url = [NSURL fileURLWithPath:@(path) isDirectory:NO]; NSData *rootCertData = [NSData dataWithContentsOfURL:url]; @@ -90,7 +97,9 @@ const char *uninstallCert() { return ""; } -const char *getExpirationDate(char *expirationDate){ +// Returns the expiration date "kSecOIDX509V1ValidityNotAfter" of the Arduino certificate. +// The value is returned as a CFAbsoluteTime: a long number of seconds from the date of 1 Jan 2001 00:00:00 GMT. +const char *getExpirationDate(long *expirationDate) { // Create a key-value dictionary used to query the Keychain and look for the "Arduino" root certificate. NSDictionary *getquery = @{ (id)kSecClass: (id)kSecClassCertificate, @@ -101,42 +110,32 @@ const char *getExpirationDate(char *expirationDate){ OSStatus err = noErr; SecCertificateRef cert = NULL; - // Use this function to check for errors + // Search the keychain for certificates matching the query above. err = SecItemCopyMatching((CFDictionaryRef)getquery, (CFTypeRef *)&cert); - if (err != noErr){ NSString *errString = [@"Error: " stringByAppendingFormat:@"%d", err]; NSLog(@"%@", errString); 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)kSecOIDX509V1ValidityNotAfter], NULL); - - id expirationDateValue; - if(valuesDict){ - CFDictionaryRef invalidityDateDictionaryRef = CFDictionaryGetValue(valuesDict, kSecOIDX509V1ValidityNotAfter); - if(invalidityDateDictionaryRef){ - CFTypeRef invalidityRef = CFDictionaryGetValue(invalidityDateDictionaryRef, kSecPropertyKeyValue); - if(invalidityRef){ - expirationDateValue = CFBridgingRelease(invalidityRef); - } - } - CFRelease(valuesDict); - } + // Get data from the certificate, as a dictionary of properties. We just need the "invalidity not after" property. + CFDictionaryRef certDict = SecCertificateCopyValues(cert, + (__bridge CFArrayRef)@[(__bridge id)kSecOIDX509V1ValidityNotAfter], NULL); + if (certDict == NULL) return toErrorString(@"SecCertificateCopyValues failed"); - NSString *outputString = [@"" stringByAppendingFormat:@"%@", expirationDateValue]; - 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; + // Get the "validity not after" property as a dictionary, and get the "value" key (that is a number). + CFDictionaryRef validityNotAfterDict = CFDictionaryGetValue(certDict, kSecOIDX509V1ValidityNotAfter); + if (validityNotAfterDict == NULL) return toErrorString(@"CFDictionaryGetValue (validity) failed"); - return ""; + CFNumberRef number = (CFNumberRef)CFDictionaryGetValue(validityNotAfterDict, kSecPropertyKeyValue); + if (number == NULL) return toErrorString(@"CFDictionaryGetValue (keyValue) failed"); + + CFNumberGetValue(number, kCFNumberSInt64Type, expirationDate); + // NSLog(@"Certificate validity not after: %ld", *expirationDate); + + CFRelease(certDict); + return ""; // No error. } const char *getDefaultBrowserName() { @@ -173,7 +172,6 @@ const char *certInKeychain() { import "C" import ( "errors" - "strconv" "time" "unsafe" @@ -215,16 +213,20 @@ func UninstallCertificates() error { // GetExpirationDate returns the expiration date of a certificate stored in the keychain func GetExpirationDate() (time.Time, error) { log.Infof("Retrieving certificate's expiration date") - 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 { - utilities.UserPrompt(s, "\"OK\"", "OK", "Arduino Agent: Error retrieving expiration date") - return time.Time{}, errors.New(s) + + expirationDateLong := C.long(0) + + err := C.getExpirationDate(&expirationDateLong) + errString := C.GoString(err) + if len(errString) > 0 { + utilities.UserPrompt(errString, "\"OK\"", "OK", "Arduino Agent: Error retrieving expiration date") + return time.Time{}, errors.New(errString) } - dateValue, _ := strconv.ParseInt(C.GoString(dateString), 10, 64) - return time.Unix(dateValue, 0).AddDate(31, 0, 0), nil + + // The expirationDate is the number of seconds from the date of 1 Jan 2001 00:00:00 GMT. + // Add 31 years to convert it to Unix Epoch. + expirationDate := int64(expirationDateLong) + return time.Unix(expirationDate, 0).AddDate(31, 0, 0), nil } // GetDefaultBrowserName returns the name of the default browser From 411d051fb7882714bbeb4a8700a43099a4629fc5 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Thu, 9 May 2024 18:00:47 +0200 Subject: [PATCH 11/19] Separate osascript default button from the one to press --- main.go | 6 +++--- systray/systray_real.go | 12 ++++++------ utilities/utilities.go | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index 8648b363..1ca857b0 100755 --- a/main.go +++ b/main.go @@ -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() { - utilities.UserPrompt("Old agent installation of the Arduino Create Agent found, please uninstall it before launching the new one", "\"OK\"", "OK", "Error") + utilities.UserPrompt("Old agent installation of the Arduino Create Agent found, please uninstall it before launching the new one", "\"OK\"", "OK", "OK", "Error") os.Exit(0) } @@ -378,7 +378,7 @@ func loop() { if expired, err := cert.IsExpired(); err != nil { log.Errorf("cannot check if certificates are expired something went wrong: %s", err) } else if expired { - buttonPressed := utilities.UserPrompt("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?", "{\"Do not update\", \"Update the certificate for Safari\"}", "Update the certificate for Safari", "Arduino Agent: Update certificate") + buttonPressed := utilities.UserPrompt("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?", "{\"Do not update\", \"Update the certificate for Safari\"}", "Update the certificate for Safari", "Update the certificate for Safari", "Arduino Agent: Update certificate") if buttonPressed { err := cert.UninstallCertificates() if err != nil { @@ -555,5 +555,5 @@ func installCertsKeyExists(filename string) (bool, error) { } func promptInstallCertsSafari() bool { - return utilities.UserPrompt("The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nIf you use Safari, you need to install it.", "{\"Do not install\", \"Install the certificate for Safari\"}", "Install the certificate for Safari", "Arduino Agent: Install certificate") + return utilities.UserPrompt("The Arduino Agent needs a local HTTPS certificate to work correctly with Safari.\nIf you use Safari, you need to install it.", "{\"Do not install\", \"Install the certificate for Safari\"}", "Install the certificate for Safari", "Install the certificate for Safari", "Arduino Agent: Install certificate") } diff --git a/systray/systray_real.go b/systray/systray_real.go index 68cbd367..af7913dc 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -95,8 +95,8 @@ 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 the certificate for Safari\"}" - defaultButton := "Install the certificate for Safari" + buttons := "{\"Install the certificate for Safari\", \"OK\"}" + toPress := "Install the certificate for Safari" certDir := config.GetCertificatesDir() if cert.CertInKeychain() || config.CertsExist() { expDate, err := cert.GetExpirationDate() @@ -104,9 +104,9 @@ 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.Format(time.DateTime) - buttons = "{\"OK\", \"Uninstall the certificate for Safari\"}" - defaultButton = "Uninstall the certificate for Safari" - pressedButton := utilities.UserPrompt(infoMsg, buttons, defaultButton, "Arduino Agent: Manage HTTPS certificate") + buttons = "{\"Uninstall the certificate for Safari\", \"OK\"}" + toPress = "Uninstall the certificate for Safari" + pressedButton := utilities.UserPrompt(infoMsg, buttons, "OK", toPress, "Arduino Agent: Manage HTTPS certificate") if pressedButton { err := cert.UninstallCertificates() if err != nil { @@ -122,7 +122,7 @@ func (s *Systray) start() { } } else { infoMsg = infoMsg + "- Certificate installed: No\n- Certificate trusted: N/A\n- Certificate expiration date: N/A" - pressedButton := utilities.UserPrompt(infoMsg, buttons, defaultButton, "Arduino Agent: Manage HTTPS certificate") + pressedButton := utilities.UserPrompt(infoMsg, buttons, "OK", toPress, "Arduino Agent: Manage HTTPS certificate") if pressedButton { cert.GenerateAndInstallCertificates(certDir) err := config.SetInstallCertsIni(s.currentConfigFilePath.String(), "true") diff --git a/utilities/utilities.go b/utilities/utilities.go index c31730b1..5979732d 100644 --- a/utilities/utilities.go +++ b/utilities/utilities.go @@ -151,8 +151,8 @@ func VerifyInput(input string, signature string) error { } // UserPrompt executes an osascript and returns the pressed button -func UserPrompt(dialog string, buttons string, defaultButton string, title string) bool { +func UserPrompt(dialog string, buttons string, defaultButton string, toPress string, title string) bool { oscmd := exec.Command("osascript", "-e", "display dialog \""+dialog+"\" buttons "+buttons+" default button\""+defaultButton+"\" with title \""+title+"\"") pressedButton, _ := oscmd.Output() - return strings.Contains(string(pressedButton), "button returned:"+defaultButton) + return strings.Contains(string(pressedButton), "button returned:"+toPress) } From c11610b46b1fbab2084aed587b7f5db08c3161e2 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Thu, 9 May 2024 18:08:16 +0200 Subject: [PATCH 12/19] Fix leftover buttons --- certificates/install_darwin.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index fb531f38..a06f9497 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -190,7 +190,7 @@ func InstallCertificate(cert *paths.Path) error { p := C.installCert(ccert) s := C.GoString(p) if len(s) != 0 { - utilities.UserPrompt(s, "\"OK\"", "OK", "Arduino Agent: Error installing certificates") + utilities.UserPrompt(s, "\"OK\"", "OK", "OK", "Arduino Agent: Error installing certificates") UninstallCertificates() return errors.New(s) } @@ -204,7 +204,7 @@ func UninstallCertificates() error { p := C.uninstallCert() s := C.GoString(p) if len(s) != 0 { - utilities.UserPrompt(s, "\"OK\"", "OK", "Arduino Agent: Error uninstalling certificates") + utilities.UserPrompt(s, "\"OK\"", "OK", "OK", "Arduino Agent: Error uninstalling certificates") return errors.New(s) } return nil @@ -219,7 +219,7 @@ func GetExpirationDate() (time.Time, error) { err := C.getExpirationDate(&expirationDateLong) errString := C.GoString(err) if len(errString) > 0 { - utilities.UserPrompt(errString, "\"OK\"", "OK", "Arduino Agent: Error retrieving expiration date") + utilities.UserPrompt(errString, "\"OK\"", "OK", "OK", "Arduino Agent: Error retrieving expiration date") return time.Time{}, errors.New(errString) } From 9494f250264fe47ea30be2529a716f2b163afcad Mon Sep 17 00:00:00 2001 From: Xayton <30591904+Xayton@users.noreply.github.com> Date: Fri, 10 May 2024 15:23:43 +0200 Subject: [PATCH 13/19] Small text fixes in the "manage certificate" dialog --- systray/systray_real.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/systray/systray_real.go b/systray/systray_real.go index af7913dc..5ae79f08 100644 --- a/systray/systray_real.go +++ b/systray/systray_real.go @@ -103,7 +103,7 @@ func (s *Systray) start() { 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.Format(time.DateTime) + infoMsg = infoMsg + "- Certificate installed:\t\tYes\n- Certificate trusted:\t\tYes\n- Certificate expiration:\t" + expDate.Format(time.DateTime) buttons = "{\"Uninstall the certificate for Safari\", \"OK\"}" toPress = "Uninstall the certificate for Safari" pressedButton := utilities.UserPrompt(infoMsg, buttons, "OK", toPress, "Arduino Agent: Manage HTTPS certificate") @@ -121,7 +121,7 @@ func (s *Systray) start() { s.Restart() } } else { - infoMsg = infoMsg + "- Certificate installed: No\n- Certificate trusted: N/A\n- Certificate expiration date: N/A" + infoMsg = infoMsg + "- Certificate installed:\t\tNo\n- Certificate trusted:\t\tN/A\n- Certificate expiration:\tN/A" pressedButton := utilities.UserPrompt(infoMsg, buttons, "OK", toPress, "Arduino Agent: Manage HTTPS certificate") if pressedButton { cert.GenerateAndInstallCertificates(certDir) From d56f231fb278863943257cfd52187eed0e887e8a Mon Sep 17 00:00:00 2001 From: Xayton <30591904+Xayton@users.noreply.github.com> Date: Fri, 10 May 2024 15:24:11 +0200 Subject: [PATCH 14/19] Simplify error management in getExpirationDate --- certificates/install_darwin.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index a06f9497..0a82c4c9 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -107,16 +107,11 @@ const char *getExpirationDate(long *expirationDate) { (id)kSecReturnRef: @YES, }; - OSStatus err = noErr; SecCertificateRef cert = NULL; // Search the keychain for certificates matching the query above. - err = SecItemCopyMatching((CFDictionaryRef)getquery, (CFTypeRef *)&cert); - if (err != noErr){ - NSString *errString = [@"Error: " stringByAppendingFormat:@"%d", err]; - NSLog(@"%@", errString); - return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; - } + OSStatus err = SecItemCopyMatching((CFDictionaryRef)getquery, (CFTypeRef *)&cert); + if (err != noErr) return toErrorString([@"Error getting the certificate: " stringByAppendingFormat:@"%d", err]); // Get data from the certificate, as a dictionary of properties. We just need the "invalidity not after" property. CFDictionaryRef certDict = SecCertificateCopyValues(cert, From 8ec1efccc37633ea2d477374ca19a7b57ca6ecbd Mon Sep 17 00:00:00 2001 From: Xayton <30591904+Xayton@users.noreply.github.com> Date: Fri, 10 May 2024 16:37:51 +0200 Subject: [PATCH 15/19] Fix compiler warnings and move obj-c code into a separate file. --- certificates/certificates.go | 1 - certificates/certificates.h | 7 ++ certificates/certificates.m | 143 ++++++++++++++++++++++++++++++ certificates/install_darwin.go | 154 ++------------------------------- 4 files changed, 157 insertions(+), 148 deletions(-) create mode 100644 certificates/certificates.h create mode 100644 certificates/certificates.m diff --git a/certificates/certificates.go b/certificates/certificates.go index 172ff054..8a8c50d8 100644 --- a/certificates/certificates.go +++ b/certificates/certificates.go @@ -37,7 +37,6 @@ import ( ) var ( - host = "localhost" validFrom = "" validFor = 365 * 24 * time.Hour * 2 // 2 years rsaBits = 2048 diff --git a/certificates/certificates.h b/certificates/certificates.h new file mode 100644 index 00000000..ba717129 --- /dev/null +++ b/certificates/certificates.h @@ -0,0 +1,7 @@ +const char *getDefaultBrowserName(); + +const char *installCert(const char *path); +const char *uninstallCert(); +const char *certInKeychain(); + +const char *getExpirationDate(long *expirationDate); \ No newline at end of file diff --git a/certificates/certificates.m b/certificates/certificates.m new file mode 100644 index 00000000..c8e6abd2 --- /dev/null +++ b/certificates/certificates.m @@ -0,0 +1,143 @@ +#import +#import +#include "certificates.h" + +// Used to return error strings (as NSString) as a C-string to the Go code. +const char *toErrorString(NSString *errString) { + NSLog(@"%@", errString); + return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; +} + +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 ""; +} + +// inspired by https://stackoverflow.com/questions/12798950/ios-install-ssl-certificate-programmatically +const char *installCert(const char *path) { + NSURL *url = [NSURL fileURLWithPath:@(path) isDirectory:NO]; + NSData *rootCertData = [NSData dataWithContentsOfURL:url]; + + OSStatus err = noErr; + SecCertificateRef rootCert = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef) rootCertData); + + CFTypeRef result; + + NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: + (id)kSecClassCertificate, kSecClass, + rootCert, kSecValueRef, + nil]; + + err = SecItemAdd((CFDictionaryRef)dict, &result); + + if (err == noErr) { + NSLog(@"Install root certificate success"); + } else if (err == errSecDuplicateItem) { + NSString *errString = [@"duplicate root certificate entry. Error: " stringByAppendingFormat:@"%d", err]; + NSLog(@"%@", errString); + return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; + } else { + NSString *errString = [@"install root certificate failure. Error: " stringByAppendingFormat:@"%d", err]; + NSLog(@"%@", errString); + return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; + } + + NSDictionary *newTrustSettings = @{(id)kSecTrustSettingsResult: [NSNumber numberWithInt:kSecTrustSettingsResultTrustRoot]}; + err = SecTrustSettingsSetTrustSettings(rootCert, kSecTrustSettingsDomainUser, (__bridge CFTypeRef)(newTrustSettings)); + if (err != errSecSuccess) { + NSString *errString = [@"Could not change the trust setting for a certificate. Error: " stringByAppendingFormat:@"%d", err]; + NSLog(@"%@", errString); + return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; + } + + return ""; +} + +const char *uninstallCert() { + // Each line is a key-value of the dictionary. Note: the the inverted order, value first then key. + NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: + (id)kSecClassCertificate, kSecClass, + CFSTR("Arduino"), kSecAttrLabel, + kSecMatchLimitOne, kSecMatchLimit, + kCFBooleanTrue, kSecReturnAttributes, + nil]; + + OSStatus err = noErr; + // Use this function to check for errors + err = SecItemCopyMatching((CFDictionaryRef)dict, nil); + if (err == noErr) { + err = SecItemDelete((CFDictionaryRef)dict); + if (err != noErr) { + NSString *errString = [@"Could not delete the certificates. Error: " stringByAppendingFormat:@"%d", err]; + NSLog(@"%@", errString); + return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; + } + } else if (err != errSecItemNotFound){ + NSString *errString = [@"Error: " stringByAppendingFormat:@"%d", err]; + NSLog(@"%@", errString); + return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; + } + return ""; +} + +const char *certInKeychain() { + // Each line is a key-value of the dictionary. Note: the the inverted order, value first then key. + NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: + (id)kSecClassCertificate, kSecClass, + CFSTR("Arduino"), kSecAttrLabel, + kSecMatchLimitOne, kSecMatchLimit, + kCFBooleanTrue, kSecReturnAttributes, + nil]; + + OSStatus err = noErr; + // Use this function to check for errors + err = SecItemCopyMatching((CFDictionaryRef)dict, nil); + NSString *exists = @"false"; + if (err == noErr) { + exists = @"true"; + } + return [exists cStringUsingEncoding:[NSString defaultCStringEncoding]]; +} + +// Returns the expiration date "kSecOIDX509V1ValidityNotAfter" of the Arduino certificate. +// The value is returned as a CFAbsoluteTime: a long number of seconds from the date of 1 Jan 2001 00:00:00 GMT. +const char *getExpirationDate(long *expirationDate) { + // 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, + }; + + SecCertificateRef cert = NULL; + + // Search the keychain for certificates matching the query above. + OSStatus err = SecItemCopyMatching((CFDictionaryRef)getquery, (CFTypeRef *)&cert); + if (err != noErr) return toErrorString([@"Error getting the certificate: " stringByAppendingFormat:@"%d", err]); + + // Get data from the certificate, as a dictionary of properties. We just need the "invalidity not after" property. + CFDictionaryRef certDict = SecCertificateCopyValues(cert, + (__bridge CFArrayRef)@[(__bridge id)kSecOIDX509V1ValidityNotAfter], NULL); + if (certDict == NULL) return toErrorString(@"SecCertificateCopyValues failed"); + + + // Get the "validity not after" property as a dictionary, and get the "value" key (that is a number). + CFDictionaryRef validityNotAfterDict = CFDictionaryGetValue(certDict, kSecOIDX509V1ValidityNotAfter); + if (validityNotAfterDict == NULL) return toErrorString(@"CFDictionaryGetValue (validity) failed"); + + CFNumberRef number = (CFNumberRef)CFDictionaryGetValue(validityNotAfterDict, kSecPropertyKeyValue); + if (number == NULL) return toErrorString(@"CFDictionaryGetValue (keyValue) failed"); + + CFNumberGetValue(number, kCFNumberSInt64Type, expirationDate); + // NSLog(@"Certificate validity not after: %ld", *expirationDate); + + CFRelease(certDict); + return ""; // No error. +} \ No newline at end of file diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index 0a82c4c9..a6e15320 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -15,154 +15,16 @@ package certificates -//inspired by https://stackoverflow.com/questions/12798950/ios-install-ssl-certificate-programmatically - /* // Explicitly tell the GCC compiler that the language is Objective-C. #cgo CFLAGS: -x objective-c -// Pass the list of macOS frameworks needed by this piece of Objective-C code. -#cgo LDFLAGS: -framework Cocoa -#import - - -// Used to return error strings (as NSString) as a C-string to the Go code. -const char *toErrorString(NSString *errString) { - NSLog(@"%@", errString); - return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; -} - -const char *installCert(const char *path) { - NSURL *url = [NSURL fileURLWithPath:@(path) isDirectory:NO]; - NSData *rootCertData = [NSData dataWithContentsOfURL:url]; - - OSStatus err = noErr; - SecCertificateRef rootCert = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef) rootCertData); - - CFTypeRef result; - - NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: - (id)kSecClassCertificate, kSecClass, - rootCert, kSecValueRef, - nil]; - - err = SecItemAdd((CFDictionaryRef)dict, &result); - - if (err == noErr) { - NSLog(@"Install root certificate success"); - } else if (err == errSecDuplicateItem) { - NSString *errString = [@"duplicate root certificate entry. Error: " stringByAppendingFormat:@"%d", err]; - NSLog(@"%@", errString); - return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]];; - } else { - NSString *errString = [@"install root certificate failure. Error: " stringByAppendingFormat:@"%d", err]; - NSLog(@"%@", errString); - return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; - } - - NSDictionary *newTrustSettings = @{(id)kSecTrustSettingsResult: [NSNumber numberWithInt:kSecTrustSettingsResultTrustRoot]}; - err = SecTrustSettingsSetTrustSettings(rootCert, kSecTrustSettingsDomainUser, (__bridge CFTypeRef)(newTrustSettings)); - if (err != errSecSuccess) { - NSString *errString = [@"Could not change the trust setting for a certificate. Error: " stringByAppendingFormat:@"%d", err]; - NSLog(@"%@", errString); - return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; - } - - return ""; -} - -const char *uninstallCert() { - // Each line is a key-value of the dictionary. Note: the the inverted order, value first then key. - NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: - (id)kSecClassCertificate, kSecClass, - CFSTR("Arduino"), kSecAttrLabel, - kSecMatchLimitOne, kSecMatchLimit, - kCFBooleanTrue, kSecReturnAttributes, - nil]; - - OSStatus err = noErr; - // Use this function to check for errors - err = SecItemCopyMatching((CFDictionaryRef)dict, nil); - if (err == noErr) { - err = SecItemDelete((CFDictionaryRef)dict); - if (err != noErr) { - NSString *errString = [@"Could not delete the certificates. Error: " stringByAppendingFormat:@"%d", err]; - NSLog(@"%@", errString); - return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]];; - } - } else if (err != errSecItemNotFound){ - NSString *errString = [@"Error: " stringByAppendingFormat:@"%d", err]; - NSLog(@"%@", errString); - return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]];; - } - return ""; -} - -// Returns the expiration date "kSecOIDX509V1ValidityNotAfter" of the Arduino certificate. -// The value is returned as a CFAbsoluteTime: a long number of seconds from the date of 1 Jan 2001 00:00:00 GMT. -const char *getExpirationDate(long *expirationDate) { - // 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, - }; - SecCertificateRef cert = NULL; - - // Search the keychain for certificates matching the query above. - OSStatus err = SecItemCopyMatching((CFDictionaryRef)getquery, (CFTypeRef *)&cert); - if (err != noErr) return toErrorString([@"Error getting the certificate: " stringByAppendingFormat:@"%d", err]); - - // Get data from the certificate, as a dictionary of properties. We just need the "invalidity not after" property. - CFDictionaryRef certDict = SecCertificateCopyValues(cert, - (__bridge CFArrayRef)@[(__bridge id)kSecOIDX509V1ValidityNotAfter], NULL); - if (certDict == NULL) return toErrorString(@"SecCertificateCopyValues failed"); - - - // Get the "validity not after" property as a dictionary, and get the "value" key (that is a number). - CFDictionaryRef validityNotAfterDict = CFDictionaryGetValue(certDict, kSecOIDX509V1ValidityNotAfter); - if (validityNotAfterDict == NULL) return toErrorString(@"CFDictionaryGetValue (validity) failed"); - - CFNumberRef number = (CFNumberRef)CFDictionaryGetValue(validityNotAfterDict, kSecPropertyKeyValue); - if (number == NULL) return toErrorString(@"CFDictionaryGetValue (keyValue) failed"); - - CFNumberGetValue(number, kCFNumberSInt64Type, expirationDate); - // NSLog(@"Certificate validity not after: %ld", *expirationDate); - - CFRelease(certDict); - return ""; // No error. -} - -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 ""; -} - -const char *certInKeychain() { - // Each line is a key-value of the dictionary. Note: the the inverted order, value first then key. - NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: - (id)kSecClassCertificate, kSecClass, - CFSTR("Arduino"), kSecAttrLabel, - kSecMatchLimitOne, kSecMatchLimit, - kCFBooleanTrue, kSecReturnAttributes, - nil]; +// Pass the list of macOS frameworks needed by this piece of Objective-C code. +// The "-ld_classic" is needed to avoid a wrong warning about duplicate libraries when building with XCode 15. +#cgo LDFLAGS: -framework Foundation -framework Security -framework AppKit -ld_classic - OSStatus err = noErr; - // Use this function to check for errors - err = SecItemCopyMatching((CFDictionaryRef)dict, nil); - NSString *exists = @"false"; - if (err == noErr) { - exists = @"true"; - } - return [exists cStringUsingEncoding:[NSString defaultCStringEncoding]];; -} +#import +#include "certificates.h" */ import "C" import ( @@ -236,8 +98,6 @@ func CertInKeychain() bool { log.Infof("Checking if the Arduino certificate is in the keychain") p := C.certInKeychain() s := C.GoString(p) - if s == "true" { - return true - } - return false + + return s == "true" } From 34fae243059f0d4db7c629f181054a64ca2b2f8b Mon Sep 17 00:00:00 2001 From: Xayton <30591904+Xayton@users.noreply.github.com> Date: Fri, 10 May 2024 17:02:43 +0200 Subject: [PATCH 16/19] certInKeychain returns a bool --- certificates/certificates.h | 2 +- certificates/certificates.m | 26 ++++++++++---------------- certificates/install_darwin.go | 5 ++--- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/certificates/certificates.h b/certificates/certificates.h index ba717129..78ba0ae5 100644 --- a/certificates/certificates.h +++ b/certificates/certificates.h @@ -2,6 +2,6 @@ const char *getDefaultBrowserName(); const char *installCert(const char *path); const char *uninstallCert(); -const char *certInKeychain(); +const bool certInKeychain(); const char *getExpirationDate(long *expirationDate); \ No newline at end of file diff --git a/certificates/certificates.m b/certificates/certificates.m index c8e6abd2..c30798e3 100644 --- a/certificates/certificates.m +++ b/certificates/certificates.m @@ -8,6 +8,7 @@ return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]]; } +// Returns a string describing the name of the default browser set for the user, nil in case of error. const char *getDefaultBrowserName() { NSURL *defaultBrowserURL = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:[NSURL URLWithString:@"http://"]]; if (defaultBrowserURL) { @@ -87,23 +88,16 @@ return ""; } -const char *certInKeychain() { - // Each line is a key-value of the dictionary. Note: the the inverted order, value first then key. - NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: - (id)kSecClassCertificate, kSecClass, - CFSTR("Arduino"), kSecAttrLabel, - kSecMatchLimitOne, kSecMatchLimit, - kCFBooleanTrue, kSecReturnAttributes, - nil]; +const bool certInKeychain() { + // 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; - // Use this function to check for errors - err = SecItemCopyMatching((CFDictionaryRef)dict, nil); - NSString *exists = @"false"; - if (err == noErr) { - exists = @"true"; - } - return [exists cStringUsingEncoding:[NSString defaultCStringEncoding]]; + OSStatus err = SecItemCopyMatching((CFDictionaryRef)getquery, nil); + return (err == noErr); // No error means the certificate was found, otherwise err will be "errSecItemNotFound". } // Returns the expiration date "kSecOIDX509V1ValidityNotAfter" of the Arduino certificate. diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index a6e15320..3db960ea 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -96,8 +96,7 @@ func GetDefaultBrowserName() string { // CertInKeychain checks if the certificate is stored inside the keychain func CertInKeychain() bool { log.Infof("Checking if the Arduino certificate is in the keychain") - p := C.certInKeychain() - s := C.GoString(p) - return s == "true" + certInKeychain := C.certInKeychain() + return bool(certInKeychain) } From 885468047b037cc85271706e1fa832fb0e4c9f28 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Mon, 13 May 2024 10:55:28 +0200 Subject: [PATCH 17/19] Fix building errors caused by objective-c files on Ubuntu and Windows --- certificates/install_default.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/certificates/install_default.go b/certificates/install_default.go index 533574d6..d909b66d 100644 --- a/certificates/install_default.go +++ b/certificates/install_default.go @@ -17,6 +17,20 @@ package certificates +/* +// Importing "certificates.h" here even if it is not used avoids building errors on Ubuntu and Windows. + +// Explicitly tell the GCC compiler that the language is Objective-C. +#cgo CFLAGS: -x objective-c + +// Pass the list of macOS frameworks needed by this piece of Objective-C code. +// The "-ld_classic" is needed to avoid a wrong warning about duplicate libraries when building with XCode 15. +#cgo LDFLAGS: -framework Foundation -framework Security -framework AppKit -ld_classic + +#import +#include "certificates.h" +*/ + import ( "errors" "time" From 681a250a8d7bc440b01710056ba872c1e9195f24 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Mon, 13 May 2024 14:15:44 +0200 Subject: [PATCH 18/19] Build objective-c files only on Darwin --- .../{certificates.h => certificates_darwin.h} | 0 .../{certificates.m => certificates_darwin.m} | 2 +- certificates/install_darwin.go | 2 +- certificates/install_default.go | 14 -------------- 4 files changed, 2 insertions(+), 16 deletions(-) rename certificates/{certificates.h => certificates_darwin.h} (100%) rename certificates/{certificates.m => certificates_darwin.m} (99%) diff --git a/certificates/certificates.h b/certificates/certificates_darwin.h similarity index 100% rename from certificates/certificates.h rename to certificates/certificates_darwin.h diff --git a/certificates/certificates.m b/certificates/certificates_darwin.m similarity index 99% rename from certificates/certificates.m rename to certificates/certificates_darwin.m index c30798e3..0ac51183 100644 --- a/certificates/certificates.m +++ b/certificates/certificates_darwin.m @@ -1,6 +1,6 @@ #import #import -#include "certificates.h" +#include "certificates_darwin.h" // Used to return error strings (as NSString) as a C-string to the Go code. const char *toErrorString(NSString *errString) { diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index 3db960ea..142a7923 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -24,7 +24,7 @@ package certificates #cgo LDFLAGS: -framework Foundation -framework Security -framework AppKit -ld_classic #import -#include "certificates.h" +#include "certificates_darwin.h" */ import "C" import ( diff --git a/certificates/install_default.go b/certificates/install_default.go index d909b66d..533574d6 100644 --- a/certificates/install_default.go +++ b/certificates/install_default.go @@ -17,20 +17,6 @@ package certificates -/* -// Importing "certificates.h" here even if it is not used avoids building errors on Ubuntu and Windows. - -// Explicitly tell the GCC compiler that the language is Objective-C. -#cgo CFLAGS: -x objective-c - -// Pass the list of macOS frameworks needed by this piece of Objective-C code. -// The "-ld_classic" is needed to avoid a wrong warning about duplicate libraries when building with XCode 15. -#cgo LDFLAGS: -framework Foundation -framework Security -framework AppKit -ld_classic - -#import -#include "certificates.h" -*/ - import ( "errors" "time" From c461c406db0cefedc3006f1b62cd68e921985d4f Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Mon, 13 May 2024 14:32:45 +0200 Subject: [PATCH 19/19] Remove -ld_classic library because XCode is not up to date on the CI --- certificates/install_darwin.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/certificates/install_darwin.go b/certificates/install_darwin.go index 142a7923..515c9f7d 100644 --- a/certificates/install_darwin.go +++ b/certificates/install_darwin.go @@ -20,8 +20,7 @@ package certificates #cgo CFLAGS: -x objective-c // Pass the list of macOS frameworks needed by this piece of Objective-C code. -// The "-ld_classic" is needed to avoid a wrong warning about duplicate libraries when building with XCode 15. -#cgo LDFLAGS: -framework Foundation -framework Security -framework AppKit -ld_classic +#cgo LDFLAGS: -framework Foundation -framework Security -framework AppKit #import #include "certificates_darwin.h"