diff --git a/.github/workflows/publish.azurepipelineextension.yml b/.github/workflows/publish.azurepipelineextension.yml index 1895b9012..4416f4988 100644 --- a/.github/workflows/publish.azurepipelineextension.yml +++ b/.github/workflows/publish.azurepipelineextension.yml @@ -1,3 +1,5 @@ +permissions: + contents: read name: Publish to Azure Pipeline Extension on: workflow_dispatch: diff --git a/.github/workflows/publish.crates.rust.sdk.yml b/.github/workflows/publish.crates.rust.sdk.yml deleted file mode 100644 index e91c10ce7..000000000 --- a/.github/workflows/publish.crates.rust.sdk.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Publish to Crates.io (Rust SDK) - -on: - workflow_dispatch: - -jobs: - hello: - runs-on: ubuntu-latest - - steps: - - name: Print Hello Rust World - run: echo "hello rust world" diff --git a/.github/workflows/publish.maven.java.storage.gcp.kms.yml b/.github/workflows/publish.maven.java.storage.gcp.kms.yml deleted file mode 100644 index 39c119f61..000000000 --- a/.github/workflows/publish.maven.java.storage.gcp.kms.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Publish to Maven (Java Storage GCP KMS) -on: - workflow_dispatch: - -jobs: - publish: - uses: ./.github/workflows/reusable.maven.central.publish.yml - with: - working-directory: ./sdk/java/storage/keeper_secrets_manager_storage_gcp_kms - project-name: keeper-secrets-manager-storage-gcp-kms - project-title: Keeper Secrets Manager GCP KMS Storage - java-version: '8' - secrets: inherit \ No newline at end of file diff --git a/.github/workflows/publish.maven.yml b/.github/workflows/publish.maven.yml new file mode 100644 index 000000000..67d9f4f3b --- /dev/null +++ b/.github/workflows/publish.maven.yml @@ -0,0 +1,74 @@ +permissions: + contents: read +name: Publish to Maven +on: + workflow_dispatch: + +jobs: + get-version: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./sdk/java/core + outputs: + version: ${{ steps.extract-version.outputs.version }} + steps: + - uses: actions/checkout@v3 + - name: Extract version from build.gradle.kts + id: extract-version + run: | + VERSION=$(grep -Po 'version\s*=\s*"\K[^"]*' build.gradle.kts || echo "0.0.0-unknown") + echo "Version retrieved: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + generate-and-upload-sbom: + needs: get-version + uses: ./.github/workflows/reusable.sbom.workflow.yml + with: + working-directory: ./sdk/java/core + project-name: keeper-secrets-manager-java + project-type: java + project-version: ${{ needs.get-version.outputs.version }} + sbom-format: spdx-json + additional-labels: ksm,sdk,java,security + secrets: + MANIFEST_TOKEN: ${{ secrets.MANIFEST_TOKEN }} + + publish-java: + needs: generate-and-upload-sbom + environment: prod + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ./sdk/java/core + + steps: + - name: Get the source code + uses: actions/checkout@v3 + + - name: Set up Java 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + + - name: Retrieve secrets from KSM + id: ksmsecrets + uses: Keeper-Security/ksm-action@v1 + with: + keeper-secret-config: ${{ secrets.KSM_ARTIFACT_JAVA_APP_CONFIG }} + secrets: | + zOVOneDczofWFlfizjC5Qw/file/90A46CD1-private-key.asc > file:/tmp/signing_secret_key_ring_file.asc + zOVOneDczofWFlfizjC5Qw/custom_field/signing.keyId > env:SIGNING_KEY_ID + zOVOneDczofWFlfizjC5Qw/custom_field/signing.password > env:SIGNING_PASSWORD + zOVOneDczofWFlfizjC5Qw/custom_field/ossrhUsername > env:OSSRH_USERNAME + zOVOneDczofWFlfizjC5Qw/custom_field/ossrhPassword > env:OSSRH_PASSWORD + + - name: Publish package + env: + SIGNING_SECRET_KEY_RING_FILE: /tmp/signing_secret_key_ring_file.asc + run: gradle publishMavenJavaPublicationToSonatypeRepository diff --git a/.github/workflows/publish.npm.yml b/.github/workflows/publish.npm.yml index 6e7a225fd..a20465950 100644 --- a/.github/workflows/publish.npm.yml +++ b/.github/workflows/publish.npm.yml @@ -3,73 +3,7 @@ on: workflow_dispatch: jobs: - generate-sbom: - runs-on: ubuntu-latest - steps: - - name: Get the source code - uses: actions/checkout@v3 - - - name: Install Syft - run: | - echo "Installing Syft v1.18.1..." - curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /tmp/bin v1.18.1 - echo "/tmp/bin" >> $GITHUB_PATH - - - name: Install Manifest CLI - run: | - echo "Installing Manifest CLI v0.18.3..." - curl -sSfL https://raw.githubusercontent.com/manifest-cyber/cli/main/install.sh | sh -s -- -b /tmp/bin v0.18.3 - - - name: Create Syft configuration - run: | - cat > syft-config.yaml << 'EOF' - package: - search: - scope: all-layers - cataloger: - enabled: true - java: - enabled: false - python: - enabled: false - nodejs: - enabled: true - EOF - - - name: Generate and upload SBOM - env: - MANIFEST_API_KEY: ${{ secrets.MANIFEST_TOKEN }} - run: | - JAVASCRIPT_SDK_DIR="./sdk/javascript" - - # Get version from package.json - echo "Detecting JavaScript SDK version..." - if [ -f "${JAVASCRIPT_SDK_DIR}/packages/core/package.json" ]; then - VERSION=$(grep -o '"version": "[^"]*"' "${JAVASCRIPT_SDK_DIR}/packages/core/package.json" | cut -d'"' -f4) - echo "Detected version: ${VERSION}" - else - VERSION="1.0.0" - echo "Could not detect version, using default: ${VERSION}" - fi - - echo "Generating SBOM with Manifest CLI..." - /tmp/bin/manifest sbom "${JAVASCRIPT_SDK_DIR}" \ - --generator=syft \ - --name=keeper-secrets-manager-js-sdk \ - --version=${VERSION} \ - --output=spdx-json \ - --file=js-sdk-sbom.json \ - --api-key=${MANIFEST_API_KEY} \ - --publish=true \ - --asset-label=application,sbom-generated,nodejs \ - --generator-config=syft-config.yaml - - echo "SBOM generated and uploaded successfully: js-sdk-sbom.json" - echo "---------- SBOM Preview (first 20 lines) ----------" - head -n 20 js-sdk-sbom.json - publish-npm: - needs: generate-sbom environment: prod runs-on: ubuntu-latest @@ -93,4 +27,4 @@ jobs: run: npm install - name: Publish package - run: npm publish + run: npm publish \ No newline at end of file diff --git a/.github/workflows/publish.pypi.sdk.storage.gcp.kms.yml b/.github/workflows/publish.pypi.sdk.storage.gcp.kms.yml deleted file mode 100644 index e22b0ce41..000000000 --- a/.github/workflows/publish.pypi.sdk.storage.gcp.kms.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Publish to PyPI (Python SDK Storage GCP KMS) -on: - workflow_dispatch: - -jobs: - publish-pypi: - name: Publish KSM Python SDK Storage GCP KMS to PyPI - environment: prod - runs-on: ubuntu-latest - timeout-minutes: 10 # To keep builds from running too long - - defaults: - run: - working-directory: ./sdk/python/storage/keeper_secrets_manager_storage_gcp_kms - - steps: - - name: Get the source code - uses: actions/checkout@v4 - - - name: Set up Python 3.12 - uses: actions/setup-python@v4 - with: - python-version: 3.12 - - - name: Retrieve secrets from KSM - id: ksmsecrets - uses: Keeper-Security/ksm-action@master - with: - keeper-secret-config: ${{ secrets.KSM_PYPI_PUBLISHER_PYPI_SDK_CONFIG }} - secrets: | - -aBWi3-yU_qvatNh0Eaqew/field/password > PYPI_API_TOKEN_GCP_KMS - - - name: Install dependencies - run: | - python3 -m pip install --upgrade pip build twine - python3 -m pip install -r requirements.txt - - - name: Build and Publish - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ steps.ksmsecrets.outputs.PYPI_API_TOKEN_GCP_KMS }} - run: | - python3 -m build - python3 -m twine upload --verbose dist/* \ No newline at end of file diff --git a/.github/workflows/publish.pypi.sdk.storage.oracle.kms.yml b/.github/workflows/publish.pypi.sdk.storage.oracle.kms.yml deleted file mode 100644 index e5d68f455..000000000 --- a/.github/workflows/publish.pypi.sdk.storage.oracle.kms.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Publish to PyPI (Python SDK Storage Oracle KMS) -permissions: - contents: read - packages: write -on: - workflow_dispatch: - -jobs: - publish-pypi: - name: Publish KSM Python SDK Storage Oracle KMS to PyPI - environment: prod - runs-on: ubuntu-latest - timeout-minutes: 10 # To keep builds from running too long - - defaults: - run: - working-directory: ./sdk/python/storage/keeper_secrets_manager_storage_oracle_kms - - steps: - - name: Get the source code - uses: actions/checkout@v4 - - - name: Set up Python 3.12 - uses: actions/setup-python@v4 - with: - python-version: 3.12 - - - name: Retrieve secrets from KSM - id: ksmsecrets - uses: Keeper-Security/ksm-action@master - with: - keeper-secret-config: ${{ secrets.KSM_PYPI_PUBLISHER_PYPI_SDK_CONFIG }} - secrets: | - -aBWi3-yU_qvatNh0Eaqew/field/password > PYPI_API_TOKEN - - - name: Install dependencies - run: | - python3 -m pip install --upgrade pip build twine - python3 -m pip install -r requirements.txt - - - name: Build and Publish - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ steps.ksmsecrets.outputs.PYPI_API_TOKEN }} - run: | - python3 -m build - python3 -m twine upload --verbose dist/* \ No newline at end of file diff --git a/.github/workflows/publish.pypi.sdk.storage.yml b/.github/workflows/publish.pypi.sdk.storage.yml index 0610e6b8e..05ca1ad97 100644 --- a/.github/workflows/publish.pypi.sdk.storage.yml +++ b/.github/workflows/publish.pypi.sdk.storage.yml @@ -11,17 +11,17 @@ jobs: defaults: run: - working-directory: ./sdk/python/storage/keeper_secrets_manager_storages + working-directory: ./sdk/python/storage steps: - name: Get the source code uses: actions/checkout@v3 - - name: Set up Python 3.12 + - name: Set up Python 3.9 uses: actions/setup-python@v4 with: - python-version: 3.12 + python-version: 3.9 - name: Retrieve secrets from KSM id: ksmsecrets diff --git a/.github/workflows/reusable.sbom.workflow.yml b/.github/workflows/reusable.sbom.workflow.yml index 0395abc8e..bea89e164 100644 --- a/.github/workflows/reusable.sbom.workflow.yml +++ b/.github/workflows/reusable.sbom.workflow.yml @@ -1,5 +1,41 @@ name: Reusable SBOM Generation +on: + workflow_call: + inputs: + working-directory: + description: 'Directory containing the project files' + required: true + type: string + project-name: + description: 'Name of the project for SBOM identification' + required: true + type: string + project-type: + description: 'Type of project (python, dotnet, nodejs)' + required: true + type: string + project-version: + description: 'Version of the project (optional, will try to detect if not provided)' + required: false + type: string + default: '' + sbom-format: + description: 'Format for SBOM output (spdx-json, cyclonedx-json)' + required: false + type: string + default: 'spdx-json' + additional-labels: + description: 'Additional labels for SBOM categorization' + required: false + type: string + default: '' + secrets: + MANIFEST_TOKEN: + description: 'Token for Manifest.io authentication' + required: true +name: Reusable SBOM Generation + on: workflow_call: inputs: @@ -36,10 +72,13 @@ on: required: true jobs: + generate-sbom: + name: Generate SBOM generate-sbom: name: Generate SBOM runs-on: ubuntu-latest + steps: - name: Checkout repository uses: actions/checkout@v3 @@ -351,7 +390,6 @@ jobs: - name: Generate and publish SBOM env: PROJECT_VERSION: ${{ inputs.project-version != '' && inputs.project-version || steps.detect-version.outputs.version }} - MANIFEST_TOKEN: ${{ secrets.MANIFEST_TOKEN }} SYFT_PACKAGE_SEARCH_UNINDEXED_ARCHIVES: "true" SYFT_PACKAGE_SEARCH_INDEXED_ARCHIVES: "true" SYFT_SCOPE: "all-layers" @@ -465,7 +503,7 @@ jobs: --name=${{ inputs.project-name }} \ --version=${PROJECT_VERSION} \ --output=${{ inputs.sbom-format }} \ - --api-key=${MANIFEST_TOKEN} \ + --api-key=${{ secrets.MANIFEST_TOKEN }} \ --publish=true \ --label=${FINAL_LABELS} diff --git a/.github/workflows/reusable.securityscan.workflow.yml b/.github/workflows/reusable.securityscan.workflow.yml index c328905ff..6a7d58f6c 100644 --- a/.github/workflows/reusable.securityscan.workflow.yml +++ b/.github/workflows/reusable.securityscan.workflow.yml @@ -1,5 +1,39 @@ name: Reusable Security Scanning +on: + workflow_call: + inputs: + working-directory: + description: 'Directory containing the project files' + required: true + type: string + project-type: + description: 'Type of project (python, dotnet, nodejs, java, go)' + required: true + type: string + fail-level: + description: 'Minimum severity level to fail the build (low, moderate, high, critical)' + required: false + type: string + default: 'moderate' + enable-snyk: + description: 'Enable Snyk scanning' + required: false + type: boolean + default: false + enable-ossar: + description: 'Enable OSSAR scanning' + required: false + type: boolean + default: true + enable-dependency-review: + description: 'Enable Dependency Review' + required: false + type: boolean + default: true + +name: Reusable Security Scanning + on: workflow_call: inputs: @@ -36,8 +70,11 @@ on: jobs: security-scan: name: Security Scan + permissions: + contents: read runs-on: ubuntu-latest + steps: - name: Checkout repository uses: actions/checkout@v4 @@ -224,3 +261,188 @@ jobs: *.xml *.txt retention-days: 90 + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Required for OSSAR + + # Language-specific setup + - name: Setup Python + if: inputs.project-type == 'python' + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Setup Node.js + if: inputs.project-type == 'nodejs' + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Setup .NET + if: inputs.project-type == 'dotnet' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Setup Java + if: inputs.project-type == 'java' + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: 'maven' + + - name: Setup Go + if: inputs.project-type == 'go' + uses: actions/setup-go@v5 + with: + go-version: '1.21' + cache: true + + # Language-specific dependency installation + - name: Install Dependencies + working-directory: ${{ inputs.working-directory }} + run: | + echo "::group::Installing Dependencies" + case "${{ inputs.project-type }}" in + "python") + if [ -f "requirements.txt" ]; then + python -m pip install -r requirements.txt + elif [ -f "pyproject.toml" ]; then + python -m pip install poetry + poetry install + fi + ;; + "nodejs") + if [ -f "package-lock.json" ]; then + npm ci + elif [ -f "package.json" ]; then + npm install + fi + ;; + "dotnet") + dotnet restore + ;; + "java") + if [ -f "pom.xml" ]; then + echo "Maven project detected" + mvn dependency:resolve + elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then + echo "Gradle project detected" + chmod +x ./gradlew + ./gradlew dependencies + else + echo "::error::No pom.xml or build.gradle found" + exit 1 + fi + ;; + "go") + echo "Downloading Go dependencies..." + go mod download + ;; + esac + echo "::endgroup::" + + # Language-specific security checks + - name: Run Language-Specific Security Checks + id: lang-security + working-directory: ${{ inputs.working-directory }} + continue-on-error: true + run: | + echo "::group::Language-Specific Security Checks" + case "${{ inputs.project-type }}" in + "python") + python -m pip install safety bandit + SAFETY_OUTPUT=$(safety check 2>&1) || true + echo "SAFETY_OUTPUT<> $GITHUB_ENV + echo "$SAFETY_OUTPUT" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + bandit -r . -ll --format json --output security-report-bandit.json || true + ;; + + "nodejs") + # Map severity levels for npm audit + NPM_LEVEL="${{ inputs.fail-level }}" + if [ "$NPM_LEVEL" = "medium" ]; then + NPM_LEVEL="moderate" + fi + + # npm audit with mapped level + NPM_AUDIT=$(npm audit --audit-level=$NPM_LEVEL --json || true) + echo "NPM_AUDIT<> $GITHUB_ENV + echo "$NPM_AUDIT" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + if echo "$NPM_AUDIT" | grep -i ".*vulnerabilities.*"; then + echo "::error::npm audit found vulnerabilities" + exit 1 + fi + ;; + + "dotnet") + # Install security tools + dotnet tool install --global security-scan + dotnet security-scan --version + dotnet security-scan . --output-format json --output-file security-report-dotnet.json || true + ;; + + "java") + echo "Running Java security checks..." + + # OWASP Dependency Check + if [ -f "pom.xml" ]; then + mvn org.owasp:dependency-check-maven:check + mvn com.github.spotbugs:spotbugs-maven-plugin:check + elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then + ./gradlew dependencyCheckAnalyze spotbugsMain + fi + ;; + + "go") + echo "Running Go security checks..." + + # Install security tools + go install golang.org/x/vuln/cmd/govulncheck@latest + go install github.com/securego/gosec/v2/cmd/gosec@latest + govulncheck ./... || true + gosec -fmt=json -out=security-report-gosec.json ./... || true + ;; + esac + echo "::endgroup::" + + # Snyk scanning (optional) + - name: Snyk Security Scan + if: inputs.enable-snyk + uses: snyk/actions/node@master + continue-on-error: true + with: + args: --severity-threshold=${{ inputs.fail-level }} + + # OSSAR scanning (optional) + - name: Run OSSAR Scan + if: inputs.enable-ossar + uses: github/ossar-action@v1 + continue-on-error: true + + # Dependency review (optional) + - name: Dependency Review + if: inputs.enable-dependency-review && github.event_name == 'pull_request' + uses: actions/dependency-review-action@v3 + continue-on-error: true + with: + fail-on-severity: ${{ inputs.fail-level }} + + # Upload scan results + - name: Upload Scan Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: security-scan-results + path: | + *.json + *.sarif + *.xml + *.txt + retention-days: 90 diff --git a/.github/workflows/test.ansible.yml b/.github/workflows/test.ansible.yml index 554141576..75119ed0b 100644 --- a/.github/workflows/test.ansible.yml +++ b/.github/workflows/test.ansible.yml @@ -6,15 +6,15 @@ on: jobs: test-ansible: - runs-on: ${{ matrix.python-version == '3.8' && 'ubuntu-22.04' || 'ubuntu-latest' }} + runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/test.cli.yml b/.github/workflows/test.cli.yml index 2fabbd397..2df1a5808 100644 --- a/.github/workflows/test.cli.yml +++ b/.github/workflows/test.cli.yml @@ -6,15 +6,15 @@ on: jobs: test-cli: - runs-on: ${{ matrix.python-version == '3.8' && 'ubuntu-22.04' || 'ubuntu-latest' }} + runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/test.java.storage.gcp.kms.yml b/.github/workflows/test.java.storage.gcp.kms.yml deleted file mode 100644 index 536795251..000000000 --- a/.github/workflows/test.java.storage.gcp.kms.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Test-Java-Storage-GCP-KMS - -on: - pull_request: - branches: [ master ] - paths: - - 'sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/**' - push: - branches: [ master ] - paths: - - 'sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/**' - workflow_dispatch: - -jobs: - test-java-storage-gcp-kms: - runs-on: ubuntu-latest - strategy: - max-parallel: 1 - matrix: - java-version: [ '8', '11', '17' ] - name: Test GCP KMS Storage with Java ${{ matrix.java-version }} - defaults: - run: - working-directory: ./sdk/java/storage/keeper_secrets_manager_storage_gcp_kms - steps: - - uses: actions/checkout@v3 - - - name: Setup Java ${{ matrix.java-version }} - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: ${{ matrix.java-version }} - - - name: Setup, Build and Test - uses: gradle/gradle-build-action@v2 - with: - gradle-version: 8.9 - arguments: build test - build-root-directory: ./sdk/java/storage/keeper_secrets_manager_storage_gcp_kms \ No newline at end of file diff --git a/.github/workflows/test.python.helper.yml b/.github/workflows/test.python.helper.yml index e9f556f18..24be36dae 100644 --- a/.github/workflows/test.python.helper.yml +++ b/.github/workflows/test.python.helper.yml @@ -6,15 +6,15 @@ on: jobs: test-cli: - runs-on: ${{ matrix.python-version == '3.8' && 'ubuntu-22.04' || 'ubuntu-latest' }} + runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/test.python.yml b/.github/workflows/test.python.yml index 3d7b3e2bb..67277bfe9 100644 --- a/.github/workflows/test.python.yml +++ b/.github/workflows/test.python.yml @@ -6,18 +6,18 @@ on: jobs: test-python: - runs-on: ${{ matrix.python-version == '3.8' && 'ubuntu-22.04' || 'ubuntu-latest' }} + runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] defaults: run: working-directory: ./sdk/python/core steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/integration/keeper_secrets_manager_azure_pipeline_extension/ksm-azure-devops-secrets-task/package-lock.json b/integration/keeper_secrets_manager_azure_pipeline_extension/ksm-azure-devops-secrets-task/package-lock.json index f2777cabe..fbca81e93 100644 --- a/integration/keeper_secrets_manager_azure_pipeline_extension/ksm-azure-devops-secrets-task/package-lock.json +++ b/integration/keeper_secrets_manager_azure_pipeline_extension/ksm-azure-devops-secrets-task/package-lock.json @@ -1,130 +1,41 @@ { "name": "keeper-secrets-manager", - "version": "1.1.0", + "version": "1.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "keeper-secrets-manager", - "version": "1.1.0", + "version": "1.0.8", "license": "MIT", "dependencies": { - "@keeper-security/secrets-manager-core": "^16.6.3", - "azure-pipelines-task-lib": "^4.17.3" + "@keeper-security/secrets-manager-core": "^16.6.2", + "azure-pipelines-task-lib": "^4.15.0" }, "devDependencies": { - "@types/mocha": "^10.0.9", - "@types/node": "^20.3.1", - "dotenv": "^16.4.5", + "@types/mocha": "^8.2.3", + "@types/node": "^14.14.37", + "dotenv": "^10.0.0", "mocha": "^10.7.3", - "ts-node": "^10.9.2", - "typescript": "^5.1.6" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "typescript": "^4.2.4" } }, "node_modules/@keeper-security/secrets-manager-core": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/@keeper-security/secrets-manager-core/-/secrets-manager-core-16.6.3.tgz", - "integrity": "sha512-41eZFAKSthk8OpN6yo/wDRyHv+c1ZXFZaXtu7gzv8MUBRPHUvC57wT18KsHc/KKWkS6N1J43Xz7Wh1dwjrja9g==" - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "version": "16.6.2", + "resolved": "https://registry.npmjs.org/@keeper-security/secrets-manager-core/-/secrets-manager-core-16.6.2.tgz", + "integrity": "sha512-aqbZ0c8Q2wJw3B/dunqZf8iYTGnMPwgA3ozthccmMdI/00WlymfcErW+agfAGDr7cJ/9GzqCty0ECee5k9Z+fw==" }, "node_modules/@types/mocha": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.9.tgz", - "integrity": "sha512-sicdRoWtYevwxjOHNMPTl3vSfJM6oyW8o1wXeI7uww6b6xHg8eBznQDNSGBCDJmsE8UMxP05JgZRtsKbTqt//Q==", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", + "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==", "dev": true }, "node_modules/@types/node": { - "version": "20.17.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.0.tgz", - "integrity": "sha512-a7zRo0f0eLo9K5X9Wp5cAqTUNGzuFLDG2R7C4HY2BhcMAsxgSPuRvAC1ZB6QkuUQXf0YZAgfOX2ZyrBa2n4nHQ==", - "dev": true, - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/acorn": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", - "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "dev": true }, "node_modules/adm-zip": { "version": "0.5.14", @@ -182,12 +93,6 @@ "node": ">= 8" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -195,15 +100,15 @@ "dev": true }, "node_modules/azure-pipelines-task-lib": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-4.17.3.tgz", - "integrity": "sha512-UxfH5pk3uOHTi9TtLtdDyugQVkFES5A836ZEePjcs3jYyxm3EJ6IlFYq6gbfd6mNBhrM9fxG2u/MFYIJ+Z0cxQ==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-4.15.0.tgz", + "integrity": "sha512-Y72FjLTE2CAM9KrBXzc6vjelTBCpdYb2NkyFB0hwksTrhA3q8nsF680dofuTeXztQ94UTpkK27hpgSHnqYf5ZA==", "dependencies": { "adm-zip": "^0.5.10", "minimatch": "3.0.5", "nodejs-file-downloader": "^4.11.1", "q": "^1.5.1", - "semver": "^5.7.2", + "semver": "^5.1.0", "shelljs": "^0.8.5", "uuid": "^3.0.1" } @@ -394,12 +299,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -438,15 +337,12 @@ } }, "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" + "node": ">=10" } }, "node_modules/emoji-regex": { @@ -790,12 +686,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -1184,77 +1074,19 @@ "utf8-byte-length": "^1.0.1" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=14.17" + "node": ">=4.2.0" } }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true - }, "node_modules/utf8-byte-length": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", @@ -1269,12 +1101,6 @@ "uuid": "bin/uuid" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, "node_modules/workerpool": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", @@ -1442,15 +1268,6 @@ "node": ">=8" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/integration/keeper_secrets_manager_azure_pipeline_extension/ksm-azure-devops-secrets-task/package.json b/integration/keeper_secrets_manager_azure_pipeline_extension/ksm-azure-devops-secrets-task/package.json index e629f4d32..0020b16e2 100644 --- a/integration/keeper_secrets_manager_azure_pipeline_extension/ksm-azure-devops-secrets-task/package.json +++ b/integration/keeper_secrets_manager_azure_pipeline_extension/ksm-azure-devops-secrets-task/package.json @@ -1,26 +1,25 @@ { "name": "keeper-secrets-manager", - "version": "1.1.0", + "version": "1.0.8", "description": "", "main": "index.js", "scripts": { "build": "tsc index.ts", "run": "tsc index.ts && node index.js", - "test": "tsc tests/_suite.ts tests/success.ts && mocha tests/_suite.js", + "test": "tsc && mocha tests/_suite.js", "quicktest": "ts-node index.ts" }, "author": "", "license": "MIT", "dependencies": { - "@keeper-security/secrets-manager-core": "^16.6.3", - "azure-pipelines-task-lib": "^4.17.3" + "@keeper-security/secrets-manager-core": "^16.6.2", + "azure-pipelines-task-lib": "^4.15.0" }, "devDependencies": { - "@types/mocha": "^10.0.9", - "@types/node": "^20.3.1", - "dotenv": "^16.4.5", + "@types/mocha": "^8.2.3", + "@types/node": "^14.14.37", "mocha": "^10.7.3", - "ts-node": "^10.9.2", - "typescript": "^5.1.6" + "typescript": "^4.2.4", + "dotenv": "^10.0.0" } } diff --git a/integration/keeper_secrets_manager_azure_pipeline_extension/ksm-azure-devops-secrets-task/task.json b/integration/keeper_secrets_manager_azure_pipeline_extension/ksm-azure-devops-secrets-task/task.json index e23291f3a..31641a6b7 100644 --- a/integration/keeper_secrets_manager_azure_pipeline_extension/ksm-azure-devops-secrets-task/task.json +++ b/integration/keeper_secrets_manager_azure_pipeline_extension/ksm-azure-devops-secrets-task/task.json @@ -9,8 +9,8 @@ "author": "Keeper Security", "version": { "Major": 1, - "Minor": 1, - "Patch": 0 + "Minor": 0, + "Patch": 8 }, "instanceNameFormat": "KeeperSecretsManager", "inputs": [ @@ -36,16 +36,9 @@ } ], - "minimumAgentVersion": "2.144.0", "execution": { "Node10": { "target": "index.js" - }, - "Node16": { - "target": "index.js" - }, - "Node20_1": { - "target": "index.js" } } } diff --git a/integration/keeper_secrets_manager_azure_pipeline_extension/vss-extension.json b/integration/keeper_secrets_manager_azure_pipeline_extension/vss-extension.json index 657bf8c26..7505970c6 100644 --- a/integration/keeper_secrets_manager_azure_pipeline_extension/vss-extension.json +++ b/integration/keeper_secrets_manager_azure_pipeline_extension/vss-extension.json @@ -1,7 +1,7 @@ { "manifestVersion": 1, "id": "keeper-secrets-manager", - "version": "1.1.0", + "version": "1.0.8", "publisher": "KeeperSecurity", "name": "Keeper Secrets Manager", "public": true, diff --git a/integration/keeper_secrets_manager_cli/tests/secret_test.py b/integration/keeper_secrets_manager_cli/tests/secret_test.py index b2a9806c5..b187fc19e 100644 --- a/integration/keeper_secrets_manager_cli/tests/secret_test.py +++ b/integration/keeper_secrets_manager_cli/tests/secret_test.py @@ -14,6 +14,7 @@ import re import os import base64 +import imghdr from requests import Response @@ -298,11 +299,9 @@ def test_get_list_field(self): result = runner.invoke(cli, ['secret', 'get', '--title', two.title, '--field', 'My Custom'], catch_exceptions=False) self.assertEqual(0, result.exit_code, "the exit code was not 0") - # The line feed are stderr to make console display more readable. - # Doing a FIELD=$(ksm ...) results in only stdout being captured. - # Depending on OS, Python version, buffering CR/LF could happend - # before or after the field value. - self.assertRegex(result.output, r"^\n*custom2\n*$", "didn't get the expected field value") + # The line feed are stderr to make console display more readable. Doing a FILED=$(ksm ...) will result + # in only the stdout being captured. + self.assertEqual("\ncustom2\n", result.output) def test_download(self): @@ -496,7 +495,7 @@ def mock_download_get(url): self.assertEqual(0, result.exit_code, "the exit code was not 0") with open(tf_name, "rb") as fh: - self.assertEqual(fh.read(4), b"\x89PNG", "did not get a PNG") + self.assertEqual("png", imghdr.what(fh), "did not get a PNG") fh.close() # Write plain text to file. This should not be binary data. @@ -1017,17 +1016,9 @@ def test_add_record_via_field(self): 'url=http://localhost' ], catch_exceptions=False) output = results.output - # stderr and stdout are merged: - # Depending on OS, Python version, buffering CR/LF could happend - # before or after the expected value. - # 'UIDxxxxxxxxxxxxxxxxxxxThe following is the new record UID..' or - # 'The following is the new record UID..\nUIDxxxxxxxxxxxxxxxxxxx\n' - prefix = "The following is the new record UID ..." - lines = [line for line in - (line.replace(prefix, "").strip() - for line in output.split("\n")) if line] - self.assertTrue(lines, "did not get back a record uid") # empty - self.assertRegex(lines[0], r'^[\w_-]{22}$', "did not get back a record uid") + # stderr and stdout are merged + output_line = output.split('\n') + self.assertRegex(output_line[1], r'^[\w_-]{22}$', "did not get back a record uid") if __name__ == '__main__': diff --git a/sdk/java/core/build.gradle.kts b/sdk/java/core/build.gradle.kts index c866d6160..632fb0756 100644 --- a/sdk/java/core/build.gradle.kts +++ b/sdk/java/core/build.gradle.kts @@ -7,7 +7,7 @@ import java.util.* group = "com.keepersecurity.secrets-manager" // During publishing, If version ends with '-SNAPSHOT' then it will be published to Maven snapshot repository -version = "17.0.1" +version = "17.0.0" plugins { `java-library` diff --git a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt index fc49c3b4b..f47f59cda 100644 --- a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt +++ b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt @@ -15,7 +15,7 @@ data class KeeperRecordData @JvmOverloads constructor( var notes: String? = null ) { inline fun getField(): T? { - return (fields + custom).find { x -> x is T } as T + return (fields + (custom ?: listOf())).find { x -> x is T } as? T } fun getField(clazz: Class): KeeperRecordField? { diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/.gitignore b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/.gitignore deleted file mode 100644 index 6f50fbbb5..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Ignore Gradle project-specific cache directory -.gradle - -# Ignore Gradle build output directory -build -.class -bin \ No newline at end of file diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/README.md b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/README.md deleted file mode 100644 index d5c47136a..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/README.md +++ /dev/null @@ -1,235 +0,0 @@ -# GCP KMS Integration - -Protect Secrets Manager connection details with GCP KMS - -Keeper Secrets Manager integrates with GCP KMS in order to provide protection for Keeper Secrets Manager configuration files. With this integration, you can protect connection details on your machine while taking advantage of Keeper's zero-knowledge encryption of all your secret credentials. - -# Features - -* Encrypt and Decrypt your Keeper Secrets Manager configuration files with GCP KMS (Symmetric/Asymmetric Key) -* Protect against unauthorized access to your Secrets Manager connections -* Requires only minor changes to code for immediate protection. Works with all Keeper Secrets Manager Java/Kotlin SDK functionality - -# Prerequisites - -* Supports the Java/Kotlin Secrets Manager SDK. -* Requires GCP package: google-cloud-kms. -* Key needs `Encrypt` and `Decrypt` permissions. - -# Set Up Authentication -Before using Google Cloud APIs, you must authenticate your Java application. The easiest way to do this is by setting up a service account and downloading a service account key file (JSON). This service account should have the appropriate permissions to interact with the KMS API. - -* Go to the Google Cloud Console. -* Navigate to IAM & Admin → Service Accounts. -* Create a new service account or select an existing one. -* Assign the necessary permissions (e.g., Cloud KMS Admin, or Cloud KMS CryptoKey Encrypter/Decrypter). -* Download the private key JSON file. - -You can also set the GOOGLE_APPLICATION_CREDENTIALS environment variable to point to the path of the downloaded key file: - -`export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your-service-account-file.json"` - -For more detail: https://cloud.google.com/kms/docs/iam - -# Permissions -Make sure that the service account you're using has appropriate permissions. Typically, you'll need: - -Cloud KMS CryptoKey Encrypter/Decrypter permission for encrypting and decrypting data. -Cloud KMS Key Viewer permission to fetch key details. You can assign these roles via IAM in the Google Cloud Console or using gcloud. - - -# Download and Installation - -**Install With Gradle or Maven** - - -
- Gradle - - ``` - repositories { - mavenCentral() -} - -dependencies { - implementation("com.keepersecurity.secrets-manager:core:17.0.0") - - implementation ("com.google.cloud:google-cloud-kms:2.62.0") - implementation ("com.google.auth:google-auth-library-oauth2-http:1.33.1") - - implementation("com.fasterxml.jackson.core:jackson-databind:2.18.2") - implementation("com.fasterxml.jackson.core:jackson-core:2.18.2") - - implementation("com.google.code.gson:gson:2.12.1") - implementation("org.slf4j:slf4j-api:1.7.32"){ - exclude("org.slf4j:slf4j-log4j12") - } - - implementation("ch.qos.logback:logback-classic:1.2.6") - implementation("ch.qos.logback:logback-core:1.2.6") - implementation("org.bouncycastle:bc-fips:1.0.2.4") -} -``` - -
-
Maven - - ``` - - - - com.keepersecurity.secrets-manager - core - [17.0.0,) - - - - - - com.google.cloud - google-cloud-kms - 2.62.0 - - - - - com.google.auth - google-auth-library-oauth2-http - 1.33.1 - - - - - - com.google.code.gson - gson - 2.12.1 - - - - - com.fasterxml.jackson.core - jackson-core - 2.18.2 - - - - - com.fasterxml.jackson.core - jackson-core - 2.18.2 - - - - - org.slf4j - slf4j-api - 1.7.32 - runtime - - - - - ch.qos.logback - logback-classic - 1.2.6 - compile - - - - - ch.qos.logback - logback-core - 1.2.6 - compile - - - - - org.bouncycastle - bc-fips - 1.0.2.4 - - - -``` -
- - -**Configure GCP Connection** - -**Initializes GcpKeyValueStorage** - -Configuration variables can be provided as - - config_file_location provides keeper secret manager config file location - if missing read from env param KSM_CONFIG_FILE - It can be export like "export KSM_CONFIG_FILE = " - - -GCPSessionConfig is needed to initialize GCPKeyValueStorage. To initialize GCPSessionConfig, You will need an GCP ProjectId, location, KeyRing, keyId and keyVersion to use the GCP KMS integration. - -``` -String projectId = ""; -String location = ""; -String keyRing = ">"; -String keyId = ""; //Symmetric or Asymmetric -String keyVersion =""; -String credentialsPaths = ""; -GcpSessionConfig sessionConfig = new GcpSessionConfig(projectId, location, keyRing, keyId, keyVersion, credentialsPaths); -``` -For more information on GCP Configuration see the GCP documentation: https://cloud.google.com/kms/docs/reference/libraries#client-libraries-install-java - OR -For more information on GCP KMS parameter see the documentation: https://cloud.google.com/kms/docs/resource-hierarchy - -**Add GCP KMS Storage to Your Code** - -Now that the GCP connection has been configured, you need to tell the Secrets Manager SDK to utilize the KMS as storage. - -To do this, use GcpKeyValueStorage as your Secrets Manager storage in the SecretsManager constructor. - -The storage will require an GCP KeyId and KeyVersion, as well as the name of the Secrets Manager configuration file which will be encrypted by GCP KMS. Below is the sample Test class - -``` -import java.security.Security; -import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; -import static com.keepersecurity.secretsManager.core.SecretsManager.initializeStorage; -import com.keepersecurity.secretsmanager.gcp.GcpKeyValueStorage; -import com.keepersecurity.secretsmanager.gcp.GcpSessionConfig; -import com.keepersecurity.secretsManager.core.SecretsManagerOptions; - -public class Test { - public static void main(String args[]){ - - String oneTimeToken = "One_Time_Token"; - String projectId = "projectId"; - String location = "cloud_region"; - String keyRing = "key_ring_name"; - String keyId = "key_id"; //Symmetric or Asymmetric - String keyVersion = "key_version"; - String configFileLocation = "client_config_test.json"; - String credentialFileLocation = "path_of_gcp_cred_file.json"; - // Used for change_key - String updatedKeyId = "updated_key_id"; - Security.addProvider(new BouncyCastleFipsProvider()); - try{ - GcpSessionConfig sessionConfig = new GcpSessionConfig(projectId, location, keyRing, updatedKeyId, keyVersion, credentialFileLocation); - GcpKeyValueStorage storage = new GcpKeyValueStorage(configFileLocation, sessionConfig); - initializeStorage(storage, oneTimeToken); - SecretsManagerOptions options = new SecretsManagerOptions(storage); - boolean isChanged = storage.changeKey(keyId); - System.out.println("Key Changed: "+isChanged); - - String plaintext = storage.decryptConfig(false); - System.out.println(plaintext); - - System.out.println("Saving the decrypt config into file"); - String plainText = storage.decryptConfig(true); - - //getSecrets(OPTIONS); - }catch (Exception e) { - System.out.println(e.getMessage()); - } - } -} - -``` diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/build.gradle.kts b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/build.gradle.kts deleted file mode 100644 index 3480fb046..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/build.gradle.kts +++ /dev/null @@ -1,167 +0,0 @@ -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.kotlin.dsl.`maven-publish` -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - -group = "com.keepersecurity.secrets-manager" - -// During publishing, If version ends with '-SNAPSHOT' then it will be published to Maven snapshot repository -version = "1.0.0" - -plugins { - `java-library` - kotlin("jvm") version "2.0.20" - kotlin("plugin.serialization") version "2.0.20" - `maven-publish` - id("org.jreleaser") version "1.18.0" -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - withJavadocJar() - withSourcesJar() -} - -tasks.withType().configureEach { - javaCompiler.set(javaToolchains.compilerFor { - languageVersion.set(JavaLanguageVersion.of(8)) - }) -} - -tasks.withType().configureEach { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_1_8) - } -} - -repositories { - // Use Maven Central for resolving dependencies. - mavenCentral() -} - -dependencies { - // Core Keeper Secrets Manager dependency - implementation("com.keepersecurity.secrets-manager:core:17.0.0") - - // Google Cloud KMS dependencies - implementation("com.google.cloud:google-cloud-kms:2.62.0") - implementation("com.google.auth:google-auth-library-oauth2-http:1.33.1") - - // JSON processing - implementation("com.fasterxml.jackson.core:jackson-databind:2.18.2") - implementation("com.fasterxml.jackson.core:jackson-core:2.18.2") - - // Logging - Only API for library consumers - implementation("org.slf4j:slf4j-api:1.7.32") { - exclude("org.slf4j:slf4j-log4j12") - } - - // Test dependencies - testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") - testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2") - testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2") - testImplementation("org.mockito:mockito-core:5.8.0") - testImplementation("org.mockito:mockito-junit-jupiter:5.8.0") - - // Logging implementation for tests only - testImplementation("ch.qos.logback:logback-classic:1.2.6") - testImplementation("ch.qos.logback:logback-core:1.2.6") -} - -tasks.jar { - manifest { - attributes( - "Implementation-Title" to "Keeper Secrets Manager GCP KMS Storage", - "Implementation-Version" to archiveVersion - ) - } -} - -publishing { - publications { - create("mavenJava") { - artifactId = project.rootProject.name - from(components["java"]) - - pom { - name.set("Keeper Secrets Manager GCP KMS Storage") - description.set("GCP KMS storage provider for Keeper Secrets Manager. " + - "Provides secure storage of KSM configuration using Google Cloud Key Management Service. " + - "Supports symmetric, asymmetric, and raw symmetric encryption.") - url.set("https://github.com/Keeper-Security/secrets-manager") - licenses { - license { - name.set("MIT") - url.set("https://opensource.org/licenses/MIT") - } - } - developers { - developer { - id.set("MaksimUstinov") - name.set("Maksim Ustinov") - email.set("mustinov@keepersecurity.com") - } - } - scm { - connection.set("scm:git:git://github.com/Keeper-Security/secrets-manager.git") - url.set("https://github.com/Keeper-Security/secrets-manager") - } - } - } - } - - repositories { - maven { - name = "staging" - url = uri(layout.buildDirectory.dir("staging-deploy")) - } - } -} - -// Configure JReleaser for Central Portal publishing -configure { - project { - copyright = "Keeper Security Inc." - description = "GCP KMS storage provider for Keeper Secrets Manager" - authors = listOf("Keeper Security Inc.") - license = "MIT" - inceptionYear = "2024" - } - - gitRootSearch = true - - signing { - active = org.jreleaser.model.Active.ALWAYS - armored = true - mode = org.jreleaser.model.Signing.Mode.FILE - } - - deploy { - maven { - mavenCentral { - create("sonatype") { - active = org.jreleaser.model.Active.ALWAYS - url = "https://central.sonatype.com/api/v1/publisher" - stagingRepository(layout.buildDirectory.dir("staging-deploy").get().asFile.path) - } - } - } - } - - release { - github { - enabled = false - } - } -} - -tasks.javadoc { - if (JavaVersion.current().isJava9Compatible) { - (options as StandardJavadocDocletOptions).addBooleanOption("html5", true) - } -} - -tasks.named("test") { - // Use JUnit Platform for unit tests. - useJUnitPlatform() -} diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradle/libs.versions.toml b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradle/libs.versions.toml deleted file mode 100644 index cc61ce4d9..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradle/libs.versions.toml +++ /dev/null @@ -1,12 +0,0 @@ -# This file was generated by the Gradle 'init' task. -# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format - -[versions] -commons-math3 = "3.6.1" -guava = "33.1.0-jre" -junit-jupiter = "5.10.2" - -[libraries] -commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" } -guava = { module = "com.google.guava:guava", version.ref = "guava" } -junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradle/wrapper/gradle-wrapper.jar b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 2c3521197..000000000 Binary files a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradle/wrapper/gradle-wrapper.properties b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 09523c0e5..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradlew b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradlew deleted file mode 100755 index f5feea6d6..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradlew +++ /dev/null @@ -1,252 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradlew.bat b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradlew.bat deleted file mode 100644 index 9d21a2183..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/settings.gradle.kts b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/settings.gradle.kts deleted file mode 100644 index 333cf1768..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/settings.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.9/userguide/multi_project_builds.html in the Gradle documentation. - */ - -plugins { - // Apply the foojay-resolver plugin to allow automatic download of JDKs - id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" -} - -rootProject.name = "storage-gcp-kms" - diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/main/java/com/keepersecurity/secretsManager/storage/gcp/Constants.java b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/main/java/com/keepersecurity/secretsManager/storage/gcp/Constants.java deleted file mode 100644 index 0b743fde9..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/main/java/com/keepersecurity/secretsManager/storage/gcp/Constants.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.keepersecurity.secretsManager.storage.gcp; - -/** - * The {@code Constants} class defines a set of constants used for cryptographic - * operations - * and configurations in the application. These constants include key types, - * encryption - * algorithms, and other related parameters. - */ -public class Constants { - /** - * Private constructor to prevent instantiation of the class. - */ - private Constants() { - // Prevent instantiation - } - - /** RSA key type with a 2048-bit key size. */ - public static final String RSA_2048 = "RSA_2048"; - - /** RSA key type with a 4096-bit key size. */ - public static final String RSA_4096 = "RSA_4096"; - - /** Default symmetric encryption algorithm. */ - public static final String SYMMETRIC_DEFAULT = "SYMMETRIC_DEFAULT"; - - /** RSA encryption scheme using OAEP with SHA-256. */ - public static final String RSAES_OAEP_SHA_256 = "RSAES_OAEP_SHA_256"; - - /** RSA encryption scheme using OAEP with SHA-1. */ - public static final String RSAES_OAEP_SHA_1 = "RSAES_OAEP_SHA_1"; - - /** SM2 public key encryption algorithm. */ - public static final String SM2PKE = "SM2PKE"; - - /** Header bytes for identifying a binary blob. */ - public static final byte[] BLOB_HEADER = { (byte) 0xFF, (byte) 0xFF }; - - /** Block size used in cryptographic operations, in bytes. */ - public static final int BLOCK_SIZE = 16; - - /** Key size used in cryptographic operations, in bytes. */ - public static final int KEY_SIZE = 32; - - /** AES encryption algorithm with GCM mode and no padding. */ - public static final String AES_GCM = "AES/GCM/NoPadding"; - - /** AES encryption algorithm. */ - public static final String AES = "AES"; - - /** Tag length for GCM mode, in bits. */ - public static final int GCM_TAG_LENGTH = 96; - - /** Add Additional Authenticate Data in bytes. */ - public static final byte[] additionalAuthenticatedData = "KeeperSecurity".getBytes(); - - /** Cloud API URL */ - public static final String CLOUD_API_URL = "https://www.googleapis.com/auth/cloud-platform"; -} \ No newline at end of file diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/main/java/com/keepersecurity/secretsManager/storage/gcp/GcpKeyValueStorage.java b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/main/java/com/keepersecurity/secretsManager/storage/gcp/GcpKeyValueStorage.java deleted file mode 100644 index b554e385d..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/main/java/com/keepersecurity/secretsManager/storage/gcp/GcpKeyValueStorage.java +++ /dev/null @@ -1,479 +0,0 @@ -package com.keepersecurity.secretsManager.storage.gcp; - -/* -* _ __ -* | |/ /___ ___ _ __ ___ _ _ (R) -* | ' configMap; - private GcpSessionConfig sessionConfig; - private KMSUtils kmsClient; - - /** - * Initialize the GCP Key Management Service Client with the given config and - * session config object - * - * @param configFileLocation KSM Config file location - * @param sessionConfig GCP Session Config object - * @throws Exception Throw Execption, if any error occurs while initializing the - * client - */ - public GcpKeyValueStorage(String configFileLocation, GcpSessionConfig sessionConfig) throws Exception { - this.configFileLocation = configFileLocation != null ? configFileLocation - : System.getenv("KSM_CONFIG_FILE") != null ? System.getenv("KSM_CONFIG_FILE") - : this.defaultConfigFileLocation; - kmsClient = new KMSUtils(sessionConfig); - this.sessionConfig = sessionConfig; - logger.info("GCP Key Management Service Client initiated."); - loadConfig(); - } - - /** - * Get the internal storage object with the given config file location and - * session config. - * - * @param configFileLocation KSM Config file location - * @param sessionConfig GCP Session Config object - * @return GcpKeyValueStorage object - * @throws Exception Throw Execption, if any error occurs while initializing the - * {@code GCPKeyValueStorage}. - */ - public static GcpKeyValueStorage getInternalStorage(String configFileLocation, GcpSessionConfig sessionConfig) - throws Exception { - GcpKeyValueStorage storage = new GcpKeyValueStorage(configFileLocation, sessionConfig); - return storage; - } - - /** - * Retrieves an OAuth token using the provided credentials file. - * - * @param credentialFileWithPath The path to the credentials file. - * @param cloudApiUrl The scope for which the token is requested. - * @return The OAuth access token as a string. - * @throws IOException If an error occurs while reading the credentials file or - * fetching the token. - */ - public static String getOAuthToken(String credentialFileWithPath, String cloudApiUrl) throws IOException { - // Load the credentials from the file - GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(credentialFileWithPath)) - .createScoped(Collections.singleton(cloudApiUrl)); - - // Refresh the token to get a new access token - credentials.refreshIfExpired(); - AccessToken token = credentials.getAccessToken(); - - // Return the access token as a string - return token.getTokenValue(); - } - - /** - * Change key method used to re-encrypt the config with new Key - * - * @param newKeyId New Key ID for re-encryption - * @return {@code true} if the key change was successful, {@code false} - * otherwise. - */ - public boolean changeKey(String newKeyId) { - logger.info("Change Key initiated"); - String oldKey = kmsClient.getKeyId(); - String configJson = ""; - Map oldconfigMap = this.configMap; - try { - kmsClient.setKeyId(newKeyId); - save(configJson, configMap); - logger.info("Encrypted using newKeyId success."); - return true; - } catch (Exception e) { - kmsClient.setKeyId(oldKey); - logger.error("Exception: " + e.getMessage()); - } - return false; - } - - /** - * Load the config from KSM json file - * - * @throws Exception - */ - private void loadConfig() throws Exception { - File file = new File(configFileLocation); - if (file.exists() && file.length() == 0) { - logger.info("File is empty"); - return; - } - if (!JsonUtil.isValidJsonFile(configFileLocation)) { - logger.debug("loadConfig::File is encryped."); - String decryptedContent = decryptBuffer(readEncryptedJsonFile()); - lastSavedConfigHash = calculateMd5(decryptedContent); - configMap = JsonUtil.convertToMap(decryptedContent); - logger.debug("loadConfig::configMap loaded from file."); - } else { - logger.debug("loadConfig::File is plain json."); - String configJson = new String(Files.readAllBytes(Paths.get(configFileLocation)), StandardCharsets.UTF_8); - lastSavedConfigHash = calculateMd5(configJson); - configMap = JsonUtil.convertToMap(configJson); - saveConfig(configMap); - } - logger.info("KSM config saved into file success."); - } - - private void saveConfig(Map updatedConfig) { - try { - if (JsonUtil.isValidJsonFile(configFileLocation)) { - Path path = Paths.get(configFileLocation); - save(new String(Files.readAllBytes(path), StandardCharsets.UTF_8), updatedConfig); - } else { - String decryptedContent = decryptBuffer(readEncryptedJsonFile()); - save(decryptedContent, updatedConfig); - } - } catch (Exception e) { - logger.error("Exception: " + e.getMessage()); - } - } - - private void save(String configJson, Map updatedConfig) throws Exception { - if (updatedConfig != null && updatedConfig.size() > 0) { - lastSavedConfigHash = calculateMd5(configJson); - String updatedConfigJson = JsonUtil.convertToString(updatedConfig); - updateConfigHash = calculateMd5(updatedConfigJson); - if (updateConfigHash != lastSavedConfigHash) { - lastSavedConfigHash = updateConfigHash; - configJson = updatedConfigJson; - configMap = JsonUtil.convertToMap(configJson); - } - byte[] encryptedData = encryptBuffer(configJson); - logger.debug("Encrypted json content."); - Files.write(Paths.get(configFileLocation), encryptedData); - } - } - - /** - * Decrypt the encrypted config, autosave=true/false - * - * @param autosave Set to {@code true} to save the ksm configuration json file - * as plaintext, and {@code false} to retrieve only the - * plaintext of the KSM configuration. - * @return The decrypted configuration as a String. - * @throws Exception Throws Exception, if any error occurs during decryption. - */ - public String decryptConfig(boolean autosave) throws Exception { - String decryptedContent = null; - if (!JsonUtil.isValidJsonFile(configFileLocation)) { - decryptedContent = decryptBuffer(readEncryptedJsonFile()); - if (autosave) { - Path path = Paths.get(configFileLocation); - if (Files.exists(path)) - Files.write(path, decryptedContent.getBytes(StandardCharsets.UTF_8)); - logger.info("Decrypted KSM config saved into file success."); - } - return decryptedContent; - } else { - logger.info("KSM config is plain json only."); - return null; - } - } - - private byte[] readEncryptedJsonFile() throws Exception { - Path path = Paths.get(configFileLocation); - if (!Files.exists(path)) { - createConfigFileIfMissing(); - } - return Files.readAllBytes(path); - - } - - /** - * - * @param stream - * @param data - * @throws IOException - */ - private void writeLengthPrefixed(ByteArrayOutputStream stream, byte[] data) throws IOException { - stream.write((data.length >> 8) & 0xFF); - stream.write(data.length & 0xFF); - stream.write(data); - } - - /** - * Generate GCM Cipher - * - * @param mode - * @param iv - * @param key - * @return - * @throws NoSuchPaddingException - * @throws NoSuchAlgorithmException - * @throws InvalidAlgorithmParameterException - * @throws InvalidKeyException - * @throws InvalidKeySpecException - */ - private Cipher getGCMCipher(int mode, byte[] iv, byte[] key) throws NoSuchPaddingException, - NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidKeySpecException { - - Cipher cipher = Cipher.getInstance(Constants.AES_GCM); - GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(Constants.GCM_TAG_LENGTH, iv); - SecretKeySpec keySpec = new SecretKeySpec(key, Constants.AES); - cipher.init(mode, keySpec, gcmParameterSpec); - return cipher; - } - - private byte[] encryptBuffer(String message) throws Exception { - if (kmsClient.isSymmetricKey()) { - byte[] encrypted = kmsClient.encryptSymmetric(message).toByteArray(); - ByteArrayOutputStream blob = new ByteArrayOutputStream(); - writeLengthPrefixed(blob, encrypted); - return blob.toByteArray(); - } else if (kmsClient.isKeyRAWSymmteric()) { - byte[] nonce = new byte[Constants.BLOCK_SIZE]; - byte[] key = new byte[Constants.KEY_SIZE]; - Cipher cipher = getGCMCipher(Cipher.ENCRYPT_MODE, key, nonce); - byte[] ciphertext = cipher.doFinal(message.getBytes()); - byte[] tag = cipher.getIV(); - String token = getOAuthToken(sessionConfig.getCredentialsPath(), Constants.CLOUD_API_URL); - EncryptResponse encryptedRawResponse = kmsClient.encryptRawSymmetric(this.sessionConfig, key, token); - ByteArrayOutputStream blob = new ByteArrayOutputStream(); - blob.write(Constants.BLOB_HEADER); - writeLengthPrefixed(blob, encryptedRawResponse.getCiphertext().getBytes()); - writeLengthPrefixed(blob, encryptedRawResponse.getInitializeVector().getBytes()); - writeLengthPrefixed(blob, nonce); - writeLengthPrefixed(blob, tag); - writeLengthPrefixed(blob, ciphertext); - return blob.toByteArray(); - } else { - byte[] nonce = new byte[Constants.BLOCK_SIZE]; - byte[] key = new byte[Constants.KEY_SIZE]; - Cipher cipher = getGCMCipher(Cipher.ENCRYPT_MODE, key, nonce); - byte[] ciphertext = cipher.doFinal(message.getBytes()); - - byte[] tag = cipher.getIV(); - byte[] encryptedKey = kmsClient.encryptAsymmetricRsa(key); - - ByteArrayOutputStream blob = new ByteArrayOutputStream(); - blob.write(Constants.BLOB_HEADER); - writeLengthPrefixed(blob, encryptedKey); - writeLengthPrefixed(blob, nonce); - writeLengthPrefixed(blob, tag); - writeLengthPrefixed(blob, ciphertext); - System.out.println(blob); - return blob.toByteArray(); - } - } - - /** - * - * @param encryptedData - * @return - * @throws Exception - */ - private String decryptBuffer(byte[] encryptedData) throws Exception { - if (kmsClient.isSymmetricKey()) { - ByteArrayInputStream blobInputStream = new ByteArrayInputStream(encryptedData); - byte[] encrypted = readLengthPrefixed(blobInputStream); - return kmsClient.decryptSymmetric(ByteString.copyFrom(encrypted)); - } else if (kmsClient.isKeyRAWSymmteric()) { - ByteArrayInputStream blobInputStream = new ByteArrayInputStream(encryptedData); - - byte[] header = new byte[Constants.BLOB_HEADER.length]; - blobInputStream.read(header); - if (!MessageDigest.isEqual(header, Constants.BLOB_HEADER)) { - throw new IllegalArgumentException("Invalid blob header"); - } - byte[] decryptedKey = readLengthPrefixed(blobInputStream); - byte[] initializationVector = readLengthPrefixed(blobInputStream); - byte[] nonce = readLengthPrefixed(blobInputStream); - byte[] tag = readLengthPrefixed(blobInputStream); - byte[] ciphertext = readLengthPrefixed(blobInputStream); - - String token = getOAuthToken(sessionConfig.getCredentialsPath(), Constants.CLOUD_API_URL); - byte[] key = kmsClient.decryptRawSymmetric(this.sessionConfig, decryptedKey, initializationVector, token); - Cipher cipher = getGCMCipher(Cipher.DECRYPT_MODE, key, nonce); - byte[] decryptedMessage = cipher.doFinal(ciphertext); - return new String(decryptedMessage, StandardCharsets.UTF_8); - } else { - ByteArrayInputStream blobInputStream = new ByteArrayInputStream(encryptedData); - - byte[] header = new byte[Constants.BLOB_HEADER.length]; - blobInputStream.read(header); - if (!MessageDigest.isEqual(header, Constants.BLOB_HEADER)) { - throw new IllegalArgumentException("Invalid blob header"); - } - byte[] encryptedKey = readLengthPrefixed(blobInputStream); - byte[] nonce = readLengthPrefixed(blobInputStream); - byte[] tag = readLengthPrefixed(blobInputStream); - byte[] ciphertext = readLengthPrefixed(blobInputStream); - // Decrypt the AES key using RSA (unwrap the key) - byte[] key = kmsClient.decryptAsymmetricRsa(encryptedKey); - - Cipher cipher = getGCMCipher(Cipher.DECRYPT_MODE, key, nonce); - - byte[] decryptedMessage = cipher.doFinal(ciphertext); - return new String(decryptedMessage, StandardCharsets.UTF_8); - } - } - - private byte[] readLengthPrefixed(InputStream stream) throws IOException { - int length = (stream.read() << 8) | stream.read(); - byte[] data = new byte[length]; - stream.read(data); - return data; - } - - /** - * - * @throws Exception - */ - private void createConfigFileIfMissing() throws Exception { - Path path = Paths.get(configFileLocation); - if (!Files.exists(path)) { - Files.write(path, encryptBuffer("{}")); - } - } - - private String calculateMd5(String input) throws Exception { - MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8)); - return Base64.getEncoder().encodeToString(digest); - } - - private byte[] base64ToBytes(String base64String) { - if (base64String == null || base64String.isEmpty()) { - return null; - } - return Base64.getDecoder().decode(base64String); - } - - private String bytesToBase64(byte[] data) { - return Base64.getEncoder().encodeToString(data); - } - - @Override - public void delete(String key) { - if (configMap.isEmpty()) { - try { - loadConfig(); - } catch (Exception e) { - logger.error("Failed to load config file.", e); - } - } - configMap.remove(key); - saveConfig(configMap); - } - - @Override - public byte[] getBytes(String key) { - if (configMap.get(key) == null) - return null; - return base64ToBytes(configMap.get(key).toString()); - } - - @Override - public String getString(String key) { - if (configMap.isEmpty()) { - try { - loadConfig(); - } catch (Exception e) { - logger.error("Failed to load config file.", e); - } - } - if (configMap.get(key) == null) - return null; - return configMap.get(key).toString(); - } - - @Override - public void saveBytes(String key, byte[] value) { - if (configMap.isEmpty()) { - try { - loadConfig(); - } catch (Exception e) { - logger.error("Failed to load config file.", e); - } - } - configMap.put(key, bytesToBase64(value)); - saveConfig(configMap); - } - - @Override - public void saveString(String key, String value) { - if (configMap.isEmpty()) { - try { - loadConfig(); - } catch (Exception e) { - logger.error("Failed to load config file.", e); - } - } - configMap.put(key, value); - saveConfig(configMap); - } - - @Override - public String toString() { - if (configMap.isEmpty()) { - try { - loadConfig(); - } catch (Exception e) { - logger.error("Failed to load config file.", e); - } - } - try { - return JsonUtil.convertToString(configMap); - } catch (JsonProcessingException e) { - logger.error("Exception: " + e.getMessage()); - } - return null; - } - -} \ No newline at end of file diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/main/java/com/keepersecurity/secretsManager/storage/gcp/GcpSessionConfig.java b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/main/java/com/keepersecurity/secretsManager/storage/gcp/GcpSessionConfig.java deleted file mode 100644 index 0c54e62cf..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/main/java/com/keepersecurity/secretsManager/storage/gcp/GcpSessionConfig.java +++ /dev/null @@ -1,218 +0,0 @@ -package com.keepersecurity.secretsManager.storage.gcp; - -/* -* _ __ -* | |/ /___ ___ _ __ ___ _ _ (R) -* | ' convertToMap(String content) throws JsonProcessingException { - return objectMapper.readValue(content, new TypeReference>() { - }); - } - - /** - * Convert Map to String - * - * @param configMap Map Object to be converted to String - * @return String representation of the Map - * @throws JsonProcessingException Throws JsonProcessingException if the - * conversion fails. - */ - public static String convertToString(Map configMap) throws JsonProcessingException { - return objectMapper.writeValueAsString(configMap); - } -} diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/main/java/com/keepersecurity/secretsManager/storage/gcp/KMSUtils.java b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/main/java/com/keepersecurity/secretsManager/storage/gcp/KMSUtils.java deleted file mode 100644 index 95803f3ba..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/main/java/com/keepersecurity/secretsManager/storage/gcp/KMSUtils.java +++ /dev/null @@ -1,456 +0,0 @@ -package com.keepersecurity.secretsManager.storage.gcp; - -/* -* _ __ -* | |/ /___ ___ _ __ ___ _ _ (R) -* | ' rsaAlgorithmToSHA = new HashMap<>(); - - static { - // Initialize the mapping of algorithms to SHA types - rsaAlgorithmToSHA.put("RSA_DECRYPT_OAEP_2048_SHA256", "SHA-256"); - rsaAlgorithmToSHA.put("RSA_DECRYPT_OAEP_3072_SHA256", "SHA-256"); - rsaAlgorithmToSHA.put("RSA_DECRYPT_OAEP_4096_SHA256", "SHA-256"); - rsaAlgorithmToSHA.put("RSA_DECRYPT_OAEP_4096_SHA512", "SHA-512"); - rsaAlgorithmToSHA.put("RSA_DECRYPT_OAEP_2048_SHA1", "SHA-1"); - rsaAlgorithmToSHA.put("RSA_DECRYPT_OAEP_3072_SHA1", "SHA-1"); - rsaAlgorithmToSHA.put("RSA_DECRYPT_OAEP_4096_SHA1", "SHA-1"); - } - - /** - * Constructs a new {@code KMSUtils} object with the specified session - * configuration. - * - * @param sessionConfig The GCP session configuration. - */ - public KMSUtils(GcpSessionConfig sessionConfig) { - // Store configuration but don't initialize client until needed - this.sessionConfig = sessionConfig; - } - - /** - * Lazily initializes the KMS client when first needed. - * - * @throws Exception if client initialization fails - */ - private void initializeClientIfNeeded() throws Exception { - if (!clientInitialized && sessionConfig != null) { - if (sessionConfig.getCredentialsPath().isEmpty()) { - // Create the KMS client using Environment variable - kmsClient = KeyManagementServiceClient.create(); - } else { - // Load the credentials from the JSON key file - GoogleCredentials credentials = GoogleCredentials - .fromStream(new FileInputStream(sessionConfig.getCredentialsPath())); - - // Create the KeyManagementServiceSettings using the credentials - KeyManagementServiceSettings kmsSettings = KeyManagementServiceSettings.newBuilder() - .setCredentialsProvider(() -> credentials) // Provide credentials to the client - .build(); - - // Create the KeyManagementServiceClient with the specified settings - kmsClient = KeyManagementServiceClient.create(kmsSettings); - } - clientInitialized = true; - } - } - - /** - * Sets the key ID for the KMS client. - * - * @param newKeyId The new key ID to set. - */ - public void setKeyId(String newKeyId) { - this.sessionConfig.setKeyId(newKeyId); - } - - /** - * Gets the key ID for the KMS client. - * - * @return The key ID. - */ - public String getKeyId() { - return this.sessionConfig.getKeyId(); - } - - /** - * Encrypt data using an asymmetric RSA public key - * - * @param text Plaintext that needs to be encrypted using asymmetric key - * @return Encrypted text as a byte array - * @throws Exception Throws Exception, if any error occurs during encryption - */ - public byte[] encryptAsymmetricRsa(byte[] text) throws Exception { - logger.debug("Encrypt Using Asymmetric Key"); - initializeClientIfNeeded(); - - // Perform encryption and get the ciphertext - CryptoKeyVersionName keyVersionName = getCryptoKeyVersionName(); - // Get the public key. - PublicKey publicKey = kmsClient.getPublicKey(keyVersionName); - - // Convert the public PEM key to a DER key (see helper below). - byte[] derKey = convertPemToDer(publicKey.getPem()); - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derKey); - - // Generate RSA public key from DER - RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(keySpec); - - CryptoKeyVersionAlgorithm algorithms = getCryptoKeyVersionAlgorithm(); - - // Choose the appropriate OAEP padding algorithm based on the key size and hash - String hashAlgorithm = getSHA(algorithms.name()); - // Initialize cipher with the correct transformation - String transformation = "RSA/ECB/OAEPWith" + hashAlgorithm + "AndMGF1Padding"; - Cipher cipher = Cipher.getInstance(transformation); - - OAEPParameterSpec oaepParams = new OAEPParameterSpec(hashAlgorithm, "MGF1", - new MGF1ParameterSpec(hashAlgorithm), PSource.PSpecified.DEFAULT); - cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey, oaepParams); - return cipher.doFinal(text); - } - - /** - * Converts a base64-encoded PEM certificate like the one returned from Cloud - * KMS into a DER formatted certificate for use with the Java APIs. - * - * @param pem The PEM certificate to convert. - * @return The DER formatted certificate as a byte array. - */ - private byte[] convertPemToDer(String pem) { - BufferedReader bufferedReader = new BufferedReader(new StringReader(pem)); - String encoded = bufferedReader.lines() - .filter(line -> !line.startsWith("-----BEGIN") && !line.startsWith("-----END")) - .collect(Collectors.joining()); - return Base64.getDecoder().decode(encoded); - } - - /** - * Decrypt data using an asymmetric RSA private key - * - * @param ciphertext Encrypted text that needs to be decrypted using asymmetric - * key - * @return Decrypted text as a byte array - * @throws Exception Throws Exception, if any error occurs during decryption - */ - public byte[] decryptAsymmetricRsa(byte[] ciphertext) throws Exception { - logger.debug("Decrypt Using Asymmetric Key"); - initializeClientIfNeeded(); - // Perform encryption and get the ciphertext - CryptoKeyVersionName keyVersionName = getCryptoKeyVersionName(); - - // Decrypt the ciphertext. - AsymmetricDecryptResponse decryptedText = kmsClient.asymmetricDecrypt(keyVersionName, - ByteString.copyFrom(ciphertext)); - - // Convert the decrypted text back to String - return decryptedText.getPlaintext().toByteArray(); - } - - /** - * Encrypt data using a symmetric key - * - * @param plaintext Plaintext that needs to be encrypted using symmetric key - * @return Encrypted text as a byte array - * @throws Exception Throws Exception, if any error occurs during encryption - */ - public ByteString encryptSymmetric(String plaintext) throws Exception { - logger.debug("Encrypt Using Symmetric Key"); - initializeClientIfNeeded(); - // Convert plaintext to ByteString - ByteString plaintextByteString = ByteString.copyFrom(plaintext, StandardCharsets.UTF_8); - - // Encrypt the data - EncryptRequest encryptRequest = EncryptRequest.newBuilder().setName(getFullName()) - .setPlaintext(plaintextByteString).build(); - ByteString ciphertext = kmsClient.encrypt(encryptRequest).getCiphertext(); - - // Return encrypted text as a Base64 string - return ciphertext; - } - - /** - * Decrypt data using a symmetric key - * - * @param ciphertext Encrypted text that needs to be decrypted using symmetric - * key - * @return Decrypted text as a string - * @throws Exception Throws Exception, if any error occurs during decryption - */ - public String decryptSymmetric(ByteString ciphertext) throws Exception { - logger.debug("Decrypt Using Symmetric Key"); - initializeClientIfNeeded(); - // Decrypt the data - DecryptRequest decryptRequest = DecryptRequest.newBuilder().setName(getFullName()).setCiphertext(ciphertext) - .build(); - ByteString decryptedText = kmsClient.decrypt(decryptRequest).getPlaintext(); - - // Convert the decrypted text back to String - return decryptedText.toStringUtf8(); - } - - private String getFullName() { - return String.format("projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s", sessionConfig.getProjectId(), - sessionConfig.getLocation(), sessionConfig.getKeyRing(), sessionConfig.getKeyId()); - } - - /** - * Checks if the key is a symmetric key. - * - * @return true if the key is symmetric, false otherwise. - */ - public boolean isSymmetricKey() { - // Fetch the key version (use the primary version of the key) - CryptoKeyVersionAlgorithm algorithms = getCryptoKeyVersionAlgorithm(); - logger.debug("Encryption Algorithm :::" + algorithms.name()); - if (algorithms.name().contains("SYMMETRIC")) - return true; - - return false; - - } - - /** - * Checks if the key is a raw symmetric key. - * - * @return true if the key is a raw symmetric key, false otherwise. - */ - public boolean isKeyRAWSymmteric() { - CryptoKeyVersionAlgorithm algorithms = getCryptoKeyVersionAlgorithm(); - logger.debug("Encryption Algorithm :::" + algorithms.name()); - if (algorithms.name().contains("AES_")) { - return true; - } - return false; - } - - private String getSHA(String rsaAlgorithm) throws IllegalArgumentException { - String shaAlgorithm = rsaAlgorithmToSHA.get(rsaAlgorithm); - if (shaAlgorithm == null) { - throw new IllegalArgumentException("Unsupported RSA algorithm: " + rsaAlgorithm); - } - return shaAlgorithm; - } - - private CryptoKeyVersionAlgorithm getCryptoKeyVersionAlgorithm() { - try { - initializeClientIfNeeded(); - } catch (Exception e) { - logger.error("Failed to initialize KMS client: " + e.getMessage()); - throw new RuntimeException("KMS client initialization failed", e); - } - CryptoKeyVersionName keyVersionName = getCryptoKeyVersionName(); - CryptoKeyVersion cryptoKeyVersion = kmsClient.getCryptoKeyVersion(keyVersionName); - CryptoKeyVersionAlgorithm algorithms = cryptoKeyVersion.getAlgorithm(); - return algorithms; - } - - private CryptoKeyVersionName getCryptoKeyVersionName() { - CryptoKeyVersionName keyVersionName = CryptoKeyVersionName.of(sessionConfig.getProjectId(), - sessionConfig.getLocation(), sessionConfig.getKeyRing(), sessionConfig.getKeyId(), - sessionConfig.getKeyVersion()); - return keyVersionName; - } - - /** - * Encrypts data using a raw symmetric key. - * - * @param sessionConfig GCPSession Config - * @param message The message to encrypt. - * @param token The authentication token - * @return The encrypted response containing ciphertext and initialization - * vector. - * @throws Exception Throws Exception, If an error occurs during encryption. - */ - public EncryptResponse encryptRawSymmetric(GcpSessionConfig sessionConfig, byte[] message, String token) - throws Exception { - logger.debug("Encrypt Using Raw Symmetric Key"); - String encodeAAD = Base64.getEncoder().encodeToString(Constants.additionalAuthenticatedData); - String encodedMessage = Base64.getEncoder().encodeToString(message); - String apiUrl = String.format( - "https://cloudkms.googleapis.com/v1/projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s/cryptoKeyVersions/%s:rawEncrypt", - sessionConfig.getProjectId(), sessionConfig.getLocation(), sessionConfig.getKeyRing(), - sessionConfig.getKeyId(), sessionConfig.getKeyVersion()); - - String payload = String.format( - "{\"plaintext\": \"%s\", \"additionalAuthenticatedData\": \"%s\"}", - encodedMessage, encodeAAD); - - URL url = URI.create(apiUrl).toURL(); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("POST"); - connection.setRequestProperty("Authorization", "Bearer " + token); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setDoOutput(true); - - try (OutputStream os = connection.getOutputStream()) { - byte[] input = payload.getBytes(StandardCharsets.UTF_8); - os.write(input, 0, input.length); - } - - int responseCode = connection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { - logger.debug("Raw encryption successful"); - try (java.io.BufferedReader reader = new java.io.BufferedReader( - new java.io.InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { - StringBuilder response = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - response.append(line.trim()); - } - String responseJson = response.toString(); - logger.debug("Response JSON: " + responseJson); - JsonObject jsonObject = JsonParser.parseString(responseJson).getAsJsonObject(); - String ciphertext = jsonObject.get("ciphertext").getAsString(); - String initializeVector = jsonObject.get("initializationVector").getAsString(); - return new EncryptResponse(ciphertext, initializeVector); - } - } else { - logger.error("Raw encryption failed with HTTP code: " + responseCode); - try (java.io.BufferedReader reader = new java.io.BufferedReader( - new java.io.InputStreamReader(connection.getErrorStream(), StandardCharsets.UTF_8))) { - StringBuilder errorResponse = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - errorResponse.append(line.trim()); - } - logger.error("Error Response: " + errorResponse); - } - } - - throw new IOException("Failed to perform raw encryption. HTTP code: " + responseCode); - } - - /** - * Decrypts data using a raw symmetric key. - * - * @param sessionConfig GCP Session Config - * @param ciphertext The ciphertext to decrypt. - * @param initializationVector The initialization vector used during encryption. - * @param token The authentication token. - * @return The decrypted plaintext as a byte array. - * @throws Exception Throws Exception, If an error occurs during decryption. - */ - public byte[] decryptRawSymmetric(GcpSessionConfig sessionConfig, byte[] ciphertext, byte[] initializationVector, - String token) throws Exception { - logger.debug("Decrypt Using Raw Symmetric Key"); - byte[] decodedCiphertext = Base64.getDecoder().decode(ciphertext); - byte[] decodedInitializationVector = Base64.getDecoder().decode(initializationVector); - String encodeAAD = Base64.getEncoder().encodeToString(Constants.additionalAuthenticatedData); - String encodedCiphertext = Base64.getEncoder().encodeToString(decodedCiphertext); - String initializationVectorBase64 = Base64.getEncoder().encodeToString(decodedInitializationVector); - String apiUrl = String.format( - "https://cloudkms.googleapis.com/v1/projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s/cryptoKeyVersions/%s:rawDecrypt", - sessionConfig.getProjectId(), sessionConfig.getLocation(), sessionConfig.getKeyRing(), - sessionConfig.getKeyId(), sessionConfig.getKeyVersion()); - - String payload = String.format( - "{\"ciphertext\": \"%s\", \"additionalAuthenticatedData\": \"%s\", \"initializationVector\": \"%s\"}", - encodedCiphertext, encodeAAD, initializationVectorBase64); - - URL url = URI.create(apiUrl).toURL(); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("POST"); - connection.setRequestProperty("Authorization", "Bearer " + token); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setDoOutput(true); - - try (OutputStream os = connection.getOutputStream()) { - byte[] input = payload.getBytes(StandardCharsets.UTF_8); - os.write(input, 0, input.length); - } - - int responseCode = connection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { - logger.debug("Raw decryption successful"); - try (java.io.BufferedReader reader = new java.io.BufferedReader( - new java.io.InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { - StringBuilder response = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - response.append(line.trim()); - } - - String responseJson = response.toString(); - logger.debug("Response JSON: " + responseJson); - JsonObject jsonObject = JsonParser.parseString(responseJson).getAsJsonObject(); - String plaintext = jsonObject.get("plaintext").getAsString(); - return Base64.getDecoder().decode(plaintext); - } - } else { - logger.error("Raw decryption failed with HTTP code: " + responseCode); - try (java.io.BufferedReader reader = new java.io.BufferedReader( - new java.io.InputStreamReader(connection.getErrorStream(), StandardCharsets.UTF_8))) { - StringBuilder errorResponse = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - errorResponse.append(line.trim()); - } - logger.error("Error Response: " + errorResponse); - } - } - - throw new IOException("Failed to perform raw decryption. HTTP code: " + responseCode); - } -} diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/test/java/com/keepersecurity/secretsManager/storage/gcp/GcpSessionConfigTest.java b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/test/java/com/keepersecurity/secretsManager/storage/gcp/GcpSessionConfigTest.java deleted file mode 100644 index 91eae63c7..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/test/java/com/keepersecurity/secretsManager/storage/gcp/GcpSessionConfigTest.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.keepersecurity.secretsManager.storage.gcp; - -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -/** - * Unit tests for GcpSessionConfig class - */ -class GcpSessionConfigTest { - - @Test - void testParameterizedConstructor() { - // Given - String projectId = "test-project-123"; - String location = "us-central1"; - String keyRing = "test-keyring"; - String keyId = "test-key"; - String keyVersion = "1"; - String credentialsPath = "/path/to/credentials.json"; - - // When - GcpSessionConfig config = new GcpSessionConfig( - projectId, location, keyRing, keyId, keyVersion, credentialsPath - ); - - // Then - assertEquals(projectId, config.getProjectId(), "ProjectId should match constructor parameter"); - assertEquals(location, config.getLocation(), "Location should match constructor parameter"); - assertEquals(keyRing, config.getKeyRing(), "KeyRing should match constructor parameter"); - assertEquals(keyId, config.getKeyId(), "KeyId should match constructor parameter"); - assertEquals(keyVersion, config.getKeyVersion(), "KeyVersion should match constructor parameter"); - assertEquals(credentialsPath, config.getCredentialsPath(), "CredentialsPath should match constructor parameter"); - } - - @Test - void testSettersAndGetters() { - // Given - GcpSessionConfig config = new GcpSessionConfig( - "initial-project", "initial-location", "initial-keyring", - "initial-key", "1", "/initial/path" - ); - - String newProjectId = "new-project-456"; - String newLocation = "europe-west1"; - String newKeyRing = "production-keyring"; - String newKeyId = "production-key"; - String newKeyVersion = "2"; - String newCredentialsPath = "/secure/credentials.json"; - - // When - config.setProjectId(newProjectId); - config.setLocation(newLocation); - config.setKeyRing(newKeyRing); - config.setKeyId(newKeyId); - config.setKeyVersion(newKeyVersion); - config.setCredentialsPath(newCredentialsPath); - - // Then - assertEquals(newProjectId, config.getProjectId(), "ProjectId getter should return value set by setter"); - assertEquals(newLocation, config.getLocation(), "Location getter should return value set by setter"); - assertEquals(newKeyRing, config.getKeyRing(), "KeyRing getter should return value set by setter"); - assertEquals(newKeyId, config.getKeyId(), "KeyId getter should return value set by setter"); - assertEquals(newKeyVersion, config.getKeyVersion(), "KeyVersion getter should return value set by setter"); - assertEquals(newCredentialsPath, config.getCredentialsPath(), "CredentialsPath getter should return value set by setter"); - } - - @Test - void testSettersWithNullValues() { - // Given - GcpSessionConfig config = new GcpSessionConfig( - "initial-project", "initial-location", "initial-keyring", - "initial-key", "1", "/initial/path" - ); - - // When - set all to null - config.setProjectId(null); - config.setLocation(null); - config.setKeyRing(null); - config.setKeyId(null); - config.setKeyVersion(null); - config.setCredentialsPath(null); - - // Then - assertNull(config.getProjectId(), "ProjectId should be null after setting to null"); - assertNull(config.getLocation(), "Location should be null after setting to null"); - assertNull(config.getKeyRing(), "KeyRing should be null after setting to null"); - assertNull(config.getKeyId(), "KeyId should be null after setting to null"); - assertNull(config.getKeyVersion(), "KeyVersion should be null after setting to null"); - assertNull(config.getCredentialsPath(), "CredentialsPath should be null after setting to null"); - } - - @Test - void testSettersWithEmptyStrings() { - // Given - GcpSessionConfig config = new GcpSessionConfig( - "initial-project", "initial-location", "initial-keyring", - "initial-key", "1", "/initial/path" - ); - - // When - config.setProjectId(""); - config.setLocation(""); - config.setKeyRing(""); - config.setKeyId(""); - config.setKeyVersion(""); - config.setCredentialsPath(""); - - // Then - assertEquals("", config.getProjectId(), "ProjectId should handle empty string"); - assertEquals("", config.getLocation(), "Location should handle empty string"); - assertEquals("", config.getKeyRing(), "KeyRing should handle empty string"); - assertEquals("", config.getKeyId(), "KeyId should handle empty string"); - assertEquals("", config.getKeyVersion(), "KeyVersion should handle empty string"); - assertEquals("", config.getCredentialsPath(), "CredentialsPath should handle empty string"); - } - - @Test - void testConstructorWithNullValues() { - // When/Then - constructor should accept null values - assertDoesNotThrow(() -> { - GcpSessionConfig config = new GcpSessionConfig(null, null, null, null, null, null); - assertNull(config.getProjectId()); - assertNull(config.getLocation()); - assertNull(config.getKeyRing()); - assertNull(config.getKeyId()); - assertNull(config.getKeyVersion()); - assertNull(config.getCredentialsPath()); - }, "Constructor should accept null values without throwing exception"); - } - - @Test - void testKeyVersionHandling() { - // Test different key version formats - String[] keyVersions = {"1", "2", "10", "latest"}; - - for (String keyVersion : keyVersions) { - // Given/When - GcpSessionConfig config = new GcpSessionConfig( - "test-project", "us-central1", "test-keyring", "test-key", keyVersion, "/path/to/credentials.json" - ); - - // Then - assertEquals(keyVersion, config.getKeyVersion(), - "Should accept key version: " + keyVersion); - } - } - - @Test - void testLocationFormats() { - // Test various GCP location formats - String[] locations = { - "us-central1", "us-east1", "us-west1", - "europe-west1", "europe-central2", - "asia-southeast1", "asia-northeast1", - "global" - }; - - for (String location : locations) { - // Given/When - GcpSessionConfig config = new GcpSessionConfig( - "test-project", location, "test-keyring", "test-key", "1", "/path/to/credentials.json" - ); - - // Then - assertEquals(location, config.getLocation(), - "Should accept location format: " + location); - } - } - - @Test - void testCredentialsPathFormats() { - // Test various credential file paths - String[] credentialsPaths = { - "/path/to/credentials.json", - "./credentials.json", - "../config/gcp-credentials.json", - "/absolute/path/to/service-account.json", - "" // empty string for environment variable credentials - }; - - for (String credentialsPath : credentialsPaths) { - // Given/When - GcpSessionConfig config = new GcpSessionConfig( - "test-project", "us-central1", "test-keyring", "test-key", "1", credentialsPath - ); - - // Then - assertEquals(credentialsPath, config.getCredentialsPath(), - "Should accept credentials path: " + credentialsPath); - } - } -} \ No newline at end of file diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/test/java/com/keepersecurity/secretsManager/storage/gcp/JsonUtilTest.java b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/test/java/com/keepersecurity/secretsManager/storage/gcp/JsonUtilTest.java deleted file mode 100644 index f06cc4a8d..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/test/java/com/keepersecurity/secretsManager/storage/gcp/JsonUtilTest.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.keepersecurity.secretsManager.storage.gcp; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import static org.junit.jupiter.api.Assertions.*; -import com.fasterxml.jackson.core.JsonProcessingException; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Map; -import java.util.HashMap; - -/** - * Unit tests for JsonUtil class - */ -class JsonUtilTest { - - @Test - void testIsValidJsonFile_ValidJson(@TempDir Path tempDir) throws IOException { - // Given - Path jsonFile = tempDir.resolve("valid.json"); - String validJson = "{\"key\": \"value\", \"number\": 123}"; - Files.write(jsonFile, validJson.getBytes()); - - // When - boolean isValid = JsonUtil.isValidJsonFile(jsonFile.toString()); - - // Then - assertTrue(isValid, "Valid JSON file should return true"); - } - - @Test - void testIsValidJsonFile_InvalidJson(@TempDir Path tempDir) throws IOException { - // Given - Path jsonFile = tempDir.resolve("invalid.json"); - String invalidJson = "{\"key\": \"value\", \"missing_quote: 123}"; - Files.write(jsonFile, invalidJson.getBytes()); - - // When - boolean isValid = JsonUtil.isValidJsonFile(jsonFile.toString()); - - // Then - assertFalse(isValid, "Invalid JSON file should return false"); - } - - @Test - void testIsValidJsonFile_NonexistentFile() { - // Given - String nonexistentFile = "/path/that/does/not/exist.json"; - - // When - boolean isValid = JsonUtil.isValidJsonFile(nonexistentFile); - - // Then - assertFalse(isValid, "Nonexistent file should return false"); - } - - @Test - void testIsValidJson_ValidJsonString() { - // Given - String validJson = "{\"key\": \"value\", \"number\": 123, \"array\": [1, 2, 3]}"; - - // When - boolean isValid = JsonUtil.isValidJson(validJson); - - // Then - assertTrue(isValid, "Valid JSON string should return true"); - } - - @Test - void testIsValidJson_InvalidJsonString() { - // Given - String invalidJson = "{\"key\": \"value\", \"missing_quote: 123}"; - - // When - boolean isValid = JsonUtil.isValidJson(invalidJson); - - // Then - assertFalse(isValid, "Invalid JSON string should return false"); - } - - @Test - void testIsValidJson_EmptyString() { - // Given - String emptyJson = ""; - - // When - boolean isValid = JsonUtil.isValidJson(emptyJson); - - // Then - assertTrue(isValid, "Empty string is considered valid JSON by Gson parser"); - } - - @Test - void testConvertToMap_ValidJson() throws JsonProcessingException { - // Given - String jsonString = "{\"hostname\": \"keepersecurity.com\", \"clientId\": \"test123\", \"port\": 443}"; - - // When - Map result = JsonUtil.convertToMap(jsonString); - - // Then - assertNotNull(result, "Result should not be null"); - assertEquals("keepersecurity.com", result.get("hostname")); - assertEquals("test123", result.get("clientId")); - assertEquals(443, result.get("port")); - assertEquals(3, result.size()); - } - - @Test - void testConvertToMap_InvalidJson() { - // Given - String invalidJson = "{\"key\": \"value\", \"missing_quote: 123}"; - - // When/Then - assertThrows(JsonProcessingException.class, () -> { - JsonUtil.convertToMap(invalidJson); - }, "Invalid JSON should throw JsonProcessingException"); - } - - @Test - void testConvertToString_ValidMap() throws JsonProcessingException { - // Given - Map configMap = new HashMap<>(); - configMap.put("hostname", "keepersecurity.com"); - configMap.put("clientId", "test123"); - configMap.put("port", 443); - - // When - String result = JsonUtil.convertToString(configMap); - - // Then - assertNotNull(result, "Result should not be null"); - assertTrue(result.contains("\"hostname\":\"keepersecurity.com\""), "Should contain hostname"); - assertTrue(result.contains("\"clientId\":\"test123\""), "Should contain clientId"); - assertTrue(result.contains("\"port\":443"), "Should contain port"); - } - - @Test - void testConvertToString_EmptyMap() throws JsonProcessingException { - // Given - Map emptyMap = new HashMap<>(); - - // When - String result = JsonUtil.convertToString(emptyMap); - - // Then - assertNotNull(result, "Result should not be null"); - assertEquals("{}", result, "Empty map should convert to empty JSON object"); - } - - @Test - void testJsonRoundTrip() throws JsonProcessingException { - // Given - Map originalMap = new HashMap<>(); - originalMap.put("hostname", "keepersecurity.com"); - originalMap.put("clientId", "test123"); - originalMap.put("port", 443); - originalMap.put("enabled", true); - - // When - String jsonString = JsonUtil.convertToString(originalMap); - Map resultMap = JsonUtil.convertToMap(jsonString); - - // Then - assertEquals(originalMap.size(), resultMap.size(), "Maps should have same size"); - assertEquals(originalMap.get("hostname"), resultMap.get("hostname")); - assertEquals(originalMap.get("clientId"), resultMap.get("clientId")); - assertEquals(originalMap.get("port"), resultMap.get("port")); - assertEquals(originalMap.get("enabled"), resultMap.get("enabled")); - } -} \ No newline at end of file diff --git a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/test/java/com/keepersecurity/secretsManager/storage/gcp/KMSUtilsTest.java b/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/test/java/com/keepersecurity/secretsManager/storage/gcp/KMSUtilsTest.java deleted file mode 100644 index 239b4aaa5..000000000 --- a/sdk/java/storage/keeper_secrets_manager_storage_gcp_kms/src/test/java/com/keepersecurity/secretsManager/storage/gcp/KMSUtilsTest.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.keepersecurity.secretsManager.storage.gcp; - -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -/** - * Unit tests for KMSUtils class - * Note: These tests focus on object instantiation and basic functionality - * without making actual HTTP calls to GCP KMS services. - */ -class KMSUtilsTest { - - @Test - void testKMSUtilsConstructorWithValidConfig() { - // Given - GcpSessionConfig config = new GcpSessionConfig( - "test-project", - "us-central1", - "test-keyring", - "test-key", - "1", - "" // Empty string to use environment credentials (won't make actual calls) - ); - - // When/Then - Constructor should not throw exception with valid config - assertDoesNotThrow(() -> { - KMSUtils kmsUtils = new KMSUtils(config); - assertNotNull(kmsUtils, "KMSUtils instance should be created successfully"); - }, "KMSUtils constructor should not throw exception with valid config"); - } - - @Test - void testKMSUtilsConstructorWithNullConfig() { - // When/Then - Constructor with null config should handle gracefully - assertDoesNotThrow(() -> { - KMSUtils kmsUtils = new KMSUtils(null); - // Constructor might handle null config gracefully or store null - assertNotNull(kmsUtils, "KMSUtils instance should still be created"); - }, "KMSUtils constructor should handle null config gracefully"); - } - - @Test - void testSetAndGetKeyId() { - // Given - GcpSessionConfig config = new GcpSessionConfig( - "test-project", "us-central1", "test-keyring", "original-key", "1", "" - ); - KMSUtils kmsUtils = new KMSUtils(config); - String newKeyId = "new-test-key"; - - // When - kmsUtils.setKeyId(newKeyId); - String retrievedKeyId = kmsUtils.getKeyId(); - - // Then - assertEquals(newKeyId, retrievedKeyId, "Retrieved keyId should match the set keyId"); - } - - @Test - void testGetKeyIdWithOriginalValue() { - // Given - String originalKeyId = "original-key-id"; - GcpSessionConfig config = new GcpSessionConfig( - "test-project", "us-central1", "test-keyring", originalKeyId, "1", "" - ); - KMSUtils kmsUtils = new KMSUtils(config); - - // When - String retrievedKeyId = kmsUtils.getKeyId(); - - // Then - assertEquals(originalKeyId, retrievedKeyId, "Retrieved keyId should match the original value"); - } - - @Test - void testSetKeyIdWithNullValue() { - // Given - GcpSessionConfig config = new GcpSessionConfig( - "test-project", "us-central1", "test-keyring", "original-key", "1", "" - ); - KMSUtils kmsUtils = new KMSUtils(config); - - // When - kmsUtils.setKeyId(null); - String retrievedKeyId = kmsUtils.getKeyId(); - - // Then - assertNull(retrievedKeyId, "Retrieved keyId should be null after setting to null"); - } - - @Test - void testSetKeyIdWithEmptyString() { - // Given - GcpSessionConfig config = new GcpSessionConfig( - "test-project", "us-central1", "test-keyring", "original-key", "1", "" - ); - KMSUtils kmsUtils = new KMSUtils(config); - - // When - kmsUtils.setKeyId(""); - String retrievedKeyId = kmsUtils.getKeyId(); - - // Then - assertEquals("", retrievedKeyId, "Retrieved keyId should be empty string after setting to empty string"); - } - - @Test - void testKMSUtilsWithDifferentConfigurations() { - // Test various configuration combinations - String[][] configCombinations = { - {"project1", "us-central1", "keyring1", "key1", "1", ""}, - {"project2", "europe-west1", "keyring2", "key2", "2", "/path/to/creds.json"}, - {"project3", "asia-southeast1", "keyring3", "key3", "latest", "./credentials.json"} - }; - - for (String[] combo : configCombinations) { - // Given - GcpSessionConfig config = new GcpSessionConfig( - combo[0], combo[1], combo[2], combo[3], combo[4], combo[5] - ); - - // When/Then - assertDoesNotThrow(() -> { - KMSUtils kmsUtils = new KMSUtils(config); - assertNotNull(kmsUtils, "KMSUtils should be created with config: " + String.join(", ", combo)); - assertEquals(combo[3], kmsUtils.getKeyId(), "KeyId should match config for: " + String.join(", ", combo)); - }, "Should handle configuration: " + String.join(", ", combo)); - } - } - - @Test - void testEncryptResponseClass() { - // Given - String ciphertext = "encrypted-data"; - String initVector = "init-vector"; - - // When - EncryptResponse response = new EncryptResponse(ciphertext, initVector); - - // Then - assertNotNull(response, "EncryptResponse should be created successfully"); - assertEquals(ciphertext, response.getCiphertext(), "Ciphertext should match"); - assertEquals(initVector, response.getInitializeVector(), "Initialize vector should match"); - } - - @Test - void testEncryptResponseSetters() { - // Given - EncryptResponse response = new EncryptResponse("original-cipher", "original-iv"); - String newCiphertext = "new-encrypted-data"; - String newInitVector = "new-init-vector"; - - // When - response.setCiphertext(newCiphertext); - response.setInitializeVector(newInitVector); - - // Then - assertEquals(newCiphertext, response.getCiphertext(), "Ciphertext should be updated"); - assertEquals(newInitVector, response.getInitializeVector(), "Initialize vector should be updated"); - } - - @Test - void testEncryptResponseWithNullValues() { - // When/Then - assertDoesNotThrow(() -> { - EncryptResponse response = new EncryptResponse(null, null); - assertNull(response.getCiphertext(), "Ciphertext should be null"); - assertNull(response.getInitializeVector(), "Initialize vector should be null"); - }, "EncryptResponse should handle null values"); - } -} \ No newline at end of file diff --git a/sdk/javascript/packages/core/README.md b/sdk/javascript/packages/core/README.md index f0b16419d..9d4201c25 100644 --- a/sdk/javascript/packages/core/README.md +++ b/sdk/javascript/packages/core/README.md @@ -4,12 +4,6 @@ For more information see our official documentation page https://docs.keeper.io/ # Change Log -## 17.2.0 -- KSM-581: Added GraphSync library to read GraphSync links - -## 17.1.0 -- KSM-588: Enhance JS SDK to enable editing of external shares - ## 17.0.0 - KSM-574 - Replace Node.js Buffer with Browser-Compatible Alternative diff --git a/sdk/javascript/packages/core/package-lock.json b/sdk/javascript/packages/core/package-lock.json index 7905354f9..861b9eeb6 100644 --- a/sdk/javascript/packages/core/package-lock.json +++ b/sdk/javascript/packages/core/package-lock.json @@ -1,12 +1,12 @@ { "name": "@keeper-security/secrets-manager-core", - "version": "17.2.0", + "version": "17.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@keeper-security/secrets-manager-core", - "version": "17.2.0", + "version": "17.0.0", "license": "MIT", "devDependencies": { "@babel/core": "^7.25.2", diff --git a/sdk/javascript/packages/core/package.json b/sdk/javascript/packages/core/package.json index 2d7bb4180..2c1267695 100644 --- a/sdk/javascript/packages/core/package.json +++ b/sdk/javascript/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@keeper-security/secrets-manager-core", - "version": "17.2.0", + "version": "17.0.0", "description": "Keeper Secrets Manager Javascript SDK", "browser": "dist/index.es.js", "main": "dist/index.cjs.js", diff --git a/sdk/javascript/packages/core/src/browser/browserPlatform.ts b/sdk/javascript/packages/core/src/browser/browserPlatform.ts index bf28aecc0..6c732f071 100644 --- a/sdk/javascript/packages/core/src/browser/browserPlatform.ts +++ b/sdk/javascript/packages/core/src/browser/browserPlatform.ts @@ -2,15 +2,17 @@ import {KeeperHttpResponse, KeyValueStorage, Platform} from '../platform' import {privateDerToPublicRaw} from '../utils' const bytesToBase64 = (data: Uint8Array): string => { - const chunkSize = 0x8000 // String.fromCharCode has limitations + const chunkSize = 0x10000 // max size accepted by String.fromCharCode if (data.length <= chunkSize) { + // @ts-ignore return btoa(String.fromCharCode(...data)) } - const chunks: string[] = [] - for (let i = 0; i < data.length; i += chunkSize) { - chunks.push(String.fromCharCode(...data.subarray(i, i + chunkSize))) + let chunks: string = '' + for (let i = 0; i < data.length; i = i + chunkSize) { + // @ts-ignore + chunks = chunks + String.fromCharCode(...data.slice(i, i + chunkSize)) } - return btoa(chunks.join('')) + return btoa(chunks) } const base64ToBytes = (data: string): Uint8Array => Uint8Array.from(atob(data), c => c.charCodeAt(0)) @@ -206,10 +208,7 @@ const __encrypt = async (data: Uint8Array, key: CryptoKey, useCBC?: boolean): Pr name: algorithmName, iv: iv }, key, data) - const encrypted = new Uint8Array(iv.length + res.byteLength) - encrypted.set(iv, 0) - encrypted.set(new Uint8Array(res), iv.length) - return encrypted + return Uint8Array.of(...iv, ...new Uint8Array(res)) } const unwrap = async (key: Uint8Array, keyId: string, unwrappingKeyId: string, storage?: KeyValueStorage, memoryOnly?: boolean, useCBC?: boolean): Promise => { @@ -335,35 +334,37 @@ const post = async ( } } -const fileUpload = async ( +const fileUpload = ( url: string, uploadParameters: { [key: string]: string }, - data: Uint8Array -): Promise => { + data: Blob +): Promise => new Promise((resolve, reject) => { const form = new FormData(); for (const key in uploadParameters) { form.append(key, uploadParameters[key]); } - form.append('file', new Blob([data], {type: 'application/octet-stream'})); + form.append('file', data) const fetchCfg = { - method: 'POST', + method: 'PUT', body: form, - }; - - try { - const res = await fetch(url, fetchCfg); - return { - headers: res.headers, - statusCode: res.status, - statusMessage: res.statusText - }; - } catch (error) { - console.error('Error uploading file:', error); - throw error; } -}; + + fetch(url, fetchCfg) + .then(response => response.json()) + .then(res => { + resolve({ + headers: res.headers, + statusCode: res.statusCode, + statusMessage: res.statusMessage + }) + }) + .catch(error => { + console.error('Error uploading file:', error); + reject(error) + }); +}) const cleanKeyCache = () => { for (const key in keyCache) { diff --git a/sdk/javascript/packages/core/src/browser/localConfigStorage.ts b/sdk/javascript/packages/core/src/browser/localConfigStorage.ts index 9add10636..c509180f0 100644 --- a/sdk/javascript/packages/core/src/browser/localConfigStorage.ts +++ b/sdk/javascript/packages/core/src/browser/localConfigStorage.ts @@ -23,11 +23,6 @@ export const localConfigStorage = (client: string, useObjects: boolean): KeyValu })) } - const getBytes = async (key: string): Promise => { - const value = await getValue(key) - return typeof value === 'string' ? platform.base64ToBytes(value) : value; - } - const saveValue = async (key: string, value: any): Promise => { const objectStore = await getObjectStore('readwrite') return new Promise(((resolve, reject) => { @@ -51,7 +46,7 @@ export const localConfigStorage = (client: string, useObjects: boolean): KeyValu let storage: KeyValueStorage = { getString: getValue, saveString: saveValue, - getBytes: getBytes, + getBytes: getValue, saveBytes: saveValue, delete: deleteValue } diff --git a/sdk/javascript/packages/core/src/keeper.ts b/sdk/javascript/packages/core/src/keeper.ts index a66719bbe..384e1f5ec 100644 --- a/sdk/javascript/packages/core/src/keeper.ts +++ b/sdk/javascript/packages/core/src/keeper.ts @@ -48,7 +48,6 @@ export type SecretManagerOptions = { export type QueryOptions = { recordsFilter?: string[] foldersFilter?: string[] - requestLinks?: boolean } export type CreateOptions = { @@ -56,11 +55,6 @@ export type CreateOptions = { subFolderUid?: string } -export type UpdateOptions = { - transactionType?: UpdateTransactionType - links2Remove?: string[] -} - export enum UpdateTransactionType { General = "general", Rotation = "rotation" @@ -86,7 +80,6 @@ type GetPayload = CommonPayload & { publicKey?: string // passed once when binding requestedRecords?: string[] // only return these records requestedFolders?: string[] // only return these folders - requestLinks?: boolean } type DeletePayload = CommonPayload & { @@ -103,7 +96,6 @@ type UpdatePayload = CommonPayload & { data: string revision: number transactionType?: UpdateTransactionType - links2Remove?: string[] } type CompleteTransactionPayload = CommonPayload & { @@ -138,7 +130,6 @@ type FileUploadPayload = CommonPayload & { fileRecordData: string ownerRecordUid: string ownerRecordData: string - ownerRecordRevision: number linkKey: string fileSize: number } @@ -171,12 +162,6 @@ type SecretsManagerResponseRecord = { revision: number files: SecretsManagerResponseFile[] innerFolderUid: string - links?: KeeperRecordLink[] -} - -type KeeperRecordLink = { - recordUid: string - data?: string } type SecretsManagerResponseFile = { @@ -227,7 +212,6 @@ export type KeeperRecord = { data: any revision: number files?: KeeperFile[] - links?: KeeperRecordLink[] } export type KeeperFolder = { @@ -288,31 +272,17 @@ const prepareGetPayload = async (storage: KeyValueStorage, queryOptions?: QueryO if (queryOptions?.foldersFilter) { payload.requestedFolders = queryOptions.foldersFilter } - if( queryOptions?.requestLinks) { - payload.requestLinks = queryOptions.requestLinks - } return payload } -const prepareUpdatePayload = async (storage: KeyValueStorage, record: KeeperRecord, updateOptions?: UpdateOptions): Promise => { +const prepareUpdatePayload = async (storage: KeyValueStorage, record: KeeperRecord, transactionType?: UpdateTransactionType): Promise => { const clientId = await storage.getString(KEY_CLIENT_ID) if (!clientId) { throw new Error('Client Id is missing from the configuration') } - const {transactionType, links2Remove} = updateOptions ?? {} - if (links2Remove && links2Remove.length > 0) { - const fields = record.data.fields; - const fileRef = fields.find(x => x.type == 'fileRef'); - if (fileRef) { - fileRef.value = fileRef.value.filter(uid => !links2Remove.includes(uid)); - if (fileRef.value.length === 0) { - record.data.fields = fields.filter(x => x.type != 'fileRef'); - } - } - } const recordBytes = platform.stringToBytes(JSON.stringify(record.data)) - const encryptedRecord = await platform.encrypt(recordBytes, record.recordUid || KEY_APP_KEY) - const payload: UpdatePayload = { + const encryptedRecord = await platform.encrypt(recordBytes, record.recordUid) + const payload: UpdatePayload = { clientVersion: 'ms' + packageVersion, clientId: clientId, recordUid: record.recordUid, @@ -322,9 +292,6 @@ const prepareUpdatePayload = async (storage: KeyValueStorage, record: KeeperReco if (transactionType) { payload.transactionType = transactionType } - if (links2Remove && links2Remove.length > 0) { - payload.links2Remove = links2Remove - } return payload } @@ -457,7 +424,7 @@ const prepareFileUploadPayload = async (storage: KeyValueStorage, ownerRecord: K const fileRecordUid = webSafe64FromBytes(platform.getRandomBytes(16)) const encryptedFileRecord = await platform.encryptWithKey(fileRecordBytes, fileRecordKey) const encryptedFileRecordKey = await platform.publicEncrypt(fileRecordKey, ownerPublicKey) - const encryptedLinkKey = await platform.encrypt(fileRecordKey, ownerRecord.recordUid || KEY_APP_KEY) + const encryptedLinkKey = await platform.encrypt(fileRecordKey, ownerRecord.recordUid) const encryptedFileData = await platform.encryptWithKey(file.data, fileRecordKey) let fileRef = ownerRecord.data.fields.find(x => x.type == 'fileRef') @@ -468,7 +435,7 @@ const prepareFileUploadPayload = async (storage: KeyValueStorage, ownerRecord: K ownerRecord.data.fields.push(fileRef) } const ownerRecordBytes = platform.stringToBytes(JSON.stringify(ownerRecord.data)) - const encryptedOwnerRecord = await platform.encrypt(ownerRecordBytes, ownerRecord.recordUid || KEY_APP_KEY) + const encryptedOwnerRecord = await platform.encrypt(ownerRecordBytes, ownerRecord.recordUid) return { payload: { @@ -479,7 +446,6 @@ const prepareFileUploadPayload = async (storage: KeyValueStorage, ownerRecord: K fileRecordData: webSafe64FromBytes(encryptedFileRecord), ownerRecordUid: ownerRecord.recordUid, ownerRecordData: webSafe64FromBytes(encryptedOwnerRecord), - ownerRecordRevision: ownerRecord.revision, linkKey: platform.bytesToBase64(encryptedLinkKey), fileSize: encryptedFileData.length }, @@ -515,12 +481,11 @@ export const generateTransmissionKey = async (storage: KeyValueStorage): Promise const encryptAndSignPayload = async (storage: KeyValueStorage, transmissionKey: TransmissionKey, payload: GetPayload | UpdatePayload | FileUploadPayload): Promise => { const payloadBytes = platform.stringToBytes(JSON.stringify(payload)) const encryptedPayload = await platform.encryptWithKey(payloadBytes, transmissionKey.key) - const signatureBase = new Uint8Array(transmissionKey.encryptedKey.length + encryptedPayload.length) - signatureBase.set(transmissionKey.encryptedKey, 0) - signatureBase.set(encryptedPayload, transmissionKey.encryptedKey.length) + const signatureBase = Uint8Array.of(...transmissionKey.encryptedKey, ...encryptedPayload) const signature = await platform.sign(signatureBase, KEY_PRIVATE_KEY, storage) return {payload: encryptedPayload, signature} } + const postQuery = async (options: SecretManagerOptions, path: string, payload: AnyPayload): Promise => { const hostName = await options.storage.getString(KEY_HOSTNAME) if (!hostName) { @@ -548,7 +513,7 @@ const postQuery = async (options: SecretManagerOptions, path: string, payload: A } throw new Error(errorMessage) } - return response.data && response.data.length > 0 + return response.data ? platform.decryptWithKey(response.data, transmissionKey.key) : new Uint8Array() } @@ -577,9 +542,6 @@ const decryptRecord = async (record: SecretsManagerResponseRecord, storage?: Key }) } } - if (record.links) { - keeperRecord.links = record.links - } return keeperRecord } @@ -946,12 +908,8 @@ export const getSecretByTitle = async (options: SecretManagerOptions, recordTitl return secrets.records.find(record => record.data.title === recordTitle) } -export const updateSecret = async (options: SecretManagerOptions, record: KeeperRecord, transactionType?: UpdateTransactionType, links2Remove?: string[]): Promise => { - return updateSecret2(options, record, {transactionType,links2Remove}) -} - -export const updateSecret2 = async (options: SecretManagerOptions, record: KeeperRecord, updateOptions?: UpdateOptions): Promise => { - const payload = await prepareUpdatePayload(options.storage, record, updateOptions) +export const updateSecret = async (options: SecretManagerOptions, record: KeeperRecord, transactionType?: UpdateTransactionType): Promise => { + const payload = await prepareUpdatePayload(options.storage, record, transactionType) await postQuery(options, 'update_secret', payload) } diff --git a/sdk/python/storage/keeper_secrets_manager_storages/LICENSE b/sdk/python/storage/LICENSE similarity index 100% rename from sdk/python/storage/keeper_secrets_manager_storages/LICENSE rename to sdk/python/storage/LICENSE diff --git a/sdk/python/storage/keeper_secrets_manager_storages/README.md b/sdk/python/storage/README.md similarity index 100% rename from sdk/python/storage/keeper_secrets_manager_storages/README.md rename to sdk/python/storage/README.md diff --git a/sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_hsm/__init__.py b/sdk/python/storage/keeper_secrets_manager_hsm/__init__.py similarity index 100% rename from sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_hsm/__init__.py rename to sdk/python/storage/keeper_secrets_manager_hsm/__init__.py diff --git a/sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_hsm/storage_aws_kms.py b/sdk/python/storage/keeper_secrets_manager_hsm/storage_aws_kms.py similarity index 100% rename from sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_hsm/storage_aws_kms.py rename to sdk/python/storage/keeper_secrets_manager_hsm/storage_aws_kms.py diff --git a/sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_hsm/storage_hsm_nfast.py b/sdk/python/storage/keeper_secrets_manager_hsm/storage_hsm_nfast.py similarity index 97% rename from sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_hsm/storage_hsm_nfast.py rename to sdk/python/storage/keeper_secrets_manager_hsm/storage_hsm_nfast.py index 2d27a09bd..8205ad34c 100644 --- a/sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_hsm/storage_hsm_nfast.py +++ b/sdk/python/storage/keeper_secrets_manager_hsm/storage_hsm_nfast.py @@ -1,380 +1,380 @@ -# -*- coding: utf-8 -*- -# _ __ -# | |/ /___ ___ _ __ ___ _ _ (R) -# | ' nfpython.KeyID: - appident = nfkm.KeyIdent(appname=self.hsm_app_name, ident=self.hsm_ident) - keydata = nfkm.findkey(self.conn, appident) - - cmd = nfpython.Command(cmd="LoadBlob") - if private: - cmd.args.blob = keydata.privblob - else: - cmd.args.blob = keydata.pubblob - cmd.args.module = module - - rep = self.conn.transact(cmd, ignorestatus=True) - if rep.status != 'OK': - logging.getLogger(logger_name).error("HSM Failed to load encryption keys. Status = " + rep.status) - raise Exception("Failed to load encryption keys. " + str(rep.status)) - - keyid = rep.reply.idka - return keyid # nfpython.KeyID - - def __encrypt_buffer(self, message: str) -> bytes: - blob = b"" + HSM_BLOB_HEADER - - c = nfpython.Command(["ChannelOpen"]) - c.args.module = 0 - c.args.type = "simple" - c.args.flags |= "key_present" - c.args.mode = "encrypt" - c.args.mech = "any" - c.args.key = self.key - - rep = self.conn.transact(c) - channel = rep.reply.idch - if rep.reply.flags.isset("new_iv_present"): - mech = rep.reply.new_iv.mech.getvalue().to_bytes(2, byteorder='big') - blob += len(mech).to_bytes(2, byteorder='big') - blob += mech - - iv = rep.reply.new_iv.iv.iv.tobytes() - blob += len(iv).to_bytes(2, byteorder='big') - blob += iv - - ciphertext = b"" - c = nfpython.Command(["ChannelUpdate"]) - for chunk in (message[i:i+HSM_CHUNK_SIZE] for i in range(0, len(message), HSM_CHUNK_SIZE)): - c.args.idch = channel - c.args.input = nfpython.ByteBlock(chunk, fromraw=True) - r = self.conn.transact(c) - ciphertext += r.reply.output.tobytes() - - c.args.input = nfpython.ByteBlock() - c.args.flags |= "final" - r = self.conn.transact(c) - ciphertext += r.reply.output.tobytes() - - blob += len(ciphertext).to_bytes(4, byteorder='big') - blob += ciphertext - return blob - - def __decrypt_buffer(self, blob: bytes) -> str: - mech_val = b"" - iv_val = "" - ciphertext = "" - - buf = blob[:2] - if buf != HSM_BLOB_HEADER: - return "" - - success = False - buf = blob[2:4] # mech len - if len(buf) == 2: - buflen = int.from_bytes(buf, byteorder='big') - buf = blob[4:4+buflen] - if len(buf) == buflen: - mech_val = buf[:] - - pos = 4 + buflen - buf = blob[pos:pos+2] # iv len - if len(buf) == 2: - buflen = int.from_bytes(buf, byteorder='big') - buf = blob[pos+2:pos+2+buflen] - if len(buf) == buflen: - iv_val = buf[:] - - pos += 2 + buflen - buf = blob[pos:pos+4] # text len - if len(buf) == 4: - buflen = int.from_bytes(buf, byteorder='big') - buf = blob[pos+4:pos+4+buflen] - if len(buf) == buflen: - ciphertext = buf[:] - - pos += 4 + buflen # EOF check - buf = blob[pos:pos+1] - if len(buf) == 0: - success = True - if not success: - return "" - - c = nfpython.Command(["ChannelOpen"]) - c.args.module = 0 - c.args.type = "simple" - c.args.flags |= "key_present" - c.args.flags |= "given_iv_present" - c.args.mode = "decrypt" - c.args.mech = "any" - c.args.key = self.key - mech_int = int.from_bytes(mech_val, byteorder='big') - mech = nfpython.Mech.names.get(mech_int, 0) - iv = nfpython.ByteBlock(iv_val, fromraw=True) - c.args.given_iv._fromdict({'mech': mech, 'iv': {'iv': iv}}) - - rep = self.conn.transact(c) - channel = rep.reply.idch - - plaintext = "" - c = nfpython.Command(["ChannelUpdate"]) - for chunk in (ciphertext[i:i+HSM_CHUNK_SIZE] for i in range(0, len(ciphertext), HSM_CHUNK_SIZE)): - c.args.idch = channel - c.args.input = nfpython.ByteBlock(chunk, fromraw=True) - r = self.conn.transact(c) - try: - output_bytes = r.reply.output.tobytes() - plaintext += output_bytes.decode('utf8') - except Exception: - logging.getLogger(logger_name).error("Error decrypting config file. Try with different key.") - raise Exception("Error decrypting config file ".format(self.default_config_file_location)) - - c.args.input = nfpython.ByteBlock() - c.args.flags |= "final" - rep = self.conn.transact(c) - plaintext += rep.reply.output.tobytes().decode('utf8') - return plaintext - - def __load_config(self, module=0): - self.create_config_file_if_missing() - - is_blob = False - try: - with open(self.default_config_file_location, "rb") as fh: - header = fh.read(2) - is_blob = (HSM_BLOB_HEADER == header) - except Exception: - pass - - try: - # try to read plain JSON (unencrypted) - config = None - if not is_blob: - with open(self.default_config_file_location, "r", encoding=ENCODING) as fh: - try: - config_data = fh.read() - config = json.loads(config_data) - except UnicodeDecodeError: - logging.getLogger(logger_name).error("Config file is not utf-8 encoded.") - raise Exception("{} is not a utf-8 encoded file".format(self.default_config_file_location)) - except JSONDecodeError as err: - # If the JSON file was not empty, it's a legit JSON error. Throw an exception. - if config_data is not None and config_data.strip() != "": - raise Exception("{} may contain JSON format problems or is not utf-8 encoded" - ": {}".format(self.default_config_file_location, err)) - # If it was an empty file, overwrite with the JSON config - logging.getLogger(logger_name).warning("Looks like config file is empty.") - config = {} - self.save_storage(config) - except Exception as err: - logging.getLogger(logger_name).error("Config JSON has problems: {}".format(err)) - if "codec" in str(err): - raise Exception("{} is not a utf-8 encoded file.".format(self.default_config_file_location)) - raise err - - if config: - # detected plaintext JSON config -> encrypt - self.config = config - self.__save_config() # save encrypted - self.last_saved_config_hash = hashlib.md5(json.dumps(config, indent=4, sort_keys=True).encode()).hexdigest() - else: - # decrypt binary blob - ciphertext: bytes = bytes() - try: - with open(self.default_config_file_location, "rb") as fh: - ciphertext = fh.read() - except Exception as e: - logging.getLogger(logger_name).error("Failed to load config file " + self.default_config_file_location) - raise Exception("Failed to load config file " + self.default_config_file_location) - - if len(ciphertext) == 0: - logging.getLogger(logger_name).warning("Empty config file " + self.default_config_file_location) - - config_json = self.__decrypt_buffer(ciphertext) - try: - config = json.loads(config_json) - self.config = config - self.last_saved_config_hash = hashlib.md5(json.dumps(config, indent=4, sort_keys=True).encode()).hexdigest() - except Exception as err: - logging.getLogger(logger_name).error("Config JSON has problems: {}".format(err)) - raise err - except IOError: - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), self.default_config_file_location) - - def __save_config(self, updated_config:dict = {}, module=0, force=False): - config = self.config if self.config else {} - config_json:str = json.dumps(config, indent=4, sort_keys=True) - config_hash = hashlib.md5(config_json.encode()).hexdigest() - - if updated_config: - ucfg_json:str = json.dumps(updated_config, indent=4, sort_keys=True) - ucfg_hash = hashlib.md5(ucfg_json.encode()).hexdigest() - if ucfg_hash != config_hash: - config_hash = ucfg_hash - config_json = ucfg_json - self.config = dict(updated_config) - # self.last_saved_config_hash = config_hash # update after save - to allow for retries - - if not force and config_hash == self.last_saved_config_hash: - logging.getLogger(logger_name).warning("Skipped config JSON save. No changes detected.") - return - - self.create_config_file_if_missing() - blob = self.__encrypt_buffer(config_json) - with open(self.default_config_file_location, "wb") as write_file: - write_file.write(blob) - self.last_saved_config_hash = config_hash - - def decrypt_config(self, autosave: bool = True) -> str: - ciphertext: bytes = bytes() - plaintext: str = "" - try: - with open(self.default_config_file_location, "rb") as fh: - ciphertext = fh.read() - if len(ciphertext) == 0: - logging.getLogger(logger_name).warning("Empty config file " + self.default_config_file_location) - return "" - except Exception as e: - logging.getLogger(logger_name).error("Failed to load config file " + self.default_config_file_location) - raise Exception("Failed to load config file " + self.default_config_file_location) - - try: - plaintext = self.__decrypt_buffer(ciphertext) - if len(plaintext) == 0: - logging.getLogger(logger_name).error("Failed to decrypt config file " + self.default_config_file_location) - elif autosave: - with open(self.default_config_file_location, "w") as fh: - fh.write(plaintext) - except Exception as err: - logging.getLogger(logger_name).error("Failed to write decrypted config file " + self.default_config_file_location) - raise Exception("Failed to write decrypted config file " + self.default_config_file_location) - return plaintext - - def change_key(self, new_ident: str) -> bool: - old_ident = self.hsm_ident - old_key = self.key - try: - self.hsm_ident = new_ident - self.key = self.__load_key() - self.__save_config(force=True) - except Exception as e: - self.hsm_ident = old_ident - self.key = old_key - logging.getLogger(logger_name).error(f"Failed to change the key to '{new_ident}' for config '{self.default_config_file_location}'") - raise Exception("Failed to change the key for " + self.default_config_file_location) - return True - - def read_storage(self): - if not self.config: - self.__load_config() - return dict(self.config) - - def save_storage(self, updated_config): - self.__save_config(updated_config) - - def get(self, key: ConfigKeys): - config = self.read_storage() - return config.get(key.value) - - def set(self, key: ConfigKeys, value): - config = self.read_storage() - config[key.value] = value - self.save_storage(config) - return config - - def delete(self, key: ConfigKeys): - config = self.read_storage() - - kv = key.value - if kv in config: - del config[kv] - logging.getLogger(logger_name).debug("Removed key %s" % kv) - else: - logging.getLogger(logger_name).debug("No key %s was found in config" % kv) - - self.save_storage(config) - return config - - def delete_all(self): - self.read_storage() - self.config.clear() - self.save_storage(self.config) - return dict(self.config) - - def contains(self, key: ConfigKeys): - config = self.read_storage() - return key.value in config - - def create_config_file_if_missing(self): - if not os.path.exists(self.default_config_file_location): - with open(self.default_config_file_location, "wb") as fh: - blob = self.__encrypt_buffer("{}") - fh.write(blob) - - def is_empty(self): - config = self.read_storage() - return not config +# -*- coding: utf-8 -*- +# _ __ +# | |/ /___ ___ _ __ ___ _ _ (R) +# | ' nfpython.KeyID: + appident = nfkm.KeyIdent(appname=self.hsm_app_name, ident=self.hsm_ident) + keydata = nfkm.findkey(self.conn, appident) + + cmd = nfpython.Command(cmd="LoadBlob") + if private: + cmd.args.blob = keydata.privblob + else: + cmd.args.blob = keydata.pubblob + cmd.args.module = module + + rep = self.conn.transact(cmd, ignorestatus=True) + if rep.status != 'OK': + logging.getLogger(logger_name).error("HSM Failed to load encryption keys. Status = " + rep.status) + raise Exception("Failed to load encryption keys. " + str(rep.status)) + + keyid = rep.reply.idka + return keyid # nfpython.KeyID + + def __encrypt_buffer(self, message: str) -> bytes: + blob = b"" + HSM_BLOB_HEADER + + c = nfpython.Command(["ChannelOpen"]) + c.args.module = 0 + c.args.type = "simple" + c.args.flags |= "key_present" + c.args.mode = "encrypt" + c.args.mech = "any" + c.args.key = self.key + + rep = self.conn.transact(c) + channel = rep.reply.idch + if rep.reply.flags.isset("new_iv_present"): + mech = rep.reply.new_iv.mech.getvalue().to_bytes(2, byteorder='big') + blob += len(mech).to_bytes(2, byteorder='big') + blob += mech + + iv = rep.reply.new_iv.iv.iv.tobytes() + blob += len(iv).to_bytes(2, byteorder='big') + blob += iv + + ciphertext = b"" + c = nfpython.Command(["ChannelUpdate"]) + for chunk in (message[i:i+HSM_CHUNK_SIZE] for i in range(0, len(message), HSM_CHUNK_SIZE)): + c.args.idch = channel + c.args.input = nfpython.ByteBlock(chunk, fromraw=True) + r = self.conn.transact(c) + ciphertext += r.reply.output.tobytes() + + c.args.input = nfpython.ByteBlock() + c.args.flags |= "final" + r = self.conn.transact(c) + ciphertext += r.reply.output.tobytes() + + blob += len(ciphertext).to_bytes(4, byteorder='big') + blob += ciphertext + return blob + + def __decrypt_buffer(self, blob: bytes) -> str: + mech_val = b"" + iv_val = "" + ciphertext = "" + + buf = blob[:2] + if buf != HSM_BLOB_HEADER: + return "" + + success = False + buf = blob[2:4] # mech len + if len(buf) == 2: + buflen = int.from_bytes(buf, byteorder='big') + buf = blob[4:4+buflen] + if len(buf) == buflen: + mech_val = buf[:] + + pos = 4 + buflen + buf = blob[pos:pos+2] # iv len + if len(buf) == 2: + buflen = int.from_bytes(buf, byteorder='big') + buf = blob[pos+2:pos+2+buflen] + if len(buf) == buflen: + iv_val = buf[:] + + pos += 2 + buflen + buf = blob[pos:pos+4] # text len + if len(buf) == 4: + buflen = int.from_bytes(buf, byteorder='big') + buf = blob[pos+4:pos+4+buflen] + if len(buf) == buflen: + ciphertext = buf[:] + + pos += 4 + buflen # EOF check + buf = blob[pos:pos+1] + if len(buf) == 0: + success = True + if not success: + return "" + + c = nfpython.Command(["ChannelOpen"]) + c.args.module = 0 + c.args.type = "simple" + c.args.flags |= "key_present" + c.args.flags |= "given_iv_present" + c.args.mode = "decrypt" + c.args.mech = "any" + c.args.key = self.key + mech_int = int.from_bytes(mech_val, byteorder='big') + mech = nfpython.Mech.names.get(mech_int, 0) + iv = nfpython.ByteBlock(iv_val, fromraw=True) + c.args.given_iv._fromdict({'mech': mech, 'iv': {'iv': iv}}) + + rep = self.conn.transact(c) + channel = rep.reply.idch + + plaintext = "" + c = nfpython.Command(["ChannelUpdate"]) + for chunk in (ciphertext[i:i+HSM_CHUNK_SIZE] for i in range(0, len(ciphertext), HSM_CHUNK_SIZE)): + c.args.idch = channel + c.args.input = nfpython.ByteBlock(chunk, fromraw=True) + r = self.conn.transact(c) + try: + output_bytes = r.reply.output.tobytes() + plaintext += output_bytes.decode('utf8') + except Exception: + logging.getLogger(logger_name).error("Error decrypting config file. Try with different key.") + raise Exception("Error decrypting config file ".format(self.default_config_file_location)) + + c.args.input = nfpython.ByteBlock() + c.args.flags |= "final" + rep = self.conn.transact(c) + plaintext += rep.reply.output.tobytes().decode('utf8') + return plaintext + + def __load_config(self, module=0): + self.create_config_file_if_missing() + + is_blob = False + try: + with open(self.default_config_file_location, "rb") as fh: + header = fh.read(2) + is_blob = (HSM_BLOB_HEADER == header) + except Exception: + pass + + try: + # try to read plain JSON (unencrypted) + config = None + if not is_blob: + with open(self.default_config_file_location, "r", encoding=ENCODING) as fh: + try: + config_data = fh.read() + config = json.loads(config_data) + except UnicodeDecodeError: + logging.getLogger(logger_name).error("Config file is not utf-8 encoded.") + raise Exception("{} is not a utf-8 encoded file".format(self.default_config_file_location)) + except JSONDecodeError as err: + # If the JSON file was not empty, it's a legit JSON error. Throw an exception. + if config_data is not None and config_data.strip() != "": + raise Exception("{} may contain JSON format problems or is not utf-8 encoded" + ": {}".format(self.default_config_file_location, err)) + # If it was an empty file, overwrite with the JSON config + logging.getLogger(logger_name).warning("Looks like config file is empty.") + config = {} + self.save_storage(config) + except Exception as err: + logging.getLogger(logger_name).error("Config JSON has problems: {}".format(err)) + if "codec" in str(err): + raise Exception("{} is not a utf-8 encoded file.".format(self.default_config_file_location)) + raise err + + if config: + # detected plaintext JSON config -> encrypt + self.config = config + self.__save_config() # save encrypted + self.last_saved_config_hash = hashlib.md5(json.dumps(config, indent=4, sort_keys=True).encode()).hexdigest() + else: + # decrypt binary blob + ciphertext: bytes = bytes() + try: + with open(self.default_config_file_location, "rb") as fh: + ciphertext = fh.read() + except Exception as e: + logging.getLogger(logger_name).error("Failed to load config file " + self.default_config_file_location) + raise Exception("Failed to load config file " + self.default_config_file_location) + + if len(ciphertext) == 0: + logging.getLogger(logger_name).warning("Empty config file " + self.default_config_file_location) + + config_json = self.__decrypt_buffer(ciphertext) + try: + config = json.loads(config_json) + self.config = config + self.last_saved_config_hash = hashlib.md5(json.dumps(config, indent=4, sort_keys=True).encode()).hexdigest() + except Exception as err: + logging.getLogger(logger_name).error("Config JSON has problems: {}".format(err)) + raise err + except IOError: + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), self.default_config_file_location) + + def __save_config(self, updated_config:dict = {}, module=0, force=False): + config = self.config if self.config else {} + config_json:str = json.dumps(config, indent=4, sort_keys=True) + config_hash = hashlib.md5(config_json.encode()).hexdigest() + + if updated_config: + ucfg_json:str = json.dumps(updated_config, indent=4, sort_keys=True) + ucfg_hash = hashlib.md5(ucfg_json.encode()).hexdigest() + if ucfg_hash != config_hash: + config_hash = ucfg_hash + config_json = ucfg_json + self.config = dict(updated_config) + # self.last_saved_config_hash = config_hash # update after save - to allow for retries + + if not force and config_hash == self.last_saved_config_hash: + logging.getLogger(logger_name).warning("Skipped config JSON save. No changes detected.") + return + + self.create_config_file_if_missing() + blob = self.__encrypt_buffer(config_json) + with open(self.default_config_file_location, "wb") as write_file: + write_file.write(blob) + self.last_saved_config_hash = config_hash + + def decrypt_config(self, autosave: bool = True) -> str: + ciphertext: bytes = bytes() + plaintext: str = "" + try: + with open(self.default_config_file_location, "rb") as fh: + ciphertext = fh.read() + if len(ciphertext) == 0: + logging.getLogger(logger_name).warning("Empty config file " + self.default_config_file_location) + return "" + except Exception as e: + logging.getLogger(logger_name).error("Failed to load config file " + self.default_config_file_location) + raise Exception("Failed to load config file " + self.default_config_file_location) + + try: + plaintext = self.__decrypt_buffer(ciphertext) + if len(plaintext) == 0: + logging.getLogger(logger_name).error("Failed to decrypt config file " + self.default_config_file_location) + elif autosave: + with open(self.default_config_file_location, "w") as fh: + fh.write(plaintext) + except Exception as err: + logging.getLogger(logger_name).error("Failed to write decrypted config file " + self.default_config_file_location) + raise Exception("Failed to write decrypted config file " + self.default_config_file_location) + return plaintext + + def change_key(self, new_ident: str) -> bool: + old_ident = self.hsm_ident + old_key = self.key + try: + self.hsm_ident = new_ident + self.key = self.__load_key() + self.__save_config(force=True) + except Exception as e: + self.hsm_ident = old_ident + self.key = old_key + logging.getLogger(logger_name).error(f"Failed to change the key to '{new_ident}' for config '{self.default_config_file_location}'") + raise Exception("Failed to change the key for " + self.default_config_file_location) + return True + + def read_storage(self): + if not self.config: + self.__load_config() + return dict(self.config) + + def save_storage(self, updated_config): + self.__save_config(updated_config) + + def get(self, key: ConfigKeys): + config = self.read_storage() + return config.get(key.value) + + def set(self, key: ConfigKeys, value): + config = self.read_storage() + config[key.value] = value + self.save_storage(config) + return config + + def delete(self, key: ConfigKeys): + config = self.read_storage() + + kv = key.value + if kv in config: + del config[kv] + logging.getLogger(logger_name).debug("Removed key %s" % kv) + else: + logging.getLogger(logger_name).debug("No key %s was found in config" % kv) + + self.save_storage(config) + return config + + def delete_all(self): + self.read_storage() + self.config.clear() + self.save_storage(self.config) + return dict(self.config) + + def contains(self, key: ConfigKeys): + config = self.read_storage() + return key.value in config + + def create_config_file_if_missing(self): + if not os.path.exists(self.default_config_file_location): + with open(self.default_config_file_location, "wb") as fh: + blob = self.__encrypt_buffer("{}") + fh.write(blob) + + def is_empty(self): + config = self.read_storage() + return not config diff --git a/sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_storage/__init__.py b/sdk/python/storage/keeper_secrets_manager_storage/__init__.py similarity index 100% rename from sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_storage/__init__.py rename to sdk/python/storage/keeper_secrets_manager_storage/__init__.py diff --git a/sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_storage/config_provider.py b/sdk/python/storage/keeper_secrets_manager_storage/config_provider.py similarity index 100% rename from sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_storage/config_provider.py rename to sdk/python/storage/keeper_secrets_manager_storage/config_provider.py diff --git a/sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_storage/storage_aws_secret.py b/sdk/python/storage/keeper_secrets_manager_storage/storage_aws_secret.py similarity index 100% rename from sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_storage/storage_aws_secret.py rename to sdk/python/storage/keeper_secrets_manager_storage/storage_aws_secret.py diff --git a/sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_storage/storage_azure_keyvault.py b/sdk/python/storage/keeper_secrets_manager_storage/storage_azure_keyvault.py similarity index 100% rename from sdk/python/storage/keeper_secrets_manager_storages/keeper_secrets_manager_storage/storage_azure_keyvault.py rename to sdk/python/storage/keeper_secrets_manager_storage/storage_azure_keyvault.py diff --git a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/LICENSE b/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/LICENSE deleted file mode 100644 index e588d4ca1..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Keeper Security - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/README.md b/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/README.md deleted file mode 100644 index 5f62a015d..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# GCP KSM -Keeper Secrets Manager integrates with GCP KMS in order to provide protection for Keeper Secrets Manager configuration files. With this integration, you can protect connection details on your machine while taking advantage of Keeper's zero-knowledge encryption of all your secret credentials. - -## Features -* Encrypt and Decrypt your Keeper Secrets Manager configuration files with GCP KMS -* Protect against unauthorized access to your Secrets Manager connections -* Requires only minor changes to code for immediate protection. Works with all Keeper Secrets Manager Python SDK functionality - -## Prerequisites -* Supports the Python Secrets Manager SDK -* Requires `google-cloud-kms` package -* These are permissions required for service account: - * Cloud KMS CryptoKey Decrypter - * Cloud KMS CryptoKey Encrypter - * Cloud KMS CryptoKey Public Key Viewer - -## Setup - -1. Install KSM Storage Module - -The Secrets Manager GCP KSM module can be installed using pip - -> `pip3 install keeper-secrets-manager-storage-gcp-kms` - -2. Configure GCP Connection - -By default the google-cloud-kms library will utilize the default connection session setup with the GCP CLI with the gcloud auth command. If you would like to specify the connection details, the two configuration files located at `~/.config/gcloud/configurations/config_default` and ~/.config/gcloud/legacy_credentials//adc.json can be manually edited. - -See the GCP documentation for more information on setting up an GCP session: https://cloud.google.com/sdk/gcloud/reference/auth - -Alternatively, configuration variables can be provided explicitly as a service account file using the GcpSessionConfig data class and providing a path to the service account json file. - -You will need a GCP service account to use the GCP KMS integration. - -For more information on GCP service accounts see the GCP documentation: https://cloud.google.com/iam/docs/service-accounts - -3. Add GCP KMS Storage to Your Code - -Now that the GCP connection has been configured, you need to tell the Secrets Manager SDK to utilize the KMS as storage. - -To do this, use GcpKmsKeyvalueStorage as your Secrets Manager storage in the SecretsManager constructor. - -The storage will require a GCP Key ID, as well as the name of the Secrets Manager configuration file which will be encrypted by GCP KMS. -``` - from keeper_secrets_manager_storage_gcp_kms import GCPKeyConfig, GCPKeyValueStorage, GCPKMSClientConfig - - from keeper_secrets_manager_core import SecretsManager - - # example key : projects//locations//keyRings//cryptoKeys//cryptoKeyVersions/ - gcp_key_config_1 = GCPKeyConfig("") - gcp_key_config_2 = GCPKeyConfig("") - - gcp_session_config = GCPKMSClientConfig().create_client_from_credentials_file('') - config_path = "" - one_time_token = "" - - storage = GCPKeyValueStorage(config_path, gcp_key_config_1, gcp_session_config) - storage.change_key(gcp_key_config_2) # if we want to change the key - secrets_manager = SecretsManager(token=one_time_token,config=storage) - all_records = secrets_manager.get_secrets() - print(storage.decrypt_config(False)) - - first_record = all_records[0] - print(first_record) -``` - -You're ready to use the KSM integration 👍 -Using the GCP KMS Integration - -Once setup, the Secrets Manager GCP KMS integration supports all Secrets Manager Python SDK functionality. Your code will need to be able to access the GCP KMS APIs in order to manage the decryption of the configuration file when run. \ No newline at end of file diff --git a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/keeper_secrets_manager_storage_gcp_kms/__init__.py b/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/keeper_secrets_manager_storage_gcp_kms/__init__.py deleted file mode 100644 index 0a2afaa53..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/keeper_secrets_manager_storage_gcp_kms/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# _ __ -# | |/ /___ ___ _ __ ___ _ _ (R) -# | ' 9 else None - else: - self.key_name = key_name - self.key_version = key_version - self.key_ring = key_ring - self.project = project - self.location = location - - def __str__(self): - return f"{self.key_name}, {self.key_version}" - - def to_key_name(self): - """Returns the key name in the required KMS format.""" - return f"projects/{self.project}/locations/{self.location}/keyRings/{self.key_ring}/cryptoKeys/{self.key_name}" - - def to_resource_name(self): - """Returns the full resource name of the KMS key.""" - return f"projects/{self.project}/locations/{self.location}/keyRings/{self.key_ring}/cryptoKeys/{self.key_name}/cryptoKeyVersions/{self.key_version}" diff --git a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/keeper_secrets_manager_storage_gcp_kms/storage_gcp_kms.py b/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/keeper_secrets_manager_storage_gcp_kms/storage_gcp_kms.py deleted file mode 100644 index 6f5beeba0..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/keeper_secrets_manager_storage_gcp_kms/storage_gcp_kms.py +++ /dev/null @@ -1,336 +0,0 @@ -# _ __ -# | |/ /___ ___ _ __ ___ _ _ (R) -# | ' str: - ciphertext : bytes = bytes() - plaintext : str= "" - - try: - # Read the config file - with open(self.config_file_location, 'rb') as config_file: - ciphertext = config_file.read() - if len(ciphertext) == 0: - self.logger.warning(f"Empty config file {self.config_file_location}") - return "" - except Exception as err: - self.logger.error(f"Failed to load config file {self.config_file_location}: {err}") - raise Exception(f"Failed to load config file {self.config_file_location}") - - try: - token=None - if self.key_purpose_details == KeyPurpose.RAW_ENCRYPT_DECRYPT: - token = self.gcp_session_config.getToken() - # Decrypt the file contents - plaintext = decrypt_buffer( - is_asymmetric=self.is_asymmetric, - ciphertext=ciphertext, - crypto_client=self.crypto_client, - key_properties=self.gcp_key_config, - token=token, - logger = self.logger - ) - if len(plaintext) == 0: - self.logger.error(f"Failed to decrypt config file {self.config_file_location}") - elif autosave: - # Optionally autosave the decrypted content - with open(self.config_file_location, 'w') as config_file: - config_file.write(plaintext) - except Exception as err: - self.logger.error(f"Failed to write decrypted config file {self.config_file_location}: {err}") - raise Exception(f"Failed to write decrypted config file {self.config_file_location}") - - return plaintext - - def __save_config(self, updated_config: Dict[str, str] = {}, force: bool = False) -> None: - try: - # Retrieve current config - config = self.config or {} - config_json = json.dumps(config, sort_keys=True, indent=4) - config_hash = hashlib.md5(config_json.encode()).hexdigest() - - # Compare updated_config hash with current config hash - if updated_config: - updated_config_json = json.dumps(updated_config, sort_keys=True, indent=4) - updated_config_hash = hashlib.md5(updated_config_json.encode()).hexdigest() - - if updated_config_hash != config_hash: - config_hash = updated_config_hash - config_json = updated_config_json - self.config = dict(updated_config) # Update the current config - - # Check if saving is necessary - if not force and config_hash == self.last_saved_config_hash: - self.logger.warning("Skipped config JSON save. No changes detected.") - return - - # Ensure the config file exists - self.create_config_file_if_missing() - - # Encrypt the config JSON and write to the file - stringified_value = json.dumps(self.config, sort_keys=True, indent=4) - token=None - if self.key_purpose_details == KeyPurpose.RAW_ENCRYPT_DECRYPT: - token = self.gcp_session_config.getToken() - blob = encrypt_buffer( - is_asymmetric=self.is_asymmetric, - message=stringified_value, - crypto_client=self.crypto_client, - key_properties=self.gcp_key_config, - encryption_algorithm=self.encryption_algorithm, - token=token, - logger = self.logger - ) - if len(blob)!=0: - with open(self.config_file_location, 'wb') as config_file: - config_file.write(blob) - - # Update the last saved config hash - self.last_saved_config_hash = config_hash - - except Exception as err: - self.logger.error(f"Error saving config: {err}") - - def load_config(self) -> None: - self.create_config_file_if_missing() - - try: - # Read the config file - contents: bytes = b"" - try: - with open(self.config_file_location, 'rb') as config_file: - contents = config_file.read() - self.logger.info(f"Loaded config file {self.config_file_location}") - except Exception as err: - self.logger.error(f"Failed to load config file {self.config_file_location}: {err}") - raise Exception(f"Failed to load config file {self.config_file_location}") - - if len(contents) == 0: - self.logger.warning(f"Empty config file {self.config_file_location}") - contents = b"{}" - - # Check if the content is plain JSON - config = None - json_error = None - decryption_error = False - try: - config_data = contents.decode() - config = json.loads(config_data) - # Encrypt and save the config if it's plain JSON - if config: - self.config = config - self.__save_config(config) - self.last_saved_config_hash = hashlib.md5( - json.dumps(config, sort_keys=True, indent=4).encode() - ).hexdigest() - except Exception as err: - json_error = err - - if json_error: - token=None - if self.key_purpose_details == KeyPurpose.RAW_ENCRYPT_DECRYPT: - token = self.gcp_session_config.getToken() - config_json = decrypt_buffer( - is_asymmetric=self.is_asymmetric, - ciphertext=contents, - crypto_client=self.crypto_client, - key_properties=self.gcp_key_config, - token=token, - logger= self.logger - ) - try: - config = json.loads(config_json) - self.config = config or {} - self.last_saved_config_hash = hashlib.md5( - json.dumps(config, sort_keys=True, indent=4).encode() - ).hexdigest() - except Exception as err: - decryption_error = True - self.logger.error(f"Failed to parse decrypted config file: {err}") - raise Exception(f"Failed to parse decrypted config file {self.config_file_location}") - - if json_error and decryption_error: - self.logger.info(f"Config file is not a valid JSON file: {json_error}") - raise Exception(f"{self.config_file_location} may contain JSON format problems") - - except Exception as err: - self.logger.error(f"Error loading config: {err}") - raise err - - def change_key(self, new_gcp_key_config: GCPKeyConfig) -> bool: - old_key_configuration = self.gcp_key_config - old_crypto_client = self.crypto_client - - try: - # Update the key and reinitialize the CryptographyClient - config = self.config - if not config: - self.load_config() - self.gcp_key_config = new_gcp_key_config - self.get_key_details() - self.__save_config({}, force=True) - except Exception as error: - # Restore the previous key and crypto client if the operation fails - self.gcp_key_config = old_key_configuration - self.crypto_client = old_crypto_client - self.logger.error( - f"Failed to change the key to '{new_gcp_key_config.to_key_name()}' for config '{self.config_file_location}': {error}" - ) - raise Exception(f"Failed to change the key for {self.config_file_location}") - - return True - - def read_storage(self) -> Dict[str, str]: - if not self.config: - self.load_config() - return self.config - - def save_storage(self, updated_config: Dict[str, str]) -> None: - self.__save_config(updated_config) - - def get(self, key: ConfigKeys) -> str: - config = self.read_storage() - return config.get(key.value) - - def set(self, key: ConfigKeys, value): - config = self.read_storage() - config[key.value] = value - self.save_storage(config) - return config - - def delete(self, key: ConfigKeys): - config = self.read_storage() - - kv = key.value - if kv in config: - del config[kv] - self.logger.debug("Removed key %s" % kv) - else: - self.logger.debug("No key %s was found in config" % kv) - - self.save_storage(config) - return config - - def delete_all(self): - self.read_storage() - self.config.clear() - self.save_storage(self.config) - return dict(self.config) - - def contains(self, key: ConfigKeys): - config = self.read_storage() - return key.value in config \ No newline at end of file diff --git a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/keeper_secrets_manager_storage_gcp_kms/util_options.py b/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/keeper_secrets_manager_storage_gcp_kms/util_options.py deleted file mode 100644 index 6eac3340a..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/keeper_secrets_manager_storage_gcp_kms/util_options.py +++ /dev/null @@ -1,45 +0,0 @@ -# _ __ -# | |/ /___ ___ _ __ ___ _ _ (R) -# | ' bytes: - logger.debug("Trying to extract resource name") - key_name = options.get("key_properties").to_resource_name() - - logger.debug("Trying to encrypt data with given resource name %s", key_name) - additional_data = ADDITIONAL_AUTHENTICATION_DATA.encode('utf-8') - encoded_message = options.get("message") - - payload = { - "plaintext": base64.b64encode(encoded_message).decode('utf-8'), - "additionalAuthenticatedData": base64.b64encode(additional_data).decode('utf-8') - } - - token = options.get("token") - api_url = RAW_ENCRYPT_GCP_API_URL.format(key_name) - - headers = { - "Authorization": f"Bearer {token}", - "Content-Type": "application/json" - } - - response = requests.post(api_url, headers=headers, data=json.dumps(payload)) - response_body = response.text - - if not response.ok: - logger.error("rawEncrypt API call failed with status: %s, response: %s", response.status_code, response_body) - raise Exception(f"rawEncrypt API call failed with status: {response.status_code}, response: {response_body}") - - logger.debug("rawEncrypt API call completed successfully") - - result = response.json() - - if "ciphertext" not in result or "initializationVector" not in result: - logger.error("Failed to parse response from rawEncrypt API call") - raise Exception("Failed to parse response from rawEncrypt API call") - - ciphertext = base64.b64decode(result["ciphertext"]) - initialization_vector = base64.b64decode(result["initializationVector"]) - encrypted_data = initialization_vector + ciphertext - - # Create 2-byte length prefix (big endian) - length = len(encrypted_data) - length_prefix = length.to_bytes(2, byteorder='big') - - logger.debug("Raw encryption completed, concatenating data with length prefix") - final_payload = length_prefix + encrypted_data - - return final_payload - -def decrypt_buffer(is_asymmetric, ciphertext, crypto_client, key_properties,logger: logging.Logger,token=None): - try: - # Validate BLOB_HEADER - header = ciphertext[:2] - if header != BLOB_HEADER: - raise ValueError("Decryption failed: Invalid header") - - pos = 2 - parts = [] - - # Parse the ciphertext into its components - encrypted_key, nonce, tag, encrypted_text = (b'', b'', b'', b'') - for x in range(1, 5): - buf = ciphertext[pos:pos + 2] # chunks are size prefixed - pos += len(buf) - if len(buf) == 2: - buflen = int.from_bytes(buf, byteorder='big') - buf = ciphertext[pos:pos + buflen] - pos += len(buf) - if len(buf) == buflen: - parts.append(buf) - else: - logging.error("Decryption buffer contains incomplete data.") - - encrypted_key, nonce, tag, encrypted_text = parts - - decrypt_options = { - 'ciphertext': encrypted_key, - 'crypto_client': crypto_client, - 'key_properties': key_properties, - 'is_asymmetric': is_asymmetric, - 'token': token, - 'logger': logger - } - - key = decrypt_data_and_validate_crc(decrypt_options) - - # Decrypt the message using AES-GCM - cipher = AES.new(key, AES.MODE_GCM, nonce=nonce) - decrypted = cipher.decrypt_and_verify(encrypted_text, tag) - - # Convert decrypted data to a UTF-8 string - return decrypted.decode() - except Exception as err: - logger.warning(f"Google KMS KeyVault Storage failed to decrypt: {err}") - return "" # Return empty string in case of an error - - -def decrypt_data_and_validate_crc(options): - cipher_data = options['ciphertext'] - cipher_data_crc = google_crc32c.value(options['ciphertext']) - client = options['crypto_client'] - - if options['is_asymmetric']: - key_name_for_asymmetric_decrypt = options['key_properties'].to_resource_name( - ) - request = AsymmetricDecryptRequest( - name=key_name_for_asymmetric_decrypt, - ciphertext=cipher_data, - ciphertext_crc32c=cipher_data_crc - ) - decrypt_response = client.asymmetric_decrypt(request=request) - else: - if options.get("token"): - plaintext = decrypt_data_symmetric_raw(options, options['logger']) - return plaintext - - key_name = options['key_properties'].to_key_name() - input = DecryptRequest(name=key_name, ciphertext=cipher_data, - ciphertext_crc32c=cipher_data_crc) - decrypt_response = client.decrypt(request=input) - - plaintext = decrypt_response.plaintext - plaintext_crc = google_crc32c.value(plaintext) - - if plaintext_crc != decrypt_response.plaintext_crc32c: - raise ValueError("Decrypt: response corrupted in-transit") - - return plaintext - -def decrypt_data_symmetric_raw(options, logger: logging.Logger) -> bytes: - - logger.debug("Trying to extract resource name") - key_name = options["key_properties"].to_resource_name() - - logger.debug(f"Trying to decrypt data with given resource name {key_name}") - additional_data = ADDITIONAL_AUTHENTICATION_DATA.encode("utf-8") - - encrypted_data = options["ciphertext"] - if len(encrypted_data) < 14: - logger.error("Invalid ciphertext structure: size buffer length mismatch.") - raise ValueError("Invalid ciphertext structure: size buffer length mismatch.") - - _length = (encrypted_data[0] << 8) | encrypted_data[1] - initialization_vector = encrypted_data[2:14] - ciphertext = encrypted_data[14:] - - payload = { - "ciphertext": base64.b64encode(ciphertext).decode("utf-8"), - "additionalAuthenticatedData": base64.b64encode(additional_data).decode("utf-8"), - "initializationVector": base64.b64encode(initialization_vector).decode("utf-8") - } - - token = options["token"] - api_url = RAW_DECRYPT_GCP_API_URL.format(key_name) - - headers = { - "Authorization": f"Bearer {token}", - "Content-Type": "application/json" - } - - response = requests.post(api_url, data=json.dumps(payload), headers=headers) - response_body = response.text - - if not response.ok: - logger.error(f"rawDecrypt API call failed with status: {response.status_code}, response: {response_body}") - raise Exception(f"rawDecrypt API call failed with status: {response.status_code}, response: {response_body}") - - logger.debug("rawDecrypt API call completed successfully") - - result = json.loads(response_body) - if result is None or "plaintext" not in result: - logger.error("Failed to parse response from rawDecrypt API call") - raise Exception("Failed to parse response from rawDecrypt API call") - - logger.debug("Raw decryption completed") - return base64.b64decode(result["plaintext"]) - -def get_key_type(key_purpose: CryptoKey.CryptoKeyPurpose): - if key_purpose == KeyPurpose.RAW_ENCRYPT_DECRYPT: - return "RAW_ENCRYPT_DECRYPT" - elif key_purpose == KeyPurpose.ENCRYPT_DECRYPT: - return "ENCRYPT_DECRYPT" - elif key_purpose == KeyPurpose.ASYMMETRIC_DECRYPT: - return "ASYMMETRIC_DECRYPT" - -def get_hash_algorithm(encryption_algorithm: KeyAlgorithm) -> hashes.HashAlgorithm: - """Converts a KeyAlgorithm to a HashAlgorithm.""" - - hash_algorithms = { - KeyAlgorithm.RSA_DECRYPT_OAEP_2048_SHA256: SHA256, - KeyAlgorithm.RSA_DECRYPT_OAEP_3072_SHA256: SHA256, - KeyAlgorithm.RSA_DECRYPT_OAEP_4096_SHA256: SHA256, - KeyAlgorithm.RSA_DECRYPT_OAEP_4096_SHA512: SHA512, - KeyAlgorithm.RSA_DECRYPT_OAEP_2048_SHA1: SHA1, - KeyAlgorithm.RSA_DECRYPT_OAEP_3072_SHA1: SHA1, - KeyAlgorithm.RSA_DECRYPT_OAEP_4096_SHA1: SHA1, - } - - try: - return hash_algorithms[encryption_algorithm] - except KeyError: - raise TypeError( - "Unsupported encryption algorithm is used for provided key" - ) from None diff --git a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/pyproject.toml b/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/pyproject.toml deleted file mode 100644 index b1cd9e998..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/pyproject.toml +++ /dev/null @@ -1,60 +0,0 @@ -[build-system] -requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"] -build-backend = "setuptools.build_meta" - -[project] -name = "keeper-secrets-manager-storage-gcp-kms" -version = "1.0.1" -description = "Keeper Secrets Manager SDK storage integration with Google Cloud KMS for encrypted key-value storage." -readme = "README.md" -requires-python = ">=3.6" -license = {file = "LICENSE"} -authors = [ - {name = "Keeper Security", email = "sm@keepersecurity.com"}, -] -keywords = [ - "Keeper", - "Password", - "Secrets Manager", - "Storage", - "GCP", - "KMS", - "Google Cloud", - "Key Management" -] -classifiers = [ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Security", - "Topic :: Security :: Cryptography", -] -dependencies = [ - "keeper-secrets-manager-core>=16.6.6", - "google-cloud-kms>=3.0.0", - "google-crc32c>=1.0.0", - "pycryptodome>=3.15.0", - "requests>=2.25.0", -] - -[project.urls] -Homepage = "https://github.com/Keeper-Security/secrets-manager" -Documentation = "https://docs.keeper.io/secrets-manager/secrets-manager/overview" -Repository = "https://github.com/Keeper-Security/secrets-manager" -"Bug Tracker" = "https://github.com/Keeper-Security/secrets-manager/issues" - -[tool.setuptools] -packages = ["keeper_secrets_manager_storage_gcp_kms"] - -[tool.setuptools.package-data] -"*" = ["*.md", "*.txt"] \ No newline at end of file diff --git a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/requirements.txt b/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/requirements.txt deleted file mode 100644 index 5897baf9f..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_gcp_kms/requirements.txt +++ /dev/null @@ -1,26 +0,0 @@ -cachetools==5.5.1 -certifi==2025.1.31 -cffi==1.17.1 -charset-normalizer==3.4.1 -cryptography==44.0.1 -google-api-core==2.24.1 -google-auth==2.38.0 -google-cloud-kms==3.3.0 -google-crc32c==1.6.0 -googleapis-common-protos==1.66.0 -grpc-google-iam-v1==0.14.0 -grpcio==1.70.0 -grpcio-status==1.70.0 -idna==3.10 -importlib_metadata==8.6.1 -keeper-secrets-manager-core>=16.6.6 -proto-plus==1.26.0 -protobuf==5.29.3 -pyasn1==0.6.1 -pyasn1_modules==0.4.1 -pycparser==2.22 -pycryptodome==3.21.0 -requests==2.32.3 -rsa==4.9 -urllib3==2.3.0 -zipp==3.21.0 diff --git a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/LICENSE b/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/LICENSE deleted file mode 100644 index e588d4ca1..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Keeper Security - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/README.md b/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/README.md deleted file mode 100644 index 72bc55b08..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/README.md +++ /dev/null @@ -1,101 +0,0 @@ -# Oracle KMS -Keeper Secrets Manager integrates with Oracle KMS in order to provide protection for Keeper Secrets Manager configuration files. With this integration, you can protect connection details on your machine while taking advantage of Keeper's zero-knowledge encryption of all your secret credentials. - -## Features -* Encrypt and Decrypt your Keeper Secrets Manager configuration files with Oracle KMS -* Protect against unauthorized access to your Secrets Manager connections -* Requires only minor changes to code for immediate protection. Works with all Keeper Secrets Manager Python SDK functionality - -## Prerequisites -* Supports the Python Secrets Manager SDK -* Requires `oci` package -* These are permissions required for Oracle Cloud service account: - * KMS CryptoKey Decrypter - * KMS CryptoKey Encrypter - * KMS CryptoKey Public Key Viewer - -## Setup - -1. Install KSM Storage Module - -The Secrets Manager Oracle KMS module can be installed using pip - -> `pip3 install keeper-secrets-manager-storage-oracle-kms` - -2. Configure Oracle Cloud Connection - -By default the oci library will utilize the default connection session setup located at `~/.oci/config`. - -See the Oracle Cloud documentation for more information on setting up an OCI session: https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm - -Alternatively, configuration variables can be provided explicitly using the `OCISessionConfig` data class and providing a path to the service account json file, profile name, and KSM endpoint name. - -You will need an Oracle Cloud service account to use the Oracle KMS integration. - -For more information on Oracle Cloud service accounts see the Oracle Cloud documentation: https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/managingcredentials.htm - -3. Add Oracle KMS Storage to Your Code - -Now that the Oracle Cloud connection has been configured, you need to tell the Secrets Manager SDK to utilize the Oracle KMS as storage. - -To do this, use `OracleKeyValueStorage` as your Secrets Manager storage in the SecretsManager constructor. - -The storage will require an Oracle Key ID, key version ID, as well as the name of the Secrets Manager configuration file which will be encrypted by Oracle KMS. - -```python -from keeper_secrets_manager_storage_oracle_kms import OracleKeyValueStorage, OCISessionConfig -from keeper_secrets_manager_core import SecretsManager - -config_file_location = "/home//.oci/config" -profile = "DEFAULT" -kms_crypto_endpoint = "https://.oraclecloud.com" -kms_mgmt_endpoint = "https://.oraclecloud.com" -key_id = '' -key_version_id = "" -config_path = "" -one_time_token = "" - -oci_session_config = OCISessionConfig(config_file_location, profile, kms_crypto_endpoint, kms_mgmt_endpoint) -storage = OracleKeyValueStorage(key_id=key_id, key_version=key_version_id, config_file_location=config_path, oci_session_config=oci_session_config, logger=None) - -secrets_manager = SecretsManager(token=one_time_token, config=storage) -all_records = secrets_manager.get_secrets() -first_record = all_records[0] -print(first_record) -``` - -## Change Key - -If you want to change the key from previous configuration, you can use the `change_key` method. - -```python -storage = OracleKeyValueStorage(key_id=key_id, key_version=key_version_id, config_file_location=config_path, oci_session_config=oci_session_config, logger=None) - -key_id_2 = "" -key_version_id_2 = "" - -is_changed = storage.change_key(key_id_2, key_version_id_2) -print("Key is changed:", is_changed) -``` - -## Decrypt Config - -You can use this method to decrypt the config file. This is not recommended for production use. - -```python -storage = OracleKeyValueStorage(key_id=key_id, key_version=key_version_id, config_file_location=config_path, oci_session_config=oci_session_config, logger=None) - -# Extract only plaintext -plaintext = storage.decrypt_config(False) -print(plaintext) - -# OR extract plaintext and save config as plaintext -plaintext = storage.decrypt_config(True) -print(plaintext) -``` - -You're ready to use the KSM integration 👍 - -## Using the Oracle KMS Integration - -Once setup, the Secrets Manager Oracle KMS integration supports all Secrets Manager Python SDK functionality. Your code will need to be able to access the Oracle KMS APIs in order to manage the decryption of the configuration file when run. \ No newline at end of file diff --git a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/keeper_secrets_manager_storage_oracle_kms/__init__.py b/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/keeper_secrets_manager_storage_oracle_kms/__init__.py deleted file mode 100644 index 8172e7c93..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/keeper_secrets_manager_storage_oracle_kms/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# _ __ -# | |/ /___ ___ _ __ ___ _ _ (R) -# | ' KmsCryptoClient: - return self.oci_kms_crypto_client - - def get_management_client(self) -> KmsManagementClient: - return self.oci_kms_management_client \ No newline at end of file diff --git a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/keeper_secrets_manager_storage_oracle_kms/oci_session_config.py b/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/keeper_secrets_manager_storage_oracle_kms/oci_session_config.py deleted file mode 100644 index 76473df6f..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/keeper_secrets_manager_storage_oracle_kms/oci_session_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# _ __ -# | |/ /___ ___ _ __ ___ _ _ (R) -# | ' str: - return self.kms_crypto_endpoint - - def get_kms_management_endpoint(self) -> str: - return self.kms_management_endpoint diff --git a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/keeper_secrets_manager_storage_oracle_kms/oracle_key_value_storage.py b/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/keeper_secrets_manager_storage_oracle_kms/oracle_key_value_storage.py deleted file mode 100644 index 5aea9e34a..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/keeper_secrets_manager_storage_oracle_kms/oracle_key_value_storage.py +++ /dev/null @@ -1,352 +0,0 @@ -# _ __ -# | |/ /___ ___ _ __ ___ _ _ (R) -# | ' str: - ciphertext: bytes = bytes() - plaintext: str = "" - - try: - # Read the config file - with open(self.config_file_location, 'rb') as config_file: - ciphertext = config_file.read() - if len(ciphertext) == 0: - self.logger.warning( - f"Empty config file {self.config_file_location}") - return "" - except Exception as err: - self.logger.error( - f"Failed to load config file {self.config_file_location}: {err}") - raise Exception( - f"Failed to load config file {self.config_file_location}") - - try: - # Decrypt the file contents - plaintext = decrypt_buffer( - key_id=self.key_id, - ciphertext=ciphertext, - crypto_client=self.crypto_client, - key_version_id=self.key_version_id, - is_asymmetric=self.is_asymmetric, - logger= self.logger - ) - if len(plaintext) == 0: - self.logger.error( - f"Failed to decrypt config file {self.config_file_location}") - elif autosave: - # Optionally autosave the decrypted content - with open(self.config_file_location, 'w') as config_file: - config_file.write(plaintext) - except Exception as err: - self.logger.error( - f"Failed to write decrypted config file {self.config_file_location}: {err}") - raise Exception( - f"Failed to write decrypted config file {self.config_file_location}") - - return plaintext - - def __save_config(self, updated_config: Dict[str, str] = {}, force: bool = False) -> None: - try: - # Retrieve current config - config = self.config or {} - config_json = json.dumps(config, sort_keys=True, indent=4) - config_hash = hashlib.md5(config_json.encode()).hexdigest() - - # Compare updated_config hash with current config hash - if updated_config: - updated_config_json = json.dumps( - updated_config, sort_keys=True, indent=4) - updated_config_hash = hashlib.md5( - updated_config_json.encode()).hexdigest() - - if updated_config_hash != config_hash: - config_hash = updated_config_hash - config_json = updated_config_json - # Update the current config - self.config = dict(updated_config) - - # Check if saving is necessary - if not force and config_hash == self.last_saved_config_hash: - self.logger.warning( - "Skipped config JSON save. No changes detected.") - return - - # Ensure the config file exists - self.create_config_file_if_missing() - - # Encrypt the config JSON and write to the file - stringified_value = json.dumps( - self.config, sort_keys=True, indent=4) - blob = encrypt_buffer( - key_id=self.key_id, - message=stringified_value, - crypto_client=self.crypto_client, - key_version_id=self.key_version_id, - is_asymmetric=self.is_asymmetric, - logger = self.logger - ) - if len(blob)!=0: - with open(self.config_file_location, 'wb') as config_file: - config_file.write(blob) - # Update the last saved config hash - self.last_saved_config_hash = config_hash - - except Exception as err: - self.logger.error(f"Error saving config: {err}") - - def load_config(self) -> None: - self.create_config_file_if_missing() - - try: - # Read the config file - contents: bytes = b"" - try: - with open(self.config_file_location, 'rb') as config_file: - contents = config_file.read() - self.logger.info( - f"Loaded config file {self.config_file_location}") - except Exception as err: - self.logger.error( - f"Failed to load config file {self.config_file_location}: {err}") - raise Exception( - f"Failed to load config file {self.config_file_location}") - - if len(contents) == 0: - self.logger.warning( - f"Empty config file {self.config_file_location}") - contents = b"{}" - - # Check if the content is plain JSON - config = None - json_error = None - decryption_error = False - try: - config_data = contents.decode() - config = json.loads(config_data) - # Encrypt and save the config if it's plain JSON - if config: - self.config = config - self.__save_config(config) - self.last_saved_config_hash = hashlib.md5( - json.dumps(config, sort_keys=True, indent=4).encode() - ).hexdigest() - except Exception as err: - json_error = err - - if json_error: - config_json = decrypt_buffer( - key_id=self.key_id, - ciphertext=contents, - crypto_client=self.crypto_client, - key_version_id=self.key_version_id, - is_asymmetric=self.is_asymmetric, - logger = self.logger - ) - try: - config = json.loads(config_json) - self.config = config or {} - self.last_saved_config_hash = hashlib.md5( - json.dumps(config, sort_keys=True, indent=4).encode() - ).hexdigest() - except Exception as err: - decryption_error = True - self.logger.error( - f"Failed to parse decrypted config file: {err}") - raise Exception( - f"Failed to parse decrypted config file {self.config_file_location}") - - if json_error and decryption_error: - self.logger.info( - f"Config file is not a valid JSON file: {json_error}") - raise Exception( - f"{self.config_file_location} may contain JSON format problems") - - except Exception as err: - self.logger.error(f"Error loading config: {err}") - raise err - - def change_key(self, new_key_id: str, new_key_version_id: str = None) -> bool: - old_key_id = self.key_id - old_key_version_id = self.key_version_id - old_crypto_client = self.crypto_client - old_management_client = self.management_client - - try: - # Update the key and reinitialize the CryptographyClient - config = self.config - if not config: - self.load_config() - self.key_id = new_key_id - self.key_version_id = new_key_version_id - self.get_key_details() - self.__save_config({}, force=True) - except Exception as error: - # Restore the previous key and crypto client if the operation fails - self.key_id = old_key_id - self.key_version_id = old_key_version_id - self.crypto_client = old_crypto_client - self.management_client = old_management_client - self.get_key_details() - self.logger.error( - f"Failed to change the key to '{new_key_id}' for config '{self.config_file_location}': {error}" - ) - raise Exception( - f"Failed to change the key for {self.config_file_location}") - - return True - - def get_key_details(self): - - opc_request_id = uuid.uuid4().hex.upper() - - key_details = self.management_client.get_key( - key_id=self.key_id, opc_request_id=opc_request_id) - - algorithm = key_details.data.key_shape.algorithm - - if algorithm == KeyShape.ALGORITHM_RSA: - self.is_asymmetric = True - elif algorithm == KeyShape.ALGORITHM_AES: - self.is_asymmetric = False - else: - raise Exception( - f"Unsupported key algorithm for the given key: {algorithm}") - - def read_storage(self) -> Dict[str, str]: - if not self.config: - self.load_config() - return self.config - - def save_storage(self, updated_config: Dict[str, str]) -> None: - self.__save_config(updated_config) - - def get(self, key: ConfigKeys) -> str: - config = self.read_storage() - return config.get(key.value) - - def set(self, key: ConfigKeys, value): - config = self.read_storage() - config[key.value] = value - self.save_storage(config) - return config - - def delete(self, key: ConfigKeys): - config = self.read_storage() - - kv = key.value - if kv in config: - del config[kv] - self.logger.debug("Removed key %s" % kv) - else: - self.logger.debug("No key %s was found in config" % kv) - - self.save_storage(config) - return config - - def delete_all(self): - self.read_storage() - self.config.clear() - self.save_storage(self.config) - return dict(self.config) - - def contains(self, key: ConfigKeys): - config = self.read_storage() - return key.value in config diff --git a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/keeper_secrets_manager_storage_oracle_kms/utils.py b/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/keeper_secrets_manager_storage_oracle_kms/utils.py deleted file mode 100644 index 3f29dc898..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/keeper_secrets_manager_storage_oracle_kms/utils.py +++ /dev/null @@ -1,122 +0,0 @@ -# _ __ -# | |/ /___ ___ _ __ ___ _ _ (R) -# | ' =45", "wheel", "setuptools_scm[toml]>=6.2"] -build-backend = "setuptools.build_meta" - -[project] -name = "keeper-secrets-manager-storage-oracle-kms" -version = "1.0.0" -description = "Keeper Secrets Manager SDK storage integration with Oracle Cloud KMS for encrypted key-value storage." -readme = "README.md" -requires-python = ">=3.6" -license = {text = "MIT"} -authors = [ - {name = "Keeper Security", email = "sm@keepersecurity.com"}, -] -keywords = [ - "Keeper", - "Password", - "Secrets Manager", - "Storage", - "Oracle", - "OCI", - "KMS", - "Oracle Cloud", - "Key Management" -] -classifiers = [ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Security", - "Topic :: Security :: Cryptography", -] -dependencies = [ - "keeper-secrets-manager-core>=16.6.6", - "oci>=2.146.0", - "pycryptodome>=3.15.0", - "requests>=2.25.0", - "cryptography>=44.0.0", -] - -[project.urls] -Homepage = "https://github.com/Keeper-Security/secrets-manager" -Documentation = "https://docs.keeper.io/secrets-manager/secrets-manager/overview" -Repository = "https://github.com/Keeper-Security/secrets-manager" -"Bug Tracker" = "https://github.com/Keeper-Security/secrets-manager/issues" - -[tool.setuptools] -packages = ["keeper_secrets_manager_storage_oracle_kms"] - -[tool.setuptools.package-data] -"*" = ["*.md", "*.txt"] \ No newline at end of file diff --git a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/requirements.txt b/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/requirements.txt deleted file mode 100644 index 7f2b7f542..000000000 --- a/sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/requirements.txt +++ /dev/null @@ -1,18 +0,0 @@ -certifi==2025.1.31 -cffi==1.17.1 -charset-normalizer==3.4.1 -circuitbreaker==2.0.0 -cryptography==44.0.1 -idna==3.10 -importlib_metadata==8.6.1 -keeper-secrets-manager-core>=16.6.6 -oci==2.146.0 -pycparser==2.22 -pycryptodome==3.21.0 -pyOpenSSL==24.3.0 -python-dateutil==2.9.0.post0 -pytz==2025.1 -requests==2.32.3 -six==1.17.0 -urllib3==2.3.0 -zipp==3.21.0 diff --git a/sdk/python/storage/keeper_secrets_manager_storages/requirements.txt b/sdk/python/storage/requirements.txt similarity index 100% rename from sdk/python/storage/keeper_secrets_manager_storages/requirements.txt rename to sdk/python/storage/requirements.txt diff --git a/sdk/python/storage/keeper_secrets_manager_storages/setup.cfg b/sdk/python/storage/setup.cfg similarity index 100% rename from sdk/python/storage/keeper_secrets_manager_storages/setup.cfg rename to sdk/python/storage/setup.cfg diff --git a/sdk/python/storage/keeper_secrets_manager_storages/setup.py b/sdk/python/storage/setup.py similarity index 100% rename from sdk/python/storage/keeper_secrets_manager_storages/setup.py rename to sdk/python/storage/setup.py diff --git a/sdk/rust/.gitignore b/sdk/rust/.gitignore deleted file mode 100644 index 09bdf0b96..000000000 --- a/sdk/rust/.gitignore +++ /dev/null @@ -1,81 +0,0 @@ -# Rust build artifacts -/target/ - -# IDE files -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# OS generated files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Logs -*.log - -# Runtime data -pids -*.pid -*.seed - -# Coverage directory used by tools like istanbul -coverage/ -*.lcov - -# nyc test coverage -.nyc_output - -# Dependency directories -node_modules/ - -# Optional npm cache directory -.npm - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# Environment variables -.env -.env.local -.env.development.local -.env.test.local -.env.production.local - -# Cache files -.cache/ - -# Temporary files -*.tmp -*.temp - -# Rust-specific -**/*.rs.bk -*.pdb - -# Documentation build -/doc/ - -# Backup files -*.bak -*.backup - -# Test artifacts -/tests/temp/ -test_* - -# Local configuration -config.local.json -*.local.json diff --git a/sdk/rust/Cargo.lock b/sdk/rust/Cargo.lock deleted file mode 100644 index 05f3347c1..000000000 --- a/sdk/rust/Cargo.lock +++ /dev/null @@ -1,2552 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - -[[package]] -name = "anstream" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - -[[package]] -name = "anstyle-parse" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" -dependencies = [ - "anstyle", - "windows-sys 0.59.0", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", -] - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" - -[[package]] -name = "cc" -version = "1.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" -dependencies = [ - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-targets", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "rand_core", - "typenum", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "data-encoding" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" - -[[package]] -name = "der" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "downcast" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "env_filter" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "humantime", - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core", - "subtle", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fragile" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core", - "subtle", -] - -[[package]] -name = "h2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "http" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" -dependencies = [ - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "hyper" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "ipnet" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itoa" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" - -[[package]] -name = "js-sys" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "keeper-secrets-manager-core" -version = "16.6.6" -dependencies = [ - "aes", - "aes-gcm", - "base64", - "block-padding", - "chrono", - "cipher", - "data-encoding", - "ecdsa", - "env_logger", - "hex", - "hmac", - "lazy_static", - "log", - "mockall", - "num-bigint", - "openssl", - "p256", - "rand", - "regex", - "reqwest", - "serde", - "serde_json", - "sha1", - "sha2", - "strum", - "strum_macros", - "tempfile", - "tokio", - "tracing", - "tracing-subscriber", - "url", - "winapi", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.169" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "matchers" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "mockall" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" -dependencies = [ - "cfg-if", - "downcast", - "fragile", - "mockall_derive", - "predicates", - "predicates-tree", -] - -[[package]] -name = "mockall_derive" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "openssl" -version = "0.10.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-src" -version = "300.4.1+3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-project-lite" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pkg-config" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "predicates" -version = "3.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" -dependencies = [ - "anstyle", - "predicates-core", -] - -[[package]] -name = "predicates-core" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" - -[[package]] -name = "predicates-tree" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" -dependencies = [ - "predicates-core", - "termtree", -] - -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - -[[package]] -name = "proc-macro2" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "redox_syscall" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "reqwest" -version = "0.12.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "mime_guess", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-native-tls", - "tower", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-registry", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustix" -version = "0.38.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls" -version = "0.23.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" - -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.217" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.217" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.134" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" -dependencies = [ - "indexmap", - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tempfile" -version = "3.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" -dependencies = [ - "cfg-if", - "fastrand", - "getrandom", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "termtree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tokio" -version = "1.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" -dependencies = [ - "ansi_term", - "chrono", - "lazy_static", - "matchers", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - -[[package]] -name = "unicode-ident" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" - -[[package]] -name = "web-sys" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result", - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/sdk/rust/Cargo.toml b/sdk/rust/Cargo.toml deleted file mode 100644 index 0fa78a58a..000000000 --- a/sdk/rust/Cargo.toml +++ /dev/null @@ -1,57 +0,0 @@ -[package] -name = "keeper-secrets-manager-core" -version = "16.6.6" -authors = ["Keeper Security "] -edition = "2021" -description = "Rust SDK for Keeper Secrets Manager" -license = "MIT" -repository = "https://github.com/Keeper-Security/secrets-manager" -homepage = "https://docs.keeper.io/en/keeperpam/secrets-manager/overview" -readme = "README.md" -keywords = ["secrets", "security", "keeper", "vault", "password-manager"] -categories = ["cryptography", "api-bindings"] - -[lib] -name = "keeper_secrets_manager_core" -path = "src/lib.rs" - -[features] -default = ["sequential_tests"] -sequential_tests = [] - -[dependencies] -aes="0.8" -aes-gcm ="0.10" -data-encoding = "2.6.0" -base64 = "0.22" -block-padding = "0.3" -cipher = "0.4" -chrono = "0.4" -ecdsa = "0.16" -hex = "0.4" -hmac = "0.12" -log = "0.4" -lazy_static = "1.4.0" -mockall = "0.13" -p256 = {version = "0.13.2", features = ["ecdh"]} -rand = "0.8" -regex = "1.9" -reqwest ={version = "0.12", features = ["blocking", "json","multipart"]} -tokio = { version = "1.15", features = ["full"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = {version= "1.0.133", features = ["preserve_order","arbitrary_precision"]} -sha2 = "0.10" -sha1 = "0.10" -strum = "0.26" -strum_macros = "0.26" -tempfile = "3.3" -tracing = "0.1" -tracing-subscriber = "0.2" -num-bigint = "0.4" -url = "2.5" - -[target.'cfg(windows)'.dependencies] -winapi = { version = "0.3" ,features = ["securitybaseapi","winbase","winerror","errhandlingapi","winnt"]} - -[dev-dependencies] -tempfile = "3.14" \ No newline at end of file diff --git a/sdk/rust/LICENSE b/sdk/rust/LICENSE deleted file mode 100644 index c68f9a1a1..000000000 --- a/sdk/rust/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Keeper Security Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/sdk/rust/README.md b/sdk/rust/README.md deleted file mode 100644 index 9e8ba6f4e..000000000 --- a/sdk/rust/README.md +++ /dev/null @@ -1,640 +0,0 @@ -## Secrets Manager - Rust -This SDK helps you retrieve and manage your secrets from keeper. - -### How to get it to work locally -To get it to work locally we need to have -* Rust installed -* cargo installed -* rustc installed - -## Code usage samples - -* this is for get_secrets functionality - -```rust - use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::FileKeyValueStorage}; - - - fn main()-> Result<(), KSMRError>{ - - let token = "".to_string(); - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let client_options = ClientOptions::new_client_options(token, file_name); - - let mut secrets_manager = SecretsManager::new(client_options)?; - - let secrets = secrets_manager.get_secrets(Vec::new())?; - - for secret in secrets { - secret.print(); - println!("---"); - } - Ok(()) - } -``` - -```rust - use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::InMemoryKeyValueStorage}; - - - fn main()-> Result<(), KSMRError>{ - - let im_base64 = "my_base_64_string".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(im_base64))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - let secrets_manager_response = secrets_manager.get_secrets_full_response(Vec::new())?; - let records = secrets_manager_response.records; - - println!("{}",records.len()); - - if !records.is_empty(){ - println!("Records returned from KSM:"); - for record in records{ - println!("UID: {}", record.uid); - } - } - - if secrets_manager_response.warnings.is_some(){ - let warnings = secrets_manager_response.warnings.unwrap(); - println!("{}", warnings); - } - - Ok(()) - } -``` - - -```rust - use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::InMemoryKeyValueStorage}; - - - fn main()-> Result<(), KSMRError>{ - - let im_base64 = "my_base_64_string".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(im_base64))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - let secrets_manager_response = secrets_manager.get_secrets_full_response(Vec::new())?; - let records = secrets_manager_response.records; - - println!("{}",records.len()); - - if !records.is_empty(){ - println!("Records returned from KSM:"); - for record in records{ - println!("UID: {}", record.uid); - } - } - - if secrets_manager_response.warnings.is_some(){ - let warnings = secrets_manager_response.warnings.unwrap(); - println!("{}", warnings); - } - - Ok(()) - } -``` - - -* Using Download file feature - -```rust - use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::FileKeyValueStorage}; - fn main()-> Result<(), KSMRError>{ - - let token = "".to_string(); - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let client_options = ClientOptions::new_client_options(token, file_name); - - let mut secrets_manager = SecretsManager::new(client_options)?; - - let records_filter = Vec::new(); // add record filters of needed based on UID - let secrets = secrets_manager.get_secrets(records_filter)?; - - for secret in secrets { - secret.download_file("file_name", "file_name_to_be_created_along_with_path")?; //secret.download("dummyy.txt","./dummy2.txt"); -> something like this - println!("---"); - } - Ok(()) - } - -``` - -* using searching standard field in a record feature - -```rust - use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::FileKeyValueStorage,enums::StandardFieldTypeEnum}; - - fn main()-> Result<(), KSMRError>{ - let token = "".to_string(); - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let client_options = ClientOptions::new_client_options(token, file_name); - - let mut secrets_manager = SecretsManager::new(client_options)?; - - let records_filter = Vec::new(); // add record filters of needed based on UID - let secrets = secrets_manager.get_secrets(records_filter)?; - - for secret in secrets { - let standard_field = secret.get_standard_field_value(StandardFieldTypeEnum::CARDREF.get_type(),false)?; - - let standard_field_2 = secret.get_standard_field_value("Pin Code",false)?; - println!("name : {}", standard_field); - println!("label : {}", standard_field_2); - println!("---"); - } - Ok(()) - } - -``` - -* using searching Custom field in a record feature - -```rust - use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::FileKeyValueStorage}; - - fn main()-> Result<(), KSMRError>{ - let token = "".to_string(); - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let client_options = ClientOptions::new_client_options(token, file_name); - - let mut secrets_manager = SecretsManager::new(client_options)?; - - let mut records_filter = Vec::new(); // add record filters of needed based on UID - records_filter.push("".to_string()); - let secrets = secrets_manager.get_secrets(records_filter)?; - - for secret in secrets { - let standard_field = secret.get_custom_field_value(">",false)?; - - let standard_field_2 = secret.get_custom_field_value("",true)?; - println!("multiple : {}", standard_field); - println!("single : {}", standard_field_2); - println!("---"); - } - Ok(()) - } -``` - -* using generate password feature - -```rust - use keeper_secrets_manager_core::{custom_error::KSMRError, utils::{generate_password_with_options, PasswordOptions}}; - - fn main()-> Result<(), KSMRError>{ - let password_options = PasswordOptions::new(); - let charset = "~".to_string(); - let password_options = password_options.length(34).digits(5).lowercase(5).uppercase(7).special_characters(5).special_characterset(charset); - - let password = generate_password_with_options(password_options)?; - println!("Password: {}", password); - Ok(()) - } - -``` - -* using get folders feature - -```rust - use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::FileKeyValueStorage}; - - fn main()-> Result<(), KSMRError>{ - let token = "".to_string(); - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let client_options = ClientOptions::new_client_options(token, file_name); - - let secrets_manager = SecretsManager::new(client_options)?; - - let secrets_folders = secrets_manager.get_folders()?; - println!("FOLDERS:--------------------------------------------------------------------------------------------------------------------------------------"); - for secret in secrets_folders { - let secret_string = secret.to_serialized_string(); - println!("{}", secret_string); - println!("---"); - } - Ok(()) - } - -``` - -* using update folder feature - -```rust -use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::FileKeyValueStorage}; - - fn main()-> Result<(), KSMRError>{ - let token = "".to_string(); - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let client_options = ClientOptions::new_client_options(token, file_name); - - println!("Update Records ---------------------------------------------------------------------------------------------------------------------------------"); - let mut secrets_manager_4 = SecretsManager::new(client_options)?; - let update_folder = secrets_manager_4.update_folder("".to_string(),"dummy_updated_API_RUST".to_string(),Vec::new())?; - println!("{}",(serde_json::to_string_pretty(&update_folder)?)); - Ok(()) - } -``` - -* using delete folder feature - -```rust -use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::FileKeyValueStorage}; - - fn main()-> Result<(), KSMRError>{ - let token = "".to_string(); - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let client_options = ClientOptions::new_client_options(token, file_name); - - let mut secrets_manager = SecretsManager::new(client_options)?; - println!("Delete Records ---------------------------------------------------------------------------------------------------------------------------------"); - let delete_response = secrets_manager.delete_folder(vec!["".to_string()],true)?; - println!("{}",(serde_json::to_string_pretty(&delete_response)?)); - - - Ok(()) - } -``` - -* using delete secret functionality - -```rust -use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::FileKeyValueStorage}; - - fn main()-> Result<(), KSMRError>{ - let token = "".to_string(); - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let client_options = ClientOptions::new_client_options(token, file_name); - - println!("Delete Secrets --------------------------------------------------------------"); - let mut secrets_manager_3 = SecretsManager::new(client_options)?; - let uids = vec!["".to_string()]; - let secrets_records_3 = secrets_manager_3.delete_secret(uids.clone())?; - - - Ok(()) - } - -``` - -* using update record standard and custom fields - -```rust -use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, enums::StandardFieldTypeEnum, storage::FileKeyValueStorage}; -use std::{collections::HashMap, fs::File, io::Write}; -use serde_json; - -fn main()-> Result<(), KSMRError>{ - let token = "".to_string(); - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let client_options = ClientOptions::new_client_options(token, file_name); - - let mut secrets_manager = SecretsManager::new(client_options)?; - - let mut uids = Vec::new(); - uids.push("".to_string()); - // get_secrets - let secrets_records = secrets_manager.get_secrets(uids.clone())?; - - for mut secret in secrets_records { - - let mut record_final_dict = HashMap::new(); - - let standard_field = secret.get_standard_field_value(StandardFieldTypeEnum::EMAIL.get_type(),false)?; - - record_final_dict.insert("before_Standard_update", secret.record_dict.clone()); - - let _standard_field_set = secret.set_standard_field_value_mut(StandardFieldTypeEnum::EMAIL.get_type(), "vfgatyth_changed_email_standard@email.com".into())?; - - record_final_dict.insert("after_Standard_update", secret.record_dict.clone()); - - let custom_field = secret.get_custom_field_value(StandardFieldTypeEnum::EMAIL.get_type(),false)?; - - record_final_dict.insert("before_custom_update", secret.record_dict.clone()); - - let _standard_field_set = secret.set_custom_field_value_mut(StandardFieldTypeEnum::EMAIL.get_type(), "vfgatyth_changed_email_custom@email.com".into())?; - - record_final_dict.insert("after_custom_update", secret.record_dict.clone()); - //save to file to check if updated as record object is very big - let created_String: String = serde_json::to_string(&record_final_dict).map_err(|err|KSMRError::SerializationError(err.to_string()))?; - let mut file = File::create("setting_fields.json").unwrap(); - file.write_all(created_String.as_bytes()).unwrap(); - } - Ok(()) -} -``` - -* using getting totp code - -```rust -use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, enums::StandardFieldTypeEnum, storage::FileKeyValueStorage}; -use std::{collections::HashMap, fs::File, io::Write}; -use serde_json; - -fn main()-> Result<(), KSMRError>{ - let token = "".to_string(); - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let client_options = ClientOptions::new_client_options(token, file_name); - - let mut secrets_manager = SecretsManager::new(client_options)?; - - let mut uids = Vec::new(); - uids.push("".to_string()); - // get_secrets - let secrets_records = secrets_manager.get_secrets(uids.clone())?; - - for mut secret in secrets_records { - let value = secret.get_standard_field_value(StandardFieldTypeEnum::ONETIMECODE.get_type(),false)?; - let url = utils::get_otp_url_from_value_obj(value)?; - let totp_code = utils::get_totp_code(&url)?; - println!("{}", totp_code.get_code()); - } - Ok(()) -} -``` - - - - -* How to upload file - -```rust -use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::FileKeyValueStorage}; - -fn main()-> Result<(), KSMRError>{ - let token = "".to_string(); - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let client_options = ClientOptions::new_client_options(token, file_name); - - println!("Delete Secrets --------------------------------------------------------------"); - let mut secrets_manager_3 = SecretsManager::new(client_options)?; - let uids = vec!["".to_string()]; - let secrets_records_3 = secrets_manager_3.get_secrets(uids.clone())?; - for secret in secret_records3{ - let keeper_file = KeeperFileUpload::get_file_for_upload( - "./dummy2222.txt", Some("test1_file.txt"), None,None - )?; - let upload_status = secrets_manager.upload_file(secret, keeper_file)?; - println!("upload status: {}", upload_status); - } - Ok(()) -} -``` - -* How to create a record - -```rust -fn test_record_create_normal() -> Result<(), KSMRError>{ - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::FileKeyValueStorage, - dto::{dtos::RecordCreate, field_structs::RecordField} - }; - use serde_json::{self, json, Number, Value}; - - // setup secrets manager - let token = "".to_string(); - let config = FileKeyValueStorage::new_config_storage("test_demo.json".to_string())?; - let client_options = ClientOptions::new_client_options(token, config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // This is how we create a Record - let mut created_record = RecordCreate::new("login".to_string(), "Login Record RUST_LOG_TEST".to_string(), Some("Dummy Notes".to_string())); - - // This is how we create a single field - let password_field = RecordField::new_record_field_with_options("password", Value::String(utils::generate_password()?), Some("Random password label".to_string()), false, true); - - // This is one of the ways to create a value object from JSON String - let security_question_value = Value::from_str("{\"question\": \"What is the question?\", \"answer\": \"This is the answer!\"}")?; - - //This is one way to create all fields directly in a vector - let fields = vec![ - RecordField::new_record_field("login", Value::String("login@email.com".to_string()), Some("My Custom Login lbl".to_string())), - - RecordField::new_record_field("login", Value::String("login@email.com".to_string()), Some("My Label".to_string())), - - password_field, - - RecordField::new_record_field("securityQuestion", security_question_value , Some("My Label".to_string())), - - RecordField::new_record_field("multiline", Value::String("This\nIs a multiline\nnote".to_string()) , Some("My Multiline lbl".to_string())), - - RecordField::new_record_field("secret", Value::String("SecretText".to_string()) , Some("My Hidden Field lbl".to_string())), - - RecordField::new_record_field("pinCode", Value::String("1234567890".to_string()) , Some("My Pin Code Field Lbl".to_string())), - - RecordField::new_record_field("addressRef", Value::String("some_UID".to_string()) , Some("My Address Reference".to_string())), - - RecordField::new_record_field("phone", json!({"region": "US", "number": "510-444-3333"}) , Some("My Phone Number".to_string())), - - RecordField::new_record_field("date", Value::Number(Number::from(1641934793000i64)) , Some("My date".to_string())), - - RecordField::new_record_field("date", Value::String("September eleventh two thousand and eleven".to_string()) , Some("Bad day in history of humanity".to_string())), - - RecordField::new_record_field("name", json!({"first": "Lincoln", "last": "Adams"}) , Some("His Name".to_string())), - ]; - - // Here we are adding fields object to standard fields - created_record.fields = Some(fields); - - created_record.custom = Some( - vec![ - RecordField::new_record_field("phone", json!({"region": "US", "number": "510-222-5555", "ext": "99887", "type": "Mobile"}) , Some("My Custom Phone Lbl".to_string())), - ] - ); - - // Make the API call - let _ = secrets_manager.create_secret("Shared Folder UID".to_string(), created_record)?; - - Ok(()) -} -``` - -* How to create a record - -```rust -use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - custom_error::KSMRError, - dto::{ - dtos::{RecordCreate}, - field_structs::{self}, - }, - enums::{DefaultRecordType}, - storage::FileKeyValueStorage, - utils::{self}, -}; -use log::error; -use tracing::{info}; - -fn main()-> Result<(), KSMRError>{ - let token = "".to_string(); - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let client_options = ClientOptions::new_client_options(token, file_name); - - println!("Create Secret\n--------------------------------------------------------------"); - let mut secrets_manager_3 = SecretsManager::new(client_options)?; - let mut new_record = RecordCreate::new( - DefaultRecordType::Login.get_type().to_string(), - "sample create record".to_string(), - None, - ); - let login_field = field_structs::Login::new( - "sample_email@metron.com".to_string(), - None, - Some(false), - Some(false), - ); - new_record.append_standard_fields(login_field); - let password_field = field_structs::Password::new( - "Dummy_Password#123".to_string(), - None, - Some(true), - Some(false), - Some(true), - None, - )?; - new_record.append_standard_fields(password_field); - let created_record: Result = - secrets_manager.create_secret("".to_string(), new_record); - match created_record { - Ok(data) => { - info!("created_record uid: {}", data); - data - } - Err(err) => { - error!("Error creating record: {}", err); - return Err(err); - } - }; - - Ok(()) -} -``` - -Using Keeper Notation -```rust -use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::FileKeyValueStorage}; - -fn main()-> Result<(), KSMRError>{ - let token = "".to_string(); - - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let client_options = ClientOptions::new_client_options(token, file_name); - - let mut secrets_manager = SecretsManager::new(client_options)?; - - let secrets_notation_result2 = secrets_manager.get_notation("/field/email[2]".to_string()); - - match secrets_notation_result2 { - Ok(data) => { - info!("Secrets data from notation: {}", data); - }, - Err(err) => { - error!("Error getting secret: {}", err); - return Err(err); - } - }; - Ok(()) -} -``` - - -Using Caching functionality -```rust -use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::FileKeyValueStorage, cache::KSMRCache}; -fn main(){ - let cache = KSMRCache::new_file_cache(Some("./cache.bin"))?; - - let token = "".to_string(); - - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let mut client_options = ClientOptions::new_client_options_with_token(token, file_name); - client_options.set_cache(cache.into()); - - let mut secrets_manager = SecretsManager::new(client_options)?; - let secrets = secrets_manager.get_secrets(Vec::new())?; - for secret in secrets { - info!("Secret: {}", secret); - }; -} -``` - -Using In Memory Storage for Creating a Folder -```rust -use keeper_secrets_manager_core::{core::{SecretsManager, ClientOptions}, enums::InMemoryKeyValueStorage, custom_error::KSMRError}; - -fn main() -> Result<(), KSMRError> { - let base_64_string = "".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(base_64_string))?; - let client_options = ClientOptions::new_client_options(config); - let secrets_manager = SecretsManager::new(client_options)?; - - //Create Folder - let parent_folder_uid: String = "".to_string(); - let sub_folder_uid: Option = Option::Some("".to_string()); - let create_options: CreateOptions = CreateOptions::new(parent_folder_uid, sub_folder_uid); - let new_folder_name: String = "New Folder".to_string(); - println!("Creating folder: {new_folder_name}"); - let created_folder_name = new_folder_name.clone(); - let result = secrets_manager.create_folder(create_options, new_folder_name, Vec::new())?; - println!("{result}"); - - Ok(()) -} -``` - -Using In Memory Storage for retrieving all folders -```rust -use keeper_secrets_manager_core::{core::{SecretsManager, ClientOptions}, enums::InMemoryKeyValueStorage, custom_error::KSMRError}; - -fn main() -> Result<(), KSMRError> { - let base_64_string = "".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(base_64_string))?; - let client_options = ClientOptions::new_client_options(config); - let secrets_manager = SecretsManager::new(client_options)?; - - //Get all Folders - let result_folders = secrets_manager.get_folders(); - let folders: Vec = result_folders.unwrap(); - for folder in folders{ - let folder_uid = folder.folder_uid; - let name:String = folder.name; - let parent_uid = folder.parent_uid; - println!("\nfolder_uid: {folder_uid}\nfolder_name: {name}\nparent_uid: {parent_uid}"); - } - - Ok(()) -} -``` - -Using In Memory Storage for update folder -```rust -use keeper_secrets_manager_core::{core::{SecretsManager, ClientOptions}, enums::InMemoryKeyValueStorage, custom_error::KSMRError}; - -fn main() -> Result<(), KSMRError> { - let base_64_string = "".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(base_64_string))?; - let client_options = ClientOptions::new_client_options(config); - let secrets_manager = SecretsManager::new(client_options)?; - - //Update folder name - secrets_manager.update_folder("".to_string(), "My folder".to_string(), Vec::new())?; - - Ok(()) -} -``` diff --git a/sdk/rust/docs/developer_docs.md b/sdk/rust/docs/developer_docs.md deleted file mode 100644 index 26b826488..000000000 --- a/sdk/rust/docs/developer_docs.md +++ /dev/null @@ -1,899 +0,0 @@ -# Rust SDK -Detailed Rust SDK docs for Keeper Secrets Manager - -## Download and Installation - -### Install with cargo - -### Source Code - -Find the Rust source code in the [GitHub repository](https://github.com/Keeper-Security/secrets-manager/tree/master/sdk/rust) - -## Using the SDK - -### Initialize - -##### Note : - -Using a token only to generate a new configuration (for later usage) requires at least one read operation to bind the token and fully populate the `test.json` - -**Secrets Manager** - -> SecretsManager::new(client\_options)? - -**Example Usage** -``` rust - use keeper_secrets_manager_core::{ClientOptions, SecretsManager,storage::FileKeyValueStorage} - let client_options = ClientOptions::new_client_options_with_token(token, config); - let mut secrets_manager = SecretsManager::new(client_options)?; -``` - -* Using token only to generate the config  -* requires at least one access operation to bind the token - - -| Parameter | Required | Type | Description | -| --- | --- | --- | --- | -| `token` | `Yes` | String | One-Time Access Token | -| `config` | `Yes` | KeyValueStorage | Storage Configuration | -| `client_options` | `Yes` | ClientOptions | Client Configuration | - -## Retrieve Secrets -### Get Secrets - -> secrets_manager.get_secrets(uids) - -**Example: Get All Secrets** -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get all records - let filtered_secrets = secrets_manager.get_secrets(Vec::new())?; - - // print out all records - for secret in filtered_secrets{ - secret.print(); - } -``` - -**Example: Get Secrets With a Filter** - -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - let uids = vec!["record_1_uid".to_string(), "record_2_uid".to_string()]; - // get filtered records - let filtered_secrets = secrets_manager.get_secrets(uids)?; - - // print out filtered records - for secret in filtered_secrets{ - secret.print(); - } -``` -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `uids` | `Vec` | Yes | None | UIDs of the records to fetch | - -**Response** - -Type: `Vec` - -All Keeper records, or records with the given UIDs - -> default - we will get all records which the token given has access to - -### Retrieve Values From a Secret - -#### Retrieve a Password - -This shortcut gets the password of a secret once that secret has been retrieved from Keeper Secrets Manager. - -**Get Password** - -> secret.get_standard_field_value('password', true) - -**Example Usage** -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get a specific secret by record UID - let secrets = secrets_manager.get_secrets(vec!["record_uid".to_string()])?; - let secret = match secrets.len(){ - 0 => return Err(KSMRError::CustomError("no secret with given uid is found".to_string())), - _ => &secrets[0], - }; - // get password from record - let my_secret_password = secret.get_standard_field_value("password", true); -``` - - -#### Retrieve Standard Fields - -**Field** - -> secret.get_standard_field_value(“FIELD_TYPE”.to_string(), true) - -**Example Usage** -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::FileKeyValueStorage, - custom_error::KSMRError, - enums::StandardFieldTypeEnum - }; - // setup secrets manager - let token = "your_token_goes_here".to_string(); - let config = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - let client_options = ClientOptions::new_client_options_with_token(token, config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get a specific secret by record UID - let secrets = secrets_manager.get_secrets(vec!["record_uid".to_string()])?; - let secret = match secrets.len(){ - 0 => return Err(KSMRError::CustomError("no secret with given uid is found".to_string())), - _ => &secrets[0], - }; - // use StandardFieldTypeEnum for getting accurate type for standard field without any typographic errors - let login_field = StandardFieldTypeEnum::LOGIN.get_type().to_string(); - // get login field from the secret - let my_secret_login = secret.get_standard_field_value(login_field, true) -``` - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `field_type` | `String` | Yes | None | Field type to get | -| `single` | `boolean` | Optional | False | Return only the first value | - -> Fields are found by type. For a list of field types, see the [Record Types](https://docs.keeper.io/en/secrets-manager/commander-cli/command-reference/record-commands/default-record-types#field-types) documentation. - - -### Retrieve Custom Fields - -**Custom Field** - -> secret.get_custom_field_value(“FIELD_TYPE”, true) - -**Example Usage** - -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get a specific secret by record UID - let secrets = secrets_manager.get_secrets(vec!["record_uid".to_string()])?; - let secret = match secrets.len(){ - 0 => return Err(KSMRError::CustomError("no secret with given uid is found".to_string())), - _ => &secrets[0], - }; - // Get a custom field, e.g. API Key - let api_key = secret.get_custom_field_value(“API Key”, true) -``` - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `field_type` | `String` | Yes | - | Field type to get | -| `single` | `boolean` | Optional | False | Return only the first value | - -Custom fields are any field that is not part of the record type definition but can be added by users. For a list of fields in each standard record type, see the [Record Types](https://docs.keeper.io/en/secrets-manager/commander-cli/command-reference/record-commands/default-record-types#standard-record-types) documentation. - -**Response** - -> Type: `String` or `Vec` - -the value or values of the field.  It will be a single value only if the `single=true` option is passed. - -### Retrieve Secrets by Title - -**Records by Title** - -> secrets_manager.get_secret_by_title(record_title) - -**Example Usage** - -``` rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get all secrets matching the record title - let secrets = secrets_manager.get_secret_by_title("My Credentials").unwrap().unwrap(); -``` -**Response** - -> Type: `Record>>` - - -| Parameter | Type | Required | Description | -| --- | --- | --- | --- | -| `record_title` | `&str` | Yes | Title of the record to be fetched | - - - - -### Retrieve Values using Keeper Notation - -**Get Notation** - -> secrets_manager.get_notation(query) - -**Example Usage** - -```rust - - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get all secrets matching the notation - let mut notation = "HDQTnxkTcPSOsHNAlbI4aQ/field/login".to_string(); - let mut result = secrets_manager.get_notation(notation)?; -``` - - -See [Keeper Notation documentation](https://docs.keeper.io/en/secrets-manager/secrets-manager/about/keeper-notation) to learn about Keeper Notation format and capabilities - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `query` | `String` | Yes | - | Keeper Notation query for getting a value from a specified field | - -#### Returns - -The value of the queried field - -Type: String or `Vec` - - - -### Retrieve a TOTP Code - -Get TOTP Code of given record - -> get_totp_code(&url) - -**Example Usage** - -```rust - - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get TOTP url value from a record - let value = record.get_standard_field_value(StandardFieldTypeEnum::ONETIMECODE.get_type(), false) - let url: String = utils::get_otp_url_from_value_obj(value)?; - - // get code from TOTP url - let totp = utils::get_totp_code(&url)?; - println!("{}", totp.get_code()); -``` -**Returns** - -> Type: `Result` - -| Parameter | Type | Required | Description | -| --- | --- | --- | --- | -| `value` | `Value` | Yes | Value from the record | -| `url` | `String` | Yes | TOTP Url | - - -## Update a Secret - -* Record update commands don't update local record data on success (esp. updated record revision) so any consecutive updates to an already updated record will fail due to revision mismatch. Make sure to reload all updated records after each update batch. - -#### Save Changes to a Secret - -**Save Secret** - -> secrets_manager.save(Record, UpdateTransactionType) - -**Example Usage** - -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get a specific secret by UID - let secret_to_update = secrets_manager.get_secrets(["".to_string()])?; - - // update a field value - let field_type= StandardFieldTypeEnum::LOGIN.get_type(); - secret_to_update.set_standard_field_value_mut(field_type, "sample@ks.com".into())?; - - let transaction_type: Option = Some(UpdateTransactionType::None); - - secrets_manager.save(secret_to_update, transaction_type); -``` - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `record` | `Record` | Yes | | Storage and query configuration | -| `transaction_type` | `UpdateTransactionType` | Yes | | Configuration for transactional update | - -Set field values using the `set_standard_field_value_mut` or the `set_custom_field_value_mut` method. - -Fields are found by type. - -For a list of field types, see the [Record Types](https://docs.keeper.io/en/secrets-manager/commander-cli/command-reference/record-commands/default-record-types#field-types) documentation. Some fields have multiple values in these cases, the value can be set to a list. - -### Update a Standard Field Value - -**Field** - -> secret.set_standard_field_value_mut(field_type, "new_field_value".into()) - -##### Example Usage -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get a specific secret by UID - let secret_to_update = secrets_manager.get_secrets(["".to_string()])?; - - // update a field value - let field_type= StandardFieldTypeEnum::LOGIN.get_type(); - secret_to_update.set_standard_field_value_mut(field_type, "sample@ks.com".into())?; - - let transaction_type: Option = Some(UpdateTransactionType::None); - - secrets_manager.save(secret_to_update, transaction_type); -``` - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `field_type` | `String` | Yes | | Field type to get | -| `transaction_type` | `UpdateTransactionType` | Yes | None | Configuration for transactional update | - -Fields are found by type. - -for a list of field types, see the [Record Types](https://docs.keeper.io/en/secrets-manager/commander-cli/command-reference/record-commands/default-record-types#field-types) documentation. - -### Update a Custom Field Value - -**Custom Field** - -> secret.set_custom_field_value_mut(field_type, "new_field_value".into()) - -**Example Usage** - - -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get a specific secret by UID - let secret_to_update = secrets_manager.get_secrets(["".to_string()])?; - - // update a field value - secret_to_update.set_custom_field_value_mut("Email", "sample@ks.com".into())?; - - let transaction_type: Option = Some(UpdateTransactionType::None); - - secrets_manager.save(secret_to_update, transaction_type); -``` - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `field_type` | `String` | Yes | | Field type to get | -| `transaction_type` | `UpdateTransactionType` | Yes | None | Configuration for transactional update | - - - -### Generate a Random Password - -Generate Password - -> generate_password_with_options(password_options) - -**Example Usage** -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get a specific secret by UID - let secret_to_update = secrets_manager.get_secrets(["".to_string()])?; - - # generate a random password - let charset: String = "$_!?#".to_string(); - let length = 32; - let digits = 2; - let lowercase = 2; - let uppercase = 2; - let special_characters = 2; - let password_options = PasswordOptions::new().length(length).digits(digits).lowercase(lowercase).uppercase(uppercase).special_characters(special_characters).special_characterset(charset); - let password = generate_password_with_options(password_options).unwrap(); - - # update a record with new password - let field_type= StandardFieldTypeEnum::PASSWORD.get_type(); - secret.set_standard_field_value_mut(field_type, password.into())?; - - # Save changes to the secret - let transaction_type: Option = Some(UpdateTransactionType::None); - secrets_manager.save(secret, transaction_type); -``` - - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `password_options` | `PasswordOptions` | Yes | | Configuration for the password | -| `charset` | `String` | Optional | | Set of special characters to be included in the password | -| `length` | `i32` | Optional | 64 | Length of password | -| `lowercase` | `i32` | Optional | 0 | Count of lowercase characters in the password | -| `uppercase` | `i32` | Optional | 0 | Count of uppercase characters in the password | -| `digits` | `i32` | Optional | 0 | Count of digits in the password | -| `special_characters` | `i32` | Optional | 0 | Count of special characters in the password | - -Each parameter indicates the minimum number of a type of character to include. For example, 'uppercase' indicates the minimum number of uppercase letters to include. - -### Download a File - -Download File - -> download_file(file_name, path) - -**Example Usage** -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get a specific secret by UID - let secrets = secrets_manager.get_secrets(["".to_string()])?[0]; - // Save all files to a tmp folder (create folder if does not exist) -    let path = format!("./temp/demo_{}.txt", secret.title); - secret.download_file("uploaded_file.txt", &path)?; -``` - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `file_name` | `&str` | Yes | | Name of the file to be downloaded | -| `path` | `&str` | Yes | | Path to download file | - - -### Upload a File - -Upload File - -> upload_file(owner_record, keeper_file) - -Example - -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get a specific secret by UID - let secrets = secrets_manager.get_secrets(["".to_string()])?[0]; - // Save all files to a tmp folder (create folder if does not exist) -    let path = format!("./temp/demo_{}.txt", secret.title); - // Prepare file data for upload - let keeper_file = KeeperFileUpload::get_file_for_upload(file_path, Some(file_name),file_title, mime_type)?; - - // Upload file attached to the owner record and get the file UID - file_uid = secrets_manager.upload_file(owner_record, keeper_file)?; -``` - -**Upload File** - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `owner_record` | `Record` | Yes | None | The record in which the file has to be uploaded | -| `keeper_file` | `KeeperFileUpload` | Yes | | The file to be uploaded | - -**Keeper File upload from File** - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `file_path` | `&str` | Yes | | Path to upload file | -| `file_name` | `Option<&str>` | Yes | | Name of the file to be uploaded | -| `file_title` | `Option<&str>` | Yes | | Title of the file to be uploaded | -| `mime_type` | `Option<&str>` | Yes | None | The type of data in the file. If none is provided, 'application/octet-stream' will be used | - - -#### Returns - -> Type: `String` - -The file UID of the attached file - -### Create a Secret - -#### Prerequisites: - -* Shared folder UID - * The shared folder must be accessible by the Secrets Manager Application - * You and the Secrets Manager application must have edit permission - * There must be at least one record in the shared folder -* Created records and record fields must be formatted correctly - * See the [documentation](https://docs.keeper.io/en/secrets-manager/commander-cli/command-reference/record-commands/default-record-types#field-types) for expected field formats for each record type -* TOTP fields accept only URL generated outside of the KSM SDK -* After record creation, you can upload file attachments using [upload\_file](https://docs.keeper.io/en/secrets-manager/secrets-manager/developer-sdk-library/python-sdk#upload-a-file) - - -**Create a Record** - -> secrets_manager.create_secret(folder_uid, record) - -**Login Record Example** - -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - dto::{dtos::RecordCreate, field_structs::RecordField} - }; - use serde_json::{self, json, Number, Value}; - - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // This is how we create a Record - let mut created_record = RecordCreate::new("login".to_string(), "Login Record RUST_LOG_TEST".to_string(), Some("Dummy Notes".to_string())); - - // This is how we create a single field - let password_field = RecordField::new_record_field_with_options("password".to_string(), Value::String(utils::generate_password()?), Some("Random password label".to_string()), false, true); - - // This is one of the ways to create a value object from JSON String - let security_question_value = Value::from_str("{\"question\": \"What is the question?\", \"answer\": \"This is the answer!\"}")?; - - //This is one way to create all fields directly in a vector - let fields = vec![ - RecordField::new_record_field("login".to_string(), Value::String("login@email.com".to_string()), Some("My Custom Login lbl".to_string())), - - RecordField::new_record_field("login".to_string(), Value::String("login@email.com".to_string()), Some("My Label".to_string())), - - password_field, - - RecordField::new_record_field("securityQuestion".to_string(),security_question_value , Some("My Label".to_string())), - - RecordField::new_record_field("multiline".to_string(),Value::String("This\nIs a multiline\nnote".to_string()) , Some("My Multiline lbl".to_string())), - - RecordField::new_record_field("secret".to_string(),Value::String("SecretText".to_string()) , Some("My Hidden Field lbl".to_string())), - - RecordField::new_record_field("pinCode".to_string(),Value::String("1234567890".to_string()) , Some("My Pin Code Field Lbl".to_string())), - - RecordField::new_record_field("addressRef".to_string(),Value::String("some_UID".to_string()) , Some("My Address Reference".to_string())), - - RecordField::new_record_field("phone".to_string(),json!({"region": "US", "number": "510-444-3333"}) , Some("My Phone Number".to_string())), - - RecordField::new_record_field("date".to_string(),Value::Number(Number::from(1641934793000i64)) , Some("My date".to_string())), - - RecordField::new_record_field("date".to_string(),Value::String("September eleventh two thousand and eleven".to_string()) , Some("Bad day in history of humanity".to_string())), - - RecordField::new_record_field("name".to_string(),json!({"first": "Lincoln", "last": "Adams"}) , Some("His Name".to_string())), - ]; - - // Here we are adding fields object to standard fields - created_record.fields = Some(fields); - - created_record.custom = Some( - vec![ - RecordField::new_record_field("phone".to_string(),json!({"region": "US", "number": "510-222-5555", "ext": "99887", "type": "Mobile"}) , Some("My Custom Phone Lbl".to_string())), - ] - ); - - // Make the API call - let _ = secrets_manager.create_secret("Shared_folder_uid".to_string(), created_record)?; -``` - -**Custom Type Example** - -> secrets_manager.create_secret(parent_folder_uid, record_create_object) - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `record_type` | `DefaultRecordType` | Yes | None | Type of record to be created | -| `title` | `String` | Yes | | The title of the created record | -| `note` | `String` | Yes | None | The note to be made in the created record | -| `value` | `String` | Yes | | Value for the field | -| `label` | `String` | Yes | None | Label for the field | -| `required` | `bool` | Yes | false | Defines if the field is required | -| `privacy_screen` | `bool` | Yes | false | Defines if the field value should be hidden | - -#### Returns - -> Type: `String` - -The record UID of the new record - -### Delete a Secret - -The Rust KSM SDK can delete records in the Keeper Vault. - -Delete Secret -> secrets_manager.delete_secret(vec![record_uid]) - -**Example Usage** -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // delete a specific secret by UID - let secret_to_delete = secrets_manager.delete_secret(["".to_string()])?; -``` - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `record_uid` | `String` | Yes | None | The uid of the record to be deleted | - - - -### Caching - -To protect against losing access to your secrets when network access is lost, the Rust SDK allows caching of secrets to the local machine in an encrypted file. - -**Setup and Configure Cache** - -In order to setup caching in the Rust SDK, include a caching post function when creating a `SecretsManager` object. - -The Rust SDK includes a default caching function in the `KSMRCache` class, which stores cached queries to a local file, thus serving as a disaster recovery function (as long as there's network connectivity, it always prefers network over cached data and will use cache only if the web vault is inaccessible). - -```rust -use keeper_secrets_manager_core::{core::{ClientOptions, SecretsManager}, custom_error::KSMRError, storage::FileKeyValueStorage, cache::KSMRCache}; -fn main(){ - let cache = KSMRCache::new_file_cache(Some("./cache.bin"))?; - - let token = "".to_string(); - - let file_name = FileKeyValueStorage::new_config_storage("test.json".to_string())?; - - let mut client_options = ClientOptions::new_client_options_with_token(token, file_name); - client_options.set_cache(cache.into()); - - let mut secrets_manager = SecretsManager::new(client_options)?; - let secrets = secrets_manager.get_secrets(Vec::new())?; - for secret in secrets { - info!("Secret: {}", secret); - }; -} -``` - -The default caching function in KSMCache class always stores last request only. -For example, if the first request (R1) successfully retrieves UID1 and updates the cache, but a subsequent request (R2) for UID2 fails, the cache will not include UID2. As a result, any later operations involving UID2 (e.g., lookup or disconnect) will return an empty response, since it was never added to the cache. - -Updating a record from cache (or creating a new record) invalidates cached record data, and consecutive updates of the same record will fail. Batch updates work as long as they modify different records. Always follow up cached record updates with a call to get\_secrets function to refresh cache (and pull updated metadata from vault like the new record revision, etc.) - -## Folders - -Folders have full CRUD support—create, read, update, and delete operations. - -### Read Folders - -Downloads full folder hierarchy. -> get_folders() - -**Response** - -> Type: `Vec` - -**Example Usage** -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - // get all folder - let secrets = secrets_manager.ger_folders()?; -``` -**Returns** -> Type: `Vec` - -### Create a Folder - -Requires `CreateOptions` and folder name to be provided. The folder UID parameter in `CreateOptions` is required—the UID of a shared folder, while sub-folder UID is optional, and if missing, a new regular folder is created directly under the parent (shared folder). There's no requirement for the sub-folder to be a direct descendant of the parent shared folder - it could be many levels deep. - -> create_folder(create_options: CreateOptions, folder_name: str, folders=None) - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `create_options` | `CreateOptions` | Yes | None | The parent and sub-folder UIDs | -| `folder_name` | `str` | Yes | | The folder name | -| `folders` | `Vec` | No | None | List of folders to use in the search for parent and sub-folder from CreateOptions | - -**Example Usage** - -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - let parent_folder_uid: String = "".to_string(); - let sub_folder_uid: Option = Option::Some(("")); - let create_options: CreateOptions = CreateOptions::new(parent_folder_uid, None); - let new_folder_name: String = "Sample Folder 200".to_string(); - println!("Creating folder: {new_folder_name}"); - let created_folder_name = new_folder_name.clone(); - let result = secrets_manager.create_folder(create_options, new_folder_name, Vec::new())?; - println!("Created folder {created_folder_name}"); -``` - -### Update a Folder - -Updates the folder metadata—currently folder name only. - -> secrets_manager.update_folder(folder_uid: str, folder_name: str, folders=None) - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `folder_uid` | `str` | Yes | | The folder uid | -| `folder_name` | `str` | Yes | | The new folder name | -| `folders` | `Vec` | No | None | List of folders to use in the search for parent folder | - -**Example Usage** -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::InMemoryKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - let update_folder = secrets_manager.update_folder("".to_string(),"dummy_updated_API_RUST".to_string(),Vec::new())?; - println!("{}",(serde_json::to_string_pretty(&update_folder)?)); -``` - -### Delete Folders - -Removes a list of folders. Use the `force_deletion` flag to remove non-empty folders. - -When using `force_deletion`, avoid sending parent with its children folder UIDs. Depending on the delete order, you may get an error—ex., if the parent force-deleted the child first. There's no guarantee that the list will always be processed in FIFO order. - -Any folder UIDs missing from the vault or not shared with the KSM application will not result in an error. - -> delete_folder(vec![“\”.to_string()], false) - -| Parameter | Type | Required | Default | Description | -| --- | --- | --- | --- | --- | -| `folder_uids` | `Vec` | Yes | | The folder UID list | -| `force_deletion` | `boolean` | No | false | Force deletion of non-empty folders | - -Example Usage - -```rust - use keeper_secrets_manager_core::{ - core::{ClientOptions, SecretsManager}, - storage::FileKeyValueStorage, - custom_error::KSMRError - }; - // setup secrets manager - let config_string = "your_base64_goes_here".to_string(); - let config = InMemoryKeyValueStorage::new_config_storage(Some(config_string))?; - let client_options = ClientOptions::new_client_options(config); - let mut secrets_manager = SecretsManager::new(client_options)?; - - let folder_uids = vec!["folder1_uid".to_string(),"folder_2_uid".to_string()]; - secrets_manager.delete_folder(folder_uids, true)?; -``` \ No newline at end of file diff --git a/sdk/rust/docs/publishing guide.md b/sdk/rust/docs/publishing guide.md deleted file mode 100644 index ab254c661..000000000 --- a/sdk/rust/docs/publishing guide.md +++ /dev/null @@ -1,72 +0,0 @@ -# GUIDE : Create and Publish a Rust Library - -Steps Involved: -1. Install Rust -2. Create a Library -3. Publish the Library to [crates.io](https::/crates.io/) -4. Optionally check with git -5. Usage of published library in other projects - -### Installing rust - -Rust can be installed using the official guide mentioned in [Rust installation guide](https://www.rust-lang.org/tools/install) - -Prerequisite : Have curl installed. -Ideal case commands to run: -> sudo apt update - -> sudo apt install curl - -> curl --version - -> curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - -After installing Rust using the curl proto link mentioned above, please check that the Rust is available on local machine. - -> rustc –version - -> cargo –version - -> rustup –version - - -### Creating a Library (optional/ just for reference) -We can use the current package as library. - -To build a new library, we can use the [cargo](https://doc.rust-lang.org/cargo/) tool. - -> cargo init --lib - -Create a sample public function/module in the library. - -Details of the library are in `Cargo.toml` file which is created by cargo when we create the new library - - -### Publishing the library to [crates.io](https::/crates.io/) -* In order to publish the rust library to crates.io, one must have an active account on crates.io \(preferably logged in with ‘GitHub’\) -* An account token is required in order to publish, which can be generated from the ‘Account Settings’ section on the crates.io account -* Open terminal in project directory (in same folder as Cargo.toml) and run the command mentioned below - > Cargo login -* This command will prompt you to enter the token. You can use your account token here. -* After successful login, add the following attributes with expected values in your Cargo.toml file: -```toml - [package] - name = "" - version = "" - authors = [" cargo publish - -### Usage of published library in other projects -To use the published library into other Rust projects, the library needs to be added in the dependencies section of `Cargo.toml` file, as mentioned below -```toml - [dependencies] - = "" -``` \ No newline at end of file diff --git a/sdk/rust/src/cache.rs b/sdk/rust/src/cache.rs deleted file mode 100644 index f038a4d86..000000000 --- a/sdk/rust/src/cache.rs +++ /dev/null @@ -1,240 +0,0 @@ -// -*- coding: utf-8 -*- -// _ __ -// | |/ /___ ___ _ __ ___ _ _ (R) -// | ' bool { - // match self { - // KSMCache::None => true, - // _ => false, - // } - - matches!(self, KSMCache::None) - } -} - -#[derive(Debug)] -pub struct KSMRCache { - cache: KSMCache, -} - -impl KSMCache { - pub fn save_cached_value(&mut self, data: &[u8]) -> Result<(), KSMRError> { - match self { - KSMCache::File(file_cache) => file_cache.save_cached_value(data), - KSMCache::Memory(memory_cache) => memory_cache.save_cached_value(data), - KSMCache::None => Err(KSMRError::CacheSaveError( - "No cache available for saving data.".to_string(), - )), - } - } - - pub fn get_cached_value(&self) -> Result, KSMRError> { - match self { - KSMCache::File(file_cache) => file_cache.get_cached_value(), - KSMCache::Memory(memory_cache) => memory_cache.get_cached_value(), - KSMCache::None => Err(KSMRError::CacheRetrieveError( - "No cache available for retrieving data.".to_string(), - )), - } - } - - pub fn purge(&mut self) -> Result<(), KSMRError> { - match self { - KSMCache::File(file_cache) => file_cache.purge(), - KSMCache::Memory(memory_cache) => memory_cache.purge(), - KSMCache::None => Ok(()), // No-op for None cache - } - } -} - -impl KSMRCache { - pub fn new_file_cache(file_path: Option<&str>) -> Result { - let file_cache = FileCache::new(file_path.unwrap_or(DEFAULT_FILE_PATH))?; - Ok(Self { - cache: KSMCache::File(file_cache), - }) - } - - /// This is not persistent and is not useful for most use cases, please prefer `new_file_cache` over this implementation. - pub fn new_memory_cache() -> Result { - Ok(Self { - cache: KSMCache::Memory(MemoryCache::new()), - }) - } - - pub fn new_none() -> Self { - Self { - cache: KSMCache::None, - } - } - - pub fn save_cached_value(&mut self, data: &[u8]) -> Result<(), KSMRError> { - match &mut self.cache { - KSMCache::File(file_cache) => file_cache.save_cached_value(data), - KSMCache::Memory(memory_cache) => memory_cache.save_cached_value(data), - KSMCache::None => Err(KSMRError::CacheSaveError( - "No cache available for saving data.".to_string(), - )), - } - } - - pub fn get_cached_value(&self) -> Result, KSMRError> { - match &self.cache { - KSMCache::File(file_cache) => file_cache.get_cached_value(), - KSMCache::Memory(memory_cache) => memory_cache.get_cached_value(), - KSMCache::None => Err(KSMRError::CacheRetrieveError( - "No cache available for retrieving data.".to_string(), - )), - } - } - - pub fn purge(&mut self) -> Result<(), KSMRError> { - match &mut self.cache { - KSMCache::File(file_cache) => file_cache.purge(), - KSMCache::Memory(memory_cache) => memory_cache.purge(), - KSMCache::None => Ok(()), // No-op for None cache - } - } -} - -impl From for KSMCache { - fn from(ksmr_cache: KSMRCache) -> Self { - ksmr_cache.cache - } -} - -impl From for KSMRCache { - fn from(ksm_cache: KSMCache) -> Self { - KSMRCache { cache: ksm_cache } - } -} - -// File-based cache -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FileCache { - file_path: String, -} - -impl FileCache { - pub fn new(file_path: &str) -> Result { - let mut path = file_path.trim().to_string(); - - if path.is_empty() { - path = DEFAULT_FILE_PATH.to_string(); - } - - if !Path::new(&path).is_absolute() { - if let Ok(ksm_cache_dir) = env::var("KSM_CACHE_DIR") { - let ksm_cache_dir = ksm_cache_dir.trim(); - if !ksm_cache_dir.is_empty() { - path = PathBuf::from(ksm_cache_dir) - .join(&path) - .to_string_lossy() - .to_string(); - } - } - } - let mut file_opened = match File::open(path.clone()) { - Ok(resp) => resp, - Err(err) => { - if err.to_string().contains("No such file or directory") - || err - .to_string() - .contains("The system cannot find the file specified") - { - let file = OpenOptions::new() - .read(true) // Open for reading - .write(true) // Open for writing - .create(true) // Create if it doesn't exist - .truncate(true)// Overwrite if already existing - .open(file_path).map_err(|err| KSMRError::CacheSaveError(format!("Error creating cache file in location mentioned {} and exited with error {}.", file_path,err))).unwrap(); - file - } else { - panic!("{}", err); - } - } - }; - - file_opened.flush().unwrap(); - - Ok(FileCache { file_path: path }) - } - - pub fn save_cached_value(&self, data: &[u8]) -> Result<(), KSMRError> { - let data = if data.is_empty() { &[] } else { data }; - let mut file = - File::create(&self.file_path).map_err(|e| KSMRError::CacheSaveError(e.to_string()))?; - file.write_all(data) - .map_err(|e| KSMRError::CacheSaveError(e.to_string()))?; - Ok(()) - } - - pub fn get_cached_value(&self) -> Result, KSMRError> { - let mut file = File::open(&self.file_path) - .map_err(|e| KSMRError::CacheRetrieveError(e.to_string()))?; - let mut data = Vec::new(); - file.read_to_end(&mut data) - .map_err(|e| KSMRError::CacheRetrieveError(e.to_string()))?; - Ok(data) - } - - pub fn purge(&self) -> Result<(), KSMRError> { - if Path::new(&self.file_path).exists() { - fs::remove_file(&self.file_path) - .map_err(|e| KSMRError::CachePurgeError(e.to_string()))?; - } - Ok(()) - } -} - -// In-memory cache -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct MemoryCache { - data: Vec, -} - -impl MemoryCache { - pub fn new() -> Self { - Self { data: Vec::new() } - } - - pub fn save_cached_value(&mut self, data: &[u8]) -> Result<(), KSMRError> { - self.data.clear(); - self.data.extend_from_slice(data); - Ok(()) - } - - pub fn get_cached_value(&self) -> Result, KSMRError> { - Ok(self.data.clone()) - } - - pub fn purge(&mut self) -> Result<(), KSMRError> { - self.data.clear(); - Ok(()) - } -} diff --git a/sdk/rust/src/config_keys.rs b/sdk/rust/src/config_keys.rs deleted file mode 100644 index 1c0aa84db..000000000 --- a/sdk/rust/src/config_keys.rs +++ /dev/null @@ -1,198 +0,0 @@ -// -*- coding: utf-8 -*- -// _ __ -// | |/ /___ ___ _ __ ___ _ _ (R) -// | ' &str { - match self { - ConfigKeys::KeyUrl => "url", - ConfigKeys::KeyClientId => "clientId", - ConfigKeys::KeyClientKey => "clientKey", - ConfigKeys::KeyAppKey => "appKey", - ConfigKeys::KeyOwnerPublicKey => "appOwnerPublicKey", - ConfigKeys::KeyPrivateKey => "privateKey", - ConfigKeys::KeyServerPublicKeyId => "serverPublicKeyId", - ConfigKeys::KeyBindingToken => "bat", - ConfigKeys::KeyBindingKey => "bindingKey", - ConfigKeys::KeyHostname => "hostname", - } - } - - /// Returns an optional `ConfigKeys` enum variant corresponding to the provided string value. - /// - /// # Parameters - /// - /// - `value`: The string representation of the key. - /// - /// # Returns - /// - /// An `Option` that will be `Some` if the string corresponds to a valid key, - /// and `None` otherwise. - /// - /// # Examples - /// - /// ``` - /// use keeper_secrets_manager_core::config_keys::ConfigKeys; - /// assert_eq!(ConfigKeys::key_from_str("url"), Some(ConfigKeys::KeyUrl)); - /// assert_eq!(ConfigKeys::key_from_str("clientId"), Some(ConfigKeys::KeyClientId)); - /// assert_eq!(ConfigKeys::key_from_str("unknown"), None); - /// ``` - pub fn key_from_str(value: &str) -> Option { - match value { - "url" => Some(ConfigKeys::KeyUrl), - "clientId" => Some(ConfigKeys::KeyClientId), - "clientKey" => Some(ConfigKeys::KeyClientKey), - "appKey" => Some(ConfigKeys::KeyAppKey), - "appOwnerPublicKey" => Some(ConfigKeys::KeyOwnerPublicKey), - "privateKey" => Some(ConfigKeys::KeyPrivateKey), - "serverPublicKeyId" => Some(ConfigKeys::KeyServerPublicKeyId), - "bat" => Some(ConfigKeys::KeyBindingToken), - "bindingKey" => Some(ConfigKeys::KeyBindingKey), - "hostname" => Some(ConfigKeys::KeyHostname), - _ => None, - } - } - - /// Returns an optional `ConfigKeys` enum variant from a string value, - /// allowing for additional variants using both the key name and the enum variant name. - /// - /// # Parameters - /// - /// - `value`: The string representation of the key. - /// - /// # Returns - /// - /// An `Option` that will be `Some` if the string corresponds to a valid key, - /// and `None` otherwise. - /// - /// # Examples - /// - /// ``` - /// use keeper_secrets_manager_core::config_keys::ConfigKeys; - /// assert_eq!(ConfigKeys::get_enum("url"), Some(ConfigKeys::KeyUrl)); - /// assert_eq!(ConfigKeys::get_enum("clientId"), Some(ConfigKeys::KeyClientId)); - /// assert_eq!(ConfigKeys::get_enum("invalidKey"), None); - /// ``` - pub fn get_enum(value: &str) -> Option { - match value { - "url" => Some(ConfigKeys::KeyUrl), - "clientId" => Some(ConfigKeys::KeyClientId), - "clientKey" => Some(ConfigKeys::KeyClientKey), - "appKey" => Some(ConfigKeys::KeyAppKey), - "appOwnerPublicKey" => Some(ConfigKeys::KeyOwnerPublicKey), - "privateKey" => Some(ConfigKeys::KeyPrivateKey), - "serverPublicKeyId" => Some(ConfigKeys::KeyServerPublicKeyId), - "bat" => Some(ConfigKeys::KeyBindingToken), - "bindingKey" => Some(ConfigKeys::KeyBindingKey), - "hostname" => Some(ConfigKeys::KeyHostname), - _ => None, - } - } -} - -/// Custom deserialization function for a `HashMap`. -/// -/// This function deserializes a map from a JSON string into a `HashMap` where the keys -/// are of type `ConfigKeys`. If an invalid key is encountered, it returns an error. -/// -/// # Parameters -/// -/// - `json_data`: A string containing the JSON structure that represents the map. -/// -/// # Returns -/// -/// A `Result, serde_json::Error>` that contains the deserialized -/// `HashMap` if successful, or an error if an invalid key is found or if the input JSON is invalid. -/// -/// # Examples -/// -/// ``` -/// use keeper_secrets_manager_core::config_keys::{ConfigKeys, deserialize_map_from_str}; -/// use std::collections::HashMap; -/// -/// let json_data = r#"{"url": "http://example.com"}"#; -/// -/// // Use the function to deserialize the JSON string directly into a HashMap -/// let result: HashMap = deserialize_map_from_str(json_data).unwrap(); -/// assert_eq!(result.get(&ConfigKeys::KeyUrl), Some(&"http://example.com".to_string())); -/// ``` -/// -/// # Errors -/// -/// This function will return a `serde_json::Error` if any key in the input map is not valid -/// according to the `ConfigKeys` enum or if the JSON structure is malformed. -/// -/// # Panics -/// -/// This function does not panic under normal circumstances. -pub fn deserialize_map_from_str(json_data: &str) -> Result, KSMRError> { - let map: HashMap = serde_json::from_str(json_data) - .map_err(|e| KSMRError::SerializationError(format!("JSON deserialization error: {}", e)))?; - let mut result = HashMap::new(); - - for (key, value) in map { - if let Some(enum_key) = ConfigKeys::key_from_str(&key) { - result.insert(enum_key, value); - } else { - return Err(KSMRError::SerializationError(format!( - "Failed to parse JSON: {}", - key - ))); - } - } - Ok(result) -} diff --git a/sdk/rust/src/constants.rs b/sdk/rust/src/constants.rs deleted file mode 100644 index 3c89edd80..000000000 --- a/sdk/rust/src/constants.rs +++ /dev/null @@ -1,64 +0,0 @@ -// -*- coding: utf-8 -*- -// _ __ -// | |/ /___ ___ _ __ ___ _ _ (R) -// | ' HashMap<&'static str, &'static str> { - // Define the static array with your key-value pairs - let data = [ - ("US", "keepersecurity.com"), - ("EU", "keepersecurity.eu"), - ("AU", "keepersecurity.com.au"), - ("GOV", "govcloud.keepersecurity.us"), - ("JP", "keepersecurity.jp"), - ("CA", "keepersecurity.ca"), - ]; - - // Build the HashMap at runtime using the array - let mut map = HashMap::new(); - for (key, value) in data.iter() { - map.insert(*key, *value); - } - - map -} - -pub fn get_keeper_public_keys() -> HashMap { - // Define the static array with key-value pairs - let data = [ - ("1", "BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM"), - ("2", "BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM"), - ("3", "BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM"), - ("4", "BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM"), - ("5", "BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM"), - ("6", "BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM"), - ("7", "BK9w6TZFxE6nFNbMfIpULCup2a8xc6w2tUTABjxny7yFmxW0dAEojwC6j6zb5nTlmb1dAx8nwo3qF7RPYGmloRM"), - ("8", "BKnhy0obglZJK-igwthNLdknoSXRrGB-mvFRzyb_L-DKKefWjYdFD2888qN1ROczz4n3keYSfKz9Koj90Z6w_tQ"), - ("9", "BAsPQdCpLIGXdWNLdAwx-3J5lNqUtKbaOMV56hUj8VzxE2USLHuHHuKDeno0ymJt-acxWV1xPlBfNUShhRTR77g"), - ("10", "BNYIh_Sv03nRZUUJveE8d2mxKLIDXv654UbshaItHrCJhd6cT7pdZ_XwbdyxAOCWMkBb9AZ4t1XRCsM8-wkEBRg"), - ("11", "BA6uNfeYSvqagwu4TOY6wFK4JyU5C200vJna0lH4PJ-SzGVXej8l9dElyQ58_ljfPs5Rq6zVVXpdDe8A7Y3WRhk"), - ("12", "BMjTIlXfohI8TDymsHxo0DqYysCy7yZGJ80WhgOBR4QUd6LBDA6-_318a-jCGW96zxXKMm8clDTKpE8w75KG-FY"), - ("13", "BJBDU1P1H21IwIdT2brKkPqbQR0Zl0TIHf7Bz_OO9jaNgIwydMkxt4GpBmkYoprZ_DHUGOrno2faB7pmTR7HhuI"), - ("14", "BJFF8j-dH7pDEw_U347w2CBM6xYM8Dk5fPPAktjib-opOqzvvbsER-WDHM4ONCSBf9O_obAHzCyygxmtpktDuiE"), - ("15", "BDKyWBvLbyZ-jMueORl3JwJnnEpCiZdN7yUvT0vOyjwpPBCDf6zfL4RWzvSkhAAFnwOni_1tQSl8dfXHbXqXsQ8"), - ("16", "BDXyZZnrl0tc2jdC5I61JjwkjK2kr7uet9tZjt8StTiJTAQQmnVOYBgbtP08PWDbecxnHghx3kJ8QXq1XE68y8c"), - ("17", "BFX68cb97m9_sweGdOVavFM3j5ot6gveg6xT4BtGahfGhKib-zdZyO9pwvv1cBda9ahkSzo1BQ4NVXp9qRyqVGU") - ]; - - // Create and populate the HashMap dynamically - let mut map = HashMap::new(); - for (key, value) in data.iter() { - map.insert(key.to_string(), value.to_string()); - } - - map -} diff --git a/sdk/rust/src/core/core.rs b/sdk/rust/src/core/core.rs deleted file mode 100644 index 74b4edcb7..000000000 --- a/sdk/rust/src/core/core.rs +++ /dev/null @@ -1,2832 +0,0 @@ -// -*- coding: utf-8 -*- -// _ __ -// | |/ /___ ___ _ __ ___ _ _ (R) -// | ' , - pub config: KvStoreType, - pub log_level: Level, - pub hostname: Option, - cache: KSMCache, -} - -impl ClientOptions { - pub fn new( - token: String, - config: KvStoreType, - log_level: Level, - hostname: Option, - insecure_skip_verify: Option, - cache: KSMCache, - ) -> Self { - Self { - token, - config, - log_level, - hostname, - insecure_skip_verify, - cache, - } - } - /// This function is used to create client options when token is involved - for FileKeyValueStorage - pub fn new_client_options_with_token(token: String, config: KvStoreType) -> Self { - Self::new( - token, - config, - Level::Error, - None, - None, - cache::KSMCache::None, - ) - } - - /// this function is used to create client options when token is not involved - for InMemoryKeyValueStorage - pub fn new_client_options(config: KvStoreType) -> Self { - Self::new( - "".to_string(), - config, - Level::Error, - None, - None, - cache::KSMCache::None, - ) - } - - pub fn set_cache(&mut self, cache: KSMCache) { - self.cache = cache; - } - - pub fn set_log_level(&mut self, log_level: Level) { - self.log_level = log_level; - } -} - -const DEFAULT_KEY_ID: &str = "10"; -const NOTATION_PREFIX: &str = "keeper"; - -pub struct SecretsManager { - pub token: String, - pub hostname: String, - pub verify_ssl_certs: bool, - pub config: KvStoreType, - pub log_level: Level, - pub cache: KSMCache, -} - -impl Clone for SecretsManager { - fn clone(&self) -> Self { - SecretsManager { - // Clone each field of the struct - token: self.token.clone(), - hostname: self.hostname.clone(), - verify_ssl_certs: self.verify_ssl_certs, - config: self.config.clone(), - log_level: self.log_level, - cache: self.cache.clone(), - } - } -} - -impl SecretsManager { - pub fn new(client_options: ClientOptions) -> Result { - let mut secrets_manager = SecretsManager { - token: String::new(), - hostname: String::new(), - verify_ssl_certs: false, - config: KvStoreType::None, - log_level: Level::Info, // Default to Info if not provided - cache: KSMCache::None, // Default is no cache - }; - - let mut config = client_options.config; - if matches!(config, KvStoreType::None) { - if env::var("KSM_CONFIG").is_ok() { - // Create a new InMemoryKeyValueStorage instance - let config_str = env::var("KSM_CONFIG").unwrap(); - let in_memory_storage = - InMemoryKeyValueStorage::new(Some(config_str)).map_err(|e| { - KSMRError::SecretManagerCreationError( - format!("Error creating InMemoryKeyValueStorage: {}", e).to_owned(), - ) - })?; - config = KvStoreType::InMemory(in_memory_storage); - secrets_manager.config = config.clone(); - } - } else if !client_options.token.is_empty() { - let token_parts: Vec<&str> = client_options.token.trim().split(":").collect(); - if token_parts.len() == 1 { - if client_options.hostname.is_none() - || client_options - .hostname - .as_ref() - .map_or(true, String::is_empty) - { - return Err(KSMRError::SecretManagerCreationError( - "The hostname must be present in the token or provided as a parameter" - .to_owned(), - )); - } - secrets_manager.token = client_options.token.clone(); - secrets_manager.hostname = client_options - .hostname - .ok_or_else(|| { - KSMRError::SecretManagerCreationError("Hostname is required".to_owned()) - })? - .clone(); - } else { - let token_host_key = token_parts[0].to_uppercase(); - let keeper_servers = get_keeper_servers(); - let token_host = keeper_servers.get(token_host_key.as_str()); - if token_host.is_none() { - secrets_manager.hostname = token_parts[0].to_string().to_owned(); - } else { - secrets_manager.hostname = token_host.as_ref().unwrap().to_string(); - } - secrets_manager.token = token_parts[1].to_string(); - } - if secrets_manager.token.is_empty() { - secrets_manager.token = client_options.token.clone(); - } - } - - if !client_options.cache.is_none() { - secrets_manager.cache = client_options.cache; - } - - secrets_manager.verify_ssl_certs = client_options.insecure_skip_verify.unwrap_or(false); - if env::var("KSM_SKIP_VERIFY").is_ok() { - let env_skip_verify = env::var("KSM_SKIP_VERIFY").unwrap().parse::(); - match env_skip_verify { - Ok(skip_verify) => secrets_manager.verify_ssl_certs = !skip_verify, - Err(e) => { - return Err(KSMRError::SecretManagerCreationError(format!( - "Error parsing KSM_SKIP_VERIFY to a boolean value: {}", - e - ))); - } - } - } - - if matches!(config, KvStoreType::None) { - config = KvStoreType::File(crate::storage::FileKeyValueStorage::new(None)?); - } - - if !secrets_manager.token.is_empty() { - config - .set(ConfigKeys::KeyClientKey, secrets_manager.token.clone()) - .unwrap(); - } - if !secrets_manager.hostname.is_empty() { - config - .set(ConfigKeys::KeyHostname, secrets_manager.hostname.clone()) - .unwrap(); - } - - info!("Initializing SecretsManager and values are set"); - - if config.get(ConfigKeys::KeyServerPublicKeyId).is_ok() { - let server_public_key_id: Option = - config.get(ConfigKeys::KeyServerPublicKeyId).unwrap(); - let keeper_public_keys = get_keeper_public_keys(); - if server_public_key_id.is_none() { - debug!("Setting public key id to the default: {}", DEFAULT_KEY_ID); - config - .set(ConfigKeys::KeyServerPublicKeyId, DEFAULT_KEY_ID.to_string()) - .unwrap(); - } else if server_public_key_id.is_some() - && !keeper_public_keys.contains_key(server_public_key_id.unwrap().as_str()) - { - debug!( - "Public key id {} does not exists, set to default : {}", - config - .get(ConfigKeys::KeyServerPublicKeyId) - .unwrap() - .unwrap(), - DEFAULT_KEY_ID - ); - config - .set(ConfigKeys::KeyServerPublicKeyId, DEFAULT_KEY_ID.to_string()) - .unwrap(); - } - } else { - return Err(KSMRError::SecretManagerCreationError( - "Failed to retrieve the server public key id from config".to_owned(), - )); - } - secrets_manager.config = config.clone(); - - match secrets_manager._init() { - Ok(secrets_manager) => Ok(secrets_manager), - Err(e) => Err(e), - } - } - - fn _init(&mut self) -> Result { - if !self.verify_ssl_certs { - debug!("WARNING: Running without SSL cert verification. Execute 'SecretsManager(..., verify_ssl_certs=True)' or 'KSM_SKIP_VERIFY=FALSE' to enable verification."); - } - - let client_id = self.config.get(ConfigKeys::KeyClientId).map_err(|e| { - KSMRError::SecretManagerCreationError(format!( - "Error getting client key from config: {}", - e - )) - })?; - - let client_id_copy = client_id.clone(); - let client_id_empty_state = match client_id_copy { - Some(client_id) => client_id.is_empty(), - None => true, - }; - let mut unbound_token = false; - if !self.token.is_empty() { - unbound_token = true; - if !client_id_empty_state { - let client_key = self.token.clone(); - let client_key_bytes = url_safe_str_to_bytes(&client_key).map_err(|e| { - KSMRError::SecretManagerCreationError(format!( - "Error parsing client key to bytes: {}", - e - )) - })?; - - let client_key_hash = Hmac::::new_from_slice(client_key_bytes.as_slice()) - .map_err(|e| { - KSMRError::SecretManagerCreationError(format!("Error creating HMAC: {}", e)) - })? - .chain_update(b"KEEPER_SECRETS_MANAGER_CLIENT_ID") - .finalize() - .into_bytes() - .to_vec(); - - let token_client_id: String = bytes_to_base64(&client_key_hash); - match client_id { - Some(client_id) => { - if token_client_id == client_id { - let app_key = self.config.get(ConfigKeys::KeyAppKey).unwrap(); - if app_key.is_some() { - unbound_token = false; - warn!("the storage is already initiated with the same token",); - } else { - warn!("the storage is already initiated but not bound"); - } - } else { - return Err(KSMRError::SecretManagerCreationError(format!("The provided token does not match the client id and is initiated with a different token - client ID: {}", client_id))); - } - } - None => { - warn!("the storage is already initiated but not bound"); - } - } - } - } - - if !(client_id_empty_state || unbound_token) { - debug!("Already bound to the token"); - - if self.config.get(ConfigKeys::KeyClientKey).unwrap().is_none() { - let _ = self.config.delete(ConfigKeys::KeyClientKey).map_err(|er| { - KSMRError::SecretManagerCreationError(format!( - "Error deleting client key: {}", - er - )) - }); - } - return Ok(self.clone()); - } else { - let existing_secret_key = self - .load_secret_key() - .map_err(|err| { - KSMRError::SecretManagerCreationError(format!( - "Error loading secret key: {}", - err - )) - })? - .clone(); - - if existing_secret_key.is_empty() { - return Err(KSMRError::SecretManagerCreationError( - "Failed to load existing secret key and cannot locate One time password" - .to_string(), - )); - } - - let existing_secret_key_bytes = url_safe_str_to_bytes(&existing_secret_key) - .map_err(|err| { - KSMRError::SecretManagerCreationError(format!( - "Error parsing existing secret key to bytes: {}", - err - )) - })? - .clone(); - - let existing_secret_key_hash_bytes = - Hmac::::new_from_slice(existing_secret_key_bytes.as_slice()) - .map_err(|e| { - KSMRError::SecretManagerCreationError(format!("Error creating HMAC: {}", e)) - })? - .chain_update(b"KEEPER_SECRETS_MANAGER_CLIENT_ID") - .finalize() - .into_bytes() - .to_vec(); - - let existing_secret_key_hash = bytes_to_base64(&existing_secret_key_hash_bytes); - - let _ = self.config.delete(ConfigKeys::KeyClientId).map_err(|err| { - KSMRError::SecretManagerCreationError(format!("Error deleting client id: {}", err)) - })?; - let _ = self - .config - .delete(ConfigKeys::KeyPrivateKey) - .map_err(|err| { - KSMRError::SecretManagerCreationError(format!( - "Error deleting private key: {}", - err - )) - })?; - - if self.config.get(ConfigKeys::KeyClientId).unwrap().is_none() { - self.config.delete(ConfigKeys::KeyAppKey).map_err(|err| { - KSMRError::SecretManagerCreationError(format!( - "Error deleting app key: {}", - err - )) - })?; - }; - - self.config - .set(ConfigKeys::KeyClientId, existing_secret_key_hash.clone()) - .unwrap(); - - let private_key = self.config.get(ConfigKeys::KeyPrivateKey).map_err(|err| { - KSMRError::SecretManagerCreationError(format!( - "Error getting private key from config: {}", - err - )) - })?; - - let private_key_value = match private_key { - Some(value) => value.clone(), - None => "".to_string(), - }; - - if private_key_value.is_empty() { - let private_key_der = CryptoUtils::generate_private_key_der()?; - let _y = private_key_der.to_vec(); - let private_key_set_result = self - .config - .set(ConfigKeys::KeyPrivateKey, bytes_to_base64(&private_key_der)); - let _ = match private_key_set_result { - Ok(_) => Ok(self.clone()), - Err(err) => Err(KSMRError::SecretManagerCreationError(format!( - "Error setting private key: {}", - err - ))), - }; - } - } - - Ok(self.clone()) - } - - pub fn load_secret_key(&self) -> Result { - let mut current_secret_key = "".to_string(); - // implementation of load_secret_key method - let env_secret_key = env::var("KSM_TOKEN") - .ok() - .filter(|val| !val.is_empty()) - .unwrap_or("".to_string()); - - if !env_secret_key.is_empty() { - current_secret_key = env_secret_key; - info!("Secret key found in environment variable"); - } - - if current_secret_key.is_empty() && !self.token.is_empty() { - current_secret_key = self.token.clone(); - info!("Secret key found in config"); - } - - if current_secret_key.is_empty() { - let config_secret_key = self.config.get(ConfigKeys::KeyClientKey)?; - current_secret_key = config_secret_key.unwrap().clone(); - info!("Secret key found in configuration file"); - } - - Ok(current_secret_key) - } - - pub fn generate_transmission_key(key_id: &str) -> Result { - let transmission_key = generate_random_bytes(32); - let keeper_public_keys = get_keeper_public_keys(); - if !keeper_public_keys.contains_key(key_id) { - return Err(KSMRError::SecretManagerCreationError(format!( - "Public key not found for key id: {}", - key_id - ))); - } - - let server_public_key = keeper_public_keys.get(key_id).unwrap(); - let server_public_key_raw_key_bytes = url_safe_str_to_bytes(server_public_key).unwrap(); - let encrypted_key = - CryptoUtils::public_encrypt(&transmission_key, &server_public_key_raw_key_bytes, None)?; - - Ok(TransmissionKey::new( - key_id.to_owned(), - transmission_key, - encrypted_key, - )) - } - - fn prepare_get_payload( - self, - storage: KvStoreType, - query_options: Option, - ) -> Result { - let client_version = KEEPER_SECRETS_MANAGER_SDK_CLIENT_ID.to_string(); - let client_id = storage - .get(crate::config_keys::ConfigKeys::KeyClientId) - .map_err(|_| KSMRError::StorageError("Client ID not found".to_string()))? - .ok_or_else(|| KSMRError::StorageError("Client ID not found".to_string()))?; - - let app_key_str_option = storage.get(ConfigKeys::KeyAppKey)?; - let app_key_str = match app_key_str_option { - Some(key_str) => key_str.to_owned(), - None => "".to_string(), - }; - let mut public_key_bytes = Vec::new(); - if app_key_str.is_empty() { - let private_key: String = match storage.get(ConfigKeys::KeyPrivateKey)? { - Some(private_key) => private_key, - None => "".to_string(), - }; - if private_key.is_empty() { - return Err(KSMRError::StorageError( - "Could not find private key when retrieving error".to_string(), - )); - } - public_key_bytes = CryptoUtils::extract_public_key_bytes(&private_key)?; - }; - - let base_64_public_key = match public_key_bytes.len() { - 0 => None, - _ => Some(bytes_to_base64(&public_key_bytes)), - }; - - let mut get_payload = - GetPayload::new(client_version, client_id, base_64_public_key, None, None); - if query_options.is_some() { - let query_options_data = query_options.unwrap(); - get_payload - .set_optional_field("records_filter", query_options_data.get_records_filter()); - get_payload - .set_optional_field("folders_filter", query_options_data.get_folders_filter()); - } - Ok(get_payload) - } - - pub fn post_function( - self, - url: String, - transmission_key: TransmissionKey, - encrypted_payload_and_signature: EncryptedPayload, - verify_ssl_certificates: bool, - ) -> Result { - let authorization_signature_string = format!( - "Signature {}", - bytes_to_base64(encrypted_payload_and_signature.signature.as_bytes()) - ); - - let auth_string = authorization_signature_string.to_string(); - let gzip_deflate = "gzip, deflate".to_string(); - let transmission_key_for_header = bytes_to_base64(&transmission_key.encrypted_key); - let transmission_key_header_name = - HeaderName::from_str("TransmissionKey").map_err(|err| { - KSMRError::SecretManagerCreationError(format!( - "error creating header name: {}", - err - )) - })?; - let public_key_header_name = HeaderName::from_str("PublicKeyId").map_err(|err| { - KSMRError::SecretManagerCreationError(format!("error creating header name: {}", err)) - })?; - let gzip_header_name = HeaderName::from_str("Accept-Encoding").map_err(|err| { - KSMRError::SecretManagerCreationError(format!("error creating header name: {}", err)) - })?; - let public_key_for_header = transmission_key.public_key_id.to_string(); - - let client = reqwest::blocking::Client::builder() - .danger_accept_invalid_certs(verify_ssl_certificates) - .build() - .map_err(|err| { - KSMRError::SecretManagerCreationError(format!("error creating builder: {}", err)) - })?; - - let request_builder = client - .post(url) - .header(header::CONTENT_TYPE, "application/octet-stream") - .header( - header::CONTENT_LENGTH, - encrypted_payload_and_signature.encrypted_payload.len(), - ) - .header(header::AUTHORIZATION, auth_string) - .header(transmission_key_header_name, transmission_key_for_header) - .header(public_key_header_name, public_key_for_header) - .header(gzip_header_name, gzip_deflate) - .body(encrypted_payload_and_signature.encrypted_payload); - - let response = request_builder - .send() - .map_err(|err| KSMRError::HTTPError(err.to_string()))?; - - let response_status = response.status().as_u16(); - let response_bytes = response - .bytes() - .map_err(|err| KSMRError::HTTPError(err.to_string()))?; - - let ksm = KsmHttpResponse::new( - response_status, - response_bytes.to_vec(), - String::from_utf8_lossy(&response_bytes).to_string(), - ); - - Ok(ksm) - } - - fn encrypt_and_sign_payload( - storage: KvStoreType, - transmission_key: TransmissionKey, - payload: &dyn Payload, - ) -> Result { - validate_payload(payload)?; - - let payload_json_str = payload - .to_json() - .map_err(|err| KSMRError::SerializationError(err.to_string()))?; - let payload_bytes = string_to_bytes(&payload_json_str); - - let encrypted_payload = - CryptoUtils::encrypt_aes_gcm(&payload_bytes, &transmission_key.key, None) - .map_err(|err| KSMRError::CryptoError(err.to_string()))?; - - let encrypted_key = transmission_key.encrypted_key.clone(); - let encrypted_payload_clone = encrypted_payload.clone(); - let signature_base = encrypted_key - .clone() - .into_iter() - .chain(encrypted_payload_clone.iter().cloned()) - .collect::>(); - - let der_private_key = storage - .get(ConfigKeys::KeyPrivateKey) - .map_err(|_| KSMRError::StorageError("Private key not found".to_string()))? - .ok_or_else(|| KSMRError::StorageError("Private key not found".to_string()))?; - - let private_key = CryptoUtils::der_base64_private_key_to_private_key(&der_private_key) - .map_err(|err| KSMRError::CryptoError(err.to_string()))?; - - let signature = CryptoUtils::sign_data(&signature_base, private_key) - .map_err(|err| KSMRError::CryptoError(err.to_string()))?; - - let private_key_for_verification = - CryptoUtils::der_base64_private_key_to_private_key(&der_private_key) - .map_err(|err| KSMRError::CryptoError(err.to_string()))?; - - //validate sign here - let signature_validity = CryptoUtils::validate_signature( - &signature_base, - signature.as_bytes(), - &private_key_for_verification.public_key().to_sec1_bytes(), - )?; - - if signature_validity { - info!("signature has been verified"); - } - - Ok(EncryptedPayload::new(encrypted_payload, signature)) - } - - fn handle_http_error( - mut self, - status_code: u16, - response: Option, - ) -> Result { - // Attempt to read the response body - let body = match response { - Some(response) => response, - None => "".to_string(), - }; - let mut _retry = false; - let log_message = format!( - "Error: {} (http error code: {}, raw: {})", - "status", status_code, body - ); - - // Check for key rotation - let key_rotation_regex = Regex::new(r#""key_id"\s*:\s*\d+\s*(?:,|\})"#).unwrap(); - let key_invalid_regex = - Regex::new(r#""error"\s*:\s*"key"|"message"\s*:\s*"invalid key id""#).unwrap(); - let key_rotation = key_rotation_regex.is_match(&body) && key_invalid_regex.is_match(&body); - - if key_rotation { - warn!("{}", log_message); - } else { - error!("{}", log_message); - } - - let val: Value = match serde_json::from_str(&body) { - Ok(json) => json, - Err(_) => { - return Err(KSMRError::DeserializationError(format!( - "Invalid JSON response: {}", - body - ))); - } - }; - let response_dict = match val.as_object() { - Some(obj_data) => obj_data - .into_iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect::>(), - None => HashMap::new(), - }; - - // Process `result_code` or `error` - let rc = response_dict - .get("result_code") - .or_else(|| response_dict.get("error")) - .and_then(|v| v.as_str()) - .unwrap_or(""); - - let mut msg = String::new(); - if rc == "invalid_client_version" { - let client_id = self - .config - .get(ConfigKeys::KeyClientId) - .unwrap_or(Some(String::from("unknown"))) - .unwrap(); - error!( - "Client version {} was not registered in the backend", - client_id.to_string() - ); - if let Some(additional_info) = response_dict.get("additional_info") { - if let Some(info) = additional_info.as_str() { - msg = info.to_string(); - } - } - } else if rc == "key" { - if let Some(key_id) = response_dict.get("key_id").and_then(|v| v.as_str()) { - info!("Server has requested we use public key {}", key_id); - let keeper_public_keys = get_keeper_public_keys(); - if key_id.is_empty() { - msg = "The public key is blank from the server".to_string(); - } else if keeper_public_keys.contains_key(key_id) { - let _ = self - .config - .set(ConfigKeys::KeyServerPublicKeyId, key_id.to_string()) - .map_err(|err| KSMRError::StorageError(err.to_string()))?; - info!("Server has requested we use public key {}", key_id); - _retry = true; - return Ok(_retry); - } else { - msg = format!("The public key at {} does not exist in the SDK", key_id); - } - } - } else { - let response_msg = response_dict - .get("message") - .and_then(|v| v.as_str()) - .unwrap_or("N/A"); - msg = format!("Error: {}, message={}", rc, response_msg); - } - - if !msg.is_empty() { - Err(KSMRError::HTTPError(msg)) - } else if !body.is_empty() { - Err(KSMRError::HTTPError(body)) - } else { - Err(KSMRError::HTTPError(format!( - "Unhandled error with status code: {}", - status_code - ))) - } - } - - fn process_post_request( - &mut self, - url: String, - transmission_key: &mut TransmissionKey, - encrypted_payload: EncryptedPayload, - verify: bool, - ) -> Result { - let keeper_response = self - .clone() - .post_function( - url.clone(), - transmission_key.clone(), - encrypted_payload, - verify, - ) - .map_err(|e| KSMRError::SecretManagerCreationError(e.to_string())); - if !url.contains("get_secret") { - return keeper_response; - } - if self.cache.is_none() { - return keeper_response; - } - let ksp = match keeper_response { - Ok(resp) => { - let response = resp.clone(); - let response_data = response.data; - let actual_data: Vec = transmission_key - .key - .iter() - .cloned() - .chain(response_data.iter().cloned()) - .collect(); - self.cache - .save_cached_value(&actual_data) - .map_err(|e| KSMRError::SecretManagerCreationError(e.to_string()))?; - resp - } - Err(e) => { - if e.to_string().contains("Error sending or receiving data from keeper servers. Exact message includes : error sending request for url ("){ - // add error handling which is pulling data from cache and giving as ksm response - let cached_data = self.cache.get_cached_value().map_err(|e| KSMRError::SecretManagerCreationError(e.to_string()))?; - let cached_data_data_part = cached_data[32..].to_vec(); - let cached_data_transmission_key = cached_data[0..32].to_vec(); - transmission_key.key = cached_data_transmission_key; - let ksp = KsmHttpResponse{ - data: cached_data_data_part, - status_code: 200, - http_response: None - }; - return Ok(ksp); - }else{ - return Err(e); - } - } - }; - Ok(ksp) - } - - fn post_query(&mut self, path: String, payload: &dyn Payload) -> Result, KSMRError> { - let keeper_server = get_servers(self.hostname.clone(), self.config.clone()) - .map_err(|e| KSMRError::StorageError(e.to_string()))?; - - let url = format!("https://{}/api/rest/sm/v1/{}", keeper_server, path); - let mut keeper_response: KsmHttpResponse; - let mut transmission_key: TransmissionKey; - let mut retry = true; - while retry { - let transmission_key_id = self - .config - .get(ConfigKeys::KeyServerPublicKeyId) - .map_err(|e| KSMRError::StorageError(e.to_string()))? - .ok_or(KSMRError::StorageError( - "Error finding public key id in storage".to_string(), - ))?; - - transmission_key = - SecretsManager::generate_transmission_key(transmission_key_id.as_str()) - .map_err(|e| KSMRError::SecretManagerCreationError(e.to_string()))?; - - let encrypted_payload_and_signature = Self::encrypt_and_sign_payload( - self.config.clone(), - transmission_key.clone(), - payload, - ) - .map_err(|e| KSMRError::SecretManagerCreationError(e.to_string()))?; - - keeper_response = self.process_post_request( - url.clone(), - &mut transmission_key, - encrypted_payload_and_signature.clone(), - true, - )?; - - if keeper_response.status_code == 200 { - info!("Successfully Made API call to {}", path); - // let keeper_result; - let keeper_result = if keeper_response.data.is_empty() { - keeper_response.data - } else { - CryptoUtils::decrypt_aes(&keeper_response.data, &transmission_key.key)? - }; - return Ok(keeper_result); - } - - // Handle the error. Handling will throw an exception if it doesn't want us to retry. - let handle_error_result: bool = self - .clone() - .handle_http_error(keeper_response.status_code, keeper_response.http_response)?; - retry = handle_error_result - } - Err(KSMRError::SecretManagerCreationError( - "Error in post_query".to_string(), - )) - } - - fn fetch_and_decrypt_secrets( - &mut self, - query_options: QueryOptions, - ) -> Result { - let payload = self - .clone() - .prepare_get_payload(self.config.clone(), Some(query_options))?; - let decrypted_response_bytes = self.post_query("get_secret".to_string(), &payload)?; - let decrypted_response_string = bytes_to_string(&decrypted_response_bytes)?; - - let decrypted_response_dict = - json_to_dict(decrypted_response_string.as_str()).unwrap_or_default(); - let mut records: Vec = Vec::new(); - let mut shared_folders: Vec = Vec::new(); - - let mut just_bound = false; - let mut _secret_key = Vec::new(); - if decrypted_response_dict.contains_key("encryptedAppKey") - && decrypted_response_dict - .get("encryptedAppKey") - .unwrap() - .as_str() - .is_some() - { - just_bound = true; - - _secret_key = self.set_app_key_if_absent(decrypted_response_dict.clone())?; - } else { - let app_key_base64 = self - .config - .get(ConfigKeys::KeyAppKey) - .map_err(|e| e.to_string()) - .unwrap() - .unwrap_or("".to_string()); - _secret_key = base64_to_bytes(app_key_base64.as_str())?; - } - - let empty_vec_for_record = Vec::new(); - let empty_vec_for_folder = Vec::new(); - let records_resp = decrypted_response_dict - .get("records") - .unwrap() - .as_array() - .unwrap_or(&empty_vec_for_record); - let folders_resp = decrypted_response_dict - .get("folders") - .unwrap() - .as_array() - .unwrap_or(&empty_vec_for_folder); - - match decrypted_response_dict.contains_key("warnings") { - true => { - let warnings_option = decrypted_response_dict.get("warnings"); - match warnings_option { - Some(warnings) => match warnings { - Value::Array(warnings_array) => { - for warning in warnings_array { - warn!( - "Warning shown while fetching secrets: `{}`", - warning.as_str().unwrap().to_string() - ); - } - } - _ => { - info!("No warnings found when pulling secrets"); - } - }, - None => { - info!("No warnings found when pulling secrets"); - } - } - } - false => { - info!("No warnings found when pulling secrets"); - } - } - // let warnings = decrypted_response_dict.get("warnings"). - - let mut secrets_manager_response = SecretsManagerResponse::new(); - let mut records_count = 0; - let mut shared_folders_count = 0; - if !records_resp.is_empty() { - let records_array = records_resp; - for record in records_array { - let new_map = serde_json::Map::new(); - let record_hashmap = record.as_object().unwrap_or(&new_map); - let record_hashmap_parsed = record_hashmap - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect::>(); - let record_result = - Record::new_from_json(record_hashmap_parsed, &_secret_key, None); - if record_result.is_err() { - log::error!("Error parsing record: {}", record); - } else { - let unwrapped_record = record_result.unwrap(); - records_count += 1; - records.push(unwrapped_record); - } - } - } - - if !folders_resp.is_empty() { - let folders_array = folders_resp; - for folder in folders_array { - let new_map = serde_json::Map::new(); - let folder_hashmap = folder.as_object().unwrap_or(&new_map); - let folder_hashmap_parsed = folder_hashmap - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect::>(); - let folder_result: Option = - Folder::new_from_json(folder_hashmap_parsed, &_secret_key); - if folder_result.is_none() { - log::error!("Error parsing folder: {}", folder); - } else { - let unwrapped_folder = folder_result.unwrap(); - shared_folders_count += 1; - records_count += unwrapped_folder.records()?.len(); - records.extend(unwrapped_folder.records()?); - shared_folders.push(unwrapped_folder); - } - } - } - - debug!("Individual records: {}", records_count); - debug!("Shared folders: {}", shared_folders_count); - debug!("total count: {}", records_count + shared_folders_count); - - if decrypted_response_dict.contains_key("appData") { - let app_data_str = CryptoUtils::url_safe_str_to_bytes( - decrypted_response_dict - .get("appData") - .unwrap() - .as_str() - .unwrap(), - ) - .unwrap(); - let app_data_key_string = self - .config - .get(ConfigKeys::KeyAppKey) - .map_err(|e| e.to_string()) - .unwrap() - .unwrap_or("".to_string()); - - let app_data_key_bytes = base64_to_bytes(app_data_key_string.as_str())?; - - let app_data_json = CryptoUtils::decrypt_aes(&app_data_str, &app_data_key_bytes)?; - - let app_data_dict = serde_json::from_slice::(&app_data_json) - .map_err(|e| KSMRError::DeserializationError(e.to_string())); - match app_data_dict { - Ok(app_data) => { - secrets_manager_response.app_data = app_data; - } - Err(err) => error!("Error parsing app data: {}", err), - } - } - - if decrypted_response_dict.contains_key("expiresOn") { - secrets_manager_response.expires_on = decrypted_response_dict - .get("expiresOn") - .unwrap() - .as_i64() - .unwrap(); - } - - if decrypted_response_dict.contains_key("warnings") { - let warnings_array = decrypted_response_dict.get("warnings").unwrap().as_str(); - match warnings_array { - Some(warnings) => { - secrets_manager_response.warnings = Some(warnings.to_string()); - } - None => info!("No warnings"), - }; - } - - secrets_manager_response.records = records; - secrets_manager_response.folders = shared_folders; - secrets_manager_response.just_bound = just_bound; - Ok(secrets_manager_response) - } - - fn fetch_and_decrypt_folders(mut self) -> Result, KSMRError> { - let payload = self - .clone() - .prepare_get_payload(self.config.clone(), None)?; - let decrypted_response_bytes = self.post_query("get_folders".to_string(), &payload)?; - let decrypted_response_string = bytes_to_string(&decrypted_response_bytes)?; - - let decrypted_response_dict = - json_to_dict(decrypted_response_string.as_str()).unwrap_or_default(); - - let app_key_base64 = match self.config.get(ConfigKeys::KeyAppKey)? { - Some(app_key) => app_key, - None => { - let _ = self.set_app_key_if_absent(decrypted_response_dict.clone())?; - - match self.config.get(ConfigKeys::KeyAppKey)? { - Some(app_key) => app_key, - None => "".to_string(), - } - } - }; - let app_key = base64_to_bytes(app_key_base64.as_str())?; - - let empty_vec_for_folder = Vec::new(); - let folders_resp = decrypted_response_dict - .get("folders") - .unwrap() - .as_array() - .unwrap_or(&empty_vec_for_folder); - - if folders_resp.is_empty() { - return Ok(Vec::new()); - } - - let mut folders: Vec = Vec::new(); - for folder in folders_resp { - let folder_obj = folder - .as_object() - .unwrap() - .iter() - .map(|(k, v)| (k.to_string(), v.clone())) - .collect::>(); - let folder_key_string = match folder_obj.get("folderKey") { - Some(folder_key_value) => folder_key_value.as_str().unwrap().to_string(), - None => "".to_string(), - }; - - let folder_parent = match folder_obj.get("parent") { - Some(folder_parent_value) => match folder_parent_value.as_str() { - Some(folder_parent_val) => folder_parent_val.to_string(), - None => "".to_string(), - }, - None => "".to_string(), - }; - let mut _folder_key = Vec::new(); - if folder_parent.is_empty() { - let folder_key_bytes = utils::base64_to_bytes(&folder_key_string)?; - _folder_key = CryptoUtils::decrypt_aes(&folder_key_bytes, &app_key)?; - } else { - let shared_folder_key = self - .clone() - .get_shared_folder_key( - folders.clone(), - folders_resp.to_vec(), - folder_parent.clone(), - ) - .unwrap_or_default(); - let folder_key_bytes = utils::base64_to_bytes(&folder_key_string)?; - _folder_key = CryptoUtils::decrypt_aes_cbc(&folder_key_bytes, &shared_folder_key)?; - } - - let mut _folder_name = "".to_string(); - let folder_data = match folder_obj.get("data") { - Some(folder_data_value) => match folder_data_value.as_str() { - Some(folder_data_val) => folder_data_val.to_string(), - None => "".to_string(), - }, - None => "".to_string(), - }; - - if !folder_data.is_empty() { - let folder_data_bytes = utils::base64_to_bytes(&folder_data)?; - _folder_key = match _folder_key.len() { - 32 => _folder_key, - _ => unpad_data(&_folder_key)?, - }; - - let folder_data_json_bytes_decrypted = - CryptoUtils::decrypt_aes_cbc(&folder_data_bytes, &_folder_key)?; - let folder_data_string = - utils::bytes_to_string_unpad(&folder_data_json_bytes_decrypted)?; - let folder_data_dict: serde_json::Value = - serde_json::from_str(&folder_data_string)?; - _folder_name = folder_data_dict - .as_object() - .unwrap() - .get("name") - .unwrap() - .as_str() - .unwrap() - .to_string(); - } - - let folder_ = KeeperFolder::new(&folder_obj, _folder_key)?; - folders.push(folder_); - } - Ok(folders) - } - - fn get_shared_folder_key( - self, - folders: Vec, - response_folders: Vec, - parent: String, - ) -> Option> { - let mut parent_copy = parent.clone(); - loop { - let parent_folder = response_folders - .clone() - .into_iter() - .filter(|folder_value| { - let folder = match folder_value.as_object() { - Some(folder) => folder, - None => return false, - }; - - let folder_uid = folder.get("folderUid").unwrap(); - let folder_uid_str = folder_uid.as_str().unwrap(); - folder_uid_str == parent_copy - }) - .collect::>(); - if parent_folder.is_empty() { - return None; - } - - let parent_folder = parent_folder.first().unwrap().as_object().unwrap(); - let parents_parent = match parent_folder.get("parent") { - Some(uid_val) => match uid_val.as_str() { - Some(uid) => uid.to_string(), - None => "".to_string(), - }, - None => "".to_string(), - }; - if !parents_parent.is_empty() { - parent_copy = parents_parent; - } else { - let shared_folder = folders - .iter() - .filter(|folder| folder.folder_uid == parent_copy) - .cloned() - .collect::>(); - - if shared_folder.is_empty() { - return None; - } else { - return Some(shared_folder.first().unwrap().folder_key.clone()); - } - } - } - } - - pub fn get_folders(self) -> Result, KSMRError> { - let folders = self.fetch_and_decrypt_folders()?; - Ok(folders) - } - - fn get_secrets_full_response_with_options( - &mut self, - query_options: QueryOptions, - ) -> Result { - let query_options_clone = query_options.clone(); - let mut secrets_manager_response = - self.fetch_and_decrypt_secrets(query_options_clone.clone())?; - - if secrets_manager_response.just_bound { - secrets_manager_response = self.fetch_and_decrypt_secrets(query_options_clone)?; - } - - if secrets_manager_response.warnings.is_some() { - warn!("{}", secrets_manager_response.warnings.as_ref().unwrap()); - } - - Ok(secrets_manager_response) - } - - pub fn get_secrets_with_options( - &mut self, - query_options: QueryOptions, - ) -> Result, KSMRError> { - let secrets_manager_response = - self.get_secrets_full_response_with_options(query_options)?; - - let records = secrets_manager_response.records; - Ok(records) - } - - pub fn get_secrets_full_response( - &mut self, - uid_array: Vec, - ) -> Result { - let query_options = QueryOptions::new(uid_array, Vec::new()); - let secrets_manager_response = - self.get_secrets_full_response_with_options(query_options)?; - Ok(secrets_manager_response) - } - - pub fn get_secrets(&mut self, uid_array: Vec) -> Result, KSMRError> { - let secrets_manager_response = self.get_secrets_full_response(uid_array)?; - Ok(secrets_manager_response.records) - } - - pub fn delete_secret(&mut self, record_uid: Vec) -> Result { - let config_clone = self.config.clone(); - let delete_payload = Self::delete_payload(config_clone, record_uid)?; - let response = self.post_query("delete_secret".to_string(), &delete_payload)?; - let response_str = utils::bytes_to_string(&response)?; - - let response_dict = json_to_dict(&response_str).ok_or_else(|| { - KSMRError::DeserializationError("Failed to parse response".to_string()) - })?; - - let records = response_dict - .get("records") - .ok_or_else(|| { - KSMRError::DeserializationError("Missing 'records' in response".to_string()) - }) - .and_then(|records| { - serde_json::from_value::>>(records.clone()).map_err( - |e| KSMRError::DeserializationError(format!("Failed to parse response: {}", e)), - ) - })?; - - let simplified_records = records - .into_iter() - .map(|record| { - record - .into_iter() - .map(|(k, v)| (k, v.to_string())) - .collect::>() - }) - .collect(); - - self.clone() - .calculate_successful_deletes(simplified_records) - } - - pub fn calculate_successful_deletes( - self, - dict: Vec>, - ) -> Result { - let deleted_secrets: Vec = dict - .into_iter() - .filter_map(|dict_value| { - if dict_value.get("responseCode") == Some(&"\"ok\"".to_string()) { - dict_value.get("recordUid").cloned() - } else { - if let Some(record_uid) = dict_value.get("recordUid") { - error!("Failed to delete secret: {}", record_uid); - } - None - } - }) - .collect(); - - Ok(deleted_secrets.join(", ")) - } - fn prepare_delete_folder_payload( - storage: KvStoreType, - folder_uids: Vec, - force_deletion: bool, - ) -> Result { - let client_version = KEEPER_SECRETS_MANAGER_SDK_CLIENT_ID.to_string(); - let client_id = match storage.get(ConfigKeys::KeyClientId)? { - Some(client_id) => client_id, - None => "".to_string(), - }; - let payload = - DeleteFolderPayload::new(client_version, client_id, folder_uids, force_deletion); - Ok(payload) - } - - pub fn delete_folder( - &mut self, - folder_uids: Vec, - force_delete: bool, - ) -> Result>, KSMRError> { - let payload = SecretsManager::prepare_delete_folder_payload( - self.config.clone(), - folder_uids, - force_delete, - )?; - let response = self.post_query("delete_folder".to_string(), &payload)?; - let response_str = utils::bytes_to_string(&response)?; - - let response_dict = json_to_dict(&response_str).ok_or_else(|| { - KSMRError::DeserializationError("Failed to parse response".to_string()) - })?; - - let folders = response_dict - .get("folders") - .ok_or_else(|| { - KSMRError::DeserializationError("Missing 'folders' in response".to_string()) - }) - .and_then(|records| { - serde_json::from_value::>>(records.clone()).map_err( - |e| KSMRError::DeserializationError(format!("Failed to parse response: {}", e)), - ) - })?; - Ok(folders) - } - - fn set_app_key_if_absent( - &mut self, - decrypted_response_dict: HashMap, - ) -> Result, KSMRError> { - let encrypted_key_value = decrypted_response_dict.get("encryptedAppKey"); - let encrypted_master_key_value = match encrypted_key_value { - Some(value) => match value.as_str() { - Some(val) => val.to_string(), - None => "".to_string(), - }, - None => "".to_string(), - }; - let encrypted_master_key = - CryptoUtils::url_safe_str_to_bytes(encrypted_master_key_value.as_str()).unwrap(); - let client_key = CryptoUtils::url_safe_str_to_bytes( - self.config - .get(ConfigKeys::KeyClientKey) - .unwrap() - .unwrap() - .as_str(), - ) - .unwrap(); - - let secret_key = CryptoUtils::decrypt_aes(&encrypted_master_key, &client_key)?; - let secret_key_bytes = bytes_to_base64(&secret_key); - self.config.set(ConfigKeys::KeyAppKey, secret_key_bytes)?; - let _ = self.config.delete(ConfigKeys::KeyClientKey)?; - - if decrypted_response_dict.contains_key("appOwnerPublicKey") { - let app_public_key = decrypted_response_dict - .get("appOwnerPublicKey") - .unwrap() - .as_str() - .unwrap(); - let app_owner_public_key_bytes = - match CryptoUtils::url_safe_str_to_bytes(app_public_key) { - Ok(val) => val, - Err(e) => { - if e.to_string().contains("Invalid padding") { - CryptoUtils::url_safe_str_to_bytes_trim_padding(app_public_key)? - } else { - return Err(KSMRError::CryptoError(e.to_string())); - } - } - }; - let app_owner_public_key_string = bytes_to_base64(&app_owner_public_key_bytes); - self.config - .set(ConfigKeys::KeyOwnerPublicKey, app_owner_public_key_string)?; - } - Ok(secret_key) - } - - fn prepare_update_payload( - &mut self, - folder_uid: String, - folder_name: String, - folder_key: Vec, - ) -> Result { - let client_version = KEEPER_SECRETS_MANAGER_SDK_CLIENT_ID.to_string(); - let client_id = match self.config.get(ConfigKeys::KeyClientId)? { - Some(client_id) => client_id, - None => Err(KSMRError::StorageError("Client ID not found".to_string()))?, - }; - - let mut keeper_folder: HashMap<_, _> = HashMap::new(); - keeper_folder.insert("name".to_string(), Value::String(folder_name)); - let folder_data_json = dict_to_json(&keeper_folder)?; - let folder_data_bytes = utils::string_to_bytes(&folder_data_json); - - let encrypted_folder_data = - CryptoUtils::encrypt_aes_cbc(&folder_data_bytes, &folder_key, None)?; - let payload_data = CryptoUtils::bytes_to_url_safe_str(&encrypted_folder_data); - - let update_payload = - UpdateFolderPayload::new(client_version, client_id, folder_uid, payload_data); - Ok(update_payload) - } - - pub fn update_folder( - &mut self, - folder_uid: String, - folder_name: String, - folders: Vec, - ) -> Result { - let folders_copy = match folders.is_empty() { - true => self.clone().get_folders()?, - false => folders, - }; - - let mut folder_key = Vec::new(); - for folder in folders_copy { - if folder.folder_uid == folder_uid { - folder_key = folder.folder_key; - break; - } - } - - if folder_key.is_empty() { - return Err(KSMRError::RecordDataError(format!( - "unable to update folder- folder key for {} not found", - folder_uid - ))); - }; - - let update_payload = self.prepare_update_payload( - folder_uid.clone(), - folder_name.clone(), - folder_key.clone(), - )?; - - let _resp = self.post_query("update_folder".to_string(), &update_payload)?; - Ok("updated folder".to_string()) - } - - fn prepare_create_folder_payload( - &mut self, - create_options: CreateOptions, - folder_name: String, - shared_folder_key: Vec, - ) -> Result { - let client_version = KEEPER_SECRETS_MANAGER_SDK_CLIENT_ID.to_string(); - let client_id = match self.config.get(ConfigKeys::KeyClientId)? { - Some(client_id) => client_id, - None => Err(KSMRError::StorageError("Client ID not found".to_string()))?, - }; - let shared_folder_uid = create_options.folder_uid.clone(); - let parent_uid = create_options.sub_folder_uid.clone(); - let folder_uid = generate_uid(); - - let folder_key = CryptoUtils::generate_random_bytes(32); - - let encrypted_folder_key_bytes = - CryptoUtils::encrypt_aes_cbc(&folder_key, &shared_folder_key, None)?; - let encrypted_folder_key = CryptoUtils::bytes_to_url_safe_str(&encrypted_folder_key_bytes); - - let mut keeper_folder_name_map = HashMap::new(); - keeper_folder_name_map.insert("name".to_string(), Value::String(folder_name.clone())); - let folder_name_json = dict_to_json(&keeper_folder_name_map)?; - let folder_name_bytes = utils::string_to_bytes(&folder_name_json); - let encrypted_folder_name_bytes = - CryptoUtils::encrypt_aes_cbc(&folder_name_bytes, &folder_key, None)?; - let encrypted_folder_name_string = - CryptoUtils::bytes_to_url_safe_str(&encrypted_folder_name_bytes); - - let created_payload = CreateFolderPayload::new( - client_version, - client_id, - folder_uid, - shared_folder_uid, - encrypted_folder_key, - encrypted_folder_name_string, - parent_uid, - ); - Ok(created_payload) - } - - pub fn create_folder( - &mut self, - create_options: CreateOptions, - folder_name: String, - folders: Vec, - ) -> Result { - let folders_copy = match folders.is_empty() { - true => self.clone().get_folders()?, - false => folders, - }; - - let shared_folder_data = folders_copy - .into_iter() - .find(|folder| folder.folder_uid == create_options.folder_uid); - - let shared_folder = match shared_folder_data { - Some(shared) => shared, - None => { - return Err(KSMRError::RecordDataError(format!( - "unable to create folder- folder key for {} not found", - create_options.folder_uid - ))); - } - }; - - if shared_folder.folder_key.is_empty() || shared_folder.folder_uid.is_empty() { - return Err(KSMRError::RecordDataError(format!( - "unable to create folder- folder key for {} not found", - create_options.folder_uid - ))); - }; - let payload = self.prepare_create_folder_payload( - create_options, - folder_name, - shared_folder.folder_key.clone(), - )?; - let _resp = self.post_query("create_folder".to_string(), &payload)?; - Ok(payload.folder_uid) - } - - pub fn get_secret_by_title(&mut self, title: &str) -> Result>, KSMRError> { - let retrieved_secrets = self.get_secrets(Vec::new())?; - let mut filtered_secrets = Vec::new(); - for secret in retrieved_secrets { - if secret.title == title { - filtered_secrets.push(secret); - } - } - match filtered_secrets.len() { - 0 => { - println!("No secrets found with title: {}", title); - Ok(None) - } - _ => { - println!( - "Secrets found with title {} are {} in number", - title, - filtered_secrets.len() - ); - Ok(Some(filtered_secrets)) - } - } - } - - fn delete_payload( - storage: KvStoreType, - record_uid: Vec, - ) -> Result { - let client_version = KEEPER_SECRETS_MANAGER_SDK_CLIENT_ID.to_string(); - let client_id = match storage.get(ConfigKeys::KeyClientId)? { - Some(client_id) => client_id, - None => "".to_string(), - }; - - let payload = DeletePayload::new(client_version, client_id, record_uid); - Ok(payload) - } - - fn prepare_update_secret_payload( - storage: KvStoreType, - record: Record, - transaction_type: Option, - ) -> Result { - let record_uid = record.uid.clone(); - let revision = record.revision.unwrap_or_default(); - let client_id = match storage.get(ConfigKeys::KeyClientId)? { - Some(client_id) => client_id, - None => { - return Err(KSMRError::CustomError( - "client id not found in config".to_string(), - )) - } - }; - - let raw_json_bytes = utils::string_to_bytes(&record.raw_json); - let encrypted_raw_json_bytes = - CryptoUtils::encrypt_aes_gcm(&raw_json_bytes, &record.record_key_bytes, None)?; - let stringified_encrypted_data = - CryptoUtils::bytes_to_url_safe_str(&encrypted_raw_json_bytes); - - let mut payload = UpdatePayload::new( - KEEPER_SECRETS_MANAGER_SDK_CLIENT_ID.to_string(), - client_id, - record_uid, - revision, - stringified_encrypted_data, - ); - - if transaction_type.is_some() - || (transaction_type.is_some() - && (transaction_type.clone().unwrap() != UpdateTransactionType::None)) - { - payload.set_transaction_type(transaction_type.unwrap()); - } - - Ok(payload) - } - - pub fn save( - &mut self, - record: Record, - transaction_type: Option, - ) -> Result<(), KSMRError> { - info!("updating record: {}", record.title); - let payload = Self::prepare_update_secret_payload( - self.config.clone(), - record, - transaction_type.clone(), - )?; - - let _result = self.post_query("update_secret".to_string(), &payload)?; - Ok(()) - } - - pub fn upload_file( - &mut self, - owner_record: Record, - file: KeeperFileUpload, - ) -> Result { - info!( - "uploading file: {} to record with UID: {}", - file.name, owner_record.uid - ); - debug!( - "preparing upload payload. owner_record.uid=[{}], fine name: {}, file_size: {}", - owner_record.uid, - file.name, - file.data.len() - ); - - let upload_payload = - Self::prepare_file_upload_payload(self.config.clone(), owner_record, file)?; - let payload = upload_payload.get_payload(); - let encrypted_file_data = upload_payload.get_encrypted_data(); - - debug!("posting prepare data"); - - let response_data = self.post_query("add_file".to_string(), &payload)?; - - let response_json_str = bytes_to_string(&response_data)?; - let response_dict = json_to_dict(&response_json_str).ok_or_else(|| { - KSMRError::DeserializationError("Failed to parse response".to_string()) - })?; - let upload_url = match response_dict.get("url") { - Some(url) => match url.as_str() { - Some(url_val) => url_val.to_string(), - None => { - return Err(KSMRError::CustomError( - "upload url not found in response".to_string(), - )) - } - }, - None => { - return Err(KSMRError::CustomError( - "upload url not found in response".to_string(), - )) - } - }; - - let parameters_json_str = match response_dict.get("parameters") { - Some(parameters) => match parameters.as_str() { - Some(parameters_val) => parameters_val.to_string(), - None => { - return Err(KSMRError::CustomError( - "parameters not found in response".to_string(), - )) - } - }, - None => { - return Err(KSMRError::CustomError( - "parameters not found in response".to_string(), - )) - } - }; - - let parameters_dict = json_to_dict(¶meters_json_str).ok_or_else(|| { - KSMRError::DeserializationError("Failed to parse response".to_string()) - })?; - debug!("uploading file to url: {}", upload_url); - let update_functionality_response = - self.upload_file_function(&upload_url, parameters_dict, encrypted_file_data)?; - let status = update_functionality_response - .get("isOk") - .ok_or_else(|| { - KSMRError::DeserializationError( - "Failed to parse response from upload file functionality".to_string(), - ) - })? - .as_bool() - .ok_or_else(|| { - KSMRError::DeserializationError( - "Failed to parse response from upload file functionality".to_string(), - ) - })?; - - if status { - Ok(payload.file_record_uid.clone()) - } else { - Err(KSMRError::CustomError("Failed to upload file".to_string())) - } - } - - fn upload_file_function( - &mut self, - url: &str, - upload_parameters: HashMap, - encrypted_file_data: Vec, - ) -> Result, KSMRError> { - // Build the multipart form with the encrypted file - let mut form = multipart::Form::new(); - - // Add upload parameters to the form - for (key, value) in upload_parameters.clone() { - form = form.text(key, value.as_str().unwrap().to_string()); - } - - // Add the file field - form = form.part("file", multipart::Part::bytes(encrypted_file_data)); - - // Send the POST request with the multipart form - let client = Client::new(); - let response = client - .post(url) - .multipart(form) - .send() - .map_err(|err| KSMRError::HTTPError(err.to_string()))?; - - // Extract response data - let status_code = response.status().as_u16(); - let is_ok = response.status().is_success(); - let text = response.text().map_err(|err| { - KSMRError::CustomError(format!( - "Error extracting text from upload file response : {}", - err - )) - })?; - - // Build the result - let mut result = HashMap::new(); - result.insert("isOk".to_string(), Value::Bool(is_ok)); - result.insert("statusCode".to_string(), Value::Number(status_code.into())); - result.insert("data".to_string(), Value::String(text)); - - Ok(result) - } - - fn prepare_file_upload_payload( - storage: KvStoreType, - mut owner_record: Record, - file: KeeperFileUpload, - ) -> Result { - let owner_public_key = match storage.get(ConfigKeys::KeyOwnerPublicKey)?{ - Some(public_key) => public_key, - None => return Err(KSMRError::CustomError("Unable to upload file - owner key is missing. Looks like application was created using out date client (Web Vault or Commander)".to_string())), - }; - - let owner_public_key_bytes = - match CryptoUtils::url_safe_str_to_bytes(owner_public_key.as_str()) { - Ok(val) => val, - Err(e) => { - if e.to_string().contains("Invalid padding") { - CryptoUtils::url_safe_str_to_bytes_trim_padding(owner_public_key.as_str())? - } else { - return Err(KSMRError::CryptoError(e.to_string())); - } - } - }; - - let mut file_record_dict = HashMap::new(); - file_record_dict.insert("name".to_string(), Value::String(file.name.clone())); - file_record_dict.insert("size".to_string(), Value::Number(file.data.len().into())); - file_record_dict.insert( - "type".to_string(), - Value::String(file.mime_type.to_string()), - ); - file_record_dict.insert("title".to_string(), Value::String(file.title)); - let _last_modified = chrono::Utc::now().timestamp_millis(); - - let file_record_json_str = dict_to_json(&file_record_dict)?; - - let file_record_json_bytes = utils::string_to_bytes(&file_record_json_str); - - let file_record_key = generate_random_bytes(32); - let file_record_uid = generate_random_bytes(16); - let file_record_uid_string = CryptoUtils::bytes_to_url_safe_str(&file_record_uid); - - let encrypted_file_record_bytes = - CryptoUtils::encrypt_aes_gcm(&file_record_json_bytes, &file_record_key, None)?; - let encrypted_file_record_key = - CryptoUtils::public_encrypt(&file_record_key, &owner_public_key_bytes, None)?; - let encrypted_link_key_bytes = - CryptoUtils::encrypt_aes_gcm(&file_record_key, &owner_record.record_key_bytes, None)?; - - let encrypted_file_data = CryptoUtils::encrypt_aes_gcm(&file.data, &file_record_key, None)?; - - //fileRef related code - let _rec_dict = &owner_record.record_dict; - - let file_ref_field_existence = - owner_record.field_exists("fields", StandardFieldTypeEnum::FILEREF.get_type()); - if !file_ref_field_existence { - let mut file_ref_obj = HashMap::new(); - file_ref_obj.insert( - "type".to_string(), - Value::String(StandardFieldTypeEnum::FILEREF.get_type().to_string()), - ); - let record_uid_value_str = Value::String(file_record_uid_string.clone()); - let record_uid_value_str_arr = vec![record_uid_value_str]; - file_ref_obj.insert("value".to_string(), Value::Array(record_uid_value_str_arr)); - owner_record.insert_field("fields", file_ref_obj)?; - } else { - let existing_file_refs = owner_record - .get_standard_field_value(StandardFieldTypeEnum::FILEREF.get_type(), false)?; - let mut existing_file_refs_array = existing_file_refs.as_array().unwrap()[0] - .as_array() - .unwrap() - .clone(); - existing_file_refs_array.push(Value::String(file_record_uid_string.clone())); - owner_record.set_standard_field_value_mut( - StandardFieldTypeEnum::FILEREF.get_type(), - serde_json::Value::Array(existing_file_refs_array), - )?; - } - - let owner_record_raw_json = utils::dict_to_json(&owner_record.record_dict.clone())?; - let owner_record_raw_json_bytes = string_to_bytes(&owner_record_raw_json); - - let encrypted_owner_record_bytes = CryptoUtils::encrypt_aes_gcm( - &owner_record_raw_json_bytes, - &owner_record.record_key_bytes, - None, - )?; - let encrypted_owner_record_str = - CryptoUtils::bytes_to_url_safe_str(&encrypted_owner_record_bytes); - - // Now we have all data required. - let client_version = KEEPER_SECRETS_MANAGER_SDK_CLIENT_ID.to_string(); - let client_id = match storage.get(ConfigKeys::KeyClientId)? { - Some(client_id) => client_id, - None => return Err(KSMRError::CustomError("Unable to upload file - client id is missing. Looks like application was created using out date client (Web Vault or Commander)".to_string())), - }; - let file_record_data = CryptoUtils::bytes_to_url_safe_str(&encrypted_file_record_bytes); - let file_record_key = bytes_to_base64(&encrypted_file_record_key); - let link_key = bytes_to_base64(&encrypted_link_key_bytes); - - let payload = FileUploadPayload::new( - client_version, - client_id, - file_record_uid_string, - file_record_key, - file_record_data, - owner_record.uid, - encrypted_owner_record_str, - link_key, - encrypted_file_data.len().try_into().unwrap(), - ); - - let result = FileUploadFunctionResult::new(payload, encrypted_file_data); - - Ok(result) - } - - pub fn create_secret( - &mut self, - parent_folder_uid: String, - record_create_object: RecordCreate, - ) -> Result { - let record_json_str = record_create_object.to_json()?; - - let folders = self.clone().get_folders()?; - - let mut parent_uid = parent_folder_uid.clone(); - let mut sub_folder_uid: Option = None; - let mut shared_folder: Option<&KeeperFolder> = None; - - loop { - let current_folder = folders.iter().find(|f| f.folder_uid == parent_uid); - match current_folder { - Some(folder) => { - if folder.parent_uid.is_empty() { - shared_folder = Some(folder); - break; - } else { - sub_folder_uid = Some(parent_uid.clone()); - parent_uid = folder.parent_uid.clone(); - } - } - None => break, - } - } - - let shared_folder = match shared_folder { - Some(folder) => folder, - None => { - return Err(KSMRError::SecretManagerCreationError(format!( - "Could not find a shared folder in the ancestry of folder uid '{}'.", - parent_folder_uid - ))) - } - }; - - if shared_folder.folder_key.is_empty() { - return Err(KSMRError::SecretManagerCreationError(format!( - "Shared folder key for '{}' is empty.", - shared_folder.folder_uid - ))); - } - - let create_options = CreateOptions::new(shared_folder.folder_uid.clone(), sub_folder_uid); - - let payload = self.prepare_create_secret_payload( - self.config.clone(), - create_options, - record_json_str, - shared_folder.folder_key.clone(), - )?; - - self.post_query("create_secret".to_string(), &payload)?; - Ok(payload.record_uid.clone()) - } - - fn prepare_create_secret_payload( - &mut self, - storage: KvStoreType, - create_options: CreateOptions, - record_data_json_str: String, - folder_key: Vec, - ) -> Result { - let owner_public_key = match storage.get(ConfigKeys::KeyOwnerPublicKey)? { - Some(public_key) => public_key, - None => { - return Err(KSMRError::StorageError( - "Unable to create secret - owner public key is missing.".to_string(), - )) - } - }; - let owner_public_key_bytes = - match CryptoUtils::url_safe_str_to_bytes(owner_public_key.as_str()) { - Ok(val) => val, - Err(e) => { - if e.to_string().contains("Invalid padding") { - CryptoUtils::url_safe_str_to_bytes_trim_padding(owner_public_key.as_str())? - } else { - return Err(KSMRError::CryptoError(e.to_string())); - } - } - }; - - if folder_key.is_empty() { - return Err(KSMRError::StorageError( - "Unable to create secret - folder key is missing.".to_string(), - )); - } - - let record_key = utils::generate_random_bytes(32); - let record_uid = generate_uid_bytes(); - - let record_data_bytes = utils::string_to_bytes(&record_data_json_str); - let record_data_encrypted = - CryptoUtils::encrypt_aes_gcm(&record_data_bytes, &record_key, None)?; - let record_key_encrypted = - CryptoUtils::public_encrypt(&record_key, &owner_public_key_bytes, None)?; - let folder_key_encrypted = CryptoUtils::encrypt_aes_gcm(&record_key, &folder_key, None)?; - - let client_version = KEEPER_SECRETS_MANAGER_SDK_CLIENT_ID.to_string(); - let client_id = match storage.get(ConfigKeys::KeyClientId)? { - Some(client_id) => client_id, - None => return Err(KSMRError::CustomError("Unable to create secret - client id is missing. Looks like application was created using out date client (Web Vault or Commander)".to_string())), - }; - let record_uid_str = CryptoUtils::bytes_to_url_safe_str(&record_uid); - let record_key_encrypted_str = utils::bytes_to_base64(&record_key_encrypted); - let folder_key_encoded = bytes_to_base64(&folder_key_encrypted); - let encoded_data = bytes_to_base64(&record_data_encrypted); - let sub_folder_uid = create_options.sub_folder_uid.clone(); - - let create_payload = CreatePayload::new( - client_version, - client_id, - record_uid_str, - record_key_encrypted_str, - create_options.folder_uid, - folder_key_encoded, - encoded_data, - sub_folder_uid, - ); - - Ok(create_payload) - } - - pub fn try_get_notation_results(&mut self, notation: &str) -> Result, KSMRError> { - let tried_results = self.get_notation_result(notation.to_string()); - let results = match tried_results { - Ok(results) => results, - Err(err) => { - error!("{}", err); - Vec::new() - } - }; - Ok(results) - } - - pub fn get_notation(&mut self, url: String) -> Result { - let result = self._get_notation(url)?; - match &result { - serde_json::Value::String(s) => { - // Try to parse as JSON, otherwise return as string - match serde_json::from_str::(s) { - Ok(val) => Ok(val), - Err(_) => Ok(result), - } - } - serde_json::Value::Array(arr) => { - // Try to parse each string as JSON, otherwise keep as string - let mut new_arr = Vec::new(); - for v in arr { - if let serde_json::Value::String(s) = v { - if let Ok(parsed) = serde_json::from_str::(s) { - new_arr.push(parsed); - } else { - new_arr.push(serde_json::Value::String(s.clone())); - } - } else { - new_arr.push(v.clone()); - } - } - Ok(serde_json::Value::Array(new_arr)) - } - _ => Ok(result), - } - } - - fn _get_notation(&mut self, url: String) -> Result { - let values = self.get_notation_result(url)?; - if values.len() == 1 { - Ok(serde_json::Value::String(values[0].clone())) - } else { - Ok(serde_json::Value::Array( - values.into_iter().map(serde_json::Value::String).collect(), - )) - } - } - - pub fn parse_subsection( - text: &str, - mut pos: usize, - delimiters: &str, - escaped: bool, - ) -> Result, KSMRError> { - let escape_char = '\\'; - let escape_chars = "/[]\\"; // Characters that can be escaped - let mut token = String::new(); - let mut raw = String::new(); - - // Validate input - if text.is_empty() || pos >= text.len() { - return Ok(None); - } - if delimiters.is_empty() || delimiters.len() > 2 { - return Err(KSMRError::NotationError(format!( - "Notation parser: Internal error - Incorrect delimiters count. Delimiters: '{}'", - delimiters - ))); - } - - let delimiters: Vec = delimiters.chars().collect(); // Convert delimiters to Vec - let chars: Vec = text.chars().collect(); // Convert text to Vec - - while pos < chars.len() { - let current_char = chars[pos]; - if escaped && current_char == escape_char { - // Handle escape sequences - if pos + 1 >= chars.len() || !escape_chars.contains(chars[pos + 1]) { - return Err(KSMRError::NotationError(format!( - "Notation parser: Incorrect escape sequence at position {}", - pos - ))); - } - - // Add escaped character to token and raw - token.push(chars[pos + 1]); - raw.push(current_char); - raw.push(chars[pos + 1]); - pos += 2; - } else { - // Add current character to raw text - raw.push(current_char); - - if delimiters.len() == 1 { - // Single delimiter case - if current_char == delimiters[0] { - break; // End of section - } else { - token.push(current_char); - } - } else { - // Two delimiters case - let start_delim = delimiters[0]; - let end_delim = delimiters[1]; - - // Ensure section starts correctly with the opening delimiter - if raw.len() == 1 && current_char != start_delim { - return Err(KSMRError::NotationError( - "Notation parser error: Index sections must start with '['".to_string(), - )); - } - // Disallow extra opening delimiters inside the section - if raw.len() > 1 && current_char == start_delim { - return Err(KSMRError::NotationError( - "Notation parser error: Index sections do not allow extra '[' inside." - .to_string(), - )); - } - // End section if the closing delimiter is found - if current_char == end_delim { - break; - } - // Add valid characters to token - if current_char != start_delim { - token.push(current_char); - } - } - pos += 1; - } - } - - // Validate enclosing delimiters for two-delimiter case - if delimiters.len() == 2 { - let start_delim = delimiters[0]; - let end_delim = delimiters[1]; - - if raw.len() < 2 - || !raw.starts_with(start_delim) - || !raw.ends_with(end_delim) - || (escaped && raw.chars().nth_back(1) == Some(escape_char)) - { - return Err(KSMRError::NotationError( - "Notation parser error: Index sections must be enclosed in '[' and ']'" - .to_string(), - )); - } - } - - Ok(Some((token, raw))) - } - - pub fn parse_section( - notation: &str, - section: &str, - pos: isize, - ) -> Result { - if notation.is_empty() { - return Err(KSMRError::NotationError( - "Keeper notation parsing error - missing notation URI".to_string(), - )); - } - - let section_name = section.to_lowercase(); - let sections = ["prefix", "record", "selector", "footer"]; - if !sections.contains(§ion_name.as_str()) { - return Err(KSMRError::NotationError( - format!( - "Keeper notation parsing error - unknown section: {}", - section_name - ) - .to_string(), - )); - } - - let mut result = NotationSection::new(section); - result.start_pos = pos; - result.index1 = None; - result.index2 = None; - - match section_name.as_str() { - "prefix" => { - let uri_prefix = format!("{}://", NOTATION_PREFIX); - if notation - .to_lowercase() - .starts_with(&uri_prefix.to_lowercase()) - { - result.is_present = true; - result.start_pos = 0; - result.end_pos = (uri_prefix.len() - 1).try_into().unwrap(); - result.text = Some(( - notation[..uri_prefix.len()].to_string(), - notation[..uri_prefix.len()].to_string(), - )); - } - } - "footer" => { - result.is_present = pos < notation.len().try_into().unwrap(); - if result.is_present { - result.start_pos = pos; - result.end_pos = (notation.len() - 1).try_into().unwrap(); - result.text = Some(( - notation[pos.try_into().unwrap()..].to_string(), - notation[pos.try_into().unwrap()..].to_string(), - )); - } - } - "record" => { - result.is_present = pos < notation.len().try_into().unwrap(); - if result.is_present { - if let Some(parsed) = - Self::parse_subsection(notation, pos.try_into().unwrap(), "/", true)? - { - result.start_pos = pos; - result.end_pos = pos + parsed.1.len() as isize - 1; - result.text = Some(parsed.clone()); - } - } - } - "selector" => { - result.is_present = pos < notation.len().try_into().unwrap(); - if result.is_present { - if let Some(parsed) = - Self::parse_subsection(notation, pos.try_into().unwrap(), "/", false)? - { - result.start_pos = pos; - result.end_pos = pos + parsed.1.len() as isize - 1; - result.text = Some(parsed.clone()); - - // Handle selector-specific logic - let long_selectors = ["field", "custom_field", "file"]; - if long_selectors.contains(&parsed.0.to_lowercase().as_str()) { - if let Some(param) = Self::parse_subsection( - notation, - result.end_pos as usize + 1, - "[", - true, - )? { - result.parameter = Some(param.clone()); - - // Adjust end_pos for parameter length - let plen_adjustment = - if param.1.ends_with('[') && !param.1.ends_with("\\[") { - 1 - } else { - 0 - }; - result.end_pos += param.1.len() as isize - plen_adjustment; - // Parse index1 - if let Some(index1) = Self::parse_subsection( - notation, - result.end_pos as usize + 1, - "[]", - true, - )? { - result.index1 = Some(index1.clone()); - result.end_pos += index1.1.len() as isize; - - // Parse index2 - if let Some(index2) = Self::parse_subsection( - notation, - result.end_pos as usize + 1, - "[]", - true, - )? { - result.index2 = Some(index2.clone()); - result.end_pos += index2.1.len() as isize; - } - } - } - } - } - } - } - _ => { - return Err(KSMRError::NotationError(format!( - "Keeper notation parsing error - unknown section '{}'", - section_name - ))); - } - } - Ok(result) - } - - pub fn parse_notation( - notation: &str, - legacy_mode: bool, - ) -> Result, KSMRError> { - if notation.is_empty() { - return Err(KSMRError::NotationError( - "Keeper notation is missing or invalid.".to_string(), - )); - } - - // Check for URL-safe base64 encoding - let mut notation = notation.to_string(); - if !notation.contains('/') { - let decoded = utils::base64_to_bytes(¬ation).map_err(|_| { - KSMRError::NotationError( - "Invalid format of Keeper notation - plaintext URI or URL-safe base64 string expected." - .to_string(), - ) - })?; - notation = utils::bytes_to_string(&decoded).map_err(|_| { - KSMRError::NotationError( - "Invalid Keeper notation - decoded base64 is not valid UTF-8.".to_string(), - ) - })?; - } - - // Parse sections - let prefix = SecretsManager::parse_section(¬ation, "prefix", 0)?; - let pos = if prefix.is_present { - prefix.end_pos + 1 - } else { - 0 - }; - - let record = SecretsManager::parse_section(¬ation, "record", pos)?; - let pos = if record.is_present { - record.end_pos + 1 - } else { - notation.len() as isize - }; - - let mut selector = SecretsManager::parse_section(¬ation, "selector", pos)?; - let pos = if selector.is_present { - selector.end_pos + 1 - } else { - notation.len() as isize - }; - let footer = SecretsManager::parse_section(¬ation, "footer", pos)?; - - // Verify parsed query - let short_selectors = ["type", "title", "notes"]; - let full_selectors = ["field", "custom_field", "file"]; - let selectors = [&short_selectors[..], &full_selectors[..]].concat(); - - if !record.is_present || !selector.is_present { - return Err(KSMRError::NotationError( - "Keeper notation URI missing information about the UID, file, field type, or field key." - .to_string(), - )); - } - - if footer.is_present { - return Err(KSMRError::NotationError( - "Keeper notation is invalid - extra characters after the last section.".to_string(), - )); - } - - if let Some(ref sel_text) = selector.text { - if !selectors.contains(&sel_text.0.to_lowercase().as_str()) { - return Err(KSMRError::NotationError( - "Keeper notation is invalid - bad selector, must be one of (type, title, notes, field, custom_field, file)." - .to_string(), - )); - } - - if short_selectors.contains(&sel_text.0.to_lowercase().as_str()) - && selector.parameter.is_some() - { - return Err(KSMRError::NotationError( - "Keeper notation is invalid - selectors (type, title, notes) do not have parameters." - .to_string(), - )); - } - - if full_selectors.contains(&sel_text.0.to_lowercase().as_str()) { - if selector.parameter.is_none() { - return Err(KSMRError::NotationError( - "Keeper notation is invalid - selectors (field, custom_field, file) require parameters." - .to_string(), - )); - } - - if sel_text.0.to_lowercase() == "file" - && !(selector.index1.is_none() && selector.index2.is_none()) - { - return Err(KSMRError::NotationError( - "Keeper notation is invalid - file selectors don't accept indexes." - .to_string(), - )); - } - - if sel_text.0.to_lowercase() != "file" - && selector.index1.is_none() - && selector.index2.is_some() - { - return Err(KSMRError::NotationError( - "Keeper notation is invalid - two indexes required.".to_string(), - )); - } - - if selector.index1.is_some() { - let sector_match_status = regex::Regex::new(r"^\[\d*\]$") - .unwrap() - .is_match(&selector.index1.clone().unwrap().1); - if !sector_match_status { - if !legacy_mode { - return Err(KSMRError::NotationError( - "Keeper notation is invalid - first index must be numeric: [n] or [].".to_string(), - )); - } - - if selector.index2.is_none() { - let index_clone = Some(( - selector.index1.clone().unwrap().0.clone(), - selector.index1.clone().unwrap().1.clone(), - )); - selector.index2 = index_clone; - selector.index1 = Some(("".to_string(), "[]".to_string())); - } - } - } - } - } - - Ok(vec![prefix, record, selector, footer]) - } - - pub fn get_notation_result(&mut self, notation: String) -> Result, KSMRError> { - let mut result = Vec::new(); - let parsed = SecretsManager::parse_notation(¬ation, false) - .map_err(|e| KSMRError::NotationError(e.to_string()))?; - - if parsed.len() < 3 { - return Err(KSMRError::NotationError(format!( - "Invalid Notation -{}", - notation - ))); - } - - let selector = parsed[2] - .text - .clone() - .ok_or_else(|| { - KSMRError::NotationError(format!("Keeper notation is invalid : {}", notation)) - })? - .0; - let record_token = parsed[1] - .text - .clone() - .ok_or_else(|| { - KSMRError::NotationError(format!( - "Keeper notation is invalid - missing UID/title {}.", - notation - )) - })? - .0; - - // Find the record by UID or title - let mut records = Vec::new(); - let re = Regex::new(r"^[A-Za-z0-9_-]{22}$").unwrap(); - if re.is_match(&record_token) { - let re_array = vec![record_token.clone()]; - records = self.get_secrets(re_array)?; - if records.len() > 1 { - return Err(KSMRError::NotationError(format!( - "found more than one record with same uid/title: {}", - record_token - ))); - } - } - if records.is_empty() { - let secrets = self.get_secrets(vec![])?; - if !secrets.is_empty() { - records = secrets - .iter() - .filter(|secret| secret.title == record_token) - .cloned() - .collect(); - } - } - if records.len() > 1 { - return Err(KSMRError::NotationError(format!( - "Notation error - multiple records matched {}", - record_token - ))); - } - if records.is_empty() { - return Err(KSMRError::NotationError(format!( - "Notation error - No records matched {}", - record_token - ))); - } - let mut record = records[0].clone(); - - let parameter: Option = parsed[2].parameter.clone().map(|par| par.0); - let index1: Option = parsed[2].index1.clone().map(|ind| ind.0); - let index2: Option = parsed[2].index2.clone().map(|ind| ind.0); - - match selector.to_lowercase().as_str() { - "type" => { - if !record.record_type.is_empty() { - result.push(record.record_type); - } - } - "title" => { - if !record.title.is_empty() { - result.push(record.title); - } - } - "notes" => { - if let Some(note) = record.record_dict.get("notes") { - if let Some(s) = note.as_str() { - result.push(s.to_string()); - } - } - } - "file" => { - if parameter.is_none() { - return Err(KSMRError::NotationError(format!( - "Notation error - Missing required parameter: filename or file UID for files in record '{record_token}'" - ))); - } - if record.files.is_empty() { - return Err(KSMRError::NotationError(format!( - "Notation error - Record {record_token} has no file attachments." - ))); - } - let param = parameter.clone().unwrap_or_default(); - let mut files: Vec<_> = record - .files - .iter_mut() - .filter(|file| file.name == param || file.title == param || file.uid == param) - .collect(); - if files.len() > 1 { - return Err(KSMRError::NotationError(format!( - "Notation error - Record {record_token} has multiple files matching the search criteria '{param}'" - ))); - } - if files.is_empty() { - return Err(KSMRError::NotationError(format!( - "Notation error - Record {record_token} has no files matching the search criteria '{param}'" - ))); - } - let contents = files[0] - .get_file_data() - .map_err(|_| { - KSMRError::NotationError(format!( - "Notation error - Record {record_token} has corrupted KeeperFile data." - )) - })? - .ok_or_else(|| { - KSMRError::NotationError(format!( - "Notation error - Record {record_token} has corrupted KeeperFile data." - )) - })?; - let text = CryptoUtils::bytes_to_url_safe_str(&contents); - result.push(text); - } - "field" | "custom_field" => { - let parameter_value = parameter.clone().ok_or_else(|| { - KSMRError::NotationError("Notation error - Missing required parameter for the field (type or label): ex. /field/type or /custom_field/MyLabel.".to_string()) - })?; - - // Find the field in the record - let fields_option = record.record_dict.get("fields"); - let fields = match fields_option { - Some(val) if val.is_array() => val.as_array().unwrap(), - _ => &Vec::new(), - }; - - let fields_filtered: Vec = fields - .iter() - .filter(|field| { - if let Some(field_obj) = field.as_object() { - let type_value = field_obj - .get("type") - .and_then(|v| v.as_str()) - .unwrap_or("some_non_existing_type"); - let label_value = field_obj - .get("label") - .and_then(|v| v.as_str()) - .unwrap_or("some_non_existing_label"); - parameter_value == type_value || parameter_value == label_value - } else { - false - } - }) - .cloned() - .collect(); - - if fields_filtered.len() > 1 { - return Err(KSMRError::NotationError(format!( - "Notation error - Record {record_token} has multiple fields matching the search criteria '{parameter_value}'" - ))); - } - if fields_filtered.is_empty() { - return Err(KSMRError::NotationError(format!( - "Notation error - Record {record_token} has no fields matching the search criteria '{parameter_value}'" - ))); - } - - let field = fields_filtered[0].clone(); - let values: Vec = field - .get("value") - .and_then(|v| v.as_array().cloned()) - .unwrap_or_else(|| vec![]); - - // Handle index1 (array index) - let idx = index1 - .as_ref() - .and_then(|s| s.parse::().ok()) - .unwrap_or(usize::MAX); - - // If index1 is specified and valid, get that value, else get all - let selected_values: Vec<&Value> = if idx != usize::MAX { - if idx < values.len() { - vec![&values[idx]] - } else { - return Err(KSMRError::NotationError(format!( - "idx out of range: {} for field {}", - idx, parameter_value - ))); - } - } else { - values.iter().collect() - }; - - // Handle index2 (object property) - let property = index2.clone().unwrap_or_default(); - - for val in selected_values { - if !property.is_empty() { - if let Some(obj) = val.as_object() { - if let Some(prop_val) = obj.get(&property) { - if let Some(s) = prop_val.as_str() { - result.push(s.to_string()); - } else { - result.push(prop_val.to_string()); - } - } else { - return Err(KSMRError::NotationError(format!( - "Property '{}' not found in field value object", - property - ))); - } - } else { - return Err(KSMRError::NotationError(format!( - "Field value is not an object, cannot extract property '{}'", - property - ))); - } - } else if let Some(s) = val.as_str() { - result.push(s.to_string()); - } else { - // If not a string, serialize to string - result.push(val.to_string()); - } - } - } - _ => { - return Err(KSMRError::NotationError(format!( - "Notation error - Invalid notation: {}", - notation - ))); - } - } - Ok(result) - } - - pub fn inflate_field_value( - &mut self, - uids: Vec, - replace_fields: Vec, - ) -> Result>, KSMRError> { - let mut value: Vec> = Vec::new(); - // Retrieve and organize records by UID - let records = self.get_secrets(uids.clone())?; - let _record_type = match records.is_empty(){ - true => return Err(KSMRError::RecordDataError(format!("No records found with the details for field given with uids : {uids:?} in given folder/application scope."))), - false => records[0].record_type.clone(), - }; - let lookup: HashMap = - records.into_iter().map(|r| (r.uid.clone(), r)).collect(); - if lookup.is_empty() { - return Err(KSMRError::RecordDataError(format!("No records found with the details for field given with uids : {uids:?} in given folder/application scope."))); - } - for uid in &uids { - if let Some(record) = lookup.get(uid) { - // let new_value: Option> = None; - let mut final_data_object = HashMap::new(); - for replacement_key in &replace_fields { - let real_field = match record.get_standard_field(replacement_key) { - Ok(field) => field, - Err(err) => { - error!("Failed to get standard field {}: {}", replacement_key, err); - continue; - } - }; - let real_field_value = - &record.standard_fields_searched_map(replacement_key)?[0]; - let real_field_value_type = real_field_value - .get("type") - .unwrap() - .as_str() - .unwrap() - .to_string(); - let real_field_value_label = match real_field_value.get("label") { - Some(val) => val.as_str().unwrap().to_string(), - None => "".to_string(), - }; - - let real_value; - match real_field.is_empty() { - // we should not have empty field array - true => continue, - false => { - // we select the first field that matches our filter - let real_field_first = real_field[0].clone(); - match real_field_first.is_array() { - // value is always array, so checking for corruption of data - false => continue, - true => { - let real_first_value_array = - real_field_first.as_array().unwrap(); - // returning the first value of data which is object - real_value = match real_first_value_array.is_empty() { - true => continue, - false => real_first_value_array[0].clone(), - }; - } - } - } - }; - - let _real_value_hashmap: HashMap = match real_value.is_object(){ - true => { - let hashmap: HashMap = real_value.as_object().unwrap().clone().iter().map(|(k, v)| (k.clone(), v.as_str().unwrap().to_string())).collect(); - hashmap.iter().for_each(|(k, v)| { - final_data_object.insert(k.clone(), v.clone()); - }); - hashmap - }, - false => match real_value.is_string(){ - true => { - let hashmap = HashMap::new(); - let val = real_value.as_str().unwrap().to_string(); - if replacement_key=="addressRef"{ - let return_value = self.inflate_field_value(vec![val], vec!["address".to_string()])?; - final_data_object.extend(return_value[0].clone()); - hashmap - }else{ - if !real_field_value_label.is_empty(){ - final_data_object.insert(real_field_value_label, val); - }else{ - final_data_object.insert(real_field_value_type, val); - } - hashmap - } - }, - false => return Err(KSMRError::NotationError(format!("Notation error - Cannot extract property '{replacement_key}' from null value."))), - }, - }; - } - value = vec![final_data_object]; - } - } - - Ok(value) - } -} - -#[derive(Debug, Default)] -pub struct NotationSection { - pub section: String, // section name - ex. prefix - pub is_present: bool, // presence flag - pub start_pos: isize, // section start pos in URI - pub end_pos: isize, // section end pos in URI - pub text: Option<(String, String)>, // [unescaped, raw] text - pub parameter: Option<(String, String)>, // || - pub index1: Option<(String, String)>, // numeric index [N] or [] - pub index2: Option<(String, String)>, // property index - ex. field/name[0][middle] -} - -impl NotationSection { - pub fn new(section: &str) -> Self { - NotationSection { - section: section.to_string(), - is_present: false, - start_pos: -1, - end_pos: -1, - text: None, - parameter: None, - index1: None, - index2: None, - } - } -} diff --git a/sdk/rust/src/core/mod.rs b/sdk/rust/src/core/mod.rs deleted file mode 100644 index 1fd6849ca..000000000 --- a/sdk/rust/src/core/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -// -*- coding: utf-8 -*- -// _ __ -// | |/ /___ ___ _ __ ___ _ _ (R) -// | ' ` containing the original data followed by the appropriate padding bytes. -/// -/// # Examples -/// -/// ``` -/// use keeper_secrets_manager_core::crypto::pad_data; -/// let data = b"YELLOW SUBMARINE"; -/// let block_size = 20; -/// let padded = pad_data(data, block_size); -/// assert_eq!(padded, b"YELLOW SUBMARINE\x04\x04\x04\x04"); -/// -/// let empty_data: &[u8] = b""; -/// let padded_empty = pad_data(empty_data, block_size); -/// assert_eq!(padded_empty, vec![20; 20]); // 20 padding bytes of value 20 -/// ``` -/// -/// # Panics -/// -/// This function does not panic, but it assumes that `block_size_var` is greater than zero. -pub fn pad_data(data: &[u8], block_size_var: usize) -> Vec { - // Calculate the padding length - let pad_len = if data.is_empty() || (data.len() % block_size_var == 0) { - block_size_var - } else { - block_size_var - (data.len() % block_size_var) - }; - - let mut padded_data = Vec::with_capacity(data.len() + pad_len); - - // Copy original data - padded_data.extend_from_slice(data); - - // Add padding bytes - padded_data.extend(vec![pad_len as u8; pad_len]); - - padded_data -} -/// Removes PKCS#7 padding from the given data. -/// -/// This function checks for and removes padding bytes added to the data according to the PKCS#7 padding scheme. -/// The last byte of the data indicates how many bytes were added as padding. The function will return an error -/// if the padding is invalid or if the data is empty. -/// -/// # Arguments -/// -/// * `data` - A slice of bytes that contains the padded data. -/// -/// # Returns -/// -/// Returns a `Result, KSMRError>` where: -/// - `Ok(Vec)` contains the unpadded data if the padding is valid. -/// - `Err(KSMRError)` provides an error message if the padding is invalid or if the data is empty. -/// -/// # Examples -/// -/// ``` -/// use keeper_secrets_manager_core::crypto::unpad_data; -/// let padded_data = b"YELLOW SUBMA\x04\x04\x04\x04"; -/// let unpadded = unpad_data(padded_data).unwrap(); -/// assert_eq!(unpadded, b"YELLOW SUBMA"); -/// -/// let invalid_padded_data = b"YELLOW SUBMARINE\x04\x04\x04\x05"; // Incorrect padding -/// assert!(unpad_data(invalid_padded_data).is_err()); -/// -/// let empty_data: &[u8] = b""; -/// assert!(unpad_data(empty_data).is_err()); // Expecting an error for empty data -/// ``` -/// -/// # Errors -/// -/// This function will return the following errors: -/// - `KSMRError::CryptoError("Data is empty")`: If the input data is an empty slice. -/// - `KSMRError::CryptoError("Invalid padding length: ...")`: If the padding length is out of the valid range. -/// - `KSMRError::CryptoError("Invalid padding bytes")`: If the padding bytes are not consistent. -pub fn unpad_data(data: &[u8]) -> Result, KSMRError> { - let data_len = data.len(); - - // Check for empty data - if data_len == 0 { - return Err(KSMRError::CryptoError("Data is empty".to_string())); - } - - let pad_len = data[data_len - 1] as usize; - - if !data[data_len - pad_len..] - .iter() - .all(|&b| b == pad_len as u8) - { - return Err(KSMRError::CryptoError("Invalid padding bytes".to_string())); - } - - // Return the unpadded data - Ok(data[..data_len - pad_len].to_vec()) -} - -impl CryptoUtils { - /// Pads the given binary data to a multiple of the block size using the PKCS#7 padding scheme. - /// - /// This function adds padding to the input `data` so its length becomes a multiple of the `BLOCK_SIZE`. - /// The padding scheme specifies that each padding byte's value is the total number of padding bytes added. - /// If `data` is already a multiple of `BLOCK_SIZE`, an additional full block of padding is appended. - /// - /// # Arguments - /// - /// * `data` - A slice of bytes to be padded. - /// - /// # Returns - /// - /// A `Vec` containing the original data followed by the necessary padding bytes. - /// - /// # Example - /// - /// ``` - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// const BLOCK_SIZE: usize = 16; - /// - /// // Example with exact block size - /// let data = b"YELLOW SUBMARINE"; - /// let padded_data = CryptoUtils::pad_binary(data); - /// assert_eq!(padded_data, b"YELLOW SUBMARINE\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10"); - /// - /// // Example with non-exact block size - /// let data = b"HELLO"; - /// let padded_data = CryptoUtils::pad_binary(data); - /// assert_eq!(padded_data, b"HELLO\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"); - /// ``` - /// - /// In the first example, the input `"YELLOW SUBMARINE"` is 16 bytes, so an extra block of padding is added. - /// In the second, `"HELLO"` is 5 bytes, and padding is added to reach the next multiple of the block size. - /// - /// # Panics - /// - /// This function does not panic under normal usage. - /// - /// # Errors - /// - /// This function does not return errors. - pub fn pad_binary(data: &[u8]) -> Vec { - const BLOCK_SIZE: usize = 16; // Define or pass as argument as needed - - let pad_len = BLOCK_SIZE - (data.len() % BLOCK_SIZE); - let mut padded_data = Vec::with_capacity(data.len() + pad_len); - - // Add original data - padded_data.extend_from_slice(data); - - // Add padding bytes - padded_data.extend(vec![pad_len as u8; pad_len]); - - padded_data - } - - /// Removes PKCS#7 padding from the given binary data. - /// - /// This function removes padding from the input `data` that was added according to the PKCS#7 padding scheme. - /// It checks the validity of the padding bytes and returns an error if the padding is invalid. The function assumes - /// that the length of the data is a multiple of the `BLOCK_SIZE`. - /// - /// # Arguments - /// - /// * `data` - A slice of padded binary data to be unpadded. - /// - /// # Returns - /// - /// * `Ok(Vec)` - The unpadded data if the padding is valid. - /// * `Err(&'static str)` - An error message if the padding or data length is invalid. - /// - /// # Errors - /// - /// This function returns an error in the following cases: - /// - The data is empty. - /// - The data length is not a multiple of the `BLOCK_SIZE`. - /// - The padding is invalid (either incorrectly formatted or out of bounds). - /// - /// # Example - /// - /// ``` - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// const BLOCK_SIZE: usize = 16; - /// - /// // Example with valid padding - /// let padded_data = b"YELLOW SUBMARINE\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10"; - /// let unpadded_data = CryptoUtils::unpad_binary(padded_data).unwrap(); - /// assert_eq!(unpadded_data, b"YELLOW SUBMARINE"); - /// - /// // Example with invalid padding - /// let invalid_padded_data = b"YELLOW SUBMARINE\x05\x05\x05\x05\x06"; - /// assert!(CryptoUtils::unpad_binary(invalid_padded_data).is_err()); - /// ``` - /// - /// # Panics - /// - /// This function does not panic but returns a `Result` in case of errors like invalid padding or improper length. - pub fn unpad_binary(data: &[u8]) -> Result, KSMRError> { - const BLOCK_SIZE: usize = 16; // Define or pass as argument as needed - - let data_len = data.len(); - - // Check if the data is empty - if data_len == 0 { - return Err(KSMRError::CryptoError("Data is empty".to_string())); - } - - // Check if the length is a multiple of the block size - if data_len % BLOCK_SIZE != 0 { - return Err(KSMRError::CryptoError("Invalid data length".to_string())); - } - - // Get the padding length from the last byte - let pad_len = data[data_len - 1]; - - // Validate the padding length - if pad_len == 0 || pad_len as usize > BLOCK_SIZE || pad_len as usize > data_len { - return Err(KSMRError::CryptoError("Invalid padding".to_string())); - } - - // Ensure padding bytes are correct - if !data[data_len - pad_len as usize..] - .iter() - .all(|&b| b == pad_len) - { - return Err(KSMRError::CryptoError("Invalid padding".to_string())); - } - - // Return the unpadded data - Ok(data[..data_len - pad_len as usize].to_vec()) - } - - /// Removes padding from the given binary data. - /// - /// This function removes padding by interpreting the last byte of the input data as the number of padding bytes added. - /// It returns the unpadded data if the padding is valid. The function is simpler than PKCS#7, and it does not verify - /// the contents of the padding bytes—just their length. - /// - /// # Arguments - /// - /// * `data` - A slice of binary data to be unpadded. - /// - /// # Returns - /// - /// * `Ok(Vec)` - The unpadded data if the padding is valid. - /// * `Err(&'static str)` - An error message if the padding or data length is invalid. - /// - /// # Errors - /// - /// This function returns an error in the following cases: - /// - The data is empty. - /// - The padding length (extracted from the last byte) is greater than the length of the data. - /// - /// # Example - /// - /// ``` - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// let padded_data = b"HELLO WORLD\x04\x04\x04\x04"; - /// let unpadded_data = CryptoUtils::unpad_char(padded_data).unwrap(); - /// assert_eq!(unpadded_data, b"HELLO WORLD"); - /// ``` - /// - /// In this example, the input `padded_data` ends with four padding bytes (`\x04`), which are removed by the function. - /// The resulting unpadded data is `"HELLO WORLD"`. - /// - /// # Errors - /// - /// * `"Data is empty"` - If the input data is empty. - /// * `"Invalid padding length"` - If the padding length exceeds the length of the input data. - /// - /// # Panics - /// - /// This function does not panic but returns a `Result` in case of errors. - pub fn unpad_char(data: &[u8]) -> Result, KSMRError> { - if data.is_empty() { - return Err(KSMRError::CryptoError("Data is empty".to_string())); - } - - let pad_len = data[data.len() - 1] as usize; - - // Ensure padding length is not greater than data length - if pad_len == 0 || pad_len > data.len() { - return Err(KSMRError::CryptoError("Invalid padding length".to_string())); - } - - // Optionally, you could also check that all padding bytes are equal to the padding length - if !data[data.len() - pad_len..] - .iter() - .all(|&b| b == pad_len as u8) - { - return Err(KSMRError::CryptoError("Invalid padding".to_string())); - } - - // Return the unpadded data - Ok(data[..data.len() - pad_len].to_vec()) - } - - /// Converts a byte slice into a `BigUint` integer. - /// - /// # Parameters - /// - /// - `b`: A byte slice representing the input bytes to be converted to an integer. - /// - /// # Returns - /// - /// This function returns a `Result`: - /// - `Ok(BigUint)`: The converted integer on success. - /// - `Err(KSMRError)`: An error message if the input is invalid (e.g., empty input or exceeds 16 bytes). - /// - /// # Errors - /// - /// - Returns `"Input is empty"` if the provided byte slice is empty. - /// - Returns `"Input exceeds maximum length of 16 bytes"` if the input byte slice is longer than 16 bytes. - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// use std::str::FromStr; - /// use num_bigint::BigUint; - /// let bytes = &[1; 17]; - /// let result = CryptoUtils::bytes_to_int(bytes).unwrap(); - /// assert_eq!(result, BigUint::from_str("341616807575530379006368233343265341697").unwrap()); - /// ``` - pub fn bytes_to_int(b: &[u8]) -> Result { - if b.is_empty() { - return Err(KSMRError::InsufficientBytes("Input is empty".to_string())); - } - let big_number = BigUint::from_bytes_be(b); - Ok(big_number) - } - - /// Converts a URL-safe Base64 encoded string to a byte vector. - /// - /// This function decodes a URL-safe Base64 encoded string into a vector of bytes. It automatically - /// adds the necessary padding (`=`) to the input string if it's missing, ensuring it conforms to - /// Base64 encoding rules. The function also includes optional checks to verify the length of the - /// decoded byte vector for specific use cases (e.g., UUIDs). - /// - /// # Parameters - /// - /// - `s`: A string slice representing the URL-safe Base64 encoded string to decode. - /// - /// # Returns - /// - /// This function returns a `Result`: - /// - `Ok(Vec)`: A vector of bytes resulting from decoding the input string on success. - /// - `Err(KSMRError)`: An error variant indicating an issue with decoding, such as invalid Base64 format. - /// - /// # Errors - /// - /// - `KSMRError::InvalidBase64`: If the input string fails to decode as valid Base64. - /// - `KSMRError::DecodedBytesTooShort`: If the decoded byte array is shorter than the required length - /// (e.g., for UUID or other fixed-length conversions). - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// use keeper_secrets_manager_core::custom_error::KSMRError; - /// - /// fn main() -> Result<(), KSMRError> { - /// let url_safe_base64 = "c29tZSBkYXRh"; // Example URL-safe Base64 string - /// - /// // Convert URL-safe Base64 string to bytes - /// let decoded_bytes = CryptoUtils::url_safe_str_to_bytes(url_safe_base64); - /// - /// // Print the resulting byte vector - /// println!("Decoded bytes: {:?}", decoded_bytes); - /// - /// Ok(()) - /// } - /// ``` - /// - /// # Panics - /// - /// This function does not panic under normal operation. However, it will return an error if the input string is not valid Base64. - /// - /// # Notes - /// - /// - The function automatically adds padding (`=`) to the input string if necessary to conform to Base64 encoding rules. - /// - The decoded byte array is optionally validated for minimum length (e.g., at least 8 bytes for UUIDs). - pub fn url_safe_str_to_bytes(s: &str) -> Result, KSMRError> { - // Attempt to decode the URL-safe Base64 string - let text = s.replace("+", "-").replace("/", "_"); - let decoded_bytes = URL_SAFE_NO_PAD - .decode(&text) - .map_err(|err| KSMRError::DecodeError(err.to_string())); - let decoded_bytes = match decoded_bytes { - Ok(decoded_bytes) => decoded_bytes, - Err(err) => { - if err == KSMRError::InvalidBase64 { - URL_SAFE_NO_PAD - .decode(s) - .map_err(|err| KSMRError::DecodeError(err.to_string()))? - } else { - return Err(err); - } - } - }; - - // Optional: Check if the decoded bytes are long enough (e.g., for UUIDs) - if decoded_bytes.len() < 8 { - return Err(KSMRError::DecodedBytesTooShort); - } - - Ok(decoded_bytes) - } - - pub fn url_safe_str_to_bytes_trim_padding(s: &str) -> Result, KSMRError> { - let mut text = s.trim_end_matches("=").to_string(); - // Attempt to decode the URL-safe Base64 string - text = text.replace("+", "-").replace("/", "_"); - let decoded_bytes = URL_SAFE_NO_PAD - .decode(&text) - .map_err(|err| KSMRError::DecodeError(err.to_string())); - let decoded_bytes = match decoded_bytes { - Ok(decoded_bytes) => decoded_bytes, - Err(err) => { - if err == KSMRError::InvalidBase64 { - URL_SAFE_NO_PAD - .decode(s) - .map_err(|err| KSMRError::DecodeError(err.to_string()))? - } else { - return Err(err); - } - } - }; - // Optional: Check if the decoded bytes are long enough (e.g., for UUIDs) - if decoded_bytes.len() < 8 { - return Err(KSMRError::DecodedBytesTooShort); - } - Ok(decoded_bytes) - } - - #[allow(clippy::needless_doctest_main)] - /// Generates a vector of random bytes of the specified length. - /// - /// # Parameters - /// - /// - `length`: The desired length of the random byte vector. - /// - /// # Returns - /// - /// This function returns a `Vec` containing `length` random bytes. - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// fn main() { - /// let length = 16; // Specify the length of random bytes - /// let random_bytes = CryptoUtils::generate_random_bytes(length); - /// - /// // Print the generated random bytes - /// println!("Generated random bytes: {:?}", random_bytes); - /// } - /// ``` - /// - /// # Panics - /// - /// This function will panic if `length` is zero. - pub fn generate_random_bytes(length: usize) -> Vec { - let mut rng = rand::thread_rng(); // Get a random number generator - let mut bytes = vec![0u8; length]; - rng.fill(&mut bytes[..]); - bytes // Return the random bytes - } - - #[allow(clippy::needless_doctest_main)] - /// Generates a 32-byte random encryption key. - /// - /// This function is suitable for creating encryption keys for symmetric encryption algorithms, - /// such as AES-256, which requires a 256-bit (32-byte) key. - /// - /// # Returns - /// - /// This function returns a `Vec` containing 32 random bytes, which can be used as an encryption key. - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// fn main() { - /// let encryption_key = CryptoUtils::generate_encryption_key_bytes(); - /// - /// // Print the generated encryption key - /// println!("Generated encryption key: {:?}", encryption_key); - /// } - /// ``` - /// - /// # Panics - /// - /// This function does not panic under normal operation since it relies on generating random bytes. - pub fn generate_encryption_key_bytes() -> Vec { - Self::generate_random_bytes(32) - } - - #[allow(clippy::needless_doctest_main)] - /// Converts a byte slice to a URL-safe Base64-encoded string. - /// - /// This function encodes the given byte slice into a URL-safe Base64 string, - /// stripping any trailing padding characters (`=`) that are typically used - /// in Base64 encoding. The resulting string can be safely included in URLs. - /// - /// # Parameters - /// - /// - `b`: A byte slice that you want to encode to a URL-safe Base64 string. - /// - /// # Returns - /// - /// This function returns a `String` containing the URL-safe Base64-encoded representation - /// of the input byte slice. - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// fn main() { - /// let data = b"Hello, World!"; - /// let encoded_str = CryptoUtils::bytes_to_url_safe_str(data); - /// - /// // Print the URL-safe Base64-encoded string - /// println!("Encoded URL-safe string: {}", encoded_str); - /// } - /// ``` - /// - /// # Notes - /// - /// - The function uses the `BASE64_URL_SAFE` encoder, which ensures that the resulting - /// string is safe for use in URLs and does not contain characters that may need - /// to be escaped. - pub fn bytes_to_url_safe_str(b: &[u8]) -> String { - // Encode bytes to URL-safe Base64 and strip padding '=' characters - let encoded_value = BASE64_URL_SAFE.encode(b); - encoded_value.trim_end_matches('=').to_string() - } - - /// Converts a URL-safe Base64-encoded string to a `BigUint` integer. - /// - /// This function first decodes the URL-safe Base64 string into a byte vector. - /// It then converts the resulting byte vector into a `BigUint` integer. The function - /// will return an error if the string cannot be decoded or if the resulting byte - /// vector cannot be converted to a valid integer. - /// - /// # Parameters - /// - /// - `s`: A string slice representing the URL-safe Base64-encoded data. - /// - /// # Returns - /// - /// This function returns a `Result`: - /// - `Ok(BigUint)`: The decoded integer value on success. - /// - `Err(KSMRError)`: An error variant indicating a failure in decoding or conversion. - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// use keeper_secrets_manager_core::custom_error::KSMRError; - /// fn main() -> Result<(), KSMRError> { - /// let url_safe_str = "AQIDBAUGBwgJCgsMDQ4PEA=="; // Example URL-safe Base64 string - /// - /// match CryptoUtils::url_safe_str_to_int(url_safe_str) { - /// Ok(int_value) => println!("Decoded integer value: {}", int_value), - /// Err(err) => println!("Error decoding string: {:?}", err), - /// } - /// Ok(()) - /// } - /// ``` - /// - /// # Errors - /// - /// - Returns a `KSMRError::InvalidBase64` if the input string is not valid Base64. - /// - Returns `KSMRError::InvalidIntegerConversion` if the decoded byte slice cannot - /// be converted to a valid `BigUint` integer. - /// - /// # Notes - /// - /// - The function assumes that the input string is a valid URL-safe Base64 string. - /// - Any invalid Base64 characters or decoding issues will result in a `KSMRError`. - pub fn url_safe_str_to_int(s: &str) -> Result { - let bytes = Self::url_safe_str_to_bytes(s)?; // Assuming this function is also updated - let int_value = Self::bytes_to_int(&bytes)?; // Now returns KSMRError - Ok(int_value) - } - - #[allow(clippy::needless_doctest_main)] - /// Generates an ECC signing key. - /// - /// This function generates a random encryption key, converts it to a URL-safe Base64 string, - /// then converts the string into an integer. The integer is used to populate the first 16 bytes - /// of a 32-byte array, with the remaining 16 bytes set to zeros. This 32-byte array is then used - /// to create a `SigningKey` that can be used for ECC-based signing operations. - /// - /// # Returns - /// - /// This function returns a `Result`: - /// - `Ok(SigningKey)`: A successfully generated `SigningKey` instance, which can be used for ECC signing operations. - /// - `Err(KSMRError)`: If any of the operations fail, such as key generation, conversion, or `SigningKey` creation. - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; // Adjust to your actual module path - /// - /// fn main() { - /// let signing_key = CryptoUtils::generate_ecc_keys().unwrap(); - /// println!("Generated ECC Signing Key: {:?}", signing_key); - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if: - /// - The conversion from URL-safe Base64 string to integer fails. - /// - The creation of the `SigningKey` from the byte array fails. - /// - /// # Notes - /// - /// - The encryption key used to generate the signing key is created randomly for each call to the function, ensuring that the signing key is unique each time. - /// - The final `SigningKey` is based on a 32-byte array, where the first 16 bytes come from the converted integer, and the remaining 16 bytes are filled with zeros. - /// - The generated signing key is suitable for use in ECC-based cryptographic operations. - pub fn generate_ecc_keys() -> Result { - // Generate encryption key bytes - let encryption_key_bytes: Vec = Self::generate_encryption_key_bytes(); - - // Convert bytes to URL-safe Base64 string - let private_key_str = Self::bytes_to_url_safe_str(&encryption_key_bytes); - - // Convert URL-safe Base64 string to integer - let encryption_key_int = Self::url_safe_str_to_int(&private_key_str).map_err(|_| { - KSMRError::CryptoError("Failed to convert URL-safe Base64 string to integer".into()) - })?; - - // Create a 32-byte array for the SigningKey - let mut key_bytes = [0u8; 32]; - - // Convert the BigUint encryption_key_int to bytes and copy it to the key_bytes array - let int_bytes = encryption_key_int.to_bytes_be(); // This gives 16 bytes - key_bytes.copy_from_slice(&int_bytes); // Copy the 16 bytes from the integer - - // Create the SigningKey from the byte array - SigningKey::from_bytes(GenericArray::from_slice(&key_bytes)) - .map_err(|_| KSMRError::CryptoError("Failed to create SigningKey from bytes".into())) - } - - - #[allow(clippy::needless_doctest_main)] - /// Derives the public key from a given ECC private key. - /// - /// This function takes a reference to a `SigningKey` (private key) and derives - /// the corresponding public key. The public key is then serialized in uncompressed - /// format (X9.62). - /// - /// # Parameters - /// - /// - `private_key`: A reference to a `SigningKey`, which represents the ECC private key - /// from which the public key will be derived. - /// - /// # Returns - /// - /// This function returns a `Vec` containing the serialized public key in uncompressed - /// format. - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; // Adjust to your actual module path - /// - /// fn main() { - /// // Assume you have a valid SigningKey instance - /// let private_key = CryptoUtils::generate_ecc_keys().unwrap(); - /// - /// // Get the corresponding public key - /// let public_key = CryptoUtils::public_key_ecc(&private_key); - /// - /// println!("Public Key: {:?}", public_key); - /// } - /// ``` - /// - /// # Notes - /// - /// - The uncompressed format of the public key allows for straightforward serialization - /// and transmission. It includes the x-coordinate and the y-coordinate of the point - /// on the elliptic curve. - /// - Ensure that the `SigningKey` provided to this function is valid and has been properly - /// initialized before calling this function. - pub fn public_key_ecc(private_key: &SigningKey) -> Vec { - // Get the public key from the private key - let public_key: VerifyingKey = *private_key.verifying_key(); - - // Serialize the public key in uncompressed format (X9.62) - let pub_key_bytes = public_key.to_encoded_point(false).as_ref().to_vec(); - - pub_key_bytes - } - - - #[allow(clippy::needless_doctest_main)] - /// Generates a new ECC private key. - /// - /// This function generates a new 256-bit (32-byte) private key suitable for ECC operations, - /// specifically for the P256 curve. The process involves generating random bytes, converting - /// those bytes into a URL-safe Base64 string, and then converting that string into an integer. - /// The integer is then used to create the `SigningKey` which represents the ECC private key. - /// - /// # Returns - /// - /// This function returns a `Result`: - /// - `Ok(SigningKey)`: The successfully generated ECC private key as a `SigningKey`. - /// - `Err(KSMRError)`: An error if any step of the key generation process fails, including random byte generation, Base64 conversion, integer conversion, or `SigningKey` creation. - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; // Adjust to your actual module path - /// - /// fn main() { - /// // Generate a new ECC private key - /// let private_key = CryptoUtils::generate_private_key_ecc().unwrap(); - /// - /// // Print or use the private key as needed - /// println!("Generated Private Key: {:?}", private_key); - /// } - /// ``` - /// - /// # Notes - /// - /// - The generated private key is a 256-bit (32-byte) key, which is compatible with the P256 curve used in ECC operations. - /// - Ensure that the random bytes are securely generated, as this key will be used in cryptographic operations. The private key should be kept confidential at all times. - /// - /// # Panics - /// - /// This function will panic if any of the following conditions occur: - /// - The conversion of the URL-safe Base64 string to an integer fails. - /// - The `SigningKey` creation from the byte array fails. - /// - /// # Implementation Details - /// - /// - The key is derived from random bytes, which are encoded into a URL-safe Base64 string, then decoded back to an integer. - /// - The integer is converted to bytes, and the first 16 bytes are used for the key, with the remaining bytes padded with zeros. - /// - The final 32-byte array is used to create the `SigningKey` using `SigningKey::from_bytes`. - pub fn generate_private_key_ecc() -> Result { - // Generate random bytes for the encryption key - let encryption_key_bytes = Self::generate_random_bytes(32); - - // Convert bytes to URL-safe Base64 string - let private_key_str = Self::bytes_to_url_safe_str(&encryption_key_bytes); - - // Convert URL-safe Base64 string to integer - let encryption_key_int = Self::url_safe_str_to_int(&private_key_str).map_err(|e| { - KSMRError::CryptoError(format!( - "Failed to convert URL-safe Base64 string to integer: {}", - e - )) - })?; - - // Create a byte array from the integer representation (needs 32 bytes) - let mut key_bytes = [0u8; 32]; - - // Right-align int_bytes in key_bytes - let int_bytes = encryption_key_int.to_bytes_be(); - let start = 32 - int_bytes.len(); - key_bytes[start..].copy_from_slice(&int_bytes); - - // Create SigningKey from the byte array - SigningKey::from_bytes(GenericArray::from_slice(&key_bytes)).map_err(|e| { - KSMRError::CryptoError(format!("Failed to create SigningKey from bytes: {}", e)) - })?; - - // Return the generated SigningKey - Ok(SigningKey::from_bytes(GenericArray::from_slice(&key_bytes)).unwrap()) - } - - #[allow(clippy::needless_doctest_main)] - /// Generates a new ECC private key. - /// - /// This function generates a new 256-bit (32-byte) private key suitable for ECC operations, - /// specifically for the P256 curve. The process involves generating random bytes, converting - /// those bytes into a URL-safe Base64 string, and then converting that string into an integer. - /// The integer is then used to create the `SigningKey`, which represents the ECC private key. - /// - /// # Returns - /// - /// This function returns a `Result`: - /// - `Ok(SigningKey)`: The successfully generated ECC private key as a `SigningKey`. - /// - `Err(KSMRError)`: An error if any step of the key generation process fails, including random byte generation, Base64 conversion, integer conversion, or `SigningKey` creation. - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; // Adjust to your actual module path - /// - /// fn main() { - /// // Generate a new ECC private key - /// let private_key = CryptoUtils::generate_private_key_ecc().unwrap(); - /// - /// // Print or use the private key as needed - /// println!("Generated Private Key: {:?}", private_key); - /// } - /// ``` - /// - /// # Notes - /// - /// - The generated private key is a 256-bit (32-byte) key, which is compatible with the P256 curve used in ECC operations. - /// - The first 16 bytes of the 32-byte private key are filled with the integer representation of the random bytes. - /// - The second 16 bytes are a repeat of the same integer to meet the required key length for P256. - /// - Ensure that the random bytes are securely generated, as this key will be used in cryptographic operations. The private key should be kept confidential at all times. - /// - /// # Panics - /// - /// This function will panic if any of the following conditions occur: - /// - The conversion of the URL-safe Base64 string to an integer fails. - /// - The `SigningKey` creation from the byte array fails. - /// - /// # Implementation Details - /// - /// - The key is derived from random bytes, which are encoded into a URL-safe Base64 string, then decoded back to an integer. - /// - The integer is converted to bytes, and the first 16 bytes are used for the key, with the remaining bytes padded with zeros. - /// - The final 32-byte array is used to create the `SigningKey` using `SigningKey::from_bytes`. - pub fn generate_private_key_der() -> Result, KSMRError> { - // Generate ECC signing key - let signing_key = Self::generate_private_key_ecc() - .map_err(|err| KSMRError::CryptoError(err.to_string())) - .unwrap(); - - // Export to DER format - match signing_key.to_pkcs8_der() { - Ok(private_key_der) => Ok(private_key_der.as_bytes().to_vec()), // Return the DER bytes - Err(e) => Err(KSMRError::CryptoError(format!( - "Failed to serialize to DER: {}", - e - ))), - } - } - - - - #[allow(clippy::needless_doctest_main)] - /// Generates a new ephemeral ECC signing key using the SECP256R1 curve. - /// - /// This function creates a new ECC signing key that can be used for cryptographic operations such as - /// signing or key exchange. It utilizes a secure random number generator to ensure the key is - /// generated in a cryptographically secure manner. - /// - /// # Returns - /// - /// This function returns a `SigningKey`, which represents the newly generated ECC signing key. - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; // Adjust to your actual module path - /// use p256::ecdsa::SigningKey; - /// fn main() { - /// // Generate a new ECC signing key - /// let signing_key: SigningKey = CryptoUtils::generate_new_ecc_key(); - /// - /// // Use the signing key for further cryptographic operations - /// println!("Generated new ECC signing key: {:?}", signing_key); - /// } - /// ``` - /// - /// # Notes - /// - /// - The generated key is ephemeral and should be used for a single session or transaction. - /// - Ensure that you securely manage and store the signing key if needed, as it is essential for - /// cryptographic integrity. - pub fn generate_new_ecc_key() -> SigningKey { - // Create a new OS random number generator - let mut rng = OsRng; - - // Generate an ephemeral ECC signing key for SECP256R1 - SigningKey::random(&mut rng) - } - - /// Encrypts data using AES-256-GCM with an optional nonce. - /// - /// This function performs authenticated encryption of the provided `data` using AES-256-GCM. - /// AES-256-GCM requires a 32-byte key for encryption and uses a 12-byte nonce. If a nonce is - /// not provided, a random 12-byte nonce will be generated. - /// - /// # Parameters - /// - /// - `data`: A byte slice representing the plaintext data to be encrypted. - /// - `key_bytes`: A byte slice representing the 32-byte AES key used for encryption (AES-256). - /// - `nonce_bytes`: An optional byte slice representing the nonce. If not provided, a random nonce will be generated. - /// - /// # Returns - /// - /// This function returns a `Result`: - /// - `Ok(Vec)`: The result will contain a vector with the concatenated nonce and the encrypted ciphertext on success. - /// - `Err(KSMRError)`: An error if encryption fails or if invalid input parameters are provided (e.g., wrong key size). - /// - /// # Errors - /// - /// - Returns `KSMRError::CryptoError("Invalid key size")` if the provided `key_bytes` slice is not exactly 32 bytes long. - /// - Returns `KSMRError::CryptoError("Encryption failed")` if the encryption operation fails (for example, due to invalid key or data). - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// use std::error::Error; - /// use hex; - /// - /// fn main() -> Result<(), Box> { - /// // 32-byte AES key (AES-256 requires a 32-byte key) - /// let key = b"an example very very secret key."; // Must be exactly 32 bytes - /// - /// // Example plaintext data - /// let data = b"plaintext message that needs encryption"; - /// - /// // Encrypt the data with a random nonce - /// let encrypted_data = CryptoUtils::encrypt_aes_gcm(data, key, None)?; - /// - /// // Print the encrypted data in hex format for better readability - /// println!("Encrypted data: {:?}", hex::encode(&encrypted_data)); - /// - /// Ok(()) - /// } - /// ``` - /// - /// # Panics - /// - /// This function does not panic under normal operation. However, it will return an error if input is invalid (e.g., wrong key size or if encryption fails). - /// - /// # Notes - /// - /// - AES-256-GCM is an authenticated encryption mode, which provides both confidentiality and integrity. The nonce should be unique for each encryption operation with the same key to maintain security. - /// - The nonce is prepended to the ciphertext before returning, allowing the recipient to extract and use it for decryption. - /// - /// # Implementation Details - /// - /// - AES-256-GCM requires a 32-byte key, and the nonce used must be 12 bytes in length. If a nonce is not provided, a random 12-byte nonce will be generated for each encryption operation. - /// - The AES-GCM encryption process uses the provided key and nonce to encrypt the `data`. The resulting ciphertext is then concatenated with the nonce before being returned. - /// - The function uses the `Aes256Gcm` cipher from the `aes-gcm` crate and the `rand` crate to generate random nonces when necessary. - pub fn encrypt_aes_gcm( - data: &[u8], - key_bytes: &[u8], - nonce_bytes: Option<&[u8]>, - ) -> Result, KSMRError> { - let _ = nonce_bytes; - - // Validate key size (32 bytes for AES-256) - if key_bytes.len() != 32 { - return Err(KSMRError::CryptoError("Invalid key size".to_string())); - } - - if key_bytes.len() != 32 { - return Err(KSMRError::CryptoError("Invalid key size".to_string())); - } - - // Create the key from the provided bytes - let mut cipher_obj = - aes_gcm::Aes256Gcm::new(aes_gcm::Key::::from_slice(key_bytes)); - let nonce_obj = aes_gcm::Aes256Gcm::generate_nonce(&mut OsRng); - let cipher_txt_obj = cipher_obj - .encrypt(&nonce_obj, data) - .map_err(|_| KSMRError::CryptoError("Encryption failed".to_string()))?; - - let mut result_obj = Vec::with_capacity(nonce_obj.as_slice().len() + cipher_txt_obj.len()); - result_obj.extend_from_slice(nonce_obj.as_slice()); - result_obj.extend_from_slice(&cipher_txt_obj); - Ok(result_obj) - } - - /// Decrypts data using AES-256-GCM with a 12-byte nonce. - /// - /// # Parameters - /// - /// - `data`: A byte slice containing the nonce followed by the ciphertext. The first 12 bytes represent the nonce, and the rest is the ciphertext. - /// - `key_bytes`: A byte slice representing the 32-byte AES key used for decryption (AES-256). - /// - /// # Returns - /// - /// This function returns a `Result`: - /// - `Ok(Vec)`: The decrypted plaintext on success. - /// - `Err(Box)`: An error if decryption fails or if invalid input parameters are provided (e.g., wrong key size). - /// - /// # Errors - /// - /// - Returns `"Invalid key size"` if the provided `key_bytes` slice is not exactly 32 bytes long. - /// - Returns `"Data too short to contain nonce"` if the provided `data` slice is smaller than 12 bytes. - /// - Returns `"Decryption failed"` if the decryption operation itself fails (for example, if the ciphertext or key is invalid). - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// use keeper_secrets_manager_core::custom_error::KSMRError; - /// use aes_gcm::{Aes256Gcm, Key, Nonce}; // Import the AES-GCM library - /// use std::error::Error; - /// use hex; - /// - /// fn main() -> Result<(), KSMRError> { - /// // 32-byte AES key (AES-256 requires a 32-byte key) - /// let key = b"an example very very secret key."; // Should be exactly 32 bytes - /// - /// // 12-byte nonce (unique for each encryption) - /// let nonce = b"unique nonce"; // Must be exactly 12 bytes - /// - /// // Example ciphertext (encrypted using the same key and nonce) - /// let ciphertext = hex::decode("c5d3db06f6c3d543663a94051a7a0d65")?; // Example encrypted data - /// - /// // Concatenate nonce and ciphertext - /// let mut encrypted_data = Vec::new(); - /// encrypted_data.extend_from_slice(nonce); - /// encrypted_data.extend_from_slice(&ciphertext); - /// - /// // Attempt to decrypt the data - /// let result = CryptoUtils::decrypt_aes(&encrypted_data, key); - /// - /// // Check if the decryption was successful - /// match result { - /// Ok(decrypted_data) => { - /// println!("Decrypted data: {:?}", decrypted_data); - /// }, - /// Err(err) => { - /// println!("Error: {}", err); - /// assert_eq!(err.to_string(), "Cryptography module Error: aead::Error"); - /// } - /// } - /// - /// Ok(()) - /// } - /// ``` - /// - /// # Panics - /// - /// This function does not panic under normal operation. However, it will return an error if input is invalid (e.g., wrong key size or if decryption fails). - /// - /// # Notes - /// - /// - This function assumes that the first 12 bytes of `data` represent the nonce. - /// - AES-256-GCM is an authenticated encryption mode, so decryption will fail if the ciphertext or key is tampered with. - pub fn decrypt_aes(data: &[u8], key_bytes: &[u8]) -> Result, KSMRError> { - use aes_gcm::KeyInit; - // Validate key size (32 bytes for AES-256) - if key_bytes.len() != 32 { - return Err(KSMRError::CryptoError("Invalid key size".to_string())); - } - - if data.len() < 12 { - return Err(KSMRError::CryptoError( - "Data too short to contain nonce".to_string(), - )); - } - - let ciphertext = &data[12..]; // The rest is the ciphertext - - let mut key2 = aes_gcm::Aes256Gcm::new_from_slice(key_bytes) - .map_err(|err| KSMRError::CryptoError(err.to_string()))?; - let nonce2 = aes_gcm::Nonce::from_slice(&data[..12]); - - // Decrypt the data - let decrypted_plaintext = key2 - .decrypt(nonce2, ciphertext) - .map_err(|err| KSMRError::CryptoError(err.to_string()))?; - Ok(decrypted_plaintext) - } - - /// Encrypts data using AES-256 in CBC (Cipher Block Chaining) mode. - /// - /// This function encrypts the provided plaintext data using AES-256 in CBC mode with a 32-byte key. - /// If an Initialization Vector (IV) is not provided, a random 16-byte IV is generated. The IV - /// is then prepended to the resulting ciphertext for later use during decryption. - /// - /// # Parameters - /// - /// - `data`: A byte slice representing the plaintext data to be encrypted. - /// - `key`: A 32-byte slice representing the AES-256 key used for encryption (AES-256 requires a 256-bit key). - /// - `iv`: An optional 16-byte slice representing the Initialization Vector (IV). If `None` is provided, - /// a random IV will be generated. - /// - /// # Returns - /// - /// This function returns a `Result`: - /// - `Ok(Vec)`: A vector containing the concatenated IV and the encrypted ciphertext on success. - /// - `Err(KSMRError)`: An error if the key size is invalid (i.e., not 32 bytes). - /// - /// # Errors - /// - /// - Returns `KSMRError::CryptoError("Invalid key size")` if the provided `key` is not 32 bytes long. - /// - Returns an error if encryption fails or if the padding or encryption process encounters issues. - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// use rand::rngs::OsRng; - /// - /// let plaintext = b"Sensitive data to encrypt"; - /// let key = b"0123456789abcdef0123456789abcdef"; // A 32-byte AES-256 key. - /// let iv = b"1234567890123456"; // A 16-byte IV (optional). - /// - /// let encrypted_data = CryptoUtils::encrypt_aes_cbc(plaintext, key, Some(iv)).unwrap(); - /// - /// println!("Encrypted data: {:?}", encrypted_data); - /// ``` - /// - /// # Panics - /// - /// This function does not panic but returns an error if input is invalid (e.g., incorrect key size or encryption failure). - /// - /// # Notes - /// - /// - AES-256 in CBC mode requires the key to be exactly 32 bytes. The IV must be 16 bytes in length. - /// - CBC mode requires padding to ensure the data is a multiple of the block size (16 bytes for AES). This function uses a padding scheme (assumed to be implemented in `pad_data`) to handle this. - /// - The resulting ciphertext is returned with the IV prepended to facilitate decryption. - pub fn encrypt_aes_cbc( - data: &[u8], - key: &[u8], - iv: Option<&[u8]>, - ) -> Result, KSMRError> { - if key.len() != AES_256_KEY_SIZE { - return Err(KSMRError::CryptoError("Invalid key size".to_string())); - } - - let iv = match iv { - Some(iv) => iv.to_vec(), - None => { - let mut iv = vec![0u8; BLOCK_SIZE]; - OsRng.fill_bytes(&mut iv); // Secure random IV generation - iv - } - }; - - match iv.len() { - BLOCK_SIZE => (), - _ => { - return Err(KSMRError::CryptoError("Invalid IV size".to_string())); - } - } - - let cipher = Aes256::new(GenericArray::from_slice(key)); - let padded_data = pad_data(data, BLOCK_SIZE); - let mut ciphertext = Vec::with_capacity(padded_data.len()); - let mut previous_block = iv.clone(); - - for block in padded_data.chunks(BLOCK_SIZE) { - let mut block = block.to_vec(); - - // XOR block with the previous block or IV - for (i, byte) in block.iter_mut().enumerate() { - *byte ^= previous_block[i]; - } - - let mut block_arr = GenericArray::clone_from_slice(&block); - cipher.encrypt_block(&mut block_arr); - - ciphertext.extend_from_slice(&block_arr); - previous_block = block_arr.to_vec(); - } - - let mut result = iv.clone(); - result.extend(ciphertext); - Ok(result) - } - - /// Decrypts data using AES-256 in CBC (Cipher Block Chaining) mode. - /// - /// This function decrypts the provided encrypted data using AES-256 in CBC mode with a 32-byte key. - /// The first 16 bytes of the input data are treated as the Initialization Vector (IV), and the remaining - /// bytes are treated as the ciphertext. After decryption, the padding is removed from the data to obtain the - /// original plaintext. - /// - /// # Parameters - /// - /// - `data`: A byte slice representing the encrypted data. The first 16 bytes are treated as the IV, and the - /// remaining bytes are the ciphertext. - /// - `key`: A 32-byte slice representing the AES-256 key used for decryption. - /// - /// # Returns - /// - /// This function returns a `Result`: - /// - `Ok(Vec)`: A vector containing the decrypted and unpadded plaintext data. - /// - `Err(KSMRError)`: An error if the key size is invalid, the data is too short to contain an IV, or the - /// data length is not a multiple of 16 bytes (indicating possible encoding issues). - /// - /// # Errors - /// - /// - Returns `KSMRError::CryptoError("Invalid key size")` if the provided `key` is not 32 bytes long. - /// - Returns `KSMRError::CryptoError("Data too short to contain IV")` if the provided `data` is less than 16 bytes long. - /// - Returns `KSMRError::CryptoError("Data is probably not encoded")` if the data length is not a multiple of 16 bytes. - /// - Returns `KSMRError::CryptoError("Unpadding failed: ")` if the unpadding process fails. - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// - /// let encrypted_data = b"\x01\x02..."; // Some encrypted data with a 16-byte IV followed by ciphertext. - /// let key = b"0123456789abcdef0123456789abcdef"; // A 32-byte AES-256 key. - /// let data = CryptoUtils::decrypt_aes_cbc(encrypted_data, key); - /// match data { - /// Ok(plaintext) => println!("Decrypted data: {:?}", plaintext), - /// Err(e) => eprintln!("Decryption error: {}", e), - /// } - /// ``` - /// - /// In this example, `decrypt_aes_cbc` attempts to decrypt the `encrypted_data` using the provided AES-256 key. - /// The decrypted data is returned after removing the padding. - /// - /// # Panics - /// - /// This function does not panic but returns a `Result` in case of errors. - /// - /// # Notes - /// - /// - AES-256 in CBC mode requires the key to be exactly 32 bytes. The IV must be 16 bytes in length. - /// - The first 16 bytes of the input data are interpreted as the IV, while the rest is treated as the ciphertext. - /// - CBC mode requires the ciphertext length to be a multiple of the AES block size (16 bytes). - /// - The padding is removed from the decrypted data using a custom unpadding function (`unpad_data`), which will return an error if the padding is incorrect. - pub fn decrypt_aes_cbc(data: &[u8], key: &[u8]) -> Result, KSMRError> { - // Validate key size (32 bytes for AES-256) - if key.len() != 32 { - return Err(KSMRError::CryptoError("Invalid key size".to_string())); - } - // Validate that data is large enough to contain an IV (16 bytes for AES-CBC) - if data.len() < 16 { - return Err(KSMRError::CryptoError( - "Data too short to contain IV".to_string(), - )); - } - // Extract the IV and ciphertext - let iv = &data[..16]; // First 16 bytes are the IV - let ciphertext = &data[16..]; // Remaining bytes are the encrypted data - // Validate ciphertext length - if ciphertext.len() % BLOCK_SIZE != 0 { - return Err(KSMRError::CryptoError("Data is probably not encoded".to_string())); - } - let cipher = Aes256::new(GenericArray::from_slice(key)); - let mut plaintext = Vec::with_capacity(ciphertext.len()); - let mut previous_block = iv.to_vec(); - for block in ciphertext.chunks(BLOCK_SIZE) { - let mut block_arr = GenericArray::clone_from_slice(block); - cipher.decrypt_block(&mut block_arr); - // XOR decrypted block with previous ciphertext block (or IV) - let decrypted_block: Vec = block_arr - .iter() - .zip(&previous_block) - .map(|(b, p)| b ^ p) - .collect(); - plaintext.extend_from_slice(&decrypted_block); - previous_block = block.to_vec(); - } - // Remove PKCS#7 padding - // let unpadded = unpad_data(&plaintext) - // .map_err(|e| KSMRError::CryptoError(format!("Unpadding failed: {}", e)))?; - Ok(plaintext) - } - - /// Encrypts data using an ephemeral ECDH key exchange and AES-GCM. - /// - /// This function uses Elliptic Curve Diffie-Hellman (ECDH) to derive a shared secret between - /// an ephemeral key generated on the fly and a server's public key provided in the input. - /// The derived key is optionally concatenated with an identifier (`idz`), hashed using SHA-256 - /// to generate an AES encryption key, and then used to encrypt the input data with AES-GCM. - /// - /// # Arguments - /// - /// * `data` - A byte slice representing the data to be encrypted. - /// * `server_public_raw_key_bytes` - A byte slice representing the server's public key in SEC1 format. - /// * `idz` - An optional byte slice identifier that, if provided, is appended to the shared secret before key derivation. - /// - /// # Returns - /// - /// This function returns a `Result` containing: - /// - `Ok(Vec)`: A vector of bytes containing the concatenation of the ephemeral public key and the encrypted data. - /// - `Err(KSMRError)`: An error if key derivation or encryption fails. - /// - /// # Errors - /// - /// * Returns an error if the server public key is invalid or encryption fails. - /// * If the `server_public_raw_key_bytes` cannot be parsed into a valid public key, it returns `"Invalid server public key!"`. - /// * If encryption fails during AES-GCM, the error message will indicate the failure. - /// - /// # Example - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// - /// // Data to encrypt - /// let data = b"Sensitive data to encrypt"; - /// - /// // A raw public key as a string (this is just an example key) - /// let server_public_key = "04d88c6fa31ea40af14c137b8e62f1151f1cc1e5688cad37b7f2e7"; - /// - /// // Convert the public key from hex string to bytes - /// let server_public_key_bytes = hex::decode(server_public_key).expect("Invalid hex key"); - /// - /// // Optional IDZ - /// let idz = Some("optional_identifier".as_bytes()); - /// - /// // Encrypt the data - /// match CryptoUtils::public_encrypt(data, &server_public_key_bytes, idz) { - /// Ok(encrypted_data) => println!("Encrypted data: {:?}", encrypted_data), - /// Err(e) => println!("Encryption failed: {}", e), - /// } - /// ``` - pub fn public_encrypt( - data: &[u8], - server_public_raw_key_bytes: &[u8], - idz: Option<&[u8]>, - ) -> Result, KSMRError> { - // Load the server public key from raw bytes - let server_public_key = PublicKey::from_sec1_bytes(server_public_raw_key_bytes) - .map_err(|_| KSMRError::CryptoError("Invalid server public key!".to_string()))?; - - // Generate a new ephemeral key - let ephemeral_key = EphemeralSecret::random(&mut OsRng); - - // Compute the shared key using ECDH (Diffie-Hellman) - let shared_key = ephemeral_key.diffie_hellman(&server_public_key); - - // If idz is provided, concatenate it with the shared secret - let mut derived_key = shared_key.raw_secret_bytes().to_vec(); - if let Some(idz_bytes) = idz { - derived_key.extend_from_slice(idz_bytes); - } - - // Hash the derived key to create a suitable AES key - let mut hasher = sha2::Sha256::new(); - hasher.update(&derived_key); - let enc_key = hasher.finalize().to_vec(); - - // Encrypt the data with AES-GCM - let encrypted_data = CryptoUtils::encrypt_aes_gcm(data, &enc_key, None) - .map_err(|e| KSMRError::CryptoError(format!("AES encryption failed: {}", e)))?; - - // Get the public key bytes from the ephemeral key - let eph_key_clone: p256::elliptic_curve::PublicKey = - ephemeral_key.public_key(); - - let binding_clone = EncodedPoint::from(eph_key_clone); - let eph_public_key_bytes: &[u8] = binding_clone.as_bytes(); - - // Combine the ephemeral public key and the encrypted data - let mut result = Vec::with_capacity(eph_public_key_bytes.len() + encrypted_data.len()); - result.extend_from_slice(eph_public_key_bytes.as_ref()); - result.extend_from_slice(&encrypted_data); - - Ok(result) - } - - /// Computes the SHA-256 hash of a Base64-encoded string. - /// - /// This function takes a Base64-encoded string, decodes it into bytes, - /// and computes its SHA-256 hash. The resulting hash is returned as a - /// vector of bytes. - /// - /// # Arguments - /// - /// * `value` - A string slice representing the Base64-encoded input to hash. - /// - /// # Returns - /// - /// This function returns a `Result`: - /// - `Ok(Vec)`: The SHA-256 hash of the decoded input as a vector of bytes. - /// - `Err(KSMRError)`: An error if the input string cannot be decoded from Base64 - /// or if any other error occurs during hashing. - /// - /// # Errors - /// - /// - Returns `KSMRError::CryptoError` if the input string cannot be decoded from Base64 - /// or if any other error occurs during hashing. - /// - /// # Examples - /// - /// ``` - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// let base64_input = "SGVsbG8sIHdvcmxkIQ=="; // Base64 encoding of "Hello, world!" - /// let hash = CryptoUtils::hash_of_string(base64_input); - /// match hash { - /// Ok(h) => println!("SHA-256 Hash: {:?}", h), - /// Err(e) => eprintln!("Error: {}", e), - /// } - /// ``` - /// - /// # Panics - /// - /// This function does not panic. It will return a `KSMRError` if the input is not valid Base64 - /// or if hashing fails. - pub fn hash_of_string(value: &str) -> Result, KSMRError> { - // Decode the Base64-encoded string into bytes - let value_bytes = URL_SAFE_NO_PAD - .decode(value) - .map_err(|e| KSMRError::CryptoError(format!("Base64 decoding failed: {}", e)))?; - - // Use sha2 crate for SHA-256 hashing - let mut hasher = sha2::Sha256::new(); - hasher.update(&value_bytes); - let hash_result = hasher.finalize(); - - Ok(hash_result.to_vec()) - } - - pub fn ecies_decrypt( - _server_public_key: &[u8], - _ciphertext: &[u8], - _priv_key_data: &[u8], - _id: &[u8], - ) -> Result, Box> { - unimplemented!("The hashing functionality is not yet implemented."); - } - - /// Decrypts a record using the provided secret key. - /// - /// This function attempts to decrypt a given record. If the record is a valid UTF-8 string, - /// it is first decoded from Base64. The decoded bytes are then decrypted using AES-GCM. - /// If the record is not a valid UTF-8 string, it is assumed to be in bytes and is decrypted - /// directly. - /// - /// # Arguments - /// - /// * `data` - A slice of bytes representing the encrypted record. This can either be a - /// Base64-encoded UTF-8 string or raw bytes. - /// * `secret_key` - A slice of bytes representing the secret key used for decryption. - /// - /// # Returns - /// - /// This function returns a `Result`. On success, it returns the - /// decrypted record as a UTF-8 string. On failure, it returns an error with a description - /// of the problem encountered. - /// - /// # Errors - /// - /// This function will return an error if: - /// * The input data cannot be decoded from Base64, returning a `KSMRError::CryptoError` with - /// the description `"Base64 decode error: {error}"`. - /// * The decryption process fails due to an incorrect key or other issues, returning a - /// `KSMRError::CryptoError` with a relevant message. - /// * The resulting decrypted bytes cannot be converted to a UTF-8 string, returning a - /// `KSMRError::Utf8Error` with a description of the error. - /// - /// # Examples - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; - /// let secret_key = CryptoUtils::generate_random_bytes(32); // Generate a dummy secret key - /// let original_data = b"Hello, World!"; - /// let encrypted_data = CryptoUtils::encrypt_aes_gcm(original_data, &secret_key, None).unwrap(); - /// let base64_encoded = URL_SAFE_NO_PAD.encode(&encrypted_data); - /// // Action - /// let result = CryptoUtils::decrypt_record(base64_encoded.as_bytes(), &secret_key); - /// // Assert - /// assert_eq!(result.unwrap(), "Hello, World!"); - /// - /// let result2 = CryptoUtils::decrypt_record(base64_encoded.as_bytes(), &secret_key); - /// match result2 { - /// Ok(record) => println!("Decrypted record: {}", record), - /// Err(e) => eprintln!("Failed to decrypt record: {}", e), - /// } - /// ``` - /// - /// # Panics - /// - /// This function does not panic under normal circumstances. It handles errors by returning a `KSMRError`. - pub fn decrypt_record(data: &[u8], secret_key: &[u8]) -> Result { - let decrypted_data = if let Ok(s) = std::str::from_utf8(data) { - // If the data is a valid UTF-8 string, decode from Base64 - let decoded_bytes = URL_SAFE_NO_PAD - .decode(s) - .map_err(|e| KSMRError::CryptoError(format!("Base64 decode error: {}", e)))?; - // Decrypt the decoded bytes - CryptoUtils::decrypt_aes(&decoded_bytes, secret_key) - .map_err(|e| KSMRError::CryptoError(format!("AES decryption error: {}", e)))? - } else { - // If the data is not a valid UTF-8 string, assume it's already in bytes - CryptoUtils::decrypt_aes(data, secret_key) - .map_err(|e| KSMRError::CryptoError(format!("AES decryption error: {}", e)))? - }; - - // Convert decrypted bytes to a UTF-8 string - let record_json = String::from_utf8(decrypted_data) - .map_err(|e| KSMRError::CryptoError(format!("UTF-8 conversion error: {}", e)))?; - Ok(record_json) - } - - pub fn decrypt_ec( - _ecc_private_key: &SecretKey, - _encrypted_data_bag: &[u8], - ) -> Result, Box> { - unimplemented!("The hashing functionality is not yet implemented."); - } - - /// Converts a Base64-encoded DER private key string to a `SecretKey`. - /// - /// This function takes a Base64-encoded DER representation of a private key, - /// decodes it, and converts it into a `SecretKey` type suitable for cryptographic - /// operations. - /// - /// # Arguments - /// - /// * `private_key_der_base64` - A string slice containing the Base64-encoded - /// DER representation of the private key. - /// - /// # Returns - /// - /// This function returns a `Result`. On success, - /// it returns the corresponding `SecretKey`. On failure, it returns an error with a - /// description of the problem encountered. - /// - /// # Errors - /// - /// This function will return an error if: - /// * The provided Base64 string cannot be decoded. The error will be wrapped in a `KSMRError::CryptoError`. - /// * The decoded bytes cannot be parsed into a `SecretKey`. The error will be wrapped in a `KSMRError::CryptoError`. - /// - /// # Examples - /// - /// ```rust - /// use keeper_secrets_manager_core::crypto::CryptoUtils; // Adjust the path as necessary - /// use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; - /// use p256::SecretKey; // Import the SecretKey type - /// - /// let private_key_der_base64 = "your_base64_encoded_der_key_here"; // Replace with your Base64 DER key - /// - /// // Attempt to convert the Base64 DER private key to a SecretKey - /// match CryptoUtils::der_base64_private_key_to_private_key(private_key_der_base64) { - /// Ok(secret_key) => println!("Successfully converted to SecretKey: {:?}", secret_key), - /// Err(e) => eprintln!("Failed to convert private key: {}", e), - /// } - /// ``` - /// - /// # Panics - /// - /// This function does not panic under normal circumstances, but it may return an - /// error if the input is invalid. - pub fn der_base64_private_key_to_private_key( - private_key_der_base64: &str, - ) -> Result< - SecretKey, - // EcKey , - KSMRError, - > { - use p256::pkcs8::DecodePrivateKey; - // Decode the Base64-encoded DER string - let private_key_der_bytes = utils::base64_to_bytes(private_key_der_base64)?; - - // Convert to SecretKey - let private_key = SecretKey::from_pkcs8_der(&private_key_der_bytes).map_err(|e| { - KSMRError::CryptoError(format!("Failed to convert DER to SecretKey: {}", e)) - })?; - - Ok(private_key) - } - - /// Extracts the public key bytes from a Base64-encoded DER private key string. - /// - /// This function takes a Base64-encoded DER representation of a private key, - /// decodes it, and extracts the corresponding public key bytes in uncompressed format. - /// - /// # Arguments - /// - /// * `private_key_der_base64` - A string slice containing the Base64-encoded DER private key. - /// - /// # Returns - /// - /// Returns a `Result, KSMRError>`. On success, it returns the public key bytes - /// in uncompressed format. On failure, it returns a `KSMRError` with a description of the problem. - /// - /// # Errors - /// - /// This function will return an error if: - /// * The provided Base64 string cannot be decoded. - /// * The decoded bytes cannot be parsed into a `SecretKey`. - /// - /// # Examples - /// - /// ``` - /// use keeper_secrets_manager_core::crypto::CryptoUtils; - /// use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; - /// - /// let private_key_der_base64 = "your_base64_encoded_der_key_here"; // Replace with your Base64 DER key - /// - /// // Attempt to extract public key bytes - /// match CryptoUtils::extract_public_key_bytes(private_key_der_base64) { - /// Ok(public_key_bytes) => println!("Successfully extracted public key bytes: {:?}", public_key_bytes), - /// Err(e) => eprintln!("Failed to extract public key bytes: {}", e), - /// } - /// ``` - /// - /// # Panics - /// - /// This function does not panic under normal circumstances, but it will return an error if - /// the Base64 string is invalid or the private key cannot be parsed. - pub fn extract_public_key_bytes(private_key_der_base64: &str) -> Result, KSMRError> { - // Decode the Base64-encoded DER string - let private_key_der_bytes = utils::base64_to_bytes(private_key_der_base64)?; - - // Convert to SecretKey - let private_key = SecretKey::from_pkcs8_der(&private_key_der_bytes) - .map_err(|e| KSMRError::CryptoError(format!("Failed to load private key: {}", e)))?; - - // Derive the public key from the private key - let public_key: VerifyingKey = private_key.public_key().into(); - - // Convert public key to bytes - let pub_key_bytes = public_key.to_encoded_point(false).as_ref().to_vec(); - - Ok(pub_key_bytes) - } - - /// Signs the provided data using the specified private key. - /// - /// This function takes a byte slice representing the data to be signed and a - /// `SecretKey`. It creates a signing key from the private key and uses it - /// to generate a digital signature for the data. - /// - /// # Arguments - /// - /// * `data` - A slice of bytes representing the data to be signed. - /// * `private_key` - A reference to the `SecretKey` used to sign the data. - /// - /// # Returns - /// - /// This function returns a `Result>`. On success, - /// it returns the generated `Signature`. On failure, it returns an error with a - /// description of the problem encountered. - /// - /// # Errors - /// - /// This function may return an error if: - /// * The signing process fails due to an invalid private key or other cryptographic issues. - /// - /// # Examples - /// - /// ``` - /// use keeper_secrets_manager_core::crypto::CryptoUtils; // Adjust the path as necessary - /// use p256::{SecretKey, ecdsa::{SigningKey, Signature}}; // Import necessary types - /// use rand::rngs::OsRng; // Use OS random number generator - /// - /// // Generate a dummy private key - /// let private_key = SecretKey::random(&mut OsRng); - /// let data = b"Hello, World!"; // Data to be signed - /// - /// // Attempt to sign the data - /// match CryptoUtils::sign_data(data, private_key) { - /// Ok(signature) => println!("Successfully signed the data: {:?}", signature), - /// Err(e) => eprintln!("Failed to sign the data: {}", e), - /// } - /// ``` - /// - /// # Panics - /// - /// This function does not panic under normal circumstances, but it may return an - /// error if the signing process encounters issues. - pub fn sign_data( - data: &[u8], - // private_key: EcKey - private_key: SecretKey, - ) -> Result< - // Signature, - ecdsa::der::Signature, - KSMRError, - > { - // Create a SigningKey from the SecretKey - let signing_key: ecdsa::SigningKey = SigningKey::from(private_key); - let signature: Signature = signing_key.sign(data); - Ok(signature.to_der()) - } - - pub fn validate_signature( - data: &[u8], // The original data that was signed - signature_bytes: &[u8], // The signature in DER format - public_key_bytes: &[u8], // The public key in uncompressed form - ) -> Result { - // Create a VerifyingKey from the public key bytes - let public_key = VerifyingKey::from_sec1_bytes(public_key_bytes).map_err(|err| { - KSMRError::CryptoError(format!( - "Failed to load public key from sec1 bytes: {}", - err - )) - })?; - - // Parse the signature from bytes - let signature = Signature::from_der(signature_bytes).map_err(|err| { - KSMRError::CryptoError(format!( - "Failed to parse signature from der while verification: {}", - err - )) - })?; - - // Verify the signature using the public key and data - public_key.verify(data, &signature).map_err(|err| { - KSMRError::CryptoError(format!("Failed to verify signature: {}", err)) - })?; - - // If verification passes, return true - Ok(true) - } -} diff --git a/sdk/rust/src/custom_error.rs b/sdk/rust/src/custom_error.rs deleted file mode 100644 index 37a2e4b28..000000000 --- a/sdk/rust/src/custom_error.rs +++ /dev/null @@ -1,185 +0,0 @@ -// -*- coding: utf-8 -*- -// _ __ -// | |/ /___ ___ _ __ ___ _ _ (R) -// | ' ) -> fmt::Result { - match self { - KSMRError::InvalidBase64 => write!(f, "Invalid Base64 encoding"), - KSMRError::DecodedBytesTooShort => write!(f, "Decoded byte array is too short"), - KSMRError::NotImplemented(msg) => write!(f, "Not implemented functionality: {}", msg), - KSMRError::InsufficientBytes(msg) => write!(f, "Insufficient bytes in input: {}", msg), - KSMRError::CacheSaveError(msg) => write!(f, "Save Error: {}", msg), - KSMRError::CacheRetrieveError(msg) => write!(f, "Retrieve Error: {}", msg), - KSMRError::CachePurgeError(msg) => write!(f, "Purge Error: {}", msg), - KSMRError::FileError(msg) => write!(f, "File Error: {}", msg), - KSMRError::SecretManagerCreationError(msg) => { - write!(f, "Secret manager creation Error: {}", msg) - } - KSMRError::PasswordCreationError(msg) => write!(f, "Password creation Error: {}", msg), - KSMRError::StorageError(msg) => write!(f, "Storage Error: {}", msg), - KSMRError::DirectoryCreationError(er, error) => { - write!(f, "Directory Creation failed: {}: {}", er, error) - } - KSMRError::FileCreationError(er, error) => { - write!(f, "File Creation failed: {}: {}", er, error) - } - KSMRError::FileWriteError(er, error) => { - write!(f, "File Write failed: {}: {}", er, error) - } - KSMRError::SerializationError(er) => { - write!(f, "JSON serialization/deserialization failed: {}", er) - } - KSMRError::DecodeError(er) => write!(f, "Decode Error: {}", er), - KSMRError::StringConversionError(er) => write!(f, "String Conversion Error: {}", er), - KSMRError::DataConversionError(er) => write!(f, "Data Conversion Error: {}", er), - KSMRError::CustomError(err) => write!(f, "{}", err), - KSMRError::CryptoError(msg) => write!(f, "Cryptography module Error: {}", msg), - KSMRError::InvalidLength(msg) => write!(f, "Invalid length: {}", msg), - KSMRError::RecordDataError(msg) => write!(f, "Record data error: {}", msg), - KSMRError::DeserializationError(msg) => write!(f, "Deserialization Error: {}", msg), - KSMRError::HTTPError(msg) => write!( - f, - "Error sending or receiving data from keeper servers. Exact message includes : {}", - msg - ), - KSMRError::InvalidPayloadError(msg) => { - write!(f, "payload doesn't belong to any of these types: {}", msg) - } - KSMRError::IOError(error) => { - write!(f, "IO Error: {}", error) - } - KSMRError::PathError(string) => { - write!(f, "Path Error: {}", string) - } - KSMRError::KeyNotFoundError(string) => { - write!(f, "Key not found: {}", string) - } - KSMRError::TOTPError(string) => write!(f, "TOTP Error: {}", string), - KSMRError::NotationError(string) => write!(f, "Notation Error: {}", string), - } - } -} - -impl PartialEq for KSMRError { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (KSMRError::InvalidBase64, KSMRError::InvalidBase64) => true, - (KSMRError::DecodedBytesTooShort, KSMRError::DecodedBytesTooShort) => true, - (KSMRError::InvalidLength(msg1), KSMRError::InvalidLength(msg2)) => msg1 == msg2, - (KSMRError::InsufficientBytes(msg1), KSMRError::InsufficientBytes(msg2)) => { - msg1 == msg2 - } - (KSMRError::CacheSaveError(msg1), KSMRError::CacheSaveError(msg2)) => msg1 == msg2, - (KSMRError::PasswordCreationError(msg1), KSMRError::PasswordCreationError(msg2)) => { - msg1 == msg2 - } - (KSMRError::CacheRetrieveError(msg1), KSMRError::CacheRetrieveError(msg2)) => { - msg1 == msg2 - } - (KSMRError::CachePurgeError(msg1), KSMRError::CachePurgeError(msg2)) => msg1 == msg2, - ( - KSMRError::SecretManagerCreationError(msg1), - KSMRError::SecretManagerCreationError(msg2), - ) => msg1 == msg2, - (KSMRError::KeyNotFoundError(msg1), KSMRError::KeyNotFoundError(msg2)) => msg1 == msg2, - (KSMRError::FileError(msg1), KSMRError::FileError(msg2)) => msg1 == msg2, - (KSMRError::StorageError(msg1), KSMRError::StorageError(msg2)) => msg1 == msg2, - ( - KSMRError::DirectoryCreationError(msg1, _), - KSMRError::DirectoryCreationError(msg2, _), - ) => msg1 == msg2, - (KSMRError::FileCreationError(msg1, _), KSMRError::FileCreationError(msg2, _)) => { - msg1 == msg2 - } - (KSMRError::PathError(msg1), KSMRError::PathError(msg2)) => msg1 == msg2, - (KSMRError::FileWriteError(msg1, _), KSMRError::FileWriteError(msg2, _)) => { - msg1 == msg2 - } - (KSMRError::SerializationError(msg1), KSMRError::SerializationError(msg2)) => { - msg1 == msg2 - } - (KSMRError::DeserializationError(msg1), KSMRError::DeserializationError(msg2)) => { - msg1 == msg2 - } - (KSMRError::DecodeError(msg1), KSMRError::DecodeError(msg2)) => msg1 == msg2, - (KSMRError::StringConversionError(msg1), KSMRError::StringConversionError(msg2)) => { - msg1 == msg2 - } - (KSMRError::CryptoError(msg1), KSMRError::CryptoError(msg2)) => msg1 == msg2, - (KSMRError::RecordDataError(msg1), KSMRError::RecordDataError(msg2)) => msg1 == msg2, - (KSMRError::DataConversionError(msg1), KSMRError::DataConversionError(msg2)) => { - msg1 == msg2 - } - (KSMRError::NotImplemented(_), KSMRError::NotImplemented(_)) => true, - (KSMRError::IOError(msg1), KSMRError::IOError(msg2)) => msg1 == msg2, - (KSMRError::TOTPError(msg1), KSMRError::TOTPError(msg2)) => msg1 == msg2, - (KSMRError::NotationError(msg1), KSMRError::NotationError(msg2)) => msg1 == msg2, - _ => false, - } - } -} - -impl From for KSMRError { - fn from(error: serde_json::Error) -> Self { - if error.is_data() { - KSMRError::DeserializationError(error.to_string()) - } else { - KSMRError::SerializationError(error.to_string()) - } - } -} - -impl From for KSMRError { - fn from(error: FromHexError) -> Self { - KSMRError::CryptoError(format!("Hex decode error: {}", error)) - } -} - -impl Error for KSMRError {} diff --git a/sdk/rust/src/dto/dtos.rs b/sdk/rust/src/dto/dtos.rs deleted file mode 100644 index 990ffcf11..000000000 --- a/sdk/rust/src/dto/dtos.rs +++ /dev/null @@ -1,1615 +0,0 @@ -// -*- coding: utf-8 -*- -// _ __ -// | |/ /___ ___ _ __ ___ _ _ (R) -// | ' , - pub uid: String, - pub title: String, - pub record_type: String, - pub files: Vec, - pub raw_json: String, - pub record_dict: HashMap, - pub password: Option, - pub revision: Option, - pub is_editable: bool, - pub folder_uid: String, - pub folder_key_bytes: Option>, - pub inner_folder_uid: Option, -} - -impl Record { - pub fn new( - record_dict: &HashMap, - secret_key: Vec, - folder_uid: Option, - ) -> Result { - let uid = record_dict - .get("recordUid") - .and_then(Value::as_str) - .unwrap_or_default() - .to_string(); - let folder_uid = folder_uid.unwrap_or_default(); - let mut record_key_bytes = Vec::new(); - secret_key.clone_into(&mut record_key_bytes); - let record_key_encrypted_bytes = record_dict - .get("recordKey") - .and_then(Value::as_str) - .map(|s| STANDARD.decode(s).unwrap_or_default()); - - if let Some(encrypted_bytes) = record_key_encrypted_bytes { - record_key_bytes = CryptoUtils::decrypt_aes(&encrypted_bytes, &secret_key).unwrap(); - } - - let record_encrypted_data_value = record_dict.get("data").and_then(Value::as_str); - - let raw_json = record_encrypted_data_value - .ok_or_else(|| KSMRError::DecodeError("cannot decrypt record".to_string()))?; - - let raw_json_string = CryptoUtils::decrypt_record(raw_json.as_bytes(), &record_key_bytes)?; - - let record_dict: HashMap = serde_json::from_str(&raw_json_string) - .map_err(|e| KSMRError::SerializationError(e.to_string()))?; - - // Title and type - let title = record_dict - .get("title") - .and_then(Value::as_str) - .map(String::from) - .unwrap_or_default(); - let record_type = record_dict - .get("type") - .and_then(Value::as_str) - .map(String::from) - .unwrap_or_default(); - - let revision = record_dict.get("revision").and_then(Value::as_i64); - let is_editable = record_dict - .get("isEditable") - .and_then(Value::as_bool) - .unwrap_or(false); - - let mut files = Vec::new(); - if let Some(file_list) = record_dict.get("files").and_then(Value::as_array) { - for file_data in file_list { - if let Some(file_map) = file_data.as_object() { - let file_map_hashmap: HashMap = file_map - .clone() - .into_iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect(); - - let created_keeper_file = - KeeperFile::new_from_json(file_map_hashmap, record_key_bytes.clone()); - match created_keeper_file { - Ok(file) => files.push(file), - Err(e) => { - let msg = format!("Error loading file: {}", e); - eprintln!("{}", msg); - } - } - } - } - } - - let password = if record_type == "login" { - record_dict - .get("fields") - .and_then(|fields| fields.as_array()) - .and_then(|fields| { - fields - .iter() - .find(|field| { - field.get("type") == Some(&Value::String("password".to_string())) - }) - .and_then(|field| field.get("value")) - .and_then(|value| value.as_array()) - .and_then(|arr| arr.first()) - .and_then(Value::as_str) - }) - .map(String::from) - } else { - None - }; - - Ok(Self { - uid, - title, - record_type, - files, - raw_json: raw_json_string, - record_dict: record_dict.clone(), - password, - revision, - is_editable, - folder_uid, - inner_folder_uid: record_dict - .get("innerFolderUid") - .and_then(Value::as_str) - .map(|s| s.to_string()), - record_key_bytes, - folder_key_bytes: None, - }) - } - - /// Finds a file by title within the Record's files. - pub fn find_file_by_title( - &mut self, - title: &str, - ) -> Result, KSMRError> { - Ok(self - .files - .iter_mut() - .find(|file: &&mut KeeperFile| file.title == *title)) - } - - pub fn update(&mut self) -> Result<(), KSMRError> { - // Update the title and type in the record_dict HashMap - self.record_dict - .insert("title".to_string(), Value::String(self.title.clone())); - self.record_dict.insert( - "record_type".to_string(), - Value::String(self.record_type.clone()), - ); - - // Find the password field in fields, and update the password attribute if it exists - if let Some(fields) = self - .record_dict - .get_mut("fields") - .and_then(|f| f.as_array_mut()) - { - if let Some(password_field) = fields - .iter_mut() - .find(|field| field.get("record_type").and_then(|t| t.as_str()) == Some("password")) - { - if let Some(values) = password_field.get("value").and_then(|v| v.as_array()) { - if let Some(Value::String(password)) = values.first() { - self.password = Some(password.clone()); - } - } - } - } - - self.raw_json = serde_json::to_string(&self.record_dict).map_err(|_| { - KSMRError::SerializationError("Failed to serialize record_dict".to_string()) - })?; - - Ok(()) - } - - pub fn _value(&self, values: Option>, single: bool) -> ValueResult { - if single { - let first_value = values - .and_then(|v| v.first().cloned()) - .map(|v| v.to_owned()); - ValueResult::Single(first_value) - } else { - let all_values = values - .map(|v| v.iter().map(|s| s.to_vec()).collect()) - .unwrap_or_default(); - ValueResult::Multiple(all_values) - } - } - - fn field_search( - mut fields: Vec>, - field_key: &str, - ) -> Option>> { - let mut fields_returned: Vec> = Vec::new(); - // Check for a matching "label" key first - for field in fields.clone().drain(..) { - if let Some(item_label) = field.get("label").and_then(Value::as_str) { - if item_label.eq(field_key) { - fields_returned.push(field.clone()); - } - } - } - if !fields_returned.is_empty() { - return Some(fields_returned.clone()); - } - // Search for a matching "type" key - for field in fields.drain(..) { - if let Some(item_type) = field.get("type").and_then(Value::as_str) { - if item_type.eq_ignore_ascii_case(field_key) { - fields_returned.push(field.clone()); - } - } - } - if !fields_returned.is_empty() { - return Some(fields_returned.clone()); - } - None - } - - // Retrieve a standard field by field type. - pub fn get_standard_field(&self, field_type: &str) -> Result, KSMRError> { - let fields_searched = self.standard_fields_searched_map(field_type)?; - - let mut fields_searched_map: Vec = Vec::new(); - for field_searched in fields_searched.clone() { - let field_searched_mapped: Value = field_searched - .get("value") //.into_iter().map(|field| field.get("value").cloned().unwrap_or(Value::Null)) - .ok_or_else(|| { - KSMRError::RecordDataError(format!("Field {} not found in record", field_type)) - })? - .clone(); - fields_searched_map.push(field_searched_mapped); - } - - Ok(fields_searched_map) - } - - pub fn standard_fields_searched_map( - &self, - field_type: &str, - ) -> Result>, KSMRError> { - let fields_2 = self.record_dict.get("fields"); - let fields = fields_2.and_then(Value::as_array).ok_or_else(|| { - KSMRError::RecordDataError(format!( - "Cannot find standard field {} in record", - field_type - )) - })?; - - // Parse each `Value` into its specific type - #[allow(clippy::unnecessary_filter_map)] - let fields_2: Vec> = fields - .iter() - .filter_map(|value| match value { - Value::Object(map) => Some(map.clone().into_iter().collect()), - Value::Array(arr) => Some( - arr.iter() - .enumerate() - .map(|(i, v)| (i.to_string(), v.clone())) - .collect(), - ), - Value::String(s) => Some( - [("string".to_string(), Value::String(s.clone()))] - .into_iter() - .collect(), - ), - Value::Number(num) => Some( - [("number".to_string(), Value::Number(num.clone()))] - .into_iter() - .collect(), - ), - Value::Bool(b) => Some( - [("bool".to_string(), Value::Bool(*b))] - .into_iter() - .collect(), - ), - Value::Null => Some(HashMap::new()), - }) - .collect(); - - let fields_searched: Vec> = - match Self::field_search(fields_2, field_type) { - Some(field) => field, - None => { - return Err(KSMRError::RecordDataError(format!( - "Field {} not found in record", - field_type - ))) - } - }; - Ok(fields_searched) - } - // Retrieve the standard field value by type, either as a single value or an array of values. - pub fn get_standard_field_value( - &self, - field_type: &str, - single: bool, - ) -> Result { - let fields = self.get_standard_field(field_type)?; - let mut arrayed_values: Vec> = Vec::new(); - - for field in fields { - let arrayed_val = field.as_array().cloned().unwrap(); - arrayed_values.push(arrayed_val); - } - - if arrayed_values.is_empty() || arrayed_values[0].is_empty() { - return Err(KSMRError::RecordDataError(format!( - "No standard field with field type: {} exists on record: {}", - field_type, self.title - ))); - } - - // Use `_value` to return a single value or an array, based on `single` parameter - match self._value( - Some(arrayed_values.iter().map(|v| v.as_slice()).collect()), - single, - ) { - ValueResult::Single(Some(value)) => Ok(value[0].clone()), - ValueResult::Single(None) => Ok(Value::Null), - ValueResult::Multiple(values) => { - // Flatten the 2D array to 1D - let flat: Vec = values.into_iter().flatten().collect(); - Ok(Value::Array(flat)) - } - } - } - - pub fn get_standard_field_mut(&mut self, field_type: &str) -> Result<&mut Value, KSMRError> { - // Get mutable reference to "fields" - let fields = self - .record_dict - .get_mut("fields") - .and_then(Value::as_array_mut) - .ok_or_else(|| { - KSMRError::RecordDataError(format!( - "Cannot find standard field {} in record", - field_type - )) - })?; - - // Find the field by "label" or "type" - let retrieved_field = fields - .iter_mut() - .find(|field| { - field.get("label").and_then(Value::as_str) == Some(field_type) - || field - .get("type") - .and_then(Value::as_str) - .map(|t| t.eq_ignore_ascii_case(field_type)) - .unwrap_or(false) - }) - .ok_or_else(|| { - KSMRError::RecordDataError(format!("Field {} not found in record", field_type)) - }); - - retrieved_field - } - - /// Set a standard field's value - pub fn set_standard_field_value_mut( - &mut self, - field_type: &str, - value: Value, - ) -> Result<(), KSMRError> { - // Get a mutable reference to the field - let field = self.get_standard_field_mut(field_type)?; - - // Ensure the field is an object and update the "value" key - let field_obj = field.as_object_mut().ok_or_else(|| { - KSMRError::RecordDataError(format!( - "Expected an object for standard field {} in record", - field_type - )) - })?; - - match value.is_array() { - true => { - field_obj.insert("value".to_string(), value); - } - false => { - field_obj.insert("value".to_string(), [value].into()); - } - } - // Update the "value" field - self.update()?; - Ok(()) - } - - // Retrieve a custom field by field type. - pub fn get_custom_field(&self, field_type: &str) -> Result, KSMRError> { - let fields_2 = self.record_dict.get("custom"); - - let fields = fields_2.and_then(Value::as_array).ok_or_else(|| { - KSMRError::RecordDataError(format!("Cannot find custom field {} in record", field_type)) - })?; - - // Parse each `Value` into its specific type - #[allow(clippy::unnecessary_filter_map)] - let fields_2: Vec> = fields - .iter() - .filter_map(|value| match value { - Value::Object(map) => Some(map.clone().into_iter().collect()), - Value::Array(arr) => Some( - arr.iter() - .enumerate() - .map(|(i, v)| (i.to_string(), v.clone())) - .collect(), - ), - Value::String(s) => Some( - [("string".to_string(), Value::String(s.clone()))] - .into_iter() - .collect(), - ), - Value::Number(num) => Some( - [("number".to_string(), Value::Number(num.clone()))] - .into_iter() - .collect(), - ), - Value::Bool(b) => Some( - [("bool".to_string(), Value::Bool(*b))] - .into_iter() - .collect(), - ), - Value::Null => Some(HashMap::new()), - }) - .collect(); - - let fields_searched = match Self::field_search(fields_2, field_type) { - Some(field) => field, - None => { - return Err(KSMRError::RecordDataError(format!( - "Field {} not found in record", - field_type - ))) - } - }; - - let mut fields_searched_map: Vec = Vec::new(); - for field_searched in fields_searched.clone() { - let field_searched_mapped: Value = field_searched - .get("value") - .ok_or_else(|| { - KSMRError::RecordDataError(format!("Field {} not found in record", field_type)) - })? - .clone(); - fields_searched_map.push(field_searched_mapped); - } - - Ok(fields_searched_map) - } - - // Retrieve the custom field value by type, either as a single value or an array of values. - pub fn get_custom_field_value( - &self, - field_type: &str, - single: bool, - ) -> Result { - let fields = self.get_custom_field(field_type)?; - let mut arrayed_values: Vec> = Vec::new(); - - for field in fields { - let arrayed_val = field.as_array().cloned().unwrap(); - arrayed_values.push(arrayed_val); - } - - // Use `_value` to return a single value or an array, based on `single` parameter - match self._value( - Some(arrayed_values.iter().map(|v| v.as_slice()).collect()), - single, - ) { - ValueResult::Single(Some(value)) => Ok(value[0].clone()), - ValueResult::Single(None) => Ok(Value::Null), - ValueResult::Multiple(values) => { - // Flatten the 2D array to 1D - let flat: Vec = values.into_iter().flatten().collect(); - Ok(Value::Array(flat)) - } - } - } - - pub fn get_custom_field_mut(&mut self, field_type: &str) -> Result<&mut Value, KSMRError> { - // Get mutable reference to "fields" - let fields = self - .record_dict - .get_mut("custom") - .and_then(Value::as_array_mut) - .ok_or_else(|| { - KSMRError::RecordDataError(format!( - "Cannot find standard field {} in record", - field_type - )) - })?; - - // Find the field by "label" or "type" - let retrieved_field = fields - .iter_mut() - .find(|field| { - field.get("label").and_then(Value::as_str) == Some(field_type) - || field - .get("type") - .and_then(Value::as_str) - .map(|t| t.eq_ignore_ascii_case(field_type)) - .unwrap_or(false) - }) - .ok_or_else(|| { - KSMRError::RecordDataError(format!("Field {} not found in record", field_type)) - }); - - retrieved_field - } - - /// Set a standard field's value - pub fn set_custom_field_value_mut( - &mut self, - field_type: &str, - value: Value, - ) -> Result<(), KSMRError> { - // Get a mutable reference to the field - let field = self.get_custom_field_mut(field_type)?; - - // Ensure the field is an object and update the "value" key - let field_obj = field.as_object_mut().ok_or_else(|| { - KSMRError::RecordDataError(format!( - "Expected an object for standard field {} in record", - field_type - )) - })?; - - // Update the "value" field - field_obj.insert("value".to_string(), [value].into()); - self.update()?; - Ok(()) - } - - pub fn new_from_json( - record_dict: HashMap, - secret_key: &[u8], - folder_uid: Option, - ) -> Result { - let mut record = Record::default(); - - // Record Key - if let Some(record_key_str) = record_dict - .get("recordKey") - .and_then(|v| v.as_str()) - .map(|s| s.trim()) - { - if !record_key_str.is_empty() { - let record_key_encrypted = utils::base64_to_bytes(record_key_str)?; - match CryptoUtils::decrypt_aes(&record_key_encrypted, secret_key) { - Ok(record_key_bytes) => { - record.record_key_bytes = record_key_bytes; - } - Err(err) => { - error!( - "Error decrypting record key: {} - Record UID: {}", - err, record.uid - ); - } - } - } - } else { - // Single Record Share - record.record_key_bytes = secret_key.to_vec(); - } - - let mut decrypted_data = HashMap::new(); - // Encrypted Record Data - if let Some(record_data_str) = record_dict.get("data").and_then(|v| v.as_str()) { - if !record.record_key_bytes.is_empty() { - let record_encrypted_data = utils::base64_to_bytes(record_data_str)?; - match CryptoUtils::decrypt_record(&record_encrypted_data, &record.record_key_bytes) - { - Ok(record_data_json) => { - record.raw_json = record_data_json.clone(); - record.record_dict = json_to_dict(&record_data_json).unwrap(); - decrypted_data = json_to_dict(&record_data_json).unwrap(); - } - Err(err) => { - error!("Error decrypting record data: {}", err); - } - } - } - } - - if !decrypted_data.is_empty() { - // Record Title - if let Some(title) = decrypted_data.get("title").and_then(|v| v.as_str()) { - record.title = title.trim().to_string(); - } - } - - // Record Type - if let Some(record_type) = record.record_dict.get("type").and_then(|v| v.as_str()) { - record.record_type = record_type.to_string(); - let password = if record_type == "login" { - record_dict - .get("fields") - .and_then(|fields| fields.as_array()) - .and_then(|fields| { - fields - .iter() - .find(|field| { - field.get("type") == Some(&Value::String("password".to_string())) - }) - .and_then(|field| field.get("value")) - .and_then(|value| value.as_array()) - .and_then(|arr| arr.first()) - .and_then(Value::as_str) - }) - .map(String::from) - } else { - None - }; - match password { - Some(pass) => record.password = Some(pass), - None => record.password = None, - } - } - - if let Some(uid) = folder_uid { - if !uid.trim().is_empty() { - record.folder_uid = uid.clone(); - record.folder_key_bytes = Some(secret_key.to_vec()); - } - } - - // Record UID - if let Some(uid) = record_dict.get("recordUid").and_then(|v| v.as_str()) { - record.uid = uid.trim().to_string(); - } - - // Inner Folder UID - if let Some(inner_folder_uid) = record_dict.get("innerFolderUid").and_then(|v| v.as_str()) { - record.inner_folder_uid = Some(inner_folder_uid.trim().to_string()); - } - - // Revision - if let Some(revision) = record_dict.get("revision").and_then(|v| v.as_f64()) { - record.revision = Some(revision as i64); - } - - // Is Editable - if let Some(is_editable) = record_dict.get("isEditable").and_then(|v| v.as_bool()) { - record.is_editable = is_editable; - } - let mut _files = Vec::new(); - if let Some(file_list) = record_dict.get("files").and_then(Value::as_array) { - for file_data in file_list { - if let Some(file_map) = file_data.as_object() { - let file_map_hashmap: HashMap = file_map - .clone() - .into_iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect(); - - let created_keeper_file = KeeperFile::new_from_json( - file_map_hashmap, - record.record_key_bytes.to_vec(), - ); - match created_keeper_file { - Ok(file) => _files.push(file), - Err(e) => { - let msg = format!("Error loading file: {}", e); - eprintln!("{}", msg); - } - } - } - } - record.files = _files; - } - - Ok(record) - } - - pub fn field_exists(&self, section: &str, name: &str) -> bool { - // Check if the section is valid - if section != "fields" && section != "custom" { - return false; - } - - // Retrieve the section from the record dictionary - let section_data = self.record_dict.get(section); - if section_data.is_none() { - return false; - } - - // Ensure the section is an array - let arr = section_data.unwrap(); - let section_array = match arr.is_array() { - true => arr.as_array().unwrap(), - false => return false, - }; - - // Iterate through the array and check for the field - for item in section_array { - let item_obj = match item.is_object() { - true => item.as_object().unwrap(), - false => return false, - }; - let item_type = match item_obj.get("type") { - Some(t) => t.as_str().unwrap(), - None => return false, - }; - if item_type == name { - let item_val = item_obj.get("value"); - match item_val { - Some(item_of_value) => match item_of_value.is_array() { - true => match item_of_value.as_array() { - Some(arr) => match arr.len() { - 0 => return false, - _ => return true, - }, - None => return false, - }, - false => return false, - }, - None => return false, - } - } - } - false - } - - pub fn insert_field( - &mut self, - section: &str, - field: HashMap, - ) -> Result<(), KSMRError> { - // Validate section - if section != "fields" && section != "custom" { - return Err(KSMRError::RecordDataError(format!( - "Unknown field section '{}'", - section - ))); - } - - // Ensure the section exists and is initialized - let section_fields = self - .record_dict - .entry(section.to_string()) - .or_insert_with(|| serde_json::Value::Array(Vec::new())); - - // Add the field - if let Some(arr) = section_fields.as_array_mut() { - arr.push(serde_json::to_value(&field).unwrap()); - } else { - // Handle the case where section_fields is not an array - return Err(KSMRError::RecordDataError(format!( - "Section '{}' is not an array", - section - ))); - } - Ok(()) - } - - pub fn print(&self) { - println!("==="); - println!("Title: {}", self.title); - println!("UID: {}", self.uid); - println!("Type: {}", self.record_type); - println!(); - - println!("Fields"); - println!("------"); - - if let Some(fields) = self.record_dict.get("fields").and_then(|v| v.as_array()) { - for field in fields { - if let (Some(field_type), Some(values)) = ( - field.get("type").and_then(|v| v.as_str()), - field.get("value").and_then(|v| v.as_array()), - ) { - if field_type != "fileRef" && field_type != "oneTimeCode" { - let value_str: Vec<_> = values - .iter() - .map(Record::extract_strings) - .map(|v| v.join(",")) - .collect(); - println!("{} : {}", field_type, value_str.join(", ")); - } - } - } - } - - println!(); - println!("Custom Fields"); - println!("------"); - - if let Some(custom_fields) = self.record_dict.get("custom").and_then(|v| v.as_array()) { - for field in custom_fields { - if let (Some(label), Some(field_type), Some(values)) = ( - field.get("label").and_then(|v| v.as_str()), - field.get("type").and_then(|v| v.as_str()), - field.get("value").and_then(|v| v.as_array()), - ) { - let value_str: Vec<_> = values.iter().filter_map(|v| v.as_str()).collect(); - println!("{} ({}) : {}", label, field_type, value_str.join(", ")); - } - } - } - } - - fn extract_strings(value: &Value) -> Vec { - let mut results = Vec::new(); - - match value { - Value::String(s) => results.push(s.clone()), // Collect strings - Value::Number(s) => results.push(s.to_string().clone()), - Value::Array(arr) => { - for item in arr { - results.extend(Self::extract_strings(item)); // Recurse for arrays - } - } - Value::Object(map) => { - for val in map.values() { - results.extend(Self::extract_strings(val)); // Recurse for map values - } - } - _ => {} // Ignore other types - } - - results - } - - pub fn find_file_by_filename( - &mut self, - filename: &str, - ) -> Result, KSMRError> { - Ok(self.files.iter_mut().find(|file| file.name == filename)) - } - - pub fn find_file(&mut self, name: &str) -> Result, KSMRError> { - Ok(self - .files - .iter_mut() - .find(|file| file.uid == name || file.name == name || file.title == name)) - } - - pub fn find_files(&mut self, name: &str) -> Vec<&mut KeeperFile> { - self.files - .iter_mut() - .filter(|file| file.uid == name || file.name == name || file.title == name) - .collect() - } - - pub fn download_file_by_title(&mut self, title: &str, path: &str) -> Result { - let found_file = self.find_file_by_title(title)?; - - match found_file { - Some(file) => { - let file_status = file.save_file(path.to_string(), false)?; - Ok(file_status) - } - None => Err(KSMRError::FileError(format!( - "File with title {} not found", - title - ))), - } - } - - pub fn download_file(&mut self, uid: &str, path: &str) -> Result { - let found_file = self.find_file(uid)?; - - match found_file { - Some(file) => { - let file_status = file.save_file(path.to_string(), false)?; - Ok(file_status) - } - None => { - info!( - "File with name/uid {} not found in record with uid {}", - uid, self.uid - ); - Ok(false) - } - } - } -} - -impl fmt::Display for Record { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "[Record: uid={}, type={:?}, title={:?}, files count={}]", - self.uid, - self.record_type, - self.title, - self.files.len() - ) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct KeeperFile { - // Define the fields of KeeperFile here - file_key: String, - pub metadata_dict: HashMap, - - data: Vec, - - pub uid: String, - pub file_type: String, - pub title: String, - pub name: String, - last_modified: i64, - size: i64, - - f: HashMap, - record_key_bytes: Vec, -} - -#[allow(clippy::inherent_to_string)] -impl KeeperFile { - pub fn deep_copy(&self) -> KeeperFile { - KeeperFile { - file_key: self.file_key.clone(), - metadata_dict: self.metadata_dict.clone(), - data: self.data.clone(), - uid: self.uid.clone(), - file_type: self.file_type.clone(), - title: self.title.clone(), - name: self.name.clone(), - last_modified: self.last_modified, - size: self.size, - f: self.f.clone(), - record_key_bytes: self.record_key_bytes.clone(), - } - } - - /// Decrypts the file key using the record key bytes. - pub fn decrypt_file_key(&self) -> Result, KSMRError> { - // Retrieve the Base64-encoded file key from metadata - let file_key_encrypted_base64 = self - .f - .get("fileKey") - .ok_or_else(|| { - KSMRError::KeyNotFoundError("fileKey not found in metadata".to_string()) - })? - .as_str() - .ok_or_else(|| KSMRError::DecodeError("fileKey is not a string".to_string()))?; - - // Decode the Base64-encoded string - let file_key_encrypted = utils::base64_to_bytes(file_key_encrypted_base64)?; - - // Decrypt the file key using AES - CryptoUtils::decrypt_aes(&file_key_encrypted, &self.record_key_bytes).map_err(|e| { - log::error!( - "Error decrypting file key: {}, error: {}", - file_key_encrypted_base64, - e - ); - KSMRError::CryptoError(format!("Failed to decrypt file key: {}", e)) - }) - } - - pub fn get_meta(&mut self) -> Result, KSMRError> { - // If metadata is already populated, return it - if !self.metadata_dict.is_empty() { - return Ok(self.metadata_dict.clone()); - } - - // Retrieve the Base64-encoded file metadata - let data_str = self - .f - .get("data") - .and_then(|data| data.as_str()) - .ok_or_else(|| { - KSMRError::KeyNotFoundError("Missing 'data' field in metadata".to_string()) - })?; - - // Decrypt the file key - let file_key = self.decrypt_file_key()?; - - // Decode the Base64-encoded metadata - let data_bytes = utils::base64_to_bytes(data_str)?; - - // Decrypt the metadata - let decrypted_meta = CryptoUtils::decrypt_aes(&data_bytes, &file_key) - .map_err(|e| KSMRError::CryptoError(format!("Failed to decrypt metadata: {}", e)))?; - - // Convert decrypted metadata into a UTF-8 string - let meta_json = utils::bytes_to_string(&decrypted_meta)?; - - // Parse the JSON string into a HashMap - self.metadata_dict = json_to_dict(&meta_json).unwrap_or_default(); - - Ok(self.metadata_dict.clone()) - } - - /// Returns the decrypted raw file data. - pub fn get_file_data(&mut self) -> Result>, KSMRError> { - // Return cached data if it exists - if !self.data.is_empty() { - return Ok(Some(self.data.clone())); - } - - // Decrypt the file key - let file_key = self.decrypt_file_key()?; - - // Get the file URL - let file_url = self - .get_url() - .map_err(|_| KSMRError::FileError("File URL is invalid".to_string()))?; - - // Fetch the file data from the URL - let mut response = get(&file_url) - .map_err(|e| KSMRError::FileError(format!("Failed to fetch file: {}", e)))?; - - // Ensure the HTTP request was successful - if !response.status().is_success() { - return Err(KSMRError::HTTPError(format!( - "HTTP request failed with status: {}", - response.status() - ))); - } - - // Read the response body - let mut encrypted_data = Vec::new(); - response - .read_to_end(&mut encrypted_data) - .map_err(|e| KSMRError::IOError(format!("Failed to read response body: {}", e)))?; - - // Decrypt the file data - let decrypted_data = CryptoUtils::decrypt_aes(&encrypted_data, &file_key) - .map_err(|e| KSMRError::CryptoError(format!("Failed to decrypt file: {}", e)))?; - - // Cache the decrypted data - self.data = decrypted_data.clone(); - - Ok(Some(decrypted_data)) - } - - /// Retrieves the URL from the `f` HashMap, if available. - pub fn get_url(&self) -> Result { - let file_url = self - .f - .get("url") // Look for the "url" key in the HashMap - .and_then(|value| value.as_str()) // Ensure the value is a string - .unwrap_or_default() // Return the string if found, or an empty string if not - .to_string(); // Convert to a String - Ok(file_url) - } - - pub fn new_from_json( - file_dict: HashMap, - record_key_bytes: Vec, - ) -> Result { - let mut file = KeeperFile { - file_key: String::new(), - metadata_dict: HashMap::new(), - data: vec![], - uid: String::new(), - file_type: String::new(), - title: String::new(), - name: String::new(), - last_modified: 0, - size: 0, - f: file_dict.clone(), - record_key_bytes, - }; - - // Extract metadata if present - let meta = file.get_meta()?; - if let Some(file_uid) = file_dict.get("fileUid").and_then(|v| v.as_str()) { - file.uid = file_uid.to_string(); - } - if let Some(file_type) = meta.get("type").and_then(|v| v.as_str()) { - file.file_type = file_type.to_string(); - } - if let Some(title) = meta.get("title").and_then(|v| v.as_str()) { - file.title = title.to_string(); - } - if let Some(name) = meta.get("name").and_then(|v| v.as_str()) { - file.name = name.to_string(); - } - if let Some(last_modified) = meta.get("lastModified").and_then(|v| v.as_f64()) { - file.last_modified = last_modified as i64; - } - if let Some(size) = meta.get("size").and_then(|v| v.as_f64()) { - file.size = size as i64; - } - - Ok(file) - } - - pub fn save_file(&mut self, path: String, create_folders: bool) -> Result { - // Resolve the absolute path - let abs_path = match fs::canonicalize(&path) { - Ok(p) => p, - Err(_) => PathBuf::from(&path), // Fallback to given path if canonicalization fails - }; - - // Get the parent directory - let dir_path = abs_path.parent().ok_or_else(|| { - KSMRError::PathError(format!( - "Failed to determine parent directory for path: {}", - path - )) - })?; - - // Create folders if needed - if create_folders { - if let Err(err) = fs::create_dir_all(dir_path) { - error!("Error creating folders: {}", err); - return Err(KSMRError::IOError(format!( - "Failed to create directories: {}", - err - ))); - } - } - - // Verify that the directory exists - if !dir_path.exists() { - return Err(KSMRError::PathError(format!( - "Directory does not exist: {}", - dir_path.display() - ))); - } - - // Write the file data - let _download_file_data = self.get_file_data()?; - - let mut file = File::create(&abs_path).map_err(|err| { - KSMRError::IOError(format!( - "Failed to create file {}: {}", - abs_path.display(), - err - )) - })?; - file.write_all(&self.data).map_err(|err| { - KSMRError::IOError(format!( - "Failed to write to file {}: {}", - abs_path.display(), - err - )) - })?; - - Ok(true) - } - - pub fn to_string(&self) -> String { - format!("[KeeperFile - name: {}, title: {}]", self.name, self.title) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct KeeperFolder { - pub folder_key: Vec, - pub folder_uid: String, - pub parent_uid: String, - pub name: String, -} - -impl KeeperFolder { - pub fn new( - folder_map: &HashMap, - folder_key: Vec, - ) -> Result { - let mut folder = KeeperFolder { - folder_key, - folder_uid: String::new(), - parent_uid: String::new(), - name: String::new(), - }; - - if let Some(serde_json::Value::String(val)) = folder_map.get("folderUid") { - folder.folder_uid = val.clone(); - } - - if let Some(serde_json::Value::String(val)) = folder_map.get("parent") { - folder.parent_uid = val.clone(); - } - - if let Some(serde_json::Value::String(val)) = folder_map.get("data") { - let data = match CryptoUtils::url_safe_str_to_bytes(val) { - Ok(data) => data, - Err(e) => { - if e.to_string().contains("Invalid padding") { - CryptoUtils::url_safe_str_to_bytes_trim_padding(val)? - } else { - return Err(e); - } - } - }; - if let Ok(decrypted_data) = CryptoUtils::decrypt_aes_cbc(&data, &folder.folder_key) { - #[derive(Deserialize)] - struct FolderName { - name: String, - } - let decrypted_data_unpadded = unpad_data(decrypted_data.as_slice()).unwrap(); - if let Ok(folder_name) = - serde_json::from_slice::(&decrypted_data_unpadded) - { - folder.name = folder_name.name; - } else { - error!("Error parsing folder name from decrypted data"); - } - } - } - Ok(folder) - } - - pub fn to_serialized_string(&self) -> String { - let mut clone: HashMap = HashMap::new(); - clone.insert( - "folderKey".to_string(), - Value::String(hex::encode(self.folder_key.clone())), - ); - clone.insert( - ("folderUid").to_string(), - Value::String(self.folder_uid.clone()), - ); - clone.insert( - "parentUid".to_string(), - Value::String(self.parent_uid.clone()), - ); - clone.insert("name".to_string(), Value::String(self.name.clone())); - serde_json::to_string_pretty(&clone).unwrap_or_else(|_| "Failed to serialize".to_string()) - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Folder { - key: Vec, - pub uid: String, - parent_uid: String, - name: String, - data: HashMap, - folder_records: Vec>, -} - -impl Folder { - pub fn new_from_json(folder_dict: HashMap, secret_key: &[u8]) -> Option { - let mut folder = Folder { - key: vec![], - uid: String::new(), - parent_uid: String::new(), - name: String::new(), - data: folder_dict.clone(), - folder_records: vec![], - }; - - if let Some(Value::String(uid)) = folder_dict.get("folderUid") { - folder.uid = uid.trim().to_string(); - - if let Some(Value::String(folder_key_enc)) = folder_dict.get("folderKey") { - let folder_key_bytes = utils::base64_to_bytes(folder_key_enc).unwrap(); - match CryptoUtils::decrypt_aes(&folder_key_bytes, secret_key) { - Ok(folder_key) => { - folder.key = folder_key; - - if let Some(Value::Array(records)) = folder_dict.get("records") { - for record in records { - if let Some(record_map) = record.as_object() { - folder.folder_records.push( - record_map - .clone() - .into_iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect(), - ); - } else { - log::error!("Folder records JSON is in incorrect format"); - } - } - } - } - Err(err) => { - log::error!("Error decrypting folder key: {:?}", err); - } - } - } - } else { - log::error!("Not a folder"); - return None; - } - - Some(folder) - } - - pub fn get_folder_key(&self) -> Vec { - self.key.clone() - } - - pub fn records(&self) -> Result, KSMRError> { - let mut records = vec![]; - for record_map in &self.folder_records { - let record_result = - Record::new_from_json(record_map.clone(), &self.key, Some(self.uid.to_string())); - - // if record_result.is_err() { - // log::error!("Error parsing folder record: {:?}", record_map); - // } else { - // records.push(record_result.unwrap()); - // } - - if let Ok(record) = record_result { - records.push(record); - } else { - log::error!("Error parsing folder record: {:?}", record_map); - } - } - Ok(records) - } -} - -#[derive(Serialize, Deserialize, Debug, Default)] -pub struct AppData { - title: Option, - app_type: Option, -} - -impl AppData { - pub fn new(title: Option, app_type: Option) -> Self { - AppData { title, app_type } - } -} - -#[derive(Serialize, Deserialize, Debug, Default)] -pub struct SecretsManagerResponse { - pub app_data: AppData, - pub folders: Vec, - pub records: Vec, - pub expires_on: i64, - pub warnings: Option, - pub just_bound: bool, -} - -impl SecretsManagerResponse { - pub fn expires_on_str(&self, date_format: Option<&str>) -> String { - let unix_time_seconds = self.expires_on / 1000; - let naive_datetime = - DateTime::from_timestamp(unix_time_seconds.saturating_sub(i64::MIN), 0) - .unwrap_or_else(|| DateTime::from_timestamp(0, 0).unwrap()); // Handle invalid timestamps gracefully - let format = date_format.unwrap_or("%Y-%m-%d %H:%M:%S"); - naive_datetime.format(format).to_string() - } - - pub fn new() -> Self { - SecretsManagerResponse { - app_data: AppData::default(), - folders: Vec::new(), - records: Vec::new(), - expires_on: 0, - warnings: None, - just_bound: false, - } - } -} - -pub struct KeeperFileUpload { - pub name: String, - pub data: Vec, - pub title: String, - pub mime_type: String, -} - -impl KeeperFileUpload { - pub fn get_file_for_upload( - file_path: &str, - file_name: Option<&str>, - file_title: Option<&str>, - mime_type: Option<&str>, - ) -> Result { - // Resolve file name - let resolved_name = file_name - .unwrap_or_else(|| { - Path::new(file_path) - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or("") - }) - .to_string(); - - // Resolve file title - let resolved_title = file_title.unwrap_or(resolved_name.as_str()).to_string(); - - // Resolve MIME type - let resolved_type = mime_type.unwrap_or("application/octet-stream").to_string(); - - // Read file data - let file_data = fs::read(file_path) - .map_err(|err| KSMRError::IOError(format!("Error reading file data: {}", err)))?; - - // Return KeeperFileUpload instance - Ok(KeeperFileUpload { - name: resolved_name, - title: resolved_title, - mime_type: resolved_type, - data: file_data, - }) - } -} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct RecordCreate { - pub record_type: String, - pub title: String, - pub notes: Option, - pub fields: Option>, - pub custom: Option>, -} - -pub const VALID_RECORD_FIELDS: [&str; 45] = [ - "accountNumber", - "address", - "addressRef", - "appFiller", - "bankAccount", - "birthDate", - "cardRef", - "checkbox", - "databaseType", - "date", - "directoryType", - "dropdown", - "email", - "birthDate", - "expirationDate", - "fileRef", - "host", - "isSSIDHidden", - "keyPair", - "licenseNumber", - "login", - "multiline", - "name", - "note", - "oneTimeCode", - "otp", - "pamHostname", - "pamRemoteBrowserSettings", - "pamResources", - "pamSettings", - "passkey", - "password", - "paymentCard", - "phone", - "pinCode", - "rbiUrl", - "recordRef", - "schedule", - "script", - "secret", - "securityQuestion", - "text", - "trafficEncryptionSeed", - "url", - "wifiEncryption", -]; - -impl RecordCreate { - pub fn new(record_type: String, title: String, notes: Option) -> Self { - Self { - record_type, - title, - notes, - fields: None, - custom: None, - } - } - - pub fn validate(&self) -> Result<(), KSMRError> { - // Validate title - if self.title.trim().is_empty() { - return Err(KSMRError::RecordDataError( - "Record title should not be empty.".to_string(), - )); - } - - // Validate notes - if let Some(notes) = &self.notes { - if notes.trim().is_empty() { - return Err(KSMRError::RecordDataError( - "Record notes should not be empty.".to_string(), - )); - } - } - - // Validate fields - if let Some(fields) = &self.fields { - let mut field_type_errors = vec![]; - let mut field_value_errors = vec![]; - - for field in fields { - // Validate field type - if !VALID_RECORD_FIELDS.contains(&field.field_type.as_str()) { - field_type_errors.push(field.field_type.clone()); - } - - // Validate field value - match field.value.is_array() { - true => { - if field.value.as_array().unwrap().is_empty() { - field_value_errors.push(field.field_type.clone()); - } - } - false => { - return Err(KSMRError::RecordDataError( - "Field value is not Array".to_string(), - )) - } - }; - } - - if !field_type_errors.is_empty() { - return Err(KSMRError::RecordDataError(format!( - "Following field types are not allowed: [{}]. Allowed field types are: [{}]", - field_type_errors.join(", "), - VALID_RECORD_FIELDS.join(", ") - ))); - } - - if !field_value_errors.is_empty() { - return Err(KSMRError::RecordDataError(format!( - "Fields with the following types should have non-empty list values: [{}]", - field_value_errors.join(", ") - ))); - } - } - - Ok(()) - } - - pub fn to_dict(&self) -> Result, KSMRError> { - self.validate()?; // Ensure validation passes before creating the dictionary - - let mut rec_dict = HashMap::new(); - rec_dict.insert("type".to_string(), Value::String(self.record_type.clone())); - rec_dict.insert("title".to_string(), Value::String(self.title.clone())); - - if let Some(notes) = &self.notes { - rec_dict.insert("notes".to_string(), Value::String(notes.clone())); - } - - if let Some(fields) = &self.fields { - rec_dict.insert( - "fields".to_string(), - Value::Array( - fields - .iter() - .map(|f| serde_json::to_value(f).unwrap()) - .collect(), - ), - ); - } - - if let Some(custom) = &self.custom { - rec_dict.insert( - "custom".to_string(), - serde_json::to_value(custom.clone()).unwrap(), - ); - } - - Ok(rec_dict) - } - - pub fn to_json(&self) -> Result { - let rec_dict = self.to_dict()?; - serde_json::to_string(&rec_dict).map_err(|e| { - KSMRError::SerializationError(format!("Error serializing record field data: {}", e)) - }) - } - - pub fn append_standard_fields(&mut self, field: KeeperField) { - if self.fields.is_none() { - self.fields = Some(vec![]); - } - self.fields.as_mut().unwrap().push(field); - } - - pub fn append_custom_field(&mut self, field: KeeperField) { - if self.custom.is_none() { - self.custom = Some(vec![]); - } - self.custom.as_mut().unwrap().push(field); - } -} diff --git a/sdk/rust/src/dto/field_structs.rs b/sdk/rust/src/dto/field_structs.rs deleted file mode 100644 index 777f408e5..000000000 --- a/sdk/rust/src/dto/field_structs.rs +++ /dev/null @@ -1,2455 +0,0 @@ -// -*- coding: utf-8 -*- -// _ __ -// | |/ /___ ___ _ __ ___ _ _ (R) -// | ' ) -> Self { - KeeperField { - field_type, - label: label.unwrap_or("".to_string()), - value: Value::Null, - required: false, - privacy_screen: false, - } - } - - pub fn get(&self, key: &str) -> Option<&str> { - match key { - "field_type" => Some(&self.field_type), - "label" => Some(&self.label), - _ => None, - } - } -} - -fn default_boolean() -> bool { - false -} - -pub fn default_value() -> Value { - Value::Null -} - -fn default_empty_vector() -> Vec { - vec![] -} - -fn default_empty_string() -> String { - "".to_string() -} - -fn default_empty_number() -> u8 { - 0 -} - -fn default_empty_number_i32() -> i32 { - 0 -} - -fn default_empty_number_i64() -> i64 { - 0 -} - -fn default_empty_vector_value() -> Value { - Value::Array(vec![]) -} - -pub fn default_empty_option_string() -> Option { - Some("".to_string()) -} - -pub fn string_to_value_array(val: String) -> Value { - Value::Array(vec![Value::String(val)]) -} - -pub fn number_value_to_value_array(val: Value) -> Value { - Value::Array(vec![val]) -} - -pub fn string_to_value(val: String) -> Value { - Value::String(val) -} - -pub fn value_to_value_array(val: Value) -> Value { - Value::Array(vec![val]) -} - -fn _extract_to_option_value(opt: ValueType) -> Option> { - match opt { - ValueType::VecValue(vec) => vec, - ValueType::StringValue(str) => Some(vec![serde_json::Value::String(str)]), - } -} - -pub enum ValueType { - VecValue(Option>), - StringValue(String), -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Login { - /// ```ignore - /// use keeper_secrets_manager_core::dto::field_structs; - /// let login_field_entry = field_structs::Login::new("dummy_Email@email.com".to_string(),Some("dummy_login_label".to_string()),None,None); - /// let mut login_new = RecordCreate::new("login".to_string(), "custom_login_new_login_create".to_string(), Some("dummy_notes_changed".to_string())); - /// login_new.append_standard_fields(login_field_entry); - /// let created_record :Result = secrets_manager.create_secret("some_folder_uid".to_string(), login_new); - /// ``` - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_empty_vector_value")] - value: Value, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, -} - -impl Login { - pub fn new_login(value: String) -> KeeperField { - let value_parsed = value; - Login::new(value_parsed, None, None, None) - } - - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: String, - label: Option, - required: Option, - privacy_screen: Option, - ) -> KeeperField { - let login_value = Value::Array(vec![Value::String(value)]); - KeeperField { - field_type: StandardFieldTypeEnum::LOGIN.get_type().to_string(), - label: label.unwrap_or(StandardFieldTypeEnum::LOGIN.get_type().to_string()), - value: login_value, - required: required.unwrap_or(false), - privacy_screen: privacy_screen.unwrap_or(false), - } - } - - pub fn as_keeper_field(&self) -> KeeperField { - self.keeper_fields.clone() - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct PasswordComplexity { - #[serde(default = "default_empty_number")] - pub length: u8, - #[serde(default = "default_empty_number")] - pub caps: u8, - #[serde(default = "default_empty_number")] - pub lower: u8, - #[serde(default = "default_empty_number")] - pub digits: u8, - #[serde(default = "default_empty_number")] - pub special: u8, -} - -impl PasswordComplexity { - pub fn new( - length: Option, - caps: Option, - lower: Option, - digits: Option, - special: Option, - ) -> Self { - PasswordComplexity { - length: length.unwrap_or(32), - caps: caps.unwrap_or(0), - lower: lower.unwrap_or(0), - digits: digits.unwrap_or(0), - special: special.unwrap_or(0), - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Password { - /// ```ignore - /// use keeper_secrets_manager_core::dto::field_structs; - /// let password_field_entry = field_structs::Password::new("".to_string(),Some("dummy_password_label".to_string()),None,Some(true),None,None)?; - /// let mut login_new = RecordCreate::new("login".to_string(), "custom_login_new_login_create".to_string(), Some("dummy_notes_changed".to_string())); - /// login_new.append_standard_fields(password_field_entry); - /// let created_record :Result = secrets_manager.create_secret("some_folder_uid".to_string(), login_new); - /// ``` - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_empty_vector_value")] - value: Value, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - enforce_generation: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - complexity: Option, -} - -impl Password { - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: String, - label: Option, - required: Option, - enforce_generation: Option, - privacy_screen: Option, - password_complexity: Option, - ) -> Result { - let password_value; - if value.is_empty() { - let enforce_generation_value = enforce_generation.unwrap_or_default(); - if enforce_generation_value { - let pass_complexity = match password_complexity { - Some(password_complexity) => password_complexity, - None => PasswordComplexity::new(None, None, None, None, None), - }; - let password_options = PasswordOptions::new() - .digits(pass_complexity.digits.into()) - .length(pass_complexity.length.into()) - .lowercase(pass_complexity.lower.into()) - .uppercase(pass_complexity.caps.into()) - .special_characters(pass_complexity.special.into()); - let generated_password_value = - utils::generate_password_with_options(password_options)?; - password_value = Value::Array(vec![Value::String(generated_password_value)]); - } else { - return Err(KSMRError::RecordDataError("Password value is empty and enforce generation is false, please make one or other a true value".to_string())); - } - } else { - password_value = Value::Array(vec![Value::String(value)]); - } - - let mut keeper_field = KeeperField::new("password".to_string(), label); - keeper_field.value = password_value; - keeper_field.required = required.unwrap_or(false); - keeper_field.privacy_screen = privacy_screen.unwrap_or(false); - - Ok(keeper_field) - } - - pub fn new_password(value: String) -> Result { - Password::new(value, None, None, None, None, None) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct URL { - /// ```ignore - /// use keeper_secrets_manager_core::dto::field_structs; - /// let url_field = field_structs::URL::new("dummy_url.com".to_string(), None, None, None); - /// let mut login_new = RecordCreate::new("login".to_string(), "custom_login_new_login_create".to_string(), Some("dummy_notes_changed".to_string())); - /// login_new.append_standard_fields(url_field); - /// let created_record :Result = secrets_manager.create_secret("some_folder_uid".to_string(), login_new); - /// ``` - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_empty_vector_value")] - value: Value, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, -} - -impl URL { - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: String, - label: Option, - required: Option, - privacy_screen: Option, - ) -> KeeperField { - let url_value = string_to_value_array(value); - let mut keeper_field = - KeeperField::new(StandardFieldTypeEnum::URL.get_type().to_string(), label); - keeper_field.value = url_value; - keeper_field.required = required.unwrap_or(false); - keeper_field.privacy_screen = privacy_screen.unwrap_or(false); - - keeper_field - } - - pub fn new_url(value: String) -> KeeperField { - URL::new(value, None, None, None) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct FileRef { - /// ```ignore - /// use keeper_secrets_manager_core::dto::field_structs; - /// let file_ref_field = field_structs::FileRef::new("file::/files.file.co".to_string(), None, None); - /// let mut login_new = RecordCreate::new("login".to_string(), "custom_login_new_login_create".to_string(), Some("dummy_notes_changed".to_string())); - /// login_new.append_standard_fields(file_ref_field); - /// let created_record :Result = secrets_manager.create_secret("some_folder_uid".to_string(), login_new); - /// ``` - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_empty_vector")] - pub value: Vec, - #[serde(default = "default_boolean")] - required: bool, -} - -impl FileRef { - #[allow(clippy::new_ret_no_self)] - pub fn new(value: String, label: Option, required: Option) -> KeeperField { - let file_ref_value = string_to_value_array(value); - let mut keeper_field = - KeeperField::new(StandardFieldTypeEnum::FILEREF.get_type().to_string(), label); - keeper_field.value = file_ref_value; - keeper_field.required = required.unwrap_or(false); - - keeper_field - } - - pub fn new_file_ref(value: String) -> KeeperField { - FileRef::new(value, None, None) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct OneTimePassword { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - #[serde(default = "default_empty_vector")] - value: Vec, -} - -impl OneTimePassword { - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: String, - label: Option, - required: Option, - privacy_screen: Option, - ) -> KeeperField { - let otp_value = string_to_value_array(value); - let mut keeper_field = KeeperField::new( - StandardFieldTypeEnum::ONETIMECODE.get_type().to_string(), - label, - ); - keeper_field.value = otp_value; - keeper_field.required = required.unwrap_or(false); - keeper_field.privacy_screen = privacy_screen.unwrap_or(true); - keeper_field - } - - pub fn new_otp(value: String) -> KeeperField { - OneTimePassword::new(value, None, None, None) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Name { - #[serde(skip_serializing_if = "Option::is_none")] - first: Option, - #[serde(skip_serializing_if = "Option::is_none")] - middle: Option, - #[serde(skip_serializing_if = "Option::is_none")] - last: Option, -} - -impl Name { - pub fn new(first: Option, middle: Option, last: Option) -> Self { - Name { - first, - middle, - last, - } - } - - pub fn to_json(&self) -> Result { - Ok(serde_json::to_string(self)?) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Names { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl Names { - /// ```ignore - /// use keeper_secrets_manager_core::dto::field_structs; - /// let mut login_new = RecordCreate::new("login".to_string(), "custom_login_new_login_create".to_string(), Some("dummy_notes_changed".to_string())); - /// let name: Name =field_structs::Name::new(Some("Sample".to_string()), None, Some("User".to_string())); - /// let names: Vec = vec![name]; - /// let names_field: KeeperField = field_structs::Names::new(names, None, false, false); - /// login_new.append_standard_fields(names_field); - /// let created_record :Result = secrets_manager.create_secret("some_folder_uid".to_string(), login_new); - /// ``` - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: Vec, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let mut keeper_field = - KeeperField::new(StandardFieldTypeEnum::NAMES.get_type().to_string(), label); - keeper_field.value = Names::vec_name_to_names_string(value); - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field - } - - fn vec_name_to_names_string(mut value: Vec) -> Value { - let names_string: Vec = value - .iter_mut() - .map(|name: &mut Name| name.to_json().unwrap()) - .map(|name: String| { - Value::from_str(name.as_str()) - .map_err(|err: Error| KSMRError::DeserializationError(err.to_string())) - .unwrap() - }) - .collect::>(); - let names_string_value_array: Value = Value::Array(names_string); - names_string_value_array - } - - pub fn new_names(value: Vec) -> KeeperField { - Names::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Date { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl Date { - #[allow(clippy::new_ret_no_self)] - pub fn new( - value_in_date_milliseconds: u128, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let date_value = number_value_to_value_array(Value::Number( - serde_json::Number::from_u128(value_in_date_milliseconds).unwrap(), - )); - - let mut keeper_field = - KeeperField::new(StandardFieldTypeEnum::NAMES.get_type().to_string(), label); - keeper_field.value = date_value; - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field - } - - pub fn new_date(value: u128) -> KeeperField { - Date::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct BirthDate { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl BirthDate { - ///```ignore - /// let mut birth_certificate = RecordCreate::new(DefaultRecordType::birthCertificate.get_type().to_string(), "birth_certificate".to_string(), Some("dummy_notes_changed".to_string())); - /// let name = field_structs::Name::new(Some("first_name".to_string()), None, Some("last name".to_string())); - /// let names = vec![name]; - /// let names_field = field_structs::Names::new(names, Some("some_label".to_string()), false, false); - /// let now = SystemTime::now(); - /// // Calculate milliseconds since UNIX epoch - /// let millis = now - /// .duration_since(UNIX_EPOCH) - /// .expect("Time went backwards") - /// .as_millis(); - /// let date_field = field_structs::BirthDate::new_birth_date(millis); - /// birth_certificate.append_standard_fields(date_field); - /// birth_certificate.append_standard_fields(names_field); - /// let created_record: Result = secrets_manager.create_secret("0fLf6oIA9KY8V4BIbWz0kA".to_string(), birth_certificate); - /// ``` - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: u128, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let mut keeper_field_date = Date::new(value, label, required, privacy_screen); - keeper_field_date.field_type = StandardFieldTypeEnum::BIRTHDATE.get_type().to_string(); - keeper_field_date - } - - pub fn new_birth_date(value: u128) -> KeeperField { - BirthDate::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ExpirationDate { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl ExpirationDate { - ///```ignore - /// let mut birth_certificate = RecordCreate::new(DefaultRecordType::birthCertificate.get_type().to_string(), "birth_certificate".to_string(), Some("dummy_notes_changed".to_string())); - /// let name = field_structs::Name::new(Some("first_name".to_string()), None, Some("last name".to_string())); - /// let names = vec![name]; - /// let names_field = field_structs::Names::new(names, Some("some_label".to_string()), false, false); - /// let now = SystemTime::now(); - /// // Calculate milliseconds since UNIX epoch - /// let millis = now - /// .duration_since(UNIX_EPOCH) - /// .expect("Time went backwards") - /// .as_millis(); - /// let date_field = field_structs::ExpirationDate::new_birth_date(millis); - /// birth_certificate.append_standard_fields(date_field); - /// birth_certificate.append_standard_fields(names_field); - /// let created_record: Result = secrets_manager.create_secret("0fLf6oIA9KY8V4BIbWz0kA".to_string(), birth_certificate); - /// ``` - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: u128, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let mut keeper_field = Date::new(value, label, required, privacy_screen); - keeper_field.field_type = StandardFieldTypeEnum::EXPIRATIONDATE.get_type().to_string(); - keeper_field - } - - pub fn new_expiration_date(value: u128) -> KeeperField { - ExpirationDate::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Text { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl Text { - /// ```ignore - /// use keeper_secrets_manager_core::dto::field_structs; - /// let text_field = field_structs::Text::new("dummy_text".to_string(), None, false, false); - /// let mut login_new = RecordCreate::new("login".to_string(), "custom_login_new_login_create".to_string(), Some("dummy_notes_changed".to_string())); - /// login_new.append_standard_fields(text_field); - /// let created_record :Result = secrets_manager.create_secret("some_folder_uid".to_string(), login_new); - /// ``` - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: String, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let text_value = string_to_value_array(value); - let mut keeper_field = - KeeperField::new(StandardFieldTypeEnum::TEXT.get_type().to_string(), label); - keeper_field.value = text_value; - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - - keeper_field - } - - pub fn new_text(value: String) -> KeeperField { - Text::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct SecurityQuestion { - question: String, - answer: String, -} - -impl SecurityQuestion { - pub fn new(question: String, answer: String) -> Self { - SecurityQuestion { question, answer } - } - - pub fn to_json(&self) -> Result { - Ok(serde_json::to_string(self)?) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct SecurityQuestions { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl SecurityQuestions { - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: Vec, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let mut keeper_field = KeeperField::new( - StandardFieldTypeEnum::SECURITYQUESTIONS - .get_type() - .to_string(), - label, - ); - keeper_field.value = - SecurityQuestions::vec_security_question_to_security_questions_string(value); - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field - } - - fn vec_security_question_to_security_questions_string( - mut value: Vec, - ) -> Value { - let security_questios_string: Vec = value - .iter_mut() - .map(|security_question| security_question.to_json().unwrap()) - .map(|security_question| { - Value::from_str(security_question.as_str()) - .map_err(|err| KSMRError::DeserializationError(err.to_string())) - .unwrap() - }) - .collect::>(); - Value::Array(security_questios_string) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Multiline { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl Multiline { - /// ```ignore - /// let mut new_record = RecordCreate::new(DefaultRecordType::Login.get_type().to_string(), "sample record".to_string(), None); - /// let multiline_field = field_structs::Multiline::new("Hello\nWorld".to_string(), None, true, false); - /// new_record.append_custom_field(multiline_field); - /// let created_record: Result = secrets_manager.create_secret("Yi_OxwTV2tdBWi-_Aegs_w".to_string(), new_record); - /// - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: String, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let mut keeper_field = KeeperField::new( - StandardFieldTypeEnum::MULTILINE.get_type().to_string(), - label, - ); - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field.value = string_to_value_array(value); - keeper_field - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Email { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl Email { - /// ```ignore - /// use keeper_secrets_manager_core::dto::field_structs; - /// let text_field = field_structs::Text::new("dummy_text".to_string(), None, false, false); - /// let email_field = field_structs::Email::new("sample_email@metron.com".to_string(), None, false, false); - /// login_new.append_standard_fields(email_field); - /// let created_record :Result = secrets_manager.create_secret("some_folder_uid".to_string(), login_new); - /// ``` - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: String, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let mut keeper_field = - KeeperField::new(StandardFieldTypeEnum::EMAIL.get_type().to_string(), label); - keeper_field.value = string_to_value_array(value); - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field - } - - pub fn new_email(value: String) -> KeeperField { - Email::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct CardRef { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl CardRef { - pub fn new(value: String, label: Option, required: bool, privacy_screen: bool) -> Self { - CardRef { - keeper_fields: KeeperField::new( - StandardFieldTypeEnum::CARDREF.get_type().to_string(), - label, - ), - value: vec![value], - required, - privacy_screen, - } - } - - pub fn new_card_ref(value: String) -> Self { - CardRef::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct AddressRef { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl AddressRef { - /// ```ignore - /// let mut login_new = RecordCreate::new("login".to_string(), "custom_login_new_login_create".to_string(), Some("dummy_notes_changed".to_string())); - /// let mut address_new = RecordCreate::new("address".to_string(), "sampleaddress1".to_string(), Some("dummy_notes_changed".to_string())); - /// let address1 = field_structs::Address::new(Some("street1".to_string()), Some("street2".to_string()), Some("city".to_string()), Some("state".to_string()), "IN".to_string(), None)?; - /// let addresses= field_structs::Addresses::new_addresses(address1); - /// address_new.append_standard_fields(addresses); - /// let created_address = secrets_manager.create_secret("0fLf6oIA9KY8V4BIbWz0kA".to_string(), address_new)?; - /// let address_ref_field = field_structs::AddressRef::new_address_ref(created_address); - /// login_new.append_custom_field(address_ref_field); - /// let created_record: Result = secrets_manager.create_secret("parent_folder_uid".to_string(), login_new); - /// ``` - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: String, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let address_ref_value = string_to_value_array(value); - let mut keeper_field = KeeperField::new( - StandardFieldTypeEnum::ADDRESSREF.get_type().to_string(), - label, - ); - keeper_field.value = address_ref_value; - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - - keeper_field - } - - pub fn new_address_ref(value: String) -> KeeperField { - AddressRef::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct PinCode { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl PinCode { - /// ```ignore - /// use keeper_secrets_manager_core::dto::field_structs; - /// let pincode_field = field_structs::PinCode::new("233556".to_string(), Some("PINCODE_DUMMY_LABEL".to_string()), false, false); - /// let mut login_new = RecordCreate::new("login".to_string(), "custom_login_new_login_create".to_string(), Some("dummy_notes_changed".to_string())); - /// login_new.append_standard_fields(pincode_field); - /// let created_record :Result = secrets_manager.create_secret("some_folder_uid".to_string(), login_new); - /// ``` - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: String, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let pincode_value = string_to_value_array(value); - let mut keeper_field = - KeeperField::new(StandardFieldTypeEnum::PINCODE.get_type().to_string(), label); - keeper_field.value = pincode_value; - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field - } - - pub fn new_pin_code(value: String) -> KeeperField { - PinCode::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub enum PhoneTypeOption { - Mobile, - Home, - Work, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Phone { - #[serde(skip_serializing_if = "Option::is_none")] - region: Option, // Region code, e.g., US - number: String, // Phone number, e.g., 510-222-5555 - #[serde(skip_serializing_if = "Option::is_none")] - ext: Option, // Extension number, e.g., 9987 - #[serde(rename(serialize = "type", deserialize = "field_type"))] - phone_type: Option, // Phone type, e.g., Mobile -} - -impl Phone { - pub fn new( - number: String, - region: Option, - ext: Option, - phone_type: Option, - ) -> Self { - let phone_type_parsed = match phone_type { - Some(PhoneTypeOption::Mobile) => Some(PhoneTypeOption::Mobile), - Some(PhoneTypeOption::Home) => Some(PhoneTypeOption::Home), - Some(PhoneTypeOption::Work) => Some(PhoneTypeOption::Work), - None => Some(PhoneTypeOption::Home), - }; - Phone { - region, - number, - ext, - phone_type: phone_type_parsed, - } - } - - pub fn to_json(&self) -> Result { - Ok(serde_json::to_string(self)?) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Phones { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl Phones { - /// ```ignore - /// use keeper_secrets_manager_core::dto::field_structs; - /// let phone1 = field_structs::Phone::new("1234567890".to_string(), None, None, None); - /// let phone2 = field_structs::Phone::new("1234567891".to_string(), Some("US".to_string()), None, None); - /// let phone3 = field_structs::Phone::new("1234567892".to_string(), None, Some("1".to_string()), None); - /// let phones = vec![phone1, phone2, phone3]; - /// let phones_field = field_structs::Phones::new_phones(phones); - /// let mut login_new = RecordCreate::new("login".to_string(), "custom_login_new_login_create".to_string(), Some("dummy_notes_changed".to_string())); - /// login_new.append_standard_fields(phones_field); - /// let created_record :Result = secrets_manager.create_secret("some_folder_uid".to_string(), login_new); - /// ``` - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: Vec, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let phones_field = Phones::vec_phone_to_phones_string(value); - let mut keeper_field = - KeeperField::new(StandardFieldTypeEnum::PHONES.get_type().to_string(), label); - keeper_field.value = phones_field; - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field - } - - fn vec_phone_to_phones_string(mut value: Vec) -> Value { - let phones_string = value - .iter_mut() - .map(|phone| phone.to_json().unwrap()) - .map(|phone| { - Value::from_str(phone.as_str()) - .map_err(|err| KSMRError::DeserializationError(err.to_string())) - .unwrap() - }) - .collect::>(); - Value::Array(phones_string) - } - - pub fn new_phones(value: Vec) -> KeeperField { - Phones::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Secret { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl Secret { - ///```ignore - /// use keeper_secrets_manager_core::dto::field_structs; - /// let mut login_new = RecordCreate::new("login".to_string(), "custom_login_new_login_create".to_string(), Some("dummy_notes_changed".to_string())); - /// let secret = field_structs::Secret::new("Dummy secret".to_string(), Some("secret".to_string()), true, false); - /// login_new.append_custom_field(secret); - /// let created_record :Result = secrets_manager.create_secret("some_folder_uid".to_string(), login_new); - /// ``` - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: String, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let mut keeper_field = - KeeperField::new(StandardFieldTypeEnum::SECRET.get_type().to_string(), label); - keeper_field.value = string_to_value_array(value); - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field - } - - pub fn new_secret(value: String) -> KeeperField { - Secret::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct SecureNote { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl SecureNote { - /// ```ignore - /// use keeper_secrets_manager_core::dto::field_structs; - /// let mut login_new = RecordCreate::new("login".to_string(), "custom_login_new_login_create".to_string(), Some("dummy_notes_changed".to_string())); - /// let secret_note = field_structs::SecureNote::new("This is a sample note".to_string(), None, true, false); - /// login_new.append_standard_fields(secret_note); - /// let created_record :Result = secrets_manager.create_secret("some_folder_uid".to_string(), login_new); - /// ``` - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: String, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let mut keeper_field = KeeperField::new( - StandardFieldTypeEnum::SECURENOTE.get_type().to_string(), - label, - ); - keeper_field.value = string_to_value_array(value); - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field - } - - pub fn new_secure_note(value: String) -> KeeperField { - SecureNote::new(value, None, false, false) - } -} -#[derive(Serialize, Deserialize, Debug)] -pub struct Note { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl Note { - #[allow(clippy::new_ret_no_self)] - pub fn new(value: String, required: bool, privacy_screen: bool) -> KeeperField { - let mut keeper_field = - KeeperField::new(StandardFieldTypeEnum::NOTE.get_type().to_string(), None); - keeper_field.value = string_to_value_array(value); - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct AccountNumber { - #[serde(flatten)] - keeper_fields: KeeperField, - value: String, -} - -impl AccountNumber { - #[allow(clippy::new_ret_no_self)] - pub fn new(value: String) -> KeeperField { - let mut keeper_field = KeeperField::new( - StandardFieldTypeEnum::ACCOUNTNUMBER.get_type().to_string(), - None, - ); - keeper_field.value = string_to_value_array(value); - keeper_field.required = true; - keeper_field.privacy_screen = false; - keeper_field - } - - pub fn new_account_number(value: String) -> KeeperField { - AccountNumber::new(value) - } -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct PaymentCard { - #[serde(skip_serializing_if = "Option::is_none")] - card_number: Option, // Card number - #[serde(skip_serializing_if = "Option::is_none")] - card_expiration_date: Option, // Expiration date - #[serde(skip_serializing_if = "Option::is_none")] - card_security_code: Option, // Security code -} - -impl PaymentCard { - /// card_expiration_date should be in format of MM/YYYY else it wont reflect correctly in your record. - pub fn new( - card_number: Option, - card_expiration_date: Option, - card_security_code: Option, - ) -> Self { - PaymentCard { - card_number, - card_expiration_date, - card_security_code, - } - } - - pub fn to_json(&self) -> Result { - Ok(serde_json::to_string(self)?) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct PaymentCards { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl PaymentCards { - /// Note that only one address can be given for a record of address type and if you want more than one address, then you have to give it as addressRef field - /// ```ignore - /// let mut bank_card_record = RecordCreate::new(DefaultRecordType::BankCard.get_type().to_string(), "samplebankcard1".to_string(), Some("dummy_notes_changed".to_string())); - /// let payment_card = field_structs::PaymentCard::new(Some("8878881234211432".to_string()), Some("".to_string()), Some("1244".to_string())); - /// let payment_cards = field_structs::PaymentCards::new_payment_cards(payment_card); - /// bank_card_record.append_standard_fields(payment_cards); - /// let created_record = secrets_manager.create_secret("".to_string(), bank_card_record); - /// ``` - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: PaymentCard, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let value_string = Value::from_str(value.to_json().unwrap().as_str()).unwrap(); - let cards_field = value_to_value_array(value_string); - let mut keeper_field = KeeperField::new( - StandardFieldTypeEnum::PAYMENTCARDS.get_type().to_string(), - label, - ); - keeper_field.value = cards_field; - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field - } - - pub fn new_payment_cards(value: PaymentCard) -> KeeperField { - PaymentCards::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub enum AccountType { - Savings, - Checking, - Other, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct BankAccount { - account_type: AccountType, // Account type (e.g., Checking, Savings) - routing_number: String, // Routing number - account_number: String, // Account number - other_type: Option, // Other account type -} - -impl BankAccount { - ///let bank_account_field = field_structs::BankAccount::new(AccountType::Savings, "1122334455".to_string(), "33445566778".to_string(), None, Some("Account Field Label".to_string())); - /// let mut bank_account_record = RecordCreate::new(DefaultRecordType::BankAccounts.get_type().to_string(), "Bank Account Reference".to_string(), Some("sum notes".to_string())); - /// bank_account_record.append_standard_fields(bank_account_field); - /// let created_record = secrets_manager.create_secret("folder_uid".to_string(), bank_account_record); - #[allow(clippy::new_ret_no_self)] - pub fn new( - account_type: AccountType, - routing_number: String, - account_number: String, - other_type: Option, - label: Option, - ) -> KeeperField { - let oth_type = match account_type == AccountType::Other { - true => Some( - other_type - .unwrap_or_else(|| "Other".to_string()) - .to_string(), - ), - false => None, - }; - let bank_account = BankAccount { - account_type, - routing_number, - account_number, - other_type: oth_type, - }; - - // Serialize the BankAccount into a JSON string - let account_json = Value::from_str(bank_account.to_json().unwrap().as_str()).unwrap(); - - // Convert the JSON string into a value array (assumes implementation of `string_to_value_array`) - let account_value = value_to_value_array(account_json); - let mut keeper_field = KeeperField::new( - StandardFieldTypeEnum::BANKACCOUNT.get_type().to_string(), - label, - ); - keeper_field.value = account_value; - keeper_field.required = false; - keeper_field.privacy_screen = false; - keeper_field - } - - fn to_json(&self) -> Result { - Ok(serde_json::to_string(self)?) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct BankAccounts { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl BankAccounts { - pub fn new( - value: BankAccount, - label: Option, - required: bool, - privacy_screen: bool, - ) -> Self { - BankAccounts { - keeper_fields: KeeperField::new( - StandardFieldTypeEnum::BANKACCOUNT.get_type().to_string(), - label, - ), - value: vec![value], - required, - privacy_screen, - } - } - - pub fn new_bank_accounts(value: BankAccount) -> Self { - BankAccounts::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct KeyPair { - public_key: Option, // Public key - private_key: Option, // Private key -} - -impl KeyPair { - pub fn new(public_key: Option, private_key: Option) -> Self { - KeyPair { - public_key, - private_key, - } - } - - pub fn to_json(&self) -> Result { - Ok(serde_json::to_string(self)?) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct KeyPairs { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl KeyPairs { - ///```ignore - /// let mut new_record = RecordCreate::new(DefaultRecordType::SSHKeys.get_type().to_string(), "sample ssh key 1".to_string(), None); - /// let key_pair = field_structs::KeyPair::new(Some("jkaghdsjabd354afzdc".to_string()), Some("jgFdjavbf34f6f".to_string())); - /// let key_pair_array = vec![key_pair]; - /// let key_pairs_field = field_structs::KeyPairs::new(key_pair_array, None, true, false); - /// new_record.append_standard_fields(key_pairs_field); - /// let created_record: Result = secrets_manager.create_secret("Yi_OxwTV2tdBWi-_Aegs_w".to_string(), new_record); - /// ``` - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: Vec, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let mut keeper_field = KeeperField::new( - StandardFieldTypeEnum::KEYPAIRS.get_type().to_string(), - label, - ); - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field.value = KeyPairs::vec_key_pair_to_key_pairs_string(value); - keeper_field - } - - fn vec_key_pair_to_key_pairs_string(mut value: Vec) -> Value { - let key_pairs_string: Vec = value - .iter_mut() - .map(|key_pair| key_pair.to_json().unwrap()) - .map(|key_pair| { - Value::from_str(key_pair.as_str()) - .map_err(|err| KSMRError::DeserializationError(err.to_string())) - .unwrap() - }) - .collect::>(); - Value::Array(key_pairs_string) - } -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct Host { - host_name: Option, // Hostname - port: Option, // Port number -} - -impl Host { - pub fn new(host_name: Option, port: Option) -> Self { - Host { host_name, port } - } - - pub fn to_json(&self) -> Result { - Ok(serde_json::to_string(self)?) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Hosts { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl Hosts { - #[allow(clippy::new_ret_no_self)] - pub fn new(value: Vec) -> KeeperField { - let mut keeper_field = - KeeperField::new(StandardFieldTypeEnum::HOSTS.get_type().to_string(), None); - keeper_field.value = Hosts::vec_host_to_hosts_string(value); - keeper_field - } - - fn vec_host_to_hosts_string(mut value: Vec) -> Value { - let hosts_string: Vec = value - .iter_mut() - .map(|host| host.to_json().unwrap()) - .map(|host| { - Value::from_str(host.as_str()) - .map_err(|err| KSMRError::DeserializationError(err.to_string())) - .unwrap() - }) - .collect::>(); - Value::Array(hosts_string) - } - - pub fn new_hosts(value: Vec) -> KeeperField { - Hosts::new(value) - } -} -#[derive(Serialize, Deserialize, Debug)] -pub struct Address { - #[serde(default = "default_empty_option_string")] - street1: Option, // Street 1 - #[serde(default = "default_empty_option_string")] - street2: Option, // Street 2 - #[serde(default = "default_empty_option_string")] - city: Option, // City - #[serde(default = "default_empty_option_string")] - state: Option, // State - country: String, // Country - #[serde(default = "default_empty_option_string")] - zip: Option, // Zip code -} - -impl Address { - pub fn new( - street1: Option, - street2: Option, - city: Option, - state: Option, - country: String, - zip: Option, - ) -> Result { - let country_parsed: Country = - match Country::from_string(&country) { - Some(country) => country, - None => return Err(KSMRError::RecordDataError( - "Country is a mandatory field for address dn country has to be a valid field" - .to_string(), - )), - }; - Ok(Address { - street1, - street2, - city, - state, - country: country_parsed.to_string(), - zip, - }) - } - - fn to_json(&self) -> Result { - Ok(serde_json::to_string(self)?) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Addresses { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Value, -} - -impl Addresses { - ///```ignore - /// use keeper_secrets_manager_core::dto::field_structs; - /// let mut login_new = RecordCreate::new("address".to_string(), "custom_login_new_login_create".to_string(), Some("dummy_notes_changed".to_string())); - /// let address: Address = field_structs::Address::new(Some("ABC".to_string()), Some("PQR".to_string()), Some("Pune".to_string()), Some("Maharashtra".to_string()), Some("baHrAin".to_string()), Some("411018".to_string())); - /// let addresses: Vec
= vec![address]; - /// let address_field: KeeperField = field_structs::Addresses::new(addresses, None, false, false); - /// login_new.append_custom_field(address_field); - /// let created_record :Result = secrets_manager.create_secret("some_folder_uid".to_string(), login_new); - /// ``` - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: Vec
, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let addresses_value = Addresses::vec_address_to_addresses_string(value); - let mut keeper_field = - KeeperField::new(StandardFieldTypeEnum::ADDRESS.get_type().to_string(), label); - keeper_field.value = addresses_value; - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field - } - - fn vec_address_to_addresses_string(mut value: Vec
) -> Value { - let addresses_string: Vec = value - .iter_mut() - .map(|address| address.to_json().unwrap()) - .map(|address| { - Value::from_str(address.as_str()) - .map_err(|err| KSMRError::DeserializationError(err.to_string())) - .unwrap() - }) - .collect::>(); - Value::Array(addresses_string) - } - - pub fn new_addresses(value: Vec
) -> KeeperField { - Addresses::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct LicenseNumber { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl LicenseNumber { - #[allow(clippy::new_ret_no_self)] - pub fn new( - value: String, - label: Option, - required: bool, - privacy_screen: bool, - ) -> KeeperField { - let mut keeper_field = KeeperField::new( - StandardFieldTypeEnum::LICENSENUMBER.get_type().to_string(), - label, - ); - keeper_field.value = string_to_value_array(value); - keeper_field.required = required; - keeper_field.privacy_screen = privacy_screen; - keeper_field - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct RecordRef { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - value: Vec, -} - -impl RecordRef { - pub fn new(value: String, label: Option, required: bool) -> Self { - RecordRef { - keeper_fields: KeeperField::new( - StandardFieldTypeEnum::RECORDREF.get_type().to_string(), - label, - ), - value: vec![value], - required, - } - } - - pub fn new_record_ref(value: String) -> Self { - RecordRef::new(value, None, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Schedule { - #[serde(default = "default_empty_string")] - schedule_type: String, - #[serde(default = "default_empty_string")] - cron: String, - #[serde(default = "default_empty_string")] - time: String, - #[serde(default = "default_empty_string")] - tz: String, - #[serde(default = "default_empty_string")] - weekday: String, - #[serde(default = "default_empty_number_i32")] - interval_count: i32, -} - -impl Schedule { - pub fn new( - schedule_type: String, - cron: String, - time: String, - tz: String, - weekday: String, - interval_count: i32, - ) -> Self { - Schedule { - schedule_type, - cron, - time, - tz, - weekday, - interval_count, - } - } - - pub fn new_schedule(schedule_type: String) -> Self { - Schedule::new( - schedule_type, - String::new(), - String::new(), - String::new(), - String::new(), - 0, - ) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Schedules { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - value: Vec, -} - -impl Schedules { - pub fn new(value: Schedule, label: Option, required: bool) -> Self { - Schedules { - keeper_fields: KeeperField::new( - StandardFieldTypeEnum::SCHEDULES.get_type().to_string(), - label, - ), - value: vec![value], - required, - } - } - - pub fn new_schedules(value: Schedule) -> Self { - Schedules::new(value, None, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct DirectoryType { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - value: Vec, -} - -impl DirectoryType { - pub fn new(value: String, label: Option, required: bool) -> Self { - DirectoryType { - keeper_fields: KeeperField::new( - StandardFieldTypeEnum::DIRECTORYTYPE.get_type().to_string(), - label, - ), - value: vec![value], - required, - } - } - - pub fn new_directory_type(value: String) -> Self { - DirectoryType::new(value, None, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct DatabaseType { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - value: Vec, -} - -impl DatabaseType { - pub fn new(value: String, label: Option, required: bool) -> Self { - DatabaseType { - keeper_fields: KeeperField::new( - StandardFieldTypeEnum::DATABASETYPE.get_type().to_string(), - label, - ), - value: vec![value], - required, - } - } - - pub fn new_database_type(value: String) -> Self { - DatabaseType::new(value, None, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct PamHostname { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec, -} - -impl PamHostname { - pub fn new(value: Host, label: Option, required: bool, privacy_screen: bool) -> Self { - PamHostname { - keeper_fields: KeeperField::new( - StandardFieldTypeEnum::PAMHOSTNAME.get_type().to_string(), - label, - ), - required, - privacy_screen, - value: vec![value], - } - } - - pub fn new_pam_hostname(value: Host) -> Self { - PamHostname::new(value, None, false, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct AllowedSettings { - #[serde(default = "default_boolean")] - connections: bool, - #[serde(default = "default_boolean")] - port_forwards: bool, - #[serde(default = "default_boolean")] - rotation: bool, - #[serde(default = "default_boolean")] - session_recording: bool, - #[serde(default = "default_boolean")] - typescript_recording: bool, -} - -impl AllowedSettings { - pub fn new( - connections: bool, - port_forwards: bool, - rotation: bool, - session_recording: bool, - typescript_recording: bool, - ) -> Self { - AllowedSettings { - connections, - port_forwards, - rotation, - session_recording, - typescript_recording, - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct PamResource { - #[serde(default = "default_empty_string")] - controller_uid: String, - #[serde(default = "default_empty_string")] - folder_uid: String, - resource_ref: Vec, - allowed_settings: AllowedSettings, -} - -impl PamResource { - pub fn new( - controller_uid: String, - folder_uid: String, - resource_ref: Vec, - allowed_settings: AllowedSettings, - ) -> Self { - PamResource { - controller_uid, - folder_uid, - resource_ref, - allowed_settings, - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct PamResources { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - value: Vec, -} - -impl PamResources { - pub fn new(value: PamResource, label: Option, required: bool) -> Self { - PamResources { - keeper_fields: KeeperField::new( - StandardFieldTypeEnum::PAMRESOURCES.get_type().to_string(), - label, - ), - required, - value: vec![value], - } - } - - pub fn new_pam_resources(value: PamResource) -> Self { - PamResources::new(value, None, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Checkbox { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - value: Vec, -} - -impl Checkbox { - pub fn new(value: bool, label: Option, required: bool) -> Self { - Checkbox { - keeper_fields: KeeperField::new( - StandardFieldTypeEnum::CHECKBOX.get_type().to_string(), - label, - ), - required, - value: vec![value], - } - } - - pub fn new_checkbox(value: bool) -> Self { - Checkbox::new(value, None, false) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Script { - #[serde(default = "default_empty_string")] - file_ref: String, - #[serde(default = "default_empty_string")] - command: String, - record_ref: Vec, -} - -impl Script { - pub fn new(file_ref: String, command: String, record_ref: Vec) -> Self { - Script { - file_ref, - command, - record_ref, - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Scripts { - #[serde(flatten)] - keeper_fields: KeeperField, - #[serde(default = "default_boolean")] - required: bool, - #[serde(default = "default_boolean")] - privacy_screen: bool, - value: Vec