I'd like to import a cert (x509) to local keystore (win and macOS). With this electron app user can create a website locally and can enable HTTPS that's why we need to store the cert.
In some rare cases the user get an error message during cert import only on macOS:
Command failed: security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /Users/user/ssl/asdasd.local.crt SecTrustSettingsSetTrustSettings: The authorization was denied since no user interaction was possible.
If the user run the exact same command with sudo
in terminal it works as expected.
Note: All users who get this error message has enabled apple watch authentication, but we know a user who has also enabled apple watch auth end he didn't get error message.
In electron renderer process we run the following commands with sudo-prompt npm package. Every time first we delete the cert from keystore if it does exist and add the new one.
Windows add cert:
certutil -addstore -f ROOT C:\Users\user\ssl\asdasd.local.crt
Windows delete cert:
certutil -delstore ROOT asdasd.local
macOS add cert:
security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /Users/user/ssl/asdasd.local.crt
macOS delete cert:
security delete-certificate -c asdasd.local
I've opened an issue in sudo-prompt
repo: https://github.com/jorangreef/sudo-prompt/issues/137
M1 mac asks password twice.
I've just realized this issue does not related to M1. Maybe related to Big Sur 11.1.
It looks like sudo-prompt
package hides the second password dialog.
The command stores the cert in keychain but the trust value is not "Always trust" if the error occurs.
I've tried osascript
way. It works perfectly on Big Sur 11.0, but It gave the same error on M1 Big Sur 11.1. (screenshot attached)
running the following command:
const sudo = require('sudo-prompt')
sudo.exec(
'security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /Users/kotapeter/ssl/test.local.crt',
{
name: 'test',
},
(error, stdout) => {
if (error) {
console.log(error)
} else {
console.log(stdout)
}
}
)
We use the following cert format:
I would suggest use a approach like below which will automatically ask for Admin access via GUI
const { exec } = require('child_process');
const proc = exec('osascript -e \'do shell script "pwd" with administrator privileges\'', function (error, stdout, stderr) {
if (error) {
console.log(error.stack);
console.log('Error code: '+error.code);
console.log('Signal received: '+error.signal);
}
console.log('Child Process STDOUT: '+stdout);
console.log('Child Process STDERR: '+stderr);
});
proc.on('exit', function (code) {
console.log('Child process exited with exit code '+code);
});
This is on MacOS Big Sur 11.1
Update 23-Jan-21:
You can build your own executable and run it from NodeJS
//
// main.swift
// AddCert
//
// Created by Tarun Lalwani on 23/01/21.
//
import Foundation
import Security
let certInfo: CFDictionary
enum SecurityError:Error {
case generalError
}
func deleteCertificateFromKeyChain(_ certificateLabel:String) -> Bool{
let delQuery : [NSString:Any] = [
kSecClass: kSecClassCertificate,
kSecAttrLabel: certificateLabel,
]
let delStatus:OSStatus = SecItemDelete(delQuery as CFDictionary)
return delStatus == errSecSuccess
}
func saveCertificateToKeyChain(_ certificate:SecCertificate, certificateLabel:String) throws {
deleteCertificateFromKeyChain(certificateLabel)
let setQuery: [NSString: AnyObject] = [
kSecClass: kSecClassCertificate,
kSecValueRef: certificate,
kSecAttrLabel: certificateLabel as AnyObject,
kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
]
let addStatus:OSStatus = SecItemAdd(setQuery as CFDictionary, nil)
guard addStatus == errSecSuccess else {
throw SecurityError.generalError
}
var status = SecTrustSettingsSetTrustSettings(certificate, SecTrustSettingsDomain.admin, nil)
}
func getCertificateFromString(stringData:String) throws -> SecCertificate{
if let data:NSData = NSData(base64Encoded: stringData, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) {
if let certificate = SecCertificateCreateWithData(kCFAllocatorDefault, data) {
return certificate
}
}
throw SecurityError.generalError
}
var certificateString:String = "MIIDUzCCAjugAwIBAgIUD9xMnL73y7fuida5TXgmklLswsowDQYJKoZIhvcNAQELBQAwGTEXMBUGA1UEAwwOdGVzdHNpdGUubG9jYWwwHhcNMjEwMTE3MTExODU1WhcNNDEwMTEyMTExODU1WjAZMRcwFQYDVQQDDA50ZXN0c2l0ZS5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANM08SDi06dvnyU1A6//BeEFd8mXsOpDQCbYEHX/Pz4jqaBYwVjD5pG7FkvDeUKZnEVyrsofjZ4Y1WAT8jxPMUi+jDlgNTiFjPVc4rA6hcGX6b70HjsCACmc8bZd+EU7gm4b5eL6exTsVzHc+lFz4eQFXgutYTL7guDQE/gFHwqPkLvnfg3rgY31p3Hm/snL8NuD154iE9O1WuSxEjik65uOQaewZmJ9ejJEuuEhMA8O9dXveJ71TMV5lqA//svDxBu3zXIxMqRy2LdzfROd+guLP6ZD3jUycWi7GpF4yN0+rD/0aXFJVHzV6TpS9oqb14jynvn1AyVfBB9+VQVNwTsCAwEAAaOBkjCBjzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIC9DA7BgNVHSUENDAyBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUHAwgwHQYDVR0OBBYEFDjAC2ObSbB59XyLW1YaD7bgY8ddMBkGA1UdEQQSMBCCDnRlc3RzaXRlLmxvY2FsMA0GCSqGSIb3DQEBCwUAA4IBAQBsU6OA4LrXQIZDXSIZPsDhtA7YZWzbrpqPceXPwBd1k9Yd9T83EdA00N6eoOWFzwnQqwqKxtYdl3x9JQ7ewhY2huH9DRtCGjiTm/GVU/WnNm4tUTuGU4FyjSTRi8bNUxTSF5PZ0U2/vFZ0d7T43NbLQAiFSxyfC1r6qjKQCYDL92XeU61zJxesxy5hxVNrbDpbPnCUZpx4hhL0RHgG+tZBOlBuW4eq249O0Ql+3ShcPom4hzfh975385bfwfUT2s/ovng67IuM9bLSWWe7U+6HbOEvzMIiqK94YYPmOC62cdhOaZIJmro6lL7eFLqlYfLU4H52ICuntBxvOx0UBExn"
let certificate = try! getCertificateFromString(stringData: certificateString)
try? saveCertificateToKeyChain(certificate, certificateLabel: "Test")
In above example, I didn't use a certificate file, but you can update the code to use that and you can also use command line arguments to take a filepath
Credits to below file for getting this code working
https://github.com/ibm-bluemix-mobile-services/bms-clientsdk-swift-security/blob/c26988a7f5de338c1d9e0d43a64c2b6db33be541/Source/mca/internal/certificate/SecurityUtils.swift
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With