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>
This commit is contained in:
118
crypto/signify/signify.go
Normal file
118
crypto/signify/signify.go
Normal file
@ -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
|
||||
}
|
Reference in New Issue
Block a user