RFC 8332, Section 2 sets up two overlapping namespaces: public key formats and public key algorithms. * The formats are what we currently have KeyAlgo constants for, and they appear in PublicKey.Type. * The algorithms are the set of both KeyAlgo and SigAlgo constants, and they appear in Signature.Format (amongst other places). This is incoherent, because that means Signature.Format can be both a KeyAlgo (like KeyAlgoECDSA256) or a SigAlgo (like SigAlgoRSASHA2256). One solution would be to duplicate all the KeyAlgo constants into the SigAlgo namespace, but that would be confusing because applications are currently using KeyAlgos where they'd be supposed to use the new SigAlgos (while we can't deprecate the KeyAlgos because they are still necessary for the PublicKey.Type namespace). Instead, drop the separate namespaces, and use KeyAlgos throughout. There are simply some KeyAlgos that can't be a PublicKey.Type. Take the opportunity to fix the stuttering SHA22565/SHA2512 names. It's totally ok to call those hashes SHA-256 and SHA-512 without the family infix. For golang/go#49952 Change-Id: Ia1fce3912a7e60aa70a88f75ed311be331fd19d5 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/392354 Trust: Filippo Valsorda <filippo@golang.org> Run-TryBot: Filippo Valsorda <filippo@golang.org> Reviewed-by: Roland Shoemaker <roland@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org>
242 lines
5.3 KiB
Go
242 lines
5.3 KiB
Go
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package agent
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"crypto/subtle"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
type privKey struct {
|
|
signer ssh.Signer
|
|
comment string
|
|
expire *time.Time
|
|
}
|
|
|
|
type keyring struct {
|
|
mu sync.Mutex
|
|
keys []privKey
|
|
|
|
locked bool
|
|
passphrase []byte
|
|
}
|
|
|
|
var errLocked = errors.New("agent: locked")
|
|
|
|
// NewKeyring returns an Agent that holds keys in memory. It is safe
|
|
// for concurrent use by multiple goroutines.
|
|
func NewKeyring() Agent {
|
|
return &keyring{}
|
|
}
|
|
|
|
// RemoveAll removes all identities.
|
|
func (r *keyring) RemoveAll() error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.locked {
|
|
return errLocked
|
|
}
|
|
|
|
r.keys = nil
|
|
return nil
|
|
}
|
|
|
|
// removeLocked does the actual key removal. The caller must already be holding the
|
|
// keyring mutex.
|
|
func (r *keyring) removeLocked(want []byte) error {
|
|
found := false
|
|
for i := 0; i < len(r.keys); {
|
|
if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) {
|
|
found = true
|
|
r.keys[i] = r.keys[len(r.keys)-1]
|
|
r.keys = r.keys[:len(r.keys)-1]
|
|
continue
|
|
} else {
|
|
i++
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
return errors.New("agent: key not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Remove removes all identities with the given public key.
|
|
func (r *keyring) Remove(key ssh.PublicKey) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.locked {
|
|
return errLocked
|
|
}
|
|
|
|
return r.removeLocked(key.Marshal())
|
|
}
|
|
|
|
// Lock locks the agent. Sign and Remove will fail, and List will return an empty list.
|
|
func (r *keyring) Lock(passphrase []byte) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.locked {
|
|
return errLocked
|
|
}
|
|
|
|
r.locked = true
|
|
r.passphrase = passphrase
|
|
return nil
|
|
}
|
|
|
|
// Unlock undoes the effect of Lock
|
|
func (r *keyring) Unlock(passphrase []byte) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if !r.locked {
|
|
return errors.New("agent: not locked")
|
|
}
|
|
if 1 != subtle.ConstantTimeCompare(passphrase, r.passphrase) {
|
|
return fmt.Errorf("agent: incorrect passphrase")
|
|
}
|
|
|
|
r.locked = false
|
|
r.passphrase = nil
|
|
return nil
|
|
}
|
|
|
|
// expireKeysLocked removes expired keys from the keyring. If a key was added
|
|
// with a lifetimesecs contraint and seconds >= lifetimesecs seconds have
|
|
// elapsed, it is removed. The caller *must* be holding the keyring mutex.
|
|
func (r *keyring) expireKeysLocked() {
|
|
for _, k := range r.keys {
|
|
if k.expire != nil && time.Now().After(*k.expire) {
|
|
r.removeLocked(k.signer.PublicKey().Marshal())
|
|
}
|
|
}
|
|
}
|
|
|
|
// List returns the identities known to the agent.
|
|
func (r *keyring) List() ([]*Key, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.locked {
|
|
// section 2.7: locked agents return empty.
|
|
return nil, nil
|
|
}
|
|
|
|
r.expireKeysLocked()
|
|
var ids []*Key
|
|
for _, k := range r.keys {
|
|
pub := k.signer.PublicKey()
|
|
ids = append(ids, &Key{
|
|
Format: pub.Type(),
|
|
Blob: pub.Marshal(),
|
|
Comment: k.comment})
|
|
}
|
|
return ids, nil
|
|
}
|
|
|
|
// Insert adds a private key to the keyring. If a certificate
|
|
// is given, that certificate is added as public key. Note that
|
|
// any constraints given are ignored.
|
|
func (r *keyring) Add(key AddedKey) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.locked {
|
|
return errLocked
|
|
}
|
|
signer, err := ssh.NewSignerFromKey(key.PrivateKey)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if cert := key.Certificate; cert != nil {
|
|
signer, err = ssh.NewCertSigner(cert, signer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
p := privKey{
|
|
signer: signer,
|
|
comment: key.Comment,
|
|
}
|
|
|
|
if key.LifetimeSecs > 0 {
|
|
t := time.Now().Add(time.Duration(key.LifetimeSecs) * time.Second)
|
|
p.expire = &t
|
|
}
|
|
|
|
r.keys = append(r.keys, p)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Sign returns a signature for the data.
|
|
func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
|
return r.SignWithFlags(key, data, 0)
|
|
}
|
|
|
|
func (r *keyring) SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFlags) (*ssh.Signature, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.locked {
|
|
return nil, errLocked
|
|
}
|
|
|
|
r.expireKeysLocked()
|
|
wanted := key.Marshal()
|
|
for _, k := range r.keys {
|
|
if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) {
|
|
if flags == 0 {
|
|
return k.signer.Sign(rand.Reader, data)
|
|
} else {
|
|
if algorithmSigner, ok := k.signer.(ssh.AlgorithmSigner); !ok {
|
|
return nil, fmt.Errorf("agent: signature does not support non-default signature algorithm: %T", k.signer)
|
|
} else {
|
|
var algorithm string
|
|
switch flags {
|
|
case SignatureFlagRsaSha256:
|
|
algorithm = ssh.KeyAlgoRSASHA256
|
|
case SignatureFlagRsaSha512:
|
|
algorithm = ssh.KeyAlgoRSASHA512
|
|
default:
|
|
return nil, fmt.Errorf("agent: unsupported signature flags: %d", flags)
|
|
}
|
|
return algorithmSigner.SignWithAlgorithm(rand.Reader, data, algorithm)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil, errors.New("not found")
|
|
}
|
|
|
|
// Signers returns signers for all the known keys.
|
|
func (r *keyring) Signers() ([]ssh.Signer, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.locked {
|
|
return nil, errLocked
|
|
}
|
|
|
|
r.expireKeysLocked()
|
|
s := make([]ssh.Signer, 0, len(r.keys))
|
|
for _, k := range r.keys {
|
|
s = append(s, k.signer)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// The keyring does not support any extensions
|
|
func (r *keyring) Extension(extensionType string, contents []byte) ([]byte, error) {
|
|
return nil, ErrExtensionUnsupported
|
|
}
|