feat(meta): values symmetric encryption
This commit is contained in:
@@ -10,11 +10,13 @@ import (
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
"github.com/ipld/go-ipld-prime/printer"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/pkg/crypto"
|
||||
)
|
||||
|
||||
var ErrUnsupported = errors.New("failure adding unsupported type to meta")
|
||||
|
||||
var ErrNotFound = errors.New("key-value not found in meta")
|
||||
var ErrNotEncryptable = errors.New("value of this type cannot be encrypted")
|
||||
|
||||
// Meta is a container for meta key-value pairs in a UCAN token.
|
||||
// This also serves as a way to construct the underlying IPLD data with minimum allocations and transformations,
|
||||
@@ -51,6 +53,21 @@ func (m *Meta) GetString(key string) (string, error) {
|
||||
return v.AsString()
|
||||
}
|
||||
|
||||
// GetEncryptedString decorates GetString and decrypt its output with the given symmetric encryption key.
|
||||
func (m *Meta) GetEncryptedString(key string, encryptionKey []byte) (string, error) {
|
||||
v, err := m.GetString(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
decrypted, err := crypto.DecryptStringWithAESKey([]byte(v), encryptionKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(decrypted), nil
|
||||
}
|
||||
|
||||
// GetInt64 retrieves a value as an int64.
|
||||
// Returns ErrNotFound if the given key is missing.
|
||||
// Returns datamodel.ErrWrongKind if the value has the wrong type.
|
||||
@@ -84,6 +101,21 @@ func (m *Meta) GetBytes(key string) ([]byte, error) {
|
||||
return v.AsBytes()
|
||||
}
|
||||
|
||||
// GetEncryptedBytes decorates GetBytes and decrypt its output with the given symmetric encryption key.
|
||||
func (m *Meta) GetEncryptedBytes(key string, encryptionKey []byte) ([]byte, error) {
|
||||
v, err := m.GetBytes(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decrypted, err := crypto.DecryptStringWithAESKey(v, encryptionKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decrypted, nil
|
||||
}
|
||||
|
||||
// GetNode retrieves a value as a raw IPLD node.
|
||||
// Returns ErrNotFound if the given key is missing.
|
||||
// Returns datamodel.ErrWrongKind if the value has the wrong type.
|
||||
@@ -125,6 +157,31 @@ func (m *Meta) Add(key string, val any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddEncrypted adds a key/value pair in the meta set.
|
||||
// The value is encrypted with the given encryptionKey.
|
||||
// Accepted types for the value are: string, []byte.
|
||||
func (m *Meta) AddEncrypted(key string, val any, encryptionKey []byte) error {
|
||||
var encrypted []byte
|
||||
var err error
|
||||
|
||||
switch val := val.(type) {
|
||||
case string:
|
||||
encrypted, err = crypto.EncryptWithAESKey([]byte(val), encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.Add(key, string(encrypted))
|
||||
case []byte:
|
||||
encrypted, err = crypto.EncryptWithAESKey(val, encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.Add(key, encrypted)
|
||||
default:
|
||||
return ErrNotEncryptable
|
||||
}
|
||||
}
|
||||
|
||||
// Equals tells if two Meta hold the same key/values.
|
||||
func (m *Meta) Equals(other *Meta) bool {
|
||||
if len(m.Keys) != len(other.Keys) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package meta_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -14,11 +15,69 @@ func TestMeta_Add(t *testing.T) {
|
||||
|
||||
type Unsupported struct{}
|
||||
|
||||
t.Run("error if not primative or Node", func(t *testing.T) {
|
||||
t.Run("error if not primitive or Node", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := (&meta.Meta{}).Add("invalid", &Unsupported{})
|
||||
require.ErrorIs(t, err, meta.ErrUnsupported)
|
||||
assert.ErrorContains(t, err, "*github.com/ucan-wg/go-ucan/pkg/meta_test.Unsupported")
|
||||
})
|
||||
|
||||
t.Run("encrypted meta", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
key := make([]byte, 32)
|
||||
_, err := rand.Read(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
m := meta.NewMeta()
|
||||
|
||||
// string encryption
|
||||
err = m.AddEncrypted("secret", "hello world", key)
|
||||
require.NoError(t, err)
|
||||
|
||||
encrypted, err := m.GetString("secret")
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, "hello world", encrypted)
|
||||
|
||||
decrypted, err := m.GetEncryptedString("secret", key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "hello world", decrypted)
|
||||
|
||||
// bytes encryption
|
||||
originalBytes := make([]byte, 128)
|
||||
_, err = rand.Read(originalBytes)
|
||||
require.NoError(t, err)
|
||||
err = m.AddEncrypted("secret-bytes", originalBytes, key)
|
||||
require.NoError(t, err)
|
||||
|
||||
encryptedBytes, err := m.GetBytes("secret-bytes")
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, originalBytes, encryptedBytes)
|
||||
|
||||
decryptedBytes, err := m.GetEncryptedBytes("secret-bytes", key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, originalBytes, decryptedBytes)
|
||||
|
||||
// error cases
|
||||
t.Run("error on unsupported type", func(t *testing.T) {
|
||||
err := m.AddEncrypted("invalid", 123, key)
|
||||
require.ErrorIs(t, err, meta.ErrNotEncryptable)
|
||||
})
|
||||
|
||||
t.Run("error on invalid key size", func(t *testing.T) {
|
||||
err := m.AddEncrypted("invalid", "test", []byte("short-key"))
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "invalid key size")
|
||||
})
|
||||
|
||||
t.Run("error on nil key", func(t *testing.T) {
|
||||
err := m.AddEncrypted("invalid", "test", nil)
|
||||
require.NoError(t, err)
|
||||
// with nil key, value should be stored unencrypted
|
||||
val, err := m.GetString("invalid")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test", val)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user