How-To : Reporting & Tracking internal certificate utilization

Automated SSL Certificate eeping track of SSL/TLS certificates across an organization's infrastructure is crucial. Expired or misconfigured certificates can lead to service disruptions, security vulnerabilities, and compliance issues. This post introduces a comprehensive bash script that automates the discovery and auditing of SSL certificates across networks and then, in the second part, crates a very easy to use dashboard to show you this data.

Let’s first begin with the NMAP scan, I have chosen to use Kali For this section of the script because I find it’s more accurate and reliable, The different sections will be divided by magenta headers.


Why NMAP?

That is a very good question, and the answer for that is quite simple, If you query the certificate server, you will be able to get information like certificate, template, and common names, subject, alternate nameAnd the person that requested it - But that does not help you solve the conundrum as to where the certificate is used.

The local certificate commands will tell you a certificate has been issued and you can gauge certain aspects of the certificate details, but what you can’t do is see where it’s actively bound - if the certificate has been issued by the Certificate authority and it’s not actually been bound to any active services then reporting on it seems to be counterproductive.

This is why I always think it’s a lot more productive to do a network scan that actively goes off, looking for live services and reports on what’s actually live on your network, i’ve also found when running this script that it finds other services you didn’t even know what on the server that are not technically using a certificate authority certificates - that means it will also pick up self sign certificates and certificates outside of your internal certificate authority remit.

I then thought about caveat with this approach, would the tool have access to discover the open ports - well, yes if this is a live service that people need to use if the port is shut, then the service is not live by definition, this means you will only discover ports that are available to the general network - which means you will pick up all the critical ports.

Part 1 : SSL Certificate Scanner

The SSL Certificate Scanner is a flexible tool that can:

  • Scan a single IP address
  • Scan an entire network range using CIDR notation
  • Process a list of servers from a text file
  • Discover open ports on target hosts
  • Identify SSL/TLS certificates on any port
  • Extract detailed certificate information
  • Generate a comprehensive CSV report

Flexible Input Options

The script accepts three different input methods:

# Scan a single IP address
./scan_certificates.sh 192.168.1.100

# Scan a network range
./scan_certificates.sh 10.0.0.0/24

# Use a server list file
./scan_certificates.sh -f servers.txt

Intelligent Certificate Discovery

The script doesn't just check standard HTTPS ports (443). It:

  1. Scans all ports on target hosts
  2. Identifies services running SSL/TLS
  3. Extracts certificates from any SSL-enabled port

Comprehensive Data Collection

For each discovered certificate, the script collects:

  • Server hostname/IP
  • Port number
  • Protocol (HTTP/HTTPS)
  • Server signature
  • Certificate Common Name (CN)
  • Subject Alternative Names (SANs)
  • Expiration date

Next, let’s dive right into how the script works with examples of code snippet to illustrate the point.

Target Discovery

When given a network range, the script first discovers live hosts:

# Discover live hosts in the network range
echo "Discovering live hosts in $NETWORK_RANGE ..."
SERVER_LIST=$(nmap -sn "$NETWORK_RANGE" | grep "Nmap scan report" | awk '{print $5}')

This uses nmap's ping scan (-sn) to quickly identify responsive hosts before attempting more intensive port scanning.

Port Scanning

For each discovered host, the script performs a comprehensive port scan:

# Run nmap to discover open ports
echo "Finding open ports on $SERVER..."
OPEN_PORTS=$(nmap -p- --min-rate=1000 -T4 --max-rtt-timeout=10s 
--initial-rtt-timeout=10s --min-rtt-timeout=10s "$SERVER" | grep ^[0-9] 
| grep "open" | cut -d '/' -f 1)

The scan parameters are optimized for speed while maintaining reliability:

  • -p-: Scan all 65,535 ports
  • --min-rate=1000: Send at least 1000 packets per second
  • -T4: Aggressive timing template
  • RTT timeouts: Prevent hanging on unresponsive hosts

Service Detection

