Certificate Distribution and Management on macOS
Efficiently distribute and manage security certificates across your MacFleet devices using automated command-line tools. This tutorial covers certificate deployment, validation, trust management, and enterprise CA integration.
Understanding macOS Certificate Management
macOS uses several certificate stores and trust policies:
- System Keychain - System-wide trusted certificates
- Login Keychain - User-specific certificates
- Trust Settings - Certificate trust policies and validation rules
- Certificate Authority - Root and intermediate CA certificates
Basic Certificate Distribution
Download and Install Certificate
#!/bin/bash
# Basic certificate installation from URL
CERT_URL="https://your-domain.com/certificates/company-ca.crt"
CERT_NAME="company-ca.crt"
# Download certificate
curl -O "$CERT_URL"
# Add to system keychain with trust
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "/Users/$CERT_NAME"
echo "Certificate installed successfully"
Install Local Certificate
#!/bin/bash
# Install certificate from local file
CERT_PATH="/path/to/certificate.crt"
if [[ -f "$CERT_PATH" ]]; then
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "$CERT_PATH"
echo "Local certificate installed successfully"
else
echo "Certificate file not found: $CERT_PATH"
exit 1
fi
Enterprise Certificate Management Script
#!/bin/bash
# MacFleet Certificate Distribution and Management
# Deploy, validate, and manage certificates across enterprise devices
# Configuration
LOG_FILE="/var/log/macfleet_certificates.log"
BACKUP_DIR="/var/backups/certificates"
CERT_STORE_DIR="/Library/Keychains"
TEMP_DIR="/tmp/macfleet_certs"
# Certificate sources configuration
declare -A CERTIFICATE_SOURCES=(
["company-root-ca"]="https://pki.company.com/certs/root-ca.crt"
["company-intermediate-ca"]="https://pki.company.com/certs/intermediate-ca.crt"
["company-ssl-ca"]="https://pki.company.com/certs/ssl-ca.crt"
)
# Logging function
log_action() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# Create necessary directories
setup_directories() {
for dir in "$BACKUP_DIR" "$TEMP_DIR"; do
if [[ ! -d "$dir" ]]; then
mkdir -p "$dir"
log_action "Created directory: $dir"
fi
done
}
# Validate certificate file
validate_certificate() {
local cert_file="$1"
local cert_name="$2"
log_action "Validating certificate: $cert_name"
# Check if file exists and is readable
if [[ ! -f "$cert_file" || ! -r "$cert_file" ]]; then
log_action "ERROR: Certificate file not accessible: $cert_file"
return 1
fi
# Validate certificate format using openssl
if ! openssl x509 -in "$cert_file" -text -noout >/dev/null 2>&1; then
log_action "ERROR: Invalid certificate format: $cert_name"
return 1
fi
# Extract certificate information
local subject
local issuer
local expiry
subject=$(openssl x509 -in "$cert_file" -subject -noout | sed 's/subject=//')
issuer=$(openssl x509 -in "$cert_file" -issuer -noout | sed 's/issuer=//')
expiry=$(openssl x509 -in "$cert_file" -enddate -noout | sed 's/notAfter=//')
log_action "Certificate Details:"
log_action " Subject: $subject"
log_action " Issuer: $issuer"
log_action " Expires: $expiry"
# Check if certificate is expired
if ! openssl x509 -in "$cert_file" -checkend 0 >/dev/null 2>&1; then
log_action "WARNING: Certificate is expired: $cert_name"
return 2
fi
# Check if certificate expires within 30 days
if ! openssl x509 -in "$cert_file" -checkend 2592000 >/dev/null 2>&1; then
log_action "WARNING: Certificate expires within 30 days: $cert_name"
fi
log_action "Certificate validation successful: $cert_name"
return 0
}
# Download certificate from URL
download_certificate() {
local cert_name="$1"
local cert_url="$2"
local cert_file="$TEMP_DIR/$cert_name.crt"
log_action "Downloading certificate: $cert_name from $cert_url"
# Download with validation
if curl -s -f -L --connect-timeout 30 --max-time 120 -o "$cert_file" "$cert_url"; then
log_action "Certificate downloaded successfully: $cert_name"
echo "$cert_file"
return 0
else
log_action "ERROR: Failed to download certificate: $cert_name"
return 1
fi
}
# Backup existing certificates
backup_certificates() {
local backup_timestamp
backup_timestamp=$(date '+%Y%m%d_%H%M%S')
local backup_file="$BACKUP_DIR/keychain_backup_$backup_timestamp.tar.gz"
log_action "Creating certificate backup: $backup_file"
if tar -czf "$backup_file" -C /Library/Keychains . 2>/dev/null; then
log_action "Backup created successfully: $backup_file"
return 0
else
log_action "ERROR: Failed to create backup"
return 1
fi
}
# Install certificate to system keychain
install_certificate() {
local cert_file="$1"
local cert_name="$2"
local trust_policy="${3:-trustRoot}"
log_action "Installing certificate: $cert_name with trust policy: $trust_policy"
# Check if certificate already exists
if security find-certificate -c "$cert_name" /Library/Keychains/System.keychain >/dev/null 2>&1; then
log_action "Certificate already exists in system keychain: $cert_name"
# Optionally remove existing certificate
read -p "Certificate already exists. Replace? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
remove_certificate "$cert_name"
else
return 0
fi
fi
# Install certificate
if sudo security add-trusted-cert -d -r "$trust_policy" -k /Library/Keychains/System.keychain "$cert_file"; then
log_action "Certificate installed successfully: $cert_name"
# Verify installation
if security find-certificate -c "$cert_name" /Library/Keychains/System.keychain >/dev/null 2>&1; then
log_action "Certificate verification successful: $cert_name"
return 0
else
log_action "ERROR: Certificate verification failed: $cert_name"
return 1
fi
else
log_action "ERROR: Failed to install certificate: $cert_name"
return 1
fi
}
# Remove certificate from system keychain
remove_certificate() {
local cert_name="$1"
log_action "Removing certificate: $cert_name"
if sudo security delete-certificate -c "$cert_name" /Library/Keychains/System.keychain 2>/dev/null; then
log_action "Certificate removed successfully: $cert_name"
return 0
else
log_action "Certificate not found or removal failed: $cert_name"
return 1
fi
}
# Configure authorization database
configure_authorization() {
log_action "Configuring authorization database for certificate management"
cat > "$TEMP_DIR/auth_config.plist" << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>allow-root</key>
<true/>
<key>authenticate-user</key>
<true/>
<key>class</key>
<string>user</string>
<key>comment</key>
<string>Checked by the Admin framework when making changes to certain System Preferences.</string>
<key>group</key>
<string>admin</string>
<key>session-owner</key>
<false/>
<key>shared</key>
<true/>
</dict>
</plist>
EOF
if sudo /usr/bin/security authorizationdb write system.preferences < "$TEMP_DIR/auth_config.plist"; then
log_action "Authorization database configured successfully"
return 0
else
log_action "ERROR: Failed to configure authorization database"
return 1
fi
}
# List installed certificates
list_certificates() {
log_action "Listing installed certificates in system keychain"
echo "=== System Keychain Certificates ==="
security find-certificate -a -p /Library/Keychains/System.keychain | \
openssl x509 -text -noout | \
grep -E "(Subject:|Issuer:|Not After)" | \
sed 's/^[[:space:]]*//'
echo -e "\n=== Certificate Summary ==="
local cert_count
cert_count=$(security find-certificate -a /Library/Keychains/System.keychain | grep -c "keychain:")
echo "Total certificates in system keychain: $cert_count"
}
# Generate certificate report
generate_certificate_report() {
local report_file="$BACKUP_DIR/certificate_report_$(date '+%Y%m%d_%H%M%S').json"
log_action "Generating certificate report: $report_file"
{
echo "{"
echo " \"report_date\": \"$(date -Iseconds)\","
echo " \"hostname\": \"$(hostname)\","
echo " \"system_info\": {"
echo " \"os_version\": \"$(sw_vers -productVersion)\","
echo " \"build_version\": \"$(sw_vers -buildVersion)\""
echo " },"
echo " \"certificates\": ["
local first=true
while IFS= read -r cert_hash; do
if [[ "$first" == true ]]; then
first=false
else
echo ","
fi
local cert_info
cert_info=$(security find-certificate -a -Z /Library/Keychains/System.keychain | \
awk -v hash="$cert_hash" '
/^SHA-1 hash:/ { if ($3 == hash) found=1; next }
found && /^[[:space:]]*"/ { print; found=0 }')
echo " {"
echo " \"hash\": \"$cert_hash\","
echo " \"subject\": $cert_info"
echo -n " }"
done < <(security find-certificate -a -Z /Library/Keychains/System.keychain | \
grep "SHA-1 hash:" | awk '{print $3}')
echo ""
echo " ],"
echo " \"trust_settings\": $(security trust-settings-export -d /tmp/trust_settings.plist && plutil -convert json -o - /tmp/trust_settings.plist 2>/dev/null || echo 'null')"
echo "}"
} > "$report_file"
log_action "Certificate report generated: $report_file"
echo "$report_file"
}
# Deploy multiple certificates
deploy_certificate_bundle() {
log_action "=== Starting certificate bundle deployment ==="
local success_count=0
local total_count=0
local failed_certs=()
for cert_name in "${!CERTIFICATE_SOURCES[@]}"; do
total_count=$((total_count + 1))
local cert_url="${CERTIFICATE_SOURCES[$cert_name]}"
log_action "Processing certificate: $cert_name"
# Download certificate
local cert_file
if cert_file=$(download_certificate "$cert_name" "$cert_url"); then
# Validate certificate
if validate_certificate "$cert_file" "$cert_name"; then
# Install certificate
if install_certificate "$cert_file" "$cert_name"; then
success_count=$((success_count + 1))
log_action "Successfully deployed certificate: $cert_name"
else
failed_certs+=("$cert_name")
fi
else
failed_certs+=("$cert_name")
fi
# Clean up temporary file
rm -f "$cert_file"
else
failed_certs+=("$cert_name")
fi
done
log_action "=== Certificate deployment summary ==="
log_action "Total certificates: $total_count"
log_action "Successfully deployed: $success_count"
log_action "Failed deployments: ${#failed_certs[@]}"
if [[ ${#failed_certs[@]} -gt 0 ]]; then
log_action "Failed certificates: ${failed_certs[*]}"
fi
return $((total_count - success_count))
}
# Main execution function
main() {
local action="${1:-deploy}"
log_action "=== MacFleet Certificate Management Started ==="
log_action "Action: $action"
log_action "Hostname: $(hostname)"
log_action "User: $(whoami)"
# Setup
setup_directories
case "$action" in
"deploy")
backup_certificates
configure_authorization
deploy_certificate_bundle
list_certificates
generate_certificate_report
;;
"list")
list_certificates
;;
"report")
generate_certificate_report
;;
"backup")
backup_certificates
;;
"remove")
if [[ -n "$2" ]]; then
remove_certificate "$2"
else
echo "Usage: $0 remove <certificate_name>"
exit 1
fi
;;
*)
echo "Usage: $0 {deploy|list|report|backup|remove}"
echo " deploy - Deploy all configured certificates"
echo " list - List installed certificates"
echo " report - Generate certificate report"
echo " backup - Backup current certificates"
echo " remove - Remove specific certificate"
exit 1
;;
esac
# Cleanup
rm -rf "$TEMP_DIR"
log_action "=== Certificate management completed ==="
}
# Execute main function
main "$@"
Certificate Trust Policies
macOS supports different trust policies for certificates:
Trust Policy | Description | Use Case |
---|---|---|
trustRoot | Trusted root certificate | CA root certificates |
trustAsRoot | Trust as root without usage constraints | Enterprise CA certificates |
deny | Explicitly deny trust | Revoked or blacklisted certificates |
unspecified | Use system default trust | Let system determine trust |
Certificate Validation and Security
Verify Certificate Chain
#!/bin/bash
# Verify certificate chain and validation
verify_certificate_chain() {
local cert_file="$1"
local ca_bundle="/etc/ssl/cert.pem"
echo "Verifying certificate chain..."
# Basic certificate verification
if openssl verify -CAfile "$ca_bundle" "$cert_file"; then
echo "✅ Certificate chain is valid"
else
echo "❌ Certificate chain verification failed"
return 1
fi
# Check certificate purposes
echo "Certificate purposes:"
openssl x509 -in "$cert_file" -purpose -noout
# Extract key usage
echo "Key usage:"
openssl x509 -in "$cert_file" -text -noout | grep -A 5 "X509v3 Key Usage"
return 0
}
Certificate Security Scan
#!/bin/bash
# Security analysis of installed certificates
security_scan_certificates() {
echo "=== Certificate Security Scan ==="
# Find weak certificates (MD5 or SHA1 signatures)
echo "Checking for weak signature algorithms..."
security find-certificate -a -p /Library/Keychains/System.keychain | \
while read -r cert_pem; do
if [[ "$cert_pem" =~ ^-----BEGIN ]]; then
local sig_algo
sig_algo=$(echo "$cert_pem" | openssl x509 -text -noout | grep "Signature Algorithm" | head -1)
if [[ "$sig_algo" =~ (md5|sha1) ]]; then
local subject
subject=$(echo "$cert_pem" | openssl x509 -subject -noout)
echo "⚠️ Weak signature found: $subject - $sig_algo"
fi
fi
done
# Find expiring certificates
echo -e "\nChecking for expiring certificates..."
security find-certificate -a -p /Library/Keychains/System.keychain | \
while read -r cert_pem; do
if [[ "$cert_pem" =~ ^-----BEGIN ]]; then
if ! echo "$cert_pem" | openssl x509 -checkend 2592000 >/dev/null 2>&1; then
local subject
local expiry
subject=$(echo "$cert_pem" | openssl x509 -subject -noout)
expiry=$(echo "$cert_pem" | openssl x509 -enddate -noout)
echo "⚠️ Expiring certificate: $subject - $expiry"
fi
fi
done
echo "Security scan completed"
}
Automated Certificate Renewal
#!/bin/bash
# Automated certificate renewal script
setup_certificate_renewal() {
local renewal_script="/usr/local/bin/macfleet_cert_renewal.sh"
local launchd_plist="/Library/LaunchDaemons/com.macfleet.cert.renewal.plist"
# Create renewal script
cat > "$renewal_script" << 'EOF'
#!/bin/bash
LOG_FILE="/var/log/macfleet_cert_renewal.log"
exec > >(tee -a "$LOG_FILE") 2>&1
echo "$(date): Starting automated certificate renewal"
# Add your certificate renewal logic here
# This could include:
# - Checking certificate expiration dates
# - Downloading new certificates from your PKI
# - Replacing expired certificates
# - Sending alerts about upcoming renewals
echo "$(date): Certificate renewal completed"
EOF
chmod +x "$renewal_script"
# Create LaunchDaemon for scheduled execution
cat > "$launchd_plist" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.macfleet.cert.renewal</string>
<key>ProgramArguments</key>
<array>
<string>$renewal_script</string>
</array>
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key>
<integer>2</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
</array>
<key>RunAtLoad</key>
<false/>
</dict>
</plist>
EOF
# Load the LaunchDaemon
sudo launchctl load "$launchd_plist"
echo "Certificate renewal automation configured"
echo "Script: $renewal_script"
echo "Schedule: Daily at 2:00 AM"
}
Enterprise Integration
Active Directory Certificate Services
#!/bin/bash
# Integration with Microsoft ADCS
deploy_adcs_certificates() {
local adcs_server="$1"
local cert_template="$2"
echo "Deploying certificates from ADCS: $adcs_server"
# Request certificate from ADCS
# This would typically use certreq or similar tools
# Implementation depends on your ADCS configuration
echo "ADCS integration requires additional configuration"
echo "Please consult your PKI administrator"
}
SCEP (Simple Certificate Enrollment Protocol)
#!/bin/bash
# SCEP certificate enrollment
enroll_scep_certificate() {
local scep_url="$1"
local challenge_password="$2"
echo "Enrolling certificate via SCEP: $scep_url"
# Generate private key
openssl genrsa -out device.key 2048
# Create certificate request
openssl req -new -key device.key -out device.csr -subj "/CN=$(hostname)"
# Note: SCEP enrollment requires additional tools like sscep
echo "SCEP enrollment requires sscep or similar tools"
echo "Please install appropriate SCEP client"
}
Best Practices
🔐 Security Considerations
- Validate certificates before installation
- Use strong trust policies appropriate for certificate purpose
- Monitor certificate expiration dates regularly
- Implement certificate pinning for critical applications
📋 Management Guidelines
- Backup certificates before making changes
- Document certificate purposes and owners
- Use automation for large-scale deployments
- Monitor certificate usage and validity
🔍 Troubleshooting
- Check certificate format if installation fails
- Verify network connectivity for downloads
- Review trust policies if certificates aren't trusted
- Check system logs for detailed error information
Important Notes
- Administrative privileges required for system keychain modifications
- Certificate validation essential for security compliance
- Backup existing certificates before bulk operations
- Test certificate deployment on subset of devices first
- Monitor certificate expiration to prevent service disruptions