1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
14
"code.google.com/p/go.crypto/ssh"
16
"launchpad.net/juju-core/utils"
17
"launchpad.net/juju-core/utils/set"
20
const clientKeyName = "juju_id_rsa"
22
// PublicKeySuffix is the file extension for public key files.
23
const PublicKeySuffix = ".pub"
26
clientKeysMutex sync.Mutex
28
// clientKeys is a cached map of private key filenames
29
// to ssh.Signers. The private keys are those loaded
30
// from the client key directory, passed to LoadClientKeys.
31
clientKeys map[string]ssh.Signer
34
// LoadClientKeys loads the client SSH keys from the
35
// specified directory, and caches them as a process-wide
36
// global. If the directory does not exist, it is created;
37
// if the directory did not exist, or contains no keys, it
38
// is populated with a new key pair.
40
// If the directory exists, then all pairs of files where one
41
// has the same name as the other + ".pub" will be loaded as
42
// private/public key pairs.
44
// Calls to LoadClientKeys will clear the previously loaded
45
// keys, and recompute the keys.
46
func LoadClientKeys(dir string) error {
47
clientKeysMutex.Lock()
48
defer clientKeysMutex.Unlock()
49
dir, err := utils.NormalizePath(dir)
53
if _, err := os.Stat(dir); err == nil {
54
keys, err := loadClientKeys(dir)
57
} else if len(keys) > 0 {
61
// Directory exists but contains no keys;
62
// fall through and create one.
64
if err := utils.MkdirAllForUser(dir, 0700); err != nil {
67
keyfile, key, err := generateClientKey(dir)
72
clientKeys = map[string]ssh.Signer{keyfile: key}
76
// ClearClientKeys clears the client keys cached in memory.
77
func ClearClientKeys() {
78
clientKeysMutex.Lock()
79
defer clientKeysMutex.Unlock()
83
func generateClientKey(dir string) (keyfile string, key ssh.Signer, err error) {
84
private, public, err := GenerateKey("juju-client-key")
88
clientPrivateKey, err := ssh.ParsePrivateKey([]byte(private))
92
privkeyFilename := filepath.Join(dir, clientKeyName)
93
if err = ioutil.WriteFile(privkeyFilename, []byte(private), 0600); err != nil {
96
if err := ioutil.WriteFile(privkeyFilename+PublicKeySuffix, []byte(public), 0600); err != nil {
97
os.Remove(privkeyFilename)
100
if err := utils.ChownToUser(privkeyFilename, privkeyFilename+PublicKeySuffix); err != nil {
101
os.Remove(privkeyFilename)
102
os.Remove(privkeyFilename + PublicKeySuffix)
105
return privkeyFilename, clientPrivateKey, nil
108
func loadClientKeys(dir string) (map[string]ssh.Signer, error) {
109
publicKeyFiles, err := publicKeyFiles(dir)
113
keys := make(map[string]ssh.Signer, len(publicKeyFiles))
114
for _, filename := range publicKeyFiles {
115
filename = filename[:len(filename)-len(PublicKeySuffix)]
116
data, err := ioutil.ReadFile(filename)
120
keys[filename], err = ssh.ParsePrivateKey(data)
122
return nil, fmt.Errorf("parsing key file %q: %v", filename, err)
128
// privateKeys returns the private keys loaded by LoadClientKeys.
129
func privateKeys() (signers []ssh.Signer) {
130
clientKeysMutex.Lock()
131
defer clientKeysMutex.Unlock()
132
for _, key := range clientKeys {
133
signers = append(signers, key)
138
// PrivateKeyFiles returns the filenames of private SSH keys loaded by
140
func PrivateKeyFiles() []string {
141
clientKeysMutex.Lock()
142
defer clientKeysMutex.Unlock()
143
keyfiles := make([]string, 0, len(clientKeys))
144
for f := range clientKeys {
145
keyfiles = append(keyfiles, f)
150
// PublicKeyFiles returns the filenames of public SSH keys loaded by
152
func PublicKeyFiles() []string {
153
privkeys := PrivateKeyFiles()
154
pubkeys := make([]string, len(privkeys))
155
for i, priv := range privkeys {
156
pubkeys[i] = priv + PublicKeySuffix
161
// publicKeyFiles returns the filenames of public SSH keys
162
// in the specified directory (all the files ending with .pub).
163
func publicKeyFiles(clientKeysDir string) ([]string, error) {
164
if clientKeysDir == "" {
168
dir, err := os.Open(clientKeysDir)
172
names, err := dir.Readdirnames(-1)
177
candidates := set.NewStrings(names...)
178
for _, name := range names {
179
if !strings.HasSuffix(name, PublicKeySuffix) {
182
// If the private key filename also exists, add the file.
183
priv := name[:len(name)-len(PublicKeySuffix)]
184
if candidates.Contains(priv) {
185
keys = append(keys, filepath.Join(dir.Name(), name))