Skip to content

Commit fa572cd

Browse files
crypto: signing builds with signify/minisign (ethereum#21798)
* internal/build: implement signify's signing func * Add signify to the ci utility * fix output file format * Add unit test for signify * holiman's + travis' feedback * internal/build: verify signify's output * crypto: move signify to common dir * use go-minisign to verify binaries * more holiman feedback * crypto, ci: support minisign output * only accept one-line trusted comments * configurable untrusted comments * code cleanup in tests * revert to use ed25519 from the stdlib * bug: fix for empty untrusted comments * write timestamp as comment if trusted comment isn't present * rename line checker to commentHasManyLines * crypto: added signify fuzzer (#6) * crypto: added signify fuzzer * stuff * crypto: updated signify fuzzer to fuzz comments * crypto: repro signify crashes * rebased fuzzer on build-signify branch * hide fuzzer behind gofuzz build flag * extract key data inside a single function * don't treat \r as a newline * travis: fix signing command line * do not use an external binary in tests * crypto: move signify to crypto/signify * travis: fix formatting issue * ci: fix linter build after package move Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de>
1 parent 429e714 commit fa572cd

File tree

7 files changed

+472
-32
lines changed

7 files changed

+472
-32
lines changed

.travis.yml

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,22 +67,22 @@ jobs:
6767
script:
6868
# Build for the primary platforms that Trusty can manage
6969
- go run build/ci.go install -dlgo
70-
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
70+
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds
7171
- go run build/ci.go install -dlgo -arch 386
72-
- go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
72+
- go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds
7373

7474
# Switch over GCC to cross compilation (breaks 386, hence why do it here only)
7575
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross
7676
- sudo ln -s /usr/include/asm-generic /usr/include/asm
7777

7878
- GOARM=5 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc
79-
- GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
79+
- GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds
8080
- GOARM=6 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc
81-
- GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
81+
- GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds
8282
- GOARM=7 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabihf-gcc
83-
- GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
83+
- GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds
8484
- go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc
85-
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
85+
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds
8686

8787
# This builder does the Linux Azure MIPS xgo uploads
8888
- stage: build
@@ -100,19 +100,19 @@ jobs:
100100
script:
101101
- go run build/ci.go xgo --alltools -- --targets=linux/mips --ldflags '-extldflags "-static"' -v
102102
- for bin in build/bin/*-linux-mips; do mv -f "${bin}" "${bin/-linux-mips/}"; done
103-
- go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
103+
- go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds
104104

105105
- go run build/ci.go xgo --alltools -- --targets=linux/mipsle --ldflags '-extldflags "-static"' -v
106106
- for bin in build/bin/*-linux-mipsle; do mv -f "${bin}" "${bin/-linux-mipsle/}"; done
107-
- go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
107+
- go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds
108108

109109
- go run build/ci.go xgo --alltools -- --targets=linux/mips64 --ldflags '-extldflags "-static"' -v
110110
- for bin in build/bin/*-linux-mips64; do mv -f "${bin}" "${bin/-linux-mips64/}"; done
111-
- go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
111+
- go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY signify LINUX_SIGNIFY_KEY -upload gethstore/builds
112112

113113
- go run build/ci.go xgo --alltools -- --targets=linux/mips64le --ldflags '-extldflags "-static"' -v
114114
- for bin in build/bin/*-linux-mips64le; do mv -f "${bin}" "${bin/-linux-mips64le/}"; done
115-
- go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
115+
- go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds
116116

117117
# This builder does the Android Maven and Azure uploads
118118
- stage: build
@@ -151,7 +151,7 @@ jobs:
151151

152152
- mkdir -p $GOPATH/src/github.com/ethereum
153153
- ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum
154-
- go run build/ci.go aar -signer ANDROID_SIGNING_KEY -deploy https://oss.sonatype.org -upload gethstore/builds
154+
- go run build/ci.go aar -signer ANDROID_SIGNING_KEY -signify ANDROID_SIGNIFY_KEY -deploy https://oss.sonatype.org -upload gethstore/builds
155155

156156
# This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads
157157
- stage: build
@@ -167,7 +167,7 @@ jobs:
167167
submodules: false # avoid cloning ethereum/tests
168168
script:
169169
- go run build/ci.go install -dlgo
170-
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds
170+
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify OSX_SIGNIFY_KEY -upload gethstore/builds
171171

172172
# Build the iOS framework and upload it to CocoaPods and Azure
173173
- gem uninstall cocoapods -a -x
@@ -182,7 +182,7 @@ jobs:
182182

183183
# Workaround for https://github.com/golang/go/issues/23749
184184
- export CGO_CFLAGS_ALLOW='-fmodules|-fblocks|-fobjc-arc'
185-
- go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds
185+
- go run build/ci.go xcode -signer IOS_SIGNING_KEY -signify IOS_SIGNIFY_KEY -deploy trunk -upload gethstore/builds
186186

187187
# These builders run the tests
188188
- stage: build

build/ci.go

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Available commands are:
2626
install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables
2727
test [ -coverage ] [ packages... ] -- runs the tests
2828
lint -- runs certain pre-selected linters
29-
archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artifacts
29+
archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -signify key-envvar ] [ -upload dest ] -- archives build artifacts
3030
importkeys -- imports signing keys from env
3131
debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package
3232
nsis -- creates a Windows NSIS installer
@@ -58,6 +58,7 @@ import (
5858
"time"
5959

6060
"github.com/cespare/cp"
61+
signifyPkg "github.com/ethereum/go-ethereum/crypto/signify"
6162
"github.com/ethereum/go-ethereum/internal/build"
6263
"github.com/ethereum/go-ethereum/params"
6364
)
@@ -396,11 +397,12 @@ func downloadLinter(cachedir string) string {
396397
// Release Packaging
397398
func doArchive(cmdline []string) {
398399
var (
399-
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
400-
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
401-
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
402-
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
403-
ext string
400+
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
401+
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
402+
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
403+
signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`)
404+
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
405+
ext string
404406
)
405407
flag.CommandLine.Parse(cmdline)
406408
switch *atype {
@@ -427,7 +429,7 @@ func doArchive(cmdline []string) {
427429
log.Fatal(err)
428430
}
429431
for _, archive := range []string{geth, alltools} {
430-
if err := archiveUpload(archive, *upload, *signer); err != nil {
432+
if err := archiveUpload(archive, *upload, *signer, *signify); err != nil {
431433
log.Fatal(err)
432434
}
433435
}
@@ -447,14 +449,20 @@ func archiveBasename(arch string, archiveVersion string) string {
447449
return platform + "-" + archiveVersion
448450
}
449451

450-
func archiveUpload(archive string, blobstore string, signer string) error {
452+
func archiveUpload(archive string, blobstore string, signer string, signify string) error {
451453
// If signing was requested, generate the signature files
452454
if signer != "" {
453455
key := getenvBase64(signer)
454456
if err := build.PGPSignFile(archive, archive+".asc", string(key)); err != nil {
455457
return err
456458
}
457459
}
460+
if signify != "" {
461+
key := getenvBase64(string(signify))
462+
if err := signifyPkg.SignifySignFile(archive, archive+".sig", string(key), "verify with geth.pub", fmt.Sprintf("%d", time.Now().UTC().Unix())); err != nil {
463+
return err
464+
}
465+
}
458466
// If uploading to Azure was requested, push the archive possibly with its signature
459467
if blobstore != "" {
460468
auth := build.AzureBlobstoreConfig{
@@ -470,6 +478,11 @@ func archiveUpload(archive string, blobstore string, signer string) error {
470478
return err
471479
}
472480
}
481+
if signify != "" {
482+
if err := build.AzureBlobstoreUpload(archive+".sig", filepath.Base(archive+".sig"), auth); err != nil {
483+
return err
484+
}
485+
}
473486
}
474487
return nil
475488
}
@@ -806,6 +819,7 @@ func doWindowsInstaller(cmdline []string) {
806819
var (
807820
arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging")
808821
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`)
822+
signify = flag.String("signify key", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`)
809823
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
810824
workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
811825
)
@@ -867,7 +881,7 @@ func doWindowsInstaller(cmdline []string) {
867881
filepath.Join(*workdir, "geth.nsi"),
868882
)
869883
// Sign and publish installer.
870-
if err := archiveUpload(installer, *upload, *signer); err != nil {
884+
if err := archiveUpload(installer, *upload, *signer, *signify); err != nil {
871885
log.Fatal(err)
872886
}
873887
}
@@ -876,10 +890,11 @@ func doWindowsInstaller(cmdline []string) {
876890

877891
func doAndroidArchive(cmdline []string) {
878892
var (
879-
local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`)
880-
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`)
881-
deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`)
882-
upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`)
893+
local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`)
894+
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`)
895+
signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. ANDROID_SIGNIFY_KEY)`)
896+
deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`)
897+
upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`)
883898
)
884899
flag.CommandLine.Parse(cmdline)
885900
env := build.Env()
@@ -908,7 +923,7 @@ func doAndroidArchive(cmdline []string) {
908923
archive := "geth-" + archiveBasename("android", params.ArchiveVersion(env.Commit)) + ".aar"
909924
os.Rename("geth.aar", archive)
910925

911-
if err := archiveUpload(archive, *upload, *signer); err != nil {
926+
if err := archiveUpload(archive, *upload, *signer, *signify); err != nil {
912927
log.Fatal(err)
913928
}
914929
// Sign and upload all the artifacts to Maven Central
@@ -1001,10 +1016,11 @@ func newMavenMetadata(env build.Environment) mavenMetadata {
10011016

10021017
func doXCodeFramework(cmdline []string) {
10031018
var (
1004-
local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`)
1005-
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`)
1006-
deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`)
1007-
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
1019+
local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`)
1020+
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`)
1021+
signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. IOS_SIGNIFY_KEY)`)
1022+
deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`)
1023+
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
10081024
)
10091025
flag.CommandLine.Parse(cmdline)
10101026
env := build.Env()
@@ -1032,7 +1048,7 @@ func doXCodeFramework(cmdline []string) {
10321048
maybeSkipArchive(env)
10331049

10341050
// Sign and upload the framework to Azure
1035-
if err := archiveUpload(archive+".tar.gz", *upload, *signer); err != nil {
1051+
if err := archiveUpload(archive+".tar.gz", *upload, *signer, *signify); err != nil {
10361052
log.Fatal(err)
10371053
}
10381054
// Prepare and upload a PodSpec to CocoaPods

crypto/signify/signify.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2020 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
// signFile reads the contents of an input file and signs it (in armored format)
18+
// with the key provided, placing the signature into the output file.
19+
20+
package signify
21+
22+
import (
23+
"encoding/base64"
24+
"errors"
25+
"fmt"
26+
"io/ioutil"
27+
"os"
28+
"strings"
29+
"time"
30+
31+
"crypto/ed25519"
32+
)
33+
34+
var (
35+
errInvalidKeyHeader = errors.New("Incorrect key header")
36+
errInvalidKeyLength = errors.New("invalid, key length != 104")
37+
)
38+
39+
func parsePrivateKey(key string) (ed25519.PrivateKey, []byte, []byte, error) {
40+
keydata, err := base64.StdEncoding.DecodeString(key)
41+
if err != nil {
42+
return nil, nil, nil, err
43+
}
44+
45+
if len(keydata) != 104 {
46+
return nil, nil, nil, errInvalidKeyLength
47+
}
48+
49+
if string(keydata[:2]) != "Ed" {
50+
return nil, nil, nil, errInvalidKeyHeader
51+
}
52+
53+
return ed25519.PrivateKey(keydata[40:]), keydata[:2], keydata[32:40], nil
54+
}
55+
56+
func commentHasManyLines(comment string) bool {
57+
firstLFIndex := strings.IndexByte(comment, 10)
58+
return (firstLFIndex >= 0 && firstLFIndex < len(comment)-1)
59+
}
60+
61+
// SignifySignFile creates a signature of the input file.
62+
func SignifySignFile(input string, output string, key string, unTrustedComment string, trustedComment string) error {
63+
in, err := os.Open(input)
64+
if err != nil {
65+
return err
66+
}
67+
defer in.Close()
68+
69+
out, err := os.Create(output)
70+
if err != nil {
71+
return err
72+
}
73+
defer out.Close()
74+
75+
skey, header, keyNum, err := parsePrivateKey(key)
76+
if err != nil {
77+
return err
78+
}
79+
80+
filedata, err := ioutil.ReadAll(in)
81+
if err != nil {
82+
return err
83+
}
84+
85+
rawSig := ed25519.Sign(skey, filedata)
86+
87+
var sigdata []byte
88+
sigdata = append(sigdata, header...)
89+
sigdata = append(sigdata, keyNum...)
90+
sigdata = append(sigdata, rawSig...)
91+
92+
// Check that the trusted comment fits in one line
93+
if commentHasManyLines(unTrustedComment) {
94+
return errors.New("untrusted comment must fit on a single line")
95+
}
96+
97+
if unTrustedComment == "" {
98+
unTrustedComment = "verify with " + input + ".pub"
99+
}
100+
out.WriteString(fmt.Sprintf("untrusted comment: %s\n%s\n", unTrustedComment, base64.StdEncoding.EncodeToString(sigdata)))
101+
102+
// Add the trusted comment if unavailable
103+
if trustedComment == "" {
104+
trustedComment = fmt.Sprintf("timestamp:%d", time.Now().Unix())
105+
}
106+
107+
// Check that the trusted comment fits in one line
108+
if commentHasManyLines(trustedComment) {
109+
return errors.New("trusted comment must fit on a single line")
110+
}
111+
112+
var sigAndComment []byte
113+
sigAndComment = append(sigAndComment, rawSig...)
114+
sigAndComment = append(sigAndComment, []byte(trustedComment)...)
115+
out.WriteString(fmt.Sprintf("trusted comment: %s\n%s\n", trustedComment, base64.StdEncoding.EncodeToString(ed25519.Sign(skey, sigAndComment))))
116+
117+
return nil
118+
}

0 commit comments

Comments
 (0)