# Changelog

All notable changes to the RockyGuard library are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).

See `docs/RockyGuard_Versioning_Policy.txt` (or its PDF) for the full
versioning policy, including the definitions of MAJOR / MINOR / PATCH,
the release procedure, and the vendor-key rotation protocol.

## [Unreleased]

(Empty — all in-flight work has been rolled into the 1.2.0 release
section below in preparation for tagging.)

## [1.2.0] - 2026-04-23

This release addresses 74 issues identified across four rounds of
code review (three internal, one by an external QA specialist),
adds first-class Linux x86_64 support, and introduces a ten-phase
pre-release QA plan (`docs/RockyGuard_QA_Plan.txt`) that now gates
every release.

**v1.2.0 is the inaugural shipping release of the library.** No
prior version has reached any customer; the 1.0.x and 1.1.x
markers that appear elsewhere in this document refer to
pre-release development snapshots. Because there is no installed
base, Phase 8 (upgrade / downgrade) of the QA Plan has no
applicable target this cycle; it becomes a BLOCKER starting with
the next release. The library ships a legacy-format HMAC read
fallback in `src/time_anchor.cpp` and `src/generation_counter.cpp`
as defensive programming for future releases.

### Added

#### Cross-platform

- **Linux x86_64 support.** Shipped as a separate
  `rockyguard-v1.2.0-linux-x64-customer.zip` via the same
  `customer-use/` layout as Windows. Build target is Ubuntu 22.04 LTS
  (glibc 2.35), but the binary's actual runtime floor is `GLIBC_2.34`
  — the highest glibc symbol version the code imports once libstdc++
  and libgcc are statically linked. Supported distros: Ubuntu 22.04,
  Ubuntu 24.04, Debian 12, RHEL 9 / CentOS Stream 9 / Rocky Linux 9,
  Amazon Linux 2023, and any other x86_64 Linux with glibc ≥ 2.34.
  Ubuntu 24.04 (glibc 2.39) is validated as a forward-compat gate;
  a 22.04-built binary runs unchanged under 24.04 with identical
  56/56 gtest pass rate. macOS and glibc-2.28 (RHEL 8 / Amazon
  Linux 2 / Debian 10) coverage remain deferred to v1.3. See
  `qa/cross_platform/linux_baseline/RESULTS.md`.
- Per-platform release zip layout (Option E): one zip per OS produced
  by running `build_and_package.sh` natively on that OS, no per-OS
  subdirectories inside a single megapackage. Cleaner downloads,
  smaller archives, matches convention used by OpenSSL / Git /
  most commercial C++ libs. `archives/MANIFEST.txt` records the
  SHA-256 and byte count of every zip this repo has ever produced
  (append-only, audit-grade).
- Per-platform CMake build directories: `build-windows/`,
  `build-linux/`, `build-macos/` (and their `_shared/` siblings)
  so a Windows host and a WSL shell can build concurrently without
  wiping each other's artifacts. Replaces the single `build/` +
  `build_shared/` pair that caused cross-OS clobbering during v1.2.0
  QA. Managed via `build_and_package.sh`; QA harnesses resolve paths
  through `qa/common/server_harness.py` helpers (`static_build_root`,
  `shared_build_root`, `build_tool`, `shared_build_tool`) with a
  legacy-path fallback for one release of backward compat.

#### Library runtime / API

- `include/rockyguard/version.h` — compile-time and run-time access
  to the library version via `ROCKYGUARD_VERSION_MAJOR/MINOR/PATCH`
  macros and `rockyguard::VERSION_STRING`. Generated from CMake so
  it always matches the package version.
- `crypto::sha256_file_hex()` — streaming file-hash API used by the
  binary integrity self-check. Bounded memory regardless of file size.
- `crypto::hmac_sha256_hex()` — keyed HMAC-SHA-256 primitive using
  OpenSSL's `HMAC()`.
