diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d326a1..34e67c52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Redact `/unredact` endpoint support - `redaction_method_overrides` field support in `/redact` and `redact_structured` endpoints - AuthN usernames support. +- Support for format-preserving encryption. ### Removed diff --git a/go.work.sum b/go.work.sum index 57872ea0..b1d6f9db 100644 --- a/go.work.sum +++ b/go.work.sum @@ -5,17 +5,9 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/pangea-sdk/v3/service/vault/api.go b/pangea-sdk/v3/service/vault/api.go index 2ff2aa70..e1fc86d0 100644 --- a/pangea-sdk/v3/service/vault/api.go +++ b/pangea-sdk/v3/service/vault/api.go @@ -620,3 +620,45 @@ func (v *vault) EncryptStructured(ctx context.Context, input *EncryptStructuredR func (v *vault) DecryptStructured(ctx context.Context, input *EncryptStructuredRequest) (*pangea.PangeaResponse[EncryptStructuredResult], error) { return request.DoPost(ctx, v.Client, "v1/key/decrypt/structured", input, &EncryptStructuredResult{}) } + +// @summary Encrypt transform +// +// @description Encrypt using a format-preserving algorithm (FPE). +// +// @operationId vault_post_v1_key_encrypt_transform +// +// @example +// +// encryptedResponse, err := client.EncryptTransform( +// ctx, +// &vault.EncryptTransformRequest{ +// ID: "pvi_[...]", +// PlainText: "123-4567-8901", +// Tweak: "MTIzMTIzMT==", +// Alphabet: vault.TAalphanumeric, +// }, +// ) +func (v *vault) EncryptTransform(ctx context.Context, input *EncryptTransformRequest) (*pangea.PangeaResponse[EncryptTransformResult], error) { + return request.DoPost(ctx, v.Client, "v1/key/encrypt/transform", input, &EncryptTransformResult{}) +} + +// @summary Decrypt transform +// +// @description Decrypt using a format-preserving algorithm (FPE). +// +// @operationId vault_post_v1_key_decrypt_transform +// +// @example +// +// decryptedResponse, err := client.DecryptTransform( +// ctx, +// &vault.DecryptTransformRequest{ +// ID: "pvi_[...]", +// CipherText: "tZB-UKVP-MzTM", +// Tweak: "MTIzMTIzMT==", +// Alphabet: vault.TAalphanumeric, +// }, +// ) +func (v *vault) DecryptTransform(ctx context.Context, input *DecryptTransformRequest) (*pangea.PangeaResponse[DecryptTransformResult], error) { + return request.DoPost(ctx, v.Client, "v1/key/decrypt/transform", input, &DecryptTransformResult{}) +} diff --git a/pangea-sdk/v3/service/vault/common.go b/pangea-sdk/v3/service/vault/common.go index aae1210b..00c6f22f 100644 --- a/pangea-sdk/v3/service/vault/common.go +++ b/pangea-sdk/v3/service/vault/common.go @@ -19,6 +19,7 @@ const ( KPsigning KeyPurpose = "signing" KPencryption KeyPurpose = "encryption" KPjwt KeyPurpose = "jwt" + KPfpe KeyPurpose = "fpe" // Format-preserving encryption. ) type AsymmetricAlgorithm string @@ -65,15 +66,17 @@ const ( type SymmetricAlgorithm string const ( - SYAhs256 SymmetricAlgorithm = "HS256" - SYAhs384 SymmetricAlgorithm = "HS384" - SYAhs512 SymmetricAlgorithm = "HS512" - SYAaes128_cfb SymmetricAlgorithm = "AES-CFB-128" - SYAaes256_cfb SymmetricAlgorithm = "AES-CFB-256" - SYAaes256_gcm SymmetricAlgorithm = "AES-GCM-256" - SYAaes128_cbc SymmetricAlgorithm = "AES-CBC-128" - SYAaes256_cbc SymmetricAlgorithm = "AES-CBC-256" - SYAaes SymmetricAlgorithm = "AES-CFB-128" // deprecated, use SYAaes128_cfb instead + SYAhs256 SymmetricAlgorithm = "HS256" + SYAhs384 SymmetricAlgorithm = "HS384" + SYAhs512 SymmetricAlgorithm = "HS512" + SYAaes128_cfb SymmetricAlgorithm = "AES-CFB-128" + SYAaes256_cfb SymmetricAlgorithm = "AES-CFB-256" + SYAaes256_gcm SymmetricAlgorithm = "AES-GCM-256" + SYAaes128_cbc SymmetricAlgorithm = "AES-CBC-128" + SYAaes256_cbc SymmetricAlgorithm = "AES-CBC-256" + SYAaes SymmetricAlgorithm = "AES-CFB-128" // deprecated, use SYAaes128_cfb instead + SYAaes_ff3_1_128 SymmetricAlgorithm = "AES-FF3-1-128-BETA" // 128-bit encryption using the FF3-1 algorithm. Beta feature. + SYAaes_ff3_1_256 SymmetricAlgorithm = "AES-FF3-1-256-BETA" // 256-bit encryption using the FF3-1 algorithm. Beta feature. ) type ItemVersionState string diff --git a/pangea-sdk/v3/service/vault/integration_test.go b/pangea-sdk/v3/service/vault/integration_test.go index eb05a9e9..ba5b4552 100644 --- a/pangea-sdk/v3/service/vault/integration_test.go +++ b/pangea-sdk/v3/service/vault/integration_test.go @@ -1196,3 +1196,63 @@ func Test_Integration_EncryptStructured(t *testing.T) { assert.Len(t, decryptedResponse.Result.StructuredData["field1"], 4) assert.Equal(t, data["field2"], decryptedResponse.Result.StructuredData["field2"]) } + +func Test_Integration_EncryptTransform(t *testing.T) { + // Test data. + plainText := "123-4567-8901" + tweak := "MTIzMTIzMT==" + + ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second) + defer cancelFn() + + client := vault.New(pangeatesting.IntegrationConfig(t, testingEnvironment)) + + // Generate an encryption key. + rGen, err := client.SymmetricGenerate( + ctx, + &vault.SymmetricGenerateRequest{ + CommonGenerateRequest: vault.CommonGenerateRequest{ + Name: GetName("Test_Integration_EncryptTransform"), + }, + Algorithm: vault.SYAaes_ff3_1_256, + Purpose: vault.KPfpe, + }, + ) + assert.NoError(t, err) + assert.NotNil(t, rGen) + assert.NotNil(t, rGen.Result) + assert.NotEmpty(t, rGen.Result.ID) + key := rGen.Result.ID + + // Encrypt. + encryptedResponse, err := client.EncryptTransform( + ctx, + &vault.EncryptTransformRequest{ + ID: key, + PlainText: plainText, + Tweak: &tweak, + Alphabet: vault.TAalphanumeric, + }, + ) + assert.NoError(t, err) + assert.NotNil(t, encryptedResponse) + assert.NotNil(t, encryptedResponse.Result) + assert.Equal(t, key, encryptedResponse.Result.ID) + assert.Len(t, encryptedResponse.Result.CipherText, len(plainText)) + + // Decrypt what we encrypted. + decryptedResponse, err := client.DecryptTransform( + ctx, + &vault.DecryptTransformRequest{ + ID: key, + CipherText: encryptedResponse.Result.CipherText, + Tweak: tweak, + Alphabet: vault.TAalphanumeric, + }, + ) + assert.NoError(t, err) + assert.NotNil(t, decryptedResponse) + assert.NotNil(t, decryptedResponse.Result) + assert.Equal(t, key, decryptedResponse.Result.ID) + assert.Equal(t, plainText, decryptedResponse.Result.PlainText) +} diff --git a/pangea-sdk/v3/service/vault/service.go b/pangea-sdk/v3/service/vault/service.go index e60edd89..9803ad6b 100644 --- a/pangea-sdk/v3/service/vault/service.go +++ b/pangea-sdk/v3/service/vault/service.go @@ -36,6 +36,12 @@ type Client interface { // Decrypt parts of a JSON object. DecryptStructured(ctx context.Context, input *EncryptStructuredRequest) (*pangea.PangeaResponse[EncryptStructuredResult], error) + // Encrypt using a format-preserving algorithm (FPE). + EncryptTransform(ctx context.Context, input *EncryptTransformRequest) (*pangea.PangeaResponse[EncryptTransformResult], error) + + // Decrypt using a format-preserving algorithm (FPE). + DecryptTransform(ctx context.Context, input *DecryptTransformRequest) (*pangea.PangeaResponse[DecryptTransformResult], error) + // Base service methods pangea.BaseServicer } diff --git a/pangea-sdk/v3/service/vault/symmetric.go b/pangea-sdk/v3/service/vault/symmetric.go index 1fcd1315..423d4fbf 100644 --- a/pangea-sdk/v3/service/vault/symmetric.go +++ b/pangea-sdk/v3/service/vault/symmetric.go @@ -88,7 +88,7 @@ type EncryptStructuredResult struct { // The ID of the item. ID string `json:"id"` - // The item version. + // The item version. Version int `json:"version"` // The algorithm of the key. @@ -97,3 +97,96 @@ type EncryptStructuredResult struct { // Structured data with filtered fields encrypted/decrypted. StructuredData map[string]interface{} `json:"structured_data"` } + +type TransformAlphabet string + +const ( + TAalphalower TransformAlphabet = "alphalower" // Lowercase alphabet (a-z). + TAalphanumeric TransformAlphabet = "alphanumeric" // Alphanumeric (a-z, A-Z, 0-9). + TAalphanumericlower TransformAlphabet = "alphanumericlower" // Lowercase alphabet with numbers (a-z, 0-9). + TAalphanumericupper TransformAlphabet = "alphanumericupper" // Uppercase alphabet with numbers (A-Z, 0-9). + TAalphaupper TransformAlphabet = "alphaupper" // Uppercase alphabet (A-Z). + TAnumeric TransformAlphabet = "numeric" // Numeric (0-9). +) + +// Parameters for an encrypt transform request. +type EncryptTransformRequest struct { + pangea.BaseRequest + + // The ID of the key to use. + ID string `json:"id"` + + // Message to be encrypted. + PlainText string `json:"plain_text"` + + // Set of characters to use for format-preserving encryption (FPE). + Alphabet TransformAlphabet `json:"alphabet"` + + // User provided tweak string. If not provided, a random string will be + // generated and returned. The user must securely store the tweak source + // which will be needed to decrypt the data. + Tweak *string `json:"tweak,omitempty"` + + // The item version. Defaults to the current version. + Version *int `json:"version,omitempty"` +} + +// Result of an encrypt transform request. +type EncryptTransformResult struct { + // The item ID. + ID string `json:"id"` + + // The encrypted message. + CipherText string `json:"cipher_text"` + + // The item version. + Version int `json:"version"` + + // The algorithm of the key. + Algorithm string `json:"algorithm"` + + // User provided tweak string. If not provided, a random string will be + // generated and returned. The user must securely store the tweak source + // which will be needed to decrypt the data. + Tweak string `json:"tweak"` + + // Set of characters to use for format-preserving encryption (FPE). + Alphabet TransformAlphabet `json:"alphabet"` +} + +// Parameters for a decrypt transform request. +type DecryptTransformRequest struct { + pangea.BaseRequest + + // The ID of the key to use. + ID string `json:"id"` + + // A message encrypted by Vault. + CipherText string `json:"cipher_text"` + + // User provided tweak string. If not provided, a random string will be + // generated and returned. The user must securely store the tweak source + // which will be needed to decrypt the data. + Tweak string `json:"tweak"` + + // Set of characters to use for format-preserving encryption (FPE). + Alphabet TransformAlphabet `json:"alphabet"` + + // The item version. Defaults to the current version. + Version *int `json:"version,omitempty"` +} + +// Result of a decrypt transform request. +type DecryptTransformResult struct { + // The item ID. + ID string `json:"id"` + + // Decrypted message. + PlainText string `json:"plain_text"` + + // The item version. + Version int `json:"version"` + + // The algorithm of the key. + Algorithm string `json:"algorithm"` +}