Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Roll over self-updater key for 2025 #3048

Merged
merged 7 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions internal/updater/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Release signing and key rollover documentation

Audience: core maintainers

This document captures the necessary steps to perform regular (usually every 2nd year) release signing key rollover.

## Updating the gopass binary

The gopass self-updater is invoked when calling `gopass update`. It works only if the binary is writable by the user running the command. It is specifically not designed to update any gopass
packages installed by a package manager.

The updater first tries to ensure that it is supposed to update the binary (usually because it can write to the binary location) and then fetches the latest release from GitHub. If this ever causes trouble we could cache this info and proxy requests through gopass.pw.

If there is a new release it will fetch both `SHA256SUMS` and `SHA256SUMS.sig` assets from the latest release and verify the signature matches one of the built-in updater keys.

If the checksum file is verified we continue to fetch the actual binary archive and compare that against
the (verified) checksum file and replace the binary.

All of this is implemented by the files in this directory.

## Publishing assets for the updater during releases

When a new release is cut we rely on GoReleaser and GitHub Action workflows to update all necessary assets.
The configuration for those is spread across the repository and the GitHub Action configuration.

A new release is published by pushing a version tag (`v*`) to the repository. Once that happens the GHA workflow `autorelease.yml` is kicked off. It is configured in `.github/workflows/autorelease.yml` and through a number of injected environment variables from the GHA settings. Most importantly `GPG_PRIVATE_KEY` which contains the armored
GPG private part of the current release signing key and the respective passphrase in `PASSPHRASE`.

GoReleaser is controlled by `.goreleaser.yml` in the root of this repository. The relevant sections there are `checksum` to ensure a checksum file is generated and the `signs` section to sign the checksum file using the provided `GPG_FINGERPRINT` in the workflow.

## Managing keys and related assets

The relase signing key is set to expire every other year, so we need to follow a certain key rotation protocol to allow for a seamless key rotation.

* At T-6 Month we should notice that `TestGPGVerifyIn6Months` starts to fail.
* There is likely a loss obstrusive way to achieve that, but I'll leave it at that for now.
* We should then create an issue to track the key rollover (this should never happen in secret). The entire security posture isn't perfect but that's the best I can do with my resources. Help always appreciated.
* For the actual rollout we first need to generate a new key. That needs to be done by exactly one core maintainer with write access to the repo and the GHA secrets since only they can inject the new key, fingerprint and passphrase.
* To generate the key run: `gpg --expert --full-generate-key` and select `RSA and RSA`, `3072` (bits) and a validity of `2y`. Use `Gopass Release Signing Key YYYY` as the name, `GitHub Actions only` as the comment and `[email protected]` as the email.
* Note: If you're correctly following the 6 Months advance notice process, use the next year for the name.
* RSA isn't perfect but we used to have some compatability issues with non-RSA keys. Feel free to revisit this in the future. Keep the keep size to a reasonable value. Last time I checked 4096 did seem a bit excessive and with different algorithms these numbers will need to change as well. In doubt the `BSI TR-02102-1` should have a reasonable recommendation.
* Use a strong, random passphrase. Since you should never have to type it anywhere make it long, cryptic and stop worrying about it, e.g. `gopass pwgen 32`.
* If you are me, you should probably also save a copy of both parts of new key into your gopass maintainer password store. For convenience add the Key ID / Fingerprint into the secret so you don't have to import the key just to get the Key ID.
* Hint: `gpg --output 0xKEYID.pub --armor --export 0xKEYID` and `gpg --output 0xKEYID.private --armor --export-secret-key 0xKEYID`
* You should also sign the new key with the old key and possibly your personal key and push it to some keyservers.
* Use `gpg --yes -u 0xOLDKEYID --sign-key 0xKEYID` to sign.
* Now export the public key and inject it into the pubkeys slice in `verify.go`. Add a comment with the year and the key id.
* As usual send a PR and get this merged. Consider kicking off a new release, if that makes sense.
* STOP HERE. For a seamless key rollover we need to wait until most users had a chance to update to a version that has both the old and the new keys. So if possible wait a few months at least. Keep the GH issue open and assigned to track that process.
* After ~5 Months continue here.
* Regenerate the test signature for verify_test.go
* Create a file that contains `gopass-sign-test\n` and run `gpg -u 0xKEYID --armor --output /tmp/testdata.sig --detatch-sign testdata`. Use the correct KEYID (the one of the NEW key).
* Hint: Make sure the input only contains one line break, not two.
* Paste the content of /tmp/testdata.sig into the `testSignature` in `verify_test.go`. Make sure all tests pass.
* Navigate to https://github.com/gopasspw/gopass/settings/secrets/actions
* Paste the armored private part of the new key into the existing `GPG_PRIVATE_KEY` secret.
* Paste the corresponding passphrase into `PASSPHRASE`.
* At this point you should be able to safely delete the old public key from verify.go and kick off a new release.
* At the very end upload the new key to some keyservers: `gpg --send-keys 0xKEYID` and possibly `gpg --keyserver pgp.mit.edu --send-keys 0xKEYID`.
* In case you mess up during key generation you might need to start over and you don't want to have conflicting keys on a keyserver where you can't delete them.
150 changes: 58 additions & 92 deletions internal/updater/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,104 +6,60 @@ import (
"encoding/hex"
"fmt"
"strings"
"time"

"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/gopasspw/gopass/internal/hashsum"
"github.com/gopasspw/gopass/pkg/debug"
)

