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