diff --git a/fips/README.md b/fips/README.md index 03838e0b..dc67e6f2 100644 --- a/fips/README.md +++ b/fips/README.md @@ -47,7 +47,6 @@ questionnaire, or filling in an audit response. | [build-fips-provider.md](doc/build-fips-provider.md) | Why the build is the way it is, and the publishing rules | | [verify-fips-provider.md](doc/verify-fips-provider.md) | Bench verification of a freshly built module, before it goes near the installer | | [zdew-integration.md](doc/zdew-integration.md) | How `ziti-edge-tunnel` finds and loads the provider, and what ZDEW has to place on disk | -| [implementation-plan.md](doc/implementation-plan.md) | The work items to get this shipped, in order, with the known gaps | | [compliance-position.md](doc/compliance-position.md) | What we may and may not claim, the operational-environment scope, and the evidence we retain | | [test-plan.md](doc/test-plan.md) | How to prove, on a real machine, that validated cryptography is in use | diff --git a/fips/doc/implementation-plan.md b/fips/doc/implementation-plan.md deleted file mode 100644 index 330006d1..00000000 --- a/fips/doc/implementation-plan.md +++ /dev/null @@ -1,230 +0,0 @@ -# Implementation plan - -Ordered so that each phase de-risks the next. Do not start phase 3 before phase 2 passes -- the installer work is -the expensive part and it is wasted if the module will not load inside the tunneler. - -## Phase 1 -- reproducible module build - -- [ ] Re-read the current security policy for CMVP certificate #4985 and reconcile it with - [build-fips-provider.md](build-fips-provider.md). The procedure previously used was written against the - 2023-12-29 draft; the certificate was validated 11 March 2025 and updated 21 November 2025. -- [ ] C runtime: **decided** -- keep the documented `/MD` build and solve loading in the installer (VC++ - redistributable prerequisite, or ship `vcruntime140.dll`). `/MT` modifies the security policy's build - method and forfeits the MM §7.9.2 footnote 5 cover. Choose which of the two installer options to take. -- [ ] Run `fips\scripts\Build-FipsProvider.ps1` end to end on a clean **Windows 10 x64** VM with **Visual Studio 2019**, - matching §5.3 of the security policy. Keep the VM image reference. CI output is a test artifact. -- [ ] Archive the evidence package produced by the script somewhere that outlives the VM. -- [ ] Do **not** sign as part of the module build. ZDEW's installer build already signs every binary it - packages, and `fips.dll` and `openssl.exe` go through that same pass. Keeping one signing process means - a change to how signing works never leaves these two files behind. - - The consequence for publishing: the release and `fips/provider.json` carry the **unsigned** hashes, - which is what ties the shipped bytes back to this build. The installer verifies those hashes on - download, then signs. `fipsinstall` runs at install time on the signed file, so the integrity HMAC - still covers what actually ships. - -The build machine from the previous attempt was an Azure VM that has been stopped for over a year. Treat it as -gone. The script exists so the procedure no longer lives on one host. - -## Phase 2 -- bench verification - -- [x] **Stages 1 to 3 pass** (`fips/scripts/Test-FipsWithTunneler.ps1`, 2026-09-16). An MSVC-built 3.1.2 `fips.dll` - loads into the mingw-built OpenSSL 3.6.3 core inside `ziti-edge-tunnel.exe`, and the tunneler reports - `[FIPS]`. The negative control confirms the marker is meaningful. This was the single largest unknown in - the plan; it is cleared, and none of the upstream fallbacks are needed. -- [x] Module self-tests pass on a host matching neither the certificate's tested environment nor the build - host: `Module_Integrity`, every algorithm KAT, all three DRBGs, every KDF, signatures and key agreement. -- [ ] Stage 4: functional pass against a quickstart network in FIPS-only mode, with any algorithm limitations - written down. X25519 and Ed25519 are absent from the 3.1.2 provider, so enrollment and TLS 1.3 group - negotiation are the cases to watch. - -## Phase 3 -- build pipeline - -**Decided:** the artifacts are published as assets on a **public GitHub release** in this repository and pinned -by SHA-256 in `fips/provider.json`. They are not committed. The earlier attempt committed both binaries -(8.7 MB) into `Installer/openssl/`; a blob in git history is not an evidence chain, and a public release plus a -hash lets a customer verify our claim without asking us for anything. - -- [x] **First provider release published** (2026-09-16), tag `fips-provider-3.1.2-20260916`, marked not-latest - so it cannot displace a ZDEW product release. All four artifacts plus `evidence.zip` attached. -- [x] `fips/provider.json` generated and verified: every pinned hash matches a fresh download from the - published URLs. -- [x] Commit `fips/provider.json`. -- [x] `Installer/build.ps1`: call `Installer/Get-FipsProvider.ps1` with `-Manifest "${checkoutRoot}\fips\provider.json"` - and `-Destination "${buildPath}\service"` - alongside the existing `ziti-edge-tunnel` fetch, so the FIPS files land next to `ziti-edge-tunnel.exe` - exactly as they will on the target machine. It fails the build on a hash mismatch. -- [x] **Decided:** always fetch. The files are inert unless `openssl.cnf` exists, and they are gated by the - `EnableFIPS` feature, so a machine that does not ask for FIPS never receives them. -- [ ] Record the FIPS provider version and `fips.dll` hash in `scripts/build-summary.txt` and in the build log - next to the existing `ziti-edge-tunnel version -v` capture. - -## Phase 4 -- installer - -Every item here touches `Installer/ZitiDesktopEdge.aip`. **Do not edit the AIP without asking first** -- see the -root `CLAUDE.md`. What follows is the specification to agree before any AIP work happens. - -The earlier attempt already built most of this and it was reverted. Recovering it is useful; shipping it as it -stood is not, because of the upgrade gap below. - -- [x] Optional feature `EnableFIPS`, Installation Behavior "Not installed" with **Installed if** - `ZITI_ENABLE_FIPS="1"`. The quotes matter: Advanced Installer accepts the unquoted `ZITI_ENABLE_FIPS=1` - and it then never matches, because MSI evaluates a string property against an integer literal as false. -- [x] `fips.dll`, `openssl.exe`, `libcrypto-3-x64.dll` and `libssl-3-x64.dll` as components in `APPDIR` with - `DigSign="true"`; `vcruntime140.dll` alongside them without it, since Microsoft already signed it. -- [x] A checkbox on `FolderDlg`, defaulted **off**, with text telling the reader to enable it only if they - understand the implications. `FolderDlg` rather than `OptionalFeatsDlg` because it is the only page - between Welcome and VerifyReady on a fresh install. -- [x] Deferred, elevated `SetFipsConfig` custom action after `InstallFiles`: runs `openssl.exe fipsinstall - -pedantic`, writes `openssl.cnf` with `APPDIR` substituted and forward slashes, confirms the provider - activates, and fails the install on any error. Conditioned `&EnableFIPS=3`, so it also runs on upgrades. -- [x] `RemoveFipsConfig` removes `openssl.cnf` and `fipsmodule.cnf`, conditioned `&EnableFIPS=2` so it covers - both uninstall and a maintenance run that unticks the feature. Verified: neither file is left behind. - -### The upgrade gap - -The earlier attempt conditioned the generation action on `NOT Installed AND ZITI_ENABLE_FIPS`. ZDEW's -auto-updater runs the new MSI **silently**, and in a silent major upgrade nothing sets `ZITI_ENABLE_FIPS`, so -a machine that was in FIPS mode would quietly stop being in FIPS mode at its next automatic update. - -What actually happens is the opposite, and it was observed rather than reasoned about. Every build gets a new -`ProductCode`, so every install over an existing one is a major upgrade, which means `MigrateFeatureStates` -runs. From an install log: - -``` -PROPERTY CHANGE: Modifying ZITI_ENABLE_FIPS property. Its current value is '0'. Its new value: '1'. -MigrateFeatureStates: based on existing product, setting feature 'EnableFIPS' to 'Absent' state. -Feature: EnableFIPS; Installed: Absent; Request: Absent; Action: Absent -``` - -The previous installation's feature selection wins over the current command line. That is the behaviour we -want for the silent case -- a FIPS machine stays a FIPS machine, and `SetFipsConfig` re-runs because it is -conditioned on `&EnableFIPS=3` rather than on `NOT Installed`, so a new `fips.dll` gets a matching -`fipsmodule.cnf`. - -It also means **FIPS cannot be turned on by upgrading**. Passing `ZITI_ENABLE_FIPS=1` to an upgrade of a -non-FIPS installation does nothing. Enabling it on an existing install needs `ADDLOCAL=EnableFIPS` or a clean -install. Document that; it will otherwise be reported as a bug. - -Still outstanding: - -- [ ] Persist the choice at install time: `HKLM\SOFTWARE\NetFoundry\Ziti Desktop Edge`, `FipsEnabled` - (REG_DWORD), so the setting is legible outside MSI's feature state -- for support, for inventory, and - for the tray UI. -### Managed policy - -Regulated fleets configure by policy, not by clicking a checkbox on 4,000 machines, so "require FIPS" has to -be expressible through Group Policy and MDM. ZDEW already has that machinery -- -`ZitiUpdateService/windows/gpo/NetFoundry.ZitiMonitorService.admx`, documented in -`ZitiUpdateService/POLICY-ADMIN-GUIDE.md` -- but FIPS does not fit its existing shape, and that needs -deciding before any of it is built. - -Every policy today (`UpdateTimer`, `InstallationCritical`, the maintenance window settings) is read **at -runtime** by `ziti-monitor`, which can act on a changed value immediately. FIPS is not a runtime setting. It -is MSI feature state fixed at install time, and a policy cannot switch on a feature whose files are not on -disk. So there are two halves, and only one of them enforces anything: - -- [ ] **Installer side.** `AppSearch` the policy value into `ZITI_ENABLE_FIPS` so a policied machine installs - the feature without anyone passing a command line. On its own this only works for fresh installs: - `MigrateFeatureStates` makes an existing installation's feature selection win on every upgrade, so - turning FIPS *on* for a machine that lacks it also needs `ADDLOCAL=EnableFIPS`. Decide whether the - installer forces that when policy requires FIPS, or whether policy-driven enablement is a documented - reinstall. -- [ ] **Runtime side.** `ziti-monitor` reads the policy and refuses to start the tunneler when policy requires - FIPS and the machine is not in FIPS mode, logging why. This is the half that actually enforces, and it - is also the answer to "what happens when `fipsinstall` fails on a machine whose administrator declared - it must not run non-approved cryptography". -- [ ] Decide what the policy is named and where it lives in the ADMX tree, alongside the existing update - settings rather than in a category of its own. -- [ ] `POLICY-ADMIN-GUIDE.md` carries compliance presets (CJIS, DISA STIG, PCI, NIST, NERC CIP, HITRUST). - Whoever reads those is the exact audience for FIPS, so the install flag belongs next to the update - cadence settings in the same presets. - -### Automatic update URL - -`AutomaticUpdateURL_Text` lets a fleet override the update stream URL entirely. Anything that depends on -changing what a stream file advertises -- notably the `-win32crypto` migration in -[../../doc/win32crypto-deprecation.md](../../doc/win32crypto-deprecation.md) -- does not reach a fleet pointing -that policy at its own mirror. Those customers have to be told directly. - -## Phase 5 -- surfacing real state - -### Where it goes - -The About screen already has the line. `DesktopEdge/Views/Screens/MainMenu.xaml.cs` renders: - -``` -App: 2.11.7.0 Service: v1.19.0 openssl -``` - -built as `$"App: {appVersion} Service: {version} {crypto}"`. That trailing word is the natural home for FIPS -state, and it sits beside the service version, which is what someone reads when they want to know what is -actually running. - -### Why it cannot simply be extended - -`crypto` is a compile-time constant: - -```csharp -#if WIN32CRYPTO - string crypto = "win32crypto"; -#else - string crypto = "openssl"; -#endif -``` - -FIPS is neither a compile-time nor an install-time fact from the UI's point of view -- it is whether the -*running* tunneler loaded the provider. Deriving it from the build, or from `File.Exists(fips.dll)` as the -earlier attempt did, is the same mistake in a new place: it reports that a file was copied. - -The data is not available to ask for. `TunnelStatus.ServiceVersion` -(`ZitiDesktopEdge.Client/DataStructures/DataStructures.cs:412`) carries `Version`, `Revision` and `BuildDate` -and nothing about the TLS backend, so the IPC payload has to grow before the UI can render anything truthful. - -### Shipped as a stopgap in 2.12.0.0 - -The About line appends `(FIPS 140-3)` when `openssl.cnf` exists in the install directory. That file is written -only after `fipsinstall` passes this machine's power-on self-tests and the provider is confirmed to activate, -and it is deleted again on failure or on feature removal -- so it reports that the installer configured FIPS -here, which is a real signal and not take-1's "a file was copied". - -What it does not report is whether the tunneler running right now loaded it. Replace it with the work below -rather than leaving it indefinitely. - -### Work - -- [ ] Decide where the truth comes from. In order of preference: - 1. the tunneler reports it in the status payload -- needs an upstream change, and is the only source - that is authoritative by construction - 2. `ziti-monitor` parses the tunneler's own startup line, `- openssl config : configured using - found by `, and forwards it -- no upstream change, and still describes the running process - 3. anything derived from files on disk -- rejected, see above -- [ ] Carry it on `ServiceVersion` (or alongside it) so the UI has something to bind to. -- [ ] Render it in the About line. Include the provider version, and link CMVP certificate #4985 rather than a - Wikipedia article on FIPS 140-2: the module is validated under **140-3**, and 140-2 validations move to - the CMVP historical list in September 2026. -- [ ] Say something unambiguous when FIPS is **off**, so a customer who believes they enabled it finds out - here rather than during an audit. -- [ ] Log the state in the monitor service log and include it in support bundles, so the - `debug-ziti-desktop-edge-win` workflow can tell whether a reporting machine was in FIPS mode. - -## Phase 6 -- documentation and release - -- [ ] Release-notes entry that states the certificate number, the provider version, the supported operational - environment, and the known algorithm limitations. -- [ ] A customer-facing page: how to enable it, how to verify it themselves, what is and is not covered. Point it - at [compliance-position.md](compliance-position.md) rather than restating the claim in new words. -- [ ] Add the FIPS cases to `manual-testing.md`. -- [ ] Subscribe someone to OpenSSL's FIPS/CVE announcements and write down what happens when one lands. See - [compliance-position.md](compliance-position.md#cves-in-the-validated-module). - -## Open questions for the tunneler and SDK maintainers - -1. Is the `-win32crypto` Windows build being retired? It is still published for v1.19.0, and ZDEW's paired - release streams and promotion tooling exist only to serve it. -2. Is an MSVC-built FIPS provider loading into the mingw-built static OpenSSL core a configuration upstream is - willing to support, or should ZDEW expect a change in how OpenSSL is linked? -3. How is the E2EE mode selected and can it be pinned to `tls` (or `aes-gcm`) from the client side? The FIPS - claim covers the transport regardless; it covers the E2EE payload only in those modes. -4. Does anything in ziti-edge-tunnel or ziti-sdk-c call OpenSSL outside the library context created by - `tlsuv`? Anything on the default context is unaffected by `openssl.cnf` and sits outside the boundary. diff --git a/fips/doc/test-plan.md b/fips/doc/test-plan.md index 413d14cd..b7c77f8d 100644 --- a/fips/doc/test-plan.md +++ b/fips/doc/test-plan.md @@ -150,9 +150,13 @@ Run, and record the result of each: ## 7 -- the setting survives an upgrade -The failure this catches is described in -[implementation-plan.md](implementation-plan.md#the-upgrade-gap----fix-this-or-the-feature-is-a-lie): an -automatic silent upgrade that drops FIPS mode without saying so. +The failure this catches is an automatic silent upgrade that drops FIPS mode without saying so. Verified +working, but it depends on `SetFipsConfig` running before `StartServices` and on `MigrateFeatureStates` +carrying the feature -- both easy to disturb. See +[zdew-integration.md](zdew-integration.md#the-config-must-exist-before-the-service-starts). + +Note also that FIPS cannot be *enabled* by upgrading: `ZITI_ENABLE_FIPS=1` passed to an upgrade is ignored, +because the previous installation's feature selection wins. 1. Note the current state -- steps 4 and 5 above. 2. Trigger an automatic update (`Restart-Service ziti-monitor` forces an immediate check). diff --git a/fips/doc/zdew-integration.md b/fips/doc/zdew-integration.md index d002dc3a..c23fb9c6 100644 --- a/fips/doc/zdew-integration.md +++ b/fips/doc/zdew-integration.md @@ -202,7 +202,7 @@ with the peer: `e2ee_tls` was added in ziti-sdk-c 1.19.0. For a FIPS claim that covers E2EE payloads and not merely the transport, the mode has to be constrained to `tls` (or `aes-gcm`, with Microsoft's certificate cited instead of OpenSSL's) on both ends. That is a network-policy decision, not an installer setting, and it needs confirming -with the SDK maintainers -- see the open questions in [implementation-plan.md](implementation-plan.md). +with the SDK maintainers. **Update verification.** `ziti-monitor` checks Authenticode signatures and talks HTTPS to the release stream using Windows CryptoAPI and .NET Framework, both of which use Microsoft's validated modules when the machine has