Update utility modules: - File utilities with secure file operations - Environment pool with resource tracking - Error types with scheduler error categories - Logging with audit context support - Network/SSH with connection pooling - Privacy/PII handling with tenant boundaries - Resource manager with scheduler allocation - Security monitor with audit integration - Tracking plugins (MLflow, TensorBoard) with auth - Crypto signing with tenant keys - Database init with multi-user support
184 lines
5.4 KiB
Go
184 lines
5.4 KiB
Go
// Package crypto provides cryptographic utilities for FetchML
|
|
package crypto
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/jfraeys/fetch_ml/internal/fileutil"
|
|
)
|
|
|
|
// ManifestSigner provides Ed25519 signing for run manifests
|
|
type ManifestSigner struct {
|
|
keyID string
|
|
privateKey ed25519.PrivateKey
|
|
publicKey ed25519.PublicKey
|
|
}
|
|
|
|
// SigningResult contains the signature and metadata
|
|
type SigningResult struct {
|
|
Signature string `json:"signature"`
|
|
KeyID string `json:"key_id"`
|
|
Algorithm string `json:"algorithm"`
|
|
}
|
|
|
|
// GenerateSigningKeys creates a new Ed25519 keypair for manifest signing
|
|
// This should be done once and the keys stored securely
|
|
func GenerateSigningKeys() (publicKey, privateKey []byte, err error) {
|
|
return ed25519.GenerateKey(rand.Reader)
|
|
}
|
|
|
|
// NewManifestSigner creates a signer from a private key
|
|
func NewManifestSigner(privateKey []byte, keyID string) (*ManifestSigner, error) {
|
|
if len(privateKey) != ed25519.PrivateKeySize {
|
|
return nil, fmt.Errorf("invalid private key size: expected %d, got %d",
|
|
ed25519.PrivateKeySize, len(privateKey))
|
|
}
|
|
|
|
// Extract public key from private key
|
|
pubKey := make([]byte, ed25519.PublicKeySize)
|
|
copy(pubKey, privateKey[32:])
|
|
|
|
return &ManifestSigner{
|
|
privateKey: ed25519.PrivateKey(privateKey),
|
|
publicKey: ed25519.PublicKey(pubKey),
|
|
keyID: keyID,
|
|
}, nil
|
|
}
|
|
|
|
// SignManifest signs a manifest and returns the signing result
|
|
// The manifest is canonicalized to JSON before signing
|
|
func (s *ManifestSigner) SignManifest(manifest any) (*SigningResult, error) {
|
|
// Marshal to canonical JSON (sorted keys)
|
|
data, err := json.Marshal(manifest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal manifest: %w", err)
|
|
}
|
|
|
|
// Sign the data
|
|
signature := ed25519.Sign(s.privateKey, data)
|
|
|
|
return &SigningResult{
|
|
Signature: base64.StdEncoding.EncodeToString(signature),
|
|
KeyID: s.keyID,
|
|
Algorithm: "Ed25519",
|
|
}, nil
|
|
}
|
|
|
|
// SignManifestBytes signs raw bytes directly (for pre-serialized manifests)
|
|
func (s *ManifestSigner) SignManifestBytes(data []byte) (*SigningResult, error) {
|
|
signature := ed25519.Sign(s.privateKey, data)
|
|
|
|
return &SigningResult{
|
|
Signature: base64.StdEncoding.EncodeToString(signature),
|
|
KeyID: s.keyID,
|
|
Algorithm: "Ed25519",
|
|
}, nil
|
|
}
|
|
|
|
// VerifyManifest verifies a manifest signature
|
|
func VerifyManifest(manifest any, result *SigningResult, publicKey []byte) (bool, error) {
|
|
if result.Algorithm != "Ed25519" {
|
|
return false, fmt.Errorf("unsupported algorithm: %s", result.Algorithm)
|
|
}
|
|
|
|
// Marshal manifest to same format used for signing
|
|
data, err := json.Marshal(manifest)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to marshal manifest: %w", err)
|
|
}
|
|
|
|
// Decode signature
|
|
signature, err := base64.StdEncoding.DecodeString(result.Signature)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to decode signature: %w", err)
|
|
}
|
|
|
|
// Verify
|
|
return ed25519.Verify(ed25519.PublicKey(publicKey), data, signature), nil
|
|
}
|
|
|
|
// VerifyManifestBytes verifies raw bytes directly
|
|
func VerifyManifestBytes(data []byte, result *SigningResult, publicKey []byte) (bool, error) {
|
|
if result.Algorithm != "Ed25519" {
|
|
return false, fmt.Errorf("unsupported algorithm: %s", result.Algorithm)
|
|
}
|
|
|
|
signature, err := base64.StdEncoding.DecodeString(result.Signature)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to decode signature: %w", err)
|
|
}
|
|
|
|
return ed25519.Verify(ed25519.PublicKey(publicKey), data, signature), nil
|
|
}
|
|
|
|
// GetPublicKey returns the signer's public key
|
|
func (s *ManifestSigner) GetPublicKey() []byte {
|
|
return s.publicKey
|
|
}
|
|
|
|
// GetKeyID returns the signer's key ID
|
|
func (s *ManifestSigner) GetKeyID() string {
|
|
return s.keyID
|
|
}
|
|
|
|
// SavePrivateKeyToFile saves a private key to a file with restricted permissions and crash safety (fsync)
|
|
func SavePrivateKeyToFile(key []byte, path string) error {
|
|
// Write with restricted permissions (owner read/write only) and fsync
|
|
if err := fileutil.WriteFileSafe(path, key, 0600); err != nil {
|
|
return fmt.Errorf("failed to write private key: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// LoadPrivateKeyFromFile loads a private key from a file
|
|
func LoadPrivateKeyFromFile(path string) ([]byte, error) {
|
|
key, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read private key: %w", err)
|
|
}
|
|
|
|
if len(key) != ed25519.PrivateKeySize {
|
|
return nil, fmt.Errorf("invalid private key size: expected %d, got %d",
|
|
ed25519.PrivateKeySize, len(key))
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
// SavePublicKeyToFile saves a public key to a file with crash safety (fsync)
|
|
func SavePublicKeyToFile(key []byte, path string) error {
|
|
if err := fileutil.WriteFileSafe(path, key, 0644); err != nil {
|
|
return fmt.Errorf("failed to write public key: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// LoadPublicKeyFromFile loads a public key from a file
|
|
func LoadPublicKeyFromFile(path string) ([]byte, error) {
|
|
key, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read public key: %w", err)
|
|
}
|
|
|
|
if len(key) != ed25519.PublicKeySize {
|
|
return nil, fmt.Errorf("invalid public key size: expected %d, got %d",
|
|
ed25519.PublicKeySize, len(key))
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
// EncodeKeyToBase64 encodes a key to base64 for storage/transmission
|
|
func EncodeKeyToBase64(key []byte) string {
|
|
return base64.StdEncoding.EncodeToString(key)
|
|
}
|
|
|
|
// DecodeKeyFromBase64 decodes a key from base64
|
|
func DecodeKeyFromBase64(encoded string) ([]byte, error) {
|
|
return base64.StdEncoding.DecodeString(encoded)
|
|
}
|