This commit is contained in:
2025-09-17 06:12:57 +02:00
parent ee11b07ffb
commit 5d4974e38a
24 changed files with 4 additions and 383 deletions

View File

@@ -1,17 +0,0 @@
security add-internet-password -s git.threefold.info -a despiegk -w mypassword
-s: The server (e.g., git.threefold.info).
-a: The account or username (e.g., despiegk).
-w: The password (e.g., mypassword).
security find-internet-password -s git.threefold.info -w
security delete-internet-password -s git.threefold.info
git config --global credential.helper osxkeychain

View File

@@ -1,202 +0,0 @@
module keysafe
import freeflowuniverse.herolib.core.pathlib
// import freeflowuniverse.herolib.data.mnemonic // buggy for now
import encoding.hex
import libsodium
import json
import os
import freeflowuniverse.herolib.ui.console
/*
* KeysSafe
*
* This module implement a secure keys manager.
*
* When loading a keysafe object, you can specify a directory and a secret.
* In that directory, a file called '.keys' will be created and encrypted using
* the 'secret' provided (AES-CBC).
*
* Content of that file is a JSON dictionnary of key-name and it's mnemonic,
* a single mnemonic is enough to derivate ed25519 and x25519 keys.
*
* When loaded, private/public signing key and public/private encryption keys
* are loaded and ready to be used.
*
* key_generate_add() generate a new key and store is as specified name
* key_import_add() import an existing key based on it's seed and specified name
*
*/
pub struct KeysSafe {
pub mut:
path pathlib.Path // file path of keys
loaded bool // flag to know if keysafe is loaded or loading
secret string // secret to encrypt local file
keys map[string]PrivKey // list of keys
}
pub struct PersistantKeysSafe {
pub mut:
keys map[string]string // store name/mnemonics only
}
// note: root key needs to be 'SigningKey' from libsodium
// from that SigningKey we can derivate PrivateKey needed to encrypt
pub fn keysafe_get(path0 string, secret string) !KeysSafe {
mut path := pathlib.get_file(path: path0 + '/.keys', create: true)!
mut safe := KeysSafe{
path: path
secret: secret
}
if os.exists(path.absolute()) {
console.print_debug('[+] key file already exists, loading it')
safe.load()
}
safe.loaded = true
return safe
}
// for testing purposes you can generate multiple keys
pub fn (mut ks KeysSafe) generate_multiple(count int) ! {
for i in 0 .. count {
ks.key_generate_add('name_${i}')!
}
}
// generate a new key is just importing a key with a random seed
pub fn (mut ks KeysSafe) key_generate_add(name string) !PrivKey {
mut seed := []u8{}
// generate a new random seed
for _ in 0 .. 32 {
seed << u8(libsodium.randombytes_random())
}
return ks.key_import_add(name, seed)
}
fn internal_key_encode(key []u8) string {
return '0x' + hex.encode(key)
}
fn internal_key_decode(key string) []u8 {
parsed := hex.decode(key.substr(2, key.len)) or { panic(err) }
return parsed
}
// import based on an existing seed
pub fn (mut ks KeysSafe) key_import_add(name string, seed []u8) !PrivKey {
if name in ks.keys {
return error('A key with that name already exists')
}
mnemonic := internal_key_encode(seed) // mnemonic(seed)
signkey := libsodium.new_ed25519_signing_key_seed(seed)
privkey := libsodium.new_private_key_from_signing_ed25519(signkey)
// console.print_debug("===== SEED ====")
// console.print_debug(seed)
// console.print_debug(mnemonic)
pk := PrivKey{
name: name
mnemonic: mnemonic
privkey: privkey
signkey: signkey
}
ks.key_add(pk)!
return pk
}
pub fn (mut ks KeysSafe) get(name string) !PrivKey {
if !ks.exists(name) {
return error('key not found')
}
return ks.keys[name]
}
pub fn (mut ks KeysSafe) exists(name string) bool {
return name in ks.keys
}
pub fn (mut ks KeysSafe) key_add(pk PrivKey) ! {
ks.keys[pk.name] = pk
// do not persist keys if keysafe is not loaded
// this mean we are probably loading keys from file
if ks.loaded {
ks.persist()
}
}
pub fn (mut ks KeysSafe) persist() {
console.print_debug('[+] saving keys to ${ks.path.absolute()}')
serialized := ks.serialize()
// console.print_debug(serialized)
encrypted := symmetric_encrypt_blocks(serialized.bytes(), ks.secret)
mut f := os.create(ks.path.absolute()) or { panic(err) }
f.write(encrypted) or { panic(err) }
f.close()
}
pub fn (mut ks KeysSafe) serialize() string {
mut pks := PersistantKeysSafe{}
// serializing mnemonics only
for key, val in ks.keys {
pks.keys[key] = val.mnemonic
}
export := json.encode(pks)
return export
}
pub fn (mut ks KeysSafe) load() {
console.print_debug('[+] loading keys from ${ks.path.absolute()}')
mut f := os.open(ks.path.absolute()) or { panic(err) }
// read encrypted file
filesize := os.file_size(ks.path.absolute())
mut encrypted := []u8{len: int(filesize)}
f.read(mut encrypted) or { panic(err) }
f.close()
// decrypt file using ks secret
plaintext := symmetric_decrypt_blocks(encrypted, ks.secret)
// (try to) decode the json and load keys
ks.deserialize(plaintext.bytestr())
}
pub fn (mut ks KeysSafe) deserialize(input string) {
mut pks := json.decode(PersistantKeysSafe, input) or {
console.print_debug('Failed to decode json, wrong secret or corrupted file: ${err}')
return
}
// serializing mnemonics only
for name, mnemo in pks.keys {
console.print_debug('[+] loading key: ${name}')
seed := internal_key_decode(mnemo) // mnemonic.parse(mnemo)
// console.print_debug("==== SEED ====")
// console.print_debug(mnemo)
// console.print_debug(seed)
ks.key_import_add(name, seed) or { panic(err) }
}
// console.print_debug(ks)
}

