├── .fmf └── version ├── .github ├── dependabot.yml └── workflows │ ├── cargo-audit.yml │ ├── container.yml │ ├── keylime-bot.yml │ ├── mockoon.yaml │ ├── rust.yml │ ├── submit-HEAD-coverage.yaml │ └── submit-PR-coverage.yaml ├── .gitignore ├── .gitleaks.toml ├── .packit.yaml ├── .rustfmt.toml ├── CODEOWNERS ├── Cargo.lock ├── Cargo.toml ├── GNUmakefile ├── LICENSE ├── MAINTAINERS ├── README.md ├── codecov.yml ├── debian └── keylime_agent.postinst ├── dist └── systemd │ └── system │ ├── keylime_agent.service │ └── var-lib-keylime-secure.mount ├── docker-compose.yml ├── docker ├── fedora │ ├── dbus-policy.conf │ ├── keylime_py.Dockerfile │ ├── keylime_rust.Dockerfile │ └── wait.sh └── release │ ├── Dockerfile.distroless │ ├── Dockerfile.fedora │ ├── Dockerfile.wolfi │ └── build_locally.sh ├── ima-policy ├── README └── ima-policy ├── keylime-agent.conf ├── keylime-agent ├── Cargo.toml ├── src │ ├── agent_handler.rs │ ├── api.rs │ ├── common.rs │ ├── config.rs │ ├── errors_handler.rs │ ├── keys_handler.rs │ ├── main.rs │ ├── notifications_handler.rs │ ├── payloads.rs │ ├── permissions.rs │ ├── quotes_handler.rs │ ├── revocation.rs │ └── secure_mount.rs ├── test-data │ ├── ima │ │ └── ascii_runtime_measurements │ ├── payload.zip │ ├── revocation.sig │ ├── test-cert.pem │ ├── test-rsa.pem │ ├── test.json │ ├── test_input.txt │ ├── test_ok.json │ └── tpmdata_test.json └── tests │ ├── actions │ ├── local_action_hello.py │ ├── local_action_hello_shell.sh │ ├── local_action_stand_alone.py │ └── shim.py │ └── unzipped │ ├── action_list │ ├── local_action_payload.py │ ├── local_action_payload_shell.sh │ ├── local_action_rev_script1.py │ ├── local_action_rev_script2.py │ ├── test_err.json │ └── test_ok.json ├── keylime-ima-emulator ├── Cargo.toml └── src │ └── main.rs ├── keylime-push-model-agent ├── Cargo.toml ├── src │ ├── json_dump.rs │ ├── main.rs │ ├── registration.rs │ ├── struct_filler.rs │ └── url_selector.rs ├── test-data │ ├── evidence_handling_request.json │ ├── evidence_supported_attestation_request.json │ ├── session_request.json │ └── verifier.json └── tests │ └── test-keylime-push-model-agent.rs ├── keylime ├── Cargo.toml ├── src │ ├── agent_data.rs │ ├── agent_identity.rs │ ├── agent_registration.rs │ ├── algorithms.rs │ ├── cert.rs │ ├── config │ │ ├── mod.rs │ │ └── push_model_config.rs │ ├── context_info.rs │ ├── crypto.rs │ ├── crypto │ │ ├── auth_tag.rs │ │ ├── encrypted_data.rs │ │ ├── symmkey.rs │ │ └── x509.rs │ ├── device_id.rs │ ├── file_ops.rs │ ├── global_config.rs │ ├── hash_ek.rs │ ├── hostname.pest │ ├── hostname_parser.rs │ ├── https_client.rs │ ├── ima │ │ ├── entry.rs │ │ ├── measurement_list.rs │ │ └── mod.rs │ ├── ip.pest │ ├── ip_parser.rs │ ├── keylime_error.rs │ ├── lib.rs │ ├── list.pest │ ├── list_parser.rs │ ├── quote.rs │ ├── registrar_client.rs │ ├── serialization.rs │ ├── structures │ │ ├── capabilities_negotiation.rs │ │ ├── evidence_handling.rs │ │ ├── mod.rs │ │ └── sessions.rs │ ├── tpm.rs │ └── version.rs └── test-data │ ├── prime256v1.cert.pem │ ├── prime256v1.pem │ ├── test-cert.pem │ ├── test-quote.txt │ ├── test-rsa.pem │ └── test-rsa.sig ├── license-header.tpl ├── packit-ci.fmf ├── rpm ├── README.md ├── centos │ └── keylime-agent-rust.spec └── fedora │ ├── keylime-agent-rust.spec │ └── rust-keylime-metadata.patch ├── scripts └── download_packit_coverage.sh └── tests ├── actions └── shim.py ├── ca.conf ├── common_tests.sh ├── generate-iak-idevid-certs.sh ├── mockoon_tests.sh ├── nopanic.ci ├── run.sh ├── seccomp-profile.json └── setup_swtpm.sh /.fmf/version: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | versioning-strategy: lockfile-only 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: "daily" 17 | -------------------------------------------------------------------------------- /.github/workflows/cargo-audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | push: 4 | branches: 5 | - master 6 | tags: 7 | - "v*" 8 | paths: 9 | - '**/Cargo.toml' 10 | - '**/Cargo.lock' 11 | pull_request: 12 | paths: 13 | - '**/Cargo.toml' 14 | - '**/Cargo.lock' 15 | workflow_dispatch: 16 | branches: 17 | - master 18 | 19 | jobs: 20 | security_audit: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions-rs/audit-check@v1 25 | with: 26 | token: ${{ secrets.GITHUB_TOKEN }} 27 | -------------------------------------------------------------------------------- /.github/workflows/container.yml: -------------------------------------------------------------------------------- 1 | name: Build Image 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - "v*" 9 | workflow_dispatch: 10 | branches: 11 | - master 12 | 13 | env: 14 | REGISTRY: quay.io 15 | IMAGE_BASE: quay.io/keylime 16 | 17 | jobs: 18 | build-images: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: Log in to the Container registry 25 | uses: docker/login-action@v3 26 | with: 27 | registry: ${{ env.REGISTRY }} 28 | username: ${{ secrets.QUAY_USER }} 29 | password: ${{ secrets.QUAY_TOKEN }} 30 | 31 | - name: Generate docker metadata for keylime_agent 32 | id: meta_agent 33 | uses: docker/metadata-action@v5 34 | with: 35 | images: | 36 | ${{ env.IMAGE_BASE }}/keylime_agent 37 | tags: | 38 | type=ref,enable=true,priority=600,prefix=,suffix=,event=branch 39 | type=ref,enable=true,priority=600,prefix=,suffix=,event=tag 40 | type=sha,prefix=sha- 41 | 42 | - name: Build and push agent image 43 | id: build_agent 44 | uses: docker/build-push-action@v6 45 | env: 46 | VERSION: "${{ steps.meta_base.outputs.version }}" 47 | with: 48 | context: . 49 | file: docker/release/Dockerfile.fedora 50 | push: true 51 | tags: ${{ steps.meta_agent.outputs.tags }} 52 | labels: ${{ steps.meta_agent.outputs.labels }} 53 | -------------------------------------------------------------------------------- /.github/workflows/keylime-bot.yml: -------------------------------------------------------------------------------- 1 | name: keylime-bot 2 | 3 | on: 4 | check_run: 5 | check_suite: 6 | create: 7 | delete: 8 | deployment: 9 | deployment_status: 10 | fork: 11 | gollum: 12 | issue_comment: 13 | issues: 14 | label: 15 | milestone: 16 | page_build: 17 | project: 18 | project_card: 19 | project_column: 20 | public: 21 | pull_request_target: 22 | types: 23 | - assigned 24 | - unassigned 25 | - labeled 26 | - unlabeled 27 | - opened 28 | - edited 29 | - closed 30 | - reopened 31 | - synchronize 32 | - ready_for_review 33 | - locked 34 | - unlocked 35 | - review_requested 36 | - review_request_removed 37 | push: 38 | registry_package: 39 | release: 40 | status: 41 | watch: 42 | schedule: 43 | - cron: '*/15 * * * *' 44 | workflow_dispatch: 45 | 46 | jobs: 47 | pull-request-responsibility: 48 | runs-on: ubuntu-latest 49 | name: pull-request-responsibility 50 | steps: 51 | - uses: actions-automation/pull-request-responsibility@main 52 | with: 53 | token: ${{ secrets.BOT_TOKEN }} 54 | actions: "request,assign,copy-labels-linked" 55 | reviewers: "rust-team" 56 | num_to_request: 3 57 | -------------------------------------------------------------------------------- /.github/workflows/mockoon.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Mockoon Tests" 3 | 4 | "on": 5 | push: 6 | branches: [master] 7 | pull_request: 8 | branches: [master] 9 | 10 | jobs: 11 | mockoon-tests: 12 | runs-on: ubuntu-latest 13 | container: 14 | image: quay.io/keylime/keylime-ci:latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: NPM installation 18 | run: dnf install -y npm 19 | - name: Run Mockoon CLI 20 | uses: mockoon/cli-action@v2 21 | with: 22 | version: latest 23 | data-file: keylime-push-model-agent/test-data/verifier.json 24 | port: 3000 25 | - name: Set git safe.directory for the working directory 26 | run: git config --system --add safe.directory "$PWD" 27 | - name: Mockoon tests custom script execution 28 | run: bash tests/mockoon_tests.sh 29 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | workflow_dispatch: 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | static: 15 | name: Static code checks 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Check formatting 20 | run: cargo fmt --all -- --check 21 | - name: Check for panics 22 | run: ./tests/nopanic.ci 23 | 24 | tests: 25 | name: Fedora tests 26 | runs-on: ubuntu-latest 27 | container: 28 | image: quay.io/keylime/keylime-ci:latest 29 | steps: 30 | - uses: actions/checkout@v4 31 | - name: Set git safe.directory for the working directory 32 | run : git config --system --add safe.directory "$PWD" 33 | - name: Run tests 34 | run: bash tests/run.sh 35 | - uses: actions/upload-artifact@v4 36 | with: 37 | name: tarpaulin-report 38 | path: | 39 | tarpaulin-report.json 40 | tarpaulin-report.html 41 | -------------------------------------------------------------------------------- /.github/workflows/submit-HEAD-coverage.yaml: -------------------------------------------------------------------------------- 1 | name: Upload code coverage for a merged PR to codecov.io 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-22.04 11 | name: Submit code coverage from merged PR 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Install testing-farm script 15 | run: pip3 -v install tft-cli 16 | - name: Run tests on Testing Farm 17 | run: testing-farm request --context distro=fedora-41 --arch x86_64 --compose Fedora-41 --plan '/e2e' -e UPLOAD_COVERAGE=1 2>&1 | tee tt_output 18 | env: 19 | TESTING_FARM_API_TOKEN: ${{ secrets.TESTING_FARM_API_TOKEN }} 20 | - name: Find PR Packit tests to finish and download e2e_coverage.txt and upstream_coverage.xml coverage files. 21 | run: grep -q 'tests passed' tt_output && sleep 20 && scripts/download_packit_coverage.sh --testing-farm-log tt_output 22 | env: 23 | MAX_DURATION: 120 24 | SLEEP_DELAY: 20 25 | - name: List downloaded files. 26 | run: ls 27 | - name: Upload e2e_coverage report to Codecov with GitHub Action. 28 | uses: codecov/codecov-action@v5 29 | env: 30 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 31 | with: 32 | files: e2e_coverage.txt 33 | flags: e2e-testsuite 34 | - name: Upload upstream_coverage report to Codecov with GitHub Action. 35 | uses: codecov/codecov-action@v5 36 | env: 37 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 38 | with: 39 | files: upstream_coverage.xml 40 | flags: upstream-unit-tests 41 | -------------------------------------------------------------------------------- /.github/workflows/submit-PR-coverage.yaml: -------------------------------------------------------------------------------- 1 | name: PR code coverage measurement on codecov.io 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | continue-on-error: true 9 | name: Submit code coverage from Packit tests 10 | defaults: 11 | run: 12 | working-directory: scripts 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Wait for Packit tests to finish and download e2e_coverage.txt and upstream_coverage.xml files. 16 | run: ./download_packit_coverage.sh 17 | env: 18 | MAX_DURATION: 5400 19 | SLEEP_DELAY: 120 20 | - name: List downloaded files. 21 | run: ls 22 | - name: Upload e2e_coverage.txt report to Codecov with GitHub Action. 23 | uses: codecov/codecov-action@v5 24 | env: 25 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 26 | with: 27 | files: scripts/e2e_coverage.txt 28 | flags: e2e-testsuite 29 | - name: Upload upstream_coverage.xml report to Codecov with GitHub Action. 30 | uses: codecov/codecov-action@v5 31 | env: 32 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 33 | with: 34 | files: scripts/upstream_coverage.xml 35 | flags: upstream-unit-tests 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .auditing-0 4 | tpmdata.json 5 | tests/.tmp* 6 | tests/actions/__pycache__ 7 | tests/unzipped/__pycache__ 8 | **/tarpaulin-report.* 9 | **/__pycache__ 10 | *.swp 11 | *.orig 12 | -------------------------------------------------------------------------------- /.gitleaks.toml: -------------------------------------------------------------------------------- 1 | # 2 | # GitLeaks Repo Specific Configuration 3 | # 4 | # This allowlist is used to help Red Hat ignore false positives during its code 5 | # scans. 6 | 7 | [allowlist] 8 | paths = [ 9 | '''test-data/test-rsa.pem''', 10 | '''keylime/test-data/test-rsa.pem''', 11 | '''keylime/test-data/prime256v1.pem''', 12 | ] 13 | -------------------------------------------------------------------------------- /.packit.yaml: -------------------------------------------------------------------------------- 1 | downstream_package_name: keylime-agent-rust 2 | upstream_project_url: https://github.com/keylime/rust-keylime 3 | specfile_path: rpm/fedora/keylime-agent-rust.spec 4 | actions: 5 | get-current-version: 6 | - bash -c "git describe --tags --abbrev=0 | sed 's/v\(.*\)/\1/g'" 7 | 8 | srpm_build_deps: 9 | - cargo 10 | - rust 11 | - git 12 | - openssl-devel 13 | 14 | jobs: 15 | - job: tests 16 | trigger: pull_request 17 | metadata: 18 | targets: 19 | - fedora-branched 20 | - centos-stream-10-x86_64 21 | skip_build: true 22 | 23 | - job: copr_build 24 | trigger: commit 25 | branch: master 26 | identifier: fedora 27 | specfile_path: rpm/fedora/keylime-agent-rust.spec 28 | files_to_sync: 29 | - rpm/fedora/* 30 | actions: 31 | get-current-version: 32 | bash -c "git describe --tags --abbrev=0 | sed 's/v\(.*\)/\1/g'" 33 | post-upstream-clone: 34 | bash -c 'if [[ ! -d /var/tmp/cargo-vendor-filterer ]]; then git clone https://github.com/cgwalters/cargo-vendor-filterer.git /var/tmp/cargo-vendor-filterer; fi && 35 | cd /var/tmp/cargo-vendor-filterer && 36 | cargo build && 37 | cd - && 38 | cp /var/tmp/cargo-vendor-filterer/target/debug/cargo-vendor-filterer . && 39 | ./cargo-vendor-filterer --all-features 40 | --platform x86_64-unknown-linux-gnu 41 | --platform powerpc64le-unknown-linux-gnu 42 | --platform aarch64-unknown-linux-gnu 43 | --platform i686-unknown-linux-gnu 44 | --platform s390x-unknown-linux-gnu 45 | --exclude-crate-path "libloading#tests" && 46 | tar jcf rpm/fedora/rust-keylime-vendor.tar.xz vendor' 47 | targets: 48 | - fedora-all 49 | preserve_project: True 50 | 51 | - job: copr_build 52 | trigger: commit 53 | branch: master 54 | identifier: centos 55 | specfile_path: rpm/centos/keylime-agent-rust.spec 56 | files_to_sync: 57 | - rpm/centos/* 58 | actions: 59 | get-current-version: 60 | bash -c "git describe --tags --abbrev=0 | sed 's/v\(.*\)/\1/g'" 61 | post-upstream-clone: 62 | bash -c 'if [[ ! -d /var/tmp/cargo-vendor-filterer ]]; then git clone https://github.com/cgwalters/cargo-vendor-filterer.git /var/tmp/cargo-vendor-filterer; fi && 63 | cd /var/tmp/cargo-vendor-filterer && 64 | cargo build && 65 | cd - && 66 | cp /var/tmp/cargo-vendor-filterer/target/debug/cargo-vendor-filterer . && 67 | ./cargo-vendor-filterer --all-features 68 | --platform x86_64-unknown-linux-gnu 69 | --platform powerpc64le-unknown-linux-gnu 70 | --platform aarch64-unknown-linux-gnu 71 | --platform i686-unknown-linux-gnu 72 | --platform s390x-unknown-linux-gnu 73 | --exclude-crate-path "libloading#tests" && 74 | tar jcf rpm/centos/rust-keylime-vendor.tar.xz vendor' 75 | targets: 76 | - centos-stream-9-x86_64 77 | - centos-stream-10-x86_64 78 | preserve_project: True 79 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 78 2 | # Enable when available: https://github.com/rust-lang/rustfmt/issues/3352 3 | #license_template_path = "license-header.tpl" 4 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | @keylime/rust-team @lkatalin 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "keylime", 4 | "keylime-agent", 5 | "keylime-ima-emulator", 6 | "keylime-push-model-agent", 7 | ] 8 | resolver = "2" 9 | 10 | [workspace.package] 11 | authors = ["Keylime Authors"] 12 | edition = "2021" 13 | license = "Apache-2.0" 14 | repository = "https://github.com/keylime/rust-keylime" 15 | version = "0.2.7" 16 | 17 | [workspace.dependencies] 18 | actix-rt = "2" 19 | actix-web = { version = "4", default-features = false, features = ["macros", "openssl"] } 20 | anyhow = { version = "1.0", features = ["backtrace"] } 21 | assert_cmd = { version = "2.0.16" } 22 | base64 = "0.22" 23 | cfg-if = "1" 24 | chrono = { version = "0.4.40", features = ["serde"] } 25 | clap = { version = "4.5", features = ["derive"] } 26 | config = { version = "0.13", default-features = false, features = ["toml"] } 27 | futures = "0.3.6" 28 | glob = "0.3" 29 | hex = "0.4" 30 | keylime = { version = "=0.2.7", path = "keylime" } 31 | libc = "0.2.43" 32 | log = "0.4" 33 | once_cell = "1.19.0" 34 | openssl = "0.10.15" 35 | pest = "2" 36 | pest_derive = "2" 37 | picky-asn1-der = "0.4" 38 | picky-asn1-x509 = "0.12" 39 | predicates = { version = "3.1.3" } 40 | pretty_env_logger = "0.5" 41 | reqwest = {version = "0.12", default-features = false, features = ["json", "native-tls"]} 42 | serde = "1.0.80" 43 | serde_derive = "1.0.80" 44 | serde_json = { version = "1.0", features = ["raw_value"] } 45 | signal-hook = "0.3" 46 | static_assertions = "1" 47 | tempfile = "3.4.0" 48 | thiserror = "2.0" 49 | tokio = {version = "1", features = ["rt", "sync", "macros"]} 50 | tss-esapi = {version = "7.6.0", features = ["generate-bindings"]} 51 | uuid = {version = "1.3", features = ["v4"]} 52 | zip = {version = "0.6", default-features = false, features= ["deflate"]} 53 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright 2022 Keylime Authors 3 | 4 | RELEASE ?= 0 5 | TARGETDIR ?= target 6 | CONFFILE ?= ./keylime-agent.conf 7 | 8 | ifeq ($(RELEASE),1) 9 | PROFILE ?= release 10 | CARGO_ARGS = --release 11 | else 12 | PROFILE ?= debug 13 | CARGO_ARGS = 14 | endif 15 | 16 | systemdsystemunitdir := $(shell pkg-config systemd --variable=systemdsystemunitdir) 17 | 18 | programs = \ 19 | ${TARGETDIR}/${PROFILE}/keylime_agent \ 20 | ${TARGETDIR}/${PROFILE}/keylime_ima_emulator 21 | 22 | .PHONY: all 23 | all: $(programs) 24 | 25 | $(programs): 26 | cargo build --target-dir="${TARGETDIR}" ${CARGO_ARGS} 27 | 28 | .PHONY: clean 29 | clean:: 30 | ${RM} -rf target 31 | 32 | .PHONY: install 33 | install: all 34 | mkdir -p ${DESTDIR}/etc/keylime/ 35 | mkdir -p ${DESTDIR}/etc/keylime/agent.conf.d 36 | cp ${CONFFILE} ${DESTDIR}/etc/keylime/agent.conf 37 | for f in $(programs); do \ 38 | install -D -t ${DESTDIR}/usr/bin "$$f"; \ 39 | done 40 | install -D -m 644 -t ${DESTDIR}$(systemdsystemunitdir) dist/systemd/system/keylime_agent.service 41 | install -D -m 644 -t ${DESTDIR}$(systemdsystemunitdir) dist/systemd/system/var-lib-keylime-secure.mount 42 | # Remove when https://github.com/keylime/rust-keylime/issues/325 is fixed 43 | install -D -t ${DESTDIR}/usr/libexec/keylime keylime-agent/tests/actions/shim.py 44 | 45 | # This only runs tests without TPM access. See tests/run.sh for 46 | # running full testsuite with swtpm. 47 | .PHONY: check 48 | check: all 49 | cargo test --target-dir="${TARGETDIR}" 50 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | @ashcrow 2 | @mbestavros 3 | @lukehinds 4 | @lkatalin 5 | @puiterwijk 6 | @mpeters 7 | @ueno 8 | @ansasaki 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Keylime 2 | 3 | [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202-blue)](https://www.apache.org/licenses/LICENSE-2.0) 4 | 5 | ## Overview 6 | 7 | This is a Rust implementation of 8 | [keylime](https://github.com/keylime/keylime) agent. Keylime is system 9 | integrity monitoring system that has the following features: 10 | 11 | * Exposes TPM trust chain for higher-level use 12 | * Provides an end-to-end solution for bootstrapping node cryptographic 13 | identities 14 | * Securely monitors system integrity 15 | 16 | For more information, visit the [keylime website](https://keylime.dev) 17 | 18 | For now, this project is focusing on the keylime agent component, which is a 19 | HTTP server running on the machine that executes keylime operations. 20 | Most keylime operations rely on TPM co-processor; therefore, the server needs 21 | a physical TPM chip (or a TPM emulator) to perform keylime operations. The 22 | TPM emulator is a program that runs in the daemon to mimic TPM commands. 23 | 24 | The rust-keylime agent is the official agent (starting with version 0.1.0) and 25 | replaces the Python implementation. 26 | 27 | ## Prerequisites 28 | 29 | ### Required Packages 30 | 31 | #### Fedora 32 | 33 | The following packages are required for building: 34 | 35 | * `clang` 36 | * `openssl-devel` 37 | * `tpm2-tss-devel` 38 | * (optional for the `with-zmq` feature): `zeromq-devel` 39 | 40 | To install, use the following command: 41 | ``` 42 | $ dnf install clang openssl-devel tpm2-tss-devel zeromq-devel 43 | ``` 44 | 45 | For runtime, the following packages are required: 46 | 47 | * `openssl` 48 | * `tpm2-tss` 49 | * `systemd` (to run as systemd service) 50 | * `util-linux-core` (for the `mount` command) 51 | * (optional for the `with-zmq` feature): `zeromq` 52 | 53 | #### Debian and Ubuntu 54 | 55 | For Debian and Ubuntu, use the following packages are required: 56 | 57 | * `libclang-dev` 58 | * `libssl-dev` 59 | * `libtss2-dev` 60 | * `pkg-config` 61 | * (optional for the `with-zmq` feature): `libzmq3-dev` 62 | 63 | To install, use the following command: 64 | 65 | ``` 66 | $ apt-get install libclang-dev libssl-dev libtss2-dev libzmq3-dev pkg-config 67 | ``` 68 | 69 | For runtime, the following packages are required: 70 | 71 | * `coreutils` (for the `mount` command) 72 | * `libssl` 73 | * `libtss2-esys-3.0.2-0` 74 | * (optional for the `with-zmq` feature): `libzmq3` 75 | * `systemd` (to run as systemd service) 76 | 77 | ### Rust 78 | 79 | Make sure Rust is installed before running Keylime. Installation 80 | instructions can be found [here](https://www.rust-lang.org/en-US/install.html). 81 | 82 | ## Logging env 83 | 84 | To run with `pretty-env-logger` trace logging active, set cargo run 85 | within `RUST_LOG`, as follows: 86 | 87 | $ RUST_LOG=keylime_agent=trace cargo run --bin keylime_agent 88 | 89 | ## Testing 90 | 91 | Unit tests are gating in CI for new code submission. To run them: 92 | 93 | ``` 94 | $ cargo test 95 | ``` 96 | In case you want to execute [Mockoon](https://mockoon.com/) based tests, you need to follow two steps: 97 | 98 | 1. Start Mockoon with [appropriate configuration file](https://github.com/keylime/rust-keylime/blob/master/keylime-push-model-agent/test-data/verifier.json) on port 3000 99 | 2. Execute tests through MOCKOON environment variable: 100 | 101 | ``` 102 | $ MOCKOON=1 cargo test 103 | ``` 104 | 105 | ## Running agent as a systemd-managed service 106 | 107 | To make deployment and management of the service easier, this crate 108 | comes with a Makefile and systemd unit file. 109 | 110 | To install the executables and the unit file, do: 111 | 112 | ```console 113 | $ make 114 | $ sudo make install 115 | ``` 116 | 117 | Then you should be able to start the service with: 118 | 119 | ```console 120 | $ sudo systemctl start keylime_agent 121 | ``` 122 | 123 | ## Building Debian package with cargo-deb 124 | 125 | Cargo deb requires Rust 1.60, so on Debian you need to install it first from rustup.rs. 126 | 127 | ```shell 128 | # Install cargo-deb 129 | rustup update 130 | cargo install cargo-deb 131 | 132 | # Build Debian package 133 | cargo deb -p keylime_agent 134 | ``` 135 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | after_n_builds: 2 4 | 5 | coverage: 6 | status: 7 | project: 8 | default: 9 | informational: true 10 | 11 | ignore: 12 | - '**/lib.rs' 13 | 14 | comment: 15 | layout: "flags,files" 16 | behavior: default 17 | require_changes: false 18 | require_base: no 19 | after_n_builds: 2 20 | 21 | flags: 22 | e2e-testsuite: 23 | carryforward: false 24 | upstream-unit-tests: 25 | carryforward: false 26 | -------------------------------------------------------------------------------- /debian/keylime_agent.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | 6 | keylime_setup_group_user () 7 | { 8 | # creating tss group if he isn't already there 9 | if ! getent group tss >/dev/null; then 10 | addgroup --system tss 11 | fi 12 | 13 | # creating tss user if he isn't already there 14 | if ! getent passwd tss >/dev/null; then 15 | adduser --system --ingroup tss --shell /bin/false \ 16 | --home /var/lib/tpm --no-create-home \ 17 | --gecos "TPM software stack" \ 18 | tss 19 | fi 20 | 21 | # creating keylime user if he isn't already there 22 | if ! getent passwd keylime >/dev/null; then 23 | adduser --system --ingroup tss --shell /bin/false \ 24 | --home /var/lib/keylime --no-create-home \ 25 | --gecos "Keylime remote attestation" \ 26 | keylime 27 | fi 28 | } 29 | 30 | case "$1" in 31 | configure) 32 | # Setup the keylime user and the tss group 33 | keylime_setup_group_user 34 | 35 | mkdir -p /var/lib/keylime 36 | 37 | # Setting owner 38 | if [ -d /var/lib/keylime ] && ! dpkg-statoverride --list /var/lib/keylime >/dev/null && getent passwd keylime >/dev/null; then 39 | chown -R keylime:tss /var/lib/keylime 40 | fi 41 | 42 | ;; 43 | 44 | abort-upgrade|abort-remove|abort-deconfigure) 45 | ;; 46 | 47 | *) 48 | echo "postinst called with unknown argument \`$1'" >&2 49 | exit 1 50 | ;; 51 | esac 52 | 53 | #DEBHELPER# 54 | -------------------------------------------------------------------------------- /dist/systemd/system/keylime_agent.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=The Keylime compute agent 3 | StartLimitInterval=10s 4 | StartLimitBurst=5 5 | Requires=var-lib-keylime-secure.mount 6 | After=var-lib-keylime-secure.mount 7 | After=network-online.target 8 | Wants=network-online.target 9 | Wants=tpm2-abrmd.service 10 | After=tpm2-abrmd.service 11 | # If the service should start only when hardware TPMs are available, uncomment the below lines 12 | #ConditionPathExistsGlob=/dev/tpm[0-9]* 13 | #ConditionPathExistsGlob=/dev/tpmrm[0-9]* 14 | 15 | [Service] 16 | ExecStart=/usr/bin/keylime_agent 17 | TimeoutSec=60s 18 | Restart=on-failure 19 | RestartSec=120s 20 | Environment="RUST_LOG=keylime_agent=info,keylime=info" 21 | # If using swtpm with tpm2-abrmd service, uncomment the line below to set TCTI 22 | # variable on the service environment 23 | #Environment="TCTI=tabrmd:" 24 | 25 | [Install] 26 | WantedBy=default.target 27 | -------------------------------------------------------------------------------- /dist/systemd/system/var-lib-keylime-secure.mount: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Keylime configuration filesystem 3 | Before=keylime_agent.service 4 | 5 | [Mount] 6 | What=tmpfs 7 | Where=/var/lib/keylime/secure 8 | Type=tmpfs 9 | Options=mode=0700,size=1m,uid=keylime,gid=tss 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | services: 3 | keylime-verifier: 4 | build: 5 | context: ./docker/fedora/ 6 | dockerfile: keylime_py.Dockerfile 7 | image: keylime_py 8 | hostname: 'keylime-verifier' 9 | user: root 10 | volumes: 11 | - secure-volume:/var/lib/keylime 12 | ports: 13 | - "8892:8892" 14 | command: [ 15 | "/usr/local/bin/keylime_verifier" 16 | ] 17 | keylime-registrar: 18 | build: 19 | context: ./docker/fedora/ 20 | dockerfile: keylime_py.Dockerfile 21 | image: keylime_py 22 | hostname: 'keylime-verifier' # this is a bit of poor workaround, will fix in python code 23 | user: root 24 | volumes: 25 | - secure-volume:/var/lib/keylime 26 | ports: 27 | - "8891:8891" 28 | - "8890:8890" 29 | command: ["/root/wait.sh", "/var/lib/keylime/cv_ca/client-cert.crt", "keylime_registrar"] 30 | keylime_agent: 31 | build: 32 | context: ./docker/fedora/ 33 | dockerfile: keylime_rust.Dockerfile 34 | image: keylime_rust 35 | hostname: 'keylime-agent' 36 | user: root 37 | volumes: 38 | - ./target/debug/:/rust-keylime 39 | network_mode: host 40 | environment: 41 | - TCTI=tabrmd:bus_type=system 42 | command: 43 | - /bin/bash 44 | - -c 45 | - | 46 | mkdir /tmp/tpmdir 47 | rm -rf /var/run/dbus 48 | mkdir /var/run/dbus 49 | dbus-daemon --system 50 | ls /etc/dbus-1/system.d/ 51 | swtpm_setup --tpm2 \ 52 | --tpmstate /tmp/tpmdir \ 53 | --createek --decryption --create-ek-cert \ 54 | --create-platform-cert \ 55 | --display 56 | swtpm socket --tpm2 \ 57 | --tpmstate dir=/tmp/tpmdir \ 58 | --flags startup-clear \ 59 | --ctrl type=tcp,port=2322 \ 60 | --server type=tcp,port=2321 \ 61 | --daemon 62 | tpm2-abrmd \ 63 | --logger=stdout \ 64 | --tcti=swtpm: \ 65 | --allow-root \ 66 | --flush-all & 67 | RUST_LOG=keylime_agent=trace /rust-keylime/keylime_agent 68 | volumes: 69 | secure-volume: 70 | 71 | -------------------------------------------------------------------------------- /docker/fedora/dbus-policy.conf: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docker/fedora/keylime_py.Dockerfile: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # keylime TPM 2.0 Python Dockerfile 3 | # 4 | # This file is for automatic test running of Keylime and rust-keylime. 5 | # It is not recommended for use beyond testing scenarios. 6 | ############################################################################## 7 | 8 | FROM fedora:latest 9 | LABEL version="2.0.1" description="Keylime - Python Devel Env" 10 | 11 | # environment variables 12 | ARG BRANCH=master 13 | ENV container docker 14 | ENV HOME /root 15 | ENV KEYLIME_HOME ${HOME}/keylime 16 | ENV TPM_HOME ${HOME}/swtpm2 17 | COPY dbus-policy.conf /etc/dbus-1/system.d/ 18 | COPY wait.sh /root/ 19 | RUN ["chmod", "+x", "/root/wait.sh"] 20 | 21 | 22 | # Install dev tools and libraries (includes openssl-devel) 23 | RUN dnf groupinstall -y \ 24 | "Development Tools" \ 25 | "Development Libraries" 26 | 27 | # Install additional packages 28 | RUN dnf install -y \ 29 | clang-devel \ 30 | kmod \ 31 | llvm llvm-devel \ 32 | pkg-config \ 33 | automake \ 34 | cargo \ 35 | clang-devel \ 36 | dbus \ 37 | dbus-daemon \ 38 | dbus-devel \ 39 | dnf-plugins-core \ 40 | efivar-devel \ 41 | gcc \ 42 | git \ 43 | glib2-devel \ 44 | glib2-static \ 45 | gnulib \ 46 | kmod \ 47 | libselinux-python3 \ 48 | libtool \ 49 | libtpms \ 50 | make \ 51 | openssl-devel \ 52 | procps \ 53 | python3-cryptography \ 54 | python3-dbus \ 55 | python3-devel \ 56 | python3-m2crypto \ 57 | python3-pip \ 58 | python3-requests \ 59 | python3-setuptools \ 60 | python3-sqlalchemy \ 61 | python3-simplejson \ 62 | python3-tornado \ 63 | python3-virtualenv \ 64 | python3-yaml \ 65 | python3-zmq \ 66 | python3-pyasn1 \ 67 | redhat-rpm-config \ 68 | tpm2-abrmd \ 69 | tpm2-tools \ 70 | tpm2-tss \ 71 | tpm2-tss-devel \ 72 | uthash-devel \ 73 | wget \ 74 | which 75 | 76 | WORKDIR ${HOME} 77 | RUN git clone https://github.com/keylime/keylime.git && \ 78 | cd keylime && \ 79 | sed -e 's/127.0.0.1/0.0.0.0/g' keylime-agent.conf > tmp_keylime-agent.conf && \ 80 | mv tmp_keylime-agent.conf keylime-agent.conf && \ 81 | python3 ${KEYLIME_HOME}/setup.py install && \ 82 | pip3 install -r $KEYLIME_HOME/requirements.txt && \ 83 | ${KEYLIME_HOME}/services/installer.sh 84 | 85 | RUN dnf makecache && \ 86 | dnf clean all && \ 87 | rm -rf /var/cache/dnf/* 88 | -------------------------------------------------------------------------------- /docker/fedora/keylime_rust.Dockerfile: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # keylime TPM 2.0 Rust Dockerfile 3 | # 4 | # This file is for automatic test running of Keylime and rust-keylime. 5 | # It is not recommended for use beyond testing scenarios. 6 | ############################################################################## 7 | 8 | FROM fedora:latest 9 | LABEL version="2.0.1" description="Keylime - Bootstrapping and Maintaining Trust in the Cloud" 10 | 11 | # environment variables 12 | ARG BRANCH=master 13 | ENV RUST_KEYLIME_HOME ${HOME}/rust-keylime 14 | ENV container docker 15 | COPY dbus-policy.conf /etc/dbus-1/system.d/ 16 | 17 | # Install dev tools and libraries (includes openssl-devel) 18 | RUN dnf groupinstall -y \ 19 | "Development Tools" \ 20 | "Development Libraries" 21 | 22 | # Packaged dependencies 23 | ENV PKGS_DEPS="automake \ 24 | cargo \ 25 | clang-devel \ 26 | dbus \ 27 | dbus-daemon \ 28 | dbus-devel \ 29 | dnf-plugins-core \ 30 | efivar-devel \ 31 | gcc \ 32 | git \ 33 | glib2-devel \ 34 | glib2-static \ 35 | gnulib \ 36 | kmod \ 37 | llvm llvm-devel \ 38 | libselinux-python3 \ 39 | libtool \ 40 | libtpms \ 41 | make \ 42 | openssl-devel \ 43 | redhat-rpm-config \ 44 | rust clippy cargo \ 45 | pkg-config \ 46 | swtpm \ 47 | swtpm-tools \ 48 | tpm2-abrmd \ 49 | tpm2-tools \ 50 | tpm2-tss \ 51 | tpm2-tss-devel \ 52 | uthash-devel \ 53 | czmq-devel" 54 | 55 | RUN dnf makecache && \ 56 | dnf -y install $PKGS_DEPS && \ 57 | dnf clean all && \ 58 | rm -rf /var/cache/dnf/* 59 | 60 | # clone and build rust-keylime 61 | WORKDIR ${RUST_KEYLIME_HOME} 62 | RUN git clone https://github.com/keylime/rust-keylime.git && \ 63 | cd rust-keylime && \ 64 | make && \ 65 | make install && \ 66 | cargo clean 67 | -------------------------------------------------------------------------------- /docker/fedora/wait.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: Apache-2.0 3 | # Copyright 2021 Keylime Authors 4 | # 5 | # wait.sh 6 | 7 | set -e 8 | 9 | file="$1" 10 | shift 11 | cmd="$@" 12 | 13 | # while ! nc -z $host 8881; 14 | while ! test -f $file; 15 | do 16 | >&2 echo "Verifier certificate unavailable - sleeping" 17 | sleep 2 18 | done 19 | 20 | >&2 echo "Verifier certificate available - executing registrar" 21 | exec $cmd -------------------------------------------------------------------------------- /docker/release/Dockerfile.distroless: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # the builder stage which uses the official rust base image (based on Debian 11) to build the keylime agent 3 | FROM rust:1.70-bullseye AS builder 4 | 5 | # we are using the "generate-bindings" feature for the tss-esapi crate which requires clang/llvm 6 | RUN apt-get update && apt-get install -y --no-install-recommends clang llvm 7 | 8 | # Install tpm2-tss (dependency for the tss-esapi crate) 9 | WORKDIR /src 10 | RUN wget https://github.com/tpm2-software/tpm2-tss/releases/download/4.0.1/tpm2-tss-4.0.1.tar.gz 11 | RUN tar xf tpm2-tss-4.0.1.tar.gz 12 | WORKDIR /src/tpm2-tss-4.0.1 13 | RUN ./configure \ 14 | --prefix=/usr \ 15 | --disable-static \ 16 | --disable-fapi \ 17 | --disable-policy \ 18 | --disable-doxygen-doc \ 19 | --disable-defaultflags 20 | RUN make 21 | RUN make install 22 | 23 | # build rust-keylime 24 | COPY . /src/rust-keylime/ 25 | WORKDIR /src/rust-keylime 26 | RUN make RELEASE=1 TARGETDIR=target target/release/keylime_agent 27 | 28 | # truly just for debugging purposes for the assembly stage 29 | RUN readelf -W \ 30 | --file-header --program-headers --sections --dynamic --notes --version-info --arch-specific --unwind --section-groups --histogram \ 31 | /src/rust-keylime/target/release/keylime_agent 32 | RUN ldd /src/rust-keylime/target/release/keylime_agent 33 | 34 | # now assemble a release docker image using a minimal docker image 35 | FROM gcr.io/distroless/cc-debian11:latest 36 | ARG VERSION=latest 37 | LABEL org.opencontainers.image.authors="Keylime Team " 38 | LABEL org.opencontainers.image.version="$VERSION" 39 | LABEL org.opencontainers.image.title="Keylime Agent" 40 | LABEL org.opencontainers.image.description="Keylime Agent - Bootstrapping and Maintaining Trust in the Cloud" 41 | LABEL org.opencontainers.image.url="https://keylime.dev/" 42 | LABEL org.opencontainers.image.source="https://github.com/keylime/rust-keylime/" 43 | LABEL org.opencontainers.image.licenses="Apache-2.0" 44 | LABEL org.opencontainers.image.vendor="The Keylime Authors" 45 | 46 | # Copy all agent dependencies from the builder image 47 | # NOTE: the cc base image comes with all C runtime dependencies (libc, libm, libgcc, etc.), so no need to copy those 48 | # TODO: Unfortunately the COPY directive is following links and not preserving the link file. This slightly bloats the image. 49 | 50 | # libz is a direct dependency for the zip crate 51 | COPY --from=builder \ 52 | /lib/x86_64-linux-gnu/libz.so* \ 53 | /usr/lib/x86_64-linux-gnu/ 54 | # tpm2-tss libraries are a dependency (probably not all of them, but we just copy all) 55 | # because we are using the tss-esapi crate which is essentially just a wrapper around those (unfortunately) 56 | COPY --from=builder \ 57 | /usr/lib/libtss2*.so* \ 58 | /usr/lib/x86_64-linux-gnu/ 59 | 60 | # now copy the agent from the builder 61 | COPY --from=builder /src/rust-keylime/target/release/keylime_agent /bin/keylime_agent 62 | COPY --from=builder /src/rust-keylime/keylime-agent.conf /etc/keylime/agent.conf 63 | ENTRYPOINT ["/bin/keylime_agent"] 64 | 65 | # we default the log level to info if not overwritten 66 | ENV RUST_LOG=keylime_agent=info 67 | 68 | # the agent currently listens on this port by default 69 | # it's good practice to declare this in the Dockerfile 70 | EXPOSE 9002/tcp 71 | 72 | # these are all podman labels that work with the 'podman container runlabel' command, and are standardized at least in RHEL (install, uninstall, run) 73 | LABEL install="podman volume create keylime-agent" 74 | LABEL uninstall="podman volume rm keylime-agent" 75 | LABEL run="podman run --read-only --name keylime-agent --rm --device /dev/tpm0 --device /dev/tpmrm0 -v keylime-agent:/var/lib/keylime -v /etc/keylime:/etc/keylime:ro --tmpfs /var/lib/keylime/secure:rw,size=1m,mode=0700 -dt IMAGE" 76 | 77 | # run as root by default 78 | USER 0:0 79 | -------------------------------------------------------------------------------- /docker/release/Dockerfile.fedora: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # the builder stage which uses the latest Fedora minimal image to build the keylime agent - we know this works well 3 | FROM registry.fedoraproject.org/fedora-minimal AS builder 4 | 5 | # Packaged dependencies 6 | RUN microdnf install -y \ 7 | cargo \ 8 | clang \ 9 | clang-devel \ 10 | dnf-plugins-core \ 11 | git \ 12 | make \ 13 | openssl-devel \ 14 | rust \ 15 | systemd \ 16 | tpm2-tss \ 17 | tpm2-tss-devel 18 | 19 | # build rust-keylime 20 | COPY . /src/rust-keylime/ 21 | WORKDIR /src/rust-keylime 22 | RUN make RELEASE=1 TARGETDIR=target target/release/keylime_agent 23 | 24 | # now assemble a release docker image using a fedora minimal base image 25 | FROM registry.fedoraproject.org/fedora-minimal 26 | ARG VERSION=latest 27 | LABEL org.opencontainers.image.authors="Keylime Team " 28 | LABEL org.opencontainers.image.version="$VERSION" 29 | LABEL org.opencontainers.image.title="Keylime Agent" 30 | LABEL org.opencontainers.image.description="Keylime Agent - Bootstrapping and Maintaining Trust in the Cloud" 31 | LABEL org.opencontainers.image.url="https://keylime.dev/" 32 | LABEL org.opencontainers.image.source="https://github.com/keylime/rust-keylime/" 33 | LABEL org.opencontainers.image.licenses="Apache-2.0" 34 | LABEL org.opencontainers.image.vendor="The Keylime Authors" 35 | 36 | # these labels are set in the fedora base image and should be overwritten 37 | LABEL name="Keylime Agent" 38 | LABEL version="$VERSION" 39 | LABEL license="Apache-2.0" 40 | LABEL vendor="The Keylime Authors" 41 | 42 | # Install all agent runtime dependencies from the builder image 43 | # NOTE: the fedora base image is "fat" and comes with basically all dependencies that we need out of the box with a few exceptions 44 | RUN microdnf makecache && \ 45 | microdnf -y install tpm2-tss openssl util-linux-core && \ 46 | microdnf clean all && \ 47 | rm -rf /var/cache/dnf/* 48 | 49 | # now copy the agent from the builder 50 | COPY --from=builder /src/rust-keylime/target/release/keylime_agent /bin/keylime_agent 51 | COPY --from=builder /src/rust-keylime/keylime-agent.conf /etc/keylime/agent.conf 52 | ENTRYPOINT ["/bin/keylime_agent"] 53 | 54 | # we default the log level to info if not overwritten 55 | ENV RUST_LOG=keylime_agent=info 56 | 57 | # the agent currently listens on this port by default 58 | # it's good practice to declare this in the Dockerfile 59 | EXPOSE 9002 60 | 61 | # these are all podman labels that work with the 'podman container runlabel' command, and are standardized at least in RHEL (install, uninstall, run) 62 | LABEL install="podman volume create keylime-agent" 63 | LABEL uninstall="podman volume rm keylime-agent" 64 | LABEL run="podman run --read-only --name keylime-agent --rm --device /dev/tpm0 --device /dev/tpmrm0 -v keylime-agent:/var/lib/keylime -v /etc/keylime:/etc/keylime:ro --tmpfs /var/lib/keylime/secure:rw,size=1m,mode=0700 -dt IMAGE" 65 | 66 | # Create a system user 'keylime' to allow dropping privileges 67 | RUN useradd -s /sbin/nologin -r -G tss keylime 68 | 69 | # run as root by default 70 | USER 0:0 71 | -------------------------------------------------------------------------------- /docker/release/Dockerfile.wolfi: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # the builder stage which uses the wolfi-base image to build the keylime agent 3 | # NOTE: even though there is a rust wolfi base image, it does not ship apk which we need to add some dependencies 4 | FROM cgr.dev/chainguard/wolfi-base AS builder 5 | 6 | # update apk 7 | RUN apk update 8 | 9 | # we are installing build dependencies, describe them per line as they are added: 10 | # - install rust 11 | # - install gcc and others to compile tpm2-tss 12 | # - we are using the "generate-bindings" feature for the tss-esapi crate which requires clang/llvm 13 | RUN apk add --no-cache --update-cache \ 14 | rust \ 15 | make pkgconf gcc glibc glibc-dev openssl openssl-dev posix-libc-utils \ 16 | clang-17 clang-17-dev wget 17 | 18 | # Install tpm2-tss (dependency for the tss-esapi crate) 19 | WORKDIR /src 20 | RUN wget https://github.com/tpm2-software/tpm2-tss/releases/download/4.0.1/tpm2-tss-4.0.1.tar.gz 21 | RUN tar xf tpm2-tss-4.0.1.tar.gz 22 | WORKDIR /src/tpm2-tss-4.0.1 23 | RUN ./configure \ 24 | --prefix=/usr \ 25 | --disable-static \ 26 | --disable-fapi \ 27 | --disable-policy \ 28 | --disable-doxygen-doc \ 29 | --disable-defaultflags \ 30 | --disable-dependency-tracking 31 | RUN make 32 | RUN make install 33 | 34 | # build rust-keylime 35 | COPY . /src/rust-keylime/ 36 | WORKDIR /src/rust-keylime 37 | RUN make RELEASE=1 TARGETDIR=target target/release/keylime_agent 38 | 39 | # truly just for debugging purposes for the assembly stage 40 | RUN readelf -W \ 41 | --file-header --program-headers --sections --dynamic --notes --version-info --arch-specific --unwind --section-groups --histogram \ 42 | /src/rust-keylime/target/release/keylime_agent 43 | RUN ldd /src/rust-keylime/target/release/keylime_agent 44 | 45 | # now assemble a release docker image using a minimal docker image 46 | FROM cgr.dev/chainguard/cc-dynamic:latest 47 | ARG VERSION=latest 48 | LABEL org.opencontainers.image.authors="Keylime Team " 49 | LABEL org.opencontainers.image.version="$VERSION" 50 | LABEL org.opencontainers.image.title="Keylime Agent" 51 | LABEL org.opencontainers.image.description="Keylime Agent - Bootstrapping and Maintaining Trust in the Cloud" 52 | LABEL org.opencontainers.image.url="https://keylime.dev/" 53 | LABEL org.opencontainers.image.source="https://github.com/keylime/rust-keylime/" 54 | LABEL org.opencontainers.image.licenses="Apache-2.0" 55 | LABEL org.opencontainers.image.vendor="The Keylime Authors" 56 | 57 | # Copy all agent dependencies from the builder image 58 | # NOTE: the cc base image comes with all C runtime dependencies (libc, libm, libgcc_s, etc.), so no need to copy those 59 | # TODO: Unfortunately the COPY directive is following links and not preserving the link file. This slightly bloats the image. 60 | 61 | # openssl (both crypto and ssl libraries) are direct dependencies 62 | COPY --from=builder \ 63 | /usr/lib/libcrypto.so* \ 64 | /usr/lib/libssl.so* \ 65 | /usr/lib/ 66 | 67 | # libz is a direct dependency for the zip crate 68 | COPY --from=builder \ 69 | /lib/libz.so* \ 70 | /lib/ 71 | 72 | # tpm2-tss libraries are a dependency (probably not all of them, but we just copy all) 73 | # because we are using the tss-esapi crate which is essentially just a wrapper around those (unfortunately) 74 | COPY --from=builder \ 75 | /usr/lib/libtss2*.so* \ 76 | /usr/lib/ 77 | 78 | # now copy the agent from the builder 79 | COPY --from=builder /src/rust-keylime/target/release/keylime_agent /bin/keylime_agent 80 | COPY --from=builder /src/rust-keylime/keylime-agent.conf /etc/keylime/agent.conf 81 | ENTRYPOINT ["/bin/keylime_agent"] 82 | 83 | # we default the log level to info if not overwritten 84 | ENV RUST_LOG=keylime_agent=info 85 | 86 | # the agent currently listens on this port by default 87 | # it's good practice to declare this in the Dockerfile 88 | EXPOSE 9002/tcp 89 | 90 | # these are all podman labels that work with the 'podman container runlabel' command, and are standardized at least in RHEL (install, uninstall, run) 91 | LABEL install="podman volume create keylime-agent" 92 | LABEL uninstall="podman volume rm keylime-agent" 93 | LABEL run="podman run --read-only --name keylime-agent --rm --device /dev/tpm0 --device /dev/tpmrm0 -v keylime-agent:/var/lib/keylime -v /etc/keylime:/etc/keylime:ro --tmpfs /var/lib/keylime/secure:rw,size=1m,mode=0700 -dt IMAGE" 94 | 95 | # run as root by default 96 | USER 0:0 97 | -------------------------------------------------------------------------------- /docker/release/build_locally.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: Apache-2.0 3 | # Copyright 2023 Keylime Authors 4 | 5 | # Build Docker container locally 6 | 7 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 8 | 9 | if [ "$1" = "--help" -o "$1" = "-h" ] ; then 10 | echo "USAGE: $0 [VERSION] [KEYLIME_DIR] [DOCKER_BUILDX_FLAGS...]" 1>&2 11 | echo 1>&2 12 | echo "Examples:" 1>&2 13 | echo "$0" 1>&2 14 | echo "$0 15.1.2" 1>&2 15 | echo "$0 15.1.2 /different/source/folder" 1>&2 16 | echo "$0 15.1.2 /source/folder --pull --push" 1>&2 17 | echo "DOCKERFILE_TYPE=fedora $0" 1>&2 18 | echo "DOCKERFILE_TYPE=wolfi $0" 1>&2 19 | echo 1>&2 20 | exit 0 21 | fi 22 | 23 | VERSION=${1:-latest} 24 | $( cd -- "${SCRIPT_DIR}/../dev/images" &> /dev/null && pwd ) 25 | KEYLIME_DIR=${2:-$( cd -- "${SCRIPT_DIR}/../../" &>/dev/null && pwd )} 26 | # TODO: why is shift N not working? 27 | shift 28 | shift 29 | DOCKER_BUILDX_FLAGS=${@:-"--load"} 30 | 31 | # overwrite this with one of the following: 32 | # - distroless (default) 33 | # - fedora 34 | # - wolfi 35 | DOCKERFILE_TYPE="${DOCKERFILE_TYPE:-distroless}" 36 | 37 | LOG_DIR=${LOG_DIR:-/tmp} 38 | DOCKERFILE="${SCRIPT_DIR}/Dockerfile.${DOCKERFILE_TYPE}" 39 | 40 | docker buildx build \ 41 | -f $DOCKERFILE \ 42 | -t keylime_agent:${VERSION}-${DOCKERFILE_TYPE} \ 43 | --progress=plain \ 44 | --platform=linux/amd64 \ 45 | --build-arg VERSION="$VERSION" \ 46 | $DOCKER_BUILDX_FLAGS $KEYLIME_DIR 2>&1 | tee $LOG_DIR/docker-keylime-agent-build.log 47 | docker tag keylime_agent:${VERSION}-${DOCKERFILE_TYPE} keylime_agent:${VERSION} 48 | -------------------------------------------------------------------------------- /ima-policy/README: -------------------------------------------------------------------------------- 1 | # Notes about the IMA policy 2 | 3 | This IMA policy is provided as an example that can be later adapted to 4 | more specific usage. 5 | 6 | This was generated from a default tcb IMA policy from a 6.1.12 Linux 7 | kernel, and extended with SELinux file types to filter out the part of 8 | the system that we usually do not want to measure. 9 | 10 | To use this policy, we need to copy it in "/etc/ima/ima-policy" and 11 | systemd will load it after the SELinux policy has been loaded. 12 | 13 | For this example, we used the initial set of SELinux attributes, that 14 | group the file types under categories. From that list we selected 15 | some of those attribute to deep more into the types that can be relevant for the IMA policy: 16 | 17 | seinfo -a 18 | 19 | The current selection covers fully or partially the types under those 20 | attributes: 21 | 22 | base_file_type 23 | base_ro_file_type 24 | configfile 25 | file_type 26 | files_unconfined_type 27 | init_script_file_type 28 | init_sock_file_type 29 | lockfile 30 | logfile 31 | non_auth_file_type 32 | non_security_file_type 33 | openshift_file_type 34 | pidfile 35 | pulseaudio_tmpfsfile 36 | security_file_type 37 | setfiles_domain 38 | spoolfile 39 | svirt_file_type 40 | systemd_unit_file_type 41 | tmpfile 42 | tmpfsfile 43 | 44 | Special mention to non_auth_file_type and non_security_file_type 45 | (among others like logfile or tmpfile), that should cover the most 46 | relevant types of the dynamic part of the system. 47 | 48 | The list should also include types from other attributes like 49 | virt_image_type and others (see the policy file comments from a 50 | complete list). 51 | 52 | Sometimes is important to see what files are labeled under a specific 53 | type, and for that we can use this: 54 | 55 | semanage fcontext -l | grep $TYPE 56 | -------------------------------------------------------------------------------- /keylime-agent/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "keylime_agent" 3 | description = "Rust agent for Keylime" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | version.workspace = true 9 | 10 | [dependencies] 11 | actix-web.workspace = true 12 | base64.workspace = true 13 | cfg-if.workspace = true 14 | clap.workspace = true 15 | config.workspace = true 16 | futures.workspace = true 17 | glob.workspace = true 18 | hex.workspace = true 19 | keylime.workspace = true 20 | libc.workspace = true 21 | log.workspace = true 22 | openssl.workspace = true 23 | pretty_env_logger.workspace = true 24 | reqwest.workspace = true 25 | serde.workspace = true 26 | serde_derive.workspace = true 27 | serde_json.workspace = true 28 | static_assertions.workspace = true 29 | tempfile.workspace = true 30 | tokio.workspace = true 31 | tss-esapi.workspace = true 32 | thiserror.workspace = true 33 | uuid.workspace = true 34 | zip.workspace = true 35 | zmq = {version = "0.9.2", optional = true} 36 | 37 | [dev-dependencies] 38 | actix-rt.workspace = true 39 | 40 | [features] 41 | # The features enabled by default 42 | default = [] 43 | testing = [] 44 | # Whether the agent should be compiled with support to listen for notification 45 | # messages on ZeroMQ 46 | # 47 | # This feature is deprecated and will be removed on next major release 48 | with-zmq = ["zmq"] 49 | # Whether the agent should be compiled with support for python revocation 50 | # actions loaded as modules, which is the only kind supported by the python 51 | # agent (unless the enhancement-55 is implemented). See: 52 | # https://github.com/keylime/enhancements/blob/master/55_revocation_actions_without_python.md 53 | # 54 | # This feature is deprecated and will be removed on next major release 55 | legacy-python-actions = [] 56 | 57 | [package.metadata.deb] 58 | section = "net" 59 | assets = [ 60 | ["target/release/keylime_agent", "usr/bin/", "755"], 61 | ["../README.md", "usr/share/doc/keylime-agent/README", "644"], 62 | ["../keylime-agent.conf", "/etc/keylime/agent.conf", "640"], 63 | ["../dist/systemd/system/var-lib-keylime-secure.mount", "lib/systemd/system/var-lib-keylime-secure.mount", "644"], 64 | ["../tests/actions/shim.py", "usr/libexec/keylime/shim.py", "755"], 65 | ] 66 | maintainer-scripts = "../debian/" 67 | systemd-units = { unit-scripts = "../dist/systemd/system/", enable = true } 68 | -------------------------------------------------------------------------------- /keylime-agent/src/agent_handler.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2023 Keylime Authors 3 | 4 | use crate::common::JsonWrapper; 5 | use crate::{tpm, Error as KeylimeError, QuoteData}; 6 | use actix_web::{http, web, HttpRequest, HttpResponse, Responder}; 7 | use base64::{engine::general_purpose, Engine as _}; 8 | use log::*; 9 | use serde::{Deserialize, Serialize}; 10 | 11 | #[derive(Serialize, Deserialize, Debug)] 12 | pub(crate) struct AgentInfo { 13 | pub agent_uuid: String, 14 | pub tpm_hash_alg: String, 15 | pub tpm_enc_alg: String, 16 | pub tpm_sign_alg: String, 17 | pub ak_handle: u32, 18 | } 19 | 20 | // This is an Info request which gets some information about this keylime agent 21 | // It should return a AgentInfo object as JSON 22 | async fn info( 23 | req: HttpRequest, 24 | data: web::Data>, 25 | ) -> impl Responder { 26 | debug!("Returning agent information"); 27 | 28 | let mut info = AgentInfo { 29 | agent_uuid: data.agent_uuid.clone(), 30 | tpm_hash_alg: data.hash_alg.to_string(), 31 | tpm_enc_alg: data.enc_alg.to_string(), 32 | tpm_sign_alg: data.sign_alg.to_string(), 33 | ak_handle: data.ak_handle.value(), 34 | }; 35 | 36 | let response = JsonWrapper::success(info); 37 | info!("GET info returning 200 response"); 38 | HttpResponse::Ok().json(response) 39 | } 40 | 41 | /// Configure the endpoints for the /agent scope 42 | async fn agent_default(req: HttpRequest) -> impl Responder { 43 | let error; 44 | let response; 45 | let message; 46 | 47 | match req.head().method { 48 | http::Method::GET => { 49 | error = 400; 50 | message = "URI not supported, only /info is supported for GET in /agent interface"; 51 | response = HttpResponse::BadRequest() 52 | .json(JsonWrapper::error(error, message)); 53 | } 54 | _ => { 55 | error = 405; 56 | message = "Method is not supported in /agent interface"; 57 | response = HttpResponse::MethodNotAllowed() 58 | .insert_header(http::header::Allow(vec![http::Method::GET])) 59 | .json(JsonWrapper::error(error, message)); 60 | } 61 | }; 62 | 63 | warn!( 64 | "{} returning {} response. {}", 65 | req.head().method, 66 | error, 67 | message 68 | ); 69 | 70 | response 71 | } 72 | 73 | /// Configure the endpoints for the /agents scope 74 | pub(crate) fn configure_agent_endpoints(cfg: &mut web::ServiceConfig) { 75 | _ = cfg 76 | .service(web::resource("/info").route(web::get().to(info))) 77 | .default_service(web::to(agent_default)); 78 | } 79 | 80 | #[cfg(test)] 81 | #[cfg(feature = "testing")] 82 | mod tests { 83 | use super::*; 84 | use actix_web::{test, web, App}; 85 | use serde_json::{json, Value}; 86 | 87 | #[actix_rt::test] 88 | async fn test_agent_info() { 89 | let (mut quotedata, mutex) = QuoteData::fixture().await.unwrap(); //#[allow_ci] 90 | quotedata.hash_alg = keylime::algorithms::HashAlgorithm::Sha256; 91 | quotedata.enc_alg = keylime::algorithms::EncryptionAlgorithm::Rsa2048; 92 | quotedata.sign_alg = keylime::algorithms::SignAlgorithm::RsaSsa; 93 | quotedata.agent_uuid = "DEADBEEF".to_string(); 94 | let data = web::Data::new(quotedata); 95 | let mut app = test::init_service( 96 | App::new() 97 | .app_data(data.clone()) 98 | .route("/vX.Y/agent/info", web::get().to(info)), 99 | ) 100 | .await; 101 | 102 | let req = test::TestRequest::get() 103 | .uri("/vX.Y/agent/info") 104 | .to_request(); 105 | 106 | let resp = test::call_service(&app, req).await; 107 | assert!(resp.status().is_success()); 108 | 109 | let result: JsonWrapper = test::read_body_json(resp).await; 110 | assert_eq!(result.results.agent_uuid.as_str(), "DEADBEEF"); 111 | assert_eq!(result.results.tpm_hash_alg.as_str(), "sha256"); 112 | assert_eq!(result.results.tpm_enc_alg.as_str(), "rsa"); 113 | assert_eq!(result.results.tpm_sign_alg.as_str(), "rsassa"); 114 | 115 | // Explicitly drop QuoteData to cleanup keys 116 | drop(data); 117 | } 118 | 119 | #[actix_rt::test] 120 | async fn test_agents_default() { 121 | let mut app = test::init_service( 122 | App::new().service(web::resource("/").to(agent_default)), 123 | ) 124 | .await; 125 | 126 | let req = test::TestRequest::get().uri("/").to_request(); 127 | 128 | let resp = test::call_service(&app, req).await; 129 | assert!(resp.status().is_client_error()); 130 | 131 | let result: JsonWrapper = test::read_body_json(resp).await; 132 | 133 | assert_eq!(result.results, json!({})); 134 | assert_eq!(result.code, 400); 135 | 136 | let req = test::TestRequest::delete().uri("/").to_request(); 137 | 138 | let resp = test::call_service(&app, req).await; 139 | assert!(resp.status().is_client_error()); 140 | 141 | let headers = resp.headers(); 142 | 143 | assert!(headers.contains_key("allow")); 144 | assert_eq!(headers.get("allow").unwrap().to_str().unwrap(), "GET"); //#[allow_ci] 145 | 146 | let result: JsonWrapper = test::read_body_json(resp).await; 147 | 148 | assert_eq!(result.results, json!({})); 149 | assert_eq!(result.code, 405); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /keylime-agent/src/common.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2021 Keylime Authors 3 | 4 | use keylime::keylime_error::{Error, Result}; 5 | 6 | use crate::permissions; 7 | 8 | use keylime::algorithms::{ 9 | EncryptionAlgorithm, HashAlgorithm, SignAlgorithm, 10 | }; 11 | use keylime::{ 12 | crypto::{hash, tss_pubkey_to_pem}, 13 | hash_ek, tpm, 14 | }; 15 | use log::*; 16 | use openssl::hash::MessageDigest; 17 | use serde::{Deserialize, Serialize}; 18 | use serde_json::{json, Value}; 19 | use std::{ 20 | convert::{Into, TryFrom, TryInto}, 21 | env, 22 | ffi::CString, 23 | fmt::{self, Debug, Display}, 24 | fs::File, 25 | path::{Path, PathBuf}, 26 | str::FromStr, 27 | }; 28 | use tss_esapi::structures::{Private, Public}; 29 | use tss_esapi::traits::Marshall; 30 | use tss_esapi::utils::PublicKey; 31 | use tss_esapi::{ 32 | structures::PcrSlot, traits::UnMarshall, utils::TpmsContext, 33 | }; 34 | 35 | #[derive(Serialize, Deserialize, Debug)] 36 | pub(crate) struct APIVersion { 37 | major: u32, 38 | minor: u32, 39 | } 40 | 41 | impl Display for APIVersion { 42 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 43 | write!(f, "v{}.{}", self.major, self.minor) 44 | } 45 | } 46 | 47 | #[derive(Serialize, Deserialize, Debug)] 48 | pub(crate) struct JsonWrapper { 49 | pub code: u16, 50 | pub status: String, 51 | pub results: A, 52 | } 53 | 54 | impl JsonWrapper { 55 | pub(crate) fn error( 56 | code: u16, 57 | status: impl ToString, 58 | ) -> JsonWrapper { 59 | JsonWrapper { 60 | code, 61 | status: status.to_string(), 62 | results: json!({}), 63 | } 64 | } 65 | } 66 | 67 | impl<'de, A> JsonWrapper 68 | where 69 | A: Deserialize<'de> + Serialize + Debug, 70 | { 71 | pub(crate) fn success(results: A) -> JsonWrapper { 72 | JsonWrapper { 73 | code: 200, 74 | status: String::from("Success"), 75 | results, 76 | } 77 | } 78 | } 79 | 80 | #[cfg(test)] 81 | mod tests { 82 | use super::*; 83 | use crate::config::KeylimeConfig; 84 | use keylime::agent_data::AgentData; 85 | use keylime::algorithms::{ 86 | EncryptionAlgorithm, HashAlgorithm, SignAlgorithm, 87 | }; 88 | use keylime::hash_ek; 89 | use std::convert::TryFrom; 90 | use tss_esapi::{ 91 | handles::KeyHandle, 92 | interface_types::algorithm::AsymmetricAlgorithm, 93 | interface_types::resource_handles::Hierarchy, 94 | structures::{Auth, PublicBuffer}, 95 | traits::Marshall, 96 | Context, 97 | }; 98 | 99 | #[tokio::test] 100 | #[cfg(feature = "testing")] 101 | async fn test_agent_data() -> Result<()> { 102 | let _mutex = tpm::testing::lock_tests().await; 103 | let mut config = KeylimeConfig::default(); 104 | 105 | let mut ctx = tpm::Context::new()?; 106 | 107 | let tpm_encryption_alg = EncryptionAlgorithm::try_from( 108 | config.agent.tpm_encryption_alg.as_str(), 109 | )?; 110 | 111 | let tpm_hash_alg = 112 | HashAlgorithm::try_from(config.agent.tpm_hash_alg.as_str()) 113 | .expect("Failed to get hash algorithm"); 114 | 115 | let tpm_signing_alg = 116 | SignAlgorithm::try_from(config.agent.tpm_signing_alg.as_str()) 117 | .expect("Failed to get signing algorithm"); 118 | 119 | let ek_result = ctx 120 | .create_ek(tpm_encryption_alg, None) 121 | .expect("Failed to create EK"); 122 | 123 | let ek_hash = hash_ek::hash_ek_pubkey(ek_result.public) 124 | .expect("Failed to get pubkey"); 125 | 126 | let ak = ctx.create_ak( 127 | ek_result.key_handle, 128 | tpm_hash_alg, 129 | tpm_encryption_alg, 130 | tpm_signing_alg, 131 | )?; 132 | 133 | let agent_data_test = AgentData::create( 134 | tpm_hash_alg, 135 | tpm_signing_alg, 136 | &ak, 137 | ek_hash.as_bytes(), 138 | )?; 139 | 140 | let valid = AgentData::valid( 141 | &agent_data_test, 142 | tpm_hash_alg, 143 | tpm_signing_alg, 144 | ek_hash.as_bytes(), 145 | ); 146 | 147 | assert!(valid); 148 | 149 | // Cleanup created keys 150 | let ak_handle = ctx.load_ak(ek_result.key_handle, &ak)?; 151 | ctx.flush_context(ak_handle.into()); 152 | ctx.flush_context(ek_result.key_handle.into()); 153 | 154 | Ok(()) 155 | } 156 | 157 | #[tokio::test] 158 | #[cfg(feature = "testing")] 159 | async fn test_hash() -> Result<()> { 160 | use keylime::agent_data; 161 | use keylime::hash_ek; 162 | 163 | let _mutex = tpm::testing::lock_tests().await; 164 | let mut config = KeylimeConfig::default(); 165 | 166 | let mut ctx = tpm::Context::new()?; 167 | 168 | let tpm_encryption_alg = EncryptionAlgorithm::try_from( 169 | config.agent.tpm_encryption_alg.as_str(), 170 | ) 171 | .expect("Failed to get encryption algorithm"); 172 | 173 | let ek_result = ctx 174 | .create_ek(tpm_encryption_alg, None) 175 | .expect("Failed to create EK"); 176 | 177 | let result = hash_ek::hash_ek_pubkey(ek_result.public); 178 | 179 | assert!(result.is_ok()); 180 | 181 | // Cleanup created keys 182 | ctx.flush_context(ek_result.key_handle.into()); 183 | 184 | Ok(()) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /keylime-agent/src/notifications_handler.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2021 Keylime Authors 3 | 4 | use crate::{ 5 | common::JsonWrapper, 6 | revocation::{Revocation, RevocationMessage}, 7 | Error, QuoteData, Result, 8 | }; 9 | use actix_web::{http, web, HttpRequest, HttpResponse, Responder}; 10 | use log::*; 11 | use serde::{Deserialize, Serialize}; 12 | use std::path::{Path, PathBuf}; 13 | 14 | // This is Revocation request from the cloud verifier via REST API 15 | async fn revocation( 16 | body: web::Json, 17 | req: HttpRequest, 18 | data: web::Data>, 19 | ) -> impl Responder { 20 | info!("Received revocation"); 21 | 22 | match data 23 | .revocation_tx 24 | .send(RevocationMessage::Revocation(body.into_inner())) 25 | .await 26 | { 27 | Err(e) => { 28 | HttpResponse::InternalServerError().json(JsonWrapper::error( 29 | 500, 30 | "Fail to send Revocation message to revocation worker" 31 | .to_string(), 32 | )) 33 | } 34 | Ok(_) => HttpResponse::Ok().json(JsonWrapper::success(())), 35 | } 36 | } 37 | 38 | async fn notifications_default(req: HttpRequest) -> impl Responder { 39 | let error; 40 | let response; 41 | let message; 42 | 43 | match req.head().method { 44 | http::Method::POST => { 45 | error = 400; 46 | message = "URI not supported, only /revocation is supported for POST in /notifications/ interface"; 47 | response = HttpResponse::BadRequest() 48 | .json(JsonWrapper::error(error, message)); 49 | } 50 | _ => { 51 | error = 405; 52 | message = "Method is not supported in /notifications/ interface"; 53 | response = HttpResponse::MethodNotAllowed() 54 | .insert_header(http::header::Allow(vec![http::Method::POST])) 55 | .json(JsonWrapper::error(error, message)); 56 | } 57 | }; 58 | 59 | warn!( 60 | "{} returning {} response. {}", 61 | req.head().method, 62 | error, 63 | message 64 | ); 65 | 66 | response 67 | } 68 | 69 | /// Configure the endpoints for the /notifications scope 70 | pub(crate) fn configure_notifications_endpoints( 71 | cfg: &mut web::ServiceConfig, 72 | ) { 73 | _ = cfg 74 | .service( 75 | web::resource("/revocation").route(web::post().to(revocation)), 76 | ) 77 | .default_service(web::to(notifications_default)); 78 | } 79 | 80 | #[cfg(test)] 81 | mod tests { 82 | use super::*; 83 | use actix_rt::Arbiter; 84 | use actix_web::{test, web, App}; 85 | use serde_json::{json, Value}; 86 | use std::{fs, path::Path}; 87 | use tokio::sync::mpsc; 88 | 89 | #[actix_rt::test] 90 | async fn test_notifications_default() { 91 | let mut app = test::init_service( 92 | App::new().service(web::resource("/").to(notifications_default)), 93 | ) 94 | .await; 95 | 96 | let req = test::TestRequest::post() 97 | .uri("/") 98 | .data("some data") 99 | .to_request(); 100 | 101 | let resp = test::call_service(&app, req).await; 102 | assert!(resp.status().is_client_error()); 103 | 104 | let result: JsonWrapper = test::read_body_json(resp).await; 105 | 106 | assert_eq!(result.results, json!({})); 107 | assert_eq!(result.code, 400); 108 | 109 | let req = test::TestRequest::delete().uri("/").to_request(); 110 | 111 | let resp = test::call_service(&app, req).await; 112 | assert!(resp.status().is_client_error()); 113 | 114 | let headers = resp.headers(); 115 | 116 | assert!(headers.contains_key("allow")); 117 | assert_eq!( 118 | headers.get("allow").unwrap().to_str().unwrap(), //#[allow_ci] 119 | "POST" 120 | ); 121 | 122 | let result: JsonWrapper = test::read_body_json(resp).await; 123 | 124 | assert_eq!(result.results, json!({})); 125 | assert_eq!(result.code, 405); 126 | } 127 | 128 | #[cfg(feature = "testing")] 129 | #[actix_rt::test] 130 | async fn test_revocation() { 131 | let revocation_cert = Some( 132 | PathBuf::from(env!("CARGO_MANIFEST_DIR")) 133 | .join("test-data/test-cert.pem"), 134 | ); 135 | 136 | let revocation_actions_dir = Some( 137 | PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/actions"), 138 | ); 139 | 140 | let (mut fixture, mutex) = QuoteData::fixture().await.unwrap(); //#[allow_ci] 141 | 142 | // Replace the channels on the fixture with some local ones 143 | let (mut revocation_tx, mut revocation_rx) = 144 | mpsc::channel::(1); 145 | fixture.revocation_tx = revocation_tx; 146 | 147 | let quotedata = web::Data::new(fixture); 148 | 149 | let mut app = 150 | test::init_service(App::new().app_data(quotedata.clone()).route( 151 | "/vX.Y/notifications/revocation", 152 | web::post().to(revocation), 153 | )) 154 | .await; 155 | 156 | let sig_path = Path::new(env!("CARGO_MANIFEST_DIR")) 157 | .join("test-data/revocation.sig"); 158 | let signature = fs::read_to_string(sig_path).unwrap(); //#[allow_ci] 159 | 160 | let message_path = Path::new(env!("CARGO_MANIFEST_DIR")) 161 | .join("test-data/test_ok.json"); 162 | let message = fs::read_to_string(message_path).unwrap(); //#[allow_ci] 163 | 164 | let arbiter = Arbiter::new(); 165 | 166 | // Create the message body with the payload and signature 167 | let revocation = Revocation { 168 | msg: message.clone(), 169 | signature: signature.clone(), 170 | }; 171 | 172 | // Run fake revocation worker 173 | assert!(arbiter.spawn(Box::pin(async move { 174 | let m = revocation_rx.recv().await; 175 | assert!( 176 | m == Some(RevocationMessage::Revocation(Revocation { 177 | msg: message, 178 | signature, 179 | })) 180 | ) 181 | }))); 182 | 183 | let req = test::TestRequest::post() 184 | .uri("/vX.Y/notifications/revocation") 185 | .set_json(&revocation) 186 | .to_request(); 187 | 188 | let resp = test::call_service(&app, req).await; 189 | assert!(resp.status().is_success()); 190 | 191 | // Explicitly drop QuoteData to cleanup keys 192 | drop(quotedata); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /keylime-agent/src/permissions.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2021 Keylime Authors 3 | 4 | use keylime::keylime_error::{Error, Result}; 5 | use libc::{c_char, c_int, gid_t, uid_t}; 6 | use log::*; 7 | use std::os::unix::ffi::OsStrExt; 8 | use std::{ 9 | convert::{TryFrom, TryInto}, 10 | ffi::CString, 11 | io, 12 | path::Path, 13 | ptr, 14 | }; 15 | 16 | pub(crate) struct UserIds { 17 | passwd: libc::passwd, 18 | group: libc::group, 19 | } 20 | 21 | pub(crate) fn get_gid() -> gid_t { 22 | unsafe { libc::getgid() } 23 | } 24 | 25 | pub(crate) fn get_uid() -> uid_t { 26 | unsafe { libc::getuid() } 27 | } 28 | 29 | pub(crate) fn get_euid() -> uid_t { 30 | unsafe { libc::geteuid() } 31 | } 32 | 33 | impl TryFrom<&str> for UserIds { 34 | type Error = Error; 35 | 36 | fn try_from(value: &str) -> Result { 37 | let parts = value.split(':').collect::>(); 38 | 39 | if parts.len() != 2 { 40 | let e = format!("Invalid parameter format: {value} cannot be parsed as 'user:group'"); 41 | error!("{}", e); 42 | return Err(Error::Conversion(e)); 43 | } 44 | 45 | let user = parts[0]; 46 | let group = parts[1]; 47 | 48 | // Get gid from group name 49 | let grnam = if let Ok(g_cstr) = CString::new(group.as_bytes()) { 50 | let p = unsafe { libc::getgrnam(g_cstr.as_ptr()) }; 51 | if p.is_null() { 52 | let e = io::Error::last_os_error(); 53 | error!("Could not get group {}: {}", group, e); 54 | return Err(Error::Conversion(e.to_string())); 55 | } 56 | unsafe { (*p) } 57 | } else { 58 | return Err(Error::Conversion(format!( 59 | "Failed to convert {group} to CString" 60 | ))); 61 | }; 62 | 63 | // Get uid from user name 64 | let passwd = if let Ok(u_cstr) = CString::new(user.as_bytes()) { 65 | let p = unsafe { libc::getpwnam(u_cstr.as_ptr()) }; 66 | if p.is_null() { 67 | let e = io::Error::last_os_error(); 68 | error!("Could not get user {}: {}", user, e); 69 | return Err(Error::Conversion(e.to_string())); 70 | } 71 | unsafe { (*p) } 72 | } else { 73 | return Err(Error::Conversion(format!( 74 | "Failed to convert {user} to CString" 75 | ))); 76 | }; 77 | 78 | Ok(UserIds { 79 | passwd, 80 | group: grnam, 81 | }) 82 | } 83 | } 84 | 85 | // Drop the process privileges and run under the provided user and group. The correct order of 86 | // operations are: drop supplementary groups, set gid, then set uid. 87 | // See: POS36-C and CWE-696 88 | pub(crate) fn run_as(user_group: &str) -> Result<()> { 89 | let ids: UserIds = user_group.try_into()?; 90 | 91 | // Set gid 92 | if unsafe { libc::setgid(ids.group.gr_gid) } != 0 { 93 | let e = io::Error::last_os_error(); 94 | error!("Could not set group id: {}", e); 95 | return Err(Error::Permission); 96 | } 97 | 98 | // Get list of supplementary groups 99 | let mut sup_groups: [gid_t; 32] = [0u32; 32]; 100 | let mut ngroups: c_int = 32; 101 | if unsafe { 102 | libc::getgrouplist( 103 | ids.passwd.pw_name, 104 | ids.group.gr_gid, 105 | sup_groups.as_mut_ptr(), 106 | &mut ngroups, 107 | ) 108 | } < 0 109 | { 110 | // Allocate a Vec and try again 111 | let mut sup_groups: Vec = Vec::with_capacity(ngroups as usize); 112 | if unsafe { 113 | libc::getgrouplist( 114 | ids.passwd.pw_name, 115 | ids.group.gr_gid, 116 | sup_groups.as_mut_ptr(), 117 | &mut ngroups, 118 | ) 119 | } < 0 120 | { 121 | error!("Could not get list of supplementary groups"); 122 | return Err(Error::Permission); 123 | } 124 | } 125 | 126 | // Set supplementary groups 127 | if unsafe { libc::setgroups(ngroups as usize, sup_groups.as_ptr()) } != 0 128 | { 129 | let e = io::Error::last_os_error(); 130 | error!("Could not set supplementary groups: {}", e); 131 | return Err(Error::Permission); 132 | } 133 | 134 | // Set uid 135 | if unsafe { libc::setuid(ids.passwd.pw_uid) } != 0 { 136 | let e = io::Error::last_os_error(); 137 | error!("Could not set user id: {}", e); 138 | return Err(Error::Permission); 139 | } 140 | 141 | info!("Dropped privileges to run as {}", user_group); 142 | 143 | Ok(()) 144 | } 145 | 146 | pub(crate) fn chown(user_group: &str, path: &Path) -> Result<()> { 147 | let ids: UserIds = user_group.try_into()?; 148 | 149 | // check privilege 150 | if get_euid() != 0 { 151 | error!( 152 | "Privilege level unable to change file {} ownership", 153 | path.display() 154 | ); 155 | return Err(Error::Permission); 156 | } 157 | 158 | // change directory owner 159 | let c_path = CString::new(path.as_os_str().as_bytes())?; 160 | if unsafe { 161 | libc::chown(c_path.as_ptr(), ids.passwd.pw_uid, ids.group.gr_gid) 162 | } != 0 163 | { 164 | error!("Failed to change file {} owner.", path.display()); 165 | return Err(Error::Permission); 166 | } 167 | 168 | info!("Changed file {} owner to {}.", path.display(), user_group); 169 | Ok(()) 170 | } 171 | -------------------------------------------------------------------------------- /keylime-agent/src/secure_mount.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2021 Keylime Authors 3 | 4 | use super::*; 5 | 6 | use keylime::keylime_error::{Error, Result}; 7 | use std::fs; 8 | use std::io::BufRead; 9 | use std::os::unix::fs::PermissionsExt; 10 | use std::path::PathBuf; 11 | use std::process::Command; 12 | 13 | pub static MOUNTINFO: &str = "/proc/self/mountinfo"; 14 | 15 | /* 16 | * Check the mount status of the secure mount directory by parsing /proc/self/mountinfo content. 17 | * 18 | * /proc/[pid]/mountinfo have 10+ elements separated with spaces (check proc (5) for a complete 19 | * description) 20 | * 21 | * The elements of interest are the mount point (5th element), and the file system type (1st 22 | * element after the '-' separator). 23 | * 24 | * Input: secure mount directory path 25 | * Return: Result wrap boolean with error message 26 | * - true if directory is mounted 27 | * - false if not mounted 28 | * 29 | */ 30 | fn check_mount(secure_dir: &Path) -> Result { 31 | let f = fs::File::open(MOUNTINFO)?; 32 | let f = BufReader::new(f); 33 | let lines = f.lines(); 34 | 35 | for line in lines.map_while(std::result::Result::ok) { 36 | let mut iter = line.split(' '); 37 | if let Some(mount_point) = &iter.nth(4) { 38 | if Path::new(mount_point) == secure_dir { 39 | // Skip all fields up to the separator 40 | let mut iter = iter.skip_while(|&x| x != "-"); 41 | 42 | if let Some(separator) = iter.next() { 43 | // The file system type is the first element after the separator 44 | if let Some(fs_type) = iter.next() { 45 | if fs_type == "tmpfs" { 46 | debug!("Secure store location {} already mounted on tmpfs", secure_dir.display()); 47 | return Ok(true); 48 | } else { 49 | let message = format!("Secure storage location {} already mounted on wrong file system type: {}. Unmount to continue.", secure_dir.display(), fs_type); 50 | error!("Secure mount error: {}", message); 51 | return Err(Error::SecureMount(message)); 52 | } 53 | } else { 54 | let message = "Mount information parsing error: missing file system type".to_string(); 55 | error!("Secure mount error: {}", &message); 56 | return Err(Error::SecureMount(message)); 57 | } 58 | } else { 59 | let message = "Separator field not found. Information line cannot be parsed".to_string(); 60 | error!("Secure mount error: {}", &message); 61 | return Err(Error::SecureMount(message)); 62 | } 63 | } 64 | } else { 65 | let message = 66 | "Mount information parsing error: not enough elements" 67 | .to_string(); 68 | error!("Secure mount error: {}", message); 69 | return Err(Error::SecureMount(message)); 70 | } 71 | } 72 | debug!("Secure store location {} not mounted", secure_dir.display()); 73 | Ok(false) 74 | } 75 | 76 | /* 77 | * Return: Result wrap secure mount directory or error code 78 | * 79 | * Mounted the work directory as tmpfs, which is owned by root. Same 80 | * implementation as the original python version, but the chown/geteuid 81 | * functions are unsafe function in Rust to use. 82 | */ 83 | pub(crate) fn mount(work_dir: &Path, secure_size: &str) -> Result { 84 | // Mount the directory to file system 85 | let secure_dir_path = Path::new(work_dir).join("secure"); 86 | 87 | // If the directory is not mount to file system, mount the directory to 88 | // file system. 89 | if !check_mount(&secure_dir_path)? { 90 | // Create directory if the directory is not exist. The 91 | // directory permission is set to 448. 92 | if !secure_dir_path.exists() { 93 | fs::create_dir(&secure_dir_path).map_err(|e| { 94 | Error::SecureMount(format!( 95 | "unable to create secure dir path: {e:?}" 96 | )) 97 | })?; 98 | 99 | info!("Directory {:?} created.", secure_dir_path); 100 | let metadata = fs::metadata(&secure_dir_path).map_err(|e| { 101 | Error::SecureMount(format!( 102 | "unable to get metadata for secure dir path: {e:?}" 103 | )) 104 | })?; 105 | metadata.permissions().set_mode(0o750); // decimal 488 106 | } 107 | 108 | info!( 109 | "Mounting secure storage location {:?} on tmpfs.", 110 | &secure_dir_path 111 | ); 112 | 113 | // mount tmpfs with secure directory 114 | match Command::new("mount") 115 | .args([ 116 | "-t", 117 | "tmpfs", 118 | "-o", 119 | format!("size={secure_size},mode=0700").as_str(), 120 | "tmpfs", 121 | secure_dir_path.to_str().unwrap(), //#[allow_ci] 122 | ]) 123 | .output() 124 | { 125 | Ok(output) => { 126 | if !output.status.success() { 127 | return Err(Error::SecureMount(format!( 128 | "unable to mount tmpfs with secure dir: exit status code {}", 129 | output.status 130 | ))); 131 | } 132 | } 133 | Err(e) => { 134 | return Err(Error::SecureMount(format!( 135 | "unable to mount tmpfs with secure dir: {e}" 136 | ))); 137 | } 138 | } 139 | } 140 | 141 | Ok(secure_dir_path) 142 | } 143 | #[cfg(test)] 144 | mod tests { 145 | use super::*; 146 | 147 | #[test] 148 | fn test_secure_mount() { 149 | let temp_workdir = tempfile::tempdir().unwrap(); //#[allow_ci] 150 | let secure_size = "1m"; 151 | let test_mount = mount(temp_workdir.path(), secure_size); 152 | assert!(check_mount(temp_workdir.path()).is_ok()); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /keylime-agent/test-data/payload.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keylime/rust-keylime/07167a0def687d6b8d4bf31b4822ea97545d48d4/keylime-agent/test-data/payload.zip -------------------------------------------------------------------------------- /keylime-agent/test-data/revocation.sig: -------------------------------------------------------------------------------- 1 | RPRVARPrI+kvxnKye77IwlslokadrrOk//+Urn0xP5fjK6SOTu2pSZi5u1tSEc1o/C6RyawBOPyE+q3qxzbJxQamCKMjomYAsuHOoSnOKpN7L2GEGNA9FjhyDlDCGo5k4ZM6QcUvqeLqDKan7yBBINQyfogEVe+DcohevXYh6e01aBLT7zVASz1Qps5QzkuTt72K02TwbkBJoo/ZRyVMknBgP09xrXPMc1AkKKIquA1CAJ7Es6dMHWHb17L31gJsFWIaClmi5BiaQeMOTRzZ9ZJOKCZwi9xt38xUJGbhhY7MrVPvWN4bNuiePkU6/zAok5Pc/+dR0SLWnL3cxsocLA== -------------------------------------------------------------------------------- /keylime-agent/test-data/test-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDBTCCAe2gAwIBAgIUMdvVLurhVJw+zgZU+tDkLs5Gzo0wDQYJKoZIhvcNAQEL 3 | BQAwEjEQMA4GA1UEAwwHa2V5bGltZTAeFw0yMjAxMjYxNjM3NTRaFw0yMzAxMjYx 4 | NjM3NTRaMBIxEDAOBgNVBAMMB2tleWxpbWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB 5 | DwAwggEKAoIBAQDtx7FvpgAvcF46+UwVoETA+KmWrzPtpzai8BTmF4oOOX3GXMtp 6 | vtjDFCYVvmbUWeQN8LqMBKoJ0O9mzB82FtXZAggSMoIy8Gimcq0TqSNCWFRs61Ho 7 | KlkeJk5gcmgG1DiMzQ6Cp+A71aKrgheaxe4t44KkP6YldF6UAWduzUL3oJQ7QsQj 8 | IWA5i0fZu+ZyTqImo9NzN20KqMCawtvCXjwUmA4qVPGgne6S0GggCnTdd7LAb15/ 9 | XPexmu+OWMH8pcfzp4wTlqar/cfJpKnb5aaemOzwwIhEMfp4gTfXyVKMP+3qCp77 10 | KwbUyXDIMXBWssig85z7aGwVUmA00rQz5REfAgMBAAGjUzBRMB0GA1UdDgQWBBTw 11 | nW9LUu8SeBOQJcL0MHkxxiMhejAfBgNVHSMEGDAWgBTwnW9LUu8SeBOQJcL0MHkx 12 | xiMhejAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB7DSJa3I75 13 | e4Zgdmvzt9CUqvgxTUb2gzevXBk3QZclBogNXDoQNYYm1eifZ8PGNj14kDBwPWQi 14 | rt0hB99O0eety5qUj7ro8lRzd7uZ/TrDGyt/mUJt05DU4zeH9mLLspQFfQqq18sO 15 | 5ytnqfrLANV+a8WUgqj/e12pkIvPfzlm8UUKW8qniEdiyVvh1MW8lmnJnlGk0AJn 16 | fpdJO1jc+1c+MTngHN/K81e8Irn+Z9pR6xOmGpZdypnQfLJpHzCyE5vpLQEVxd28 17 | 3kts+VSvxSz1kaKI15mZHykWZ+L1DGYRG9Oopz49uPb9VjqUrSiWjc2lviLbEPeb 18 | pJmGJUTwt5ea 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /keylime-agent/test-data/test-rsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpgIBAAKCAQEA7cexb6YAL3BeOvlMFaBEwPiplq8z7ac2ovAU5heKDjl9xlzL 3 | ab7YwxQmFb5m1FnkDfC6jASqCdDvZswfNhbV2QIIEjKCMvBopnKtE6kjQlhUbOtR 4 | 6CpZHiZOYHJoBtQ4jM0OgqfgO9Wiq4IXmsXuLeOCpD+mJXRelAFnbs1C96CUO0LE 5 | IyFgOYtH2bvmck6iJqPTczdtCqjAmsLbwl48FJgOKlTxoJ3uktBoIAp03XeywG9e 6 | f1z3sZrvjljB/KXH86eME5amq/3HyaSp2+Wmnpjs8MCIRDH6eIE318lSjD/t6gqe 7 | +ysG1MlwyDFwVrLIoPOc+2hsFVJgNNK0M+URHwIDAQABAoIBAQCspZ8XAwAFceBp 8 | j5OH7Eufla2FRIc+2neYTRvPiW3rMCE7wyrLCBBZbKrOhOYi73XgDVdVzRktcXAy 9 | Qqmy21fAbnIvzE6u79H8cS1sJhX82SfLwf1BxmXYt1WXP9p6guLgkQ8lHQF6UH8B 10 | ar762RY8aYH1AmX/sgPuESrpz838/0v4Tq52yJeSiGOS3MG6iRWQ6/K0CHT3olbH 11 | RDN/48YrCSRP3RkAXoGcbev59+qOFJazC2av+qQM+59FwM2SrzrKa7RJLSQ1+wEz 12 | gZEmmgJu6tOVUnAd7mg5VS+N7cXu6+Xl1jCg1N7XaQWh3nRCI1bysJ9UJQQZHl0B 13 | gnIDXAU5AoGBAPex+wA1oaf4y22PIrVoLhD1HksdIxCOhmMYkojaIo3qZCVi8bS8 14 | JnUiHhvo25xYuUtB2Qw0VstzBaq6RDUnVgO3yVBYABc9MSmO+up/2ljNb9S5uWrm 15 | +FzqjCRIWwABTWVyI7zvgVKSuSbvVLAQkgwAEBibp26V5E5SmuB4uKcNAoGBAPXA 16 | m8n9Ajo+2Fzage0JZuW4uELbCqUwCkAlwJoSP42y0SIpB5fITkwRIwOat1MEhQ0I 17 | yh68ZxGvqfE2fO78ZbE2bSlKkkadrZjPFAG2svd57MdHnR5c4iCm6ui7D0snCgcg 18 | pUt2qlXYesABWb7o3tKOezDlbjFtv9JDJPOmnI3bAoGBAJw/RINsUW5BDiotWYqv 19 | jieaSCK/3YerMHDAZmc3mwaErem7kZcd/PB0tiOK70Wf3jrv7be6KGosQ43f8/jH 20 | uIWd4Lry2BPQwPtjOzrDrfvIk9vP0Hvz+QW72u1kSyskpyrwJkUfnCd3cJ5z6Ksr 21 | uMUjIQQ05BhpK1yQ1Sv2WxzdAoGBAKkLwd5SzOp1+mz83azI7+ALjaxnck4o2pQ/ 22 | o9oXvWHiZFuEL7Xn0nweuaAsF/jiPge2SRqVbKzM0jCb05qtQeKB1ts1caNjqVtY 23 | 7qEzJK55TzfRejG9oMrnJuXKbv26L/qxKSLc0NTWYbGb/DkHhOb/nZwH5iHYJcAj 24 | 8dIshLpLAoGBAK9RBWaP3KdDheUDOVpSSnOjVOtzq0T2qHPLklpdZzz2BHGKTNly 25 | qek7gOTiy/DvON0F/gFhVi0+7RnCG+CnMXe2Ac/vn3QqZLAL5pSc+AUhmfE72hn2 26 | HNz0GFbYmFqr8QpzXG7b4aCF/yxcPrHGN3LdBKZvPPv7OmcCPGVEOKVk 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /keylime-agent/test-data/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": "black", 5 | "category": "hue", 6 | "type": "primary", 7 | "code": { 8 | "rgba": [255,255,255,1], 9 | "hex": "#000" 10 | } 11 | }, 12 | { 13 | "color": "white", 14 | "category": "value", 15 | "code": { 16 | "rgba": [0,0,0,1], 17 | "hex": "#FFF" 18 | } 19 | }, 20 | { 21 | "color": "red", 22 | "category": "hue", 23 | "type": "primary", 24 | "code": { 25 | "rgba": [255,0,0,1], 26 | "hex": "#FF0" 27 | } 28 | }, 29 | { 30 | "color": "blue", 31 | "category": "hue", 32 | "type": "primary", 33 | "code": { 34 | "rgba": [0,0,255,1], 35 | "hex": "#00F" 36 | } 37 | }, 38 | { 39 | "color": "yellow", 40 | "category": "hue", 41 | "type": "primary", 42 | "code": { 43 | "rgba": [255,255,0,1], 44 | "hex": "#FF0" 45 | } 46 | }, 47 | { 48 | "color": "green", 49 | "category": "hue", 50 | "type": "secondary", 51 | "code": { 52 | "rgba": [0,255,0,1], 53 | "hex": "#0F0" 54 | } 55 | }, 56 | ] 57 | } -------------------------------------------------------------------------------- /keylime-agent/test-data/test_input.txt: -------------------------------------------------------------------------------- 1 | Hello World! 2 | -------------------------------------------------------------------------------- /keylime-agent/test-data/test_ok.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "there" 3 | } 4 | -------------------------------------------------------------------------------- /keylime-agent/test-data/tpmdata_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "aik": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwWnAON9h+AYu4Ee+D5LY\nhqKYiQpbFqboE4ohGYjy/eIAOJ//TKysXmdraiq9XYqRWXC/+K5kFn+irRc9k1sn\nhX2HIsIxdqJOZvzzbWXlZ4VyPsc18DQvNiabelBSWxX3rcpB01liooclPB+WsDks\no0Ay/jwgiJvuEbfACzf4z+noQep4x4ysnPd/lcPnN6+TD7TMaaNrMUxp5o8/cmve\nIor6BuFf9bgXH58UrA0V2Bejh83eoyQQ1V/TkDBbnov9/FGaMTURGonapIh9jy9K\nSkmsjFp6LQ/YySIArk6gqRS0fSCp//vX/hTBknpcu6u0CqgEzkBhbGnNa7tMOyo9\npQIDAQAB\n-----END PUBLIC KEY-----\n", 3 | "aik_handle": "FB1F19E0", 4 | "aik_pw": "QXgMbqSxSuB05MVHO0Ox", 5 | "aikmod": "wWnAON9h+AYu4Ee+D5LYhqKYiQpbFqboE4ohGYjy/eIAOJ//TKysXmdraiq9XYqRWXC/+K5kFn+irRc9k1snhX2HIsIxdqJOZvzzbWXlZ4VyPsc18DQvNiabelBSWxX3rcpB01liooclPB+WsDkso0Ay/jwgiJvuEbfACzf4z+noQep4x4ysnPd/lcPnN6+TD7TMaaNrMUxp5o8/cmveIor6BuFf9bgXH58UrA0V2Bejh83eoyQQ1V/TkDBbnov9/FGaMTURGonapIh9jy9KSkmsjFp6LQ/YySIArk6gqRS0fSCp//vX/hTBknpcu6u0CqgEzkBhbGnNa7tMOyo9pQ==", 6 | "aikpriv": "AQEAAAASAAAAAAEAAAABAAEAAgAAAAwAAAgAAAAAAgAAAAAAAAAAAAABAMFpwDjfYfgGLuBHvg+S2IaimIkKWxam6BOKIRmI8v3iADif/0ysrF5na2oqvV2KkVlwv/iuZBZ/oq0XPZNbJ4V9hyLCMXaiTmb8821l5WeFcj7HNfA0LzYmm3pQUlsV963KQdNZYqKHJTwflrA5LKNAMv48IIib7hG3wAs3+M/p6EHqeMeMrJz3f5XD5zevkw+0zGmjazFMaeaPP3Jr3iKK+gbhX/W4Fx+fFKwNFdgXo4fN3qMkENVf05AwW56L/fxRmjE1ERqJ2qSIfY8vSkpJrIxaei0P2MkiAK5OoKkUtH0gqf/71/4UwZJ6XLurtAqoBM5AYWxpzWu7TDsqPaUAAAEAI/83lAiEz8a8RmgennBJNUELDu+zTARQxjcaWR95cEIMqYP/2V4IEbpp78o9l3BBYyZWnWEUPvzV8z2XpQJ60zp07mjTKTFwH84nl+NAh0wu/CwCfTaLxS0kty2drLHcr/YRC+3g7vvztpmRz3NfgUYvUsozgakfC3IZYXPY0vPFH8uYWFwSnwG7xPgoamcHct0x48N4ejAWx8492QYNyex07A/wdOTETCO7oMTRXlrK4MijgGJ+odZ7owBWrLXHKTl6A6mqmGCbaLR49xgVNTyjdRyEFitpkRMfm59lwab0Ljin2p6YyzN1i0oONpPL8PHtLz9liubQymK73i1KWg==", 7 | "ek": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzLJC3n/KpLjUn3DIRkhw\nUO9igtnCugQ92sAM/hJt7wj8uRDtz5O0mv7aHLmrlKA2XVmPgnInD3WOEVyfYpM3\nRXOftLSDdouB/C5iwqCJRiH+gr012SMDdqJsnWb4ofdPNBgJXRTs6+ztXH8GNO0c\nfqoMJBIBizE3P8kRPcqVGMJSXgkS1p9Otp0w431ckXZwdkoNxuM2GF6ktVfOEdCZ\nr8Idm+qbQaK+Ur57VCB02U49ekGdd1qA76BjmSVyHYPEkce7Jf2ipWK3d/yP0exs\neLKL9rO+Aab2X4/2v2NQPwEj0bz6oEAhvgpaoV0eakN2WiXxTVUG30OvCsRFxvUu\nvwIDAQAB\n-----END PUBLIC KEY-----\n", 8 | "owner_pw": "keylime" 9 | } -------------------------------------------------------------------------------- /keylime-agent/tests/actions/local_action_hello.py: -------------------------------------------------------------------------------- 1 | ''' 2 | SPDX-License-Identifier: Apache-2.0 3 | Copyright 2021 Keylime Authors 4 | ''' 5 | 6 | 7 | async def execute(revocation): 8 | try: 9 | value = revocation['hello'] 10 | print(value) 11 | except Exception as e: 12 | raise Exception( 13 | "The provided dictionary does not contain the 'hello' key") 14 | -------------------------------------------------------------------------------- /keylime-agent/tests/actions/local_action_hello_shell.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #SPDX-License-Identifier: Apache-2.0 4 | #Copyright 2021 Keylime Authors 5 | 6 | echo "Hello from non-python local action!" 7 | -------------------------------------------------------------------------------- /keylime-agent/tests/actions/local_action_stand_alone.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | ''' 3 | SPDX-License-Identifier: Apache-2.0 4 | Copyright 2021 Keylime Authors 5 | ''' 6 | 7 | import argparse 8 | import json 9 | import sys 10 | 11 | 12 | def main(): 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument('json_file') 15 | args = parser.parse_args() 16 | 17 | with open(args.json_file, 'r') as f: 18 | input_json = json.load(f) 19 | value = input_json['hello'] 20 | 21 | print(value) 22 | 23 | 24 | if __name__ == "__main__": 25 | main() 26 | 27 | -------------------------------------------------------------------------------- /keylime-agent/tests/actions/shim.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | ''' 3 | SPDX-License-Identifier: Apache-2.0 4 | Copyright 2021 Keylime Authors 5 | ''' 6 | 7 | import argparse 8 | import asyncio 9 | import importlib 10 | import json 11 | import os 12 | import sys 13 | 14 | 15 | def main(): 16 | # Parse arguments to get action name and input json file path 17 | parser = argparse.ArgumentParser() 18 | parser.add_argument('action', type=str, help='The revocation action to be' 19 | ' executed. The module must provide an \'execute()\'' 20 | ' method which receives a JSON as argument') 21 | parser.add_argument('json_file', type=str, help='Input file') 22 | 23 | args = parser.parse_args() 24 | 25 | with open(args.json_file, 'r') as f: 26 | input_json = json.load(f) 27 | 28 | try: 29 | module = importlib.import_module(args.action) 30 | execute = getattr(module, 'execute') 31 | asyncio.run(execute(input_json)) 32 | except Exception as e: 33 | print("Exception during execution of revocation action {}: {}".format( 34 | args.action, e), file=sys.stderr) 35 | 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /keylime-agent/tests/unzipped/action_list: -------------------------------------------------------------------------------- 1 | local_action_rev_script1.py 2 | local_action_rev_script2.py 3 | -------------------------------------------------------------------------------- /keylime-agent/tests/unzipped/local_action_payload.py: -------------------------------------------------------------------------------- 1 | ''' 2 | SPDX-License-Identifier: Apache-2.0 3 | Copyright 2021 Keylime Authors 4 | ''' 5 | 6 | 7 | async def execute(revocation): 8 | try: 9 | value = revocation['hello'] 10 | print(value) 11 | except Exception as e: 12 | raise Exception( 13 | "The provided dictionary does not contain the 'hello' key") 14 | -------------------------------------------------------------------------------- /keylime-agent/tests/unzipped/local_action_payload_shell.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #SPDX-License-Identifier: Apache-2.0 4 | #Copyright 2021 Keylime Authors 5 | 6 | echo "Hello from non-python payload action!" 7 | -------------------------------------------------------------------------------- /keylime-agent/tests/unzipped/local_action_rev_script1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | ''' 3 | SPDX-License-Identifier: Apache-2.0 4 | Copyright 2021 Keylime Authors 5 | ''' 6 | 7 | import argparse 8 | import json 9 | import sys 10 | 11 | 12 | def main(): 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument('json_file') 15 | args = parser.parse_args() 16 | 17 | with open(args.json_file, 'r') as f: 18 | input_json = json.load(f) 19 | value = input_json['hello'] 20 | 21 | print(value) 22 | 23 | 24 | if __name__ == "__main__": 25 | main() 26 | -------------------------------------------------------------------------------- /keylime-agent/tests/unzipped/local_action_rev_script2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | ''' 3 | SPDX-License-Identifier: Apache-2.0 4 | Copyright 2021 Keylime Authors 5 | ''' 6 | 7 | import argparse 8 | import json 9 | import sys 10 | 11 | 12 | def main(): 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument('json_file') 15 | args = parser.parse_args() 16 | 17 | with open(args.json_file, 'r') as f: 18 | input_json = json.load(f) 19 | value = input_json['hello'] 20 | 21 | print(value) 22 | 23 | 24 | if __name__ == "__main__": 25 | main() 26 | -------------------------------------------------------------------------------- /keylime-agent/tests/unzipped/test_err.json: -------------------------------------------------------------------------------- 1 | { 2 | "goodbye" : "there" 3 | } 4 | -------------------------------------------------------------------------------- /keylime-agent/tests/unzipped/test_ok.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "there" 3 | } 4 | -------------------------------------------------------------------------------- /keylime-ima-emulator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "keylime_ima_emulator" 3 | description = "IMA emulator for Keylime" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | version.workspace = true 9 | 10 | [dependencies] 11 | clap.workspace = true 12 | hex.workspace = true 13 | keylime.workspace = true 14 | log.workspace = true 15 | openssl.workspace = true 16 | signal-hook.workspace = true 17 | thiserror.workspace = true 18 | tss-esapi.workspace = true 19 | -------------------------------------------------------------------------------- /keylime-push-model-agent/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "keylime_push_model_agent" 3 | description = "Rust agent for Keylime (Push Model)" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | version.workspace = true 9 | 10 | [dependencies] 11 | actix-web.workspace = true 12 | anyhow.workspace = true 13 | assert_cmd.workspace = true 14 | chrono.workspace = true 15 | clap.workspace = true 16 | keylime.workspace = true 17 | log.workspace = true 18 | predicates.workspace = true 19 | pretty_env_logger.workspace = true 20 | reqwest.workspace = true 21 | serde.workspace = true 22 | serde_derive.workspace = true 23 | serde_json.workspace = true 24 | static_assertions.workspace = true 25 | tokio.workspace = true 26 | 27 | [dev-dependencies] 28 | actix-rt.workspace = true 29 | tempfile.workspace = true 30 | 31 | [features] 32 | # The features enabled by default 33 | default = [] 34 | testing = [] 35 | legacy-python-actions = [] 36 | 37 | [package.metadata.deb] 38 | section = "net" 39 | assets = [ 40 | ["target/release/keylime_push_model_agent", "usr/bin/", "755"], 41 | ["../README.md", "usr/share/doc/keylime-agent/README", "644"], 42 | ["../keylime-agent.conf", "/etc/keylime/agent.conf", "640"], 43 | ] 44 | maintainer-scripts = "../debian/" 45 | systemd-units = { unit-scripts = "../dist/systemd/system/", enable = true } 46 | -------------------------------------------------------------------------------- /keylime-push-model-agent/src/json_dump.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2025 Keylime Authors 3 | use keylime::structures; 4 | use serde_json::{Error, Value}; 5 | 6 | pub fn dump_attestation_request_to_value( 7 | request: &structures::AttestationRequest, 8 | ) -> Result { 9 | serde_json::to_value(request) 10 | } 11 | 12 | pub fn dump_session_request_to_value( 13 | request: &structures::SessionRequest, 14 | ) -> Result { 15 | serde_json::to_value(request) 16 | } 17 | 18 | pub fn dump_evidence_handling_request_to_value( 19 | request: &structures::EvidenceHandlingRequest, 20 | ) -> Result { 21 | serde_json::to_value(request) 22 | } 23 | -------------------------------------------------------------------------------- /keylime-push-model-agent/src/registration.rs: -------------------------------------------------------------------------------- 1 | use keylime::agent_registration::{ 2 | AgentRegistration, AgentRegistrationConfig, 3 | }; 4 | use keylime::config::PushModelConfigTrait; 5 | use keylime::keylime_error::Result; 6 | use keylime::{cert, context_info}; 7 | 8 | pub async fn check_registration( 9 | context_info: Option, 10 | ) -> Result<()> { 11 | let reg_config = keylime::config::PushModelConfig::default(); 12 | if context_info.is_some() { 13 | crate::registration::register_agent( 14 | ®_config, 15 | &mut context_info.unwrap(), 16 | ) 17 | .await?; 18 | } 19 | Ok(()) 20 | } 21 | 22 | pub async fn register_agent( 23 | config: &T, 24 | context_info: &mut context_info::ContextInfo, 25 | ) -> Result<()> { 26 | let ac = AgentRegistrationConfig { 27 | contact_ip: config.get_contact_ip(), 28 | contact_port: config.get_contact_port(), 29 | registrar_ip: config.get_registrar_ip(), 30 | registrar_port: config.get_registrar_port(), 31 | enable_iak_idevid: config.get_enable_iak_idevid(), 32 | ek_handle: config.get_ek_handle(), 33 | }; 34 | 35 | let cert_config = cert::CertificateConfig { 36 | agent_uuid: config.get_uuid(), 37 | contact_ip: config.get_contact_ip(), 38 | contact_port: config.get_contact_port(), 39 | server_cert: config.get_server_cert(), 40 | server_key: config.get_server_key(), 41 | server_key_password: config.get_server_key_password(), 42 | }; 43 | 44 | let server_cert_key = cert::cert_from_server_key(&cert_config)?; 45 | 46 | let aa = AgentRegistration { 47 | ak: context_info.ak.clone(), 48 | ek_result: context_info.ek_result.clone(), 49 | api_versions: config.get_registrar_api_versions(), 50 | agent_registration_config: ac, 51 | agent_uuid: config.get_uuid(), 52 | mtls_cert: Some(server_cert_key.0), 53 | device_id: None, // TODO: Check how to proceed with device ID 54 | attest: None, // TODO: Check how to proceed with attestation, normally, no device ID means no attest 55 | signature: None, // TODO: Normally, no device ID means no signature 56 | ak_handle: context_info.ak_handle, 57 | }; 58 | let ctx = context_info.get_mutable_tpm_context(); 59 | match keylime::agent_registration::register_agent(aa, ctx).await { 60 | Ok(_) => Ok(()), 61 | Err(e) => Err(e), 62 | } 63 | } // register_agent 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::*; 68 | 69 | #[cfg(feature = "testing")] 70 | use keylime::context_info::{AlgorithmConfigurationString, ContextInfo}; 71 | 72 | #[actix_rt::test] 73 | async fn test_avoid_registration() { 74 | let result = check_registration(None).await; 75 | assert!(result.is_ok()); 76 | } 77 | 78 | #[tokio::test] 79 | #[cfg(feature = "testing")] 80 | async fn test_register_agent() { 81 | use keylime::tpm::testing; 82 | let _mutex = testing::lock_tests().await; 83 | let config = keylime::config::PushModelConfig::default(); 84 | let alg_config = AlgorithmConfigurationString { 85 | tpm_encryption_alg: "rsa".to_string(), 86 | tpm_hash_alg: "sha256".to_string(), 87 | tpm_signing_alg: "rsassa".to_string(), 88 | }; 89 | let mut context_info = ContextInfo::new_from_str(alg_config); 90 | let result = register_agent(&config, &mut context_info).await; 91 | assert!(result.is_err()); 92 | assert!(context_info.flush_context().is_ok()); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /keylime-push-model-agent/test-data/evidence_handling_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "attestation", 4 | "attributes": { 5 | "evidence_collected": [ 6 | { 7 | "evidence_class": "certification", 8 | "evidence_type": "tpm_quote", 9 | "data": { 10 | "subject_data": "subject_data_deserialized", 11 | "message": "message_deserialized", 12 | "signature": "signature_deserialized" 13 | } 14 | }, 15 | { 16 | "evidence_class": "log", 17 | "evidence_type": "uefi_log", 18 | "data": { 19 | "entries": "uefi_log_entries_deserialized" 20 | } 21 | }, 22 | { 23 | "evidence_class": "log", 24 | "evidence_type": "ima_log", 25 | "data": { 26 | "entries": "ima_log_entries_deserialized", 27 | "entry_count": 96 28 | } 29 | } 30 | ] 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /keylime-push-model-agent/test-data/evidence_supported_attestation_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "attestation", 4 | "attributes": { 5 | "evidence_supported": [ 6 | { 7 | "evidence_class": "certification", 8 | "evidence_type": "tpm_quote", 9 | "capabilities": { 10 | "component_version": "2.0", 11 | "hash_algorithms": [ 12 | "sha3_512" 13 | ], 14 | "signature_schemes": [ 15 | "rsassa" 16 | ], 17 | "available_subjects": { 18 | "sha1": [ 19 | 1, 20 | 2, 21 | 3 22 | ], 23 | "sha256": [ 24 | 4, 25 | 5, 26 | 6 27 | ] 28 | }, 29 | "certification_keys": [ 30 | { 31 | "key_class": "asymmetric", 32 | "local_identifier": "att_local_identifier", 33 | "key_algorithm": "rsa", 34 | "key_size": 2048, 35 | "server_identifier": "ak", 36 | "public": "VGhpcyBpcyBhIHRlc3QgZm9yIGEgYmFzZTY0IGVuY29kZWQgZm9ybWF0IHN0cmluZw==" 37 | } 38 | ] 39 | } 40 | }, 41 | { 42 | "evidence_class": "log", 43 | "evidence_type": "uefi_log", 44 | "capabilities": { 45 | "evidence_version": "2.1", 46 | "entry_count": 20, 47 | "supports_partial_access": false, 48 | "appendable": false, 49 | "formats": [ 50 | "application/octet-stream" 51 | ] 52 | } 53 | }, 54 | { 55 | "evidence_class": "log", 56 | "evidence_type": "ima_log", 57 | "capabilities": { 58 | "entry_count": 20, 59 | "supports_partial_access": true, 60 | "appendable": true, 61 | "formats": [ 62 | "text/plain" 63 | ] 64 | } 65 | } 66 | ], 67 | "system_info": { 68 | "boot_time": "2025-04-02T12:12:51Z" 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /keylime-push-model-agent/test-data/session_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "type": "session", 4 | "attributes": { 5 | "agent_id": "example-agent", 6 | "authentication_supported": [ 7 | { 8 | "authentication_class": "pop", 9 | "authentication_type": "tpm_pop" 10 | } 11 | ] 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /keylime-push-model-agent/tests/test-keylime-push-model-agent.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2025 Keylime Authors 3 | use assert_cmd::Command; 4 | use predicates::prelude::*; 5 | 6 | const KEYLIME_PUSH_MODEL_AGENT_BINARY: &str = "keylime_push_model_agent"; 7 | const ERROR_SENDING_REQUEST: &str = 8 | "error sending request for url (http://1.2.3.4:5678/v3.0/agents/12345678/attestations)"; 9 | const ERROR_SENDING_LOCALHOST_REQUEST: &str = 10 | "error sending request for url (http://localhost:3000/v3.0/agents/12345678/attestations)"; 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | 15 | use super::*; 16 | 17 | #[test] 18 | fn print_help_test() -> Result<(), Box> { 19 | let mut cmd = Command::cargo_bin(KEYLIME_PUSH_MODEL_AGENT_BINARY)?; 20 | cmd.arg("-h"); 21 | cmd.assert().success().stdout(predicate::str::contains( 22 | KEYLIME_PUSH_MODEL_AGENT_BINARY, 23 | )); 24 | Ok(()) 25 | } 26 | 27 | #[test] 28 | fn connection_error_test() -> Result<(), Box> { 29 | let mut cmd = Command::cargo_bin(KEYLIME_PUSH_MODEL_AGENT_BINARY)?; 30 | cmd.arg("-v") 31 | .arg("http://1.2.3.4:5678") 32 | .arg("--timeout") 33 | .arg("10") 34 | .arg("--agent-identifier") 35 | .arg("12345678") 36 | .arg("--message-type") 37 | .arg("attestation") 38 | .arg("--method") 39 | .arg("--avoid-tpm") 40 | .arg("true") 41 | .arg("POST"); 42 | cmd.assert() 43 | .success() 44 | .stderr(predicate::str::contains(ERROR_SENDING_REQUEST)); 45 | Ok(()) 46 | } 47 | #[test] 48 | fn mockoon_based_test() -> Result<(), Box> { 49 | let mut cmd = Command::cargo_bin(KEYLIME_PUSH_MODEL_AGENT_BINARY)?; 50 | cmd.arg("-v") 51 | .arg("http://localhost:3000") 52 | .arg("--avoid-tpm") 53 | .arg("true") 54 | .arg("--agent-identifier") 55 | .arg("12345678") 56 | .arg("--timeout") 57 | .arg("1000"); 58 | // Check if MOCKOON environment variable is set and, if so, assume 59 | // that the mock server is running in port 3000 and response will be received 60 | // in less than 1 second. 61 | if std::env::var("MOCKOON").is_ok() { 62 | cmd.assert().success(); 63 | } else { 64 | // If MOCKOON is not set, assume that the server is not running 65 | // and we expect an error. 66 | cmd.assert().success().stderr(predicate::str::contains( 67 | ERROR_SENDING_LOCALHOST_REQUEST, 68 | )); 69 | } 70 | Ok(()) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /keylime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "keylime" 3 | description = "Shared utilities for Keylime crates" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | version.workspace = true 9 | 10 | [dependencies] 11 | actix-web.workspace = true 12 | anyhow.workspace = true 13 | base64.workspace = true 14 | chrono.workspace = true 15 | config.workspace = true 16 | glob.workspace = true 17 | hex.workspace = true 18 | log.workspace = true 19 | once_cell.workspace = true 20 | openssl.workspace = true 21 | pest.workspace = true 22 | pest_derive.workspace = true 23 | reqwest.workspace = true 24 | serde.workspace = true 25 | serde_derive.workspace = true 26 | serde_json.workspace = true 27 | static_assertions.workspace = true 28 | tempfile.workspace = true 29 | thiserror.workspace = true 30 | tss-esapi.workspace = true 31 | picky-asn1-der.workspace = true 32 | picky-asn1-x509.workspace = true 33 | tokio.workspace = true 34 | # wiremock was moved to be a regular dependency because optional 35 | # dev-dependencies are not supported 36 | # see: https://github.com/rust-lang/cargo/issues/1596 37 | wiremock = {version = "0.6", optional = true} 38 | uuid.workspace = true 39 | zip.workspace = true 40 | zmq = {version = "0.9.2", optional = true} 41 | 42 | [dev-dependencies] 43 | tempfile.workspace = true 44 | actix-rt.workspace = true 45 | 46 | [features] 47 | # This feature enables tests that require a TPM and the TCTI environment 48 | # variable properly configured 49 | # This should change to dev-dependencies when we have integration testing 50 | testing = ["wiremock"] 51 | # This feature is deprecated and will be removed on next major release 52 | with-zmq = ["zmq"] 53 | -------------------------------------------------------------------------------- /keylime/src/agent_data.rs: -------------------------------------------------------------------------------- 1 | use crate::algorithms::{HashAlgorithm, SignAlgorithm}; 2 | use crate::keylime_error::Result; 3 | use crate::tpm; 4 | use serde::{Deserialize, Serialize}; 5 | use std::fs::File; 6 | use std::path::Path; 7 | use tss_esapi::structures::{Private, Public}; 8 | use tss_esapi::traits::{Marshall, UnMarshall}; 9 | 10 | // TPM data and agent related that can be persisted and loaded on agent startup. 11 | #[derive(Debug, Clone, Serialize, Deserialize)] 12 | pub struct AgentData { 13 | pub ak_hash_alg: HashAlgorithm, 14 | pub ak_sign_alg: SignAlgorithm, 15 | ak_public: Vec, 16 | ak_private: Vec, 17 | ek_hash: Vec, 18 | } 19 | 20 | impl AgentData { 21 | pub fn create( 22 | ak_hash_alg: HashAlgorithm, 23 | ak_sign_alg: SignAlgorithm, 24 | ak: &tpm::AKResult, 25 | ek_hash: &[u8], 26 | ) -> Result { 27 | let ak_public = ak.public.marshall()?; 28 | let ak_private: Vec = ak.private.to_vec(); 29 | let ek_hash: Vec = ek_hash.to_vec(); 30 | Ok(Self { 31 | ak_hash_alg, 32 | ak_sign_alg, 33 | ak_public, 34 | ak_private, 35 | ek_hash, 36 | }) 37 | } 38 | 39 | pub fn load(path: &Path) -> Result { 40 | let file = File::open(path)?; 41 | let data: Self = serde_json::from_reader(file)?; 42 | Ok(data) 43 | } 44 | 45 | pub fn store(&self, path: &Path) -> Result<()> { 46 | let file = File::create(path)?; 47 | serde_json::to_writer_pretty(file, self)?; 48 | Ok(()) 49 | } 50 | 51 | pub fn get_ak(&self) -> Result { 52 | let public = Public::unmarshall(&self.ak_public)?; 53 | let private = Private::try_from(self.ak_private.clone())?; 54 | 55 | Ok(tpm::AKResult { public, private }) 56 | } 57 | 58 | pub fn valid( 59 | &self, 60 | hash_alg: HashAlgorithm, 61 | sign_alg: SignAlgorithm, 62 | ek_hash: &[u8], 63 | ) -> bool { 64 | hash_alg == self.ak_hash_alg 65 | && sign_alg == self.ak_sign_alg 66 | && ek_hash.to_vec() == self.ek_hash 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /keylime/src/agent_registration.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2025 Keylime Authors 3 | use crate::keylime_error::{Error, Result}; 4 | use crate::{ 5 | agent_identity::AgentIdentityBuilder, 6 | crypto::{self}, 7 | device_id, 8 | registrar_client::RegistrarClientBuilder, 9 | tpm::{self}, 10 | }; 11 | use base64::{engine::general_purpose, Engine as _}; 12 | use log::{error, info}; 13 | use openssl::x509::X509; 14 | use tss_esapi::{ 15 | handles::KeyHandle, structures::PublicBuffer, traits::Marshall, 16 | }; 17 | 18 | #[derive(Debug)] 19 | pub struct AgentRegistrationConfig { 20 | pub contact_ip: String, 21 | pub contact_port: u32, 22 | pub ek_handle: String, 23 | pub enable_iak_idevid: bool, 24 | pub registrar_ip: String, 25 | pub registrar_port: u32, 26 | } 27 | 28 | #[derive(Debug)] 29 | pub struct AgentRegistration { 30 | pub ak: tpm::AKResult, 31 | pub ek_result: tpm::EKResult, 32 | pub api_versions: Vec, 33 | pub agent_registration_config: AgentRegistrationConfig, 34 | pub agent_uuid: String, 35 | pub mtls_cert: Option, 36 | pub device_id: Option, 37 | pub attest: Option, 38 | pub signature: Option, 39 | pub ak_handle: KeyHandle, 40 | } 41 | 42 | pub async fn register_agent( 43 | aa: AgentRegistration, 44 | ctx: &mut tpm::Context<'_>, 45 | ) -> Result<()> { 46 | let iak_pub; 47 | let idevid_pub; 48 | let ak_pub = &PublicBuffer::try_from(aa.ak.public)?.marshall()?; 49 | let ek_pub = 50 | &PublicBuffer::try_from(aa.ek_result.public.clone())?.marshall()?; 51 | 52 | let mut ai_builder = AgentIdentityBuilder::new() 53 | .ak_pub(ak_pub) 54 | .ek_pub(ek_pub) 55 | .enabled_api_versions( 56 | aa.api_versions.iter().map(|ver| ver.as_ref()).collect(), 57 | ) 58 | .uuid(&aa.agent_uuid) 59 | .ip(aa.agent_registration_config.contact_ip.clone()) 60 | .port(aa.agent_registration_config.contact_port); 61 | 62 | if let Some(mtls_cert) = aa.mtls_cert { 63 | ai_builder = ai_builder.mtls_cert(mtls_cert); 64 | } 65 | 66 | // If the certificate is not None add it to the builder 67 | if let Some(ekchain) = aa.ek_result.to_pem() { 68 | ai_builder = ai_builder.ek_cert(ekchain); 69 | } 70 | 71 | // Set the IAK/IDevID related fields, if enabled 72 | if aa.agent_registration_config.enable_iak_idevid { 73 | let (Some(dev_id), Some(attest), Some(signature)) = 74 | (&aa.device_id, aa.attest, aa.signature) 75 | else { 76 | error!("IDevID and IAK are enabled but could not be generated"); 77 | return Err(Error::ConfigurationGenericError( 78 | "IDevID and IAK are enabled but could not be generated" 79 | .to_string(), 80 | )); 81 | }; 82 | 83 | iak_pub = 84 | PublicBuffer::try_from(dev_id.iak_pubkey.clone())?.marshall()?; 85 | idevid_pub = PublicBuffer::try_from(dev_id.idevid_pubkey.clone())? 86 | .marshall()?; 87 | ai_builder = ai_builder 88 | .iak_attest(attest.marshall()?) 89 | .iak_sign(signature.marshall()?) 90 | .iak_pub(&iak_pub) 91 | .idevid_pub(&idevid_pub); 92 | 93 | // If the IAK certificate was provided, set it 94 | if let Some(iak_cert) = dev_id.iak_cert.clone() { 95 | ai_builder = ai_builder.iak_cert(iak_cert); 96 | } 97 | 98 | // If the IDevID certificate was provided, set it 99 | if let Some(idevid_cert) = dev_id.idevid_cert.clone() { 100 | ai_builder = ai_builder.idevid_cert(idevid_cert); 101 | } 102 | } 103 | 104 | // Build the Agent Identity 105 | let ai = ai_builder.build().await?; 106 | 107 | let ac = &aa.agent_registration_config; 108 | // Build the registrar client 109 | // Create a RegistrarClientBuilder and set the parameters 110 | let mut registrar_client = RegistrarClientBuilder::new() 111 | .registrar_address(ac.registrar_ip.clone()) 112 | .registrar_port(ac.registrar_port) 113 | .build() 114 | .await?; 115 | 116 | // Request keyblob material 117 | let keyblob = registrar_client.register_agent(&ai).await?; 118 | 119 | info!("SUCCESS: Agent {} registered", &aa.agent_uuid); 120 | 121 | let key = ctx.activate_credential( 122 | keyblob, 123 | aa.ak_handle, 124 | aa.ek_result.key_handle, 125 | )?; 126 | 127 | // Flush EK if we created it 128 | if aa.agent_registration_config.ek_handle.is_empty() { 129 | ctx.flush_context(aa.ek_result.key_handle.into())?; 130 | } 131 | 132 | let mackey = general_purpose::STANDARD.encode(key.value()); 133 | let auth_tag = 134 | crypto::compute_hmac(mackey.as_bytes(), aa.agent_uuid.as_bytes())?; 135 | let auth_tag = hex::encode(&auth_tag); 136 | 137 | registrar_client.activate_agent(&ai, &auth_tag).await?; 138 | 139 | info!("SUCCESS: Agent {} activated", &aa.agent_uuid); 140 | Ok(()) 141 | } 142 | -------------------------------------------------------------------------------- /keylime/src/cert.rs: -------------------------------------------------------------------------------- 1 | use crate::crypto; 2 | use crate::keylime_error::Result; 3 | use log::debug; 4 | use openssl::pkey::{PKey, Public}; 5 | use openssl::x509::X509; 6 | use std::path::Path; 7 | 8 | pub struct CertificateConfig { 9 | pub agent_uuid: String, 10 | pub contact_ip: String, 11 | pub contact_port: u32, 12 | pub server_cert: String, 13 | pub server_key: String, 14 | pub server_key_password: String, 15 | } 16 | 17 | pub fn cert_from_server_key( 18 | config: &CertificateConfig, 19 | ) -> Result<(X509, PKey)> { 20 | let cert: X509; 21 | let (nk_pub, nk_priv) = match config.server_key.as_ref() { 22 | "" => { 23 | debug!( 24 | "The server_key option was not set in the configuration file" 25 | ); 26 | debug!("Generating new key pair"); 27 | crypto::rsa_generate_pair(2048)? 28 | } 29 | path => { 30 | let key_path = Path::new(&path); 31 | if key_path.exists() { 32 | debug!( 33 | "Loading existing key pair from {}", 34 | key_path.display() 35 | ); 36 | crypto::load_key_pair( 37 | key_path, 38 | Some(&config.server_key_password), 39 | )? 40 | } else { 41 | debug!("Generating new key pair"); 42 | let (public, private) = crypto::rsa_generate_pair(2048)?; 43 | // Write the generated key to the file 44 | crypto::write_key_pair( 45 | &private, 46 | key_path, 47 | Some(&config.server_key_password), 48 | )?; 49 | (public, private) 50 | } 51 | } 52 | }; 53 | 54 | let contact_ips = vec![config.contact_ip.as_str()]; 55 | cert = match config.server_cert.as_ref() { 56 | "" => { 57 | debug!("The server_cert option was not set in the configuration file"); 58 | 59 | crypto::x509::CertificateBuilder::new() 60 | .private_key(&nk_priv) 61 | .common_name(&config.agent_uuid) 62 | .add_ips(contact_ips) 63 | .build()? 64 | } 65 | path => { 66 | let cert_path = Path::new(&path); 67 | if cert_path.exists() { 68 | debug!( 69 | "Loading existing mTLS certificate from {}", 70 | cert_path.display() 71 | ); 72 | crypto::load_x509_pem(cert_path)? 73 | } else { 74 | debug!("Generating new mTLS certificate"); 75 | let cert = crypto::x509::CertificateBuilder::new() 76 | .private_key(&nk_priv) 77 | .common_name(&config.agent_uuid) 78 | .add_ips(contact_ips) 79 | .build()?; 80 | crypto::write_x509(&cert, cert_path)?; 81 | cert 82 | } 83 | } 84 | }; 85 | Ok((cert, nk_pub)) 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | use super::*; 91 | use crate::cert::CertificateConfig; 92 | 93 | #[test] 94 | fn test_cert_from_server_key() { 95 | const TEST_CERT_PATH: &str = "test_cert.pem"; 96 | const TEST_KEY_PATH: &str = "test_key.pem"; 97 | // Ensure the test cert file does not exist before the test 98 | let _ = std::fs::remove_file(TEST_CERT_PATH); 99 | let _ = std::fs::remove_file(TEST_KEY_PATH); 100 | let config = CertificateConfig { 101 | agent_uuid: "test-uuid".to_string(), 102 | contact_ip: "1.2.3.4".to_string(), 103 | contact_port: 8080, 104 | server_cert: TEST_CERT_PATH.to_string(), 105 | server_key: TEST_KEY_PATH.to_string(), 106 | server_key_password: "test_password".to_string(), 107 | }; 108 | let result = cert_from_server_key(&config); 109 | assert!(result.is_ok()); 110 | // Clean up the test cert file after the test 111 | let _ = std::fs::remove_file(TEST_CERT_PATH); 112 | let _ = std::fs::remove_file(TEST_KEY_PATH); 113 | } // test_cert_from_server_key 114 | 115 | #[test] 116 | fn test_cert_no_server_key() { 117 | const TEST_CERT_PATH: &str = "test_cert2.pem"; 118 | // Ensure the test cert file does not exist before the test 119 | let _ = std::fs::remove_file(TEST_CERT_PATH); 120 | let config = CertificateConfig { 121 | agent_uuid: "test-uuid".to_string(), 122 | contact_ip: "1.2.3.4".to_string(), 123 | contact_port: 8080, 124 | server_cert: TEST_CERT_PATH.to_string(), 125 | server_key: "".to_string(), 126 | server_key_password: "test_password2".to_string(), 127 | }; 128 | let result = cert_from_server_key(&config); 129 | assert!(result.is_ok()); 130 | // Clean up the test cert file after the test 131 | let _ = std::fs::remove_file(TEST_CERT_PATH); 132 | } // test_cert_no_server_key 133 | 134 | #[test] 135 | fn test_cert_no_server_cert() { 136 | // Ensure the test cert file does not exist before the test 137 | const TEST_KEY_PATH: &str = "test_key.pem"; 138 | let config = CertificateConfig { 139 | agent_uuid: "test-uuid".to_string(), 140 | contact_ip: "1.2.3.4".to_string(), 141 | contact_port: 8080, 142 | server_cert: "".to_string(), 143 | server_key: TEST_KEY_PATH.to_string(), 144 | server_key_password: "test_password".to_string(), 145 | }; 146 | let result = cert_from_server_key(&config); 147 | assert!(result.is_ok()); 148 | // Clean up the test key file after the test 149 | let _ = std::fs::remove_file(TEST_KEY_PATH); 150 | } // test_cert_no_server_cert 151 | 152 | #[test] 153 | fn test_cert_wrong_server_key_path() { 154 | let config = CertificateConfig { 155 | agent_uuid: "test-uuid".to_string(), 156 | contact_ip: "1.2.3.4".to_string(), 157 | contact_port: 8080, 158 | server_cert: "test_cert3.pem".to_string(), 159 | server_key: "/server/key/can/not/be/created/here".to_string(), 160 | server_key_password: "test_password3".to_string(), 161 | }; 162 | let result = cert_from_server_key(&config); 163 | assert!(result.is_err()); 164 | } // test_cert_wrong_server_key_path 165 | 166 | #[test] 167 | fn test_cert_correct_server_key_path() { 168 | const TEST_KEY_PATH: &str = "test_key2.pem"; 169 | const TEST_CERT_PATH: &str = "test_cert4.pem"; 170 | // Ensure the test key file does not exist before the test 171 | let _ = std::fs::remove_file(TEST_KEY_PATH); 172 | let _ = std::fs::remove_file(TEST_CERT_PATH); 173 | let config = CertificateConfig { 174 | agent_uuid: "test-uuid".to_string(), 175 | contact_ip: "1.2.3.4".to_string(), 176 | contact_port: 8080, 177 | server_cert: TEST_CERT_PATH.to_string(), 178 | server_key: TEST_KEY_PATH.to_string(), 179 | server_key_password: "test_password4".to_string(), 180 | }; 181 | let result = cert_from_server_key(&config); 182 | assert!(result.is_ok()); 183 | // Clean up files after the test 184 | let _ = std::fs::remove_file(TEST_KEY_PATH); 185 | let _ = std::fs::remove_file(TEST_CERT_PATH); 186 | } // test_cert_correct_server_key_path 187 | } 188 | -------------------------------------------------------------------------------- /keylime/src/config/mod.rs: -------------------------------------------------------------------------------- 1 | mod push_model_config; 2 | 3 | pub use push_model_config::*; 4 | -------------------------------------------------------------------------------- /keylime/src/crypto/auth_tag.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2025 Keylime Authors 3 | 4 | use crate::crypto::AUTH_TAG_LEN; 5 | use serde::{Deserialize, Serialize}; 6 | use thiserror::Error; 7 | 8 | #[derive(Debug, Error)] 9 | pub enum AuthTagError { 10 | // Invalid authentication tag size 11 | #[error("auth tag length {0} does not correspond to valid SHA-384 HMAC")] 12 | InvalidAuthTagSize(usize), 13 | } 14 | 15 | #[derive(Debug, Clone, Serialize, Deserialize)] 16 | pub struct AuthTag { 17 | bytes: Vec, 18 | } 19 | 20 | impl AsRef<[u8]> for AuthTag { 21 | fn as_ref(&self) -> &[u8] { 22 | self.bytes.as_slice() 23 | } 24 | } 25 | 26 | impl TryFrom<&[u8]> for AuthTag { 27 | type Error = AuthTagError; 28 | 29 | fn try_from(v: &[u8]) -> std::result::Result { 30 | match v.len() { 31 | AUTH_TAG_LEN => Ok(AuthTag { bytes: v.to_vec() }), 32 | _ => Err(AuthTagError::InvalidAuthTagSize(v.len())), 33 | } 34 | } 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use super::*; 40 | 41 | #[test] 42 | fn test_convert() { 43 | let a: [u8; AUTH_TAG_LEN] = [0xAA; AUTH_TAG_LEN]; 44 | let invalid: [u8; 32] = [0xBB; 32]; 45 | 46 | let r = AuthTag::try_from(a.as_ref()); 47 | assert!(r.is_ok()); 48 | 49 | let r = AuthTag::try_from(invalid.as_ref()); 50 | assert!(r.is_err()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /keylime/src/crypto/encrypted_data.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2025 Keylime Authors 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 7 | pub struct EncryptedData { 8 | bytes: Vec, 9 | } 10 | 11 | impl AsRef<[u8]> for EncryptedData { 12 | fn as_ref(&self) -> &[u8] { 13 | self.bytes.as_slice() 14 | } 15 | } 16 | 17 | impl From<&[u8]> for EncryptedData { 18 | fn from(v: &[u8]) -> Self { 19 | EncryptedData { bytes: v.to_vec() } 20 | } 21 | } 22 | 23 | impl From> for EncryptedData { 24 | fn from(v: Vec) -> Self { 25 | EncryptedData { bytes: v } 26 | } 27 | } 28 | 29 | #[cfg(test)] 30 | mod test { 31 | use super::*; 32 | 33 | #[test] 34 | fn test_encrypted_data_as_ref() { 35 | let a = EncryptedData { 36 | bytes: vec![0x0A, 16], 37 | }; 38 | 39 | let r = a.as_ref(); 40 | assert_eq!(r, vec![0x0A, 16]); 41 | } 42 | 43 | #[test] 44 | fn test_encrypted_data_from_slice() { 45 | let a: [u8; 16] = [0x0B; 16]; 46 | let expected = EncryptedData { 47 | bytes: vec![0x0B; 16], 48 | }; 49 | 50 | let r = EncryptedData::from(a.as_ref()); 51 | 52 | assert_eq!(r, expected); 53 | } 54 | 55 | #[test] 56 | fn test_encrypted_data_from_vec() { 57 | let a: Vec = vec![0x0C; 16]; 58 | let expected = EncryptedData { 59 | bytes: vec![0x0C; 16], 60 | }; 61 | 62 | let r = EncryptedData::from(a); 63 | 64 | assert_eq!(r, expected); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /keylime/src/crypto/symmkey.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2025 Keylime Authors 3 | 4 | use crate::crypto::{AES_128_KEY_LEN, AES_256_KEY_LEN}; 5 | use serde::{Deserialize, Serialize}; 6 | use thiserror::Error; 7 | 8 | #[derive(Debug, Error)] 9 | pub enum SymmKeyError { 10 | // Invalid key size for AES 11 | #[error("invalid AES key size: {0}")] 12 | InvalidKeySize(usize), 13 | 14 | // Incompatible sizes for XOR 15 | #[error("cannot XOR slices of different sizes")] 16 | XorIncompatibleSizes, 17 | } 18 | 19 | // a vector holding keys 20 | pub type KeySet = Vec; 21 | 22 | // a key of len AES_128_KEY_LEN or AES_256_KEY_LEN 23 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 24 | pub struct SymmKey { 25 | bytes: Vec, 26 | } 27 | 28 | impl SymmKey { 29 | pub fn xor(&self, other: &Self) -> Result { 30 | let my_bytes = self.as_ref(); 31 | let other_bytes = other.as_ref(); 32 | if my_bytes.len() != other_bytes.len() { 33 | return Err(SymmKeyError::XorIncompatibleSizes); 34 | } 35 | let mut outbuf = vec![0u8; my_bytes.len()]; 36 | for (out, (x, y)) in 37 | outbuf.iter_mut().zip(my_bytes.iter().zip(other_bytes)) 38 | { 39 | *out = x ^ y; 40 | } 41 | Ok(Self { bytes: outbuf }) 42 | } 43 | } 44 | 45 | impl AsRef<[u8]> for SymmKey { 46 | fn as_ref(&self) -> &[u8] { 47 | self.bytes.as_slice() 48 | } 49 | } 50 | 51 | impl TryFrom<&[u8]> for SymmKey { 52 | type Error = SymmKeyError; 53 | 54 | fn try_from(v: &[u8]) -> std::result::Result { 55 | match v.len() { 56 | AES_128_KEY_LEN | AES_256_KEY_LEN => { 57 | Ok(SymmKey { bytes: v.to_vec() }) 58 | } 59 | other => Err(SymmKeyError::InvalidKeySize(other)), 60 | } 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | use super::*; 67 | 68 | #[test] 69 | fn test_convert() { 70 | let a_128: [u8; AES_128_KEY_LEN] = [0; AES_128_KEY_LEN]; 71 | let a_256: [u8; AES_256_KEY_LEN] = [0; AES_256_KEY_LEN]; 72 | let a_unknown: [u8; 127] = [0; 127]; 73 | 74 | let r_128 = SymmKey::try_from(a_128.as_ref()); 75 | assert!(r_128.is_ok()); 76 | 77 | let r_256 = SymmKey::try_from(a_256.as_ref()); 78 | assert!(r_256.is_ok()); 79 | 80 | let r_unknown = SymmKey::try_from(a_unknown.as_ref()); 81 | assert!(r_unknown.is_err()); 82 | } 83 | 84 | #[test] 85 | fn test_xor() { 86 | // Input for 128 bits keys 87 | let a: [u8; AES_128_KEY_LEN] = [0xA0; AES_128_KEY_LEN]; 88 | let b: [u8; AES_128_KEY_LEN] = [0x0A; AES_128_KEY_LEN]; 89 | let axb: [u8; AES_128_KEY_LEN] = [0xAA; AES_128_KEY_LEN]; 90 | let r_128 = 91 | SymmKey::try_from(axb.as_ref()).expect("failed to convert"); 92 | 93 | // Input for 256 bits keys 94 | let c: [u8; AES_256_KEY_LEN] = [0xA0; AES_256_KEY_LEN]; 95 | let d: [u8; AES_256_KEY_LEN] = [0x0A; AES_256_KEY_LEN]; 96 | let cxd: [u8; AES_256_KEY_LEN] = [0xAA; AES_256_KEY_LEN]; 97 | let r_256 = 98 | SymmKey::try_from(cxd.as_ref()).expect("failed to convert"); 99 | 100 | // Test for each set of inputs 101 | for (i, j, expected) in [ 102 | (a.as_ref(), b.as_ref(), &r_128), 103 | (c.as_ref(), d.as_ref(), &r_256), 104 | ] { 105 | let k_i = 106 | SymmKey::try_from(i).expect("failed to get key from slice"); 107 | let k_j = 108 | SymmKey::try_from(j).expect("failed to get key from slice"); 109 | let result = k_i.xor(&k_j); 110 | assert!(result.is_ok()); 111 | 112 | let out = result.expect("xor failed"); 113 | assert_eq!(&out, expected); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /keylime/src/file_ops.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | pub fn read_file(path: &str) -> Result { 4 | fs::read_to_string(path) 5 | } 6 | 7 | #[cfg(test)] 8 | mod tests { 9 | use super::*; 10 | 11 | #[test] 12 | fn test_read_file() { 13 | let path = "test.txt"; 14 | fs::write(path, "Hello, world!").unwrap(); //#[allow_ci] 15 | let content = read_file(path).unwrap(); //#[allow_ci] 16 | assert_eq!(content, "Hello, world!"); 17 | fs::remove_file(path).unwrap(); //#[allow_ci] 18 | } 19 | 20 | #[test] 21 | fn test_read_nonexistent_file() { 22 | let result = read_file("nonexistent.txt"); 23 | assert!(result.is_err()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /keylime/src/global_config.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | hostname_parser::HostnameParsingError, ip_parser::IpParsingError, 3 | list_parser::ListParsingError, 4 | }; 5 | use config::ConfigError; 6 | use thiserror::Error; 7 | 8 | #[derive(Error, Debug)] 9 | pub enum KeylimeConfigError { 10 | // Error from config crate 11 | #[error("Error from the config crate")] 12 | Config(#[from] ConfigError), 13 | 14 | // Generic configuration error 15 | #[error("Configuration error: {0}")] 16 | Generic(String), 17 | 18 | // Glob error 19 | #[error("Glob pattern error")] 20 | GlobPattern(#[from] glob::PatternError), 21 | 22 | // Host name parsing error 23 | #[error("Host name parsing error")] 24 | HostnameParsing(#[from] HostnameParsingError), 25 | 26 | // Incompatible options error 27 | #[error("Incompatible configuration options '{option_a}' set as '{value_a}', but '{option_b}' is set as '{value_b}'")] 28 | IncompatibleOptions { 29 | option_a: String, 30 | value_a: String, 31 | option_b: String, 32 | value_b: String, 33 | }, 34 | 35 | // Infallible 36 | #[error("Infallible")] 37 | Infallible(#[from] std::convert::Infallible), 38 | 39 | // IP parsing error 40 | #[error("IP parsing error")] 41 | IpParsing(#[from] IpParsingError), 42 | 43 | // Unsupported type in configuration 44 | #[error( 45 | "Unsupported type conversion from serde_json::Value to config::Value" 46 | )] 47 | JsonConversion, 48 | 49 | // List parsing error 50 | #[error("List parsing error")] 51 | ListParsing(#[from] ListParsingError), 52 | 53 | // Missing configuration file set in KEYLIME_AGENT_CONFIG 54 | #[error("Missing file {file} set in 'KEYLIME_AGENT_CONFIG' environment variable")] 55 | MissingEnvConfigFile { file: String }, 56 | 57 | // Missing directory set in keylime_dir configuration option 58 | #[error( 59 | "Missing directory {path} set in 'keylime_dir' configuration option" 60 | )] 61 | MissingKeylimeDir { 62 | path: String, 63 | source: std::io::Error, 64 | }, 65 | 66 | #[error("Required option {0} not set in configuration")] 67 | RequiredOption(String), 68 | 69 | // Error from serde crate 70 | #[error("Serde error")] 71 | Serde(#[from] serde_json::Error), 72 | } 73 | -------------------------------------------------------------------------------- /keylime/src/hash_ek.rs: -------------------------------------------------------------------------------- 1 | use crate::keylime_error::Result; 2 | use openssl::hash::MessageDigest; 3 | use tss_esapi::structures::Public; 4 | 5 | use crate::crypto::{hash, tss_pubkey_to_pem}; 6 | 7 | /// Calculate the SHA-256 hash of the TPM public key in PEM format 8 | /// 9 | /// This is used as the agent UUID when the configuration option 'uuid' is set as 'hash_ek' 10 | pub fn hash_ek_pubkey(ek_pub: Public) -> Result { 11 | // Calculate the SHA-256 hash of the public key in PEM format 12 | let pem = tss_pubkey_to_pem(ek_pub)?; 13 | let hash = hash(&pem, MessageDigest::sha256())?; 14 | Ok(hex::encode(hash)) 15 | } 16 | -------------------------------------------------------------------------------- /keylime/src/hostname.pest: -------------------------------------------------------------------------------- 1 | hostname = {SOI ~ label ~ ("." ~ label)* ~ EOI} 2 | label = { ASCII_ALPHANUMERIC+ ~ ("-"+ ~ ASCII_ALPHANUMERIC+)*} 3 | -------------------------------------------------------------------------------- /keylime/src/hostname_parser.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2024 Keylime Authors 3 | 4 | use pest::Parser; 5 | use pest_derive::Parser; 6 | use thiserror::Error; 7 | 8 | #[derive(Parser)] 9 | #[grammar = "hostname.pest"] 10 | pub struct HostnameParser; 11 | 12 | #[derive(Error, Debug)] 13 | pub enum HostnameParsingError { 14 | #[error("Invalid input {0}")] 15 | InvalidInput(String), 16 | 17 | #[error("failed to parse the input {input}")] 18 | ParseError { 19 | input: String, 20 | source: Box>, 21 | }, 22 | } 23 | 24 | /// Parses a hostname from a string slice following RFC-1123 25 | /// 26 | /// Valid hostnames are formed by labels separated by dots ('.'). 27 | /// 28 | /// The labels can only contain alphanumeric characters ('a'..'z' | 'A'..'Z' | '0'..'9') and the 29 | /// hyphen ('-'). The labels cannot begin or end with an hyphen. 30 | /// 31 | /// # Arguments 32 | /// 33 | /// * `hostname` the string to be parsed 34 | /// 35 | /// # Returns 36 | /// 37 | /// The obtained hostname as a &str if it is a valid hostname 38 | /// 39 | /// # Examples 40 | /// 41 | /// Valid hostnames: 42 | /// 43 | /// * `hostname` 44 | /// * `host-name` 45 | /// * `a.b.c` 46 | /// * `a-b.c-d.e-f` 47 | /// 48 | /// Invalid hostnames: 49 | /// 50 | /// * `a_b.c` 51 | /// * `a.b-.c` 52 | /// * `a.-b.c` 53 | pub fn parse_hostname(hostname: &str) -> Result<&str, HostnameParsingError> { 54 | let Some(pair) = HostnameParser::parse(Rule::hostname, hostname) 55 | .map_err(|e| HostnameParsingError::ParseError { 56 | input: hostname.to_string(), 57 | source: Box::new(e), 58 | })? 59 | .next() 60 | else { 61 | return Err(HostnameParsingError::InvalidInput(hostname.to_string())); 62 | }; 63 | Ok(pair.as_str()) 64 | } 65 | 66 | // Unit Testing 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | 71 | #[test] 72 | fn test_parse_hostname() { 73 | // Sanity: most common case 74 | assert_eq!(parse_hostname("hostname").unwrap(), "hostname"); //#[allow_ci] 75 | assert_eq!(parse_hostname("ab.cd.ef").unwrap(), "ab.cd.ef"); //#[allow_ci] 76 | assert_eq!(parse_hostname("ab-cd-ef").unwrap(), "ab-cd-ef"); //#[allow_ci] 77 | 78 | // More advanced cases 79 | assert_eq!( 80 | parse_hostname("hostname-123.test").unwrap(), //#[allow_ci] 81 | "hostname-123.test" 82 | ); 83 | assert_eq!(parse_hostname("123-456.789").unwrap(), "123-456.789"); //#[allow_ci] 84 | assert_eq!(parse_hostname("1----9").unwrap(), "1----9"); //#[allow_ci] 85 | 86 | // Invalid input 87 | assert!(parse_hostname("-host-na.me").is_err()); 88 | assert!(parse_hostname("host-na.me-").is_err()); 89 | assert!(parse_hostname(".host-na.me").is_err()); 90 | assert!(parse_hostname("host-na.me.").is_err()); 91 | assert!(parse_hostname("host_name").is_err()); 92 | assert!(parse_hostname("host..name").is_err()); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /keylime/src/https_client.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use std::{ 3 | fs::{self, File}, 4 | io::Read, 5 | time::Duration, 6 | }; 7 | 8 | pub struct ClientArgs { 9 | pub ca_certificate: String, 10 | pub certificate: String, 11 | pub key: String, 12 | pub insecure: Option, 13 | pub timeout: u64, 14 | } 15 | 16 | pub fn get_https_client(args: &ClientArgs) -> Result { 17 | let mut builder = reqwest::Client::builder() 18 | .connection_verbose(true) 19 | .timeout(Duration::from_millis(args.timeout)); 20 | 21 | if args.insecure.unwrap_or(false) { 22 | builder = builder.danger_accept_invalid_certs(true); 23 | } else { 24 | // Get CA certificate from file 25 | let mut buf = Vec::new(); 26 | File::open(args.ca_certificate.clone()) 27 | .context(format!( 28 | "Failed to open '{}' file", 29 | args.ca_certificate 30 | ))? 31 | .read_to_end(&mut buf) 32 | .context(format!( 33 | "Failed to read '{}' to the end", 34 | args.ca_certificate 35 | ))?; 36 | let ca_cert = 37 | reqwest::Certificate::from_pem(&buf).context(format!( 38 | "Failed to parse certificate from PEM file '{}'", 39 | args.ca_certificate 40 | ))?; 41 | 42 | // Get client key and certificate from files 43 | let cert = fs::read(args.certificate.clone()).context(format!( 44 | "Failed to read client certificate from file '{}'", 45 | args.certificate 46 | ))?; 47 | let key = fs::read(args.key.clone()).context(format!( 48 | "Failed to read key from file '{}'", 49 | args.key 50 | ))?; 51 | let identity = reqwest::Identity::from_pkcs8_pem(&cert, &key) 52 | .context(format!( 53 | "Failed to add client identity from certificate '{}' and key '{}'", 54 | args.certificate, 55 | args.key 56 | ))?; 57 | 58 | builder = builder 59 | .add_root_certificate(ca_cert) 60 | .identity(identity) 61 | // Here we disable hostname verification because the certificates generated by the 62 | // verifier don't have the Subject Alternative Name (SAN) properly set, failing 63 | // hostname verification 64 | .danger_accept_invalid_hostnames(true) 65 | } 66 | builder.build().context("Failed to create HTTPS client") 67 | } 68 | -------------------------------------------------------------------------------- /keylime/src/ima/measurement_list.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2021 Keylime Authors 3 | 4 | use std::{ 5 | collections::HashSet, 6 | fs::File, 7 | io::{prelude::*, Error, SeekFrom}, 8 | }; 9 | 10 | /// MeasurementList models the IMA measurement lists's last two known 11 | /// numbers of entries in the log and filesizes at that point 12 | #[derive(Debug)] 13 | pub struct MeasurementList { 14 | entries: HashSet<(u64, u64)>, 15 | } 16 | 17 | impl MeasurementList { 18 | pub fn new() -> Self { 19 | Self { 20 | entries: HashSet::new(), 21 | } 22 | } 23 | 24 | pub fn reset(&mut self) { 25 | self.entries = HashSet::new(); 26 | } 27 | 28 | fn update(&mut self, num_entries: u64, filesize: u64) -> Option { 29 | if self.entries.len() > 32 { 30 | let e = *self.entries.iter().next()?; 31 | let _ = self.entries.remove(&e); 32 | } 33 | Some(self.entries.insert((num_entries, filesize))) 34 | } 35 | 36 | fn find(&self, nth_entry: u64) -> (u64, u64) { 37 | self.entries.iter().fold((0, 0), |best, entry| { 38 | if entry.0 > best.0 && entry.0 < nth_entry { 39 | *entry 40 | } else { 41 | best 42 | } 43 | }) 44 | } 45 | 46 | /// Read the IMA measurement list starting from a given entry. 47 | /// The entry may be of any value 0 <= entry <= entries_in_log where 48 | /// entries_in_log + 1 indicates that the client wants to read the next entry 49 | /// once available. If the entry is outside this range, the function will 50 | /// automatically read from the 0-th entry. 51 | /// This function returns the measurement list and the entry from where it 52 | /// was read and the current number of entries in the file. 53 | pub fn read( 54 | &mut self, 55 | ima_file: &mut File, 56 | nth_entry: u64, 57 | ) -> Result<(String, u64, u64), Error> { 58 | // Try to find the closest entry to the nth_entry 59 | let (mut num_entries, filesize) = self.find(nth_entry); 60 | 61 | let mut ml = None; 62 | let mut filedata = String::new(); 63 | let _ = ima_file.seek(SeekFrom::Start(filesize))?; 64 | let _ = ima_file.read_to_string(&mut filedata)?; 65 | let mut offset: usize = 0; 66 | 67 | loop { 68 | if nth_entry == num_entries { 69 | ml = Some(&filedata[offset..]); 70 | } 71 | let s = &filedata[offset..]; 72 | let idx = match s.find('\n') { 73 | None => break, 74 | Some(i) => i, 75 | }; 76 | offset = offset + idx + 1; 77 | num_entries += 1; 78 | } 79 | 80 | let _ = self.update(num_entries, filesize + offset as u64); 81 | 82 | match ml { 83 | None => self.read(ima_file, 0), 84 | Some(slice) => Ok((String::from(slice), nth_entry, num_entries)), 85 | } 86 | } 87 | } 88 | 89 | impl Default for MeasurementList { 90 | fn default() -> Self { 91 | Self::new() 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod tests { 97 | use super::*; 98 | use tempfile::NamedTempFile; 99 | 100 | #[test] 101 | fn read_measurement_list_test() { 102 | let mut ima_ml = MeasurementList::new(); 103 | 104 | let filedata = "0-entry\n1-entry\n2-entry\n"; 105 | let mut tf = NamedTempFile::new().unwrap(); //#[allow_ci] 106 | tf.write_all(filedata.as_bytes()).unwrap(); //#[allow_ci] 107 | tf.flush().unwrap(); //#[allow_ci] 108 | 109 | let mut ima_file = File::open(tf.path()).unwrap(); //#[allow_ci] 110 | 111 | // Request the 2nd entry, which is available 112 | let (ml, nth_entry, num_entries) = 113 | ima_ml.read(&mut ima_file, 2).unwrap(); //#[allow_ci] 114 | assert_eq!(num_entries, 3); 115 | assert_eq!(nth_entry, 2); 116 | assert_eq!(ml.find("2-entry").unwrap(), 0); //#[allow_ci] 117 | 118 | // Request the 3rd entry, which is not available yet, thus we get an empty list 119 | let (ml, nth_entry, num_entries) = 120 | ima_ml.read(&mut ima_file, 3).unwrap(); //#[allow_ci] 121 | assert_eq!(num_entries, 3); 122 | assert_eq!(nth_entry, 3); 123 | assert_eq!(ml.len(), 0); //#[allow_ci] 124 | 125 | // Request the 4th entry, which is beyond the next entry; since this is wrong, 126 | // we expect the entire list now. 127 | let (ml, nth_entry, num_entries) = 128 | ima_ml.read(&mut ima_file, 4).unwrap(); //#[allow_ci] 129 | assert_eq!(num_entries, 3); 130 | assert_eq!(nth_entry, 0); 131 | assert_eq!(ml.find("0-entry").unwrap(), 0); //#[allow_ci] 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /keylime/src/ima/mod.rs: -------------------------------------------------------------------------------- 1 | mod entry; 2 | mod measurement_list; 3 | 4 | pub use entry::*; 5 | pub use measurement_list::*; 6 | -------------------------------------------------------------------------------- /keylime/src/ip.pest: -------------------------------------------------------------------------------- 1 | WHITESPACE = _{ " " | NEWLINE | "\t" } 2 | 3 | ipv4 = { (ASCII_DIGIT+ ~ ".") ~ (ASCII_DIGIT+ ~ ".") ~ (ASCII_DIGIT+ ~ ".") ~ (ASCII_DIGIT)+ } 4 | 5 | ipv6 = { (ASCII_HEX_DIGIT* ~ ":")? ~ (ASCII_HEX_DIGIT+ ~ ":")* ~ 6 | (ASCII_HEX_DIGIT+)? ~ (":" ~ ASCII_HEX_DIGIT+)* ~ (":" ~ ASCII_HEX_DIGIT*)?} 7 | 8 | unbracketed = { ipv4 | ipv6 } 9 | bracketed = { "[" ~ unbracketed ~ "]" } 10 | 11 | ip = { SOI ~ (bracketed | unbracketed) ~ EOI } 12 | -------------------------------------------------------------------------------- /keylime/src/ip_parser.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2022 Keylime Authors 3 | 4 | use pest::{iterators::Pair, Parser}; 5 | use pest_derive::Parser; 6 | use std::net::{AddrParseError, Ipv4Addr, Ipv6Addr}; 7 | use thiserror::Error; 8 | 9 | #[derive(Parser)] 10 | #[grammar = "ip.pest"] 11 | pub struct IpParser; 12 | 13 | #[derive(Error, Debug)] 14 | pub enum IpParsingError { 15 | #[error("Invalid input {0}")] 16 | InvalidInput(String), 17 | 18 | #[error("Invalid IP")] 19 | InvalidIP(#[from] AddrParseError), 20 | 21 | #[error("failed to parse the input {input}")] 22 | ParseError { 23 | input: String, 24 | source: Box>, 25 | }, 26 | 27 | #[error("Unexpected end of input")] 28 | UnexpectedEOI, 29 | } 30 | 31 | fn get_inner_ip(pair: Pair) -> Result<&str, IpParsingError> { 32 | let Some(item) = pair.into_inner().next() else { 33 | unreachable!() 34 | }; 35 | 36 | match item.as_rule() { 37 | Rule::ip | Rule::bracketed | Rule::unbracketed => get_inner_ip(item), 38 | Rule::ipv4 => { 39 | // Validate the IP using the standard parser 40 | let _parsed_ipv4 = item.as_str().parse::()?; 41 | Ok(item.as_str()) 42 | } 43 | Rule::ipv6 => { 44 | // Validate the IP using the standard parser 45 | let _parsed_ipv6 = item.as_str().parse::()?; 46 | Ok(item.as_str()) 47 | } 48 | Rule::EOI => Err(IpParsingError::UnexpectedEOI), 49 | _ => { 50 | unreachable!() 51 | } 52 | } 53 | } 54 | 55 | /// Parses an ip address from a string slice removing eventual brackets. 56 | /// This is mostly to remove brackets when using IPv6 57 | /// 58 | /// Both IPv4 and IPv6 are supported: 59 | /// 60 | /// * The IPv4 and IPv6 can be inside square brackets ("[]") or not 61 | /// 62 | /// # Arguments 63 | /// 64 | /// * `ip` the string to be parsed 65 | /// 66 | /// # Returns 67 | /// 68 | /// The obtained ip as a &str without brackets, if they were present 69 | /// 70 | /// # Examples 71 | /// 72 | /// Valid input lists, and respective result: 73 | /// 74 | /// * `127.0.0.1` => `127.0.0.1` 75 | /// * `::1` => `::1` 76 | /// * `[127.0.0.1]` => `127.0.0.1 77 | /// * `[::1]` => `::1` 78 | pub fn parse_ip(ip: &str) -> Result<&str, IpParsingError> { 79 | let Some(pair) = IpParser::parse(Rule::ip, ip) 80 | .map_err(|e| IpParsingError::ParseError { 81 | input: ip.to_string(), 82 | source: Box::new(e), 83 | })? 84 | .next() 85 | else { 86 | return Err(IpParsingError::InvalidInput(ip.to_string())); 87 | }; 88 | get_inner_ip(pair) 89 | } 90 | 91 | // Unit Testing 92 | #[cfg(test)] 93 | mod tests { 94 | use super::*; 95 | 96 | #[test] 97 | fn test_parse_ip() { 98 | // Sanity: most common case 99 | assert_eq!(parse_ip("127.0.0.1").unwrap(), "127.0.0.1"); //#[allow_ci] 100 | assert_eq!(parse_ip("[127.0.0.1]").unwrap(), "127.0.0.1"); //#[allow_ci] 101 | assert_eq!(parse_ip("::1").unwrap(), "::1"); //#[allow_ci] 102 | assert_eq!(parse_ip("[::1]").unwrap(), "::1"); //#[allow_ci] 103 | 104 | // More advanced cases 105 | assert_eq!(parse_ip("::").unwrap(), "::"); //#[allow_ci] 106 | assert_eq!(parse_ip("::1").unwrap(), "::1"); //#[allow_ci] 107 | assert_eq!(parse_ip("1::").unwrap(), "1::"); //#[allow_ci] 108 | assert_eq!(parse_ip("1::1").unwrap(), "1::1"); //#[allow_ci] 109 | assert_eq!(parse_ip("[1::1]").unwrap(), "1::1"); //#[allow_ci] 110 | assert_eq!(parse_ip("1::2:3:4").unwrap(), "1::2:3:4"); //#[allow_ci] 111 | assert_eq!(parse_ip("1:2::3:4").unwrap(), "1:2::3:4"); //#[allow_ci] 112 | assert_eq!(parse_ip("1:2:3::4").unwrap(), "1:2:3::4"); //#[allow_ci] 113 | assert_eq!(parse_ip("1:2:3:4:5:6:7:8").unwrap(), "1:2:3:4:5:6:7:8"); //#[allow_ci] 114 | 115 | // Invalid input 116 | assert!(parse_ip("1").is_err()); 117 | assert!(parse_ip("1.2").is_err()); 118 | assert!(parse_ip("1.2.3.4.5").is_err()); 119 | assert!(parse_ip("1:2:3").is_err()); 120 | assert!(parse_ip("1::2::3").is_err()); 121 | assert!(parse_ip("1:2::3:4::5:6").is_err()); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /keylime/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod agent_data; 2 | pub mod agent_identity; 3 | pub mod agent_registration; 4 | pub mod algorithms; 5 | pub mod cert; 6 | pub mod config; 7 | pub mod context_info; 8 | pub mod crypto; 9 | pub mod device_id; 10 | pub mod file_ops; 11 | pub mod global_config; 12 | pub mod hash_ek; 13 | pub mod hostname_parser; 14 | pub mod https_client; 15 | pub mod ima; 16 | pub mod ip_parser; 17 | pub mod keylime_error; 18 | pub mod list_parser; 19 | pub mod quote; 20 | pub mod registrar_client; 21 | pub mod serialization; 22 | pub mod structures; 23 | pub mod tpm; 24 | pub mod version; 25 | 26 | #[macro_use] 27 | extern crate static_assertions; 28 | -------------------------------------------------------------------------------- /keylime/src/list.pest: -------------------------------------------------------------------------------- 1 | WHITESPACE = _{ " " | NEWLINE | "\t" } 2 | separator = { "," | NEWLINE | " " | "\t" } 3 | 4 | list = { SOI ~ (bracketed | unbracketed) ~ EOI } 5 | 6 | bracketed = { "[" ~ (bracketed_name)* ~ ("," ~ bracketed_name)* ~ ","? ~ "]" } 7 | unbracketed = { (name)* ~ ("," ~ name)* ~ ","? } 8 | 9 | single_content = @{ (!"'" ~ ANY)* } 10 | single = ${ "'" ~ single_content ~ "'" } 11 | 12 | double_content = @{ (!"\"" ~ ANY)* } 13 | double = ${ "\"" ~ double_content ~ "\"" } 14 | 15 | unquoted = @{ (!separator ~ ANY)+ } 16 | 17 | not_closer = @{ !("]" | separator) ~ ANY } 18 | bracketed_unquoted = @{ (!separator ~ !"]" ~ ANY)+ } 19 | 20 | name = { single | double | unquoted } 21 | bracketed_name = { single | double | bracketed_unquoted } 22 | -------------------------------------------------------------------------------- /keylime/src/list_parser.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2022 Keylime Authors 3 | 4 | use pest::{iterators::Pair, Parser}; 5 | use pest_derive::Parser; 6 | use thiserror::Error; 7 | 8 | #[derive(Parser)] 9 | #[grammar = "list.pest"] 10 | pub struct ListParser; 11 | 12 | #[derive(Error, Debug)] 13 | pub enum ListParsingError { 14 | #[error("failed to parse list")] 15 | ParseError(#[source] Box>), 16 | } 17 | 18 | fn get_inner_str(pair: Pair) -> Vec<&str> { 19 | let mut l = Vec::new(); 20 | for item in pair.into_inner() { 21 | match item.as_rule() { 22 | Rule::list 23 | | Rule::bracketed 24 | | Rule::unbracketed 25 | | Rule::name 26 | | Rule::bracketed_name => { 27 | l.extend(get_inner_str(item)); 28 | } 29 | Rule::single | Rule::double => { 30 | let s = item.as_str(); 31 | let c = get_inner_str(item); 32 | if !c.is_empty() { 33 | l.push(s); 34 | } 35 | } 36 | Rule::single_content 37 | | Rule::double_content 38 | | Rule::unquoted 39 | | Rule::bracketed_unquoted => { 40 | let i = item.as_str(); 41 | if !i.is_empty() { 42 | l.push(i); 43 | } 44 | } 45 | Rule::EOI => { 46 | break; 47 | } 48 | _ => { 49 | unreachable!() 50 | } 51 | } 52 | } 53 | l 54 | } 55 | 56 | /// Parses a list from a string slice and return a Vec<&str> 57 | /// 58 | /// The list in the input string: 59 | /// 60 | /// * can contain single-quoted, double-quoted, or unquoted strings 61 | /// * can be inside square brackets ("[]") or not 62 | /// * can have string separated by commas, white spaces, or newlines 63 | /// 64 | /// If the string is single-quoted or double-quoted, the quotes are kept in the 65 | /// output Vec<&str>. 66 | /// 67 | /// # Arguments 68 | /// 69 | /// * `list` the string to be parsed 70 | /// 71 | /// # Returns 72 | /// 73 | /// The obtained list as a Vec<&str> 74 | /// 75 | /// # Examples 76 | /// 77 | /// Valid input lists, and respective result: 78 | /// 79 | /// * `a b c` => `["a", "b", "c"]` 80 | /// * `'a' "b" c` => `["'a'", "\"b\"", "c"]` 81 | /// * `[a b c]` => `["a", "b", "c"]` 82 | /// * `['a', "b", c]` => `["'a'", "\"b\"", "c"]` 83 | /// 84 | pub fn parse_list(list: &str) -> Result, ListParsingError> { 85 | if let Some(pair) = ListParser::parse(Rule::list, list) 86 | .map_err(|e| ListParsingError::ParseError(Box::new(e)))? 87 | .next() 88 | { 89 | return Ok(get_inner_str(pair)); 90 | } 91 | Ok(Vec::new()) 92 | } 93 | 94 | // Unit Testing 95 | #[cfg(test)] 96 | mod tests { 97 | use super::*; 98 | 99 | #[test] 100 | fn test_parse_list() { 101 | // Sanity: most common case 102 | assert_eq!( 103 | parse_list("[\"aa\", \"bb\", \"cc\"]").unwrap(), //#[allow_ci] 104 | ["\"aa\"", "\"bb\"", "\"cc\""] 105 | ); 106 | 107 | // Unquoted 108 | assert_eq!(parse_list("default").unwrap(), ["default"]); //#[allow_ci] 109 | 110 | // Double-quoted 111 | assert_eq!(parse_list("\"[default]\"").unwrap(), ["\"[default]\""]); //#[allow_ci] 112 | 113 | // Single-quoted 114 | assert_eq!(parse_list("'[default]'").unwrap(), ["'[default]'"]); //#[allow_ci] 115 | 116 | // Brackets, no quotes 117 | assert_eq!(parse_list("[default]").unwrap(), ["default"]); //#[allow_ci] 118 | assert_eq!(parse_list("[aa, bb]").unwrap(), ["aa", "bb"]); //#[allow_ci] 119 | 120 | // Brackets, double-quotes 121 | assert_eq!(parse_list("[\"default\"]").unwrap(), ["\"default\""]); //#[allow_ci] 122 | assert_eq!( 123 | parse_list("[\"aa\", \"bb\"]").unwrap(), //#[allow_ci] 124 | ["\"aa\"", "\"bb\""] 125 | ); 126 | 127 | // Brackets, single-quotes 128 | assert_eq!(parse_list("['default']").unwrap(), ["'default'"]); //#[allow_ci] 129 | assert_eq!(parse_list("['aa', 'bb']").unwrap(), ["'aa'", "'bb'"]); //#[allow_ci] 130 | 131 | // Comma-separated 132 | assert_eq!(parse_list("aa,bb,cc").unwrap(), ["aa", "bb", "cc"]); //#[allow_ci] 133 | assert_eq!( 134 | parse_list("'aa','bb','cc'").unwrap(), //#[allow_ci] 135 | ["'aa'", "'bb'", "'cc'"] 136 | ); 137 | assert_eq!( 138 | parse_list("'aa',\"bb\",'cc'").unwrap(), //#[allow_ci] 139 | ["'aa'", "\"bb\"", "'cc'"] 140 | ); 141 | 142 | // Spaces-separated 143 | assert_eq!(parse_list("aa bb cc").unwrap(), ["aa", "bb", "cc"]); //#[allow_ci] 144 | assert_eq!( 145 | parse_list("'aa' 'bb' 'cc'").unwrap(), //#[allow_ci] 146 | ["'aa'", "'bb'", "'cc'"] 147 | ); 148 | assert_eq!( 149 | parse_list("\"aa\" \"bb\" \"cc\"").unwrap(), //#[allow_ci] 150 | ["\"aa\"", "\"bb\"", "\"cc\""] 151 | ); 152 | assert_eq!( 153 | parse_list("aa 'bb' \"cc\"").unwrap(), //#[allow_ci] 154 | ["aa", "'bb'", "\"cc\""] 155 | ); 156 | 157 | // New-line-separated 158 | assert_eq!(parse_list("aa\nbb\ncc").unwrap(), ["aa", "bb", "cc"]); //#[allow_ci] 159 | 160 | // Tab-separated 161 | assert_eq!(parse_list("aa\tbb\tcc").unwrap(), ["aa", "bb", "cc"]); //#[allow_ci] 162 | 163 | // Carriage-return-separated 164 | assert_eq!(parse_list("aa\rbb\rcc").unwrap(), ["aa", "bb", "cc"]); //#[allow_ci] 165 | 166 | // Corner cases 167 | 168 | // Entry named "[" 169 | assert_eq!(parse_list("\"[\"").unwrap(), ["\"[\""]); //#[allow_ci] 170 | assert_eq!(parse_list("'['").unwrap(), ["'['"]); //#[allow_ci] 171 | 172 | // Entry named "]" 173 | assert_eq!(parse_list("\"]\"").unwrap(), ["\"]\""]); //#[allow_ci] 174 | assert_eq!(parse_list("']'").unwrap(), ["']'"]); //#[allow_ci] 175 | assert_eq!(parse_list("]").unwrap(), ["]"]); //#[allow_ci] 176 | 177 | // Entry named "\'" 178 | assert_eq!(parse_list("\"'\"").unwrap(), ["\"'\""]); //#[allow_ci] 179 | assert_eq!(parse_list("[']").unwrap(), ["'"]); //#[allow_ci] 180 | 181 | // Entry named "\"" 182 | assert_eq!(parse_list("'\"'").unwrap(), ["'\"'"]); //#[allow_ci] 183 | assert_eq!(parse_list("[\"]").unwrap(), ["\""]); //#[allow_ci] 184 | 185 | // Entry named "," 186 | assert_eq!(parse_list("','").unwrap(), ["','"]); //#[allow_ci] 187 | assert_eq!(parse_list("\",\"").unwrap(), ["\",\""]); //#[allow_ci] 188 | 189 | // Entry named " " 190 | assert_eq!(parse_list("' '").unwrap(), ["' '"]); //#[allow_ci] 191 | assert_eq!(parse_list("\" \"").unwrap(), ["\" \""]); //#[allow_ci] 192 | 193 | // Empty lists 194 | assert_eq!(parse_list("[]").unwrap(), [] as [&str; 0]); //#[allow_ci] 195 | assert_eq!(parse_list("").unwrap(), [] as [&str; 0]); //#[allow_ci] 196 | assert_eq!(parse_list("\"\"").unwrap(), [] as [&str; 0]); //#[allow_ci] 197 | assert_eq!(parse_list("'', ''").unwrap(), [] as [&str; 0]); //#[allow_ci] 198 | assert_eq!(parse_list(" \n \t \r").unwrap(), [] as [&str; 0]); //#[allow_ci] 199 | assert_eq!(parse_list("[ \n \t \r]").unwrap(), [] as [&str; 0]); //#[allow_ci] 200 | 201 | // Uncommon cases 202 | assert_eq!(parse_list("[a,").unwrap(), ["[a"]); //#[allow_ci] 203 | assert_eq!(parse_list("[a b").unwrap(), ["[a", "b"]); //#[allow_ci] 204 | assert_eq!(parse_list("'[ ]").unwrap(), ["'[", "]"]); //#[allow_ci] 205 | 206 | // Error cases 207 | assert!(parse_list(",,").is_err()); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /keylime/src/quote.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Deserialize)] 4 | pub struct Integ { 5 | pub nonce: String, 6 | pub mask: String, 7 | pub partial: String, 8 | pub ima_ml_entry: Option, 9 | } 10 | 11 | #[derive(Serialize, Deserialize, Debug, Default)] 12 | pub struct KeylimeQuote { 13 | pub quote: String, // 'r' + quote + sig + pcrblob 14 | pub hash_alg: String, 15 | pub enc_alg: String, 16 | pub sign_alg: String, 17 | #[serde(skip_serializing_if = "Option::is_none")] 18 | pub pubkey: Option, 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub ima_measurement_list: Option, 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | pub mb_measurement_list: Option, 23 | #[serde(skip_serializing_if = "Option::is_none")] 24 | pub ima_measurement_list_entry: Option, 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use super::*; 30 | 31 | #[test] 32 | fn test_keylime_quote_serialization() { 33 | let quote = KeylimeQuote { 34 | quote: "example_quote".to_string(), 35 | hash_alg: "SHA256".to_string(), 36 | enc_alg: "AES".to_string(), 37 | sign_alg: "RSASSA-PSS".to_string(), 38 | pubkey: Some("example_pubkey".to_string()), 39 | ima_measurement_list: Some("example_ima_ml".to_string()), 40 | mb_measurement_list: None, 41 | ima_measurement_list_entry: Some(12345), 42 | }; 43 | 44 | let serialized = serde_json::to_string("e).unwrap(); //#[allow_ci] 45 | assert!(serialized.contains("example_quote")); 46 | assert!(serialized.contains("SHA256")); 47 | assert!(serialized.contains("AES")); 48 | assert!(serialized.contains("RSASSA-PSS")); 49 | assert!(serialized.contains("example_pubkey")); 50 | assert!(serialized.contains("example_ima_ml")); 51 | assert!(serialized.contains("12345")); 52 | 53 | let pretty_serialized = serde_json::to_string_pretty("e).unwrap(); //#[allow_ci] 54 | assert_eq!( 55 | pretty_serialized, 56 | r#"{ 57 | "quote": "example_quote", 58 | "hash_alg": "SHA256", 59 | "enc_alg": "AES", 60 | "sign_alg": "RSASSA-PSS", 61 | "pubkey": "example_pubkey", 62 | "ima_measurement_list": "example_ima_ml", 63 | "ima_measurement_list_entry": 12345 64 | }"# 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /keylime/src/serialization.rs: -------------------------------------------------------------------------------- 1 | use base64::{engine::general_purpose, Engine as _}; 2 | use serde::Deserialize; 3 | 4 | #[derive(Debug, Deserialize)] 5 | struct WrappedBase64Encoded( 6 | #[serde(deserialize_with = "deserialize_as_base64")] Vec, 7 | ); 8 | 9 | pub fn is_empty(buf: &[u8]) -> bool { 10 | buf.is_empty() 11 | } 12 | 13 | pub fn serialize_as_base64( 14 | bytes: &[u8], 15 | serializer: S, 16 | ) -> Result 17 | where 18 | S: serde::Serializer, 19 | { 20 | serializer.serialize_str(&general_purpose::STANDARD.encode(bytes)) 21 | } 22 | 23 | pub fn deserialize_as_base64<'de, D>( 24 | deserializer: D, 25 | ) -> Result, D::Error> 26 | where 27 | D: serde::Deserializer<'de>, 28 | { 29 | String::deserialize(deserializer).and_then(|string| { 30 | general_purpose::STANDARD 31 | .decode(string) 32 | .map_err(serde::de::Error::custom) 33 | }) 34 | } 35 | 36 | pub fn serialize_maybe_base64( 37 | value: &Option>, 38 | serializer: S, 39 | ) -> Result 40 | where 41 | S: serde::Serializer, 42 | { 43 | match *value { 44 | Some(ref value) => { 45 | serializer.serialize_str(&general_purpose::STANDARD.encode(value)) 46 | } 47 | None => serializer.serialize_none(), 48 | } 49 | } 50 | 51 | pub fn serialize_option_base64( 52 | value: &Option<&[u8]>, 53 | serializer: S, 54 | ) -> Result 55 | where 56 | S: serde::Serializer, 57 | { 58 | match *value { 59 | Some(value) => { 60 | serializer.serialize_str(&general_purpose::STANDARD.encode(value)) 61 | } 62 | None => serializer.serialize_none(), 63 | } 64 | } 65 | 66 | pub fn deserialize_maybe_base64<'de, D>( 67 | deserializer: D, 68 | ) -> Result>, D::Error> 69 | where 70 | D: serde::Deserializer<'de>, 71 | { 72 | Option::::deserialize(deserializer) 73 | .map(|wrapped| wrapped.map(|wrapped| wrapped.0)) 74 | } 75 | 76 | #[cfg(test)] 77 | mod test { 78 | use super::*; 79 | use serde::Serialize; 80 | 81 | #[derive(Serialize, Deserialize, Debug, PartialEq)] 82 | struct TestStruct<'a> { 83 | #[serde( 84 | serialize_with = "serialize_maybe_base64", 85 | deserialize_with = "deserialize_maybe_base64" 86 | )] 87 | maybe_base64: Option>, 88 | #[serde( 89 | serialize_with = "serialize_as_base64", 90 | skip_serializing_if = "is_empty" 91 | )] 92 | as_base64: &'a [u8], 93 | #[serde( 94 | serialize_with = "serialize_option_base64", 95 | skip_serializing_if = "Option::is_none" 96 | )] 97 | option_base64: Option<&'a [u8]>, 98 | } 99 | 100 | #[derive(Serialize, Deserialize, Debug, PartialEq)] 101 | struct TestResponse { 102 | #[serde(deserialize_with = "deserialize_maybe_base64", default)] 103 | maybe_base64: Option>, 104 | #[serde(deserialize_with = "deserialize_maybe_base64", default)] 105 | as_base64: Option>, 106 | #[serde(deserialize_with = "deserialize_maybe_base64", default)] 107 | option_base64: Option>, 108 | } 109 | 110 | #[test] 111 | fn test_serialization() { 112 | let maybe_base64: Option> = Some("test".as_bytes().to_vec()); 113 | let as_base64: &[u8] = "test".as_bytes(); 114 | 115 | let complete = TestStruct { 116 | maybe_base64, 117 | as_base64, 118 | option_base64: Some(as_base64), 119 | }; 120 | 121 | let serialized = serde_json::to_string(&complete).unwrap(); //#[allow_ci] 122 | 123 | assert_eq!(serialized, "{\"maybe_base64\":\"dGVzdA==\",\"as_base64\":\"dGVzdA==\",\"option_base64\":\"dGVzdA==\"}"); 124 | 125 | let deserialized: TestResponse = 126 | serde_json::from_str(&serialized).unwrap(); //#[allow_ci] 127 | 128 | assert_eq!( 129 | deserialized.maybe_base64, 130 | Some("test".as_bytes().to_vec()) 131 | ); 132 | assert_eq!(deserialized.as_base64, Some("test".as_bytes().to_vec())); 133 | assert_eq!( 134 | deserialized.option_base64, 135 | Some("test".as_bytes().to_vec()) 136 | ); 137 | 138 | let maybe_base64: Option> = Some("test".as_bytes().to_vec()); 139 | let options_missing = TestStruct { 140 | maybe_base64, 141 | as_base64: &[], 142 | option_base64: None, 143 | }; 144 | 145 | let serialized = serde_json::to_string(&options_missing).unwrap(); //#[allow_ci] 146 | 147 | // Expect the None and empty fields to be skipped 148 | assert_eq!(serialized, "{\"maybe_base64\":\"dGVzdA==\"}"); 149 | 150 | let deserialized: TestResponse = 151 | serde_json::from_str(&serialized).unwrap(); //#[allow_ci] 152 | 153 | assert_eq!( 154 | deserialized.maybe_base64, 155 | Some("test".as_bytes().to_vec()) 156 | ); 157 | assert_eq!(deserialized.as_base64, None); 158 | assert_eq!(deserialized.option_base64, None); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /keylime/src/structures/mod.rs: -------------------------------------------------------------------------------- 1 | mod capabilities_negotiation; 2 | mod evidence_handling; 3 | mod sessions; 4 | 5 | pub use capabilities_negotiation::*; 6 | pub use evidence_handling::*; 7 | pub use sessions::*; 8 | -------------------------------------------------------------------------------- /keylime/src/version.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::{fmt, str::FromStr}; 3 | use thiserror::Error; 4 | 5 | #[derive(Serialize, Deserialize, Debug)] 6 | pub struct KeylimeVersion { 7 | pub supported_version: String, 8 | } 9 | 10 | #[derive(Serialize, Deserialize, Debug)] 11 | pub struct KeylimeRegistrarVersion { 12 | pub current_version: String, 13 | pub supported_versions: Vec, 14 | } 15 | 16 | pub trait GetErrorInput { 17 | fn input(&self) -> String; 18 | } 19 | 20 | #[derive(Error, Debug)] 21 | pub enum VersionParsingError { 22 | /// The version input was malformed 23 | #[error("input '{input}' malformed as a version")] 24 | MalformedVersion { input: String }, 25 | 26 | /// The parts of the version were not numbers 27 | #[error("parts of version '{input}' were not numbers")] 28 | ParseError { 29 | input: String, 30 | source: std::num::ParseIntError, 31 | }, 32 | } 33 | 34 | impl GetErrorInput for VersionParsingError { 35 | fn input(&self) -> String { 36 | match self { 37 | VersionParsingError::MalformedVersion { input } => input.into(), 38 | VersionParsingError::ParseError { input, source: _ } => { 39 | input.into() 40 | } 41 | } 42 | } 43 | } 44 | 45 | // Implement the trait for all the references 46 | impl GetErrorInput for &T 47 | where 48 | T: GetErrorInput, 49 | { 50 | fn input(&self) -> String { 51 | (**self).input() 52 | } 53 | } 54 | 55 | #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] 56 | pub struct Version { 57 | major: u32, 58 | minor: u32, 59 | } 60 | 61 | impl fmt::Display for Version { 62 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 63 | write!(f, "{}.{}", self.major, self.minor) 64 | } 65 | } 66 | 67 | impl FromStr for Version { 68 | type Err = VersionParsingError; 69 | 70 | fn from_str(input: &str) -> Result { 71 | let mut parts = input.split('.'); 72 | match (parts.next(), parts.next()) { 73 | (Some(major), Some(minor)) => Ok(Version { 74 | major: major.parse().map_err(|e| { 75 | VersionParsingError::ParseError { 76 | input: input.to_string(), 77 | source: e, 78 | } 79 | })?, 80 | minor: minor.parse().map_err(|e| { 81 | VersionParsingError::ParseError { 82 | input: input.to_string(), 83 | source: e, 84 | } 85 | })?, 86 | }), 87 | _ => Err(VersionParsingError::MalformedVersion { 88 | input: input.to_string(), 89 | }), 90 | } 91 | } 92 | } 93 | 94 | impl TryFrom<&str> for Version { 95 | type Error = VersionParsingError; 96 | 97 | fn try_from(input: &str) -> Result { 98 | Version::from_str(input) 99 | } 100 | } 101 | 102 | impl TryFrom for Version { 103 | type Error = VersionParsingError; 104 | 105 | fn try_from(input: String) -> Result { 106 | Version::from_str(input.as_str()) 107 | } 108 | } 109 | 110 | #[cfg(test)] 111 | mod test { 112 | use super::*; 113 | 114 | #[test] 115 | fn test_from_str() { 116 | let v = Version::from_str("1.2").unwrap(); //#[allow_ci] 117 | assert_eq!(v, Version { major: 1, minor: 2 }); 118 | let v2: Version = "3.4".try_into().unwrap(); //#[allow_ci] 119 | assert_eq!(v2, Version { major: 3, minor: 4 }); 120 | let v3: Version = "5.6".to_string().try_into().unwrap(); //#[allow_ci] 121 | assert_eq!(v3, Version { major: 5, minor: 6 }); 122 | } 123 | 124 | #[test] 125 | fn test_display() { 126 | let s = format!("{}", Version { major: 1, minor: 2 }); 127 | assert_eq!(s, "1.2".to_string()); 128 | } 129 | 130 | #[test] 131 | fn test_ord() { 132 | let v11: Version = "1.1".try_into().unwrap(); //#[allow_ci] 133 | let v12: Version = "1.2".try_into().unwrap(); //#[allow_ci] 134 | let v21: Version = "2.1".try_into().unwrap(); //#[allow_ci] 135 | let v110: Version = "1.10".try_into().unwrap(); //#[allow_ci] 136 | assert!(v11 < v12); 137 | assert!(v12 < v110); 138 | assert!(v110 < v21); 139 | 140 | let mut v = vec![v12.clone(), v110.clone(), v11.clone()]; 141 | v.sort(); 142 | let expected = vec![v11, v12, v110]; 143 | assert_eq!(v, expected); 144 | } 145 | 146 | #[test] 147 | fn test_invalid() { 148 | let result = Version::from_str("a.b"); 149 | assert!(result.is_err()); 150 | let result = Version::from_str("1.b"); 151 | assert!(result.is_err()); 152 | let result = Version::from_str("a.2"); 153 | assert!(result.is_err()); 154 | let result = Version::from_str("22"); 155 | assert!(result.is_err()); 156 | let result = Version::from_str(".12"); 157 | assert!(result.is_err()); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /keylime/test-data/prime256v1.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFKDCCAxCgAwIBAgICEAIwDQYJKoZIhvcNAQEMBQAwWjELMAkGA1UEBhMCR0Ix 3 | EDAOBgNVBAgMB0VuZ2xhbmQxFjAUBgNVBAoMDUtleWxpbWUgVGVzdHMxITAfBgNV 4 | BAsMGEtleWxpbWUgVGVzdHMgSURldklEIElBSzAeFw0yNDAyMDkxNzA1NDNaFw0y 5 | NjExMDQxNzA1NDNaMIGjMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEV 6 | MBMGA1UEBwwMRGVmYXVsdCBDaXR5MRYwFAYDVQQKDA1LZXlsaW1lIFRlc3RzMSEw 7 | HwYDVQQLDBhLZXlsaW1lIFRlc3RzIElEZXZJRCBJQUsxEzARBgNVBAMMCnByaW1l 8 | MjU2djExGzAZBgkqhkiG9w0BCQEWDHByaW1lQDI1Ni52MTBZMBMGByqGSM49AgEG 9 | CCqGSM49AwEHA0IABP8NOjf+hsV1K4TS4BwiLYsUI6rfGp0gru1hXIztoNRtz8sR 10 | rSGD+pypc3pp++rsjO+QgtFpk/+Wt12ijWNslaujggF3MIIBczAJBgNVHRMEAjAA 11 | MBEGCWCGSAGG+EIBAQQEAwIGQDAzBglghkgBhvhCAQ0EJhYkT3BlblNTTCBHZW5l 12 | cmF0ZWQgU2VydmVyIENlcnRpZmljYXRlMB0GA1UdDgQWBBTl0udpJzE+J2XwqSLa 13 | S6Aa1r3FHjBgBgNVHSMEWTBXgBTMmxEZjh+joMUGhuAc0t+rk343y6E7pDkwNzEL 14 | MAkGA1UEBhMCR0IxEDAOBgNVBAgMB0VuZ2xhbmQxFjAUBgNVBAoMDUtleWxpbWUg 15 | VGVzdHOCAhAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATB4 16 | BgNVHREEcTBvoG0GCCsGAQUFBwgEoGEwXwYFZ4EFAQIEVlNUAAAAAAAAAAAAAAAA 17 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 18 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA0GCSqGSIb3DQEBDAUAA4ICAQCf4u72 19 | 8q+ltmSAcuFSs0slyMdFCI0eRIxzzKxdJDtPgBgNFNZiGlAAaux17k9G5uuLnQ8V 20 | H/yL0n5CUkW3yNz/9YuT4fGFRoEuSS07SeGlqS5akIeaFFUEH0eO4YMhZ+3sSCZF 21 | 0Glo6urZnT5uTTs9J4DGktRlxeSsANfaKnDb3PUxKgY62ve+F4BvslQK2qTsk1Ve 22 | vDipLAwvjSPAZug07y7NWCr7p/or5kQq6eV9IWfDI9xYcJNpDEuyYFojZ6+XTQQJ 23 | nxH/FY2vgjx24Vht+Pz16xSpzOpl/ZNPaCF5pybT91bjB6MyBdfbWrLMjxIDjtPK 24 | hOqdIDH9sqDDPHgWum6xdYv2RXVNHMLdFxzrvqzN1AInLaFBwgkyhh3nhqhsERAq 25 | tpDTnOeaIQXCaYezx0A4iKGq4GOssrJx+ebbpRUzpPLT1lAghlEMnRHjZzMAzuJ3 26 | r+rB9t1eyxKEQMWgC5qIW05ExeIpOn2a16YjkCgW1UUY+SkHpOm19GuGXgau/M3y 27 | h9RwO9YkDCTfK/O6JhG0QNUGjNBx49VSE/Z+0B5fdmhLDJwjN5ajO6hoy5n3NrEy 28 | L0VaXwhlOKMnc/C45pOfknz/3rHPv22dazLe5yKht1x6dOUGKmgP9H27naeHYEn8 29 | d5D/jJkoFFnifMJ8Tji1CsloIcNHnN381tgh2Q== 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /keylime/test-data/prime256v1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEICJWrrdTKyFA/pCrmbA9rPkmDz/WMBEmwqtxiLA2+8e3oAoGCCqGSM49 3 | AwEHoUQDQgAE/w06N/6GxXUrhNLgHCItixQjqt8anSCu7WFcjO2g1G3PyxGtIYP6 4 | nKlzemn76uyM75CC0WmT/5a3XaKNY2yVqw== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /keylime/test-data/test-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDBTCCAe2gAwIBAgIUMdvVLurhVJw+zgZU+tDkLs5Gzo0wDQYJKoZIhvcNAQEL 3 | BQAwEjEQMA4GA1UEAwwHa2V5bGltZTAeFw0yMjAxMjYxNjM3NTRaFw0yMzAxMjYx 4 | NjM3NTRaMBIxEDAOBgNVBAMMB2tleWxpbWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB 5 | DwAwggEKAoIBAQDtx7FvpgAvcF46+UwVoETA+KmWrzPtpzai8BTmF4oOOX3GXMtp 6 | vtjDFCYVvmbUWeQN8LqMBKoJ0O9mzB82FtXZAggSMoIy8Gimcq0TqSNCWFRs61Ho 7 | KlkeJk5gcmgG1DiMzQ6Cp+A71aKrgheaxe4t44KkP6YldF6UAWduzUL3oJQ7QsQj 8 | IWA5i0fZu+ZyTqImo9NzN20KqMCawtvCXjwUmA4qVPGgne6S0GggCnTdd7LAb15/ 9 | XPexmu+OWMH8pcfzp4wTlqar/cfJpKnb5aaemOzwwIhEMfp4gTfXyVKMP+3qCp77 10 | KwbUyXDIMXBWssig85z7aGwVUmA00rQz5REfAgMBAAGjUzBRMB0GA1UdDgQWBBTw 11 | nW9LUu8SeBOQJcL0MHkxxiMhejAfBgNVHSMEGDAWgBTwnW9LUu8SeBOQJcL0MHkx 12 | xiMhejAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB7DSJa3I75 13 | e4Zgdmvzt9CUqvgxTUb2gzevXBk3QZclBogNXDoQNYYm1eifZ8PGNj14kDBwPWQi 14 | rt0hB99O0eety5qUj7ro8lRzd7uZ/TrDGyt/mUJt05DU4zeH9mLLspQFfQqq18sO 15 | 5ytnqfrLANV+a8WUgqj/e12pkIvPfzlm8UUKW8qniEdiyVvh1MW8lmnJnlGk0AJn 16 | fpdJO1jc+1c+MTngHN/K81e8Irn+Z9pR6xOmGpZdypnQfLJpHzCyE5vpLQEVxd28 17 | 3kts+VSvxSz1kaKI15mZHykWZ+L1DGYRG9Oopz49uPb9VjqUrSiWjc2lviLbEPeb 18 | pJmGJUTwt5ea 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /keylime/test-data/test-quote.txt: -------------------------------------------------------------------------------- 1 | r/1RDR4AYABYABPihP2yz+HcGF0vD0c4qiKt4nvSOAARURVNUAAAAAAAyQ9AAAAAAAAAAAAEgGRAjABY2NgAAAAEABAMAAAEAFCkk4YmhQECgWR+MnHqT9zftc3J8:ABQABAEAQ8IwX6Ak83zGhF6w8vOKOxsyTbxACQakYWGJaan3ewf+2O9TtiH5TLB1PXrPdhknsR/yx6OVUze9jTDvML9xkkK1ghXObCJ5gH+QX0udKfrLacm/iMds28SBtVO0rjqDIoYqGgXhH2ZhwGNDwjRCp6HquvtBe7pGEgtZlxf7Hr3wQRLO3FtliBPBR6gjOo7NC/uGsuPjdPU7c9ls29NgYSqdwShuNdRzwmZrF57umuUgF6GREFlxqLkGcbDIT1itV4zJZtI1caLVxqiH0Qv3sNqlNLsSHggkgc5S2EvNqwv/TsEZOq/leCoLtyVGYghPeGwg0RJfbe8cdyBWCQ6nOA==:AQAAAAQAAwAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAUABdJ/ntmsqy2aDi6NhKnLKz4k4uEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 2 | -------------------------------------------------------------------------------- /keylime/test-data/test-rsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpgIBAAKCAQEA7cexb6YAL3BeOvlMFaBEwPiplq8z7ac2ovAU5heKDjl9xlzL 3 | ab7YwxQmFb5m1FnkDfC6jASqCdDvZswfNhbV2QIIEjKCMvBopnKtE6kjQlhUbOtR 4 | 6CpZHiZOYHJoBtQ4jM0OgqfgO9Wiq4IXmsXuLeOCpD+mJXRelAFnbs1C96CUO0LE 5 | IyFgOYtH2bvmck6iJqPTczdtCqjAmsLbwl48FJgOKlTxoJ3uktBoIAp03XeywG9e 6 | f1z3sZrvjljB/KXH86eME5amq/3HyaSp2+Wmnpjs8MCIRDH6eIE318lSjD/t6gqe 7 | +ysG1MlwyDFwVrLIoPOc+2hsFVJgNNK0M+URHwIDAQABAoIBAQCspZ8XAwAFceBp 8 | j5OH7Eufla2FRIc+2neYTRvPiW3rMCE7wyrLCBBZbKrOhOYi73XgDVdVzRktcXAy 9 | Qqmy21fAbnIvzE6u79H8cS1sJhX82SfLwf1BxmXYt1WXP9p6guLgkQ8lHQF6UH8B 10 | ar762RY8aYH1AmX/sgPuESrpz838/0v4Tq52yJeSiGOS3MG6iRWQ6/K0CHT3olbH 11 | RDN/48YrCSRP3RkAXoGcbev59+qOFJazC2av+qQM+59FwM2SrzrKa7RJLSQ1+wEz 12 | gZEmmgJu6tOVUnAd7mg5VS+N7cXu6+Xl1jCg1N7XaQWh3nRCI1bysJ9UJQQZHl0B 13 | gnIDXAU5AoGBAPex+wA1oaf4y22PIrVoLhD1HksdIxCOhmMYkojaIo3qZCVi8bS8 14 | JnUiHhvo25xYuUtB2Qw0VstzBaq6RDUnVgO3yVBYABc9MSmO+up/2ljNb9S5uWrm 15 | +FzqjCRIWwABTWVyI7zvgVKSuSbvVLAQkgwAEBibp26V5E5SmuB4uKcNAoGBAPXA 16 | m8n9Ajo+2Fzage0JZuW4uELbCqUwCkAlwJoSP42y0SIpB5fITkwRIwOat1MEhQ0I 17 | yh68ZxGvqfE2fO78ZbE2bSlKkkadrZjPFAG2svd57MdHnR5c4iCm6ui7D0snCgcg 18 | pUt2qlXYesABWb7o3tKOezDlbjFtv9JDJPOmnI3bAoGBAJw/RINsUW5BDiotWYqv 19 | jieaSCK/3YerMHDAZmc3mwaErem7kZcd/PB0tiOK70Wf3jrv7be6KGosQ43f8/jH 20 | uIWd4Lry2BPQwPtjOzrDrfvIk9vP0Hvz+QW72u1kSyskpyrwJkUfnCd3cJ5z6Ksr 21 | uMUjIQQ05BhpK1yQ1Sv2WxzdAoGBAKkLwd5SzOp1+mz83azI7+ALjaxnck4o2pQ/ 22 | o9oXvWHiZFuEL7Xn0nweuaAsF/jiPge2SRqVbKzM0jCb05qtQeKB1ts1caNjqVtY 23 | 7qEzJK55TzfRejG9oMrnJuXKbv26L/qxKSLc0NTWYbGb/DkHhOb/nZwH5iHYJcAj 24 | 8dIshLpLAoGBAK9RBWaP3KdDheUDOVpSSnOjVOtzq0T2qHPLklpdZzz2BHGKTNly 25 | qek7gOTiy/DvON0F/gFhVi0+7RnCG+CnMXe2Ac/vn3QqZLAL5pSc+AUhmfE72hn2 26 | HNz0GFbYmFqr8QpzXG7b4aCF/yxcPrHGN3LdBKZvPPv7OmcCPGVEOKVk 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /keylime/test-data/test-rsa.sig: -------------------------------------------------------------------------------- 1 | Xjqywb8k8mjMREThTm9iicVtX8x1GPQCUqmgbyoNAp0UunA8+QbQ7ha1P205+VhegKz+6iD/DZb8nZlr/Tu3xw7NVEWxXQfzQrqhLkS78RU+tWuxSi6y5Qb1tHqZHcP7DUpDCt2DZnidKGaI0oybEsdVXy7UF6zm1gobvRBsFWyacgwTWh/PAYU7S2yfuX7sKrBP4Yw59STpb2GnsAhfFhqBk2RnfGsDjD/DDAn+CNxacKQzeoq9i6XPTy7fk8xY01yTi9cbOWu1ZWQw9cpYO/R0S1Bn4A7ksyZLWp3X9HjMrhOa2TRfXZHRv4PZUIymSiiPTj3TQdQu6S11v1pHxw== -------------------------------------------------------------------------------- /license-header.tpl: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright {\d+} Keylime Authors 3 | -------------------------------------------------------------------------------- /packit-ci.fmf: -------------------------------------------------------------------------------- 1 | /e2e: 2 | 3 | summary: run keylime e2e tests 4 | 5 | environment: 6 | TPM_BINARY_MEASUREMENTS: /var/tmp/binary_bios_measurements 7 | RUST_IMA_EMULATOR: 1 8 | KEYLIME_RUST_CODE_COVERAGE: 1 9 | 10 | context: 11 | swtpm: yes 12 | agent: rust 13 | faked_measured_boot_log: yes 14 | 15 | prepare: 16 | - how: shell 17 | script: 18 | - ln -s $(pwd) /var/tmp/rust-keylime_sources 19 | - dnf makecache 20 | - systemctl disable --now dnf-makecache.service || true 21 | - systemctl disable --now dnf-makecache.timer || true 22 | 23 | discover: 24 | how: fmf 25 | url: https://github.com/RedHat-SP-Security/keylime-tests 26 | ref: main 27 | test: 28 | - /setup/apply_workarounds 29 | - /setup/configure_tpm_emulator 30 | - /setup/install_upstream_keylime 31 | - /setup/install_upstream_rust_keylime 32 | # change IMA policy to simple and run one attestation scenario 33 | # this is to utilize also a different parser 34 | - /setup/configure_kernel_ima_module/ima_policy_simple 35 | - /functional/basic-attestation-on-localhost 36 | # now change IMA policy to signing and run all tests 37 | - /setup/configure_kernel_ima_module/ima_policy_signing 38 | - /compatibility/api_version_compatibility 39 | - /compatibility/basic-attestation-on-localhost-api-version-bump 40 | - /compatibility/basic-attestation-on-localhost-with-allowlist-excludelist 41 | - /functional/agent_UUID_assignment_options 42 | - /functional/basic-attestation-on-localhost 43 | - /functional/basic-attestation-with-custom-certificates 44 | - /functional/basic-attestation-with-concatenated-certificates 45 | - /functional/basic-attestation-with-ima-signatures 46 | - /functional/basic-attestation-without-mtls 47 | - /functional/basic-attestation-with-unpriviledged-agent 48 | - /functional/db-postgresql-sanity-on-localhost 49 | - /functional/db-mariadb-sanity-on-localhost 50 | - /functional/db-mysql-sanity-on-localhost 51 | - /functional/durable-attestion-sanity-on-localhost 52 | - /functional/ek-cert-use-ek_check_script 53 | - /functional/ek-cert-use-ek_handle-custom-ca_certs 54 | - /functional/iak-idevid-persisted-and-protected 55 | - /functional/iak-idevid-register-with-certificates 56 | - /functional/install-rpm-with-ima-signature 57 | - /functional/keylime-non-default-ports 58 | - /functional/keylime_create_policy-static-data 59 | - /functional/keylime_policy-commands 60 | - /functional/keylime_tenant-commands-on-localhost 61 | - /functional/keylime_tenant-ima-signature-sanity 62 | - /functional/measured-boot-swtpm-sanity 63 | - /functional/service-logfiles-logging 64 | - /functional/tenant-runtime-policy-sanity 65 | - /functional/tpm-issuer-cert-using-ecc 66 | - /functional/tpm_policy-sanity-on-localhost 67 | - /functional/use-multiple-ima-sign-verification-keys 68 | - /functional/webhook-certificate-on-localhost 69 | - /regression/cve-2023-38200 70 | - /regression/cve-2023-38201 71 | - /regression/CVE-2023-3674 72 | - /regression/issue-1380-agent-removed-and-re-added 73 | - /regression/keylime-agent-option-override-through-envvar 74 | - /sanity/keylime-secure_mount 75 | - /upstream/run_rust_keylime_tests 76 | - /setup/generate_upstream_rust_keylime_code_coverage 77 | 78 | adjust: 79 | # prepare step adjustments 80 | - when: distro == centos-stream-9 81 | prepare+: 82 | - how: shell 83 | script: 84 | - yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm 85 | # disable code coverage measurement everywhere except F41 and CS9 86 | - when: distro != fedora-41 87 | environment+: 88 | KEYLIME_RUST_CODE_COVERAGE: 0 89 | discover+: 90 | test-: 91 | - /setup/generate_upstream_rust_keylime_code_coverage 92 | 93 | execute: 94 | how: tmt 95 | -------------------------------------------------------------------------------- /rpm/README.md: -------------------------------------------------------------------------------- 1 | # Keylime testing RPM 2 | 3 | The specfiles in this directory are used to build RPM packages on Copr using 4 | packit for testing purposes. Do not use the RPM built using these files in a 5 | production environment. 6 | 7 | The goal is to avoid recompiling the project multiple times during testing. 8 | 9 | The binaries in the test RPM are build with the `testing` feature enabled. 10 | -------------------------------------------------------------------------------- /rpm/centos/keylime-agent-rust.spec: -------------------------------------------------------------------------------- 1 | # keylime-agent-rust.spec 2 | 3 | %bcond_without check 4 | 5 | %global crate keylime_agent 6 | 7 | # Centos: Use bundled deps as it doesn't ship Rust libraries 8 | %global bundled_rust_deps 1 9 | %global __brp_mangle_shebangs_exclude_from ^/usr/src/debug/.*$ 10 | 11 | Name: keylime-agent-rust 12 | Version: 0.2.7 13 | Release: %{?autorelease}%{!?autorelease:1%{?dist}} 14 | Summary: Rust agent for Keylime 15 | 16 | # Upstream license specification: Apache-2.0 17 | # 18 | # The build dependencies have the following licenses: 19 | # 20 | # 0BSD or MIT or ASL 2.0 21 | # ASL 2.0 22 | # ASL 2.0 or Boost 23 | # ASL 2.0 or MIT 24 | # ASL 2.0 with exceptions 25 | # BSD 26 | # MIT 27 | # MIT or ASL 2.0 28 | # MIT or ASL 2.0 or zlib 29 | # MIT or zlib or ASL 2.0 30 | # Unlicense or MIT 31 | # zlib or ASL 2.0 or MIT 32 | # 33 | License: ASL 2.0 and BSD and MIT 34 | URL: https://github.com/keylime/rust-keylime/ 35 | Source0: rust-keylime-v%{version}.tar.gz 36 | # The vendor tarball is created using cargo-vendor-filterer to remove Windows 37 | # related files (https://github.com/cgwalters/cargo-vendor-filterer) 38 | # tar xf rust-keylime-%%{version}.tar.gz 39 | # cd rust-keylime-%%{version} 40 | # cargo vendor-filterer --platform x86_64-unknown-linux-gnu \ 41 | # --platform powerpc64le-unknown-linux-gnu \ 42 | # --platform aarch64-unknown-linux-gnu \ 43 | # --platform i686-unknown-linux-gnu \ 44 | # --platform s390x-unknown-linux-gnu \ 45 | # --exclude-crate-path "libloading#tests" 46 | # tar jcf rust-keylime-%%{version}-vendor.tar.xz vendor 47 | Source1: rust-keylime-vendor.tar.xz 48 | 49 | ExclusiveArch: %{rust_arches} 50 | 51 | Requires: tpm2-tss 52 | Requires: util-linux-core 53 | 54 | # The keylime-base package provides the keylime user creation. It is available 55 | # from Fedora 36 56 | %if 0%{?fedora} >= 36 || 0%{?rhel} >= 9 57 | Requires: keylime-base 58 | %endif 59 | 60 | BuildRequires: systemd 61 | BuildRequires: openssl-devel 62 | BuildRequires: tpm2-tss-devel 63 | BuildRequires: clang 64 | BuildRequires: rust-toolset 65 | 66 | # Virtual Provides to support swapping between Python and Rust implementation 67 | Provides: keylime-agent 68 | Conflicts: keylime-agent 69 | 70 | %description 71 | Rust agent for Keylime 72 | 73 | %prep 74 | %autosetup -n rust-keylime-%{version} -N 75 | %autopatch -m 100 -p1 76 | # Source1 is vendored dependencies 77 | %cargo_prep -V 1 78 | 79 | %build 80 | %if 0%{?rhel} >= 10 81 | %cargo_build -ftesting 82 | %else 83 | %cargo_build --features=testing 84 | %endif 85 | 86 | %install 87 | 88 | mkdir -p %{buildroot}/%{_sharedstatedir}/keylime 89 | mkdir -p --mode=0700 %{buildroot}/%{_rundir}/keylime 90 | mkdir -p --mode=0700 %{buildroot}/%{_libexecdir}/keylime 91 | mkdir -p --mode=0700 %{buildroot}/%{_sysconfdir}/keylime 92 | mkdir -p --mode=0700 %{buildroot}/%{_sysconfdir}/keylime/agent.conf.d 93 | 94 | install -Dpm 400 keylime-agent.conf \ 95 | %{buildroot}%{_sysconfdir}/keylime/agent.conf 96 | 97 | install -Dpm 644 ./dist/systemd/system/keylime_agent.service \ 98 | %{buildroot}%{_unitdir}/keylime_agent.service 99 | 100 | install -Dpm 644 ./dist/systemd/system/var-lib-keylime-secure.mount \ 101 | %{buildroot}%{_unitdir}/var-lib-keylime-secure.mount 102 | 103 | # Setting up the agent to use keylime:keylime user/group after dropping privileges. 104 | cat > %{buildroot}/%{_sysconfdir}/keylime/agent.conf.d/001-run_as.conf << EOF 105 | [agent] 106 | run_as = "keylime:keylime" 107 | EOF 108 | 109 | install -Dpm 0755 \ 110 | -t %{buildroot}%{_bindir} \ 111 | ./target/release/keylime_agent 112 | install -Dpm 0755 \ 113 | -t %{buildroot}%{_bindir} \ 114 | ./target/release/keylime_ima_emulator 115 | 116 | %posttrans 117 | chmod 500 %{_sysconfdir}/keylime/agent.conf.d 118 | chmod 400 %{_sysconfdir}/keylime/agent.conf.d/*.conf 119 | chmod 500 %{_sysconfdir}/keylime 120 | chown -R keylime:keylime %{_sysconfdir}/keylime 121 | 122 | %preun 123 | %systemd_preun keylime_agent.service 124 | %systemd_preun var-lib-keylime-secure.mount 125 | 126 | %postun 127 | %systemd_postun_with_restart keylime_agent.service 128 | %systemd_postun_with_restart var-lib-keylime-secure.mount 129 | 130 | %files 131 | %license LICENSE 132 | %doc README.md 133 | %attr(500,keylime,keylime) %dir %{_sysconfdir}/keylime 134 | %attr(500,keylime,keylime) %dir %{_sysconfdir}/keylime/agent.conf.d 135 | %config(noreplace) %attr(400,keylime,keylime) %{_sysconfdir}/keylime/agent.conf.d/001-run_as.conf 136 | %config(noreplace) %attr(400,keylime,keylime) %{_sysconfdir}/keylime/agent.conf 137 | %{_unitdir}/keylime_agent.service 138 | %{_unitdir}/var-lib-keylime-secure.mount 139 | %attr(700,keylime,keylime) %dir %{_rundir}/keylime 140 | %attr(700,keylime,keylime) %{_sharedstatedir}/keylime 141 | %attr(700,keylime,keylime) %{_libexecdir}/keylime 142 | %{_bindir}/keylime_agent 143 | %{_bindir}/keylime_ima_emulator 144 | 145 | %if %{with check} 146 | %check 147 | %cargo_test 148 | %endif 149 | 150 | %changelog 151 | %autochangelog 152 | -------------------------------------------------------------------------------- /rpm/fedora/keylime-agent-rust.spec: -------------------------------------------------------------------------------- 1 | # keylime-agent-rust.spec 2 | 3 | %bcond_without check 4 | 5 | %global crate keylime_agent 6 | 7 | # On Fedora-38 and current Rawhide, it is not possible to build due to missing 8 | # dependency base64 version 0.13 (required by rust-tss-esapi) 9 | # Also due to https://github.com/tpm2-software/tpm2-tools/issues/3210, 10 | # tpm2-tools is currently broken. 11 | # Use vendored dependencies for all Fedora versions. 12 | %global bundled_rust_deps 1 13 | 14 | %global __brp_mangle_shebangs_exclude_from ^/usr/src/debug/.*$ 15 | 16 | Name: keylime-agent-rust 17 | Version: 0.2.7 18 | Release: %{?autorelease}%{!?autorelease:1%{?dist}} 19 | Summary: Rust agent for Keylime 20 | 21 | # Upstream license specification: Apache-2.0 22 | # 23 | # The build dependencies have the following licenses: 24 | # 25 | # 0BSD or MIT or ASL 2.0 26 | # ASL 2.0 27 | # ASL 2.0 or Boost 28 | # ASL 2.0 or MIT 29 | # ASL 2.0 with exceptions 30 | # BSD 31 | # MIT 32 | # MIT or ASL 2.0 33 | # MIT or ASL 2.0 or zlib 34 | # MIT or zlib or ASL 2.0 35 | # Unlicense or MIT 36 | # zlib or ASL 2.0 or MIT 37 | # 38 | License: ASL 2.0 and BSD and MIT 39 | URL: https://github.com/keylime/rust-keylime/ 40 | Source0: rust-keylime-v%{version}.tar.gz 41 | # The vendor tarball is created using cargo-vendor-filterer to remove Windows 42 | # related files (https://github.com/cgwalters/cargo-vendor-filterer) 43 | # tar xf rust-keylime-%%{version}.tar.gz 44 | # cd rust-keylime-%%{version} 45 | # cargo vendor-filterer --platform x86_64-unknown-linux-gnu \ 46 | # --platform powerpc64le-unknown-linux-gnu \ 47 | # --platform aarch64-unknown-linux-gnu \ 48 | # --platform i686-unknown-linux-gnu \ 49 | # --platform s390x-unknown-linux-gnu \ 50 | # --exclude-crate-path "libloading#tests" 51 | # tar jcf rust-keylime-%%{version}-vendor.tar.xz vendor 52 | Source1: rust-keylime-vendor.tar.xz 53 | ## Patches for building from system Rust libraries (Fedora) 54 | # Fix picky-asn1-der and picky-asn1-x509 to use available versions 55 | # Drop completely the legacy-python-actions feature 56 | Patch1: rust-keylime-metadata.patch 57 | 58 | ExclusiveArch: %{rust_arches} 59 | 60 | Requires: tpm2-tss 61 | Requires: util-linux-core 62 | 63 | # The keylime-base package provides the keylime user creation. It is available 64 | # from Fedora 36 65 | %if 0%{?fedora} >= 36 66 | Requires: keylime-base 67 | %endif 68 | 69 | BuildRequires: systemd 70 | BuildRequires: openssl-devel 71 | BuildRequires: tpm2-tss-devel 72 | BuildRequires: clang 73 | BuildRequires: rust-packaging >= 21-2 74 | 75 | # Virtual Provides to support swapping between Python and Rust implementation 76 | Provides: keylime-agent 77 | Conflicts: keylime-agent 78 | 79 | %description 80 | Rust agent for Keylime 81 | 82 | %prep 83 | %autosetup -n rust-keylime-%{version} -N %{?bundled_rust_deps:-a1} 84 | %if 0%{?bundled_rust_deps} 85 | %autopatch -m 100 -p1 86 | # Source1 contains vendored dependencies 87 | %cargo_prep -v vendor 88 | %cargo_generate_buildrequires 89 | %else 90 | %autopatch -M 99 -p1 91 | %cargo_prep 92 | %cargo_generate_buildrequires 93 | %endif 94 | 95 | %build 96 | %cargo_build -ftesting 97 | %cargo_license_summary 98 | %{cargo_license} > LICENSE.dependencies 99 | %if 0%{?bundled_rust_deps} 100 | %cargo_vendor_manifest 101 | %endif 102 | 103 | %install 104 | 105 | mkdir -p %{buildroot}/%{_sharedstatedir}/keylime 106 | mkdir -p --mode=0700 %{buildroot}/%{_rundir}/keylime 107 | mkdir -p --mode=0700 %{buildroot}/%{_libexecdir}/keylime 108 | mkdir -p --mode=0700 %{buildroot}/%{_sysconfdir}/keylime 109 | mkdir -p --mode=0700 %{buildroot}/%{_sysconfdir}/keylime/agent.conf.d 110 | 111 | install -Dpm 400 keylime-agent.conf \ 112 | %{buildroot}%{_sysconfdir}/keylime/agent.conf 113 | 114 | install -Dpm 644 ./dist/systemd/system/keylime_agent.service \ 115 | %{buildroot}%{_unitdir}/keylime_agent.service 116 | 117 | install -Dpm 644 ./dist/systemd/system/var-lib-keylime-secure.mount \ 118 | %{buildroot}%{_unitdir}/var-lib-keylime-secure.mount 119 | 120 | # Setting up the agent to use keylime:keylime user/group after dropping privileges. 121 | cat > %{buildroot}/%{_sysconfdir}/keylime/agent.conf.d/001-run_as.conf << EOF 122 | [agent] 123 | run_as = "keylime:keylime" 124 | EOF 125 | 126 | install -Dpm 0755 \ 127 | -t %{buildroot}%{_bindir} \ 128 | ./target/release/keylime_agent 129 | install -Dpm 0755 \ 130 | -t %{buildroot}%{_bindir} \ 131 | ./target/release/keylime_ima_emulator 132 | 133 | %posttrans 134 | chmod 500 %{_sysconfdir}/keylime/agent.conf.d 135 | chmod 400 %{_sysconfdir}/keylime/agent.conf.d/*.conf 136 | chmod 500 %{_sysconfdir}/keylime 137 | chown -R keylime:keylime %{_sysconfdir}/keylime 138 | 139 | %preun 140 | %systemd_preun keylime_agent.service 141 | %systemd_preun var-lib-keylime-secure.mount 142 | 143 | %postun 144 | %systemd_postun_with_restart keylime_agent.service 145 | %systemd_postun_with_restart var-lib-keylime-secure.mount 146 | 147 | %files 148 | %license LICENSE 149 | %license LICENSE.dependencies 150 | %license cargo-vendor.txt 151 | %doc README.md 152 | %attr(500,keylime,keylime) %dir %{_sysconfdir}/keylime 153 | %attr(500,keylime,keylime) %dir %{_sysconfdir}/keylime/agent.conf.d 154 | %config(noreplace) %attr(400,keylime,keylime) %{_sysconfdir}/keylime/agent.conf.d/001-run_as.conf 155 | %config(noreplace) %attr(400,keylime,keylime) %{_sysconfdir}/keylime/agent.conf 156 | %{_unitdir}/keylime_agent.service 157 | %{_unitdir}/var-lib-keylime-secure.mount 158 | %attr(700,keylime,keylime) %dir %{_rundir}/keylime 159 | %attr(700,keylime,keylime) %{_sharedstatedir}/keylime 160 | %attr(700,keylime,keylime) %{_libexecdir}/keylime 161 | %{_bindir}/keylime_agent 162 | %{_bindir}/keylime_ima_emulator 163 | 164 | %if %{with check} 165 | %check 166 | %cargo_test 167 | %endif 168 | 169 | %changelog 170 | %autochangelog 171 | -------------------------------------------------------------------------------- /rpm/fedora/rust-keylime-metadata.patch: -------------------------------------------------------------------------------- 1 | --- a/keylime-agent/Cargo.toml 2024-01-31 10:25:42.291841679 +0100 2 | +++ b/keylime-agent/Cargo.toml 2024-01-31 10:28:02.795282892 +0100 3 | @@ -48,18 +48,6 @@ 4 | default = [] 5 | # this should change to dev-dependencies when we have integration testing 6 | testing = ["wiremock"] 7 | -# Whether the agent should be compiled with support to listen for notification 8 | -# messages on ZeroMQ 9 | -# 10 | -# This feature is deprecated and will be removed on next major release 11 | -with-zmq = ["zmq"] 12 | -# Whether the agent should be compiled with support for python revocation 13 | -# actions loaded as modules, which is the only kind supported by the python 14 | -# agent (unless the enhancement-55 is implemented). See: 15 | -# https://github.com/keylime/enhancements/blob/master/55_revocation_actions_without_python.md 16 | -# 17 | -# This feature is deprecated and will be removed on next major release 18 | -legacy-python-actions = [] 19 | 20 | [package.metadata.deb] 21 | section = "net" 22 | -------------------------------------------------------------------------------- /scripts/download_packit_coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # There are 3 options how to tell this script where to start 4 | # --artifacts-url - Testing Farm artifacts URL, provided by testing-farm script 5 | # --testing-farm-log - Log of 'testing-farm request' command from where artifacts URL will be parsed 6 | # --github-sha - PR merge commit provided by GitHub, here we will try to get artifacts URL using GitHub API 7 | 8 | if [ "$1" == "--artifacts-url" -a -n "$2" ]; then 9 | TF_ARTIFACTS_URL="$2" 10 | elif [ "$1" == "--testing-farm-log" -a -n "$2" ]; then 11 | TT_LOG="$2" 12 | elif [ "$1" == "--github-sha" -a -n "$2" ]; then 13 | GITHUB_SHA="$2" 14 | elif [ -n "$GITHUB_SHA" ]; then 15 | : 16 | else 17 | echo "Neither --github-sha nor --artifacts-url nor --testing-farm-log arguments were provided" 18 | exit 1 19 | fi 20 | 21 | ############################################## 22 | # initial configuration, adjust when necessary 23 | ############################################## 24 | 25 | # maximum duration of the task in seconds 26 | MAX_DURATION="${MAX_DURATION:-5400}" # 90 minutes 27 | 28 | # delay in seconds before doing another URL read 29 | # should not be too short not to exceed GitHub API quota 30 | SLEEP_DELAY="${SLEEP_DELAY:-120}" 31 | 32 | # github user/project we are going to work with 33 | PROJECT="keylime/rust-keylime" 34 | 35 | # TF_JOB_DESC points to a Testing farm job that does code coverage measurement and 36 | # uploads coverage XML files to a web drive 37 | # currently we are doing that in a job running tests on Fedora-41 38 | TF_JOB_DESC="testing-farm:fedora-41-x86_64" 39 | TF_ARTIFACTS_URL_PREFIX="https://artifacts.dev.testing-farm.io" 40 | TF_COVERAGE_DATA_DIR="/setup/generate_upstream_rust_keylime_code_coverage.*/data" 41 | 42 | GITHUB_API_PREFIX_URL="https://api.github.com/repos/${PROJECT}" 43 | 44 | ################################## 45 | # no need to change anything below 46 | ################################## 47 | 48 | # build GITHUB_API_URLs 49 | GITHUB_API_COMMIT_URL="${GITHUB_API_PREFIX_URL}/commits" 50 | DURATION=0 51 | 52 | TMPFILE=$( mktemp ) 53 | 54 | # run API call and parse the required value 55 | # repeat until we get the value or exceed job duration 56 | # URL - API URL 57 | # JQ_REF - code for jq that will be used for JSON parsing 58 | # ERROR_MSG - error message to print in case we fail to parse the value 59 | # EXP_VALUE - expected value (used e.g. when waiting for job completion) 60 | function do_GitHub_API_call() { 61 | local URL="$1" 62 | local JQ_REF="$2" 63 | local ERROR_MSG="$3" 64 | local EXP_VALUE="$4" 65 | local VALUE='' 66 | 67 | while [ -z "${VALUE}" -o \( -n "${EXP_VALUE}" -a "${VALUE}" != "${EXP_VALUE}" \) ] && [ ${DURATION} -lt ${MAX_DURATION} ]; do 68 | if [ "$URL" != "-" ]; then # when URL='-', we reuse data downloaded previously 69 | curl --retry 5 -s -H "Accept: application/vnd.github.v3+json" "$URL" &> ${TMPFILE} 70 | fi 71 | VALUE=$( cat ${TMPFILE} | jq "${JQ_REF}" | sed 's/"//g' ) 72 | if [ -z "${VALUE}" ] || [ -n "${EXP_VALUE}" -a "${VALUE}" != "${EXP_VALUE}" ]; then 73 | if [ -z "${ERROR_MSG}" ]; then 74 | echo "Warning: Failed to read data using GitHub API, trying again after ${SLEEP_DELAY} seconds" 1>&2 75 | else 76 | echo "$ERROR_MSG" 1>&2 77 | fi 78 | sleep ${SLEEP_DELAY} 79 | DURATION=$(( ${DURATION}+${SLEEP_DELAY} )) 80 | fi 81 | done 82 | 83 | if [ ${DURATION} -ge ${MAX_DURATION} ]; then 84 | echo "Error: Maximum job duration exceeded. Terminating" 1>&2 85 | exit 9 86 | fi 87 | 88 | echo $VALUE 89 | } 90 | 91 | 92 | # if the GitHub Action has been triggered by a PR, 93 | # we need to find Testing farm test results through GitHub API 94 | if [ -n "${GITHUB_SHA}" -a -z "${TF_ARTIFACTS_URL}" -a -z "${TT_LOG}" ]; then 95 | 96 | echo "Trying to find Testing Farm / Packit CI test results using GitHub API" 97 | 98 | echo "Fist I need to find the respective PR commit" 99 | GITHUB_API_SHA_URL="${GITHUB_API_COMMIT_URL}/${GITHUB_SHA}" 100 | 101 | # Now we try to parse URL of Testing farm job from GITHUB_API_RUNS_URL page 102 | GITHUB_PR_SHA=$( do_GitHub_API_call "${GITHUB_API_SHA_URL}" \ 103 | ".parents[1].sha" \ 104 | "Failed to parse PR commit from ${GITHUB_API_RUNS_URL}, trying again after ${SLEEP_DELAY} seconds..." ) 105 | echo "GITHUB_PR_SHA=${GITHUB_PR_SHA}" 106 | 107 | echo "Now we read check-runs details" 108 | # build GITHUB_API_RUNS_URL using the COMMIT 109 | GITHUB_API_RUNS_URL="${GITHUB_API_COMMIT_URL}/${GITHUB_PR_SHA}/check-runs?check_name=${TF_JOB_DESC}" 110 | echo "GITHUB_API_RUNS_URL=${GITHUB_API_RUNS_URL}" 111 | 112 | # Now we try to parse URL of Testing farm job from GITHUB_API_RUNS_URL page 113 | TF_ARTIFACTS_URL=$( do_GitHub_API_call "${GITHUB_API_RUNS_URL}" \ 114 | ".check_runs[0] | .output.summary | match(\"${TF_ARTIFACTS_URL_PREFIX}[^ ]*\") | .string" \ 115 | "Failed to parse Testing Farm job ${TF_JOB_DESC} URL from ${GITHUB_API_RUNS_URL}, trying again after ${SLEEP_DELAY} seconds..." ) 116 | echo "TF_ARTIFACTS_URL=${TF_ARTIFACTS_URL}" 117 | 118 | # now we wait for the Testing farm job to finish 119 | TF_STATUS=$( do_GitHub_API_call "${GITHUB_API_RUNS_URL}" \ 120 | '.check_runs[0] | .status' \ 121 | "Testing Farm job ${TF_JOB_DESC} hasn't completed yet, trying again after ${SLEEP_DELAY} seconds..." \ 122 | "completed" ) 123 | echo "TF_STATUS=${TF_STATUS}" 124 | 125 | fi 126 | 127 | # if we were provided with testing-farm command log 128 | # we will parse artifacts from the log 129 | if [ -n "${TT_LOG}" ]; then 130 | cat ${TT_LOG} 131 | TF_ARTIFACTS_URL=$( grep -E -o "${TF_ARTIFACTS_URL_PREFIX}[^ ]*" ${TT_LOG} ) 132 | fi 133 | 134 | # now we have TF_ARTIFACTS_URL so we can proceed with the download 135 | echo "TF_ARTIFACTS_URL=${TF_ARTIFACTS_URL}" 136 | 137 | COVERAGE_DIR=$( curl --retry 5 ${TF_ARTIFACTS_URL}/results.xml | grep -E -m 1 -o "${TF_ARTIFACTS_URL}.*${TF_COVERAGE_DATA_DIR}" ) 138 | echo "COVERAGE_DIR=${COVERAGE_DIR}" 139 | 140 | DOWNLOADED="False" 141 | for REPORT in e2e_coverage.txt.tar.gz upstream_coverage.xml.tar.gz; do 142 | # download test coverage 143 | COVERAGE_URL=${COVERAGE_DIR}/${REPORT} 144 | echo "Trying to download \"${COVERAGE_URL}\"" 145 | # download the file 146 | if curl --fail-with-body --retry 5 -L -o "${REPORT}" "${COVERAGE_URL}"; then 147 | echo "Successfully downloaded \"${COVERAGE_URL}\"" 148 | DOWNLOADED="True" 149 | tar -xvf ${REPORT} 150 | else 151 | echo "Failed to download \"${COVERAGE_URL}\"" 152 | rm -f ${REPORT} 153 | fi 154 | done 155 | 156 | # Fail in case no file was downloaded 157 | if [[ "${DOWNLOADED}" != "True" ]]; then 158 | echo "Could not download any coverage report" 159 | exit 5 160 | fi 161 | -------------------------------------------------------------------------------- /tests/actions/shim.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | ''' 3 | SPDX-License-Identifier: Apache-2.0 4 | Copyright 2021 Keylime Authors 5 | ''' 6 | 7 | import argparse 8 | import asyncio 9 | import importlib 10 | import json 11 | import os 12 | import sys 13 | 14 | 15 | def main(): 16 | # Parse arguments to get action name and input json file path 17 | parser = argparse.ArgumentParser() 18 | parser.add_argument('action', type=str, help='The revocation action to be' 19 | ' executed. The module must provide an \'execute()\'' 20 | ' method which receives a JSON as argument') 21 | parser.add_argument('json_file', type=str, help='Input file') 22 | 23 | args = parser.parse_args() 24 | 25 | with open(args.json_file, 'r') as f: 26 | input_json = json.load(f) 27 | 28 | try: 29 | module = importlib.import_module(args.action) 30 | execute = getattr(module, 'execute') 31 | asyncio.run(execute(input_json)) 32 | except Exception as e: 33 | print("Exception during execution of revocation action {}: {}".format( 34 | args.action, e), file=sys.stderr) 35 | 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /tests/ca.conf: -------------------------------------------------------------------------------- 1 | [ ca ] 2 | default_ca = CA_default 3 | 4 | [ CA_default ] 5 | dir = REPLACE_ROOT_CA_DIR 6 | certs = $dir/certs 7 | crl_dir = $dir/crl 8 | new_certs_dir = $dir/certs 9 | database = $dir/index.txt 10 | serial = $dir/serial 11 | RANDFILE = $dir/.rand 12 | 13 | private_key = $dir/private.pem 14 | certificate = $dir/cacert.pem 15 | 16 | crlnumber = $dir/crl/crlnumber 17 | crl = $dir/crl/ca.crl.pem 18 | crl_extensions = crl_ext 19 | default_crl_days = 30 20 | 21 | default_md = sha256 22 | 23 | name_opt = ca_default 24 | cert_opt = ca_default 25 | default_days = 375 26 | preserve = no 27 | policy = policy_strict 28 | 29 | [ CA_intermediate ] 30 | dir = REPLACE_INTERMEDIATE_CA_DIR 31 | certs = $dir/certs 32 | crl_dir = $dir/crl 33 | new_certs_dir = $dir/certs 34 | database = $dir/index.txt 35 | serial = $dir/serial 36 | RANDFILE = $dir/.rand 37 | 38 | private_key = $dir/private.pem 39 | certificate = $dir/cacert.pem 40 | 41 | crlnumber = $dir/crl/crlnumber 42 | crl = $dir/crl/intermediate.crl.pem 43 | crl_extensions = crl_ext 44 | default_crl_days = 30 45 | 46 | default_md = sha256 47 | 48 | name_opt = ca_default 49 | cert_opt = ca_default 50 | default_days = 375 51 | preserve = no 52 | policy = policy_loose 53 | unique_subject = no 54 | 55 | [ policy_strict ] 56 | countryName = match 57 | stateOrProvinceName = match 58 | organizationName = match 59 | organizationalUnitName = optional 60 | commonName = optional 61 | emailAddress = optional 62 | 63 | [ policy_loose ] 64 | countryName = match 65 | stateOrProvinceName = optional 66 | localityName = optional 67 | organizationName = optional 68 | organizationalUnitName = optional 69 | commonName = optional 70 | emailAddress = optional 71 | 72 | [ req ] 73 | prompt = no 74 | default_bits = 2048 75 | distinguished_name = req_distinguished_name 76 | string_mask = utf8only 77 | 78 | default_md = sha256 79 | 80 | x509_extensions = v3_ca 81 | 82 | [ req_distinguished_name ] 83 | C = US 84 | ST = MA 85 | L = Lexington 86 | O = Keylime Tests 87 | 88 | [ v3_ca ] 89 | subjectKeyIdentifier = hash 90 | authorityKeyIdentifier = keyid:always,issuer 91 | basicConstraints = critical, CA:true 92 | keyUsage = critical, digitalSignature, cRLSign, keyCertSign 93 | 94 | [ v3_intermediate_ca ] 95 | subjectKeyIdentifier = hash 96 | authorityKeyIdentifier = keyid:always,issuer 97 | basicConstraints = critical, CA:true, pathlen:0 98 | keyUsage = critical, digitalSignature, cRLSign, keyCertSign 99 | 100 | [ server_cert ] 101 | # Extensions for server certificates (`man x509v3_config`). 102 | basicConstraints = CA:FALSE 103 | nsCertType = server 104 | nsComment = "OpenSSL Generated Server Certificate" 105 | subjectKeyIdentifier = hash 106 | authorityKeyIdentifier = keyid,issuer:always 107 | keyUsage = critical, digitalSignature, keyEncipherment 108 | extendedKeyUsage = serverAuth 109 | # These OIDs are taken from the SubjectAltName from section 8.1 of the TPM 2.0 Keys for Device Identity and Attestation 110 | # https://trustedcomputinggroup.org/wp-content/uploads/TPM-2p0-Keys-for-Device-Identity-and-Attestation_v1_r12_pub10082021.pdf 111 | subjectAltName=DER:306FA06D06082B06010505070804A061305F0605678105010204565354000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 112 | -------------------------------------------------------------------------------- /tests/common_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: Apache-2.0 3 | # Copyright 2021 Keylime Authors 4 | 5 | # Check that the script is running from inside the repository tree 6 | GIT_ROOT=$(git rev-parse --show-toplevel) || { 7 | echo "Please run this script from inside the rust-keylime repository tree" 8 | exit 1 9 | } 10 | 11 | TESTS_DIR="${GIT_ROOT}/tests" 12 | TEST_DATA_DIR="${GIT_ROOT}/test-data" 13 | TPMDIR="${TEST_DATA_DIR}/tpm-state" 14 | 15 | # These certificates are used for the keylime/device_id tests 16 | IAK_IDEVID_CERTS="${GIT_ROOT}/keylime/test-data/iak-idevid-certs" 17 | 18 | # Store the old TCTI setting 19 | OLD_TCTI=$TCTI 20 | OLD_TPM2TOOLS_TCTI=$TPM2TOOLS_TCTI 21 | OLD_TPM2OPENSSL_TCTI=$TPM2OPENSSL_TCTI 22 | 23 | set -euf -o pipefail 24 | 25 | echo "-------- Setting up Software TPM" 26 | 27 | if [[ ! -d "${TPMDIR}" ]]; then 28 | mkdir -p "${TPMDIR}" 29 | fi 30 | 31 | # Manufacture a new Software TPM 32 | swtpm_setup --tpm2 \ 33 | --tpmstate "${TPMDIR}" \ 34 | --createek --decryption --create-ek-cert \ 35 | --create-platform-cert \ 36 | --lock-nvram \ 37 | --not-overwrite \ 38 | --pcr-banks sha256 \ 39 | --display 40 | 41 | function start_swtpm { 42 | # Initialize the swtpm socket 43 | swtpm socket --tpm2 \ 44 | --tpmstate dir="${TPMDIR}" \ 45 | --flags startup-clear \ 46 | --ctrl type=tcp,port=2322 \ 47 | --server type=tcp,port=2321 \ 48 | --log level=1 & 49 | SWTPM_PID=$! 50 | } 51 | 52 | function stop_swtpm { 53 | # Stop swtpm if running 54 | if [[ -n "${SWTPM_PID}" ]]; then 55 | echo "Stopping swtpm" 56 | kill $SWTPM_PID 57 | fi 58 | } 59 | 60 | # Set cleanup function to run at exit 61 | function cleanup { 62 | 63 | echo "-------- Restore TCTI settings" 64 | TCTI=$OLD_TCTI 65 | TPM2TOOLS_TCTI=$OLD_TPM2TOOLS_TCTI 66 | TPM2OPENSSL_TCTI=$OLD_TPM2OPENSSL_TCTI 67 | 68 | echo "-------- Cleanup processes" 69 | stop_swtpm 70 | } 71 | trap cleanup EXIT 72 | 73 | # Set the TCTI to use the swtpm socket 74 | export TCTI=swtpm 75 | export TPM2TOOLS_TCTI=swtpm 76 | export TPM2OPENSSL_TCTI=swtpm 77 | -------------------------------------------------------------------------------- /tests/mockoon_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: Apache-2.0 3 | # Copyright 2021 Keylime Authors 4 | 5 | source ./tests/common_tests.sh || source ./common_tests.sh 6 | 7 | echo "-------- Testing" 8 | start_swtpm 9 | 10 | 11 | # Check that tpm2-openssl provider is available 12 | if openssl list -provider tpm2 -providers > /dev/null; then 13 | # If any IAK/IDevID related certificate is missing, re-generate them 14 | if [[ ( ! -f "${IAK_IDEVID_CERTS}/iak.cert.pem" ) || 15 | ( ! -f "${IAK_IDEVID_CERTS}/iak.cert.der" ) || 16 | ( ! -f "${IAK_IDEVID_CERTS}/idevid.cert.pem" ) || 17 | ( ! -f "${IAK_IDEVID_CERTS}/idevid.cert.der" ) || 18 | ( ! -f "${IAK_IDEVID_CERTS}/ca-cert-chain.pem" ) ]] 19 | then 20 | # Remove any leftover from old certificates 21 | rm -rf "${IAK_IDEVID_CERTS}" 22 | mkdir -p "${IAK_IDEVID_CERTS}" 23 | echo "-------- Create IAK/IDevID certificates" 24 | "${GIT_ROOT}/tests/generate-iak-idevid-certs.sh" -o "${IAK_IDEVID_CERTS}" 25 | fi 26 | fi 27 | 28 | mkdir -p /var/lib/keylime 29 | RUST_BACKTRACE=1 RUST_LOG=info \ 30 | KEYLIME_CONFIG=$PWD/keylime-agent.conf \ 31 | MOCKOON=1 cargo test --features testing -- --nocapture 32 | -------------------------------------------------------------------------------- /tests/nopanic.ci: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | SPDX-License-Identifier: Apache-2.0 4 | Copyright 2021 Keylime Authors 5 | 6 | To prevent CI failing for approved instance of banned words, add a comment: //#[allow_ci] 7 | ''' 8 | 9 | import pathlib 10 | 11 | banned = ["unwrap(", "panic!("] 12 | 13 | toplevel = ["keylime", "keylime-agent", "keylime-ima-emulator"] 14 | 15 | srcs = [] 16 | 17 | for d in toplevel: 18 | srcs += list(pathlib.Path(d).glob("**/*.rs")) 19 | 20 | print("Files to check: %s" % srcs) 21 | 22 | failed = False 23 | for f in srcs: 24 | with open(f) as src_file: 25 | for line_no, line in enumerate(src_file): 26 | for b in banned: 27 | if b not in line or "//#[allow_ci]" in line: 28 | continue 29 | failed = True 30 | print("File %s on line number %s calls banned function: %s)" % (f, line_no + 1, b)) 31 | pass 32 | exit(failed) 33 | -------------------------------------------------------------------------------- /tests/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: Apache-2.0 3 | # Copyright 2021 Keylime Authors 4 | 5 | source ./tests/common_tests.sh || source ./common_tests.sh 6 | 7 | echo "-------- Running clippy" 8 | # The cargo denies are currently disabled, because that will require a bunch of dep cleanup 9 | cargo clippy --all-targets --all-features -- -D clippy::all # -D clippy::cargo 10 | 11 | echo "-------- Building" 12 | RUST_BACKTRACE=1 cargo build 13 | 14 | echo "-------- Testing" 15 | start_swtpm 16 | 17 | 18 | # Check that tpm2-openssl provider is available 19 | if openssl list -provider tpm2 -providers > /dev/null; then 20 | # If any IAK/IDevID related certificate is missing, re-generate them 21 | if [[ ( ! -f "${IAK_IDEVID_CERTS}/iak.cert.pem" ) || 22 | ( ! -f "${IAK_IDEVID_CERTS}/iak.cert.der" ) || 23 | ( ! -f "${IAK_IDEVID_CERTS}/idevid.cert.pem" ) || 24 | ( ! -f "${IAK_IDEVID_CERTS}/idevid.cert.der" ) || 25 | ( ! -f "${IAK_IDEVID_CERTS}/ca-cert-chain.pem" ) ]] 26 | then 27 | # Remove any leftover from old certificates 28 | rm -rf "${IAK_IDEVID_CERTS}" 29 | mkdir -p "${IAK_IDEVID_CERTS}" 30 | echo "-------- Create IAK/IDevID certificates" 31 | "${GIT_ROOT}/tests/generate-iak-idevid-certs.sh" -o "${IAK_IDEVID_CERTS}" 32 | fi 33 | fi 34 | 35 | mkdir -p /var/lib/keylime 36 | RUST_BACKTRACE=1 RUST_LOG=info \ 37 | KEYLIME_CONFIG=$PWD/keylime-agent.conf \ 38 | cargo test --features testing -- --nocapture 39 | 40 | echo "-------- Testing with coverage" 41 | RUST_BACKTRACE=1 RUST_LOG=info \ 42 | KEYLIME_CONFIG=$PWD/keylime-agent.conf \ 43 | cargo tarpaulin --verbose \ 44 | --target-dir target/tarpaulin \ 45 | --workspace \ 46 | --exclude-files 'target/*' \ 47 | --ignore-panics --ignore-tests \ 48 | --out Html --out Json \ 49 | --all-features \ 50 | --engine llvm 51 | -------------------------------------------------------------------------------- /tests/seccomp-profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "names": [ 3 | "personality" 4 | ], 5 | "action": "SCMP_ACT_ALLOW", 6 | "args": [ 7 | { 8 | "index": 0, 9 | "value": 262144, 10 | "valueTwo": 0, 11 | "op": "SCMP_CMP_EQ" 12 | } 13 | ], 14 | "comment": "Enable personality(ADDR_NO_RANDOMIZE) syscall", 15 | "includes": {}, 16 | "excludes": {} 17 | } 18 | -------------------------------------------------------------------------------- /tests/setup_swtpm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: Apache-2.0 3 | # Copyright 2021 Keylime Authors 4 | 5 | # Store the old TCTI setting 6 | OLD_TCTI=$TCTI 7 | OLD_TPM2TOOLS_TCTI=$TPM2TOOLS_TCTI 8 | OLD_TPM2OPENSSL_TCTI=$TPM2OPENSSL_TCTI 9 | 10 | set -euf -o pipefail 11 | 12 | if [[ $# -eq 0 ]] || [[ -z "$1" ]]; then 13 | TEMPDIR=$(mktemp -d) 14 | TPMDIR="${TEMPDIR}/tpmdir" 15 | mkdir -p ${TPMDIR} 16 | else 17 | echo "Using TPM state from $1" 18 | TPMDIR=$1 19 | fi 20 | 21 | # Manufacture a new Software TPM 22 | swtpm_setup --tpm2 \ 23 | --tpmstate ${TPMDIR} \ 24 | --createek --decryption --create-ek-cert \ 25 | --create-platform-cert \ 26 | --lock-nvram \ 27 | --not-overwrite \ 28 | --pcr-banks sha256 \ 29 | --display 30 | 31 | function start_swtpm { 32 | # Initialize the swtpm socket 33 | swtpm socket --tpm2 \ 34 | --tpmstate dir=${TPMDIR} \ 35 | --flags startup-clear \ 36 | --ctrl type=tcp,port=2322 \ 37 | --server type=tcp,port=2321 \ 38 | --log level=1 & 39 | SWTPM_PID=$! 40 | } 41 | 42 | function stop_swtpm { 43 | # Stop swtpm if running 44 | if [[ -n "$SWTPM_PID" ]]; then 45 | echo "Stopping swtpm" 46 | kill $SWTPM_PID 47 | fi 48 | } 49 | 50 | # Set cleanup function to run at exit 51 | function cleanup { 52 | echo "-------- Restore TCTI settings" 53 | TCTI=$OLD_TCTI 54 | TPM2TOOLS_TCTI=$OLD_TPM2TOOLS_TCTI 55 | TPM2OPENSSL_TCTI=$OLD_TPM2OPENSSL_TCTI 56 | 57 | echo "-------- Cleanup processes" 58 | stop_swtpm 59 | } 60 | trap cleanup EXIT 61 | 62 | # Set the TCTI to use the swtpm socket 63 | export TCTI=swtpm 64 | export TPM2TOOLS_TCTI=swtpm 65 | export TPM2OPENSSL_TCTI=swtpm 66 | 67 | start_swtpm 68 | bash 69 | --------------------------------------------------------------------------------