6.0 KiB
Job Signature Authentication
Overview
The job model now includes cryptographic signature verification using secp256k1 to ensure that only authorized signatories can execute jobs.
Changes Made
1. Job Model Updates (src/job.rs)
New Fields:
signatures: Vec<JobSignature>- Signatures from authorized signatories (includes public keys)
New Types:
pub struct JobSignature {
pub public_key: String, // Hex-encoded secp256k1 public key
pub signature: String, // Hex-encoded secp256k1 signature
}
New Methods:
canonical_representation()- Creates deterministic string for signingverify_signatures()- Verifies all signatures are cryptographically validsignatories()- Returns list of public keys from signatures
New Errors:
SignatureVerificationFailed- Signature validation failedUnauthorized- Missing or invalid signatories
2. Signature Verification Process
- Canonical Representation: Job data is serialized deterministically (excluding signatures)
- SHA-256 Hash: Canonical representation is hashed
- secp256k1 Verification: Each signature is verified against its public key
- Authorization: Signatories are derived from the signatures themselves
3. Supervisor Integration (supervisor/src/mycelium.rs)
The job.run endpoint now:
- Deserializes the job from JSON
- Calls
job.verify_signatures() - Logs successful verification with signatory list
- Queues job only if verification passes
- Returns error if verification fails
4. Dependencies
Added to runner_rust/Cargo.toml:
secp256k1 = { version = "0.28", features = ["recovery", "rand"], optional = true }
sha2 = { version = "0.10", optional = true }
hex = { version = "0.4", optional = true }
rand = { version = "0.8", optional = true }
[features]
crypto = ["secp256k1", "sha2", "hex", "rand"]
Usage Example
See examples/sign_job.rs for a complete example:
cargo run --example sign_job --features crypto
Creating a Signed Job
use runner_rust::job::{Job, JobSignature};
use secp256k1::{Secp256k1, SecretKey, Message};
use sha2::{Sha256, Digest};
use rand::rngs::OsRng;
// 1. Generate keypairs
let secp = Secp256k1::new();
let (secret_key, public_key) = secp.generate_keypair(&mut OsRng);
let pubkey_hex = hex::encode(public_key.serialize());
// 2. Create job
let mut job = Job::new(
"alice".to_string(),
"shared_context".to_string(),
"print('Hello!')".to_string(),
"runner1".to_string(),
"rhai".to_string(),
);
// 3. Sign the job
let canonical = job.canonical_representation();
let mut hasher = Sha256::new();
hasher.update(canonical.as_bytes());
let hash = hasher.finalize();
let message = Message::from_digest_slice(&hash)?;
let signature = secp.sign_ecdsa(&message, &secret_key);
job.signatures = vec![JobSignature {
public_key: pubkey_hex,
signature: hex::encode(signature.serialize_compact()),
}];
// 4. Verify signatures
job.verify_signatures()?;
// 5. Send to supervisor
let job_json = serde_json::to_string(&job)?;
// POST to supervisor's job.run endpoint
Security Features
Multi-Party Authorization
- Jobs can have multiple signatories
- Each signature proves authorization from that public key
- Useful for multi-sig workflows and collaborative execution
Tamper Detection
- Any modification to job data invalidates signatures
- Canonical representation ensures deterministic signing
- SHA-256 hash prevents collision attacks
Public Key Cryptography
- secp256k1 (same as Bitcoin/Ethereum)
- 256-bit security level
- Compact signatures (64 bytes)
API Changes
Job Structure (JSON)
{
"id": "uuid",
"caller_id": "alice",
"context_id": "shared_context",
"payload": "print('Hello!')",
"runner": "runner1",
"executor": "rhai",
"timeout": 300,
"env_vars": {},
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z",
"signatures": [
{
"public_key": "03a1b2c3d4e5f6...",
"signature": "304402201234..."
},
{
"public_key": "02f1e2d3c4b5a6...",
"signature": "30440220abcd..."
}
]
}
Supervisor job.run Endpoint
Request:
{
"method": "job.run",
"params": [{
"secret": "admin_secret",
"job": { /* job with signatures */ }
}]
}
Success Response:
{
"status": "job_queued",
"job_id": "uuid"
}
Error Response:
{
"error": "signature verification failed: Invalid signature"
}
Note: Signatories can be retrieved from the job using job.signatories() which extracts public keys from the signatures.
Migration Guide
For Existing Jobs
Old jobs without signatures will fail verification. To migrate:
- Add crypto feature to your build
- Generate keypairs for all job creators
- Sign all jobs before submission (signatures include public keys)
Backward Compatibility
To temporarily disable signature verification:
# In Cargo.toml, remove crypto from default features
[features]
default = ["calendar", "finance"] # Remove "crypto"
This will use the no-op implementation that logs a warning.
Testing
The example includes comprehensive tests:
- ✅ Valid signatures from all signatories
- ✅ Missing signature detection
- ✅ Wrong signature detection
- ✅ Unauthorized signer detection
Run tests:
cargo test --features crypto
Next Steps
- Key Management: Implement secure key storage
- Key Distribution: PKI or key exchange protocol
- Revocation: Handle compromised keys
- Audit Trail: Log all signature verifications
- Time-based Signatures: Add expiration timestamps
References
Implementation Status: ✅ Complete
Feature Flag: crypto (enabled by default)
Security Level: Production-ready