|
#!/bin/bash |
|
|
|
# Certificate Common Library |
|
# Shared functions for certificate management scripts |
|
# Usage: source ./cert_common.sh |
|
|
|
# Default configuration - can be overridden by environment variables |
|
GLOBAL_CERT_PATH="${GLOBAL_CERT_PATH:-$HOME/ssl/certs}" |
|
CA_FOLDER="${CA_FOLDER:-CA}" |
|
SERVER_FOLDER="${SERVER_FOLDER:-Server}" |
|
KEY_SIZE="${KEY_SIZE:-2048}" |
|
|
|
# Default certificate details for CA |
|
DEFAULT_CA_COUNTRY="${CA_COUNTRY:-US}" |
|
DEFAULT_CA_STATE="${CA_STATE:-California}" |
|
DEFAULT_CA_CITY="${CA_CITY:-San Francisco}" |
|
DEFAULT_CA_ORG="${CA_ORG:-My Organization}" |
|
DEFAULT_CA_ORG_UNIT="${CA_ORG_UNIT:-IT Department}" |
|
DEFAULT_CA_EMAIL="${CA_EMAIL:-admin@example.com}" |
|
CA_VALIDITY_DAYS="${CA_VALIDITY_DAYS:-3650}" # 10 years |
|
|
|
# Default certificate details for Server |
|
DEFAULT_SERVER_COUNTRY="${SERVER_COUNTRY:-US}" |
|
DEFAULT_SERVER_STATE="${SERVER_STATE:-California}" |
|
DEFAULT_SERVER_CITY="${SERVER_CITY:-San Francisco}" |
|
DEFAULT_SERVER_ORG="${SERVER_ORG:-My Organization}" |
|
DEFAULT_SERVER_ORG_UNIT="${SERVER_ORG_UNIT:-IT Department}" |
|
DEFAULT_SERVER_EMAIL="${SERVER_EMAIL:-server@example.com}" |
|
SERVER_VALIDITY_DAYS="${SERVER_VALIDITY_DAYS:-365}" # 1 year |
|
|
|
# Color codes for output |
|
RED='\033[0;31m' |
|
GREEN='\033[0;32m' |
|
YELLOW='\033[0;33m' |
|
BLUE='\033[0;34m' |
|
NC='\033[0m' # No Color |
|
|
|
# Function to log messages with colors |
|
log_info() { |
|
echo -e "${BLUE}ℹ${NC} $1" |
|
} |
|
|
|
log_success() { |
|
echo -e "${GREEN}✓${NC} $1" |
|
} |
|
|
|
log_warning() { |
|
echo -e "${YELLOW}⚠${NC} $1" |
|
} |
|
|
|
log_error() { |
|
echo -e "${RED}✗${NC} $1" |
|
} |
|
|
|
# Function to check if OpenSSL is available |
|
check_openssl() { |
|
if ! command -v openssl > /dev/null 2>&1; then |
|
log_error "OpenSSL is not installed or not in PATH" |
|
log_error "Please install OpenSSL to manage certificates" |
|
exit 1 |
|
fi |
|
} |
|
|
|
# Function to create directory if it doesn't exist |
|
create_directory() { |
|
local dir_path="$1" |
|
|
|
if [[ ! -d "$dir_path" ]]; then |
|
log_info "Creating directory: $dir_path" |
|
if ! mkdir -p "$dir_path"; then |
|
log_error "Failed to create directory $dir_path" |
|
log_error "Check permissions or run with sudo if needed" |
|
exit 1 |
|
fi |
|
log_success "Directory created successfully" |
|
fi |
|
} |
|
|
|
# Function to check if file exists |
|
check_file_exists() { |
|
local file_path="$1" |
|
local file_type="$2" |
|
|
|
if [[ -f "$file_path" ]]; then |
|
log_success "$file_type found: $file_path" |
|
return 0 |
|
else |
|
log_error "$file_type NOT found: $file_path" |
|
return 1 |
|
fi |
|
} |
|
|
|
# Function to validate certificate format |
|
validate_certificate() { |
|
local cert_path="$1" |
|
|
|
if [[ ! -f "$cert_path" ]]; then |
|
log_error "Certificate file does not exist: $cert_path" |
|
return 1 |
|
fi |
|
|
|
if ! openssl x509 -in "$cert_path" -noout > /dev/null 2>&1; then |
|
log_error "Invalid certificate format: $cert_path" |
|
return 1 |
|
fi |
|
|
|
return 0 |
|
} |
|
|
|
# Function to check if certificate is a valid CA |
|
validate_ca_certificate() { |
|
local cert_path="$1" |
|
|
|
if ! validate_certificate "$cert_path"; then |
|
return 1 |
|
fi |
|
|
|
# Check if certificate has CA:TRUE in basic constraints |
|
local ca_constraint=$(openssl x509 -in "$cert_path" -noout -text 2>/dev/null | grep -A1 "Basic Constraints:" | grep "CA:TRUE") |
|
|
|
if [[ -n "$ca_constraint" ]]; then |
|
log_success "Certificate has proper CA constraints" |
|
return 0 |
|
else |
|
log_warning "Certificate does not have CA:TRUE constraint" |
|
log_warning "This may not be a proper CA certificate" |
|
return 1 |
|
fi |
|
} |
|
|
|
# Function to check certificate validity (not expired) |
|
check_certificate_validity() { |
|
local cert_path="$1" |
|
|
|
if ! validate_certificate "$cert_path"; then |
|
return 1 |
|
fi |
|
|
|
if openssl x509 -in "$cert_path" -noout -checkend 0 > /dev/null 2>&1; then |
|
log_success "Certificate is currently valid" |
|
return 0 |
|
else |
|
log_error "Certificate is expired or invalid" |
|
return 1 |
|
fi |
|
} |
|
|
|
# Function to check certificate expiration warning |
|
check_expiration_warning() { |
|
local cert_path="$1" |
|
local days_warning="${2:-30}" |
|
|
|
if ! validate_certificate "$cert_path"; then |
|
return 2 |
|
fi |
|
|
|
# Check if certificate expires within warning period |
|
if openssl x509 -in "$cert_path" -noout -checkend $((days_warning * 24 * 3600)) > /dev/null 2>&1; then |
|
# Get actual days until expiration |
|
local end_date=$(openssl x509 -in "$cert_path" -noout -enddate 2>/dev/null | sed 's/notAfter=//') |
|
local end_epoch=$(date -d "$end_date" +%s 2>/dev/null) |
|
local current_epoch=$(date +%s) |
|
local days_left=$(( (end_epoch - current_epoch) / 86400 )) |
|
|
|
if [[ $days_left -le $days_warning ]]; then |
|
log_warning "Certificate expires in $days_left days" |
|
return 1 |
|
else |
|
log_success "Certificate expires in $days_left days (OK)" |
|
return 0 |
|
fi |
|
else |
|
log_error "Certificate is expired" |
|
return 2 |
|
fi |
|
} |
|
|
|
# Function to display certificate information |
|
show_certificate_info() { |
|
local cert_path="$1" |
|
local title="${2:-Certificate Information}" |
|
|
|
if ! validate_certificate "$cert_path"; then |
|
return 1 |
|
fi |
|
|
|
echo "" |
|
echo "$title:" |
|
echo "$(printf '=%.0s' $(seq 1 ${#title}))=" |
|
|
|
echo "Subject: $(openssl x509 -in "$cert_path" -noout -subject 2>/dev/null | sed 's/subject=//')" |
|
echo "Issuer: $(openssl x509 -in "$cert_path" -noout -issuer 2>/dev/null | sed 's/issuer=//')" |
|
echo "Valid From: $(openssl x509 -in "$cert_path" -noout -startdate 2>/dev/null | sed 's/notBefore=//')" |
|
echo "Valid Until: $(openssl x509 -in "$cert_path" -noout -enddate 2>/dev/null | sed 's/notAfter=//')" |
|
echo "Serial Number: $(openssl x509 -in "$cert_path" -noout -serial 2>/dev/null | sed 's/serial=//')" |
|
|
|
# Show Subject Alternative Names for server certificates |
|
local san=$(openssl x509 -in "$cert_path" -noout -text 2>/dev/null | grep -A1 "Subject Alternative Name:" | grep -v "Subject Alternative Name:") |
|
if [[ -n "$san" ]]; then |
|
echo "Subject Alternative Names: $san" |
|
fi |
|
|
|
echo "SHA1 Fingerprint: $(openssl x509 -in "$cert_path" -noout -fingerprint 2>/dev/null | sed 's/SHA1 Fingerprint=//')" |
|
} |
|
|
|
# Function to set file permissions securely |
|
set_secure_permissions() { |
|
local cert_path="$1" |
|
local key_path="$2" |
|
|
|
if [[ -f "$cert_path" ]]; then |
|
chmod 644 "$cert_path" |
|
fi |
|
|
|
if [[ -f "$key_path" ]]; then |
|
chmod 600 "$key_path" |
|
fi |
|
|
|
log_success "Permissions set correctly" |
|
echo " Certificate: 644 (readable by all)" |
|
echo " Private key: 600 (readable by owner only)" |
|
} |
|
|
|
# Function to verify certificate chain |
|
verify_certificate_chain() { |
|
local cert_path="$1" |
|
local ca_cert_path="$2" |
|
|
|
if ! validate_certificate "$cert_path" || ! validate_certificate "$ca_cert_path"; then |
|
return 1 |
|
fi |
|
|
|
echo "" |
|
echo "Verifying certificate chain..." |
|
echo "=============================" |
|
|
|
if openssl verify -CAfile "$ca_cert_path" "$cert_path" > /dev/null 2>&1; then |
|
log_success "Certificate chain verification successful" |
|
echo " Certificate is properly signed by the CA" |
|
return 0 |
|
else |
|
log_error "Certificate chain verification failed" |
|
echo " Certificate may not be properly signed by the CA" |
|
return 1 |
|
fi |
|
} |
|
|
|
# Function to collect certificate details interactively |
|
collect_certificate_details() { |
|
local cert_type="$1" # "ca" or "server" |
|
local common_name_default="$2" |
|
|
|
echo "" |
|
echo "Enter ${cert_type^^} Certificate Details:" |
|
echo "$(printf '=%.0s' $(seq 1 $((${#cert_type} + 22))))" |
|
|
|
# Set defaults based on certificate type |
|
if [[ "$cert_type" == "ca" ]]; then |
|
local default_country="$DEFAULT_CA_COUNTRY" |
|
local default_state="$DEFAULT_CA_STATE" |
|
local default_city="$DEFAULT_CA_CITY" |
|
local default_org="$DEFAULT_CA_ORG" |
|
local default_org_unit="$DEFAULT_CA_ORG_UNIT" |
|
local default_email="$DEFAULT_CA_EMAIL" |
|
local default_cn="${common_name_default:-${DEFAULT_CA_ORG} Root CA}" |
|
else |
|
local default_country="$DEFAULT_SERVER_COUNTRY" |
|
local default_state="$DEFAULT_SERVER_STATE" |
|
local default_city="$DEFAULT_SERVER_CITY" |
|
local default_org="$DEFAULT_SERVER_ORG" |
|
local default_org_unit="$DEFAULT_SERVER_ORG_UNIT" |
|
local default_email="$DEFAULT_SERVER_EMAIL" |
|
local default_cn="${common_name_default:-localhost}" |
|
fi |
|
|
|
read -p "Country (2 letter code) [$default_country]: " CERT_COUNTRY |
|
CERT_COUNTRY=${CERT_COUNTRY:-$default_country} |
|
|
|
read -p "State/Province [$default_state]: " CERT_STATE |
|
CERT_STATE=${CERT_STATE:-$default_state} |
|
|
|
read -p "City/Locality [$default_city]: " CERT_CITY |
|
CERT_CITY=${CERT_CITY:-$default_city} |
|
|
|
read -p "Organization [$default_org]: " CERT_ORG |
|
CERT_ORG=${CERT_ORG:-$default_org} |
|
|
|
read -p "Organizational Unit [$default_org_unit]: " CERT_ORG_UNIT |
|
CERT_ORG_UNIT=${CERT_ORG_UNIT:-$default_org_unit} |
|
|
|
if [[ "$cert_type" == "ca" ]]; then |
|
read -p "Common Name (CA Name) [$default_cn]: " CERT_COMMON_NAME |
|
else |
|
read -p "Common Name (Server FQDN) [$default_cn]: " CERT_COMMON_NAME |
|
fi |
|
CERT_COMMON_NAME=${CERT_COMMON_NAME:-$default_cn} |
|
|
|
read -p "Email Address [$default_email]: " CERT_EMAIL |
|
CERT_EMAIL=${CERT_EMAIL:-$default_email} |
|
|
|
# Additional fields for server certificates |
|
if [[ "$cert_type" == "server" ]]; then |
|
if [[ -z "$CERT_DOMAINS" ]]; then |
|
read -p "Additional domain names (comma-separated, optional): " CERT_DOMAINS |
|
fi |
|
|
|
if [[ -z "$CERT_IP_ADDRESSES" ]]; then |
|
read -p "IP addresses (comma-separated, optional): " CERT_IP_ADDRESSES |
|
fi |
|
fi |
|
|
|
echo "" |
|
echo "${cert_type^^} Certificate Details Summary:" |
|
echo "$(printf '=%.0s' $(seq 1 $((${#cert_type} + 30))))" |
|
echo "Country: $CERT_COUNTRY" |
|
echo "State: $CERT_STATE" |
|
echo "City: $CERT_CITY" |
|
echo "Organization: $CERT_ORG" |
|
echo "Organizational Unit: $CERT_ORG_UNIT" |
|
echo "Common Name: $CERT_COMMON_NAME" |
|
echo "Email: $CERT_EMAIL" |
|
|
|
if [[ "$cert_type" == "server" ]]; then |
|
echo "Additional Domains: ${CERT_DOMAINS:-none}" |
|
echo "IP Addresses: ${CERT_IP_ADDRESSES:-none}" |
|
fi |
|
|
|
echo "Key Size: $KEY_SIZE bits" |
|
if [[ "$cert_type" == "ca" ]]; then |
|
echo "Validity: $CA_VALIDITY_DAYS days" |
|
else |
|
echo "Validity: $SERVER_VALIDITY_DAYS days" |
|
fi |
|
echo "" |
|
|
|
read -p "Proceed with these details? [Y/n]: " CONFIRM |
|
if [[ $CONFIRM =~ ^[Nn] ]]; then |
|
echo "${cert_type^} certificate creation cancelled" |
|
exit 0 |
|
fi |
|
} |
|
|
|
# Function to set default certificate details |
|
set_default_certificate_details() { |
|
local cert_type="$1" # "ca" or "server" |
|
local common_name_default="$2" |
|
|
|
if [[ "$cert_type" == "ca" ]]; then |
|
CERT_COUNTRY="$DEFAULT_CA_COUNTRY" |
|
CERT_STATE="$DEFAULT_CA_STATE" |
|
CERT_CITY="$DEFAULT_CA_CITY" |
|
CERT_ORG="$DEFAULT_CA_ORG" |
|
CERT_ORG_UNIT="$DEFAULT_CA_ORG_UNIT" |
|
CERT_COMMON_NAME="${common_name_default:-${DEFAULT_CA_ORG} Root CA}" |
|
CERT_EMAIL="$DEFAULT_CA_EMAIL" |
|
else |
|
CERT_COUNTRY="$DEFAULT_SERVER_COUNTRY" |
|
CERT_STATE="$DEFAULT_SERVER_STATE" |
|
CERT_CITY="$DEFAULT_SERVER_CITY" |
|
CERT_ORG="$DEFAULT_SERVER_ORG" |
|
CERT_ORG_UNIT="$DEFAULT_SERVER_ORG_UNIT" |
|
CERT_COMMON_NAME="${common_name_default:-localhost}" |
|
CERT_EMAIL="$DEFAULT_SERVER_EMAIL" |
|
fi |
|
} |
|
|
|
# Function to construct certificate subject |
|
get_certificate_subject() { |
|
echo "/C=${CERT_COUNTRY}/ST=${CERT_STATE}/L=${CERT_CITY}/O=${CERT_ORG}/OU=${CERT_ORG_UNIT}/CN=${CERT_COMMON_NAME}/emailAddress=${CERT_EMAIL}" |
|
} |
|
|
|
# Function to get certificate paths (automatically adds .crt extension) |
|
get_certificate_paths() { |
|
local cert_name="$1" |
|
local folder_type="$2" # "ca" or "server" |
|
|
|
# Remove any existing extension and add .crt |
|
local base_name="${cert_name%.*}" |
|
local cert_filename="${base_name}.crt" |
|
local key_filename="${base_name}.key" |
|
|
|
if [[ "$folder_type" == "ca" ]]; then |
|
local folder="$CA_FOLDER" |
|
else |
|
local folder="$SERVER_FOLDER" |
|
fi |
|
|
|
local cert_dir="${GLOBAL_CERT_PATH}/${folder}" |
|
local cert_path="${cert_dir}/${cert_filename}" |
|
local key_path="${cert_dir}/${key_filename}" |
|
|
|
echo "$cert_dir|$cert_path|$key_path|$cert_filename|$key_filename" |
|
} |
|
|
|
# Function to check if certificate should be created (exists check) |
|
should_create_certificate() { |
|
local cert_path="$1" |
|
local force_overwrite="$2" |
|
local cert_type="$3" |
|
|
|
if [[ -f "$cert_path" ]] && [[ "$force_overwrite" != "true" ]]; then |
|
log_success "${cert_type^} certificate already exists: $cert_path" |
|
echo "" |
|
echo "Certificate information:" |
|
show_certificate_info "$cert_path" |
|
echo "" |
|
echo "Use --force to overwrite existing certificate" |
|
return 1 # Don't create |
|
fi |
|
|
|
return 0 # Should create |
|
} |
|
|
|
# Function to parse common command line arguments |
|
parse_common_args() { |
|
FORCE_OVERWRITE=false |
|
INTERACTIVE_MODE=false |
|
|
|
local remaining_args=() |
|
|
|
while [[ $# -gt 0 ]]; do |
|
case $1 in |
|
-f|--force) |
|
FORCE_OVERWRITE=true |
|
shift |
|
;; |
|
-i|--interactive) |
|
INTERACTIVE_MODE=true |
|
shift |
|
;; |
|
-h|--help) |
|
return 2 # Signal to show usage |
|
;; |
|
*) |
|
remaining_args+=("$1") |
|
shift |
|
;; |
|
esac |
|
done |
|
|
|
# Return remaining args as separate echo statements |
|
for arg in "${remaining_args[@]}"; do |
|
echo "$arg" |
|
done |
|
return 0 |
|
} |
|
|
|
# Function to display script header |
|
show_script_header() { |
|
local script_name="$1" |
|
local description="$2" |
|
|
|
echo "$script_name" |
|
echo "$(printf '=%.0s' $(seq 1 ${#script_name}))" |
|
if [[ -n "$description" ]]; then |
|
echo "$description" |
|
echo "" |
|
fi |
|
} |