Repository: bitmaelum/bitmaelum-suite
Pull request #59 (2020-10-28 08:00:45)

Added auth keys and new container setup
- Added initial auth-key system. It allows to create and list keys from the bm-client
- Added bm-server side of auth-key system (pretty much a duplicate of the api-key system)
- Added new container system that allows for easier mocking when needed (old container could not do this properly)
- Updated the API/AUTH key systems so they are merged into a single system (avoid lots of duplication)

sonarcloud[bot]: SonarCloud Quality Gate failed.

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo.png' alt='No Coverage information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=bitmaelum_bitmaelum-suite&pullRequest=59) No Coverage information  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/5.png' alt='3.5%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=bitmaelum_bitmaelum-suite&pullRequest=59&metric=new_duplicated_lines_density&view=list) [3.5% Duplication](https://sonarcloud.io/component_measures?id=bitmaelum_bitmaelum-suite&pullRequest=59&metric=new_duplicated_lines_density&view=list)


codecov[bot]: # [Codecov](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59?src=pr&el=h1) Report
> Merging [#59](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59?src=pr&el=desc) into [develop](https://codecov.io/gh/bitmaelum/bitmaelum-suite/commit/834ddaf16bed91754ab66aa46a27c60edb3e7b73?el=desc) will **decrease** coverage by `9.77%`.
> The diff coverage is `55.50%`.

[![Impacted file tree graph](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59/graphs/tree.svg?width=650&height=150&src=pr&token=8VR72NKYS0)](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##           develop      #59      +/-   ##
===========================================
- Coverage    59.00%   49.23%   -9.78%     
===========================================
  Files           67       88      +21     
  Lines         2615     3378     +763     
===========================================
+ Hits          1543     1663     +120     
- Misses         878     1508     +630     
- Partials       194      207      +13     
```

| Flag | Coverage Δ | |
|---|---|---|
| #unittests | `49.23% <55.50%> (-9.78%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [cmd/bm-client/handlers/compose.go](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59/diff?src=pr&el=tree#diff-Y21kL2JtLWNsaWVudC9oYW5kbGVycy9jb21wb3NlLmdv) | `0.00% <0.00%> (ø)` | |
| [cmd/bm-client/handlers/fetch\_messages.go](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59/diff?src=pr&el=tree#diff-Y21kL2JtLWNsaWVudC9oYW5kbGVycy9mZXRjaF9tZXNzYWdlcy5nbw==) | `0.00% <0.00%> (ø)` | |
| [cmd/bm-client/handlers/read\_message.go](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59/diff?src=pr&el=tree#diff-Y21kL2JtLWNsaWVudC9oYW5kbGVycy9yZWFkX21lc3NhZ2UuZ28=) | `0.00% <0.00%> (ø)` | |
| [internal/container/helpers.go](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59/diff?src=pr&el=tree#diff-aW50ZXJuYWwvY29udGFpbmVyL2hlbHBlcnMuZ28=) | `0.00% <0.00%> (ø)` | |
| [internal/message/catalog.go](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59/diff?src=pr&el=tree#diff-aW50ZXJuYWwvbWVzc2FnZS9jYXRhbG9nLmdv) | `78.94% <ø> (ø)` | |
| [internal/parse/parse\_args.go](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59/diff?src=pr&el=tree#diff-aW50ZXJuYWwvcGFyc2UvcGFyc2VfYXJncy5nbw==) | `100.00% <ø> (ø)` | |
| [internal/resolver/service.go](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59/diff?src=pr&el=tree#diff-aW50ZXJuYWwvcmVzb2x2ZXIvc2VydmljZS5nbw==) | `13.43% <0.00%> (+0.57%)` | :arrow_up: |
| [internal/container/authkey.go](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59/diff?src=pr&el=tree#diff-aW50ZXJuYWwvY29udGFpbmVyL2F1dGhrZXkuZ28=) | `8.33% <8.33%> (ø)` | |
| [internal/container/proof-of-work.go](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59/diff?src=pr&el=tree#diff-aW50ZXJuYWwvY29udGFpbmVyL3Byb29mLW9mLXdvcmsuZ28=) | `10.00% <10.00%> (ø)` | |
| [cmd/bm-client/handlers/auth\_create.go](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59/diff?src=pr&el=tree#diff-Y21kL2JtLWNsaWVudC9oYW5kbGVycy9hdXRoX2NyZWF0ZS5nbw==) | `12.00% <12.00%> (ø)` | |
| ... and [39 more](https://codecov.io/gh/bitmaelum/bitmaelum-suite/pull/59/diff?src=pr&el=tree-more) | |

sonarcloud[bot]: SonarCloud Quality Gate failed.

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=bitmaelum_bitmaelum-suite&pullRequest=59&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo.png' alt='No Coverage information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=bitmaelum_bitmaelum-suite&pullRequest=59) No Coverage information  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/5.png' alt='3.5%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=bitmaelum_bitmaelum-suite&pullRequest=59&metric=new_duplicated_lines_density&view=list) [3.5% Duplication](https://sonarcloud.io/component_measures?id=bitmaelum_bitmaelum-suite&pullRequest=59&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/cmd/bm-client/cmd/api.go b/cmd/bm-client/cmd/api.go
index 9252de6..87f34ca 100644
--- a/cmd/bm-client/cmd/api.go
+++ b/cmd/bm-client/cmd/api.go
@@ -34,5 +34,6 @@ var apiAddress *string
 func init() {
 	rootCmd.AddCommand(apiCmd)
 
-	apiAddress = apiCreateCmd.Flags().StringP("address", "a", "", "Address")
+	apiAddress = apiCmd.PersistentFlags().StringP("address", "a", "", "Address")
+	_ = apiCmd.MarkPersistentFlagRequired("address")
 }
diff --git a/cmd/bm-client/cmd/api_create.go b/cmd/bm-client/cmd/api_create.go
index 81b9d0c..47bb152 100644
--- a/cmd/bm-client/cmd/api_create.go
+++ b/cmd/bm-client/cmd/api_create.go
@@ -25,9 +25,9 @@ import (
 	"time"
 
 	"github.com/bitmaelum/bitmaelum-suite/internal/api"
-	"github.com/bitmaelum/bitmaelum-suite/internal/apikey"
 	"github.com/bitmaelum/bitmaelum-suite/internal/config"
 	"github.com/bitmaelum/bitmaelum-suite/internal/container"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
 	"github.com/bitmaelum/bitmaelum-suite/internal/vault"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
@@ -55,7 +55,7 @@ var apiCreateCmd = &cobra.Command{
 			fmt.Printf("UNTIL EXPIRY: %#v\n", expiry)
 		}
 
-		key := apikey.NewKey(*acPerms, expiry, *acDesc)
+		key := key.NewAPIKey(*acPerms, expiry, *acDesc)
 		fmt.Printf("%#v\n", key)
 		fmt.Printf("%#v\n", *acValidUntil)
 
diff --git a/cmd/bm-client/handlers/sign_onbehalf.go b/cmd/bm-client/cmd/auth.go
similarity index 70%
rename from cmd/bm-client/handlers/sign_onbehalf.go
rename to cmd/bm-client/cmd/auth.go
index 5d56a42..643f1bb 100644
--- a/cmd/bm-client/handlers/sign_onbehalf.go
+++ b/cmd/bm-client/cmd/auth.go
@@ -17,22 +17,25 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package handlers
+package cmd
 
 import (
-	"encoding/base64"
+	"github.com/spf13/cobra"
+)
+
+var authCmd = &cobra.Command{
+	Use:   "auth",
+	Short: "Authorization management",
+}
 
-	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
-	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
+var (
+	authAccount *string
 )
 
-// SignOnbehalf will sign a target key by the private key of info
-func SignOnbehalf(sourceKey bmcrypto.PrivKey, targetKey bmcrypto.PubKey) (string, error) {
-	h := hash.New(targetKey.String())
-	signedKey, err := bmcrypto.Sign(sourceKey, h.Byte())
-	if err != nil {
-		return "", err
-	}
+func init() {
+	rootCmd.AddCommand(authCmd)
+
+	authAccount = authCmd.PersistentFlags().StringP("account", "a", "", "Account")
 
-	return base64.StdEncoding.EncodeToString(signedKey), nil
+	_ = authCmd.MarkPersistentFlagRequired("account")
 }
diff --git a/cmd/bm-client/cmd/sign_onbehalf.go b/cmd/bm-client/cmd/auth_create.go
similarity index 60%
rename from cmd/bm-client/cmd/sign_onbehalf.go
rename to cmd/bm-client/cmd/auth_create.go
index 81587ae..d06cca0 100644
--- a/cmd/bm-client/cmd/sign_onbehalf.go
+++ b/cmd/bm-client/cmd/auth_create.go
@@ -20,8 +20,8 @@
 package cmd
 
 import (
-	"fmt"
 	"os"
+	"time"
 
 	"github.com/bitmaelum/bitmaelum-suite/cmd/bm-client/handlers"
 	"github.com/bitmaelum/bitmaelum-suite/internal/vault"
@@ -31,48 +31,48 @@ import (
 	"github.com/spf13/cobra"
 )
 
-var onbehalfCmd = &cobra.Command{
-	Use:   "sign-onbehalf",
-	Short: "Sign a key that allows mail on behalf of you",
-	Long: `By signing another users key, you can allow it to send messages for you. This way, you can let other people 
-or tools send messages without exposing your own private key.
+var authCreateCmd = &cobra.Command{
+	Use:   "create",
+	Short: "Create a new authorized key to send messages on your behalf",
+	Long: `By authorizing another users public key, you can allow it to send messages for you. This way, you can let other people or tools send messages without exposing your own private key.
 `,
 	Run: func(cmd *cobra.Command, args []string) {
-		v := vault.OpenVault()
-
-		info := vault.GetAccountOrDefault(v, *oAccount)
-		if info == nil {
-			logrus.Fatal("No account found in vault")
+		targetKey, err := bmcrypto.NewPubKey(*authPublicKey)
+		if err != nil {
+			logrus.Fatal("Incorrect public key to sign")
 			os.Exit(1)
 		}
 
-		targetKey, err := bmcrypto.NewPubKey(*oTargetKey)
-		if err != nil {
-			logrus.Fatal("Not a valid key")
+		v := vault.OpenVault()
+		info := vault.GetAccountOrDefault(v, *authAccount)
+		if info == nil {
+			logrus.Fatal("No account found in vault")
 			os.Exit(1)
 		}
 
-		signedTargetKey, err := handlers.SignOnbehalf(info.PrivKey, *targetKey)
+		err = handlers.CreateAuthorizedKey(info, targetKey, *authValidUntil, *authDesc)
 		if err != nil {
-			logrus.Fatal("Error while signing key: ", err)
+			logrus.Fatal(err)
 			os.Exit(1)
 		}
 
-		fmt.Printf("Signed key: %s\n", signedTargetKey)
+		logrus.Print("Created new authorized key")
 	},
 }
 
 var (
-	oAccount   *string
-	oTargetKey *string
+	authPublicKey  *string
+	authValidUntil *time.Duration
+	authDesc       *string
 )
 
 func init() {
-	rootCmd.AddCommand(onbehalfCmd)
+	authCmd.AddCommand(authCreateCmd)
 
-	oAccount = onbehalfCmd.Flags().StringP("account", "a", "", "Account")
-	oTargetKey = onbehalfCmd.Flags().StringP("key", "k", "", "Key to sign")
+	authPublicKey = authCreateCmd.Flags().StringP("key", "k", "", "Key to sign")
+	authValidUntil = authCreateCmd.Flags().Duration("duration", time.Duration(0), "Time valid")
+	authDesc = authCreateCmd.Flags().StringP("desc", "d", "", "Value to set")
 
-	_ = onbehalfCmd.MarkFlagRequired("account")
-	_ = onbehalfCmd.MarkFlagRequired("key")
+	_ = authCreateCmd.MarkFlagRequired("desc")
+	_ = authCreateCmd.MarkFlagRequired("key")
 }
diff --git a/cmd/bm-client/cmd/auth_list.go b/cmd/bm-client/cmd/auth_list.go
new file mode 100644
index 0000000..4ae87bc
--- /dev/null
+++ b/cmd/bm-client/cmd/auth_list.go
@@ -0,0 +1,84 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package cmd
+
+import (
+	"os"
+	"time"
+
+	"github.com/bitmaelum/bitmaelum-suite/internal/api"
+	"github.com/bitmaelum/bitmaelum-suite/internal/config"
+	"github.com/bitmaelum/bitmaelum-suite/internal/container"
+	"github.com/bitmaelum/bitmaelum-suite/internal/vault"
+	"github.com/olekukonko/tablewriter"
+	"github.com/sirupsen/logrus"
+
+	"github.com/spf13/cobra"
+)
+
+var authListCmd = &cobra.Command{
+	Use:   "list",
+	Short: "Display all authorized keys",
+	Long:  ``,
+	Run: func(cmd *cobra.Command, args []string) {
+		v := vault.OpenVault()
+		info := vault.GetAccountOrDefault(v, *authAccount)
+
+		resolver := container.GetResolveService()
+		routingInfo, err := resolver.ResolveRouting(info.RoutingID)
+		if err != nil {
+			logrus.Fatal("Cannot find routing ID for this account")
+			os.Exit(1)
+		}
+
+		client, err := api.NewAuthenticated(info, api.ClientOpts{
+			Host:          routingInfo.Routing,
+			AllowInsecure: config.Client.Server.AllowInsecure,
+			Debug:         config.Client.Server.DebugHTTP,
+		})
+		if err != nil {
+			logrus.Fatal(err)
+			os.Exit(1)
+		}
+
+		keys, err := client.ListAuthKeys(info.AddressHash())
+		if err != nil {
+			logrus.Fatal(err)
+			os.Exit(1)
+		}
+
+		table := tablewriter.NewWriter(os.Stdout)
+		table.SetHeader([]string{"Fingerprint", "Valid until", "Description"})
+
+		for _, key := range keys {
+			table.Append([]string{
+				key.Fingerprint,
+				key.Expires.Format(time.ANSIC),
+				key.Description,
+			})
+		}
+
+		table.Render()
+	},
+}
+
+func init() {
+	authCmd.AddCommand(authListCmd)
+}
diff --git a/cmd/bm-client/handlers/auth_create.go b/cmd/bm-client/handlers/auth_create.go
new file mode 100644
index 0000000..fd5c855
--- /dev/null
+++ b/cmd/bm-client/handlers/auth_create.go
@@ -0,0 +1,82 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package handlers
+
+import (
+	"encoding/base64"
+	"errors"
+	"time"
+
+	"github.com/bitmaelum/bitmaelum-suite/internal"
+	"github.com/bitmaelum/bitmaelum-suite/internal/api"
+	"github.com/bitmaelum/bitmaelum-suite/internal/config"
+	"github.com/bitmaelum/bitmaelum-suite/internal/container"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
+	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
+	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
+)
+
+// CreateAuthorizedKey creates a new authorized key
+func CreateAuthorizedKey(info *internal.AccountInfo, targetKey *bmcrypto.PubKey, validUntil time.Duration, desc string) error {
+	// Sign key
+	signature, err := signKey(info.PrivKey, targetKey)
+	if err != nil {
+		return err
+	}
+
+	// Send key
+	client, err := getAPIClient(info)
+	if err != nil {
+		return err
+	}
+
+	var expiry = time.Time{}
+	if validUntil > 0 {
+		expiry = time.Now().Add(validUntil)
+	}
+
+	key := key.NewAuthKey(info.AddressHash(), targetKey, signature, expiry, desc)
+	return client.CreateAuthKey(info.AddressHash(), key)
+}
+
+func signKey(privkey bmcrypto.PrivKey, pubkey *bmcrypto.PubKey) (string, error) {
+	h := hash.New(pubkey.String())
+
+	signedKey, err := bmcrypto.Sign(privkey, h.Byte())
+	if err != nil {
+		return "", errors.New("cannot sing the public key")
+	}
+
+	return base64.StdEncoding.EncodeToString(signedKey), nil
+}
+
+func getAPIClient(info *internal.AccountInfo) (*api.API, error) {
+	resolver := container.GetResolveService()
+	routingInfo, err := resolver.ResolveRouting(info.RoutingID)
+	if err != nil {
+		return nil, errors.New("cannot find routing ID for this account")
+	}
+
+	return api.NewAuthenticated(info, api.ClientOpts{
+		Host:          routingInfo.Routing,
+		AllowInsecure: config.Client.Server.AllowInsecure,
+		Debug:         config.Client.Server.DebugHTTP,
+	})
+}
diff --git a/cmd/bm-config/cmd/apikey_create.go b/cmd/bm-config/cmd/apikey_create.go
index b15727d..915e957 100644
--- a/cmd/bm-config/cmd/apikey_create.go
+++ b/cmd/bm-config/cmd/apikey_create.go
@@ -24,9 +24,9 @@ import (
 	"os"
 	"time"
 
-	"github.com/bitmaelum/bitmaelum-suite/internal/apikey"
 	"github.com/bitmaelum/bitmaelum-suite/internal/config"
 	"github.com/bitmaelum/bitmaelum-suite/internal/container"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
 	"github.com/bitmaelum/bitmaelum-suite/internal/parse"
 	"github.com/spf13/cobra"
 )
@@ -73,34 +73,34 @@ Note: Creating an admin key can only be done locally on the mail-server.
 			os.Exit(1)
 		}
 
-		var key apikey.KeyType
+		var k key.APIKeyType
 		if *mgAdmin {
 			fmt.Printf("Creating new admin key\n")
 			if len(*mgPerms) > 0 {
 				fmt.Printf("Error: cannot specify permissions when you create an admin key (all permissions are automatically granted)\n")
 				os.Exit(1)
 			}
-			key = apikey.NewAdminKey(expires, *mgDesc)
+			k = key.NewAPIAdminKey(expires, *mgDesc)
 		} else {
 			fmt.Printf("Creating new regular key\n")
 			if len(*mgPerms) == 0 {
 				fmt.Printf("Error: need a set of permissions when generating a regular key\n")
 				os.Exit(1)
 			}
-			key = apikey.NewKey(*mgPerms, expires, *mgDesc)
+			k = key.NewAPIKey(*mgPerms, expires, *mgDesc)
 		}
 
 		// Store API key into persistent storage
 		repo := container.GetAPIKeyRepo()
-		err = repo.Store(key)
+		err = repo.Store(k)
 		if err != nil {
 			fmt.Printf("Error: cannot store key: %s\n", err)
 			os.Exit(1)
 		}
 
-		fmt.Printf("Your API key: %s\n", key.ID)
-		if !key.Expires.IsZero() {
-			fmt.Printf("Key is valid until %s\n", key.Expires.Format(time.RFC822))
+		fmt.Printf("Your API key: %s\n", k.ID)
+		if !k.Expires.IsZero() {
+			fmt.Printf("Key is valid until %s\n", k.Expires.Format(time.RFC822))
 		}
 	},
 }
diff --git a/cmd/bm-config/cmd/generate_key.go b/cmd/bm-config/cmd/generate_key.go
new file mode 100644
index 0000000..1cdda2a
--- /dev/null
+++ b/cmd/bm-config/cmd/generate_key.go
@@ -0,0 +1,52 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package cmd
+
+import (
+	"fmt"
+
+	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
+	"github.com/sirupsen/logrus"
+	"github.com/spf13/cobra"
+)
+
+// generateKeyCmd represents the generatekey command
+var generateKeyCmd = &cobra.Command{
+	Use:   "generate-key",
+	Short: "Generates a new key",
+	Long:  `This command generates a new key`,
+	Run: func(cmd *cobra.Command, args []string) {
+		privKey, pubKey, err := bmcrypto.GenerateKeyPair(*keyType)
+		if err != nil {
+			logrus.Fatal(err)
+		}
+
+		fmt.Println("Private key : ", privKey.String())
+		fmt.Println("Public key  : ", pubKey.String())
+	},
+}
+
+var keyType *string
+
+func init() {
+	rootCmd.AddCommand(generateKeyCmd)
+
+	keyType = generateKeyCmd.Flags().StringP("type", "t", "ed25519", "The keytype (ed25519, rsa or ecdsa)")
+}
diff --git a/cmd/bm-server/handler/api_key.go b/cmd/bm-server/handler/api_key.go
index ad002ce..47fc9dd 100644
--- a/cmd/bm-server/handler/api_key.go
+++ b/cmd/bm-server/handler/api_key.go
@@ -25,8 +25,8 @@ import (
 	"net/http"
 	"time"
 
-	"github.com/bitmaelum/bitmaelum-suite/internal/apikey"
 	"github.com/bitmaelum/bitmaelum-suite/internal/container"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
 	"github.com/bitmaelum/bitmaelum-suite/internal/parse"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
 	"github.com/gorilla/mux"
@@ -60,7 +60,7 @@ func CreateAPIKey(w http.ResponseWriter, req *http.Request) {
 		return
 	}
 
-	newAPIKey := apikey.NewAccountKey(*h, input.Permissions, time.Unix(input.Expires, 0), input.Desc)
+	newAPIKey := key.NewAPIAccountKey(*h, input.Permissions, time.Unix(input.Expires, 0), input.Desc)
 
 	// Store API key into persistent storage
 	repo := container.GetAPIKeyRepo()
@@ -114,14 +114,14 @@ func DeleteAPIKey(w http.ResponseWriter, req *http.Request) {
 
 	// Fetch key
 	repo := container.GetAPIKeyRepo()
-	key, err := repo.Fetch(keyID)
-	if err != nil || key.AddrHash.String() != h.String() {
+	k, err := repo.Fetch(keyID)
+	if err != nil || k.AddressHash.String() != h.String() {
 		// Only allow deleting of keys that we own as account
 		ErrorOut(w, http.StatusNotFound, "key not found")
 		return
 	}
 
-	_ = repo.Remove(*key)
+	_ = repo.Remove(*k)
 
 	// All is well
 	w.Header().Set("Content-Type", "application/json")
@@ -140,8 +140,8 @@ func GetAPIKeyDetails(w http.ResponseWriter, req *http.Request) {
 
 	// Fetch key
 	repo := container.GetAPIKeyRepo()
-	key, err := repo.Fetch(keyID)
-	if err != nil || key.AddrHash.String() != h.String() {
+	k, err := repo.Fetch(keyID)
+	if err != nil || k.AddressHash.String() != h.String() {
 		ErrorOut(w, http.StatusNotFound, "key not found")
 		return
 	}
@@ -149,5 +149,5 @@ func GetAPIKeyDetails(w http.ResponseWriter, req *http.Request) {
 	// Output key
 	w.Header().Set("Content-Type", "application/json")
 	w.WriteHeader(http.StatusCreated)
-	_ = json.NewEncoder(w).Encode(key)
+	_ = json.NewEncoder(w).Encode(k)
 }
diff --git a/cmd/bm-server/handler/auth_key.go b/cmd/bm-server/handler/auth_key.go
new file mode 100644
index 0000000..4ece148
--- /dev/null
+++ b/cmd/bm-server/handler/auth_key.go
@@ -0,0 +1,149 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package handler
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/bitmaelum/bitmaelum-suite/internal/container"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
+	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
+	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
+	"github.com/gorilla/mux"
+)
+
+type inputAuthKeyType struct {
+	Fingerprint string           `json:"fingerprint"`
+	PublicKey   *bmcrypto.PubKey `json:"public_key"`
+	Signature   string           `json:"signature"`
+	Expires     int64            `json:"expires,omitempty"`
+	Desc        string           `json:"description,omitempty"`
+}
+
+// CreateAuthKey is a handler that will create a new auth key
+func CreateAuthKey(w http.ResponseWriter, req *http.Request) {
+	var input inputAuthKeyType
+	err := DecodeBody(w, req.Body, &input)
+	if err != nil {
+		ErrorOut(w, http.StatusBadRequest, "incorrect body")
+		return
+	}
+
+	//
+	h, err := hash.NewFromHash(mux.Vars(req)["addr"])
+	if err != nil {
+		ErrorOut(w, http.StatusNotFound, accountNotFound)
+		return
+	}
+
+	newAuthKey := key.NewAuthKey(*h, input.PublicKey, input.Signature, time.Unix(input.Expires, 0), input.Desc)
+
+	// Store auth key into persistent storage
+	repo := container.GetAuthKeyRepo()
+	err = repo.Store(newAuthKey)
+	if err != nil {
+		msg := fmt.Sprintf("error while storing key: %s", err)
+		ErrorOut(w, http.StatusInternalServerError, msg)
+		return
+	}
+
+	// Output key
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusCreated)
+	_ = json.NewEncoder(w).Encode(jsonOut{
+		"auth_key": newAuthKey.Fingerprint,
+	})
+}
+
+// ListAuthKeys returns a list of all keys for the given account
+func ListAuthKeys(w http.ResponseWriter, req *http.Request) {
+	h, err := hash.NewFromHash(mux.Vars(req)["addr"])
+	if err != nil {
+		ErrorOut(w, http.StatusNotFound, accountNotFound)
+		return
+	}
+
+	// Store Auth key into persistent storage
+	repo := container.GetAuthKeyRepo()
+	keys, err := repo.FetchByHash(h.String())
+	if err != nil {
+		msg := fmt.Sprintf("error while retrieving keys: %s", err)
+		ErrorOut(w, http.StatusInternalServerError, msg)
+		return
+	}
+
+	// Output key
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusCreated)
+	_ = json.NewEncoder(w).Encode(keys)
+}
+
+// DeleteAuthKey will remove a key
+func DeleteAuthKey(w http.ResponseWriter, req *http.Request) {
+	h, err := hash.NewFromHash(mux.Vars(req)["addr"])
+	if err != nil {
+		ErrorOut(w, http.StatusNotFound, accountNotFound)
+		return
+	}
+
+	keyID := mux.Vars(req)["key"]
+
+	// Fetch key
+	repo := container.GetAuthKeyRepo()
+	k, err := repo.Fetch(keyID)
+	if err != nil || k.AddressHash.String() != h.String() {
+		// Only allow deleting of keys that we own as account
+		ErrorOut(w, http.StatusNotFound, "key not found")
+		return
+	}
+
+	_ = repo.Remove(*k)
+
+	// All is well
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusOK)
+}
+
+// GetAuthKeyDetails will get a key
+func GetAuthKeyDetails(w http.ResponseWriter, req *http.Request) {
+	h, err := hash.NewFromHash(mux.Vars(req)["addr"])
+	if err != nil {
+		ErrorOut(w, http.StatusNotFound, accountNotFound)
+		return
+	}
+
+	keyID := mux.Vars(req)["key"]
+
+	// Fetch key
+	repo := container.GetAuthKeyRepo()
+	k, err := repo.Fetch(keyID)
+	if err != nil || k.AddressHash.String() != h.String() {
+		ErrorOut(w, http.StatusNotFound, "key not found")
+		return
+	}
+
+	// Output key
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusCreated)
+	_ = json.NewEncoder(w).Encode(k)
+}
diff --git a/cmd/bm-server/handler/mgmt/api_key.go b/cmd/bm-server/handler/mgmt/api_key.go
index 18be3f5..168b1b9 100644
--- a/cmd/bm-server/handler/mgmt/api_key.go
+++ b/cmd/bm-server/handler/mgmt/api_key.go
@@ -26,8 +26,8 @@ import (
 	"time"
 
 	"github.com/bitmaelum/bitmaelum-suite/cmd/bm-server/handler"
-	"github.com/bitmaelum/bitmaelum-suite/internal/apikey"
 	"github.com/bitmaelum/bitmaelum-suite/internal/container"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
 	"github.com/bitmaelum/bitmaelum-suite/internal/parse"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
 )
@@ -59,8 +59,8 @@ func NewAPIKey(w http.ResponseWriter, req *http.Request) {
 		return
 	}
 
-	key := handler.GetAPIKey(req)
-	if !key.HasPermission(apikey.PermAPIKeys, h) {
+	k := handler.GetAPIKey(req)
+	if !k.HasPermission(key.PermAPIKeys, h) {
 		handler.ErrorOut(w, http.StatusUnauthorized, "unauthorized")
 		return
 	}
@@ -71,7 +71,7 @@ func NewAPIKey(w http.ResponseWriter, req *http.Request) {
 		return
 	}
 
-	newAPIKey := apikey.NewAccountKey(*h, input.Permissions, time.Unix(input.Expires, 0), input.Desc)
+	newAPIKey := key.NewAPIAccountKey(*h, input.Permissions, time.Unix(input.Expires, 0), input.Desc)
 
 	// Store API key into persistent storage
 	repo := container.GetAPIKeyRepo()
diff --git a/cmd/bm-server/handler/mgmt/flush.go b/cmd/bm-server/handler/mgmt/flush.go
index e066d88..a80c52e 100644
--- a/cmd/bm-server/handler/mgmt/flush.go
+++ b/cmd/bm-server/handler/mgmt/flush.go
@@ -26,13 +26,13 @@ import (
 	"github.com/bitmaelum/bitmaelum-suite/cmd/bm-server/handler"
 	"github.com/bitmaelum/bitmaelum-suite/cmd/bm-server/processor"
 	"github.com/bitmaelum/bitmaelum-suite/internal"
-	"github.com/bitmaelum/bitmaelum-suite/internal/apikey"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
 )
 
 // FlushQueues handler will flush all the queues normally on tickers
 func FlushQueues(w http.ResponseWriter, req *http.Request) {
-	key := handler.GetAPIKey(req)
-	if !key.HasPermission(apikey.PermFlush, nil) {
+	k := handler.GetAPIKey(req)
+	if !k.HasPermission(key.PermFlush, nil) {
 		handler.ErrorOut(w, http.StatusUnauthorized, "unauthorized")
 		return
 	}
diff --git a/cmd/bm-server/handler/mgmt/invite.go b/cmd/bm-server/handler/mgmt/invite.go
index 6113d1c..7639606 100644
--- a/cmd/bm-server/handler/mgmt/invite.go
+++ b/cmd/bm-server/handler/mgmt/invite.go
@@ -25,9 +25,9 @@ import (
 	"time"
 
 	"github.com/bitmaelum/bitmaelum-suite/cmd/bm-server/handler"
-	"github.com/bitmaelum/bitmaelum-suite/internal/apikey"
 	"github.com/bitmaelum/bitmaelum-suite/internal/config"
 	"github.com/bitmaelum/bitmaelum-suite/internal/invite"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
 )
 
@@ -40,8 +40,8 @@ type jsonOut map[string]interface{}
 
 // NewInvite handler will generate a new invite token for a given address
 func NewInvite(w http.ResponseWriter, req *http.Request) {
-	key := handler.GetAPIKey(req)
-	if !key.HasPermission(apikey.PermGenerateInvites, nil) {
+	k := handler.GetAPIKey(req)
+	if !k.HasPermission(key.PermGenerateInvites, nil) {
 		handler.ErrorOut(w, http.StatusUnauthorized, "unauthorized")
 		return
 	}
diff --git a/cmd/bm-server/handler/utils.go b/cmd/bm-server/handler/utils.go
index 715e5a3..85f043c 100644
--- a/cmd/bm-server/handler/utils.go
+++ b/cmd/bm-server/handler/utils.go
@@ -25,7 +25,7 @@ import (
 	"net/http"
 
 	"github.com/bitmaelum/bitmaelum-suite/cmd/bm-server/middleware/auth"
-	"github.com/bitmaelum/bitmaelum-suite/internal/apikey"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
 	"github.com/sirupsen/logrus"
 )
 
@@ -99,13 +99,13 @@ func DecodeBody(w http.ResponseWriter, body io.ReadCloser, v interface{}) error
 }
 
 // GetAPIKey returns the api key stored in the request context. If not found, it will return a dummy key with no permissions
-func GetAPIKey(req *http.Request) *apikey.KeyType {
+func GetAPIKey(req *http.Request) *key.APIKeyType {
 	val := req.Context().Value(auth.APIKeyContext)
 	if val == nil {
-		return &apikey.KeyType{}
+		return &key.APIKeyType{}
 	}
 
-	return val.(*apikey.KeyType)
+	return val.(*key.APIKeyType)
 }
 
 // IsAPIKeyAuthenticated returns true when the given request is authenticated by a api key
diff --git a/cmd/bm-server/main.go b/cmd/bm-server/main.go
index 66023da..c7bcab0 100644
--- a/cmd/bm-server/main.go
+++ b/cmd/bm-server/main.go
@@ -157,11 +157,14 @@ func setupRouter() *mux.Router {
 
 	mainRouter := mux.NewRouter().StrictSlash(true)
 
-	// Public things router
+	//
+	// Router with public endpoints. These can be called without any form of authentication
+	//
 	publicRouter := mainRouter.PathPrefix("/").Subrouter()
 	publicRouter.Use(logger.Middleware)
 	publicRouter.Use(prettyJSON.Middleware)
 	publicRouter.Use(tracer.Middleware)
+
 	publicRouter.HandleFunc("/", handler.HomePage).Methods("GET")
 	publicRouter.HandleFunc("/account", handler.CreateAccount).Methods("POST")
 	publicRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/keys", handler.RetrieveKeys).Methods("GET")
@@ -176,7 +179,9 @@ func setupRouter() *mux.Router {
 	publicRouter.HandleFunc("/incoming", handler.CompleteIncoming).Methods("POST")
 	publicRouter.HandleFunc("/incoming", handler.DeleteIncoming).Methods("DELETE")
 
+	//
 	// Routes that need to be authenticated through JWT alone
+	//
 	authorizer := &middleware.Authenticate{}
 	authorizer.Add(&auth.JwtAuth{})
 
@@ -190,6 +195,7 @@ func setupRouter() *mux.Router {
 	authRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/boxes", handler.CreateBox).Methods("POST")
 	authRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/box/{box:[0-9+}", handler.DeleteBox).Methods("DELETE")
 	// Message fetching
+	authRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/ticket", handler.GetClientToServerTicket).Methods("POST")
 	authRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/box/{box:[0-9]+}", handler.RetrieveMessagesFromBox).Methods("GET")
 	authRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/box/{box:[0-9]+}/message/{message:[A-Za-z0-9-]+}", handler.GetMessage).Methods("GET")
 	authRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/box/{box:[0-9]+}/message/{message:[A-Za-z0-9-]+}/block/{block:[A-Za-z0-9-]+}", handler.GetMessageBlock).Methods("GET")
@@ -199,8 +205,15 @@ func setupRouter() *mux.Router {
 	authRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/apikey", handler.ListAPIKeys).Methods("GET")
 	authRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/apikey/{key}", handler.GetAPIKeyDetails).Methods("GET")
 	authRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/apikey/{key}", handler.DeleteAPIKey).Methods("DELETE")
-
-	// Routes that need to be authenticated through JWT alone
+	// Auth keys
+	authRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/authkey", handler.CreateAuthKey).Methods("POST")
+	authRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/authkey", handler.ListAuthKeys).Methods("GET")
+	authRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/authkey/{key}", handler.GetAuthKeyDetails).Methods("GET")
+	authRouter.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/authkey/{key}", handler.DeleteAuthKey).Methods("DELETE")
+
+	//
+	// Routes that need to be authenticated through JWT or API keys
+	//
 	authorizer = &middleware.Authenticate{}
 	authorizer.Add(&auth.JwtAuth{})
 	authorizer.Add(&auth.APIKeyAuth{
@@ -214,7 +227,6 @@ func setupRouter() *mux.Router {
 	auth2Router.Use(authorizer.Middleware)
 
 	// Authorized sending
-	auth2Router.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/ticket", handler.GetClientToServerTicket).Methods("POST").Name("ticket")
 	auth2Router.HandleFunc("/account/{addr:[A-Za-z0-9]{64}}/boxes", handler.RetrieveBoxes).Methods("GET").Name("boxes")
 
 	// Add management endpoints if enabled
diff --git a/cmd/bm-server/middleware/auth/auth_apikey.go b/cmd/bm-server/middleware/auth/auth_apikey.go
index e94d04c..725c4cc 100644
--- a/cmd/bm-server/middleware/auth/auth_apikey.go
+++ b/cmd/bm-server/middleware/auth/auth_apikey.go
@@ -26,8 +26,8 @@ import (
 	"strings"
 	"time"
 
-	"github.com/bitmaelum/bitmaelum-suite/internal/apikey"
 	"github.com/bitmaelum/bitmaelum-suite/internal/container"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
 	"github.com/gorilla/mux"
 	"github.com/sirupsen/logrus"
@@ -60,8 +60,6 @@ const (
 
 // @TODO make sure we can't use a key to fetch other people's info
 
-var apiKeyRepo = container.GetAPIKeyRepo
-
 // Authenticate will check if an API key matches the request
 func (a *APIKeyAuth) Authenticate(req *http.Request, route string) (context.Context, bool) {
 	// Check if the address actually exists
@@ -70,34 +68,35 @@ func (a *APIKeyAuth) Authenticate(req *http.Request, route string) (context.Cont
 		return nil, false
 	}
 
-	if !accountRepo().Exists(*haddr) {
+	accountRepo := container.GetAccountRepo()
+	if !accountRepo.Exists(*haddr) {
 		logrus.Trace("auth: address not found")
 		return nil, false
 	}
 
 	// Check api key.
-	key, err := a.checkAPIKey(req.Header.Get("Authorization"), *haddr, route)
+	k, err := a.checkAPIKey(req.Header.Get("Authorization"), *haddr, route)
 	if err != nil {
 		return nil, false
 	}
 
 	ctx := req.Context()
-	ctx = context.WithValue(ctx, APIKeyContext, key)
+	ctx = context.WithValue(ctx, APIKeyContext, k)
 
 	return ctx, true
 }
 
-func (a *APIKeyAuth) checkAPIKey(bearerToken string, addrHash hash.Hash, routeName string) (*apikey.KeyType, error) {
-	key, err := a.getAPIKey(bearerToken)
+func (a *APIKeyAuth) checkAPIKey(bearerToken string, addrHash hash.Hash, routeName string) (*key.APIKeyType, error) {
+	k, err := a.getAPIKey(bearerToken)
 	if err != nil {
 		return nil, err
 	}
 
-	if key.AddrHash.String() != addrHash.String() {
+	if k.AddressHash.String() != addrHash.String() {
 		return nil, ErrInvalidAPIKey
 	}
 
-	if !key.Expires.IsZero() && time.Now().After(key.Expires) {
+	if !k.Expires.IsZero() && time.Now().After(k.Expires) {
 		return nil, ErrExpiredAPIKey
 	}
 
@@ -112,9 +111,9 @@ func (a *APIKeyAuth) checkAPIKey(bearerToken string, addrHash hash.Hash, routeNa
 	}
 
 	for _, perm := range perms {
-		for _, userperm := range key.Permissions {
+		for _, userperm := range k.Permissions {
 			if userperm == perm {
-				return key, nil
+				return k, nil
 			}
 		}
 	}
@@ -123,7 +122,7 @@ func (a *APIKeyAuth) checkAPIKey(bearerToken string, addrHash hash.Hash, routeNa
 }
 
 // check authorization API key
-func (*APIKeyAuth) getAPIKey(bearerToken string) (*apikey.KeyType, error) {
+func (*APIKeyAuth) getAPIKey(bearerToken string) (*key.APIKeyType, error) {
 	if bearerToken == "" {
 		return nil, ErrInvalidAuthentication
 	}
@@ -133,10 +132,11 @@ func (*APIKeyAuth) getAPIKey(bearerToken string) (*apikey.KeyType, error) {
 	}
 	apiKeyID := bearerToken[7:]
 
-	key, err := apiKeyRepo().Fetch(apiKeyID)
+	apiKeyRepo := container.GetAPIKeyRepo()
+	k, err := apiKeyRepo.Fetch(apiKeyID)
 	if err != nil {
 		return nil, ErrInvalidAPIKey
 	}
 
-	return key, nil
+	return k, nil
 }
diff --git a/cmd/bm-server/middleware/auth/auth_jwt.go b/cmd/bm-server/middleware/auth/auth_jwt.go
index 89e36b6..18b9270 100644
--- a/cmd/bm-server/middleware/auth/auth_jwt.go
+++ b/cmd/bm-server/middleware/auth/auth_jwt.go
@@ -46,8 +46,6 @@ const (
 // ErrTokenNotValidated is returned when the token could not be validated (for any reason)
 var ErrTokenNotValidated = errors.New("token could not be validated")
 
-var accountRepo = container.GetAccountRepo
-
 // Authenticate will check if an API key matches the request
 func (mw *JwtAuth) Authenticate(req *http.Request, _ string) (context.Context, bool) {
 	// Check if the address actually exists
@@ -56,7 +54,8 @@ func (mw *JwtAuth) Authenticate(req *http.Request, _ string) (context.Context, b
 		return nil, false
 	}
 
-	if !accountRepo().Exists(*haddr) {
+	accountRepo := container.GetAccountRepo()
+	if !accountRepo.Exists(*haddr) {
 		logrus.Trace("auth: address not found")
 		return nil, false
 	}
@@ -88,7 +87,8 @@ func checkToken(bearerToken string, addr hash.Hash) (*jwt.Token, error) {
 	}
 	tokenString := bearerToken[7:]
 
-	keys, err := accountRepo().FetchKeys(addr)
+	accountRepo := container.GetAccountRepo()
+	keys, err := accountRepo.FetchKeys(addr)
 	if err != nil {
 		logrus.Trace("auth: cannot fetch keys: ", err)
 		return nil, ErrTokenNotValidated
diff --git a/go.mod b/go.mod
index 62f1083..8826ec4 100644
--- a/go.mod
+++ b/go.mod
@@ -8,14 +8,11 @@ require (
 	github.com/coreos/go-semver v0.3.0
 	github.com/davecgh/go-spew v1.1.1
 	github.com/ernesto-jimenez/httplogger v0.0.0-20150224132909-86cc44f6150a
-	github.com/fzipp/gocyclo v0.3.1 // indirect
 	github.com/gabriel-vasile/mimetype v1.1.1
 	github.com/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28
 	github.com/go-redis/redis/v8 v8.2.2
-	github.com/google/addlicense v0.0.0-20200906110928-a0294312aa76 // indirect
 	github.com/google/uuid v1.1.1
 	github.com/gookit/color v1.2.7
-	github.com/gordonklaus/ineffassign v0.0.0-20200809085317-e36bfde3bb78 // indirect
 	github.com/gorilla/handlers v1.4.2
 	github.com/gorilla/mux v1.7.4
 	github.com/hashicorp/golang-lru v0.5.4
@@ -27,6 +24,7 @@ require (
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/nightlyone/lockfile v1.0.0
 	github.com/olekukonko/tablewriter v0.0.4
+	github.com/sarulabs/di v2.0.0+incompatible
 	github.com/sirupsen/logrus v1.6.0
 	github.com/spf13/afero v1.1.2
 	github.com/spf13/cobra v1.0.0
@@ -38,10 +36,9 @@ require (
 	github.com/zalando/go-keyring v0.1.0
 	go.etcd.io/bbolt v1.3.5
 	golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
-	golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
+	golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect
 	golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
-	golang.org/x/tools v0.0.0-20201023150057-2f4fa188d925 // indirect
+	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
 	gopkg.in/yaml.v2 v2.3.0
-	honnef.co/go/tools v0.0.1-2020.1.6 // indirect
 )
diff --git a/go.sum b/go.sum
index f17702c..d853f36 100644
--- a/go.sum
+++ b/go.sum
@@ -42,12 +42,6 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fzipp/gocyclo v0.2.0 h1:z/qni2Z/kcVXbFugyB+B9eU+JzZPPi3ShLoYczJHCJo=
-github.com/fzipp/gocyclo v0.2.0/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
-github.com/fzipp/gocyclo v0.3.0 h1:GGYiyz9o4lDXRn4hA3FidRkR3agaTwbDEMd98qFG6Lk=
-github.com/fzipp/gocyclo v0.3.0/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
-github.com/fzipp/gocyclo v0.3.1 h1:A9UeX3HJSXTBzvHzhqoYVuE0eAhe+aM8XBCCwsPMZOc=
-github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
 github.com/gabriel-vasile/mimetype v1.1.1 h1:qbN9MPuRf3bstHu9zkI9jDWNfH//9+9kHxr9oRBBBOA=
 github.com/gabriel-vasile/mimetype v1.1.1/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
 github.com/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28 h1:M2Zt3G2w6Q57GZndOYk42p7RvMeO8izO8yKTfIxGqxA=
@@ -79,8 +73,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/google/addlicense v0.0.0-20200906110928-a0294312aa76 h1:JypWNzPMSgH5yL0NvFoAIsDRlKFgL0AsS3GO5bg4Pto=
-github.com/google/addlicense v0.0.0-20200906110928-a0294312aa76/go.mod h1:EMjYTRimagHs1FwlIqKyX3wAM0u3rA+McvlIIWmSamA=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -88,7 +80,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
 github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gookit/color v1.2.7 h1:4qePMNWZhrmbfYJDix+J4V2l0iVW+6jQGjicELlN14E=
@@ -97,8 +88,6 @@ github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSf
 github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherwasm v1.1.0 h1:fA2uLoctU5+T3OhOn2vYP0DVT6pxc7xhTlBB1paATqQ=
 github.com/gopherjs/gopherwasm v1.1.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
-github.com/gordonklaus/ineffassign v0.0.0-20200809085317-e36bfde3bb78 h1:U/zHjaVG/sECz5xhnh7kPH+Fv/maPbhZPcaTquo5sPg=
-github.com/gordonklaus/ineffassign v0.0.0-20200809085317-e36bfde3bb78/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
 github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
 github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
 github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
@@ -182,8 +171,9 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sarulabs/di v2.0.0+incompatible h1:gsiKbengnJvdA+XkdV7SqlH3kFQMaIqKD+rgefIRwS0=
+github.com/sarulabs/di v2.0.0+incompatible/go.mod h1:w5YAFs2sBoVzwDsWaBqJ2NzOmUHo/EZKdB3DOJ+BmHI=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
@@ -224,8 +214,6 @@ github.com/xhit/go-str2duration/v2 v2.0.0 h1:uFtk6FWB375bP7ewQl+/1wBcn840GPhnySO
 github.com/xhit/go-str2duration/v2 v2.0.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/zalando/go-keyring v0.1.0 h1:ffq972Aoa4iHNzBlUHgK5Y+k8+r/8GvcGd80/OFZb/k=
 github.com/zalando/go-keyring v0.1.0/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0=
 go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
@@ -240,18 +228,11 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
 golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -260,22 +241,15 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520 h1:Bx6FllMpG4NWDOfhMBz1VR2QYNp/SAOHPIAsaVmxfPo=
-golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -309,21 +283,6 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20201017001424-6003fad69a88 h1:ZB1XYzdDo7c/O48jzjMkvIjnC120Z9/CwgDWhePjQdQ=
-golang.org/x/tools v0.0.0-20201017001424-6003fad69a88/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
-golang.org/x/tools v0.0.0-20201021000207-d49c4edd7d96 h1:K+nJoPcImWk+ZGPHOKkDocKcQPACCz8usiCiVQYfXsk=
-golang.org/x/tools v0.0.0-20201021000207-d49c4edd7d96/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
-golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9 h1:sEvmEcJVKBNUvgCUClbUQeHOAa9U0I2Ce1BooMvVCY4=
-golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201022215848-ffe8bce740af h1:L1EV/2WjPBRxjdgDu0bbZ5gWPuQ6yX9HjG6hSb2dFV4=
-golang.org/x/tools v0.0.0-20201022215848-ffe8bce740af/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201023150057-2f4fa188d925 h1:iGGR3nU1TUd+WTF17QPTTShBEDG66IKsDIDKtC4EseY=
-golang.org/x/tools v0.0.0-20201023150057-2f4fa188d925/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -336,14 +295,16 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
 google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -354,5 +315,3 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc=
-honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=
diff --git a/internal/api/apikey.go b/internal/api/apikey.go
index 57a44a1..d90db9d 100644
--- a/internal/api/apikey.go
+++ b/internal/api/apikey.go
@@ -23,12 +23,12 @@ import (
 	"encoding/json"
 	"fmt"
 
-	"github.com/bitmaelum/bitmaelum-suite/internal/apikey"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
 )
 
 // CreateAPIKey Create a new API key
-func (api *API) CreateAPIKey(addrHash hash.Hash, key apikey.KeyType) error {
+func (api *API) CreateAPIKey(addrHash hash.Hash, key key.APIKeyType) error {
 	// Zero is not 1970, but year 1
 	var expires int64
 	if !key.Expires.IsZero() {
@@ -81,7 +81,7 @@ func (api *API) DeleteAPIKey(addrHash hash.Hash, ID string) error {
 }
 
 // ListAPIKeys lists alll API keys
-func (api *API) ListAPIKeys(addrHash hash.Hash) ([]apikey.KeyType, error) {
+func (api *API) ListAPIKeys(addrHash hash.Hash) ([]key.APIKeyType, error) {
 	url := fmt.Sprintf("/account/%s/apikey", addrHash.String())
 	body, statusCode, err := api.Get(url)
 	if err != nil {
@@ -97,7 +97,7 @@ func (api *API) ListAPIKeys(addrHash hash.Hash) ([]apikey.KeyType, error) {
 	}
 
 	// Parse body for keys
-	keys := &[]apikey.KeyType{}
+	keys := &[]key.APIKeyType{}
 	err = json.Unmarshal(body, &keys)
 	if err != nil {
 		return nil, err
@@ -107,7 +107,7 @@ func (api *API) ListAPIKeys(addrHash hash.Hash) ([]apikey.KeyType, error) {
 }
 
 // GetAPIKey gets a single key
-func (api *API) GetAPIKey(addrHash hash.Hash, ID string) (*apikey.KeyType, error) {
+func (api *API) GetAPIKey(addrHash hash.Hash, ID string) (*key.APIKeyType, error) {
 	url := fmt.Sprintf("/account/%s/apikey/%s", addrHash.String(), ID)
 	body, statusCode, err := api.Get(url)
 	if err != nil {
@@ -123,7 +123,7 @@ func (api *API) GetAPIKey(addrHash hash.Hash, ID string) (*apikey.KeyType, error
 	}
 
 	// Parse body for key
-	key := &apikey.KeyType{}
+	key := &key.APIKeyType{}
 	err = json.Unmarshal(body, &key)
 	if err != nil {
 		return nil, err
diff --git a/internal/api/authkey.go b/internal/api/authkey.go
new file mode 100644
index 0000000..64ea02c
--- /dev/null
+++ b/internal/api/authkey.go
@@ -0,0 +1,135 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package api
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
+	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
+)
+
+// CreateAuthKey will create a new authorized key on the server
+func (api *API) CreateAuthKey(addrHash hash.Hash, key key.AuthKeyType) error {
+	// Zero is not 1970, but year 1
+	var expires int64
+	if !key.Expires.IsZero() {
+		expires = key.Expires.Unix()
+	}
+
+	data, err := json.MarshalIndent(jsonOut{
+		"fingerprint": key.Fingerprint,
+		"public_key":  key.PublicKey,
+		"signature":   key.Signature,
+		"expires":     expires,
+		"description": key.Description,
+	}, "", "  ")
+	if err != nil {
+		return err
+	}
+
+	url := fmt.Sprintf("/account/%s/authkey", addrHash.String())
+	body, statusCode, err := api.Post(url, data)
+	if err != nil {
+		return err
+	}
+
+	if statusCode < 200 || statusCode > 299 {
+		return errNoSuccess
+	}
+
+	if isErrorResponse(body) {
+		return getErrorFromResponse(body)
+	}
+
+	return nil
+}
+
+// DeleteAuthKey deletes a new auth key
+func (api *API) DeleteAuthKey(addrHash hash.Hash, fingerprint string) error {
+	url := fmt.Sprintf("/account/%s/authkey/%s", addrHash.String(), fingerprint)
+	body, statusCode, err := api.Delete(url)
+	if err != nil {
+		return err
+	}
+
+	if statusCode < 200 || statusCode > 299 {
+		return errNoSuccess
+	}
+
+	if isErrorResponse(body) {
+		return getErrorFromResponse(body)
+	}
+
+	return nil
+}
+
+// ListAuthKeys lists all auth keys
+func (api *API) ListAuthKeys(addrHash hash.Hash) ([]key.AuthKeyType, error) {
+	url := fmt.Sprintf("/account/%s/authkey", addrHash.String())
+	body, statusCode, err := api.Get(url)
+	if err != nil {
+		return nil, err
+	}
+
+	if statusCode < 200 || statusCode > 299 {
+		return nil, errNoSuccess
+	}
+
+	if isErrorResponse(body) {
+		return nil, getErrorFromResponse(body)
+	}
+
+	// Parse body for keys
+	keys := &[]key.AuthKeyType{}
+	err = json.Unmarshal(body, &keys)
+	if err != nil {
+		return nil, err
+	}
+
+	return *keys, nil
+}
+
+// GetAuthKey gets a single key
+func (api *API) GetAuthKey(addrHash hash.Hash, fingerprint string) (*key.AuthKeyType, error) {
+	url := fmt.Sprintf("/account/%s/authkey/%s", addrHash.String(), fingerprint)
+	body, statusCode, err := api.Get(url)
+	if err != nil {
+		return nil, err
+	}
+
+	if statusCode < 200 || statusCode > 299 {
+		return nil, errNoSuccess
+	}
+
+	if isErrorResponse(body) {
+		return nil, getErrorFromResponse(body)
+	}
+
+	// Parse body for key
+	key := &key.AuthKeyType{}
+	err = json.Unmarshal(body, &key)
+	if err != nil {
+		return nil, err
+	}
+
+	return key, nil
+}
diff --git a/internal/client/signature.go b/internal/client/signature.go
index c83b9eb..cffa0e0 100644
--- a/internal/client/signature.go
+++ b/internal/client/signature.go
@@ -34,7 +34,7 @@ import (
 // SignHeader will add a client signature to a message header. This can be used to proof the origin of the message
 func SignHeader(header *message.Header, privKey bmcrypto.PrivKey) error {
 	// Already signed? Then skip
-	if len(header.ClientSignature) > 0 {
+	if len(header.Signatures.Client) > 0 {
 		fmt.Println("already signed")
 		return nil
 	}
@@ -50,7 +50,7 @@ func SignHeader(header *message.Header, privKey bmcrypto.PrivKey) error {
 		return err
 	}
 
-	header.ClientSignature = base64.StdEncoding.EncodeToString(sig)
+	header.Signatures.Client = base64.StdEncoding.EncodeToString(sig)
 	return nil
 }
 
@@ -65,17 +65,17 @@ func VerifyHeader(header message.Header) bool {
 	}
 
 	// No header at all
-	if len(header.ClientSignature) == 0 {
+	if len(header.Signatures.Client) == 0 {
 		return false
 	}
 
 	// Store signature
-	targetSignature, err := base64.StdEncoding.DecodeString(header.ClientSignature)
+	targetSignature, err := base64.StdEncoding.DecodeString(header.Signatures.Client)
 	if err != nil {
 		return false
 	}
-	header.ServerSignature = ""
-	header.ClientSignature = ""
+	header.Signatures.Server = ""
+	header.Signatures.Client = ""
 
 	// Generate hash
 	data, err := json.Marshal(&header)
@@ -84,20 +84,20 @@ func VerifyHeader(header message.Header) bool {
 	}
 	h := sha256.Sum256(data)
 
-	// If we have sent an onbehalf key, we need to validate this first
+	// If we have sent an authorized key key, we need to validate this first
 	pubKey := addr.PublicKey
-	if header.From.OnBehalfSignature != "" && header.From.OnBehalfPublicKey != nil {
-		pubKey = *header.From.OnBehalfPublicKey
+	if header.From.SignedBy == message.SignedByTypeAuthorized {
+		pubKey = *header.AuthorizedBy.PublicKey
 
-		// Verify our onbehalf key
-		msg := hash.New(header.From.OnBehalfPublicKey.String())
-		sig, err := base64.StdEncoding.DecodeString(header.From.OnBehalfSignature)
+		// Verify our authorized key
+		msg := hash.New(header.AuthorizedBy.PublicKey.String())
+		sig, err := base64.StdEncoding.DecodeString(header.AuthorizedBy.Signature)
 		if err != nil {
 			return false
 		}
 		ok, err := bmcrypto.Verify(addr.PublicKey, msg.Byte(), sig)
 		if err != nil || !ok {
-			// Cannot validate the onbehalf key
+			// Cannot validate the authorized key
 			return false
 		}
 	}
diff --git a/internal/config/config.go b/internal/config/config.go
index 2f6af47..cf828c3 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -72,24 +72,18 @@ func LoadClientConfigOrPass(configPath string) error {
 
 	// Try custom path first
 	if configPath != "" {
-		err = readConfigPath(configPath, Client.LoadConfig)
-		if err != nil {
-			return err
-		}
+		return readConfigPath(configPath, "from commandline", Client.LoadConfig)
 	}
 
 	configPath = os.Getenv("BITMAELUM_CLIENT_CONFIG")
 	if configPath != "" {
-		err = readConfigPath(configPath, Client.LoadConfig)
-		if err != nil {
-			return err
-		}
+		return readConfigPath(configPath, "from BITMAELUM_CLIENT_CONFIG environment", Client.LoadConfig)
 	}
 
 	// try on our search paths
 	for _, p := range getSearchPaths() {
 		p = filepath.Join(p, ClientConfigFile)
-		err = readConfigPath(p, Client.LoadConfig)
+		err = readConfigPath(p, "from hardcoded search path", Client.LoadConfig)
 		if err == nil || err != errNotFound {
 			return err
 		}
@@ -104,18 +98,18 @@ func LoadServerConfigOrPass(configPath string) error {
 
 	// Try custom path first
 	if configPath != "" {
-		return readConfigPath(configPath, Server.LoadConfig)
+		return readConfigPath(configPath, "from commandline", Server.LoadConfig)
 	}
 
 	configPath = os.Getenv("BITMAELUM_SERVER_CONFIG")
 	if configPath != "" {
-		return readConfigPath(configPath, Server.LoadConfig)
+		return readConfigPath(configPath, "from BITMAELUM_SERVER_CONFIG environment", Server.LoadConfig)
 	}
 
 	// try on our search paths
 	for _, p := range getSearchPaths() {
 		p = filepath.Join(p, ServerConfigFile)
-		err = readConfigPath(p, Server.LoadConfig)
+		err = readConfigPath(p, "from hardcoded search path", Server.LoadConfig)
 		if err == nil || err != errNotFound {
 			return err
 		}
@@ -125,10 +119,10 @@ func LoadServerConfigOrPass(configPath string) error {
 }
 
 // Expands the given path and loads the configuration
-func readConfigPath(p string, loader func(r io.Reader) error) error {
+func readConfigPath(p, src string, loader func(r io.Reader) error) error {
 	p, _ = homedir.Expand(p)
 
-	triedPaths = append(triedPaths, p)
+	triedPaths = append(triedPaths, p+" ("+src+")")
 
 	f, err := fs.Open(p)
 	if err != nil {
diff --git a/internal/config/paths_windows.go b/internal/config/paths_windows.go
index cc11b7b..677050a 100644
--- a/internal/config/paths_windows.go
+++ b/internal/config/paths_windows.go
@@ -25,6 +25,5 @@ func getSearchPaths() []string {
 	return []string{
 		"./",
 		"%ProgramData%/BitMaelum/etc/bitmaelum/",
-		"/etc/bitmaelum/",
 	}
 }
diff --git a/internal/container/account.go b/internal/container/account.go
index 0a6397e..7358b10 100644
--- a/internal/container/account.go
+++ b/internal/container/account.go
@@ -20,11 +20,25 @@
 package container
 
 import (
+	"sync"
+
 	"github.com/bitmaelum/bitmaelum-suite/internal/account"
 	"github.com/bitmaelum/bitmaelum-suite/internal/config"
 )
 
-// GetAccountRepo retrieves an account repository
-func GetAccountRepo() account.Repository {
-	return account.NewFileRepository(config.Server.Paths.Accounts)
+var (
+	accountOnce       sync.Once
+	accountRepository account.Repository
+)
+
+func setupAccountRepo() (interface{}, error) {
+	accountOnce.Do(func() {
+		accountRepository = account.NewFileRepository(config.Server.Paths.Accounts)
+	})
+
+	return accountRepository, nil
+}
+
+func init() {
+	Set("account", setupAccountRepo)
 }
diff --git a/internal/container/apikey.go b/internal/container/apikey.go
index f78fa41..56d89a9 100644
--- a/internal/container/apikey.go
+++ b/internal/container/apikey.go
@@ -23,19 +23,17 @@ import (
 	"os"
 	"sync"
 
-	"github.com/bitmaelum/bitmaelum-suite/internal/apikey"
 	"github.com/bitmaelum/bitmaelum-suite/internal/config"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
 	"github.com/go-redis/redis/v8"
 )
 
 var (
 	apikeyOnce       sync.Once
-	apikeyRepository apikey.Repository
+	apikeyRepository key.APIKeyRepo
 )
 
-// GetAPIKeyRepo returns the repository for storing and fetching api keys
-func GetAPIKeyRepo() apikey.Repository {
-
+func setupAPIKeyRepo() (interface{}, error) {
 	apikeyOnce.Do(func() {
 		// If redis.host is set on the config file it will use redis instead of bolt
 		if config.Server.Redis.Host != "" {
@@ -44,7 +42,7 @@ func GetAPIKeyRepo() apikey.Repository {
 				DB:   config.Server.Redis.Db,
 			}
 
-			apikeyRepository = apikey.NewRedisRepository(&opts)
+			apikeyRepository = key.NewAPIKeyRedisRepository(&opts)
 			return
 		}
 
@@ -54,8 +52,12 @@ func GetAPIKeyRepo() apikey.Repository {
 		if config.Server.Bolt.DatabasePath == "" {
 			config.Server.Bolt.DatabasePath = os.TempDir()
 		}
-		apikeyRepository = apikey.NewBoltRepository(config.Server.Bolt.DatabasePath)
+		apikeyRepository = key.NewAPIBoltRepository(config.Server.Bolt.DatabasePath)
 	})
 
-	return apikeyRepository
+	return apikeyRepository, nil
+}
+
+func init() {
+	Set("api-key", setupAPIKeyRepo)
 }
diff --git a/internal/container/authkey.go b/internal/container/authkey.go
new file mode 100644
index 0000000..1a7e171
--- /dev/null
+++ b/internal/container/authkey.go
@@ -0,0 +1,68 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package container
+
+import (
+	"os"
+	"sync"
+
+	"github.com/bitmaelum/bitmaelum-suite/internal/config"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
+	"github.com/go-redis/redis/v8"
+	"github.com/sirupsen/logrus"
+)
+
+var (
+	authKeyOnce       sync.Once
+	authKeyRepository key.AuthKeyRepo
+)
+
+func setupAuthKeyRepo() (interface{}, error) {
+	authKeyOnce.Do(func() {
+		logrus.Trace("Setting up authKeyOnce")
+
+		// If redis.host is set on the config file it will use redis instead of bolt
+		logrus.Trace("config.Server.Redis.Host:", config.Server)
+		if config.Server.Redis.Host != "" {
+			opts := redis.Options{
+				Addr: config.Server.Redis.Host,
+				DB:   config.Server.Redis.Db,
+			}
+
+			authKeyRepository = key.NewAuthKeyRedisRepository(&opts)
+			return
+		}
+
+		// If redis is not set then it will use BoltDB as default
+
+		// Use temp dir if no dir is given
+		if config.Server.Bolt.DatabasePath == "" {
+			config.Server.Bolt.DatabasePath = os.TempDir()
+		}
+		authKeyRepository = key.NewAuthBoltRepository(config.Server.Bolt.DatabasePath)
+	})
+
+	logrus.Trace("returning: ", authKeyRepository)
+	return authKeyRepository, nil
+}
+
+func init() {
+	Set("auth-key", setupAuthKeyRepo)
+}
diff --git a/internal/container/container.go b/internal/container/container.go
new file mode 100644
index 0000000..61c3c73
--- /dev/null
+++ b/internal/container/container.go
@@ -0,0 +1,125 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package container
+
+/*
+ * This is a very basic container system. We use this to easily locate services (ie: service locator) inside our code.
+ * With this container, we can also easily change a service to another instance, like a mocking service.
+ * From the code point of view, we still can use container.Get("service"), while we have set this to a mocked service.
+ *
+ * There is no functionality for dependencies etc, but we have shared/unshared services.
+ */
+
+// Container is the main container structure holding all service
+type Container struct {
+	definitions map[string]*ServiceDefinition
+	resolved    map[string]interface{}
+}
+
+// The main container instance
+var container = Container{
+	definitions: make(map[string]*ServiceDefinition), // List of definitions
+	resolved:    make(map[string]interface{}),        // Resolved definition functions
+}
+
+// Get returns a service from the container
+func Get(key string) interface{} {
+	return container.Get(key)
+}
+
+// ServiceFunc is the function that needs to be resolved in the definition
+type ServiceFunc func() (interface{}, error)
+
+// Set sets a service from the container as a singleton
+func Set(key string, build ServiceFunc) {
+	container.Set(key, ServiceTypeSingle, build)
+}
+
+// SetMulti sets a service from the container. It will return a new instance on each call
+func SetMulti(key string, build ServiceFunc) {
+	container.Set(key, ServiceTypeMulti, build)
+}
+
+// ServiceType defines what kind of service it is (singleton, or new instance on each call)
+type ServiceType int
+
+// Service types
+const (
+	ServiceTypeSingle ServiceType = iota
+	ServiceTypeMulti
+)
+
+// ServiceDefinition is a single service definition
+type ServiceDefinition struct {
+	Func ServiceFunc
+	Type ServiceType
+}
+
+// NewContainer will create a new container
+func NewContainer() Container {
+	c := Container{
+		definitions: make(map[string]*ServiceDefinition),
+		resolved:    make(map[string]interface{}),
+	}
+
+	return c
+}
+
+// Set will set the function for the given service
+func (c Container) Set(key string, t ServiceType, f ServiceFunc) {
+	c.definitions[key] = &ServiceDefinition{
+		Func: f,
+		Type: t,
+	}
+
+	// Delete existing resolved object if any
+	delete(c.resolved, key)
+}
+
+// Get will retrieve the function for the given service, or nil when not found
+func (c Container) Get(key string) interface{} {
+	s, ok := c.definitions[key]
+	if !ok {
+		return nil
+	}
+
+	// Multi means we don't use a shared instance but instead instantiate a new object each time called
+	if s.Type == ServiceTypeMulti {
+		obj, err := s.Func()
+		if err != nil {
+			return nil
+		}
+		return obj
+	}
+
+	// Already resolved, return
+	if c.resolved[key] != nil {
+		return c.resolved[key]
+	}
+
+	// Create instance, save it and return
+	obj, err := s.Func()
+	if err != nil {
+		return nil
+	}
+	c.resolved[key] = obj
+
+	return obj
+}
diff --git a/internal/container/helpers.go b/internal/container/helpers.go
new file mode 100644
index 0000000..809b12d
--- /dev/null
+++ b/internal/container/helpers.go
@@ -0,0 +1,65 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package container
+
+import (
+	"github.com/bitmaelum/bitmaelum-suite/internal/account"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
+	"github.com/bitmaelum/bitmaelum-suite/internal/resolver"
+	"github.com/bitmaelum/bitmaelum-suite/internal/subscription"
+	"github.com/bitmaelum/bitmaelum-suite/internal/ticket"
+)
+
+/*
+ * Simple helpers that makes it easier to fetch services. Normally, these things should be generated by a full-fledged
+ * dependency container system I guess. It basically converts the interface{} returned by the container into the
+ * correct interface.
+ */
+
+// GetAccountRepo will return the current account repository
+func GetAccountRepo() account.Repository {
+	// return Get("account").(func() account.Repository)()
+	return Get("account").(account.Repository)
+}
+
+// GetAPIKeyRepo will return the current api key repository
+func GetAPIKeyRepo() key.APIKeyRepo {
+	return Get("api-key").(key.APIKeyRepo)
+}
+
+// GetAuthKeyRepo will return the current auth key repository
+func GetAuthKeyRepo() key.AuthKeyRepo {
+	return Get("auth-key").(key.AuthKeyRepo)
+}
+
+// GetResolveService will return the current resolver service
+func GetResolveService() *resolver.Service {
+	return Get("resolver").(*resolver.Service)
+}
+
+// GetSubscriptionRepo will return the current subscription repository
+func GetSubscriptionRepo() subscription.Repository {
+	return Get("subscription").(subscription.Repository)
+}
+
+// GetTicketRepo will return the current ticket repository
+func GetTicketRepo() ticket.Repository {
+	return Get("ticket").(ticket.Repository)
+}
diff --git a/internal/container/proof-of-work.go b/internal/container/proof-of-work.go
index 1fc75cb..6e4b846 100644
--- a/internal/container/proof-of-work.go
+++ b/internal/container/proof-of-work.go
@@ -20,30 +20,42 @@
 package container
 
 import (
+	"os"
+	"sync"
+
 	"github.com/bitmaelum/bitmaelum-suite/cmd/bm-server/storage"
 	"github.com/bitmaelum/bitmaelum-suite/internal/config"
 	"github.com/go-redis/redis/v8"
 )
 
-var powService storage.Storable
+var (
+	proofOnce    sync.Once
+	proofService storage.Storable
+)
+
+func setupProofService() (interface{}, error) {
+	proofOnce.Do(func() {
+		//If redis.host is set on the config file it will use redis instead of bolt
+		if config.Server.Redis.Host != "" {
+			opts := redis.Options{
+				Addr: config.Server.Redis.Host,
+				DB:   config.Server.Redis.Db,
+			}
 
-// GetProofOfWorkService returns a service that can store a proof of work
-func GetProofOfWorkService() storage.Storable {
-	if powService != nil {
-		return powService
-	}
+			proofService = storage.NewRedis(&opts)
+		} else {
+			if config.Server.Bolt.DatabasePath == "" {
+				config.Server.Bolt.DatabasePath = os.TempDir()
+			}
 
-	//If redis.host is set on the config file it will use redis instead of bolt
-	if config.Server.Redis.Host != "" {
-		opts := redis.Options{
-			Addr: config.Server.Redis.Host,
-			DB:   config.Server.Redis.Db,
+			proofService = storage.NewBolt(&config.Server.Bolt.DatabasePath)
 		}
 
-		powService = storage.NewRedis(&opts)
-	} else {
-		powService = storage.NewBolt(&config.Server.Bolt.DatabasePath)
-	}
+	})
+
+	return proofService, nil
+}
 
-	return powService
+func init() {
+	Set("proof-of-work", setupProofService)
 }
diff --git a/internal/container/resolver.go b/internal/container/resolver.go
index 80bc0cd..587f7f0 100644
--- a/internal/container/resolver.go
+++ b/internal/container/resolver.go
@@ -20,54 +20,40 @@
 package container
 
 import (
+	"sync"
+
 	"github.com/bitmaelum/bitmaelum-suite/internal/config"
 	"github.com/bitmaelum/bitmaelum-suite/internal/resolver"
 )
 
-// We can have multiple resolvers to resolve a single address. We could resolve locally, remotely through resolver-services, or through DHT.
-// We chain them all together with the ChainRepository
+// We can have multiple resolvers to resolve a single address. We could resolve locally, remotely through
+// resolver-services, or through DHT. We chain them all together with the ChainRepository
 
 var (
-	resolveService          *resolver.Service
-	chainResolverRepository *resolver.ChainRepository
+	resolveOnce    sync.Once
+	resolveService *resolver.Service
 )
 
-// SetResolveService allows you to easily set your own resolve service. Used for unit testing
-func SetResolveService(s *resolver.Service) {
-	resolveService = s
-}
-
-// GetResolveService retrieves a resolver service
-func GetResolveService() *resolver.Service {
-	if resolveService != nil {
-		return resolveService
-	}
-
-	repo := getChainRepository()
-	if config.Server.Resolver.Sqlite.Enabled {
-		r, err := getSQLiteRepository(config.Server.Resolver.Sqlite.Dsn)
-		if err == nil {
-			_ = repo.Add(r)
+func setupResolverService() (interface{}, error) {
+	resolveOnce.Do(func() {
+		repo := resolver.NewChainRepository()
+		if config.Server.Resolver.Sqlite.Enabled {
+			r, err := getSQLiteRepository(config.Server.Resolver.Sqlite.Dsn)
+			if err == nil {
+				_ = repo.Add(r)
+			}
+		}
+		if config.Client.Resolver.Remote.Enabled {
+			_ = repo.Add(*getRemoteRepository(config.Client.Resolver.Remote.URL, config.Client.Server.DebugHTTP))
+		}
+		if config.Server.Resolver.Remote.Enabled {
+			_ = repo.Add(*getRemoteRepository(config.Server.Resolver.Remote.URL, false))
 		}
-	}
-	if config.Client.Resolver.Remote.Enabled {
-		_ = repo.Add(*getRemoteRepository(config.Client.Resolver.Remote.URL, config.Client.Server.DebugHTTP))
-	}
-	if config.Server.Resolver.Remote.Enabled {
-		_ = repo.Add(*getRemoteRepository(config.Server.Resolver.Remote.URL, false))
-	}
-
-	resolveService = resolver.KeyRetrievalService(repo)
-	return resolveService
-}
 
-func getChainRepository() *resolver.ChainRepository {
-	if chainResolverRepository != nil {
-		return chainResolverRepository
-	}
+		resolveService = resolver.KeyRetrievalService(repo)
+	})
 
-	chainResolverRepository = resolver.NewChainRepository()
-	return chainResolverRepository
+	return resolveService, nil
 }
 
 func getRemoteRepository(url string, debug bool) *resolver.Repository {
@@ -79,3 +65,7 @@ func getSQLiteRepository(dsn string) (resolver.Repository, error) {
 	repo, err := resolver.NewSqliteRepository(dsn)
 	return repo, err
 }
+
+func init() {
+	Set("resolver", setupResolverService)
+}
diff --git a/internal/container/subscription.go b/internal/container/subscription.go
index f7a5d43..0f39658 100644
--- a/internal/container/subscription.go
+++ b/internal/container/subscription.go
@@ -20,10 +20,12 @@
 package container
 
 import (
+	"os"
 	"sync"
 
 	"github.com/bitmaelum/bitmaelum-suite/internal/config"
 	"github.com/bitmaelum/bitmaelum-suite/internal/subscription"
+
 	"github.com/go-redis/redis/v8"
 )
 
@@ -32,11 +34,9 @@ var (
 	subscriptionRepository subscription.Repository
 )
 
-// GetSubscriptionRepo returns the repository for storing and fetching subscriptions
-func GetSubscriptionRepo() subscription.Repository {
-
+func setupSubscriptionRepo() (interface{}, error) {
 	subscriptionOnce.Do(func() {
-		//If redis.host is set on the config file it will use redis instead of bolt
+		// If redis.host is set on the config file it will use redis instead of bolt
 		if config.Server.Redis.Host != "" {
 			opts := redis.Options{
 				Addr: config.Server.Redis.Host,
@@ -47,9 +47,17 @@ func GetSubscriptionRepo() subscription.Repository {
 			return
 		}
 
-		//If redis is not set then it will use BoltDB as default
+		// If redis is not set then it will use BoltDB as default
+		if config.Server.Bolt.DatabasePath == "" {
+			config.Server.Bolt.DatabasePath = os.TempDir()
+		}
+
 		subscriptionRepository = subscription.NewBoltRepository(config.Server.Bolt.DatabasePath)
 	})
 
-	return subscriptionRepository
+	return subscriptionRepository, nil
+}
+
+func init() {
+	Set("subscription", setupSubscriptionRepo)
 }
diff --git a/internal/container/ticket.go b/internal/container/ticket.go
index 5aa18f4..b18ef91 100644
--- a/internal/container/ticket.go
+++ b/internal/container/ticket.go
@@ -20,6 +20,7 @@
 package container
 
 import (
+	"os"
 	"sync"
 
 	"github.com/bitmaelum/bitmaelum-suite/internal/config"
@@ -33,9 +34,7 @@ var (
 	ticketRepository ticket.Repository
 )
 
-// GetTicketRepo returns the repository for storing and fetching tickets
-func GetTicketRepo() ticket.Repository {
-
+func setupTicketRepo() (interface{}, error) {
 	ticketOnce.Do(func() {
 		//If redis.host is set on the config file it will use redis instead of bolt
 		if config.Server.Redis.Host != "" {
@@ -49,8 +48,16 @@ func GetTicketRepo() ticket.Repository {
 		}
 
 		//If redis is not set then it will use BoltDB as default
+		if config.Server.Bolt.DatabasePath == "" {
+			config.Server.Bolt.DatabasePath = os.TempDir()
+		}
+
 		ticketRepository = ticket.NewBoltRepository(config.Server.Bolt.DatabasePath)
 	})
 
-	return ticketRepository
+	return ticketRepository, nil
+}
+
+func init() {
+	Set("ticket", setupTicketRepo)
 }
diff --git a/internal/key/README.md b/internal/key/README.md
new file mode 100644
index 0000000..a9e6f17
--- /dev/null
+++ b/internal/key/README.md
@@ -0,0 +1,48 @@
+We allow different kind of keys to be stored. Examples are API keys, and Auth keys. In practice, these keys are very
+similar and creating their own storage systems for them would result in lots of duplicate code (which our CI system 
+does not approve of).
+
+So instead, we have a generic "key" system that allows us to store multiple types of keys, each with their own 
+properties. This however, results in a more complex setup, as Go isn't able to do OO.
+
+
+    +-------------------+           +------------------+          +-----------+
+    | ApiKeyRepository  |           | (storageBackend) |          | boltRepo  |
+    |   (ApiKeyRepo)    | ------+-> |                  | -+-----> |           |
+    +-------------------+       |   +------------------+  |       +-----------+
+                                |                         |
+                                |                         |
+                                |                         |
+    +-------------------+       |                         |       +-----------+
+    | AuthKeyRepository |       |                         |       | mockRepo  |
+    |   (AuthKeyRepo)   | ------+                         +-----> |           |
+    +-------------------+                                 |       +-----------+
+                                                          |       +-----------+
+                                                          +-----> | redisRepo |
+                                                                  +-----------+
+                                                                 
+Both the ApiKeyRepository and AuthKeyRepository deal with the corresponding ApiKeyType and AuthKeyType structures. 
+They both are a small wrapper around a storage repository that deals with interfaces{}. So, we convert these 
+interfaces to actual correct types in the api and key repository:
+
+``` 
+func (a AuthKeyRepository) Fetch(ID string) (AuthKeyType, error) {
+    v := &AuthkeyType{}
+	err := a.repo.Fetch(ID, v)
+	return v, err
+}
+```
+
+The storage repository will simply fetch data from the storage, and unmarshals this trough the given `v` value. 
+Sometimes we need to use GenericKey functionality (`GetID()`, `GetHashAddress()`), which is where we typecast this
+at certain places in the storage repositories.
+
+There is a bit of reflection going on when dealing with lists. This is because we need to allocate items, but we don't 
+know which items. We deal with this in the `Fetch` methods by passing the actual storage value, but we cannot do this
+in the `FetchByHash` method. Instead, we pass again, a single structure (like in `Fetch()`), and based on that value,
+we create with reflection a new variable. This variable gets marshalled through JSON and stored in the list.
+
+Finally, the ApiKey and AuthKey repositories will retrieve these lists with undefined interfaces, and cast them to
+either `[]ApiKeyType` or `[]AuthKeyType`.
+
+With the amount of effort it took to get this up and running decently, DRY isn't always holy. 
diff --git a/internal/apikey/apikey.go b/internal/key/api_key.go
similarity index 60%
rename from internal/apikey/apikey.go
rename to internal/key/api_key.go
index d74fc07..6d87e2a 100644
--- a/internal/apikey/apikey.go
+++ b/internal/key/api_key.go
@@ -17,7 +17,7 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package apikey
+package key
 
 import (
 	"math/rand"
@@ -27,54 +27,64 @@ import (
 	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
 )
 
-// KeyType represents a key with a validity and permissions. When admin is true, permission checks are always true
-type KeyType struct {
-	ID          string     `json:"key"`
-	Expires     time.Time  `json:"expires"`
-	Permissions []string   `json:"permissions"`
-	Admin       bool       `json:"admin"`
-	AddrHash    *hash.Hash `json:"addr_hash"`
-	Desc        string     `json:"description"`
+// APIKeyType represents a key with a validity and permissions. When admin is true, permission checks are always true
+type APIKeyType struct {
+	ID          string     `json:"key"`          // ID
+	AddressHash *hash.Hash `json:"address_hash"` // Address hash
+	Expires     time.Time  `json:"expires"`      // Time the key expires
+	Permissions []string   `json:"permissions"`  // List of permissions for this key
+	Admin       bool       `json:"admin"`        // Admin key or not?
+	Desc        string     `json:"description"`  // Description of the key
 }
 
-// NewAdminKey creates a new admin key
-func NewAdminKey(expiry time.Time, desc string) KeyType {
-	return KeyType{
+// GetID returns ID
+func (key *APIKeyType) GetID() string {
+	return key.ID
+}
+
+// GetAddressHash returns address hash or nil
+func (key *APIKeyType) GetAddressHash() *hash.Hash {
+	return key.AddressHash
+}
+
+// NewAPIAdminKey creates a new admin key
+func NewAPIAdminKey(expiry time.Time, desc string) APIKeyType {
+	return APIKeyType{
 		ID:          GenerateKey("BMK-", 32),
+		AddressHash: nil,
 		Expires:     expiry,
 		Permissions: nil,
 		Admin:       true,
-		AddrHash:    nil,
 		Desc:        desc,
 	}
 }
 
-// NewAccountKey creates a new key with the given permissions and duration for the given account
-func NewAccountKey(addrHash hash.Hash, perms []string, expiry time.Time, desc string) KeyType {
-	return KeyType{
+// NewAPIAccountKey creates a new key with the given permissions and duration for the given account
+func NewAPIAccountKey(addrHash hash.Hash, perms []string, expiry time.Time, desc string) APIKeyType {
+	return APIKeyType{
 		ID:          GenerateKey("BMK-", 32),
+		AddressHash: &addrHash,
 		Expires:     expiry,
 		Permissions: perms,
 		Admin:       false,
-		AddrHash:    &addrHash,
 		Desc:        desc,
 	}
 }
 
-// NewKey creates a new key with the given permissions and duration
-func NewKey(perms []string, expiry time.Time, desc string) KeyType {
-	return KeyType{
+// NewAPIKey creates a new key with the given permissions and duration
+func NewAPIKey(perms []string, expiry time.Time, desc string) APIKeyType {
+	return APIKeyType{
 		ID:          GenerateKey("BMK-", 32),
+		AddressHash: nil,
 		Expires:     expiry,
 		Permissions: perms,
 		Admin:       false,
-		AddrHash:    nil,
 		Desc:        desc,
 	}
 }
 
 // HasPermission returns true when this key contains the given permission, or when the key is an admin key (granted all permissions)
-func (key *KeyType) HasPermission(perm string, addrHash *hash.Hash) bool {
+func (key *APIKeyType) HasPermission(perm string, addrHash *hash.Hash) bool {
 	if key.Admin {
 		return true
 	}
@@ -82,7 +92,7 @@ func (key *KeyType) HasPermission(perm string, addrHash *hash.Hash) bool {
 	perm = strings.ToLower(perm)
 
 	for _, p := range key.Permissions {
-		if strings.ToLower(p) == perm && (addrHash == nil || addrHash.String() == key.AddrHash.String()) {
+		if strings.ToLower(p) == perm && (addrHash == nil || addrHash.String() == key.AddressHash.String()) {
 			return true
 		}
 	}
@@ -90,14 +100,6 @@ func (key *KeyType) HasPermission(perm string, addrHash *hash.Hash) bool {
 	return false
 }
 
-// Repository is a repository to fetch and store API keys
-type Repository interface {
-	Fetch(ID string) (*KeyType, error)
-	FetchByHash(h string) ([]KeyType, error)
-	Store(key KeyType) error
-	Remove(key KeyType) error
-}
-
 // GenerateKey generates a random key based on a given string length
 func GenerateKey(prefix string, n int) string {
 	var letter = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
diff --git a/internal/key/api_key_repo.go b/internal/key/api_key_repo.go
new file mode 100644
index 0000000..6e67e2b
--- /dev/null
+++ b/internal/key/api_key_repo.go
@@ -0,0 +1,120 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package key
+
+import (
+	"path/filepath"
+
+	"github.com/bitmaelum/bitmaelum-suite/internal"
+	"github.com/go-redis/redis/v8"
+	"github.com/sirupsen/logrus"
+	bolt "go.etcd.io/bbolt"
+)
+
+// APIKeyRepository is a repository to fetch and store API keys
+type APIKeyRepository struct {
+	storageRepo StorageBackend
+}
+
+// NewAPIKeyRedisRepository initializes a new repository
+func NewAPIKeyRedisRepository(opts *redis.Options) APIKeyRepo {
+	c := redis.NewClient(opts)
+
+	// Setup generic repository
+	repo := &redisRepo{
+		client:    &internal.RedisBridge{Client: *c},
+		context:   c.Context(),
+		KeyPrefix: "apikey",
+	}
+
+	return &APIKeyRepository{
+		storageRepo: repo,
+	}
+}
+
+// NewAPIBoltRepository initializes a new BoltDb repository
+func NewAPIBoltRepository(dbPath string) APIKeyRepo {
+	p := filepath.Join(dbPath, BoltDBFile)
+	db, err := bolt.Open(p, 0600, nil)
+	if err != nil {
+		logrus.Error("Unable to open filepath ", p, err)
+		return nil
+	}
+
+	repo := boltRepo{
+		client:     db,
+		BucketName: "api",
+	}
+
+	return &APIKeyRepository{
+		storageRepo: repo,
+	}
+}
+
+// NewAPIMockRepository initializes a new mock repository
+func NewAPIMockRepository() APIKeyRepo {
+	repo := &mockRepo{
+		keys: map[string][]byte{},
+		addr: map[string]map[string]int{},
+	}
+
+	return &APIKeyRepository{
+		storageRepo: repo,
+	}
+}
+
+// FetchByHash will fetch all api keys for the given address hash
+func (a APIKeyRepository) FetchByHash(h string) ([]APIKeyType, error) {
+	v := &APIKeyType{}
+	l, err := a.storageRepo.FetchByHash(h, v)
+
+	var ll []APIKeyType
+	for _, item := range l.([]interface{}) {
+		if item == nil {
+			continue
+		}
+
+		p := item.(*APIKeyType)
+		ll = append(ll, *p)
+	}
+
+	return ll, err
+}
+
+// Fetch will get a single API key
+func (a APIKeyRepository) Fetch(ID string) (*APIKeyType, error) {
+	v := &APIKeyType{}
+	err := a.storageRepo.Fetch(ID, &v)
+	if err != nil {
+		return nil, errKeyNotFound
+	}
+
+	return v, err
+}
+
+// Store will store a single api key
+func (a APIKeyRepository) Store(v APIKeyType) error {
+	return a.storageRepo.Store(&v)
+}
+
+// Remove will remove a single api key
+func (a APIKeyRepository) Remove(v APIKeyType) error {
+	return a.storageRepo.Remove(&v)
+}
diff --git a/internal/key/auth_key.go b/internal/key/auth_key.go
new file mode 100644
index 0000000..7712a5a
--- /dev/null
+++ b/internal/key/auth_key.go
@@ -0,0 +1,59 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package key
+
+import (
+	"time"
+
+	"github.com/bitmaelum/bitmaelum-suite/pkg/bmcrypto"
+	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
+)
+
+// AuthKeyType represents a public key that is authorized to send email. This public key is signed by the private key of the owner and stored in the signature.
+type AuthKeyType struct {
+	Fingerprint string           `json:"fingerprint"`  // Fingerprint / ID
+	AddressHash hash.Hash        `json:"address_hash"` // Address hash
+	Expires     time.Time        `json:"expires"`      // Expiry time
+	PublicKey   *bmcrypto.PubKey `json:"public_key"`   // Actual public key
+	Signature   string           `json:"signature"`    // Signed public key by the owner's private key
+	Description string           `json:"description"`  // User description
+}
+
+// GetID returns fingerprint of the public key
+func (a AuthKeyType) GetID() string {
+	return a.Fingerprint
+}
+
+// GetAddressHash returns address hash
+func (a AuthKeyType) GetAddressHash() *hash.Hash {
+	return &a.AddressHash
+}
+
+// NewAuthKey creates a new authorized key
+func NewAuthKey(addrHash hash.Hash, pubKey *bmcrypto.PubKey, signature string, expiry time.Time, desc string) AuthKeyType {
+	return AuthKeyType{
+		Fingerprint: pubKey.Fingerprint(),
+		AddressHash: addrHash,
+		Signature:   signature,
+		PublicKey:   pubKey,
+		Expires:     expiry,
+		Description: desc,
+	}
+}
diff --git a/internal/key/auth_key_repo.go b/internal/key/auth_key_repo.go
new file mode 100644
index 0000000..a2c080f
--- /dev/null
+++ b/internal/key/auth_key_repo.go
@@ -0,0 +1,121 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package key
+
+import (
+	"path/filepath"
+
+	"github.com/bitmaelum/bitmaelum-suite/internal"
+	"github.com/go-redis/redis/v8"
+	"github.com/sirupsen/logrus"
+	bolt "go.etcd.io/bbolt"
+)
+
+// AuthKeyRepository is a repository to fetch and store auth keys
+type AuthKeyRepository struct {
+	storageRepo StorageBackend
+}
+
+// NewAuthKeyRedisRepository initializes a new Redis repository
+func NewAuthKeyRedisRepository(opts *redis.Options) AuthKeyRepo {
+	c := redis.NewClient(opts)
+
+	// Setup generic repository
+	repo := &redisRepo{
+		client:    &internal.RedisBridge{Client: *c},
+		context:   c.Context(),
+		KeyPrefix: "authkey",
+	}
+
+	return &AuthKeyRepository{
+		storageRepo: repo,
+	}
+}
+
+// NewAuthBoltRepository initializes a new BoltDb repository
+func NewAuthBoltRepository(dbPath string) AuthKeyRepo {
+	p := filepath.Join(dbPath, BoltDBFile)
+	db, err := bolt.Open(p, 0600, nil)
+	if err != nil {
+		logrus.Error("Unable to open filepath ", p, err)
+		return nil
+	}
+
+	repo := boltRepo{
+		client:     db,
+		BucketName: "auth",
+	}
+
+	return &AuthKeyRepository{
+		storageRepo: repo,
+	}
+}
+
+// NewAuthMockRepository initializes a new mock repository
+func NewAuthMockRepository() AuthKeyRepo {
+	repo := &mockRepo{
+		keys: map[string][]byte{},
+		addr: map[string]map[string]int{},
+	}
+
+	return &AuthKeyRepository{
+		storageRepo: repo,
+	}
+}
+
+// FetchByHash will fetch all api keys for the given address hash
+func (a AuthKeyRepository) FetchByHash(h string) ([]AuthKeyType, error) {
+	v := &AuthKeyType{}
+	l, err := a.storageRepo.FetchByHash(h, v)
+
+	var ll []AuthKeyType
+	for _, item := range l.([]interface{}) {
+		if item == nil {
+			continue
+		}
+
+		p := item.(*AuthKeyType)
+		ll = append(ll, *p)
+	}
+
+	return ll, err
+}
+
+// Fetch will get a single API key
+func (a AuthKeyRepository) Fetch(ID string) (*AuthKeyType, error) {
+	v := &AuthKeyType{}
+
+	err := a.storageRepo.Fetch(ID, &v)
+	if err != nil {
+		return nil, errKeyNotFound
+	}
+
+	return v, err
+}
+
+// Store will store a single api key
+func (a AuthKeyRepository) Store(v AuthKeyType) error {
+	return a.storageRepo.Store(v)
+}
+
+// Remove will remove a single api key
+func (a AuthKeyRepository) Remove(v AuthKeyType) error {
+	return a.storageRepo.Remove(v)
+}
diff --git a/internal/apikey/bolt.go b/internal/key/bolt_storage.go
similarity index 59%
rename from internal/apikey/bolt.go
rename to internal/key/bolt_storage.go
index b5eedb5..7cb0931 100644
--- a/internal/apikey/bolt.go
+++ b/internal/key/bolt_storage.go
@@ -17,70 +17,44 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package apikey
+package key
 
 import (
 	"encoding/json"
-	"errors"
-	"fmt"
-	"path/filepath"
 
 	"github.com/sirupsen/logrus"
 	bolt "go.etcd.io/bbolt"
 )
 
 type boltRepo struct {
-	client *bolt.DB
+	client     *bolt.DB
+	BucketName string
 }
 
-const (
-	apiKeyNotFound string = "apikey not found"
-)
-
-// BucketName is the bucket name to store the invitations on the bolt db
-const BucketName = "apikeys"
-
 // BoltDBFile is the filename to store the boltdb database
-const BoltDBFile = "apikeys.db"
+const BoltDBFile = "keys.db"
 
-// NewBoltRepository initializes a new repository
-func NewBoltRepository(dbpath string) Repository {
-	dbFile := filepath.Join(dbpath, BoltDBFile)
-	db, err := bolt.Open(dbFile, 0600, nil)
-	if err != nil {
-		logrus.Error("Unable to open filepath ", dbFile, err)
-		return nil
-	}
-
-	return boltRepo{
-		client: db,
-	}
-}
-
-func (b boltRepo) FetchByHash(h string) ([]KeyType, error) {
-	keys := []KeyType{}
+func (b boltRepo) FetchByHash(h string, v interface{}) (interface{}, error) {
+	var keys []interface{}
 
 	err := b.client.View(func(tx *bolt.Tx) error {
-		bucket := tx.Bucket([]byte(BucketName))
+		bucket := tx.Bucket([]byte(b.BucketName))
 		if bucket == nil {
 			logrus.Trace("keys for account not found in BOLT: ", h, nil)
-			return errors.New(apiKeyNotFound)
+			return errKeyNotFound
 		}
 
 		// @TODO: we iterate all keys, unmarshall them to see if we need to add on a list. Please refactor
 		//  into something better.. :(
 		c := bucket.Cursor()
-		for k, v := c.First(); k != nil; k, v = c.Next() {
-			fmt.Printf("key=%s, value=%s\n", k, v)
-
-			key := &KeyType{}
-			err := json.Unmarshal(v, &key)
+		for k, data := c.First(); k != nil; k, data = c.Next() {
+			err := json.Unmarshal(data, &v)
 			if err != nil {
 				continue
 			}
 
-			if key.AddrHash.String() == h {
-				keys = append(keys, *key)
+			if v.(GenericKey).GetAddressHash().String() == h {
+				keys = append(keys, v.(GenericKey))
 			}
 		}
 
@@ -95,23 +69,21 @@ func (b boltRepo) FetchByHash(h string) ([]KeyType, error) {
 }
 
 // Fetch a key from the repository, or err
-func (b boltRepo) Fetch(ID string) (*KeyType, error) {
-	key := &KeyType{}
-
+func (b boltRepo) Fetch(ID string, v interface{}) error {
 	err := b.client.View(func(tx *bolt.Tx) error {
-		bucket := tx.Bucket([]byte(BucketName))
+		bucket := tx.Bucket([]byte(b.BucketName))
 		if bucket == nil {
 			logrus.Trace("apikey not found in BOLT: ", ID, nil)
-			return errors.New(apiKeyNotFound)
+			return errKeyNotFound
 		}
 
 		data := bucket.Get([]byte(ID))
 		if data == nil {
 			logrus.Trace("apikey not found in BOLT: ", data, nil)
-			return errors.New(apiKeyNotFound)
+			return errKeyNotFound
 		}
 
-		err := json.Unmarshal([]byte(data), &key)
+		err := json.Unmarshal([]byte(data), v)
 		if err != nil {
 			return err
 		}
@@ -119,42 +91,38 @@ func (b boltRepo) Fetch(ID string) (*KeyType, error) {
 		return nil
 	})
 
-	if err != nil {
-		return nil, err
-	}
-
-	return key, nil
+	return err
 }
 
 // Store the given key in the repository
-func (b boltRepo) Store(apiKey KeyType) error {
-	data, err := json.Marshal(apiKey)
+func (b boltRepo) Store(v GenericKey) error {
+	data, err := json.Marshal(v)
 	if err != nil {
 		return err
 	}
 
 	err = b.client.Update(func(tx *bolt.Tx) error {
-		bucket, err := tx.CreateBucketIfNotExists([]byte(BucketName))
+		bucket, err := tx.CreateBucketIfNotExists([]byte(b.BucketName))
 		if err != nil {
-			logrus.Trace("unable to create bucket on BOLT: ", BucketName, err)
+			logrus.Trace("unable to create bucket on BOLT: ", b.BucketName, err)
 			return err
 		}
 
-		return bucket.Put([]byte(apiKey.ID), data)
+		return bucket.Put([]byte(v.GetID()), data)
 	})
 
 	return err
 }
 
 // Remove the given key from the repository
-func (b boltRepo) Remove(apiKey KeyType) error {
+func (b boltRepo) Remove(v GenericKey) error {
 	return b.client.Update(func(tx *bolt.Tx) error {
-		bucket := tx.Bucket([]byte(BucketName))
+		bucket := tx.Bucket([]byte(b.BucketName))
 		if bucket == nil {
-			logrus.Trace("unable to delete apikey, apikey not found in BOLT: ", apiKey.ID, nil)
+			logrus.Trace("unable to delete apikey, apikey not found in BOLT: ", v.GetID(), nil)
 			return nil
 		}
 
-		return bucket.Delete([]byte(apiKey.ID))
+		return bucket.Delete([]byte(v.GetID()))
 	})
 }
diff --git a/internal/apikey/mock.go b/internal/key/mock_storage.go
similarity index 65%
rename from internal/apikey/mock.go
rename to internal/key/mock_storage.go
index 79ad1b7..f7ab4c4 100644
--- a/internal/apikey/mock.go
+++ b/internal/key/mock_storage.go
@@ -17,11 +17,11 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package apikey
+package key
 
 import (
 	"encoding/json"
-	"errors"
+	"reflect"
 )
 
 type mockRepo struct {
@@ -29,77 +29,72 @@ type mockRepo struct {
 	addr map[string]map[string]int
 }
 
-// NewMockRepository initializes a new repository
-func NewMockRepository() Repository {
-	return &mockRepo{
-		keys: map[string][]byte{},
-		addr: map[string]map[string]int{},
-	}
-}
-
 // FetchByHash will retrieve all keys for the given account
-func (r mockRepo) FetchByHash(h string) ([]KeyType, error) {
-	var keys []KeyType
+func (r mockRepo) FetchByHash(h string, v interface{}) (interface{}, error) {
+	rv := reflect.ValueOf(v)
+	if rv.Kind() != reflect.Ptr {
+		return nil, errNeedsPointerValue
+	}
 
 	items, ok := r.addr[h]
 	if !ok {
-		return nil, errors.New("not found")
+		return nil, errKeyNotFound
 	}
 
+	var keys []interface{}
+
+	ve := reflect.TypeOf(v).Elem()
 	for item := range items {
-		key, err := r.Fetch(item)
+		// Create a new item based on the structure of v
+		newItem := reflect.New(ve).Interface()
+
+		err := r.Fetch(item, &newItem)
 		if err != nil {
 			continue
 		}
 
-		keys = append(keys, *key)
+		keys = append(keys, newItem.(interface{}))
 	}
 
 	return keys, nil
 }
 
 // Fetch a key from the repository, or err
-func (r mockRepo) Fetch(ID string) (*KeyType, error) {
+func (r mockRepo) Fetch(ID string, v interface{}) error {
 	data, ok := r.keys[ID]
 	if data == nil || !ok {
-		return nil, errors.New("key not found")
-	}
-
-	key := &KeyType{}
-	err := json.Unmarshal(data, &key)
-	if err != nil {
-		return nil, err
+		return errKeyNotFound
 	}
 
-	return key, nil
+	return json.Unmarshal(data, &v)
 }
 
 // Store the given key in the repository
-func (r mockRepo) Store(apiKey KeyType) error {
-	data, err := json.Marshal(apiKey)
+func (r mockRepo) Store(v GenericKey) error {
+	data, err := json.Marshal(v)
 	if err != nil {
 		return err
 	}
 
 	// Add to account set if an hash is given
-	if apiKey.AddrHash != nil {
-		h := apiKey.AddrHash.String()
+	if v.GetAddressHash() != nil {
+		h := v.GetAddressHash().String()
 		if r.addr[h] == nil {
 			r.addr[h] = map[string]int{}
 		}
-		r.addr[h][apiKey.ID] = 1
+		r.addr[h][v.GetID()] = 1
 	}
 
-	r.keys[apiKey.ID] = data
+	r.keys[v.GetID()] = data
 	return err
 }
 
 // Remove the given key from the repository
-func (r mockRepo) Remove(apiKey KeyType) error {
-	if apiKey.AddrHash != nil {
-		delete(r.addr[apiKey.ID], apiKey.AddrHash.String())
+func (r mockRepo) Remove(v GenericKey) error {
+	if v.GetAddressHash() != nil {
+		delete(r.addr[v.GetID()], v.GetAddressHash().String())
 	}
 
-	delete(r.keys, apiKey.ID)
+	delete(r.keys, v.GetID())
 	return nil
 }
diff --git a/internal/apikey/permissions.go b/internal/key/permissions.go
similarity index 94%
rename from internal/apikey/permissions.go
rename to internal/key/permissions.go
index 0c30ac5..e7f9f22 100644
--- a/internal/apikey/permissions.go
+++ b/internal/key/permissions.go
@@ -17,7 +17,7 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package apikey
+package key
 
 const (
 	// PermFlush Permission to restart/reload the system including flushing/forcing the queues
@@ -26,8 +26,6 @@ const (
 	PermGenerateInvites string = "invite"
 	// PermAPIKeys Permission to create api keys
 	PermAPIKeys string = "apikey"
-	// PermSendMail Permission to send email
-	PermSendMail string = "send-mail"
 	// PermGetHeaders allows you to fetch header and catalog from messages
 	PermGetHeaders string = "get-headers"
 )
@@ -42,5 +40,4 @@ var ManagementPermissons = []string{
 // AccountPermissions is a set of permissions for specific accounts
 var AccountPermissions = []string{
 	PermGetHeaders,
-	PermSendMail,
 }
diff --git a/internal/apikey/redis.go b/internal/key/redis_storage.go
similarity index 55%
rename from internal/apikey/redis.go
rename to internal/key/redis_storage.go
index 2bd1508..ce443cb 100644
--- a/internal/apikey/redis.go
+++ b/internal/key/redis_storage.go
@@ -17,101 +17,97 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package apikey
+package key
 
 import (
 	"context"
 	"encoding/json"
-	"errors"
 	"fmt"
+	"reflect"
 
 	"github.com/bitmaelum/bitmaelum-suite/internal"
-	"github.com/go-redis/redis/v8"
 )
 
-// We don't use redis clients directly, but a redis result wrapper which does the calling of .Result() for us. This
-// makes testing and mocking redis clients possible. It serves no other purpose.
-
 type redisRepo struct {
-	client  internal.RedisResultWrapper
-	context context.Context
-}
-
-// NewRedisRepository initializes a new repository
-func NewRedisRepository(opts *redis.Options) Repository {
-	c := redis.NewClient(opts)
-
-	return &redisRepo{
-		client:  &internal.RedisBridge{Client: *c},
-		context: c.Context(),
-	}
+	client    internal.RedisResultWrapper
+	context   context.Context
+	KeyPrefix string // redis key prefix
 }
 
 // FetchByHash will retrieve all keys for the given account
-func (r redisRepo) FetchByHash(h string) ([]KeyType, error) {
-	keys := []KeyType{}
+func (r redisRepo) FetchByHash(h string, v interface{}) (interface{}, error) {
+	// v needs to be a pointer
+	rv := reflect.ValueOf(v)
+	if rv.Kind() != reflect.Ptr {
+		return nil, errNeedsPointerValue
+	}
 
-	items, err := r.client.SMembers(r.context, h)
+	items, err := r.client.SMembers(r.context, r.createRedisKey(h))
 	if err != nil {
-		return nil, err
+		return nil, errKeyNotFound
 	}
 
+	var keys []interface{}
+
+	ve := reflect.TypeOf(v).Elem()
 	for _, item := range items {
-		key, err := r.Fetch(item)
+		// Create a new item based on the structure of v
+		newItem := reflect.New(ve).Interface()
+
+		err := r.Fetch(item, &newItem)
 		if err != nil {
 			continue
 		}
 
-		keys = append(keys, *key)
+		keys = append(keys, newItem.(interface{}))
 	}
 
 	return keys, nil
 }
 
 // Fetch a key from the repository, or err
-func (r redisRepo) Fetch(ID string) (*KeyType, error) {
-	data, err := r.client.Get(r.context, createRedisKey(ID))
+func (r redisRepo) Fetch(ID string, v interface{}) error {
+	data, err := r.client.Get(r.context, r.createRedisKey(ID))
 	if data == "" || err != nil {
-		return nil, errors.New("key not found")
+		return errKeyNotFound
 	}
 
-	key := &KeyType{}
-	err = json.Unmarshal([]byte(data), &key)
+	err = json.Unmarshal([]byte(data), v)
 	if err != nil {
-		return nil, err
+		return err
 	}
 
-	return key, nil
+	return nil
 }
 
 // Store the given key in the repository
-func (r redisRepo) Store(apiKey KeyType) error {
-	data, err := json.Marshal(apiKey)
+func (r redisRepo) Store(k GenericKey) error {
+	data, err := json.Marshal(k)
 	if err != nil {
 		return err
 	}
 
 	// Add to account set if an hash is given
-	if apiKey.AddrHash != nil {
-		_, _ = r.client.SAdd(r.context, apiKey.AddrHash.String(), apiKey.ID)
+	if k.GetAddressHash() != nil {
+		_, _ = r.client.SAdd(r.context, r.createRedisKey(k.GetAddressHash().String()), k.GetID())
 	}
 
-	_, err = r.client.Set(r.context, createRedisKey(apiKey.ID), data, 0)
+	_, err = r.client.Set(r.context, r.createRedisKey(k.GetID()), data, 0)
 	return err
 }
 
 // Remove the given key from the repository
-func (r redisRepo) Remove(apiKey KeyType) error {
-	if apiKey.AddrHash != nil {
-		_, _ = r.client.SRem(r.context, apiKey.AddrHash.String(), apiKey.ID)
+func (r redisRepo) Remove(k GenericKey) error {
+	if k.GetAddressHash() != nil {
+		_, _ = r.client.SRem(r.context, r.createRedisKey(k.GetAddressHash().String()), k.GetID())
 	}
 
-	_, err := r.client.Del(r.context, createRedisKey(apiKey.ID))
+	_, err := r.client.Del(r.context, r.createRedisKey(k.GetID()))
 	return err
 }
 
 // createRedisKey creates a key based on the given ID. This is needed otherwise we might send any data as api-id
 // to redis in order to extract other kind of data (and you don't want that).
-func createRedisKey(id string) string {
-	return fmt.Sprintf("apikey-%s", id)
+func (r redisRepo) createRedisKey(id string) string {
+	return fmt.Sprintf("%s-%s", r.KeyPrefix, id)
 }
diff --git a/internal/key/repository.go b/internal/key/repository.go
new file mode 100644
index 0000000..c95127a
--- /dev/null
+++ b/internal/key/repository.go
@@ -0,0 +1,62 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package key
+
+import (
+	"errors"
+
+	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
+)
+
+var (
+	errKeyNotFound       = errors.New("key not found")
+	errNeedsPointerValue = errors.New("needs a pointer value")
+)
+
+// GenericKey is a generic structure for storing and fetching keys
+type GenericKey interface {
+	GetID() string
+	GetAddressHash() *hash.Hash
+}
+
+// StorageBackend is the main backend interface which can stores undefined structures. This can be api keys or auth
+// key or even other kind of keys.
+type StorageBackend interface {
+	FetchByHash(h string, v interface{}) (interface{}, error)
+	Fetch(ID string, v interface{}) error
+	Store(v GenericKey) error
+	Remove(v GenericKey) error
+}
+
+// AuthKeyRepo is the repository to the outside world. It allows us to fetch and store auth keys
+type AuthKeyRepo interface {
+	FetchByHash(h string) ([]AuthKeyType, error)
+	Fetch(ID string) (*AuthKeyType, error)
+	Store(v AuthKeyType) error
+	Remove(v AuthKeyType) error
+}
+
+// APIKeyRepo is the repository to the outside world. It allows us to fetch and store api keys
+type APIKeyRepo interface {
+	FetchByHash(h string) ([]APIKeyType, error)
+	Fetch(ID string) (*APIKeyType, error)
+	Store(v APIKeyType) error
+	Remove(v APIKeyType) error
+}
diff --git a/internal/message/header.go b/internal/message/header.go
index 9be4037..5abd3ce 100644
--- a/internal/message/header.go
+++ b/internal/message/header.go
@@ -27,28 +27,47 @@ import (
 // ChecksumList is a list of key/value pairs of checksums. ie: ["sha1"] = "123456abcde"
 type ChecksumList map[string]string
 
+// SignedByType is a type that tells us how a message is signed
+type SignedByType string
+
+const (
+	// SignedByTypeOrigin signed by origin address / private key
+	SignedByTypeOrigin SignedByType = "origin"
+	// SignedByTypeAuthorized signed by an authorized private key (info stored in authorizedPublicKey)
+	SignedByTypeAuthorized SignedByType = "authorized"
+)
+
 // Header represents a message header
 type Header struct {
+	// Information on the sender of the message
 	From struct {
-		Addr hash.Hash `json:"address"`
-		// Optional signature when the message is not signed with the private key, but with the onbehalf key
-		OnBehalfSignature string           `json:"onbehalf_signature,omitempty"`
-		OnBehalfPublicKey *bmcrypto.PubKey `json:"onbehalf_public_key,omitempty"`
+		Addr     hash.Hash    `json:"address"`   // Address hash of the sender
+		SignedBy SignedByType `json:"signed_by"` // Who has signed this message (the originator, or an authorized sender?)
 	} `json:"from"`
+
+	// Information on the recipient of the message
 	To struct {
-		Addr hash.Hash `json:"address"`
+		Addr hash.Hash `json:"address"` // Address hash of the recipient
 	} `json:"to"`
+
+	// Information about the catalog of this message
 	Catalog struct {
-		Size          uint64       `json:"size"`
-		Checksum      ChecksumList `json:"checksum"`
-		Crypto        string       `json:"crypto"`
-		TransactionID string       `json:"txid"`
-		EncryptedKey  []byte       `json:"encrypted_key"`
+		Size          uint64       `json:"size"`           // Size of the catalog file
+		Checksum      ChecksumList `json:"checksum"`       // Checksum of the catalog file
+		Crypto        string       `json:"crypto"`         // Crypto used for encrypting the catalog file
+		TransactionID string       `json:"txid,omitempty"` // Transaction ID (if used) for encryption
+		EncryptedKey  []byte       `json:"encrypted_key"`  // The actual encrypted key, only to be decrypted by the private key of the recipient
 	} `json:"catalog"`
 
-	// Signature of the from, to and catalog structures, as signed by the private key of the server.
-	ServerSignature string `json:"sender_signature,omitempty"`
+	// Information about the authorized sender in case the message is send and signed by an authorized sender instead of the origin sender
+	AuthorizedBy struct {
+		PublicKey *bmcrypto.PubKey `json:"public_key"` // Public key of the authorized sender
+		Signature string           `json:"signature"`  // Signature signed by the origin address
+	} `json:"authorized_by,omitempty"`
 
-	// Signature of the from, to and catalog structures, as signed by the private key of the client.
-	ClientSignature string `json:"client_signature,omitempty"`
+	// Signatures on the message header (except signatures and authorizedBy sections)
+	Signatures struct {
+		Server string `json:"server"` // Signature of the from, to and catalog structures, as signed by the private key of the server. Filled in when sending the message
+		Client string `json:"client"` // Signature of the from, to and catalog structures, as signed by the private key of the client. Filled in by the client sending the message
+	} `json:"signatures"`
 }
diff --git a/internal/parse/parse_args.go b/internal/parse/parse_args.go
index ca4e928..943dd45 100644
--- a/internal/parse/parse_args.go
+++ b/internal/parse/parse_args.go
@@ -25,7 +25,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/bitmaelum/bitmaelum-suite/internal/apikey"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
 	"github.com/xhit/go-str2duration/v2"
 )
 
@@ -54,7 +54,7 @@ func ManagementPermissions(perms []string) error {
 		p = strings.ToLower(p)
 
 		found := false
-		for _, ap := range apikey.ManagementPermissons {
+		for _, ap := range key.ManagementPermissons {
 			if p == ap {
 				found = true
 			}
@@ -73,7 +73,7 @@ func AccountPermissions(perms []string) error {
 		p = strings.ToLower(p)
 
 		found := false
-		for _, ap := range apikey.AccountPermissions {
+		for _, ap := range key.AccountPermissions {
 			if p == ap {
 				found = true
 			}
diff --git a/internal/server/signature.go b/internal/server/signature.go
index c47f3c8..ee03e6b 100644
--- a/internal/server/signature.go
+++ b/internal/server/signature.go
@@ -33,7 +33,7 @@ import (
 // SignHeader will add a server signature to a message header. This can be used to proof the origin of the message
 func SignHeader(header *message.Header) error {
 	// Already signed? Then skip
-	if len(header.ServerSignature) > 0 {
+	if len(header.Signatures.Server) > 0 {
 		return nil
 	}
 
@@ -48,7 +48,7 @@ func SignHeader(header *message.Header) error {
 		return err
 	}
 
-	header.ServerSignature = base64.StdEncoding.EncodeToString(sig)
+	header.Signatures.Server = base64.StdEncoding.EncodeToString(sig)
 	return nil
 }
 
@@ -62,16 +62,16 @@ func VerifyHeader(header message.Header) bool {
 	}
 
 	// No header at all
-	if len(header.ServerSignature) == 0 {
+	if len(header.Signatures.Server) == 0 {
 		return false
 	}
 
 	// Store signature
-	targetSignature, err := base64.StdEncoding.DecodeString(header.ServerSignature)
+	targetSignature, err := base64.StdEncoding.DecodeString(header.Signatures.Server)
 	if err != nil {
 		return false
 	}
-	header.ServerSignature = ""
+	header.Signatures.Server = ""
 
 	// Generate hash
 	data, err := json.Marshal(&header)
diff --git a/internal/vault/vault.go b/internal/vault/vault.go
index 344a59e..31df658 100644
--- a/internal/vault/vault.go
+++ b/internal/vault/vault.go
@@ -38,7 +38,6 @@ import (
 	"github.com/bitmaelum/bitmaelum-suite/internal/config"
 	"github.com/bitmaelum/bitmaelum-suite/internal/console"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/address"
-	"github.com/sirupsen/logrus"
 	"github.com/spf13/afero"
 	"golang.org/x/crypto/pbkdf2"
 )
@@ -320,19 +319,18 @@ func GetAccountOrDefault(vault *Vault, a string) *internal.AccountInfo {
 		return vault.GetDefaultAccount()
 	}
 
-	addr, err := address.NewAddress(a)
-	if err != nil {
-		logrus.Fatal(err)
-		os.Exit(1)
-	}
+	acc, _ := GetAccount(vault, a)
+	return acc
+}
 
-	info, err := vault.GetAccountInfo(*addr)
+// GetAccount returns the given account, or nil when not found
+func GetAccount(vault *Vault, a string) (*internal.AccountInfo, error) {
+	addr, err := address.NewAddress(a)
 	if err != nil {
-		logrus.Fatal("Address not found in vault")
-		os.Exit(1)
+		return nil, err
 	}
 
-	return info
+	return vault.GetAccountInfo(*addr)
 }
 
 // OpenVault returns an opened vault, or opens the vault, asking a password if needed
diff --git a/pkg/bmcrypto/keys.go b/pkg/bmcrypto/keys.go
index 67a4ce7..a871f04 100644
--- a/pkg/bmcrypto/keys.go
+++ b/pkg/bmcrypto/keys.go
@@ -23,8 +23,10 @@ import (
 	"crypto/ecdsa"
 	"crypto/ed25519"
 	"crypto/rsa"
+	"crypto/sha256"
 	"crypto/x509"
 	"encoding/base64"
+	"encoding/hex"
 	"encoding/json"
 	"errors"
 	"strings"
@@ -66,6 +68,13 @@ func (pk *PubKey) String() string {
 	return strings.TrimSpace(pk.Type + " " + pk.S + " " + pk.Description)
 }
 
+// Fingerprint return the fingerprint of the key
+func (pk *PubKey) Fingerprint() string {
+	binKey, _ := base64.StdEncoding.DecodeString(pk.S)
+	b := sha256.Sum256(binKey)
+	return hex.EncodeToString(b[:])
+}
+
 // UnmarshalJSON unmarshals bytes into a key
 func (pk *PubKey) UnmarshalJSON(b []byte) error {
 	var s string


Test patch:
diff --git a/cmd/bm-client/handlers/sign_onbehalf_test.go b/cmd/bm-client/handlers/auth_create_test.go
similarity index 94%
rename from cmd/bm-client/handlers/sign_onbehalf_test.go
rename to cmd/bm-client/handlers/auth_create_test.go
index 90df311..37eb62c 100644
--- a/cmd/bm-client/handlers/sign_onbehalf_test.go
+++ b/cmd/bm-client/handlers/auth_create_test.go
@@ -26,11 +26,11 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestSignOnbehalf(t *testing.T) {
+func TestSignKey(t *testing.T) {
 	sourcePrivkey, _, _ := testing2.ReadTestKey("../../../testdata/key-ed25519-1.json")
 	_, targetPubkey, _ := testing2.ReadTestKey("../../../testdata/key-ed25519-2.json")
 
-	res, err := SignOnbehalf(*sourcePrivkey, *targetPubkey)
+	res, err := signKey(*sourcePrivkey, targetPubkey)
 	assert.NoError(t, err)
 	assert.Equal(t, "GRlSsdrkcciQSOiZNPHO0e0emRAv9x0rhB+eARdarkHeCJL9YOIHghen5C8+8IAaNGe0qHVPal+EcdXCKjQVBg==", res)
 }
diff --git a/cmd/bm-server/middleware/auth/auth_apikey_test.go b/cmd/bm-server/middleware/auth/auth_apikey_test.go
index 4f12687..d325781 100644
--- a/cmd/bm-server/middleware/auth/auth_apikey_test.go
+++ b/cmd/bm-server/middleware/auth/auth_apikey_test.go
@@ -27,7 +27,8 @@ import (
 
 	"github.com/bitmaelum/bitmaelum-suite/cmd/bm-server/middleware"
 	"github.com/bitmaelum/bitmaelum-suite/internal/account"
-	"github.com/bitmaelum/bitmaelum-suite/internal/apikey"
+	"github.com/bitmaelum/bitmaelum-suite/internal/container"
+	"github.com/bitmaelum/bitmaelum-suite/internal/key"
 	testing2 "github.com/bitmaelum/bitmaelum-suite/internal/testing"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
 	"github.com/gorilla/mux"
@@ -36,32 +37,43 @@ import (
 
 // Lots of code is abstracted into functions. THis is to please sonarcloud duplication system
 
-var apiKeyFixtures = []apikey.KeyType{
-	apikey.NewAccountKey(hash.New("user-1!"), []string{"a"}, time.Time{}, "my desc 1"),
-	apikey.NewAccountKey(hash.New("user-2!"), []string{"b"}, time.Time{}, "my desc 2"),
-	apikey.NewAccountKey(hash.New("user-3!"), []string{"b", "c"}, time.Time{}, "my desc 3"),
-	apikey.NewAccountKey(hash.New("expired!"), []string{"a", "b", "c"}, time.Unix(12521510, 0), "expired key"),
+var apiKeyFixtures = []key.APIKeyType{
+	key.NewAPIAccountKey(hash.New("user-1!"), []string{"a"}, time.Time{}, "my desc 1"),
+	key.NewAPIAccountKey(hash.New("user-2!"), []string{"b"}, time.Time{}, "my desc 2"),
+	key.NewAPIAccountKey(hash.New("user-3!"), []string{"b", "c"}, time.Time{}, "my desc 3"),
+	key.NewAPIAccountKey(hash.New("expired!"), []string{"a", "b", "c"}, time.Unix(12521510, 0), "expired key"),
 }
 
 func TestAuthAPIKeyAuthenticate(t *testing.T) {
-	t.SkipNow()
-
 	_, pubkey, err := testing2.ReadTestKey("../../../../testdata/key-ed25519-1.json")
 	assert.NoError(t, err)
-	accountRepo = account.NewMockRepository
-	_ = accountRepo().Create(hash.New("example!"), *pubkey)
-	_ = accountRepo().Create(hash.New("user-1!"), *pubkey)
-	_ = accountRepo().Create(hash.New("user-2!"), *pubkey)
-	_ = accountRepo().Create(hash.New("user-3!"), *pubkey)
-	_ = accountRepo().Create(hash.New("expired!"), *pubkey)
+
+	accountRepo := container.GetAccountRepo()
+	accountRepo.Exists(hash.New("foobar!"))
+
+	accountRepo = container.GetAccountRepo()
+	accountRepo.Exists(hash.New("foobar!"))
+
+	container.Set("account", func() (interface{}, error) {
+		return account.NewMockRepository(), nil
+	})
+
+	accountRepo = container.GetAccountRepo()
+	// container.Set("account", func() interface{} { return accountRepo })
+	_ = accountRepo.Create(hash.New("example!"), *pubkey)
+	_ = accountRepo.Create(hash.New("user-1!"), *pubkey)
+	_ = accountRepo.Create(hash.New("user-2!"), *pubkey)
+	_ = accountRepo.Create(hash.New("user-3!"), *pubkey)
+	_ = accountRepo.Create(hash.New("expired!"), *pubkey)
 
 	// 42 creates BMK-dl2INvNSQTZ5zQu9MxNmGyAVmNkB33io
 	rand.Seed(42)
-	apiKeyRepo = apikey.NewMockRepository
+	apiKeyRepo := key.NewAPIMockRepository()
+	container.Set("api-key", func() (interface{}, error) { return apiKeyRepo, nil })
 	for _, k := range apiKeyFixtures {
 		// Create a new key, so it will randomize through our seed
-		nk := apikey.NewAccountKey(*k.AddrHash, k.Permissions, k.Expires, k.Desc)
-		_ = apiKeyRepo().Store(nk)
+		nk := key.NewAPIAccountKey(*k.AddressHash, k.Permissions, k.Expires, k.Desc)
+		_ = apiKeyRepo.Store(nk)
 	}
 
 	a := APIKeyAuth{
@@ -156,9 +168,9 @@ func checkKey(t *testing.T, a APIKeyAuth, pass bool, token, addr, routeName stri
 		assert.True(t, ok)
 		assert.NotNil(t, ctx)
 		// Check token in context
-		k := ctx.Value(APIKeyContext).(*apikey.KeyType)
+		k := ctx.Value(APIKeyContext).(*key.APIKeyType)
 		assert.Equal(t, token, k.ID)
-		assert.Equal(t, hash.New(addr).String(), k.AddrHash.String())
+		assert.Equal(t, hash.New(addr).String(), k.AddressHash.String())
 		return
 	}
 
diff --git a/cmd/bm-server/middleware/auth/auth_jwt_test.go b/cmd/bm-server/middleware/auth/auth_jwt_test.go
index 4d6b516..9d8ae3c 100644
--- a/cmd/bm-server/middleware/auth/auth_jwt_test.go
+++ b/cmd/bm-server/middleware/auth/auth_jwt_test.go
@@ -25,7 +25,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/bitmaelum/bitmaelum-suite/internal/account"
+	"github.com/bitmaelum/bitmaelum-suite/internal/container"
 	testing2 "github.com/bitmaelum/bitmaelum-suite/internal/testing"
 	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
 	"github.com/gorilla/mux"
@@ -36,12 +36,10 @@ import (
 // Lots of code is abstracted into functions. THis is to please sonarcloud duplication system
 
 func TestAuthJwtAuthenticate(t *testing.T) {
-	t.SkipNow()
-	
 	_, pubkey, err := testing2.ReadTestKey("../../../../testdata/key-ed25519-1.json")
 	assert.NoError(t, err)
-	accountRepo = account.NewMockRepository
-	_ = accountRepo().Create(hash.New("example!"), *pubkey)
+	accountRepo := container.GetAccountRepo()
+	_ = accountRepo.Create(hash.New("example!"), *pubkey)
 
 	jwt.TimeFunc = func() time.Time {
 		return time.Date(2020, 01, 01, 12, 34, 56, 0, time.UTC)
diff --git a/internal/client/signature_test.go b/internal/client/signature_test.go
index a797633..12fea36 100644
--- a/internal/client/signature_test.go
+++ b/internal/client/signature_test.go
@@ -32,8 +32,7 @@ import (
 )
 
 const (
-	expectedSignature  = "MsvxtxsxihfHRNDG20hmydSg7xjSIdaFmiz7iZVT+KLpvzP4isVimqzblN9bfxj+kLKuxKpXvSR6gS4Mi5yMwh1Xfsk0QJaVNJYNwB12i+kB5of2pm1vTipU4Y4I9E2Kj2TUfGuJZ51XeELPgr/d85ttrAk3DUKPXG5DcpEKqzJeocxryYq9968WB0Yal1SnxFc93HK1Lx5GWXGvXbVXnencCfZ+OYAVjuYOxtyAXQBEG2OjQUTfnmH+h8dyP0zIMl2xpku32uynp9Qy7gKyytACSFSSDTlHAAvZA8ARj7vrCGQWsUW0popMvx9N+z2KY1sL4q8/B/jg/uIpKa2AIQ=="
-	expectedSignature2 = "Gy7Wz2Qj/kWBgO8r0ltYEbaS01EpNh0o2kyzmGsr7KPyH6X8Dl/uipYotEovbPEP5rhbip3Lwl6k1hO6FqqZE4v/2zyTdT+UN2o4Sm/BcsA00Cd7kttxyOdeqOpG+nJTLTi1nG/zbqSFli7qEAVtdUwMgs+w6/Vd0uv0SK4FnrcHqWIIRWBB0AbkMoVtR69J/zsPhvI6lA741cLM/P1K+CnrnK4OkoJ1nvXw9NX7AxBGh9yDyVMdAjLKjniLViXp+ZQwh9o/mIzus5mP8BS5q3tga9nhhg8k+IdXvs6kc0yURYhMESsbKroL+lLn1qvc0SdJkthjWhwa/e5MotXUCg=="
+	expectedSignature = "lIqI1QYBRHl7yRW367Lx2n/PFadrYDZ2a2NGSaL40EKum0ncOIXs8CIqKZ+LCUgmK2a9iH2d3mbXVPwZ3PBGsVgReaomyG6NrDbZ0PCbgnjmrmkVAFV0bDHlOxUl/BzyV+seIL7FL0lu+cODaHkmzH16FsZ5Vqcf1/Qe2GR/0Ka6xbWcIcajGsKtTx+WtGeZGZ5oLbAFatEjiv5gMAn2umKpP+w7uKhPa6CsYkv2YMVw+z/1NU2CO0jE6/2muihF9x4nPw6yiy+sXP86B26FQXLBcMgTZ4TAtzr/b2KvcEDj8y8HISs/YHJvTdqAXzYTPnha37ZIIZ7ce27Z41GAUQ=="
 )
 
 func TestSignHeader(t *testing.T) {
@@ -41,19 +40,19 @@ func TestSignHeader(t *testing.T) {
 
 	header := &message.Header{}
 	_ = testing2.ReadJSON("../../testdata/header-001.json", &header)
-	assert.Empty(t, header.ClientSignature)
+	assert.Empty(t, header.Signatures.Client)
 	err := SignHeader(header, *privKey)
 	assert.NoError(t, err)
-	assert.Equal(t, expectedSignature2, header.ClientSignature)
+	assert.Equal(t, expectedSignature, header.Signatures.Client)
 
 	// Already present, don't overwrite
 	_ = testing2.ReadJSON("../../testdata/header-001.json", &header)
-	assert.NotEmpty(t, header.ClientSignature)
-	header.ClientSignature = "foobar"
+	assert.NotEmpty(t, header.Signatures.Client)
+	header.Signatures.Client = "foobar"
 	err = SignHeader(header, *privKey)
 	assert.NoError(t, err)
 
-	assert.Equal(t, "foobar", header.ClientSignature)
+	assert.Equal(t, "foobar", header.Signatures.Client)
 }
 
 func TestVerifyHeader(t *testing.T) {
@@ -61,51 +60,37 @@ func TestVerifyHeader(t *testing.T) {
 
 	header := &message.Header{}
 	_ = testing2.ReadJSON("../../testdata/header-001.json", &header)
-	assert.Empty(t, header.ClientSignature)
+	assert.Empty(t, header.Signatures.Client)
 	err := SignHeader(header, *privKey)
 	assert.NoError(t, err)
-	assert.Equal(t, expectedSignature2, header.ClientSignature)
+	assert.Equal(t, expectedSignature, header.Signatures.Client)
 
 	// All is ok
 	ok := VerifyHeader(*header)
 	assert.True(t, ok)
 
 	// Incorrect decoding
-	header.ClientSignature = "A"
+	header.Signatures.Client = "A"
 	ok = VerifyHeader(*header)
 	assert.False(t, ok)
 
 	// Empty sig is not ok
-	header.ClientSignature = ""
+	header.Signatures.Client = ""
 	ok = VerifyHeader(*header)
 	assert.False(t, ok)
 
 	// incorrect key
-	header.ClientSignature = "Zm9vYmFy"
+	header.Signatures.Client = "Zm9vYmFy"
 	ok = VerifyHeader(*header)
 	assert.False(t, ok)
 }
 
-func TestSignHeaderWithOnbehalfKey(t *testing.T) {
-	_ = setup()
-	// This is our onbehalf key
-	privKey, _, _ := testing2.ReadTestKey("../../testdata/key-ed25519-2.json")
-
-	header := &message.Header{}
-	_ = testing2.ReadJSON("../../testdata/header-003.json", &header)
-	assert.Empty(t, header.ClientSignature)
-	err := SignHeader(header, *privKey)
-	assert.NoError(t, err)
-	assert.Equal(t, "ynMf3K4f0kZLIgcvw7I3cu9p9aBO8BlpAC8hswBQ3oUFtmpn+s6ND2iciDga7zEhP/hbWbGYFo8ITySpH3PIDA==", header.ClientSignature)
-
-	ok := VerifyHeader(*header)
-	assert.True(t, ok)
-}
-
 func setup() *bmcrypto.PrivKey {
 	// Setup container with mock repository for routing
 	repo, _ := resolver.NewMockRepository()
-	container.SetResolveService(resolver.KeyRetrievalService(repo))
+	container.Set("resolver", func() (interface{}, error) {
+		return resolver.KeyRetrievalService(repo), nil
+	})
 
 	pow := proofofwork.NewWithoutProof(1, "foobar")
 	var (
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 4b15f50..e4458c4 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -132,7 +132,7 @@ func TestServerConfig(t *testing.T) {
 
 func TestLoadClientConfig(t *testing.T) {
 	// Failed loading
-	err := readConfigPath("/foo/bar", Client.LoadConfig)
+	err := readConfigPath("/foo/bar", "", Client.LoadConfig)
 	assert.Error(t, err)
 }
 
diff --git a/internal/container/container_test.go b/internal/container/container_test.go
new file mode 100644
index 0000000..729792e
--- /dev/null
+++ b/internal/container/container_test.go
@@ -0,0 +1,67 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package container
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+var called = 0
+
+func barFunc() string {
+	return "this is bar"
+}
+
+func TestContainer(t *testing.T) {
+	c := NewContainer()
+
+	// We set a function that we can call here
+	c.Set("foo", ServiceTypeSingle, func() (interface{}, error) {
+		return func() string {
+			return "this is foo"
+		}, nil
+	})
+
+	svc, ok := c.Get("foo").(func() string)
+	assert.True(t, ok)
+	assert.Equal(t, "this is foo", svc())
+
+	c.Set("foo", ServiceTypeSingle, func() (interface{}, error) { called++; return barFunc, nil })
+	_, _ = c.Get("foo").(func() string)
+	assert.Equal(t, 1, called)
+	_, _ = c.Get("foo").(func() string)
+	assert.Equal(t, 1, called)
+	_, _ = c.Get("foo").(func() string)
+	assert.Equal(t, 1, called)
+	_, _ = c.Get("foo").(func() string)
+	assert.Equal(t, 1, called)
+
+	c.Set("foo", ServiceTypeMulti, func() (interface{}, error) { called++; return barFunc, nil })
+	_, _ = c.Get("foo").(func() string)
+	assert.Equal(t, 2, called)
+	_, _ = c.Get("foo").(func() string)
+	assert.Equal(t, 3, called)
+	_, _ = c.Get("foo").(func() string)
+	assert.Equal(t, 4, called)
+	_, _ = c.Get("foo").(func() string)
+	assert.Equal(t, 5, called)
+}
diff --git a/internal/apikey/apikey_test.go b/internal/key/api_key_test.go
similarity index 92%
rename from internal/apikey/apikey_test.go
rename to internal/key/api_key_test.go
index c562cea..9bd621e 100644
--- a/internal/apikey/apikey_test.go
+++ b/internal/key/api_key_test.go
@@ -17,7 +17,7 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package apikey
+package key
 
 import (
 	"math/rand"
@@ -46,7 +46,7 @@ func TestGenerateKey(t *testing.T) {
 func TestNewAdminKey(t *testing.T) {
 	expiry := time.Unix(1603442983, 0)
 
-	key := NewAdminKey(expiry, "foobar")
+	key := NewAPIAdminKey(expiry, "foobar")
 	assert.True(t, strings.HasPrefix(key.ID, "BMK-"))
 	assert.True(t, key.Admin)
 	assert.Equal(t, "foobar", key.Desc)
@@ -62,15 +62,15 @@ func TestNewAdminKey(t *testing.T) {
 func TestNewKey(t *testing.T) {
 	expiry := time.Unix(1603442983, 0)
 
-	key := NewKey([]string{"foo"}, expiry, "")
+	key := NewAPIKey([]string{"foo"}, expiry, "")
 	assert.True(t, strings.HasPrefix(key.ID, "BMK-"))
 	assert.False(t, key.Admin)
-	assert.Nil(t, key.AddrHash)
+	assert.Nil(t, key.AddressHash)
 	assert.True(t, key.HasPermission("foo", nil))
 	assert.False(t, key.HasPermission("bar", nil))
 
 	h := hash.New("foobar")
-	key = NewAccountKey(h, []string{"foo"}, expiry, "")
+	key = NewAPIAccountKey(h, []string{"foo"}, expiry, "")
 	assert.True(t, key.HasPermission("foo", &h))
 	assert.False(t, key.HasPermission("bar", &h))
 
diff --git a/internal/key/auth_key_test.go b/internal/key/auth_key_test.go
new file mode 100644
index 0000000..3a35f05
--- /dev/null
+++ b/internal/key/auth_key_test.go
@@ -0,0 +1,37 @@
+// Copyright (c) 2020 BitMaelum Authors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+package key
+
+import (
+	"testing"
+	"time"
+
+	testing2 "github.com/bitmaelum/bitmaelum-suite/internal/testing"
+	"github.com/bitmaelum/bitmaelum-suite/pkg/hash"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAuthKey(t *testing.T) {
+	_, pubkey, _ := testing2.ReadTestKey("../../testdata/key-1.json")
+	a := NewAuthKey(hash.New("example!"), pubkey, "sig", time.Time{}, "desc")
+
+	assert.Equal(t, "9c9afac4a3603d7b05fd0b3a57eba3c30da421c5dc1a18b2cffeef5ebca24480", a.GetID())
+	assert.Equal(t, "2e4551de804e27aacf20f9df5be3e8cd384ed64488b21ab079fb58e8c90068ab", a.GetAddressHash().String())
+}
diff --git a/internal/apikey/mock_test.go b/internal/key/mock_storage_test.go
similarity index 90%
rename from internal/apikey/mock_test.go
rename to internal/key/mock_storage_test.go
index 8804560..f76da0b 100644
--- a/internal/apikey/mock_test.go
+++ b/internal/key/mock_storage_test.go
@@ -17,7 +17,7 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package apikey
+package key
 
 import (
 	"testing"
@@ -28,10 +28,10 @@ import (
 )
 
 func TestMockRepo(t *testing.T) {
-	repo := NewMockRepository()
+	repo := NewAPIMockRepository()
 
 	h := hash.New("example!")
-	k := NewAccountKey(h, []string{"foo", "bar"}, time.Time{}, "my desc")
+	k := NewAPIAccountKey(h, []string{"foo", "bar"}, time.Time{}, "my desc")
 	err := repo.Store(k)
 	assert.NoError(t, err)
 
@@ -41,7 +41,7 @@ func TestMockRepo(t *testing.T) {
 	assert.Equal(t, "my desc", k2.Desc)
 
 	h = hash.New("example!")
-	k = NewAccountKey(h, []string{"foo", "bar"}, time.Time{}, "my desc 2")
+	k = NewAPIAccountKey(h, []string{"foo", "bar"}, time.Time{}, "my desc 2")
 	err = repo.Store(k)
 	assert.NoError(t, err)
 
diff --git a/internal/apikey/redis_test.go b/internal/key/redis_storage_test.go
similarity index 77%
rename from internal/apikey/redis_test.go
rename to internal/key/redis_storage_test.go
index cc7c2da..ad8e981 100644
--- a/internal/apikey/redis_test.go
+++ b/internal/key/redis_storage_test.go
@@ -17,11 +17,10 @@
 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package apikey
+package key
 
 import (
 	"context"
-	"errors"
 	"testing"
 	"time"
 
@@ -33,24 +32,28 @@ import (
 func TestRedis(t *testing.T) {
 	m := &testing2.RedisClientMock{}
 
-	repo := redisRepo{
+	storage := redisRepo{
 		client:  m,
 		context: context.Background(),
 	}
 
+	repo := &APIKeyRepository{
+		storageRepo: storage,
+	}
+
 	var (
 		err error
-		kt  *KeyType
-		kts []KeyType
+		kt  *APIKeyType
+		kts []APIKeyType
 	)
 
 	h1 := hash.Hash("set 1")
-	kt = &KeyType{
+	kt = &APIKeyType{
 		ID:          "abc",
+		AddressHash: &h1,
 		Expires:     time.Time{},
 		Permissions: []string{"foobar"},
 		Admin:       true,
-		AddrHash:    &h1,
 		Desc:        "test key",
 	}
 
@@ -59,13 +62,13 @@ func TestRedis(t *testing.T) {
 	err = repo.Store(*kt)
 	assert.NoError(t, err)
 
-	m.Queue("get", "{\"key\":\"abc\",\"valid_until\":\"0001-01-01T00:00:00Z\",\"permissions\":[\"foobar\"],\"admin\":true,\"addr_hash\":\"set 1\",\"description\":\"test key\"}", nil)
+	m.Queue("get", "{\"key\":\"abc\",\"valid_until\":\"0001-01-01T00:00:00Z\",\"permissions\":[\"foobar\"],\"admin\":true,\"address_hash\":\"set 1\",\"description\":\"test key\"}", nil)
 	kt2, err := repo.Fetch("abc")
 	assert.NoError(t, err)
 	assert.Equal(t, "abc", kt2.ID)
 	assert.Equal(t, "test key", kt2.Desc)
 
-	m.Queue("get", "", errors.New("not found"))
+	m.Queue("get", "", errKeyNotFound)
 	kt2, err = repo.Fetch("efg")
 	assert.Error(t, err)
 	assert.Nil(t, kt2)
@@ -76,8 +79,8 @@ func TestRedis(t *testing.T) {
 	assert.Nil(t, kt2)
 
 	m.Queue("smembers", []string{"foo", "bar"}, nil)
-	m.Queue("get", "{\"key\":\"abc\",\"valid_until\":\"0001-01-01T00:00:00Z\",\"permissions\":[\"foobar\"],\"admin\":true,\"addr_hash\":\"set 1\",\"description\":\"test key\"}", nil)
-	m.Queue("get", "{\"key\":\"def\",\"valid_until\":\"0001-01-01T00:00:00Z\",\"permissions\":[\"foobar\"],\"admin\":true,\"addr_hash\":\"set 1\",\"description\":\"test key 2\"}", nil)
+	m.Queue("get", "{\"key\":\"abc\",\"valid_until\":\"0001-01-01T00:00:00Z\",\"permissions\":[\"foobar\"],\"admin\":true,\"address_hash\":\"set 1\",\"description\":\"test key\"}", nil)
+	m.Queue("get", "{\"key\":\"def\",\"valid_until\":\"0001-01-01T00:00:00Z\",\"permissions\":[\"foobar\"],\"admin\":true,\"address_hash\":\"set 1\",\"description\":\"test key 2\"}", nil)
 	kts, err = repo.FetchByHash("set 1")
 	assert.NoError(t, err)
 	assert.Len(t, kts, 2)
@@ -91,5 +94,12 @@ func TestRedis(t *testing.T) {
 }
 
 func TestCreateRedisKey(t *testing.T) {
-	assert.Equal(t, "apikey-foobar", createRedisKey("foobar"))
+	m := &testing2.RedisClientMock{}
+	r := redisRepo{
+		client:    m,
+		context:   context.Background(),
+		KeyPrefix: "apikey",
+	}
+
+	assert.Equal(t, "apikey-foobar", r.createRedisKey("foobar"))
 }
diff --git a/internal/server/signature_test.go b/internal/server/signature_test.go
index 3b4d885..848f742 100644
--- a/internal/server/signature_test.go
+++ b/internal/server/signature_test.go
@@ -31,25 +31,29 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+const (
+	expectedSignature = "smq7qWrkwTL1vquhEAn/WoRDZBT1BjUQaUSsmTSSPePLRpM1sjX10mJwxXlivYOIlgTtJ+0SIeBM9rDPBSTw5JJhOS9ZFmpAYEPG9tkU+9EjxfBNnEBPrsYaqE81tO7OtY4xFrlYecdhepGUQSxbZQU4+Ih5jE9jLb+SuUxR6lGw2u2P+Ngy75dD33zlMTTgnmaVTxBueRlfArDExW5QE+pFv9/uFi8xM5a7eGnQHSjufQ9gM6WOWzhyWAnKI+6XMwx3MoQ53H3OU2vn4tPSUQQyxB+L4WTH9JtC0nLC0ggzvo5LOdCw4rCljsciYiEZ2WssGD9kXLGFIU/ixEX2Kw=="
+)
+
 func TestSignHeader(t *testing.T) {
 	setup()
 
 	header := &message.Header{}
 	_ = bmtest.ReadJSON("../../testdata/header-002.json", &header)
-	assert.Empty(t, header.ServerSignature)
+	assert.Empty(t, header.Signatures.Server)
 	err := SignHeader(header)
 	assert.NoError(t, err)
 
-	assert.Equal(t, "Gb85h74QniufBocnLldn5duNvXzkAgrM/shrnVzOMu5+YDgwEwd6hVxYd57U3iC6aFeU87iyqCVnwI0a3AyiMOiq9Xsq4A5RwvWfjnOmsMegt1pEzgHkU4MuQrCn4JKSmleST1ETSpauAYjVIwYA31JXJnQpcKohN6MWoLPZRFGNi/mBUDv2x7S9mqchXxOhn2i/5f/wBr49Q9EUd/ihhS1JFEtcNEmNOMQnvj9ciBafUYqlBhP9fJ1LkQtsG3aJ4zcg5gBhjsAYIkgsbmbgMGEOFZIIryLXg1vcP+nRk59VugAeI2kawJXIq+qnnLBUUF1GS85ha9OeIhA9MdAJgg==", header.ServerSignature)
+	assert.Equal(t, expectedSignature, header.Signatures.Server)
 
 	// Already present, don't overwrite
 	_ = bmtest.ReadJSON("../../testdata/header-002.json", &header)
-	assert.NotEmpty(t, header.ServerSignature)
-	header.ServerSignature = "foobar"
+	assert.NotEmpty(t, header.Signatures.Server)
+	header.Signatures.Server = "foobar"
 	err = SignHeader(header)
 	assert.NoError(t, err)
 
-	assert.Equal(t, "foobar", header.ServerSignature)
+	assert.Equal(t, "foobar", header.Signatures.Server)
 }
 
 func TestVerifyHeader(t *testing.T) {
@@ -57,27 +61,27 @@ func TestVerifyHeader(t *testing.T) {
 
 	header := &message.Header{}
 	_ = bmtest.ReadJSON("../../testdata/header-002.json", &header)
-	assert.Empty(t, header.ServerSignature)
+	assert.Empty(t, header.Signatures.Server)
 	err := SignHeader(header)
 	assert.NoError(t, err)
-	assert.Equal(t, "Gb85h74QniufBocnLldn5duNvXzkAgrM/shrnVzOMu5+YDgwEwd6hVxYd57U3iC6aFeU87iyqCVnwI0a3AyiMOiq9Xsq4A5RwvWfjnOmsMegt1pEzgHkU4MuQrCn4JKSmleST1ETSpauAYjVIwYA31JXJnQpcKohN6MWoLPZRFGNi/mBUDv2x7S9mqchXxOhn2i/5f/wBr49Q9EUd/ihhS1JFEtcNEmNOMQnvj9ciBafUYqlBhP9fJ1LkQtsG3aJ4zcg5gBhjsAYIkgsbmbgMGEOFZIIryLXg1vcP+nRk59VugAeI2kawJXIq+qnnLBUUF1GS85ha9OeIhA9MdAJgg==", header.ServerSignature)
+	assert.Equal(t, expectedSignature, header.Signatures.Server)
 
 	// All is ok
 	ok := VerifyHeader(*header)
 	assert.True(t, ok)
 
 	// Incorrect decoding
-	header.ServerSignature = "A"
+	header.Signatures.Server = "A"
 	ok = VerifyHeader(*header)
 	assert.False(t, ok)
 
 	// Empty sig is not ok
-	header.ServerSignature = ""
+	header.Signatures.Server = ""
 	ok = VerifyHeader(*header)
 	assert.False(t, ok)
 
 	// incorrect key
-	header.ServerSignature = "Zm9vYmFy"
+	header.Signatures.Server = "Zm9vYmFy"
 	ok = VerifyHeader(*header)
 	assert.False(t, ok)
 }
@@ -96,7 +100,9 @@ func setup() {
 
 	// Setup container with mock repository for routing
 	repo, _ := resolver.NewMockRepository()
-	container.SetResolveService(resolver.KeyRetrievalService(repo))
+	container.Set("resolver", func() (interface{}, error) {
+		return resolver.KeyRetrievalService(repo), nil
+	})
 
 	pow := proofofwork.NewWithoutProof(1, "foobar")
 	var (
diff --git a/internal/subscription/redis_test.go b/internal/subscription/redis_test.go
index 1427d1a..5d54809 100644
--- a/internal/subscription/redis_test.go
+++ b/internal/subscription/redis_test.go
@@ -32,7 +32,7 @@ import (
 func TestRedisRepo(t *testing.T) {
 	m := &testing2.RedisClientMock{}
 
-	repo := redisRepo{
+	repo := &redisRepo{
 		client:  m,
 		context: context.Background(),
 	}
diff --git a/pkg/bmcrypto/keys_test.go b/pkg/bmcrypto/keys_test.go
index 1508b06..c1510ad 100644
--- a/pkg/bmcrypto/keys_test.go
+++ b/pkg/bmcrypto/keys_test.go
@@ -198,3 +198,11 @@ func TestPubKeyJSON(t *testing.T) {
 	assert.Equal(t, "rsa", foo.Type)
 	assert.Equal(t, "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC57qC/BeoYcM6ijazuaCdJkbT8pvPpFEDVzf9ZQ9axswXU3mywSOaR3wflriSjmvRfUNs/BAjshgtJqgviUXx7lE5aG9mcUyvomyFFpfCR2l2Lvow0H8y7JoL6yxMSQf8gpAcaQzPB8dsfGe+DqA+5wjxXPOhC1QUcllt08yBB3wIDAQAB", foo.S)
 }
+
+func TestFingerprint(t *testing.T) {
+	pk, _ := NewPubKey(TestKeySet1[0])
+	assert.Equal(t, "54fe2065620a04c17b022aa0f07461a862919e7c832d79aa211e6cebd784620b", pk.Fingerprint())
+
+	pk, _ = NewPubKey(TestKeySet2[0])
+	assert.Equal(t, "3bde10ad2dc6508163a8a4944c46684102ed777e57f73514c75e6e62448a3d85", pk.Fingerprint())
+}