The script intelligently determines which protocol to use for each port:

# Determine protocol based on port number
case $PORT in
    80|8080|8000)
        PROTOCOL="http"
        ;;
    443|8443)
        PROTOCOL="https"
        ;;
    *)
        # Try to get service info
        SERVICE=$(nmap -p $PORT -sV "$SERVER" | grep "^$PORT" | awk '{print $4}')
        if [[ "$SERVICE" == *"ssl"* || "$SERVICE" == *"tls"* || "$SERVICE" == *"https"* ]]; 
        then
            PROTOCOL="https"
        elif [[ "$SERVICE" == *"http"* ]]; then
            PROTOCOL="http"
        else
            PROTOCOL="unknown"
        fi
        ;;
esac

Certificate Extraction

For each port potentially running SSL/TLS, the script attempts to extract certificate information:

# Check for SSL certificate
CERT_OUTPUT=$(timeout 10 openssl s_client -connect ${SERVER}:${PORT} -servername ${SERVER} 
</dev/null 2>/dev/null)

# Process certificate if found
if echo "$CERT_OUTPUT" | grep -q "BEGIN CERTIFICATE"; then
    # Extract certificate info
    CERT_INFO=$(echo "$CERT_OUTPUT" | openssl x509 -noout -subject -ext 
    subjectAltName -enddate 2>/dev/null)
    
    # Extract CN
    CN=$(echo "$CERT_INFO" | grep "subject" | sed -n 's/.*CN = \([^,]*\).*/\1/p')
    
    # Extract SAN
    SAN=$(echo "$CERT_INFO" | grep -A1 "X509v3 Subject Alternative Name" | tail -n1 
    | sed 's/DNS://g; s/, /|/g' | tr -d ' ')
    
    # Extract expiry date
    EXPIRY=$(echo "$CERT_INFO" | grep "notAfter=" | sed 's/notAfter=//g')
fi

Server Signature Collection

The script also collects server signatures when available:

# Get server signature for HTTP/HTTPS
if [[ "$PROTOCOL" == "http" || "$PROTOCOL" == "https" ]]; then
    SERVER_SIG=$(timeout 10 curl -s -I -m 10 ${PROTOCOL}://${SERVER}:${PORT} 2>/dev/null
     | grep -i "Server:" | sed 's/Server: //i' | tr -d '\r')
    if [ -z "$SERVER_SIG" ]; then
        SERVER_SIGNATURE="Unknown"
    else
        SERVER_SIGNATURE="$SERVER_SIG"
    fi
else
    SERVER_SIGNATURE="N/A"
fi

Output Format

The script generates a CSV file with the following columns:

  • Hostname
  • Port
  • Protocol
  • Server_Signature
  • Certificate_CN
  • Certificate_SAN
  • Expiry_Date

Excluded Ports

The script supports port exclusion via an excluded_ports.txt file:

# Load excluded ports from file if it exists
if [ -f "$EXCLUDED_PORTS_FILE" ]; then
    EXCLUDED_PORTS=$(cat "$EXCLUDED_PORTS_FILE" | tr '\n' ' ')
    echo "Loaded excluded ports from ${EXCLUDED_PORTS_FILE}: ${EXCLUDED_PORTS}"
else
    echo "Using default excluded ports: ${EXCLUDED_PORTS}"
fi

Timeout Settings

All network operations have timeouts to prevent the script from hanging:

  • OpenSSL connections: 10 seconds
  • Curl requests: 10 seconds
  • Nmap scans: Various RTT timeouts
Scripts: ssl-nmap-command.sh
#!/bin/bash

# Script to scan for SSL/TLS certificates with flexible input options:
# - Single IP address
# - Network range in CIDR notation
# - List of servers from servers.txt file
# Outputs a combined CSV file with all results
# Supports excluded ports via excluded_ports.txt