- `crypto::constant_time_compare()` — timing-safe string comparison
  for HMAC verification in the floating protocol.
- Monotonic sequence counter (`seq`) on every floating client request
  to defeat replay attacks. Each lease tracks `last_seq` on the
  server.
- Response envelope format for signed server responses: `{payload,
  nonce, signature}` where `payload` is a JSON string literal.
  Eliminates JSON-canonicalization fragility.
- Bounded task queue on the floating server: excess connections are
  closed cleanly when the queue fills (4x pool size).
- Server-side total deadline for `read_http_request` (anti-Slowloris).
- Server-side total deadline for the TLS handshake via non-blocking
  `SSL_accept` with `poll`/`select`.
- Listener non-blocking mode to prevent `accept()` from freezing on
  SYN+RST races.
- Full TLS hostname and IP verification via
  `X509_VERIFY_PARAM_set1_host` / `set1_ip_asc`.
- SNI (`SSL_set_tlsext_host_name`) on the floating client and the
  online time anchor check.
- Fail-closed behavior in `init_tls()` when the configured CA bundle
  cannot be loaded — previously silently disabled verification.
- Fail-closed integrity check in shared builds when the DLL file is
  unreadable (e.g., locked by a malicious launcher) or when the
  module path cannot be resolved.
- HTTP response header streaming in the online time check
  (`fetch_time_from_host`) — reads until `\r\n\r\n` rather than one
  record, so fragmented responses parse correctly.
- 52 new test cases bringing the suite from 7 to 59 on Windows
  (56 on Linux — the three Windows-only ones are WMI-codepage,
  registry-anchor, and `LockFileEx` byte-range). Categories:
  end-user license, vendor license, floating protocol, clock
  manipulation, crypto, fingerprint, TLS certificate validation.
- **Phase 2 harness suite** — automation that exercises the
  library's hardening end-to-end, beyond unit tests:
  - 2A.1 bad-cert MITM (`qa/red_team/bad_cert_mitm/`) —
    validates issues #68 and #71 on both Windows and Linux.
  - 2A.2 file-lock attack (`qa/red_team/file_lock_attack/`) —
    three Windows cases (`FileShare.None` on DLL / `.sig` /
    `LockFileEx` byte-range) + two Linux cases (`chmod 000` on
    `.so` / `.sig`). Validates #3 and #73.
  - 2A.3 time-traveler (`qa/red_team/time_traveler/`) — anchor
    tamper harness with three cases (2-year rollback / 65-min
    past tolerance / 30-min within tolerance regression guard)
    on both OSes; HOME-sandboxed on Linux so no real user-state
    pollution. Validates #4, #9, #72.
  - 2B.1 University Lab burst (`qa/stress/university_lab/`) —
    500-client burst against a 50-seat floating server.
    Validates #20 and #70.
  - 2B.2 Ghost SYN-Flood / Slowloris (`qa/stress/ghost_synflood/`)
    — validates #10 and #70.
  - 2B.3 Ghost Client lease reaping (`qa/stress/ghost_client/`).
  - 2B.4 Network Fragmentation via `clumsy.exe`
    (`qa/stress/network_fragmentation/`) — issue #72 under TCP
    fragmentation + loss. Validates the HTTP `Date:` header parse
    under fragmentation (with optional
    `ROCKYGUARD_DEBUG_TIME_ANCHOR=1` positive-signal diagnostic).
  - 2C.1 Zombie-thread regression (`qa/lifecycle/zombie_thread/`)
    — validates #67.
  - 2C.2 Wi-Fi Drop via `clumsy.exe` (`qa/lifecycle/wifi_drop/`).
  - 2C.3 VS Diagnostic Tools memory profile — streaming SHA-256,
    issue #74.
  - 2C.4 TLS misconfig fail-closed (`qa/red_team/bad_cert_mitm/`)
    — issue #69.
  - 2D.1 HTTP fuzzer (`qa/fuzzing/http_fuzzer/`) — random
    wire-format fuzzing, caught the `nlohmann::json::type_error`
    crash DoS in the floating server handlers.
  - 2D.2 JSON payload fuzzer (`qa/fuzzing/json_fuzzer/`) — same
    crash surface from a different angle.
  - 2D.3 TLS handshake fuzzer (`qa/fuzzing/tls_fuzzer/`) — issue
    #60 (SSL_accept hang with drip ClientHellos).
  - 2E.1 non-ASCII ANSI codepage (`qa/locale/non_ascii_ansi_codepage/`)
    — surfaces the WMI → UTF-8 fix on Chinese-codepage Windows.
  - 2E.2 timezone edge (`qa/locale/timezone_edge/`) — Nepal +5:45,
    Newfoundland -3:30, Lord Howe +10:30.
  - Phase 2 Linux subset orchestrator
    (`qa/run_phase2_linux_subset.sh`) — runs the seven
    portable harnesses (2A.1, 2A.2, 2A.3, 2B.1, 2D.1, 2D.2, 2D.3)
    in one invocation on WSL/Linux, complementing the 56/56 Linux
    gtests.
  - `qa/common/server_harness.py` — shared Python library for
    harness-side server provisioning, with platform-aware
    path resolution and tool-error surfacing.
