diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..df7397d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,87 @@ +on: push + +jobs: + build: + name: Build + strategy: + matrix: + os: + - ubuntu-24.04 + - ubuntu-22.04 + - macos-13 + runs-on: ${{ matrix.os }} + steps: + - name: Set up Elixir + if: ${{ startsWith(matrix.os, 'ubuntu') }} + uses: erlef/setup-beam@v1 + with: + otp-version: "27.1.3" + elixir-version: "1.17.3" + - name: Set up Elixir + if: ${{ startsWith(matrix.os, 'macos') }} + run: brew install elixir + - name: Checkout repository + uses: actions/checkout@v4 + - name: Get dependencies + run: mix deps.get --only dev + - name: Restore PLTs + uses: actions/cache@v4 + with: + path: _build/dev/plt + key: plt-${{ github.ref }}-${{ github.sha }} + restore-keys: | + plt-${{ github.ref }}-${{ github.sha }} + plt-${{ github.ref }}- + plt-refs/heads/master- + - name: Install system deps + if: ${{ startsWith(matrix.os, 'ubuntu') }} + run: | + sudo apt install \ + build-essential \ + devscripts \ + equivs \ + libtasn1-6-dev \ + libjson-glib-dev \ + libseccomp-dev \ + libgmp-dev + - name: Install system deps + if: ${{ startsWith(matrix.os, 'macos') }} + run: brew install automake gawk socat json-glib + - name: Compile + run: mix compile + type_check: + name: Type Check + runs-on: ubuntu-24.04 + steps: + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + otp-version: "27.1.3" + elixir-version: "1.17.3" + - name: Checkout repository + uses: actions/checkout@v4 + - name: Get dependencies + run: mix deps.get --only dev + - name: Restore PLTs + uses: actions/cache@v4 + with: + path: _build/dev/plt + key: plt-${{ github.ref }}-${{ github.sha }} + restore-keys: | + plt-${{ github.ref }}-${{ github.sha }} + plt-${{ github.ref }}- + plt-refs/heads/master- + - name: Install system deps + run: | + sudo apt install \ + build-essential \ + devscripts \ + equivs \ + libtasn1-6-dev \ + libjson-glib-dev \ + libseccomp-dev \ + libgmp-dev + - name: Compile + run: mix compile + - name: Run dialyzer + run: mix dialyzer diff --git a/.gitignore b/.gitignore index ab3fdd4..93d21b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /_build/ /cover/ +/data/ /deps/ /doc/ /.fetch diff --git a/CMakeLists.txt b/CMakeLists.txt index 286f4c8..31cf34d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,3 +12,60 @@ if(NOT EXISTS ${CPM_PATH}) file(DOWNLOAD ${CPM_RELEASE_URL} ${CPM_PATH}) endif() include(${CPM_PATH}) + +# dependencies +set(LIBTPMS_VER v0.10.0) +set(SWTPM_VER v0.10.0) + +if(DEFINED ENV{LIBTPMS_VER}) + set(LIBTPMS_VER $ENV{LIBTPMS_VER}) +endif() + +if(DEFINED ENV{SWTPM_VER}) + set(SWTPM_VER $ENV{SWTPM_VER}) +endif() + +CPMFindPackage( + NAME libtpms + GITHUB_REPOSITORY stefanberger/libtpms + GIT_TAG ${LIBTPMS_VER}) + +CPMFindPackage( + NAME swtpm + GITHUB_REPOSITORY stefanberger/swtpm + GIT_TAG ${SWTPM_VER}) + +# build +project(swtpm_ex) + +include(ExternalProject) +include(ProcessorCount) + +find_program(MAKE make REQUIRED) +find_package(PkgConfig REQUIRED) +pkg_check_modules(LIBTASN1 libtasn1 REQUIRED) +pkg_check_modules(LIBJASON_GLIB json-glib-1.0 REQUIRED) +pkg_check_modules(LIBGMP gmp REQUIRED) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + pkg_check_modules(LIBSECCOMP libseccomp REQUIRED) +endif() + +ProcessorCount(NPROC) + +ExternalProject_Add(libtpms + SOURCE_DIR ${libtpms_SOURCE_DIR} + CONFIGURE_COMMAND ${libtpms_SOURCE_DIR}/autogen.sh + --with-tpm2 + --with-openssl + --prefix=$ENV{MIX_APP_PATH}/priv + BUILD_COMMAND ${MAKE} -j ${NPROC}) + +ExternalProject_Add(swtpm + SOURCE_DIR ${swtpm_SOURCE_DIR} + CONFIGURE_COMMAND PKG_CONFIG_PATH=$ENV{MIX_APP_PATH}/priv/lib/pkgconfig + ${swtpm_SOURCE_DIR}/autogen.sh + --disable-tests + --with-openssl + --prefix=$ENV{MIX_APP_PATH}/priv + BUILD_COMMAND ${MAKE} -j ${NPROC}) diff --git a/README.md b/README.md index 224251f..999f693 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,16 @@ # swtpm +An easy way to bring a Software TPM emulator into your elixir application by +wrapping [swtpm](https://github.com/stefanberger/swtpm). + +## Prerequisites + +Ensure the following are installed on your host system: +* [libtasn1](https://www.gnu.org/software/libtasn1/) +* [json-glib-1.0](https://wiki.gnome.org/Projects/JsonGlib) +* [libseccomp](https://github.com/seccomp/libseccomp) +* [gmp](https://gmplib.org/) + ## Installation The package can be installed by adding `swtpm` to your list of dependencies @@ -12,3 +23,16 @@ def deps do ] end ``` + +## Supervision + +Add the `SWTPM` module spec to your `application.ex`. For more details see docs +for `&SWTPM.child_spec/1`. + +```elixir +defp children(:host) do + [ + {SWTPM, [state_dir: "data/tpm"]} + ] +end +``` diff --git a/lib/swtpm.ex b/lib/swtpm.ex index 7308e79..db79da9 100644 --- a/lib/swtpm.ex +++ b/lib/swtpm.ex @@ -2,4 +2,59 @@ defmodule SWTPM do @moduledoc """ Software TPM emulator. """ + + @doc """ + Integrates the `SWTPM` module with a supervision tree, providing a + standardized way to configure and start the Software TPM emulator as a + supervised process. + + ## Options + - `:state_dir` - Specifies the directory where the TPM state will be stored. + + - `:server_port` - Specifies the TCP port used for the TPM server. Defaults + to `2321`. + + - `:ctrl_port` - Specifies the TCP port used for TPM control commands. + Defaults to `2322`. + + - `:flags` - Refer to https://man.archlinux.org/man/swtpm.8.en for full list + of flags. Defaults to `[:not_need_init, :startup_clear]`. + + ## Example + ```elixir + children = [ + {SWTPM, [state_dir: "/data/tpm"]} + ] + + Supervisor.start_link(children, [strategy: :one_for_one, name: MySupervisor]) + ``` + """ + @spec child_spec(keyword) :: Supervisor.child_spec() + def child_spec(opts) do + state_dir = opts[:state_dir] + server_port = opts[:server_port] || 2321 + ctrl_port = opts[:ctrl_port] || 2322 + + flags = + Keyword.get(opts, :flags, [:not_need_init, :startup_clear]) + |> Enum.map(& to_string(&1) |> String.replace("_", "-")) + |> Enum.join(",") + + command = Path.join([:code.priv_dir(:swtpm), "bin", "swtpm"]) + args = [ + "socket", + "--tpm2", + "--tpmstate", "dir=#{state_dir}", + "--server", "type=tcp,port=#{server_port}", + "--ctrl", "type=tcp,port=#{ctrl_port}", + "--flags", flags, + ] + + %{ + id: __MODULE__, + start: {MuonTrap.Daemon, :start_link, [command, args, []]}, + type: :worker, + restart: :permanent, + } + end end diff --git a/mix.exs b/mix.exs index 5a09934..9852ba7 100644 --- a/mix.exs +++ b/mix.exs @@ -28,7 +28,7 @@ defmodule SWTPM.MixProject do def application do [ - extra_applications: [:logger] + extra_applications: [:logger], ] end @@ -38,7 +38,7 @@ defmodule SWTPM.MixProject do cookie: "#{@app}_cookie", include_erts: &Nerves.Release.erts/0, steps: [&Nerves.Release.init/1, :assemble], - strip_beams: Mix.env() == :prod or [keep: ["Docs"]] + strip_beams: Mix.env() == :prod or [keep: ["Docs"]], ] end @@ -68,7 +68,7 @@ defmodule SWTPM.MixProject do defp docs do [ main: "readme", - extras: ["README.md"] + extras: ["README.md"], ] end