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  }