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]) }