...
This commit is contained in:
Binary file not shown.
@@ -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
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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.
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,10 @@ for the doc (html endpoint)
|
|||||||
- the template uses the openrpc spec obj comes from lib/schemas/openrpc and lib/schemas/jsonrpc for the schema's
|
- the template uses the openrpc spec obj comes from lib/schemas/openrpc and lib/schemas/jsonrpc for the schema's
|
||||||
- so first fo the spec decode to the object from schemas/openrpc then use this obj to populate the template
|
- so first fo the spec decode to the object from schemas/openrpc then use this obj to populate the template
|
||||||
- in the template make a header 1 for each rootobject e.g. calendar, then dense show the methods with directly in the method a dense representation of the params and return
|
- in the template make a header 1 for each rootobject e.g. calendar, then dense show the methods with directly in the method a dense representation of the params and return
|
||||||
- each object e.g. pub fn (self Comment) description(methodname string) string { has a description and pub fn (self Comment) example(methodname string) (string, string) wich returns description per method, if not filled in then its for the full rootobject, and also example, make sure to use those in the template for the documentation
|
- each object has a method to show description and example (returns string)
|
||||||
|
- e.g. fn (self Comment) description(methodname string) string
|
||||||
|
- e.g. fn (self Comment) example(methodname string) (string, string) wich returns description per method
|
||||||
|
|
||||||
- the template is markdown, we will have to use a .md to .html conversion (best to do in browser itself) and get .md from the webserver directly and convert
|
- the template is markdown, we will have to use a .md to .html conversion (best to do in browser itself) and get .md from the webserver directly and convert
|
||||||
- the purpose is to have a very nice documentation done per object so we know what the object does, and how to use it
|
- the purpose is to have a very nice documentation done per object so we know what the object does, and how to use it
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user