- Opt-in dev diagnostic `ROCKYGUARD_DEBUG_TIME_ANCHOR`. When set to
  `1` in the environment, `time_anchor::fetch_time_from_host` emits
  one stderr line per online anchor attempt reporting `host`,
  `response_bytes`, `read_loops`, `date_found`, and `date_parsed`.
  Used by Phase 2B.4 to assert a direct positive signal that the
  `SSL_read` loop for issue #72 completed the HTTP `Date:` header
  parse under fragmentation, rather than inferring it from a timing
  delta. Internal only; not part of the public API and silent
  unless the env var is set.
- `RockyGuard_License_Agreement.txt`/`.pdf` — template commercial
  software license agreement (requires attorney review before use).
- `RockyGuard_QA_Plan.txt`/`.pdf` — 10-phase pre-release QA plan.
- `RockyGuard_QA_Folder_Structure.txt`/`.pdf` — explains the `qa/`
  directory layout.
- `RockyGuard_Versioning_Policy.txt`/`.pdf` — this versioning scheme.
- `CHANGELOG.md` — this file.

### Changed

- HMAC format for time-anchor files, generation-counter files, and
  registry entries switched from `SHA-256(data + seed + salt)` to
  proper HMAC-SHA-256 keyed with the seed. Since v1.2.0 is the
  first shipping release, no customer has state files in the old
  format; the library nevertheless carries a legacy-format read
  fallback so future in-field upgrades (v1.2.x → v1.3.x etc.) that
  cross a similar format boundary will migrate cleanly.
- Floating protocol: HMAC client authentication is now always
  required, including under TLS. Previously TLS short-circuited the
  HMAC check, but TLS alone does not authenticate application-level
  session identity (a user with access to server logs could forge
  requests for other clients).
