Merge pull request #51 from ucan-wg/v1-meta-encryption
feat(meta): values symmetric encryption
This commit is contained in:
132
pkg/meta/internal/crypto/aes.go
Normal file
132
pkg/meta/internal/crypto/aes.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// KeySize represents valid AES key sizes
|
||||
type KeySize int
|
||||
|
||||
const (
|
||||
KeySize128 KeySize = 16 // AES-128
|
||||
KeySize192 KeySize = 24 // AES-192
|
||||
KeySize256 KeySize = 32 // AES-256 (recommended)
|
||||
)
|
||||
|
||||
// IsValid returns true if the key size is valid for AES
|
||||
func (ks KeySize) IsValid() bool {
|
||||
switch ks {
|
||||
case KeySize128, KeySize192, KeySize256:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var ErrShortCipherText = errors.New("ciphertext too short")
|
||||
var ErrNoEncryptionKey = errors.New("encryption key is required")
|
||||
var ErrInvalidKeySize = errors.New("invalid key size: must be 16, 24, or 32 bytes")
|
||||
var ErrZeroKey = errors.New("encryption key cannot be all zeros")
|
||||
|
||||
// GenerateKey generates a random AES key of default size KeySize256 (32 bytes).
|
||||
// Returns an error if the specified size is invalid or if key generation fails.
|
||||
func GenerateKey() ([]byte, error) {
|
||||
return GenerateKeyWithSize(KeySize256)
|
||||
}
|
||||
|
||||
// GenerateKeyWithSize generates a random AES key of the specified size.
|
||||
// Returns an error if the specified size is invalid or if key generation fails.
|
||||
func GenerateKeyWithSize(size KeySize) ([]byte, error) {
|
||||
if !size.IsValid() {
|
||||
return nil, ErrInvalidKeySize
|
||||
}
|
||||
|
||||
key := make([]byte, size)
|
||||
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
||||
return nil, fmt.Errorf("failed to generate AES key: %w", err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// EncryptWithAESKey encrypts data using AES-GCM with the provided key.
|
||||
// The key must be 16, 24, or 32 bytes long (for AES-128, AES-192, or AES-256).
|
||||
// Returns the encrypted data with the nonce prepended, or an error if encryption fails.
|
||||
func EncryptWithAESKey(data, key []byte) ([]byte, error) {
|
||||
if err := validateAESKey(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gcm.Seal(nonce, nonce, data, nil), nil
|
||||
}
|
||||
|
||||
// DecryptStringWithAESKey decrypts data that was encrypted with EncryptWithAESKey.
|
||||
// The key must match the one used for encryption.
|
||||
// Expects the input to have a prepended nonce.
|
||||
// Returns the decrypted data or an error if decryption fails.
|
||||
func DecryptStringWithAESKey(data, key []byte) ([]byte, error) {
|
||||
if err := validateAESKey(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) < gcm.NonceSize() {
|
||||
return nil, ErrShortCipherText
|
||||
}
|
||||
|
||||
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
|
||||
decrypted, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decrypted, nil
|
||||
}
|
||||
|
||||
func validateAESKey(key []byte) error {
|
||||
if key == nil {
|
||||
return ErrNoEncryptionKey
|
||||
}
|
||||
|
||||
if !KeySize(len(key)).IsValid() {
|
||||
return ErrInvalidKeySize
|
||||
}
|
||||
|
||||
// check if key is all zeros
|
||||
for _, b := range key {
|
||||
if b != 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return ErrZeroKey
|
||||
}
|
||||
124
pkg/meta/internal/crypto/aes_test.go
Normal file
124
pkg/meta/internal/crypto/aes_test.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAESEncryption(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
key := make([]byte, 32) // generated random 32-byte key
|
||||
_, errKey := rand.Read(key)
|
||||
require.NoError(t, errKey)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
key []byte
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "valid encryption/decryption",
|
||||
data: []byte("hello world"),
|
||||
key: key,
|
||||
},
|
||||
{
|
||||
name: "nil key returns error",
|
||||
data: []byte("hello world"),
|
||||
key: nil,
|
||||
wantErr: ErrNoEncryptionKey,
|
||||
},
|
||||
{
|
||||
name: "empty data",
|
||||
data: []byte{},
|
||||
key: key,
|
||||
},
|
||||
{
|
||||
name: "invalid key size",
|
||||
data: []byte("hello world"),
|
||||
key: make([]byte, 31),
|
||||
wantErr: ErrInvalidKeySize,
|
||||
},
|
||||
{
|
||||
name: "zero key returns error",
|
||||
data: []byte("hello world"),
|
||||
key: make([]byte, 32),
|
||||
wantErr: ErrZeroKey,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
encrypted, err := EncryptWithAESKey(tt.data, tt.key)
|
||||
if tt.wantErr != nil {
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
decrypted, err := DecryptStringWithAESKey(encrypted, tt.key)
|
||||
require.NoError(t, err)
|
||||
|
||||
if tt.key == nil {
|
||||
require.Equal(t, tt.data, encrypted)
|
||||
require.Equal(t, tt.data, decrypted)
|
||||
} else {
|
||||
require.NotEqual(t, tt.data, encrypted)
|
||||
require.True(t, bytes.Equal(tt.data, decrypted))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptionErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
key := make([]byte, 32)
|
||||
_, err := rand.Read(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
key []byte
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "short ciphertext",
|
||||
data: []byte("short"),
|
||||
key: key,
|
||||
errMsg: "ciphertext too short",
|
||||
},
|
||||
{
|
||||
name: "invalid ciphertext",
|
||||
data: make([]byte, 16), // just nonce size
|
||||
key: key,
|
||||
errMsg: "message authentication failed",
|
||||
},
|
||||
{
|
||||
name: "missing key",
|
||||
data: []byte("<22>`M<><4D><EFBFBD>l\u001AIF<49>\u0012<31><32><EFBFBD>=h<>?<3F>c<EFBFBD> <20><>\u0012<31><32><EFBFBD><EFBFBD>\u001C<31>\u0018Ƽ(g"),
|
||||
key: nil,
|
||||
errMsg: "encryption key is required",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := DecryptStringWithAESKey(tt.data, tt.key)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.errMsg)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,22 @@
|
||||
package meta
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/printer"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/pkg/meta/internal/crypto"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||
)
|
||||
|
||||
var ErrNotFound = fmt.Errorf("key-value not found in meta")
|
||||
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
|
||||
@@ -50,6 +56,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.GetBytes(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
decrypted, err := crypto.DecryptStringWithAESKey(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.
|
||||
@@ -83,6 +104,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.
|
||||
@@ -112,6 +148,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
|
||||
}
|
||||
case []byte:
|
||||
encrypted, err = crypto.EncryptWithAESKey(val, encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return ErrNotEncryptable
|
||||
}
|
||||
|
||||
return m.Add(key, encrypted)
|
||||
}
|
||||
|
||||
// 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"
|
||||
@@ -13,10 +14,64 @@ 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.Error(t, err)
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
_, err = m.GetString("secret")
|
||||
require.Error(t, err) // the ciphertext is saved as []byte instead of string
|
||||
|
||||
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.Error(t, err)
|
||||
require.Contains(t, err.Error(), "encryption key is required")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ func (r ReadOnly) GetString(key string) (string, error) {
|
||||
return r.m.GetString(key)
|
||||
}
|
||||
|
||||
func (r ReadOnly) GetEncryptedString(key string, encryptionKey []byte) (string, error) {
|
||||
return r.m.GetEncryptedString(key, encryptionKey)
|
||||
}
|
||||
|
||||
func (r ReadOnly) GetInt64(key string) (int64, error) {
|
||||
return r.m.GetInt64(key)
|
||||
}
|
||||
@@ -29,6 +33,10 @@ func (r ReadOnly) GetBytes(key string) ([]byte, error) {
|
||||
return r.m.GetBytes(key)
|
||||
}
|
||||
|
||||
func (r ReadOnly) GetEncryptedBytes(key string, encryptionKey []byte) ([]byte, error) {
|
||||
return r.m.GetEncryptedBytes(key, encryptionKey)
|
||||
}
|
||||
|
||||
func (r ReadOnly) GetNode(key string) (ipld.Node, error) {
|
||||
return r.m.GetNode(key)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package delegation_test
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -66,6 +67,8 @@ const (
|
||||
|
||||
newCID = "zdpuAn9JgGPvnt2WCmTaKktZdbuvcVGTg9bUT5kQaufwUtZ6e"
|
||||
rootCID = "zdpuAkgGmUp5JrXvehGuuw9JA8DLQKDaxtK3R8brDQQVC2i5X"
|
||||
|
||||
aesKey = "xQklMmNTnVrmaPBq/0pwV5fEwuv/iClF5HWak9MsgI8="
|
||||
)
|
||||
|
||||
func TestConstructors(t *testing.T) {
|
||||
@@ -121,6 +124,109 @@ func TestConstructors(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestEncryptedMeta(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
privKey := privKey(t, issuerPrivKeyCfg)
|
||||
aud, err := did.Parse(AudienceDID)
|
||||
require.NoError(t, err)
|
||||
cmd, err := command.Parse(subJectCmd)
|
||||
require.NoError(t, err)
|
||||
pol, err := policy.FromDagJson(subjectPol)
|
||||
require.NoError(t, err)
|
||||
|
||||
encryptionKey, err := base64.StdEncoding.DecodeString(aesKey)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, encryptionKey, 32)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
value string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "simple string",
|
||||
key: "secret1",
|
||||
value: "hello world",
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
key: "secret2",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
name: "special characters",
|
||||
key: "secret3",
|
||||
value: "!@#$%^&*()_+-=[]{}|;:,.<>?",
|
||||
},
|
||||
{
|
||||
name: "unicode characters",
|
||||
key: "secret4",
|
||||
value: "Hello, 世界! 👋",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tkn, err := delegation.New(privKey, aud, cmd, pol,
|
||||
delegation.WithEncryptedMetaString(tt.key, tt.value, encryptionKey),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := tkn.ToDagCbor(privKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedTkn, _, err := delegation.FromSealed(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = decodedTkn.Meta().GetString(tt.key)
|
||||
require.Error(t, err)
|
||||
|
||||
decrypted, err := decodedTkn.Meta().GetEncryptedString(tt.key, encryptionKey)
|
||||
require.NoError(t, err)
|
||||
// Verify the decrypted value is equal to the original
|
||||
require.Equal(t, tt.value, decrypted)
|
||||
|
||||
// Try to decrypt with wrong key
|
||||
wrongKey := make([]byte, 32)
|
||||
_, err = decodedTkn.Meta().GetEncryptedString(tt.key, wrongKey)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("multiple encrypted values in the same token", func(t *testing.T) {
|
||||
values := map[string]string{
|
||||
"secret1": "value1",
|
||||
"secret2": "value2",
|
||||
"secret3": "value3",
|
||||
}
|
||||
var opts []delegation.Option
|
||||
for k, v := range values {
|
||||
opts = append(opts, delegation.WithEncryptedMetaString(k, v, encryptionKey))
|
||||
}
|
||||
|
||||
// Create token with multiple encrypted values
|
||||
tkn, err := delegation.New(privKey, aud, cmd, pol, opts...)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := tkn.ToDagCbor(privKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedTkn, _, err := delegation.FromSealed(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
for k, v := range values {
|
||||
decrypted, err := decodedTkn.Meta().GetEncryptedString(k, encryptionKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, v, decrypted)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func privKey(t require.TestingT, privKeyCfg string) crypto.PrivKey {
|
||||
privKeyMar, err := crypto.ConfigDecodeKey(privKeyCfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -44,6 +44,22 @@ func WithMeta(key string, val any) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithEncryptedMetaString adds a key/value pair in the "meta" field.
|
||||
// The string value is encrypted with the given aesKey.
|
||||
func WithEncryptedMetaString(key, val string, encryptionKey []byte) Option {
|
||||
return func(t *Token) error {
|
||||
return t.meta.AddEncrypted(key, val, encryptionKey)
|
||||
}
|
||||
}
|
||||
|
||||
// WithEncryptedMetaBytes adds a key/value pair in the "meta" field.
|
||||
// The []byte value is encrypted with the given aesKey.
|
||||
func WithEncryptedMetaBytes(key string, val, encryptionKey []byte) Option {
|
||||
return func(t *Token) error {
|
||||
return t.meta.AddEncrypted(key, val, encryptionKey)
|
||||
}
|
||||
}
|
||||
|
||||
// WithNotBefore set's the Token's optional "notBefore" field to the value
|
||||
// of the provided time.Time.
|
||||
func WithNotBefore(nbf time.Time) Option {
|
||||
|
||||
@@ -44,6 +44,22 @@ func WithMeta(key string, val any) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithEncryptedMetaString adds a key/value pair in the "meta" field.
|
||||
// The string value is encrypted with the given aesKey.
|
||||
func WithEncryptedMetaString(key, val string, encryptionKey []byte) Option {
|
||||
return func(t *Token) error {
|
||||
return t.meta.AddEncrypted(key, val, encryptionKey)
|
||||
}
|
||||
}
|
||||
|
||||
// WithEncryptedMetaBytes adds a key/value pair in the "meta" field.
|
||||
// The []byte value is encrypted with the given aesKey.
|
||||
func WithEncryptedMetaBytes(key string, val, encryptionKey []byte) Option {
|
||||
return func(t *Token) error {
|
||||
return t.meta.AddEncrypted(key, val, encryptionKey)
|
||||
}
|
||||
}
|
||||
|
||||
// WithNonce sets the Token's nonce with the given value.
|
||||
//
|
||||
// If this option is not used, a random 12-byte nonce is generated for
|
||||
|
||||
Reference in New Issue
Block a user