#!/bin/bash #################################################################################################### # # # Import DoD Certs # # Purpose: Import the latest DoD Certs from dod.cyber.mil # # https://github.com/cocopuff2u # #################################################################################################### # # History # # 1.0 8/07/23 Original # 1.1 10/21/24 - Removed force smart card removal changed it to a warning, cleaned up log out, # added logging, changed OSAprompts, and changed wording for things # 1.2 11/26/24 - Added a check/create section to verify /var/tmp exists, does not exist on brand new # out of the box machines # # Issues: If Smart Card is present it will not accept the pin when granting trust per self-signed cert # Solution: Ask the user to remove smart card doing those steps, logic put in place if smart card is not # present to keep running the script. # # Issue Update 4/10/24: Noticed with Sonoma 14.4.1 the keychain will accept your pin on trust import, did not change the # to reflect the changes until i can confirm its working moving forward. # # Issue Update 10/21/24: So far every version past that supports smart card with keychain. # #################################################################################################### # Log file location for capturing the script's activities LOG_FILE="/private/var/log/Import_DoD_Certs.log" ENABLE_LOGGING=false # Default logging setting #################################################################################################### # Function to log messages to console and log file #################################################################################################### SCRIPT_VERSION="1.2" # Function to display usage instructions usage() { echo "Usage: $0 [--enable-logging | --disable-logging]" exit 1 } # Parse command-line arguments for enabling or disabling logging while [[ "$#" -gt 0 ]]; do case $1 in --enable-logging) ENABLE_LOGGING=true ;; # Enable logging if argument provided --disable-logging) ENABLE_LOGGING=false ;; # Disable logging if argument provided *) usage ;; # Show usage if an unknown argument is provided esac shift # Move to the next argument done # Function to log messages with timestamps log_message() { local MESSAGE="$1" if $ENABLE_LOGGING; then echo "DoD Import Certs Script: $(date '+%Y-%m-%d %H:%M:%S') - $MESSAGE" | tee -a "$LOG_FILE" else echo "DoD Import Certs Script: $MESSAGE" # Print to console if logging is disabled fi } # Function to create log file with a header if it doesn't exist initialize_log_file() { if [ ! -f "$LOG_FILE" ]; then echo -e "\n##############################################\n# DoD Import Certs Script (v$SCRIPT_VERSION)\n# \n# Always grab the latest at \n# github.com/cocopuff2u/MacOS_GOV_Scripts/blob/main/Keychain_And_Certificates_Scripts/Import_DoD_Certs.sh \n# \n# Script by: https://github.com/cocopuff2u/\n##############################################\n" > "$LOG_FILE" fi } #################################################################################################### # Initialize logging if enabled if $ENABLE_LOGGING; then initialize_log_file fi # Print header to console echo -e "\n##############################################\n# DoD Import Certs Script (v$SCRIPT_VERSION)\n# \n# Always grab the latest at \n# github.com/cocopuff2u/MacOS_GOV_Scripts/blob/main/Keychain_And_Certificates_Scripts/Import_DoD_Certs.sh \n# \n# Script by: https://github.com/cocopuff2u/\n##############################################\n" log_message "Initiating Script..." # Prompt user for confirmation to proceed with the certificate import log_message "User script confirmation prompt..." userChoice=$(osascript -e 'tell application "System Events" to set frontmost of process "osascript" to true' -e 'button returned of (display dialog "This script will import DoD certificates directly from dod.cyber.mil into the system keychain.\n\nDo you want to continue with the import?" with title "Confirmation" buttons {"Exit", "Continue"} default button "Continue" with icon caution)') # Check the user's choice and exit if they chose to exit if [ "$userChoice" == "Exit" ]; then log_message "User selected to exit the script. Exiting." exit 1 else log_message "User selected to continue with running the script..." fi # Notify the user that the import process has started osascript -e 'display notification "Initiating import of DoD certificates in the background. Please wait..." with title "Certificate Import"' # Set the URL of the ZIP file containing the certificates ZIP_URL="https://dl.dod.cyber.mil/wp-content/uploads/pki-pke/zip/unclass-dod_approved_external_pkis_trust_chains.zip" # Define temporary folder and file names for processing TEMP_DIR="/var/tmp" GENERIC_FOLDER_NAME="DOD_Certs" ZIP_FILE_NAME="DOD_Certs.zip" # Ensure /var/tmp exists if [ ! -d "$TEMP_DIR" ]; then log_message "Temporary directory $TEMP_DIR does not exist. Creating it..." mkdir -p "$TEMP_DIR" if [ $? -ne 0 ]; then log_message "Failed to create temporary directory $TEMP_DIR. Exiting." exit 1 fi log_message "Temporary directory created: $TEMP_DIR" fi # Define subfolder names for different certificate types SUBFOLDER1="_DoD/Intermediate_and_Issuing_CA_Certs" SUBFOLDER2="_DoD/Trust_Anchors_Self-Signed" # Calculate full paths for folder and subfolders FULL_FOLDER_PATH="$TEMP_DIR/$GENERIC_FOLDER_NAME" FULL_PATH_WITH_CERTS_SUBFOLDERS="$FULL_FOLDER_PATH/$SUBFOLDER1" FULL_PATH_WITH_SELF_SIGNED_CERTS_SUBFOLDERS="$FULL_FOLDER_PATH/$SUBFOLDER2" # Function to cleanup and exit the script on error cleanup_and_exit() { log_message "Cleaning up any files created..." rm -rf "$TEMP_DIR" # Remove temporary directory log_message "Files cleaned." log_message "An error occurred while running the script. Exiting." exit 1 # Exit with an error code } # Check if the generic folder already exists; if it does, remove it to start fresh if [ -d "$FULL_FOLDER_PATH" ]; then log_message "Generic folder already exists. Removing..." rm -rf "$FULL_FOLDER_PATH" log_message "Folder removed." fi # Download the ZIP file containing the certificates log_message "Certificate URL: $ZIP_URL" log_message "Downloading ZIP file..." if ! curl -L -o "$TEMP_DIR/$ZIP_FILE_NAME" "$ZIP_URL"; then log_message "Failed to download the ZIP file." cleanup_and_exit # Exit on failure to download fi sleep 2 # Pause for a moment to allow download completion # Extract the downloaded ZIP file into the temporary directory log_message "Unzipping the downloaded file..." unzip -q "$TEMP_DIR/$ZIP_FILE_NAME" -d "$TEMP_DIR" # Identify the name of the extracted folder (assumed to be the only folder) extracted_folder_name=$(unzip -l "$TEMP_DIR/$ZIP_FILE_NAME" | awk 'NR==4{print $4}') if [ -z "$extracted_folder_name" ]; then log_message "Failed to determine the extracted folder name." cleanup_and_exit # Exit if unable to determine folder name fi # Rename the extracted folder to a more generic name for easier reference mv "$TEMP_DIR/$extracted_folder_name" "$FULL_FOLDER_PATH" # Remove the ZIP file after extraction to save space rm "$TEMP_DIR/$ZIP_FILE_NAME" log_message "Downloaded and extracted the ZIP file to: $FULL_FOLDER_PATH" log_message "Full path for DoD Certs: $FULL_PATH_WITH_CERTS_SUBFOLDERS" log_message "Full path for DoD Self-Signed Certs: $FULL_PATH_WITH_SELF_SIGNED_CERTS_SUBFOLDERS" # Import all the non-self-signed certificates from the designated folder log_message "Initiating import of non-self-signed certificates from $FULL_PATH_WITH_CERTS_SUBFOLDERS..." for cert_file in "$FULL_PATH_WITH_CERTS_SUBFOLDERS"/*; do if [ -f "$cert_file" ]; then output=$(security import "$cert_file" -k /Library/Keychains/system.keychain 2>&1) # Import the certificate and capture output # Check for an error indicating that the certificate already exists if echo "$output" | grep -q "The specified item already exists in the keychain."; then short_name="${cert_file#$FULL_PATH_WITH_CERTS_SUBFOLDERS/}" # Extract short name for logging log_message "Certificate $short_name already exists" # Log the existing certificate else short_name="${cert_file#$FULL_PATH_WITH_CERTS_SUBFOLDERS/}" # Extract short name for logging log_message "Adding Certificate $short_name to the system keychain" # Log the addition fi fi done log_message "Completed Import of non-self-signed certificates." # Prompt the user regarding self-signed certificate import preferences log_message "User self-signed warning prompt..." userChoice=$(osascript -e 'tell application "System Events" to set frontmost of process "osascript" to true' -e \ 'set dialogText to "If you select \"Trusted\", you will be prompted for the smart card PIN or your machine password for each certificate. This is an Apple security function.\n\nNote: In certain OS versions (especially 14.3 or below), you will be prompted for the smart card PIN but it may not accept the PIN. It is advised to remove the smart card before proceeding.\n\nDo you want to import self-signed certificates as trusted or untrusted?"' -e \ 'set dialogTitle to "Self-Signed Certificate Import"' -e \ 'set userChoice to display dialog dialogText with title dialogTitle buttons {"Untrusted", "Trusted"} default button "Trusted" with icon caution' -e \ 'return button returned of userChoice') # Handle the user's choice for importing self-signed certificates if [ "$userChoice" == "Untrusted" ]; then log_message "User selected to import as untrusted." # Log user choice osascript -e 'display notification "Initiating import of Self-Signed DoD certificates as untrusted in the background. Please wait..." with title "Certificate Import"' log_message "Initiating import of self-signed certificates from $FULL_PATH_WITH_SELF_SIGNED_CERTS_SUBFOLDERS..." # Import self-signed certificates as untrusted for cert_file in "$FULL_PATH_WITH_SELF_SIGNED_CERTS_SUBFOLDERS"/*; do if [ -f "$cert_file" ]; then short_name="${cert_file#$FULL_PATH_WITH_SELF_SIGNED_CERTS_SUBFOLDERS/}" # Extract short name for logging output=$(security import "$cert_file" -k /Library/Keychains/system.keychain 2>&1) if echo "$output" | grep -q "The specified item already exists in the keychain."; then log_message "Already exists, skipping: $short_name" # Log if the certificate already exists else log_message "Adding $short_name to the keychain." # Log addition of the certificate fi fi done else log_message "User selected to import as trusted." # Log user choice osascript -e 'display notification "Initiating import of Self-Signed DoD certificates as trusted in the background. Will prompt for password/pin. Please wait..." with title "Certificate Import"' log_message "Initiating import of self-signed certificates from $FULL_PATH_WITH_SELF_SIGNED_CERTS_SUBFOLDERS..." # Import self-signed certificates as trusted for cert_file in "$FULL_PATH_WITH_SELF_SIGNED_CERTS_SUBFOLDERS"/*; do if [ -f "$cert_file" ]; then short_name="${cert_file#$FULL_PATH_WITH_SELF_SIGNED_CERTS_SUBFOLDERS/}" # Extract short name for logging output=$(sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "$cert_file" 2>&1) if echo "$output" | grep -q "The authorization was canceled by the user."; then log_message "User declined PIN or password for: $short_name" # Log if the user declined else log_message "Successfully added trusted certificate: $short_name" # Log successful addition fi fi done fi # Notify the user of the completion of the certificate import process osascript -e 'display notification "Completed Import of all Certificates" with title "Certificate Import Complete"' osascript -e 'tell application "System Events" to set frontmost of process "osascript" to true' -e \ 'display dialog "Certificate import completed successfully!" with title "Import Complete" buttons {"OK"} default button "OK" with icon note' > /dev/null 2>&1 & # Clean up all temporary files and directories created during the process log_message "Cleaning up files..." rm -rf "$FULL_FOLDER_PATH" # Remove the folder containing imported certificates log_message "Files cleaned." log_message "Script execution completed." # Log the completion of the script exit 0 # Exit the script successfully