Skip to content

feat(licensing): add a verify command to prove the key pair before a release (ADR D84) - #284

Merged
thiagoluga merged 2 commits into
masterfrom
feat/license-verify-command
Aug 9, 2026
Merged

feat(licensing): add a verify command to prove the key pair before a release (ADR D84)#284
thiagoluga merged 2 commits into
masterfrom
feat/license-verify-command

Conversation

@thiagoluga

Copy link
Copy Markdown
Owner

Follow-up to #283, from the question "should I test a key before publishing the version?" — yes, and the check that matters was not possible with the tooling as it stood.

The gap D83 does not cover

D83's guard catches the burned placeholder coming back — a revert. It does not catch the other way of getting the key wrong on a rotation, which is at least as likely: committing a public key belonging to a different pair than the private key in the vault (a second keygen run, a copy from the wrong terminal scrollback).

Nothing detected that. It compiles, packs, passes CI and publishes. Then it surfaces later and all at once, as every license the maintainer issues being rejected by every customer — and since D83 a v* tag publishes with no human step, against immutable NuGet versions.

The tool had keygen and sign but no way to exercise both halves together.

verify --license <key>

Runs ProLicense.Validate against the key embedded in that build — deliberately the same code path a customer's process takes, so what it proves is what they will experience.

dotnet run --project tools/NeoReports.LicenseTool -- \
    sign --key <vaulted-key.pem> --licensee "Key pair check" --days 1 > check.key

dotnet run --project tools/NeoReports.LicenseTool -- verify --license "$(cat check.key)"

VALID (exit 0) → the halves match, safe to tag. INVALID (SignatureInvalid) → the committed public key is from another pair; stop before tagging. Documented in the tool README as a pre-release step.

It takes the license key, never a .pem: verification needs only the public half, so the command has no reason to be able to read a private key at all.

The test pins that it can fail

A verify that reports success regardless would be worse than not having one — it would launder the exact mistake it exists to catch into a green tick. So the test asserts rejection, not acceptance.

A freshly generated pair stands in for a mismatched one (by construction it is not the embedded pair). The positive path cannot run in CI — it needs the vaulted key, which never touches CI. That asymmetry is the design: CI pins that the command discriminates; the maintainer runs the positive half by hand.

Verified failing without the fix: making verify always return 0 fails that test and only that test.

Verification

  • Solution builds clean, 0 warnings.
  • Full suite 33/33 projects, 0 failures (NEOREPORTS_REQUIRE_DOCKER=1); Licensing 50/50.
  • Maintainer tooling — NeoReports.LicenseTool is not packable and ships nowhere.

/code-review is reserved for explicit user invocation and could not be run from this session. /security-review ran clean on #283's diff; this change adds no new surface (read-only validation, no key material handled).

…release (ADR D84)

D83's guard catches the burned placeholder coming back — a revert. It does not
catch the other way of getting the key wrong on a rotation, which is at least as
likely: committing a public key that belongs to a DIFFERENT pair than the private
key in the vault. A second keygen run, a copy from the wrong scrollback.

Nothing detected that. It compiles, packs, passes CI and publishes. The failure
surfaces later and all at once, as every license the maintainer issues being
rejected by every customer — and since D83 a v* tag publishes with no human step,
against immutable NuGet versions.

The tool had keygen and sign but no way to exercise both halves together.
`verify --license <key>` runs ProLicense.Validate against the key embedded in
that build — deliberately the same code path a customer's process takes, so what
it proves is what they will experience. The README documents the pre-release
ritual: sign a throwaway one-day license with the vaulted key, verify it, expect
VALID.

It takes the license key, never a .pem. Verification needs only the public half,
so the command has no reason to be able to read a private key at all.

The test pins that it can FAIL, not that it works. A verify that reports success
regardless would be worse than not having one — it would launder the exact
mistake it exists to catch into a green tick. A freshly generated pair stands in
for a mismatched one, being by construction not the embedded pair. The positive
path cannot run in CI because it needs the vaulted key; that asymmetry is the
design. Verified by making verify always return 0 — that test, and only that
test, fails.
…it away

The coverage gate rejected the first cut, correctly. The three uncovered lines
were the VALID branch — the one the maintainer depends on before every release —
and the PR had rationalized that as inherent: "the positive path needs the
vaulted private key, so CI cannot run it."

True, and not the same as untestable. As shipped, the branch would have gone out
having never executed once, so a null licensee or a bad format string in it would
surface on the single occasion it matters, while the operator trusted the answer.

The body moves behind an internal seam taking an explicit verifying key — null
for every call the CLI makes, meaning the embedded key — so a test drives the
same code with a generated pair. The CLI surface is unchanged; the command still
means "validated against what we shipped".

A --public-key flag would have been simpler and was rejected: it would let the
pre-release check run against the key it was just handed, verifying nothing, and
that footgun would sit on the one command whose purpose is catching exactly that
class of mistake.
@sonarqubecloud

sonarqubecloud Bot commented Aug 9, 2026

Copy link
Copy Markdown

@sonarqubecloud

sonarqubecloud Bot commented Aug 9, 2026

Copy link
Copy Markdown

@thiagoluga
thiagoluga merged commit 6b5f20f into master Aug 9, 2026
5 checks passed
@thiagoluga
thiagoluga deleted the feat/license-verify-command branch August 9, 2026 01:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant