Securing the supply chain
Supply-chain features describes what each feature does. This chapter is the reasoning
around them: which attack each one answers, where each one stops, and what a build looks like when they are
combined. It also documents @jenesis.signature, the one mechanism that has no other home.
What an attacker needs
A build is an attractive target because it runs code with your rights and produces something other people install. Two surfaces carry that risk:
- What you ship. The jars in your artifact's closure. A compromised release, a hijacked publishing account or a typo-squatted coordinate arrives as an ordinary dependency and runs wherever your artifact runs.
- What builds it. Compilers, linters, formatters, test engines. These never ship, but they execute on your machine and write the bytes you publish, so a compromised one can alter output that looks untouched.
The second surface is usually the larger one: a build resolves far more tools than a distribution ships libraries. It is also where Jenesis differs most from the usual arrangement.
The tool is not a dependency
build.jenesis requires jdk.compiler and java.xml and nothing else - there is no third-party library
anywhere in the engine. It ships as plain Java source under build/jenesis/ in your own repository
(see Getting started), so the build tool arrives the way your application code
does: by review, in a diff, under version control.
That removes a class of risk rather than mitigating it: there is no plugin resolution before a build starts, and no plugin closure to pin. External tools a build genuinely needs - a Kotlin compiler, PMD, a formatter - resolve as ordinary dependencies in their own group and are pinned like any other.
Four questions
Each mechanism answers one question, and none answers another's:
| Question | Answered by | Runs |
|---|---|---|
| May we use a dependency we cannot verify at all? | strict pinning | every build |
| Are these the exact bytes we vetted? | pin checksums | every build |
| Were the bytes we vetted the ones upstream released? | @jenesis.signature, naming a key or an identity |
every download, opt-in |
| What did we ship, and is it permitted or known-vulnerable? | SBOM, licence and vulnerability checks | every build |
| What can a dependency reach when it executes? | container isolation | opt-in |
The first two are Pinning: a pin records an exact version and the SHA-256 of the jar in
your own sources, every build re-hashes what it downloads, and a mismatch fails. Strict mode additionally
refuses any third-party coordinate without a checksum. That also blunts a tampered POM, since a dependency it
introduces arrives unpinned and is rejected - though signature verification addresses the POM directly.
The fourth is the supply-chain features chapter, and the fifth is
Build performance & isolation, which confines what test code and
an artifact's main can reach. Pinning guarantees what runs; isolation limits what it can do.
That leaves the third question, which no hash can answer - and which matters most at one particular moment, the run that writes the pins. The one build a pin cannot protect is that argument; what follows is the mechanism.
Provenance: who produced the bytes
A checksum is computed from whatever the repository served, so it proves an artifact has not changed since you
recorded it - not that what you recorded was genuine. An artifact swapped before your first pin is frozen as
an accepted pin just the same.
@jenesis.signature says who that is, in one of two ways: an OpenPGP key somebody keeps, or a Sigstore
identity nobody keeps a key for at all. The key form comes first here; An identity instead of a
key is the other.
Naming a key, the declaration is the fingerprint of the key that signs a dependency's artifacts. Right after an
artifact is downloaded, Jenesis fetches the detached signature published beside it, forks a local gpgv to
check it, and compares the primary key fingerprint against your declarations.
/**
* @jenesis.pin org.junit.jupiter/junit-jupiter 5.11.3 SHA-256/ac7578ef...
* @jenesis.signature OpenPGP/FF6E2C001948C5F2F38B0CC385911F425EC61B51 org.junit.jupiter/* org.opentest4j/*
*/
The fingerprint comes first because one key normally signs many artifacts, and the tokens that follow use the
same grammar as every other @jenesis tag. A Maven token may end in /* to cover every artifact of one
groupId.
Nothing writes these lines. A fingerprint is obtained out of band, checked against the upstream project's
published KEYS, and added by hand - that judgement is the thing the mechanism rests on, and a tool that
filled the line in from whatever it downloaded would only be recording its own guess. Widening trust across a
whole group with /* is the same kind of decision. The declaration is a javadoc tag on module-info.java,
exactly as @jenesis.bom is; a pom.xml has no equivalent form, since a Maven project states its BOM imports
in <dependencyManagement> and has no place for a key.
One vetted list can serve many modules. A declaration naming a lone signature-<name>.properties reads
<algorithm>/<fingerprint>=<token>... lines from a local file instead, the shape @jenesis.bom already uses
for a local pin-<name>.properties:
/**
* @jenesis.signature signature-vendor.properties
*/
# build.jenesis/signature-vendor.properties
OpenPGP/FF6E2C001948C5F2F38B0CC385911F425EC61B51 = org.apiguardian/* org.junit.jupiter/* org.opentest4j/*
OpenPGP/BE685132AFD2740D9095F9040CC0B712FEE75827 = org.assertj/*
The fingerprint is the properties key rather than the coordinate, so the same coordinate can sit under two
keys through a rotation. The file is found in -Djenesis.project.signatures, which defaults to the
configuration folders, and its tokens expand by the same grammar as the tag.
A coordinate's POM is verified with its artifact, and must carry the same signer. POMs are read during resolution but never pinned, because some servers re-serialise them and a byte checksum would mismatch for no reason. A signature closes that gap directly, rather than leaving strict pinning to catch what a tampered POM adds. The cost is that a repository which re-serialises POMs invalidates their signatures, so resolve from one that serves the published bytes.
The line carries no version, and that is the point. One key signs every release it signs, so vetting a key
once covers every future release from that key, where a checksum covers exactly one file and every version bump
is a fresh, unvetted trust event. -Djenesis.dependency.signature chooses how much is checked and defaults to
none: declared verifies every coordinate a line covers, and strict additionally rejects one that no line
covers, or whose artifact or POM publishes no signature. Verification is opt-in, so a declaration alone does
not switch it on - set the property in jenesis.properties as you would any other project default, or pass it
on the runs that matter: a dependency update, and CI.
-Djenesis.print.signatures names each dependency that was checked with the key that signed it, and each one
no declaration covers, which is how you find out what to declare before moving from declared to strict.
A coordinate signed by some other key fails, naming both fingerprints: a signature can be cryptographically perfect and still be the wrong signer. A genuine key rotation is accepted by addition - list the new fingerprint alongside the old - so no window exists in which nothing verifies.
A coordinate that publishes no signature
Under strict a coordinate with no signature fails, and one such dependency otherwise costs you the whole
mode - which is the mode worth having, since the rest of the closure goes back to being taken on trust. An
unsigned/ declaration says that this one was looked at, and its value says what to do if a signature turns
up after all:
| Value | An artifact that publishes no signature |
|---|---|
unsigned/missing |
is accepted, and fails once a signature appears, naming the fingerprint seen |
unsigned/ignored |
is accepted, signed or not, and never looked at |
unsigned/missing is the one to reach for: an upstream that starts signing is discovered on the next build
rather than quietly left unverified, and the failure tells you which fingerprint to put in its place. Both
are ordinary declarations - as narrow as the tokens they name, and sitting in the diff where a reviewer reads
them.
A signing key that has since expired
Keys expire; the releases they signed do not change. An old artifact is commonly signed by a key that lapsed
years later, and where the keyservers publish no extended expiry there is nothing to update - so treating
every expired key as a failure would mean deleting the declaration, which verifies nothing at all.
-Djenesis.openpgp.expiry says what an expired signing key means:
| Value | An expired signing key |
|---|---|
ignored |
is accepted, whenever it signed |
signing |
is accepted for what it signed before it expired - the default |
current |
is always rejected, however old the signature |
The default reads the signature's own date against the key's expiry, both of which gpgv reports while it
verifies, so nothing extra is fetched or asked. A signature made after the key expired still fails under
signing, and so does one whose expiry gpgv does not report - an expiry that cannot be established is refused
rather than assumed. Revocation is never affected: a revoked key fails under every value, because revocation
says the key should not have been trusted, where expiry only says it is no longer current.
-Djenesis.print.signatures marks a coordinate accepted this way [EXPIRED] rather than [VERIFIED], with
the date it was signed and the date the key lapsed, so the ones resting on an unmaintained key can be found
and reviewed rather than passing silently.
.asc. The fingerprint hashes the public
key itself, while the expiry is an assertion in the key's self-signature, which is why extending a key's
expiry leaves its fingerprint - and so your @jenesis.signature line - untouched, and why the
expiry has to come from the keyring at the moment of verification.
Verification is a step of the dependency module rather than something wired beside it, so it rides along with
every resolution a build performs - a module's own closure, and equally the linter, formatter, alternative
compiler or test launcher a build module resolves for itself. Switching the property on re-runs only that step
rather than re-downloading anything, and the fetched .asc files are cached beside the jars they verify.
It is deliberately no part of pin: pin pins, recording the versions and checksums a resolution produced,
and it never adds, removes or reads a signature line. Keeping them apart is what stops the dangerous operation
from looking safer than it is: a pin refresh re-blesses whatever the repository serves today, and a signature
is the one check that still has something to say while the checksums are being rewritten.
When verification is switched on - and only then - it forks the gpgv command rather than linking a library,
so gpgv has to be installed and on the PATH of whatever machine runs it. A build that has not enabled it
needs none of this. That the verifier is a forked tool is not a convenience: a Java OpenPGP implementation would have to be resolved from a
repository, which is the very thing being verified, and a verifier you downloaded on trust verifies nothing.
The same reasoning keeps build.jenesis free of third-party libraries, and it is why the tool declines to
obtain one for you. -Djenesis.openpgp.command names a different binary when yours is not called gpgv: a plain name is looked
up on the PATH, and a value containing a path separator is used as a path, so a wrapper script can be named
without rewriting the PATH.
gpgv is the verify-only half of GnuPG. It reads a keyring file and nothing else: no home directory, no
agent, no trust database, no import step, and so none of the ambient state that makes "it works on my
machine" a signature problem. Jenesis builds that keyring itself, out of the fingerprints your
declarations name and nothing else - which is why NO_PUBKEY means exactly no line covers this signer
rather than your keyring is incomplete.
Where the keys come from
Each declared fingerprint is resolved through the repository registered under the algorithm the line names,
so OpenPGP/<hex> asks the repository registered as OpenPGP. By default that is an HKP client over
-Djenesis.openpgp.uri, which names key server roots, comma-separated and asked in order:
jenesis.openpgp.uri = https://keyserver.ubuntu.com/, https://keys.openpgp.org/
Both defaults are asked because neither is complete: a key published only on one is common enough to break a
build that names just the other. Ubuntu is asked first because keys.openpgp.org serves a key with its user
IDs stripped unless the owner has verified an address, and gpgv refuses a key that has none.
What is fetched is held in -Djenesis.openpgp.local, one file per fingerprint, and a populated folder with
an empty jenesis.openpgp.uri is the offline form - vendored keys, no network. Both settings take
OPENPGP_REPOSITORY_URI and OPENPGP_REPOSITORY_LOCAL from the environment, and a containerised build
forwards them the way it forwards the Maven and module repository settings. A key source that is not an HKP
server - a corporate key store, a git tree, a service of your own - is a different repository registered
under the same name, not a different URL in that list.
gpgv reports the primary key's fingerprint
for whatever it was given, so a server can withhold a key or serve junk - it cannot serve one that passes.
What is never fetched is the fingerprint: that is the judgement the mechanism rests on, and it
comes from the project's published location, reviewed once, then committed. With verification unset, a
build enforces the pin with no verifier, no keys and no key server at all.
Getting hold of a key
The build names the key it needs and stops. Find it somewhere the project controls: a fingerprint read from the signature itself, or from whatever a keyserver returns, tells you which key signed - never whether it should have. Best source first:
| Source | Look for |
|---|---|
| The project's own site, over HTTPS | a KEYS file; Apache publishes downloads.apache.org/<project>/KEYS |
| Its source repository | a committed KEYS file, or the release documentation |
| GitHub | https://github.com/<user>.gpg serves that account's public keys |
| A signed release tag | git verify-tag v1.2.3 names the signing key |
| Web Key Directory | gpg --locate-keys someone@example.org, served from the project's own domain |
A keyserver is fine for fetching bytes and worthless as evidence, since anyone can upload a key under any name. Inspect before importing, then compare against the source above:
gpg --list-packets some-artifact.jar.asc # which key signed this
gpg --show-keys --with-fingerprint key.asc # what a key file contains, without importing it
gpg --import key.asc # only once the fingerprint matches
gpg --recv-keys followed by gpg --fingerprint proves nothing: it reports the
fingerprint of whatever was just downloaded, which is the thing you set out to check. The comparison has to
be against a channel an attacker does not control.
An identity instead of a key
Everything above rests on a maintainer holding a private key for years, and on you obtaining its fingerprint through a channel an attacker does not control. Sigstore answers the same question without either. The signer authenticates to an identity provider, a certificate authority issues a certificate that is valid for ten minutes and names that identity, the signature is recorded in a public append-only log, and the private key is discarded. What you verify afterwards is an identity and a log entry rather than a key somebody kept.
For a coordinate whose repository publishes a .sigstore.json beside the artifact, the declaration names that
identity:
/**
* @jenesis.pin dev.sigstore/protobuf-specs 0.5.2 SHA-256/e2368fd2...
* @jenesis.signature Sigstore/github.com/sigstore/protobuf-specs dev.sigstore/*
*/
Read against the certificate that signed that release, the parts are:
Sigstore / github.com / sigstore / protobuf-specs
| | | `- repository
| | `- owner
| `- issuer token.actions.githubusercontent.com
`- the bundle beside the artifact carries the material; nothing is fetched to check it
accepts https://github.com/sigstore/protobuf-specs/.github/workflows/java-release.yml@refs/tags/release/java/v0.5.2
The path is a prefix, and it narrows a segment at a time. Sigstore/github.com/sigstore covers every
repository of one owner, the line above covers one repository, and a workflow file may follow to cover a
single workflow. A prefix ends at a / or an @, so a declaration for protobuf-spec never covers
protobuf-specs.
The tag a release was built from is never written. The identity above ends in
@refs/tags/release/java/v0.5.2, and that is the part that moves with every version. Stopping before it is
what lets one line cover every future release, the way a fingerprint does - the property that makes a
declaration worth writing once.
The host also names the issuer that must have authenticated the identity, and it is the host itself unless
-Djenesis.sigstore.issuers names another. Today it does so only for
github.com=token.actions.githubusercontent.com, GitHub being the provider whose identities are issued
elsewhere; GitLab and anything self-managed need no entry. A host is written without a scheme wherever it
appears, since an OpenID Connect issuer is an https URL and nothing else. The issuer is compared rather than
assumed: it is what stops a certificate from another provider carrying a github.com identity.
A coordinate signed by some other identity fails, naming both sides, exactly as a wrong key does. The signature can be perfect and the log entry genuine - a release built in a fork is both - and only the comparison against your declaration sees it.
Where a bundle is checked against
A bundle carries its own certificate and log entry, but something has to say which certificate authority and which log are the real ones. Jenesis carries that trust root as source: the published root of the public Sigstore instance, vendored into your project with the rest of the tool and reviewed in the same diff. A project that adds a declaration needs nothing else, and no build downloads a trust root.
-Djenesis.sigstore.uri names another, and is the only way another is read - for a private Sigstore instance,
or a root that has rotated since the one you vendored. A log that was added after your copy was published is
reported as unknown rather than accepted, and the message names that property as the way past it.
What this costs, and what it does not need
Verification happens in the same step and under the same -Djenesis.dependency.signature modes as a key:
declared checks every coordinate a line covers, strict additionally rejects one that no line covers or that
publishes nothing to check. Under strict a coordinate's POM must carry a bundle from the same identity,
closing the same gap the key form closes.
A coordinate covered only by an identity needs no gpg, no keyring and no key server: the check is JDK
cryptography in process, over a bundle the repository publishes. The certificate in that bundle is expired by
the time you read it, which is the design rather than a lapse - verification asks whether it was valid at the
moment the log recorded the signature, not whether it is valid today.
Both forms may cover one coordinate, and each is then verified against whatever that coordinate publishes, so
a project moving from one to the other declares both and neither is weakened. Which to use is decided by what
a repository publishes: a detached .asc is near-universal on Maven Central, while bundles are still the
exception, so an identity is an additional answer where one exists rather than a replacement.
The one build a pin cannot protect
A pinned project is easy to reason about. The pins sit in your own sources, so they were reviewed the way any other change is, and every later build enforces them: whatever the repository serves must hash to what the pin says, or the build fails. Trust the project and you trust its pins; trust the pins and you trust every download that follows - on every machine, for every contributor, with no keys, no gpg and no network beyond the bytes themselves.
That reasoning holds for every build except the one that writes the pins.
Initialising a project, or updating a dependency, is the moment when there is nothing committed to check
against. The resolver takes what the repository serves and the pin records it. Whatever arrives becomes the
definition of correct, and every later build then enforces it faithfully - including when what arrived was not
what the publisher released. -Djenesis.dependency.pin=ignore makes this explicit by dropping the existing
pins first, but the first pin on a new project is the same act with nothing to drop.
At that moment a checksum has nothing to say, because it is the thing being written. What can speak is the signature: the publisher's key, applied to those bytes, checked against a fingerprint you vetted once and committed. This is why verification runs during resolution rather than during the rewrite - the run that establishes a pin is exactly the run whose bytes nobody has vouched for yet.
And a key is cheap to keep, because it does not move with the version. One key signs every release a project makes until it is rotated, so a declaration written once keeps paying: a routine version bump costs nothing, and if a new version arrives signed by somebody else, that is precisely the thing you are told. A checksum covers exactly one file, so every bump is a fresh, unvetted trust event; a key covers the publisher.
So the strongest posture is not a stricter everyday build - an ordinary build already enforces the pins and needs no gpg at all. It is to make the pin-writing run the careful one, on a machine you trust and against a repository you trust:
java -Djenesis.dependency.pin=ignore \
-Djenesis.dependency.signature=strict \
build/jenesis/Make.java pin
Both flags are needed, and neither is a default. @jenesis.signature lines sitting in your sources verify
nothing on their own; jenesis.dependency.signature is none until something sets it, which is what keeps
every ordinary build free of gpg. The run that writes the pins is the one where you ask for it.
That machine then needs two things in place beforehand, and Jenesis can supply neither: a JDK, and a gpg on
the PATH that you trust. Bootstrapping either from the network would defeat the exercise - a verifier fetched
from the repository under verification proves nothing about it - so a hardened image is one where the JDK and
the OpenPGP tooling are already present and vetted, not one that assembles them on the way.
pin runs after a full build, so the resolution that feeds the rewrite is the verified one. strict refuses
any coordinate no key vouches for, which means the checksums that land in your sources are the ones whose
signatures were checked. Review the resulting diff, commit it, and the pins carry that verdict forward to
everyone who trusts your repository.
Getting there is the only fiddly part, and -Djenesis.print.signatures is what makes it tractable: under
declared it names every coordinate no declaration covers, which is exactly the list strict would refuse.
Work through that list once, and the run that writes your pins is one you can defend.
Where each one stops
Stating the limits plainly matters more than the guarantees:
- A checksum is trust-on-first-use without a signature beside it.
- A signature proves that the holder of a key asserted these bytes. It does not prove the artifact is benign, that it matches its published source, or that the key was not stolen.
- An SBOM describes what you shipped; it gates nothing.
- A vulnerability scan knows only what OSV has published, so it is silent on an advisory filed tomorrow and on one that was never filed.
- A licence check reads declarations, which can be wrong or absent.
- Isolation limits what code can reach, not whether it should be there.
- A declaration only covers what it names. A coordinate no line mentions is verified by nobody under
declared; onlystrictturns that silence into a failure. pinitself runs after a full build, because a dependency can be introduced by any step and the closure is only complete at the end, so a pin rewrite is not the moment anything is checked.
A defensible posture
Nothing here needs a build script, and the layers are independent, so adopt them in the order that pays:
- Pin everything, then build under
-Djenesis.dependency.pin=strictin CI, so no unverified coordinate can enter unnoticed. - Declare a key per upstream project and build with
-Djenesis.dependency.signature=declared, so the first acceptance of an artifact is a decision rather than a download. Move tostrictonce every external coordinate is covered. - Refresh deliberately.
-Djenesis.dependency.pin=ignorere-blesses whatever the repository serves today, so run it on a trusted machine, with-Djenesis.dependency.signature=strict, and review the diff - see The one build a pin cannot protect, which is the whole argument for why this run, rather than every run, is the one to harden. - Turn on the checks you will act on - a licence policy, a vulnerability threshold - and keep the SBOM you already get.
- Containerise the builds you do not trust.
What remains is the trust you extend deliberately: your JDK, the repository you resolve from, the keys you vetted, and the gpg that checks them. Naming that list is the useful outcome; a build whose trusted set cannot be written down has not been secured, only described.
The Jenesis signing key
Jenesis artifacts published to Maven Central are signed with this key. Declare its primary fingerprint -
not the signing subkey, which is what gpg --verify prints first:
OpenPGP/B4AC8CDC141AF0AE468D16921DA784CCB5C46DD5
/**
* @jenesis.signature OpenPGP/B4AC8CDC141AF0AE468D16921DA784CCB5C46DD5 build.jenesis/*
*/
The same key is published as /KEYS on this site, as a KEYS file in each Jenesis repository, and
by the maintainer's GitHub account at github.com/raphw.gpg. Import it, then
confirm the fingerprint matches the one above:
curl -O https://jenesis.build/KEYS
gpg --import KEYS
gpg --fingerprint B4AC8CDC141AF0AE468D16921DA784CCB5C46DD5
The block below carries two keys: the current signing key, RSA 4096 created on 7 November 2019, and the DSA key that preceded it, revoked on 8 November 2019 and included so the succession can be checked.
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
+ua1Dys=
=66HO
-----END PGP PUBLIC KEY BLOCK-----