mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-02-07 13:31:37 +02:00
Merge pull request #460 from magiconair/sign-pipeline
feat: add artifact signing pipeline
This commit is contained in:
commit
5e22ef1130
@ -144,6 +144,14 @@ type FPM struct {
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// Sign config
|
||||
type Sign struct {
|
||||
Cmd string `yaml:"cmd,omitempty"`
|
||||
Args []string `yaml:"args,omitempty"`
|
||||
Signature string `yaml:"signature,omitempty"`
|
||||
Artifacts string `yaml:"artifacts,omitempty"`
|
||||
}
|
||||
|
||||
// SnapcraftAppMetadata for the binaries that will be in the snap package
|
||||
type SnapcraftAppMetadata struct {
|
||||
Plugs []string
|
||||
@ -238,6 +246,7 @@ type Project struct {
|
||||
Artifactories []Artifactory `yaml:",omitempty"`
|
||||
Changelog Changelog `yaml:",omitempty"`
|
||||
Dist string `yaml:",omitempty"`
|
||||
Sign Sign `yaml:",omitempty"`
|
||||
|
||||
// this is a hack ¯\_(ツ)_/¯
|
||||
SingleBuild Build `yaml:"build,omitempty"`
|
||||
|
@ -37,6 +37,7 @@ type Context struct {
|
||||
Git GitInfo
|
||||
Binaries map[string]map[string][]Binary
|
||||
Artifacts []string
|
||||
Checksums []string
|
||||
Dockers []string
|
||||
ReleaseNotes string
|
||||
Version string
|
||||
@ -50,6 +51,7 @@ type Context struct {
|
||||
|
||||
var (
|
||||
artifactsLock sync.Mutex
|
||||
checksumsLock sync.Mutex
|
||||
dockersLock sync.Mutex
|
||||
binariesLock sync.Mutex
|
||||
)
|
||||
@ -63,6 +65,15 @@ func (ctx *Context) AddArtifact(file string) {
|
||||
log.WithField("artifact", file).Info("new release artifact")
|
||||
}
|
||||
|
||||
// AddChecksum adds a checksum file.
|
||||
func (ctx *Context) AddChecksum(file string) {
|
||||
checksumsLock.Lock()
|
||||
defer checksumsLock.Unlock()
|
||||
file = strings.TrimPrefix(file, ctx.Config.Dist+string(filepath.Separator))
|
||||
ctx.Checksums = append(ctx.Checksums, file)
|
||||
log.WithField("checksum", file).Info("new checksum file")
|
||||
}
|
||||
|
||||
// AddDocker adds a docker image to the docker images list
|
||||
func (ctx *Context) AddDocker(image string) {
|
||||
dockersLock.Lock()
|
||||
|
@ -15,6 +15,9 @@ func TestMultipleAdds(t *testing.T) {
|
||||
"dist/c",
|
||||
"dist/d",
|
||||
}
|
||||
var checksums = []string{
|
||||
"dist/a.sha256",
|
||||
}
|
||||
var dockerfiles = []string{
|
||||
"a/b:1.0.0",
|
||||
"c/d:2.0.0",
|
||||
@ -32,6 +35,14 @@ func TestMultipleAdds(t *testing.T) {
|
||||
})
|
||||
}
|
||||
assert.NoError(t, g.Wait())
|
||||
for _, c := range checksums {
|
||||
c := c
|
||||
g.Go(func() error {
|
||||
ctx.AddChecksum(c)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
assert.NoError(t, g.Wait())
|
||||
for _, d := range dockerfiles {
|
||||
d := d
|
||||
g.Go(func() error {
|
||||
@ -42,6 +53,8 @@ func TestMultipleAdds(t *testing.T) {
|
||||
assert.NoError(t, g.Wait())
|
||||
assert.Len(t, ctx.Artifacts, len(artifacts))
|
||||
assert.Contains(t, ctx.Artifacts, "a", "b", "c", "d")
|
||||
assert.Len(t, ctx.Checksums, len(checksums))
|
||||
assert.Contains(t, ctx.Checksums, "a.sha256")
|
||||
assert.Len(t, ctx.Dockers, len(dockerfiles))
|
||||
assert.Contains(t, ctx.Dockers, "a/b:1.0.0", "c/d:2.0.0", "e/f:3.0.0")
|
||||
}
|
||||
|
51
docs/075-sign.md
Normal file
51
docs/075-sign.md
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
title: Signing
|
||||
---
|
||||
|
||||
GoReleaser can sign some or all of the generated artifacts. Signing ensures
|
||||
that the artifacts have been generated by yourself and your users can verify
|
||||
that by comparing the generated signature with your public signing key.
|
||||
|
||||
Signing works in combination with checksum files and it is generally sufficient
|
||||
to sign the checksum files only.
|
||||
|
||||
The default is configured to create a detached signature for the checksum files
|
||||
with [GunPG](https://www.gnupg.org/) and your default key. To enable signing
|
||||
just add
|
||||
|
||||
```yaml
|
||||
# goreleaser.yml
|
||||
sign:
|
||||
artifacts: checksum
|
||||
```
|
||||
|
||||
To customize the signing pipeline you can use the following options:
|
||||
|
||||
```yml
|
||||
# .goreleaser.yml
|
||||
sign:
|
||||
# name of the signature file.
|
||||
# '${in}' is the path to the artifact that should be signed.
|
||||
#
|
||||
# signature: "${artifact}.sig"
|
||||
|
||||
# path to the signature command
|
||||
#
|
||||
# cmd: gpg
|
||||
|
||||
# command line arguments for the command
|
||||
#
|
||||
# to sign with a specific key use
|
||||
# args: ["-u", "<key id, fingerprint, email, ...>", "--output", "${signature}", "--detach-sign", "${artifact}"]
|
||||
#
|
||||
# args: ["--output", "${signature}", "--detach-sign", "${artifact}"]
|
||||
|
||||
|
||||
# which artifacts to sign
|
||||
#
|
||||
# checksum: only checksum file(s)
|
||||
# all: all artifacts
|
||||
# none: no signing
|
||||
#
|
||||
# artifacts: none
|
||||
```
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/goreleaser/goreleaser/pipeline/fpm"
|
||||
"github.com/goreleaser/goreleaser/pipeline/git"
|
||||
"github.com/goreleaser/goreleaser/pipeline/release"
|
||||
"github.com/goreleaser/goreleaser/pipeline/sign"
|
||||
"github.com/goreleaser/goreleaser/pipeline/snapcraft"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
@ -50,6 +51,7 @@ var pipes = []pipeline.Piper{
|
||||
fpm.Pipe{}, // archive via fpm (deb, rpm, etc)
|
||||
snapcraft.Pipe{}, // archive via snapcraft (snap)
|
||||
checksums.Pipe{}, // checksums of the files
|
||||
sign.Pipe{}, // sign artifacts
|
||||
docker.Pipe{}, // create and push docker images
|
||||
artifactory.Pipe{}, // push to artifactory
|
||||
release.Pipe{}, // release to github
|
||||
|
@ -39,6 +39,7 @@ func (Pipe) Run(ctx *context.Context) (err error) {
|
||||
log.WithError(err).Errorf("failed to close %s", file.Name())
|
||||
}
|
||||
ctx.AddArtifact(file.Name())
|
||||
ctx.AddChecksum(file.Name())
|
||||
}()
|
||||
var g errgroup.Group
|
||||
for _, artifact := range ctx.Artifacts {
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/goreleaser/goreleaser/pipeline/docker"
|
||||
"github.com/goreleaser/goreleaser/pipeline/fpm"
|
||||
"github.com/goreleaser/goreleaser/pipeline/release"
|
||||
"github.com/goreleaser/goreleaser/pipeline/sign"
|
||||
"github.com/goreleaser/goreleaser/pipeline/snapshot"
|
||||
)
|
||||
|
||||
@ -31,6 +32,7 @@ var defaulters = []pipeline.Defaulter{
|
||||
build.Pipe{},
|
||||
fpm.Pipe{},
|
||||
checksums.Pipe{},
|
||||
sign.Pipe{},
|
||||
docker.Pipe{},
|
||||
artifactory.Pipe{},
|
||||
brew.Pipe{},
|
||||
|
97
pipeline/sign/sign.go
Normal file
97
pipeline/sign/sign.go
Normal file
@ -0,0 +1,97 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/goreleaser/goreleaser/pipeline"
|
||||
)
|
||||
|
||||
// Pipe for artifact signing.
|
||||
type Pipe struct{}
|
||||
|
||||
func (Pipe) String() string {
|
||||
return "signing artifacts"
|
||||
}
|
||||
|
||||
// Default sets the Pipes defaults.
|
||||
func (Pipe) Default(ctx *context.Context) error {
|
||||
cfg := &ctx.Config.Sign
|
||||
if cfg.Cmd == "" {
|
||||
cfg.Cmd = "gpg"
|
||||
}
|
||||
if cfg.Signature == "" {
|
||||
cfg.Signature = "${artifact}.sig"
|
||||
}
|
||||
if len(cfg.Args) == 0 {
|
||||
cfg.Args = []string{"--output", "$signature", "--detach-sig", "$artifact"}
|
||||
}
|
||||
if cfg.Artifacts == "" {
|
||||
cfg.Artifacts = "none"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run executes the Pipe.
|
||||
func (Pipe) Run(ctx *context.Context) error {
|
||||
switch ctx.Config.Sign.Artifacts {
|
||||
case "checksum":
|
||||
return sign(ctx, ctx.Checksums)
|
||||
case "all":
|
||||
return sign(ctx, ctx.Artifacts)
|
||||
case "none":
|
||||
return pipeline.Skip("artifact signing disabled")
|
||||
default:
|
||||
return fmt.Errorf("invalid list of artifacts to sign: %s", ctx.Config.Sign.Artifacts)
|
||||
}
|
||||
}
|
||||
|
||||
func sign(ctx *context.Context, artifacts []string) error {
|
||||
var sigs []string
|
||||
for _, a := range artifacts {
|
||||
sig, err := signone(ctx, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sigs = append(sigs, sig)
|
||||
}
|
||||
for _, sig := range sigs {
|
||||
ctx.AddArtifact(sig)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func signone(ctx *context.Context, artifact string) (string, error) {
|
||||
cfg := ctx.Config.Sign
|
||||
|
||||
artifact = filepath.Join(ctx.Config.Dist, artifact)
|
||||
env := map[string]string{
|
||||
"artifact": artifact,
|
||||
}
|
||||
env["signature"] = expand(cfg.Signature, env)
|
||||
|
||||
var args []string
|
||||
for _, a := range cfg.Args {
|
||||
args = append(args, expand(a, env))
|
||||
}
|
||||
|
||||
// The GoASTScanner flags this as a security risk.
|
||||
// However, this works as intended. The nosec annotation
|
||||
// tells the scanner to ignore this.
|
||||
// #nosec
|
||||
cmd := exec.Command(cfg.Cmd, args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("sign: %s failed with %q", cfg.Cmd, string(output))
|
||||
}
|
||||
return env["signature"], nil
|
||||
}
|
||||
|
||||
func expand(s string, env map[string]string) string {
|
||||
return os.Expand(s, func(key string) string {
|
||||
return env[key]
|
||||
})
|
||||
}
|
165
pipeline/sign/sign_test.go
Normal file
165
pipeline/sign/sign_test.go
Normal file
@ -0,0 +1,165 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDescription(t *testing.T) {
|
||||
assert.NotEmpty(t, Pipe{}.String())
|
||||
}
|
||||
|
||||
func TestSignDefault(t *testing.T) {
|
||||
ctx := &context.Context{}
|
||||
Pipe{}.Default(ctx)
|
||||
assert.Equal(t, ctx.Config.Sign.Cmd, "gpg")
|
||||
assert.Equal(t, ctx.Config.Sign.Signature, "${artifact}.sig")
|
||||
assert.Equal(t, ctx.Config.Sign.Args, []string{"--output", "$signature", "--detach-sig", "$artifact"})
|
||||
assert.Equal(t, ctx.Config.Sign.Artifacts, "none")
|
||||
}
|
||||
|
||||
func TestSignDisabled(t *testing.T) {
|
||||
ctx := &context.Context{}
|
||||
ctx.Config.Sign.Artifacts = "none"
|
||||
err := Pipe{}.Run(ctx)
|
||||
assert.EqualError(t, err, "artifact signing disabled")
|
||||
}
|
||||
|
||||
func TestSignInvalidArtifacts(t *testing.T) {
|
||||
ctx := &context.Context{}
|
||||
ctx.Config.Sign.Artifacts = "foo"
|
||||
err := Pipe{}.Run(ctx)
|
||||
assert.EqualError(t, err, "invalid list of artifacts to sign: foo")
|
||||
}
|
||||
|
||||
func TestSignArtifacts(t *testing.T) {
|
||||
// fix permission on keyring dir to suppress warning about insecure permissions
|
||||
if err := os.Chmod(keyring, 0700); err != nil {
|
||||
t.Fatal("Chmod: ", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
desc string
|
||||
ctx *context.Context
|
||||
signatures []string
|
||||
}{
|
||||
{
|
||||
desc: "sign all artifacts",
|
||||
ctx: &context.Context{
|
||||
Config: config.Project{
|
||||
Sign: config.Sign{Artifacts: "all"},
|
||||
},
|
||||
Artifacts: []string{"artifact1", "artifact2", "checksum"},
|
||||
Checksums: []string{"checksum"},
|
||||
},
|
||||
signatures: []string{"artifact1.sig", "artifact2.sig", "checksum.sig"},
|
||||
},
|
||||
{
|
||||
desc: "sign only checksums",
|
||||
ctx: &context.Context{
|
||||
Config: config.Project{
|
||||
Sign: config.Sign{Artifacts: "checksum"},
|
||||
},
|
||||
Artifacts: []string{"artifact1", "artifact2", "checksum"},
|
||||
Checksums: []string{"checksum"},
|
||||
},
|
||||
signatures: []string{"checksum.sig"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
testSign(t, tt.ctx, tt.signatures)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const keyring = "testdata/gnupg"
|
||||
const user = "nopass"
|
||||
|
||||
func testSign(t *testing.T, ctx *context.Context, signatures []string) {
|
||||
// create temp dir for file and signature
|
||||
tmpdir, err := ioutil.TempDir("", "goreleaser")
|
||||
if err != nil {
|
||||
t.Fatal("TempDir: ", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
ctx.Config.Dist = tmpdir
|
||||
|
||||
// create some fake artifacts
|
||||
artifacts := ctx.Artifacts
|
||||
for _, f := range artifacts {
|
||||
file := filepath.Join(tmpdir, f)
|
||||
if err2 := ioutil.WriteFile(file, []byte("foo"), 0644); err2 != nil {
|
||||
t.Fatal("WriteFile: ", err2)
|
||||
}
|
||||
}
|
||||
|
||||
// configure the pipeline
|
||||
// make sure we are using the test keyring
|
||||
err = Pipe{}.Default(ctx)
|
||||
if err != nil {
|
||||
t.Fatal("Default: ", err)
|
||||
}
|
||||
ctx.Config.Sign.Args = append([]string{"--homedir", keyring}, ctx.Config.Sign.Args...)
|
||||
|
||||
// run the pipeline
|
||||
err = Pipe{}.Run(ctx)
|
||||
if err != nil {
|
||||
t.Fatal("Run: ", err)
|
||||
}
|
||||
|
||||
// verify that only the artifacts and the signatures are in the dist dir
|
||||
files, err := ioutil.ReadDir(tmpdir)
|
||||
if err != nil {
|
||||
t.Fatal("ReadDir: ", err)
|
||||
}
|
||||
gotFiles := []string{}
|
||||
for _, f := range files {
|
||||
gotFiles = append(gotFiles, f.Name())
|
||||
}
|
||||
|
||||
wantFiles := append(artifacts, signatures...)
|
||||
sort.Strings(wantFiles)
|
||||
|
||||
assert.Equal(t, wantFiles, gotFiles)
|
||||
|
||||
// verify the signatures
|
||||
for _, sig := range signatures {
|
||||
verifySignature(t, ctx, sig)
|
||||
}
|
||||
|
||||
// check signature is an artifact
|
||||
assert.Equal(t, ctx.Artifacts, append(artifacts, signatures...))
|
||||
}
|
||||
|
||||
func verifySignature(t *testing.T, ctx *context.Context, sig string) {
|
||||
artifact := sig[:len(sig)-len(".sig")]
|
||||
|
||||
// verify signature was made with key for usesr 'nopass'
|
||||
cmd := exec.Command("gpg", "--homedir", keyring, "--verify", filepath.Join(ctx.Config.Dist, sig), filepath.Join(ctx.Config.Dist, artifact))
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Log(string(out))
|
||||
t.Fatal("verify: ", err)
|
||||
}
|
||||
|
||||
// check if the signature matches the user we expect to do this properly we
|
||||
// might need to have either separate keyrings or export the key from the
|
||||
// keyring before we do the verification. For now we punt and look in the
|
||||
// output.
|
||||
if !bytes.Contains(out, []byte(user)) {
|
||||
t.Fatalf("signature is not from %s", user)
|
||||
}
|
||||
}
|
1
pipeline/sign/testdata/.gitignore
vendored
Normal file
1
pipeline/sign/testdata/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
S-*
|
80
pipeline/sign/testdata/README.md
vendored
Normal file
80
pipeline/sign/testdata/README.md
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
# Creating test keys for GnuPG
|
||||
|
||||
The unit tests needs a test key to work with. I have tried to create a test keyring
|
||||
on the fly and while that worked I was not able to successfully sign with that.
|
||||
gpg would bail with an ioctl error which I didn't track down since using a static
|
||||
key works.
|
||||
|
||||
This uses the `--homedir .` option to create the test keys so that we do not touch
|
||||
the local keyring file.
|
||||
|
||||
1. Create signing keys
|
||||
|
||||
cd $GOPATH/src/github.com/goreleaser/goreleaser/pipeline/sign/testdata/gnupg
|
||||
gpg --homedir . --quick-generate-key --batch --passphrase '' nopass default default 10y
|
||||
|
||||
1. Check that the key exists
|
||||
|
||||
## $ gpg --homedir . --list-keys
|
||||
|
||||
pub rsa2048 2017-12-13 [SC][expires: 2027-12-11]
|
||||
FB6BEDFCECE1761EDD68BF32EF2D274B0EDAAE12
|
||||
uid [ultimate] nopass
|
||||
sub rsa2048 2017-12-13 [E]
|
||||
|
||||
1) Check that signing works
|
||||
|
||||
# create a test file
|
||||
|
||||
echo "bar" > foo
|
||||
|
||||
# sign and verfiy
|
||||
|
||||
gpg --homedir . --detach-sign foo
|
||||
gpg --homedir . --verify foo.sig foo
|
||||
|
||||
gpg: Signature made Wed Dec 13 22:02:49 2017 CET
|
||||
gpg: using RSA key FB6BEDFCECE1761EDD68BF32EF2D274B0EDAAE12
|
||||
gpg: Good signature from "nopass" [ultimate]
|
||||
|
||||
# cleanup
|
||||
|
||||
rm foo foo.sig
|
||||
|
||||
1) Make sure you have keyrings for both gpg1 and gpg2
|
||||
|
||||
travis-ci.org runs on an old Ubuntu installation which
|
||||
has gpg 1.4 installed. We need to provide keyrings that
|
||||
have the same keys and users for both formats.
|
||||
|
||||
This demonstrates the conversion from gpg2 to gpg1
|
||||
format but should work the same the other way around.
|
||||
|
||||
# get gpg version
|
||||
|
||||
gpg --version
|
||||
gpg (GnuPG) 2.2.3
|
||||
...
|
||||
|
||||
# install gpg1
|
||||
|
||||
brew install gpg1
|
||||
|
||||
# brew install gpg2 # if you have gpg1 installed
|
||||
|
||||
# migrate the keys from gpg2 to gpg1
|
||||
|
||||
gpg --homedir . --export nopass | gpg1 --homedir . --import
|
||||
gpg --homedir . --export-secret-key nopass | gpg1 --homedir . --import
|
||||
|
||||
# check keys are the same
|
||||
|
||||
gpg --homedir . --list-keys --keyid-format LONG
|
||||
gpg1 --homedir . --list-keys --keyid-format LONG
|
||||
|
||||
gpg --homedir . --list-secret-keys --keyid-format LONG
|
||||
gpg1 --homedir . --list-secret-keys --keyid-format LONG
|
||||
|
||||
```
|
||||
|
||||
```
|
0
pipeline/sign/testdata/gnupg/.gpg-v21-migrated
vendored
Normal file
0
pipeline/sign/testdata/gnupg/.gpg-v21-migrated
vendored
Normal file
32
pipeline/sign/testdata/gnupg/openpgp-revocs.d/23E7505EC0A490C582CB9B27F1D733BF0B343347.rev
vendored
Normal file
32
pipeline/sign/testdata/gnupg/openpgp-revocs.d/23E7505EC0A490C582CB9B27F1D733BF0B343347.rev
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
This is a revocation certificate for the OpenPGP key:
|
||||
|
||||
pub rsa2048 2017-12-13 [SC] [expires: 2027-12-11]
|
||||
23E7505EC0A490C582CB9B27F1D733BF0B343347
|
||||
uid nopass
|
||||
|
||||
A revocation certificate is a kind of "kill switch" to publicly
|
||||
declare that a key shall not anymore be used. It is not possible
|
||||
to retract such a revocation certificate once it has been published.
|
||||
|
||||
Use it to revoke this key in case of a compromise or loss of
|
||||
the secret key. However, if the secret key is still accessible,
|
||||
it is better to generate a new revocation certificate and give
|
||||
a reason for the revocation. For details see the description of
|
||||
of the gpg command "--generate-revocation" in the GnuPG manual.
|
||||
|
||||
To avoid an accidental use of this file, a colon has been inserted
|
||||
before the 5 dashes below. Remove this colon with a text editor
|
||||
before importing and publishing this revocation certificate.
|
||||
|
||||
:-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: This is a revocation certificate
|
||||
|
||||
iQE2BCABCAAgFiEEI+dQXsCkkMWCy5sn8dczvws0M0cFAloxnpUCHQAACgkQ8dcz
|
||||
vws0M0dbdwf+KevSTLl688bOMMKcexoUra4porA/JJvtrkQAdHWqspqf+lQ+1rK0
|
||||
Y4YNHkXixv6+R0aoECSrnx4ehk9nLH7hx5423DXEvmPkF70rWkF0eGeG8gVrUz2O
|
||||
YWobOMldWPk6QPZ6rV5c5PdQSCx8+WHVXu/ym7u70fbmJV4IHuFlKiFUXsGk2PYj
|
||||
k38ssFeqGo1bNZlIfuCggOurpfSXhKAsLRHjJVQe7ZowmloPiwHFfMwtuiBWpsZ/
|
||||
3niEy3A5mdBC+ebtEL1KyaqjV6IN58YT3z0aBnDjA/qeweeT2jKjEWqHAPsJaJke
|
||||
AyC0e8t71lnH4RZyKyfsBveKFfC4KN9SUA==
|
||||
=DnZE
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
BIN
pipeline/sign/testdata/gnupg/private-keys-v1.d/6B365DDB6E25317FED9A739DC350E033855A833A.key
vendored
Normal file
BIN
pipeline/sign/testdata/gnupg/private-keys-v1.d/6B365DDB6E25317FED9A739DC350E033855A833A.key
vendored
Normal file
Binary file not shown.
BIN
pipeline/sign/testdata/gnupg/private-keys-v1.d/939A5E777E4F7DFF2FA87806D0155052B5FC8F56.key
vendored
Normal file
BIN
pipeline/sign/testdata/gnupg/private-keys-v1.d/939A5E777E4F7DFF2FA87806D0155052B5FC8F56.key
vendored
Normal file
Binary file not shown.
BIN
pipeline/sign/testdata/gnupg/pubring.gpg
vendored
Normal file
BIN
pipeline/sign/testdata/gnupg/pubring.gpg
vendored
Normal file
Binary file not shown.
BIN
pipeline/sign/testdata/gnupg/pubring.kbx
vendored
Normal file
BIN
pipeline/sign/testdata/gnupg/pubring.kbx
vendored
Normal file
Binary file not shown.
BIN
pipeline/sign/testdata/gnupg/secring.gpg
vendored
Normal file
BIN
pipeline/sign/testdata/gnupg/secring.gpg
vendored
Normal file
Binary file not shown.
BIN
pipeline/sign/testdata/gnupg/trustdb.gpg
vendored
Normal file
BIN
pipeline/sign/testdata/gnupg/trustdb.gpg
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user