package main import ( "context" "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "encoding/hex" "errors" "io" "os" "path/filepath" "git.ipao.vip/rogee/creator-hub/internal/phasea" ) const credentialFileVersion byte = 1 type persistentCredentialBridge struct { directory string key [32]byte chmod func(*os.File, os.FileMode) error syncDirectory func(string) error } func newPersistentCredentialBridge(directory string, key []byte) (*persistentCredentialBridge, error) { if !filepath.IsAbs(directory) || len(key) != 32 { return nil, errors.New("invalid credential provider configuration") } if err := os.MkdirAll(directory, 0o700); err != nil || os.Chmod(directory, 0o700) != nil { return nil, errors.New("initialize credential provider") } bridge := &persistentCredentialBridge{ directory: directory, chmod: (*os.File).Chmod, syncDirectory: syncCredentialDirectory, } copy(bridge.key[:], key) return bridge, nil } func (bridge *persistentCredentialBridge) Store(ctx context.Context, reference phasea.CredentialReference, key, value string) error { if err := ctx.Err(); err != nil { return err } if !validCredentialReference(reference.Provider, key) || value == "" || len(value) > 64<<10 { return errors.New("invalid credential") } aead, err := bridge.aead() if err != nil { return err } nonce := make([]byte, aead.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return errors.New("encrypt credential") } payload := append([]byte{credentialFileVersion}, nonce...) payload = aead.Seal(payload, nonce, []byte(value), credentialAAD(reference.Provider, key)) temporary, err := os.CreateTemp(bridge.directory, ".credential-*") if err != nil { return errors.New("store credential") } temporaryName := temporary.Name() defer os.Remove(temporaryName) if err = bridge.chmod(temporary, 0o600); err == nil { _, err = temporary.Write(payload) } if err == nil { err = temporary.Sync() } if closeErr := temporary.Close(); err == nil { err = closeErr } if err != nil { return errors.New("store credential") } target := bridge.path(reference.Provider, key) if os.Rename(temporaryName, target) != nil { return errors.New("store credential") } if bridge.syncDirectory(bridge.directory) != nil { cleanupErr := os.Remove(target) if cleanupErr == nil { cleanupErr = bridge.syncDirectory(bridge.directory) } if cleanupErr != nil { return errors.Join(errors.New("store credential"), errors.New("delete incomplete credential")) } return errors.New("store credential") } return nil } func (bridge *persistentCredentialBridge) Delete(ctx context.Context, reference phasea.CredentialReference, key string) error { if err := ctx.Err(); err != nil { return err } if !validCredentialReference(reference.Provider, key) { return errors.New("invalid credential reference") } if err := os.Remove(bridge.path(reference.Provider, key)); err != nil && !errors.Is(err, os.ErrNotExist) { return errors.New("delete credential") } if err := bridge.syncDirectory(bridge.directory); err != nil { return errors.New("delete credential") } return nil } func (bridge *persistentCredentialBridge) Resolve(ctx context.Context, reference phasea.CredentialReference, key string) ([]byte, error) { if err := ctx.Err(); err != nil { return nil, err } if !validCredentialReference(reference.Provider, key) { return nil, errors.New("invalid credential reference") } payload, err := os.ReadFile(bridge.path(reference.Provider, key)) if err != nil { return nil, errors.New("resolve credential") } aead, err := bridge.aead() if err != nil { return nil, err } if len(payload) < 1+aead.NonceSize() || payload[0] != credentialFileVersion { return nil, errors.New("invalid credential") } nonceEnd := 1 + aead.NonceSize() value, err := aead.Open(nil, payload[1:nonceEnd], payload[nonceEnd:], credentialAAD(reference.Provider, key)) if err != nil { return nil, errors.New("resolve credential") } return value, nil } func (bridge *persistentCredentialBridge) aead() (cipher.AEAD, error) { block, err := aes.NewCipher(bridge.key[:]) if err != nil { return nil, errors.New("initialize credential encryption") } return cipher.NewGCM(block) } func (bridge *persistentCredentialBridge) path(provider, key string) string { digest := sha256.Sum256(credentialAAD(provider, key)) return filepath.Join(bridge.directory, hex.EncodeToString(digest[:])+".credential") } func credentialAAD(provider, key string) []byte { return []byte(provider + "\x00" + key) } func validCredentialReference(provider, key string) bool { return (provider == "os_keyring" || provider == "secret_manager") && key != "" && len(key) <= 192 } func syncCredentialDirectory(path string) error { directory, err := os.Open(path) if err != nil { return err } defer directory.Close() return directory.Sync() }