softKMS uses ECC public keys for client identity and provides isolated access between identities. This guide covers the identity system, token authentication, and access control.
- Identity: An ECC public key that uniquely identifies a client
- Token: A bearer token used for authentication (base64 encoded)
- Isolation: Each identity can only access keys they create
- Admin: Full access via passphrase
| Identity | Auth Method | Scope | Use Case |
|---|---|---|---|
| Admin | Passphrase | All keys | System administrator |
| Client | Token | Own keys only | Services, AI agents, applications |
- Algorithm: EdDSA with Edwards-curve Digital Signature Algorithm
- Key size: 32 bytes
- Benefits: Fast signing, compact keys, modern and secure
- Use case: General purpose, recommended for most applications
# Create Ed25519 identity (default)
softkms identity create --type ai-agent
# Public Key: ed25519:MCowBQYDK2VwAyE...- Algorithm: ECDSA with NIST P-256 curve
- Key size: 33 bytes (compressed)
- Benefits: Industry standard, PKCS#11 compatible, FIPS compliant
- Use case: PKCS#11 clients, compliance requirements
# Create P-256 identity (the `pkcs11` type yields a P-256 key)
softkms identity create --type pkcs11
# Public Key: p256:BL5a5tD5x0vM...# Create identity (Ed25519 by default)
$ softkms identity create --type ai-agent --description "Trading Bot"
Public Key: ed25519:MCowBQYDK2VwAyE...
Token: ZGlkOmtleTp6Nk1rLi4uOnNlY3JldDEyMw==
# SAVE THIS TOKEN - never shown again!
# Create a P-256 identity — use --type pkcs11 (there is no --key-type flag;
# the pkcs11 type selects P-256, all other types select Ed25519)
$ softkms identity create --type pkcs11 --description "Payment API"
Public Key: p256:BL5a5tD5x0vM...
Token: cDI1NjpCTDVhNXRENHgwdk0...
# SAVE THIS TOKEN!use softkms::identity::{Identity, IdentityType};
// Create identity
let identity = Identity::create(
IdentityType::Ed25519,
"ai-agent",
Some("Trading Bot")
)?;
// Get token (SHOW ONCE)
let token = identity.generate_token();
println!("Token: {}", token); // Save this securely!
// Later: validate token
let identity = Identity::validate_token(&token)?;token = base64(identity_format)
identity_format = "{key_type}:{base64_public_key}:{secret}"
Where:
- key_type: "ed25519" or "p256"
- public_key: Base64-encoded public key (32 or 33 bytes)
- secret: Random 32-byte secret (base64-encoded)
Raw: ed25519:MCowBQYDK2VwAyE...:MTIzNDQ0NTU2Njc3
Base64: ZWQyNTUxOTpNQ293QlFZREsyVndBeUU...6TTEyek5EVTRRNVUyTmpjMw==
# Decode token
echo "ZGlkOmtleTp6Nk1rLi4uOnNlY3JldDEyMw==" | base64 -d
# Output: ed25519:MCowBQYDK2VwAyE...:123444556677# Keep the token in a shell variable and pass it via --token on each call.
# (The CLI reads the token from the --token/-t flag only — it does NOT read a
# SOFTKMS_TOKEN environment variable.)
TOKEN="ZGlkOmtleTp6Nk1rLi4uOnNlY3JldDEyMw=="
softkms --token "$TOKEN" list
softkms --token "$TOKEN" generate --algorithm ed25519 --label mykey
softkms --token "$TOKEN" sign --label mykey --data "Hello"# Pass token directly
softkms --token "ZGlkOmtleTp6Nk1rLi4uOnNlY3JldDEyMw==" list
softkms --token "..." generate --algorithm ed25519 --label mykey# Use the identity token as the PIN
pkcs11-tool --module libsoftkms.so \
--login --pin "ZGlkOmtleTp6Nk1rLi4uOnNlY3JldDEyMw==" \
--keypairgen --key-type EC:prime256v1 -m 0x1040
# Point the module at a non-default daemon REST address if needed
export SOFTKMS_DAEMON_ADDR="127.0.0.1:8080"- Save immediately: Token shown only once at creation
- Secure storage: Use secret managers, not plaintext files
- Environment variables: Set via secure deployment, not in code
- Rotation: Revoke and recreate if compromised
- No sharing: Each service should have its own identity
Server side:
- Only stores
SHA256(secret), never the plaintext - Token cannot be retrieved after creation
- Identity metadata stored separately
Client side:
# Good: Secret manager
export SOFTKMS_TOKEN=$(secret-tool lookup service softkms identity bot-1)
# Good: Environment file (permissions 600)
source /etc/softkms/bot-1.env
# Bad: Hardcoded in scripts
softkms --token "ZGlkOmtleTp6Nk1rLi4u..." ... # Don't do this!// 1. Client sends token
let token = "ZGlkOmtleTp6Nk1rLi4u...";
// 2. Server decodes
let parts: Vec<&str> = decode_base64(token)?.split(':').collect();
let key_type = parts[0];
let public_key = parts[1];
let secret = parts[2];
// 3. Server validates
let stored_hash = get_stored_hash(public_key)?;
if sha256(secret) != stored_hash {
return Err(InvalidToken);
}
// 4. Check if active
let identity = get_identity(public_key)?;
if !identity.is_active {
return Err(RevokedIdentity);
}
// 5. Grant access (filtered by identity)Currently, softKMS uses a simple role-based model:
Admin (passphrase):
- ✅ Full access to all keys
- ✅ Create identities
- ✅ Revoke identities
- ✅ View audit logs
Client (token):
- ✅ Create keys (in their namespace)
- ✅ List keys (only their own)
- ✅ Sign with their keys
- ✅ Delete their keys
- ❌ Access other identities' keys
- ❌ Create/revoke identities
- ❌ View audit logs
~/.local/share/softkms/keys/
├── admin/ # Admin keys
│ └── {key_id}.json
├── ed25519_AAA.../ # Client A (isolated)
│ └── keys/
│ ├── key_001.json
│ └── key_002.json
└── ed25519_BBB.../ # Client B (isolated)
└── keys/
└── key_001.json
Access Rules:
- Client A sees:
ed25519_AAA.../keys/*only - Client B sees:
ed25519_BBB.../keys/*only - Admin sees: All of the above
| Operation | Admin | Client A | Client B |
|---|---|---|---|
| Create key | ✅ | ✅ (own only) | ✅ (own only) |
| List keys | ✅ All | ✅ Own only | ✅ Own only |
| Sign | ✅ All | ✅ Own only | ✅ Own only |
| Delete key | ✅ All | ✅ Own only | ❌ No access |
| Access A's keys | ✅ | ✅ | ❌ |
| Access B's keys | ✅ | ❌ | ✅ |
| Create identity | ✅ | ❌ | ❌ |
| Revoke identity | ✅ | ❌ | ❌ |
$ softkms identity list
ed25519:MCowBQY... | ai-agent | Trading Bot | Active | 3 keys
p256:BL5a5tD5... | service | Payment API | Active | 5 keys
ed25519:ZZ9ybmQ... | ai-agent | Revoked Bot | Revoked | 0 keys# Revoke an identity
$ softkms identity revoke ed25519:MCowBQY...
Identity ed25519:MCowBQY... has been revoked
# Token no longer works
$ softkms --token "..." list
Error: Invalid or revoked identityRevocation effects:
- Token immediately invalid
- Existing keys remain but inaccessible via token
- Can be reactivated by admin (future feature)
Storage: ~/.local/share/softkms/identities/{public_key}.json
{
"public_key": "ed25519:MCowBQYDK2VwAyE...",
"key_type": "ed25519",
"token_hash": "a1b2c3d4...",
"created_at": "2026-02-16T14:30:00Z",
"last_used": "2026-02-16T15:45:00Z",
"is_active": true,
"role": "client",
"client_type": "ai-agent",
"description": "Trading Bot",
"key_count": 3
}Custom Policies:
{
"name": "limited-signing",
"statements": [
{
"effect": "Allow",
"actions": ["Sign", "GetPublicKey"],
"resources": ["ed25519:AAA.../keys/*"]
},
{
"effect": "Deny",
"actions": ["DeleteKey", "CreateKey"]
}
]
}Policy Examples:
- Read-only: Can sign but not create/delete keys
- Time-bound: Access only during business hours
- Rate-limited: Max 100 operations per minute
- Shared keys: Access to specific shared keys
Error: Invalid token
Causes:
- Token expired (if we add expiration later)
- Identity revoked
- Token malformed
- Server restarted (unlikely with current implementation)
Solution: Create new identity
Error: Access denied to key
Causes:
- Trying to access another identity's key
- Identity doesn't have permission for operation
- Key doesn't exist
Solution: Check identity ownership
CKR_USER_NOT_LOGGED_IN
Causes:
- Token not provided as the PIN, or an invalid/expired/revoked token
- Attempting an admin-passphrase login over PKCS#11 (not allowed)
Solution:
# The PIN must be a valid identity token. Admin-passphrase login over PKCS#11
# is intentionally rejected (CKR_PIN_INCORRECT) — use the gRPC/CLI admin channel
# for admin operations.
pkcs11-tool ... --pin "$TOKEN"- One identity per service: Don't share tokens between services
- Descriptive names: Use
--descriptionto identify services - Monitor audit logs: Watch for unauthorized access attempts
- Rotate tokens: Revoke and recreate periodically
- Store securely: Use Kubernetes secrets, AWS Secrets Manager, etc.
- Ephemeral tokens: Create identity per agent instance
- Scope minimization: Agents only get access they need
- Key cleanup: Delete keys when agent terminates
- Audit trail: Log all agent operations
- Separate identities: Dev, staging, production
- Admin for setup: Use admin for initial configuration
- Client for testing: Create test identities
Protected against:
- ✅ Token interception (TLS in transit)
- ✅ Token replay (bound to identity)
- ✅ Cross-identity access (namespace isolation)
- ✅ Token guessing (256-bit random secret)
Assumed:
- Token stored securely by client
- Admin passphrase strong
- Server process protected
Audit Requirements:
- All operations logged with identity
- Who accessed what key, when
- Failed authentication attempts
- Identity creation/revocation
Data Residency:
- Keys encrypted at rest
- Identity metadata stored locally
- Audit logs can be exported
- Architecture - System design
- Usage Guide - Practical examples
- Security Model - Detailed security design
- API Reference - Identity RPCs