isle/go/nebula/signed.go

115 lines
2.6 KiB
Go

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
}