Skip to content

Instantly share code, notes, and snippets.

@timhodson
Created December 3, 2025 12:58
Show Gist options
  • Select an option

  • Save timhodson/a4acee9965926c2338258448d7fbe558 to your computer and use it in GitHub Desktop.

Select an option

Save timhodson/a4acee9965926c2338258448d7fbe558 to your computer and use it in GitHub Desktop.
Find and print information about each of the certificates found in a SAML federation metadata file hosted online
#!/bin/bash
# Script to check all certificates in SAML metadata
# Prints expiry, use attribute, and fingerprint for each certificate found
read -d '' HELP <<- PLEH
Usage: ./check-saml-certs <metadataURL>
Fetches SAML metadata from the given URL and reports:
- Entity ID
- Total certificates found
- For each certificate:
* Use attribute (signing/encryption/unknown)
* SHA-512 Fingerprint
* Expiry date
* Days remaining until expiry
Example:
./check-saml-certs https://example.edu/idp/metadata
PLEH
METADATA_URL=$1
if [[ -z ${METADATA_URL} ]]; then
echo "Error: Metadata URL is required"
echo "$HELP"
exit 1
fi
echo -e "==== SAML Metadata Certificate Check ===="
echo -e "- Metadata URL\t: ${METADATA_URL}\n"
# Fetch metadata
METADATA_XML=$(curl -s -A "Talis Metadata Check" ${METADATA_URL})
if [[ -z "${METADATA_XML}" ]]; then
echo "Error: Failed to fetch metadata from ${METADATA_URL}"
exit 1
fi
# Extract entity ID
ENTITY_ID=$(xpath -e '//*[local-name()="EntityDescriptor"]/@entityID' <(echo ${METADATA_XML}) 2> /dev/null | cut -d '=' -f 2)
# Find all X509Certificate elements (compatible with older bash)
CERTS=()
while IFS= read -r line; do
[[ -n "$line" ]] && CERTS+=("$line")
done < <(xpath -e '//*[local-name()="X509Certificate"]/text()' <(echo ${METADATA_XML}) 2> /dev/null | sed -E 's/[[:space:]]+//g' | sed '/^$/d')
if [[ ${#CERTS[@]} -eq 0 ]]; then
echo "No X509Certificate elements found in metadata"
exit 1
fi
echo -e "- Entity ID\t: ${ENTITY_ID}"
echo -e "- Certificates\t: ${#CERTS[@]} found\n"
# Extract KeyDescriptor use attributes (best effort alignment with cert order)
USES=()
while IFS= read -r line; do
[[ -n "$line" ]] && USES+=("$line")
done < <(xpath -e '//*[local-name()="X509Certificate"]/ancestor::*[local-name()="KeyDescriptor"]/attribute::use' <(echo ${METADATA_XML}) 2> /dev/null | sed -E 's/^[^=]*=//; s/"//g')
# Build a simple index to track certificate locations
# We'll identify whether each cert is in a KeyDescriptor or Signature element
PATHS=()
# Check KeyDescriptor certificates
while IFS= read -r use_attr; do
if [[ -n "$use_attr" ]]; then
PATHS+=("KeyDescriptor[@use='${use_attr}']/KeyInfo/X509Data/X509Certificate")
else
PATHS+=("KeyDescriptor/KeyInfo/X509Data/X509Certificate")
fi
done < <(xpath -e '//*[local-name()="KeyDescriptor"]//*[local-name()="X509Certificate"]/ancestor::*[local-name()="KeyDescriptor"]/@use' <(echo ${METADATA_XML}) 2> /dev/null | sed -E 's/^[^=]*=//; s/"//g')
# Check Signature certificates (these won't have KeyDescriptor)
sig_count=$(xpath -e 'count(//*[local-name()="Signature"]//*[local-name()="X509Certificate"])' <(echo ${METADATA_XML}) 2> /dev/null)
if [[ -n "$sig_count" ]] && [[ "$sig_count" != "0" ]]; then
for ((i=0; i<sig_count; i++)); do
PATHS+=("Signature/KeyInfo/X509Data/X509Certificate")
done
fi
# If we still don't have enough paths, fill with generic ones
while [[ ${#PATHS[@]} -lt ${#CERTS[@]} ]]; do
PATHS+=("X509Certificate[position()=$((${#PATHS[@]}+1))]")
done
# Process each certificate
idx=0
for CERT in "${CERTS[@]}"; do
# Calculate SHA-512 fingerprint
FINGERPRINT=$(echo -n "$CERT" | gsha512sum | tr "[:lower:]" "[:upper:]" | cut -f 1 -d " ")
# Build PEM format for openssl
PEM_CERT=$(echo -e "-----BEGIN CERTIFICATE-----\n${CERT}\n-----END CERTIFICATE-----")
# Extract expiry date
ENDDATE_LINE=$(openssl x509 -enddate -noout 2>/dev/null <<< "${PEM_CERT}")
ENDDATE=${ENDDATE_LINE#notAfter=}
# Calculate days remaining
REMAIN_DAYS=""
if [[ -n "${ENDDATE}" ]]; then
END_EPOCH=$(date -j -f "%b %e %H:%M:%S %Y %Z" "${ENDDATE}" +%s 2>/dev/null)
NOW_EPOCH=$(date +%s)
if [[ -n "${END_EPOCH}" ]]; then
REMAIN_DAYS=$(( (END_EPOCH - NOW_EPOCH) / 86400 ))
fi
fi
# Get use attribute (signing/encryption/unknown)
USE_LABEL="${USES[$idx]}"
[[ -z "${USE_LABEL}" ]] && USE_LABEL="unknown"
# Get path location
PATH_LABEL="${PATHS[$idx]}"
[[ -z "${PATH_LABEL}" ]] && PATH_LABEL="X509Certificate[$((idx+1))]"
# Print certificate details
echo -e "==================== Certificate #$((idx+1)) ===================="
echo -e "Location:\t${PATH_LABEL}"
echo -e "Use:\t\t${USE_LABEL}"
echo -e "Fingerprint:\t${FINGERPRINT}"
if [[ -n "${ENDDATE}" ]]; then
echo -e "Expiry:\t\t${ENDDATE}"
if [[ -n "${REMAIN_DAYS}" ]]; then
if [[ ${REMAIN_DAYS} -lt 0 ]]; then
echo -e "Status:\t\tEXPIRED (${REMAIN_DAYS#-} days ago)"
elif [[ ${REMAIN_DAYS} -lt 30 ]]; then
echo -e "Days Left:\t${REMAIN_DAYS} ⚠️ EXPIRING SOON"
else
echo -e "Days Left:\t${REMAIN_DAYS}"
fi
fi
else
echo -e "Expiry:\t\t(unavailable)"
fi
echo ""
idx=$((idx+1))
done
echo "==== Check Complete ===="
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment