Files

96 lines
2.8 KiB
Go

package webingress
import (
"context"
"crypto/ed25519"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"strings"
"time"
)
var ErrFabricEnvelopeSigningKeyInvalid = errors.New("web ingress fabric envelope signing key invalid")
type Ed25519EnvelopeSigner struct {
PrivateKey ed25519.PrivateKey
KeyID string
Now func() time.Time
}
func NewEd25519EnvelopeSigner(privateKeyB64, keyID string) (Ed25519EnvelopeSigner, error) {
privateKey, err := decodeEd25519PrivateKey(privateKeyB64)
if err != nil {
return Ed25519EnvelopeSigner{}, err
}
keyID = strings.TrimSpace(keyID)
if keyID == "" {
publicKey, ok := privateKey.Public().(ed25519.PublicKey)
if !ok {
return Ed25519EnvelopeSigner{}, ErrFabricEnvelopeSigningKeyInvalid
}
keyID = ed25519EnvelopeKeyID(publicKey)
}
return Ed25519EnvelopeSigner{PrivateKey: privateKey, KeyID: keyID}, nil
}
func (s Ed25519EnvelopeSigner) Sign(_ context.Context, canonical []byte) (FabricEnvelopeSignature, error) {
if len(s.PrivateKey) != ed25519.PrivateKeySize {
return FabricEnvelopeSignature{}, ErrFabricEnvelopeSigningKeyInvalid
}
if len(canonical) == 0 {
return FabricEnvelopeSignature{}, fmt.Errorf("%w: canonical envelope empty", ErrFabricEnvelopeSigningKeyInvalid)
}
keyID := strings.TrimSpace(s.KeyID)
if keyID == "" {
publicKey, ok := s.PrivateKey.Public().(ed25519.PublicKey)
if !ok {
return FabricEnvelopeSignature{}, ErrFabricEnvelopeSigningKeyInvalid
}
keyID = ed25519EnvelopeKeyID(publicKey)
}
now := time.Now().UTC()
if s.Now != nil {
now = s.Now().UTC()
}
return FabricEnvelopeSignature{
KeyID: keyID,
Alg: "ed25519",
Signature: base64.StdEncoding.EncodeToString(ed25519.Sign(s.PrivateKey, canonical)),
SignedAt: now.Format(time.RFC3339Nano),
}, nil
}
func decodeEd25519PrivateKey(value string) (ed25519.PrivateKey, error) {
decoded, err := decodeEnvelopeBase64(strings.TrimSpace(value))
if err != nil {
return nil, fmt.Errorf("%w: private key must be base64 encoded", ErrFabricEnvelopeSigningKeyInvalid)
}
if len(decoded) != ed25519.PrivateKeySize {
return nil, fmt.Errorf("%w: private key must decode to %d bytes", ErrFabricEnvelopeSigningKeyInvalid, ed25519.PrivateKeySize)
}
return ed25519.PrivateKey(decoded), nil
}
func decodeEnvelopeBase64(value string) ([]byte, error) {
if value == "" {
return nil, errors.New("empty base64 value")
}
decoded, err := base64.StdEncoding.DecodeString(value)
if err == nil {
return decoded, nil
}
decoded, err = base64.RawStdEncoding.DecodeString(value)
if err == nil {
return decoded, nil
}
return base64.RawURLEncoding.DecodeString(value)
}
func ed25519EnvelopeKeyID(publicKey ed25519.PublicKey) string {
sum := sha256.Sum256(publicKey)
return "rap-node-ed25519-" + hex.EncodeToString(sum[:16])
}