mirror of https://github.com/ethereum/go-ethereum
crypto: signing builds with signify/minisign (#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>pull/21913/head
parent
429e7141f2
commit
fa572cd297
@ -0,0 +1,118 @@ |
|||||||
|
// Copyright 2020 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// signFile reads the contents of an input file and signs it (in armored format)
|
||||||
|
// with the key provided, placing the signature into the output file.
|
||||||
|
|
||||||
|
package signify |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/base64" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"crypto/ed25519" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
errInvalidKeyHeader = errors.New("Incorrect key header") |
||||||
|
errInvalidKeyLength = errors.New("invalid, key length != 104") |
||||||
|
) |
||||||
|
|
||||||
|
func parsePrivateKey(key string) (ed25519.PrivateKey, []byte, []byte, error) { |
||||||
|
keydata, err := base64.StdEncoding.DecodeString(key) |
||||||
|
if err != nil { |
||||||
|
return nil, nil, nil, err |
||||||
|
} |
||||||
|
|
||||||
|
if len(keydata) != 104 { |
||||||
|
return nil, nil, nil, errInvalidKeyLength |
||||||
|
} |
||||||
|
|
||||||
|
if string(keydata[:2]) != "Ed" { |
||||||
|
return nil, nil, nil, errInvalidKeyHeader |
||||||
|
} |
||||||
|
|
||||||
|
return ed25519.PrivateKey(keydata[40:]), keydata[:2], keydata[32:40], nil |
||||||
|
} |
||||||
|
|
||||||
|
func commentHasManyLines(comment string) bool { |
||||||
|
firstLFIndex := strings.IndexByte(comment, 10) |
||||||
|
return (firstLFIndex >= 0 && firstLFIndex < len(comment)-1) |
||||||
|
} |
||||||
|
|
||||||
|
// SignifySignFile creates a signature of the input file.
|
||||||
|
func SignifySignFile(input string, output string, key string, unTrustedComment string, trustedComment string) error { |
||||||
|
in, err := os.Open(input) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer in.Close() |
||||||
|
|
||||||
|
out, err := os.Create(output) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer out.Close() |
||||||
|
|
||||||
|
skey, header, keyNum, err := parsePrivateKey(key) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
filedata, err := ioutil.ReadAll(in) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
rawSig := ed25519.Sign(skey, filedata) |
||||||
|
|
||||||
|
var sigdata []byte |
||||||
|
sigdata = append(sigdata, header...) |
||||||
|
sigdata = append(sigdata, keyNum...) |
||||||
|
sigdata = append(sigdata, rawSig...) |
||||||
|
|
||||||
|
// Check that the trusted comment fits in one line
|
||||||
|
if commentHasManyLines(unTrustedComment) { |
||||||
|
return errors.New("untrusted comment must fit on a single line") |
||||||
|
} |
||||||
|
|
||||||
|
if unTrustedComment == "" { |
||||||
|
unTrustedComment = "verify with " + input + ".pub" |
||||||
|
} |
||||||
|
out.WriteString(fmt.Sprintf("untrusted comment: %s\n%s\n", unTrustedComment, base64.StdEncoding.EncodeToString(sigdata))) |
||||||
|
|
||||||
|
// Add the trusted comment if unavailable
|
||||||
|
if trustedComment == "" { |
||||||
|
trustedComment = fmt.Sprintf("timestamp:%d", time.Now().Unix()) |
||||||
|
} |
||||||
|
|
||||||
|
// Check that the trusted comment fits in one line
|
||||||
|
if commentHasManyLines(trustedComment) { |
||||||
|
return errors.New("trusted comment must fit on a single line") |
||||||
|
} |
||||||
|
|
||||||
|
var sigAndComment []byte |
||||||
|
sigAndComment = append(sigAndComment, rawSig...) |
||||||
|
sigAndComment = append(sigAndComment, []byte(trustedComment)...) |
||||||
|
out.WriteString(fmt.Sprintf("trusted comment: %s\n%s\n", trustedComment, base64.StdEncoding.EncodeToString(ed25519.Sign(skey, sigAndComment)))) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,148 @@ |
|||||||
|
// Copyright 2020 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// +build gofuzz
|
||||||
|
|
||||||
|
package signify |
||||||
|
|
||||||
|
import ( |
||||||
|
"bufio" |
||||||
|
"fmt" |
||||||
|
"io/ioutil" |
||||||
|
"log" |
||||||
|
"os" |
||||||
|
"os/exec" |
||||||
|
"runtime" |
||||||
|
|
||||||
|
fuzz "github.com/google/gofuzz" |
||||||
|
"github.com/jedisct1/go-minisign" |
||||||
|
) |
||||||
|
|
||||||
|
func Fuzz(data []byte) int { |
||||||
|
if len(data) < 32 { |
||||||
|
return -1 |
||||||
|
} |
||||||
|
tmpFile, err := ioutil.TempFile("", "") |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
defer os.Remove(tmpFile.Name()) |
||||||
|
defer tmpFile.Close() |
||||||
|
|
||||||
|
testSecKey, testPubKey := createKeyPair() |
||||||
|
// Create message
|
||||||
|
tmpFile.Write(data) |
||||||
|
if err = tmpFile.Close(); err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
// Fuzz comments
|
||||||
|
var untrustedComment string |
||||||
|
var trustedComment string |
||||||
|
f := fuzz.NewFromGoFuzz(data) |
||||||
|
f.Fuzz(&untrustedComment) |
||||||
|
f.Fuzz(&trustedComment) |
||||||
|
fmt.Printf("untrusted: %v\n", untrustedComment) |
||||||
|
fmt.Printf("trusted: %v\n", trustedComment) |
||||||
|
|
||||||
|
err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, untrustedComment, trustedComment) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
defer os.Remove(tmpFile.Name() + ".sig") |
||||||
|
|
||||||
|
signify := "signify" |
||||||
|
path := os.Getenv("SIGNIFY") |
||||||
|
if path != "" { |
||||||
|
signify = path |
||||||
|
} |
||||||
|
|
||||||
|
_, err := exec.LookPath(signify) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
// Write the public key into the file to pass it as
|
||||||
|
// an argument to signify-openbsd
|
||||||
|
pubKeyFile, err := ioutil.TempFile("", "") |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
defer os.Remove(pubKeyFile.Name()) |
||||||
|
defer pubKeyFile.Close() |
||||||
|
pubKeyFile.WriteString("untrusted comment: signify public key\n") |
||||||
|
pubKeyFile.WriteString(testPubKey) |
||||||
|
pubKeyFile.WriteString("\n") |
||||||
|
|
||||||
|
cmd := exec.Command(signify, "-V", "-p", pubKeyFile.Name(), "-x", tmpFile.Name()+".sig", "-m", tmpFile.Name()) |
||||||
|
if output, err := cmd.CombinedOutput(); err != nil { |
||||||
|
panic(fmt.Sprintf("could not verify the file: %v, output: \n%s", err, output)) |
||||||
|
} |
||||||
|
|
||||||
|
// Verify the signature using a golang library
|
||||||
|
sig, err := minisign.NewSignatureFromFile(tmpFile.Name() + ".sig") |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
pKey, err := minisign.NewPublicKey(testPubKey) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
valid, err := pKey.VerifyFromFile(tmpFile.Name(), sig) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
if !valid { |
||||||
|
panic("invalid signature") |
||||||
|
} |
||||||
|
return 1 |
||||||
|
} |
||||||
|
|
||||||
|
func getKey(fileS string) (string, error) { |
||||||
|
file, err := os.Open(fileS) |
||||||
|
if err != nil { |
||||||
|
log.Fatal(err) |
||||||
|
} |
||||||
|
defer file.Close() |
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file) |
||||||
|
// Discard the first line
|
||||||
|
scanner.Scan() |
||||||
|
scanner.Scan() |
||||||
|
return scanner.Text(), scanner.Err() |
||||||
|
} |
||||||
|
|
||||||
|
func createKeyPair() (string, string) { |
||||||
|
// Create key and put it in correct format
|
||||||
|
tmpKey, err := ioutil.TempFile("", "") |
||||||
|
defer os.Remove(tmpKey.Name()) |
||||||
|
defer os.Remove(tmpKey.Name() + ".pub") |
||||||
|
defer os.Remove(tmpKey.Name() + ".sec") |
||||||
|
cmd := exec.Command("signify", "-G", "-n", "-p", tmpKey.Name()+".pub", "-s", tmpKey.Name()+".sec") |
||||||
|
if output, err := cmd.CombinedOutput(); err != nil { |
||||||
|
panic(fmt.Sprintf("could not verify the file: %v, output: \n%s", err, output)) |
||||||
|
} |
||||||
|
secKey, err := getKey(tmpKey.Name() + ".sec") |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
pubKey, err := getKey(tmpKey.Name() + ".pub") |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
return secKey, pubKey |
||||||
|
} |
@ -0,0 +1,154 @@ |
|||||||
|
// Copyright 2020 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// signFile reads the contents of an input file and signs it (in armored format)
|
||||||
|
// with the key provided, placing the signature into the output file.
|
||||||
|
|
||||||
|
package signify |
||||||
|
|
||||||
|
import ( |
||||||
|
"io/ioutil" |
||||||
|
"math/rand" |
||||||
|
"os" |
||||||
|
"testing" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/jedisct1/go-minisign" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
testSecKey = "RWRCSwAAAABVN5lr2JViGBN8DhX3/Qb/0g0wBdsNAR/APRW2qy9Fjsfr12sK2cd3URUFis1jgzQzaoayK8x4syT4G3Gvlt9RwGIwUYIQW/0mTeI+ECHu1lv5U4Wa2YHEPIesVPyRm5M=" |
||||||
|
testPubKey = "RWTAPRW2qy9FjsBiMFGCEFv9Jk3iPhAh7tZb+VOFmtmBxDyHrFT8kZuT" |
||||||
|
) |
||||||
|
|
||||||
|
func TestSignify(t *testing.T) { |
||||||
|
tmpFile, err := ioutil.TempFile("", "") |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
defer os.Remove(tmpFile.Name()) |
||||||
|
defer tmpFile.Close() |
||||||
|
|
||||||
|
rand.Seed(time.Now().UnixNano()) |
||||||
|
|
||||||
|
data := make([]byte, 1024) |
||||||
|
rand.Read(data) |
||||||
|
tmpFile.Write(data) |
||||||
|
|
||||||
|
if err = tmpFile.Close(); err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "clé", "croissants") |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
defer os.Remove(tmpFile.Name() + ".sig") |
||||||
|
|
||||||
|
// Verify the signature using a golang library
|
||||||
|
sig, err := minisign.NewSignatureFromFile(tmpFile.Name() + ".sig") |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
pKey, err := minisign.NewPublicKey(testPubKey) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
valid, err := pKey.VerifyFromFile(tmpFile.Name(), sig) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
if !valid { |
||||||
|
t.Fatal("invalid signature") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestSignifyTrustedCommentTooManyLines(t *testing.T) { |
||||||
|
tmpFile, err := ioutil.TempFile("", "") |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
defer os.Remove(tmpFile.Name()) |
||||||
|
defer tmpFile.Close() |
||||||
|
|
||||||
|
rand.Seed(time.Now().UnixNano()) |
||||||
|
|
||||||
|
data := make([]byte, 1024) |
||||||
|
rand.Read(data) |
||||||
|
tmpFile.Write(data) |
||||||
|
|
||||||
|
if err = tmpFile.Close(); err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "crois\nsants") |
||||||
|
if err == nil || err.Error() == "" { |
||||||
|
t.Fatalf("should have errored on a multi-line trusted comment, got %v", err) |
||||||
|
} |
||||||
|
defer os.Remove(tmpFile.Name() + ".sig") |
||||||
|
} |
||||||
|
|
||||||
|
func TestSignifyTrustedCommentTooManyLinesLF(t *testing.T) { |
||||||
|
tmpFile, err := ioutil.TempFile("", "") |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
defer os.Remove(tmpFile.Name()) |
||||||
|
defer tmpFile.Close() |
||||||
|
|
||||||
|
rand.Seed(time.Now().UnixNano()) |
||||||
|
|
||||||
|
data := make([]byte, 1024) |
||||||
|
rand.Read(data) |
||||||
|
tmpFile.Write(data) |
||||||
|
|
||||||
|
if err = tmpFile.Close(); err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "crois\rsants", "") |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
defer os.Remove(tmpFile.Name() + ".sig") |
||||||
|
} |
||||||
|
|
||||||
|
func TestSignifyTrustedCommentEmpty(t *testing.T) { |
||||||
|
tmpFile, err := ioutil.TempFile("", "") |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
defer os.Remove(tmpFile.Name()) |
||||||
|
defer tmpFile.Close() |
||||||
|
|
||||||
|
rand.Seed(time.Now().UnixNano()) |
||||||
|
|
||||||
|
data := make([]byte, 1024) |
||||||
|
rand.Read(data) |
||||||
|
tmpFile.Write(data) |
||||||
|
|
||||||
|
if err = tmpFile.Close(); err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "") |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
defer os.Remove(tmpFile.Name() + ".sig") |
||||||
|
} |
Loading…
Reference in new issue