...
This commit is contained in:
112
examples/hero/crypt/hero_crypt_example.vsh
Executable file
112
examples/hero/crypt/hero_crypt_example.vsh
Executable file
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.hero.crypt
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
|
||||
// Connect to default Redis instance (127.0.0.1:6379)
|
||||
mut age_client := crypt.new_age_client()!
|
||||
|
||||
// Test stateless encryption
|
||||
println('Testing stateless encryption...')
|
||||
keypair := age_client.generate_keypair() or {
|
||||
println('Error generating keypair: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
message := 'Hello, AGE encryption!'
|
||||
encrypted := age_client.encrypt(keypair.recipient, message) or {
|
||||
println('Error encrypting message: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
decrypted := age_client.decrypt(keypair.identity, encrypted.ciphertext) or {
|
||||
println('Error decrypting message: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
println('Original message: ${message}')
|
||||
println('Encrypted message: ${encrypted.ciphertext}')
|
||||
println('Decrypted message: ${decrypted}')
|
||||
|
||||
assert decrypted == message
|
||||
println('Stateless encryption test passed!')
|
||||
|
||||
// Test stateless signing
|
||||
println('\nTesting stateless signing...')
|
||||
signing_keypair := age_client.generate_signing_keypair() or {
|
||||
println('Error generating signing keypair: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
!signed := age_client.sign(signing_keypair.sign_key, message) or {
|
||||
println('Error signing message: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
verified := age_client.verify(signing_keypair.verify_key, message, signed.signature) or {
|
||||
println('Error verifying signature: ${err}')
|
||||
return
|
||||
}
|
||||
!println('Message: ${message}')
|
||||
println('Signature: ${signed.signature}')
|
||||
println('Signature valid: ${verified}')
|
||||
|
||||
assert verified == true
|
||||
println('Stateless signing test passed!')
|
||||
|
||||
// Test key-managed encryption
|
||||
println('\nTesting key-managed encryption...')
|
||||
key_name := 'example_encryption_key'
|
||||
named_keypair := age_client.create_named_keypair(key_name) or {
|
||||
println('Error creating named keypair: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
named_encrypted := age_client.encrypt_with_named_key(key_name, message) or {
|
||||
println('Error encrypting with named key: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
named_decrypted := age_client.decrypt_with_named_key(key_name, named_encrypted.ciphertext) or {
|
||||
println('Error decrypting with named key: ${err}')
|
||||
return
|
||||
}
|
||||
println('Key name: ${key_name}')
|
||||
println('Encrypted with named key: ${named_encrypted.ciphertext}')
|
||||
println('Decrypted with named key: ${named_decrypted}')
|
||||
|
||||
assert named_decrypted == message
|
||||
println('Key-managed encryption test passed!')
|
||||
|
||||
// Test key-managed signing
|
||||
println('\nTesting key-managed signing...')
|
||||
signing_key_name := 'example_signing_key'
|
||||
age_client.create_named_signing_keypair(signing_key_name) or {
|
||||
println('Error creating named signing keypair: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
named_signed := age_client.sign_with_named_key(signing_key_name, message) or {
|
||||
println('Error signing with named key: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
named_verified := age_client.verify_with_named_key(signing_key_name, message, named_signed.signature) or {
|
||||
println('Error verifying with named key: ${err}')
|
||||
return
|
||||
}
|
||||
println('Signing key name: ${signing_key_name}')
|
||||
println('Signature: ${named_signed.signature}')
|
||||
println('Signature valid: ${named_verified}')
|
||||
|
||||
assert named_verified == true
|
||||
println('Key-managed signing test passed!')
|
||||
|
||||
// Test list keys
|
||||
println('\nTesting list keys...')
|
||||
keys := age_client.list_keys() or {
|
||||
println('Error listing keys: ${err}')
|
||||
return
|
||||
}
|
||||
println('Stored keys: ${keys}')
|
||||
println('All tests completed successfully!')
|
||||
133
lib/hero/crypt/age.v
Normal file
133
lib/hero/crypt/age.v
Normal file
@@ -0,0 +1,133 @@
|
||||
module crypt
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
|
||||
// Stateless AGE operations
|
||||
|
||||
// generate_keypair creates a new Age encryption key pair
|
||||
pub fn (client &AGEClient) generate_keypair() !KeyPair {
|
||||
response := client.redis.send_expect_list(['AGE', 'GENENC'])!
|
||||
|
||||
if response.len < 2 {
|
||||
return error('Invalid response from AGE GENENC command')
|
||||
}
|
||||
|
||||
return KeyPair{
|
||||
recipient: response[0].str()
|
||||
identity: response[1].str()
|
||||
}
|
||||
}
|
||||
|
||||
// generate_signing_keypair creates a new Age signing key pair
|
||||
pub fn (client &AGEClient) generate_signing_keypair() !SigningKeyPair {
|
||||
response := client.redis.send_expect_list(['AGE', 'GENSIGN'])!
|
||||
|
||||
if response.len < 2 {
|
||||
return error('Invalid response from AGE GENSIGN command')
|
||||
}
|
||||
|
||||
return SigningKeyPair{
|
||||
verify_key: response[0].str()
|
||||
sign_key: response[1].str()
|
||||
}
|
||||
}
|
||||
|
||||
// encrypt encrypts a message with the recipient's public key
|
||||
pub fn (client &AGEClient) encrypt(recipient string, message string) !EncryptionResult {
|
||||
ciphertext := client.redis.send_expect_str(['AGE', 'ENCRYPT', recipient, message])!
|
||||
|
||||
return EncryptionResult{
|
||||
ciphertext: ciphertext
|
||||
}
|
||||
}
|
||||
|
||||
// decrypt decrypts a message with the identity (private key)
|
||||
pub fn (client &AGEClient) decrypt(identity string, ciphertext string) !string {
|
||||
return client.redis.send_expect_str(['AGE', 'DECRYPT', identity, ciphertext])!
|
||||
}
|
||||
|
||||
// sign signs a message with the signing key
|
||||
pub fn (client &AGEClient) sign(sign_key string, message string) !SignatureResult {
|
||||
signature := client.redis.send_expect_str(['AGE', 'SIGN', sign_key, message])!
|
||||
|
||||
return SignatureResult{
|
||||
signature: signature
|
||||
}
|
||||
}
|
||||
|
||||
// verify verifies a signature with the verification key
|
||||
pub fn (client &AGEClient) verify(verify_key string, message string, signature string) !bool {
|
||||
result := client.redis.send_expect_int(['AGE', 'VERIFY', verify_key, message, signature])!
|
||||
return result == 1
|
||||
}
|
||||
|
||||
// Key-managed AGE operations
|
||||
|
||||
// create_named_keypair creates and stores a named encryption key pair
|
||||
pub fn (client &AGEClient) create_named_keypair(name string) !KeyPair {
|
||||
response := client.redis.send_expect_list(['AGE', 'KEYGEN', name])!
|
||||
|
||||
if response.len < 2 {
|
||||
return error('Invalid response from AGE KEYGEN command')
|
||||
}
|
||||
|
||||
return KeyPair{
|
||||
recipient: response[0].str()
|
||||
identity: response[1].str()
|
||||
}
|
||||
}
|
||||
|
||||
// create_named_signing_keypair creates and stores a named signing key pair
|
||||
pub fn (client &AGEClient) create_named_signing_keypair(name string) !SigningKeyPair {
|
||||
response := client.redis.send_expect_list(['AGE', 'SIGNKEYGEN', name])!
|
||||
|
||||
if response.len < 2 {
|
||||
return error('Invalid response from AGE SIGNKEYGEN command')
|
||||
}
|
||||
|
||||
return SigningKeyPair{
|
||||
verify_key: response[0].str()
|
||||
sign_key: response[1].str()
|
||||
}
|
||||
}
|
||||
|
||||
// encrypt_with_named_key encrypts a message using a named key
|
||||
pub fn (client &AGEClient) encrypt_with_named_key(key_name string, message string) !EncryptionResult {
|
||||
ciphertext := client.redis.send_expect_str(['AGE', 'ENCRYPTNAME', key_name, message])!
|
||||
|
||||
return EncryptionResult{
|
||||
ciphertext: ciphertext
|
||||
}
|
||||
}
|
||||
|
||||
// decrypt_with_named_key decrypts a message using a named key
|
||||
pub fn (client &AGEClient) decrypt_with_named_key(key_name string, ciphertext string) !string {
|
||||
return client.redis.send_expect_str(['AGE', 'DECRYPTNAME', key_name, ciphertext])!
|
||||
}
|
||||
|
||||
// sign_with_named_key signs a message using a named signing key
|
||||
pub fn (client &AGEClient) sign_with_named_key(key_name string, message string) !SignatureResult {
|
||||
signature := client.redis.send_expect_str(['AGE', 'SIGNNAME', key_name, message])!
|
||||
|
||||
return SignatureResult{
|
||||
signature: signature
|
||||
}
|
||||
}
|
||||
|
||||
// verify_with_named_key verifies a signature using a named verification key
|
||||
pub fn (client &AGEClient) verify_with_named_key(key_name string, message string, signature string) !bool {
|
||||
result := client.redis.send_expect_int(['AGE', 'VERIFYNAME', key_name, message, signature])!
|
||||
return result == 1
|
||||
}
|
||||
|
||||
// list_keys lists all stored AGE keys
|
||||
pub fn (client &AGEClient) list_keys() ![]string {
|
||||
response := client.redis.send_expect_list(['AGE', 'LIST'])!
|
||||
|
||||
mut keys := []string{}
|
||||
for i in 0 .. response.len {
|
||||
keys << response[i].str()
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
81
lib/hero/crypt/age_test.v
Normal file
81
lib/hero/crypt/age_test.v
Normal file
@@ -0,0 +1,81 @@
|
||||
module crypt
|
||||
|
||||
fn test_stateless_encryption() {
|
||||
mut client := new_age_client()!
|
||||
|
||||
// Generate a keypair
|
||||
keypair := client.generate_keypair()!
|
||||
|
||||
// Encrypt a message
|
||||
message := 'Hello, AGE encryption!'
|
||||
encrypted := client.encrypt(keypair.recipient, message)!
|
||||
|
||||
// Decrypt the message
|
||||
decrypted := client.decrypt(keypair.identity, encrypted.ciphertext)!
|
||||
|
||||
assert decrypted == message
|
||||
}
|
||||
|
||||
fn test_stateless_signing() {
|
||||
mut client := new_age_client()!
|
||||
|
||||
// Generate a signing keypair
|
||||
keypair := client.generate_signing_keypair()!
|
||||
|
||||
// Sign a message
|
||||
message := 'This message is signed'
|
||||
signed := client.sign(keypair.sign_key, message)!
|
||||
|
||||
// Verify the signature
|
||||
verified := client.verify(keypair.verify_key, message, signed.signature)!
|
||||
|
||||
assert verified == true
|
||||
}
|
||||
|
||||
fn test_key_managed_encryption() {
|
||||
mut client := new_age_client()!
|
||||
|
||||
// Create a named keypair
|
||||
key_name := 'test_encryption_key'
|
||||
client.create_named_keypair(key_name)!
|
||||
|
||||
// Encrypt with the named key
|
||||
message := 'Hello, key-managed encryption!'
|
||||
encrypted := client.encrypt_with_named_key(key_name, message)!
|
||||
|
||||
// Decrypt with the named key
|
||||
decrypted := client.decrypt_with_named_key(key_name, encrypted.ciphertext)!
|
||||
|
||||
assert decrypted == message
|
||||
}
|
||||
|
||||
fn test_key_managed_signing() {
|
||||
mut client := new_age_client()!
|
||||
|
||||
// Create a named signing keypair
|
||||
key_name := 'test_signing_key'
|
||||
client.create_named_signing_keypair(key_name)!
|
||||
|
||||
// Sign with the named key
|
||||
message := 'This message is signed with a named key'
|
||||
signed := client.sign_with_named_key(key_name, message)!
|
||||
|
||||
// Verify with the named key
|
||||
verified := client.verify_with_named_key(key_name, message, signed.signature)!
|
||||
|
||||
assert verified == true
|
||||
}
|
||||
|
||||
fn test_list_keys() {
|
||||
mut client := new_age_client()!
|
||||
|
||||
// Create some named keys
|
||||
client.create_named_keypair('list_test_key1')!
|
||||
client.create_named_signing_keypair('list_test_key2')!
|
||||
|
||||
// List the keys
|
||||
keys := client.list_keys()!
|
||||
|
||||
assert 'list_test_key1' in keys
|
||||
assert 'list_test_key2' in keys
|
||||
}
|
||||
31
lib/hero/crypt/factory.v
Normal file
31
lib/hero/crypt/factory.v
Normal file
@@ -0,0 +1,31 @@
|
||||
module crypt
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
|
||||
// AGEClient provides access to the Age encryption features in HeroDB
|
||||
pub struct AGEClient {
|
||||
pub mut:
|
||||
redis &redisclient.Redis @[str: skip]
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct AGEClientConfig {
|
||||
pub mut:
|
||||
redis_url redisclient.RedisURL = redisclient.RedisURL{}
|
||||
redis ?&redisclient.Redis
|
||||
}
|
||||
|
||||
// new_age_client creates a new AGE encryption client
|
||||
pub fn new_age_client(config AGEClientConfig) !&AGEClient {
|
||||
// If a Redis client is provided, use it
|
||||
mut redis := if r := config.redis {
|
||||
r
|
||||
} else {
|
||||
// Otherwise create a new Redis client
|
||||
redisclient.core_get(config.redis_url)!
|
||||
}
|
||||
|
||||
return &AGEClient{
|
||||
redis: redis
|
||||
}
|
||||
}
|
||||
27
lib/hero/crypt/model.v
Normal file
27
lib/hero/crypt/model.v
Normal file
@@ -0,0 +1,27 @@
|
||||
module crypt
|
||||
|
||||
// KeyPair represents an Age encryption key pair
|
||||
pub struct KeyPair {
|
||||
pub:
|
||||
recipient string // Public key (can be shared)
|
||||
identity string // Private key (must be kept secret)
|
||||
}
|
||||
|
||||
// SigningKeyPair represents an Age signing key pair
|
||||
pub struct SigningKeyPair {
|
||||
pub:
|
||||
verify_key string // Public verification key
|
||||
sign_key string // Private signing key
|
||||
}
|
||||
|
||||
// EncryptionResult represents the result of an encryption operation
|
||||
pub struct EncryptionResult {
|
||||
pub:
|
||||
ciphertext string // Base64-encoded encrypted data
|
||||
}
|
||||
|
||||
// SignatureResult represents the result of a signing operation
|
||||
pub struct SignatureResult {
|
||||
pub:
|
||||
signature string // Base64-encoded signature
|
||||
}
|
||||
@@ -1,2 +1,187 @@
|
||||
this will have client using the redis client as basis, but with some added functionality
|
||||
this functionality is for AGE features from herodb which is our own customer redis server
|
||||
# HeroDB AGE Encryption (hero.crypt)
|
||||
|
||||
This module provides client-side access to HeroDB's AGE encryption features, offering two distinct modes of operation:
|
||||
|
||||
- **Stateless Mode**: Client manages keys, nothing stored on server
|
||||
- **Key-Managed Mode**: Server stores named keys
|
||||
|
||||
## Installation
|
||||
|
||||
Ensure HeroDB is running with encryption enabled:
|
||||
|
||||
```bash
|
||||
herodb --dir /path/to/data --port 6379 --encrypt --encryption-key yoursecretkey
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating an AGE Client
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.hero.crypt
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
|
||||
// Connect to default Redis instance (127.0.0.1:6379)
|
||||
mut age_client := crypt.new_age_client()!
|
||||
|
||||
// Or connect to a specific Redis instance
|
||||
mut age_client := crypt.new_age_client(
|
||||
redis_url: redisclient.RedisURL{
|
||||
address: 'my.herodb.server',
|
||||
port: 6379
|
||||
}
|
||||
)!
|
||||
|
||||
// Or use an existing Redis client
|
||||
mut redis := redisclient.core_get()!
|
||||
mut age_client := crypt.new_age_client(
|
||||
redis: redis
|
||||
)!
|
||||
```
|
||||
|
||||
## Stateless Encryption (Client-Managed Keys)
|
||||
|
||||
In stateless mode, the client generates and manages keys. The server never stores any keys.
|
||||
|
||||
### Generate a Keypair
|
||||
|
||||
```v
|
||||
// Generate a new encryption keypair
|
||||
keypair := age_client.generate_keypair()!
|
||||
println('Public key: ${keypair.recipient}')
|
||||
println('Private key: ${keypair.identity}')
|
||||
|
||||
// Important: Store the identity (private key) securely!
|
||||
```
|
||||
|
||||
### Encrypt and Decrypt
|
||||
|
||||
```v
|
||||
// Encrypt a message with the public key
|
||||
message := 'Hello, encrypted world!'
|
||||
encrypted := age_client.encrypt(keypair.recipient, message)!
|
||||
println('Encrypted: ${encrypted.ciphertext}')
|
||||
|
||||
// Decrypt with the private key
|
||||
decrypted := age_client.decrypt(keypair.identity, encrypted.ciphertext)!
|
||||
println('Decrypted: ${decrypted}')
|
||||
```
|
||||
|
||||
### Signing and Verification
|
||||
|
||||
```v
|
||||
// Generate a signing keypair
|
||||
signing_keypair := age_client.generate_signing_keypair()!
|
||||
|
||||
// Sign a message
|
||||
message := 'This message is authentic'
|
||||
signed := age_client.sign(signing_keypair.sign_key, message)!
|
||||
println('Signature: ${signed.signature}')
|
||||
|
||||
// Verify the signature
|
||||
is_valid := age_client.verify(signing_keypair.verify_key, message, signed.signature)!
|
||||
println('Signature valid: ${is_valid}')
|
||||
```
|
||||
|
||||
## Key-Managed Encryption (Server-Stored Keys)
|
||||
|
||||
In key-managed mode, the server generates and stores named keys. Clients reference keys by name.
|
||||
|
||||
### Create Named Keys
|
||||
|
||||
```v
|
||||
// Create a named encryption keypair
|
||||
named_keypair := age_client.create_named_keypair('app1_encryption')!
|
||||
println('Created encryption keypair: ${named_keypair.recipient}')
|
||||
|
||||
// Create a named signing keypair
|
||||
named_signing_keypair := age_client.create_named_signing_keypair('app1_signing')!
|
||||
println('Created signing keypair: ${named_signing_keypair.verify_key}')
|
||||
```
|
||||
|
||||
### Encrypt and Decrypt with Named Keys
|
||||
|
||||
```v
|
||||
// Encrypt with a named key
|
||||
message := 'This message is encrypted with a named key'
|
||||
encrypted := age_client.encrypt_with_named_key('app1_encryption', message)!
|
||||
println('Encrypted: ${encrypted.ciphertext}')
|
||||
|
||||
// Decrypt with a named key
|
||||
decrypted := age_client.decrypt_with_named_key('app1_encryption', encrypted.ciphertext)!
|
||||
println('Decrypted: ${decrypted}')
|
||||
```
|
||||
|
||||
### Sign and Verify with Named Keys
|
||||
|
||||
```v
|
||||
// Sign with a named key
|
||||
message := 'This message is signed with a named key'
|
||||
signed := age_client.sign_with_named_key('app1_signing', message)!
|
||||
println('Signature: ${signed.signature}')
|
||||
|
||||
// Verify with a named key
|
||||
is_valid := age_client.verify_with_named_key('app1_signing', message, signed.signature)!
|
||||
println('Signature valid: ${is_valid}')
|
||||
```
|
||||
|
||||
### List Stored Keys
|
||||
|
||||
```v
|
||||
// List all stored keys
|
||||
keys := age_client.list_keys()!
|
||||
println('Stored keys: ${keys}')
|
||||
```
|
||||
|
||||
## Choosing Between Modes
|
||||
|
||||
### Use Stateless Mode When:
|
||||
|
||||
- You want complete control over key management
|
||||
- You don't want the server to store any private keys
|
||||
- You need to use the same keys across multiple servers
|
||||
|
||||
### Use Key-Managed Mode When:
|
||||
|
||||
- You want simpler client code (no need to manage keys)
|
||||
- You trust the server to securely store private keys
|
||||
- You need centralized key management with rotation options
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Private Keys**: In stateless mode, you are responsible for securely storing private keys.
|
||||
2. **Server Security**: In key-managed mode, ensure your HeroDB server is properly secured.
|
||||
3. **Encryption at Rest**: HeroDB supports database-level encryption separate from AGE commands.
|
||||
4. **Transport Security**: Consider using SSL/TLS to protect data in transit.
|
||||
|
||||
## Examples
|
||||
|
||||
### Secure Message Exchange
|
||||
|
||||
```v
|
||||
// Sender (Alice)
|
||||
mut alice_client := crypt.new_age_client()!
|
||||
alice_keypair := alice_client.generate_keypair()!
|
||||
|
||||
// Bob gets Alice's public key (recipient)
|
||||
mut bob_client := crypt.new_age_client()!
|
||||
bob_message := 'Secret message for Alice'
|
||||
encrypted := bob_client.encrypt(alice_keypair.recipient, bob_message)!
|
||||
|
||||
// Alice decrypts Bob's message
|
||||
decrypted := alice_client.decrypt(alice_keypair.identity, encrypted.ciphertext)!
|
||||
assert decrypted == bob_message
|
||||
```
|
||||
|
||||
### Document Signing
|
||||
|
||||
```v
|
||||
// Author
|
||||
mut author_client := crypt.new_age_client()!
|
||||
author_keypair := author_client.create_named_signing_keypair('document_author')!
|
||||
document := 'This is an official document'
|
||||
signature := author_client.sign_with_named_key('document_author', document)!
|
||||
|
||||
// Verifier
|
||||
mut verifier_client := crypt.new_age_client()!
|
||||
is_authentic := verifier_client.verify_with_named_key('document_author', document, signature.signature)!
|
||||
|
||||
Reference in New Issue
Block a user