Merge pull request #16972 from hashicorp/jbardin/ssh-agent-identity
ssh connection `agent_identity`
This commit is contained in:
commit
bf5944a92c
|
@ -1,11 +1,15 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/communicator/shared"
|
||||
|
@ -50,6 +54,8 @@ type connectionInfo struct {
|
|||
BastionPrivateKey string `mapstructure:"bastion_private_key"`
|
||||
BastionHost string `mapstructure:"bastion_host"`
|
||||
BastionPort int `mapstructure:"bastion_port"`
|
||||
|
||||
AgentIdentity string `mapstructure:"agent_identity"`
|
||||
}
|
||||
|
||||
// parseConnectionInfo is used to convert the ConnInfo of the InstanceState into
|
||||
|
@ -186,7 +192,8 @@ type sshClientConfigOpts struct {
|
|||
|
||||
func buildSSHClientConfig(opts sshClientConfigOpts) (*ssh.ClientConfig, error) {
|
||||
conf := &ssh.ClientConfig{
|
||||
User: opts.user,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
User: opts.user,
|
||||
}
|
||||
|
||||
if opts.privateKey != "" {
|
||||
|
@ -246,6 +253,7 @@ func connectToAgent(connInfo *connectionInfo) (*sshAgent, error) {
|
|||
return &sshAgent{
|
||||
agent: agent,
|
||||
conn: conn,
|
||||
id: connInfo.AgentIdentity,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
@ -255,6 +263,7 @@ func connectToAgent(connInfo *connectionInfo) (*sshAgent, error) {
|
|||
type sshAgent struct {
|
||||
agent agent.Agent
|
||||
conn net.Conn
|
||||
id string
|
||||
}
|
||||
|
||||
func (a *sshAgent) Close() error {
|
||||
|
@ -265,8 +274,127 @@ func (a *sshAgent) Close() error {
|
|||
return a.conn.Close()
|
||||
}
|
||||
|
||||
// make an attempt to either read the identity file or find a corresponding
|
||||
// public key file using the typical openssh naming convention.
|
||||
// This returns the public key in wire format, or nil when a key is not found.
|
||||
func findIDPublicKey(id string) []byte {
|
||||
for _, d := range idKeyData(id) {
|
||||
signer, err := ssh.ParsePrivateKey(d)
|
||||
if err == nil {
|
||||
log.Println("[DEBUG] parsed id private key")
|
||||
pk := signer.PublicKey()
|
||||
return pk.Marshal()
|
||||
}
|
||||
|
||||
// try it as a publicKey
|
||||
pk, err := ssh.ParsePublicKey(d)
|
||||
if err == nil {
|
||||
log.Println("[DEBUG] parsed id public key")
|
||||
return pk.Marshal()
|
||||
}
|
||||
|
||||
// finally try it as an authorized key
|
||||
pk, _, _, _, err = ssh.ParseAuthorizedKey(d)
|
||||
if err == nil {
|
||||
log.Println("[DEBUG] parsed id authorized key")
|
||||
return pk.Marshal()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try to read an id file using the id as the file path. Also read the .pub
|
||||
// file if it exists, as the id file may be encrypted. Return only the file
|
||||
// data read. We don't need to know what data came from which path, as we will
|
||||
// try parsing each as a private key, a public key and an authorized key
|
||||
// regardless.
|
||||
func idKeyData(id string) [][]byte {
|
||||
idPath, err := filepath.Abs(id)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var fileData [][]byte
|
||||
|
||||
paths := []string{idPath}
|
||||
|
||||
if !strings.HasSuffix(idPath, ".pub") {
|
||||
paths = append(paths, idPath+".pub")
|
||||
}
|
||||
|
||||
for _, p := range paths {
|
||||
d, err := ioutil.ReadFile(p)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] error reading %q: %s", p, err)
|
||||
continue
|
||||
}
|
||||
log.Printf("[DEBUG] found identity data at %q", p)
|
||||
fileData = append(fileData, d)
|
||||
}
|
||||
|
||||
return fileData
|
||||
}
|
||||
|
||||
// sortSigners moves a signer with an agent comment field matching the
|
||||
// agent_identity to the head of the list when attempting authentication. This
|
||||
// helps when there are more keys loaded in an agent than the host will allow
|
||||
// attempts.
|
||||
func (s *sshAgent) sortSigners(signers []ssh.Signer) {
|
||||
if s.id == "" || len(signers) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
// if we can locate the public key, either by extracting it from the id or
|
||||
// locating the .pub file, then we can more easily determine an exact match
|
||||
idPk := findIDPublicKey(s.id)
|
||||
|
||||
// if we have a signer with a connect field that matches the id, send that
|
||||
// first, otherwise put close matches at the front of the list.
|
||||
head := 0
|
||||
for i := range signers {
|
||||
pk := signers[i].PublicKey()
|
||||
k, ok := pk.(*agent.Key)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// check for an exact match first
|
||||
if bytes.Equal(pk.Marshal(), idPk) || s.id == k.Comment {
|
||||
signers[0], signers[i] = signers[i], signers[0]
|
||||
break
|
||||
}
|
||||
|
||||
// no exact match yet, move it to the front if it's close. The agent
|
||||
// may have loaded as a full filepath, while the config refers to it by
|
||||
// filename only.
|
||||
if strings.HasSuffix(k.Comment, s.id) {
|
||||
signers[head], signers[i] = signers[i], signers[head]
|
||||
head++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
ss := []string{}
|
||||
for _, signer := range signers {
|
||||
pk := signer.PublicKey()
|
||||
k := pk.(*agent.Key)
|
||||
ss = append(ss, k.Comment)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sshAgent) Signers() ([]ssh.Signer, error) {
|
||||
signers, err := s.agent.Signers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.sortSigners(signers)
|
||||
return signers, nil
|
||||
}
|
||||
|
||||
func (a *sshAgent) Auth() ssh.AuthMethod {
|
||||
return ssh.PublicKeysCallback(a.agent.Signers)
|
||||
return ssh.PublicKeysCallback(a.Signers)
|
||||
}
|
||||
|
||||
func (a *sshAgent) ForwardToAgent(client *ssh.Client) error {
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// verify that we can locate public key data
|
||||
func TestFindKeyData(t *testing.T) {
|
||||
// setup a test directory
|
||||
td, err := ioutil.TempDir("", "ssh")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chdir(td); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Chdir(cwd)
|
||||
|
||||
id := "provisioner_id"
|
||||
|
||||
pub := generateSSHKey(t, id)
|
||||
pubData := pub.Marshal()
|
||||
|
||||
// backup the pub file, and replace it with a broken file to ensure we
|
||||
// extract the public key from the private key.
|
||||
if err := os.Rename(id+".pub", "saved.pub"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(id+".pub", []byte("not a public key"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
foundData := findIDPublicKey(id)
|
||||
if !bytes.Equal(foundData, pubData) {
|
||||
t.Fatalf("public key %q does not match", foundData)
|
||||
}
|
||||
|
||||
// move the pub file back, and break the private key file to simulate an
|
||||
// encrypted private key
|
||||
if err := os.Rename("saved.pub", id+".pub"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(id, []byte("encrypted private key"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
foundData = findIDPublicKey(id)
|
||||
if !bytes.Equal(foundData, pubData) {
|
||||
t.Fatalf("public key %q does not match", foundData)
|
||||
}
|
||||
|
||||
// check the file by path too
|
||||
foundData = findIDPublicKey(filepath.Join(".", id))
|
||||
if !bytes.Equal(foundData, pubData) {
|
||||
t.Fatalf("public key %q does not match", foundData)
|
||||
}
|
||||
}
|
||||
|
||||
func generateSSHKey(t *testing.T, idFile string) ssh.PublicKey {
|
||||
t.Helper()
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
privFile, err := os.OpenFile(idFile, os.O_RDWR|os.O_CREATE, 0600)
|
||||
defer privFile.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
privPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}
|
||||
if err := pem.Encode(privFile, privPEM); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// generate and write public key
|
||||
pub, err := ssh.NewPublicKey(&priv.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(idFile+".pub", ssh.MarshalAuthorizedKey(pub), 0600)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return pub
|
||||
}
|
|
@ -150,6 +150,7 @@ func (n *EvalValidateProvisioner) validateConnConfig(connConfig *ResourceConfig)
|
|||
BastionUser interface{} `mapstructure:"bastion_user"`
|
||||
BastionPassword interface{} `mapstructure:"bastion_password"`
|
||||
BastionPrivateKey interface{} `mapstructure:"bastion_private_key"`
|
||||
AgentIdentity interface{} `mapstructure:"agent_identity"`
|
||||
|
||||
// For type=winrm only (enforced in winrm communicator)
|
||||
HTTPS interface{} `mapstructure:"https"`
|
||||
|
|
|
@ -57,6 +57,17 @@ type Agent interface {
|
|||
Signers() ([]ssh.Signer, error)
|
||||
}
|
||||
|
||||
// ConstraintExtension describes an optional constraint defined by users.
|
||||
type ConstraintExtension struct {
|
||||
// ExtensionName consist of a UTF-8 string suffixed by the
|
||||
// implementation domain following the naming scheme defined
|
||||
// in Section 4.2 of [RFC4251], e.g. "foo@example.com".
|
||||
ExtensionName string
|
||||
// ExtensionDetails contains the actual content of the extended
|
||||
// constraint.
|
||||
ExtensionDetails []byte
|
||||
}
|
||||
|
||||
// AddedKey describes an SSH key to be added to an Agent.
|
||||
type AddedKey struct {
|
||||
// PrivateKey must be a *rsa.PrivateKey, *dsa.PrivateKey or
|
||||
|
@ -73,6 +84,9 @@ type AddedKey struct {
|
|||
// ConfirmBeforeUse, if true, requests that the agent confirm with the
|
||||
// user before each use of this key.
|
||||
ConfirmBeforeUse bool
|
||||
// ConstraintExtensions are the experimental or private-use constraints
|
||||
// defined by users.
|
||||
ConstraintExtensions []ConstraintExtension
|
||||
}
|
||||
|
||||
// See [PROTOCOL.agent], section 3.
|
||||
|
@ -84,7 +98,7 @@ const (
|
|||
agentAddIdentity = 17
|
||||
agentRemoveIdentity = 18
|
||||
agentRemoveAllIdentities = 19
|
||||
agentAddIdConstrained = 25
|
||||
agentAddIDConstrained = 25
|
||||
|
||||
// 3.3 Key-type independent requests from client to agent
|
||||
agentAddSmartcardKey = 20
|
||||
|
@ -94,8 +108,9 @@ const (
|
|||
agentAddSmartcardKeyConstrained = 26
|
||||
|
||||
// 3.7 Key constraint identifiers
|
||||
agentConstrainLifetime = 1
|
||||
agentConstrainConfirm = 2
|
||||
agentConstrainLifetime = 1
|
||||
agentConstrainConfirm = 2
|
||||
agentConstrainExtension = 3
|
||||
)
|
||||
|
||||
// maxAgentResponseBytes is the maximum agent reply size that is accepted. This
|
||||
|
@ -151,6 +166,19 @@ type publicKey struct {
|
|||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// 3.7 Key constraint identifiers
|
||||
type constrainLifetimeAgentMsg struct {
|
||||
LifetimeSecs uint32 `sshtype:"1"`
|
||||
}
|
||||
|
||||
type constrainExtensionAgentMsg struct {
|
||||
ExtensionName string `sshtype:"3"`
|
||||
ExtensionDetails []byte
|
||||
|
||||
// Rest is a field used for parsing, not part of message
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// Key represents a protocol 2 public key as defined in
|
||||
// [PROTOCOL.agent], section 2.5.2.
|
||||
type Key struct {
|
||||
|
@ -487,7 +515,7 @@ func (c *client) insertKey(s interface{}, comment string, constraints []byte) er
|
|||
|
||||
// if constraints are present then the message type needs to be changed.
|
||||
if len(constraints) != 0 {
|
||||
req[0] = agentAddIdConstrained
|
||||
req[0] = agentAddIDConstrained
|
||||
}
|
||||
|
||||
resp, err := c.call(req)
|
||||
|
@ -542,22 +570,18 @@ func (c *client) Add(key AddedKey) error {
|
|||
var constraints []byte
|
||||
|
||||
if secs := key.LifetimeSecs; secs != 0 {
|
||||
constraints = append(constraints, agentConstrainLifetime)
|
||||
|
||||
var secsBytes [4]byte
|
||||
binary.BigEndian.PutUint32(secsBytes[:], secs)
|
||||
constraints = append(constraints, secsBytes[:]...)
|
||||
constraints = append(constraints, ssh.Marshal(constrainLifetimeAgentMsg{secs})...)
|
||||
}
|
||||
|
||||
if key.ConfirmBeforeUse {
|
||||
constraints = append(constraints, agentConstrainConfirm)
|
||||
}
|
||||
|
||||
if cert := key.Certificate; cert == nil {
|
||||
cert := key.Certificate
|
||||
if cert == nil {
|
||||
return c.insertKey(key.PrivateKey, key.Comment, constraints)
|
||||
} else {
|
||||
return c.insertCert(key.PrivateKey, cert, key.Comment, constraints)
|
||||
}
|
||||
return c.insertCert(key.PrivateKey, cert, key.Comment, constraints)
|
||||
}
|
||||
|
||||
func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string, constraints []byte) error {
|
||||
|
@ -609,7 +633,7 @@ func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string
|
|||
|
||||
// if constraints are present then the message type needs to be changed.
|
||||
if len(constraints) != 0 {
|
||||
req[0] = agentAddIdConstrained
|
||||
req[0] = agentAddIDConstrained
|
||||
}
|
||||
|
||||
signer, err := ssh.NewSignerFromKey(s)
|
||||
|
|
|
@ -106,7 +106,7 @@ func (s *server) processRequest(data []byte) (interface{}, error) {
|
|||
return nil, s.agent.Lock(req.Passphrase)
|
||||
|
||||
case agentUnlock:
|
||||
var req agentLockMsg
|
||||
var req agentUnlockMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -148,13 +148,51 @@ func (s *server) processRequest(data []byte) (interface{}, error) {
|
|||
}
|
||||
return rep, nil
|
||||
|
||||
case agentAddIdConstrained, agentAddIdentity:
|
||||
case agentAddIDConstrained, agentAddIdentity:
|
||||
return nil, s.insertIdentity(data)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown opcode %d", data[0])
|
||||
}
|
||||
|
||||
func parseConstraints(constraints []byte) (lifetimeSecs uint32, confirmBeforeUse bool, extensions []ConstraintExtension, err error) {
|
||||
for len(constraints) != 0 {
|
||||
switch constraints[0] {
|
||||
case agentConstrainLifetime:
|
||||
lifetimeSecs = binary.BigEndian.Uint32(constraints[1:5])
|
||||
constraints = constraints[5:]
|
||||
case agentConstrainConfirm:
|
||||
confirmBeforeUse = true
|
||||
constraints = constraints[1:]
|
||||
case agentConstrainExtension:
|
||||
var msg constrainExtensionAgentMsg
|
||||
if err = ssh.Unmarshal(constraints, &msg); err != nil {
|
||||
return 0, false, nil, err
|
||||
}
|
||||
extensions = append(extensions, ConstraintExtension{
|
||||
ExtensionName: msg.ExtensionName,
|
||||
ExtensionDetails: msg.ExtensionDetails,
|
||||
})
|
||||
constraints = msg.Rest
|
||||
default:
|
||||
return 0, false, nil, fmt.Errorf("unknown constraint type: %d", constraints[0])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setConstraints(key *AddedKey, constraintBytes []byte) error {
|
||||
lifetimeSecs, confirmBeforeUse, constraintExtensions, err := parseConstraints(constraintBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key.LifetimeSecs = lifetimeSecs
|
||||
key.ConfirmBeforeUse = confirmBeforeUse
|
||||
key.ConstraintExtensions = constraintExtensions
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseRSAKey(req []byte) (*AddedKey, error) {
|
||||
var k rsaKeyMsg
|
||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||
|
@ -173,7 +211,11 @@ func parseRSAKey(req []byte) (*AddedKey, error) {
|
|||
}
|
||||
priv.Precompute()
|
||||
|
||||
return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil
|
||||
addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments}
|
||||
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addedKey, nil
|
||||
}
|
||||
|
||||
func parseEd25519Key(req []byte) (*AddedKey, error) {
|
||||
|
@ -182,7 +224,12 @@ func parseEd25519Key(req []byte) (*AddedKey, error) {
|
|||
return nil, err
|
||||
}
|
||||
priv := ed25519.PrivateKey(k.Priv)
|
||||
return &AddedKey{PrivateKey: &priv, Comment: k.Comments}, nil
|
||||
|
||||
addedKey := &AddedKey{PrivateKey: &priv, Comment: k.Comments}
|
||||
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addedKey, nil
|
||||
}
|
||||
|
||||
func parseDSAKey(req []byte) (*AddedKey, error) {
|
||||
|
@ -202,7 +249,11 @@ func parseDSAKey(req []byte) (*AddedKey, error) {
|
|||
X: k.X,
|
||||
}
|
||||
|
||||
return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil
|
||||
addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments}
|
||||
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addedKey, nil
|
||||
}
|
||||
|
||||
func unmarshalECDSA(curveName string, keyBytes []byte, privScalar *big.Int) (priv *ecdsa.PrivateKey, err error) {
|
||||
|
@ -243,7 +294,12 @@ func parseEd25519Cert(req []byte) (*AddedKey, error) {
|
|||
if !ok {
|
||||
return nil, errors.New("agent: bad ED25519 certificate")
|
||||
}
|
||||
return &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}, nil
|
||||
|
||||
addedKey := &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}
|
||||
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addedKey, nil
|
||||
}
|
||||
|
||||
func parseECDSAKey(req []byte) (*AddedKey, error) {
|
||||
|
@ -257,7 +313,11 @@ func parseECDSAKey(req []byte) (*AddedKey, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil
|
||||
addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments}
|
||||
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addedKey, nil
|
||||
}
|
||||
|
||||
func parseRSACert(req []byte) (*AddedKey, error) {
|
||||
|
@ -300,7 +360,11 @@ func parseRSACert(req []byte) (*AddedKey, error) {
|
|||
}
|
||||
priv.Precompute()
|
||||
|
||||
return &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}, nil
|
||||
addedKey := &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}
|
||||
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addedKey, nil
|
||||
}
|
||||
|
||||
func parseDSACert(req []byte) (*AddedKey, error) {
|
||||
|
@ -338,7 +402,11 @@ func parseDSACert(req []byte) (*AddedKey, error) {
|
|||
X: k.X,
|
||||
}
|
||||
|
||||
return &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}, nil
|
||||
addedKey := &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}
|
||||
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addedKey, nil
|
||||
}
|
||||
|
||||
func parseECDSACert(req []byte) (*AddedKey, error) {
|
||||
|
@ -371,7 +439,11 @@ func parseECDSACert(req []byte) (*AddedKey, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}, nil
|
||||
addedKey := &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}
|
||||
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addedKey, nil
|
||||
}
|
||||
|
||||
func (s *server) insertIdentity(req []byte) error {
|
||||
|
|
|
@ -51,13 +51,12 @@ func (b *buffer) write(buf []byte) {
|
|||
}
|
||||
|
||||
// eof closes the buffer. Reads from the buffer once all
|
||||
// the data has been consumed will receive os.EOF.
|
||||
func (b *buffer) eof() error {
|
||||
// the data has been consumed will receive io.EOF.
|
||||
func (b *buffer) eof() {
|
||||
b.Cond.L.Lock()
|
||||
b.closed = true
|
||||
b.Cond.Signal()
|
||||
b.Cond.L.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read reads data from the internal buffer in buf. Reads will block
|
||||
|
|
|
@ -251,10 +251,18 @@ type CertChecker struct {
|
|||
// for user certificates.
|
||||
SupportedCriticalOptions []string
|
||||
|
||||
// IsAuthority should return true if the key is recognized as
|
||||
// an authority. This allows for certificates to be signed by other
|
||||
// certificates.
|
||||
IsAuthority func(auth PublicKey) bool
|
||||
// IsUserAuthority should return true if the key is recognized as an
|
||||
// authority for the given user certificate. This allows for
|
||||
// certificates to be signed by other certificates. This must be set
|
||||
// if this CertChecker will be checking user certificates.
|
||||
IsUserAuthority func(auth PublicKey) bool
|
||||
|
||||
// IsHostAuthority should report whether the key is recognized as
|
||||
// an authority for this host. This allows for certificates to be
|
||||
// signed by other keys, and for those other keys to only be valid
|
||||
// signers for particular hostnames. This must be set if this
|
||||
// CertChecker will be checking host certificates.
|
||||
IsHostAuthority func(auth PublicKey, address string) bool
|
||||
|
||||
// Clock is used for verifying time stamps. If nil, time.Now
|
||||
// is used.
|
||||
|
@ -268,7 +276,7 @@ type CertChecker struct {
|
|||
// HostKeyFallback is called when CertChecker.CheckHostKey encounters a
|
||||
// public key that is not a certificate. It must implement host key
|
||||
// validation or else, if nil, all such keys are rejected.
|
||||
HostKeyFallback func(addr string, remote net.Addr, key PublicKey) error
|
||||
HostKeyFallback HostKeyCallback
|
||||
|
||||
// IsRevoked is called for each certificate so that revocation checking
|
||||
// can be implemented. It should return true if the given certificate
|
||||
|
@ -290,8 +298,17 @@ func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey)
|
|||
if cert.CertType != HostCert {
|
||||
return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType)
|
||||
}
|
||||
if !c.IsHostAuthority(cert.SignatureKey, addr) {
|
||||
return fmt.Errorf("ssh: no authorities for hostname: %v", addr)
|
||||
}
|
||||
|
||||
return c.CheckCert(addr, cert)
|
||||
hostname, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Pass hostname only as principal for host certificates (consistent with OpenSSH)
|
||||
return c.CheckCert(hostname, cert)
|
||||
}
|
||||
|
||||
// Authenticate checks a user certificate. Authenticate can be used as
|
||||
|
@ -308,6 +325,9 @@ func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permis
|
|||
if cert.CertType != UserCert {
|
||||
return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType)
|
||||
}
|
||||
if !c.IsUserAuthority(cert.SignatureKey) {
|
||||
return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
||||
}
|
||||
|
||||
if err := c.CheckCert(conn.User(), cert); err != nil {
|
||||
return nil, err
|
||||
|
@ -323,7 +343,7 @@ func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
|||
return fmt.Errorf("ssh: certicate serial %d revoked", cert.Serial)
|
||||
}
|
||||
|
||||
for opt, _ := range cert.CriticalOptions {
|
||||
for opt := range cert.CriticalOptions {
|
||||
// sourceAddressCriticalOption will be enforced by
|
||||
// serverAuthenticate
|
||||
if opt == sourceAddressCriticalOption {
|
||||
|
@ -356,10 +376,6 @@ func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
|||
}
|
||||
}
|
||||
|
||||
if !c.IsAuthority(cert.SignatureKey) {
|
||||
return fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
||||
}
|
||||
|
||||
clock := c.Clock
|
||||
if clock == nil {
|
||||
clock = time.Now
|
||||
|
|
|
@ -205,32 +205,32 @@ type channel struct {
|
|||
|
||||
// writePacket sends a packet. If the packet is a channel close, it updates
|
||||
// sentClose. This method takes the lock c.writeMu.
|
||||
func (c *channel) writePacket(packet []byte) error {
|
||||
c.writeMu.Lock()
|
||||
if c.sentClose {
|
||||
c.writeMu.Unlock()
|
||||
func (ch *channel) writePacket(packet []byte) error {
|
||||
ch.writeMu.Lock()
|
||||
if ch.sentClose {
|
||||
ch.writeMu.Unlock()
|
||||
return io.EOF
|
||||
}
|
||||
c.sentClose = (packet[0] == msgChannelClose)
|
||||
err := c.mux.conn.writePacket(packet)
|
||||
c.writeMu.Unlock()
|
||||
ch.sentClose = (packet[0] == msgChannelClose)
|
||||
err := ch.mux.conn.writePacket(packet)
|
||||
ch.writeMu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *channel) sendMessage(msg interface{}) error {
|
||||
func (ch *channel) sendMessage(msg interface{}) error {
|
||||
if debugMux {
|
||||
log.Printf("send(%d): %#v", c.mux.chanList.offset, msg)
|
||||
log.Printf("send(%d): %#v", ch.mux.chanList.offset, msg)
|
||||
}
|
||||
|
||||
p := Marshal(msg)
|
||||
binary.BigEndian.PutUint32(p[1:], c.remoteId)
|
||||
return c.writePacket(p)
|
||||
binary.BigEndian.PutUint32(p[1:], ch.remoteId)
|
||||
return ch.writePacket(p)
|
||||
}
|
||||
|
||||
// WriteExtended writes data to a specific extended stream. These streams are
|
||||
// used, for example, for stderr.
|
||||
func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err error) {
|
||||
if c.sentEOF {
|
||||
func (ch *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err error) {
|
||||
if ch.sentEOF {
|
||||
return 0, io.EOF
|
||||
}
|
||||
// 1 byte message type, 4 bytes remoteId, 4 bytes data length
|
||||
|
@ -241,16 +241,16 @@ func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err er
|
|||
opCode = msgChannelExtendedData
|
||||
}
|
||||
|
||||
c.writeMu.Lock()
|
||||
packet := c.packetPool[extendedCode]
|
||||
ch.writeMu.Lock()
|
||||
packet := ch.packetPool[extendedCode]
|
||||
// We don't remove the buffer from packetPool, so
|
||||
// WriteExtended calls from different goroutines will be
|
||||
// flagged as errors by the race detector.
|
||||
c.writeMu.Unlock()
|
||||
ch.writeMu.Unlock()
|
||||
|
||||
for len(data) > 0 {
|
||||
space := min(c.maxRemotePayload, len(data))
|
||||
if space, err = c.remoteWin.reserve(space); err != nil {
|
||||
space := min(ch.maxRemotePayload, len(data))
|
||||
if space, err = ch.remoteWin.reserve(space); err != nil {
|
||||
return n, err
|
||||
}
|
||||
if want := headerLength + space; uint32(cap(packet)) < want {
|
||||
|
@ -262,13 +262,13 @@ func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err er
|
|||
todo := data[:space]
|
||||
|
||||
packet[0] = opCode
|
||||
binary.BigEndian.PutUint32(packet[1:], c.remoteId)
|
||||
binary.BigEndian.PutUint32(packet[1:], ch.remoteId)
|
||||
if extendedCode > 0 {
|
||||
binary.BigEndian.PutUint32(packet[5:], uint32(extendedCode))
|
||||
}
|
||||
binary.BigEndian.PutUint32(packet[headerLength-4:], uint32(len(todo)))
|
||||
copy(packet[headerLength:], todo)
|
||||
if err = c.writePacket(packet); err != nil {
|
||||
if err = ch.writePacket(packet); err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
|
@ -276,14 +276,14 @@ func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err er
|
|||
data = data[len(todo):]
|
||||
}
|
||||
|
||||
c.writeMu.Lock()
|
||||
c.packetPool[extendedCode] = packet
|
||||
c.writeMu.Unlock()
|
||||
ch.writeMu.Lock()
|
||||
ch.packetPool[extendedCode] = packet
|
||||
ch.writeMu.Unlock()
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *channel) handleData(packet []byte) error {
|
||||
func (ch *channel) handleData(packet []byte) error {
|
||||
headerLen := 9
|
||||
isExtendedData := packet[0] == msgChannelExtendedData
|
||||
if isExtendedData {
|
||||
|
@ -303,7 +303,7 @@ func (c *channel) handleData(packet []byte) error {
|
|||
if length == 0 {
|
||||
return nil
|
||||
}
|
||||
if length > c.maxIncomingPayload {
|
||||
if length > ch.maxIncomingPayload {
|
||||
// TODO(hanwen): should send Disconnect?
|
||||
return errors.New("ssh: incoming packet exceeds maximum payload size")
|
||||
}
|
||||
|
@ -313,21 +313,21 @@ func (c *channel) handleData(packet []byte) error {
|
|||
return errors.New("ssh: wrong packet length")
|
||||
}
|
||||
|
||||
c.windowMu.Lock()
|
||||
if c.myWindow < length {
|
||||
c.windowMu.Unlock()
|
||||
ch.windowMu.Lock()
|
||||
if ch.myWindow < length {
|
||||
ch.windowMu.Unlock()
|
||||
// TODO(hanwen): should send Disconnect with reason?
|
||||
return errors.New("ssh: remote side wrote too much")
|
||||
}
|
||||
c.myWindow -= length
|
||||
c.windowMu.Unlock()
|
||||
ch.myWindow -= length
|
||||
ch.windowMu.Unlock()
|
||||
|
||||
if extended == 1 {
|
||||
c.extPending.write(data)
|
||||
ch.extPending.write(data)
|
||||
} else if extended > 0 {
|
||||
// discard other extended data.
|
||||
} else {
|
||||
c.pending.write(data)
|
||||
ch.pending.write(data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -384,31 +384,31 @@ func (c *channel) close() {
|
|||
// responseMessageReceived is called when a success or failure message is
|
||||
// received on a channel to check that such a message is reasonable for the
|
||||
// given channel.
|
||||
func (c *channel) responseMessageReceived() error {
|
||||
if c.direction == channelInbound {
|
||||
func (ch *channel) responseMessageReceived() error {
|
||||
if ch.direction == channelInbound {
|
||||
return errors.New("ssh: channel response message received on inbound channel")
|
||||
}
|
||||
if c.decided {
|
||||
if ch.decided {
|
||||
return errors.New("ssh: duplicate response received for channel")
|
||||
}
|
||||
c.decided = true
|
||||
ch.decided = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *channel) handlePacket(packet []byte) error {
|
||||
func (ch *channel) handlePacket(packet []byte) error {
|
||||
switch packet[0] {
|
||||
case msgChannelData, msgChannelExtendedData:
|
||||
return c.handleData(packet)
|
||||
return ch.handleData(packet)
|
||||
case msgChannelClose:
|
||||
c.sendMessage(channelCloseMsg{PeersId: c.remoteId})
|
||||
c.mux.chanList.remove(c.localId)
|
||||
c.close()
|
||||
ch.sendMessage(channelCloseMsg{PeersID: ch.remoteId})
|
||||
ch.mux.chanList.remove(ch.localId)
|
||||
ch.close()
|
||||
return nil
|
||||
case msgChannelEOF:
|
||||
// RFC 4254 is mute on how EOF affects dataExt messages but
|
||||
// it is logical to signal EOF at the same time.
|
||||
c.extPending.eof()
|
||||
c.pending.eof()
|
||||
ch.extPending.eof()
|
||||
ch.pending.eof()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -419,24 +419,24 @@ func (c *channel) handlePacket(packet []byte) error {
|
|||
|
||||
switch msg := decoded.(type) {
|
||||
case *channelOpenFailureMsg:
|
||||
if err := c.responseMessageReceived(); err != nil {
|
||||
if err := ch.responseMessageReceived(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.mux.chanList.remove(msg.PeersId)
|
||||
c.msg <- msg
|
||||
ch.mux.chanList.remove(msg.PeersID)
|
||||
ch.msg <- msg
|
||||
case *channelOpenConfirmMsg:
|
||||
if err := c.responseMessageReceived(); err != nil {
|
||||
if err := ch.responseMessageReceived(); err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
||||
return fmt.Errorf("ssh: invalid MaxPacketSize %d from peer", msg.MaxPacketSize)
|
||||
}
|
||||
c.remoteId = msg.MyId
|
||||
c.maxRemotePayload = msg.MaxPacketSize
|
||||
c.remoteWin.add(msg.MyWindow)
|
||||
c.msg <- msg
|
||||
ch.remoteId = msg.MyID
|
||||
ch.maxRemotePayload = msg.MaxPacketSize
|
||||
ch.remoteWin.add(msg.MyWindow)
|
||||
ch.msg <- msg
|
||||
case *windowAdjustMsg:
|
||||
if !c.remoteWin.add(msg.AdditionalBytes) {
|
||||
if !ch.remoteWin.add(msg.AdditionalBytes) {
|
||||
return fmt.Errorf("ssh: invalid window update for %d bytes", msg.AdditionalBytes)
|
||||
}
|
||||
case *channelRequestMsg:
|
||||
|
@ -444,12 +444,12 @@ func (c *channel) handlePacket(packet []byte) error {
|
|||
Type: msg.Request,
|
||||
WantReply: msg.WantReply,
|
||||
Payload: msg.RequestSpecificData,
|
||||
ch: c,
|
||||
ch: ch,
|
||||
}
|
||||
|
||||
c.incomingRequests <- &req
|
||||
ch.incomingRequests <- &req
|
||||
default:
|
||||
c.msg <- msg
|
||||
ch.msg <- msg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -488,23 +488,23 @@ func (e *extChannel) Read(data []byte) (n int, err error) {
|
|||
return e.ch.ReadExtended(data, e.code)
|
||||
}
|
||||
|
||||
func (c *channel) Accept() (Channel, <-chan *Request, error) {
|
||||
if c.decided {
|
||||
func (ch *channel) Accept() (Channel, <-chan *Request, error) {
|
||||
if ch.decided {
|
||||
return nil, nil, errDecidedAlready
|
||||
}
|
||||
c.maxIncomingPayload = channelMaxPacket
|
||||
ch.maxIncomingPayload = channelMaxPacket
|
||||
confirm := channelOpenConfirmMsg{
|
||||
PeersId: c.remoteId,
|
||||
MyId: c.localId,
|
||||
MyWindow: c.myWindow,
|
||||
MaxPacketSize: c.maxIncomingPayload,
|
||||
PeersID: ch.remoteId,
|
||||
MyID: ch.localId,
|
||||
MyWindow: ch.myWindow,
|
||||
MaxPacketSize: ch.maxIncomingPayload,
|
||||
}
|
||||
c.decided = true
|
||||
if err := c.sendMessage(confirm); err != nil {
|
||||
ch.decided = true
|
||||
if err := ch.sendMessage(confirm); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return c, c.incomingRequests, nil
|
||||
return ch, ch.incomingRequests, nil
|
||||
}
|
||||
|
||||
func (ch *channel) Reject(reason RejectionReason, message string) error {
|
||||
|
@ -512,7 +512,7 @@ func (ch *channel) Reject(reason RejectionReason, message string) error {
|
|||
return errDecidedAlready
|
||||
}
|
||||
reject := channelOpenFailureMsg{
|
||||
PeersId: ch.remoteId,
|
||||
PeersID: ch.remoteId,
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
Language: "en",
|
||||
|
@ -541,7 +541,7 @@ func (ch *channel) CloseWrite() error {
|
|||
}
|
||||
ch.sentEOF = true
|
||||
return ch.sendMessage(channelEOFMsg{
|
||||
PeersId: ch.remoteId})
|
||||
PeersID: ch.remoteId})
|
||||
}
|
||||
|
||||
func (ch *channel) Close() error {
|
||||
|
@ -550,7 +550,7 @@ func (ch *channel) Close() error {
|
|||
}
|
||||
|
||||
return ch.sendMessage(channelCloseMsg{
|
||||
PeersId: ch.remoteId})
|
||||
PeersID: ch.remoteId})
|
||||
}
|
||||
|
||||
// Extended returns an io.ReadWriter that sends and receives data on the given,
|
||||
|
@ -577,7 +577,7 @@ func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (boo
|
|||
}
|
||||
|
||||
msg := channelRequestMsg{
|
||||
PeersId: ch.remoteId,
|
||||
PeersID: ch.remoteId,
|
||||
Request: name,
|
||||
WantReply: wantReply,
|
||||
RequestSpecificData: payload,
|
||||
|
@ -614,11 +614,11 @@ func (ch *channel) ackRequest(ok bool) error {
|
|||
var msg interface{}
|
||||
if !ok {
|
||||
msg = channelRequestFailureMsg{
|
||||
PeersId: ch.remoteId,
|
||||
PeersID: ch.remoteId,
|
||||
}
|
||||
} else {
|
||||
msg = channelRequestSuccessMsg{
|
||||
PeersId: ch.remoteId,
|
||||
PeersID: ch.remoteId,
|
||||
}
|
||||
}
|
||||
return ch.sendMessage(msg)
|
||||
|
|
|
@ -304,7 +304,7 @@ type gcmCipher struct {
|
|||
buf []byte
|
||||
}
|
||||
|
||||
func newGCMCipher(iv, key, macKey []byte) (packetCipher, error) {
|
||||
func newGCMCipher(iv, key []byte) (packetCipher, error) {
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -372,7 +372,7 @@ func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
|||
}
|
||||
length := binary.BigEndian.Uint32(c.prefix[:])
|
||||
if length > maxPacket {
|
||||
return nil, errors.New("ssh: max packet length exceeded.")
|
||||
return nil, errors.New("ssh: max packet length exceeded")
|
||||
}
|
||||
|
||||
if cap(c.buf) < int(length+gcmTagSize) {
|
||||
|
@ -392,7 +392,9 @@ func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
|||
c.incIV()
|
||||
|
||||
padding := plain[0]
|
||||
if padding < 4 || padding >= 20 {
|
||||
if padding < 4 {
|
||||
// padding is a byte, so it automatically satisfies
|
||||
// the maximum size, which is 255.
|
||||
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
|
||||
}
|
||||
|
||||
|
@ -546,11 +548,11 @@ func (c *cbcCipher) readPacketLeaky(seqNum uint32, r io.Reader) ([]byte, error)
|
|||
c.packetData = c.packetData[:entirePacketSize]
|
||||
}
|
||||
|
||||
if n, err := io.ReadFull(r, c.packetData[firstBlockLength:]); err != nil {
|
||||
n, err := io.ReadFull(r, c.packetData[firstBlockLength:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.oracleCamouflage -= uint32(n)
|
||||
}
|
||||
c.oracleCamouflage -= uint32(n)
|
||||
|
||||
remainingCrypted := c.packetData[firstBlockLength:macStart]
|
||||
c.decrypter.CryptBlocks(remainingCrypted, remainingCrypted)
|
||||
|
|
|
@ -5,15 +5,17 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Client implements a traditional SSH client that supports shells,
|
||||
// subprocesses, port forwarding and tunneled dialing.
|
||||
// subprocesses, TCP port/streamlocal forwarding and tunneled dialing.
|
||||
type Client struct {
|
||||
Conn
|
||||
|
||||
|
@ -59,6 +61,7 @@ func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
|
|||
conn.forwards.closeAll()
|
||||
}()
|
||||
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip"))
|
||||
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-streamlocal@openssh.com"))
|
||||
return conn
|
||||
}
|
||||
|
||||
|
@ -68,6 +71,11 @@ func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
|
|||
func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
|
||||
fullConf := *config
|
||||
fullConf.SetDefaults()
|
||||
if fullConf.HostKeyCallback == nil {
|
||||
c.Close()
|
||||
return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback")
|
||||
}
|
||||
|
||||
conn := &connection{
|
||||
sshConn: sshConn{conn: c},
|
||||
}
|
||||
|
@ -173,6 +181,17 @@ func Dial(network, addr string, config *ClientConfig) (*Client, error) {
|
|||
return NewClient(c, chans, reqs), nil
|
||||
}
|
||||
|
||||
// HostKeyCallback is the function type used for verifying server
|
||||
// keys. A HostKeyCallback must return nil if the host key is OK, or
|
||||
// an error to reject it. It receives the hostname as passed to Dial
|
||||
// or NewClientConn. The remote address is the RemoteAddr of the
|
||||
// net.Conn underlying the the SSH connection.
|
||||
type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
|
||||
|
||||
// BannerCallback is the function type used for treat the banner sent by
|
||||
// the server. A BannerCallback receives the message sent by the remote server.
|
||||
type BannerCallback func(message string) error
|
||||
|
||||
// A ClientConfig structure is used to configure a Client. It must not be
|
||||
// modified after having been passed to an SSH function.
|
||||
type ClientConfig struct {
|
||||
|
@ -188,10 +207,18 @@ type ClientConfig struct {
|
|||
// be used during authentication.
|
||||
Auth []AuthMethod
|
||||
|
||||
// HostKeyCallback, if not nil, is called during the cryptographic
|
||||
// handshake to validate the server's host key. A nil HostKeyCallback
|
||||
// implies that all host keys are accepted.
|
||||
HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
|
||||
// HostKeyCallback is called during the cryptographic
|
||||
// handshake to validate the server's host key. The client
|
||||
// configuration must supply this callback for the connection
|
||||
// to succeed. The functions InsecureIgnoreHostKey or
|
||||
// FixedHostKey can be used for simplistic host key checks.
|
||||
HostKeyCallback HostKeyCallback
|
||||
|
||||
// BannerCallback is called during the SSH dance to display a custom
|
||||
// server's message. The client configuration can supply this callback to
|
||||
// handle it as wished. The function BannerDisplayStderr can be used for
|
||||
// simplistic display on Stderr.
|
||||
BannerCallback BannerCallback
|
||||
|
||||
// ClientVersion contains the version identification string that will
|
||||
// be used for the connection. If empty, a reasonable default is used.
|
||||
|
@ -209,3 +236,43 @@ type ClientConfig struct {
|
|||
// A Timeout of zero means no timeout.
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// InsecureIgnoreHostKey returns a function that can be used for
|
||||
// ClientConfig.HostKeyCallback to accept any host key. It should
|
||||
// not be used for production code.
|
||||
func InsecureIgnoreHostKey() HostKeyCallback {
|
||||
return func(hostname string, remote net.Addr, key PublicKey) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type fixedHostKey struct {
|
||||
key PublicKey
|
||||
}
|
||||
|
||||
func (f *fixedHostKey) check(hostname string, remote net.Addr, key PublicKey) error {
|
||||
if f.key == nil {
|
||||
return fmt.Errorf("ssh: required host key was nil")
|
||||
}
|
||||
if !bytes.Equal(key.Marshal(), f.key.Marshal()) {
|
||||
return fmt.Errorf("ssh: host key mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FixedHostKey returns a function for use in
|
||||
// ClientConfig.HostKeyCallback to accept only a specific host key.
|
||||
func FixedHostKey(key PublicKey) HostKeyCallback {
|
||||
hk := &fixedHostKey{key}
|
||||
return hk.check
|
||||
}
|
||||
|
||||
// BannerDisplayStderr returns a function that can be used for
|
||||
// ClientConfig.BannerCallback to display banners on os.Stderr.
|
||||
func BannerDisplayStderr() BannerCallback {
|
||||
return func(banner string) error {
|
||||
_, err := os.Stderr.WriteString(banner)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,31 +179,26 @@ func (cb publicKeyCallback) method() string {
|
|||
}
|
||||
|
||||
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
// Authentication is performed in two stages. The first stage sends an
|
||||
// enquiry to test if each key is acceptable to the remote. The second
|
||||
// stage attempts to authenticate with the valid keys obtained in the
|
||||
// first stage.
|
||||
// Authentication is performed by sending an enquiry to test if a key is
|
||||
// acceptable to the remote. If the key is acceptable, the client will
|
||||
// attempt to authenticate with the valid key. If not the client will repeat
|
||||
// the process with the remaining keys.
|
||||
|
||||
signers, err := cb()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
var validKeys []Signer
|
||||
for _, signer := range signers {
|
||||
if ok, err := validateKey(signer.PublicKey(), user, c); ok {
|
||||
validKeys = append(validKeys, signer)
|
||||
} else {
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// methods that may continue if this auth is not successful.
|
||||
var methods []string
|
||||
for _, signer := range validKeys {
|
||||
pub := signer.PublicKey()
|
||||
for _, signer := range signers {
|
||||
ok, err := validateKey(signer.PublicKey(), user, c)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
pub := signer.PublicKey()
|
||||
pubKey := pub.Marshal()
|
||||
sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
|
||||
User: user,
|
||||
|
@ -236,13 +231,29 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
|||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if success {
|
||||
|
||||
// If authentication succeeds or the list of available methods does not
|
||||
// contain the "publickey" method, do not attempt to authenticate with any
|
||||
// other keys. According to RFC 4252 Section 7, the latter can occur when
|
||||
// additional authentication methods are required.
|
||||
if success || !containsMethod(methods, cb.method()) {
|
||||
return success, methods, err
|
||||
}
|
||||
}
|
||||
|
||||
return false, methods, nil
|
||||
}
|
||||
|
||||
func containsMethod(methods []string, method string) bool {
|
||||
for _, m := range methods {
|
||||
if m == method {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// validateKey validates the key provided is acceptable to the server.
|
||||
func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
|
||||
pubKey := key.Marshal()
|
||||
|
@ -272,7 +283,9 @@ func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
|
|||
}
|
||||
switch packet[0] {
|
||||
case msgUserAuthBanner:
|
||||
// TODO(gpaul): add callback to present the banner to the user
|
||||
if err := handleBannerResponse(c, packet); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case msgUserAuthPubKeyOk:
|
||||
var msg userAuthPubKeyOkMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
|
@ -314,7 +327,9 @@ func handleAuthResponse(c packetConn) (bool, []string, error) {
|
|||
|
||||
switch packet[0] {
|
||||
case msgUserAuthBanner:
|
||||
// TODO: add callback to present the banner to the user
|
||||
if err := handleBannerResponse(c, packet); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
case msgUserAuthFailure:
|
||||
var msg userAuthFailureMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
|
@ -329,6 +344,24 @@ func handleAuthResponse(c packetConn) (bool, []string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func handleBannerResponse(c packetConn, packet []byte) error {
|
||||
var msg userAuthBannerMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
transport, ok := c.(*handshakeTransport)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if transport.bannerCallback != nil {
|
||||
return transport.bannerCallback(msg.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeyboardInteractiveChallenge should print questions, optionally
|
||||
// disabling echoing (e.g. for passwords), and return all the answers.
|
||||
// Challenge may be called multiple times in a single session. After
|
||||
|
@ -338,7 +371,7 @@ func handleAuthResponse(c packetConn) (bool, []string, error) {
|
|||
// both CLI and GUI environments.
|
||||
type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error)
|
||||
|
||||
// KeyboardInteractive returns a AuthMethod using a prompt/response
|
||||
// KeyboardInteractive returns an AuthMethod using a prompt/response
|
||||
// sequence controlled by the server.
|
||||
func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod {
|
||||
return challenge
|
||||
|
@ -374,7 +407,9 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
|
|||
// like handleAuthResponse, but with less options.
|
||||
switch packet[0] {
|
||||
case msgUserAuthBanner:
|
||||
// TODO: Print banners during userauth.
|
||||
if err := handleBannerResponse(c, packet); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
continue
|
||||
case msgUserAuthInfoRequest:
|
||||
// OK
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
_ "crypto/sha1"
|
||||
|
@ -40,7 +41,7 @@ var supportedKexAlgos = []string{
|
|||
kexAlgoDH14SHA1, kexAlgoDH1SHA1,
|
||||
}
|
||||
|
||||
// supportedKexAlgos specifies the supported host-key algorithms (i.e. methods
|
||||
// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods
|
||||
// of authenticating servers) in preference order.
|
||||
var supportedHostKeyAlgos = []string{
|
||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
|
||||
|
@ -186,7 +187,7 @@ type Config struct {
|
|||
|
||||
// The maximum number of bytes sent or received after which a
|
||||
// new key is negotiated. It must be at least 256. If
|
||||
// unspecified, 1 gigabyte is used.
|
||||
// unspecified, a size suitable for the chosen cipher is used.
|
||||
RekeyThreshold uint64
|
||||
|
||||
// The allowed key exchanges algorithms. If unspecified then a
|
||||
|
@ -230,17 +231,18 @@ func (c *Config) SetDefaults() {
|
|||
}
|
||||
|
||||
if c.RekeyThreshold == 0 {
|
||||
// RFC 4253, section 9 suggests rekeying after 1G.
|
||||
c.RekeyThreshold = 1 << 30
|
||||
}
|
||||
if c.RekeyThreshold < minRekeyThreshold {
|
||||
// cipher specific default
|
||||
} else if c.RekeyThreshold < minRekeyThreshold {
|
||||
c.RekeyThreshold = minRekeyThreshold
|
||||
} else if c.RekeyThreshold >= math.MaxInt64 {
|
||||
// Avoid weirdness if somebody uses -1 as a threshold.
|
||||
c.RekeyThreshold = math.MaxInt64
|
||||
}
|
||||
}
|
||||
|
||||
// buildDataSignedForAuth returns the data that is signed in order to prove
|
||||
// possession of a private key. See RFC 4252, section 7.
|
||||
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
|
||||
func buildDataSignedForAuth(sessionID []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
|
||||
data := struct {
|
||||
Session []byte
|
||||
Type byte
|
||||
|
@ -251,7 +253,7 @@ func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubK
|
|||
Algo []byte
|
||||
PubKey []byte
|
||||
}{
|
||||
sessionId,
|
||||
sessionID,
|
||||
msgUserAuthRequest,
|
||||
req.User,
|
||||
req.Service,
|
||||
|
|
|
@ -25,7 +25,7 @@ type ConnMetadata interface {
|
|||
// User returns the user ID for this connection.
|
||||
User() string
|
||||
|
||||
// SessionID returns the sesson hash, also denoted by H.
|
||||
// SessionID returns the session hash, also denoted by H.
|
||||
SessionID() []byte
|
||||
|
||||
// ClientVersion returns the client's version string as hashed
|
||||
|
|
|
@ -14,5 +14,8 @@ others.
|
|||
References:
|
||||
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
|
||||
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
|
||||
|
||||
This package does not fall under the stability promise of the Go language itself,
|
||||
so its API may be changed when pressing needs arise.
|
||||
*/
|
||||
package ssh // import "golang.org/x/crypto/ssh"
|
||||
|
|
|
@ -74,10 +74,15 @@ type handshakeTransport struct {
|
|||
startKex chan *pendingKex
|
||||
|
||||
// data for host key checking
|
||||
hostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
|
||||
hostKeyCallback HostKeyCallback
|
||||
dialAddress string
|
||||
remoteAddr net.Addr
|
||||
|
||||
// bannerCallback is non-empty if we are the client and it has been set in
|
||||
// ClientConfig. In that case it is called during the user authentication
|
||||
// dance to handle a custom server's message.
|
||||
bannerCallback BannerCallback
|
||||
|
||||
// Algorithms agreed in the last key exchange.
|
||||
algorithms *algorithms
|
||||
|
||||
|
@ -107,6 +112,8 @@ func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion,
|
|||
|
||||
config: config,
|
||||
}
|
||||
t.resetReadThresholds()
|
||||
t.resetWriteThresholds()
|
||||
|
||||
// We always start with a mandatory key exchange.
|
||||
t.requestKex <- struct{}{}
|
||||
|
@ -118,6 +125,7 @@ func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byt
|
|||
t.dialAddress = dialAddr
|
||||
t.remoteAddr = addr
|
||||
t.hostKeyCallback = config.HostKeyCallback
|
||||
t.bannerCallback = config.BannerCallback
|
||||
if config.HostKeyAlgorithms != nil {
|
||||
t.hostKeyAlgorithms = config.HostKeyAlgorithms
|
||||
} else {
|
||||
|
@ -237,6 +245,17 @@ func (t *handshakeTransport) requestKeyExchange() {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) resetWriteThresholds() {
|
||||
t.writePacketsLeft = packetRekeyThreshold
|
||||
if t.config.RekeyThreshold > 0 {
|
||||
t.writeBytesLeft = int64(t.config.RekeyThreshold)
|
||||
} else if t.algorithms != nil {
|
||||
t.writeBytesLeft = t.algorithms.w.rekeyBytes()
|
||||
} else {
|
||||
t.writeBytesLeft = 1 << 30
|
||||
}
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) kexLoop() {
|
||||
|
||||
write:
|
||||
|
@ -285,12 +304,8 @@ write:
|
|||
t.writeError = err
|
||||
t.sentInitPacket = nil
|
||||
t.sentInitMsg = nil
|
||||
t.writePacketsLeft = packetRekeyThreshold
|
||||
if t.config.RekeyThreshold > 0 {
|
||||
t.writeBytesLeft = int64(t.config.RekeyThreshold)
|
||||
} else if t.algorithms != nil {
|
||||
t.writeBytesLeft = t.algorithms.w.rekeyBytes()
|
||||
}
|
||||
|
||||
t.resetWriteThresholds()
|
||||
|
||||
// we have completed the key exchange. Since the
|
||||
// reader is still blocked, it is safe to clear out
|
||||
|
@ -344,6 +359,17 @@ write:
|
|||
// key exchange itself.
|
||||
const packetRekeyThreshold = (1 << 31)
|
||||
|
||||
func (t *handshakeTransport) resetReadThresholds() {
|
||||
t.readPacketsLeft = packetRekeyThreshold
|
||||
if t.config.RekeyThreshold > 0 {
|
||||
t.readBytesLeft = int64(t.config.RekeyThreshold)
|
||||
} else if t.algorithms != nil {
|
||||
t.readBytesLeft = t.algorithms.r.rekeyBytes()
|
||||
} else {
|
||||
t.readBytesLeft = 1 << 30
|
||||
}
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) {
|
||||
p, err := t.conn.readPacket()
|
||||
if err != nil {
|
||||
|
@ -391,12 +417,7 @@ func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
t.readPacketsLeft = packetRekeyThreshold
|
||||
if t.config.RekeyThreshold > 0 {
|
||||
t.readBytesLeft = int64(t.config.RekeyThreshold)
|
||||
} else {
|
||||
t.readBytesLeft = t.algorithms.r.rekeyBytes()
|
||||
}
|
||||
t.resetReadThresholds()
|
||||
|
||||
// By default, a key exchange is hidden from higher layers by
|
||||
// translating it into msgIgnore.
|
||||
|
@ -574,7 +595,9 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
|||
}
|
||||
result.SessionID = t.sessionID
|
||||
|
||||
t.conn.prepareKeyChange(t.algorithms, result)
|
||||
if err := t.conn.prepareKeyChange(t.algorithms, result); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -614,11 +637,9 @@ func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if t.hostKeyCallback != nil {
|
||||
err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
|
|
@ -119,7 +119,7 @@ func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handsha
|
|||
return nil, err
|
||||
}
|
||||
|
||||
kInt, err := group.diffieHellman(kexDHReply.Y, x)
|
||||
ki, err := group.diffieHellman(kexDHReply.Y, x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -129,8 +129,8 @@ func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handsha
|
|||
writeString(h, kexDHReply.HostKey)
|
||||
writeInt(h, X)
|
||||
writeInt(h, kexDHReply.Y)
|
||||
K := make([]byte, intLength(kInt))
|
||||
marshalInt(K, kInt)
|
||||
K := make([]byte, intLength(ki))
|
||||
marshalInt(K, ki)
|
||||
h.Write(K)
|
||||
|
||||
return &kexResult{
|
||||
|
@ -164,7 +164,7 @@ func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handsha
|
|||
}
|
||||
|
||||
Y := new(big.Int).Exp(group.g, y, group.p)
|
||||
kInt, err := group.diffieHellman(kexDHInit.X, y)
|
||||
ki, err := group.diffieHellman(kexDHInit.X, y)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -177,8 +177,8 @@ func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handsha
|
|||
writeInt(h, kexDHInit.X)
|
||||
writeInt(h, Y)
|
||||
|
||||
K := make([]byte, intLength(kInt))
|
||||
marshalInt(K, kInt)
|
||||
K := make([]byte, intLength(ki))
|
||||
marshalInt(K, ki)
|
||||
h.Write(K)
|
||||
|
||||
H := h.Sum(nil)
|
||||
|
@ -383,8 +383,8 @@ func init() {
|
|||
// 4253 and Oakley Group 2 in RFC 2409.
|
||||
p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF", 16)
|
||||
kexAlgoMap[kexAlgoDH1SHA1] = &dhGroup{
|
||||
g: new(big.Int).SetInt64(2),
|
||||
p: p,
|
||||
g: new(big.Int).SetInt64(2),
|
||||
p: p,
|
||||
pMinus1: new(big.Int).Sub(p, bigOne),
|
||||
}
|
||||
|
||||
|
@ -393,8 +393,8 @@ func init() {
|
|||
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
|
||||
|
||||
kexAlgoMap[kexAlgoDH14SHA1] = &dhGroup{
|
||||
g: new(big.Int).SetInt64(2),
|
||||
p: p,
|
||||
g: new(big.Int).SetInt64(2),
|
||||
p: p,
|
||||
pMinus1: new(big.Int).Sub(p, bigOne),
|
||||
}
|
||||
|
||||
|
@ -462,9 +462,9 @@ func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handsh
|
|||
writeString(h, kp.pub[:])
|
||||
writeString(h, reply.EphemeralPubKey)
|
||||
|
||||
kInt := new(big.Int).SetBytes(secret[:])
|
||||
K := make([]byte, intLength(kInt))
|
||||
marshalInt(K, kInt)
|
||||
ki := new(big.Int).SetBytes(secret[:])
|
||||
K := make([]byte, intLength(ki))
|
||||
marshalInt(K, ki)
|
||||
h.Write(K)
|
||||
|
||||
return &kexResult{
|
||||
|
@ -510,9 +510,9 @@ func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handsh
|
|||
writeString(h, kexInit.ClientPubKey)
|
||||
writeString(h, kp.pub[:])
|
||||
|
||||
kInt := new(big.Int).SetBytes(secret[:])
|
||||
K := make([]byte, intLength(kInt))
|
||||
marshalInt(K, kInt)
|
||||
ki := new(big.Int).SetBytes(secret[:])
|
||||
K := make([]byte, intLength(ki))
|
||||
marshalInt(K, ki)
|
||||
h.Write(K)
|
||||
|
||||
H := h.Sum(nil)
|
||||
|
|
|
@ -363,10 +363,21 @@ func (r *rsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|||
|
||||
type dsaPublicKey dsa.PublicKey
|
||||
|
||||
func (r *dsaPublicKey) Type() string {
|
||||
func (k *dsaPublicKey) Type() string {
|
||||
return "ssh-dss"
|
||||
}
|
||||
|
||||
func checkDSAParams(param *dsa.Parameters) error {
|
||||
// SSH specifies FIPS 186-2, which only provided a single size
|
||||
// (1024 bits) DSA key. FIPS 186-3 allows for larger key
|
||||
// sizes, which would confuse SSH.
|
||||
if l := param.P.BitLen(); l != 1024 {
|
||||
return fmt.Errorf("ssh: unsupported DSA key size %d", l)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseDSA parses an DSA key according to RFC 4253, section 6.6.
|
||||
func parseDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
||||
var w struct {
|
||||
|
@ -377,13 +388,18 @@ func parseDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
param := dsa.Parameters{
|
||||
P: w.P,
|
||||
Q: w.Q,
|
||||
G: w.G,
|
||||
}
|
||||
if err := checkDSAParams(¶m); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
key := &dsaPublicKey{
|
||||
Parameters: dsa.Parameters{
|
||||
P: w.P,
|
||||
Q: w.Q,
|
||||
G: w.G,
|
||||
},
|
||||
Y: w.Y,
|
||||
Parameters: param,
|
||||
Y: w.Y,
|
||||
}
|
||||
return key, w.Rest, nil
|
||||
}
|
||||
|
@ -465,12 +481,12 @@ func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
|||
|
||||
type ecdsaPublicKey ecdsa.PublicKey
|
||||
|
||||
func (key *ecdsaPublicKey) Type() string {
|
||||
return "ecdsa-sha2-" + key.nistID()
|
||||
func (k *ecdsaPublicKey) Type() string {
|
||||
return "ecdsa-sha2-" + k.nistID()
|
||||
}
|
||||
|
||||
func (key *ecdsaPublicKey) nistID() string {
|
||||
switch key.Params().BitSize {
|
||||
func (k *ecdsaPublicKey) nistID() string {
|
||||
switch k.Params().BitSize {
|
||||
case 256:
|
||||
return "nistp256"
|
||||
case 384:
|
||||
|
@ -483,7 +499,7 @@ func (key *ecdsaPublicKey) nistID() string {
|
|||
|
||||
type ed25519PublicKey ed25519.PublicKey
|
||||
|
||||
func (key ed25519PublicKey) Type() string {
|
||||
func (k ed25519PublicKey) Type() string {
|
||||
return KeyAlgoED25519
|
||||
}
|
||||
|
||||
|
@ -502,23 +518,23 @@ func parseED25519(in []byte) (out PublicKey, rest []byte, err error) {
|
|||
return (ed25519PublicKey)(key), w.Rest, nil
|
||||
}
|
||||
|
||||
func (key ed25519PublicKey) Marshal() []byte {
|
||||
func (k ed25519PublicKey) Marshal() []byte {
|
||||
w := struct {
|
||||
Name string
|
||||
KeyBytes []byte
|
||||
}{
|
||||
KeyAlgoED25519,
|
||||
[]byte(key),
|
||||
[]byte(k),
|
||||
}
|
||||
return Marshal(&w)
|
||||
}
|
||||
|
||||
func (key ed25519PublicKey) Verify(b []byte, sig *Signature) error {
|
||||
if sig.Format != key.Type() {
|
||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type())
|
||||
func (k ed25519PublicKey) Verify(b []byte, sig *Signature) error {
|
||||
if sig.Format != k.Type() {
|
||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
||||
}
|
||||
|
||||
edKey := (ed25519.PublicKey)(key)
|
||||
edKey := (ed25519.PublicKey)(k)
|
||||
if ok := ed25519.Verify(edKey, b, sig.Blob); !ok {
|
||||
return errors.New("ssh: signature did not verify")
|
||||
}
|
||||
|
@ -579,9 +595,9 @@ func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|||
return (*ecdsaPublicKey)(key), w.Rest, nil
|
||||
}
|
||||
|
||||
func (key *ecdsaPublicKey) Marshal() []byte {
|
||||
func (k *ecdsaPublicKey) Marshal() []byte {
|
||||
// See RFC 5656, section 3.1.
|
||||
keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y)
|
||||
keyBytes := elliptic.Marshal(k.Curve, k.X, k.Y)
|
||||
// ECDSA publickey struct layout should match the struct used by
|
||||
// parseECDSACert in the x/crypto/ssh/agent package.
|
||||
w := struct {
|
||||
|
@ -589,20 +605,20 @@ func (key *ecdsaPublicKey) Marshal() []byte {
|
|||
ID string
|
||||
Key []byte
|
||||
}{
|
||||
key.Type(),
|
||||
key.nistID(),
|
||||
k.Type(),
|
||||
k.nistID(),
|
||||
keyBytes,
|
||||
}
|
||||
|
||||
return Marshal(&w)
|
||||
}
|
||||
|
||||
func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||
if sig.Format != key.Type() {
|
||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type())
|
||||
func (k *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||
if sig.Format != k.Type() {
|
||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
||||
}
|
||||
|
||||
h := ecHash(key.Curve).New()
|
||||
h := ecHash(k.Curve).New()
|
||||
h.Write(data)
|
||||
digest := h.Sum(nil)
|
||||
|
||||
|
@ -619,7 +635,7 @@ func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if ecdsa.Verify((*ecdsa.PublicKey)(key), digest, ecSig.R, ecSig.S) {
|
||||
if ecdsa.Verify((*ecdsa.PublicKey)(k), digest, ecSig.R, ecSig.S) {
|
||||
return nil
|
||||
}
|
||||
return errors.New("ssh: signature did not verify")
|
||||
|
@ -630,19 +646,28 @@ func (k *ecdsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|||
}
|
||||
|
||||
// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey,
|
||||
// *ecdsa.PrivateKey or any other crypto.Signer and returns a corresponding
|
||||
// Signer instance. ECDSA keys must use P-256, P-384 or P-521.
|
||||
// *ecdsa.PrivateKey or any other crypto.Signer and returns a
|
||||
// corresponding Signer instance. ECDSA keys must use P-256, P-384 or
|
||||
// P-521. DSA keys must use parameter size L1024N160.
|
||||
func NewSignerFromKey(key interface{}) (Signer, error) {
|
||||
switch key := key.(type) {
|
||||
case crypto.Signer:
|
||||
return NewSignerFromSigner(key)
|
||||
case *dsa.PrivateKey:
|
||||
return &dsaPrivateKey{key}, nil
|
||||
return newDSAPrivateKey(key)
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
||||
}
|
||||
}
|
||||
|
||||
func newDSAPrivateKey(key *dsa.PrivateKey) (Signer, error) {
|
||||
if err := checkDSAParams(&key.PublicKey.Parameters); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dsaPrivateKey{key}, nil
|
||||
}
|
||||
|
||||
type wrappedSigner struct {
|
||||
signer crypto.Signer
|
||||
pubKey PublicKey
|
||||
|
@ -733,7 +758,7 @@ func NewPublicKey(key interface{}) (PublicKey, error) {
|
|||
return (*rsaPublicKey)(key), nil
|
||||
case *ecdsa.PublicKey:
|
||||
if !supportedEllipticCurve(key.Curve) {
|
||||
return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported.")
|
||||
return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported")
|
||||
}
|
||||
return (*ecdsaPublicKey)(key), nil
|
||||
case *dsa.PublicKey:
|
||||
|
@ -756,6 +781,18 @@ func ParsePrivateKey(pemBytes []byte) (Signer, error) {
|
|||
return NewSignerFromKey(key)
|
||||
}
|
||||
|
||||
// ParsePrivateKeyWithPassphrase returns a Signer from a PEM encoded private
|
||||
// key and passphrase. It supports the same keys as
|
||||
// ParseRawPrivateKeyWithPassphrase.
|
||||
func ParsePrivateKeyWithPassphrase(pemBytes, passPhrase []byte) (Signer, error) {
|
||||
key, err := ParseRawPrivateKeyWithPassphrase(pemBytes, passPhrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewSignerFromKey(key)
|
||||
}
|
||||
|
||||
// encryptedBlock tells whether a private key is
|
||||
// encrypted by examining its Proc-Type header
|
||||
// for a mention of ENCRYPTED
|
||||
|
@ -790,6 +827,43 @@ func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// ParseRawPrivateKeyWithPassphrase returns a private key decrypted with
|
||||
// passphrase from a PEM encoded private key. If wrong passphrase, return
|
||||
// x509.IncorrectPasswordError.
|
||||
func ParseRawPrivateKeyWithPassphrase(pemBytes, passPhrase []byte) (interface{}, error) {
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return nil, errors.New("ssh: no key found")
|
||||
}
|
||||
buf := block.Bytes
|
||||
|
||||
if encryptedBlock(block) {
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
var err error
|
||||
buf, err = x509.DecryptPEMBlock(block, passPhrase)
|
||||
if err != nil {
|
||||
if err == x509.IncorrectPasswordError {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("ssh: cannot decode encrypted private keys: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch block.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
return x509.ParsePKCS1PrivateKey(buf)
|
||||
case "EC PRIVATE KEY":
|
||||
return x509.ParseECPrivateKey(buf)
|
||||
case "DSA PRIVATE KEY":
|
||||
return ParseDSAPrivateKey(buf)
|
||||
case "OPENSSH PRIVATE KEY":
|
||||
return parseOpenSSHPrivateKey(buf)
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// ParseDSAPrivateKey returns a DSA private key from its ASN.1 DER encoding, as
|
||||
// specified by the OpenSSL DSA man page.
|
||||
func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
|
||||
|
@ -824,7 +898,7 @@ func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
|
|||
|
||||
// Implemented based on the documentation at
|
||||
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
|
||||
func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) {
|
||||
func parseOpenSSHPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
||||
magic := append([]byte("openssh-key-v1"), 0)
|
||||
if !bytes.Equal(magic, key[0:len(magic)]) {
|
||||
return nil, errors.New("ssh: invalid openssh private key format")
|
||||
|
@ -844,14 +918,15 @@ func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if w.KdfName != "none" || w.CipherName != "none" {
|
||||
return nil, errors.New("ssh: cannot decode encrypted private keys")
|
||||
}
|
||||
|
||||
pk1 := struct {
|
||||
Check1 uint32
|
||||
Check2 uint32
|
||||
Keytype string
|
||||
Pub []byte
|
||||
Priv []byte
|
||||
Comment string
|
||||
Pad []byte `ssh:"rest"`
|
||||
Rest []byte `ssh:"rest"`
|
||||
}{}
|
||||
|
||||
if err := Unmarshal(w.PrivKeyBlock, &pk1); err != nil {
|
||||
|
@ -862,24 +937,75 @@ func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) {
|
|||
return nil, errors.New("ssh: checkint mismatch")
|
||||
}
|
||||
|
||||
// we only handle ed25519 keys currently
|
||||
if pk1.Keytype != KeyAlgoED25519 {
|
||||
// we only handle ed25519 and rsa keys currently
|
||||
switch pk1.Keytype {
|
||||
case KeyAlgoRSA:
|
||||
// https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L2760-L2773
|
||||
key := struct {
|
||||
N *big.Int
|
||||
E *big.Int
|
||||
D *big.Int
|
||||
Iqmp *big.Int
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
Comment string
|
||||
Pad []byte `ssh:"rest"`
|
||||
}{}
|
||||
|
||||
if err := Unmarshal(pk1.Rest, &key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, b := range key.Pad {
|
||||
if int(b) != i+1 {
|
||||
return nil, errors.New("ssh: padding not as expected")
|
||||
}
|
||||
}
|
||||
|
||||
pk := &rsa.PrivateKey{
|
||||
PublicKey: rsa.PublicKey{
|
||||
N: key.N,
|
||||
E: int(key.E.Int64()),
|
||||
},
|
||||
D: key.D,
|
||||
Primes: []*big.Int{key.P, key.Q},
|
||||
}
|
||||
|
||||
if err := pk.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pk.Precompute()
|
||||
|
||||
return pk, nil
|
||||
case KeyAlgoED25519:
|
||||
key := struct {
|
||||
Pub []byte
|
||||
Priv []byte
|
||||
Comment string
|
||||
Pad []byte `ssh:"rest"`
|
||||
}{}
|
||||
|
||||
if err := Unmarshal(pk1.Rest, &key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(key.Priv) != ed25519.PrivateKeySize {
|
||||
return nil, errors.New("ssh: private key unexpected length")
|
||||
}
|
||||
|
||||
for i, b := range key.Pad {
|
||||
if int(b) != i+1 {
|
||||
return nil, errors.New("ssh: padding not as expected")
|
||||
}
|
||||
}
|
||||
|
||||
pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize))
|
||||
copy(pk, key.Priv)
|
||||
return &pk, nil
|
||||
default:
|
||||
return nil, errors.New("ssh: unhandled key type")
|
||||
}
|
||||
|
||||
for i, b := range pk1.Pad {
|
||||
if int(b) != i+1 {
|
||||
return nil, errors.New("ssh: padding not as expected")
|
||||
}
|
||||
}
|
||||
|
||||
if len(pk1.Priv) != ed25519.PrivateKeySize {
|
||||
return nil, errors.New("ssh: private key unexpected length")
|
||||
}
|
||||
|
||||
pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize))
|
||||
copy(pk, pk1.Priv)
|
||||
return &pk, nil
|
||||
}
|
||||
|
||||
// FingerprintLegacyMD5 returns the user presentation of the key's
|
||||
|
|
|
@ -23,10 +23,6 @@ const (
|
|||
msgUnimplemented = 3
|
||||
msgDebug = 4
|
||||
msgNewKeys = 21
|
||||
|
||||
// Standard authentication messages
|
||||
msgUserAuthSuccess = 52
|
||||
msgUserAuthBanner = 53
|
||||
)
|
||||
|
||||
// SSH messages:
|
||||
|
@ -137,6 +133,18 @@ type userAuthFailureMsg struct {
|
|||
PartialSuccess bool
|
||||
}
|
||||
|
||||
// See RFC 4252, section 5.1
|
||||
const msgUserAuthSuccess = 52
|
||||
|
||||
// See RFC 4252, section 5.4
|
||||
const msgUserAuthBanner = 53
|
||||
|
||||
type userAuthBannerMsg struct {
|
||||
Message string `sshtype:"53"`
|
||||
// unused, but required to allow message parsing
|
||||
Language string
|
||||
}
|
||||
|
||||
// See RFC 4256, section 3.2
|
||||
const msgUserAuthInfoRequest = 60
|
||||
const msgUserAuthInfoResponse = 61
|
||||
|
@ -154,7 +162,7 @@ const msgChannelOpen = 90
|
|||
|
||||
type channelOpenMsg struct {
|
||||
ChanType string `sshtype:"90"`
|
||||
PeersId uint32
|
||||
PeersID uint32
|
||||
PeersWindow uint32
|
||||
MaxPacketSize uint32
|
||||
TypeSpecificData []byte `ssh:"rest"`
|
||||
|
@ -165,7 +173,7 @@ const msgChannelData = 94
|
|||
|
||||
// Used for debug print outs of packets.
|
||||
type channelDataMsg struct {
|
||||
PeersId uint32 `sshtype:"94"`
|
||||
PeersID uint32 `sshtype:"94"`
|
||||
Length uint32
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
@ -174,8 +182,8 @@ type channelDataMsg struct {
|
|||
const msgChannelOpenConfirm = 91
|
||||
|
||||
type channelOpenConfirmMsg struct {
|
||||
PeersId uint32 `sshtype:"91"`
|
||||
MyId uint32
|
||||
PeersID uint32 `sshtype:"91"`
|
||||
MyID uint32
|
||||
MyWindow uint32
|
||||
MaxPacketSize uint32
|
||||
TypeSpecificData []byte `ssh:"rest"`
|
||||
|
@ -185,7 +193,7 @@ type channelOpenConfirmMsg struct {
|
|||
const msgChannelOpenFailure = 92
|
||||
|
||||
type channelOpenFailureMsg struct {
|
||||
PeersId uint32 `sshtype:"92"`
|
||||
PeersID uint32 `sshtype:"92"`
|
||||
Reason RejectionReason
|
||||
Message string
|
||||
Language string
|
||||
|
@ -194,7 +202,7 @@ type channelOpenFailureMsg struct {
|
|||
const msgChannelRequest = 98
|
||||
|
||||
type channelRequestMsg struct {
|
||||
PeersId uint32 `sshtype:"98"`
|
||||
PeersID uint32 `sshtype:"98"`
|
||||
Request string
|
||||
WantReply bool
|
||||
RequestSpecificData []byte `ssh:"rest"`
|
||||
|
@ -204,28 +212,28 @@ type channelRequestMsg struct {
|
|||
const msgChannelSuccess = 99
|
||||
|
||||
type channelRequestSuccessMsg struct {
|
||||
PeersId uint32 `sshtype:"99"`
|
||||
PeersID uint32 `sshtype:"99"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.4.
|
||||
const msgChannelFailure = 100
|
||||
|
||||
type channelRequestFailureMsg struct {
|
||||
PeersId uint32 `sshtype:"100"`
|
||||
PeersID uint32 `sshtype:"100"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.3
|
||||
const msgChannelClose = 97
|
||||
|
||||
type channelCloseMsg struct {
|
||||
PeersId uint32 `sshtype:"97"`
|
||||
PeersID uint32 `sshtype:"97"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.3
|
||||
const msgChannelEOF = 96
|
||||
|
||||
type channelEOFMsg struct {
|
||||
PeersId uint32 `sshtype:"96"`
|
||||
PeersID uint32 `sshtype:"96"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 4
|
||||
|
@ -255,7 +263,7 @@ type globalRequestFailureMsg struct {
|
|||
const msgChannelWindowAdjust = 93
|
||||
|
||||
type windowAdjustMsg struct {
|
||||
PeersId uint32 `sshtype:"93"`
|
||||
PeersID uint32 `sshtype:"93"`
|
||||
AdditionalBytes uint32
|
||||
}
|
||||
|
||||
|
|
|
@ -278,7 +278,7 @@ func (m *mux) handleChannelOpen(packet []byte) error {
|
|||
|
||||
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
||||
failMsg := channelOpenFailureMsg{
|
||||
PeersId: msg.PeersId,
|
||||
PeersID: msg.PeersID,
|
||||
Reason: ConnectionFailed,
|
||||
Message: "invalid request",
|
||||
Language: "en_US.UTF-8",
|
||||
|
@ -287,7 +287,7 @@ func (m *mux) handleChannelOpen(packet []byte) error {
|
|||
}
|
||||
|
||||
c := m.newChannel(msg.ChanType, channelInbound, msg.TypeSpecificData)
|
||||
c.remoteId = msg.PeersId
|
||||
c.remoteId = msg.PeersID
|
||||
c.maxRemotePayload = msg.MaxPacketSize
|
||||
c.remoteWin.add(msg.PeersWindow)
|
||||
m.incomingChannels <- c
|
||||
|
@ -313,7 +313,7 @@ func (m *mux) openChannel(chanType string, extra []byte) (*channel, error) {
|
|||
PeersWindow: ch.myWindow,
|
||||
MaxPacketSize: ch.maxIncomingPayload,
|
||||
TypeSpecificData: extra,
|
||||
PeersId: ch.localId,
|
||||
PeersID: ch.localId,
|
||||
}
|
||||
if err := m.sendMessage(open); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -14,23 +14,34 @@ import (
|
|||
)
|
||||
|
||||
// The Permissions type holds fine-grained permissions that are
|
||||
// specific to a user or a specific authentication method for a
|
||||
// user. Permissions, except for "source-address", must be enforced in
|
||||
// the server application layer, after successful authentication. The
|
||||
// Permissions are passed on in ServerConn so a server implementation
|
||||
// can honor them.
|
||||
// specific to a user or a specific authentication method for a user.
|
||||
// The Permissions value for a successful authentication attempt is
|
||||
// available in ServerConn, so it can be used to pass information from
|
||||
// the user-authentication phase to the application layer.
|
||||
type Permissions struct {
|
||||
// Critical options restrict default permissions. Common
|
||||
// restrictions are "source-address" and "force-command". If
|
||||
// the server cannot enforce the restriction, or does not
|
||||
// recognize it, the user should not authenticate.
|
||||
// CriticalOptions indicate restrictions to the default
|
||||
// permissions, and are typically used in conjunction with
|
||||
// user certificates. The standard for SSH certificates
|
||||
// defines "force-command" (only allow the given command to
|
||||
// execute) and "source-address" (only allow connections from
|
||||
// the given address). The SSH package currently only enforces
|
||||
// the "source-address" critical option. It is up to server
|
||||
// implementations to enforce other critical options, such as
|
||||
// "force-command", by checking them after the SSH handshake
|
||||
// is successful. In general, SSH servers should reject
|
||||
// connections that specify critical options that are unknown
|
||||
// or not supported.
|
||||
CriticalOptions map[string]string
|
||||
|
||||
// Extensions are extra functionality that the server may
|
||||
// offer on authenticated connections. Common extensions are
|
||||
// "permit-agent-forwarding", "permit-X11-forwarding". Lack of
|
||||
// support for an extension does not preclude authenticating a
|
||||
// user.
|
||||
// offer on authenticated connections. Lack of support for an
|
||||
// extension does not preclude authenticating a user. Common
|
||||
// extensions are "permit-agent-forwarding",
|
||||
// "permit-X11-forwarding". The Go SSH library currently does
|
||||
// not act on any extension, and it is up to server
|
||||
// implementations to honor them. Extensions can be used to
|
||||
// pass data from the authentication callbacks to the server
|
||||
// application layer.
|
||||
Extensions map[string]string
|
||||
}
|
||||
|
||||
|
@ -45,13 +56,24 @@ type ServerConfig struct {
|
|||
// authenticating.
|
||||
NoClientAuth bool
|
||||
|
||||
// MaxAuthTries specifies the maximum number of authentication attempts
|
||||
// permitted per connection. If set to a negative number, the number of
|
||||
// attempts are unlimited. If set to zero, the number of attempts are limited
|
||||
// to 6.
|
||||
MaxAuthTries int
|
||||
|
||||
// PasswordCallback, if non-nil, is called when a user
|
||||
// attempts to authenticate using a password.
|
||||
PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error)
|
||||
|
||||
// PublicKeyCallback, if non-nil, is called when a client attempts public
|
||||
// key authentication. It must return true if the given public key is
|
||||
// valid for the given user. For example, see CertChecker.Authenticate.
|
||||
// PublicKeyCallback, if non-nil, is called when a client
|
||||
// offers a public key for authentication. It must return a nil error
|
||||
// if the given public key can be used to authenticate the
|
||||
// given user. For example, see CertChecker.Authenticate. A
|
||||
// call to this function does not guarantee that the key
|
||||
// offered is in fact used to authenticate. To record any data
|
||||
// depending on the public key, store it inside a
|
||||
// Permissions.Extensions entry.
|
||||
PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
|
||||
|
||||
// KeyboardInteractiveCallback, if non-nil, is called when
|
||||
|
@ -73,6 +95,10 @@ type ServerConfig struct {
|
|||
// Note that RFC 4253 section 4.2 requires that this string start with
|
||||
// "SSH-2.0-".
|
||||
ServerVersion string
|
||||
|
||||
// BannerCallback, if present, is called and the return string is sent to
|
||||
// the client after key exchange completed but before authentication.
|
||||
BannerCallback func(conn ConnMetadata) string
|
||||
}
|
||||
|
||||
// AddHostKey adds a private key as a host key. If an existing host
|
||||
|
@ -143,6 +169,10 @@ type ServerConn struct {
|
|||
func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) {
|
||||
fullConf := *config
|
||||
fullConf.SetDefaults()
|
||||
if fullConf.MaxAuthTries == 0 {
|
||||
fullConf.MaxAuthTries = 6
|
||||
}
|
||||
|
||||
s := &connection{
|
||||
sshConn: sshConn{conn: c},
|
||||
}
|
||||
|
@ -226,7 +256,7 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
|
|||
func isAcceptableAlgo(algo string) bool {
|
||||
switch algo {
|
||||
case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoED25519,
|
||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
|
||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -262,15 +292,52 @@ func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
|
|||
return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)
|
||||
}
|
||||
|
||||
// ServerAuthError implements the error interface. It appends any authentication
|
||||
// errors that may occur, and is returned if all of the authentication methods
|
||||
// provided by the user failed to authenticate.
|
||||
type ServerAuthError struct {
|
||||
// Errors contains authentication errors returned by the authentication
|
||||
// callback methods.
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func (l ServerAuthError) Error() string {
|
||||
var errs []string
|
||||
for _, err := range l.Errors {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
return "[" + strings.Join(errs, ", ") + "]"
|
||||
}
|
||||
|
||||
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
|
||||
sessionID := s.transport.getSessionID()
|
||||
var cache pubKeyCache
|
||||
var perms *Permissions
|
||||
|
||||
authFailures := 0
|
||||
var authErrs []error
|
||||
var displayedBanner bool
|
||||
|
||||
userAuthLoop:
|
||||
for {
|
||||
if authFailures >= config.MaxAuthTries && config.MaxAuthTries > 0 {
|
||||
discMsg := &disconnectMsg{
|
||||
Reason: 2,
|
||||
Message: "too many authentication failures",
|
||||
}
|
||||
|
||||
if err := s.transport.writePacket(Marshal(discMsg)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, discMsg
|
||||
}
|
||||
|
||||
var userAuthReq userAuthRequestMsg
|
||||
if packet, err := s.transport.readPacket(); err != nil {
|
||||
if err == io.EOF {
|
||||
return nil, &ServerAuthError{Errors: authErrs}
|
||||
}
|
||||
return nil, err
|
||||
} else if err = Unmarshal(packet, &userAuthReq); err != nil {
|
||||
return nil, err
|
||||
|
@ -281,6 +348,20 @@ userAuthLoop:
|
|||
}
|
||||
|
||||
s.user = userAuthReq.User
|
||||
|
||||
if !displayedBanner && config.BannerCallback != nil {
|
||||
displayedBanner = true
|
||||
msg := config.BannerCallback(s)
|
||||
if msg != "" {
|
||||
bannerMsg := &userAuthBannerMsg{
|
||||
Message: msg,
|
||||
}
|
||||
if err := s.transport.writePacket(Marshal(bannerMsg)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
perms = nil
|
||||
authErr := errors.New("no auth passed yet")
|
||||
|
||||
|
@ -289,6 +370,11 @@ userAuthLoop:
|
|||
if config.NoClientAuth {
|
||||
authErr = nil
|
||||
}
|
||||
|
||||
// allow initial attempt of 'none' without penalty
|
||||
if authFailures == 0 {
|
||||
authFailures--
|
||||
}
|
||||
case "password":
|
||||
if config.PasswordCallback == nil {
|
||||
authErr = errors.New("ssh: password auth not configured")
|
||||
|
@ -360,6 +446,7 @@ userAuthLoop:
|
|||
if isQuery {
|
||||
// The client can query if the given public key
|
||||
// would be okay.
|
||||
|
||||
if len(payload) > 0 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
|
@ -401,6 +488,8 @@ userAuthLoop:
|
|||
authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method)
|
||||
}
|
||||
|
||||
authErrs = append(authErrs, authErr)
|
||||
|
||||
if config.AuthLogCallback != nil {
|
||||
config.AuthLogCallback(s, userAuthReq.Method, authErr)
|
||||
}
|
||||
|
@ -409,6 +498,8 @@ userAuthLoop:
|
|||
break userAuthLoop
|
||||
}
|
||||
|
||||
authFailures++
|
||||
|
||||
var failureMsg userAuthFailureMsg
|
||||
if config.PasswordCallback != nil {
|
||||
failureMsg.Methods = append(failureMsg.Methods, "password")
|
||||
|
|
|
@ -231,6 +231,26 @@ func (s *Session) RequestSubsystem(subsystem string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// RFC 4254 Section 6.7.
|
||||
type ptyWindowChangeMsg struct {
|
||||
Columns uint32
|
||||
Rows uint32
|
||||
Width uint32
|
||||
Height uint32
|
||||
}
|
||||
|
||||
// WindowChange informs the remote host about a terminal window dimension change to h rows and w columns.
|
||||
func (s *Session) WindowChange(h, w int) error {
|
||||
req := ptyWindowChangeMsg{
|
||||
Columns: uint32(w),
|
||||
Rows: uint32(h),
|
||||
Width: uint32(w * 8),
|
||||
Height: uint32(h * 8),
|
||||
}
|
||||
_, err := s.ch.SendRequest("window-change", false, Marshal(&req))
|
||||
return err
|
||||
}
|
||||
|
||||
// RFC 4254 Section 6.9.
|
||||
type signalMsg struct {
|
||||
Signal string
|
||||
|
@ -386,7 +406,7 @@ func (s *Session) Wait() error {
|
|||
s.stdinPipeWriter.Close()
|
||||
}
|
||||
var copyError error
|
||||
for _ = range s.copyFuncs {
|
||||
for range s.copyFuncs {
|
||||
if err := <-s.errors; err != nil && copyError == nil {
|
||||
copyError = err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
// streamLocalChannelOpenDirectMsg is a struct used for SSH_MSG_CHANNEL_OPEN message
|
||||
// with "direct-streamlocal@openssh.com" string.
|
||||
//
|
||||
// See openssh-portable/PROTOCOL, section 2.4. connection: Unix domain socket forwarding
|
||||
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL#L235
|
||||
type streamLocalChannelOpenDirectMsg struct {
|
||||
socketPath string
|
||||
reserved0 string
|
||||
reserved1 uint32
|
||||
}
|
||||
|
||||
// forwardedStreamLocalPayload is a struct used for SSH_MSG_CHANNEL_OPEN message
|
||||
// with "forwarded-streamlocal@openssh.com" string.
|
||||
type forwardedStreamLocalPayload struct {
|
||||
SocketPath string
|
||||
Reserved0 string
|
||||
}
|
||||
|
||||
// streamLocalChannelForwardMsg is a struct used for SSH2_MSG_GLOBAL_REQUEST message
|
||||
// with "streamlocal-forward@openssh.com"/"cancel-streamlocal-forward@openssh.com" string.
|
||||
type streamLocalChannelForwardMsg struct {
|
||||
socketPath string
|
||||
}
|
||||
|
||||
// ListenUnix is similar to ListenTCP but uses a Unix domain socket.
|
||||
func (c *Client) ListenUnix(socketPath string) (net.Listener, error) {
|
||||
m := streamLocalChannelForwardMsg{
|
||||
socketPath,
|
||||
}
|
||||
// send message
|
||||
ok, _, err := c.SendRequest("streamlocal-forward@openssh.com", true, Marshal(&m))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer")
|
||||
}
|
||||
ch := c.forwards.add(&net.UnixAddr{Name: socketPath, Net: "unix"})
|
||||
|
||||
return &unixListener{socketPath, c, ch}, nil
|
||||
}
|
||||
|
||||
func (c *Client) dialStreamLocal(socketPath string) (Channel, error) {
|
||||
msg := streamLocalChannelOpenDirectMsg{
|
||||
socketPath: socketPath,
|
||||
}
|
||||
ch, in, err := c.OpenChannel("direct-streamlocal@openssh.com", Marshal(&msg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go DiscardRequests(in)
|
||||
return ch, err
|
||||
}
|
||||
|
||||
type unixListener struct {
|
||||
socketPath string
|
||||
|
||||
conn *Client
|
||||
in <-chan forward
|
||||
}
|
||||
|
||||
// Accept waits for and returns the next connection to the listener.
|
||||
func (l *unixListener) Accept() (net.Conn, error) {
|
||||
s, ok := <-l.in
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
ch, incoming, err := s.newCh.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go DiscardRequests(incoming)
|
||||
|
||||
return &chanConn{
|
||||
Channel: ch,
|
||||
laddr: &net.UnixAddr{
|
||||
Name: l.socketPath,
|
||||
Net: "unix",
|
||||
},
|
||||
raddr: &net.UnixAddr{
|
||||
Name: "@",
|
||||
Net: "unix",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close closes the listener.
|
||||
func (l *unixListener) Close() error {
|
||||
// this also closes the listener.
|
||||
l.conn.forwards.remove(&net.UnixAddr{Name: l.socketPath, Net: "unix"})
|
||||
m := streamLocalChannelForwardMsg{
|
||||
l.socketPath,
|
||||
}
|
||||
ok, _, err := l.conn.SendRequest("cancel-streamlocal-forward@openssh.com", true, Marshal(&m))
|
||||
if err == nil && !ok {
|
||||
err = errors.New("ssh: cancel-streamlocal-forward@openssh.com failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Addr returns the listener's network address.
|
||||
func (l *unixListener) Addr() net.Addr {
|
||||
return &net.UnixAddr{
|
||||
Name: l.socketPath,
|
||||
Net: "unix",
|
||||
}
|
||||
}
|
|
@ -20,12 +20,20 @@ import (
|
|||
// addr. Incoming connections will be available by calling Accept on
|
||||
// the returned net.Listener. The listener must be serviced, or the
|
||||
// SSH connection may hang.
|
||||
// N must be "tcp", "tcp4", "tcp6", or "unix".
|
||||
func (c *Client) Listen(n, addr string) (net.Listener, error) {
|
||||
laddr, err := net.ResolveTCPAddr(n, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
switch n {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
laddr, err := net.ResolveTCPAddr(n, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.ListenTCP(laddr)
|
||||
case "unix":
|
||||
return c.ListenUnix(addr)
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported protocol: %s", n)
|
||||
}
|
||||
return c.ListenTCP(laddr)
|
||||
}
|
||||
|
||||
// Automatic port allocation is broken with OpenSSH before 6.0. See
|
||||
|
@ -116,7 +124,7 @@ func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
|
|||
}
|
||||
|
||||
// Register this forward, using the port number we obtained.
|
||||
ch := c.forwards.add(*laddr)
|
||||
ch := c.forwards.add(laddr)
|
||||
|
||||
return &tcpListener{laddr, c, ch}, nil
|
||||
}
|
||||
|
@ -131,7 +139,7 @@ type forwardList struct {
|
|||
// forwardEntry represents an established mapping of a laddr on a
|
||||
// remote ssh server to a channel connected to a tcpListener.
|
||||
type forwardEntry struct {
|
||||
laddr net.TCPAddr
|
||||
laddr net.Addr
|
||||
c chan forward
|
||||
}
|
||||
|
||||
|
@ -139,16 +147,16 @@ type forwardEntry struct {
|
|||
// arguments to add/remove/lookup should be address as specified in
|
||||
// the original forward-request.
|
||||
type forward struct {
|
||||
newCh NewChannel // the ssh client channel underlying this forward
|
||||
raddr *net.TCPAddr // the raddr of the incoming connection
|
||||
newCh NewChannel // the ssh client channel underlying this forward
|
||||
raddr net.Addr // the raddr of the incoming connection
|
||||
}
|
||||
|
||||
func (l *forwardList) add(addr net.TCPAddr) chan forward {
|
||||
func (l *forwardList) add(addr net.Addr) chan forward {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
f := forwardEntry{
|
||||
addr,
|
||||
make(chan forward, 1),
|
||||
laddr: addr,
|
||||
c: make(chan forward, 1),
|
||||
}
|
||||
l.entries = append(l.entries, f)
|
||||
return f.c
|
||||
|
@ -176,44 +184,69 @@ func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) {
|
|||
|
||||
func (l *forwardList) handleChannels(in <-chan NewChannel) {
|
||||
for ch := range in {
|
||||
var payload forwardedTCPPayload
|
||||
if err := Unmarshal(ch.ExtraData(), &payload); err != nil {
|
||||
ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error())
|
||||
continue
|
||||
}
|
||||
var (
|
||||
laddr net.Addr
|
||||
raddr net.Addr
|
||||
err error
|
||||
)
|
||||
switch channelType := ch.ChannelType(); channelType {
|
||||
case "forwarded-tcpip":
|
||||
var payload forwardedTCPPayload
|
||||
if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
|
||||
ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
// RFC 4254 section 7.2 specifies that incoming
|
||||
// addresses should list the address, in string
|
||||
// format. It is implied that this should be an IP
|
||||
// address, as it would be impossible to connect to it
|
||||
// otherwise.
|
||||
laddr, err := parseTCPAddr(payload.Addr, payload.Port)
|
||||
if err != nil {
|
||||
ch.Reject(ConnectionFailed, err.Error())
|
||||
continue
|
||||
}
|
||||
raddr, err := parseTCPAddr(payload.OriginAddr, payload.OriginPort)
|
||||
if err != nil {
|
||||
ch.Reject(ConnectionFailed, err.Error())
|
||||
continue
|
||||
}
|
||||
// RFC 4254 section 7.2 specifies that incoming
|
||||
// addresses should list the address, in string
|
||||
// format. It is implied that this should be an IP
|
||||
// address, as it would be impossible to connect to it
|
||||
// otherwise.
|
||||
laddr, err = parseTCPAddr(payload.Addr, payload.Port)
|
||||
if err != nil {
|
||||
ch.Reject(ConnectionFailed, err.Error())
|
||||
continue
|
||||
}
|
||||
raddr, err = parseTCPAddr(payload.OriginAddr, payload.OriginPort)
|
||||
if err != nil {
|
||||
ch.Reject(ConnectionFailed, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if ok := l.forward(*laddr, *raddr, ch); !ok {
|
||||
case "forwarded-streamlocal@openssh.com":
|
||||
var payload forwardedStreamLocalPayload
|
||||
if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
|
||||
ch.Reject(ConnectionFailed, "could not parse forwarded-streamlocal@openssh.com payload: "+err.Error())
|
||||
continue
|
||||
}
|
||||
laddr = &net.UnixAddr{
|
||||
Name: payload.SocketPath,
|
||||
Net: "unix",
|
||||
}
|
||||
raddr = &net.UnixAddr{
|
||||
Name: "@",
|
||||
Net: "unix",
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("ssh: unknown channel type %s", channelType))
|
||||
}
|
||||
if ok := l.forward(laddr, raddr, ch); !ok {
|
||||
// Section 7.2, implementations MUST reject spurious incoming
|
||||
// connections.
|
||||
ch.Reject(Prohibited, "no forward for address")
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// remove removes the forward entry, and the channel feeding its
|
||||
// listener.
|
||||
func (l *forwardList) remove(addr net.TCPAddr) {
|
||||
func (l *forwardList) remove(addr net.Addr) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
for i, f := range l.entries {
|
||||
if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port {
|
||||
if addr.Network() == f.laddr.Network() && addr.String() == f.laddr.String() {
|
||||
l.entries = append(l.entries[:i], l.entries[i+1:]...)
|
||||
close(f.c)
|
||||
return
|
||||
|
@ -231,12 +264,12 @@ func (l *forwardList) closeAll() {
|
|||
l.entries = nil
|
||||
}
|
||||
|
||||
func (l *forwardList) forward(laddr, raddr net.TCPAddr, ch NewChannel) bool {
|
||||
func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
for _, f := range l.entries {
|
||||
if laddr.IP.Equal(f.laddr.IP) && laddr.Port == f.laddr.Port {
|
||||
f.c <- forward{ch, &raddr}
|
||||
if laddr.Network() == f.laddr.Network() && laddr.String() == f.laddr.String() {
|
||||
f.c <- forward{newCh: ch, raddr: raddr}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -262,7 +295,7 @@ func (l *tcpListener) Accept() (net.Conn, error) {
|
|||
}
|
||||
go DiscardRequests(incoming)
|
||||
|
||||
return &tcpChanConn{
|
||||
return &chanConn{
|
||||
Channel: ch,
|
||||
laddr: l.laddr,
|
||||
raddr: s.raddr,
|
||||
|
@ -277,7 +310,7 @@ func (l *tcpListener) Close() error {
|
|||
}
|
||||
|
||||
// this also closes the listener.
|
||||
l.conn.forwards.remove(*l.laddr)
|
||||
l.conn.forwards.remove(l.laddr)
|
||||
ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m))
|
||||
if err == nil && !ok {
|
||||
err = errors.New("ssh: cancel-tcpip-forward failed")
|
||||
|
@ -293,29 +326,52 @@ func (l *tcpListener) Addr() net.Addr {
|
|||
// Dial initiates a connection to the addr from the remote host.
|
||||
// The resulting connection has a zero LocalAddr() and RemoteAddr().
|
||||
func (c *Client) Dial(n, addr string) (net.Conn, error) {
|
||||
// Parse the address into host and numeric port.
|
||||
host, portString, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var ch Channel
|
||||
switch n {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
// Parse the address into host and numeric port.
|
||||
host, portString, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port, err := strconv.ParseUint(portString, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ch, err = c.dial(net.IPv4zero.String(), 0, host, int(port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Use a zero address for local and remote address.
|
||||
zeroAddr := &net.TCPAddr{
|
||||
IP: net.IPv4zero,
|
||||
Port: 0,
|
||||
}
|
||||
return &chanConn{
|
||||
Channel: ch,
|
||||
laddr: zeroAddr,
|
||||
raddr: zeroAddr,
|
||||
}, nil
|
||||
case "unix":
|
||||
var err error
|
||||
ch, err = c.dialStreamLocal(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &chanConn{
|
||||
Channel: ch,
|
||||
laddr: &net.UnixAddr{
|
||||
Name: "@",
|
||||
Net: "unix",
|
||||
},
|
||||
raddr: &net.UnixAddr{
|
||||
Name: addr,
|
||||
Net: "unix",
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported protocol: %s", n)
|
||||
}
|
||||
port, err := strconv.ParseUint(portString, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Use a zero address for local and remote address.
|
||||
zeroAddr := &net.TCPAddr{
|
||||
IP: net.IPv4zero,
|
||||
Port: 0,
|
||||
}
|
||||
ch, err := c.dial(net.IPv4zero.String(), 0, host, int(port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tcpChanConn{
|
||||
Channel: ch,
|
||||
laddr: zeroAddr,
|
||||
raddr: zeroAddr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DialTCP connects to the remote address raddr on the network net,
|
||||
|
@ -332,7 +388,7 @@ func (c *Client) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error)
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tcpChanConn{
|
||||
return &chanConn{
|
||||
Channel: ch,
|
||||
laddr: laddr,
|
||||
raddr: raddr,
|
||||
|
@ -366,26 +422,26 @@ type tcpChan struct {
|
|||
Channel // the backing channel
|
||||
}
|
||||
|
||||
// tcpChanConn fulfills the net.Conn interface without
|
||||
// chanConn fulfills the net.Conn interface without
|
||||
// the tcpChan having to hold laddr or raddr directly.
|
||||
type tcpChanConn struct {
|
||||
type chanConn struct {
|
||||
Channel
|
||||
laddr, raddr net.Addr
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
func (t *tcpChanConn) LocalAddr() net.Addr {
|
||||
func (t *chanConn) LocalAddr() net.Addr {
|
||||
return t.laddr
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (t *tcpChanConn) RemoteAddr() net.Addr {
|
||||
func (t *chanConn) RemoteAddr() net.Addr {
|
||||
return t.raddr
|
||||
}
|
||||
|
||||
// SetDeadline sets the read and write deadlines associated
|
||||
// with the connection.
|
||||
func (t *tcpChanConn) SetDeadline(deadline time.Time) error {
|
||||
func (t *chanConn) SetDeadline(deadline time.Time) error {
|
||||
if err := t.SetReadDeadline(deadline); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -396,12 +452,14 @@ func (t *tcpChanConn) SetDeadline(deadline time.Time) error {
|
|||
// A zero value for t means Read will not time out.
|
||||
// After the deadline, the error from Read will implement net.Error
|
||||
// with Timeout() == true.
|
||||
func (t *tcpChanConn) SetReadDeadline(deadline time.Time) error {
|
||||
func (t *chanConn) SetReadDeadline(deadline time.Time) error {
|
||||
// for compatibility with previous version,
|
||||
// the error message contains "tcpChan"
|
||||
return errors.New("ssh: tcpChan: deadline not supported")
|
||||
}
|
||||
|
||||
// SetWriteDeadline exists to satisfy the net.Conn interface
|
||||
// but is not implemented by this type. It always returns an error.
|
||||
func (t *tcpChanConn) SetWriteDeadline(deadline time.Time) error {
|
||||
func (t *chanConn) SetWriteDeadline(deadline time.Time) error {
|
||||
return errors.New("ssh: tcpChan: deadline not supported")
|
||||
}
|
||||
|
|
|
@ -76,17 +76,17 @@ type connectionState struct {
|
|||
// both directions are triggered by reading and writing a msgNewKey packet
|
||||
// respectively.
|
||||
func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error {
|
||||
if ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult); err != nil {
|
||||
ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
t.reader.pendingKeyChange <- ciph
|
||||
}
|
||||
t.reader.pendingKeyChange <- ciph
|
||||
|
||||
if ciph, err := newPacketCipher(t.writer.dir, algs.w, kexResult); err != nil {
|
||||
ciph, err = newPacketCipher(t.writer.dir, algs.w, kexResult)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
t.writer.pendingKeyChange <- ciph
|
||||
}
|
||||
t.writer.pendingKeyChange <- ciph
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) {
|
|||
case cipher := <-s.pendingKeyChange:
|
||||
s.packetCipher = cipher
|
||||
default:
|
||||
return nil, errors.New("ssh: got bogus newkeys message.")
|
||||
return nil, errors.New("ssh: got bogus newkeys message")
|
||||
}
|
||||
|
||||
case msgDisconnect:
|
||||
|
@ -254,7 +254,7 @@ func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (pac
|
|||
iv, key, macKey := generateKeys(d, algs, kex)
|
||||
|
||||
if algs.Cipher == gcmCipherID {
|
||||
return newGCMCipher(iv, key, macKey)
|
||||
return newGCMCipher(iv, key)
|
||||
}
|
||||
|
||||
if algs.Cipher == aes128cbcID {
|
||||
|
|
|
@ -2252,16 +2252,20 @@
|
|||
"revisionTime": "2017-02-08T20:51:15Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "fsrFs762jlaILyqqQImS1GfvIvw=",
|
||||
"checksumSHA1": "pySTR3iSeU7FhjbBnQPNxgyIa4I=",
|
||||
"path": "golang.org/x/crypto/ssh",
|
||||
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
|
||||
"revisionTime": "2017-02-08T20:51:15Z"
|
||||
"revision": "d585fd2cc9195196078f516b69daff6744ef5e84",
|
||||
"revisionTime": "2017-12-16T04:08:15Z",
|
||||
"version": "master",
|
||||
"versionExact": "master"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "SJ3Ma3Ozavxpbh1usZWBCnzMKIc=",
|
||||
"checksumSHA1": "NMRX0onGReaL9IfLr0XQ3kl5Id0=",
|
||||
"path": "golang.org/x/crypto/ssh/agent",
|
||||
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
|
||||
"revisionTime": "2017-02-08T20:51:15Z"
|
||||
"revision": "d585fd2cc9195196078f516b69daff6744ef5e84",
|
||||
"revisionTime": "2017-12-16T04:08:15Z",
|
||||
"version": "master",
|
||||
"versionExact": "master"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=",
|
||||
|
|
Loading…
Reference in New Issue