Additional Credentials and Activation/Deactivation

Overview

For security reasons or convenience, you may want to add more than one signing key to Due Vaults. You can add several keys and store them as backups for when the main key is lost, or organize key rotation for security considerations.

While the first credential is automatically approved during creation, additional credentials require manual approval. You need to approve newly added keys with a previously created and approved credential. This ensures that even if your API key is compromised, an attacker cannot steal money by adding an additional key.

This allows you to:

  • Create backup credentials for recovery scenarios
  • Implement key rotation for enhanced security

Note: All credentials have equal access to vault operations - there are no different permission levels or restricted access credentials.

Prerequisites

  • Existing active and approved credential (from the initial setup)
  • OpenSSL toolkit installed
  • API access token

Step 1: Create Additional Credential

Initialize Additional Credential Creation

The process is identical to creating the first credential - using Pattern 1: Direct JSON Signing. See Create Credential guide for detailed steps.

curl --location 'https://api.due.network/v1/vaults/credentials/init' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <token>' \
--data '{
    "kind": "Key",
    "name": "Backup key 1"
}'

Generate New Key Pair

Generate a separate key pair for the additional credential:

# Generate new private key
openssl ecparam -genkey -name prime256v1 -out private_backup.pem

# Generate corresponding public key  
openssl ec -in private_backup.pem -pubout -out public_backup.pem

Prepare Challenge Data

Create a JSON file with the challenge data to avoid issues with escape sequences:

# Create JSON file with clientDataHash and public key
cat > challenge.json << EOF
{"clientDataHash":"<clientDataHash_from_response>","publicKey":$(cat public_backup.pem | jq -Rs .)}
EOF

Sign and Submit Additional Credential

Sign the challenge data:

# Sign the JSON data from file (removing any trailing newline before signing)
cat challenge.json | tr -d '\n' | openssl dgst -sha256 -sign private_backup.pem | xxd -p | tr -d '\n'

Submit the credential:

curl --location 'https://api.due.network/v1/vaults/credentials' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <token>' \
--data '{
    "kind": "Key",
    "signature": "<your_hex_signature>",
    "publicKey": "<your_public_key_pem>",
    "challenge": "<challenge_from_init_response>"
}'

Response for additional credential:

{
    "id": "passkey_xpbaGnRJUMsrmx4is6EKE",
    "kind": "Key",
    "algorithm": "ECDSA:256:P-256",
    "location": {
        "deviceType": "",
        "deviceOS": ""
    },
    "name": "Backup key 1",
    "publicKey": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENsRgOcOnWHVRyDI1JnCbh1+QglIF\n4ufYtmiP1c6Hxgiaeoxt2ZLzeQcCqvSIUSR15nZRbpYDABxNab9rhpSzSQ==\n-----END PUBLIC KEY-----\n",
    "hasWalletAccess": true,
    "createdAt": "2025-09-15T11:29:36.970110202Z",
    "approveUntil": "2025-09-15T11:42:46Z",
    "isActive": true
}

Note the difference from first credential:

  • "isActive": true - credential is active (same as first credential)
  • "approveUntil": "2025-09-15T11:42:46Z" - has a deadline, meaning it needs approval

Step 2: Approve Additional Credential

Additional credentials require approval before the approveUntil deadline. Approval must be done using an existing fully approved credential.

Note: This process uses Pattern 2: Challenge-Response Flow.

Request Approval Challenge

curl --location 'https://api.due.network/v1/vaults/credentials/approve' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <token>' \
--data '{
    "payload": {
        "credentialId": "passkey_xpbaGnRJUMsrmx4is6EKE"
    }
}'

You'll receive a 403 error with a challenge (standard Pattern 2 response).

Sign Approval Challenge

Important: Sign with your existing fully approved credential's private key, not the new credential being approved.

# Sign the challenge with existing credential's private key
echo -n "<clientData_from_response>" | base64 -d | openssl dgst -sha256 -sign private.pem | base64 -w 0 | tr '+/' '-_' | tr -d '='

Complete Approval

curl --location 'https://api.due.network/v1/vaults/credentials/approve' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <token>' \
--data '{
    "payload": {
        "credentialId": "passkey_xpbaGnRJUMsrmx4is6EKE"
    },
    "signature": {
        "challengeIdentifier": "<challengeIdentifier_from_response>",
        "firstFactor": {
            "kind": "Key",
            "credentialAssertion": {
                "credId": "passkey_xonETR6gAv3wIyhy8ehjx",
                "clientData": "<original_clientData>",
                "signature": "<your_signature>"
            }
        }
    }
}'

Response after successful approval:

{
    "id": "passkey_xpbaGnRJUMsrmx4is6EKE",
    "kind": "Key",
    "algorithm": "ECDSA:256:P-256",
    "location": {
        "deviceType": "",
        "deviceOS": ""
    },
    "name": "Backup key 1",
    "publicKey": "...",
    "hasWalletAccess": true,
    "createdAt": "2025-09-15T11:29:36.97011Z",
    "approveUntil": null,
    "isActive": true
}

Notice after successful approval:

  • "approveUntil": null - no longer requires approval, fully approved and ready to use

Step 3: Credential Deactivation

You can deactivate credentials that are no longer needed. This also uses Pattern 2.

Request Deactivation

curl --location 'https://api.due.network/v1/vaults/credentials/deactivate' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <token>' \
--data '{
    "payload": {
        "credentialId": "passkey_xpbaGnRJUMsrmx4is6EKE"
    }
}'

Sign and Complete Deactivation

Sign the challenge with an active credential and submit following Pattern 2 structure.

Response after deactivation:

{
    "id": "passkey_xpbaGnRJUMsrmx4is6EKE",
    "isActive": false
}

The credential is now deactivated ("isActive": false).