# Help function
show_help() {
    echo "SSL Certificate Scanner"
    echo ""
    echo "Usage:"
    echo "  $0                     # Scan servers from servers.txt file"
    echo "  $0 <ip_address>        # Scan a single IP address"
    echo "  $0 <network_range>     # Scan a network range (CIDR notation)"
    echo "  $0 -f <file>           # Scan servers from specified file"
    echo ""
    echo "Examples:"
    echo "  $0 192.168.1.1"
    echo "  $0 10.0.0.0/24"
    echo "  $0 -f my_servers.txt"
    echo ""
    exit 0
}

# Check for help option
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
    show_help
fi

# Locate script directory and required files
SCRIPT_DIR="$(dirname "$0")"
EXCLUDED_PORTS_FILE="${SCRIPT_DIR}/excluded_ports.txt"

# Set up excluded ports list
EXCLUDED_PORTS="3389 2381"  # Default excluded ports

# Load excluded ports from file if it exists
if [ -f "$EXCLUDED_PORTS_FILE" ]; then
    EXCLUDED_PORTS=$(cat "$EXCLUDED_PORTS_FILE" | tr '\n' ' ')
    echo "Loaded excluded ports from ${EXCLUDED_PORTS_FILE}: ${EXCLUDED_PORTS}"
else
    echo "Using default excluded ports: ${EXCLUDED_PORTS}"
    echo "To customize excluded ports, create a file named excluded_ports.txt"
    echo "in the same directory as this script with one port per line."
fi

