From 7ec8462bafdef0f3fd04f2de5393244fd9860df8 Mon Sep 17 00:00:00 2001 From: sujan kota Date: Sat, 1 Mar 2025 05:02:10 -0500 Subject: [PATCH 1/5] feat(core): support for ec-wrapping --- cmd/tdf-decrypt.go | 25 +++++++++++++++++++++++-- cmd/tdf-encrypt.go | 23 ++++++++++++++++++++++- docs/man/decrypt/_index.md | 22 ++++++++++++++++++++++ docs/man/encrypt/_index.md | 22 ++++++++++++++++++++++ go.mod | 6 +++--- pkg/handlers/tdf.go | 18 +++++++++++++++--- 6 files changed, 107 insertions(+), 9 deletions(-) diff --git a/cmd/tdf-decrypt.go b/cmd/tdf-decrypt.go index 7651cdf4..ebbf7c4c 100644 --- a/cmd/tdf-decrypt.go +++ b/cmd/tdf-decrypt.go @@ -8,6 +8,7 @@ import ( "github.com/opentdf/otdfctl/pkg/cli" "github.com/opentdf/otdfctl/pkg/man" "github.com/opentdf/otdfctl/pkg/utils" + "github.com/opentdf/platform/lib/ocrypto" "github.com/spf13/cobra" ) @@ -18,12 +19,26 @@ var assertionVerification string const TDF_MAX_FILE_SIZE = int64(10 * 1024 * 1024 * 1024) // 10 GB func dev_tdfDecryptCmd(cmd *cobra.Command, args []string) { - c := cli.New(cmd, args, cli.WithPrintJson()) + c := cli.New(cmd, args) h := NewHandler(c) defer h.Close() output := c.Flags.GetOptionalString("out") disableAssertionVerification := c.Flags.GetOptionalBool("no-verify-assertions") + sessionKeyAlgStr := c.Flags.GetOptionalString("session-key-algorithm") + var sessionKeyAlgorithm ocrypto.KeyType + switch sessionKeyAlgStr { + case "rsa:2048": + sessionKeyAlgorithm = "rsa:2048" + case "ec:secp256r1": + sessionKeyAlgorithm = "ec:secp256r1" + case "ec:secp384r1": + sessionKeyAlgorithm = "ec:secp384r1" + case "ec:secp521r1": + sessionKeyAlgorithm = "ec:secp521r1" + default: + sessionKeyAlgorithm = "rsa:2048" + } // check for piped input piped := readPipedStdin() @@ -44,7 +59,7 @@ func dev_tdfDecryptCmd(cmd *cobra.Command, args []string) { cli.ExitWithError("Must provide ONE of the following to decrypt: [file argument, stdin input]", errors.New("no input provided")) } - decrypted, err := h.DecryptBytes(bytesToDecrypt, assertionVerification, disableAssertionVerification) + decrypted, err := h.DecryptBytes(bytesToDecrypt, assertionVerification, disableAssertionVerification, sessionKeyAlgorithm) if err != nil { cli.ExitWithError("Failed to decrypt file", err) } @@ -90,6 +105,12 @@ func init() { "", decryptCmd.GetDocFlag("with-assertion-verification-keys").Description, ) + decryptCmd.Flags().StringP( + decryptCmd.GetDocFlag("session-key-algorithm").Name, + decryptCmd.GetDocFlag("session-key-algorithm").Shorthand, + decryptCmd.GetDocFlag("session-key-algorithm").Default, + decryptCmd.GetDocFlag("session-key-algorithm").Description, + ) decryptCmd.Flags().Bool( decryptCmd.GetDocFlag("no-verify-assertions").Name, decryptCmd.GetDocFlag("no-verify-assertions").DefaultAsBool(), diff --git a/cmd/tdf-encrypt.go b/cmd/tdf-encrypt.go index 9b8a027f..942e6600 100644 --- a/cmd/tdf-encrypt.go +++ b/cmd/tdf-encrypt.go @@ -12,6 +12,7 @@ import ( "github.com/opentdf/otdfctl/pkg/cli" "github.com/opentdf/otdfctl/pkg/man" "github.com/opentdf/otdfctl/pkg/utils" + "github.com/opentdf/platform/lib/ocrypto" "github.com/spf13/cobra" ) @@ -44,6 +45,20 @@ func dev_tdfEncryptCmd(cmd *cobra.Command, args []string) { attrValues = c.Flags.GetStringSlice("attr", attrValues, cli.FlagsStringSliceOptions{Min: 0}) tdfType := c.Flags.GetOptionalString("tdf-type") kasURLPath := c.Flags.GetOptionalString("kas-url-path") + wrappingKeyAlgStr := c.Flags.GetOptionalString("wrapping-key-algorithm") + var wrappingKeyAlgorithm ocrypto.KeyType + switch wrappingKeyAlgStr { + case "rsa:2048": + wrappingKeyAlgorithm = "rsa:2048" + case "ec:secp256r1": + wrappingKeyAlgorithm = "ec:secp256r1" + case "ec:secp384r1": + wrappingKeyAlgorithm = "ec:secp384r1" + case "ec:secp521r1": + wrappingKeyAlgorithm = "ec:secp521r1" + default: + wrappingKeyAlgorithm = "rsa:2048" + } piped := readPipedStdin() @@ -95,7 +110,7 @@ func dev_tdfEncryptCmd(cmd *cobra.Command, args []string) { ) // Do the encryption - encrypted, err := h.EncryptBytes(tdfType, bytesSlice, attrValues, fileMimeType, kasURLPath, c.Flags.GetOptionalBool("ecdsa-binding"), assertions) + encrypted, err := h.EncryptBytes(tdfType, bytesSlice, attrValues, fileMimeType, kasURLPath, c.Flags.GetOptionalBool("ecdsa-binding"), assertions, wrappingKeyAlgorithm) if err != nil { cli.ExitWithError("Failed to encrypt", err) } @@ -158,6 +173,12 @@ func init() { encryptCmd.GetDocFlag("tdf-type").Default, encryptCmd.GetDocFlag("tdf-type").Description, ) + encryptCmd.Flags().StringP( + encryptCmd.GetDocFlag("wrapping-key-algorithm").Name, + encryptCmd.GetDocFlag("wrapping-key-algorithm").Shorthand, + encryptCmd.GetDocFlag("wrapping-key-algorithm").Default, + encryptCmd.GetDocFlag("wrapping-key-algorithm").Description, + ) encryptCmd.Flags().Bool( encryptCmd.GetDocFlag("ecdsa-binding").Name, false, diff --git a/docs/man/decrypt/_index.md b/docs/man/decrypt/_index.md index 682d53a5..4632c446 100644 --- a/docs/man/decrypt/_index.md +++ b/docs/man/decrypt/_index.md @@ -13,6 +13,15 @@ command: - name: no-verify-assertions description: disable verification of assertions default: false + - name: session-key-algorithm + shorthand: s + description: The type of session key algorithm to use for decryption + enum: + - rsa:2048 + - ec:secp256r1 + - ec:secp384r1 + - ec:secp521r1 + default: rsa:2048 - name: with-assertion-verification-keys description: > EXPERIMENTAL: path to JSON file of keys to verify signed assertions. See examples for more information. @@ -44,6 +53,19 @@ $ echo "hello world" | otdfctl encrypt | otdfctl decrypt | cat hello world ``` +## Session Key Algorithm +The session-key-algorithm specifies the algorithm to use for the wrapping key. The available options are: +- rsa:2048 +- ec:secp256r1 +- ec:secp384r1 +- ec:secp521r1 + +Example +```shell +# Decrypt a file using the ec:secp256r1 algorithm for the session key +otdfctl decrypt hello.txt --session-key-algorithm ec:secp256r1 +``` + ### ZTDF Assertion Verification (experimental) To verify the signed assertions (metadata bound to the TDF), you can provide verification keys. The supported assertion signing algorithms are HS256 and RS256 so the keys provided should either be an HS256 key or a public RS256 key. diff --git a/docs/man/encrypt/_index.md b/docs/man/encrypt/_index.md index 88b09f67..93671944 100644 --- a/docs/man/encrypt/_index.md +++ b/docs/man/encrypt/_index.md @@ -10,6 +10,15 @@ command: - name: attr shorthand: a description: Attribute value Fully Qualified Names (FQNs, i.e. 'https://example.com/attr/attr1/value/value1') to apply to the encrypted data. + - name: wrapping-key-algorithm + shorthand: w + description: The algorithm to use for the wrapping key + enum: + - rsa:2048 + - ec:secp256r1 + - ec:secp384r1 + - ec:secp521r1 + default: rsa:2048 - name: mime-type description: The MIME type of the input data. If not provided, the MIME type is inferred from the input data. - name: tdf-type @@ -64,6 +73,19 @@ $ echo "hello world" | otdfctl encrypt | otdfctl decrypt | cat hello world ``` +## Wrapping Key Algorithm +The wrapping-key-algorithm specifies the algorithm to use for the wrapping key. The available options are: +- rsa:2048 +- ec:secp256r1 +- ec:secp384r1 +- ec:secp521r1 + +Example +```shell +# Encrypt a file using the ec:secp256r1 algorithm for the wrapping key +otdfctl encrypt hello.txt --wrapping-key-algorithm ec:secp256r1 --out hello.txt.tdf +``` + ## Attributes Attributes can be added to the encrypted data. The attribute value is a Fully Qualified Name (FQN) that is used to diff --git a/go.mod b/go.mod index 98e3c549..e0374d64 100644 --- a/go.mod +++ b/go.mod @@ -16,8 +16,9 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/uuid v1.6.0 github.com/opentdf/platform/lib/flattening v0.1.3 - github.com/opentdf/platform/protocol/go v0.2.27 - github.com/opentdf/platform/sdk v0.3.27 + github.com/opentdf/platform/lib/ocrypto v0.1.8 + github.com/opentdf/platform/protocol/go v0.2.28 + github.com/opentdf/platform/sdk v0.3.28 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 @@ -79,7 +80,6 @@ require ( github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect github.com/muhlemmer/gu v0.3.1 // indirect - github.com/opentdf/platform/lib/ocrypto v0.1.8 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect diff --git a/pkg/handlers/tdf.go b/pkg/handlers/tdf.go index d141ea85..b7e9c198 100644 --- a/pkg/handlers/tdf.go +++ b/pkg/handlers/tdf.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/opentdf/otdfctl/pkg/utils" + "github.com/opentdf/platform/lib/ocrypto" "github.com/opentdf/platform/sdk" ) @@ -38,7 +39,16 @@ type TDFInspect struct { UnencryptedMetadata []byte } -func (h Handler) EncryptBytes(tdfType string, unencrypted []byte, attrValues []string, mimeType string, kasUrlPath string, ecdsaBinding bool, assertions string) (*bytes.Buffer, error) { +func (h Handler) EncryptBytes( + tdfType string, + unencrypted []byte, + attrValues []string, + mimeType string, + kasUrlPath string, + ecdsaBinding bool, + assertions string, + wrappingKeyAlgorithm ocrypto.KeyType, +) (*bytes.Buffer, error) { var encrypted []byte enc := bytes.NewBuffer(encrypted) @@ -55,6 +65,7 @@ func (h Handler) EncryptBytes(tdfType string, unencrypted []byte, attrValues []s URL: h.platformEndpoint + kasUrlPath, }), sdk.WithMimeType(mimeType), + sdk.WithWrappingKeyAlg(wrappingKeyAlgorithm), } var assertionConfigs []sdk.AssertionConfig @@ -115,7 +126,7 @@ func (h Handler) EncryptBytes(tdfType string, unencrypted []byte, attrValues []s } } -func (h Handler) DecryptBytes(toDecrypt []byte, assertionVerificationKeysFile string, disableAssertionCheck bool) (*bytes.Buffer, error) { +func (h Handler) DecryptBytes(toDecrypt []byte, assertionVerificationKeysFile string, disableAssertionCheck bool, sessionKeyAlgorithm ocrypto.KeyType) (*bytes.Buffer, error) { out := &bytes.Buffer{} pt := io.Writer(out) ec := bytes.NewReader(toDecrypt) @@ -125,7 +136,8 @@ func (h Handler) DecryptBytes(toDecrypt []byte, assertionVerificationKeysFile st return nil, err } case sdk.Standard: - opts := []sdk.TDFReaderOption{sdk.WithDisableAssertionVerification(disableAssertionCheck)} + opts := []sdk.TDFReaderOption{sdk.WithDisableAssertionVerification(disableAssertionCheck), + sdk.WithSessionKeyType(sessionKeyAlgorithm)} var assertionVerificationKeys sdk.AssertionVerificationKeys if assertionVerificationKeysFile != "" { // read the file From b49944a1a76bd5efdd83c0701817ebd8e8d0c1aa Mon Sep 17 00:00:00 2001 From: sujan kota Date: Sat, 1 Mar 2025 05:11:59 -0500 Subject: [PATCH 2/5] fix the build --- go.sum | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/go.sum b/go.sum index e68bd720..f36c89bb 100644 --- a/go.sum +++ b/go.sum @@ -228,10 +228,10 @@ github.com/opentdf/platform/lib/flattening v0.1.3 h1:IuOm/wJVXNrzOV676Ticgr0wyBk github.com/opentdf/platform/lib/flattening v0.1.3/go.mod h1:Gs/T+6FGZKk9OAdz2Jf1R8CTGeNRYrq1lZGDeYT3hrY= github.com/opentdf/platform/lib/ocrypto v0.1.8 h1:FUKMHsVCjU4NmgaXgS1RFstl19tkX/7USTIubAuUBlA= github.com/opentdf/platform/lib/ocrypto v0.1.8/go.mod h1:UTtqh8mvhAYA+sEnaMxpr/406e84L5Q1sAxtKGIXfu4= -github.com/opentdf/platform/protocol/go v0.2.27 h1:ZnfXvVio+j/LzfEY8cHo8/tS45XAPWa2xO7Y1tn/hWs= -github.com/opentdf/platform/protocol/go v0.2.27/go.mod h1:eldxqX2oF2ADtG8ivhfwn1lALVMX4aaUM+Lp9ynOJXs= -github.com/opentdf/platform/sdk v0.3.27 h1:O9jCdpnxz3FEaTXj/hAOixR5mk/APsalcWCexGxfwkM= -github.com/opentdf/platform/sdk v0.3.27/go.mod h1:ZJyz6hy0CMiD3MFfG4PrByTnSJnEtArTGA6ZoR1Xg6E= +github.com/opentdf/platform/protocol/go v0.2.28 h1:UfX+yFWFGCtxsvCyIO62p4v7CBtcVR+2dCqHq3O0vy4= +github.com/opentdf/platform/protocol/go v0.2.28/go.mod h1:eldxqX2oF2ADtG8ivhfwn1lALVMX4aaUM+Lp9ynOJXs= +github.com/opentdf/platform/sdk v0.3.28 h1:rdfZCc5nLXDoljZEDin312T0K/RbNO5mpu1GlMTi8to= +github.com/opentdf/platform/sdk v0.3.28/go.mod h1:25xOGtsBPqbj8VDbn5yN5i6D/9fXNy/WDAM1NSPIXcY= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= From cefcb66ff93db0c496e663d9f54cc0a56984f53e Mon Sep 17 00:00:00 2001 From: sujan kota Date: Sat, 1 Mar 2025 05:19:26 -0500 Subject: [PATCH 3/5] fix the lint errors --- cmd/tdf-decrypt.go | 25 ++++++++++++++++--------- cmd/tdf-encrypt.go | 18 +++++++++--------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/cmd/tdf-decrypt.go b/cmd/tdf-decrypt.go index ebbf7c4c..655375ed 100644 --- a/cmd/tdf-decrypt.go +++ b/cmd/tdf-decrypt.go @@ -12,6 +12,13 @@ import ( "github.com/spf13/cobra" ) +const ( + RSA2048 = "rsa:2048" + ECSECP256R1 = "ec:secp256r1" + ECSECP384R1 = "ec:secp384r1" + ECSECP521R1 = "ec:secp521r1" +) + var TDF = "tdf" var assertionVerification string @@ -28,16 +35,16 @@ func dev_tdfDecryptCmd(cmd *cobra.Command, args []string) { sessionKeyAlgStr := c.Flags.GetOptionalString("session-key-algorithm") var sessionKeyAlgorithm ocrypto.KeyType switch sessionKeyAlgStr { - case "rsa:2048": - sessionKeyAlgorithm = "rsa:2048" - case "ec:secp256r1": - sessionKeyAlgorithm = "ec:secp256r1" - case "ec:secp384r1": - sessionKeyAlgorithm = "ec:secp384r1" - case "ec:secp521r1": - sessionKeyAlgorithm = "ec:secp521r1" + case RSA2048: + sessionKeyAlgorithm = RSA2048 + case ECSECP256R1: + sessionKeyAlgorithm = ECSECP256R1 + case ECSECP384R1: + sessionKeyAlgorithm = ECSECP384R1 + case ECSECP521R1: + sessionKeyAlgorithm = ECSECP521R1 default: - sessionKeyAlgorithm = "rsa:2048" + sessionKeyAlgorithm = RSA2048 } // check for piped input diff --git a/cmd/tdf-encrypt.go b/cmd/tdf-encrypt.go index 942e6600..c3cfe169 100644 --- a/cmd/tdf-encrypt.go +++ b/cmd/tdf-encrypt.go @@ -48,16 +48,16 @@ func dev_tdfEncryptCmd(cmd *cobra.Command, args []string) { wrappingKeyAlgStr := c.Flags.GetOptionalString("wrapping-key-algorithm") var wrappingKeyAlgorithm ocrypto.KeyType switch wrappingKeyAlgStr { - case "rsa:2048": - wrappingKeyAlgorithm = "rsa:2048" - case "ec:secp256r1": - wrappingKeyAlgorithm = "ec:secp256r1" - case "ec:secp384r1": - wrappingKeyAlgorithm = "ec:secp384r1" - case "ec:secp521r1": - wrappingKeyAlgorithm = "ec:secp521r1" + case RSA2048: + wrappingKeyAlgorithm = RSA2048 + case ECSECP256R1: + wrappingKeyAlgorithm = ECSECP256R1 + case ECSECP384R1: + wrappingKeyAlgorithm = ECSECP384R1 + case ECSECP521R1: + wrappingKeyAlgorithm = ECSECP521R1 default: - wrappingKeyAlgorithm = "rsa:2048" + wrappingKeyAlgorithm = RSA2048 } piped := readPipedStdin() From 572f819eb13ea41f289edb38b11cfccbc48fe174 Mon Sep 17 00:00:00 2001 From: sujan kota Date: Sat, 1 Mar 2025 05:22:57 -0500 Subject: [PATCH 4/5] fix spellcheck --- .github/spellcheck.ignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/spellcheck.ignore b/.github/spellcheck.ignore index 2e9949ad..517f1273 100644 --- a/.github/spellcheck.ignore +++ b/.github/spellcheck.ignore @@ -118,4 +118,7 @@ uri with-client-creds with-client-creds-file yaml -ztdf \ No newline at end of file +ztdf +rsa +ec +secp \ No newline at end of file From a312242d04e0694cb314d5af7ca4461e556b6e45 Mon Sep 17 00:00:00 2001 From: sujan kota Date: Mon, 3 Mar 2025 12:15:28 -0500 Subject: [PATCH 5/5] code cleanup --- cmd/tdf-decrypt.go | 25 +++++++++---------------- cmd/tdf-encrypt.go | 18 +++++++++--------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/cmd/tdf-decrypt.go b/cmd/tdf-decrypt.go index 655375ed..e62e95df 100644 --- a/cmd/tdf-decrypt.go +++ b/cmd/tdf-decrypt.go @@ -12,13 +12,6 @@ import ( "github.com/spf13/cobra" ) -const ( - RSA2048 = "rsa:2048" - ECSECP256R1 = "ec:secp256r1" - ECSECP384R1 = "ec:secp384r1" - ECSECP521R1 = "ec:secp521r1" -) - var TDF = "tdf" var assertionVerification string @@ -35,16 +28,16 @@ func dev_tdfDecryptCmd(cmd *cobra.Command, args []string) { sessionKeyAlgStr := c.Flags.GetOptionalString("session-key-algorithm") var sessionKeyAlgorithm ocrypto.KeyType switch sessionKeyAlgStr { - case RSA2048: - sessionKeyAlgorithm = RSA2048 - case ECSECP256R1: - sessionKeyAlgorithm = ECSECP256R1 - case ECSECP384R1: - sessionKeyAlgorithm = ECSECP384R1 - case ECSECP521R1: - sessionKeyAlgorithm = ECSECP521R1 + case string(ocrypto.RSA2048Key): + sessionKeyAlgorithm = ocrypto.RSA2048Key + case string(ocrypto.EC256Key): + sessionKeyAlgorithm = ocrypto.EC256Key + case string(ocrypto.EC384Key): + sessionKeyAlgorithm = ocrypto.EC384Key + case string(ocrypto.EC521Key): + sessionKeyAlgorithm = ocrypto.EC521Key default: - sessionKeyAlgorithm = RSA2048 + sessionKeyAlgorithm = ocrypto.RSA2048Key } // check for piped input diff --git a/cmd/tdf-encrypt.go b/cmd/tdf-encrypt.go index c3cfe169..0faf1512 100644 --- a/cmd/tdf-encrypt.go +++ b/cmd/tdf-encrypt.go @@ -48,16 +48,16 @@ func dev_tdfEncryptCmd(cmd *cobra.Command, args []string) { wrappingKeyAlgStr := c.Flags.GetOptionalString("wrapping-key-algorithm") var wrappingKeyAlgorithm ocrypto.KeyType switch wrappingKeyAlgStr { - case RSA2048: - wrappingKeyAlgorithm = RSA2048 - case ECSECP256R1: - wrappingKeyAlgorithm = ECSECP256R1 - case ECSECP384R1: - wrappingKeyAlgorithm = ECSECP384R1 - case ECSECP521R1: - wrappingKeyAlgorithm = ECSECP521R1 + case string(ocrypto.RSA2048Key): + wrappingKeyAlgorithm = ocrypto.RSA2048Key + case string(ocrypto.EC256Key): + wrappingKeyAlgorithm = ocrypto.EC256Key + case string(ocrypto.EC384Key): + wrappingKeyAlgorithm = ocrypto.EC384Key + case string(ocrypto.EC521Key): + wrappingKeyAlgorithm = ocrypto.EC521Key default: - wrappingKeyAlgorithm = RSA2048 + wrappingKeyAlgorithm = ocrypto.RSA2048Key } piped := readPipedStdin()