Quellcode durchsuchen

move kms definitions to the sdk package

This is the first step to make the sdk a separate module

Signed-off-by: Nicola Murino <[email protected]>
Nicola Murino vor 3 Jahren
Ursprung
Commit
a6fe802370

+ 1 - 1
cmd/portable.go

@@ -14,8 +14,8 @@ import (
 
 
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/service"
 	"github.com/drakkan/sftpgo/v2/service"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/version"
 	"github.com/drakkan/sftpgo/v2/version"

+ 1 - 1
common/common_test.go

@@ -19,8 +19,8 @@ import (
 	"golang.org/x/crypto/bcrypt"
 	"golang.org/x/crypto/bcrypt"
 
 
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"
 )
 )

+ 1 - 1
common/connection_test.go

@@ -13,8 +13,8 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 
 
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"
 )
 )
 
 

+ 2 - 1
common/protocol_test.go

@@ -34,10 +34,11 @@ import (
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
-	"github.com/drakkan/sftpgo/v2/kms"
+	_ "github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"
 )
 )

+ 1 - 1
common/transfer_test.go

@@ -11,8 +11,8 @@ import (
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/require"
 
 
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"
 )
 )
 
 

+ 1 - 1
config/config.go

@@ -16,9 +16,9 @@ import (
 	"github.com/drakkan/sftpgo/v2/ftpd"
 	"github.com/drakkan/sftpgo/v2/ftpd"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpd"
 	"github.com/drakkan/sftpgo/v2/httpd"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/mfa"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/smtp"
 	"github.com/drakkan/sftpgo/v2/smtp"

+ 2 - 1
config/config_test.go

@@ -17,8 +17,9 @@ import (
 	"github.com/drakkan/sftpgo/v2/ftpd"
 	"github.com/drakkan/sftpgo/v2/ftpd"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpd"
 	"github.com/drakkan/sftpgo/v2/httpd"
-	"github.com/drakkan/sftpgo/v2/kms"
+	_ "github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/mfa"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/smtp"
 	"github.com/drakkan/sftpgo/v2/smtp"

+ 1 - 1
dataprovider/admin.go

@@ -15,10 +15,10 @@ import (
 	passwordvalidator "github.com/wagslane/go-password-validator"
 	passwordvalidator "github.com/wagslane/go-password-validator"
 	"golang.org/x/crypto/bcrypt"
 	"golang.org/x/crypto/bcrypt"
 
 
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 )
 )
 
 

+ 1 - 1
dataprovider/dataprovider.go

@@ -46,11 +46,11 @@ import (
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/crypto/ssh"
 
 
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpclient"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/metric"
 	"github.com/drakkan/sftpgo/v2/metric"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"

+ 1 - 1
dataprovider/user.go

@@ -15,10 +15,10 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"
 )
 )

+ 1 - 1
ftpd/cryptfs_test.go

@@ -18,8 +18,8 @@ import (
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 )
 )
 
 
 func TestBasicFTPHandlingCryptFs(t *testing.T) {
 func TestBasicFTPHandlingCryptFs(t *testing.T) {

+ 2 - 1
ftpd/ftpd_test.go

@@ -32,10 +32,11 @@ import (
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/ftpd"
 	"github.com/drakkan/sftpgo/v2/ftpd"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
-	"github.com/drakkan/sftpgo/v2/kms"
+	_ "github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"
 )
 )

+ 1 - 1
httpd/api_mfa.go

@@ -8,9 +8,9 @@ import (
 	"github.com/go-chi/render"
 	"github.com/go-chi/render"
 
 
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 )
 )
 
 

+ 1 - 1
httpd/api_user.go

