Created
December 3, 2025 12:58
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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