The below Script should be saved as a .rego file and uploaded to the


  1. Open the Aqua CSP platform and navigate to Workload Protection -> Policies -> Assurance Policies -> Custom Compliance Checks
  2. Select "Create New". Leave all fields blank and click "upload"
  3. Upload certificate, fields will auto-populate, hit Enter
  4. Click up one level to Assurance Policies, open an Image Assurance Policy, select the Custom compliance Checks control, and add the check in question. note: please ensure the Application scope includes the script in question
  5. Rescan the image in question, then navigate to Images & Functions -> Images -> expand the repository, click the image, and review the Scan Results at bottom!


Raw rego scripts provided below, as well as in downloadable format for convenience!


Image Assurance Detection
Detects according to filename and path, which may create false positives for secure emudhra certificates.
This is due to technology limitations that limit rego to the filenames, and not the contents themselves, so we do not anticipate a SHA-based solution will be made available for rego
Note: please save as a .rego file

package custom.aqua.selectors.emcert_paths

import future.keywords.if
import future.keywords.in

# METADATA
# title: eMudhra path check
# description: Enforce on certificate file paths containing eMudhra or emSign, without claiming certificate content verification
# custom:
#   id: AQUA-CERT-PATH-001
#   severity: MEDIUM
#   input:
#     selectors:
#       - selectorId: cert_files
#         type: file
#         name: '.*[.](crt|pem|cer|der|CRT|PEM|CER|DER)$'
#         path: ["/*"]

# Filename/path indicators only; does not inspect Subject/Issuer or certificates
# inside a bundle. Case-insensitive matches cover eMudhra, emSign and names such
# as emSign_ECC_Root_CA. Directory names also count, including emudhra-test.
# Renamed files and generic bundle names can evade these indicators.
# A layer digest is not a certificate fingerprint.
# Exactly one deny; no result is emitted when there are no matching paths.

path_findings[finding] {
    resource := input.custom_resources[_]
    detection := resource.detections[_]
    detection.selector_id == "cert_files"
    candidate_paths := {
        object.get(resource, "path", ""),
        object.get(detection, "path", ""),
    }
    path := candidate_paths[_]
    is_string(path)
    indicators := {"emudhra", "emsign"}
    indicator := indicators[_]
    contains(lower(path), indicator)
    finding := {
        "path": path,
        "matched_indicator": indicator,
        "layer_digest": object.get(resource, "layer_digest", ""),
        "evidence_type": "filename_or_path_match",
    }
}

deny[res] {
    findings := sort(path_findings)
    count(findings) > 0
    matched_paths := {finding.path | finding := findings[_]}
    lines := [
        sprintf("MATCH: %s | indicator=%s | layer=%s", [finding.path, finding.matched_indicator, finding.layer_digest]) |
        finding := findings[_]
    ]
    metadata := object.get(input, "metadata", {})
    res := {
        "msg": "eMudhra or emSign certificate path found",
        "severity": "medium",
        "resolution": sprintf(
            "ENFORCED: %d distinct certificate file paths matched eMudhra/emSign text (%d path/indicator/layer findings). Image tags: %v. Image ID: %s. MATCHED PATH EVIDENCE: %s. Review or remove these files as required by your policy. This finding is based on filenames/directory paths only; certificate identity, bundle contents, compromise and effective trust were not verified. No matches does not establish absence of affected certificates.",
            [count(matched_paths), count(findings), object.get(metadata, "repo_tags", []), object.get(metadata, "id", ""), concat(" ; ", lines)],
        ),
        "status": "enforce",
    }
}


Container Detection
Find emudhra in all active containers. Searched by SHA to prevent false positives!

Note: please save as .yaml file

---
controls: null
version: 1.0.0
id: emcert-docker
description: Running Docker container certificate evidence, including host, container and image identity;
  shell/OpenSSL only
