2018-10-18 17:10:29 +09:00
|
|
|
// sign.go -- Ed25519 keys and signature handling
|
|
|
|
//
|
|
|
|
// (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 sign implements Ed25519 signing, verification on files.
|
|
|
|
// It builds upon golang.org/x/crypto/ed25519 by adding methods
|
|
|
|
// for serializing and deserializing Ed25519 private & public keys.
|
|
|
|
// In addition, it works with large files - by precalculating their
|
|
|
|
// SHA512 checksum in mmap'd mode and sending the 64 byte signature
|
|
|
|
// for Ed25519 signing.
|
|
|
|
package sign
|
|
|
|
|
|
|
|
import (
|
2019-11-05 21:42:25 +01:00
|
|
|
"bytes"
|
2018-10-18 17:10:29 +09:00
|
|
|
"crypto"
|
2020-01-08 09:17:54 -08:00
|
|
|
"crypto/aes"
|
|
|
|
"crypto/cipher"
|
2018-10-18 17:10:29 +09:00
|
|
|
"crypto/rand"
|
|
|
|
"crypto/sha256"
|
|
|
|
"crypto/sha512"
|
|
|
|
"crypto/subtle"
|
|
|
|
"encoding/base64"
|
|
|
|
"encoding/binary"
|
|
|
|
"fmt"
|
|
|
|
"hash"
|
2019-10-09 14:52:34 -07:00
|
|
|
"io"
|
2018-10-18 17:10:29 +09:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
|
2019-10-09 14:52:34 -07:00
|
|
|
Ed "crypto/ed25519"
|
2018-10-18 17:10:29 +09:00
|
|
|
"golang.org/x/crypto/scrypt"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
|
|
|
|
|
|
"github.com/opencoff/go-utils"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Private Ed25519 key
|
|
|
|
type PrivateKey struct {
|
|
|
|
Sk []byte
|
|
|
|
|
2019-10-09 14:52:34 -07:00
|
|
|
// Encryption key: Curve25519 point corresponding to this Ed25519 key
|
|
|
|
ck []byte
|
|
|
|
|
2018-10-18 17:10:29 +09:00
|
|
|
// Cached copy of the public key
|
2019-10-09 14:52:34 -07:00
|
|
|
pk *PublicKey
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// Public Ed25519 key
|
|
|
|
type PublicKey struct {
|
|
|
|
Pk []byte
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
// Comment string
|
|
|
|
Comment string
|
|
|
|
|
2019-10-09 14:52:34 -07:00
|
|
|
// Curve25519 point corresponding to this Ed25519 key
|
|
|
|
ck []byte
|
|
|
|
|
|
|
|
hash []byte
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ed25519 key pair
|
|
|
|
type Keypair struct {
|
|
|
|
Sec PrivateKey
|
|
|
|
Pub PublicKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// An Ed25519 Signature
|
|
|
|
type Signature struct {
|
2019-10-09 14:52:34 -07:00
|
|
|
Sig []byte // Ed25519 sig bytes
|
2018-10-18 17:10:29 +09:00
|
|
|
pkhash []byte // [0:16] SHA256 hash of public key needed for verification
|
|
|
|
}
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
// Length of Ed25519 Public Key Hash
|
|
|
|
const PKHashLength = 16
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
const (
|
|
|
|
// Scrypt parameters
|
|
|
|
_N int = 1 << 19
|
|
|
|
_r int = 8
|
|
|
|
_p int = 1
|
|
|
|
|
|
|
|
// Algorithm used in the encrypted private key
|
|
|
|
sk_algo = "scrypt-sha256"
|
|
|
|
sig_algo = "sha512-ed25519"
|
|
|
|
)
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
// Encrypted Private key
|
2020-01-08 09:17:54 -08:00
|
|
|
type serializedPrivKey struct {
|
|
|
|
Comment string `yaml:"comment,omitempty"`
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
// Encrypted Sk
|
|
|
|
Esk string `yaml:"esk"`
|
|
|
|
Salt string `yaml:"salt,omitempty"`
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
// Algorithm used for checksum and KDF
|
2020-01-08 09:17:54 -08:00
|
|
|
Algo string `yaml:"algo,omitempty"`
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
// These are params for scrypt.Key()
|
|
|
|
// CPU Cost parameter; must be a power of 2
|
2020-01-08 09:17:54 -08:00
|
|
|
N int `yaml:"Z,flow,omitempty"`
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
// r * p should be less than 2^30
|
|
|
|
R int `yaml:"r,flow,omitempty"`
|
|
|
|
P int `yaml:"p,flow,omitempty"`
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// serialized representation of public key
|
|
|
|
type serializedPubKey struct {
|
|
|
|
Comment string `yaml:"comment,omitempty"`
|
|
|
|
Pk string `yaml:"pk"`
|
2019-10-09 14:52:34 -07:00
|
|
|
Hash string `yaml:"hash"`
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// Serialized signature
|
|
|
|
type signature struct {
|
|
|
|
Comment string `yaml:"comment,omitempty"`
|
|
|
|
Pkhash string `yaml:"pkhash,omitempty"`
|
|
|
|
Signature string `yaml:"signature"`
|
|
|
|
}
|
|
|
|
|
2019-10-09 14:52:34 -07:00
|
|
|
func pkhash(pk []byte) []byte {
|
|
|
|
z := sha256.Sum256(pk)
|
2019-10-17 14:29:01 -07:00
|
|
|
return z[:PKHashLength]
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2018-10-18 17:10:29 +09:00
|
|
|
// Generate a new Ed25519 keypair
|
|
|
|
func NewKeypair() (*Keypair, error) {
|
|
|
|
//kp := &Keypair{Sec: PrivateKey{N: 1 << 17, r: 64, p: 1}}
|
|
|
|
kp := &Keypair{}
|
|
|
|
sk := &kp.Sec
|
|
|
|
pk := &kp.Pub
|
2019-10-09 14:52:34 -07:00
|
|
|
sk.pk = pk
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
p, s, err := Ed.GenerateKey(rand.Reader)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Can't generate Ed25519 keys: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
pk.Pk = []byte(p)
|
|
|
|
sk.Sk = []byte(s)
|
2019-10-09 14:52:34 -07:00
|
|
|
pk.hash = pkhash(pk.Pk)
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
return kp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Serialize the keypair to two separate files. The basename of the
|
|
|
|
// file is 'bn'; the public key goes in $bn.pub and the private key
|
|
|
|
// goes in $bn.key.
|
|
|
|
// If password is non-empty, then the private key is encrypted
|
|
|
|
// before writing to disk.
|
2019-11-05 21:42:25 +01:00
|
|
|
func (kp *Keypair) Serialize(bn, comment string, getpw func() ([]byte, error)) error {
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
sk := &kp.Sec
|
|
|
|
pk := &kp.Pub
|
|
|
|
|
|
|
|
skf := fmt.Sprintf("%s.key", bn)
|
|
|
|
pkf := fmt.Sprintf("%s.pub", bn)
|
|
|
|
|
|
|
|
err := pk.serialize(pkf, comment)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Can't serialize to %s: %s", pkf, err)
|
|
|
|
}
|
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
err = sk.serialize(skf, comment, getpw)
|
2018-10-18 17:10:29 +09:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Can't serialize to %s: %s", pkf, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the private key in 'fn', optionally decrypting it using
|
|
|
|
// password 'pw' and create new instance of PrivateKey
|
2019-11-05 21:42:25 +01:00
|
|
|
func ReadPrivateKey(fn string, getpw func() ([]byte, error)) (*PrivateKey, error) {
|
2018-10-18 17:10:29 +09:00
|
|
|
yml, err := ioutil.ReadFile(fn)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
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
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make a private key from bytes 'yml' and password 'pw'. The bytes
|
|
|
|
// are assumed to be serialized version of the private key.
|
2019-11-05 21:42:25 +01:00
|
|
|
func MakePrivateKey(yml []byte, pw []byte) (*PrivateKey, error) {
|
2018-10-18 17:10:29 +09:00
|
|
|
var ssk serializedPrivKey
|
|
|
|
|
|
|
|
err := yaml.Unmarshal(yml, &ssk)
|
|
|
|
if err != nil {
|
2020-01-08 09:17:54 -08:00
|
|
|
return nil, fmt.Errorf("make priv key: can't parse YAML: %s", err)
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
b64 := base64.StdEncoding.DecodeString
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
salt, err := b64(ssk.Salt)
|
2018-10-18 17:10:29 +09:00
|
|
|
if err != nil {
|
2020-01-08 09:17:54 -08:00
|
|
|
return nil, fmt.Errorf("make priv key: can't decode salt: %s", err)
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
esk, err := b64(ssk.Esk)
|
2018-10-18 17:10:29 +09:00
|
|
|
if err != nil {
|
2020-01-08 09:17:54 -08:00
|
|
|
return nil, fmt.Errorf("make priv key: can't decode key: %s", err)
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// We take short passwords and extend them
|
2019-11-05 21:42:25 +01:00
|
|
|
pwb := sha512.Sum512(pw)
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
// "32" == Length of AES-256 key
|
|
|
|
key, err := scrypt.Key(pwb[:], salt, ssk.N, ssk.R, ssk.P, 32)
|
2018-10-18 17:10:29 +09:00
|
|
|
if err != nil {
|
2020-01-08 09:17:54 -08:00
|
|
|
return nil, fmt.Errorf("make priv key: can't derive key: %s", err)
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
aes, err := aes.NewCipher(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("make priv key: aes failure: %s", err)
|
|
|
|
}
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
ae, err := cipher.NewGCM(aes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("make priv key: aes failure: %s", err)
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
skb, err := ae.Open(nil, salt[:ae.NonceSize()], esk, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("make priv key: wrong password")
|
2019-10-17 14:29:01 -07:00
|
|
|
}
|
|
|
|
|
2019-10-19 14:58:07 -07:00
|
|
|
return PrivateKeyFromBytes(skb)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make a private key from 64-bytes of extended Ed25519 key
|
2019-11-05 21:42:25 +01:00
|
|
|
func PrivateKeyFromBytes(buf []byte) (*PrivateKey, error) {
|
|
|
|
if len(buf) != 64 {
|
|
|
|
return nil, fmt.Errorf("private key is malformed (len %d!)", len(buf))
|
2019-10-19 14:58:07 -07:00
|
|
|
}
|
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
skb := make([]byte, 64)
|
|
|
|
copy(skb, buf)
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
edsk := Ed.PrivateKey(skb)
|
|
|
|
edpk := edsk.Public().(Ed.PublicKey)
|
|
|
|
|
|
|
|
pk := &PublicKey{
|
|
|
|
Pk: []byte(edpk),
|
|
|
|
hash: pkhash([]byte(edpk)),
|
|
|
|
}
|
|
|
|
sk := &PrivateKey{
|
|
|
|
Sk: skb,
|
|
|
|
pk: pk,
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
return sk, nil
|
|
|
|
}
|
|
|
|
|
2019-10-09 14:52:34 -07:00
|
|
|
// Given a secret key, return the corresponding Public Key
|
|
|
|
func (sk *PrivateKey) PublicKey() *PublicKey {
|
|
|
|
return sk.pk
|
|
|
|
}
|
|
|
|
|
|
|
|
// Public Key Hash
|
|
|
|
func (pk *PublicKey) Hash() []byte {
|
|
|
|
return pk.hash
|
|
|
|
}
|
|
|
|
|
2018-10-18 17:10:29 +09:00
|
|
|
// Serialize the private key to a file
|
2020-01-08 09:17:54 -08:00
|
|
|
// AEAD encryption for protecting the private key
|
2018-10-18 17:10:29 +09:00
|
|
|
// Format: YAML
|
|
|
|
// All []byte are in base64 (RawEncoding)
|
2019-11-05 21:42:25 +01:00
|
|
|
func (sk *PrivateKey) serialize(fn, comment string, getpw func() ([]byte, error)) error {
|
|
|
|
pw, err := getpw()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
// expand the password into 64 bytes
|
2020-01-08 09:17:54 -08:00
|
|
|
pass := sha512.Sum512(pw)
|
|
|
|
salt := make([]byte, 32)
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
randread(salt)
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
// "32" == Length of AES-256 key
|
|
|
|
key, err := scrypt.Key(pass[:], salt, _N, _r, _p, 32)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("marshal: can't derive scrypt key: %s", err)
|
|
|
|
}
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
aes, err := aes.NewCipher(key)
|
2018-10-18 17:10:29 +09:00
|
|
|
if err != nil {
|
2020-01-08 09:17:54 -08:00
|
|
|
return fmt.Errorf("marshal: %s", err)
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
ae, err := cipher.NewGCM(aes)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("marshal: %s", err)
|
|
|
|
}
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
tl := ae.Overhead()
|
|
|
|
buf := make([]byte, tl+len(sk.Sk))
|
|
|
|
esk := ae.Seal(buf[:0], salt[:ae.NonceSize()], sk.Sk, nil)
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
enc := base64.StdEncoding.EncodeToString
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
ssk := serializedPrivKey{
|
|
|
|
Comment: comment,
|
|
|
|
Esk: enc(esk),
|
|
|
|
Salt: enc(salt),
|
|
|
|
Algo: sk_algo,
|
|
|
|
N: _N,
|
|
|
|
R: _r,
|
|
|
|
P: _p,
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
// We won't protect the Scrypt parameters with the hash above
|
|
|
|
// because it is not needed. If the parameters are wrong, the
|
|
|
|
// derived key will be wrong and thus, the hash will not match.
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
out, err := yaml.Marshal(&ssk)
|
2018-10-18 17:10:29 +09:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("can't marahal to YAML: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return writeFile(fn, out, 0600)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sign a prehashed Message; return the signature as opaque bytes
|
|
|
|
// Signature is an YAML file:
|
|
|
|
// Comment: source file path
|
|
|
|
// Signature: Ed25519 signature
|
|
|
|
func (sk *PrivateKey) SignMessage(ck []byte, comment string) (*Signature, error) {
|
|
|
|
x := Ed.PrivateKey(sk.Sk)
|
|
|
|
|
|
|
|
sig, err := x.Sign(rand.Reader, ck, crypto.Hash(0))
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("can't sign %x: %s", ck, err)
|
|
|
|
}
|
|
|
|
|
2019-10-19 14:42:19 -07:00
|
|
|
ss := &Signature{
|
2019-10-09 14:52:34 -07:00
|
|
|
Sig: sig,
|
2019-10-19 14:42:19 -07:00
|
|
|
pkhash: make([]byte, len(sk.pk.hash)),
|
|
|
|
}
|
|
|
|
|
|
|
|
copy(ss.pkhash, sk.pk.hash)
|
|
|
|
return ss, nil
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// Read and sign a file
|
|
|
|
//
|
|
|
|
// We calculate the signature differently here: We first calculate
|
|
|
|
// the SHA-512 checksum of the file and its size. We sign the
|
|
|
|
// checksum.
|
|
|
|
func (sk *PrivateKey) SignFile(fn string) (*Signature, error) {
|
|
|
|
|
|
|
|
ck, err := fileCksum(fn, sha512.New())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return sk.SignMessage(ck, fn)
|
|
|
|
}
|
|
|
|
|
|
|
|
// -- Signature Methods --
|
|
|
|
|
|
|
|
// Read serialized signature from file 'fn' and construct a
|
|
|
|
// Signature object
|
|
|
|
func ReadSignature(fn string) (*Signature, error) {
|
|
|
|
yml, err := ioutil.ReadFile(fn)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return MakeSignature(yml)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse serialized signature from bytes 'b' and construct a
|
|
|
|
// Signature object
|
|
|
|
func MakeSignature(b []byte) (*Signature, error) {
|
|
|
|
var ss signature
|
|
|
|
err := yaml.Unmarshal(b, &ss)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("can't parse YAML signature: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
b64 := base64.StdEncoding.DecodeString
|
|
|
|
|
|
|
|
s, err := b64(ss.Signature)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("can't decode Base64:Signature <%s>: %s", ss.Signature, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
p, err := b64(ss.Pkhash)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("can't decode Base64:Pkhash <%s>: %s", ss.Pkhash, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Signature{Sig: s, pkhash: p}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Serialize a signature suitable for storing in durable media
|
|
|
|
func (sig *Signature) Serialize(comment string) ([]byte, error) {
|
|
|
|
|
|
|
|
sigs := base64.StdEncoding.EncodeToString(sig.Sig)
|
|
|
|
pks := base64.StdEncoding.EncodeToString(sig.pkhash)
|
|
|
|
ss := &signature{Comment: comment, Pkhash: pks, Signature: sigs}
|
|
|
|
|
|
|
|
out, err := yaml.Marshal(ss)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("can't marshal signature of %x to YAML: %s", sig.Sig, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SerializeFile serializes the signature to an output file 'f'
|
|
|
|
func (sig *Signature) SerializeFile(fn, comment string) error {
|
|
|
|
b, err := sig.Serialize(comment)
|
|
|
|
if err == nil {
|
|
|
|
err = writeFile(fn, b, 0644)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsPKMatch returns true if public key 'pk' can potentially validate
|
|
|
|
// the signature. It does this by comparing the hash of 'pk' against
|
|
|
|
// 'Pkhash' of 'sig'.
|
|
|
|
func (sig *Signature) IsPKMatch(pk *PublicKey) bool {
|
2019-10-09 14:52:34 -07:00
|
|
|
return subtle.ConstantTimeCompare(pk.hash, sig.pkhash) == 1
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// --- Public Key Methods ---
|
|
|
|
|
|
|
|
// Read the public key from 'fn' and create new instance of
|
|
|
|
// PublicKey
|
|
|
|
func ReadPublicKey(fn string) (*PublicKey, error) {
|
|
|
|
var err error
|
|
|
|
var yml []byte
|
|
|
|
|
|
|
|
if yml, err = ioutil.ReadFile(fn); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
// first try to parse as a ssh key
|
|
|
|
pk, err := parseSSHPublicKey(yml)
|
|
|
|
if err != nil {
|
|
|
|
pk, err = MakePublicKey(yml)
|
|
|
|
}
|
|
|
|
return pk, err
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse a serialized public in 'yml' and return the resulting
|
|
|
|
// public key instance
|
|
|
|
func MakePublicKey(yml []byte) (*PublicKey, error) {
|
|
|
|
var spk serializedPubKey
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if err = yaml.Unmarshal(yml, &spk); err != nil {
|
|
|
|
return nil, fmt.Errorf("can't parse YAML: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
b64 := base64.StdEncoding.DecodeString
|
2019-11-05 21:42:25 +01:00
|
|
|
var pkb []byte
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
if pkb, err = b64(spk.Pk); err != nil {
|
2018-10-18 17:10:29 +09:00
|
|
|
return nil, fmt.Errorf("can't decode YAML:Pk: %s", err)
|
|
|
|
}
|
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
if pk, err := PublicKeyFromBytes(pkb); err == nil {
|
|
|
|
pk.Comment = spk.Comment
|
|
|
|
return pk, nil
|
|
|
|
}
|
|
|
|
return nil, err
|
2019-10-19 14:58:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make a public key from a byte string
|
|
|
|
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
|
|
|
|
if len(b) != 32 {
|
|
|
|
return nil, fmt.Errorf("public key is malformed (len %d!)", len(b))
|
2019-10-09 14:52:34 -07:00
|
|
|
}
|
|
|
|
|
2019-10-19 14:58:07 -07:00
|
|
|
pk := &PublicKey{
|
2019-10-19 21:12:57 -05:00
|
|
|
Pk: make([]byte, 32),
|
2019-10-19 14:58:07 -07:00
|
|
|
hash: pkhash(b),
|
|
|
|
}
|
2019-10-09 14:52:34 -07:00
|
|
|
|
2019-10-19 14:58:07 -07:00
|
|
|
copy(pk.Pk, b)
|
2018-10-18 17:10:29 +09:00
|
|
|
return pk, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Serialize Public Keys
|
|
|
|
func (pk *PublicKey) serialize(fn, comment string) error {
|
|
|
|
b64 := base64.StdEncoding.EncodeToString
|
2019-10-09 14:52:34 -07:00
|
|
|
spk := &serializedPubKey{
|
|
|
|
Comment: comment,
|
|
|
|
Pk: b64(pk.Pk),
|
|
|
|
Hash: b64(pk.hash),
|
|
|
|
}
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
out, err := yaml.Marshal(spk)
|
|
|
|
if err != nil {
|
2019-10-09 14:52:34 -07:00
|
|
|
return fmt.Errorf("can't marahal to YAML: %s", err)
|
2018-10-18 17:10:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
return writeFile(fn, out, 0644)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify a signature 'sig' for file 'fn' against public key 'pk'
|
|
|
|
// Return True if signature matches, False otherwise
|
|
|
|
func (pk *PublicKey) VerifyFile(fn string, sig *Signature) (bool, error) {
|
|
|
|
|
|
|
|
ck, err := fileCksum(fn, sha512.New())
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return pk.VerifyMessage(ck, sig)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify a signature 'sig' for a pre-calculated checksum 'ck' against public key 'pk'
|
|
|
|
// Return True if signature matches, False otherwise
|
|
|
|
func (pk *PublicKey) VerifyMessage(ck []byte, sig *Signature) (bool, error) {
|
|
|
|
|
|
|
|
x := Ed.PublicKey(pk.Pk)
|
|
|
|
return Ed.Verify(x, ck, sig.Sig), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// -- Internal Utility Functions --
|
|
|
|
|
|
|
|
// Unlink a file.
|
|
|
|
func unlink(f string) {
|
|
|
|
st, err := os.Stat(f)
|
|
|
|
if err == nil {
|
|
|
|
if !st.Mode().IsRegular() {
|
|
|
|
panic(fmt.Sprintf("%s can't be unlinked. Not a regular file?", f))
|
|
|
|
}
|
|
|
|
|
|
|
|
os.Remove(f)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Simple function to reliably write data to a file.
|
|
|
|
// Does MORE than ioutil.WriteFile() - in that it doesn't trash the
|
|
|
|
// existing file with an incomplete write.
|
|
|
|
func writeFile(fn string, b []byte, mode uint32) error {
|
|
|
|
tmp := fmt.Sprintf("%s.tmp", fn)
|
|
|
|
unlink(tmp)
|
|
|
|
|
|
|
|
fd, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(mode))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Can't create file %s: %s", tmp, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = fd.Write(b)
|
|
|
|
if err != nil {
|
|
|
|
fd.Close()
|
|
|
|
// XXX Do we delete the tmp file?
|
|
|
|
return fmt.Errorf("Can't write %v bytes to %s: %s", len(b), tmp, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fd.Close() // we ignore close(2) errors; unrecoverable anyway.
|
|
|
|
|
|
|
|
os.Rename(tmp, fn)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate file checksum out of hash function h
|
|
|
|
func fileCksum(fn string, h hash.Hash) ([]byte, error) {
|
|
|
|
|
|
|
|
fd, err := os.Open(fn)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("can't open %s: %s", fn, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer fd.Close()
|
|
|
|
|
|
|
|
sz, err := utils.MmapReader(fd, 0, 0, h)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var b [8]byte
|
|
|
|
binary.BigEndian.PutUint64(b[:], uint64(sz))
|
|
|
|
h.Write(b[:])
|
|
|
|
|
|
|
|
return h.Sum(nil), nil
|
|
|
|
}
|
|
|
|
|
2019-10-09 14:52:34 -07:00
|
|
|
func randread(b []byte) []byte {
|
|
|
|
_, err := io.ReadFull(rand.Reader, b)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("can't read %d bytes of random data: %s", len(b), err))
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2018-10-18 17:10:29 +09:00
|
|
|
// EOF
|
|
|
|
// vim: noexpandtab:ts=8:sw=8:tw=92:
|