Teach sigtool verify to use public key as a command line string (in lieu of a file).
- Reorganized the code a bit and split each of sigtool's commands into a separate file. - Added extra tests to validate verify's new capabilities - Updated README
This commit is contained in:
parent
d49f732c71
commit
e3053142f5
11 changed files with 475 additions and 362 deletions
|
@ -101,6 +101,10 @@ e.g., to verify the signature of *archive.tar.gz* against
|
||||||
sigtool verify /tmp/testkey.pub archive.sig archive.tar.gz
|
sigtool verify /tmp/testkey.pub archive.sig archive.tar.gz
|
||||||
|
|
||||||
|
|
||||||
|
You can also pass a public key as a string (instead of a file):
|
||||||
|
|
||||||
|
sigtool verify iF84Dymq/bAEnUMK6DRIHWAQDRD8FwDDDfsgFfzdjWM= archive.sig archive.tar.gz
|
||||||
|
|
||||||
Note that signing and verifying can also work with OpenSSH ed25519
|
Note that signing and verifying can also work with OpenSSH ed25519
|
||||||
keys.
|
keys.
|
||||||
|
|
||||||
|
|
2
build
2
build
|
@ -13,7 +13,7 @@
|
||||||
#
|
#
|
||||||
# License: GPLv2
|
# License: GPLv2
|
||||||
#
|
#
|
||||||
Progs=".:sigtool"
|
Progs="src:sigtool"
|
||||||
|
|
||||||
# Relative path to protobuf sources
|
# Relative path to protobuf sources
|
||||||
# e.g. src/foo/a.proto
|
# e.g. src/foo/a.proto
|
||||||
|
|
23
sign/keys.go
23
sign/keys.go
|
@ -369,6 +369,29 @@ func MakePublicKey(yml []byte) (*PublicKey, error) {
|
||||||
return &pk, nil
|
return &pk, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make a public key from a string
|
||||||
|
func MakePublicKeyFromString(s string) (*PublicKey, error) {
|
||||||
|
// first try to decode it as a openssh key
|
||||||
|
if pk2, err := parseEncPubKey([]byte(s), "command-line-pk"); err == nil {
|
||||||
|
return pk2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try to decode as an sigtool key
|
||||||
|
b64 := base64.StdEncoding.DecodeString
|
||||||
|
|
||||||
|
pkb, err := b64(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pk PublicKey
|
||||||
|
err = makePublicKeyFromBytes(&pk, pkb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pk, nil
|
||||||
|
}
|
||||||
|
|
||||||
func makePublicKeyFromBytes(pk *PublicKey, b []byte) error {
|
func makePublicKeyFromBytes(pk *PublicKey, b []byte) error {
|
||||||
if len(b) != 32 {
|
if len(b) != 32 {
|
||||||
return fmt.Errorf("public key is malformed (len %d!)", len(b))
|
return fmt.Errorf("public key is malformed (len %d!)", len(b))
|
||||||
|
|
360
sigtool.go
360
sigtool.go
|
@ -1,360 +0,0 @@
|
||||||
// sigtool.go -- Tool to generate, manage Ed25519 keys and
|
|
||||||
// signatures.
|
|
||||||
//
|
|
||||||
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
|
||||||
//
|
|
||||||
// Licensing Terms: GPLv2
|
|
||||||
//
|
|
||||||
// If you need a commercial license for this work, please contact
|
|
||||||
// the author.
|
|
||||||
//
|
|
||||||
// This software does not come with any express or implied
|
|
||||||
// warranty; it is provided "as is". No claim is made to its
|
|
||||||
// suitability for any purpose.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/opencoff/go-utils"
|
|
||||||
flag "github.com/opencoff/pflag"
|
|
||||||
"github.com/opencoff/sigtool/sign"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Z string = path.Base(os.Args[0])
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
var ver, help, debug bool
|
|
||||||
|
|
||||||
mf := flag.NewFlagSet(Z, flag.ExitOnError)
|
|
||||||
mf.SetInterspersed(false)
|
|
||||||
mf.BoolVarP(&ver, "version", "v", false, "Show version info and exit")
|
|
||||||
mf.BoolVarP(&help, "help", "h", false, "Show help info exit")
|
|
||||||
mf.BoolVarP(&debug, "debug", "", false, "Enable debug mode")
|
|
||||||
mf.Parse(os.Args[1:])
|
|
||||||
|
|
||||||
if ver {
|
|
||||||
fmt.Printf("%s - %s [%s; %s]\n", Z, ProductVersion, RepoVersion, Buildtime)
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if help {
|
|
||||||
usage(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
args := mf.Args()
|
|
||||||
if len(args) < 1 {
|
|
||||||
Die("Insufficient arguments. Try '%s -h'", Z)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds := map[string]func(args []string){
|
|
||||||
"generate": gen,
|
|
||||||
"sign": signify,
|
|
||||||
"verify": verify,
|
|
||||||
"encrypt": encrypt,
|
|
||||||
"decrypt": decrypt,
|
|
||||||
|
|
||||||
"help": func(_ []string) {
|
|
||||||
usage(0)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
words := make([]string, 0, len(cmds))
|
|
||||||
for k := range cmds {
|
|
||||||
words = append(words, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
ab := utils.Abbrev(words)
|
|
||||||
canon, ok := ab[strings.ToLower(args[0])]
|
|
||||||
if !ok {
|
|
||||||
Die("Unknown command %s", args[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := cmds[canon]
|
|
||||||
if cmd == nil {
|
|
||||||
Die("can't map command %s", canon)
|
|
||||||
}
|
|
||||||
|
|
||||||
if debug {
|
|
||||||
sign.Debug(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd(args[1:])
|
|
||||||
|
|
||||||
// always call Exit so that at-exit handlers are called.
|
|
||||||
Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the generate command
|
|
||||||
func gen(args []string) {
|
|
||||||
|
|
||||||
var nopw, help, force bool
|
|
||||||
var comment string
|
|
||||||
var envpw string
|
|
||||||
|
|
||||||
fs := flag.NewFlagSet("generate", flag.ExitOnError)
|
|
||||||
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
|
|
||||||
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
|
|
||||||
fs.StringVarP(&comment, "comment", "c", "", "Use `C` as the text comment for the keys")
|
|
||||||
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
|
|
||||||
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
|
|
||||||
|
|
||||||
fs.Parse(args)
|
|
||||||
|
|
||||||
if help {
|
|
||||||
fs.SetOutput(os.Stdout)
|
|
||||||
fmt.Printf(`%s generate|gen|g [options] file-prefix
|
|
||||||
|
|
||||||
Generate a new Ed25519 public+private key pair and write public key to
|
|
||||||
FILE-PREFIX.pub and private key to FILE-PREFIX.key.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
`, Z)
|
|
||||||
fs.PrintDefaults()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
args = fs.Args()
|
|
||||||
if len(args) < 1 {
|
|
||||||
Die("Insufficient arguments to 'generate'. Try '%s generate -h' ..", Z)
|
|
||||||
}
|
|
||||||
|
|
||||||
bn := args[0]
|
|
||||||
|
|
||||||
pkn := fmt.Sprintf("%s.pub", path.Clean(bn))
|
|
||||||
skn := fmt.Sprintf("%s.key", path.Clean(bn))
|
|
||||||
|
|
||||||
if !force {
|
|
||||||
if exists(pkn) || exists(skn) {
|
|
||||||
Die("Public/Private key files (%s, %s) exist. won't overwrite!", skn, pkn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var pw []byte
|
|
||||||
|
|
||||||
if !nopw {
|
|
||||||
var pws string
|
|
||||||
if len(envpw) > 0 {
|
|
||||||
pws = os.Getenv(envpw)
|
|
||||||
} else {
|
|
||||||
pws, err = utils.Askpass("Enter passphrase for private key", true)
|
|
||||||
if err != nil {
|
|
||||||
Die("%s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pw = []byte(pws)
|
|
||||||
}
|
|
||||||
|
|
||||||
sk, err := sign.NewPrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
Die("%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = sk.Serialize(skn, comment, force, pw); err != nil {
|
|
||||||
Die("%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pk := sk.PublicKey()
|
|
||||||
if err = pk.Serialize(pkn, comment, force); err != nil {
|
|
||||||
Die("%s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the 'sign' command.
|
|
||||||
func signify(args []string) {
|
|
||||||
var nopw, help, force bool
|
|
||||||
var output string
|
|
||||||
var envpw string
|
|
||||||
|
|
||||||
fs := flag.NewFlagSet("sign", flag.ExitOnError)
|
|
||||||
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
|
|
||||||
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
|
|
||||||
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
|
|
||||||
fs.StringVarP(&output, "output", "o", "", "Write signature to file `F`")
|
|
||||||
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite previous signature file if it exists")
|
|
||||||
|
|
||||||
fs.Parse(args)
|
|
||||||
|
|
||||||
if help {
|
|
||||||
fs.SetOutput(os.Stdout)
|
|
||||||
fmt.Printf(`%s sign|s [options] privkey file
|
|
||||||
|
|
||||||
Sign FILE with a Ed25519 private key PRIVKEY and write signature to FILE.sig
|
|
||||||
|
|
||||||
Options:
|
|
||||||
`, Z)
|
|
||||||
fs.PrintDefaults()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
args = fs.Args()
|
|
||||||
if len(args) < 2 {
|
|
||||||
Die("Insufficient arguments to 'sign'. Try '%s sign -h' ..", Z)
|
|
||||||
}
|
|
||||||
|
|
||||||
kn := args[0]
|
|
||||||
fn := args[1]
|
|
||||||
outf := fmt.Sprintf("%s.sig", fn)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if len(output) > 0 {
|
|
||||||
outf = output
|
|
||||||
}
|
|
||||||
|
|
||||||
var fd io.WriteCloser = os.Stdout
|
|
||||||
|
|
||||||
if outf != "-" {
|
|
||||||
sf, err := utils.NewSafeFile(outf, force, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
||||||
if err != nil {
|
|
||||||
Die("can't create sig file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we unlink and remove temp on any error
|
|
||||||
AtExit(sf.Abort)
|
|
||||||
defer sf.Abort()
|
|
||||||
fd = sf
|
|
||||||
}
|
|
||||||
|
|
||||||
sk, err := sign.ReadPrivateKey(kn, func() ([]byte, error) {
|
|
||||||
if nopw {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var pws string
|
|
||||||
if len(envpw) > 0 {
|
|
||||||
pws = os.Getenv(envpw)
|
|
||||||
} else {
|
|
||||||
pws, err = utils.Askpass("Enter passphrase for private key", false)
|
|
||||||
if err != nil {
|
|
||||||
Die("%s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(pws), nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
Die("%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sig, err := sk.SignFile(fn)
|
|
||||||
if err != nil {
|
|
||||||
Die("%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sigbytes, err := sig.MarshalBinary(fmt.Sprintf("input=%s", fn))
|
|
||||||
fd.Write(sigbytes)
|
|
||||||
fd.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify signature on a given file
|
|
||||||
func verify(args []string) {
|
|
||||||
var help, quiet bool
|
|
||||||
|
|
||||||
fs := flag.NewFlagSet("verify", flag.ExitOnError)
|
|
||||||
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
|
|
||||||
fs.BoolVarP(&quiet, "quiet", "q", false, "Don't show any output; exit with status code only")
|
|
||||||
|
|
||||||
fs.Parse(args)
|
|
||||||
|
|
||||||
if help {
|
|
||||||
fs.SetOutput(os.Stdout)
|
|
||||||
fmt.Printf(`%s verify|v [options] pubkey sig file
|
|
||||||
|
|
||||||
Verify an Ed25519 signature in SIG of FILE using a public key PUBKEY.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
`, Z)
|
|
||||||
fs.PrintDefaults()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
args = fs.Args()
|
|
||||||
if len(args) < 3 {
|
|
||||||
Die("Insufficient arguments to 'verify'. Try '%s verify -h' ..", Z)
|
|
||||||
}
|
|
||||||
|
|
||||||
pn := args[0]
|
|
||||||
sn := args[1]
|
|
||||||
fn := args[2]
|
|
||||||
|
|
||||||
sig, err := sign.ReadSignature(sn)
|
|
||||||
if err != nil {
|
|
||||||
Die("Can't read signature '%s': %s", sn, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pk, err := sign.ReadPublicKey(pn)
|
|
||||||
if err != nil {
|
|
||||||
Die("%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sig.IsPKMatch(pk) {
|
|
||||||
Die("Wrong public key '%s' for verifying '%s'", pn, sn)
|
|
||||||
}
|
|
||||||
|
|
||||||
ok, err := pk.VerifyFile(fn, sig)
|
|
||||||
if err != nil {
|
|
||||||
Die("%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
exit := 0
|
|
||||||
if !ok {
|
|
||||||
exit = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if !quiet {
|
|
||||||
if ok {
|
|
||||||
fmt.Printf("%s: Signature %s verified\n", fn, sn)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%s: Signature %s verification failure\n", fn, sn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(exit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func usage(c int) {
|
|
||||||
x := fmt.Sprintf(`%s is a tool to generate, sign and verify files with Ed25519 signatures.
|
|
||||||
|
|
||||||
Usage: %s [global-options] command [options] arg [args..]
|
|
||||||
|
|
||||||
Global options:
|
|
||||||
-h, --help Show help and exit
|
|
||||||
-v, --version Show version info and exit
|
|
||||||
--debug Enable debug (DANGEROUS)
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
generate, g Generate a new Ed25519 keypair
|
|
||||||
sign, s Sign a file with a private key
|
|
||||||
verify, v Verify a signature against a file and a public key
|
|
||||||
encrypt, e Encrypt an input file to one or more recipients
|
|
||||||
decrypt, d Decrypt a file with a private key
|
|
||||||
`, Z, Z)
|
|
||||||
|
|
||||||
os.Stdout.Write([]byte(x))
|
|
||||||
os.Exit(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if $bn.key or $bn.pub exist; false otherwise
|
|
||||||
func exists(nm string) bool {
|
|
||||||
if _, err := os.Stat(nm); err == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will be filled in by "build"
|
|
||||||
var RepoVersion string = "UNDEFINED"
|
|
||||||
var Buildtime string = "UNDEFINED"
|
|
||||||
var ProductVersion string = "UNDEFINED"
|
|
||||||
|
|
||||||
// vim: ft=go:sw=8:ts=8:noexpandtab:tw=98:
|
|
100
src/gen.go
Normal file
100
src/gen.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// gen.go -- generate keys
|
||||||
|
//
|
||||||
|
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||||
|
//
|
||||||
|
// Licensing Terms: GPLv2
|
||||||
|
//
|
||||||
|
// If you need a commercial license for this work, please contact
|
||||||
|
// the author.
|
||||||
|
//
|
||||||
|
// This software does not come with any express or implied
|
||||||
|
// warranty; it is provided "as is". No claim is made to its
|
||||||
|
// suitability for any purpose.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/opencoff/go-utils"
|
||||||
|
flag "github.com/opencoff/pflag"
|
||||||
|
"github.com/opencoff/sigtool/sign"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run the generate command
|
||||||
|
func gen(args []string) {
|
||||||
|
var nopw, help, force bool
|
||||||
|
var comment string
|
||||||
|
var envpw string
|
||||||
|
|
||||||
|
fs := flag.NewFlagSet("generate", flag.ExitOnError)
|
||||||
|
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
|
||||||
|
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
|
||||||
|
fs.StringVarP(&comment, "comment", "c", "", "Use `C` as the text comment for the keys")
|
||||||
|
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
|
||||||
|
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite the output file if it exists")
|
||||||
|
|
||||||
|
fs.Parse(args)
|
||||||
|
|
||||||
|
if help {
|
||||||
|
fs.SetOutput(os.Stdout)
|
||||||
|
fmt.Printf(`%s generate|gen|g [options] file-prefix
|
||||||
|
|
||||||
|
Generate a new Ed25519 public+private key pair and write public key to
|
||||||
|
FILE-PREFIX.pub and private key to FILE-PREFIX.key.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
`, Z)
|
||||||
|
fs.PrintDefaults()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
args = fs.Args()
|
||||||
|
if len(args) < 1 {
|
||||||
|
Die("Insufficient arguments to 'generate'. Try '%s generate -h' ..", Z)
|
||||||
|
}
|
||||||
|
|
||||||
|
bn := args[0]
|
||||||
|
|
||||||
|
pkn := fmt.Sprintf("%s.pub", path.Clean(bn))
|
||||||
|
skn := fmt.Sprintf("%s.key", path.Clean(bn))
|
||||||
|
|
||||||
|
if !force {
|
||||||
|
if exists(pkn) || exists(skn) {
|
||||||
|
Die("Public/Private key files (%s, %s) exist. won't overwrite!", skn, pkn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var pw []byte
|
||||||
|
|
||||||
|
if !nopw {
|
||||||
|
var pws string
|
||||||
|
if len(envpw) > 0 {
|
||||||
|
pws = os.Getenv(envpw)
|
||||||
|
} else {
|
||||||
|
pws, err = utils.Askpass("Enter passphrase for private key", true)
|
||||||
|
if err != nil {
|
||||||
|
Die("%s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pw = []byte(pws)
|
||||||
|
}
|
||||||
|
|
||||||
|
sk, err := sign.NewPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
Die("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = sk.Serialize(skn, comment, force, pw); err != nil {
|
||||||
|
Die("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pk := sk.PublicKey()
|
||||||
|
if err = pk.Serialize(pkn, comment, force); err != nil {
|
||||||
|
Die("%s", err)
|
||||||
|
}
|
||||||
|
}
|
111
src/sign.go
Normal file
111
src/sign.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
// sign.go -- 'sign' command implementation
|
||||||
|
//
|
||||||
|
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||||
|
//
|
||||||
|
// Licensing Terms: GPLv2
|
||||||
|
//
|
||||||
|
// If you need a commercial license for this work, please contact
|
||||||
|
// the author.
|
||||||
|
//
|
||||||
|
// This software does not come with any express or implied
|
||||||
|
// warranty; it is provided "as is". No claim is made to its
|
||||||
|
// suitability for any purpose.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/opencoff/go-utils"
|
||||||
|
flag "github.com/opencoff/pflag"
|
||||||
|
"github.com/opencoff/sigtool/sign"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run the 'sign' command.
|
||||||
|
func signify(args []string) {
|
||||||
|
var nopw, help, force bool
|
||||||
|
var output string
|
||||||
|
var envpw string
|
||||||
|
|
||||||
|
fs := flag.NewFlagSet("sign", flag.ExitOnError)
|
||||||
|
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
|
||||||
|
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for a password for the private key")
|
||||||
|
fs.StringVarP(&envpw, "env-password", "E", "", "Use passphrase from environment variable `E`")
|
||||||
|
fs.StringVarP(&output, "output", "o", "", "Write signature to file `F`")
|
||||||
|
fs.BoolVarP(&force, "overwrite", "", false, "Overwrite previous signature file if it exists")
|
||||||
|
|
||||||
|
fs.Parse(args)
|
||||||
|
|
||||||
|
if help {
|
||||||
|
fs.SetOutput(os.Stdout)
|
||||||
|
fmt.Printf(`%s sign|s [options] privkey file
|
||||||
|
|
||||||
|
Sign FILE with a Ed25519 private key PRIVKEY and write signature to FILE.sig
|
||||||
|
|
||||||
|
Options:
|
||||||
|
`, Z)
|
||||||
|
fs.PrintDefaults()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
args = fs.Args()
|
||||||
|
if len(args) < 2 {
|
||||||
|
Die("Insufficient arguments to 'sign'. Try '%s sign -h' ..", Z)
|
||||||
|
}
|
||||||
|
|
||||||
|
kn := args[0]
|
||||||
|
fn := args[1]
|
||||||
|
outf := fmt.Sprintf("%s.sig", fn)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if len(output) > 0 {
|
||||||
|
outf = output
|
||||||
|
}
|
||||||
|
|
||||||
|
var fd io.WriteCloser = os.Stdout
|
||||||
|
|
||||||
|
if outf != "-" {
|
||||||
|
sf, err := utils.NewSafeFile(outf, force, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
Die("can't create sig file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we unlink and remove temp on any error
|
||||||
|
AtExit(sf.Abort)
|
||||||
|
defer sf.Abort()
|
||||||
|
fd = sf
|
||||||
|
}
|
||||||
|
|
||||||
|
sk, err := sign.ReadPrivateKey(kn, func() ([]byte, error) {
|
||||||
|
if nopw {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var pws string
|
||||||
|
if len(envpw) > 0 {
|
||||||
|
pws = os.Getenv(envpw)
|
||||||
|
} else {
|
||||||
|
pws, err = utils.Askpass("Enter passphrase for private key", false)
|
||||||
|
if err != nil {
|
||||||
|
Die("%s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(pws), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
Die("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := sk.SignFile(fn)
|
||||||
|
if err != nil {
|
||||||
|
Die("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sigbytes, err := sig.MarshalBinary(fmt.Sprintf("input=%s", fn))
|
||||||
|
fd.Write(sigbytes)
|
||||||
|
fd.Close()
|
||||||
|
}
|
130
src/sigtool.go
Normal file
130
src/sigtool.go
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
// sigtool.go -- Tool to generate, manage Ed25519 keys and
|
||||||
|
// signatures.
|
||||||
|
//
|
||||||
|
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||||
|
//
|
||||||
|
// Licensing Terms: GPLv2
|
||||||
|
//
|
||||||
|
// If you need a commercial license for this work, please contact
|
||||||
|
// the author.
|
||||||
|
//
|
||||||
|
// This software does not come with any express or implied
|
||||||
|
// warranty; it is provided "as is". No claim is made to its
|
||||||
|
// suitability for any purpose.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/opencoff/go-utils"
|
||||||
|
flag "github.com/opencoff/pflag"
|
||||||
|
"github.com/opencoff/sigtool/sign"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Z string = path.Base(os.Args[0])
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var ver, help, debug bool
|
||||||
|
|
||||||
|
mf := flag.NewFlagSet(Z, flag.ExitOnError)
|
||||||
|
mf.SetInterspersed(false)
|
||||||
|
mf.BoolVarP(&ver, "version", "v", false, "Show version info and exit")
|
||||||
|
mf.BoolVarP(&help, "help", "h", false, "Show help info exit")
|
||||||
|
mf.BoolVarP(&debug, "debug", "", false, "Enable debug mode")
|
||||||
|
mf.Parse(os.Args[1:])
|
||||||
|
|
||||||
|
if ver {
|
||||||
|
fmt.Printf("%s - %s [%s; %s]\n", Z, ProductVersion, RepoVersion, Buildtime)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if help {
|
||||||
|
usage(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := mf.Args()
|
||||||
|
if len(args) < 1 {
|
||||||
|
Die("Insufficient arguments. Try '%s -h'", Z)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds := map[string]func(args []string){
|
||||||
|
"generate": gen,
|
||||||
|
"sign": signify,
|
||||||
|
"verify": verify,
|
||||||
|
"encrypt": encrypt,
|
||||||
|
"decrypt": decrypt,
|
||||||
|
|
||||||
|
"help": func(_ []string) {
|
||||||
|
usage(0)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
words := make([]string, 0, len(cmds))
|
||||||
|
for k := range cmds {
|
||||||
|
words = append(words, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
ab := utils.Abbrev(words)
|
||||||
|
canon, ok := ab[strings.ToLower(args[0])]
|
||||||
|
if !ok {
|
||||||
|
Die("Unknown command %s", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := cmds[canon]
|
||||||
|
if cmd == nil {
|
||||||
|
Die("can't map command %s", canon)
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
sign.Debug(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd(args[1:])
|
||||||
|
|
||||||
|
// always call Exit so that at-exit handlers are called.
|
||||||
|
Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify signature on a given file
|
||||||
|
|
||||||
|
func usage(c int) {
|
||||||
|
x := fmt.Sprintf(`%s is a tool to generate, sign and verify files with Ed25519 signatures.
|
||||||
|
|
||||||
|
Usage: %s [global-options] command [options] arg [args..]
|
||||||
|
|
||||||
|
Global options:
|
||||||
|
-h, --help Show help and exit
|
||||||
|
-v, --version Show version info and exit
|
||||||
|
--debug Enable debug (DANGEROUS)
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
generate, g Generate a new Ed25519 keypair
|
||||||
|
sign, s Sign a file with a private key
|
||||||
|
verify, v Verify a signature against a file and a public key
|
||||||
|
encrypt, e Encrypt an input file to one or more recipients
|
||||||
|
decrypt, d Decrypt a file with a private key
|
||||||
|
`, Z, Z)
|
||||||
|
|
||||||
|
os.Stdout.Write([]byte(x))
|
||||||
|
os.Exit(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if $bn.key or $bn.pub exist; false otherwise
|
||||||
|
func exists(nm string) bool {
|
||||||
|
if _, err := os.Stat(nm); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will be filled in by "build"
|
||||||
|
var RepoVersion string = "UNDEFINED"
|
||||||
|
var Buildtime string = "UNDEFINED"
|
||||||
|
var ProductVersion string = "UNDEFINED"
|
||||||
|
|
||||||
|
// vim: ft=go:sw=8:ts=8:noexpandtab:tw=98:
|
96
src/verify.go
Normal file
96
src/verify.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
// verify.go -- Verify signatures
|
||||||
|
//
|
||||||
|
// (c) 2016 Sudhi Herle <sudhi@herle.net>
|
||||||
|
//
|
||||||
|
// Licensing Terms: GPLv2
|
||||||
|
//
|
||||||
|
// If you need a commercial license for this work, please contact
|
||||||
|
// the author.
|
||||||
|
//
|
||||||
|
// This software does not come with any express or implied
|
||||||
|
// warranty; it is provided "as is". No claim is made to its
|
||||||
|
// suitability for any purpose.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
flag "github.com/opencoff/pflag"
|
||||||
|
"github.com/opencoff/sigtool/sign"
|
||||||
|
)
|
||||||
|
|
||||||
|
func verify(args []string) {
|
||||||
|
var help, quiet bool
|
||||||
|
|
||||||
|
fs := flag.NewFlagSet("verify", flag.ExitOnError)
|
||||||
|
fs.BoolVarP(&help, "help", "h", false, "Show this help and exit")
|
||||||
|
fs.BoolVarP(&quiet, "quiet", "q", false, "Don't show any output; exit with status code only")
|
||||||
|
|
||||||
|
fs.Parse(args)
|
||||||
|
|
||||||
|
if help {
|
||||||
|
fs.SetOutput(os.Stdout)
|
||||||
|
fmt.Printf(`%s verify|v [options] pubkey sig file
|
||||||
|
|
||||||
|
Verify an Ed25519 signature in SIG of FILE using a public key PUBKEY.
|
||||||
|
The pubkey can be one of:
|
||||||
|
- a file: either OpenSSH ed25519 pubkey or a sigtool pubkey
|
||||||
|
- a string: the raw OpenSSH or sigtool pubkey
|
||||||
|
|
||||||
|
%s will first parse it as a string before trying to parse it as a file.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
`, Z, Z)
|
||||||
|
fs.PrintDefaults()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
args = fs.Args()
|
||||||
|
if len(args) < 3 {
|
||||||
|
Die("Insufficient arguments to 'verify'. Try '%s verify -h' ..", Z)
|
||||||
|
}
|
||||||
|
|
||||||
|
pn := args[0]
|
||||||
|
sn := args[1]
|
||||||
|
fn := args[2]
|
||||||
|
|
||||||
|
// We first try to read the public key as a base64/openssh string
|
||||||
|
pk, err := sign.MakePublicKeyFromString(pn)
|
||||||
|
if err != nil {
|
||||||
|
pk, err = sign.ReadPublicKey(pn)
|
||||||
|
if err != nil {
|
||||||
|
Die("%s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := sign.ReadSignature(sn)
|
||||||
|
if err != nil {
|
||||||
|
Die("Can't read signature '%s': %s", sn, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sig.IsPKMatch(pk) {
|
||||||
|
Die("Wrong public key '%s' for verifying '%s'", pn, sn)
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := pk.VerifyFile(fn, sig)
|
||||||
|
if err != nil {
|
||||||
|
Die("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exit := 0
|
||||||
|
if !ok {
|
||||||
|
exit = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if !quiet {
|
||||||
|
if ok {
|
||||||
|
fmt.Printf("%s: Signature %s verified\n", fn, sn)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s: Signature %s verification failure\n", fn, sn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(exit)
|
||||||
|
}
|
11
tests.sh
11
tests.sh
|
@ -77,8 +77,12 @@ spk2=$ssk2.pub
|
||||||
$keygen -q -C 'ssk1@foo' -t ed25519 -f $ssk1 -N ""
|
$keygen -q -C 'ssk1@foo' -t ed25519 -f $ssk1 -N ""
|
||||||
$keygen -q -C 'ssk2@foo' -t ed25519 -f $ssk2 -N ""
|
$keygen -q -C 'ssk2@foo' -t ed25519 -f $ssk2 -N ""
|
||||||
|
|
||||||
|
# extract the pk string
|
||||||
|
spk1_str=$(cat $spk1 | awk '{ print $2 }')
|
||||||
|
|
||||||
$bin s --no-password $ssk1 -o $sig $0 || die "can't sign with $ssk1"
|
$bin s --no-password $ssk1 -o $sig $0 || die "can't sign with $ssk1"
|
||||||
$bin v -q $spk1 $sig $0 || die "can't verify with $spk2"
|
$bin v -q $spk1 $sig $0 || die "can't verify with $spk2"
|
||||||
|
$bin v -q $spk1_str $sig $0 || die "can't verify with $spk2_str"
|
||||||
|
|
||||||
$bin e --no-password -o $encout $spk2 $0 || die "can't encrypt to $spk2 with $ssk1"
|
$bin e --no-password -o $encout $spk2 $0 || die "can't encrypt to $spk2 with $ssk1"
|
||||||
$bin d --no-password -o $decout $ssk2 $encout || die "can't decrypt with $ssk2"
|
$bin d --no-password -o $decout $ssk2 $encout || die "can't decrypt with $ssk2"
|
||||||
|
@ -86,17 +90,22 @@ $bin d --no-password -o $decout $ssk2 $encout || die "can't decrypt with $ssk
|
||||||
# cleanup state
|
# cleanup state
|
||||||
rm -f $sig $encout $decout
|
rm -f $sig $encout $decout
|
||||||
|
|
||||||
|
|
||||||
# generate keys
|
# generate keys
|
||||||
$bin g -E FOO $bn || die "can't gen keypair $pk, $sk"
|
$bin g -E FOO $bn || die "can't gen keypair $pk, $sk"
|
||||||
$bin g -E FOO $bn 2>/dev/null && die "overwrote prev keypair"
|
$bin g -E FOO $bn 2>/dev/null && die "overwrote prev keypair"
|
||||||
$bin g -E FOO --overwrite $bn || die "can't force gen keypair $pk, $sk"
|
$bin g -E FOO --overwrite $bn || die "can't force gen keypair $pk, $sk"
|
||||||
$bin g -E FOO $bn2 || die "can't force gen keypair $pk2, $sk2"
|
$bin g -E FOO $bn2 || die "can't force gen keypair $pk2, $sk2"
|
||||||
|
|
||||||
|
# extract pk string
|
||||||
|
pk_str=$(cat $pk | grep 'pk:' | sed -e 's/^pk: //g')
|
||||||
|
pk2_str=$(cat $pk2 | grep 'pk:' | sed -e 's/^pk: //g')
|
||||||
|
|
||||||
# sign and verify
|
# sign and verify
|
||||||
$bin s -E FOO $sk $0 -o $sig || die "can't sign $0"
|
$bin s -E FOO $sk $0 -o $sig || die "can't sign $0"
|
||||||
$bin v -q $pk $sig $0 || die "can't verify signature of $0"
|
$bin v -q $pk $sig $0 || die "can't verify signature of $0"
|
||||||
|
$bin v -q $pk_str $sig $0 || die "can't verify signature of $0"
|
||||||
$bin v -q $pk2 $sig $0 2>/dev/null && die "bad verification with wrong $pk2"
|
$bin v -q $pk2 $sig $0 2>/dev/null && die "bad verification with wrong $pk2"
|
||||||
|
$bin v -q $pk2_str $sig $0 2>/dev/null && die "bad verification with wrong $pk2"
|
||||||
|
|
||||||
# encrypt/decrypt
|
# encrypt/decrypt
|
||||||
$bin e -E FOO -o $encout $pk2 $0 || die "can't encrypt to $pk2"
|
$bin e -E FOO -o $encout $pk2 $0 || die "can't encrypt to $pk2"
|
||||||
|
|
Loading…
Add table
Reference in a new issue