github.com/glycerine/xcryptossh@v7.0.4+incompatible/agent/keyring.go (about) 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 5 package agent 6 7 import ( 8 "bytes" 9 "crypto/rand" 10 "crypto/subtle" 11 "errors" 12 "fmt" 13 "sync" 14 "time" 15 16 "github.com/glycerine/xcryptossh" 17 ) 18 19 type privKey struct { 20 signer ssh.Signer 21 comment string 22 expire *time.Time 23 } 24 25 type keyring struct { 26 mu sync.Mutex 27 keys []privKey 28 29 locked bool 30 passphrase []byte 31 } 32 33 var 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. 37 func NewKeyring() Agent { 38 return &keyring{} 39 } 40 41 // RemoveAll removes all identities. 42 func (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. 55 func (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. 75 func (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. 86 func (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 99 func (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 len(passphrase) != len(r.passphrase) || 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. 117 func (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. 126 func (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. 149 func (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. 184 func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) { 185 r.mu.Lock() 186 defer r.mu.Unlock() 187 if r.locked { 188 return nil, errLocked 189 } 190 191 r.expireKeysLocked() 192 wanted := key.Marshal() 193 for _, k := range r.keys { 194 if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) { 195 return k.signer.Sign(rand.Reader, data) 196 } 197 } 198 return nil, errors.New("not found") 199 } 200 201 // Signers returns signers for all the known keys. 202 func (r *keyring) Signers() ([]ssh.Signer, error) { 203 r.mu.Lock() 204 defer r.mu.Unlock() 205 if r.locked { 206 return nil, errLocked 207 } 208 209 r.expireKeysLocked() 210 s := make([]ssh.Signer, 0, len(r.keys)) 211 for _, k := range r.keys { 212 s = append(s, k.signer) 213 } 214 return s, nil 215 }