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 }