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 diff --git a/cmd/tdf-decrypt.go b/cmd/tdf-decrypt.go index 7651cdf4..e62e95df 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 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 = ocrypto.RSA2048Key + } // 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..0faf1512 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 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 = ocrypto.RSA2048Key + } 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/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= 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