// To generate the private key use:
// ```
// gpg --expert --full-generate-key
// (1) RSA
// 3072
// 2y
// ```
// .
// To update see README.md.
var pubkeys = [][]byte{
// 2025 key - 0x67E6E8D2
[]byte(`-----BEGIN PGP PUBLIC KEY BLOCK-----

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
jxn7SFTOuQxBJAsmB7Q0aGs=
=HkIB
-----END PGP PUBLIC KEY BLOCK-----
`),
[]byte(`-----BEGIN PGP PUBLIC KEY BLOCK-----

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
4e8VuujUJ7JmdEbfFhsx7sk=
=XyXN
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
fT1TaQbilUs+PL7VNI6d2uAPwQ==
=bs0I
-----END PGP PUBLIC KEY BLOCK-----
`),
}
Expand All @@ -126,6 +82,14 @@ func (k *krLogger) Str() string {
}

func gpgVerify(data, sig []byte) (bool, error) {
return gpgVerifyAt(data, sig, nil)
}

func gpgVerifyAt(data, sig []byte, nowFn func() time.Time) (bool, error) {
if nowFn == nil {
nowFn = time.Now
}

var keyring openpgp.EntityList
for _, pubkey := range pubkeys {
k, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(pubkey))
Expand All @@ -139,10 +103,12 @@ func gpgVerify(data, sig []byte) (bool, error) {

debug.Log("Keyring: %q", &krLogger{keyring})

_, err := openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewReader(data), bytes.NewReader(sig), nil)
_, err := openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewReader(data), bytes.NewReader(sig), &packet.Config{
Time: nowFn,
})
if err != nil {
debug.Log("failed to validate detached GPG signature: %q", err)
debug.Log("data: %q", string(data))
debug.Log("data: %q, %s", string(data), hashsum.MD5Hex(string(data)))
debug.Log("sig: %q", string(sig))

return false, fmt.Errorf("failed to validated detached GPG signature: %w", err)
Expand Down
37 changes: 24 additions & 13 deletions internal/updater/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@ package updater

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// To generate use:
// `gpg -u 0x4B75BB5995892BE3 --armor --output testdata.sig --detach-sign testdata`.
// To update see README.md.
var testSignature = []byte(`
-----BEGIN PGP SIGNATURE-----

iQGzBAABCgAdFiEEwhyMrSlNNb9aO7sVs8WxoFYNhSIFAmPW2HQACgkQs8WxoFYN
hSLmNgv8CLlft+O7vTolDPM/kZNOlM3UvAzbeA+JkeMyl7snWHnmWgtggtZhMIbq
DIj1OjfW/JKiEqJZy2LCaXUxSshXJ2WHRfxTBvDprSQK5PHiVZwGmsJPXn1aOXSm
OUCPNhv0/wl729reQ7VrLNVM6zXwY91+77XePMsKXV90Vdc+RXucEtZULNeZlhvE
nnJ03ZHLeUpN61CJh6UhBP3dF1aFWiW4+oyONnNyxlC1QNh4oiwcP3iAe8m+gHWj
cQ1z8sTyJFl2l6Mk9cq6wmMwhzyrPgxdre+YDa1oWb/hmq8U3qFH6kkaoYS6b9x3
rEedoxQYh0N7B74IFSgjnKgtUkfQPRXFEUfbGpz03NVtwMnqg8IiCO5bMcbHiqDG
UezkZM1wpxVWCgoGcZaCv6c1gu4KAx8iVhovxekJcEVf+BUahMWBPwIjsmJg5kCU
L63+5ieE6wuYAcVPKZBUG3v9J6VjKpc3puv0sKUPw6swYOF973u3vfChs0oHjpiS
/+EULiNZ
=dS8F
iQGzBAABCAAdFiEEofrA/VCsqN4ejERxfqcKNWfm6NIFAmena44ACgkQfqcKNWfm
6NLPcgv+PeNs5OLB9y+kJhcWJXyGMyCCq4fj8ACA/mMkRxi+T9iP+51Di+GWyXvd
iMAHCBNbra2qn6nfiy7YJbgFDWZZVVUOXayqbgoGuxojO3n5AF9sK8Ieou7iYXpd
TXx0Zr8XFrhMMvzHVEDNqMtrRpmuwtixHA1PtGx/8Adv35gHRFZzW8xZ1ar5FVXk
Jk/bjo7h1bVf/jaakN9SDx8xc0D72LniPFNrEeOf8QTxSHZFaAOXuU9GsED8Cx1U
wQKBwveBSFKy17dGx03xcknqF/V3djApIgOIZ3MbaD50gpu3x9ltt9yOtkP9op0B
ANkUpIyrgcv39Trf44Z/rgj/bZz0UaagjMwA/RWtjnA6Kuw93BctVcfxuA2jC00g
GSny65MYtI6ynXnJ3xJVrIlNDawK/PjkS/HFWHFLKF7/K4ycL0KBVm/SETdIoGDK
gGTBIqBqDvHISE686mpH6rBRvyu7VOdbh6WTvztynHbdX/1cwyTKghnHNlw6gtIP
rp7LGb+c
=NeAM
-----END PGP SIGNATURE-----

`)
Expand All @@ -37,3 +37,14 @@ func TestGPGVerify(t *testing.T) {
require.NoError(t, err)
assert.True(t, ok)
}

// TestGPGVerifyIn6Months tests that the signature is still valid in 6 months.
// This is supposed to act as a canary so we don't forget to roll the key
// before it expires. See README.md for details.
func TestGPGVerifyIn6Months(t *testing.T) {
t.Parallel()

ok, err := gpgVerifyAt(testData, testSignature, func() time.Time { return time.Now().AddDate(0, 6, 0) })
require.NoError(t, err, "If TestGPGVerify succeeds but this test fails the self-updater key is about to expire. Please open an issue to update the key. Thank you.")
assert.True(t, ok)
}
Loading