// Copyright 2026 Alibaba Group // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build darwin || linux package keychain import ( "crypto/cipher" "crypto/rand" "fmt" "os" "path/filepath" "github.com/google/uuid" ) type keychainGCMFactory func(cipher.Block) (cipher.AEAD, error) var ( keychainMkdirAll = os.MkdirAll keychainWriteFile = os.WriteFile keychainUserHomeDir = os.UserHomeDir keychainRandRead = rand.Read ) // fileDEK retrieves or generates a Data Encryption Key stored as a plain // file under the platform storage directory. Shared by Linux (default) and // the macOS sandbox fallback path (DWS_DISABLE_KEYCHAIN=1). func fileDEK(service string) ([]byte, error) { dir := StorageDir(service) keyPath := filepath.Join(dir, "dek") key, err := keychainReadFile(keyPath) if err == nil && len(key) == dekBytes { return key, nil } if err := keychainMkdirAll(dir, 0700); err != nil { return nil, fmt.Errorf("create keychain dir: %w", err) } key = make([]byte, dekBytes) if _, err := keychainRandRead(key); err != nil { return nil, fmt.Errorf("generate dek: %w", err) } tmpKeyPath := filepath.Join(dir, "dek."+uuid.New().String()+".tmp") defer keychainRemove(tmpKeyPath) if err := keychainWriteFile(tmpKeyPath, key, 0600); err != nil { return nil, fmt.Errorf("write dek: %w", err) } if err := keychainRename(tmpKeyPath, keyPath); err != nil { // If rename fails, another process might have created it. Try reading again. existingKey, readErr := keychainReadFile(keyPath) if readErr == nil && len(existingKey) == dekBytes { return existingKey, nil } return nil, fmt.Errorf("save dek: %w", err) } return key, nil } func fileDEKReadOnly(service string) ([]byte, error) { keyPath := filepath.Join(StorageDir(service), "dek") key, err := keychainReadFile(keyPath) if err == nil && len(key) == dekBytes { return key, nil } if err != nil { if os.IsNotExist(err) { return nil, ErrDEKMissing } return nil, fmt.Errorf("read dek: %w", err) } return nil, fmt.Errorf("read dek: %w", ErrDEKMissing) }