Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
181 lines (148 sloc)
3.53 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package keyring | |
import ( | |
"encoding/json" | |
"fmt" | |
"io/ioutil" | |
"os" | |
"path/filepath" | |
"time" | |
jose "github.com/dvsekhvalnov/jose2go" | |
"github.com/mtibben/percent" | |
) | |
func init() { | |
supportedBackends[FileBackend] = opener(func(cfg Config) (Keyring, error) { | |
return &fileKeyring{ | |
dir: cfg.FileDir, | |
passwordFunc: cfg.FilePasswordFunc, | |
}, nil | |
}) | |
} | |
var filenameEscape = func(s string) string { | |
return percent.Encode(s, "/") | |
} | |
var filenameUnescape = percent.Decode | |
type fileKeyring struct { | |
dir string | |
passwordFunc PromptFunc | |
password string | |
} | |
func (k *fileKeyring) resolveDir() (string, error) { | |
if k.dir == "" { | |
return "", fmt.Errorf("No directory provided for file keyring") | |
} | |
dir, err := ExpandTilde(k.dir) | |
if err != nil { | |
return "", err | |
} | |
stat, err := os.Stat(dir) | |
if os.IsNotExist(err) { | |
err = os.MkdirAll(dir, 0700) | |
} else if err != nil && !stat.IsDir() { | |
err = fmt.Errorf("%s is a file, not a directory", dir) | |
} | |
return dir, err | |
} | |
func (k *fileKeyring) unlock() error { | |
dir, err := k.resolveDir() | |
if err != nil { | |
return err | |
} | |
if k.password == "" { | |
pwd, err := k.passwordFunc(fmt.Sprintf("Enter passphrase to unlock %q", dir)) | |
if err != nil { | |
return err | |
} | |
k.password = pwd | |
} | |
return nil | |
} | |
func (k *fileKeyring) Get(key string) (Item, error) { | |
filename, err := k.filename(key) | |
if err != nil { | |
return Item{}, err | |
} | |
bytes, err := ioutil.ReadFile(filename) | |
if os.IsNotExist(err) { | |
return Item{}, ErrKeyNotFound | |
} else if err != nil { | |
return Item{}, err | |
} | |
if err = k.unlock(); err != nil { | |
return Item{}, err | |
} | |
payload, _, err := jose.Decode(string(bytes), k.password) | |
if err != nil { | |
return Item{}, err | |
} | |
var decoded Item | |
err = json.Unmarshal([]byte(payload), &decoded) | |
return decoded, err | |
} | |
func (k *fileKeyring) GetMetadata(key string) (Metadata, error) { | |
filename, err := k.filename(key) | |
if err != nil { | |
return Metadata{}, err | |
} | |
stat, err := os.Stat(filename) | |
if os.IsNotExist(err) { | |
return Metadata{}, ErrKeyNotFound | |
} else if err != nil { | |
return Metadata{}, err | |
} | |
// For the File provider, all internal data is encrypted, not just the | |
// credentials. Thus we only have the timestamps. Return a nil *Item. | |
// | |
// If we want to change this ... how portable are extended file attributes | |
// these days? Would it break user expectations of the security model to | |
// leak data into those? I'm hesitant to do so. | |
return Metadata{ | |
ModificationTime: stat.ModTime(), | |
}, nil | |
} | |
func (k *fileKeyring) Set(i Item) error { | |
bytes, err := json.Marshal(i) | |
if err != nil { | |
return err | |
} | |
if err = k.unlock(); err != nil { | |
return err | |
} | |
token, err := jose.Encrypt(string(bytes), jose.PBES2_HS256_A128KW, jose.A256GCM, k.password, | |
jose.Headers(map[string]interface{}{ | |
"created": time.Now().String(), | |
})) | |
if err != nil { | |
return err | |
} | |
filename, err := k.filename(i.Key) | |
if err != nil { | |
return err | |
} | |
return ioutil.WriteFile(filename, []byte(token), 0600) | |
} | |
func (k *fileKeyring) filename(key string) (string, error) { | |
dir, err := k.resolveDir() | |
if err != nil { | |
return "", err | |
} | |
return filepath.Join(dir, filenameEscape(key)), nil | |
} | |
func (k *fileKeyring) Remove(key string) error { | |
filename, err := k.filename(key) | |
if err != nil { | |
return err | |
} | |
return os.Remove(filename) | |
} | |
func (k *fileKeyring) Keys() ([]string, error) { | |
dir, err := k.resolveDir() | |
if err != nil { | |
return nil, err | |
} | |
var keys = []string{} | |
files, _ := ioutil.ReadDir(dir) | |
for _, f := range files { | |
keys = append(keys, filenameUnescape(f.Name())) | |
} | |
return keys, nil | |
} |