From 3dae8f7f7fbea271ff9221a3c4fbdc80cc69c1e4 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Tue, 4 Mar 2025 06:27:20 +0000 Subject: [PATCH] feat: Add environment variable support for DID registration - Bash implementation of DID creation-registration - Move admin credentials to .env file for better security - Add .env.example with default values - Add dotenv support to TypeScript, Python and Bash implementations - Update dependencies to include dotenv packages - Fix JWT signature format in Bash implementation - Add DER signature parsing for ES256K in Bash script The admin DID and private key can now be configured via environment variables, with fallback to default values if not set. This allows for easier testing and deployment across different environments. --- .env.example | 6 ++ .gitignore | 3 +- package.json | 3 +- test-scripts/did_generator.py | 17 ++- test-scripts/did_generator.sh | 189 ++++++++++++++++++++++++++++++++++ test-scripts/did_generator.ts | 21 ++-- test-scripts/requirements.txt | 3 +- 7 files changed, 227 insertions(+), 15 deletions(-) create mode 100644 .env.example create mode 100755 test-scripts/did_generator.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..983c4b6 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# Admin DID credentials +ADMIN_DID=did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F +ADMIN_PRIVATE_KEY=2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b + +# API Configuration +ENDORSER_API_URL=https://test-api.endorser.ch/api/v2/claim \ No newline at end of file diff --git a/.gitignore b/.gitignore index edc9e9f..c61f208 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ playwright-tests test-playwright dist-electron-packages ios -.ruby-version \ No newline at end of file +.ruby-version ++.env \ No newline at end of file diff --git a/package.json b/package.json index 7376495..7cce50c 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,8 @@ "vue-qrcode-reader": "^5.5.3", "vue-router": "^4.5.0", "web-did-resolver": "^2.0.27", - "zod": "^3.24.2" + "zod": "^3.24.2", + "dotenv": "^16.0.3" }, "devDependencies": { "@playwright/test": "^1.45.2", diff --git a/test-scripts/did_generator.py b/test-scripts/did_generator.py index e17cf1d..dd0ab54 100644 --- a/test-scripts/did_generator.py +++ b/test-scripts/did_generator.py @@ -22,6 +22,8 @@ Dependencies: base64: For JWT encoding argparse: For command-line argument parsing pathlib: For path handling + dotenv: For environment variable loading + os: For environment variable access Usage: python did_generator.py [options] @@ -46,6 +48,11 @@ import argparse import secrets import hashlib from pathlib import Path +from dotenv import load_dotenv +import os + +# Load environment variables +load_dotenv() class DIDRegistration: """ @@ -305,12 +312,14 @@ def main(): parser.add_argument( '--api-url', help='Override API URL', - default="https://test-api.endorser.ch/api/v2/claim" + default=os.getenv('ENDORSER_API_URL', 'https://test-api.endorser.ch/api/v2/claim') ) args = parser.parse_args() - admin_keypair = { - 'did': 'did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F', - 'private_key': '2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b' + + # Get admin credentials from environment + admin_keypair = { + 'did': os.getenv('ADMIN_DID', 'did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F'), + 'private_key': os.getenv('ADMIN_PRIVATE_KEY', '2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b') } admin_did = args.admin_did diff --git a/test-scripts/did_generator.sh b/test-scripts/did_generator.sh new file mode 100755 index 0000000..c22a1b0 --- /dev/null +++ b/test-scripts/did_generator.sh @@ -0,0 +1,189 @@ +#!/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"} + +# Function to generate a new keypair +generate_keypair() { + echo "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" + + echo "Generated DID Details:" + echo "----------------------" + echo "DID: $DID" + echo "Address: 0x$ADDRESS" + echo "Private Key: $PRIVATE_KEY" + echo "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 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" + + # 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 R+S format + der_sig=$(xxd -p -c 256 "$TMPDIR/signature.der") + + # Debug the full DER signature + echo "Debug - Full DER signature:" + echo "$der_sig" + + # Parse DER structure - more robust version + 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]}" + + echo "Debug - DER parsing:" + echo "Sequence length: $seq_len" + echo "R length: $r_len" + echo "R value: $r_val" + echo "S length: $s_len" + echo "S value: $s_val" + + # Remove leading zeros if present + r_val=${r_val#"00"} + s_val=${s_val#"00"} + + # Pad R and S to 32 bytes each + r_val=$(printf "%064s" "$r_val" | tr ' ' '0') + s_val=$(printf "%064s" "$s_val" | tr ' ' '0') + + # Concatenate R+S and convert to base64url + signature=$(echo -n "${r_val}${s_val}" | xxd -r -p | base64 -w 0 | tr '/+' '_-' | tr -d '=') + else + echo "Error: Invalid DER signature format" + echo "DER: $der_sig" + return 1 + fi + + # Create final JWT + JWT="$message.$signature" + + echo -e "\nCreated JWT: ${JWT:0:50}..." + export JWT + return 0 +} + +# Function to register DID +register_did() { + echo "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 + echo "Registration successful!" + echo "Response:" + echo "$response" | jq '.' + else + echo "Registration failed!" + echo "Error: $(echo "$response" | jq -r '.error.message // empty')" + echo "Full response:" + echo "$response" | jq '.' || echo "$response" + fi +} + +# Main execution +echo "Starting DID Generation..." +echo "Using admin DID: $ADMIN_DID" +echo "API URL: $API_URL" + +generate_keypair && \ +create_jwt && \ +register_did + +exit $? \ No newline at end of file diff --git a/test-scripts/did_generator.ts b/test-scripts/did_generator.ts index 7639913..51b6bf8 100644 --- a/test-scripts/did_generator.ts +++ b/test-scripts/did_generator.ts @@ -18,6 +18,7 @@ * ethers: For Ethereum account operations * node-fetch: For API communication * commander: For CLI argument parsing + * dotenv: For environment variable loading * * Usage: * npm run generate-did -- [options] @@ -37,16 +38,20 @@ import * as didJwt from 'did-jwt'; import { ethers } from 'ethers'; import fetch from 'node-fetch'; import { program } from 'commander'; +import * as dotenv from 'dotenv'; +import { config } from 'dotenv'; -const admin_keypair = { - did: 'did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F', - privateKey: '2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b' -} -// Default admin DID -const DEFAULT_ADMIN_DID = admin_keypair.did; +// Load environment variables +config(); -// Add constant for default API URL -const DEFAULT_API_URL = 'https://test-api.endorser.ch/api/v2/claim'; +const admin_keypair = { + did: process.env.ADMIN_DID || 'did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F', + privateKey: process.env.ADMIN_PRIVATE_KEY || '2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b' +}; + +// Default values from environment +const DEFAULT_ADMIN_DID = admin_keypair.did; +const DEFAULT_API_URL = process.env.ENDORSER_API_URL || 'https://test-api.endorser.ch/api/v2/claim'; /** * Result interface for DID creation process diff --git a/test-scripts/requirements.txt b/test-scripts/requirements.txt index ced4027..7b63c58 100644 --- a/test-scripts/requirements.txt +++ b/test-scripts/requirements.txt @@ -2,4 +2,5 @@ eth-account>=0.8.0 eth-keys>=0.4.0 PyJWT>=2.8.0 requests>=2.31.0 -cryptography>=41.0.0 \ No newline at end of file +cryptography>=41.0.0 +python-dotenv==1.0.0 \ No newline at end of file