Skip to content
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

Add native macOS Security support #1080

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions src/org/dyorgio/jna/platform/mac/CoreFoundation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.dyorgio.jna.platform.mac;

import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.PointerType;
import com.sun.jna.ptr.PointerByReference;

public interface CoreFoundation extends com.sun.jna.platform.mac.CoreFoundation {
CoreFoundation INSTANCE = Native.load("CoreFoundation", CoreFoundation.class);

void CFDictionaryAddValue(com.sun.jna.platform.mac.CoreFoundation.CFMutableDictionaryRef theDict, PointerType key, PointerType value);
void CFDictionaryGetKeysAndValues(com.sun.jna.platform.mac.CoreFoundation.CFMutableDictionaryRef theDict, PointerByReference keys, Pointer[] values);
}
304 changes: 304 additions & 0 deletions src/org/dyorgio/jna/platform/mac/Security.java

Large diffs are not rendered by default.

154 changes: 154 additions & 0 deletions src/org/dyorgio/jna/platform/mac/Security_Main_Temp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package org.dyorgio.jna.platform.mac;

import com.sun.jna.Memory;
import org.apache.commons.ssl.Base64;
import qz.installer.certificate.MacCertificateNativeAccess;

import static com.sun.jna.platform.mac.CoreFoundation.*;

