#!/bin/bash # DID Visibility Check Script # @author Matthew Raymer # # This script checks visibility permissions for DIDs within the endorser.ch system. # It creates a signed JWT using admin credentials and queries the visibility API. # # Features: # - JWT creation and signing using ES256K-R # - DID visibility checking # - Environment variable support # - Debug logging # - Command line argument parsing # # Usage: # ./dids_seen.sh [-d did_to_check] # DEBUG=1 ./dids_seen.sh # For debug output # # Environment Variables: # ADMIN_DID - Admin DID for authorization # ADMIN_PRIVATE_KEY - Private key for signing # ENDORSER_API_URL - API endpoint (defaults to test) # DEBUG - Enable debug logging when set to 1 # Enhanced debug logging debug_log() { if [ "${DEBUG:-0}" = "1" ]; then echo "DEBUG: $*" >&2 fi } # Parse command line arguments # -d: Specific DID to check visibility for CHECK_DID="" while getopts "d:" opt; do case $opt in d) CHECK_DID="$OPTARG" ;; \?) echo "Usage: $0 [-d did_to_check]" >&2; exit 1 ;; esac done # Load environment variables from .env file if present # Supports: # - ADMIN_DID # - ADMIN_PRIVATE_KEY # - ENDORSER_API_URL if [ -f .env ]; then export $(cat .env | grep -v '^#' | xargs) fi # Default values for required parameters ADMIN_DID=${ADMIN_DID:-"did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F"} ADMIN_PRIVATE_KEY=${ADMIN_PRIVATE_KEY:-"2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b"} API_URL=${ENDORSER_API_URL:-"https://test-api.endorser.ch/api/report/whichDidsICanSee"} # Create JWT payload with: # - Issuer (iss) # - Subject (sub) # - Issued At (iat) # - Expiration (exp) now=$(date +%s) exp=$((now + 86400)) # 24 hours from now payload=$(jq -n \ --arg iss "$ADMIN_DID" \ --arg sub "$ADMIN_DID" \ --arg iat "$now" \ --arg exp "$exp" \ '{ iss: $iss, sub: $sub, iat: ($iat | tonumber), exp: ($exp | tonumber) }') # Base64url encode header and payload # Header specifies ES256K-R algorithm for Ethereum compatibility header='{"alg":"ES256K-R","typ":"JWT"}' header_b64=$(echo -n "$header" | base64 -w 0 | tr '/+' '_-' | tr -d '=') payload_b64=$(echo -n "$payload" | base64 -w 0 | tr '/+' '_-' | tr -d '=') # Create message to sign (header.payload) message="$header_b64.$payload_b64" # Add debug points debug_log "Creating JWT with:" debug_log "ADMIN_DID: $ADMIN_DID" debug_log "API_URL: $API_URL" debug_log "Payload: $payload" debug_log "Header base64: $header_b64" debug_log "Payload base64: $payload_b64" debug_log "Message to sign: $message" # Create temporary directory for key operations # Uses trap to ensure cleanup on exit TMPDIR=$(mktemp -d) trap 'rm -rf "$TMPDIR"' EXIT # Create private key PEM file # Converts raw private key to PEM format for OpenSSL echo "-----BEGIN EC PRIVATE KEY-----" > "$TMPDIR/private.pem" ( echo "302e0201010420" # Private key header echo -n "$ADMIN_PRIVATE_KEY" # Private key bytes echo "a00706052b8104000a" # secp256k1 OID ) | xxd -r -p | base64 >> "$TMPDIR/private.pem" echo "-----END EC PRIVATE KEY-----" >> "$TMPDIR/private.pem" # Write message to file for signing echo -n "$message" > "$TMPDIR/message.txt" # Sign message and get DER format signature openssl dgst -sha256 -sign "$TMPDIR/private.pem" "$TMPDIR/message.txt" > "$TMPDIR/signature.der" # Convert DER signature to hex der_sig=$(xxd -p -c 256 "$TMPDIR/signature.der") # Parse DER structure # Extracts R and S values from signature if [[ $der_sig =~ ^30([0-9a-f]{2})02([0-9a-f]{2})([0-9a-f]*)02([0-9a-f]{2})([0-9a-f]*)$ ]]; then r_len="${BASH_REMATCH[2]}" r_val="${BASH_REMATCH[3]}" s_len="${BASH_REMATCH[4]}" s_val="${BASH_REMATCH[5]}" debug_log "Raw signature values:" debug_log " R (${#r_val} chars): $r_val" debug_log " S (${#s_val} chars): $s_val" # Convert lengths to decimal r_len_dec=$((16#$r_len)) s_len_dec=$((16#$s_len)) # Handle R value padding if [ $r_len_dec -gt 32 ]; then r_val=${r_val: -64} # Take last 32 bytes elif [ $r_len_dec -lt 32 ]; then r_val=$(printf "%064s" "$r_val" | tr ' ' '0') # Left pad fi # Handle S value padding if [ $s_len_dec -gt 32 ]; then s_val=${s_val: -64} # Take last 32 bytes elif [ $s_len_dec -lt 32 ]; then s_val=$(printf "%064s" "$s_val" | tr ' ' '0') # Left pad fi # Ensure both values are exactly 64 characters r_val=$(printf "%064s" "$r_val" | tr ' ' '0') s_val=$(printf "%064s" "$s_val" | tr ' ' '0') debug_log "Normalized values:" debug_log " R (${#r_val} chars): $r_val" debug_log " S (${#s_val} chars): $s_val" # Create final signature concat_sig="${r_val}${s_val}" # Debug the DER parsing debug_log "DER Signature Analysis:" debug_log " Full DER: $der_sig" debug_log " Sequence length: ${BASH_REMATCH[1]}" debug_log " R length: $r_len ($r_len_dec bytes)" debug_log " S length: $s_len ($s_len_dec bytes)" # Debug signature components debug_log "Signature components:" debug_log " R value: $r_val (length: ${#r_val})" debug_log " S value: $s_val (length: ${#s_val})" debug_log " Concatenated: $concat_sig (length: ${#concat_sig})" # Try both normal and high-S value signatures s_val_alt="" if [ $s_len_dec -gt 32 ]; then # Store alternative S value s_val_alt="$s_val" # Calculate N - s for high-S values n="FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" # Use Python for hex math to avoid bc issues normalized_s=$(python3 -c " n = int('$n', 16) s = int('${s_val}', 16) result = hex(n - s)[2:].zfill(64) print(result.lower()) " 2>/dev/null || echo "ERROR") if [ "$normalized_s" = "ERROR" ]; then debug_log " Failed to normalize S value" # Keep original s_val if normalization fails s_val_alt="" else s_val=$(printf "%064s" "$normalized_s" | tr ' ' '0' | tr '[:upper:]' '[:lower:]') debug_log " Normalized S value: $s_val" fi fi # Calculate recovery bit (v) # Try each possible recovery value (0-3) until we find one that works for v in {0..3}; do # Try both S values if we have an alternative s_values=("$s_val") if [ -n "$s_val_alt" ]; then s_values+=("$s_val_alt") fi for current_s in "${s_values[@]}"; do concat_sig="${r_val}${current_s}" debug_log "Trying with S value: $current_s" recovery_sig=$(printf "%s%02x" "$concat_sig" "$v") debug_log "Recovery attempt $v:" debug_log " Concatenated signature: $concat_sig" debug_log " Recovery signature: $recovery_sig" debug_log " Recovery signature length: ${#recovery_sig}" # Ensure signature is exactly 65 bytes (130 hex chars) if [ ${#recovery_sig} -ne 130 ]; then debug_log " Invalid signature length: ${#recovery_sig}, expected 130" continue fi # Convert hex to binary, then to base64url signature=$(echo -n "$recovery_sig" | xxd -r -p 2>/dev/null | base64 -w 0 | tr '/+' '_-' | tr -d '=') debug_log " Base64URL signature: $signature" # Create JWT with this signature JWT="$message.$signature" debug_log " Testing JWT: $JWT" # Test the JWT against the API response=$(curl -s -X GET "$API_URL" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $JWT") debug_log " API Response: $response" # Check if the JWT worked if ! echo "$response" | grep -q "JWT.*failed"; then echo "JWT Token:" echo "$JWT" debug_log "Success with v=$v and S=${current_s:0:8}..." echo echo "Export command:" echo "export TOKEN='$JWT'" echo if [ -n "$CHECK_DID" ]; then # Check if specific DID is in the list if echo "$response" | jq -e --arg did "$CHECK_DID" 'contains([$did])' > /dev/null; then echo "✅ DID $CHECK_DID is in the list" exit 0 else echo "❌ DID $CHECK_DID is not in the list" echo "Attempting to add visibility..." # Request visibility visibility_response=$(curl -s -X POST \ 'http://test-api.endorser.ch/api/report/canSeeMe' \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d "{\"did\": \"$CHECK_DID\"}") if echo "$visibility_response" | grep -q "error"; then echo "❌ Failed to add visibility:" echo "$visibility_response" | jq '.' --indent 2 exit 1 else echo "✅ Successfully requested visibility. Please try checking again." exit 0 fi fi else # Show full list of visible DIDs echo "$response" | jq '.' --indent 2 fi exit 0 fi done done echo "Error: Could not find valid recovery bit" exit 1 else echo "Error: Invalid DER signature format" echo "DER: $der_sig" exit 1 fi