From a5dda01654457e95e5d7a3dd08cc08c31ae92adb Mon Sep 17 00:00:00 2001 From: kalipso Date: Tue, 13 Aug 2024 15:18:22 +0200 Subject: [PATCH] WIP crypto prototyping --- cmd/ppass/ppass.go | 55 ++++++++++++++++- crypto/age/age.go | 113 ++++++++++++++++++++++++++++++++++ crypto/crypto.go | 30 ++------- crypto/rsa/rsa.go | 147 +++++++++++++++++++++++++++++++++++++++++++++ storage/storage.go | 19 +++++- 5 files changed, 335 insertions(+), 29 deletions(-) create mode 100644 crypto/age/age.go create mode 100644 crypto/rsa/rsa.go diff --git a/cmd/ppass/ppass.go b/cmd/ppass/ppass.go index d8b53c1..77b76db 100644 --- a/cmd/ppass/ppass.go +++ b/cmd/ppass/ppass.go @@ -18,6 +18,7 @@ import ( logging "github.com/ipfs/go-log/v2" "github.com/k4lipso/pentapass/storage" + "github.com/k4lipso/pentapass/crypto/age" "github.com/k4lipso/pentapass/crypto" ) @@ -33,12 +34,24 @@ var ( func main() { - crypto.Encrypt() - flag.Parse() ctx := context.Background() data := *dbPath + key, err := age.LoadOrGenerateKeys(*dbPath + "/age.key") + + if err != nil { + panic(err) + } + + fmt.Printf("AgeKey: %s\n", key.String()) + fmt.Printf("AgePublicKey: %s\n", key.Recipient().String()) + + cipher, err := age.Encrypt([]byte("Test Message"), []string{key.Recipient().String()}) + fmt.Printf("Encrypted: %s\n", cipher) + decrypted, err := age.Decrypt(cipher, key) + fmt.Printf("Decrypted: %s\n", decrypted) + h, dht, err := storage.SetupLibp2pHost(ctx, *dbPath) pid := h.ID().String() @@ -77,6 +90,7 @@ func main() { Host: h, Ipfs: ipfs, PubSub: ps, + Key: key, } Cfg := storage.NewConfig() @@ -214,6 +228,43 @@ Commands: } fmt.Printf("[%s] -> %s\n", k, string(v)) + case "generate": + if len(fields) < 3 { + fmt.Println("generate ") + fmt.Println("> ") + continue + } + + namespace := fields[1] + + val, ok := Namespaces[namespace] + + if !ok { + fmt.Println("Namespace does not exist") + continue + } + + service := fields[2] + password := crypto.NewPassword() + password.Service = service + + data, err := password.ToJson() + if err != nil { + printErr(err) + continue + } + + encryptedPassword, err := age.Encrypt(data, []string{key.Recipient().String()}) + if err != nil { + printErr(err) + continue + } + + err = val.Put(password.Id.String(), string(encryptedPassword)) + if err != nil { + printErr(err) + continue + } case "put": if len(fields) < 4 { fmt.Println("put ") diff --git a/crypto/age/age.go b/crypto/age/age.go new file mode 100644 index 0000000..6e4d906 --- /dev/null +++ b/crypto/age/age.go @@ -0,0 +1,113 @@ +package age + +import ( + "fmt" + "os" + "io" + "bytes" + "errors" + + "filippo.io/age" +) + +var ( + ageKeyFileName = "age.key" +) + +func GenerateAgeKey(filename string) (*age.X25519Identity, error) { + // Generate a new X25519 identity (private key) + identity, err := age.GenerateX25519Identity() + if err != nil { + return nil, fmt.Errorf("failed to generate identity: %w", err) + } + + // Convert the identity to its string representation + privateKey := identity.String() + + // Write the private key to a file + err = os.WriteFile(filename, []byte(privateKey), 0600) + if err != nil { + return nil, fmt.Errorf("failed to save private key to file: %w", err) + } + + fmt.Printf("Private key saved to %s\n", filename) + return identity, nil +} + +func LoadAgeKey(filename string) (*age.X25519Identity, error) { + // Read the private key from the file + privateKeyBytes, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to read private key from file: %w", err) + } + + // Parse the private key + identity, err := age.ParseX25519Identity(string(privateKeyBytes)) + if err != nil { + return nil, fmt.Errorf("failed to parse private key: %w", err) + } + + return identity, nil +} + +func LoadOrGenerateKeys(filename string) (*age.X25519Identity, error) { + _, err := os.Open(filename) + if errors.Is(err, os.ErrNotExist) { + fmt.Println("Not Exists - Generate Keys") + return GenerateAgeKey(filename) + } + + return LoadAgeKey(filename) +} + +func Decrypt(encryptedData []byte, identity *age.X25519Identity) ([]byte, error) { + // Create a new decryptor using the recipient's identity + decryptor, err := age.Decrypt(bytes.NewReader(encryptedData), identity) + if err != nil { + return nil, fmt.Errorf("failed to create decryptor: %w", err) + } + + // Read the decrypted data + decryptedData, err := io.ReadAll(decryptor) + if err != nil { + return nil, fmt.Errorf("failed to decrypt data: %w", err) + } + + return decryptedData, nil +} + + +func Encrypt(data []byte, recipientKeys []string) ([]byte, error) { + var recipients []age.Recipient + + // Parse all recipient keys + for _, key := range recipientKeys { + recipient, err := age.ParseX25519Recipient(key) + if err != nil { + return nil, fmt.Errorf("failed to parse recipient key: %w", err) + } + recipients = append(recipients, recipient) + } + + // Create a new buffer to hold the encrypted data + var encryptedData bytes.Buffer + + // Create a new age encryptor for the recipients + encryptor, err := age.Encrypt(&encryptedData, recipients...) + if err != nil { + return nil, fmt.Errorf("failed to create encryptor: %w", err) + } + + // Write the data to the encryptor + _, err = encryptor.Write(data) + if err != nil { + return nil, fmt.Errorf("failed to encrypt data: %w", err) + } + + // Close the encryptor to finalize the encryption process + if err := encryptor.Close(); err != nil { + return nil, fmt.Errorf("failed to finalize encryption: %w", err) + } + + return encryptedData.Bytes(), nil +} diff --git a/crypto/crypto.go b/crypto/crypto.go index 7dfdaca..b4fc4fb 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -1,12 +1,9 @@ package crypto import ( - "fmt" - "bytes" - "io" "encoding/json" - "filippo.io/age" + "github.com/google/uuid" ) type Password struct { @@ -15,6 +12,7 @@ type Password struct { Username string `json:"Username"` Password string `json:"Password"` Tags []string `json:"Tags"` + Id uuid.UUID `json:"Id"` } func (p *Password) ToJson() ([]byte, error) { @@ -32,25 +30,9 @@ func GetPasswordFromJson(b []byte) (Password, error) { return result, nil } -func Encrypt() { - publicKey := "age1cy0su9fwf3gf9mw868g5yut09p6nytfmmnktexz2ya5uqg9vl9sss4euqm" - recipient, err := age.ParseX25519Recipient(publicKey) - if err != nil { - fmt.Printf("Failed to parse public key %q: %v", publicKey, err) +func NewPassword() *Password { + return &Password{ + Id: uuid.New(), } - - out := &bytes.Buffer{} - - w, err := age.Encrypt(out, recipient) - if err != nil { - fmt.Printf("Failed to create encrypted file: %v\n", err) - } - if _, err := io.WriteString(w, "Black lives matter."); err != nil { - fmt.Printf("Failed to write to encrypted file: %v\n", err) - } - if err := w.Close(); err != nil { - fmt.Printf("Failed to close encrypted file: %v\n", err) - } - - fmt.Printf("Encrypted file size: %d\n", out.Len()) } + diff --git a/crypto/rsa/rsa.go b/crypto/rsa/rsa.go new file mode 100644 index 0000000..a91c219 --- /dev/null +++ b/crypto/rsa/rsa.go @@ -0,0 +1,147 @@ +package rsa + +import ( + "fmt" + "errors" + "os" + "log" + + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" +) + + +var ( + privateKeyName = "rsa_private_key.pem" + publicKeyName = "rsa_public_key.pem" +) + +func GenerateKeys(path string) (*rsa.PrivateKey, *rsa.PublicKey, error) { + // Generate a new RSA key pair + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + // Save the private key to a file + privateKeyPath := path + privateKeyName + privateKeyFile, err := os.Create(privateKeyPath) + if err != nil { + return nil, nil, err + } + defer privateKeyFile.Close() + + privateKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + }) + _, err = privateKeyFile.Write(privateKeyPEM) + if err != nil { + return nil, nil, err + } + fmt.Printf("Private key saved to %s\n", privateKeyPath) + + // Extract and save the public key + publicKeyPath := path + publicKeyName + publicKeyFile, err := os.Create(publicKeyPath) + if err != nil { + return nil, nil, err + } + defer publicKeyFile.Close() + + publicKey := &privateKey.PublicKey + publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return nil, nil, err + } + + publicKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: publicKeyBytes, + }) + _, err = publicKeyFile.Write(publicKeyPEM) + if err != nil { + return nil, nil, err + } + fmt.Printf("Public key saved to %s\n", publicKeyPath) + + return privateKey, publicKey, nil +} + +func LoadKeys(path string) (*rsa.PrivateKey, *rsa.PublicKey, error) { + // Load private key from file + privateKeyPath := path + privateKeyName + privateKeyData, err := os.ReadFile(privateKeyPath) + if err != nil { + log.Fatalf("Failed to read private key: %v", err) + return nil, nil, err + } + + block, _ := pem.Decode(privateKeyData) + if block == nil || block.Type != "RSA PRIVATE KEY" { + log.Fatalf("Failed to decode PEM block containing private key") + return nil, nil, err + } + privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + log.Fatalf("Failed to parse private key: %v", err) + return nil, nil, err + } + + // Load public key from file + publicKeyPath := path + publicKeyName + publicKeyData, err := os.ReadFile(publicKeyPath) + if err != nil { + log.Fatalf("Failed to read public key: %v", err) + return nil, nil, err + } + + block, _ = pem.Decode(publicKeyData) + if block == nil || block.Type != "RSA PUBLIC KEY" { + log.Fatalf("Failed to decode PEM block containing public key") + return nil, nil, err + } + publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + log.Fatalf("Failed to parse public key: %v", err) + return nil, nil, err + } + publicKey := publicKeyInterface.(*rsa.PublicKey) + + return privateKey, publicKey, nil +} + + +func LoadOrGenerateKeys(path string) (*rsa.PrivateKey, *rsa.PublicKey, error) { + _, err := os.Open(path + privateKeyName) + if errors.Is(err, os.ErrNotExist) { + fmt.Println("Not Exists - Generate Keys") + return GenerateKeys(path) + } + + return LoadKeys(path) +} + + +func Encrypt(data []byte, key *rsa.PublicKey) ([]byte, error) { + ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, key, data) + if err != nil { + log.Fatalf("Failed to encrypt: %v", err) + return nil, err + } + + return ciphertext, nil +} + +func Decrypt(data []byte, key *rsa.PrivateKey) ([]byte, error) { + decrypted, err := rsa.DecryptPKCS1v15(rand.Reader, key, data) + if err != nil { + log.Fatalf("Failed to decrypt: %v", err) + return nil, err + } + + return decrypted, nil + +} diff --git a/storage/storage.go b/storage/storage.go index 461d403..a0dff93 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -31,6 +31,9 @@ import ( crypto "github.com/libp2p/go-libp2p/core/crypto" routed "github.com/libp2p/go-libp2p/p2p/host/routed" logging "github.com/ipfs/go-log/v2" + + agelib "filippo.io/age" + "github.com/k4lipso/pentapass/crypto/age" ) var ( @@ -99,7 +102,7 @@ func SetupLibp2pHost(ctx context.Context, dbPath string) (host host.Host, dht *d type Peer struct { Id string - //Todo: AgeKey Key + Key string } type NamespaceConfig struct { @@ -200,6 +203,7 @@ type Namespace struct { //Registry *sharedKeyRegistry CancelFunc context.CancelFunc ctx context.Context + Key *agelib.X25519Identity } @@ -237,7 +241,15 @@ func (n *Namespace) List() { printErr(err) continue } - fmt.Printf("[%s] -> %s\n", r.Key, string(r.Value)) + + val, err := age.Decrypt(r.Value, n.Key) + + if err != nil { + printErr(err) + continue + } + + fmt.Printf("[%s] -> %s\n", r.Key, string(val)) } } @@ -252,6 +264,7 @@ type StorageHandler struct { Host host.Host Ipfs *ipfslite.Peer PubSub *pubsub.PubSub + Key *agelib.X25519Identity } func IsTrustedPeer(ctx context.Context, id peer.ID, namespace string) bool { @@ -321,7 +334,7 @@ func CreateNamespace(ID string, storageHandler StorageHandler) (*Namespace, erro return nil, err } - return &Namespace{ID: ID, Datastore: crdt, CancelFunc: psubCancel, ctx: storageHandler.Ctx}, nil + return &Namespace{ID: ID, Datastore: crdt, CancelFunc: psubCancel, ctx: storageHandler.Ctx, Key: storageHandler.Key}, nil }