package nebula import ( "bytes" "crypto" "crypto/ed25519" "crypto/rand" "encoding/json" "errors" "fmt" "github.com/slackhq/nebula/cert" ) // ErrInvalidSignature is returned from functions when a signature validation // fails. var ErrInvalidSignature = errors.New("invalid signature") func signCert(c *cert.NebulaCertificate, k SigningPrivateKey) error { return c.Sign(ed25519.PrivateKey(k)) } // Signed wraps an arbitrary value with a signature which was generated using a // SigningPrivateKey. It can be JSON (un)marshaled while preserving all of its // properties. type Signed[T any] json.RawMessage type signed[T any] struct { Signature []byte Body json.RawMessage } // Sign will generate a Signed of the given value by first JSON marshaling it, // and then signing the resulting bytes. func Sign[T any](v T, k SigningPrivateKey) (Signed[T], error) { var res Signed[T] b, err := json.Marshal(v) if err != nil { return res, fmt.Errorf("json marshaling: %w", err) } sig, err := ed25519.PrivateKey(k).Sign(rand.Reader, b, crypto.Hash(0)) if err != nil { return res, fmt.Errorf("generating signature: %w", err) } return json.Marshal(signed[T]{Signature: sig, Body: json.RawMessage(b)}) } var jsonNull = []byte("null") // MarshalJSON implements the json.Marshaler interface. func (s Signed[T]) MarshalJSON() ([]byte, error) { if s == nil { return jsonNull, nil } return []byte(s), nil } // UnmarshalJSON implements the json.Unmarshaler interface. func (s *Signed[T]) UnmarshalJSON(b []byte) error { if bytes.Equal(b, jsonNull) { *s = nil return nil } *s = b return nil } // Unwrap produces the original value which Sign was called on, or returns // ErrInvalidSignature if the signature is not valid for the value and public // key. func (s Signed[T]) Unwrap(pubK SigningPublicKey) (T, error) { var ( res T into signed[T] ) if err := json.Unmarshal(s, &into); err != nil { return res, fmt.Errorf("json unmarshaling outer Signed: %w", err) } if !ed25519.Verify( ed25519.PublicKey(pubK), []byte(into.Body), into.Signature, ) { return res, ErrInvalidSignature } if err := json.Unmarshal(into.Body, &res); err != nil { return res, fmt.Errorf("json unmarshaling: %w", err) } return res, nil } // UnwrapUnsafe is like Unwrap, but it will not check the signature. func (s Signed[T]) UnwrapUnsafe() (T, error) { var ( res T into signed[T] ) if err := json.Unmarshal(s, &into); err != nil { return res, fmt.Errorf("json unmarshaling outer Signed: %w", err) } if err := json.Unmarshal(into.Body, &res); err != nil { return res, fmt.Errorf("json unmarshaling: %w", err) } return res, nil }