From b94846867fd8420724f17a773c93d8e91de808eb Mon Sep 17 00:00:00 2001 From: Paul Flynn Date: Tue, 24 Sep 2024 18:36:19 -0400 Subject: [PATCH] Add Swift SDK support to testing framework Introduce support for the Swift SDK (OpenTDFKit) in the testing framework. This includes new scripts for CLI interactions, project setup in Swift Package Manager, and adjustments to the testing codebase and documentation to accommodate Swift along with existing SDKs. --- .gitignore | 4 ++ xtest/README.md | 46 ++++++++++++++++-- xtest/conftest.py | 5 +- xtest/nano.py | 12 ++--- xtest/sdk/swift/Package.swift | 31 ++++++++++++ xtest/sdk/swift/cli.sh | 55 +++++++++++++++++++++ xtest/sdk/swift/main.swift | 91 +++++++++++++++++++++++++++++++++++ xtest/tdfs.py | 11 +++-- 8 files changed, 237 insertions(+), 18 deletions(-) create mode 100644 xtest/sdk/swift/Package.swift create mode 100755 xtest/sdk/swift/cli.sh create mode 100644 xtest/sdk/swift/main.swift diff --git a/.gitignore b/.gitignore index 3792296b..cf534eee 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,7 @@ xtest/sdk/java/cmdline.jar /xtest/java-sdk/ /xtest/sdk/go/otdfctl /xtest/otdfctl/ +/xtest/OpenTDFKit/ +/xtest/sdk/swift/Package.resolved +/xtest/sdk/swift/.build/ +/xtest/OpenTDFKit/ diff --git a/xtest/README.md b/xtest/README.md index 7c9cfcec..1ad33a7b 100644 --- a/xtest/README.md +++ b/xtest/README.md @@ -30,7 +30,7 @@ mv cmdline/target/cmdline.jar ../sdk/java/cmdline.jar #### Go SDK wrapped by otdfctl ```shell -git clone https://github.com/opentdf/platform +git clone https://github.com/opentdf/platform.git git clone https://github.com/opentdf/otdfctl.git cd otdfctl go mod edit -replace github.com/opentdf/platform/protocol/go=../platform/protocol/go @@ -40,6 +40,35 @@ go build . mv otdfctl ../sdk/go/otdfctl ``` +#### (External) Swift SDK - OpenTDFKit + +##### Prerequisite + +Xcode 16 + +##### Clone + +```shell +git clone https://github.com/arkavo-org/OpenTDFKit.git +``` + +##### Build + +```shell +cd sdk/swift +swift build --configuration release +``` + +##### Development + +```shell +brew install swiftformat +``` + +```shell +swiftformat --swiftversion 6.0 . +``` + ### Platform Backend 1. **Initialize Platform Configuration** @@ -61,13 +90,13 @@ mv otdfctl ../sdk/go/otdfctl ```shell go run ./service provision keycloak ``` -4. **Add Sample Attributes and Metadata** +4. **Start Server in Background** ```shell - go run ./service provision fixtures + go run ./service start ``` -5. **Start Server in Background** +5. **Add Sample Attributes and Metadata** ```shell - go run ./service start + go run ./service provision fixtures ``` ## Testing with Released Software @@ -90,3 +119,10 @@ pytest rm -rf tmp pytest test_tdfs.py ``` + +#### Run NanoTDF Tests + +```shell +rm -rf tmp +pytest test_tdfs.py --containers nano +``` diff --git a/xtest/conftest.py b/xtest/conftest.py index 800e9776..e1fe2d44 100644 --- a/xtest/conftest.py +++ b/xtest/conftest.py @@ -4,6 +4,7 @@ import string import abac +from xtest.tdfs import SUPPORTED_SDKS def pytest_addoption(parser): @@ -33,7 +34,7 @@ def pytest_generate_tests(metafunc): elif metafunc.config.getoption("--sdks"): encrypt_sdks = metafunc.config.getoption("--sdks").split() else: - encrypt_sdks = ["js", "go", "java"] + encrypt_sdks = SUPPORTED_SDKS metafunc.parametrize("encrypt_sdk", encrypt_sdks) if "decrypt_sdk" in metafunc.fixturenames: if metafunc.config.getoption("--sdks-decrypt"): @@ -41,7 +42,7 @@ def pytest_generate_tests(metafunc): elif metafunc.config.getoption("--sdks"): decrypt_sdks = metafunc.config.getoption("--sdks").split() else: - decrypt_sdks = ["js", "go", "java"] + decrypt_sdks = SUPPORTED_SDKS metafunc.parametrize("decrypt_sdk", decrypt_sdks) if "container" in metafunc.fixturenames: if metafunc.config.getoption("--containers"): diff --git a/xtest/nano.py b/xtest/nano.py index 3feeda43..8a2dca18 100644 --- a/xtest/nano.py +++ b/xtest/nano.py @@ -20,10 +20,10 @@ def to_s(n: int) -> str: def enc_hex_w_comment(B: bytes, c: str) -> str: - hex = enc_hex(B) - if len(hex) < 60: - return f"{hex} # {c}" - return f"\n# {c}\n{hex}" + hexes = enc_hex(B) + if len(hexes) < 60: + return f"{hexes} # {c}" + return f"\n# {c}\n{hexes}" def dec_hex(hexes: str) -> bytes: @@ -64,12 +64,10 @@ def bit_length(self) -> int: @property def byte_length(self) -> int: - # return int(math.ceil(self.bit_length / 8.0)) return (self.bit_length + 7) >> 3 @property def signature_length(self) -> int: - # return self.byte_length * 2 return self.byte_length << 1 @property @@ -301,7 +299,7 @@ def embedded_policy(embedded: str) -> Policy: def encrypted_policy(encrypted: bytes) -> Policy: return Policy( - policy_type=(PolicyType.ENCRYPTED), + policy_type=PolicyType.ENCRYPTED, encrypted_length=len(encrypted), encrypted=encrypted, embedded=None, diff --git a/xtest/sdk/swift/Package.swift b/xtest/sdk/swift/Package.swift new file mode 100644 index 00000000..074e9b25 --- /dev/null +++ b/xtest/sdk/swift/Package.swift @@ -0,0 +1,31 @@ +// swift-tools-version:6.0 +import PackageDescription + +let package = Package( + name: "cli", + platforms: [ + .macOS(.v10_15), + ], + products: [ + .executable( + name: "cli", + targets: ["cli"] + ), + ], + dependencies: [ + .package(url: "https://github.com/arkavo-org/OpenTDFKit.git", exact: "2.0.1"), + .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"), + ], + targets: [ + .executableTarget( + name: "cli", + dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser"), + ], + path: "./", + exclude: [ + "cli.sh", + ] + ), + ] +) diff --git a/xtest/sdk/swift/cli.sh b/xtest/sdk/swift/cli.sh new file mode 100755 index 00000000..64b8eddc --- /dev/null +++ b/xtest/sdk/swift/cli.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2206,SC1091 + +# Common shell wrapper used to interface to SDK implementation. +# +# Usage: ./cli.sh +# +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) + +# shellcheck source=../../test.env +source "$SCRIPT_DIR"/../../test.env + +args=( + -o "$3" + --host "$PLATFORMURL" + --tls-no-verify + --log-level debug + --with-client-creds '{"clientId":"'$CLIENTID'","clientSecret":"'$CLIENTSECRET'"}' +) +if [ "$4" == "nano" ]; then + args+=(--tdf-type "$4") +fi + +if [ -n "$5" ]; then + args+=(--mime-type "$5") +fi + +if [ -n "$6" ]; then + args+=(--attr "$6") +fi + +if [ -z "$SCRIPT_DIR" ]; then + echo "Error: SCRIPT_DIR is not set." + exit 1 +fi + +CMD_PATH="$SCRIPT_DIR"/.build/release/cli +cmd=("$CMD_PATH") + +if [ "$1" == "encrypt" ]; then + echo "${cmd[@]}" encrypt "${args[@]}" "$2" + if ! "${cmd[@]}" encrypt "${args[@]}" "$2"; then + exit 1 + fi + if [ -f "${3}.tdf" ]; then + # go helpfully adds a tdf extension to all files + mv "${3}.tdf" "${3}" + fi +elif [ "$1" == "decrypt" ]; then + echo "${cmd[@]}" decrypt "${args[@]}" "$2" + "${cmd[@]}" decrypt "${args[@]}" "$2" +else + echo "Incorrect argument provided" + exit 1 +fi diff --git a/xtest/sdk/swift/main.swift b/xtest/sdk/swift/main.swift new file mode 100644 index 00000000..a70cdef3 --- /dev/null +++ b/xtest/sdk/swift/main.swift @@ -0,0 +1,91 @@ +import ArgumentParser + import Foundation + + @main + struct CLI: ParsableCommand { + @Argument(help: "Operation to perform (encrypt/decrypt)") + var operation: String + + @Option(name: .shortAndLong, help: "Output file") + var outputFile: String + + @Option(name: .long, help: "Platform URL") + var host: String + + @Flag(name: .long, help: "Disable TLS verification") + var tlsNoVerify: Bool = false + + @Option(name: .long, help: "Log level") + var logLevel: String = "info" + + @Option(name: .long, help: "Client credentials in JSON format") + var withClientCreds: String + + @Option(name: .long, help: "TDF type") + var tdfType: String? + + @Option(name: .long, help: "MIME type") + var mimeType: String? + + @Option(name: .long, help: "Attribute") + var attr: String? + + @Argument(help: "Input file") + var inputFile: String + + func run() throws { + var args: [String] = [ + operation, + "-o", outputFile, + "--host", host, + "--log-level", logLevel, + "--with-client-creds", withClientCreds, + ] + + if tlsNoVerify { + args.append("--tls-no-verify") + } + + if let tdfType { + args.append(contentsOf: ["--tdf-type", tdfType]) + } + + if let mimeType { + args.append(contentsOf: ["--mime-type", mimeType]) + } + + if let attr { + args.append(contentsOf: ["--attr", attr]) + } + + // Append the input file at the end of arguments + args.append(inputFile) + + let cmd = ["./.build/release/cli"] + args + + print(cmd) + runCommand(cmd) + } + + func runCommand(_ command: [String]) { + let process = Process() + process.executableURL = URL(fileURLWithPath: command[0]) + process.arguments = Array(command.dropFirst()) + + do { + try process.run() + process.waitUntilExit() + + if process.terminationStatus != 0 { + Foundation.exit(1) + } + + if operation == "encrypt", FileManager.default.fileExists(atPath: "\(outputFile).tdf") { + try FileManager.default.moveItem(atPath: "\(outputFile).tdf", toPath: outputFile) + } + } catch { + print("Error: \(error)") + Foundation.exit(1) + } + } + } diff --git a/xtest/tdfs.py b/xtest/tdfs.py index d9979e8b..33436dd8 100644 --- a/xtest/tdfs.py +++ b/xtest/tdfs.py @@ -4,14 +4,14 @@ import zipfile from pydantic import BaseModel -from typing import Literal +from typing import Literal, get_args logger = logging.getLogger("xtest") logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) - -sdk_type = Literal["go", "java", "js"] +sdk_type = Literal["js", "go", "java", "swift"] +SUPPORTED_SDKS = list(get_args(sdk_type)) feature_type = Literal["autoconfigure", "nano_ecdsa", "ns_grants"] @@ -19,6 +19,7 @@ "go": "sdk/go/cli.sh", "java": "sdk/java/cli.sh", "js": "sdk/js/cli/cli.sh", + "swift": "sdk/swift/cli.sh", } @@ -98,9 +99,11 @@ def encrypt( ct_file, mime_type="application/octet-stream", fmt="nano", - attr_values=[], + attr_values=None, use_ecdsa_binding=False, ): + if attr_values is None: + attr_values = [] c = [ sdk_paths[sdk], "encrypt",