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// ellapsed, 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	r.keys = append(r.keys, p)
179
180	return nil
181}
182
183// Sign returns a signature for the data.
184func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
185	return r.SignWithFlags(key, data, 0)
186}
187
188func (r *keyring) SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFlags) (*ssh.Signature, error) {
189	r.mu.Lock()
190	defer r.mu.Unlock()
191	if r.locked {
192		return nil, errLocked
193	}
194
195	r.expireKeysLocked()
196	wanted := key.Marshal()
197	for _, k := range r.keys {
198		if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) {
199			if flags == 0 {
200				return k.signer.Sign(rand.Reader, data)
201			} else {
202				if algorithmSigner, ok := k.signer.(ssh.AlgorithmSigner); !ok {
203					return nil, fmt.Errorf("agent: signature does not support non-default signature algorithm: %T", k.signer)
204				} else {
205					var algorithm string
206					switch flags {
207					case SignatureFlagRsaSha256:
208						algorithm = ssh.SigAlgoRSASHA2256
209					case SignatureFlagRsaSha512:
210						algorithm = ssh.SigAlgoRSASHA2512
211					default:
212						return nil, fmt.Errorf("agent: unsupported signature flags: %d", flags)
213					}
214					return algorithmSigner.SignWithAlgorithm(rand.Reader, data, algorithm)
215				}
216			}
217		}
218	}
219	return nil, errors.New("not found")
220}
221
222// Signers returns signers for all the known keys.
223func (r *keyring) Signers() ([]ssh.Signer, error) {
224	r.mu.Lock()
225	defer r.mu.Unlock()
226	if r.locked {
227		return nil, errLocked
228	}
229
230	r.expireKeysLocked()
231	s := make([]ssh.Signer, 0, len(r.keys))
232	for _, k := range r.keys {
233		s = append(s, k.signer)
234	}
235	return s, nil
236}
237
238// The keyring does not support any extensions
239func (r *keyring) Extension(extensionType string, contents []byte) ([]byte, error) {
240	return nil, ErrExtensionUnsupported
241}
242