96 lines
2.8 KiB
Go
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])
|
|
}
|