- Floating server now uses `SSL_CTX_use_certificate_chain_file()`
  instead of `SSL_CTX_use_certificate_file()` so intermediate-issued
  certs (Let's Encrypt, DigiCert, etc.) work without manual chain
  stitching.
- Integrity self-check now streams the DLL through SHA-256 in 64 KB
  chunks instead of buffering the full file — bounded memory regardless
  of library size.
- `parse_iso8601()` now logs a warning when it cannot parse a date,
  rather than silently treating the license as expired.
- CLI tools (`license_create`, `vendor_license_create`) now reject
  unknown command-line arguments with a clear error instead of
  silently ignoring them. Negative numeric arguments are also rejected.
- Thread-safety: `FloatingLicenseClient::checkout()/checkin()` now
  serialized by a mutex. `std::atomic` access paths audited for
  proper memory ordering.
- Listener backlog raised from 32 to `SOMAXCONN`.
- Server accept loop uses `poll()` on Linux (replaces `select()`)
  to avoid `FD_SETSIZE` undefined behavior on high-fd servers.
- Linux builds now statically link libstdc++ and libgcc and
  dead-strip unused symbols from static archives
  (`-ffunction-sections -fdata-sections` + `-Wl,--gc-sections`).
  `build_and_package.sh` also runs `strip --strip-all` on the
  shipped Linux artifacts. Effect: the shipped `.so` and CLI
  tools have **no `GLIBCXX_*` or `CXXABI_*` runtime dependency** —
  only glibc is dynamically linked. The Linux runtime floor
  dropped from the build host's glibc (2.35 on Ubuntu 22.04) to
  the true code-imported floor (`GLIBC_2.34`), adding RHEL 9,
  CentOS Stream 9, Rocky Linux 9, and Amazon Linux 2023 to the
  supported-distro list. Binary sizes grew from ~500 KB to
  2–12 MB depending on target (library includes OpenSSL
  statically; `rg_fingerprint` is 2 MB, `license_verify` is
  9 MB, `librockyguard.so` is 12 MB). Covered by the Phase 3
  baseline procedure in `qa/cross_platform/linux_baseline/`.

### Fixed

Too many individual fixes to list here; see the four rounds of code
review in `memory/code_review_issues.md` for the complete 74-issue
list with per-issue descriptions and validating tests.

Issue-tracker highlights:
- #1 Timing-side-channel in HMAC comparison (now constant-time)
- #3 Integrity check bypass by deleting the `.sig` file
- #5 COM state corruption on Windows when library was not the first
  to initialize COM
- #7 `SSL_set_fd` ignored SOCKET type on 64-bit Windows
- #8 Vendor license global state not protected by a mutex
- #10 Slowloris DoS on the floating server
- #15 Attacker-controlled `Content-Length` via `std::atoi`
- #18 `FloatingLicenseClient::checkout()` could crash via
  `std::terminate` when re-acquiring after an error
- #39 NULL-deref in `base64_encode/decode` when `BIO_new` fails
- #62 Request-replay against `/heartbeat`
- #68 Missing TLS hostname/IP verification (full MITM possible)
- #69 `init_tls` silently disabled verification on bad CA path
- #72 Online time check false-positive "clock set forward" from
  misinterpreting `cert_time` as current time
- #73 Integrity check fail-open on locked DLL file

Late-cycle fixes surfaced by the Phase 2 harness suite (not
previously tracked as numbered issues):

- `hardware_fingerprint_win`: the WMI-to-`std::string` narrow path
  now uses `WideCharToMultiByte(CP_UTF8, ...)` instead of
  `_bstr_t`'s implicit `CP_ACP` conversion. On machines whose
  WMI fields contain non-ASCII characters (e.g., a Chinese-OEM
  motherboard whose serial includes hanzi), the old path produced
  non-UTF-8 bytes in the system's ANSI code page (CP936 on zh-CN,
  CP932 on ja-JP, Windows-1252 on en-US). Those bytes then flowed
  into the fingerprint hash and, if reused verbatim by the
  customer's license JSON, broke nlohmann::json's UTF-8 contract.
  Post-fix the fingerprint string is always valid UTF-8
  regardless of system locale. ASCII input is unchanged (UTF-8
  is a superset of ASCII), so machines with the common plain-
  alphanumeric WMI values see no fingerprint hash drift.
  There is no customer-visible compatibility break because
  v1.2.0 is the inaugural shipping release; future updates that
  affect the fingerprint algorithm will need to ship with a
  re-issuance policy. Covered by
  `FingerprintTest.WideToUtf8IsLocaleIndependent` and
  `FingerprintTest.WideToUtf8PreservesAscii` which run on every
  Debug build, plus the Phase 2E.1 harness.
