5 changed files with 497 additions and 80 deletions
@ -1,3 +1,4 @@ |
|||
eth_keys |
|||
pywebview |
|||
pyinstaller>=6.12.0 |
|||
# For development |
|||
|
@ -0,0 +1,284 @@ |
|||
#!/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 |
Loading…
Reference in new issue