[#529] Use salt when deriving the encryption key
All checks were successful
/ DCO (pull_request) Successful in 2m58s
/ Vulncheck (pull_request) Successful in 3m12s
/ Builds (pull_request) Successful in 2m42s
/ Lint (pull_request) Successful in 4m28s
/ Tests (pull_request) Successful in 3m6s

Salt is used when generating encryption
keys for data (tokens) in the access box.
Now frostfs-s3-authmate always derivation
an encryption key with salt.

Signed-off-by: Roman Loginov <r.loginov@yadro.com>
This commit is contained in:
Roman Loginov 2024-12-05 16:08:55 +03:00
parent f215d200e8
commit d1812cefc0
7 changed files with 107 additions and 107 deletions

View file

@ -19,6 +19,11 @@ import (
"google.golang.org/protobuf/proto"
)
const (
secretLength = 32
saltLength = 16
)
// Box represents friendly AccessBox.
type Box struct {
Gate *GateData
@ -109,7 +114,7 @@ func PackTokens(gatesData []*GateData, secret []byte, isCustomSecret bool) (*Acc
box.IsCustom = isCustomSecret
if secret == nil {
secret, err = generateSecret()
secret, err = generateRandomBytes(secretLength)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate accessKey as hex: %w", err)
}
@ -212,7 +217,12 @@ func encodeGate(ephemeralKey *keys.PrivateKey, seedKey *keys.PublicKey, tokens *
return nil, fmt.Errorf("encode tokens: %w", err)
}
encrypted, err := encrypt(ephemeralKey, seedKey, data)
salt, err := generateRandomBytes(saltLength)
if err != nil {
return nil, fmt.Errorf("failed to generate salt for encryption key: %w", err)
}
encrypted, err := encrypt(ephemeralKey, seedKey, data, salt)
if err != nil {
return nil, fmt.Errorf("ecrypt tokens: %w", err)
}
@ -220,11 +230,12 @@ func encodeGate(ephemeralKey *keys.PrivateKey, seedKey *keys.PublicKey, tokens *
gate := new(AccessBox_Gate)
gate.GatePublicKey = seedKey.Bytes()
gate.Tokens = encrypted
gate.EncryptionKeySalt = salt
return gate, nil
}
func (x *AccessBox) decodeGate(gate *AccessBox_Gate, owner *keys.PrivateKey, seedKey *keys.PublicKey) (*GateData, error) {
data, err := decrypt(owner, seedKey, gate.Tokens)
data, err := decrypt(owner, seedKey, gate.Tokens, gate.EncryptionKeySalt)
if err != nil {
return nil, fmt.Errorf("decrypt tokens: %w", err)
}
@ -273,16 +284,16 @@ func generateShared256(prv *keys.PrivateKey, pub *keys.PublicKey) (sk []byte, er
return sk, nil
}
func deriveKey(secret []byte) ([]byte, error) {
func deriveKey(secret, salt []byte) ([]byte, error) {
hash := sha256.New
kdf := hkdf.New(hash, secret, nil, nil)
kdf := hkdf.New(hash, secret, salt, nil)
key := make([]byte, 32)
_, err := io.ReadFull(kdf, key)
return key, err
}
func encrypt(owner *keys.PrivateKey, seedKey *keys.PublicKey, data []byte) ([]byte, error) {
enc, err := getCipher(owner, seedKey)
func encrypt(owner *keys.PrivateKey, seedKey *keys.PublicKey, data, salt []byte) ([]byte, error) {
enc, err := getCipher(owner, seedKey, salt)
if err != nil {
return nil, fmt.Errorf("get chiper: %w", err)
}
@ -295,8 +306,8 @@ func encrypt(owner *keys.PrivateKey, seedKey *keys.PublicKey, data []byte) ([]by
return enc.Seal(nonce, nonce, data, nil), nil
}
func decrypt(owner *keys.PrivateKey, seedKey *keys.PublicKey, data []byte) ([]byte, error) {
dec, err := getCipher(owner, seedKey)
func decrypt(owner *keys.PrivateKey, seedKey *keys.PublicKey, data, salt []byte) ([]byte, error) {
dec, err := getCipher(owner, seedKey, salt)
if err != nil {
return nil, fmt.Errorf("get chiper: %w", err)
}
@ -309,13 +320,13 @@ func decrypt(owner *keys.PrivateKey, seedKey *keys.PublicKey, data []byte) ([]by
return dec.Open(nil, nonce, cypher, nil)
}
func getCipher(owner *keys.PrivateKey, seedKey *keys.PublicKey) (cipher.AEAD, error) {
func getCipher(owner *keys.PrivateKey, seedKey *keys.PublicKey, salt []byte) (cipher.AEAD, error) {
secret, err := generateShared256(owner, seedKey)
if err != nil {
return nil, fmt.Errorf("generate shared key: %w", err)
}
key, err := deriveKey(secret)
key, err := deriveKey(secret, salt)
if err != nil {
return nil, fmt.Errorf("derive key: %w", err)
}
@ -323,8 +334,8 @@ func getCipher(owner *keys.PrivateKey, seedKey *keys.PublicKey) (cipher.AEAD, er
return chacha20poly1305.NewX(key)
}
func generateSecret() ([]byte, error) {
b := make([]byte, 32)
func generateRandomBytes(length int) ([]byte, error) {
b := make([]byte, length)
_, err := io.ReadFull(rand.Reader, b)
return b, err
}

View file

@ -159,8 +159,9 @@ type AccessBox_Gate struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Tokens []byte `protobuf:"bytes,1,opt,name=tokens,proto3" json:"tokens,omitempty"`
GatePublicKey []byte `protobuf:"bytes,2,opt,name=gatePublicKey,proto3" json:"gatePublicKey,omitempty"`
Tokens []byte `protobuf:"bytes,1,opt,name=tokens,proto3" json:"tokens,omitempty"`
GatePublicKey []byte `protobuf:"bytes,2,opt,name=gatePublicKey,proto3" json:"gatePublicKey,omitempty"`
EncryptionKeySalt []byte `protobuf:"bytes,3,opt,name=encryptionKeySalt,proto3" json:"encryptionKeySalt,omitempty"`
}
func (x *AccessBox_Gate) Reset() {
@ -209,6 +210,13 @@ func (x *AccessBox_Gate) GetGatePublicKey() []byte {
return nil
}
func (x *AccessBox_Gate) GetEncryptionKeySalt() []byte {
if x != nil {
return x.EncryptionKeySalt
}
return nil
}
type AccessBox_ContainerPolicy struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -269,7 +277,7 @@ var File_creds_accessbox_accessbox_proto protoreflect.FileDescriptor
var file_creds_accessbox_accessbox_proto_rawDesc = []byte{
0x0a, 0x1f, 0x63, 0x72, 0x65, 0x64, 0x73, 0x2f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f,
0x78, 0x2f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x12, 0x09, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x78, 0x22, 0xe3, 0x02, 0x0a,
0x6f, 0x12, 0x09, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x78, 0x22, 0x91, 0x03, 0x0a,
0x09, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x42, 0x6f, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65,
0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x73, 0x65, 0x65,
0x64, 0x4b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x67, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20,
@ -282,29 +290,31 @@ var file_creds_accessbox_accessbox_proto_rawDesc = []byte{
0x6c, 0x69, 0x63, 0x79, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50,
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x73, 0x43, 0x75, 0x73, 0x74, 0x6f,
0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x43, 0x75, 0x73, 0x74, 0x6f,
0x6d, 0x1a, 0x44, 0x0a, 0x04, 0x47, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x6f, 0x6b,
0x6d, 0x1a, 0x72, 0x0a, 0x04, 0x47, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x6f, 0x6b,
0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x61, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b,
0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x67, 0x61, 0x74, 0x65, 0x50, 0x75,
0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x1a, 0x59, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61,
0x69, 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x12, 0x6c, 0x6f,
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f,
0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69,
0x63, 0x79, 0x22, 0x6e, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09,
0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x09, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x62, 0x65,
0x61, 0x72, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x0b, 0x62, 0x65, 0x61, 0x72, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x24, 0x0a, 0x0d,
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x03, 0x20,
0x03, 0x28, 0x0c, 0x52, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65,
0x6e, 0x73, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x2e, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66,
0x73, 0x2e, 0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x54, 0x72, 0x75, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64,
0x4c, 0x61, 0x62, 0x2f, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66, 0x73, 0x2d, 0x73, 0x33, 0x2d, 0x67,
0x77, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x73, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x62, 0x6f, 0x78,
0x3b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x78, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x11, 0x65, 0x6e, 0x63, 0x72, 0x79,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x53, 0x61, 0x6c, 0x74, 0x18, 0x03, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x11, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65,
0x79, 0x53, 0x61, 0x6c, 0x74, 0x1a, 0x59, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,
0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x12, 0x6c, 0x6f, 0x63, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f,
0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69,
0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79,
0x22, 0x6e, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65,
0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73,
0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x62, 0x65, 0x61, 0x72,
0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62,
0x65, 0x61, 0x72, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x65,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
0x0c, 0x52, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73,
0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x2e, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66, 0x73, 0x2e,
0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x54, 0x72, 0x75, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x4c, 0x61,
0x62, 0x2f, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66, 0x73, 0x2d, 0x73, 0x33, 0x2d, 0x67, 0x77, 0x2f,
0x63, 0x72, 0x65, 0x64, 0x73, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x62, 0x6f, 0x78, 0x3b, 0x61,
0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x78, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View file

@ -10,6 +10,7 @@ message AccessBox {
message Gate {
bytes tokens = 1 [json_name = "tokens"];
bytes gatePublicKey = 2 [json_name = "gatePublicKey"];
bytes encryptionKeySalt = 3 [json_name = "encryptionKeySalt"];
}
message ContainerPolicy {

View file

@ -23,6 +23,10 @@ func TestTokensEncryptDecrypt(t *testing.T) {
tkn bearer.Token
tkn2 bearer.Token
)
salt, err := generateRandomBytes(saltLength)
require.NoError(t, err)
sec, err := keys.NewPrivateKey()
require.NoError(t, err)
@ -32,16 +36,47 @@ func TestTokensEncryptDecrypt(t *testing.T) {
tkn.SetEACLTable(*eacl.NewTable())
require.NoError(t, tkn.Sign(sec.PrivateKey))
data, err := encrypt(cred, cred.PublicKey(), tkn.Marshal())
require.NoError(t, err)
t.Run("positive case without salt", func(t *testing.T) {
data, err := encrypt(cred, cred.PublicKey(), tkn.Marshal(), nil)
require.NoError(t, err)
rawTkn2, err := decrypt(cred, cred.PublicKey(), data)
require.NoError(t, err)
rawTkn2, err := decrypt(cred, cred.PublicKey(), data, nil)
require.NoError(t, err)
err = tkn2.Unmarshal(rawTkn2)
require.NoError(t, err)
err = tkn2.Unmarshal(rawTkn2)
require.NoError(t, err)
assertBearerToken(t, tkn, tkn2)
assertBearerToken(t, tkn, tkn2)
})
t.Run("positive case with salt", func(t *testing.T) {
data, err := encrypt(cred, cred.PublicKey(), tkn.Marshal(), salt)
require.NoError(t, err)
rawTkn2, err := decrypt(cred, cred.PublicKey(), data, salt)
require.NoError(t, err)
err = tkn2.Unmarshal(rawTkn2)
require.NoError(t, err)
assertBearerToken(t, tkn, tkn2)
})
t.Run("wrang salt", func(t *testing.T) {
data, err := encrypt(cred, cred.PublicKey(), tkn.Marshal(), salt)
require.NoError(t, err)
_, err = decrypt(cred, cred.PublicKey(), data, nil)
require.Error(t, err)
})
t.Run("wrang private key", func(t *testing.T) {
data, err := encrypt(cred, cred.PublicKey(), tkn.Marshal(), nil)
require.NoError(t, err)
_, err = decrypt(sec, cred.PublicKey(), data, nil)
require.Error(t, err)
})
}
func TestBearerTokenInAccessBox(t *testing.T) {

View file

@ -199,6 +199,7 @@ It contains:
in [spec](https://git.frostfs.info/TrueCloudLab/frostfs-api/src/commit/4c68d92468503b10282c8a92af83a56f170c8a3a/acl/types.proto#L189)
* Marshaled session token - more detail
in [spec](https://git.frostfs.info/TrueCloudLab/frostfs-api/src/commit/4c68d92468503b10282c8a92af83a56f170c8a3a/session/types.proto#L89)
* Encryption Key Salt - randomly generated 16 bytes that are used to derivation the encryption key
* Container placement policies:
* `LocationsConstraint` - name of location constraint that can be used to create bucket/container using s3
credentials related to this `AccessBox`
@ -258,19 +259,20 @@ secp256r1 or prime256v1) is used (unless otherwise stated).
* Create ephemeral key (`SeedKey`), it's need to generate shared secret
* Generate random 32-byte (that after hex-encoded be `SecretAccessKey`) or use existing secret access key
(if `AccessBox` is being updated rather than creating brand new) or use arbitrary user-provided string
* Generate random 16-byte salt
* Generate shared secret as [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman)
* Derive 32-byte key using shared secret from previous step with key derivation function based on
* Derive 32-byte key using shared secret from previous step and the salt with key derivation function based on
HMAC with SHA256 [HKDF](https://en.wikipedia.org/wiki/HKDF)
* Encrypt marshaled [Tokens](../creds/accessbox) using derived key
with [ChaCha20-Poly1305](https://en.wikipedia.org/wiki/ChaCha20-Poly1305) algorithm without additional data.
**Decryption:**
* Get public part of `SeedKey` from `AccessBox`
* Get public part of `SeedKey` and the salt from `AccessBox`
* Generate shared secret as follows:
* Make scalar curve multiplication of public part of `SeedKey` and private part of s3-gw key
* Use `X` part of multiplication (with zero padding at the beginning to fit 32-byte)
* Derive 32-byte key using shared secret from previous step with key derivation function based on
* Derive 32-byte key using shared secret from previous step and the salt with key derivation function based on
HMAC with SHA256 [HKDF](https://en.wikipedia.org/wiki/HKDF)
* Decrypt encrypted marshaled [Tokens](../creds/accessbox) using derived key
with [ChaCha20-Poly1305](https://en.wikipedia.org/wiki/ChaCha20-Poly1305) algorithm without additional data.

View file

@ -10,6 +10,7 @@ package AccessBox {
map Gate {
GateKey => Encoded public gate key
Encrypted tokens *--> Tokens
EncryptionKeySalt => Salt for derivation the encryption key
}
map ContainerPolicy {

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB