enprot 0.5.58

Engyon Protected Text (EPT) — confidentiality processor and capability ledger
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
name: deploy

# Prevent overlapping deploy runs for the same tag. Don't cancel
# in-progress runs — a partially-built release is worse than waiting.
concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: false

on:
  push:
    tags:
      # release-plz now uses per-crate tags in a workspace:
      #   v0.5.X            (legacy, pre-split)
      #   enprot-v0.5.X     (enprot CLI + library, post-split)
      #   enprot-ffi-v0.5.X (the cdylib — not deployed, no binary)
      # Match the first two; enprot-ffi has no standalone binary to ship.
      - 'v[0-9]*.[0-9]*.[0-9]*'
      - '[0-9]*.[0-9]*.[0-9]*'
      - 'enprot-v[0-9]*.[0-9]*.[0-9]*'
  # Allow manual trigger to rebuild binaries for existing tags
  # (e.g., v0.5.0 was tagged before the deploy pattern was fixed).
  # Test builds: omit `tag` and set `ref` to a branch to exercise
  # the archive matrix without publishing anything (used to iterate
  # on cross-compile fixes without cutting releases — issue #368),
  # optionally narrowed to one matrix leg with `target`.
  workflow_dispatch:
    inputs:
      tag:
        description: 'Tag to deploy (e.g., v0.5.0, enprot-v0.5.13). Empty = test build (no publish).'
        required: false
        type: string
      ref:
        description: 'Checkout ref override (defaults to the tag; a branch for test builds)'
        required: false
        type: string
      target:
        description: 'Build only this matrix target (e.g., x86_64-unknown-linux-musl)'
        required: false
        type: string

env:
  CROSS_VERSION: 0.2.5
  BOTAN_VERSION: 3.7.0
  PROJECT_NAME: enprot
  EXE_NAME: enprot
  # Reproducible-build timestamp: pinned to the deploy tag's commit
  # time so rustc/linker timestamps are stable across reruns (see
  # docs/reproducible-builds.md). Overridden per-job from git after
  # checkout.
  SOURCE_DATE_EPOCH: '0'
  # Resolved tag for both trigger types:
  #   tag push → github.ref_name = "enprot-v0.5.13" or "v0.5.13"
  #   workflow_dispatch → inputs.tag = "enprot-v0.5.13"
  # Jobs strip any non-digit prefix (both `v` and `enprot-v`) to get
  # the bare version (0.5.13) via `sed 's/^[^0-9]*//'`.
  # Tag pushes always carry a tag; dispatch carries one only when
  # `tag` is set (empty otherwise — a test build).
  DEPLOY_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.event_name != 'workflow_dispatch' && github.ref_name || '' }}
  # What every job checks out: an explicit ref override (test
  # builds), else the tag, else the pushed ref.
  DEPLOY_REF: ${{ inputs.ref || (github.event_name == 'workflow_dispatch' && inputs.tag) || github.ref_name }}

jobs:
  checks:
    name: Checks
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ env.DEPLOY_REF }}
      - name: Check version
        if: env.DEPLOY_TAG != ''
        run: |
          set -euxo pipefail
          # Strip any non-digit prefix (handles 'v', 'enprot-v', bare).
          TAG="$(echo "$DEPLOY_TAG" | sed 's/^[^0-9]*//')"
          if [ "$(grep '^version' Cargo.toml | cut -d '"' -f2)" != "$TAG" ]; then
            echo "Version tag ($TAG) does not match manifest"
            exit 1
          fi

  generate-extras:
    name: man page + completions
    needs: [checks]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ env.DEPLOY_REF }}
      - name: Install rust toolchain
        uses: dtolnay/rust-toolchain@stable
      - name: Restore /usr/local C-stack cache
        # install.sh builds Botan + librnp (~20 min); the guards we
        # added there skip both wholesale when the cache is warm.
        # /usr/local on a bare ubuntu runner is tiny, so caching it
        # wholesale is cheap and cannot shadow runner tooling that
        # would matter (that lives in /usr, which we leave alone).
        uses: actions/cache@v4
        with:
          path: /usr/local
          key: extras-stack-${{ env.BOTAN_VERSION }}-${{ hashFiles('ci/install.sh', 'ci/build-librnp.sh') }}
      - name: Install dependencies
        env:
          BOTAN_VERSION: ${{ env.BOTAN_VERSION }}
          PREFIX: /usr/local
        run: ./ci/install.sh
      - name: Build enprot
        env:
          PKG_CONFIG_PATH: /usr/local/lib/pkgconfig
        run: cargo build --features cli --release
      - name: Generate man page
        run: cargo run --features cli --example gen-manpage > enprot.1
      - name: Generate shell completions
        run: |
          mkdir -p completions
          target/release/enprot completions bash > completions/enprot.bash
          target/release/enprot completions fish > completions/enprot.fish
          target/release/enprot completions zsh  > completions/_enprot
      - name: Generate SBOMs
        # TODO.complete/62: the binary itself emits its SBOM — Rust
        # crates are baked in from Cargo.lock at build time; the C
        # library versions come from the linked libraries at runtime.
        # SOURCE_DATE_EPOCH (commit timestamp) keeps the documents
        # byte-identical for identical releases (TODO.complete/45).
        run: |
          set -euxo pipefail
          epoch=$(git log -1 --format=%ct)
          ver=$(echo "$DEPLOY_TAG" | sed 's/^[^0-9]*//')
          SOURCE_DATE_EPOCH=$epoch target/release/enprot sbom --output "enprot-${ver}.spdx.json"
          SOURCE_DATE_EPOCH=$epoch target/release/enprot sbom --sbom-format cyclonedx-json --output "enprot-${ver}.cdx.json"
      - name: Upload extras
        uses: actions/upload-artifact@v7
        with:
          name: extras
          path: |
            enprot.1
            completions/
            enprot-*.spdx.json
            enprot-*.cdx.json

  release-archive:
    needs: [checks, generate-extras]
    name: archive
    # Docker cross builds compile Botan up to THREE times per leg
    # (botan-sys's own vendored copy, rnp-src's copy for librnp,
    # and the host librnp for bindgen); amalgamation cut the file
    # count but the legs still need ~2h on 2-core runners. Every
    # shorter limit (20/45/90) killed them mid-build, surfacing as
    # "cancelled" at exactly the limit.
    timeout-minutes: 240
    strategy:
      # Don't cancel native builds (macOS, Windows MSVC) when Docker
      # cross-compile targets fail (blocked on upstream librnp).
      fail-fast: false
      matrix:
        # workflow_dispatch `target` narrows the matrix to one leg
        # (test builds / surgical per-tag rebuilds). Push events and
        # dispatch without `target` build the full matrix.
        target: ${{ github.event_name == 'workflow_dispatch' && inputs.target != '' && fromJSON(format('["{0}"]', inputs.target)) || fromJSON('["x86_64-unknown-linux-musl","aarch64-unknown-linux-musl","x86_64-apple-darwin","aarch64-apple-darwin","x86_64-pc-windows-gnu","x86_64-pc-windows-msvc"]') }}
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-musl
            env:
              TARGET: x86_64-unknown-linux-musl
              PREFIX: /usr/local/x86_64-linux-musl
              BOTAN_CONFIGURE_CC: clang
              # zig toolchain via the image-baked OUT_DIR-branching
              # tool-* wrappers (musl-common.sh + wrappers.sh): target
              # build gets zig, host build (bindgen) gets native cc.
              # The cargo linker must be zig too: the C objects are
              # libc++ (std::__1), which cross's default musl gcc 9.2
              # linker does not link against.
              CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: tool-cxx
              TARGET_CC: tool-cc
              TARGET_CXX: tool-cxx
              TARGET_AR: tool-ar
              BOTAN_CONFIGURE_CC_BIN: tool-ccbin
              BOTAN_CONFIGURE_AR_COMMAND: tool-ar
              # zig 0.13 (LLVM 18) gates AVX-512 intrinsics behind the
              # separate 'evex512' target feature; Botan's makefiles
              # only pass -mavx512*, so simd_avx512.h fails to build.
              BOTAN_CONFIGURE_CC_ABI_FLAGS: -mevex512
              # One amalgamated TU: zig's per-file process overhead
              # across ~400 Botan sources costs ~40 min per leg on
              # 2-core runners (the 45- and 90-min timeouts both hit).
              BOTAN_CONFIGURE_AMALGAMATION: "1"
              # Plain CC drives rnp-src's CMake deps (cmake reads env
              # CC, not TARGET_CC); the tool-* wrappers keep the host
              # librnp copy on native gcc. HOST_* outranks plain CC in
              # the cc crate, shielding host-side ring builds.
              CC: tool-cc
              CXX: tool-cxx
              AR: tool-ar
              HOST_CC: gcc
              HOST_CXX: g++
              HOST_AR: ar
          - os: ubuntu-latest
            target: aarch64-unknown-linux-musl
            env:
              TARGET: aarch64-unknown-linux-musl
              PREFIX: /usr/local/aarch64-linux-musl
              CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: tool-cxx
              TARGET_CC: tool-cc
              TARGET_CXX: tool-cxx
              TARGET_AR: tool-ar
              BOTAN_CONFIGURE_CC: clang
              BOTAN_CONFIGURE_CC_BIN: tool-ccbin
              BOTAN_CONFIGURE_AR_COMMAND: tool-ar
              CC: tool-cc
              CXX: tool-cxx
              AR: tool-ar
              HOST_CC: gcc
              HOST_CXX: g++
              HOST_AR: ar
              # zig 0.13's LLVM cannot lower llvm.aarch64.crypto.sha512h2
              # ("Cannot select" backend error in sha2_64_armv8.cpp);
              # use Botan's portable SHA-2 instead.
              BOTAN_CONFIGURE_DISABLE_MODULES: sha2_64_armv8
              BOTAN_CONFIGURE_AMALGAMATION: "1"
          # macos-15-intel is the x86_64 runner (macos-13 is deprecated).
          - os: macos-15-intel
            target: x86_64-apple-darwin
            env:
              TARGET: x86_64-apple-darwin
              PREFIX: /usr/local
          - os: macos-latest
            target: aarch64-apple-darwin
            env:
              TARGET: aarch64-apple-darwin
              PREFIX: /usr/local
          - os: ubuntu-latest
            target: x86_64-pc-windows-gnu
            env:
              TARGET: x86_64-pc-windows-gnu
              # tool-* are the image-baked OUT_DIR-branching wrappers
              # (ci/build-static/pre/wrappers.sh): the target librnp
              # build gets mingw, the host librnp build (rnp-rs bindgen)
              # gets native gcc. Plain CC/CXX would poison the host side
              # (issue #368).
              TARGET_CC: tool-cc
              TARGET_CXX: tool-cxx
              TARGET_AR: tool-ar
              BOTAN_CONFIGURE_CC: gcc
              BOTAN_CONFIGURE_CC_BIN: tool-ccbin
              BOTAN_CONFIGURE_AR_COMMAND: tool-ar
              # Botan's getentropy entropy source does not exist on
              # mingw ('::getentropy' has not been declared); Windows
              # uses the system_rng/bcrypt path instead.
              BOTAN_CONFIGURE_DISABLE_MODULES: getentropy
              # Single-TU Botan: the mingw build was also hitting the
              # archive job timeout.
              BOTAN_CONFIGURE_AMALGAMATION: "1"
              CC: tool-cc
              CXX: tool-cxx
              AR: tool-ar
              HOST_CC: gcc
              HOST_CXX: g++
              HOST_AR: ar
              EXE_NAME: enprot.exe
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            env:
              TARGET: x86_64-pc-windows-msvc
              EXE_NAME: enprot.exe
              GIT_REDIRECT_STDERR: '2>&1'
              # install.ps1 builds Botan from this tag; the Rust side
              # links botan-src 0.31200 (Botan 3.12) via the vendored
              # feature, so the C side must match or newer FFI symbols
              # (botan_nist_kw_enc, ...) are missing at link (LNK2019).
              BOTAN_VERSION: 3.12.0

    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ env.DEPLOY_REF }}

      - name: Set environment (unix)
        if: "!startsWith(matrix.os, 'windows')"
        env: ${{ matrix.env }}
        run: |
          set -euxo pipefail
          # Strip any non-digit prefix (handles 'v', 'enprot-v', bare).
          TAG="$(echo "$DEPLOY_TAG" | sed 's/^[^0-9]*//')"
          echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
          echo "PREFIX=${PREFIX-$GITHUB_WORKSPACE/installs}" >> $GITHUB_ENV
          echo "EXE_PATH=target/$TARGET/release/$EXE_NAME" >> $GITHUB_ENV
          # Reproducible-build timestamp: pin to the tag's commit time.
          echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> $GITHUB_ENV
      - name: Set environment (windows)
        if: startsWith(matrix.os, 'windows')
        env: ${{ matrix.env }}
        run: |
          # Strip any non-digit prefix (handles 'v', 'enprot-v', bare).
          $Tag = $Env:DEPLOY_TAG -replace '^[^0-9]+',''
          "RELEASE_TAG=$Tag" | Out-File -Append -Encoding UTF8 -LiteralPath $Env:GITHUB_ENV
          $Prefix = (Join-Path $PWD 'installs').Replace('\', '/')
          "PREFIX=$Prefix" | Out-File -Append -Encoding UTF8 -LiteralPath $Env:GITHUB_ENV
          "EXE_PATH=target/$Env:TARGET/release/$Env:EXE_NAME" | Out-File -Append -Encoding UTF8 -LiteralPath $Env:GITHUB_ENV
          # Reproducible-build timestamp: pin to the tag's commit time.
          $CommitEpoch = git log -1 --format=%ct
          "SOURCE_DATE_EPOCH=$CommitEpoch" | Out-File -Append -Encoding UTF8 -LiteralPath $Env:GITHUB_ENV

      - name: Install rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          target: ${{ matrix.env.TARGET }}

      - name: Rust toolchain fingerprint
        # Fingerprints are only stable for a given rustc; key the
        # cache on it so toolchain rotation means a cold rebuild.
        shell: bash
        run: echo "v=$(rustc -vV | shasum -a 256 | cut -c1-12)" >> "$GITHUB_OUTPUT"
        id: rc

      - name: Restore cross C-stack cache
        # The vendored C stack (Botan x3, librnp, json-c, sexpp,
        # zlib, bzip2) lives inside cargo build-script OUT_DIRs
        # under target/. Cache exactly those trees plus their
        # fingerprints: cargo then skips the build scripts outright
        # (~90 min per leg on 2-core runners) and only recompiles
        # the Rust side. Rust rlibs are deliberately NOT cached —
        # they dominate the cache size for far less wall time.
        # Strictly keyed per (target, Cargo.lock, rustc, ci files,
        # this workflow) so a change in any input — including the
        # BOTAN_CONFIGURE_* matrix values — is a cold rebuild, and
        # legs never see another leg's artifacts.
        uses: actions/cache@v4
        with:
          path: |
            target/release/build/botan-sys-*/out
            target/release/build/rnp-src-*/out
            target/release/.fingerprint/botan-sys-*
            target/release/.fingerprint/rnp-src-*
            target/*/release/build/botan-sys-*/out
            target/*/release/build/rnp-src-*/out
            target/*/release/build/ring-*/out
            target/*/.fingerprint/botan-sys-*
            target/*/.fingerprint/rnp-src-*
          key: cross-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }}-${{ steps.rc.outputs.v }}-${{ hashFiles('ci/**', '.cargo/**', '.github/workflows/deploy.yml') }}

      - name: Install python (windows)
        if: startsWith(matrix.os, 'windows')
        uses: actions/setup-python@v7
        with:
          python-version: '3.x'

      - name: Restore MSVC C-stack cache
        # install.ps1 builds the full C stack into ./installs
        # (~30 min); its $stackCacheHit guard skips it all when
        # warm. Keyed on the script + Botan version only.
        if: startsWith(matrix.os, 'windows')
        uses: actions/cache@v4
        with:
          path: installs
          key: msvc-stack-${{ matrix.env.BOTAN_VERSION || env.BOTAN_VERSION }}-${{ hashFiles('ci/install.ps1') }}
      - name: Install dependencies (windows)
        if: startsWith(matrix.os, 'windows')
        env:
          GIT_REDIRECT_STDERR: '2>&1'
        shell: pwsh
        run: |
          $Env:Path += ";$Env:PREFIX/lib"
          $Env:TARGET = "x86_64-pc-windows-msvc"
          ./ci/install.ps1

      - name: Build (unix)
        if: "!startsWith(matrix.os, 'windows')"
        env: ${{ matrix.env }}
        run: |
          set -euxo pipefail
          . ci/build-static.sh
      - name: Build (windows)
        if: startsWith(matrix.os, 'windows')
        env: ${{ matrix.env }}
        shell: pwsh
        run: |
          . .\ci\build-static.ps1

      - name: Verify binary (windows)
        if: startsWith(matrix.os, 'windows')
        shell: pwsh
        run: |
          $v = & "$Env:EXE_PATH" --version
          if ($v -notmatch "enprot") { throw "Binary verification failed: $v" }

      - name: Download extras
        uses: actions/download-artifact@v8
        with:
          name: extras
          path: extras

      - name: Archive (unix)
        if: "!startsWith(matrix.os, 'windows')"
        env: ${{ matrix.env }}
        run: |
          set -euxo pipefail
          . ci/archive.sh
      - name: Archive (windows)
        if: startsWith(matrix.os, 'windows')
        env: ${{ matrix.env }}
        shell: pwsh
        run: |
          Set-StrictMode -Version 3.0
          . .\ci\archive.ps1

      - name: Upload artifacts
        # One artifact per matrix leg: parallel uploads to a single
        # name collide (only the first leg's files survive), which
        # shipped releases with one asset instead of six. The
        # download step merges the per-leg artifacts back together.
        uses: actions/upload-artifact@v7
        with:
          name: archives-${{ matrix.target }}
          path: archives

  github-release:
    name: github release
    needs: [release-archive]
    # Run even if some archive targets failed (e.g., Docker cross-compile
    # targets blocked on upstream librnp). Publish whatever archives
    # succeeded so users get macOS + Windows MSVC binaries.
    # Test builds (dispatch without `tag`) must not publish.
    if: always() && !cancelled() && (github.event_name != 'workflow_dispatch' || inputs.tag != '')
    runs-on: ubuntu-latest
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v8
        with:
          pattern: archives-*
          merge-multiple: true
      - name: Upload release assets
        # release-plz already created the release object (the tag is
        # its); this step only attaches artifacts. The previous
        # Ruby-based create-github-release helper broke on current
        # runners (bundler/rubygems mismatch) — gh does the same job
        # with one dependency the workflow already trusts.
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          set -euxo pipefail
          # Create-if-missing (manual dispatch for a pre-release-plz
          # tag); idempotent otherwise. --clobber makes re-dispatching
          # a tag safe.
          gh release view "$DEPLOY_TAG" -R "$GITHUB_REPOSITORY" ||
            gh release create "$DEPLOY_TAG" -R "$GITHUB_REPOSITORY" \
              --title "$DEPLOY_TAG" --generate-notes
          if compgen -G "*" > /dev/null; then
            gh release upload "$DEPLOY_TAG" -R "$GITHUB_REPOSITORY" --clobber *
          else
            echo "no archive artifacts to upload"
          fi

  publish-snap:
    name: snap
    needs: [github-release]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ env.DEPLOY_REF }}
      # snapcore/action-build@v1 invokes snapcraft as part of its
      # action — it needs the version replaced BEFORE this step.
      # Step is named "build" so ${{ steps.build.outputs.snap }} works.
      - name: Set snap version
        run: |
          # Strip any non-digit prefix (handles 'v', 'enprot-v', bare).
          RELEASE_TAG="$(echo "$DEPLOY_TAG" | sed 's/^[^0-9]*//')"
          sed -i "s/RELEASE_TAG/${RELEASE_TAG}/g" snap/snapcraft.yaml
      - name: Install snapcraft
        id: build
        uses: snapcore/action-build@v1
        with:
          snapcraft_token: ${{ secrets.SNAPCRAFT_RELEASE_LOGIN }}
      - name: Install review-tools
        run: sudo snap install review-tools
      - name: Publish
        uses: snapcore/action-publish@v1
        with:
          store_token: ${{ secrets.SNAPCRAFT_RELEASE_LOGIN }}
          snap: ${{ steps.build.outputs.snap }}
          release: stable