66 lines
2.2 KiB
Go
66 lines
2.2 KiB
Go
package wire
|
|
|
|
import (
|
|
"crypto/ecdh"
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// Ephemeral is a one-use X25519 key pair.
|
|
//
|
|
// One use, enforced by not exposing the private half after the exchange: the caller gets the derived
|
|
// key material, never the key, so there is nothing to reuse by accident later.
|
|
type Ephemeral struct {
|
|
private []byte
|
|
public []byte
|
|
}
|
|
|
|
// ErrEphemeralExhausted is returned when the system RNG cannot supply bytes. It is fatal for the
|
|
// request and never retried: a PRNG failure means the process cannot be trusted to produce secrets.
|
|
var ErrEphemeralExhausted = errors.New("wire: cannot read random bytes for an ephemeral key")
|
|
|
|
// NewEphemeral generates a key pair for exactly one exchange.
|
|
func NewEphemeral() (Ephemeral, error) {
|
|
private := make([]byte, KeyBytes)
|
|
if _, err := rand.Read(private); err != nil {
|
|
return Ephemeral{}, ErrEphemeralExhausted
|
|
}
|
|
public, err := PublicFrom(private)
|
|
if err != nil {
|
|
return Ephemeral{}, err
|
|
}
|
|
return Ephemeral{private: private, public: public}, nil
|
|
}
|
|
|
|
// PublicFrom derives the X25519 public key for a private key.
|
|
//
|
|
// Exported because both sides of a test need it, and because a caller that already holds a private
|
|
// key should not have to build a throwaway Ephemeral just to learn its own public half.
|
|
func PublicFrom(private []byte) ([]byte, error) {
|
|
key, err := ecdh.X25519().NewPrivateKey(private)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: private key rejected: %v", ErrBadKey, err)
|
|
}
|
|
return key.PublicKey().Bytes(), nil
|
|
}
|
|
|
|
// Public is what goes in the envelope's ek field, signed along with everything else.
|
|
func (e Ephemeral) Public() []byte { return e.public }
|
|
|
|
// Exchange performs X25519 against a peer public key and discards the private half.
|
|
//
|
|
// Zeroing is not a substitute for a proper memory barrier and does not defend against a process
|
|
// dump, but it costs one line and removes the private key from the heap at the earliest point the
|
|
// protocol no longer needs it.
|
|
func (e Ephemeral) Exchange(peerPublic []byte) ([]byte, error) {
|
|
shared, err := SharedSecret(e.private, peerPublic)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for i := range e.private {
|
|
e.private[i] = 0
|
|
}
|
|
return shared, nil
|
|
}
|