Skip to content

feat(core): support for ec-wrapping #499

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions .github/spellcheck.ignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,28 @@ ConditionGroup
CoolTool
Decrypt
ERS
FQNs
FQN
FQN'd
FQNs
Hostname
JSON
JWT
KASes
KASs
LDAP
MacOS
mapping's
Nano
NanoTDF
NPM
Namespace
namespace's
Nano
NanoTDF
OIDC
OpenTDF
OpenID
OpenTDF
PDP
PKCE
plaintext
pluggability
README
RadService
README
SCS
SCSs
SDK
Expand Down Expand Up @@ -63,6 +60,7 @@ decrypt
decryptable
decrypted
dev
ec
ecdsa
encodings
enum
Expand All @@ -79,6 +77,7 @@ keychain
keycloak
keyring
localhost
mapping's
namespace
namespaces
nano
Expand All @@ -88,31 +87,34 @@ ns
ocl
otdfctl
performant
plaintext
pluggability
poc
pubkey
quickstart
resm
resolvers
rsa
scs
secp
sel
sm
stanag
stdin
stdout
stdout
subcommand
subcs
subm
submap
tdo
tdf
tdf-type
tdo
tls
tls-no-verify
txt
un-mapped
unassign
unassignment
un-mapped
upsert
uri
with-client-creds
Expand Down
22 changes: 21 additions & 1 deletion cmd/tdf-decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -24,6 +25,20 @@ func dev_tdfDecryptCmd(cmd *cobra.Command, args []string) {

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()
Expand All @@ -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)
}
Expand Down Expand Up @@ -90,6 +105,11 @@ func init() {
"",
decryptCmd.GetDocFlag("with-assertion-verification-keys").Description,
)
decryptCmd.Flags().String(
decryptCmd.GetDocFlag("session-key-algorithm").Name,
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(),
Expand Down
26 changes: 23 additions & 3 deletions cmd/tdf-encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -152,12 +167,17 @@ func init() {
encryptCmd.GetDocFlag("mime-type").Default,
encryptCmd.GetDocFlag("mime-type").Description,
)
encryptCmd.Flags().StringP(
encryptCmd.Flags().String(
encryptCmd.GetDocFlag("tdf-type").Name,
encryptCmd.GetDocFlag("tdf-type").Shorthand,
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,
Expand Down
21 changes: 21 additions & 0 deletions docs/man/decrypt/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ command:
- name: no-verify-assertions
description: disable verification of assertions
default: false
- name: session-key-algorithm
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.
Expand Down Expand Up @@ -44,6 +52,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 session key. The available options are (default: rsa:2048):
- 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.
Expand Down
21 changes: 21 additions & 0 deletions docs/man/encrypt/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ 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
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
Expand Down Expand Up @@ -64,6 +72,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 (default: rsa:2048):
- 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
Expand Down
8 changes: 8 additions & 0 deletions e2e/encrypt-decrypt.bats
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ setup_file() {
export INFILE_GO_MOD=go.mod
export OUTFILE_GO_MOD=go.mod.tdf
export RESULTFILE_GO_MOD=result.mod
export SESSION_KEY_ALGORITHM=ec:secp256r1
export WRAPPING_KEY_ALGORITHM=ec:secp256r1

export SECRET_TEXT="my special secret"
export OUT_TXT=secret.txt
Expand Down Expand Up @@ -66,6 +68,12 @@ teardown_file(){
diff $INFILE_GO_MOD $RESULTFILE_GO_MOD
}

@test "roundtrip TDF3, no attributes, ec-wrapping, file" {
./otdfctl encrypt -o $OUTFILE_GO_MOD --host $HOST --tls-no-verify $DEBUG_LEVEL $WITH_CREDS --tdf-type tdf3 --wrapping-key-algorithm $WRAPPING_KEY_ALGORITHM $INFILE_GO_MOD
./otdfctl decrypt -o $RESULTFILE_GO_MOD --host $HOST --tls-no-verify $DEBUG_LEVEL $WITH_CREDS --tdf-type tdf3 --session-key-algorithm $SESSION_KEY_ALGORITHM $OUTFILE_GO_MOD
diff $INFILE_GO_MOD $RESULTFILE_GO_MOD
}

@test "roundtrip TDF3, one attribute, stdin" {
echo $SECRET_TEXT | ./otdfctl encrypt -o $OUT_TXT --host $HOST --tls-no-verify $DEBUG_LEVEL $WITH_CREDS -a $FQN
./otdfctl decrypt --host $HOST --tls-no-verify $DEBUG_LEVEL $WITH_CREDS $OUTFILE_TXT | grep "$SECRET_TEXT"
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
19 changes: 16 additions & 3 deletions pkg/handlers/tdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"

"github.com/opentdf/otdfctl/pkg/utils"
"github.com/opentdf/platform/lib/ocrypto"
"github.com/opentdf/platform/sdk"
)

Expand All @@ -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)

Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -125,7 +136,9 @@ 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
Expand Down
Loading