Document toolboxDocument toolbox

(2022.1) dod_crl.sh script

  1. Create the dod-crl  script, enter:

    vi /usr/local/sbin/dod-crl.sh
  2. Add the following contents:

    #!/usr/bin/env bash
    
    # CRL Downloader script for manually downloading
    # CRL .zip bundle from CRL server to support
    # offline CRL validation.
    #
    # Last updated 04 FEB 2023
    #
    
    # Declare Variables
    SCRIPT="$(readlink -f "$0")"
    LOCALREPO_DODLOCAL_CRL_URL="http://crl.dod.local/CertEnroll/ALLCRLZIP.zip"
    LOCALREPO_DODJITC_CRL_URL="http://crl.dod.local/CertEnroll/JITCALLCRLZIP.zip"
    DODJITC_CRL_URL="https://crl.nit.disa.mil/getcrlzip?ALL+CRL+ZIP"
    DODPROD_CRL_URL="https://crl.gds.disa.mil/getcrlzip?ALL+CRL+ZIP"
    ALLCRLURL="http://crl.dod.local/CertEnroll/ALLCRLZIP.zip"
    default_crldir="/etc/pki/tls/crl/"
    
    # Script Validation: Ensure this script is run as root
    if [[ "$(whoami)" != "root" ]]; then
        logger "$0 ERROR: script must be run as root."
        echo "$0 ERROR: script must be run as root." >&2
        exit 77
    fi
    validate_required_commands() {
        type -P c_rehash >/dev/null 2>&1 || {
            logger "$0 ERROR: Required dependency c_rehash is not installed.";
            echo "$0 ERROR: Required dependency c_rehash is not installed." >&2;
            exit 72;
        }
        type -P curl >/dev/null 2>&1 || {
            logger "$0 ERROR: Required dependency curl is not installed.";
            echo "$0 ERROR: Required dependency curl is not installed." >&2;
            exit 72;
        }
        type -P unzip >/dev/null 2>&1 || {
            logger "$0 ERROR: Required dependency unzip is not installed.";
            echo "$0 ERROR: Required dependency unzip is not installed." >&2;
            exit 72;
        }
    
    } # End validate_required_commands()
    
    override_dir_format_ck() {
        if [[ -n "${override_crldir}" ]];then
            if [[ "${override_crldir}" =~ ^(/)+([a-zA-Z0-9]*/)+$ ]];then
                effective_crldir="${override_crldir}"
            else
                logger "$0 ERROR: --override_crldir ${override_crldir} incorrect directory format."
                echo "$0 ERROR: --override_crldir ${override_crldir} incorrect directory format." >&2
                exit 64
            fi
        else
            effective_crldir="${default_crldir}"
        fi
    } # End override_dir_format_ck()
    
    dir_setup() {
    # Check input parameter for valid crldir or default
        if [[ ! -d "${effective_crldir}" ]];then
            mkdir -m 755 -p ${effective_crldir} || {
                logger "$0 ERROR: Could not create CRL directory at ${effective_crldir}.";
                echo "$0 ERROR: Could not create CRL directory at ${effective_crldir}." >&2; exit 74;
            } && {
                logger "$0 INFO: ${effective_crldir} did not exist and has been created.";
                echo "$0 INFO: ${effective_crldir} did not exist and has been created." >&2;
            }
        fi
    } # End dir_setup()
    
    staging_area() {
    # Set up a clean staging area to process CRL ZIP
    # Remove existing /tmp/.crl_staging so we start clean
        if [[ -d /tmp/.crl_staging ]];then
            rm -rf /tmp/.crl_staging || {
            logger "$0 ERROR: Could not remove existing staging area at /tmp/.crl_staging.";
            echo "$0 ERROR: Could not remove existing staging area at /tmp/.crl_staging." >&2;
            exit 74;
        }
        fi
        mkdir -m 755 -p /tmp/.crl_staging || {
            logger "$0 ERROR: Could not create staging area at /tmp/.crl_staging.";
            echo "$0 ERROR: Could not create staging area at /tmp/.crl_staging." >&2;
            exit 74;
        }
    } # End staging_area()
    
    fetch_crls_from_repo() {
    crlerrorqty=0
    # Loop through each CRL source and perform fetch and extract process if source was requested
        for CRL_SOURCE in LOCALREPO_DODLOCAL_CRL LOCALREPO_DODJITC_CRL DODJITC_CRL DODPROD_CRL;do
            unset CRL_SOURCE_URL
            while [[ "${!CRL_SOURCE}" = true ]];do
                CRL_SOURCE_URL=${CRL_SOURCE}_URL
                curl "${!CRL_SOURCE_URL}" -o /tmp/.crl_staging/"${CRL_SOURCE}".zip || {
                    logger "$0 ERROR: Could not download ${CRL_SOURCE} from ${!CRL_SOURCE_URL}.";
                    echo "$0 ERROR: Could not download ${CRL_SOURCE} from ${!CRL_SOURCE_URL}." >&2;
                    ((crlerrorqty++))
                    break
                }
                if [[ -f /tmp/.crl_staging/"${CRL_SOURCE}".zip ]];then
                    unzip -o /tmp/.crl_staging/"${CRL_SOURCE}".zip -d /tmp/.crl_staging/ || {
                        logger "$0 ERROR: Could not extract /tmp/.crl_staging/${CRL_SOURCE}.zip.";
                        echo "$0 ERROR: Could not extract /tmp/.crl_staging/${CRL_SOURCE}.zip." >&2;
                        ((crlerrorqty++))
                        break
                    }
                else
                    logger "$0 ERROR: Could not find /tmp/.crl_staging/${CRL_SOURCE}.zip for processing.";
                    echo "$0 ERROR: Could not find /tmp/.crl_staging/${CRL_SOURCE}.zip for processing." >&2;
                    ((crlerrorqty++))
                    break
                fi
                # Workaround for JITC CA naming issue
                if [[ "${CRL_SOURCE}" =~ ^(LOCALREPO_DODJITC_CRL|DODJITC_CRL)$ ]];then
                    if [[ -f /tmp/.crl_staging/DODJITCSWCA_60.crl ]];then
                        /bin/cp -rf /tmp/.crl_staging/DODJITCSWCA_60.crl /tmp/.crl_staging/DODJITCSWCA_60_SSL.crl || {
                            logger "$0 ERROR: Could not copy /tmp/.crl_staging/DODJITCSWCA_60.crl to /tmp/.crl_staging/DODJITCSWCA_60_SSL.crl. CRL for this CA may not work.";
                            echo "$0 ERROR: Could not copy /tmp/.crl_staging/DODJITCSWCA_60.crl to /tmp/.crl_staging/DODJITCSWCA_60_SSL.crl. CRL for this CA may not work." >&2;
                            ((crlerrorqty++))
                        }
                    fi
                fi
                break
            done
        done
    } # End fetch_crls_from_repo()
    
    convert_crl_to_pem_and_rehash() {
    converterror=0
    crldirpermerror=0
    # If there are .crl files in the staging directory, convert them from DER to PEM and place them in effective CRL directory
    # then run c_rehash on the effective CRL directory to create hashed symbolic links to the CRL files
        if [[ -n "$(find /tmp/.crl_staging/ -maxdepth 1 -name '*.crl' -print -quit)" ]];then
            for dercrl in $(find /tmp/.crl_staging/ -maxdepth 1 -name '*.crl' -printf "%f\n");do
                openssl crl -in "/tmp/.crl_staging/${dercrl}" -inform DER -outform PEM -out "${effective_crldir}"/"${dercrl}" || {
                    logger "$0 ERROR: Could not convert /tmp/.crl_staging/${dercrl} to PEM.";
                    echo "$0 ERROR: Could not convert /tmp/.crl_staging/${dercrl} to PEM." >&2;
                    (converterror++)
                }
            done
            chown root:root "${effective_crldir}"/*.crl || {
                logger "$0 ERROR: Could not set ownership on ${effective_crldir}/*.crl.";
                echo "$0 ERROR: Could not set ownership on ${effective_crldir}/*.crl." >&2;
                ((crldirpermerror++));
            }
            chmod 644 "${effective_crldir}"/*.crl || {
                logger "$0 ERROR: Could not set permissions on ${effective_crldir}/*.crl.";
                echo "$0 ERROR: Could not set permissions on ${effective_crldir}/*.crl." >&2;
                ((crldirpermerror++));
            }
            c_rehash "${effective_crldir}" || {
                logger "$0 ERROR: Could not rehash CRLs in ${effective_crldir}. CRLs may be in an inconsistent state!";
                echo "$0 ERROR: Could not rehash CRLs in ${effective_crldir}. CRLs may be in an inconsistent state!" >&2;
                staging_area_cleanup
                exit 74
            }
        else
            logger "$0 ERROR: No .crl files to process under /tmp/.crl_staging/.";
            echo "$0 ERROR: No .crl files to process under /tmp/.crl_staging/." >&2;
            staging_area_cleanup
            exit 74
        fi
    } # End convert_crl_to_pem_and_rehash()
    
    staging_area_cleanup() {
    # Clean up the staging area before exiting
        if [[ "${debug}" = true ]];then
            logger "$0 DEBUG: Skipping removal of existing staging area at /tmp/.crl_staging.";
            echo "$0 DEBUG: Skipping removal of existing staging area at /tmp/.crl_staging." >&2;
        else
            if [[ -d /tmp/.crl_staging ]];then
                rm -rf /tmp/.crl_staging || {
                logger "$0 ERROR: Could not remove existing staging area at /tmp/.crl_staging.";
                echo "$0 ERROR: Could not remove existing staging area at /tmp/.crl_staging." >&2;
                exit 74;
                }
            fi
        fi
    } # End staging_area_cleanup()
    
    crontab_update() {
    # Create or replace the crontab entry for this script
        # Remove previous entry for this script from crontab
        (crontab -l 2>/dev/null | sed -e "\,$SCRIPT,d")| crontab -
        # Append new entry for this script to crontab
        (crontab -l 2>/dev/null || true; echo "0 */4 * * *     ${SCRIPT} ${SCRIPTARGS} >/dev/null 2>&1") | crontab -
    }
    exit_status() {
    # Evaluate success and abnormal but non-abort exit status counters and exit script
        if [[ "${crlerrorqty}" > 0 ]]|| [[ "${converterror}" > 0 ]]|| [[ "${crldirpermerror}" > 0 ]];then
            logger "$0 ERROR: Script completed, but failed to update all requested CRLs."
            echo "$0 ERROR: Script completed, but failed to update all requested CRLs." >&2
            exit 75
        else
            logger "$0 INFO: Script completed. All requested CRLs updated successfully."
            echo "$0 INFO: Script completed. All requested CRLs updated successfully." >&2
            exit 0
        fi
    
    } # End exit_status()
    
    print_usage() {
        echo "Usage for $0:"
    echo "Usage: $0 [OPTION] [REPOSITORY]"
    echo "Update local CRL cache from web repository, either manually or via crontab schedule."
    echo "Example 1, update CRLs to include internal and DoD JITC CRLs from local repository: $0 -uc -lri -lrdj"
    echo "Example 2, update cron schedule to include internal and DoD JITC CRLs from local repository: $0 -us -lri -lrdj"
    echo ""
    echo "Mode arguments, must specify only one mode."
    echo "  -uc, --update-cache     update local CRL cache from one or more repositories"
    echo "  -us, --update-schedule  update crontab schedule to run this script automatically for one or more repositories"
    echo "Repository arguments, must specify one or more repository arguments."
    echo "  -lri, --local-repo-internal include the local repository for the internal CA CRLs"
    echo "  -lrdj, --local-repo-dodjitc include the local repository for the DoD JITC CA CRLs"
    echo "  -dj, --dodjitc      include the DISA repository for the DoD JITC CA CRLs"
    echo "  -dp, --dodprod      include the DISA repository for the DoD production CA CRLs"
    echo "Optional arguments. May specify optional override path for CRLs. The default should be sufficient for most use cases."
    echo "  -o, --output [/target/dir/] override the default /etc/pki/tls/crl/ path with an alternative path. must include trailing /"
    echo "  -d, --debug         retain /tmp/.crl_staging on exit to support troubleshooting"
    echo "  -h, --help          print this help menu"
    echo ""
    echo "Exit status:"
    echo " 0  if OK,"
    echo " 64 if syntax error,"
    echo " 72 if missing required dependencies,"
    echo " 74 if error performing file/directory tasks,"
    echo " 75 if error processing one or more requested CRL,"
    echo " 77 if script not run as root."
    } # End print_usage()
    
    # Script execution options
    debug=false
    uc=false
    us=false
    LOCALREPO_DODLOCAL_CRL=false
    LOCALREPO_DODJITC_CRL=false
    DODJITC_CRL=false
    DODPROD_CRL=false
    SCRIPTARGS=$(echo $@ | sed -e 's/-us//g' -e 's/--update-schedule//g' -e 's/ $//g'  -e 's/^ //g' -e 's/  -/ -/g' -e 's/^/-uc /g')
    while [[ $# -gt 0 ]]&& [[ ."$1" =~ .([-]){0,1}+([a-z]) ]];
    do
        opt="$1";
        shift;
        case "$opt" in
            "-"|"--" )
                break 2
            ;;
            "-uc"|"--update-cache" )
                uc=true
            ;;
            "-us"|"--update-" )
                us=true
            ;;
            "-lri"|"--local-repo-internal" )
                LOCALREPO_DODLOCAL_CRL=true
            ;;
            "-lrdj"|"--local-repo-dodjitc" )
                LOCALREPO_DODJITC_CRL=true
            ;;
            "-dj"|"--dodjitc" )
                DODJITC_CRL=true
            ;;
            "-dp"|"--dodprod" )
                DODPROD_CRL=true
            ;;
            "-o"|"--output" )
                override_crldir="$1"
                shift
            ;;
            "-d"|"--debug" )
                debug=true
            ;;
            "-h"|"--help" )
                print_usage
                exit 0
            ;;
            *)
                echo >&2 "Invalid option: $opt $1"
                print_usage
                exit 1
            ;;
       esac
    done
    
    if [[ "${uc}" = false ]]&& [[ "${us}" = false ]];then
        logger "$0 ERROR: No modes specified, expected a single mode."
        echo "$0 ERROR: No modes specified, expected a single mode." >&2
        print_usage
        exit 64
    fi
    if [[ "${uc}" = true ]]&& [[ "${us}" = true ]];then
        logger "$0 ERROR: Multiple modes specified, expected a single mode."
        echo "$0 ERROR: Multiple modes specified, expected a single mode." >&2
        print_usage
        exit 64
    fi
    if [[ "${uc}" = true ]]&& [[ "${us}" = false ]];then
        mode="update-cache"
    fi
    if [[ "${us}" = true ]]&& [[ "${uc}" = false ]];then
        mode="update-schedule"
    fi
    if [[ "${LOCALREPO_DODLOCAL_CRL}" = false ]]&& [[ "${LOCALREPO_DODJITC_CRL}" = false ]]&& [[ "${DODJITC_CRL}" = false ]]&& [[ "${DODPROD_CRL}" = false ]];then
        logger "$0 ERROR: No repository specified, expected one or more repositories."
        echo "$0 ERROR: No repositories specified, expected one or more repositories." >&2
        print_usage
        exit 64
    fi
    
    validate_required_commands
    if [[ "${mode}" = "update-cache" ]];then
        override_dir_format_ck
        dir_setup
        staging_area
        fetch_crls_from_repo
        convert_crl_to_pem_and_rehash
        staging_area_cleanup
        exit_status
    fi
    if [[ "${mode}" = "update-schedule" ]];then
        override_dir_format_ck
        crontab_update
    fi
  3. Save the file, enter: :wq!

  4. Set the permissions on the dod-crl script, enter:

    chmod 700 /usr/local/sbin/dod-crl.sh
  5. Install the dod-crl script as a cron task, enter:

    /usr/local/sbin/dod-crl.sh -us -dp
  6. Note -dp is for DoD Production CRLs. The system must have internet access to https://crl.gds.disa.mil/getcrlzip?ALL+CRL+ZIP

  7. Manually run the dod-crl script for the first time, enter: /usr/local/sbin/dod-crl.sh -uc -dp