@@ -10,8 +10,8 @@ import (
 
 
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/smtp"
 	"github.com/drakkan/sftpgo/v2/smtp"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"

+ 2 - 1
httpd/httpd_test.go

@@ -45,10 +45,11 @@ import (
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpd"
 	"github.com/drakkan/sftpgo/v2/httpd"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
-	"github.com/drakkan/sftpgo/v2/kms"
+	_ "github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/smtp"
 	"github.com/drakkan/sftpgo/v2/smtp"

+ 1 - 1
httpd/internal_test.go

@@ -33,8 +33,8 @@ import (
 
 
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"

+ 1 - 1
httpd/webadmin.go

@@ -16,9 +16,9 @@ import (
 
 
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/smtp"
 	"github.com/drakkan/sftpgo/v2/smtp"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/version"
 	"github.com/drakkan/sftpgo/v2/version"

+ 1 - 1
httpdtest/httpdtest.go

@@ -20,7 +20,7 @@ import (
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpd"
 	"github.com/drakkan/sftpgo/v2/httpd"
-	"github.com/drakkan/sftpgo/v2/kms"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/version"
 	"github.com/drakkan/sftpgo/v2/version"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"

+ 20 - 13
kms/builtin.go

@@ -6,18 +6,25 @@ import (
 	"crypto/rand"
 	"crypto/rand"
 	"crypto/sha256"
 	"crypto/sha256"
 	"encoding/hex"
 	"encoding/hex"
+	"errors"
 	"io"
 	"io"
+
+	sdkkms "github.com/drakkan/sftpgo/v2/sdk/kms"
+)
+
+var (
+	errMalformedCiphertext = errors.New("malformed ciphertext")
 )
 )
 
 
 type builtinSecret struct {
 type builtinSecret struct {
-	BaseSecret
+	sdkkms.BaseSecret
 }
 }
 
 
 func init() {
 func init() {
-	RegisterSecretProvider(SchemeBuiltin, SecretStatusAES256GCM, newBuiltinSecret)
+	sdkkms.RegisterSecretProvider(sdkkms.SchemeBuiltin, sdkkms.SecretStatusAES256GCM, newBuiltinSecret)
 }
 }
 
 
-func newBuiltinSecret(base BaseSecret, url, masterKey string) SecretProvider {
+func newBuiltinSecret(base sdkkms.BaseSecret, url, masterKey string) sdkkms.SecretProvider {
 	return &builtinSecret{
 	return &builtinSecret{
 		BaseSecret: base,
 		BaseSecret: base,
 	}
 	}
@@ -28,7 +35,7 @@ func (s *builtinSecret) Name() string {
 }
 }
 
 
 func (s *builtinSecret) IsEncrypted() bool {
 func (s *builtinSecret) IsEncrypted() bool {
-	return s.Status == SecretStatusAES256GCM
+	return s.Status == sdkkms.SecretStatusAES256GCM
 }
 }
 
 
 func (s *builtinSecret) deriveKey(key []byte) []byte {
 func (s *builtinSecret) deriveKey(key []byte) []byte {
@@ -44,10 +51,10 @@ func (s *builtinSecret) deriveKey(key []byte) []byte {
 
 
 func (s *builtinSecret) Encrypt() error {
 func (s *builtinSecret) Encrypt() error {
 	if s.Payload == "" {
 	if s.Payload == "" {
-		return ErrInvalidSecret
+		return sdkkms.ErrInvalidSecret
 	}
 	}
 	switch s.Status {
 	switch s.Status {
-	case SecretStatusPlain:
+	case sdkkms.SecretStatusPlain:
 		key := make([]byte, 32)
 		key := make([]byte, 32)
 		if _, err := io.ReadFull(rand.Reader, key); err != nil {
 		if _, err := io.ReadFull(rand.Reader, key); err != nil {
 			return err
 			return err
@@ -71,16 +78,16 @@ func (s *builtinSecret) Encrypt() error {
 		ciphertext := gcm.Seal(nonce, nonce, []byte(s.Payload), aad)
 		ciphertext := gcm.Seal(nonce, nonce, []byte(s.Payload), aad)
 		s.Key = hex.EncodeToString(key)
 		s.Key = hex.EncodeToString(key)
 		s.Payload = hex.EncodeToString(ciphertext)
 		s.Payload = hex.EncodeToString(ciphertext)
-		s.Status = SecretStatusAES256GCM
+		s.Status = sdkkms.SecretStatusAES256GCM
 		return nil
 		return nil
 	default:
 	default:
-		return ErrWrongSecretStatus
+		return sdkkms.ErrWrongSecretStatus
 	}
 	}
 }
 }
 
 
 func (s *builtinSecret) Decrypt() error {
 func (s *builtinSecret) Decrypt() error {
 	switch s.Status {
 	switch s.Status {
-	case SecretStatusAES256GCM:
+	case sdkkms.SecretStatusAES256GCM:
 		encrypted, err := hex.DecodeString(s.Payload)
 		encrypted, err := hex.DecodeString(s.Payload)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -110,18 +117,18 @@ func (s *builtinSecret) Decrypt() error {
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		s.Status = SecretStatusPlain
+		s.Status = sdkkms.SecretStatusPlain
 		s.Payload = string(plaintext)
 		s.Payload = string(plaintext)
 		s.Key = ""
 		s.Key = ""
 		s.AdditionalData = ""
 		s.AdditionalData = ""
 		return nil
 		return nil
 	default:
 	default:
-		return ErrWrongSecretStatus
+		return sdkkms.ErrWrongSecretStatus
 	}
 	}
 }
 }
 
 
-func (s *builtinSecret) Clone() SecretProvider {
-	baseSecret := BaseSecret{
+func (s *builtinSecret) Clone() sdkkms.SecretProvider {
+	baseSecret := sdkkms.BaseSecret{
 		Status:         s.Status,
 		Status:         s.Status,
 		Payload:        s.Payload,
 		Payload:        s.Payload,
 		Key:            s.Key,
 		Key:            s.Key,

+ 1 - 445
kms/kms.go

@@ -1,446 +1,2 @@
-// Package kms provides Key Management Services support
+// Package kms provides built-in Key Management Services support
 package kms
 package kms
-
-import (
-	"encoding/json"
-	"errors"
-	"os"
-	"strings"
-	"sync"
-
-	"github.com/drakkan/sftpgo/v2/logger"
-	"github.com/drakkan/sftpgo/v2/util"
-)
-
-// SecretProvider defines the interface for a KMS secrets provider
-type SecretProvider interface {
-	Name() string
-	Encrypt() error
-	Decrypt() error
-	IsEncrypted() bool
-	GetStatus() SecretStatus
-	GetPayload() string
-	GetKey() string
-	GetAdditionalData() string
-	GetMode() int
-	SetKey(string)
-	SetAdditionalData(string)
-	SetStatus(SecretStatus)
-	Clone() SecretProvider
-}
-
-const (
-	logSender = "kms"
-)
-
-// SecretStatus defines the statuses of a Secret object
-type SecretStatus = string
-
-const (
-	// SecretStatusPlain means the secret is in plain text and must be encrypted
-	SecretStatusPlain SecretStatus = "Plain"
-	// SecretStatusAES256GCM means the secret is encrypted using AES-256-GCM
-	SecretStatusAES256GCM SecretStatus = "AES-256-GCM"
-	// SecretStatusSecretBox means the secret is encrypted using a locally provided symmetric key
-	SecretStatusSecretBox SecretStatus = "Secretbox"
-	// SecretStatusGCP means we use keys from Google Cloud Platform’s Key Management Service
-	// (GCP KMS) to keep information secret
-	SecretStatusGCP SecretStatus = "GCP"
-	// SecretStatusAWS means we use customer master keys from Amazon Web Service’s
-	// Key Management Service (AWS KMS) to keep information secret
-	SecretStatusAWS SecretStatus = "AWS"
-	// SecretStatusVaultTransit means we use the transit secrets engine in Vault
-	// to keep information secret
-	SecretStatusVaultTransit SecretStatus = "VaultTransit"
-	// SecretStatusAzureKeyVault means we use Azure KeyVault to keep information secret
-	SecretStatusAzureKeyVault SecretStatus = "AzureKeyVault"
-	// SecretStatusRedacted means the secret is redacted
-	SecretStatusRedacted SecretStatus = "Redacted"
-)
-
-// Scheme defines the supported URL scheme
-type Scheme = string
-
-// supported URL schemes
-const (
-	SchemeLocal         Scheme = "local"
-	SchemeBuiltin       Scheme = "builtin"
-	SchemeAWS           Scheme = "awskms"
-	SchemeGCP           Scheme = "gcpkms"
-	SchemeVaultTransit  Scheme = "hashivault"
-	SchemeAzureKeyVault Scheme = "azurekeyvault"
-)
-
-// Configuration defines the KMS configuration
-type Configuration struct {
-	Secrets Secrets `json:"secrets" mapstructure:"secrets"`
-}
-
-// Secrets define the KMS configuration for encryption/decryption
-type Secrets struct {
-	URL             string `json:"url" mapstructure:"url"`
-	MasterKeyPath   string `json:"master_key_path" mapstructure:"master_key_path"`
-	MasterKeyString string `json:"master_key" mapstructure:"master_key"`
-	masterKey       string
-}
-
-type registeredSecretProvider struct {
-	encryptedStatus SecretStatus
-	newFn           func(base BaseSecret, url, masterKey string) SecretProvider
-}
-
-var (
-	// ErrWrongSecretStatus defines the error to return if the secret status is not appropriate
-	// for the request operation
-	ErrWrongSecretStatus = errors.New("wrong secret status")
-	// ErrInvalidSecret defines the error to return if a secret is not valid
-	ErrInvalidSecret       = errors.New("invalid secret")
-	errMalformedCiphertext = errors.New("malformed ciphertext")
-	validSecretStatuses    = []string{SecretStatusPlain, SecretStatusAES256GCM, SecretStatusSecretBox,
-		SecretStatusVaultTransit, SecretStatusAWS, SecretStatusGCP, SecretStatusRedacted}
-	config          Configuration
-	secretProviders = make(map[string]registeredSecretProvider)
-)
-
-// RegisterSecretProvider register a new secret provider
-func RegisterSecretProvider(scheme string, encryptedStatus SecretStatus, fn func(base BaseSecret, url, masterKey string) SecretProvider) {
-	secretProviders[scheme] = registeredSecretProvider{
-		encryptedStatus: encryptedStatus,
-		newFn:           fn,
-	}
-}
-
-// NewSecret builds a new Secret using the provided arguments
-func NewSecret(status SecretStatus, payload, key, data string) *Secret {
-	return config.newSecret(status, payload, key, data)
-}
-
-// NewEmptySecret returns an empty secret
-func NewEmptySecret() *Secret {
-	return NewSecret("", "", "", "")
-}
-
-// NewPlainSecret stores the give payload in a plain text secret
-func NewPlainSecret(payload string) *Secret {
-	return NewSecret(SecretStatusPlain, payload, "", "")
-}
-
-// Initialize configures the KMS support
-func (c *Configuration) Initialize() error {
-	if c.Secrets.MasterKeyString != "" {
-		c.Secrets.masterKey = c.Secrets.MasterKeyString
-	}
-	if c.Secrets.masterKey == "" && c.Secrets.MasterKeyPath != "" {
-		mKey, err := os.ReadFile(c.Secrets.MasterKeyPath)
-		if err != nil {
-			return err
-		}
-		c.Secrets.masterKey = strings.TrimSpace(string(mKey))
-	}
-	config = *c
-	if config.Secrets.URL == "" {
-		config.Secrets.URL = SchemeLocal + "://"
-	}
-	for k, v := range secretProviders {
-		logger.Debug(logSender, "", "secret provider registered for scheme: %#v, encrypted status: %#v",
-			k, v.encryptedStatus)
-	}
-	return nil
-}
-
-func (c *Configuration) newSecret(status SecretStatus, payload, key, data string) *Secret {
-	base := BaseSecret{
-		Status:         status,
-		Key:            key,
-		Payload:        payload,
-		AdditionalData: data,
-	}
-	return &Secret{
-		provider: c.getSecretProvider(base),
-	}
-}
-
-func (c *Configuration) getSecretProvider(base BaseSecret) SecretProvider {
-	for k, v := range secretProviders {
-		if strings.HasPrefix(c.Secrets.URL, k) {
-			return v.newFn(base, c.Secrets.URL, c.Secrets.masterKey)
-		}
-	}
-	logger.Warn(logSender, "", "no secret provider registered for URL %v, fallback to local provider", c.Secrets.URL)
-	return NewLocalSecret(base, c.Secrets.URL, c.Secrets.masterKey)
-}
-
-// Secret defines the struct used to store confidential data
-type Secret struct {
-	sync.RWMutex
-	provider SecretProvider
-}
-
-// MarshalJSON return the JSON encoding of the Secret object
-func (s *Secret) MarshalJSON() ([]byte, error) {
-	s.RLock()
-	defer s.RUnlock()
-
-	return json.Marshal(&BaseSecret{
-		Status:         s.provider.GetStatus(),
-		Payload:        s.provider.GetPayload(),
-		Key:            s.provider.GetKey(),
-		AdditionalData: s.provider.GetAdditionalData(),
-		Mode:           s.provider.GetMode(),
-	})
-}
-
-// UnmarshalJSON parses the JSON-encoded data and stores the result
-// in the Secret object
-func (s *Secret) UnmarshalJSON(data []byte) error {
-	s.Lock()
-	defer s.Unlock()
-
-	baseSecret := BaseSecret{}
-	err := json.Unmarshal(data, &baseSecret)
-	if err != nil {
-		return err
-	}
-	if baseSecret.isEmpty() {
-		s.provider = config.getSecretProvider(baseSecret)
-		return nil
-	}
-
-	if baseSecret.Status == SecretStatusPlain || baseSecret.Status == SecretStatusRedacted {
-		s.provider = config.getSecretProvider(baseSecret)
-		return nil
-	}
-
-	for _, v := range secretProviders {
-		if v.encryptedStatus == baseSecret.Status {
-			s.provider = v.newFn(baseSecret, config.Secrets.URL, config.Secrets.masterKey)
-			return nil
-		}
-	}
-	logger.Debug(logSender, "", "no provider registered for status %#v", baseSecret.Status)
-
-	return ErrInvalidSecret
-}
-
-// IsEqual returns true if all the secrets fields are equal
-func (s *Secret) IsEqual(other *Secret) bool {
-	if s.GetStatus() != other.GetStatus() {
-		return false
-	}
-	if s.GetPayload() != other.GetPayload() {
-		return false
-	}
-	if s.GetKey() != other.GetKey() {
-		return false
-	}
-	if s.GetAdditionalData() != other.GetAdditionalData() {
-		return false
-	}
-	if s.GetMode() != other.GetMode() {
-		return false
-	}
-	return true
-}
-
-// Clone returns a copy of the secret object
-func (s *Secret) Clone() *Secret {
-	s.RLock()
-	defer s.RUnlock()
-
-	return &Secret{
-		provider: s.provider.Clone(),
-	}
-}
-
-// IsEncrypted returns true if the secret is encrypted
-// This isn't a pointer receiver because we don't want to pass
-// a pointer to html template
-func (s *Secret) IsEncrypted() bool {
-	s.RLock()
-	defer s.RUnlock()
-
-	return s.provider.IsEncrypted()
-}
-
-// IsPlain returns true if the secret is in plain text
-func (s *Secret) IsPlain() bool {
-	s.RLock()
-	defer s.RUnlock()
-
-	return s.provider.GetStatus() == SecretStatusPlain
-}
-
-// IsNotPlainAndNotEmpty returns true if the secret is not plain and not empty.
-// This is an utility method, we update the secret for an existing user
-// if it is empty or plain
-func (s *Secret) IsNotPlainAndNotEmpty() bool {
-	s.RLock()
-	defer s.RUnlock()
-
-	return !s.IsPlain() && !s.IsEmpty()
-}
-
-// IsRedacted returns true if the secret is redacted
-func (s *Secret) IsRedacted() bool {
-	s.RLock()
-	defer s.RUnlock()
-
-	return s.provider.GetStatus() == SecretStatusRedacted
-}
-
-// GetPayload returns the secret payload
-func (s *Secret) GetPayload() string {
-	s.RLock()
-	defer s.RUnlock()
-
-	return s.provider.GetPayload()
-}
-
-// GetAdditionalData returns the secret additional data
-func (s *Secret) GetAdditionalData() string {
-	s.RLock()
-	defer s.RUnlock()
-
-	return s.provider.GetAdditionalData()
-}
-
-// GetStatus returns the secret status
-func (s *Secret) GetStatus() SecretStatus {
-	s.RLock()
-	defer s.RUnlock()
-
-	return s.provider.GetStatus()
-}
-
-// GetKey returns the secret key
-func (s *Secret) GetKey() string {
-	s.RLock()
-	defer s.RUnlock()
-
-	return s.provider.GetKey()
-}
-
-// GetMode returns the secret mode
-func (s *Secret) GetMode() int {
-	s.RLock()
-	defer s.RUnlock()
-
-	return s.provider.GetMode()
-}
-
-// SetAdditionalData sets the given additional data
-func (s *Secret) SetAdditionalData(value string) {
-	s.Lock()
-	defer s.Unlock()
-
-	s.provider.SetAdditionalData(value)
-}
-
-// SetStatus sets the status for this secret
-func (s *Secret) SetStatus(value SecretStatus) {
-	s.Lock()
-	defer s.Unlock()
-
-	s.provider.SetStatus(value)
-}
-
-// SetKey sets the key for this secret
-func (s *Secret) SetKey(value string) {
-	s.Lock()
-	defer s.Unlock()
-
-	s.provider.SetKey(value)
-}
-
-// IsEmpty returns true if all fields are empty
-func (s *Secret) IsEmpty() bool {
-	s.RLock()
-	defer s.RUnlock()
-
-	if s.provider.GetStatus() != "" {
-		return false
-	}
-	if s.provider.GetPayload() != "" {
-		return false
-	}
-	if s.provider.GetKey() != "" {
-		return false
-	}
-	if s.provider.GetAdditionalData() != "" {
-		return false
-	}
-	return true
-}
-
-// IsValid returns true if the secret is not empty and valid
-func (s *Secret) IsValid() bool {
-	s.RLock()
-	defer s.RUnlock()
-
-	if !s.IsValidInput() {
-		return false
-	}
-	switch s.provider.GetStatus() {
-	case SecretStatusAES256GCM, SecretStatusSecretBox:
-		if len(s.provider.GetKey()) != 64 {
-			return false
-		}
-	case SecretStatusAWS, SecretStatusGCP, SecretStatusVaultTransit:
-		key := s.provider.GetKey()
-		if key != "" && len(key) != 64 {
-			return false
-		}
-	}
-	return true
-}
-
-// IsValidInput returns true if the secret is a valid user input
-func (s *Secret) IsValidInput() bool {
-	s.RLock()
-	defer s.RUnlock()
-
-	if !util.IsStringInSlice(s.provider.GetStatus(), validSecretStatuses) {
-		return false
-	}
-	if s.provider.GetPayload() == "" {
-		return false
-	}
-	return true
-}
-
-// Hide hides info to decrypt data
-func (s *Secret) Hide() {
-	s.Lock()
-	defer s.Unlock()
-
-	s.provider.SetKey("")
-	s.provider.SetAdditionalData("")
-}
-
-// Encrypt encrypts a plain text Secret object
-func (s *Secret) Encrypt() error {
-	s.Lock()
-	defer s.Unlock()
-
-	return s.provider.Encrypt()
-}
-
-// Decrypt decrypts a Secret object
-func (s *Secret) Decrypt() error {
-	s.Lock()
-	defer s.Unlock()
-
-	return s.provider.Decrypt()
-}
-
-// TryDecrypt decrypts a Secret object if encrypted.
-// It returns a nil error if the object is not encrypted
-func (s *Secret) TryDecrypt() error {
-	s.Lock()
-	defer s.Unlock()
-
-	if s.provider.IsEncrypted() {
-		return s.provider.Decrypt()
-	}
-	return nil
-}

+ 14 - 12
kms/local.go

@@ -9,19 +9,21 @@ import (
 
 
 	"gocloud.dev/secrets/localsecrets"
 	"gocloud.dev/secrets/localsecrets"
 	"golang.org/x/crypto/hkdf"
 	"golang.org/x/crypto/hkdf"
+
+	sdkkms "github.com/drakkan/sftpgo/v2/sdk/kms"
 )
 )
 
 
 func init() {
 func init() {
-	RegisterSecretProvider(SchemeLocal, SecretStatusSecretBox, NewLocalSecret)
+	sdkkms.RegisterSecretProvider(sdkkms.SchemeLocal, sdkkms.SecretStatusSecretBox, NewLocalSecret)
 }
 }
 
 
 type localSecret struct {
 type localSecret struct {
-	BaseSecret
+	sdkkms.BaseSecret
 	masterKey string
 	masterKey string
 }
 }
 
 
 // NewLocalSecret returns a SecretProvider that use a locally provided symmetric key
 // NewLocalSecret returns a SecretProvider that use a locally provided symmetric key
-func NewLocalSecret(base BaseSecret, url, masterKey string) SecretProvider {
+func NewLocalSecret(base sdkkms.BaseSecret, url, masterKey string) sdkkms.SecretProvider {
 	return &localSecret{
 	return &localSecret{
 		BaseSecret: base,
 		BaseSecret: base,
 		masterKey:  masterKey,
 		masterKey:  masterKey,
@@ -33,15 +35,15 @@ func (s *localSecret) Name() string {
 }
 }
 
 
 func (s *localSecret) IsEncrypted() bool {
 func (s *localSecret) IsEncrypted() bool {
-	return s.Status == SecretStatusSecretBox
+	return s.Status == sdkkms.SecretStatusSecretBox
 }
 }
 
 
 func (s *localSecret) Encrypt() error {
 func (s *localSecret) Encrypt() error {
-	if s.Status != SecretStatusPlain {
-		return ErrWrongSecretStatus
+	if s.Status != sdkkms.SecretStatusPlain {
+		return sdkkms.ErrWrongSecretStatus
 	}
 	}
 	if s.Payload == "" {
 	if s.Payload == "" {
-		return ErrInvalidSecret
+		return sdkkms.ErrInvalidSecret
 	}
 	}
 	secretKey, err := localsecrets.NewRandomKey()
 	secretKey, err := localsecrets.NewRandomKey()
 	if err != nil {
 	if err != nil {
@@ -60,14 +62,14 @@ func (s *localSecret) Encrypt() error {
 	}
 	}
 	s.Key = hex.EncodeToString(secretKey[:])
 	s.Key = hex.EncodeToString(secretKey[:])
 	s.Payload = base64.StdEncoding.EncodeToString(ciphertext)
 	s.Payload = base64.StdEncoding.EncodeToString(ciphertext)
-	s.Status = SecretStatusSecretBox
+	s.Status = sdkkms.SecretStatusSecretBox
 	s.Mode = s.getEncryptionMode()
 	s.Mode = s.getEncryptionMode()
 	return nil
 	return nil
 }
 }
 
 
 func (s *localSecret) Decrypt() error {
 func (s *localSecret) Decrypt() error {
 	if !s.IsEncrypted() {
 	if !s.IsEncrypted() {
-		return ErrWrongSecretStatus
+		return sdkkms.ErrWrongSecretStatus
 	}
 	}
 	encrypted, err := base64.StdEncoding.DecodeString(s.Payload)
 	encrypted, err := base64.StdEncoding.DecodeString(s.Payload)
 	if err != nil {
 	if err != nil {
@@ -88,7 +90,7 @@ func (s *localSecret) Decrypt() error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	s.Status = SecretStatusPlain
+	s.Status = sdkkms.SecretStatusPlain
 	s.Payload = string(plaintext)
 	s.Payload = string(plaintext)
 	s.Key = ""
 	s.Key = ""
 	s.AdditionalData = ""
 	s.AdditionalData = ""
@@ -129,8 +131,8 @@ func (s *localSecret) getEncryptionMode() int {
 	return 1
 	return 1
 }
 }
 
 
-func (s *localSecret) Clone() SecretProvider {
-	baseSecret := BaseSecret{
+func (s *localSecret) Clone() sdkkms.SecretProvider {
+	baseSecret := sdkkms.BaseSecret{
 		Status:         s.Status,
 		Status:         s.Status,
 		Payload:        s.Payload,
 		Payload:        s.Payload,
 		Key:            s.Key,
 		Key:            s.Key,

+ 1 - 0
main.go

@@ -11,6 +11,7 @@ import (
 	"go.uber.org/automaxprocs/maxprocs"
 	"go.uber.org/automaxprocs/maxprocs"
 
 
 	"github.com/drakkan/sftpgo/v2/cmd"
 	"github.com/drakkan/sftpgo/v2/cmd"
+	_ "github.com/drakkan/sftpgo/v2/kms"
 )
 )
 
 
 func main() {
 func main() {

+ 1 - 1
sdk/filesystem.go

@@ -1,6 +1,6 @@
 package sdk
 package sdk
 
 
-import "github.com/drakkan/sftpgo/v2/kms"
+import "github.com/drakkan/sftpgo/v2/sdk/kms"
 
 
 // FilesystemProvider defines the supported storage filesystems
 // FilesystemProvider defines the supported storage filesystems
 type FilesystemProvider int
 type FilesystemProvider int

+ 0 - 0
kms/basesecret.go → sdk/kms/basesecret.go


+ 446 - 0
sdk/kms/kms.go

@@ -0,0 +1,446 @@
+// Package kms provides Key Management Services support
+package kms
+
+import (
+	"encoding/json"
+	"errors"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/drakkan/sftpgo/v2/logger"
+	"github.com/drakkan/sftpgo/v2/util"
+)
+
+// SecretProvider defines the interface for a KMS secrets provider
+type SecretProvider interface {
+	Name() string
+	Encrypt() error
+	Decrypt() error
+	IsEncrypted() bool
+	GetStatus() SecretStatus
+	GetPayload() string
+	GetKey() string
+	GetAdditionalData() string
+	GetMode() int
+	SetKey(string)
+	SetAdditionalData(string)
+	SetStatus(SecretStatus)
+	Clone() SecretProvider
+}
+
+const (
+	logSender = "kms"
+)
+
+// SecretStatus defines the statuses of a Secret object
+type SecretStatus = string
+
+const (
+	// SecretStatusPlain means the secret is in plain text and must be encrypted
+	SecretStatusPlain SecretStatus = "Plain"
+	// SecretStatusAES256GCM means the secret is encrypted using AES-256-GCM
+	SecretStatusAES256GCM SecretStatus = "AES-256-GCM"
+	// SecretStatusSecretBox means the secret is encrypted using a locally provided symmetric key
+	SecretStatusSecretBox SecretStatus = "Secretbox"
+	// SecretStatusGCP means we use keys from Google Cloud Platform’s Key Management Service
+	// (GCP KMS) to keep information secret
+	SecretStatusGCP SecretStatus = "GCP"
+	// SecretStatusAWS means we use customer master keys from Amazon Web Service’s
+	// Key Management Service (AWS KMS) to keep information secret
+	SecretStatusAWS SecretStatus = "AWS"
+	// SecretStatusVaultTransit means we use the transit secrets engine in Vault
+	// to keep information secret
+	SecretStatusVaultTransit SecretStatus = "VaultTransit"
+	// SecretStatusAzureKeyVault means we use Azure KeyVault to keep information secret
+	SecretStatusAzureKeyVault SecretStatus = "AzureKeyVault"
+	// SecretStatusRedacted means the secret is redacted
+	SecretStatusRedacted SecretStatus = "Redacted"
+)
+
+// Scheme defines the supported URL scheme
+type Scheme = string
+
+// supported URL schemes
+const (
+	SchemeLocal         Scheme = "local"
+	SchemeBuiltin       Scheme = "builtin"
+	SchemeAWS           Scheme = "awskms"
+	SchemeGCP           Scheme = "gcpkms"
+	SchemeVaultTransit  Scheme = "hashivault"
+	SchemeAzureKeyVault Scheme = "azurekeyvault"
+)
+
+// Configuration defines the KMS configuration
+type Configuration struct {
+	Secrets Secrets `json:"secrets" mapstructure:"secrets"`
+}
+
+// Secrets define the KMS configuration for encryption/decryption
+type Secrets struct {
+	URL             string `json:"url" mapstructure:"url"`
+	MasterKeyPath   string `json:"master_key_path" mapstructure:"master_key_path"`
+	MasterKeyString string `json:"master_key" mapstructure:"master_key"`
+	masterKey       string
+}
+
+type registeredSecretProvider struct {
+	encryptedStatus SecretStatus
+	newFn           func(base BaseSecret, url, masterKey string) SecretProvider
+}
+
+var (
+	// ErrWrongSecretStatus defines the error to return if the secret status is not appropriate
+	// for the request operation
+	ErrWrongSecretStatus = errors.New("wrong secret status")
+	// ErrInvalidSecret defines the error to return if a secret is not valid
+	ErrInvalidSecret    = errors.New("invalid secret")
+	validSecretStatuses = []string{SecretStatusPlain, SecretStatusAES256GCM, SecretStatusSecretBox,
+		SecretStatusVaultTransit, SecretStatusAWS, SecretStatusGCP, SecretStatusRedacted}
+	config          Configuration
+	secretProviders = make(map[string]registeredSecretProvider)
+)
+
+// RegisterSecretProvider register a new secret provider
+func RegisterSecretProvider(scheme string, encryptedStatus SecretStatus, fn func(base BaseSecret, url, masterKey string) SecretProvider) {
+	secretProviders[scheme] = registeredSecretProvider{
+		encryptedStatus: encryptedStatus,
+		newFn:           fn,
+	}
+}
+
+// NewSecret builds a new Secret using the provided arguments
+func NewSecret(status SecretStatus, payload, key, data string) *Secret {
+	return config.newSecret(status, payload, key, data)
+}
+
+// NewEmptySecret returns an empty secret
+func NewEmptySecret() *Secret {
+	return NewSecret("", "", "", "")
+}
+
+// NewPlainSecret stores the give payload in a plain text secret
+func NewPlainSecret(payload string) *Secret {
+	return NewSecret(SecretStatusPlain, payload, "", "")
+}
+
+// Initialize configures the KMS support
+func (c *Configuration) Initialize() error {
+	if c.Secrets.MasterKeyString != "" {
+		c.Secrets.masterKey = c.Secrets.MasterKeyString
+	}
+	if c.Secrets.masterKey == "" && c.Secrets.MasterKeyPath != "" {
+		mKey, err := os.ReadFile(c.Secrets.MasterKeyPath)
+		if err != nil {
+			return err
+		}
+		c.Secrets.masterKey = strings.TrimSpace(string(mKey))
+	}
+	config = *c
+	if config.Secrets.URL == "" {
+		config.Secrets.URL = SchemeLocal + "://"
+	}
+	for k, v := range secretProviders {
+		logger.Debug(logSender, "", "secret provider registered for scheme: %#v, encrypted status: %#v",
+			k, v.encryptedStatus)
+	}
+	return nil
+}
+
+func (c *Configuration) newSecret(status SecretStatus, payload, key, data string) *Secret {
+	base := BaseSecret{
+		Status:         status,
+		Key:            key,
+		Payload:        payload,
+		AdditionalData: data,
+	}
+	return &Secret{
+		provider: c.getSecretProvider(base),
+	}
+}
+
+func (c *Configuration) getSecretProvider(base BaseSecret) SecretProvider {
+	for k, v := range secretProviders {
+		if strings.HasPrefix(c.Secrets.URL, k) {
+			return v.newFn(base, c.Secrets.URL, c.Secrets.masterKey)
+		}
+	}
+	// we assume that SchemeLocal is always registered
+	logger.Warn(logSender, "", "no secret provider registered for URL %v, fallback to local provider", c.Secrets.URL)
+	return secretProviders[SchemeLocal].newFn(base, c.Secrets.URL, c.Secrets.masterKey)
+}
+
+// Secret defines the struct used to store confidential data
+type Secret struct {
+	sync.RWMutex
+	provider SecretProvider
+}
+
+// MarshalJSON return the JSON encoding of the Secret object
+func (s *Secret) MarshalJSON() ([]byte, error) {
+	s.RLock()
+	defer s.RUnlock()
+
+	return json.Marshal(&BaseSecret{
+		Status:         s.provider.GetStatus(),
+		Payload:        s.provider.GetPayload(),
+		Key:            s.provider.GetKey(),
+		AdditionalData: s.provider.GetAdditionalData(),
+		Mode:           s.provider.GetMode(),
+	})
+}
+
+// UnmarshalJSON parses the JSON-encoded data and stores the result
+// in the Secret object
+func (s *Secret) UnmarshalJSON(data []byte) error {
+	s.Lock()
+	defer s.Unlock()
+
+	baseSecret := BaseSecret{}
+	err := json.Unmarshal(data, &baseSecret)
+	if err != nil {
+		return err
+	}
+	if baseSecret.isEmpty() {
+		s.provider = config.getSecretProvider(baseSecret)
+		return nil
+	}
+
+	if baseSecret.Status == SecretStatusPlain || baseSecret.Status == SecretStatusRedacted {
+		s.provider = config.getSecretProvider(baseSecret)
+		return nil
+	}
+
+	for _, v := range secretProviders {
+		if v.encryptedStatus == baseSecret.Status {
+			s.provider = v.newFn(baseSecret, config.Secrets.URL, config.Secrets.masterKey)
+			return nil
+		}
+	}
+	logger.Debug(logSender, "", "no provider registered for status %#v", baseSecret.Status)
+
+	return ErrInvalidSecret
+}
+
+// IsEqual returns true if all the secrets fields are equal
+func (s *Secret) IsEqual(other *Secret) bool {
+	if s.GetStatus() != other.GetStatus() {
+		return false
+	}
+	if s.GetPayload() != other.GetPayload() {
+		return false
+	}
+	if s.GetKey() != other.GetKey() {
+		return false
+	}
+	if s.GetAdditionalData() != other.GetAdditionalData() {
+		return false
+	}
+	if s.GetMode() != other.GetMode() {
+		return false
+	}
+	return true
+}
+
+// Clone returns a copy of the secret object
+func (s *Secret) Clone() *Secret {
+	s.RLock()
+	defer s.RUnlock()
+
+	return &Secret{
+		provider: s.provider.Clone(),
+	}
+}
+
+// IsEncrypted returns true if the secret is encrypted
+// This isn't a pointer receiver because we don't want to pass
+// a pointer to html template
+func (s *Secret) IsEncrypted() bool {
+	s.RLock()
+	defer s.RUnlock()
+
+	return s.provider.IsEncrypted()
+}
+
+// IsPlain returns true if the secret is in plain text
+func (s *Secret) IsPlain() bool {
+	s.RLock()
+	defer s.RUnlock()
+
+	return s.provider.GetStatus() == SecretStatusPlain
+}
+
+// IsNotPlainAndNotEmpty returns true if the secret is not plain and not empty.
+// This is an utility method, we update the secret for an existing user
+// if it is empty or plain
+func (s *Secret) IsNotPlainAndNotEmpty() bool {
+	s.RLock()
+	defer s.RUnlock()
+
+	return !s.IsPlain() && !s.IsEmpty()
+}
+
+// IsRedacted returns true if the secret is redacted
+func (s *Secret) IsRedacted() bool {
+	s.RLock()
+	defer s.RUnlock()
+
+	return s.provider.GetStatus() == SecretStatusRedacted
+}
+
+// GetPayload returns the secret payload
+func (s *Secret) GetPayload() string {
+	s.RLock()
+	defer s.RUnlock()
+
+	return s.provider.GetPayload()
+}
+
+// GetAdditionalData returns the secret additional data
+func (s *Secret) GetAdditionalData() string {
+	s.RLock()
+	defer s.RUnlock()
+
+	return s.provider.GetAdditionalData()
+}
+
+// GetStatus returns the secret status
+func (s *Secret) GetStatus() SecretStatus {
+	s.RLock()
+	defer s.RUnlock()
+
+	return s.provider.GetStatus()
+}
+
+// GetKey returns the secret key
+func (s *Secret) GetKey() string {
+	s.RLock()
+	defer s.RUnlock()
+
+	return s.provider.GetKey()
+}
+
+// GetMode returns the secret mode
+func (s *Secret) GetMode() int {
+	s.RLock()
+	defer s.RUnlock()
+
+	return s.provider.GetMode()
+}
+
+// SetAdditionalData sets the given additional data
+func (s *Secret) SetAdditionalData(value string) {
+	s.Lock()
+	defer s.Unlock()
+
+	s.provider.SetAdditionalData(value)
+}
+
+// SetStatus sets the status for this secret
+func (s *Secret) SetStatus(value SecretStatus) {
+	s.Lock()
+	defer s.Unlock()
+
+	s.provider.SetStatus(value)
+}
+
+// SetKey sets the key for this secret
+func (s *Secret) SetKey(value string) {
+	s.Lock()
+	defer s.Unlock()
+
+	s.provider.SetKey(value)
+}
+
+// IsEmpty returns true if all fields are empty
+func (s *Secret) IsEmpty() bool {
+	s.RLock()
+	defer s.RUnlock()
+
+	if s.provider.GetStatus() != "" {
+		return false
+	}
+	if s.provider.GetPayload() != "" {
+		return false
+	}
+	if s.provider.GetKey() != "" {
+		return false
+	}
+	if s.provider.GetAdditionalData() != "" {
+		return false
+	}
+	return true
+}
+
+// IsValid returns true if the secret is not empty and valid
+func (s *Secret) IsValid() bool {
+	s.RLock()
+	defer s.RUnlock()
+
+	if !s.IsValidInput() {
+		return false
+	}
+	switch s.provider.GetStatus() {
+	case SecretStatusAES256GCM, SecretStatusSecretBox:
+		if len(s.provider.GetKey()) != 64 {
+			return false
+		}
+	case SecretStatusAWS, SecretStatusGCP, SecretStatusVaultTransit:
+		key := s.provider.GetKey()
+		if key != "" && len(key) != 64 {
+			return false
+		}
+	}
+	return true
+}
+
+// IsValidInput returns true if the secret is a valid user input
+func (s *Secret) IsValidInput() bool {
+	s.RLock()
+	defer s.RUnlock()
+
+	if !util.IsStringInSlice(s.provider.GetStatus(), validSecretStatuses) {
+		return false
+	}
+	if s.provider.GetPayload() == "" {
+		return false
+	}
+	return true
+}
+
+// Hide hides info to decrypt data
+func (s *Secret) Hide() {
+	s.Lock()
+	defer s.Unlock()
+
+	s.provider.SetKey("")
+	s.provider.SetAdditionalData("")
+}
+
+// Encrypt encrypts a plain text Secret object
+func (s *Secret) Encrypt() error {
+	s.Lock()
+	defer s.Unlock()
+
+	return s.provider.Encrypt()
+}
+
+// Decrypt decrypts a Secret object
+func (s *Secret) Decrypt() error {
+	s.Lock()
+	defer s.Unlock()
+
+	return s.provider.Decrypt()
+}
+
+// TryDecrypt decrypts a Secret object if encrypted.
+// It returns a nil error if the object is not encrypted
+func (s *Secret) TryDecrypt() error {
+	s.Lock()
+	defer s.Unlock()
+
+	if s.provider.IsEncrypted() {
+		return s.provider.Decrypt()
+	}
+	return nil
+}

+ 1 - 1
sdk/plugin/kms.go

@@ -9,8 +9,8 @@ import (
 	"github.com/hashicorp/go-hclog"
 	"github.com/hashicorp/go-hclog"
 	"github.com/hashicorp/go-plugin"
 	"github.com/hashicorp/go-plugin"
 
 
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	kmsplugin "github.com/drakkan/sftpgo/v2/sdk/plugin/kms"
 	kmsplugin "github.com/drakkan/sftpgo/v2/sdk/plugin/kms"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 )
 )

+ 1 - 1
sdk/plugin/plugin.go

@@ -11,8 +11,8 @@ import (
 
 
 	"github.com/hashicorp/go-hclog"
 	"github.com/hashicorp/go-hclog"
 
 
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/auth"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/auth"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/eventsearcher"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/eventsearcher"
 	kmsplugin "github.com/drakkan/sftpgo/v2/sdk/plugin/kms"
 	kmsplugin "github.com/drakkan/sftpgo/v2/sdk/plugin/kms"

+ 1 - 1
sdk/user.go

@@ -3,7 +3,7 @@ package sdk
 import (
 import (
 	"strings"
 	"strings"
 
 
-	"github.com/drakkan/sftpgo/v2/kms"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 )
 )
 
 

+ 1 - 1
service/service_portable.go

@@ -17,9 +17,9 @@ import (
 	"github.com/drakkan/sftpgo/v2/config"
 	"github.com/drakkan/sftpgo/v2/config"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/ftpd"
 	"github.com/drakkan/sftpgo/v2/ftpd"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/version"
 	"github.com/drakkan/sftpgo/v2/version"

+ 1 - 1
sftpd/cryptfs_test.go

@@ -15,8 +15,8 @@ import (
 
 
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"
 )
 )
 
 

+ 1 - 1
sftpd/internal_test.go

@@ -20,8 +20,8 @@ import (
 
 
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"
 )
 )

+ 2 - 1
sftpd/sftpd_test.go

@@ -42,10 +42,11 @@ import (
 	"github.com/drakkan/sftpgo/v2/config"
 	"github.com/drakkan/sftpgo/v2/config"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
-	"github.com/drakkan/sftpgo/v2/kms"
+	_ "github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"

+ 1 - 1
vfs/filesystem.go

@@ -3,8 +3,8 @@ package vfs
 import (
 import (
 	"fmt"
 	"fmt"
 
 
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 )
 )
 
 

+ 1 - 1
vfs/gcsfs.go

@@ -23,9 +23,9 @@ import (
 	"google.golang.org/api/iterator"
 	"google.golang.org/api/iterator"
 	"google.golang.org/api/option"
 	"google.golang.org/api/option"
 
 
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/metric"
 	"github.com/drakkan/sftpgo/v2/metric"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/version"
 	"github.com/drakkan/sftpgo/v2/version"

+ 1 - 1
vfs/sftpfs.go

@@ -19,9 +19,9 @@ import (
 	"github.com/rs/xid"
 	"github.com/rs/xid"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/crypto/ssh"
 
 
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/version"
 	"github.com/drakkan/sftpgo/v2/version"
 )
 )

+ 1 - 1
vfs/vfs.go

@@ -16,9 +16,9 @@ import (
 	"github.com/eikenb/pipeat"
 	"github.com/eikenb/pipeat"
 	"github.com/pkg/sftp"
 	"github.com/pkg/sftp"
 
 
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/metadata"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/metadata"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"

+ 1 - 1
webdavd/internal_test.go

@@ -22,8 +22,8 @@ import (
 
 
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
-	"github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"
 )
 )

+ 2 - 1
webdavd/webdavd_test.go

@@ -31,9 +31,10 @@ import (
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
-	"github.com/drakkan/sftpgo/v2/kms"
+	_ "github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/sftpd"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/vfs"
 	"github.com/drakkan/sftpgo/v2/webdavd"
 	"github.com/drakkan/sftpgo/v2/webdavd"