main
1// Copyright 2014 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package agent
6
7import (
8 "bytes"
9 "crypto/rand"
10 "crypto/subtle"
11 "errors"
12 "fmt"
13 "sync"
14 "time"
15
16 "golang.org/x/crypto/ssh"
17)
18
19type privKey struct {
20 signer ssh.Signer
21 comment string
22 expire *time.Time
23}
24
25type keyring struct {
26 mu sync.Mutex
27 keys []privKey
28
29 locked bool
30 passphrase []byte
31}
32
33var errLocked = errors.New("agent: locked")
34
35// NewKeyring returns an Agent that holds keys in memory. It is safe
36// for concurrent use by multiple goroutines.
37func NewKeyring() Agent {
38 return &keyring{}
39}
40
41// RemoveAll removes all identities.
42func (r *keyring) RemoveAll() error {
43 r.mu.Lock()
44 defer r.mu.Unlock()
45 if r.locked {
46 return errLocked
47 }
48
49 r.keys = nil
50 return nil
51}
52
53// removeLocked does the actual key removal. The caller must already be holding the
54// keyring mutex.
55func (r *keyring) removeLocked(want []byte) error {
56 found := false
57 for i := 0; i < len(r.keys); {
58 if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) {
59 found = true
60 r.keys[i] = r.keys[len(r.keys)-1]
61 r.keys = r.keys[:len(r.keys)-1]
62 continue
63 } else {
64 i++
65 }
66 }
67
68 if !found {
69 return errors.New("agent: key not found")
70 }
71 return nil
72}
73
74// Remove removes all identities with the given public key.
75func (r *keyring) Remove(key ssh.PublicKey) error {
76 r.mu.Lock()
77 defer r.mu.Unlock()
78 if r.locked {
79 return errLocked
80 }
81
82 return r.removeLocked(key.Marshal())
83}
84
85// Lock locks the agent. Sign and Remove will fail, and List will return an empty list.
86func (r *keyring) Lock(passphrase []byte) error {
87 r.mu.Lock()
88 defer r.mu.Unlock()
89 if r.locked {
90 return errLocked
91 }
92
93 r.locked = true
94 r.passphrase = passphrase
95 return nil
96}
97
98// Unlock undoes the effect of Lock
99func (r *keyring) Unlock(passphrase []byte) error {
100 r.mu.Lock()
101 defer r.mu.Unlock()
102 if !r.locked {
103 return errors.New("agent: not locked")
104 }
105 if 1 != subtle.ConstantTimeCompare(passphrase, r.passphrase) {
106 return fmt.Errorf("agent: incorrect passphrase")
107 }
108
109 r.locked = false
110 r.passphrase = nil
111 return nil
112}
113
114// expireKeysLocked removes expired keys from the keyring. If a key was added
115// with a lifetimesecs contraint and seconds >= lifetimesecs seconds have
116// elapsed, it is removed. The caller *must* be holding the keyring mutex.
117func (r *keyring) expireKeysLocked() {
118 for _, k := range r.keys {
119 if k.expire != nil && time.Now().After(*k.expire) {
120 r.removeLocked(k.signer.PublicKey().Marshal())
121 }
122 }
123}
124
125// List returns the identities known to the agent.
126func (r *keyring) List() ([]*Key, error) {
127 r.mu.Lock()
128 defer r.mu.Unlock()
129 if r.locked {
130 // section 2.7: locked agents return empty.
131 return nil, nil
132 }
133
134 r.expireKeysLocked()
135 var ids []*Key
136 for _, k := range r.keys {
137 pub := k.signer.PublicKey()
138 ids = append(ids, &Key{
139 Format: pub.Type(),
140 Blob: pub.Marshal(),
141 Comment: k.comment})
142 }
143 return ids, nil
144}
145
146// Insert adds a private key to the keyring. If a certificate
147// is given, that certificate is added as public key. Note that
148// any constraints given are ignored.
149func (r *keyring) Add(key AddedKey) error {
150 r.mu.Lock()
151 defer r.mu.Unlock()
152 if r.locked {
153 return errLocked
154 }
155 signer, err := ssh.NewSignerFromKey(key.PrivateKey)
156
157 if err != nil {
158 return err
159 }
160
161 if cert := key.Certificate; cert != nil {
162 signer, err = ssh.NewCertSigner(cert, signer)
163 if err != nil {
164 return err
165 }
166 }
167
168 p := privKey{
169 signer: signer,
170 comment: key.Comment,
171 }
172
173 if key.LifetimeSecs > 0 {
174 t := time.Now().Add(time.Duration(key.LifetimeSecs) * time.Second)
175 p.expire = &t
176 }
177
178 // If we already have a Signer with the same public key, replace it with the
179 // new one.
180 for idx, k := range r.keys {
181 if bytes.Equal(k.signer.PublicKey().Marshal(), p.signer.PublicKey().Marshal()) {
182 r.keys[idx] = p
183 return nil
184 }
185 }
186
187 r.keys = append(r.keys, p)
188
189 return nil
190}
191
192// Sign returns a signature for the data.
193func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
194 return r.SignWithFlags(key, data, 0)
195}
196
197func (r *keyring) SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFlags) (*ssh.Signature, error) {
198 r.mu.Lock()
199 defer r.mu.Unlock()
200 if r.locked {
201 return nil, errLocked
202 }
203
204 r.expireKeysLocked()
205 wanted := key.Marshal()
206 for _, k := range r.keys {
207 if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) {
208 if flags == 0 {
209 return k.signer.Sign(rand.Reader, data)
210 } else {
211 if algorithmSigner, ok := k.signer.(ssh.AlgorithmSigner); !ok {
212 return nil, fmt.Errorf("agent: signature does not support non-default signature algorithm: %T", k.signer)
213 } else {
214 var algorithm string
215 switch flags {
216 case SignatureFlagRsaSha256:
217 algorithm = ssh.KeyAlgoRSASHA256
218 case SignatureFlagRsaSha512:
219 algorithm = ssh.KeyAlgoRSASHA512
220 default:
221 return nil, fmt.Errorf("agent: unsupported signature flags: %d", flags)
222 }
223 return algorithmSigner.SignWithAlgorithm(rand.Reader, data, algorithm)
224 }
225 }
226 }
227 }
228 return nil, errors.New("not found")
229}
230
231// Signers returns signers for all the known keys.
232func (r *keyring) Signers() ([]ssh.Signer, error) {
233 r.mu.Lock()
234 defer r.mu.Unlock()
235 if r.locked {
236 return nil, errLocked
237 }
238
239 r.expireKeysLocked()
240 s := make([]ssh.Signer, 0, len(r.keys))
241 for _, k := range r.keys {
242 s = append(s, k.signer)
243 }
244 return s, nil
245}
246
247// The keyring does not support any extensions
248func (r *keyring) Extension(extensionType string, contents []byte) ([]byte, error) {
249 return nil, ErrExtensionUnsupported
250}