- `floating_server`: field-access inside the three request handlers
  (`handle_checkout`, `handle_checkin`, `handle_heartbeat`) is now
  inside the `try/catch` around `json::parse`. Previously the catch
  only covered the parse itself; a valid-JSON-but-wrong-type field
  (e.g. `{"client_id":[1,2,3],"nonce":"x"}`) would parse fine and
  then throw `nlohmann::json::type_error` from `value<string>(...)`.
  The exception escaped the worker thread, MSVC's thread wrapper
  called `std::terminate()`, which called `abort()` and killed the
  whole server process with `STATUS_BREAKPOINT`. Four such inputs
  drained a 4-worker pool and took the server down; any customer
  running the floating server on a public network could have been
  DoS'd by a single malformed JSON. Discovered by the Phase 2D.1
  Python fuzzer (`qa/fuzzing/http_fuzzer/`). Post-fix the same
  fuzzer ran 1766 iterations in 60 s with zero crashes. A
  defense-in-depth `try/catch` around `route_request` in
  `worker_loop` was also added as a backstop so any future missed
  case downgrades to a 500 response rather than a worker kill.
- `time_anchor::fetch_time_from_host` now loads the Windows system
  root-certificate store (the "ROOT" store, via
  `CertOpenSystemStoreW` + `X509_STORE_add_cert`) before the TLS
  handshake with the public time-anchor hosts. Previously the code
  called `SSL_CTX_set_default_verify_paths()`, which on Windows
  searches only Unix-style paths (`/usr/local/ssl/certs` etc.) that
  don't exist. Every public-host TLS handshake therefore failed
  chain validation, and the library silently fell through to its
  first-run accommodation path — effectively **disabling the
  online-time rollback defense on Windows** in pre-release code.
  File- and registry-anchor rollback detection was unaffected, so
  license verification would have worked, but the online safety
  net that catches a rolled-back clock on a freshly-installed /
  anchor-cleaned machine was not in effect. Closed before v1.2.0
  reached any customer. Discovered during Phase 2B.4 diagnostic
  probing;
  see `qa/stress/network_fragmentation/RESULTS.md` for the full
  forensic. POSIX builds keep the existing
  `SSL_CTX_set_default_verify_paths()` code path.
- `crypto::sha256_file_hex()` now verifies the total bytes hashed
  matches the file size captured at open time. If another process
  locks a byte range mid-read (e.g. `LockFileEx`), an ACL change
  revokes read access, or the file is truncated under us, the
  function returns an empty string instead of finalizing a hash
  over the partial prefix. The binary integrity check therefore
  surfaces *"Cannot read library binary"* (its real fail-closed
  branch) instead of *"library file has been modified"* when the
  DLL is locked by an attacker. Covered by a new regression in
  `qa/red_team/file_lock_attack/` Case C and by unit tests in
  `tests/test_crypto.cpp`. The fail-closed invariant was already
  in place; this change only improves the diagnostic accuracy.

### Security

- Adopted HMAC-SHA-256 as a real keyed MAC for persistent-state
  integrity (previously a bare SHA-256 concatenation, vulnerable
  to length-extension attacks in principle).
- Fail-closed defaults throughout: TLS misconfig, missing `.sig`,
  unreadable DLL, empty session secret, missing HMAC all now
  reject rather than silently allowing.
- Online time verification uses full hostname validation plus a
  12-host random pool, making hosts-file redirection impractical.

### Deprecated

- (None in this release.)

### Removed

- (None in this release.)

---

## Prior history

Prior to v1.2.0, the project was at v1.0.0 with no formal CHANGELOG.
The move from 1.0.0 directly to 1.2.0 reflects the substantial
additions captured above (multiple MINOR-worthy features across the
four review rounds). No public API breakage was introduced in any
round, so no MAJOR bump is warranted.