# Determine scan mode and prepare server list
SCAN_MODE=""
SERVER_LIST=""
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# Process arguments
if [ $# -eq 0 ]; then
    # No arguments, use servers.txt
    SCAN_MODE="file"
    SERVERS_FILE="${SCRIPT_DIR}/servers.txt"
    if [ ! -f "$SERVERS_FILE" ]; then
        echo "Error: servers.txt not found in ${SCRIPT_DIR}"
        echo "Please create a servers.txt file or specify an IP/network range"
        show_help
    fi
    SERVER_LIST=$(cat "$SERVERS_FILE" | grep -v "^#" | sed '/^$/d')
    CSV_FILE="certificate_scan_file_${TIMESTAMP}.csv"
    echo "Using servers from servers.txt"
elif [ "$1" = "-f" ] && [ -n "$2" ]; then
    # Custom file specified
    SCAN_MODE="file"
    SERVERS_FILE="$2"
    if [ ! -f "$SERVERS_FILE" ]; then
        echo "Error: File '$SERVERS_FILE' not found"
        exit 1
    fi
    SERVER_LIST=$(cat "$SERVERS_FILE" | grep -v "^#" | sed '/^$/d')
    CSV_FILE="certificate_scan_file_${TIMESTAMP}.csv"
    echo "Using servers from $SERVERS_FILE"
elif [[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$ ]]; then
    # Network range in CIDR notation
    SCAN_MODE="network"
    NETWORK_RANGE="$1"
    CSV_FILE="certificate_scan_network_${TIMESTAMP}.csv"
    echo "Scanning network range: $NETWORK_RANGE"
    echo "Discovering live hosts..."
    SERVER_LIST=$(nmap -sn "$NETWORK_RANGE" | grep "Nmap scan report" | awk '{print $5}')
    
    if [ -z "$SERVER_LIST" ]; then
        echo "No live hosts found in $NETWORK_RANGE"
        exit 0
    fi
    echo "Found $(echo "$SERVER_LIST" | wc -l) live hosts"
elif [[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    # Single IP address
    SCAN_MODE="single"
    SERVER_LIST="$1"
    CSV_FILE="certificate_scan_ip_${TIMESTAMP}.csv"
    echo "Scanning single IP: $1"
else
    echo "Error: Invalid argument '$1'"
    show_help
fi

# Create CSV file with header
echo "Hostname,Port,Protocol,Server_Signature,Certificate_CN,Certificate_SAN,Expiry_Date" 
> "$CSV_FILE"

echo "Starting certificate scan"
echo "========================================================"

# Process each server in the list
echo "$SERVER_LIST" | while IFS= read -r SERVER || [ -n "$SERVER" ]; do
    # Skip empty lines
    if [[ -z "$SERVER" ]]; then
        continue
    fi
    
    echo ""
    echo "Scanning server: $SERVER"
    echo "----------------------------------------"
    
    # Run nmap to discover open ports
    echo "Finding open ports on $SERVER..."
    OPEN_PORTS=$(nmap -p- --min-rate=1000 -T4 --max-rtt-timeout=10s --initial-rtt-timeout=10s 
    --min-rtt-timeout=10s "$SERVER" | grep ^[0-9] | grep "open" | cut -d '/' -f 1)
    
    # Check if any ports were found
    if [ -z "$OPEN_PORTS" ]; then
        echo "No open ports found on $SERVER"
        continue
    fi
    
    # Scan each open port for certificates
    for PORT in $OPEN_PORTS; do
        # Check if port is in the excluded list
        if echo "$EXCLUDED_PORTS" | grep -w -q "$PORT"; then
            echo "Skipping excluded port $PORT"
            continue
        fi
        
        # Determine protocol based on port number
        case $PORT in
            80|8080|8000)
                PROTOCOL="http"
                ;;
            443|8443)
                PROTOCOL="https"
                ;;
            *)
                # Try to get service info
                SERVICE=$(nmap -p $PORT -sV "$SERVER" | grep "^$PORT" | awk '{print $4}')
                if [[ "$SERVICE" == *"ssl"* || "$SERVICE" == *"tls"* 
                || "$SERVICE" == *"https"* ]]; then
                    PROTOCOL="https"
                elif [[ "$SERVICE" == *"http"* ]]; then
                    PROTOCOL="http"
                else
                    PROTOCOL="unknown"
                fi
                ;;
        esac
        
        echo "Checking port $PORT ($PROTOCOL)..."
        
        # Get server signature for HTTP/HTTPS
        if [[ "$PROTOCOL" == "http" || "$PROTOCOL" == "https" ]]; then
            SERVER_SIG=$(timeout 10 curl -s -I -m 10 ${PROTOCOL}://${SERVER}:${PORT} 
            2>/dev/null | grep -i "Server:" | sed 's/Server: //i' | tr -d '\r')
            if [ -z "$SERVER_SIG" ]; then
                SERVER_SIGNATURE="Unknown"
            else
                SERVER_SIGNATURE="$SERVER_SIG"
            fi
        else
            SERVER_SIGNATURE="N/A"
        fi
        
        # Check for SSL certificate
        CERT_OUTPUT=$(timeout 10 openssl s_client -connect ${SERVER}:${PORT} 
        -servername ${SERVER} </dev/null 2>/dev/null)
        
        # Process certificate if found
        if echo "$CERT_OUTPUT" | grep -q "BEGIN CERTIFICATE"; then
            # Extract certificate info
            CERT_INFO=$(echo "$CERT_OUTPUT" | openssl x509 -noout -subject 
            -ext subjectAltName -enddate 2>/dev/null)
            
            # Extract CN
            CN=$(echo "$CERT_INFO" | grep "subject" | sed -n 's/.*CN = \([^,]*\).*/\1/p')
            if [ -z "$CN" ]; then
                CN="Not found"
            fi
            
            # Extract SAN
            SAN=$(echo "$CERT_INFO" | grep -A1 "X509v3 Subject Alternative Name" 
            | tail -n1 | sed 's/DNS://g; s/, /|/g' | tr -d ' ')
            if [ -z "$SAN" ]; then
                SAN="Not found"
            fi
            
            # Extract expiry date
            EXPIRY=$(echo "$CERT_INFO" | grep "notAfter=" | sed 's/notAfter=//g')
            if [ -z "$EXPIRY" ]; then
                EXPIRY="Not found"
            fi
            
            # Adjust protocol if needed
            if [ "$PROTOCOL" = "http" ] || [ "$PROTOCOL" = "unknown" ]; then
                PROTOCOL="https"
                # Try to get updated server signature
                SERVER_SIG=$(timeout 10 curl -s -I -m 10 https://${SERVER}:${PORT} 
                2>/dev/null | grep -i "Server:" | sed 's/Server: //i' | tr -d '\r')
                if [ ! -z "$SERVER_SIG" ]; then
                    SERVER_SIGNATURE="$SERVER_SIG"
                fi
            fi
            
            # Write directly to the combined CSV file
            echo "$SERVER,$PORT,$PROTOCOL,$SERVER_SIGNATURE,$CN,$SAN,$EXPIRY" 
            >> "$CSV_FILE"
            echo "  - Found certificate on port $PORT (CN: $CN)"
        else
            echo "  - No certificate found on port $PORT"
        fi
    done
    
    # Count certificates found for this server by filtering the combined CSV
    CERT_COUNT=$(grep "^$SERVER," "$CSV_FILE" | wc -l)
    
    if [ $CERT_COUNT -eq 0 ]; then
        echo "No SSL certificates found on $SERVER"
    else
        echo "Found $CERT_COUNT certificates on $SERVER"
    fi
    
    echo ""