View File

@@ -1,45 +0,0 @@
module keysafe
import libsodium
import encoding.hex
pub struct PrivKey {
pub:
name string
mnemonic string
signkey libsodium.SigningKey // master key
privkey libsodium.PrivateKey // derivated from master key
}
pub fn key_encode(key []u8) string {
return '0x' + hex.encode(key)
}
// retrieve the master public key from PrivKey object
// this is the public key as need to be shared to a remote user to verify that we signed with our private key
// is shared as hex key in string format (66 chars)
pub fn (key PrivKey) master_public() string {
x := key.signkey.verify_key.public_key
mut target := []u8{len: x.len}
unsafe { C.memcpy(target.data, &x[0], x.len) }
return key_encode(target)
}
// sign data with our signing key
// data is bytestr
// output is []u8 bytestring
// to get bytes from string do: mystring.bytes().
pub fn (key PrivKey) sign(data []u8) []u8 {
return key.signkey.sign(data)
}
// sign data with our signing key.
// data is bytestr.
// output is hex string.
// to get bytes from string do: mystring.bytes().
// size of output is ?
pub fn (key PrivKey) sign_hex(data []u8) string {
return hex.encode(key.sign(data))
}

View File

@@ -1,41 +0,0 @@
module keysafe
import libsodium
import encoding.hex
pub struct PubKey {
pub:
name string
source PrivKey // ourself (private key, to sign message)
remote []u8 // target public key (to encrypt)
}
pub fn pubkey_new(name string, myself PrivKey, remote string) !PubKey {
parsed := hex.decode(remote.substr(2, remote.len))!
// convert SigningKey to PrivateKey (ed25519 > x25519)
// to allow encryption and decryption
target := []u8{len: libsodium.public_key_size}
libsodium.crypto_sign_ed25519_pk_to_curve25519(target.data, parsed[0])
return PubKey{
name: name
source: myself
remote: target
}
}
// this will encrypt bytes so that only the owner of this pubkey can decrypt it
pub fn (key PubKey) encrypt(data []u8) ![]u8 {
box := libsodium.new_box(key.source.privkey, key.remote)
return box.encrypt(data)
}
// verify a signed data
pub fn (key PubKey) decrypt(data []u8) []u8 {
box := libsodium.new_box(key.source.privkey, key.remote)
decrypted := box.decrypt(data)
return decrypted
}

View File

@@ -1,47 +0,0 @@
# Keysafe
A safe implementation to help you sign, encrypt, decrypt and store your keys locally.
## Internals
When loading a keysafe object, you can specify a `directory` and a `secret`.
In that directory, a file called `.keys` will be created and encrypted using
the `secret` provided (`AES-CBC`).
Content of that file is a JSON dictionnary of key-name and it's mnemonic,
a single mnemonic is enough to derivate `ed25519` and `x25519` keys.
When loaded, private/public signing key and public/private encryption keys
are loaded and ready to be used.
- `key_generate_add()` generate a new key and store is as specified name
- `key_import_add()` import an existing key based on it's seed and specified name
## Example
```v
module main
import freeflowuniverse.herolib.crypt.keysafe
fn main() {
mut ks := keysafe.keysafe_get("/tmp/", "helloworld")!
println(ks)
ks.key_generate_add("demo") or { println(err) }
println(ks)
if ks.exists("demo") {
println("key demo exists")
}
}
```
## Keys
Note about keys: when generating a new key, the "master key" is a SigningKey Ed25519 key. From
that key, we can derivate a PrivateKey (encrypting key) X25519.
We can convert public-key only as well. On public key exchange, please always exchange the public SigningKey
(aka the master key for us). Based on that SignigKey, we can derivate the Encyption PublicKey and KeysSafe
does it for you.

View File

@@ -1,30 +0,0 @@
module keysafe
import libsodium
import encoding.hex
pub struct VerifyKey {
pub:
name string
remote libsodium.VerifyKey // target public master key
}
// remote is the public master key which needs to verify
pub fn verifykey_new(name string, remote string) !VerifyKey {
parsed := hex.decode(remote.substr(2, remote.len))!
v := [libsodium.public_key_size]u8{}
unsafe { C.memcpy(&v[0], parsed.data, libsodium.public_key_size) }
return VerifyKey{
name: name
remote: libsodium.VerifyKey{
public_key: v
}
}
}
// verify a signed data, returns true if signature is correct
pub fn (key VerifyKey) verify(data []u8) bool {
return key.remote.verify(data)
}