From 304cdb59189e855c5f1b91c32719b92521e60d5a Mon Sep 17 00:00:00 2001 From: despiegk Date: Wed, 17 Sep 2025 07:22:27 +0200 Subject: [PATCH] ... --- examples/crypt/.gitignore | 1 + examples/crypt/crypt_example.vsh | 103 ++++++++++++++++++++++ install_v.sh | 6 +- lib/core/redisclient/redisclient_cmd.v | 29 +++++++ lib/crypt/herocrypt/herocrypt.v | 102 ++++++++++++++++++++++ lib/crypt/herocrypt/readme.md | 116 +++++++++++++++++++++++++ lib/crypt/herocrypt/specs.md | 84 ++++++++++++++++++ 7 files changed, 440 insertions(+), 1 deletion(-) create mode 100644 examples/crypt/.gitignore create mode 100755 examples/crypt/crypt_example.vsh create mode 100644 lib/core/redisclient/redisclient_cmd.v create mode 100644 lib/crypt/herocrypt/herocrypt.v create mode 100644 lib/crypt/herocrypt/readme.md create mode 100644 lib/crypt/herocrypt/specs.md diff --git a/examples/crypt/.gitignore b/examples/crypt/.gitignore new file mode 100644 index 00000000..dbb41895 --- /dev/null +++ b/examples/crypt/.gitignore @@ -0,0 +1 @@ +crypt_example \ No newline at end of file diff --git a/examples/crypt/crypt_example.vsh b/examples/crypt/crypt_example.vsh new file mode 100755 index 00000000..93568c59 --- /dev/null +++ b/examples/crypt/crypt_example.vsh @@ -0,0 +1,103 @@ +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run + +import freeflowuniverse.herolib.crypt.herocrypt +import time + +// Initialize the HeroCrypt client +// Assumes herodb is running on localhost:6381 +mut client := herocrypt.new_default()! + +println('HeroCrypt client initialized') + +// -- Stateless (Ephemeral) Workflow -- +println('\n--- Stateless (Ephemeral) Workflow ---') + +// 1. Generate ephemeral encryption keypair +println('Generating ephemeral encryption keypair...') +enc_keypair := client.gen_enc_keypair()! +recipient_pub := enc_keypair[0] +identity_sec := enc_keypair[1] +println(' Recipient Public Key: ${recipient_pub[..30]}...') +println(' Identity Secret Key: ${identity_sec[..30]}...') + +// 2. Encrypt a message +message := 'Hello, Stateless World!' +println('\nEncrypting message: "${message}"') +ciphertext := client.encrypt(recipient_pub, message)! +println(' Ciphertext: ${ciphertext[..30]}...') + +// 3. Decrypt the message +println('\nDecrypting ciphertext...') +decrypted_message := client.decrypt(identity_sec, ciphertext)! +println(' Decrypted Message: ${decrypted_message}') +assert decrypted_message == message + +// 4. Generate ephemeral signing keypair +println('\nGenerating ephemeral signing keypair...') +sign_keypair := client.gen_sign_keypair()! +verify_pub_b64 := sign_keypair[0] +sign_sec_b64 := sign_keypair[1] +println(' Verify Public Key (b64): ${verify_pub_b64[..30]}...') +println(' Sign Secret Key (b64): ${sign_sec_b64[..30]}...') + +// 5. Sign a message +sign_message := 'This message is signed.' +println('\nSigning message: "${sign_message}"') +signature := client.sign(sign_sec_b64, sign_message)! +println(' Signature: ${signature[..30]}...') + +// 6. Verify the signature +println('\nVerifying signature...') +is_valid := client.verify(verify_pub_b64, sign_message, signature)! +println(' Signature is valid: ${is_valid}') +assert is_valid + +// -- Key-Managed (Persistent, Named) Workflow -- +println('\n--- Key-Managed (Persistent, Named) Workflow ---') + +// 1. Generate and persist a named encryption keypair +enc_key_name := 'my_app_enc_key' +println('\nGenerating and persisting named encryption keypair: "${enc_key_name}"') +client.keygen(enc_key_name)! + +// 2. Encrypt a message by name +persistent_message := 'Hello, Persistent World!' +println('Encrypting message by name: "${persistent_message}"') +persistent_ciphertext := client.encrypt_by_name(enc_key_name, persistent_message)! +println(' Ciphertext: ${persistent_ciphertext[..30]}...') + +// 3. Decrypt the message by name +println('Decrypting ciphertext by name...') +decrypted_persistent_message := client.decrypt_by_name(enc_key_name, persistent_ciphertext)! +println(' Decrypted Message: ${decrypted_persistent_message}') +assert decrypted_persistent_message == persistent_message + +// 4. Generate and persist a named signing keypair +sign_key_name := 'my_app_sign_key' +println('\nGenerating and persisting named signing keypair: "${sign_key_name}"') +client.sign_keygen(sign_key_name)! + +// 5. Sign a message by name +persistent_sign_message := 'This persistent message is signed.' +println('Signing message by name: "${persistent_sign_message}"') +persistent_signature := client.sign_by_name(sign_key_name, persistent_sign_message)! +println(' Signature: ${persistent_signature[..30]}...') + +// 6. Verify the signature by name +println('Verifying signature by name...') +is_persistent_valid := client.verify_by_name(sign_key_name, persistent_sign_message, persistent_signature)! +println(' Signature is valid: ${is_persistent_valid}') +assert is_persistent_valid + +// // 7. List all stored keys +// println('\n--- Listing Stored Keys ---') +// keys := client.list_keys()! +// println('Stored keys: ${keys}') + +// // -- Clean up created keys -- +// println('\n--- Cleaning up ---') +// client.redis_client.del('age:enc:${enc_key_name}')! +// client.redis_client.del('age:sign:${sign_key_name}')! +// println('Cleaned up persistent keys.') + +println('\nHeroCrypt example finished successfully!') diff --git a/install_v.sh b/install_v.sh index 6f048b46..d579af67 100755 --- a/install_v.sh +++ b/install_v.sh @@ -1,4 +1,8 @@ -#!/bin/bash -e +#!/bin/bash + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" # Help function print_help() { diff --git a/lib/core/redisclient/redisclient_cmd.v b/lib/core/redisclient/redisclient_cmd.v new file mode 100644 index 00000000..fff6e29b --- /dev/null +++ b/lib/core/redisclient/redisclient_cmd.v @@ -0,0 +1,29 @@ +module redisclient + +import freeflowuniverse.herolib.data.resp + +pub fn (mut r Redis) cmd_str(script string, args []string) !string { + mut cmds := []string{} + cmds << script + cmds << args + return r.send_expect_strnil(cmds)! +} + +pub fn (mut r Redis) cmd_list_str(script string, args []string) ![]string { + mut cmds := []string{} + cmds << script + cmds << args + response := r.send_expect_list(cmds)! + mut result := []string{} + for item in response { + result << resp.get_redis_value(item) + } + return result +} + +pub fn (mut r Redis) cmd_int(script string, args []string) !int { + mut cmds := []string{} + cmds << script + cmds << args + return r.send_expect_int(cmds)! +} diff --git a/lib/crypt/herocrypt/herocrypt.v b/lib/crypt/herocrypt/herocrypt.v new file mode 100644 index 00000000..621c9eba --- /dev/null +++ b/lib/crypt/herocrypt/herocrypt.v @@ -0,0 +1,102 @@ +module herocrypt + +import freeflowuniverse.herolib.core.redisclient + +// HeroCrypt provides a client for HeroDB's AGE cryptography features. +pub struct HeroCrypt { +pub mut: + redis_client &redisclient.Redis +} + +// new returns a new HeroCrypt client +// url e.g. localhost:6381 (default) +// It pings the server to ensure a valid connection. +pub fn new(url_ string) !&HeroCrypt { + mut url := url_ + if url == '' { + url = 'localhost:6381' + } + mut redis := redisclient.new(url)! + redis.ping()! + return &HeroCrypt{ + redis_client: redis + } +} + +// new_default returns a new HeroCrypt client with the default URL. +pub fn new_default() !&HeroCrypt { + return new('') +} + +// -- Stateless (Ephemeral) Methods -- + +// gen_enc_keypair generates an ephemeral encryption keypair. +// Returns: [recipient_public_key, identity_secret_key] +pub fn (mut self HeroCrypt) gen_enc_keypair() ![]string { + return self.redis_client.cmd_list_str('AGE', ['GENENC']) +} + +// encrypt encrypts a message with a recipient public key. +pub fn (mut self HeroCrypt) encrypt(recipient_public_key string, message string) !string { + return self.redis_client.cmd_str('AGE', ['ENCRYPT', recipient_public_key, message]) +} + +// decrypt decrypts a ciphertext with an identity secret key. +pub fn (mut self HeroCrypt) decrypt(identity_secret_key string, ciphertext_b64 string) !string { + return self.redis_client.cmd_str('AGE', ['DECRYPT', identity_secret_key, ciphertext_b64]) +} + +// gen_sign_keypair generates an ephemeral signing keypair. +// Returns: [verify_public_key_b64, sign_secret_key_b64] +pub fn (mut self HeroCrypt) gen_sign_keypair() ![]string { + return self.redis_client.cmd_list_str('AGE', ['GENSIGN']) +} + +// sign signs a message with a signing secret key. +pub fn (mut self HeroCrypt) sign(sign_secret_key_b64 string, message string) !string { + return self.redis_client.cmd_str('AGE', ['SIGN', sign_secret_key_b64, message]) +} + +// verify verifies a signature with a public verification key. +pub fn (mut self HeroCrypt) verify(verify_public_key_b64 string, message string, signature_b64 string) !bool { + res := self.redis_client.cmd_str('AGE', ['VERIFY', verify_public_key_b64, message, signature_b64])! + return res == '1' +} + +// -- Key-Managed (Persistent, Named) Methods -- + +// keygen generates and persists a named encryption keypair. +pub fn (mut self HeroCrypt) keygen(name string) ![]string { + return self.redis_client.cmd_list_str('AGE', ['KEYGEN', name]) +} + +// encrypt_by_name encrypts a message using a named key. +pub fn (mut self HeroCrypt) encrypt_by_name(name string, message string) !string { + return self.redis_client.cmd_str('AGE', ['ENCRYPTNAME', name, message]) +} + +// decrypt_by_name decrypts a ciphertext using a named key. +pub fn (mut self HeroCrypt) decrypt_by_name(name string, ciphertext_b64 string) !string { + return self.redis_client.cmd_str('AGE', ['DECRYPTNAME', name, ciphertext_b64]) +} + +// sign_keygen generates and persists a named signing keypair. +pub fn (mut self HeroCrypt) sign_keygen(name string) ![]string { + return self.redis_client.cmd_list_str('AGE', ['SIGNKEYGEN', name]) +} + +// sign_by_name signs a message using a named signing key. +pub fn (mut self HeroCrypt) sign_by_name(name string, message string) !string { + return self.redis_client.cmd_str('AGE', ['SIGNNAME', name, message]) +} + +// verify_by_name verifies a signature with a named verification key. +pub fn (mut self HeroCrypt) verify_by_name(name string, message string, signature_b64 string) !bool { + res := self.redis_client.cmd_str('AGE', ['VERIFYNAME', name, message, signature_b64])! + return res == '1' +} + +// // list_keys lists all stored AGE keys. +// pub fn (mut self HeroCrypt) list_keys() ![]string { +// return self.redis_client.cmd_list_str('AGE', ['LIST']) +// } diff --git a/lib/crypt/herocrypt/readme.md b/lib/crypt/herocrypt/readme.md new file mode 100644 index 00000000..57124e30 --- /dev/null +++ b/lib/crypt/herocrypt/readme.md @@ -0,0 +1,116 @@ +# HeroCrypt: Effortless Cryptography with HeroDB + +HeroCrypt is a library that provides a simple and secure way to handle encryption and digital signatures by leveraging the power of [HeroDB](https://git.ourworld.tf/herocode/herodb). It abstracts the underlying complexity of cryptographic operations, allowing you to secure your application's data with minimal effort. + +## What is HeroDB? + +HeroDB is a high-performance, Redis-compatible database that offers built-in support for advanced cryptographic operations using the [AGE encryption format](https://age-encryption.org/). This integration makes it an ideal backend for applications requiring robust, end-to-end security. + +## Core Features of HeroCrypt + +- **Encryption & Decryption**: Securely encrypt and decrypt data. +- **Digital Signatures**: Sign and verify messages to ensure their integrity and authenticity. +- **Flexible Key Management**: Choose between two modes for managing your cryptographic keys: + 1. **Key-Managed (Server-Side)**: Let HeroDB manage your keys. Keys are stored securely within the database and are referenced by a name. This is the recommended approach for simplicity and centralized key management. + 2. **Stateless (Client-Side)**: Manage your own keys on the client side. You pass the key material with every cryptographic operation. This mode is for advanced users who require full control over their keys. + + +## To start a db see + +https://git.ourworld.tf/herocode/herodb + +to do: + +```bash +hero git pull https://git.ourworld.tf/herocode/herodb +~/code/git.ourworld.tf/herocode/herodb/run.sh +``` + +## Key-Managed Mode (Recommended) + +In this mode, HeroDB generates and stores the keypairs for you. You only need to provide a name for your key. + +### Encryption + +```v +import freeflowuniverse.herolib.crypt.herocrypt + +mut client := herocrypt.new_default()! + +// Generate and persist a named encryption keypair +client.keygen('my_app_key')! + +// Encrypt a message +message := 'This is a secret message.' +ciphertext := client.encrypt_by_name('my_app_key', message)! + +// Decrypt the message +decrypted_message := client.decrypt_by_name('my_app_key', ciphertext)! +assert decrypted_message == message +``` + +### Signing + +```v +import freeflowuniverse.herolib.crypt.herocrypt + +mut client := herocrypt.new_default()! + +// Generate and persist a named signing keypair +client.sign_keygen('my_signer_key')! + +// Sign a message +message := 'This message needs to be signed.' +signature := client.sign_by_name('my_signer_key', message)! + +// Verify the signature +is_valid := client.verify_by_name('my_signer_key', message, signature)! +assert is_valid +``` + +## Stateless Mode (Advanced) + +In this mode, you are responsible for generating and managing your own keys. + +### Encryption + +```v +import freeflowuniverse.herolib.crypt.herocrypt + +mut client := herocrypt.new_default()! + +// Generate an ephemeral encryption keypair +keypair := client.gen_enc_keypair()! +recipient_pub := keypair[0] +identity_sec := keypair[1] + +// Encrypt a message +message := 'This is a secret message.' +ciphertext := client.encrypt(recipient_pub, message)! + +// Decrypt the message +decrypted_message := client.decrypt(identity_sec, ciphertext)! +assert decrypted_message == message +``` + +### Signing + +```v +import freeflowuniverse.herolib.crypt.herocrypt + +mut client := herocrypt.new_default()! + +// Generate an ephemeral signing keypair +keypair := client.gen_sign_keypair()! +verify_pub_b64 := keypair[0] +sign_sec_b64 := keypair[1] + +// Sign a message +message := 'This message needs to be signed.' +signature := client.sign(sign_sec_b64, message)! + +// Verify the signature +is_valid := client.verify(verify_pub_b64, message, signature)! +assert is_valid + +``` \ No newline at end of file diff --git a/lib/crypt/herocrypt/specs.md b/lib/crypt/herocrypt/specs.md new file mode 100644 index 00000000..a784cd5f --- /dev/null +++ b/lib/crypt/herocrypt/specs.md @@ -0,0 +1,84 @@ +# HeroCrypt Technical Specifications + +This document provides the low-level technical details for the HeroCrypt library, including the underlying Redis commands used to interact with HeroDB's AGE cryptography features. + +## Communication Protocol + +HeroCrypt communicates with HeroDB using the standard Redis protocol. All commands are sent as RESP (Redis Serialization Protocol) arrays. + +## Stateless (Ephemeral) AGE Operations + +These commands are used when the client manages its own keys. + +### Encryption + +- **`AGE GENENC`**: Generates an ephemeral encryption keypair. + - **Returns**: An array containing the recipient (public key) and the identity (secret key). + +- **`AGE ENCRYPT `**: Encrypts a message using the recipient's public key. + - **``**: The public key (`age1...`). + - **``**: The plaintext message to encrypt. + - **Returns**: A base64-encoded ciphertext. + +- **`AGE DECRYPT `**: Decrypts a ciphertext using the identity (secret key). + - **``**: The secret key (`AGE-SECRET-KEY-1...`). + - **``**: The base64-encoded ciphertext. + - **Returns**: The decrypted plaintext message. + +### Signing + +- **`AGE GENSIGN`**: Generates an ephemeral signing keypair. + - **Returns**: An array containing the public verification key (base64) and the secret signing key (base64). + +- **`AGE SIGN `**: Signs a message with the secret key. + - **``**: The base64-encoded secret signing key. + - **``**: The message to sign. + - **Returns**: A base64-encoded signature. + +- **`AGE VERIFY `**: Verifies a signature. + - **``**: The base64-encoded public verification key. + - **``**: The original message. + - **``**: The base64-encoded signature. + - **Returns**: `1` if the signature is valid, `0` otherwise. + +## Key-Managed (Persistent, Named) AGE Operations + +These commands are used when HeroDB manages the keys. + +### Encryption + +- **`AGE KEYGEN `**: Generates and persists a named encryption keypair. + - **``**: A unique name for the key. + - **Returns**: An array containing the recipient and identity (for initial export, if needed). + +- **`AGE ENCRYPTNAME `**: Encrypts a message using a named key. + - **``**: The name of the stored key. + - **``**: The plaintext message. + - **Returns**: A base64-encoded ciphertext. + +- **`AGE DECRYPTNAME `**: Decrypts a ciphertext using a named key. + - **``**: The name of the stored key. + - **``**: The base64-encoded ciphertext. + - **Returns**: The decrypted plaintext message. + +### Signing + +- **`AGE SIGNKEYGEN `**: Generates and persists a named signing keypair. + - **``**: A unique name for the key. + - **Returns**: An array containing the public and secret keys (for initial export). + +- **`AGE SIGNNAME `**: Signs a message using a named key. + - **``**: The name of the stored key. + - **``**: The message to sign. + - **Returns**: A base64-encoded signature. + +- **`AGE VERIFYNAME `**: Verifies a signature using a named key. + - **``**: The name of the stored key. + - **``**: The original message. + - **``**: The base64-encoded signature. + - **Returns**: `1` if the signature is valid, `0` otherwise. + +### Key Listing + +- **`AGE LIST`**: Lists all stored AGE keys. + - **Returns**: An array of key names. \ No newline at end of file