sigtool now supports openssh ed25519 public and private keys.

* Added support to read openssh public keys and encrypted private keys
* reworked private key handling
* made password the default; generating keys without password
  requires explicit "--no-password"
This commit is contained in:
Sudhi Herle 2019-11-05 21:42:25 +01:00
parent b14f9d1e53
commit f82c1336ac
8 changed files with 581 additions and 78 deletions

View file

@ -21,6 +21,9 @@ during the encryption process - this has the benefit of also authenticating
the sender (and the receiver can verify the sender if they possess the
corresponding public key).
The sign, verify, encrypt, decrypt operations can use OpenSSH Ed25519 keys
*or* the keys generated by sigtool. This means, you can send encrypted
files to any recipient identified by their comment in `~/.ssh/authorized_keys`.
## How do I build it?
With Go 1.5 and later:
@ -61,15 +64,15 @@ e.g., to sign `archive.tar.gz` with private key `/tmp/testkey.key`:
sigtool sign /tmp/testkey.key archive.tar.gz
If *testkey.key* was encrypted with a user pass phrase:
If *testkey.key* was encrypted without a user pass phrase:
sigtool sign -p /tmp/testkey.key archive.tar.gz
sigtool sign --no-password /tmp/testkey.key archive.tar.gz
The signature can also be written directly to a user supplied output
file.
sigtool sign -p -o archive.sig /tmp/testkey.key archive.tar.gz
sigtool sign -o archive.sig /tmp/testkey.key archive.tar.gz
### Verify a signature against a file
@ -85,6 +88,10 @@ e.g., to verify the signature of *archive.tar.gz* against
sigtool verify /tmp/testkey.pub archive.sig archive.tar.gz
Note that signing and verifying can also work with OpenSSH ed25519
keys.
### Encrypt a file by authenticating the sender
If the sender wishes to prove to the recipient that they encrypted
a file:
@ -115,6 +122,20 @@ the receiver doesn't need to authenticate the sender:
This will create an encrypted file *archive.tar.gz.enc* such that the
recipient in possession of *to.key* can decrypt it.
### Encrypt a file to an OpenSSH recipient *without* authenticating the sender
Suppose you want to send an encrypted file where the recipient's
public key is in `~/.ssh/authorized_keys`. Such a recipient is identified
by their OpenSSH key comment (typically `name@domain`):
sigtool encrypt user@domain -o archive.tar.gz.enc archive.tar.gz
If you have their public key in file "name-domain.pub", you can do:
sigtool encrypt name-domain.pub -o archive.tar.gz.enc archive.tar.gz
This will create an encrypted file *archive.tar.gz.enc* such that the
recipient can decrypt using their private key.
## Technical Details
### How is the private key protected?
@ -144,7 +165,7 @@ using HKDF to generate a key-encryption-key. The file-encryption key
is AEAD encrypted with this key-encryption-key. Thus, each recipient
has their own individual encrypted key blob.
The Ed25519 keys generated by `sigtool` are transformed to their
The Ed25519 keys generated by `sigtool` are transformed to their
corresponding Curve25519 points in order to generate the shared secret.
This elliptic co-ordinate transform follows [FiloSottile's writeup][2].

113
crypt.go
View file

@ -16,7 +16,9 @@ package main
import (
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"github.com/opencoff/go-utils"
flag "github.com/opencoff/pflag"
@ -34,12 +36,12 @@ func encrypt(args []string) {
var outfile string
var keyfile string
var envpw string
var pw bool
var nopw bool
var sblksize string
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
fs.StringVarP(&keyfile, "sign", "s", "", "Sign using private key `S`")
fs.BoolVarP(&pw, "password", "p", false, "Ask for passphrase to decrypt the private key")
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for passphrase to decrypt the private key")
fs.StringVarP(&envpw, "env-password", "", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&sblksize, "block-size", "B", "4M", "Use `S` as the encryption block size")
@ -55,19 +57,23 @@ func encrypt(args []string) {
var pws, infile string
if len(envpw) > 0 {
pws = os.Getenv(envpw)
} else if pw {
pws, err = utils.Askpass("Enter passphrase for private key", false)
if err != nil {
die("%s", err)
}
}
var sk *sign.PrivateKey
if len(keyfile) > 0 {
sk, err = sign.ReadPrivateKey(keyfile, pws)
sk, err = sign.ReadPrivateKey(keyfile, func() ([]byte, error) {
if nopw {
return nil, nil
}
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)
}
@ -75,7 +81,7 @@ func encrypt(args []string) {
args = fs.Args()
if len(args) < 2 {
die("Insufficient args. Try '%s --help'")
die("Insufficient args. Try '%s --help'", os.Args[0])
}
var infd io.Reader = os.Stdin
@ -92,6 +98,27 @@ func encrypt(args []string) {
}
}
// Lets try to read the authorized files
home, err := os.UserHomeDir()
if err != nil {
die("can't find homedir for this user")
}
authkeys := fmt.Sprintf("%s/.ssh/authorized_keys", home)
authdata, err := ioutil.ReadFile(authkeys)
if err != nil {
if err != os.ErrNotExist {
die("can't open %s: %s", authkeys, err)
}
}
pka, err := sign.ParseAuthorizedKeys(authdata)
keymap := make(map[string]*sign.PublicKey)
for _, pk := range pka {
keymap[pk.Comment] = pk
}
if len(outfile) > 0 && outfile != "-" {
if inf != nil {
ost, err := os.Stat(outfile)
@ -120,11 +147,27 @@ func encrypt(args []string) {
die("%s", err)
}
errs := 0
for i := 0; i < len(args)-1; i++ {
var err error
var pk *sign.PublicKey
fn := args[i]
pk, err := sign.ReadPublicKey(fn)
if err != nil {
die("%s", err)
if strings.Index(fn, "@") > 0 {
var ok bool
pk, ok = keymap[fn]
if !ok {
warn("can't find user %s in %s", fn, authkeys)
errs += 1
continue
}
} else {
pk, err = sign.ReadPublicKey(fn)
if err != nil {
warn("%s", err)
errs += 1
continue
}
}
err = en.AddRecipient(pk)
@ -133,6 +176,10 @@ func encrypt(args []string) {
}
}
if errs > 0 {
die("Too many errors!")
}
err = en.Encrypt(infd, outfd)
if err != nil {
die("%s", err)
@ -149,10 +196,10 @@ func decrypt(args []string) {
var envpw string
var outfile string
var pubkey string
var pw bool
var nopw bool
fs.StringVarP(&outfile, "outfile", "o", "", "Write the output to file `F`")
fs.BoolVarP(&pw, "password", "p", false, "Ask for passphrase to decrypt the private key")
fs.BoolVarP(&nopw, "no-password", "", false, "Don't ask for passphrase to decrypt the private key")
fs.StringVarP(&envpw, "env-password", "", "", "Use passphrase from environment variable `E`")
fs.StringVarP(&pubkey, "verify-sender", "v", "", "Verify that the sender matches public key in `F`")
@ -163,25 +210,31 @@ func decrypt(args []string) {
args = fs.Args()
if len(args) < 1 {
die("Insufficient args. Try '%s --help'")
die("Insufficient args. Try '%s --help'", os.Args[0])
}
var infd io.Reader = os.Stdin
var outfd io.Writer = os.Stdout
var inf *os.File
var pws, infile string
if len(envpw) > 0 {
pws = os.Getenv(envpw)
} else if pw {
pws, err = utils.Askpass("Enter passphrase for private key", false)
if err != nil {
die("%s", err)
}
}
var infile string
keyfile := args[0]
sk, err := sign.ReadPrivateKey(keyfile, pws)
sk, err := sign.ReadPrivateKey(keyfile, func() ([]byte, error) {
var pws string
if nopw {
return nil, nil
}
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)
}

1
go.mod
View file

@ -3,6 +3,7 @@ module github.com/opencoff/sigtool
go 1.13
require (
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a
github.com/gogo/protobuf v1.3.1
github.com/opencoff/go-utils v0.4.0
github.com/opencoff/pflag v0.3.3

2
go.sum
View file

@ -1,3 +1,5 @@
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=

View file

@ -20,6 +20,7 @@
package sign
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/sha256"
@ -55,6 +56,9 @@ type PrivateKey struct {
type PublicKey struct {
Pk []byte
// Comment string
Comment string
// Curve25519 point corresponding to this Ed25519 key
ck []byte
@ -163,7 +167,7 @@ func NewKeypair() (*Keypair, error) {
// goes in $bn.key.
// If password is non-empty, then the private key is encrypted
// before writing to disk.
func (kp *Keypair) Serialize(bn, comment string, pw string) error {
func (kp *Keypair) Serialize(bn, comment string, getpw func() ([]byte, error)) error {
sk := &kp.Sec
pk := &kp.Pub
@ -176,7 +180,7 @@ func (kp *Keypair) Serialize(bn, comment string, pw string) error {
return fmt.Errorf("Can't serialize to %s: %s", pkf, err)
}
err = sk.serialize(skf, comment, pw)
err = sk.serialize(skf, comment, getpw)
if err != nil {
return fmt.Errorf("Can't serialize to %s: %s", pkf, err)
}
@ -186,18 +190,25 @@ func (kp *Keypair) Serialize(bn, comment string, pw string) error {
// Read the private key in 'fn', optionally decrypting it using
// password 'pw' and create new instance of PrivateKey
func ReadPrivateKey(fn string, pw string) (*PrivateKey, error) {
func ReadPrivateKey(fn string, getpw func() ([]byte, error)) (*PrivateKey, error) {
yml, err := ioutil.ReadFile(fn)
if err != nil {
return nil, err
}
return MakePrivateKey(yml, pw)
if bytes.Index(yml, []byte("OPENSSH PRIVATE KEY-")) > 0 {
return parseSSHPrivateKey(yml, getpw)
}
if pw, err := getpw(); err == nil {
return MakePrivateKey(yml, pw)
}
return nil, err
}
// Make a private key from bytes 'yml' and password 'pw'. The bytes
// are assumed to be serialized version of the private key.
func MakePrivateKey(yml []byte, pw string) (*PrivateKey, error) {
func MakePrivateKey(yml []byte, pw []byte) (*PrivateKey, error) {
var ssk serializedPrivKey
err := yaml.Unmarshal(yml, &ssk)
@ -224,7 +235,7 @@ func MakePrivateKey(yml []byte, pw string) (*PrivateKey, error) {
}
// We take short passwords and extend them
pwb := sha512.Sum512([]byte(pw))
pwb := sha512.Sum512(pw)
xork, err := scrypt.Key(pwb[:], esk.Salt, int(esk.N), int(esk.r), int(esk.p), len(esk.Esk))
if err != nil {
@ -250,11 +261,14 @@ func MakePrivateKey(yml []byte, pw string) (*PrivateKey, error) {
}
// Make a private key from 64-bytes of extended Ed25519 key
func PrivateKeyFromBytes(skb []byte) (*PrivateKey, error) {
if len(skb) != 64 {
return nil, fmt.Errorf("private key is malformed (len %d!)", len(skb))
func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
if len(buf) != 64 {
return nil, fmt.Errorf("private key is malformed (len %d!)", len(buf))
}
skb := make([]byte, 64)
copy(skb, buf)
edsk := Ed.PrivateKey(skb)
edpk := edsk.Public().(Ed.PublicKey)
@ -283,16 +297,19 @@ func (pk *PublicKey) Hash() []byte {
// Serialize the private key to a file
// Format: YAML
// All []byte are in base64 (RawEncoding)
func (sk *PrivateKey) serialize(fn, comment string, pw string) error {
func (sk *PrivateKey) serialize(fn, comment string, getpw func() ([]byte, error)) error {
pw, err := getpw()
if err != nil {
return err
}
b64 := base64.StdEncoding.EncodeToString
esk := &encPrivKey{}
ssk := &serializedPrivKey{Comment: comment}
// Even with an empty password, we still encrypt and store.
// expand the password into 64 bytes
pwb := sha512.Sum512([]byte(pw))
pwb := sha512.Sum512(pw)
esk.N = _N
esk.r = _r
@ -455,7 +472,12 @@ func ReadPublicKey(fn string) (*PublicKey, error) {
return nil, err
}
return MakePublicKey(yml)
// first try to parse as a ssh key
pk, err := parseSSHPublicKey(yml)
if err != nil {
pk, err = MakePublicKey(yml)
}
return pk, err
}
// Parse a serialized public in 'yml' and return the resulting
@ -469,13 +491,17 @@ func MakePublicKey(yml []byte) (*PublicKey, error) {
}
b64 := base64.StdEncoding.DecodeString
var pk []byte
var pkb []byte
if pk, err = b64(spk.Pk); err != nil {
if pkb, err = b64(spk.Pk); err != nil {
return nil, fmt.Errorf("can't decode YAML:Pk: %s", err)
}
return PublicKeyFromBytes(pk)
if pk, err := PublicKeyFromBytes(pkb); err == nil {
pk.Comment = spk.Comment
return pk, nil
}
return nil, err
}
// Make a public key from a byte string

389
sign/ssh.go Normal file
View file

@ -0,0 +1,389 @@
// ssh.go - support for reading ssh private and public keys
//
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file is a bastardization of github.com/ScaleFT/sshkeys and
// golang.org/x/crypto/ssh/keys.go
//
// It is licensed under the terms of the original go source code
// OR the Apache 2.0 license (terms of sshkeys).
//
// Changes from that version:
// - don't use password but call a func() to get the password as needed
// - narrowly scope the key support for ONLY ed25519 keys
// - support reading multiple public keys from authorized_keys
package sign
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"encoding/pem"
"errors"
"fmt"
"strings"
"github.com/dchest/bcrypt_pbkdf"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh"
)
var (
ErrIncorrectPassword = errors.New("ssh: Invalid Passphrase")
ErrNoPEMFound = errors.New("no PEM block found")
ErrBadPublicKey = errors.New("ssh: malformed public key")
ErrKeyTooShort = errors.New("ssh: public key too short")
ErrBadTrailers = errors.New("ssh: trailing junk in public key")
ErrBadFormat = errors.New("ssh: invalid openssh private key format")
ErrBadLength = errors.New("ssh: private key unexpected length")
ErrBadPadding = errors.New("ssh: padding not as expected")
)
const keySizeAES256 = 32
// ParseEncryptedRawPrivateKey returns a private key from an
// encrypted ed25519 private key.
func parseSSHPrivateKey(data []byte, getpw func() ([]byte, error)) (*PrivateKey, error) {
block, _ := pem.Decode(data)
if block == nil {
return nil, ErrNoPEMFound
}
if x509.IsEncryptedPEMBlock(block) {
return nil, fmt.Errorf("ssh: no support for legacy PEM encrypted keys")
}
switch block.Type {
case "OPENSSH PRIVATE KEY":
return parseOpenSSHPrivateKey(block.Bytes, getpw)
default:
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
}
}
func parseSSHPublicKey(in []byte) (*PublicKey, error) {
v := bytes.Split(in, []byte(" \t"))
if len(v) != 3 {
return nil, ErrBadPublicKey
}
return parseEncPubKey(v[1], string(v[2]))
}
// parse a wire encoded public key
func parseEncPubKey(in []byte, comm string) (*PublicKey, error) {
in, err := base64.StdEncoding.DecodeString(string(in))
if err != nil {
return nil, err
}
algo, in, ok := parseString(in)
if !ok {
return nil, ErrKeyTooShort
}
if string(algo) != ssh.KeyAlgoED25519 {
return nil, nil
}
var w struct {
KeyBytes []byte
Rest []byte `ssh:"rest"`
}
if err := ssh.Unmarshal(in, &w); err != nil {
return nil, err
}
if len(w.Rest) > 0 {
return nil, ErrBadTrailers
}
pk, err := PublicKeyFromBytes(w.KeyBytes)
if err == nil {
pk.Comment = strings.TrimSpace(comm)
}
return pk, err
}
func parseString(in []byte) (out, rest []byte, ok bool) {
if len(in) < 4 {
return
}
length := binary.BigEndian.Uint32(in)
in = in[4:]
if uint32(len(in)) < length {
return
}
out = in[:length]
rest = in[length:]
ok = true
return
}
// parseAuthorizedKey parses a public key in OpenSSH binary format and decodes it.
// removed.
func parseAuthorizedKey(in []byte) (*PublicKey, error) {
in = bytes.TrimSpace(in)
i := bytes.IndexAny(in, " \t")
if i == -1 {
i = len(in)
}
pk, err := parseEncPubKey(in[:i], string(in[i:]))
if err != nil {
return nil, err
}
return pk, nil
}
// ParseAuthorizedKeys parses a public key from an authorized_keys
// file used in OpenSSH according to the sshd(8) manual page.
func ParseAuthorizedKeys(in []byte) ([]*PublicKey, error) {
var pka []*PublicKey
var rest []byte
for len(in) > 0 {
end := bytes.IndexByte(in, '\n')
if end != -1 {
rest = in[end+1:]
in = in[:end]
} else {
rest = nil
}
end = bytes.IndexByte(in, '\r')
if end != -1 {
in = in[:end]
}
in = bytes.TrimSpace(in)
if len(in) == 0 || in[0] == '#' {
in = rest
continue
}
i := bytes.IndexAny(in, " \t")
if i == -1 {
in = rest
continue
}
if pk, err := parseAuthorizedKey(in[i:]); err == nil {
if pk != nil {
pka = append(pka, pk)
}
in = rest
continue
}
// No key type recognised. Maybe there's an options field at
// the beginning.
var b byte
inQuote := false
var candidateOptions []string
optionStart := 0
for i, b = range in {
isEnd := !inQuote && (b == ' ' || b == '\t')
if (b == ',' && !inQuote) || isEnd {
if i-optionStart > 0 {
candidateOptions = append(candidateOptions, string(in[optionStart:i]))
}
optionStart = i + 1
}
if isEnd {
break
}
if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) {
inQuote = !inQuote
}
}
for i < len(in) && (in[i] == ' ' || in[i] == '\t') {
i++
}
if i == len(in) {
// Invalid line: unmatched quote
in = rest
continue
}
in = in[i:]
i = bytes.IndexAny(in, " \t")
if i == -1 {
in = rest
continue
}
if pk, err := parseAuthorizedKey(in[i:]); err == nil {
if pk != nil {
pka = append(pka, pk)
}
}
in = rest
continue
}
return pka, nil
}
const opensshv1Magic = "openssh-key-v1"
type opensshHeader struct {
CipherName string
KdfName string
KdfOpts string
NumKeys uint32
PubKey string
PrivKeyBlock string
}
type opensshKey struct {
Check1 uint32
Check2 uint32
Keytype string
Rest []byte `ssh:"rest"`
}
type opensshED25519 struct {
Pub []byte
Priv []byte
Comment string
Pad []byte `ssh:"rest"`
}
func parseOpenSSHPrivateKey(data []byte, getpw func() ([]byte, error)) (*PrivateKey, error) {
magic := append([]byte(opensshv1Magic), 0)
if !bytes.Equal(magic, data[0:len(magic)]) {
return nil, ErrBadFormat
}
remaining := data[len(magic):]
w := opensshHeader{}
if err := ssh.Unmarshal(remaining, &w); err != nil {
return nil, err
}
if w.NumKeys != 1 {
return nil, fmt.Errorf("ssh: NumKeys must be 1: %d", w.NumKeys)
}
var privateKeyBytes []byte
var encrypted bool
switch {
// OpenSSH supports bcrypt KDF w/ AES256-CBC or AES256-CTR mode
case w.KdfName == "bcrypt" && w.CipherName == "aes256-cbc":
pw, err := getpw()
if err != nil {
return nil, err
}
iv, block, err := extractBcryptIvBlock(pw, &w)
if err != nil {
return nil, err
}
cbc := cipher.NewCBCDecrypter(block, iv)
privateKeyBytes = []byte(w.PrivKeyBlock)
cbc.CryptBlocks(privateKeyBytes, privateKeyBytes)
encrypted = true
case w.KdfName == "bcrypt" && w.CipherName == "aes256-ctr":
pw, err := getpw()
if err != nil {
return nil, err
}
iv, block, err := extractBcryptIvBlock(pw, &w)
if err != nil {
return nil, err
}
stream := cipher.NewCTR(block, iv)
privateKeyBytes = []byte(w.PrivKeyBlock)
stream.XORKeyStream(privateKeyBytes, privateKeyBytes)
encrypted = true
case w.KdfName == "none" && w.CipherName == "none":
privateKeyBytes = []byte(w.PrivKeyBlock)
default:
return nil, fmt.Errorf("ssh: unknown Cipher/KDF: %s:%s", w.CipherName, w.KdfName)
}
pk1 := opensshKey{}
if err := ssh.Unmarshal(privateKeyBytes, &pk1); err != nil {
if encrypted {
return nil, ErrIncorrectPassword
}
return nil, err
}
if pk1.Check1 != pk1.Check2 {
return nil, ErrIncorrectPassword
}
// we only handle ed25519 and rsa keys currently
switch pk1.Keytype {
case ssh.KeyAlgoED25519:
key := opensshED25519{}
err := ssh.Unmarshal(pk1.Rest, &key)
if err != nil {
return nil, err
}
if len(key.Priv) != ed25519.PrivateKeySize {
return nil, ErrBadLength
}
for i, b := range key.Pad {
if int(b) != i+1 {
return nil, ErrBadPadding
}
}
pk, err := PrivateKeyFromBytes(key.Priv)
return pk, err
default:
return nil, fmt.Errorf("ssh: unhandled key type: %v", pk1.Keytype)
}
}
func extractBcryptIvBlock(passphrase []byte, w *opensshHeader) ([]byte, cipher.Block, error) {
cipherKeylen := keySizeAES256
cipherIvLen := aes.BlockSize
var opts struct {
Salt []byte
Rounds uint32
}
if err := ssh.Unmarshal([]byte(w.KdfOpts), &opts); err != nil {
return nil, nil, err
}
kdfdata, err := bcrypt_pbkdf.Key(passphrase, opts.Salt, int(opts.Rounds), cipherKeylen+cipherIvLen)
if err != nil {
return nil, nil, err
}
iv := kdfdata[cipherKeylen : cipherIvLen+cipherKeylen]
aeskey := kdfdata[0:cipherKeylen]
block, err := aes.NewCipher(aeskey)
if err != nil {
return nil, nil, err
}
return iv, block, nil
}

View file

@ -87,13 +87,13 @@ func main() {
// Run the generate command
func gen(args []string) {
var pw, help, force bool
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(&pw, "password", "p", false, "Ask for passphrase to encrypt the private key")
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, "force", "F", false, "Overwrite the output file if it exists")
@ -124,24 +124,29 @@ Options:
die("Public/Private key files (%s.key, %s.pub) exist. Won't overwrite!", bn, bn)
}
var pws string
var err error
if len(envpw) > 0 {
pws = os.Getenv(envpw)
} else if pw {
pws, err = utils.Askpass("Enter passphrase for private key", true)
if err != nil {
die("%s", err)
}
}
kp, err := sign.NewKeypair()
if err != nil {
die("%s", err)
}
err = kp.Serialize(bn, comment, pws)
err = kp.Serialize(bn, comment, 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", true)
if err != nil {
die("%s", err)
}
}
return []byte(pws), nil
})
if err != nil {
die("%s", err)
}
@ -149,13 +154,13 @@ Options:
// Run the 'sign' command.
func signify(args []string) {
var pw, help bool
var nopw, help 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(&pw, "password", "p", false, "Ask for passphrase to decrypt the private key")
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`")
@ -182,23 +187,29 @@ Options:
fn := args[1]
outf := fmt.Sprintf("%s.sig", fn)
var pws string
var err error
if len(envpw) > 0 {
pws = os.Getenv(envpw)
} else if pw {
pws, err = utils.Askpass("Enter passphrase for private key", false)
if err != nil {
die("%s", err)
}
}
if len(output) > 0 {
outf = output
}
sk, err := sign.ReadPrivateKey(kn, pws)
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)
}

View file

@ -1 +1 @@
0.3.1
0.4.0