From 3f4ed25a8399f28ff22872f49cfe5efa39d87200 Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Thu, 2 Sep 2021 13:31:23 +0200 Subject: [PATCH 1/6] allow empty Client for Registration --- api/auth.go | 4 ++++ api/client.go | 34 +++++++++++++++++++--------------- api/encryption.go | 17 ++++++++++++++++- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/api/auth.go b/api/auth.go index a128bff..5882f87 100644 --- a/api/auth.go +++ b/api/auth.go @@ -57,6 +57,10 @@ func (c *Client) CheckSession(ctx context.Context) bool { // Login gets a Session and CSRF Token from Passbolt and Stores them in the Clients Cookie Jar func (c *Client) Login(ctx context.Context) error { + if c.userPrivateKey == "" { + return fmt.Errorf("Client has no Private Key") + } + privateKeyObj, err := crypto.NewKeyFromArmored(c.userPrivateKey) if err != nil { return fmt.Errorf("Parsing User Private Key: %w", err) diff --git a/api/client.go b/api/client.go index 2a7833d..210e8eb 100644 --- a/api/client.go +++ b/api/client.go @@ -36,6 +36,8 @@ type Client struct { // NewClient Returns a new Passbolt Client. // if httpClient is nil http.DefaultClient will be used. // if UserAgent is "" "goPassboltClient/1.0" will be used. +// if UserPrivateKey is "" Key Setup is Skipped to Enable using the Client for User Registration, Most other function will be broken. +// After Registration a new Client Should be Created. func NewClient(httpClient *http.Client, UserAgent, BaseURL, UserPrivateKey, UserPassword string) (*Client, error) { if httpClient == nil { httpClient = http.DefaultClient @@ -49,22 +51,24 @@ func NewClient(httpClient *http.Client, UserAgent, BaseURL, UserPrivateKey, User return nil, fmt.Errorf("Parsing Base URL: %w", err) } - // Verify that the Given Privatekey and Password are valid and work Together - privateKeyObj, err := crypto.NewKeyFromArmored(UserPrivateKey) - if err != nil { - return nil, fmt.Errorf("Unable to Create Key From UserPrivateKey string: %w", err) - } - unlockedKeyObj, err := privateKeyObj.Unlock([]byte(UserPassword)) - if err != nil { - return nil, fmt.Errorf("Unable to Unlock UserPrivateKey using UserPassword: %w", err) - } - privateKeyRing, err := crypto.NewKeyRing(unlockedKeyObj) - if err != nil { - return nil, fmt.Errorf("Unable to Create a new Key Ring using the unlocked UserPrivateKey: %w", err) - } + // Verify that the Given Privatekey and Password are valid and work Together if we were provieded one + if UserPrivateKey != "" { + privateKeyObj, err := crypto.NewKeyFromArmored(UserPrivateKey) + if err != nil { + return nil, fmt.Errorf("Unable to Create Key From UserPrivateKey string: %w", err) + } + unlockedKeyObj, err := privateKeyObj.Unlock([]byte(UserPassword)) + if err != nil { + return nil, fmt.Errorf("Unable to Unlock UserPrivateKey using UserPassword: %w", err) + } + privateKeyRing, err := crypto.NewKeyRing(unlockedKeyObj) + if err != nil { + return nil, fmt.Errorf("Unable to Create a new Key Ring using the unlocked UserPrivateKey: %w", err) + } - // Cleanup Secrets - privateKeyRing.ClearPrivateParams() + // Cleanup Secrets + privateKeyRing.ClearPrivateParams() + } // Create Client Object c := &Client{ diff --git a/api/encryption.go b/api/encryption.go index 8193115..e87537e 100644 --- a/api/encryption.go +++ b/api/encryption.go @@ -1,19 +1,34 @@ package api -import "github.com/ProtonMail/gopenpgp/v2/helper" +import ( + "fmt" + + "github.com/ProtonMail/gopenpgp/v2/helper" +) // EncryptMessage encrypts a message using the users public key and then signes the message using the users private key func (c *Client) EncryptMessage(message string) (string, error) { + if c.userPrivateKey == "" { + return "", fmt.Errorf("Client has no Private Key") + } else if c.userPublicKey == "" { + return "", fmt.Errorf("Client has no Public Key") + } return helper.EncryptSignMessageArmored(c.userPublicKey, c.userPrivateKey, c.userPassword, message) } // EncryptMessageWithPublicKey encrypts a message using the provided public key and then signes the message using the users private key func (c *Client) EncryptMessageWithPublicKey(publickey, message string) (string, error) { + if c.userPrivateKey == "" { + return "", fmt.Errorf("Client has no Private Key") + } return helper.EncryptSignMessageArmored(publickey, c.userPrivateKey, c.userPassword, message) } // DecryptMessage decrypts a message using the users Private Key func (c *Client) DecryptMessage(message string) (string, error) { + if c.userPrivateKey == "" { + return "", fmt.Errorf("Client has no Private Key") + } // We cant Verify the signature as we don't store other users public keys locally and don't know which user did encrypt it //return helper.DecryptVerifyMessageArmored(c.userPublicKey, c.userPrivateKey, c.userPassword, message) return helper.DecryptMessageArmored(c.userPrivateKey, c.userPassword, message) From 8c2be0f37f956fe279f24390ae2c7ef4f16ae151 Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Thu, 2 Sep 2021 13:31:58 +0200 Subject: [PATCH 2/6] Add missing field for registration --- api/users.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/users.go b/api/users.go index b71a296..5ded066 100644 --- a/api/users.go +++ b/api/users.go @@ -5,6 +5,8 @@ import ( "encoding/json" ) +const UserLocaleENUK = "en-UK" + // User contains information about a passbolt User type User struct { ID string `json:"id,omitempty"` @@ -20,6 +22,7 @@ type User struct { Role *Role `json:"role,omitempty"` GPGKey *GPGKey `json:"gpgKey,omitempty"` LastLoggedIn string `json:"last_logged_in,omitempty"` + Locale string `json:"locale,omitempty"` } // Profile is a Profile From 39dc7c833db0f6a698c34f3f4583bfb3711e6ec5 Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Thu, 2 Sep 2021 13:32:50 +0200 Subject: [PATCH 3/6] support for registration --- api/setup.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 api/setup.go diff --git a/api/setup.go b/api/setup.go new file mode 100644 index 0000000..17b4a57 --- /dev/null +++ b/api/setup.go @@ -0,0 +1,44 @@ +package api + +import ( + "context" + "encoding/json" +) + +type SetupInstallResponse struct { + User `json:"user,omitempty"` +} + +type AuthenticationToken struct { + Token string `json:"token,omitempty"` +} + +type SetupCompleteRequest struct { + AuthenticationToken AuthenticationToken `json:"authenticationtoken,omitempty"` + GPGKey GPGKey `json:"gpgkey,omitempty"` + User User `json:"user,omitempty"` +} + +// SetupInstall validates the userid and token used for Account setup, gives back the User Information +func (c *Client) SetupInstall(ctx context.Context, userID, token string) (*SetupInstallResponse, error) { + msg, err := c.DoCustomRequest(ctx, "GET", "/setup/install/"+userID+"/"+token+".json", "v2", nil, nil) + if err != nil { + return nil, err + } + + var install SetupInstallResponse + err = json.Unmarshal(msg.Body, &install) + if err != nil { + return nil, err + } + return &install, nil +} + +// SetupComplete Completes setup of a Passbolt Account +func (c *Client) SetupComplete(ctx context.Context, userID string, request SetupCompleteRequest) error { + _, err := c.DoCustomRequest(ctx, "POST", "/setup/complete/ "+userID+".json", "v2", request, nil) + if err != nil { + return err + } + return nil +} From 716e537f245b76374c98b524010358a3fd45654a Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Thu, 2 Sep 2021 13:40:52 +0200 Subject: [PATCH 4/6] add Setup Account Helper function --- helper/setup.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 helper/setup.go diff --git a/helper/setup.go b/helper/setup.go new file mode 100644 index 0000000..3971cb4 --- /dev/null +++ b/helper/setup.go @@ -0,0 +1,62 @@ +package helper + +import ( + "context" + "fmt" + "strings" + + "github.com/speatzle/go-passbolt/api" + + "github.com/ProtonMail/gopenpgp/v2/crypto" + "github.com/ProtonMail/gopenpgp/v2/helper" +) + +// SetupAccount Setup a Account for a Invited User +func SetupAccount(ctx context.Context, c *api.Client, inviteURL, password string) (string, error) { + split := strings.Split(inviteURL, "/") + if len(split) < 4 { + return "", fmt.Errorf("Invite URL does not have enough slashes") + } + userID := split[len(split)-2] + token := strings.TrimSuffix(split[len(split)-1], ".json") + + install, err := c.SetupInstall(ctx, userID, token) + if err != nil { + return "", fmt.Errorf("Get Setup Install Data: %w", err) + } + + keyName := install.Profile.FirstName + " " + install.Profile.LastName + " <" + install.Username + ">" + + privateKey, err := helper.GenerateKey(keyName, install.Username, []byte(password), "rsa", 2048) + if err != nil { + return "", fmt.Errorf("Generating Private Key: %w", err) + } + + key, err := crypto.NewKeyFromArmoredReader(strings.NewReader(privateKey)) + if err != nil { + return "", fmt.Errorf("Reading Private Key: %w", err) + } + + publicKey, err := key.GetArmoredPublicKey() + if err != nil { + return "", fmt.Errorf("Get Public Key: %w", err) + } + + request := api.SetupCompleteRequest{ + AuthenticationToken: api.AuthenticationToken{ + Token: token, + }, + User: api.User{ + Locale: api.UserLocaleENUK, + }, + GPGKey: api.GPGKey{ + ArmoredKey: publicKey, + }, + } + + err = c.SetupComplete(ctx, userID, request) + if err != nil { + return "", fmt.Errorf("Setup Completion Failed: %w", err) + } + return privateKey, nil +} From 492cc57a97e813cfb714c7ab8a805fdbd07e8038 Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Thu, 2 Sep 2021 16:11:42 +0200 Subject: [PATCH 5/6] remove bad space --- api/setup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/setup.go b/api/setup.go index 17b4a57..b7a941b 100644 --- a/api/setup.go +++ b/api/setup.go @@ -36,7 +36,7 @@ func (c *Client) SetupInstall(ctx context.Context, userID, token string) (*Setup // SetupComplete Completes setup of a Passbolt Account func (c *Client) SetupComplete(ctx context.Context, userID string, request SetupCompleteRequest) error { - _, err := c.DoCustomRequest(ctx, "POST", "/setup/complete/ "+userID+".json", "v2", request, nil) + _, err := c.DoCustomRequest(ctx, "POST", "/setup/complete/"+userID+".json", "v2", request, nil) if err != nil { return err } From e5e0a160103a3617cb37a86ca2ea26486950ec29 Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Thu, 2 Sep 2021 16:12:28 +0200 Subject: [PATCH 6/6] Move Invite URL Parsing to Seperate function --- helper/setup.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/helper/setup.go b/helper/setup.go index 3971cb4..02c0e50 100644 --- a/helper/setup.go +++ b/helper/setup.go @@ -11,21 +11,25 @@ import ( "github.com/ProtonMail/gopenpgp/v2/helper" ) -// SetupAccount Setup a Account for a Invited User -func SetupAccount(ctx context.Context, c *api.Client, inviteURL, password string) (string, error) { - split := strings.Split(inviteURL, "/") +// ParseInviteUrl Parses a Passbolt Invite URL into a user id and token +func ParseInviteUrl(url string) (string, string, error) { + split := strings.Split(url, "/") if len(split) < 4 { - return "", fmt.Errorf("Invite URL does not have enough slashes") + return "", "", fmt.Errorf("Invite URL does not have enough slashes") } - userID := split[len(split)-2] - token := strings.TrimSuffix(split[len(split)-1], ".json") + return split[len(split)-2], strings.TrimSuffix(split[len(split)-1], ".json"), nil +} + +// SetupAccount Setup a Account for a Invited User. +// (Use ParseInviteUrl to get the userid and token from a Invite URL) +func SetupAccount(ctx context.Context, c *api.Client, userID, token, password string) (string, error) { install, err := c.SetupInstall(ctx, userID, token) if err != nil { return "", fmt.Errorf("Get Setup Install Data: %w", err) } - keyName := install.Profile.FirstName + " " + install.Profile.LastName + " <" + install.Username + ">" + keyName := install.Profile.FirstName + " " + install.Profile.LastName + " " + install.Username privateKey, err := helper.GenerateKey(keyName, install.Username, []byte(password), "rsa", 2048) if err != nil {