Skip to content

Commit 3190a1a

Browse files
Fix buttons and improve handling of certificates when Safari is not the default browser (#949)
* Fix check on buttons returning the correct message * Update certificates regardless of the default browser * Set installCerts when the certificate is installed from previous versions of the Agent regardless of the default browser * Do not set installCerts to false if the default browser is not Safari * Do not ask again to update the certificate if the user refuses once * Fix user script on macOS * Check for the presence of the certificate in the keychain to determine if it is installed * Fix getExpirationDate breaking when the certificate is expired * Fix return value in case of error * getExpirationDate rewritten to use the correct expiration field. * Separate osascript default button from the one to press * Fix leftover buttons * Small text fixes in the "manage certificate" dialog * Simplify error management in getExpirationDate * Fix compiler warnings and move obj-c code into a separate file. * certInKeychain returns a bool * Fix building errors caused by objective-c files on Ubuntu and Windows * Build objective-c files only on Darwin * Remove -ld_classic library because XCode is not up to date on the CI --------- Co-authored-by: Xayton <[email protected]>
1 parent 222a505 commit 3190a1a

File tree

8 files changed

+264
-228
lines changed

8 files changed

+264
-228
lines changed

Diff for: certificates/certificates.go

+3-31
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,13 @@ import (
3030
"math/big"
3131
"net"
3232
"os"
33-
"strings"
3433
"time"
3534

36-
"github.com/arduino/arduino-create-agent/utilities"
3735
"github.com/arduino/go-paths-helper"
3836
log "github.com/sirupsen/logrus"
3937
)
4038

4139
var (
42-
host = "localhost"
4340
validFrom = ""
4441
validFor = 365 * 24 * time.Hour * 2 // 2 years
4542
rsaBits = 2048
@@ -270,41 +267,16 @@ func DeleteCertificates(certDir *paths.Path) {
270267
certDir.Join("cert.cer").Remove()
271268
}
272269

273-
// isExpired checks if a certificate is expired or about to expire (less than 1 month)
274-
func isExpired() (bool, error) {
270+
// IsExpired checks if a certificate is expired or about to expire (less than 1 month)
271+
func IsExpired() (bool, error) {
275272
bound := time.Now().AddDate(0, 1, 0)
276-
dateS, err := GetExpirationDate()
273+
date, err := GetExpirationDate()
277274
if err != nil {
278275
return false, err
279276
}
280-
date, _ := time.Parse(time.DateTime, dateS)
281277
return date.Before(bound), nil
282278
}
283279

284-
// PromptInstallCertsSafari prompts the user to install the HTTPS certificates if they are using Safari
285-
func PromptInstallCertsSafari() bool {
286-
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\"")
287-
return strings.Contains(string(buttonPressed), "button returned:Install the certificate for Safari")
288-
}
289-
290-
// PromptExpiredCerts prompts the user to update the HTTPS certificates if they are using Safari
291-
func PromptExpiredCerts(certDir *paths.Path) {
292-
if expired, err := isExpired(); err != nil {
293-
log.Errorf("cannot check if certificates are expired something went wrong: %s", err)
294-
} else if expired {
295-
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\"")
296-
if strings.Contains(string(buttonPressed), "button returned:Update the certificate for Safari") {
297-
err := UninstallCertificates()
298-
if err != nil {
299-
log.Errorf("cannot uninstall certificates something went wrong: %s", err)
300-
} else {
301-
DeleteCertificates(certDir)
302-
GenerateAndInstallCertificates(certDir)
303-
}
304-
}
305-
}
306-
}
307-
308280
// GenerateAndInstallCertificates generates and installs the certificates
309281
func GenerateAndInstallCertificates(certDir *paths.Path) {
310282
GenerateCertificates(certDir)

Diff for: certificates/certificates_darwin.h

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const char *getDefaultBrowserName();
2+
3+
const char *installCert(const char *path);
4+
const char *uninstallCert();
5+
const bool certInKeychain();
6+
7+
const char *getExpirationDate(long *expirationDate);

Diff for: certificates/certificates_darwin.m

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#import <Foundation/Foundation.h>
2+
#import <AppKit/AppKit.h>
3+
#include "certificates_darwin.h"
4+
5+
// Used to return error strings (as NSString) as a C-string to the Go code.
6+
const char *toErrorString(NSString *errString) {
7+
NSLog(@"%@", errString);
8+
return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]];
9+
}
10+
11+
// Returns a string describing the name of the default browser set for the user, nil in case of error.
12+
const char *getDefaultBrowserName() {
13+
NSURL *defaultBrowserURL = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:[NSURL URLWithString:@"http://"]];
14+
if (defaultBrowserURL) {
15+
NSBundle *defaultBrowserBundle = [NSBundle bundleWithURL:defaultBrowserURL];
16+
NSString *defaultBrowser = [defaultBrowserBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
17+
18+
return [defaultBrowser cStringUsingEncoding:[NSString defaultCStringEncoding]];
19+
}
20+
21+
return "";
22+
}
23+
24+
// inspired by https://stackoverflow.com/questions/12798950/ios-install-ssl-certificate-programmatically
25+
const char *installCert(const char *path) {
26+
NSURL *url = [NSURL fileURLWithPath:@(path) isDirectory:NO];
27+
NSData *rootCertData = [NSData dataWithContentsOfURL:url];
28+
29+
OSStatus err = noErr;
30+
SecCertificateRef rootCert = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef) rootCertData);
31+
32+
CFTypeRef result;
33+
34+
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
35+
(id)kSecClassCertificate, kSecClass,
36+
rootCert, kSecValueRef,
37+
nil];
38+
39+
err = SecItemAdd((CFDictionaryRef)dict, &result);
40+
41+
if (err == noErr) {
42+
NSLog(@"Install root certificate success");
43+
} else if (err == errSecDuplicateItem) {
44+
NSString *errString = [@"duplicate root certificate entry. Error: " stringByAppendingFormat:@"%d", err];
45+
NSLog(@"%@", errString);
46+
return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]];
47+
} else {
48+
NSString *errString = [@"install root certificate failure. Error: " stringByAppendingFormat:@"%d", err];
49+
NSLog(@"%@", errString);
50+
return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]];
51+
}
52+
53+
NSDictionary *newTrustSettings = @{(id)kSecTrustSettingsResult: [NSNumber numberWithInt:kSecTrustSettingsResultTrustRoot]};
54+
err = SecTrustSettingsSetTrustSettings(rootCert, kSecTrustSettingsDomainUser, (__bridge CFTypeRef)(newTrustSettings));
55+
if (err != errSecSuccess) {
56+
NSString *errString = [@"Could not change the trust setting for a certificate. Error: " stringByAppendingFormat:@"%d", err];
57+
NSLog(@"%@", errString);
58+
return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]];
59+
}
60+
61+
return "";
62+
}
63+
64+
const char *uninstallCert() {
65+
// Each line is a key-value of the dictionary. Note: the the inverted order, value first then key.
66+
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
67+
(id)kSecClassCertificate, kSecClass,
68+
CFSTR("Arduino"), kSecAttrLabel,
69+
kSecMatchLimitOne, kSecMatchLimit,
70+
kCFBooleanTrue, kSecReturnAttributes,
71+
nil];
72+
73+
OSStatus err = noErr;
74+
// Use this function to check for errors
75+
err = SecItemCopyMatching((CFDictionaryRef)dict, nil);
76+
if (err == noErr) {
77+
err = SecItemDelete((CFDictionaryRef)dict);
78+
if (err != noErr) {
79+
NSString *errString = [@"Could not delete the certificates. Error: " stringByAppendingFormat:@"%d", err];
80+
NSLog(@"%@", errString);
81+
return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]];
82+
}
83+
} else if (err != errSecItemNotFound){
84+
NSString *errString = [@"Error: " stringByAppendingFormat:@"%d", err];
85+
NSLog(@"%@", errString);
86+
return [errString cStringUsingEncoding:[NSString defaultCStringEncoding]];
87+
}
88+
return "";
89+
}
90+
91+
const bool certInKeychain() {
92+
// Create a key-value dictionary used to query the Keychain and look for the "Arduino" root certificate.
93+
NSDictionary *getquery = @{
94+
(id)kSecClass: (id)kSecClassCertificate,
95+
(id)kSecAttrLabel: @"Arduino",
96+
(id)kSecReturnRef: @YES,
97+
};
98+
99+
OSStatus err = SecItemCopyMatching((CFDictionaryRef)getquery, nil);
100+
return (err == noErr); // No error means the certificate was found, otherwise err will be "errSecItemNotFound".
101+
}
102+
103+
// Returns the expiration date "kSecOIDX509V1ValidityNotAfter" of the Arduino certificate.
104+
// The value is returned as a CFAbsoluteTime: a long number of seconds from the date of 1 Jan 2001 00:00:00 GMT.
105+
const char *getExpirationDate(long *expirationDate) {
106+
// Create a key-value dictionary used to query the Keychain and look for the "Arduino" root certificate.
107+
NSDictionary *getquery = @{
108+
(id)kSecClass: (id)kSecClassCertificate,
109+
(id)kSecAttrLabel: @"Arduino",
110+
(id)kSecReturnRef: @YES,
111+
};
112+
113+
SecCertificateRef cert = NULL;
114+
115+
// Search the keychain for certificates matching the query above.
116+
OSStatus err = SecItemCopyMatching((CFDictionaryRef)getquery, (CFTypeRef *)&cert);
117+
if (err != noErr) return toErrorString([@"Error getting the certificate: " stringByAppendingFormat:@"%d", err]);
118+
119+
// Get data from the certificate, as a dictionary of properties. We just need the "invalidity not after" property.
120+
CFDictionaryRef certDict = SecCertificateCopyValues(cert,
121+
(__bridge CFArrayRef)@[(__bridge id)kSecOIDX509V1ValidityNotAfter], NULL);
122+
if (certDict == NULL) return toErrorString(@"SecCertificateCopyValues failed");
123+
124+
125+
// Get the "validity not after" property as a dictionary, and get the "value" key (that is a number).
126+
CFDictionaryRef validityNotAfterDict = CFDictionaryGetValue(certDict, kSecOIDX509V1ValidityNotAfter);
127+
if (validityNotAfterDict == NULL) return toErrorString(@"CFDictionaryGetValue (validity) failed");
128+
129+
CFNumberRef number = (CFNumberRef)CFDictionaryGetValue(validityNotAfterDict, kSecPropertyKeyValue);
130+
if (number == NULL) return toErrorString(@"CFDictionaryGetValue (keyValue) failed");
131+
132+
CFNumberGetValue(number, kCFNumberSInt64Type, expirationDate);
133+
// NSLog(@"Certificate validity not after: %ld", *expirationDate);
134+
135+
CFRelease(certDict);
136+
return ""; // No error.
137+
}

0 commit comments

Comments
 (0)