Files
msa-agent/internal/wire/ephemeral.go
T

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
}