type: master
groups:
- id: '1'
  description: Running Docker container certificate detection
  checks:
  - id: '1.1'
    description: eMudhra certificate indicators detected in running Docker containers
    audit: |
      # Self-contained Aqua host audit: Docker client + host OpenSSL; no Python.
      # Access must be to the local Docker daemon and the actual host's utilities.
      AQUA_SCAN_HOST=$(hostname -f 2>/dev/null || hostname 2>/dev/null || uname -n 2>/dev/null)
      if [ -z "$AQUA_SCAN_HOST" ]; then echo 'INCOMPLETE: cannot determine hostname'; exit 0; fi
      export AQUA_SCAN_HOST
      printf 'HOST=%s\n' "$AQUA_SCAN_HOST"
      umask 077
      workspace=$(mktemp -d /tmp/aqua-docker-cert-check.XXXXXXXX) || { echo 'INCOMPLETE: cannot create workspace'; exit 0; }
      trap 'rm -rf "$workspace"' 0
      trap 'exit 1' 1 2 15
      cat > "$workspace/scanner.sh" <<'AQUA_DOCKER_SCANNER'
      #!/bin/sh
      # Read-only inspection of copied files; no commands are run inside containers.
      # Dependencies are installed on the Docker client host only.
      # Usage: sudo sh scan-running-container-certs.sh [all|affected|dn|roots] [container]
      # Without a container argument, inspect the containers running at list time.
      # Exit 0: no selected-mode matches/errors in inspected files; 1: matches;
      # 2: incomplete coverage or an execution error. None of these proves TLS trust.
      set -u
      umask 077
      mode=${1:-all}
      case "$mode" in all) modes='dn affected roots' ;; dn|affected|roots) modes=$mode ;; *) echo 'Usage: sh scan-running-container-certs.sh [all|affected|dn|roots] [container]' >&2; exit 2 ;; esac
      for tool in docker openssl find awk grep sed tr wc mktemp cat rm mkdir mv; do
        command -v "$tool" >/dev/null 2>&1 || { echo "ERROR: missing host command: $tool" >&2; exit 2; }
      done
      root=$(mktemp -d /tmp/aqua-container-certs.XXXXXXXX) || exit 2
      trap 'rm -rf "$root"' 0
      trap 'exit 2' 1 2 15
      if [ "$#" -ge 2 ]; then
        if ! docker inspect --type container --format '{{.Id}}' "$2" > "$root/ids"; then exit 2; fi
      else
        if ! docker ps --no-trunc --quiet > "$root/ids"; then exit 2; fi
      fi
      if [ ! -s "$root/ids" ]; then echo 'No running containers found.'; exit 0; fi
      cat > "$root/audit.sh" <<'AQUA_AUDIT'
      # All detection logic and indicators are embedded in this YAML audit.
      # No Python, installed helper, JSON source file or network access is required.
      # Scan results describe discovered certificate files, not effective application trust.
      # Supported: PEM/DER, PEM bundles and selected OpenSSL hash-named links.
      # Excluded: opaque JKS/PKCS12/NSS stores, unknown filenames and inaccessible paths.
      # OpenSSL validates X.509 parsing and calculates identities; it does not prove
      # effective trust, certificate revocation enforcement or TLS exploitability.
      for tool in openssl find awk grep sed tr wc mktemp cat rm; do
        if ! command -v "$tool" >/dev/null 2>&1; then
          echo "FAIL: missing required OS command: $tool"
          exit 0
        fi
      done
      work=$(mktemp -d /tmp/aqua-cert-audit.XXXXXXXX) || { echo "FAIL: cannot create audit workspace"; exit 0; }
      trap 'rm -rf "$work"' 0
      trap 'exit 1' 1 2 15
      : > "$work/errors"
      cat "$CONTAINER_COPY_ERRORS" >> "$work/errors"
      : > "$work/files"
      : > "$work/parsed"
      : > "$work/matches"
      : > "$work/evidence"
      : > "$work/skipped"
      cat > "$work/indicators" <<'AQUA_INDICATORS'
      CERT|42C4ACCD12C4F59597925E1A08A7A90102608543809308D50EC427E118BA1493|EM DV TLS CA - G3B
      CERT|45A883F349AEDFD5F3A649B5498FFB121712BA8B448F3F2A91DBC5A766FCDDBE|EM DV TLS CA - G3B
      CERT|98B896625C0C230ADEEC3EB2CF6AAD5001ECF9156A68644E82374CE5833BC25B|EM OV TLS CA - G3B
      CERT|5F1DED4DD590AF74631290CF073B122A2AAC6A1FA479778A0BD43579BD832699|EM OV TLS CA - G3B
      CERT|E7568FE6E12331F4FD7DE61DA4C7559D0F8EB192A218D4741A3BA75184333838|EM EV TLS CA - G3B
      CERT|579480AEA166290BD3C643589E795FC0D0E54E86505D180F1DFF99EFD3199512|EM EV TLS CA - G3B
      CERT|0164D7CEAE5BB909C272AF151FF44B1177AE9B47D9424B94F1D9558C30934D19|EM DV TLS CA - G3C
      CERT|62B7F9868263F72F6911FC78CEAF72C1979158B7368BF648683BB2AA39B02E72|EM DV TLS CA - G3C
      CERT|CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F|EM OV TLS CA - G3C
      CERT|7485B51C0ED192878FA1D2F948FB529AE1842CC5086C4B67F7BE5B2039DE9489|EM OV TLS CA - G3C
      CERT|432EC25F0D28DE9006D97BF752F17BBB2CD41F669933FCB072D9A1148D0261F3|EM EV TLS CA - G3C
      CERT|FD9D5C2DBBD4995AB69B8D50C439D9527B00529199C2FD01B2174EB58F07021F|EM EV TLS CA - G3C
      CERT|9E39DE2881273ACC225A149C72BA3E0F51424963669AB633EE1DC7DF5DB067B4|EM OV CS CA - G3B
      CERT|A496E23AC0CAB8155FC9B117B97EAE368EFCBB9041AEC8804D5A308D2D01C2D8|EM OV CS CA - G3B
      CERT|4B2460A466F95FD40113BCDCCA2CEB29B3B093921DE45474DA7DF48874C630B1|EM OV CS CA - G3C
      CERT|84112972B8E59D14560AE7E7119654474AAD119990CF3722BFC1E3B91615E1FD|EM OV CS CA - G3C
      KEY|EBED6475C2C2D80FCB199F3FE8DD6E6B3CE60B77EB15D4E42C3DEC0C6F5B5105|EM DV TLS CA - G3B
      KEY|43BFFA19D6EF651451F6841E019931E96F6F2EAEFF28AB4B6F1CE4576DE2717B|EM OV TLS CA - G3B
      KEY|E0254C7FCDC265E2F0F8A428DABB75AB4AE039B4872D62F5EE57F0877D5A8A41|EM EV TLS CA - G3B
      KEY|49DD353DC2B919A3770645117D0E60C16115E7D1BC259EEFE734B00EA434FE5F|EM DV TLS CA - G3C
      KEY|63ECEFEE431D49523BC1136C4E72D0206A477086CAF606CD5CF025686C109CF8|EM OV TLS CA - G3C
      KEY|5C0660A2B164910D8FF589C18E14552E9E0EC237DCEFE2CB736D41A4811F81A0|EM EV TLS CA - G3C
      KEY|EC4FFA1AD0AC88081314B7AAFFEF6A91F97CD57291BB5BA686C5415F5EB4F6B6|EM OV CS CA - G3B
      KEY|92C063D67E8F14DB71D288E11266FFDAF69CBF115F7BAB14F3BC2F68D25E0E30|EM OV CS CA - G3C
      ROOT|7DD78D5F4F13459A83DFF9ABBBA62EDBAF6F2D102BF257FD712F4D9F2746ED8D|emSign Root TLS CA - G3
      ROOT|F2EDAAFF3D9B701FB4D14C9F285F8C0BAD90FC3BCC1FA07D47F4718EDABA34A5|emSign Root CS CA - G3
      ROOT|D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4|AAA Certificate Services
      ROOT|86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B|emSign ECC Root CA - G3
      AQUA_INDICATORS
      cat > "$work/scan-files.sh" <<'AQUA_SCANNER'
      work=$1
      mode=$2
      shift 2
      for file do
        display_path=${file#"$CONTAINER_CERT_ROOT"}
        [ -f "$file" ] || continue
        if [ ! -r "$file" ]; then
          printf 'Unreadable file: %s\n' "$file" >> "$work/errors"
          continue
        fi
        printf '1\n' >> "$work/files"
        size=$(wc -c < "$file")
        if [ "$size" -gt 33554432 ]; then
          printf 'File exceeds 32 MiB inspection limit: %s\n' "$file" >> "$work/errors"
          continue
        fi
        parts=$(mktemp -d "$work/parts.XXXXXXXX") || { echo "Cannot create certificate workspace" >> "$work/errors"; continue; }
        # Extract every PEM certificate, including OpenSSL TRUSTED CERTIFICATE blocks.
        if ! awk -v dir="$parts" '
          /^-----BEGIN (TRUSTED )?CERTIFICATE-----\r?$/ {
            if (inside) bad=1
            n++; dest=sprintf("%s/cert-%06d.pem",dir,n); inside=1
          }
          inside { print > dest }
          /^-----END (TRUSTED )?CERTIFICATE-----\r?$/ {
            if (inside) { close(dest); inside=0 } else bad=1
          }
          END { if (inside || bad) exit 2 }
        ' "$file"; then
          printf 'Malformed or unreadable PEM: %s\n' "$file" >> "$work/errors"
        fi
        if [ ! -f "$parts/cert-000001.pem" ]; then
          if grep -q '^-----BEGIN .*-----' "$file"; then
            # Known non-certificate PEM files are outside certificate inventory scope.
            # Malformed CERTIFICATE PEMs must remain coverage errors.
            if grep -Eq '^-----BEGIN (TRUSTED )?CERTIFICATE' "$file"; then
              printf 'No complete certificate block: %s\n' "$file" >> "$work/errors"
            elif grep -Eq '^-----BEGIN (.*PRIVATE KEY|PUBLIC KEY|CERTIFICATE REQUEST|NEW CERTIFICATE REQUEST|X509 CRL|DH PARAMETERS)-----' "$file"; then
              printf 'Non-certificate PEM skipped: %s\n' "$file" >> "$work/skipped"
            else
              printf 'Unsupported PEM content: %s\n' "$file" >> "$work/errors"
            fi
            rm -rf "$parts"
            continue
          fi
          if ! openssl x509 -inform DER -in "$file" -out "$parts/cert-000001.pem" 2>/dev/null; then
            printf 'Cannot parse certificate as PEM or DER: %s\n' "$file" >> "$work/errors"
            rm -rf "$parts"
            continue
          fi
        fi
        index=-1
        for cert in "$parts"/cert-*.pem; do
          index=$((index + 1))
          if ! openssl x509 -in "$cert" -outform DER -out "$parts/certificate.der" 2>/dev/null; then
            printf 'Invalid X.509 certificate: %s index=%s\n' "$file" "$index" >> "$work/errors"
            continue
          fi
          fingerprint=$(openssl dgst -sha256 "$parts/certificate.der" 2>/dev/null | awk '{print toupper($NF)}')
          if ! printf '%s\n' "$fingerprint" | grep -Eq '^[0-9A-F]{64}$'; then
            printf 'Certificate fingerprint calculation failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
            continue
          fi
          printf '1\n' >> "$work/parsed"
          name=
          kind=
          spki=
          case "$mode" in
            dn)
              dn=$(openssl x509 -in "$cert" -noout -subject -issuer -nameopt RFC2253 2>/dev/null)
              if printf '%s\n' "$dn" | grep -Eiq '(emudhra|emsign)'; then
                name='eMudhra/emSign Subject or Issuer'
                kind=DN_MATCH
              fi
              ;;
            affected)
              name=$(awk -F '|' -v h="$fingerprint" '$1=="CERT" && $2==h {print $3;exit}' "$work/indicators")
              if [ -n "$name" ]; then
                kind=AFFECTED_CERT_SHA256
              else
                if ! openssl x509 -in "$cert" -pubkey -noout > "$parts/pubkey.pem" 2>/dev/null; then
                  printf 'Public key extraction failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
                  continue
                fi
                if ! openssl pkey -pubin -in "$parts/pubkey.pem" -outform DER -out "$parts/spki.der" 2>/dev/null; then
                  printf 'SPKI conversion failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
                  continue
                fi
                spki=$(openssl dgst -sha256 "$parts/spki.der" 2>/dev/null | awk '{print toupper($NF)}')
                if ! printf '%s\n' "$spki" | grep -Eq '^[0-9A-F]{64}$'; then
                  printf 'SPKI fingerprint calculation failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
                  continue
                fi
                name=$(awk -F '|' -v h="$spki" '$1=="KEY" && $2==h {print $3;exit}' "$work/indicators")
                [ -z "$name" ] || kind=AFFECTED_SPKI_SHA256
              fi
              ;;
            roots)
              name=$(awk -F '|' -v h="$fingerprint" '$1=="ROOT" && $2==h {print $3;exit}' "$work/indicators")
              [ -z "$name" ] || kind=ASSOCIATED_ROOT
              ;;
          esac
          if [ -n "$kind" ]; then
            location_role=certificate_file
            case "$file" in
              */blocklist/*|*/blacklist/*) location_role=distrust_directory_review ;;
            esac
            dn=$(openssl x509 -in "$cert" -noout -subject -issuer -nameopt RFC2253 2>/dev/null)
            printf '1\n' >> "$work/matches"
            {
              printf '  host=%s\n  container=%s\n  container_id=%s\n  image=%s\n  image_id=%s\n' "$AQUA_SCAN_HOST" "$AQUA_CONTAINER_NAME" "$AQUA_CONTAINER_ID" "$AQUA_IMAGE_NAME" "$AQUA_IMAGE_ID"
              printf 'MATCH type=%s name=%s\n' "$kind" "$name"
              printf '  location_role=%s\n' "$location_role"
              printf '  path=%s\n  certificate_index=%s (zero-based)\n' "$display_path" "$index"
              printf '  certificate_sha256=%s\n' "$fingerprint"
              [ -z "$spki" ] || printf '  spki_sha256=%s\n' "$spki"
              printf '%s\n' "$dn" | sed 's/^/  /'
            } >> "$work/evidence"
          fi
        done
        rm -rf "$parts"
      done
      AQUA_SCANNER
      # Recursively inspect common Linux certificate roots; overlapping subfolders
      # are covered by their parents. Absent OS/application directories are skipped.
      # Java/NSS/PKCS12 stores require separate parsing and are not covered.
      # Readable file symlinks are inspected; directory symlinks are not followed.
      roots_scanned=0
      echo 'SCAN_SCOPE: /etc/ssl /usr/local/share/ca-certificates /usr/share/ca-certificates /etc/pki /usr/share/pki /usr/local/ssl/certs /etc/letsencrypt (recursive)'
      for directory in "$CONTAINER_CERT_ROOT/etc/ssl" "$CONTAINER_CERT_ROOT/usr/local/share/ca-certificates" "$CONTAINER_CERT_ROOT/usr/share/ca-certificates" "$CONTAINER_CERT_ROOT/etc/pki" "$CONTAINER_CERT_ROOT/usr/share/pki" "$CONTAINER_CERT_ROOT/usr/local/ssl/certs" "$CONTAINER_CERT_ROOT/etc/letsencrypt"; do
        if [ ! -d "$directory" ]; then
          echo "SCOPE_NOTE: directory absent: $directory"
          continue
        fi
        roots_scanned=$((roots_scanned + 1))
        if ! find "$directory" \( -type f -o -type l \) \
          \( -iname '*.crt' -o -iname '*.pem' -o -iname '*.cer' -o -iname '*.der' \
             -o -name ca-bundle -o -name ca-certificates \
             -o -name '[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F].[0-9]*' \) \
          -exec sh "$work/scan-files.sh" "$work" "$SCAN_MODE" {} + 2>> "$work/errors"; then
          echo "Certificate-directory traversal or audit execution failed: $directory" >> "$work/errors"
        fi
      done
      files=$(wc -l < "$work/files" | tr -d ' ')
      parsed=$(wc -l < "$work/parsed" | tr -d ' ')
      matches=$(wc -l < "$work/matches" | tr -d ' ')
      skipped=$(wc -l < "$work/skipped" | tr -d ' ')
      printf 'SUMMARY mode=%s directories=%s files=%s parsed_certificate_occurrences=%s matching_occurrences=%s skipped_noncertificate_PEM_files=%s\n' "$SCAN_MODE" "$roots_scanned" "$files" "$parsed" "$matches" "$skipped"
      # Sanitize the reserved success token in untrusted DN/path text, so evidence
      # cannot accidentally satisfy Aqua's output flag test.
      sed 's/AQUA_CERT_CHECK_OK_7C89/[reserved-token]/g' "$work/evidence"
      if [ -s "$work/errors" ]; then
        echo 2 > "$CONTAINER_AUDIT_STATUS"
        echo 'INCOMPLETE: copy/collection/parsing errors; coverage is incomplete'
        sed 's/AQUA_CERT_CHECK_OK_7C89/[reserved-token]/g' "$work/errors"
      elif [ "$parsed" -eq 0 ]; then
        echo 2 > "$CONTAINER_AUDIT_STATUS"
        echo 'INCOMPLETE: no certificates parsed; coverage is unconfirmed'
      elif [ "$matches" -gt 0 ]; then
        echo 1 > "$CONTAINER_AUDIT_STATUS"
        echo 'MATCHES FOUND: matching certificate content; effective trust not assessed'
      else
        echo 0 > "$CONTAINER_AUDIT_STATUS"
        echo 'NO MATCHES IN INSPECTED CERTIFICATE FILES: effective trust not assessed'
      fi
      exit 0

      AQUA_AUDIT
      cat > "$root/resolve-links.sh" <<'AQUA_LINKS'
      stage=$1
      container=$2
      errors=$3
      shift 3
      for link do
        rel=${link#"$stage"}
        temp=$(mktemp -d "$stage/../link.XXXXXXXX") || { echo 'Could not create symlink resolution workspace' >> "$errors"; continue; }
        if docker cp -L "$container:$rel" "$temp/resolved" > /dev/null 2> "$temp/error"; then
          if [ -f "$temp/resolved" ] && [ ! -L "$temp/resolved" ]; then
            rm -- "$link"
            mv -- "$temp/resolved" "$link"
          else
            echo "Unresolved/non-file symlink: container=$container path=$rel (not followed in the host filesystem)" >> "$errors"
            rm -- "$link"
          fi
        else
          echo "Cannot resolve container symlink: container=$container path=$rel" >> "$errors"
          cat "$temp/error" >> "$errors"
          rm -- "$link"
        fi
        rm -rf "$temp"
      done
      AQUA_LINKS
      any_matches=0
      any_errors=0
      while IFS= read -r id; do
        name=$(docker inspect --type container --format '{{.Name}}' "$id" 2>/dev/null) || { echo "ERROR: cannot inspect $id"; any_errors=1; continue; }
        image=$(docker inspect --type container --format '{{.Config.Image}}' "$id" 2>/dev/null) || { echo "ERROR: cannot inspect image for $id"; any_errors=1; continue; }
        image_id=$(docker inspect --type container --format '{{.Image}}' "$id" 2>/dev/null) || { echo "ERROR: cannot inspect image ID for $id"; any_errors=1; continue; }
        export AQUA_CONTAINER_NAME="$name" AQUA_CONTAINER_ID="$id" AQUA_IMAGE_NAME="$image" AQUA_IMAGE_ID="$image_id"
        printf 'HOST=%s CONTAINER=%s CONTAINER_ID=%s IMAGE=%s IMAGE_ID=%s\n' "$AQUA_SCAN_HOST" "$name" "$id" "$image" "$image_id"
        echo 'Source files can change during copying; this is not an atomic snapshot.'
        echo 'Container mounts (only certificate paths within the configured scope are attempted):'
        if ! docker inspect --type container --format '{{range .Mounts}}{{println .Type .Destination}}{{end}}' "$id"; then
          echo 'INCOMPLETE: cannot inspect container mount configuration'
          any_errors=1
        fi
        stage="$root/$id/rootfs"
        mkdir -p "$stage" || { any_errors=1; continue; }
        errors="$root/$id/copy-errors"
        : > "$errors"
        for directory in /etc/ssl /usr/local/share/ca-certificates /usr/share/ca-certificates /etc/pki /usr/share/pki /usr/local/ssl/certs /etc/letsencrypt; do
          target="$stage$directory"
          mkdir -p "$target" || { echo "Cannot create staging directory: $directory" >> "$errors"; continue; }
          if docker cp -L "$id:$directory/." "$target/" > /dev/null 2> "$root/copy-error"; then
            echo "COPIED $directory"
          elif grep -Eiq 'could not find the file|no such file or directory|does not exist' "$root/copy-error"; then
            # A failed recursive copy may have copied part of an existing source.
            # Only treat missing paths as ordinary absence if no entry was copied.
            if [ -n "$(find "$target" -mindepth 1 -print -quit)" ]; then
              echo "Partial copy of $directory; coverage incomplete" >> "$errors"
              cat "$root/copy-error" >> "$errors"
            else
              echo "SCOPE_NOTE: missing or unavailable path: $directory"
            fi
          else
            echo "Copy failed: container=$id path=$directory" >> "$errors"
            cat "$root/copy-error" >> "$errors"
          fi
        done
        # Never interpret a copied absolute symlink against the HOST filesystem.
        # Resolve each copied symlink using Docker's container-side path handling.
        if ! find "$stage" -type l -exec sh "$root/resolve-links.sh" "$stage" "$id" "$errors" {} +; then
          echo 'Symlink traversal/resolution failed' >> "$errors"
        fi
        for selected in $modes; do
          status="$root/$id/status-$selected"
          echo "--- CHECK $selected ---"
          if ! CONTAINER_CERT_ROOT="$stage" CONTAINER_COPY_ERRORS="$errors" SCAN_MODE="$selected" CONTAINER_AUDIT_STATUS="$status" sh "$root/audit.sh"; then
            echo 'INCOMPLETE: audit execution failed'
            any_errors=1
            continue
          fi
          if [ ! -s "$status" ]; then
            echo 'INCOMPLETE: no audit status produced'
            any_errors=1
            continue
          fi
          case "$(cat "$status")" in 0) : ;; 1) any_matches=1 ;; *) any_errors=1 ;; esac
        done
        rm -rf "$root/$id"
      done < "$root/ids"
      if [ "$any_errors" -ne 0 ]; then echo 'OVERALL: incomplete coverage; review errors and any matches above'; exit 2; fi
      if [ "$any_matches" -ne 0 ]; then echo 'OVERALL: selected certificate indicators found'; exit 1; fi
      echo 'OVERALL: no selected indicators in inspected files; application trust not assessed'
      exit 0
      AQUA_DOCKER_SCANNER
      # Capture all output so untrusted certificate names cannot inject the pass flag.
      sh "$workspace/scanner.sh" all > "$workspace/result" 2>&1
      scan_status=$?
      sed 's/AQUA_DOCKER_CERT_OK_91B7/[reserved-token]/g' "$workspace/result"
      case "$scan_status" in
        0) echo 'AQUA_DOCKER_CERT_OK_91B7' ;;
        1) echo 'FAIL: certificate indicators found; review match types and evidence above' ;;
        *) echo 'FAIL: incomplete collection or inspection; review errors and findings above' ;;
      esac
      # Aqua evaluates the output flag, rather than relying on the shell exit code.
      exit 0
    tests:
      test_items:
      - flag: AQUA_DOCKER_CERT_OK_91B7
        set: true
    remediation: Review AFFECTED_CERT_SHA256 and AFFECTED_SPKI_SHA256 matches as known affected certificate/key
      identities. DN_MATCH indicates name association only. ASSOCIATED_ROOT does not establish a compromised
      root key or effective trust. Remove or replace affected material according to your PKI response
      process, rebuild images where applicable, and review application trust. Resolve INCOMPLETE errors
      before accepting coverage.
    scored: true



Host Detection
please save as .yaml file

---
controls: null
version: 2.3.0
id: emcert-host-os
description: Certificate content evidence in common Linux certificate directories; shell/OpenSSL only;
  directory role is reported, effective trust is not inferred
type: master
groups:
- id: '1'
  description: Certificate detection findings and evidence
  checks:
  - id: '1.1'
    description: eMudhra/emSign certificate DN association detected
    audit: |
      # All detection logic and indicators are embedded in this YAML audit.
      # No Python, installed helper, JSON source file or network access is required.
      # Scan results describe discovered certificate files, not effective application trust.
      # Supported: PEM/DER, PEM bundles and selected OpenSSL hash-named links.
      # Excluded: opaque JKS/PKCS12/NSS stores, unknown filenames and inaccessible paths.
      # OpenSSL validates X.509 parsing and calculates identities; it does not prove
      # effective trust, certificate revocation enforcement or TLS exploitability.
      for tool in openssl find awk grep sed tr wc mktemp cat rm; do
        if ! command -v "$tool" >/dev/null 2>&1; then
          echo "FAIL: missing required OS command: $tool"
          exit 0
        fi
      done
      work=$(mktemp -d /tmp/aqua-cert-audit.XXXXXXXX) || { echo "FAIL: cannot create audit workspace"; exit 0; }
      trap 'rm -rf "$work"' 0
      trap 'exit 1' 1 2 15
      : > "$work/errors"
      : > "$work/files"
      : > "$work/parsed"
      : > "$work/matches"
      : > "$work/evidence"
      : > "$work/skipped"
      cat > "$work/indicators" <<'AQUA_INDICATORS'
      CERT|42C4ACCD12C4F59597925E1A08A7A90102608543809308D50EC427E118BA1493|EM DV TLS CA - G3B
      CERT|45A883F349AEDFD5F3A649B5498FFB121712BA8B448F3F2A91DBC5A766FCDDBE|EM DV TLS CA - G3B
      CERT|98B896625C0C230ADEEC3EB2CF6AAD5001ECF9156A68644E82374CE5833BC25B|EM OV TLS CA - G3B
      CERT|5F1DED4DD590AF74631290CF073B122A2AAC6A1FA479778A0BD43579BD832699|EM OV TLS CA - G3B
      CERT|E7568FE6E12331F4FD7DE61DA4C7559D0F8EB192A218D4741A3BA75184333838|EM EV TLS CA - G3B
      CERT|579480AEA166290BD3C643589E795FC0D0E54E86505D180F1DFF99EFD3199512|EM EV TLS CA - G3B
      CERT|0164D7CEAE5BB909C272AF151FF44B1177AE9B47D9424B94F1D9558C30934D19|EM DV TLS CA - G3C
      CERT|62B7F9868263F72F6911FC78CEAF72C1979158B7368BF648683BB2AA39B02E72|EM DV TLS CA - G3C
      CERT|CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F|EM OV TLS CA - G3C
      CERT|7485B51C0ED192878FA1D2F948FB529AE1842CC5086C4B67F7BE5B2039DE9489|EM OV TLS CA - G3C
      CERT|432EC25F0D28DE9006D97BF752F17BBB2CD41F669933FCB072D9A1148D0261F3|EM EV TLS CA - G3C
      CERT|FD9D5C2DBBD4995AB69B8D50C439D9527B00529199C2FD01B2174EB58F07021F|EM EV TLS CA - G3C
      CERT|9E39DE2881273ACC225A149C72BA3E0F51424963669AB633EE1DC7DF5DB067B4|EM OV CS CA - G3B
      CERT|A496E23AC0CAB8155FC9B117B97EAE368EFCBB9041AEC8804D5A308D2D01C2D8|EM OV CS CA - G3B
      CERT|4B2460A466F95FD40113BCDCCA2CEB29B3B093921DE45474DA7DF48874C630B1|EM OV CS CA - G3C
      CERT|84112972B8E59D14560AE7E7119654474AAD119990CF3722BFC1E3B91615E1FD|EM OV CS CA - G3C
      KEY|EBED6475C2C2D80FCB199F3FE8DD6E6B3CE60B77EB15D4E42C3DEC0C6F5B5105|EM DV TLS CA - G3B
      KEY|43BFFA19D6EF651451F6841E019931E96F6F2EAEFF28AB4B6F1CE4576DE2717B|EM OV TLS CA - G3B
      KEY|E0254C7FCDC265E2F0F8A428DABB75AB4AE039B4872D62F5EE57F0877D5A8A41|EM EV TLS CA - G3B
      KEY|49DD353DC2B919A3770645117D0E60C16115E7D1BC259EEFE734B00EA434FE5F|EM DV TLS CA - G3C
      KEY|63ECEFEE431D49523BC1136C4E72D0206A477086CAF606CD5CF025686C109CF8|EM OV TLS CA - G3C
      KEY|5C0660A2B164910D8FF589C18E14552E9E0EC237DCEFE2CB736D41A4811F81A0|EM EV TLS CA - G3C
      KEY|EC4FFA1AD0AC88081314B7AAFFEF6A91F97CD57291BB5BA686C5415F5EB4F6B6|EM OV CS CA - G3B
      KEY|92C063D67E8F14DB71D288E11266FFDAF69CBF115F7BAB14F3BC2F68D25E0E30|EM OV CS CA - G3C
      ROOT|7DD78D5F4F13459A83DFF9ABBBA62EDBAF6F2D102BF257FD712F4D9F2746ED8D|emSign Root TLS CA - G3
      ROOT|F2EDAAFF3D9B701FB4D14C9F285F8C0BAD90FC3BCC1FA07D47F4718EDABA34A5|emSign Root CS CA - G3
      ROOT|D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4|AAA Certificate Services
      ROOT|86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B|emSign ECC Root CA - G3
      AQUA_INDICATORS
      cat > "$work/scan-files.sh" <<'AQUA_SCANNER'
      work=$1
      mode=$2
      shift 2
      for file do
        [ -f "$file" ] || continue
        if [ ! -r "$file" ]; then
          printf 'Unreadable file: %s\n' "$file" >> "$work/errors"
          continue
        fi
        printf '1\n' >> "$work/files"
        size=$(wc -c < "$file")
        if [ "$size" -gt 33554432 ]; then
          printf 'File exceeds 32 MiB inspection limit: %s\n' "$file" >> "$work/errors"
          continue
        fi
        parts=$(mktemp -d "$work/parts.XXXXXXXX") || { echo "Cannot create certificate workspace" >> "$work/errors"; continue; }
        # Extract every PEM certificate, including OpenSSL TRUSTED CERTIFICATE blocks.
        if ! awk -v dir="$parts" '
          /^-----BEGIN (TRUSTED )?CERTIFICATE-----\r?$/ {
            if (inside) bad=1
            n++; dest=sprintf("%s/cert-%06d.pem",dir,n); inside=1
          }
          inside { print > dest }
          /^-----END (TRUSTED )?CERTIFICATE-----\r?$/ {
            if (inside) { close(dest); inside=0 } else bad=1
          }
          END { if (inside || bad) exit 2 }
        ' "$file"; then
          printf 'Malformed or unreadable PEM: %s\n' "$file" >> "$work/errors"
        fi
        if [ ! -f "$parts/cert-000001.pem" ]; then
          if grep -q '^-----BEGIN .*-----' "$file"; then
            # Known non-certificate PEM files are outside certificate inventory scope.
            # Malformed CERTIFICATE PEMs must remain coverage errors.
            if grep -Eq '^-----BEGIN (TRUSTED )?CERTIFICATE' "$file"; then
              printf 'No complete certificate block: %s\n' "$file" >> "$work/errors"
            elif grep -Eq '^-----BEGIN (.*PRIVATE KEY|PUBLIC KEY|CERTIFICATE REQUEST|NEW CERTIFICATE REQUEST|X509 CRL|DH PARAMETERS)-----' "$file"; then
              printf 'Non-certificate PEM skipped: %s\n' "$file" >> "$work/skipped"
            else
              printf 'Unsupported PEM content: %s\n' "$file" >> "$work/errors"
            fi
            rm -rf "$parts"
            continue
          fi
          if ! openssl x509 -inform DER -in "$file" -out "$parts/cert-000001.pem" 2>/dev/null; then
            printf 'Cannot parse certificate as PEM or DER: %s\n' "$file" >> "$work/errors"
            rm -rf "$parts"
            continue
          fi
        fi
        index=-1
        for cert in "$parts"/cert-*.pem; do
          index=$((index + 1))
          if ! openssl x509 -in "$cert" -outform DER -out "$parts/certificate.der" 2>/dev/null; then
            printf 'Invalid X.509 certificate: %s index=%s\n' "$file" "$index" >> "$work/errors"
            continue
          fi
          fingerprint=$(openssl dgst -sha256 "$parts/certificate.der" 2>/dev/null | awk '{print toupper($NF)}')
          if ! printf '%s\n' "$fingerprint" | grep -Eq '^[0-9A-F]{64}$'; then
            printf 'Certificate fingerprint calculation failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
            continue
          fi
          printf '1\n' >> "$work/parsed"
          name=
          kind=
          spki=
          case "$mode" in
            dn)
              dn=$(openssl x509 -in "$cert" -noout -subject -issuer -nameopt RFC2253 2>/dev/null)
              if printf '%s\n' "$dn" | grep -Eiq '(emudhra|emsign)'; then
                name='eMudhra/emSign Subject or Issuer'
                kind=DN_MATCH
              fi
              ;;
            affected)
              name=$(awk -F '|' -v h="$fingerprint" '$1=="CERT" && $2==h {print $3;exit}' "$work/indicators")
              if [ -n "$name" ]; then
                kind=AFFECTED_CERT_SHA256
              else
                if ! openssl x509 -in "$cert" -pubkey -noout > "$parts/pubkey.pem" 2>/dev/null; then
                  printf 'Public key extraction failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
                  continue
                fi
                if ! openssl pkey -pubin -in "$parts/pubkey.pem" -outform DER -out "$parts/spki.der" 2>/dev/null; then
                  printf 'SPKI conversion failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
                  continue
                fi
                spki=$(openssl dgst -sha256 "$parts/spki.der" 2>/dev/null | awk '{print toupper($NF)}')
                if ! printf '%s\n' "$spki" | grep -Eq '^[0-9A-F]{64}$'; then
                  printf 'SPKI fingerprint calculation failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
                  continue
                fi
                name=$(awk -F '|' -v h="$spki" '$1=="KEY" && $2==h {print $3;exit}' "$work/indicators")
                [ -z "$name" ] || kind=AFFECTED_SPKI_SHA256
              fi
              ;;
            roots)
              name=$(awk -F '|' -v h="$fingerprint" '$1=="ROOT" && $2==h {print $3;exit}' "$work/indicators")
              [ -z "$name" ] || kind=ASSOCIATED_ROOT
              ;;
          esac
          if [ -n "$kind" ]; then
            location_role=certificate_file
            case "$file" in
              */blocklist/*|*/blacklist/*) location_role=distrust_directory_review ;;
            esac
            dn=$(openssl x509 -in "$cert" -noout -subject -issuer -nameopt RFC2253 2>/dev/null)
            printf '1\n' >> "$work/matches"
            {
              printf 'MATCH type=%s name=%s\n' "$kind" "$name"
              printf '  location_role=%s\n' "$location_role"
              printf '  path=%s\n  certificate_index=%s (zero-based)\n' "$file" "$index"
              printf '  certificate_sha256=%s\n' "$fingerprint"
              [ -z "$spki" ] || printf '  spki_sha256=%s\n' "$spki"
              printf '%s\n' "$dn" | sed 's/^/  /'
            } >> "$work/evidence"
          fi
        done
        rm -rf "$parts"
      done
      AQUA_SCANNER
      # Recursively inspect common Linux certificate roots; overlapping subfolders
      # are covered by their parents. Absent OS/application directories are skipped.
      # Java/NSS/PKCS12 stores require separate parsing and are not covered.
      # Readable file symlinks are inspected; directory symlinks are not followed.
      roots_scanned=0
      echo 'SCAN_SCOPE: /etc/ssl /usr/local/share/ca-certificates /usr/share/ca-certificates /etc/pki /usr/share/pki /usr/local/ssl/certs /etc/letsencrypt (recursive)'
      for directory in /etc/ssl /usr/local/share/ca-certificates /usr/share/ca-certificates /etc/pki /usr/share/pki /usr/local/ssl/certs /etc/letsencrypt; do
        if [ ! -d "$directory" ]; then
          echo "SCOPE_NOTE: directory absent: $directory"
          continue
        fi
        roots_scanned=$((roots_scanned + 1))
        if ! find "$directory" \( -type f -o -type l \) \
          \( -iname '*.crt' -o -iname '*.pem' -o -iname '*.cer' -o -iname '*.der' \
             -o -name ca-bundle -o -name ca-certificates \
             -o -name '[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F].[0-9]*' \) \
          -exec sh "$work/scan-files.sh" "$work" dn {} + 2>> "$work/errors"; then
          echo "Certificate-directory traversal or audit execution failed: $directory" >> "$work/errors"
        fi
      done
      files=$(wc -l < "$work/files" | tr -d ' ')
      parsed=$(wc -l < "$work/parsed" | tr -d ' ')
      matches=$(wc -l < "$work/matches" | tr -d ' ')
      skipped=$(wc -l < "$work/skipped" | tr -d ' ')
      printf 'SUMMARY mode=dn directories=%s files=%s parsed_certificate_occurrences=%s matching_occurrences=%s skipped_noncertificate_PEM_files=%s\n' "$roots_scanned" "$files" "$parsed" "$matches" "$skipped"
      # Sanitize the reserved success token in untrusted DN/path text, so evidence
      # cannot accidentally satisfy Aqua's output flag test.
      sed 's/AQUA_CERT_CHECK_OK_7C89/[reserved-token]/g' "$work/evidence"
      if [ -s "$work/errors" ]; then
        echo 'FAIL: collection/parsing errors; coverage is incomplete'
        sed 's/AQUA_CERT_CHECK_OK_7C89/[reserved-token]/g' "$work/errors"
      elif [ "$parsed" -eq 0 ]; then
        echo 'FAIL: no certificates parsed; coverage is unconfirmed'
      elif [ "$matches" -gt 0 ]; then
        echo 'FAIL: matching certificate content found; presence does not establish effective trust'
      else
        echo 'AQUA_CERT_CHECK_OK_7C89'
      fi
      exit 0
    tests:
      test_items:
      - flag: AQUA_CERT_CHECK_OK_7C89
        set: true
    remediation: Review the Subject/Issuer, certificate path and bundle index. A DN match is association
      evidence, not proof of compromise.
    scored: true
  - id: '1.2'
    description: Compromised eMudhra intermediate certificate or public key detected
    audit: |
      # All detection logic and indicators are embedded in this YAML audit.
      # No Python, installed helper, JSON source file or network access is required.
      # Scan results describe discovered certificate files, not effective application trust.
      # Supported: PEM/DER, PEM bundles and selected OpenSSL hash-named links.
      # Excluded: opaque JKS/PKCS12/NSS stores, unknown filenames and inaccessible paths.
      # OpenSSL validates X.509 parsing and calculates identities; it does not prove
      # effective trust, certificate revocation enforcement or TLS exploitability.
      for tool in openssl find awk grep sed tr wc mktemp cat rm; do
        if ! command -v "$tool" >/dev/null 2>&1; then
          echo "FAIL: missing required OS command: $tool"
          exit 0
        fi
      done
      work=$(mktemp -d /tmp/aqua-cert-audit.XXXXXXXX) || { echo "FAIL: cannot create audit workspace"; exit 0; }
      trap 'rm -rf "$work"' 0
      trap 'exit 1' 1 2 15
      : > "$work/errors"
      : > "$work/files"
      : > "$work/parsed"
      : > "$work/matches"
      : > "$work/evidence"
      : > "$work/skipped"
      cat > "$work/indicators" <<'AQUA_INDICATORS'
      CERT|42C4ACCD12C4F59597925E1A08A7A90102608543809308D50EC427E118BA1493|EM DV TLS CA - G3B
      CERT|45A883F349AEDFD5F3A649B5498FFB121712BA8B448F3F2A91DBC5A766FCDDBE|EM DV TLS CA - G3B
      CERT|98B896625C0C230ADEEC3EB2CF6AAD5001ECF9156A68644E82374CE5833BC25B|EM OV TLS CA - G3B
      CERT|5F1DED4DD590AF74631290CF073B122A2AAC6A1FA479778A0BD43579BD832699|EM OV TLS CA - G3B
      CERT|E7568FE6E12331F4FD7DE61DA4C7559D0F8EB192A218D4741A3BA75184333838|EM EV TLS CA - G3B
      CERT|579480AEA166290BD3C643589E795FC0D0E54E86505D180F1DFF99EFD3199512|EM EV TLS CA - G3B
      CERT|0164D7CEAE5BB909C272AF151FF44B1177AE9B47D9424B94F1D9558C30934D19|EM DV TLS CA - G3C
      CERT|62B7F9868263F72F6911FC78CEAF72C1979158B7368BF648683BB2AA39B02E72|EM DV TLS CA - G3C
      CERT|CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F|EM OV TLS CA - G3C
      CERT|7485B51C0ED192878FA1D2F948FB529AE1842CC5086C4B67F7BE5B2039DE9489|EM OV TLS CA - G3C
      CERT|432EC25F0D28DE9006D97BF752F17BBB2CD41F669933FCB072D9A1148D0261F3|EM EV TLS CA - G3C
      CERT|FD9D5C2DBBD4995AB69B8D50C439D9527B00529199C2FD01B2174EB58F07021F|EM EV TLS CA - G3C
      CERT|9E39DE2881273ACC225A149C72BA3E0F51424963669AB633EE1DC7DF5DB067B4|EM OV CS CA - G3B
      CERT|A496E23AC0CAB8155FC9B117B97EAE368EFCBB9041AEC8804D5A308D2D01C2D8|EM OV CS CA - G3B
      CERT|4B2460A466F95FD40113BCDCCA2CEB29B3B093921DE45474DA7DF48874C630B1|EM OV CS CA - G3C
      CERT|84112972B8E59D14560AE7E7119654474AAD119990CF3722BFC1E3B91615E1FD|EM OV CS CA - G3C
      KEY|EBED6475C2C2D80FCB199F3FE8DD6E6B3CE60B77EB15D4E42C3DEC0C6F5B5105|EM DV TLS CA - G3B
      KEY|43BFFA19D6EF651451F6841E019931E96F6F2EAEFF28AB4B6F1CE4576DE2717B|EM OV TLS CA - G3B
      KEY|E0254C7FCDC265E2F0F8A428DABB75AB4AE039B4872D62F5EE57F0877D5A8A41|EM EV TLS CA - G3B
      KEY|49DD353DC2B919A3770645117D0E60C16115E7D1BC259EEFE734B00EA434FE5F|EM DV TLS CA - G3C
      KEY|63ECEFEE431D49523BC1136C4E72D0206A477086CAF606CD5CF025686C109CF8|EM OV TLS CA - G3C
      KEY|5C0660A2B164910D8FF589C18E14552E9E0EC237DCEFE2CB736D41A4811F81A0|EM EV TLS CA - G3C
      KEY|EC4FFA1AD0AC88081314B7AAFFEF6A91F97CD57291BB5BA686C5415F5EB4F6B6|EM OV CS CA - G3B
      KEY|92C063D67E8F14DB71D288E11266FFDAF69CBF115F7BAB14F3BC2F68D25E0E30|EM OV CS CA - G3C
      ROOT|7DD78D5F4F13459A83DFF9ABBBA62EDBAF6F2D102BF257FD712F4D9F2746ED8D|emSign Root TLS CA - G3
      ROOT|F2EDAAFF3D9B701FB4D14C9F285F8C0BAD90FC3BCC1FA07D47F4718EDABA34A5|emSign Root CS CA - G3
      ROOT|D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4|AAA Certificate Services
      ROOT|86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B|emSign ECC Root CA - G3
      AQUA_INDICATORS
      cat > "$work/scan-files.sh" <<'AQUA_SCANNER'
      work=$1
      mode=$2
      shift 2
      for file do
        [ -f "$file" ] || continue
        if [ ! -r "$file" ]; then
          printf 'Unreadable file: %s\n' "$file" >> "$work/errors"
          continue
        fi
        printf '1\n' >> "$work/files"
        size=$(wc -c < "$file")
        if [ "$size" -gt 33554432 ]; then
          printf 'File exceeds 32 MiB inspection limit: %s\n' "$file" >> "$work/errors"
          continue
        fi
        parts=$(mktemp -d "$work/parts.XXXXXXXX") || { echo "Cannot create certificate workspace" >> "$work/errors"; continue; }
        # Extract every PEM certificate, including OpenSSL TRUSTED CERTIFICATE blocks.
        if ! awk -v dir="$parts" '
          /^-----BEGIN (TRUSTED )?CERTIFICATE-----\r?$/ {
            if (inside) bad=1
            n++; dest=sprintf("%s/cert-%06d.pem",dir,n); inside=1
          }
          inside { print > dest }
          /^-----END (TRUSTED )?CERTIFICATE-----\r?$/ {
            if (inside) { close(dest); inside=0 } else bad=1
          }
          END { if (inside || bad) exit 2 }
        ' "$file"; then
          printf 'Malformed or unreadable PEM: %s\n' "$file" >> "$work/errors"
        fi
        if [ ! -f "$parts/cert-000001.pem" ]; then
          if grep -q '^-----BEGIN .*-----' "$file"; then
            # Known non-certificate PEM files are outside certificate inventory scope.
            # Malformed CERTIFICATE PEMs must remain coverage errors.
            if grep -Eq '^-----BEGIN (TRUSTED )?CERTIFICATE' "$file"; then
              printf 'No complete certificate block: %s\n' "$file" >> "$work/errors"
            elif grep -Eq '^-----BEGIN (.*PRIVATE KEY|PUBLIC KEY|CERTIFICATE REQUEST|NEW CERTIFICATE REQUEST|X509 CRL|DH PARAMETERS)-----' "$file"; then
              printf 'Non-certificate PEM skipped: %s\n' "$file" >> "$work/skipped"
            else
              printf 'Unsupported PEM content: %s\n' "$file" >> "$work/errors"
            fi
            rm -rf "$parts"
            continue
          fi
          if ! openssl x509 -inform DER -in "$file" -out "$parts/cert-000001.pem" 2>/dev/null; then
            printf 'Cannot parse certificate as PEM or DER: %s\n' "$file" >> "$work/errors"
            rm -rf "$parts"
            continue
          fi
        fi
        index=-1
        for cert in "$parts"/cert-*.pem; do
          index=$((index + 1))
          if ! openssl x509 -in "$cert" -outform DER -out "$parts/certificate.der" 2>/dev/null; then
            printf 'Invalid X.509 certificate: %s index=%s\n' "$file" "$index" >> "$work/errors"
            continue
          fi
          fingerprint=$(openssl dgst -sha256 "$parts/certificate.der" 2>/dev/null | awk '{print toupper($NF)}')
          if ! printf '%s\n' "$fingerprint" | grep -Eq '^[0-9A-F]{64}$'; then
            printf 'Certificate fingerprint calculation failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
            continue
          fi
          printf '1\n' >> "$work/parsed"
          name=
          kind=
          spki=
          case "$mode" in
            dn)
              dn=$(openssl x509 -in "$cert" -noout -subject -issuer -nameopt RFC2253 2>/dev/null)
              if printf '%s\n' "$dn" | grep -Eiq '(emudhra|emsign)'; then
                name='eMudhra/emSign Subject or Issuer'
                kind=DN_MATCH
              fi
              ;;
            affected)
              name=$(awk -F '|' -v h="$fingerprint" '$1=="CERT" && $2==h {print $3;exit}' "$work/indicators")
              if [ -n "$name" ]; then
                kind=AFFECTED_CERT_SHA256
              else
                if ! openssl x509 -in "$cert" -pubkey -noout > "$parts/pubkey.pem" 2>/dev/null; then
                  printf 'Public key extraction failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
                  continue
                fi
                if ! openssl pkey -pubin -in "$parts/pubkey.pem" -outform DER -out "$parts/spki.der" 2>/dev/null; then
                  printf 'SPKI conversion failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
                  continue
                fi
                spki=$(openssl dgst -sha256 "$parts/spki.der" 2>/dev/null | awk '{print toupper($NF)}')
                if ! printf '%s\n' "$spki" | grep -Eq '^[0-9A-F]{64}$'; then
                  printf 'SPKI fingerprint calculation failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
                  continue
                fi
                name=$(awk -F '|' -v h="$spki" '$1=="KEY" && $2==h {print $3;exit}' "$work/indicators")
                [ -z "$name" ] || kind=AFFECTED_SPKI_SHA256
              fi
              ;;
            roots)
              name=$(awk -F '|' -v h="$fingerprint" '$1=="ROOT" && $2==h {print $3;exit}' "$work/indicators")
              [ -z "$name" ] || kind=ASSOCIATED_ROOT
              ;;
          esac
          if [ -n "$kind" ]; then
            location_role=certificate_file
            case "$file" in
              */blocklist/*|*/blacklist/*) location_role=distrust_directory_review ;;
            esac
            dn=$(openssl x509 -in "$cert" -noout -subject -issuer -nameopt RFC2253 2>/dev/null)
            printf '1\n' >> "$work/matches"
            {
              printf 'MATCH type=%s name=%s\n' "$kind" "$name"
              printf '  location_role=%s\n' "$location_role"
              printf '  path=%s\n  certificate_index=%s (zero-based)\n' "$file" "$index"
              printf '  certificate_sha256=%s\n' "$fingerprint"
              [ -z "$spki" ] || printf '  spki_sha256=%s\n' "$spki"
              printf '%s\n' "$dn" | sed 's/^/  /'
            } >> "$work/evidence"
          fi
        done
        rm -rf "$parts"
      done
      AQUA_SCANNER
      # Recursively inspect common Linux certificate roots; overlapping subfolders
      # are covered by their parents. Absent OS/application directories are skipped.
      # Java/NSS/PKCS12 stores require separate parsing and are not covered.
      # Readable file symlinks are inspected; directory symlinks are not followed.
      roots_scanned=0
      echo 'SCAN_SCOPE: /etc/ssl /usr/local/share/ca-certificates /usr/share/ca-certificates /etc/pki /usr/share/pki /usr/local/ssl/certs /etc/letsencrypt (recursive)'
      for directory in /etc/ssl /usr/local/share/ca-certificates /usr/share/ca-certificates /etc/pki /usr/share/pki /usr/local/ssl/certs /etc/letsencrypt; do
        if [ ! -d "$directory" ]; then
          echo "SCOPE_NOTE: directory absent: $directory"
          continue
        fi
        roots_scanned=$((roots_scanned + 1))
        if ! find "$directory" \( -type f -o -type l \) \
          \( -iname '*.crt' -o -iname '*.pem' -o -iname '*.cer' -o -iname '*.der' \
             -o -name ca-bundle -o -name ca-certificates \
             -o -name '[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F].[0-9]*' \) \
          -exec sh "$work/scan-files.sh" "$work" affected {} + 2>> "$work/errors"; then
          echo "Certificate-directory traversal or audit execution failed: $directory" >> "$work/errors"
        fi
      done
      files=$(wc -l < "$work/files" | tr -d ' ')
      parsed=$(wc -l < "$work/parsed" | tr -d ' ')
      matches=$(wc -l < "$work/matches" | tr -d ' ')
      skipped=$(wc -l < "$work/skipped" | tr -d ' ')
      printf 'SUMMARY mode=affected directories=%s files=%s parsed_certificate_occurrences=%s matching_occurrences=%s skipped_noncertificate_PEM_files=%s\n' "$roots_scanned" "$files" "$parsed" "$matches" "$skipped"
      # Sanitize the reserved success token in untrusted DN/path text, so evidence
      # cannot accidentally satisfy Aqua's output flag test.
      sed 's/AQUA_CERT_CHECK_OK_7C89/[reserved-token]/g' "$work/evidence"
      if [ -s "$work/errors" ]; then
        echo 'FAIL: collection/parsing errors; coverage is incomplete'
        sed 's/AQUA_CERT_CHECK_OK_7C89/[reserved-token]/g' "$work/errors"
      elif [ "$parsed" -eq 0 ]; then
        echo 'FAIL: no certificates parsed; coverage is unconfirmed'
      elif [ "$matches" -gt 0 ]; then
        echo 'FAIL: matching certificate content found; presence does not establish effective trust'
      else
        echo 'AQUA_CERT_CHECK_OK_7C89'
      fi
      exit 0
    tests:
      test_items:
      - flag: AQUA_CERT_CHECK_OK_7C89
        set: true
    remediation: Review exact certificate/SPKI fingerprint matches. Apply relevant vendor trust-store/runtime
      updates and verify actual chain rejection separately.
    scored: true
  - id: '1.3'
    description: Associated root CA detected for affected eMudhra chains
    audit: |
      # All detection logic and indicators are embedded in this YAML audit.
      # No Python, installed helper, JSON source file or network access is required.
      # Scan results describe discovered certificate files, not effective application trust.
      # Supported: PEM/DER, PEM bundles and selected OpenSSL hash-named links.
      # Excluded: opaque JKS/PKCS12/NSS stores, unknown filenames and inaccessible paths.
      # OpenSSL validates X.509 parsing and calculates identities; it does not prove
      # effective trust, certificate revocation enforcement or TLS exploitability.
      for tool in openssl find awk grep sed tr wc mktemp cat rm; do
        if ! command -v "$tool" >/dev/null 2>&1; then
          echo "FAIL: missing required OS command: $tool"
          exit 0
        fi
      done
      work=$(mktemp -d /tmp/aqua-cert-audit.XXXXXXXX) || { echo "FAIL: cannot create audit workspace"; exit 0; }
      trap 'rm -rf "$work"' 0
      trap 'exit 1' 1 2 15
      : > "$work/errors"
      : > "$work/files"
      : > "$work/parsed"
      : > "$work/matches"
      : > "$work/evidence"
      : > "$work/skipped"
      cat > "$work/indicators" <<'AQUA_INDICATORS'
      CERT|42C4ACCD12C4F59597925E1A08A7A90102608543809308D50EC427E118BA1493|EM DV TLS CA - G3B
      CERT|45A883F349AEDFD5F3A649B5498FFB121712BA8B448F3F2A91DBC5A766FCDDBE|EM DV TLS CA - G3B
      CERT|98B896625C0C230ADEEC3EB2CF6AAD5001ECF9156A68644E82374CE5833BC25B|EM OV TLS CA - G3B
      CERT|5F1DED4DD590AF74631290CF073B122A2AAC6A1FA479778A0BD43579BD832699|EM OV TLS CA - G3B
      CERT|E7568FE6E12331F4FD7DE61DA4C7559D0F8EB192A218D4741A3BA75184333838|EM EV TLS CA - G3B
      CERT|579480AEA166290BD3C643589E795FC0D0E54E86505D180F1DFF99EFD3199512|EM EV TLS CA - G3B
      CERT|0164D7CEAE5BB909C272AF151FF44B1177AE9B47D9424B94F1D9558C30934D19|EM DV TLS CA - G3C
      CERT|62B7F9868263F72F6911FC78CEAF72C1979158B7368BF648683BB2AA39B02E72|EM DV TLS CA - G3C
      CERT|CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F|EM OV TLS CA - G3C
      CERT|7485B51C0ED192878FA1D2F948FB529AE1842CC5086C4B67F7BE5B2039DE9489|EM OV TLS CA - G3C
      CERT|432EC25F0D28DE9006D97BF752F17BBB2CD41F669933FCB072D9A1148D0261F3|EM EV TLS CA - G3C
      CERT|FD9D5C2DBBD4995AB69B8D50C439D9527B00529199C2FD01B2174EB58F07021F|EM EV TLS CA - G3C
      CERT|9E39DE2881273ACC225A149C72BA3E0F51424963669AB633EE1DC7DF5DB067B4|EM OV CS CA - G3B
      CERT|A496E23AC0CAB8155FC9B117B97EAE368EFCBB9041AEC8804D5A308D2D01C2D8|EM OV CS CA - G3B
      CERT|4B2460A466F95FD40113BCDCCA2CEB29B3B093921DE45474DA7DF48874C630B1|EM OV CS CA - G3C
      CERT|84112972B8E59D14560AE7E7119654474AAD119990CF3722BFC1E3B91615E1FD|EM OV CS CA - G3C
      KEY|EBED6475C2C2D80FCB199F3FE8DD6E6B3CE60B77EB15D4E42C3DEC0C6F5B5105|EM DV TLS CA - G3B
      KEY|43BFFA19D6EF651451F6841E019931E96F6F2EAEFF28AB4B6F1CE4576DE2717B|EM OV TLS CA - G3B
      KEY|E0254C7FCDC265E2F0F8A428DABB75AB4AE039B4872D62F5EE57F0877D5A8A41|EM EV TLS CA - G3B
      KEY|49DD353DC2B919A3770645117D0E60C16115E7D1BC259EEFE734B00EA434FE5F|EM DV TLS CA - G3C
      KEY|63ECEFEE431D49523BC1136C4E72D0206A477086CAF606CD5CF025686C109CF8|EM OV TLS CA - G3C
      KEY|5C0660A2B164910D8FF589C18E14552E9E0EC237DCEFE2CB736D41A4811F81A0|EM EV TLS CA - G3C
      KEY|EC4FFA1AD0AC88081314B7AAFFEF6A91F97CD57291BB5BA686C5415F5EB4F6B6|EM OV CS CA - G3B
      KEY|92C063D67E8F14DB71D288E11266FFDAF69CBF115F7BAB14F3BC2F68D25E0E30|EM OV CS CA - G3C
      ROOT|7DD78D5F4F13459A83DFF9ABBBA62EDBAF6F2D102BF257FD712F4D9F2746ED8D|emSign Root TLS CA - G3
      ROOT|F2EDAAFF3D9B701FB4D14C9F285F8C0BAD90FC3BCC1FA07D47F4718EDABA34A5|emSign Root CS CA - G3
      ROOT|D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4|AAA Certificate Services
      ROOT|86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B|emSign ECC Root CA - G3
      AQUA_INDICATORS
      cat > "$work/scan-files.sh" <<'AQUA_SCANNER'
      work=$1
      mode=$2
      shift 2
      for file do
        [ -f "$file" ] || continue
        if [ ! -r "$file" ]; then
          printf 'Unreadable file: %s\n' "$file" >> "$work/errors"
          continue
        fi
        printf '1\n' >> "$work/files"
        size=$(wc -c < "$file")
        if [ "$size" -gt 33554432 ]; then
          printf 'File exceeds 32 MiB inspection limit: %s\n' "$file" >> "$work/errors"
          continue
        fi
        parts=$(mktemp -d "$work/parts.XXXXXXXX") || { echo "Cannot create certificate workspace" >> "$work/errors"; continue; }
        # Extract every PEM certificate, including OpenSSL TRUSTED CERTIFICATE blocks.
        if ! awk -v dir="$parts" '
          /^-----BEGIN (TRUSTED )?CERTIFICATE-----\r?$/ {
            if (inside) bad=1
            n++; dest=sprintf("%s/cert-%06d.pem",dir,n); inside=1
          }
          inside { print > dest }
          /^-----END (TRUSTED )?CERTIFICATE-----\r?$/ {
            if (inside) { close(dest); inside=0 } else bad=1
          }
          END { if (inside || bad) exit 2 }
        ' "$file"; then
          printf 'Malformed or unreadable PEM: %s\n' "$file" >> "$work/errors"
        fi
        if [ ! -f "$parts/cert-000001.pem" ]; then
          if grep -q '^-----BEGIN .*-----' "$file"; then
            # Known non-certificate PEM files are outside certificate inventory scope.
            # Malformed CERTIFICATE PEMs must remain coverage errors.
            if grep -Eq '^-----BEGIN (TRUSTED )?CERTIFICATE' "$file"; then
              printf 'No complete certificate block: %s\n' "$file" >> "$work/errors"
            elif grep -Eq '^-----BEGIN (.*PRIVATE KEY|PUBLIC KEY|CERTIFICATE REQUEST|NEW CERTIFICATE REQUEST|X509 CRL|DH PARAMETERS)-----' "$file"; then
              printf 'Non-certificate PEM skipped: %s\n' "$file" >> "$work/skipped"
            else
              printf 'Unsupported PEM content: %s\n' "$file" >> "$work/errors"
            fi
            rm -rf "$parts"
            continue
          fi
          if ! openssl x509 -inform DER -in "$file" -out "$parts/cert-000001.pem" 2>/dev/null; then
            printf 'Cannot parse certificate as PEM or DER: %s\n' "$file" >> "$work/errors"
            rm -rf "$parts"
            continue
          fi
        fi
        index=-1
        for cert in "$parts"/cert-*.pem; do
          index=$((index + 1))
          if ! openssl x509 -in "$cert" -outform DER -out "$parts/certificate.der" 2>/dev/null; then
            printf 'Invalid X.509 certificate: %s index=%s\n' "$file" "$index" >> "$work/errors"
            continue
          fi
          fingerprint=$(openssl dgst -sha256 "$parts/certificate.der" 2>/dev/null | awk '{print toupper($NF)}')
          if ! printf '%s\n' "$fingerprint" | grep -Eq '^[0-9A-F]{64}$'; then
            printf 'Certificate fingerprint calculation failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
            continue
          fi
          printf '1\n' >> "$work/parsed"
          name=
          kind=
          spki=
          case "$mode" in
            dn)
              dn=$(openssl x509 -in "$cert" -noout -subject -issuer -nameopt RFC2253 2>/dev/null)
              if printf '%s\n' "$dn" | grep -Eiq '(emudhra|emsign)'; then
                name='eMudhra/emSign Subject or Issuer'
                kind=DN_MATCH
              fi
              ;;
            affected)
              name=$(awk -F '|' -v h="$fingerprint" '$1=="CERT" && $2==h {print $3;exit}' "$work/indicators")
              if [ -n "$name" ]; then
                kind=AFFECTED_CERT_SHA256
              else
                if ! openssl x509 -in "$cert" -pubkey -noout > "$parts/pubkey.pem" 2>/dev/null; then
                  printf 'Public key extraction failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
                  continue
                fi
                if ! openssl pkey -pubin -in "$parts/pubkey.pem" -outform DER -out "$parts/spki.der" 2>/dev/null; then
                  printf 'SPKI conversion failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
                  continue
                fi
                spki=$(openssl dgst -sha256 "$parts/spki.der" 2>/dev/null | awk '{print toupper($NF)}')
                if ! printf '%s\n' "$spki" | grep -Eq '^[0-9A-F]{64}$'; then
                  printf 'SPKI fingerprint calculation failed: %s index=%s\n' "$file" "$index" >> "$work/errors"
                  continue
                fi
                name=$(awk -F '|' -v h="$spki" '$1=="KEY" && $2==h {print $3;exit}' "$work/indicators")
                [ -z "$name" ] || kind=AFFECTED_SPKI_SHA256
              fi
              ;;
            roots)
              name=$(awk -F '|' -v h="$fingerprint" '$1=="ROOT" && $2==h {print $3;exit}' "$work/indicators")
              [ -z "$name" ] || kind=ASSOCIATED_ROOT
              ;;
          esac
          if [ -n "$kind" ]; then
            location_role=certificate_file
            case "$file" in
              */blocklist/*|*/blacklist/*) location_role=distrust_directory_review ;;
            esac
            dn=$(openssl x509 -in "$cert" -noout -subject -issuer -nameopt RFC2253 2>/dev/null)
            printf '1\n' >> "$work/matches"
            {
              printf 'MATCH type=%s name=%s\n' "$kind" "$name"
              printf '  location_role=%s\n' "$location_role"
              printf '  path=%s\n  certificate_index=%s (zero-based)\n' "$file" "$index"
              printf '  certificate_sha256=%s\n' "$fingerprint"
              [ -z "$spki" ] || printf '  spki_sha256=%s\n' "$spki"
              printf '%s\n' "$dn" | sed 's/^/  /'
            } >> "$work/evidence"
          fi
        done
        rm -rf "$parts"
      done
      AQUA_SCANNER
      # Recursively inspect common Linux certificate roots; overlapping subfolders
      # are covered by their parents. Absent OS/application directories are skipped.
      # Java/NSS/PKCS12 stores require separate parsing and are not covered.
      # Readable file symlinks are inspected; directory symlinks are not followed.
      roots_scanned=0
      echo 'SCAN_SCOPE: /etc/ssl /usr/local/share/ca-certificates /usr/share/ca-certificates /etc/pki /usr/share/pki /usr/local/ssl/certs /etc/letsencrypt (recursive)'
      for directory in /etc/ssl /usr/local/share/ca-certificates /usr/share/ca-certificates /etc/pki /usr/share/pki /usr/local/ssl/certs /etc/letsencrypt; do
        if [ ! -d "$directory" ]; then
          echo "SCOPE_NOTE: directory absent: $directory"
          continue
        fi
        roots_scanned=$((roots_scanned + 1))
        if ! find "$directory" \( -type f -o -type l \) \
          \( -iname '*.crt' -o -iname '*.pem' -o -iname '*.cer' -o -iname '*.der' \
             -o -name ca-bundle -o -name ca-certificates \
             -o -name '[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F].[0-9]*' \) \
          -exec sh "$work/scan-files.sh" "$work" roots {} + 2>> "$work/errors"; then
          echo "Certificate-directory traversal or audit execution failed: $directory" >> "$work/errors"
        fi
      done
      files=$(wc -l < "$work/files" | tr -d ' ')
      parsed=$(wc -l < "$work/parsed" | tr -d ' ')
      matches=$(wc -l < "$work/matches" | tr -d ' ')
      skipped=$(wc -l < "$work/skipped" | tr -d ' ')
      printf 'SUMMARY mode=roots directories=%s files=%s parsed_certificate_occurrences=%s matching_occurrences=%s skipped_noncertificate_PEM_files=%s\n' "$roots_scanned" "$files" "$parsed" "$matches" "$skipped"
      # Sanitize the reserved success token in untrusted DN/path text, so evidence
      # cannot accidentally satisfy Aqua's output flag test.
      sed 's/AQUA_CERT_CHECK_OK_7C89/[reserved-token]/g' "$work/evidence"
      if [ -s "$work/errors" ]; then
        echo 'FAIL: collection/parsing errors; coverage is incomplete'
        sed 's/AQUA_CERT_CHECK_OK_7C89/[reserved-token]/g' "$work/errors"
      elif [ "$parsed" -eq 0 ]; then
        echo 'FAIL: no certificates parsed; coverage is unconfirmed'
      elif [ "$matches" -gt 0 ]; then
        echo 'FAIL: matching certificate content found; presence does not establish effective trust'
      else
        echo 'AQUA_CERT_CHECK_OK_7C89'
      fi
      exit 0
    tests:
      test_items:
      - flag: AQUA_CERT_CHECK_OK_7C89
        set: true
    remediation: This flags related public roots, not compromised root private keys. Set scored false
      if associated-root presence should be review-only.
    scored: true

Additional Resources

Rego Custom Compliance Checks – Aqua Docs