done

# Summary
TOTAL_CERTS=$(wc -l < "$CSV_FILE")
TOTAL_CERTS=$((TOTAL_CERTS - 1))  # Subtract header line

echo "========================================================"
echo "Scan completed"
echo "Total certificates found: $TOTAL_CERTS"
echo "Results saved to $CSV_FILE"
echo "========================================================

This is that script running from the "external file" option:


When this scan completed you end up with a CSV file, this CSV will then drive the graphical website generation which is coming up next.

Part 2 : Creating the web dashboard

Leading on from Part 1 you will find managing SSL/TLS certificates across multiple servers can be a challenging task, especially when you need to keep track of expiration dates and ensure everything remains valid. I've developed a PowerShell script that transforms certificate scan data into a beautiful, interactive HTML dashboard that makes certificate monitoring effortless.

The Problem

In today's environment, tracking certificates across numerous servers is crucial. Missing an expiration date can lead to service outages and security vulnerabilities. 

While various tools exist for certificate scanning, presenting this data in a meaningful, actionable format is often the missing piece.

The Solution

I created a PowerShell script that:

  • Automatically finds the latest certificate scan CSV file in the current directory
  • Processes the data, handling even malformed CSV files gracefully
  • Generates a clean, professional HTML dashboard
  • Provides interactive filtering and search capabilities

Smart Data Processing

The script handles real-world CSV files that might have:

  • Records split across multiple lines
  • Inconsistent formatting
  • Special characters in certificate SANs

It intelligently parses these files and reconstructs the data properly before processing.

Interactive Health Cards

At the top of the dashboard, you'll find clickable health cards showing:

  • Total Servers: Complete count of all monitored servers
  • Valid: Certificates that are current and valid
  • Nearly Expiring: Certificates expiring within the next 30 days
  • Expired: Certificates that have already expired

Clicking any card instantly filters the table to show only those results.

Real-Time Search

Below the health cards, there's a search box that enables instant filtering of servers. You can search by hostname, port, protocol, or any other field - the results update as you type.

Intelligent Status Detection

The script automatically categorizes certificates:

  • Expired certificates are flagged in red
  • Certificates expiring within 30 days are marked in yellow
  • Valid certificates appear in green

Clean Data Presentation

I've ensured the dashboard only displays relevant information. If certain data isn't available across all records (like server signatures), the script filters out incomplete entries to maintain a clean presentation.

How to Use

  1. Place the script in a directory containing your certificate scan CSV files
  2. Run the script - it automatically finds the latest scan file
  3. Open the generated HTML file in any browser
  4. Use the health cards to filter by status
  5. Search for specific servers using the search box

Visual Results

The outputted report will then look like this:

Previous Post Next Post

نموذج الاتصال