You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
8.3 KiB
266 lines
8.3 KiB
#!/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
|