forked from jsnbuchanan/crowd-funder-for-time-pwa
- Convert did_generator.sh to output clean JSON - Add structured error reporting with stages - Improve debug logging with DEBUG flag - Better error handling in run-deeplink-tests.sh - Add detailed debug tracing - Fix JSON parsing and validation - Add visual feedback for generated DIDs - Use printf instead of echo for consistent output - Remove stderr mixing with stdout - Add proper exit status handling This refactors the DID generation process to be more reliable and maintainable by using structured JSON output and proper error handling throughout the pipeline.
266 lines
8.3 KiB
Bash
Executable File
266 lines
8.3 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Check for required commands
|
|
for cmd in openssl xxd jq curl base64; do
|
|
if ! command -v $cmd &> /dev/null; then
|
|
echo "Error: $cmd is not installed. Please install it first."
|
|
echo "On Arch Linux: sudo pacman -S $cmd"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# Load environment variables
|
|
if [ -f .env ]; then
|
|
export $(cat .env | grep -v '^#' | xargs)
|
|
fi
|
|
|
|
# Default values
|
|
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/v2/claim"}
|
|
DEBUG=${DEBUG:-0}
|
|
|
|
# Function to log debug info
|
|
debug_log() {
|
|
if [ "$DEBUG" = "1" ]; then
|
|
echo "$@" >&2
|
|
fi
|
|
}
|
|
|
|
# Function to generate a new keypair
|
|
generate_keypair() {
|
|
debug_log "Generating new keypair..."
|
|
|
|
# Create a temporary directory for key operations
|
|
TMPDIR=$(mktemp -d)
|
|
trap 'rm -rf "$TMPDIR"' EXIT
|
|
|
|
# Generate private key
|
|
openssl ecparam -name secp256k1 -genkey -noout > "$TMPDIR/private.pem"
|
|
|
|
# Extract raw private key
|
|
PRIVATE_KEY=$(openssl ec -in "$TMPDIR/private.pem" -text -noout 2>/dev/null | \
|
|
grep priv -A 3 | tail -n +2 | \
|
|
tr -d '\n[:space:]:' | \
|
|
sed 's/^00//')
|
|
|
|
# Generate public key (compressed format)
|
|
PUBLIC_KEY=$(openssl ec -in "$TMPDIR/private.pem" -pubout -outform DER 2>/dev/null | \
|
|
tail -c 65 | \
|
|
xxd -p -c 65 | \
|
|
sed 's/^04/02/')
|
|
|
|
# Generate Ethereum address
|
|
ADDRESS=$(echo -n "$PUBLIC_KEY" | \
|
|
xxd -r -p | \
|
|
openssl dgst -sha3-256 -binary | \
|
|
tail -c 20 | \
|
|
xxd -p)
|
|
|
|
# Create DID
|
|
DID="did:ethr:0x$ADDRESS"
|
|
|
|
# Print debug info if enabled
|
|
debug_log "Generated DID Details:"
|
|
debug_log "----------------------"
|
|
debug_log "DID: $DID"
|
|
debug_log "Address: $ADDRESS"
|
|
debug_log "Private Key: $PRIVATE_KEY"
|
|
debug_log "Public Key: $PUBLIC_KEY"
|
|
|
|
# Export variables for other functions
|
|
export DID PRIVATE_KEY PUBLIC_KEY ADDRESS
|
|
return 0
|
|
}
|
|
|
|
# Function to hex_to_dec
|
|
hex_to_dec() {
|
|
printf "%d" "0x$1"
|
|
}
|
|
|
|
# Function to attempt signing with retries
|
|
attempt_signing() {
|
|
local max_attempts=5
|
|
local attempt=1
|
|
|
|
while [ $attempt -le $max_attempts ]; do
|
|
debug_log "Signing attempt $attempt of $max_attempts..."
|
|
|
|
# Sign message and get DER format signature
|
|
openssl dgst -sha256 -sign "$TMPDIR/private.pem" "$TMPDIR/message.txt" 2>/dev/null > "$TMPDIR/signature.der"
|
|
|
|
# Convert DER signature to hex
|
|
der_sig=$(xxd -p -c 256 "$TMPDIR/signature.der")
|
|
|
|
debug_log "Debug - Full DER signature:"
|
|
debug_log "$der_sig"
|
|
|
|
# Parse DER structure with length checking
|
|
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
|
|
seq_len="${BASH_REMATCH[1]}"
|
|
r_len="${BASH_REMATCH[2]}"
|
|
r_val="${BASH_REMATCH[3]}"
|
|
s_len="${BASH_REMATCH[4]}"
|
|
s_val="${BASH_REMATCH[5]}"
|
|
|
|
# Convert lengths to decimal
|
|
r_len_dec=$((16#$r_len))
|
|
s_len_dec=$((16#$s_len))
|
|
|
|
debug_log "R length: $r_len_dec bytes"
|
|
debug_log "S length: $s_len_dec bytes"
|
|
|
|
# Handle R value
|
|
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
|
|
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
|
|
|
|
# Validate final lengths
|
|
if [ ${#r_val} -eq 64 ] && [ ${#s_val} -eq 64 ]; then
|
|
debug_log "Valid signature found on attempt $attempt"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
debug_log "Invalid signature on attempt $attempt, retrying..."
|
|
attempt=$((attempt + 1))
|
|
done
|
|
|
|
echo "Failed to generate valid signature after $max_attempts attempts" >&2
|
|
return 1
|
|
}
|
|
|
|
# Function to create and sign JWT
|
|
create_jwt() {
|
|
local now=$(date +%s)
|
|
local exp=$((now + 300))
|
|
|
|
# Create header (compact JSON)
|
|
local header='{"typ":"JWT","alg":"ES256K"}'
|
|
|
|
# Create registration claim (compact JSON)
|
|
local claim="{\"iat\":$now,\"exp\":$exp,\"sub\":\"RegisterAction\",\"vc\":{\"@context\":[\"https://www.w3.org/2018/credentials/v1\"],\"type\":[\"VerifiableCredential\"],\"credentialSubject\":{\"@context\":\"https://schema.org\",\"@type\":\"RegisterAction\",\"agent\":{\"did\":\"$ADMIN_DID\"},\"participant\":{\"did\":\"$DID\"},\"object\":\"endorser.ch\"}},\"iss\":\"$ADMIN_DID\"}"
|
|
|
|
# Base64url encode header and claim
|
|
header_b64=$(echo -n "$header" | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
|
claim_b64=$(echo -n "$claim" | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
|
|
|
# Create message to sign
|
|
message="$header_b64.$claim_b64"
|
|
|
|
# Create temporary directory for key operations
|
|
TMPDIR=$(mktemp -d)
|
|
trap 'rm -rf "$TMPDIR"' EXIT
|
|
|
|
# Create private key PEM file
|
|
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
|
|
echo -n "$message" > "$TMPDIR/message.txt"
|
|
|
|
# Attempt signing with retries
|
|
if attempt_signing; then
|
|
# Create final JWT
|
|
concat_sig="${r_val}${s_val}"
|
|
signature=$(echo -n "$concat_sig" | xxd -r -p | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
|
JWT="$message.$signature"
|
|
|
|
debug_log "Created JWT: ${JWT:0:50}..."
|
|
export JWT
|
|
return 0
|
|
else
|
|
echo "Failed to generate valid signature" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to register DID
|
|
register_did() {
|
|
debug_log "Attempting registration..."
|
|
|
|
# Create request body
|
|
body="{\"jwtEncoded\":\"$JWT\"}"
|
|
|
|
# Send registration request
|
|
response=$(curl -s -X POST "$API_URL" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$body")
|
|
|
|
# Check response
|
|
if echo "$response" | jq -e '.success' >/dev/null 2>&1; then
|
|
debug_log "Registration successful!"
|
|
debug_log "Response: $(echo "$response" | jq -c '.')"
|
|
|
|
# Output only the JSON result
|
|
debug_log "About to output JSON..."
|
|
jq -n \
|
|
--arg did "$DID" \
|
|
--arg private_key "$PRIVATE_KEY" \
|
|
--arg registration_id "$(echo "$response" | jq -r '.success.registrationId')" \
|
|
--arg claim_id "$(echo "$response" | jq -r '.success.claimId')" \
|
|
'{
|
|
status: "success",
|
|
did: $did,
|
|
privateKey: $private_key,
|
|
registrationId: $registration_id,
|
|
claimId: $claim_id
|
|
}'
|
|
debug_log "JSON output complete"
|
|
exit 0
|
|
else
|
|
error_msg=$(echo "$response" | jq -r '.error.message // "Unknown error"')
|
|
debug_log "Registration failed: $error_msg"
|
|
debug_log "Full response: $(echo "$response" | jq -c '.' || echo "$response")"
|
|
|
|
# Output error as JSON
|
|
jq -n \
|
|
--arg error "$error_msg" \
|
|
--arg stage "registration" \
|
|
'{
|
|
status: "error",
|
|
stage: $stage,
|
|
error: $error
|
|
}'
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Main execution
|
|
debug_log "Starting DID Generation..."
|
|
debug_log "Using admin DID: $ADMIN_DID"
|
|
debug_log "API URL: $API_URL"
|
|
|
|
if ! generate_keypair; then
|
|
jq -n '{
|
|
status: "error",
|
|
stage: "keypair",
|
|
error: "Failed to generate keypair"
|
|
}'
|
|
exit 1
|
|
fi
|
|
|
|
if ! create_jwt; then
|
|
jq -n '{
|
|
status: "error",
|
|
stage: "jwt",
|
|
error: "Failed to generate valid signature"
|
|
}'
|
|
exit 1
|
|
fi
|
|
|
|
register_did |