diff --git a/.github/spellcheck.ignore b/.github/spellcheck.ignore index 95aaf8bc..bd302959 100644 --- a/.github/spellcheck.ignore +++ b/.github/spellcheck.ignore @@ -22,6 +22,7 @@ NPM Namespace namespace's Nano +NanoTDF OIDC OpenTDF OpenID @@ -108,8 +109,11 @@ uri with-client-creds with-client-creds-file yaml +ZTDF +ztdf tdo appliesToState stanag nato -ocl \ No newline at end of file +ocl +cryptographically \ No newline at end of file diff --git a/cmd/tdf-decrypt.go b/cmd/tdf-decrypt.go index e940471d..70d65661 100644 --- a/cmd/tdf-decrypt.go +++ b/cmd/tdf-decrypt.go @@ -1,7 +1,6 @@ package cmd import ( - "bytes" "errors" "fmt" "os" @@ -14,16 +13,12 @@ import ( var TDF = "tdf" func dev_tdfDecryptCmd(cmd *cobra.Command, args []string) { - c := cli.New(cmd, args) + c := cli.New(cmd, args, cli.WithPrintJson()) h := NewHandler(c) defer h.Close() output := c.Flags.GetOptionalString("out") - tdfType := c.Flags.GetOptionalString("tdf-type") disableAssertionVerification := c.Flags.GetOptionalBool("no-verify-assertions") - if tdfType == "" { - tdfType = TDF3 - } // check for piped input piped := readPipedStdin() @@ -40,16 +35,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")) } - var decrypted *bytes.Buffer - var err error - switch tdfType { - case TDF3: - decrypted, err = h.DecryptTDF(bytesToDecrypt, disableAssertionVerification) - case NANO: - decrypted, err = h.DecryptNanoTDF(bytesToDecrypt) - default: - cli.ExitWithError("Failed to decrypt", fmt.Errorf("unrecognized tdf-type: %s", tdfType)) - } + decrypted, err := h.DecryptBytes(bytesToDecrypt, disableAssertionVerification) if err != nil { cli.ExitWithError("Failed to decrypt file", err) } @@ -81,6 +67,7 @@ func init() { decryptCmd.GetDocFlag("out").Default, decryptCmd.GetDocFlag("out").Description, ) + // deprecated flag decryptCmd.Flags().StringP( decryptCmd.GetDocFlag("tdf-type").Name, decryptCmd.GetDocFlag("tdf-type").Shorthand, diff --git a/cmd/tdf-encrypt.go b/cmd/tdf-encrypt.go index 66eac70d..aa9e20ae 100644 --- a/cmd/tdf-encrypt.go +++ b/cmd/tdf-encrypt.go @@ -1,7 +1,6 @@ package cmd import ( - "bytes" "fmt" "io" "log/slog" @@ -16,16 +15,17 @@ import ( ) const ( - TDF3 = "tdf3" - NANO = "nano" - Size_1MB = 1024 * 1024 + TDFTYPE_ZTDF = "ztdf" + TDF3 = "tdf3" + NANO = "nano" + Size_1MB = 1024 * 1024 ) var attrValues []string var assertions string func dev_tdfEncryptCmd(cmd *cobra.Command, args []string) { - c := cli.New(cmd, args) + c := cli.New(cmd, args, cli.WithPrintJson()) h := NewHandler(c) defer h.Close() @@ -40,9 +40,6 @@ func dev_tdfEncryptCmd(cmd *cobra.Command, args []string) { fileMimeType := c.Flags.GetOptionalString("mime-type") attrValues = c.Flags.GetStringSlice("attr", attrValues, cli.FlagsStringSliceOptions{Min: 0}) tdfType := c.Flags.GetOptionalString("tdf-type") - if tdfType == "" { - tdfType = TDF3 - } kasURLPath := c.Flags.GetOptionalString("kas-url-path") piped := readPipedStdin() @@ -91,17 +88,7 @@ func dev_tdfEncryptCmd(cmd *cobra.Command, args []string) { ) // Do the encryption - var encrypted *bytes.Buffer - var err error - switch tdfType { - case TDF3: - encrypted, err = h.EncryptBytes(bytesSlice, attrValues, fileMimeType, kasURLPath, assertions) - case NANO: - ecdsaBinding := c.Flags.GetOptionalBool("ecdsa-binding") - encrypted, err = h.EncryptNanoBytes(bytesSlice, attrValues, kasURLPath, ecdsaBinding) - default: - cli.ExitWithError("Failed to encrypt", fmt.Errorf("unrecognized tdf-type: %s", tdfType)) - } + encrypted, err := h.EncryptBytes(tdfType, bytesSlice, attrValues, fileMimeType, kasURLPath, c.Flags.GetOptionalBool("ecdsa-binding"), assertions) if err != nil { cli.ExitWithError("Failed to encrypt", err) } diff --git a/docs/man/auth/_index.md b/docs/man/auth/_index.md index 858ee028..909ba22f 100644 --- a/docs/man/auth/_index.md +++ b/docs/man/auth/_index.md @@ -5,4 +5,8 @@ command: name: auth --- -This command will allow you to manage your local authentication session with the OpenTDF platform. \ No newline at end of file +> [!NOTE] +> Requires experimental profiles feature. (Linux not yet supported. Windows is brittle.) + +The auth commands facilitate the process of authenticating the user with the system using profiles to store the +credentials. diff --git a/docs/man/auth/clear-client-credentials.md b/docs/man/auth/clear-client-credentials.md index 43d32b1d..d24b0ae4 100644 --- a/docs/man/auth/clear-client-credentials.md +++ b/docs/man/auth/clear-client-credentials.md @@ -9,4 +9,5 @@ command: default: false --- -This command has been deprecated. Use the `profile` subcommand to manage profiles and credentials. +> [!WARNING] +> Deprecated. Use the `profile` subcommand to manage profiles and credentials. diff --git a/docs/man/auth/client-credentials.md b/docs/man/auth/client-credentials.md index 47ccedd7..97f97e20 100644 --- a/docs/man/auth/client-credentials.md +++ b/docs/man/auth/client-credentials.md @@ -9,5 +9,28 @@ command: - client-secret --- +> [!NOTE] +> Requires experimental profiles feature. +> +> | OS | Keychain | State | +> | --- | --- | --- | +> | MacOS | Keychain | Stable | +> | Windows | Credential Manager | Alpha | +> | Linux | Secret Service | Not yet supported | + Allows the user to login in via Client Credentials flow. The client credentials will be stored safely in the OS keyring for future use. + +## Examples + +Authenticate with client credentials (secret provided interactively) + +```shell +opentdf auth client-credentials --client-id +``` + +Authenticate with client credentials (secret provided as argument) + +```shell +opentdf auth client-credentials --client-id --client-secret +``` diff --git a/docs/man/auth/login.md b/docs/man/auth/login.md index 7e5ba257..db134900 100644 --- a/docs/man/auth/login.md +++ b/docs/man/auth/login.md @@ -10,6 +10,15 @@ command: required: false --- +> [!NOTE] +> Requires experimental profiles feature. +> +> | OS | Keychain | State | +> | --- | --- | --- | +> | MacOS | Keychain | Stable | +> | Windows | Credential Manager | Alpha | +> | Linux | Secret Service | Not yet supported | + Authenticate for use of the OpenTDF Platform through a browser (required). Provide a specific public 'client-id' known to support the Auth Code PKCE flow and recognized diff --git a/docs/man/auth/logout.md b/docs/man/auth/logout.md index b295f3ab..f92e2a68 100644 --- a/docs/man/auth/logout.md +++ b/docs/man/auth/logout.md @@ -5,5 +5,15 @@ command: name: logout --- + +> [!NOTE] +> Requires experimental profiles feature. +> +> | OS | Keychain | State | +> | --- | --- | --- | +> | MacOS | Keychain | Stable | +> | Windows | Credential Manager | Alpha | +> | Linux | Secret Service | Not yet supported | + Removes any auth credentials (Client Credentials or an Access Token from a login) from the current profile. diff --git a/docs/man/auth/print-access-token.md b/docs/man/auth/print-access-token.md index 376f97c9..50774446 100644 --- a/docs/man/auth/print-access-token.md +++ b/docs/man/auth/print-access-token.md @@ -9,4 +9,13 @@ command: default: false --- -Retrieves a new OIDC Access Token using the client credentials from the OS-specific keychain and prints to stdout if found. +> [!NOTE] +> Requires experimental profiles feature. +> +> | OS | Keychain | State | +> | --- | --- | --- | +> | MacOS | Keychain | Stable | +> | Windows | Credential Manager | Alpha | +> | Linux | Secret Service | Not yet supported | + +Retrieves a new OIDC Access Token using the client credentials and prints to stdout if found. diff --git a/docs/man/decrypt/_index.md b/docs/man/decrypt/_index.md index 02efd114..35ed235b 100644 --- a/docs/man/decrypt/_index.md +++ b/docs/man/decrypt/_index.md @@ -9,11 +9,7 @@ command: default: '' - name: tdf-type shorthand: t - description: The type of tdf to decrypt as - enum: - - tdf3 - - nano - default: tdf3 + description: Deprecated. TDF type is now auto-detected. - name: no-verify-assertions description: disable verification of assertions default: false @@ -23,14 +19,24 @@ Decrypt a Trusted Data Format (TDF) file and output the contents to stdout or a The first argument is the TDF file with path from the current working directory being decrypted. -## Examples: +## Examples -```bash -# specify the TDF to decrypt then output decrypted contents -otdfctl decrypt hello.txt.tdf # write to stdout -otdfctl decrypt hello.txt.tdf > hello.txt # consume stdout to write to hello.txt file -otdfctl decrypt hello.txt.tdf -o hello.txt # write to hello.txt file instead of stdout +Various ways to decrypt a TDF file -# pipe the TDF to decrypt -cat hello.txt.tdf | otdfctl decrypt > hello.txt +```shell +# decrypt file and write to standard output +otdfctl decrypt hello.txt.tdf + +# decrypt file and write to hello.txt file +otdfctl decrypt hello.txt.tdf -o hello.txt + +# decrypt piped TDF content and write to hello.txt file +cat hello.txt.tdf | otdfctl decrypt -o hello.txt +``` + +Advanced piping is supported + +```shell +$ echo "hello world" | otdfctl encrypt | otdfctl decrypt | cat +hello world ``` diff --git a/docs/man/encrypt/_index.md b/docs/man/encrypt/_index.md index e8203ab5..2b6c76b8 100644 --- a/docs/man/encrypt/_index.md +++ b/docs/man/encrypt/_index.md @@ -14,11 +14,12 @@ command: description: The MIME type of the input data. If not provided, the MIME type is inferred from the input data. - name: tdf-type shorthand: t - description: The type of tdf to encrypt as. TDF3 supports structured manifests and larger payloads. Nano has a smaller footprint and more performant, but does not support structured manifests or large payloads. + description: The type of tdf to encrypt as. ZTDF supports structured manifests and larger payloads. NanoTDF has a smaller footprint and more performant, but does not support structured manifests or large payloads. (tdf3 is an alias for ztdf) enum: + - ztdf - tdf3 - nano - default: tdf3 + default: ztdf - name: ecdsa-binding description: For nano type containers only, enables ECDSA policy binding - name: kas-url-path @@ -26,22 +27,71 @@ command: default: /kas - name: with-assertions description: > - EXPERIMENTAL: JSON string containing list of assertions to be applied during encryption. example - '[{"id":"assertion1","type":"handling","scope":"tdo","appliesToState":"encrypted","statement":{"format":"json+stanag5636","schema":"urn:nato:stanag:5636:A:1:elements:json","value":"{\"ocl\":\"2024-10-21T20:47:36Z\"}"}}]' + EXPERIMENTAL: JSON string of assertions to bind metadata to the TDF. See examples for more information. --- Build a Trusted Data Format (TDF) with encrypted content from a specified file or input from stdin utilizing OpenTDF platform. -## Examples: +## Examples -```bash +Various ways to encrypt a file + +```shell # output to stdout -echo "some text" | otdfctl encrypt otdfctl encrypt hello.txt -# pipe stdout to a bucket -echo "my secret" | otdfctl encrypt | aws s3 cp - s3://my-bucket/secret.txt.tdf -# output hello.txt.tdf in root directory -echo "hello world" | otdfctl encrypt -o hello.txt -cat hello.txt | otdfctl encrypt -o hello.txt -cat hello.txt | otdfctl encrypt -o hello.txt.tdf #.tdf extension is only added once +# output to hello.txt.tdf +otdfctl encrypt hello.txt --out hello.txt.tdf + +# encrypt piped content and write to hello.txt.tdf +cat hello.txt | otdfctl encrypt --out hello.txt.tdf +``` + +Automatically append .tdf to the output file name + +```shell +$ cat hello.txt | otdfctl encrypt --out hello.txt; ls +hello.txt hello.txt.tdf + +$ cat hello.txt | otdfctl encrypt --out hello.txt.tdf; ls +hello.txt hello.txt.tdf +``` + +Advanced piping is supported + +```shell +$ echo "hello world" | otdfctl encrypt | otdfctl decrypt | cat +hello world +``` + +## Attributes + +Attributes can be added to the encrypted data. The attribute value is a Fully Qualified Name (FQN) that is used to +restrict access to the data based on entity entitlements. + +```shell +# output to hello.txt.tdf with attribute +otdfctl encrypt hello.txt --out hello.txt.tdf --attr https://example.com/attr/attr1/value/value1 +``` + +## NanoTDF + +NanoTDF is a lightweight TDF format that is more performant and has a smaller footprint than ZTDF. NanoTDF does not +support structured manifests or large payloads. + +```shell +# output to nano.tdf +otdfctl encrypt hello.txt --tdf-type nano --out hello.txt.tdf +``` + +## ZTDF Assertions (experimental) + +Assertions are a way to bind metadata to the TDF data object in a cryptographically secure way. + +### STANAG 5636 + +The following example demonstrates how to bind a STANAG 5636 metadata assertion to the TDF data object. + +```shell +otdfctl encrypt hello.txt --out hello.txt.tdf --with-assertions '[{"id":"assertion1","type":"handling","scope":"tdo","appliesToState":"encrypted","statement":{"format":"json+stanag5636","schema":"urn:nato:stanag:5636:A:1:elements:json","value":"{\"ocl\":\"2024-10-21T20:47:36Z\"}"}}]' ``` diff --git a/go.mod b/go.mod index 936c0b12..7a8fee32 100644 --- a/go.mod +++ b/go.mod @@ -16,8 +16,8 @@ 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.1 - github.com/opentdf/platform/protocol/go v0.2.18 - github.com/opentdf/platform/sdk v0.3.17 + github.com/opentdf/platform/protocol/go v0.2.20 + github.com/opentdf/platform/sdk v0.3.18 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 diff --git a/go.sum b/go.sum index 3676750a..e24f1b18 100644 --- a/go.sum +++ b/go.sum @@ -223,8 +223,12 @@ github.com/opentdf/platform/lib/ocrypto v0.1.6 h1:rd4ctCZOE/c3qDJORtkSK9tw6dEXb+ github.com/opentdf/platform/lib/ocrypto v0.1.6/go.mod h1:ne+l8Q922OdzA0xesK3XJmfECBnn5vLSGYU3/3OhiHM= github.com/opentdf/platform/protocol/go v0.2.18 h1:s+TVZkOPGCzy7WyObtJWJNaFeOGDUTuSmAsq3omvugY= github.com/opentdf/platform/protocol/go v0.2.18/go.mod h1:WqDcnFQJb0v8ivRQPidbehcL8ils5ZSZYXkuv0nyvsI= +github.com/opentdf/platform/protocol/go v0.2.20 h1:FPU1ZcXvPm/QeE2nqgbD/HMTOCICQSD0DoncQbAZ1ws= +github.com/opentdf/platform/protocol/go v0.2.20/go.mod h1:TWIuf387VeR3q0TL4nAMKQTWEqqID+8Yjao76EX9Dto= github.com/opentdf/platform/sdk v0.3.17 h1:Uo/kTMneB18i0gZNfTRtvw34bGLFUc8BEnA/BMK0VVs= github.com/opentdf/platform/sdk v0.3.17/go.mod h1:c2+nrsRLvLf2OOryXnNy0iGZN/TScc21Pul7uqKVXIs= +github.com/opentdf/platform/sdk v0.3.18 h1:IY6fNrOfQD9lF/hZp9ewZsH0PMuLe17HlSE1A5kyIWc= +github.com/opentdf/platform/sdk v0.3.18/go.mod h1:u+XZhVRsMq5blukCFCHcjk6HLCp4Y5mmIQu7GhtKQ3E= 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/nano-tdf.go b/pkg/handlers/nano-tdf.go deleted file mode 100644 index 8f6b2203..00000000 --- a/pkg/handlers/nano-tdf.go +++ /dev/null @@ -1,44 +0,0 @@ -package handlers - -import ( - "bytes" - "io" -) - -func (h Handler) EncryptNanoBytes(b []byte, values []string, kasUrlPath string, ecdsaBinding bool) (*bytes.Buffer, error) { - var encrypted []byte - enc := bytes.NewBuffer(encrypted) - - nanoTDFConfig, err := h.sdk.NewNanoTDFConfig() - if err != nil { - return nil, err - } - - err = nanoTDFConfig.SetKasURL(h.platformEndpoint + kasUrlPath) - if err != nil { - return nil, err - } - err = nanoTDFConfig.SetAttributes(values) - if err != nil { - return nil, err - } - if ecdsaBinding { - nanoTDFConfig.EnableECDSAPolicyBinding() - } - - // TODO: validate values are FQNs or return an error [https://github.com/opentdf/platform/issues/515] - _, err = h.sdk.CreateNanoTDF(enc, bytes.NewReader(b), *nanoTDFConfig) - if err != nil { - return nil, err - } - return enc, nil -} - -func (h Handler) DecryptNanoTDF(toDecrypt []byte) (*bytes.Buffer, error) { - outBuf := bytes.Buffer{} - _, err := h.sdk.ReadNanoTDF(io.Writer(&outBuf), bytes.NewReader(toDecrypt)) - if err != nil { - return nil, err - } - return &outBuf, nil -} diff --git a/pkg/handlers/tdf.go b/pkg/handlers/tdf.go index e533d4bd..f6250f72 100644 --- a/pkg/handlers/tdf.go +++ b/pkg/handlers/tdf.go @@ -17,115 +17,156 @@ var ( ErrTDFUnableToReadAttributes = errors.New("unable to read attributes from TDF") ErrTDFUnableToReadUnencryptedMetadata = errors.New("unable to read unencrypted metadata from TDF") ErrTDFUnableToReadAssertions = errors.New("unable to read assertions") - minBytesLength = 3 ) -func (h Handler) EncryptBytes(b []byte, values []string, mimeType string, kasUrlPath string, assertions string) (*bytes.Buffer, error) { +const ( + TDF_TYPE_ZTDF = "ztdf" + TDF_TYPE_TDF3 = "tdf3" // alias for TDF + TDF_TYPE_NANO = "nano" +) + +type TDFInspect struct { + NanoHeader *sdk.NanoTDFHeader + ZTDFManifest *sdk.Manifest + Attributes []string + UnencryptedMetadata []byte +} + +func (h Handler) EncryptBytes(tdfType string, b []byte, values []string, mimeType string, kasUrlPath string, ecdsaBinding bool, assertions string) (*bytes.Buffer, error) { var encrypted []byte enc := bytes.NewBuffer(encrypted) - var assertionConfigs []sdk.AssertionConfig - if assertions != "" { - err := json.Unmarshal([]byte(assertions), &assertionConfigs) - if err != nil { - return nil, errors.Join(ErrTDFUnableToReadAssertions, err) + switch tdfType { + // Encrypt the data as a ZTDF + case "", TDF_TYPE_TDF3, TDF_TYPE_ZTDF: + if ecdsaBinding { + return nil, errors.New("ECDSA policy binding is not supported for ZTDF") } - } - // TODO: validate values are FQNs or return an error [https://github.com/opentdf/platform/issues/515] - _, err := h.sdk.CreateTDF(enc, bytes.NewReader(b), - sdk.WithDataAttributes(values...), - sdk.WithKasInformation(sdk.KASInfo{ - URL: h.platformEndpoint + kasUrlPath, - }), - sdk.WithAssertions(assertionConfigs...), - sdk.WithMimeType(mimeType), - ) - if err != nil { - return nil, err - } - return enc, nil -} + opts := []sdk.TDFOption{ + sdk.WithDataAttributes(values...), + sdk.WithKasInformation(sdk.KASInfo{ + URL: h.platformEndpoint + kasUrlPath, + }), + sdk.WithMimeType(mimeType), + } -func (h Handler) DecryptTDF(toDecrypt []byte, disableAssertionVerification bool) (*bytes.Buffer, error) { - tdfreader, err := h.sdk.LoadTDF(bytes.NewReader(toDecrypt), - sdk.WithDisableAssertionVerification(disableAssertionVerification), - ) - if err != nil { - return nil, err - } + var assertionConfigs []sdk.AssertionConfig + if assertions != "" { + err := json.Unmarshal([]byte(assertions), &assertionConfigs) + if err != nil { + return nil, errors.Join(ErrTDFUnableToReadAssertions, err) + } + opts = append(opts, sdk.WithAssertions(assertionConfigs...)) + } - buf := new(bytes.Buffer) - _, err = io.Copy(buf, tdfreader) - //nolint:errorlint // callers intended to test error equality directly - if err != nil && err != io.EOF { - return nil, err - } - return buf, nil -} + _, err := h.sdk.CreateTDF(enc, bytes.NewReader(b), opts...) + return enc, err -type TDFInspect struct { - NanoHeader *sdk.NanoTDFHeader - ZTDFManifest *sdk.Manifest - Attributes []string - UnencryptedMetadata []byte + // Encrypt the data as a Nano TDF + case TDF_TYPE_NANO: + nanoTDFConfig, err := h.sdk.NewNanoTDFConfig() + if err != nil { + return nil, err + } + // set the KAS URL + if err = nanoTDFConfig.SetKasURL(h.platformEndpoint + kasUrlPath); err != nil { + return nil, err + } + // set the attributes + if err = nanoTDFConfig.SetAttributes(values); err != nil { + return nil, err + } + // enable ECDSA policy binding + if ecdsaBinding { + nanoTDFConfig.EnableECDSAPolicyBinding() + } + // create the nano TDF + if _, err = h.sdk.CreateNanoTDF(enc, bytes.NewReader(b), *nanoTDFConfig); err != nil { + return nil, err + } + return enc, nil + default: + return nil, errors.New("unknown TDF type") + } } -func (h Handler) InspectTDF(toInspect []byte) (TDFInspect, []error) { - if len(toInspect) < minBytesLength { - return TDFInspect{}, []error{fmt.Errorf("tdf too small [%d] bytes", len(toInspect))} - } - switch { - case bytes.Equal([]byte("PK"), toInspect[0:2]): - return h.InspectZTDF(toInspect) - case bytes.Equal([]byte("L1L"), toInspect[0:3]): - return h.InspectNanoTDF(toInspect) +func (h Handler) DecryptBytes(toDecrypt []byte, disableAssertionCheck bool) (*bytes.Buffer, error) { + out := &bytes.Buffer{} + pt := io.Writer(out) + ec := bytes.NewReader(toDecrypt) + switch sdk.GetTdfType(ec) { + case sdk.Nano: + if _, err := h.sdk.ReadNanoTDF(pt, ec); err != nil { + return nil, err + } + case sdk.Standard: + r, err := h.sdk.LoadTDF(ec, sdk.WithDisableAssertionVerification(disableAssertionCheck)) + if err != nil { + return nil, err + } + //nolint:errorlint // callers intended to test error equality directly + if _, err = io.Copy(pt, r); err != nil && err != io.EOF { + return nil, err + } + case sdk.Invalid: + return nil, errors.New("invalid TDF") + default: + return nil, errors.New("unknown TDF type") } - return TDFInspect{}, []error{fmt.Errorf("tdf format unrecognized")} + return out, nil } -func (h Handler) InspectZTDF(toInspect []byte) (TDFInspect, []error) { - // grouping errors so we don't impact the piping of the data - errs := []error{} +// TODO: Rename. Not sure what this value is at present +const inspectTDFEighteen = 18 - tdfreader, err := h.sdk.LoadTDF(bytes.NewReader(toInspect)) - if err != nil { - if strings.Contains(err.Error(), "zip: not a valid zip file") { - return TDFInspect{}, []error{ErrTDFInspectFailNotInspectable} - } - return TDFInspect{}, []error{errors.Join(ErrTDFInspectFailNotValidTDF, err)} - } +func (h Handler) InspectTDF(toInspect []byte) (TDFInspect, []error) { + b := bytes.NewReader(toInspect) + switch sdk.GetTdfType(b) { + case sdk.Standard: + // grouping errors so we don't impact the piping of the data + errs := []error{} - attributes, err := tdfreader.DataAttributes() - if err != nil { - errs = append(errs, errors.Join(ErrTDFUnableToReadAttributes, err)) - } + tdfreader, err := h.sdk.LoadTDF(bytes.NewReader(toInspect)) + if err != nil { + if strings.Contains(err.Error(), "zip: not a valid zip file") { + return TDFInspect{}, []error{ErrTDFInspectFailNotInspectable} + } + return TDFInspect{}, []error{errors.Join(ErrTDFInspectFailNotValidTDF, err)} + } - unencryptedMetadata, err := tdfreader.UnencryptedMetadata() - if err != nil { - errs = append(errs, errors.Join(ErrTDFUnableToReadUnencryptedMetadata, err)) - } + attributes, err := tdfreader.DataAttributes() + if err != nil { + errs = append(errs, errors.Join(ErrTDFUnableToReadAttributes, err)) + } - m := tdfreader.Manifest() - return TDFInspect{ - ZTDFManifest: &m, - Attributes: attributes, - UnencryptedMetadata: unencryptedMetadata, - }, errs -} + unencryptedMetadata, err := tdfreader.UnencryptedMetadata() + if err != nil { + errs = append(errs, errors.Join(ErrTDFUnableToReadUnencryptedMetadata, err)) + } -//nolint:gosec,mnd // SDK should secure lengths of inputs/outputs -func (h Handler) InspectNanoTDF(toInspect []byte) (TDFInspect, []error) { - header, size, err := sdk.NewNanoTDFHeaderFromReader(bytes.NewReader(toInspect)) - if err != nil { - return TDFInspect{}, []error{errors.Join(ErrTDFInspectFailNotValidTDF, err)} - } - r := TDFInspect{ - NanoHeader: &header, - } - remainder := uint32(len(toInspect)) - size - if remainder < 18 { - return r, []error{ErrTDFInspectFailNotValidTDF} + m := tdfreader.Manifest() + return TDFInspect{ + ZTDFManifest: &m, + Attributes: attributes, + UnencryptedMetadata: unencryptedMetadata, + }, errs + case sdk.Nano: + header, size, err := sdk.NewNanoTDFHeaderFromReader(b) + if err != nil { + return TDFInspect{}, []error{errors.Join(ErrTDFInspectFailNotValidTDF, err)} + } + r := TDFInspect{ + NanoHeader: &header, + } + remainder := len(toInspect) - int(size) + if remainder < inspectTDFEighteen { + return r, []error{ErrTDFInspectFailNotValidTDF} + } + return r, nil + case sdk.Invalid: + return TDFInspect{}, []error{ErrTDFInspectFailNotValidTDF} + default: + return TDFInspect{}, []error{fmt.Errorf("tdf format unrecognized")} } - return r, nil }