Skip to content
Draft
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
8 changes: 6 additions & 2 deletions otdfctl/cmd/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func InitProfile(c *cli.Cli) *profiles.OtdfctlProfileStore {
// TODO make this a preRun hook
//
//nolint:nestif // separate refactor [https://github.com/opentdf/otdfctl/issues/383]
func NewHandler(c *cli.Cli) handlers.Handler {
func NewHandler(c *cli.Cli, extraSDKOpts ...sdk.Option) handlers.Handler {
// if global flags are set then validate and create a temporary profile in memory
var cp *profiles.OtdfctlProfileStore

Expand Down Expand Up @@ -209,7 +209,11 @@ func NewHandler(c *cli.Cli) handlers.Handler {
cli.ExitWithError("Failed to get access token.", err)
}

h, err := handlers.New(handlers.WithProfile(cp))
handlerFuncs := []handlers.HandlerOptsFunc{handlers.WithProfile(cp)}
if len(extraSDKOpts) > 0 {
handlerFuncs = append(handlerFuncs, handlers.WithExtraSDKOpts(extraSDKOpts...))
}
h, err := handlers.New(handlerFuncs...)
if err != nil {
cli.ExitWithError("Unexpected error", err)
}
Expand Down
14 changes: 13 additions & 1 deletion otdfctl/cmd/tdf/decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var (

func decryptRun(cmd *cobra.Command, args []string) {
c := cli.New(cmd, args, cli.WithPrintJSON())
h := common.NewHandler(c)
h := common.NewHandler(c, dpopSDKOpts(c)...)
defer h.Close()

output := c.Flags.GetOptionalString("out")
Expand Down Expand Up @@ -133,6 +133,18 @@ func InitDecryptCommand() {
nil,
decryptDoc.GetDocFlag("kas-allowlist").Description,
)
decryptDoc.Flags().String(
decryptDoc.GetDocFlag("dpop").Name,
decryptDoc.GetDocFlag("dpop").Default,
decryptDoc.GetDocFlag("dpop").Description,
)
// NoOptDefVal enables bare --dpop (without =value) to default to ES256.
decryptDoc.Flags().Lookup(decryptDoc.GetDocFlag("dpop").Name).NoOptDefVal = "ES256"
decryptDoc.Flags().String(
decryptDoc.GetDocFlag("dpop-key").Name,
decryptDoc.GetDocFlag("dpop-key").Default,
decryptDoc.GetDocFlag("dpop-key").Description,
)

decryptDoc.GroupID = TDF
}
30 changes: 30 additions & 0 deletions otdfctl/cmd/tdf/dpop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package tdf

import (
"os"

"github.com/opentdf/platform/otdfctl/pkg/cli"
"github.com/opentdf/platform/sdk"
)

// dpopSDKOpts reads --dpop and --dpop-key flags from the CLI and returns the
// corresponding SDK options. Returns an empty slice when DPoP is not configured.
func dpopSDKOpts(c *cli.Cli) []sdk.Option {
dpopAlg := c.Flags.GetOptionalString("dpop")
dpopKeyPath := c.Flags.GetOptionalString("dpop-key")

var opts []sdk.Option
if dpopKeyPath != "" {
pemBytes, err := os.ReadFile(dpopKeyPath)
if err != nil {
cli.ExitWithError("Failed to read DPoP key file", err)
}
opts = append(opts, sdk.WithDPoPKeyPEM(pemBytes))
if dpopAlg != "" {
opts = append(opts, sdk.WithDPoPAlgorithm(dpopAlg))
}
} else if dpopAlg != "" {
opts = append(opts, sdk.WithDPoPAlgorithm(dpopAlg))
}
return opts
}
14 changes: 13 additions & 1 deletion otdfctl/cmd/tdf/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var (

func encryptRun(cmd *cobra.Command, args []string) {
c := cli.New(cmd, args, cli.WithPrintJSON())
h := common.NewHandler(c)
h := common.NewHandler(c, dpopSDKOpts(c)...)
defer h.Close()

var filePath string
Expand Down Expand Up @@ -191,5 +191,17 @@ func InitEncryptCommand() {
encryptDoc.GetDocFlag("target-mode").Default,
encryptDoc.GetDocFlag("target-mode").Description,
)
encryptDoc.Flags().String(
encryptDoc.GetDocFlag("dpop").Name,
encryptDoc.GetDocFlag("dpop").Default,
encryptDoc.GetDocFlag("dpop").Description,
)
// NoOptDefVal enables bare --dpop (without =value) to default to ES256.
encryptDoc.Flags().Lookup(encryptDoc.GetDocFlag("dpop").Name).NoOptDefVal = "ES256"
encryptDoc.Flags().String(
encryptDoc.GetDocFlag("dpop-key").Name,
encryptDoc.GetDocFlag("dpop-key").Default,
encryptDoc.GetDocFlag("dpop-key").Description,
)
encryptDoc.GroupID = TDF
}
10 changes: 10 additions & 0 deletions otdfctl/docs/man/decrypt/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ command:
EXPERIMENTAL: path to JSON file of keys to verify signed assertions. See examples for more information.
- name: kas-allowlist
description: A custom allowlist of comma-separated KAS Urls, e.g. `https://example.com/kas,http://localhost:8080`. If none specified, the platform will use the list of KASes in the KAS registry. To ignore the allowlist, use a quoted wildcard e.g. `--kas-allowlist '*'` **WARNING:** Bypassing the allowlist may expose you to potential security risks, as untrusted KAS URLs could be used.
- name: dpop
description: >
Enable DPoP (RFC 9449) sender-constrained tokens. Use bare --dpop for ES256 (default), or
--dpop=<alg> for a specific algorithm. Allowed algorithms: ES256, ES384, ES512, RS256, RS384, RS512.
An ephemeral key is generated per session. Combines with --dpop-key to override inferred algorithm.
- name: dpop-key
description: >
Path to a PEM-encoded private key for DPoP. Enables DPoP without requiring --dpop.
Algorithm is inferred from the key type (EC → ES256/384/512, RSA → RS256).
Use --dpop=<alg> to override the inferred algorithm.
---

Decrypt a Trusted Data Format (TDF) file and output the contents to stdout or a file in the current working directory.
Expand Down
10 changes: 10 additions & 0 deletions otdfctl/docs/man/encrypt/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ command:
- name: with-assertions
description: >
EXPERIMENTAL: JSON string or path to a JSON file of assertions to bind metadata to the TDF. See examples for more information. WARNING: Providing keys in a JSON string is strongly discouraged. If including sensitive keys, instead provide a path to a JSON file containing that information.
- name: dpop
description: >
Enable DPoP (RFC 9449) sender-constrained tokens. Use bare --dpop for ES256 (default), or
--dpop=<alg> for a specific algorithm. Allowed algorithms: ES256, ES384, ES512, RS256, RS384, RS512.
An ephemeral key is generated per session. Combines with --dpop-key to override inferred algorithm.
- name: dpop-key
description: >
Path to a PEM-encoded private key for DPoP. Enables DPoP without requiring --dpop.
Algorithm is inferred from the key type (EC → ES256/384/512, RSA → RS256).
Use --dpop=<alg> to override the inferred algorithm.
---

Build a Trusted Data Format (TDF) with encrypted content from a specified file or input from stdin utilizing OpenTDF platform.
Expand Down
18 changes: 13 additions & 5 deletions otdfctl/pkg/handlers/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ type handlerOpts struct {
sdkOpts []sdk.Option
}

type handlerOptsFunc func(handlerOpts) handlerOpts
type HandlerOptsFunc func(handlerOpts) handlerOpts

func WithEndpoint(endpoint string, tlsNoVerify bool) handlerOptsFunc {
func WithEndpoint(endpoint string, tlsNoVerify bool) HandlerOptsFunc {
return func(c handlerOpts) handlerOpts {
c.endpoint = endpoint
c.TLSNoVerify = tlsNoVerify
return c
}
}

func WithProfile(profile *profiles.OtdfctlProfileStore) handlerOptsFunc {
func WithProfile(profile *profiles.OtdfctlProfileStore) HandlerOptsFunc {
return func(c handlerOpts) handlerOpts {
c.profile = profile
c.endpoint = profile.GetEndpoint()
Expand All @@ -58,15 +58,23 @@ func WithProfile(profile *profiles.OtdfctlProfileStore) handlerOptsFunc {
}
}

func WithSDKOpts(opts ...sdk.Option) handlerOptsFunc {
func WithSDKOpts(opts ...sdk.Option) HandlerOptsFunc {
return func(c handlerOpts) handlerOpts {
c.sdkOpts = opts
return c
}
}

// WithExtraSDKOpts appends additional SDK options without replacing those set by WithProfile.
func WithExtraSDKOpts(opts ...sdk.Option) HandlerOptsFunc {
return func(c handlerOpts) handlerOpts {
c.sdkOpts = append(c.sdkOpts, opts...)
return c
}
}

// Creates a new handler wrapping the SDK, which is authenticated through the cached client-credentials flow tokens
func New(opts ...handlerOptsFunc) (Handler, error) {
func New(opts ...HandlerOptsFunc) (Handler, error) {
var o handlerOpts
for _, f := range opts {
o = f(o)
Expand Down
Loading
Loading