Skip to content

Fix buttons and improve handling of certificates when Safari is not the default browser #949

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f43b0e4
Fix check on buttons returning the correct message
MatteoPologruto May 8, 2024
f69980a
Update certificates regardless of the default browser
MatteoPologruto May 8, 2024
f7bd6e3
Set installCerts when the certificate is installed from previous vers…
MatteoPologruto May 8, 2024
8006310
Do not set installCerts to false if the default browser is not Safari
MatteoPologruto May 8, 2024
aa02ed0
Do not ask again to update the certificate if the user refuses once
MatteoPologruto May 9, 2024
e46bfbf
Fix user script on macOS
MatteoPologruto May 9, 2024
7f4cdf6
Check for the presence of the certificate in the keychain to determin…
MatteoPologruto May 9, 2024
4285c04
Fix getExpirationDate breaking when the certificate is expired
MatteoPologruto May 9, 2024
4064541
Fix return value in case of error
MatteoPologruto May 9, 2024
cf31546
getExpirationDate rewritten to use the correct expiration field.
Xayton May 9, 2024
411d051
Separate osascript default button from the one to press
MatteoPologruto May 9, 2024
c11610b
Fix leftover buttons
MatteoPologruto May 9, 2024
9494f25
Small text fixes in the "manage certificate" dialog
Xayton May 10, 2024
d56f231
Simplify error management in getExpirationDate
Xayton May 10, 2024
8ec1efc
Fix compiler warnings and move obj-c code into a separate file.
Xayton May 10, 2024
34fae24
certInKeychain returns a bool
Xayton May 10, 2024
8854680
Fix building errors caused by objective-c files on Ubuntu and Windows
MatteoPologruto May 13, 2024
681a250
Build objective-c files only on Darwin
MatteoPologruto May 13, 2024
c461c40
Remove -ld_classic library because XCode is not up to date on the CI
MatteoPologruto May 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix compiler warnings and move obj-c code into a separate file.
  • Loading branch information
Xayton committed May 10, 2024
commit 8ec1efccc37633ea2d477374ca19a7b57ca6ecbd
1 change: 0 additions & 1 deletion certificates/certificates.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import (
)

var (
host = "localhost"
validFrom = ""
validFor = 365 * 24 * time.Hour * 2 // 2 years
rsaBits = 2048
Expand Down
7 changes: 7 additions & 0 deletions certificates/certificates.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const char *getDefaultBrowserName();

const char *installCert(const char *path);
const char *uninstallCert();
const char *certInKeychain();

const char *getExpirationDate(long *expirationDate);
143 changes: 143 additions & 0 deletions certificates/certificates.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#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.
}
154 changes: 7 additions & 147 deletions certificates/install_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Cocoa/Cocoa.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 *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 <Foundation/Foundation.h>
#include "certificates.h"
*/
import "C"
import (
Expand Down Expand Up @@ -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"
}