public class Security_Main_Temp {
static int a;
public static void main(String ... args) {
MacCertificateNativeAccess nativeAccess = new MacCertificateNativeAccess();
nativeAccess.findIdsByEmail("","[email protected]");
}
public static void oldMain () {
CFAllocatorRef alloc = INSTANCE.CFAllocatorGetDefault();
// Keys
CFStringRef cfLabel = CFStringRef.createCFString("My Certificate");
//CFDataRef cfValueAsData = getCFDataCert(DER_ENCODED_CERT);

byte[] certBytes = Base64.decodeBase64(CLEAN_CERT);
Memory nativeBytes = new Memory(certBytes.length);
nativeBytes.write(0, certBytes, 0, certBytes.length);

CFDataRef cfValueAsData = INSTANCE.CFDataCreate(alloc, nativeBytes, new CFIndex(nativeBytes.size()));
CFTypeRef cfValueAsCert = Security.INSTANCE.SecCertificateCreateWithData(null, cfValueAsData);

CFStringRef summary = Security.INSTANCE.SecCertificateCopySubjectSummary(cfValueAsCert);
System.out.println("SecCertificateCopySubjectSummary: " + summary.stringValue());
summary.release();

//NativeLibrary lib = NativeLibrary.getInstance("Security");
//Pointer paddr = ((NativeLibrary)(Security.INSTANCE)).getGlobalVariableAddress("kSecValueRef");
//System.out.println("~~~ " + paddr.getLong(0));
// Write to dictionary
Memory keystoreRefData = new Memory(8);
Security.INSTANCE.SecKeychainCopyDomainDefault(1, keystoreRefData.share(0));
CFTypeRef keystoreRef = new CFTypeRef(keystoreRefData.getPointer(0));

CFMutableDictionaryRef dict = INSTANCE.CFDictionaryCreateMutable(alloc, new CFIndex(4), null, null);

CoreFoundation.INSTANCE.CFDictionaryAddValue(dict, Security.kSecClass, Security.kSecClassCertificate);
CoreFoundation.INSTANCE.CFDictionaryAddValue(dict, Security.kSecAttrLabel, cfLabel);
CoreFoundation.INSTANCE.CFDictionaryAddValue(dict, Security.kSecValueRef, cfValueAsCert);
CoreFoundation.INSTANCE.CFDictionaryAddValue(dict, Security.kSecUseKeychain, keystoreRef);

System.out.println(CoreFoundation.INSTANCE.CFCopyDescription(dict).stringValue());

// Call SecAddItem
int retVal = Security.INSTANCE.SecTrustSettingsSetTrustSettings(cfValueAsCert, 1, null);
System.out.println(retVal);
retVal = Security.INSTANCE.SecItemAdd(dict.getPointer(), null);
System.out.println(retVal);
Security.Status code = Security.Status.parse(retVal);

// Show output
switch(code) {
case SUCCESS:
System.out.println(code);
break;
default:
System.err.println(code);
}

cfLabel.release();
cfValueAsData.release();

dict.release();
alloc.release();
}

// Attempt to convert data bytes to CFDataRef
private static CFDataRef getCFDataCert(String derEncodedData) {
String certFixed = DER_ENCODED_CERT.split("-----")[2];
System.out.println(certFixed);
byte[] certBytes = Base64.decodeBase64(certFixed);
Memory nativeBytes = new Memory(certBytes.length);
nativeBytes.write(0, certBytes, 0, certBytes.length);
return INSTANCE.CFDataCreate(null, nativeBytes, new CFIndex(certBytes.length));
}


// SEE ALSO: https://www.andyibanez.com/posts/using-ios-keychain-swift/

/**
* let secCert = SecCertificateCreateWithData(nil, certInDer as CFData) // certInDer is Certificate(.der) data
* var keychainQueryDictionary = [String : Any]()
*
* if let tempSecCert = secCert {
* keychainQueryDictionary = [kSecClass as String : kSecClassCertificate, kSecValueRef as String : tempSecCert, kSecAttrLabel as String: "My Certificate"]
* }
*
* let summary = SecCertificateCopySubjectSummary(secCert!)! as String
* print("Cert summary: \(summary)")
*
* let status = SecItemAdd(keychainQueryDictionary as CFDictionary, nil)
*
* guard status == errSecSuccess else {
* print("Error")
* return
* }
*
* print("success")
*/
static String CLEAN_CERT = "MIIELzCCAxegAwIBAgIJALm151zCHDxiMA0GCSqGSIb3DQEBCwUAMIGsMQswCQYD" +
"VQQGEwJVUzELMAkGA1UECAwCTlkxEjAQBgNVBAcMCUNhbmFzdG90YTEbMBkGA1UE" +
"CgwSUVogSW5kdXN0cmllcywgTExDMRswGQYDVQQLDBJRWiBJbmR1c3RyaWVzLCBM" +
"TEMxGTAXBgNVBAMMEHF6aW5kdXN0cmllcy5jb20xJzAlBgkqhkiG9w0BCQEWGHN1" +
"cHBvcnRAcXppbmR1c3RyaWVzLmNvbTAgFw0xNTAzMDEyMzM4MjlaGA8yMTE1MDMw" +
"MjIzMzgyOVowgawxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOWTESMBAGA1UEBwwJ" +
"Q2FuYXN0b3RhMRswGQYDVQQKDBJRWiBJbmR1c3RyaWVzLCBMTEMxGzAZBgNVBAsM" +
"ElFaIEluZHVzdHJpZXMsIExMQzEZMBcGA1UEAwwQcXppbmR1c3RyaWVzLmNvbTEn" +
"MCUGCSqGSIb3DQEJARYYc3VwcG9ydEBxemluZHVzdHJpZXMuY29tMIIBIjANBgkq" +
"hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuWsBa6uk+RM4OKBZTRfIIyqaaFD71FAS" +
"7kojAQ+ySMpYuqLjIVZuCh92o1FGBvyBKUFc6knAHw5749yhLCYLXhzWwiNW2ri1" +
"Jwx/d83Wnaw6qA3lt++u3tmiA8tsFtss0QZW0YBpFsIqhamvB3ypwu0bdUV/oH7g" +
"/s8TFR5LrDfnfxlLFYhTUVWuWzMqEFAGnFG3uw/QMWZnQgkGbx0LMcYzdqFb7/vz" +
"rTSHfjJsisUTWPjo7SBnAtNYCYaGj0YH5RFUdabnvoTdV2XpA5IPYa9Q597g/M0z" +
"icAjuaK614nKXDaAUCbjki8RL3OK9KY920zNFboq/jKG6rKW2t51ZQIDAQABo1Aw" +
"TjAdBgNVHQ4EFgQUA0XGTcD6jqkL2oMPQaVtEgZDqV4wHwYDVR0jBBgwFoAUA0XG" +
"TcD6jqkL2oMPQaVtEgZDqV4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC" +
"AQEAijcT5QMVqrWWqpNEe1DidzQfSnKo17ZogHW+BfUbxv65JbDIntnk1XgtLTKB" +
"VAdIWUtGZbXxrp16NEsh96V2hjDIoiAaEpW+Cp6AHhIVgVh7Q9Knq9xZ1t6H8PL5" +
"QiYQKQgJ0HapdCxlPKBfUm/Mj1ppNl9mPFJwgHmzORexbxrzU/M5i2jlies+CXNq" +
"cvmF2l33QNHnLwpFGwYKs08pyHwUPp6+bfci6lRvavztgvnKroWWIRq9ZPlC0yVK" +
"FFemhbCd7ZVbrTo0NcWZM1PTAbvlOikV9eh3i1Vot+3dJ8F27KwUTtnV0B9Jrxum" +
"W9P3C48mvwTxYZJFOu0N9UBLLg==";

static String DER_ENCODED_CERT = "-----BEGIN CERTIFICATE-----\n" +
"MIIELzCCAxegAwIBAgIJALm151zCHDxiMA0GCSqGSIb3DQEBCwUAMIGsMQswCQYD\n" +
"VQQGEwJVUzELMAkGA1UECAwCTlkxEjAQBgNVBAcMCUNhbmFzdG90YTEbMBkGA1UE\n" +
"CgwSUVogSW5kdXN0cmllcywgTExDMRswGQYDVQQLDBJRWiBJbmR1c3RyaWVzLCBM\n" +
"TEMxGTAXBgNVBAMMEHF6aW5kdXN0cmllcy5jb20xJzAlBgkqhkiG9w0BCQEWGHN1\n" +
"cHBvcnRAcXppbmR1c3RyaWVzLmNvbTAgFw0xNTAzMDEyMzM4MjlaGA8yMTE1MDMw\n" +
"MjIzMzgyOVowgawxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOWTESMBAGA1UEBwwJ\n" +
"Q2FuYXN0b3RhMRswGQYDVQQKDBJRWiBJbmR1c3RyaWVzLCBMTEMxGzAZBgNVBAsM\n" +
"ElFaIEluZHVzdHJpZXMsIExMQzEZMBcGA1UEAwwQcXppbmR1c3RyaWVzLmNvbTEn\n" +
"MCUGCSqGSIb3DQEJARYYc3VwcG9ydEBxemluZHVzdHJpZXMuY29tMIIBIjANBgkq\n" +
"hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuWsBa6uk+RM4OKBZTRfIIyqaaFD71FAS\n" +
"7kojAQ+ySMpYuqLjIVZuCh92o1FGBvyBKUFc6knAHw5749yhLCYLXhzWwiNW2ri1\n" +
"Jwx/d83Wnaw6qA3lt++u3tmiA8tsFtss0QZW0YBpFsIqhamvB3ypwu0bdUV/oH7g\n" +
"/s8TFR5LrDfnfxlLFYhTUVWuWzMqEFAGnFG3uw/QMWZnQgkGbx0LMcYzdqFb7/vz\n" +
"rTSHfjJsisUTWPjo7SBnAtNYCYaGj0YH5RFUdabnvoTdV2XpA5IPYa9Q597g/M0z\n" +
"icAjuaK614nKXDaAUCbjki8RL3OK9KY920zNFboq/jKG6rKW2t51ZQIDAQABo1Aw\n" +
"TjAdBgNVHQ4EFgQUA0XGTcD6jqkL2oMPQaVtEgZDqV4wHwYDVR0jBBgwFoAUA0XG\n" +
"TcD6jqkL2oMPQaVtEgZDqV4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC\n" +
"AQEAijcT5QMVqrWWqpNEe1DidzQfSnKo17ZogHW+BfUbxv65JbDIntnk1XgtLTKB\n" +
"VAdIWUtGZbXxrp16NEsh96V2hjDIoiAaEpW+Cp6AHhIVgVh7Q9Knq9xZ1t6H8PL5\n" +
"QiYQKQgJ0HapdCxlPKBfUm/Mj1ppNl9mPFJwgHmzORexbxrzU/M5i2jlies+CXNq\n" +
"cvmF2l33QNHnLwpFGwYKs08pyHwUPp6+bfci6lRvavztgvnKroWWIRq9ZPlC0yVK\n" +
"FFemhbCd7ZVbrTo0NcWZM1PTAbvlOikV9eh3i1Vot+3dJ8F27KwUTtnV0B9Jrxum\n" +
"W9P3C48mvwTxYZJFOu0N9UBLLg==\n" +
"-----END CERTIFICATE-----";
}
16 changes: 15 additions & 1 deletion src/qz/installer/Installer.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ public enum PrivilegeLevel {

public abstract Installer removeLegacyStartup();
public abstract Installer addAppLauncher();
public abstract Installer removeAppLauncher();
public abstract Installer addStartupEntry();
public abstract Installer removeStartupEntry();
public abstract Installer addSystemSettings();
public abstract Installer removeSystemSettings();
public abstract void spawn(List<String> args) throws Exception;
Expand Down Expand Up @@ -108,7 +110,9 @@ public static void uninstall() {
TaskKiller.killAll();
getInstance();
log.info("Uninstalling from {}", instance.getDestination());
instance.removeSharedDirectory()
instance.removeStartupEntry()
.removeAppLauncher()
.removeSharedDirectory()
.removeSystemSettings()
.removeCerts();
}
Expand Down Expand Up @@ -333,4 +337,14 @@ public static Properties persistProperties(File oldFile, Properties newProps) {
public void spawn(String ... args) throws Exception {
spawn(new ArrayList(Arrays.asList(args)));
}

boolean hasVersion_2_0() {
// QZ Tray 2.1 removed the "auth" folder, this should be a valid test
try {
Path p = Paths.get(getDestination(), "auth");
return p.toFile().isDirectory();
}
catch(Exception ignore) {}
return false;
}
}
14 changes: 14 additions & 0 deletions src/qz/installer/LinuxInstaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,21 @@ public Installer addAppLauncher() {
return this;
}

public Installer removeAppLauncher() {
FileUtils.deleteQuietly(new File(APP_LAUNCHER));
return this;
}

public Installer addStartupEntry() {
addLauncher(STARTUP_LAUNCHER, false);
return this;
}

public Installer removeStartupEntry() {
FileUtils.deleteQuietly(new File(STARTUP_LAUNCHER));
return this;
}

private void addLauncher(String location, boolean isStartup) {
HashMap<String, String> fieldMap = new HashMap<>();
// Dynamic fields
Expand All @@ -69,6 +79,10 @@ private void addLauncher(String location, boolean isStartup) {
}

public Installer removeLegacyStartup() {
if(!hasVersion_2_0()) {
log.info("{} 2.0 was not found; skipping removal of legacy startup entries", ABOUT_TITLE);
return this;
}
log.info("Removing legacy autostart entries for all users matching {} or {}", ABOUT_TITLE, PROPS_FILE);
// assume users are in /home
String[] shortcutNames = {ABOUT_TITLE, PROPS_FILE};
Expand Down
20 changes: 17 additions & 3 deletions src/qz/installer/MacInstaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public Installer addAppLauncher() {
return this;
}

public Installer removeAppLauncher() {
// not needed; unregistered when "QZ Tray.app" is deleted
return this;
}

public Installer addStartupEntry() {
File dest = new File(LAUNCH_AGENT_PATH);
HashMap<String, String> fieldMap = new HashMap<>();
Expand All @@ -57,6 +62,13 @@ public Installer addStartupEntry() {
return this;
}

public Installer removeStartupEntry() {
// Remove startup entry
File dest = new File(LAUNCH_AGENT_PATH);
dest.delete();
return this;
}

public void setDestination(String destination) {
this.destination = destination;
}
Expand All @@ -74,16 +86,18 @@ public Installer addSystemSettings() {
return this;
}
public Installer removeSystemSettings() {
// Remove startup entry
File dest = new File(LAUNCH_AGENT_PATH);
dest.delete();
// not yet needed
return this;
}

/**
* Removes legacy (<= 2.0) startup entries
*/
public Installer removeLegacyStartup() {
if(!hasVersion_2_0()) {
log.info("{} 2.0 was not found; skipping removal of legacy startup entries", ABOUT_TITLE);
return this;
}
log.info("Removing startup entries for all users matching " + ABOUT_TITLE);
String script = "tell application \"System Events\" to delete "
+ "every login item where name is \"" + ABOUT_TITLE + "\""
Expand Down
35 changes: 26 additions & 9 deletions src/qz/installer/WindowsInstaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public void setDestination(String destination) {
* Cycles through registry keys removing legacy (<= 2.0) startup entries
*/
public Installer removeLegacyStartup() {
if(!hasVersion_2_0()) {
log.info("{} 2.0 was not found; skipping removal of legacy startup entries", ABOUT_TITLE);
return this;
}
log.info("Removing legacy startup entries for all users matching " + ABOUT_TITLE);
for (String user : Advapi32Util.registryGetKeys(HKEY_USERS)) {
WindowsUtilities.deleteRegValue(HKEY_USERS, user.trim() + "\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", ABOUT_TITLE);
Expand All @@ -65,7 +69,7 @@ public Installer removeLegacyStartup() {

public Installer addAppLauncher() {
try {
// Delete old 2.0 launcher
// Delete old 2.0 launcher in case a previous installer forgot to
FileUtils.deleteQuietly(new File(COMMON_START_MENU + File.separator + "Programs" + File.separator + ABOUT_TITLE + ".lnk"));
Path loc = Paths.get(COMMON_START_MENU.toString(), "Programs", ABOUT_TITLE);
loc.toFile().mkdirs();
Expand All @@ -79,6 +83,11 @@ public Installer addAppLauncher() {
return this;
}

public Installer removeAppLauncher() {
removeLaunchers(START_MENU, COMMON_START_MENU, DESKTOP, PUBLIC_DESKTOP, RECENT);
return this;
}

public Installer addStartupEntry() {
try {
String lnk = WindowsSpecialFolders.COMMON_STARTUP + File.separator + ABOUT_TITLE + ".lnk";
Expand All @@ -91,16 +100,15 @@ public Installer addStartupEntry() {
}
return this;
}
public Installer removeSystemSettings() {
// Cleanup registry
WindowsUtilities.deleteRegKey(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + ABOUT_TITLE);
WindowsUtilities.deleteRegKey(HKEY_LOCAL_MACHINE, "Software\\" + ABOUT_TITLE);
WindowsUtilities.deleteRegKey(HKEY_LOCAL_MACHINE, DATA_DIR);
// Chrome protocol handler
WindowsUtilities.deleteRegData(HKEY_LOCAL_MACHINE, "SOFTWARE\\Policies\\Google\\Chrome\\URLWhitelist", String.format("%s://*", DATA_DIR));

public Installer removeStartupEntry() {
removeLaunchers(COMMON_STARTUP);
return this;
}

private void removeLaunchers(WindowsSpecialFolders ... folders) {
// Cleanup launchers
for(WindowsSpecialFolders folder : new WindowsSpecialFolders[] { START_MENU, COMMON_START_MENU, DESKTOP, PUBLIC_DESKTOP, COMMON_STARTUP, RECENT }) {
for(WindowsSpecialFolders folder : folders) {
try {
new File(folder + File.separator + ABOUT_TITLE + ".lnk").delete();
// Since 2.1, start menus use subfolder
Expand All @@ -110,6 +118,15 @@ public Installer removeSystemSettings() {
}
} catch(InvalidPathException | IOException | Win32Exception ignore) {}
}
}

public Installer removeSystemSettings() {
// Cleanup registry
WindowsUtilities.deleteRegKey(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + ABOUT_TITLE);
WindowsUtilities.deleteRegKey(HKEY_LOCAL_MACHINE, "Software\\" + ABOUT_TITLE);
WindowsUtilities.deleteRegKey(HKEY_LOCAL_MACHINE, DATA_DIR);
// Chrome protocol handler
WindowsUtilities.deleteRegData(HKEY_LOCAL_MACHINE, "SOFTWARE\\Policies\\Google\\Chrome\\URLWhitelist", String.format("%s://*", DATA_DIR));

// Cleanup firewall rules
ShellUtilities.execute("netsh.exe", "advfirewall", "firewall", "delete", "rule", String.format("name=%s", ABOUT_TITLE));
Expand Down
Loading