Add SOURCE_DATE_EPOCH support for GPG signers

Both the external GPG signer (--faked-system-time) and internal Go
OpenPGP signer (signerConfig.Time) now honor SOURCE_DATE_EPOCH,
producing reproducible signatures alongside the plain Release file dates.

Adds system tests for both signer backends verifying byte-identical
Release, Release.gpg and InRelease across repeated publishes.

The signer tests (PublishRepo3[78]Test) are using an ed25519 key because
ed25519 signatures are deterministic by design. The Go openpgp library
uses a random nonce for DSA/ECDSA (see signature.go Sign calls using
config.Random() link below) so those signatures vary across runs
even with a fixed timestamp, making byte-identical verification impossible.

In addition to 49f342878a
Ref: https://github.com/aptly-dev/aptly/pull/1537
Ref: https://github.com/ProtonMail/go-crypto/blob/v1.4.0/openpgp/packet/signature.go#L945-L979
This commit is contained in:
Tim Foerster
2026-03-03 12:58:43 +00:00
committed by André Roth
parent 3c068febde
commit d616977904
9 changed files with 121 additions and 18 deletions
Binary file not shown.
Binary file not shown.
+14
View File
@@ -0,0 +1,14 @@
Loading packages...
Generating metadata files and linking package files...
Finalizing metadata files...
Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Local repo local-repo has been successfully published.
Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing.
Now you can add following line to apt sources:
deb http://your-server/ maverick main
deb-src http://your-server/ maverick main
Don't forget to add your GPG key to apt with apt-key.
You can also use `aptly serve` to publish your repositories over HTTP quickly.
+14
View File
@@ -0,0 +1,14 @@
Loading packages...
Generating metadata files and linking package files...
Finalizing metadata files...
openpgp: signing file 'Release'...
openpgp: clearsigning file 'Release'...
Local repo local-repo has been successfully published.
Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing.
Now you can add following line to apt sources:
deb http://your-server/ maverick main
deb-src http://your-server/ maverick main
Don't forget to add your GPG key to apt with apt-key.
You can also use `aptly serve` to publish your repositories over HTTP quickly.
+74
View File
@@ -1024,3 +1024,77 @@ class PublishRepo37Test(BaseTest):
# verify contents except of sums
self.check_file_contents(
'public/dists/maverick/Release', 'release', match_prepare=strip_processor)
class PublishRepo38Test(BaseTest):
"""
publish repo: SOURCE_DATE_EPOCH produces reproducible GPG signatures (external gpg)
"""
fixtureCmds = [
"aptly repo create local-repo",
"aptly repo add local-repo ${files}",
]
runCmd = "aptly publish repo -gpg-provider=gpg -gpg-key=21DBB89C16DB3E6D -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick local-repo"
gold_processor = BaseTest.expand_environ
outputMatchPrepare = staticmethod(lambda s: "\n".join(l for l in s.split("\n") if not l.startswith("gpg:")))
environmentOverride = {"SOURCE_DATE_EPOCH": "1750000000"}
def check(self):
super(PublishRepo38Test, self).check()
# verify Release file date matches SOURCE_DATE_EPOCH
release = self.read_file('public/dists/maverick/Release')
date_line = [l for l in release.split("\n") if l.startswith("Date:")]
if date_line[0] != "Date: Sun, 15 Jun 2025 15:06:40 UTC":
raise Exception("expected Date from SOURCE_DATE_EPOCH, got: %s" % date_line[0])
# save all release artifacts from first publish
first_release = self.read_file('public/dists/maverick/Release')
first_release_gpg = self.read_file('public/dists/maverick/Release.gpg')
first_inrelease = self.read_file('public/dists/maverick/InRelease')
# drop and republish with same SOURCE_DATE_EPOCH
self.run_cmd("aptly publish drop maverick")
self.run_cmd("aptly publish repo -gpg-provider=gpg -gpg-key=21DBB89C16DB3E6D -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick local-repo")
# verify byte-identical output for all release artifacts
self.check_equal(first_release, self.read_file('public/dists/maverick/Release'))
self.check_equal(first_release_gpg, self.read_file('public/dists/maverick/Release.gpg'))
self.check_equal(first_inrelease, self.read_file('public/dists/maverick/InRelease'))
class PublishRepo39Test(BaseTest):
"""
publish repo: SOURCE_DATE_EPOCH produces reproducible GPG signatures (internal signer, ed25519)
"""
fixtureCmds = [
"aptly repo create local-repo",
"aptly repo add local-repo ${files}",
]
runCmd = "aptly publish repo -gpg-key=BBF4E19434E91E4E -keyring=${files}/aptly-dual-binary.pub -secret-keyring=${files}/aptly3-binary.sec -distribution=maverick local-repo"
gold_processor = BaseTest.expand_environ
configOverride = {"gpgProvider": "internal"}
environmentOverride = {"SOURCE_DATE_EPOCH": "1750000000"}
def check(self):
super(PublishRepo39Test, self).check()
# verify Release file date matches SOURCE_DATE_EPOCH
release = self.read_file('public/dists/maverick/Release')
date_line = [l for l in release.split("\n") if l.startswith("Date:")]
if date_line[0] != "Date: Sun, 15 Jun 2025 15:06:40 UTC":
raise Exception("expected Date from SOURCE_DATE_EPOCH, got: %s" % date_line[0])
# save all release artifacts from first publish
first_release = self.read_file('public/dists/maverick/Release')
first_release_gpg = self.read_file('public/dists/maverick/Release.gpg')
first_inrelease = self.read_file('public/dists/maverick/InRelease')
# drop and republish with same SOURCE_DATE_EPOCH
self.run_cmd("aptly publish drop maverick")
self.run_cmd("aptly publish repo -gpg-key=BBF4E19434E91E4E -keyring=${files}/aptly-dual-binary.pub -secret-keyring=${files}/aptly3-binary.sec -distribution=maverick local-repo")
# verify byte-identical output for all release artifacts
self.check_equal(first_release, self.read_file('public/dists/maverick/Release'))
self.check_equal(first_release_gpg, self.read_file('public/dists/maverick/Release.gpg'))
self.check_equal(first_inrelease, self.read_file('public/dists/maverick/InRelease'))