move crypto ssh hack to external repo
This commit is contained in:
parent
928c2ffd2b
commit
1cdfa1870b
499 changed files with 4968 additions and 163911 deletions
20
Gopkg.lock
generated
20
Gopkg.lock
generated
|
|
@ -28,18 +28,20 @@
|
|||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["curve25519","ed25519","ed25519/internal/edwards25519","ssh","ssh/agent","ssh/terminal","ssh/testdata"]
|
||||
revision = "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "fb4cac33e3196ff7f507ab9b2d2a44b0142f5b5a"
|
||||
packages = [
|
||||
"curve25519",
|
||||
"ed25519",
|
||||
"ed25519/internal/edwards25519",
|
||||
"internal/chacha20",
|
||||
"poly1305",
|
||||
"ssh"
|
||||
]
|
||||
revision = "743ed784ccc2ebde7f34698af76a6acdf4e0c251"
|
||||
source = "https://github.com/tg123/sshpiper.crypto"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "b49a5156e489a901653f4402c2936f4d010e5d22b31dece100978b64b9d3a111"
|
||||
inputs-digest = "520641d8f3577123162ab4eca84b1d5765a60d84897a760e50b5073a0e7afddd"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
|
|||
|
|
@ -32,3 +32,9 @@
|
|||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/vvanpo/golang-pam"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
source = "https://github.com/tg123/sshpiper.crypto"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,683 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package agent implements the ssh-agent protocol, and provides both
|
||||
// a client and a server. The client can talk to a standard ssh-agent
|
||||
// that uses UNIX sockets, and one could implement an alternative
|
||||
// ssh-agent process using the sample server.
|
||||
//
|
||||
// References:
|
||||
// [PROTOCOL.agent]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent?rev=HEAD
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Agent represents the capabilities of an ssh-agent.
|
||||
type Agent interface {
|
||||
// List returns the identities known to the agent.
|
||||
List() ([]*Key, error)
|
||||
|
||||
// Sign has the agent sign the data using a protocol 2 key as defined
|
||||
// in [PROTOCOL.agent] section 2.6.2.
|
||||
Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error)
|
||||
|
||||
// Add adds a private key to the agent.
|
||||
Add(key AddedKey) error
|
||||
|
||||
// Remove removes all identities with the given public key.
|
||||
Remove(key ssh.PublicKey) error
|
||||
|
||||
// RemoveAll removes all identities.
|
||||
RemoveAll() error
|
||||
|
||||
// Lock locks the agent. Sign and Remove will fail, and List will empty an empty list.
|
||||
Lock(passphrase []byte) error
|
||||
|
||||
// Unlock undoes the effect of Lock
|
||||
Unlock(passphrase []byte) error
|
||||
|
||||
// Signers returns signers for all the known keys.
|
||||
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
|
||||
// *ecdsa.PrivateKey, which will be inserted into the agent.
|
||||
PrivateKey interface{}
|
||||
// Certificate, if not nil, is communicated to the agent and will be
|
||||
// stored with the key.
|
||||
Certificate *ssh.Certificate
|
||||
// Comment is an optional, free-form string.
|
||||
Comment string
|
||||
// LifetimeSecs, if not zero, is the number of seconds that the
|
||||
// agent will store the key for.
|
||||
LifetimeSecs uint32
|
||||
// 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.
|
||||
const (
|
||||
agentRequestV1Identities = 1
|
||||
agentRemoveAllV1Identities = 9
|
||||
|
||||
// 3.2 Requests from client to agent for protocol 2 key operations
|
||||
agentAddIdentity = 17
|
||||
agentRemoveIdentity = 18
|
||||
agentRemoveAllIdentities = 19
|
||||
agentAddIdConstrained = 25
|
||||
|
||||
// 3.3 Key-type independent requests from client to agent
|
||||
agentAddSmartcardKey = 20
|
||||
agentRemoveSmartcardKey = 21
|
||||
agentLock = 22
|
||||
agentUnlock = 23
|
||||
agentAddSmartcardKeyConstrained = 26
|
||||
|
||||
// 3.7 Key constraint identifiers
|
||||
agentConstrainLifetime = 1
|
||||
agentConstrainConfirm = 2
|
||||
agentConstrainExtension = 3
|
||||
)
|
||||
|
||||
// maxAgentResponseBytes is the maximum agent reply size that is accepted. This
|
||||
// is a sanity check, not a limit in the spec.
|
||||
const maxAgentResponseBytes = 16 << 20
|
||||
|
||||
// Agent messages:
|
||||
// These structures mirror the wire format of the corresponding ssh agent
|
||||
// messages found in [PROTOCOL.agent].
|
||||
|
||||
// 3.4 Generic replies from agent to client
|
||||
const agentFailure = 5
|
||||
|
||||
type failureAgentMsg struct{}
|
||||
|
||||
const agentSuccess = 6
|
||||
|
||||
type successAgentMsg struct{}
|
||||
|
||||
// See [PROTOCOL.agent], section 2.5.2.
|
||||
const agentRequestIdentities = 11
|
||||
|
||||
type requestIdentitiesAgentMsg struct{}
|
||||
|
||||
// See [PROTOCOL.agent], section 2.5.2.
|
||||
const agentIdentitiesAnswer = 12
|
||||
|
||||
type identitiesAnswerAgentMsg struct {
|
||||
NumKeys uint32 `sshtype:"12"`
|
||||
Keys []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// See [PROTOCOL.agent], section 2.6.2.
|
||||
const agentSignRequest = 13
|
||||
|
||||
type signRequestAgentMsg struct {
|
||||
KeyBlob []byte `sshtype:"13"`
|
||||
Data []byte
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// See [PROTOCOL.agent], section 2.6.2.
|
||||
|
||||
// 3.6 Replies from agent to client for protocol 2 key operations
|
||||
const agentSignResponse = 14
|
||||
|
||||
type signResponseAgentMsg struct {
|
||||
SigBlob []byte `sshtype:"14"`
|
||||
}
|
||||
|
||||
type publicKey struct {
|
||||
Format string
|
||||
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 {
|
||||
Format string
|
||||
Blob []byte
|
||||
Comment string
|
||||
}
|
||||
|
||||
func clientErr(err error) error {
|
||||
return fmt.Errorf("agent: client error: %v", err)
|
||||
}
|
||||
|
||||
// String returns the storage form of an agent key with the format, base64
|
||||
// encoded serialized key, and the comment if it is not empty.
|
||||
func (k *Key) String() string {
|
||||
s := string(k.Format) + " " + base64.StdEncoding.EncodeToString(k.Blob)
|
||||
|
||||
if k.Comment != "" {
|
||||
s += " " + k.Comment
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Type returns the public key type.
|
||||
func (k *Key) Type() string {
|
||||
return k.Format
|
||||
}
|
||||
|
||||
// Marshal returns key blob to satisfy the ssh.PublicKey interface.
|
||||
func (k *Key) Marshal() []byte {
|
||||
return k.Blob
|
||||
}
|
||||
|
||||
// Verify satisfies the ssh.PublicKey interface.
|
||||
func (k *Key) Verify(data []byte, sig *ssh.Signature) error {
|
||||
pubKey, err := ssh.ParsePublicKey(k.Blob)
|
||||
if err != nil {
|
||||
return fmt.Errorf("agent: bad public key: %v", err)
|
||||
}
|
||||
return pubKey.Verify(data, sig)
|
||||
}
|
||||
|
||||
type wireKey struct {
|
||||
Format string
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
func parseKey(in []byte) (out *Key, rest []byte, err error) {
|
||||
var record struct {
|
||||
Blob []byte
|
||||
Comment string
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
if err := ssh.Unmarshal(in, &record); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var wk wireKey
|
||||
if err := ssh.Unmarshal(record.Blob, &wk); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &Key{
|
||||
Format: wk.Format,
|
||||
Blob: record.Blob,
|
||||
Comment: record.Comment,
|
||||
}, record.Rest, nil
|
||||
}
|
||||
|
||||
// client is a client for an ssh-agent process.
|
||||
type client struct {
|
||||
// conn is typically a *net.UnixConn
|
||||
conn io.ReadWriter
|
||||
// mu is used to prevent concurrent access to the agent
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewClient returns an Agent that talks to an ssh-agent process over
|
||||
// the given connection.
|
||||
func NewClient(rw io.ReadWriter) Agent {
|
||||
return &client{conn: rw}
|
||||
}
|
||||
|
||||
// call sends an RPC to the agent. On success, the reply is
|
||||
// unmarshaled into reply and replyType is set to the first byte of
|
||||
// the reply, which contains the type of the message.
|
||||
func (c *client) call(req []byte) (reply interface{}, err error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
msg := make([]byte, 4+len(req))
|
||||
binary.BigEndian.PutUint32(msg, uint32(len(req)))
|
||||
copy(msg[4:], req)
|
||||
if _, err = c.conn.Write(msg); err != nil {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
|
||||
var respSizeBuf [4]byte
|
||||
if _, err = io.ReadFull(c.conn, respSizeBuf[:]); err != nil {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
respSize := binary.BigEndian.Uint32(respSizeBuf[:])
|
||||
if respSize > maxAgentResponseBytes {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
|
||||
buf := make([]byte, respSize)
|
||||
if _, err = io.ReadFull(c.conn, buf); err != nil {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
reply, err = unmarshal(buf)
|
||||
if err != nil {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
return reply, err
|
||||
}
|
||||
|
||||
func (c *client) simpleCall(req []byte) error {
|
||||
resp, err := c.call(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := resp.(*successAgentMsg); ok {
|
||||
return nil
|
||||
}
|
||||
return errors.New("agent: failure")
|
||||
}
|
||||
|
||||
func (c *client) RemoveAll() error {
|
||||
return c.simpleCall([]byte{agentRemoveAllIdentities})
|
||||
}
|
||||
|
||||
func (c *client) Remove(key ssh.PublicKey) error {
|
||||
req := ssh.Marshal(&agentRemoveIdentityMsg{
|
||||
KeyBlob: key.Marshal(),
|
||||
})
|
||||
return c.simpleCall(req)
|
||||
}
|
||||
|
||||
func (c *client) Lock(passphrase []byte) error {
|
||||
req := ssh.Marshal(&agentLockMsg{
|
||||
Passphrase: passphrase,
|
||||
})
|
||||
return c.simpleCall(req)
|
||||
}
|
||||
|
||||
func (c *client) Unlock(passphrase []byte) error {
|
||||
req := ssh.Marshal(&agentUnlockMsg{
|
||||
Passphrase: passphrase,
|
||||
})
|
||||
return c.simpleCall(req)
|
||||
}
|
||||
|
||||
// List returns the identities known to the agent.
|
||||
func (c *client) List() ([]*Key, error) {
|
||||
// see [PROTOCOL.agent] section 2.5.2.
|
||||
req := []byte{agentRequestIdentities}
|
||||
|
||||
msg, err := c.call(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *identitiesAnswerAgentMsg:
|
||||
if msg.NumKeys > maxAgentResponseBytes/8 {
|
||||
return nil, errors.New("agent: too many keys in agent reply")
|
||||
}
|
||||
keys := make([]*Key, msg.NumKeys)
|
||||
data := msg.Keys
|
||||
for i := uint32(0); i < msg.NumKeys; i++ {
|
||||
var key *Key
|
||||
var err error
|
||||
if key, data, err = parseKey(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys[i] = key
|
||||
}
|
||||
return keys, nil
|
||||
case *failureAgentMsg:
|
||||
return nil, errors.New("agent: failed to list keys")
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Sign has the agent sign the data using a protocol 2 key as defined
|
||||
// in [PROTOCOL.agent] section 2.6.2.
|
||||
func (c *client) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
||||
req := ssh.Marshal(signRequestAgentMsg{
|
||||
KeyBlob: key.Marshal(),
|
||||
Data: data,
|
||||
})
|
||||
|
||||
msg, err := c.call(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *signResponseAgentMsg:
|
||||
var sig ssh.Signature
|
||||
if err := ssh.Unmarshal(msg.SigBlob, &sig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &sig, nil
|
||||
case *failureAgentMsg:
|
||||
return nil, errors.New("agent: failed to sign challenge")
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// unmarshal parses an agent message in packet, returning the parsed
|
||||
// form and the message type of packet.
|
||||
func unmarshal(packet []byte) (interface{}, error) {
|
||||
if len(packet) < 1 {
|
||||
return nil, errors.New("agent: empty packet")
|
||||
}
|
||||
var msg interface{}
|
||||
switch packet[0] {
|
||||
case agentFailure:
|
||||
return new(failureAgentMsg), nil
|
||||
case agentSuccess:
|
||||
return new(successAgentMsg), nil
|
||||
case agentIdentitiesAnswer:
|
||||
msg = new(identitiesAnswerAgentMsg)
|
||||
case agentSignResponse:
|
||||
msg = new(signResponseAgentMsg)
|
||||
case agentV1IdentitiesAnswer:
|
||||
msg = new(agentV1IdentityMsg)
|
||||
default:
|
||||
return nil, fmt.Errorf("agent: unknown type tag %d", packet[0])
|
||||
}
|
||||
if err := ssh.Unmarshal(packet, msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
type rsaKeyMsg struct {
|
||||
Type string `sshtype:"17|25"`
|
||||
N *big.Int
|
||||
E *big.Int
|
||||
D *big.Int
|
||||
Iqmp *big.Int // IQMP = Inverse Q Mod P
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type dsaKeyMsg struct {
|
||||
Type string `sshtype:"17|25"`
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
G *big.Int
|
||||
Y *big.Int
|
||||
X *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type ecdsaKeyMsg struct {
|
||||
Type string `sshtype:"17|25"`
|
||||
Curve string
|
||||
KeyBytes []byte
|
||||
D *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type ed25519KeyMsg struct {
|
||||
Type string `sshtype:"17|25"`
|
||||
Pub []byte
|
||||
Priv []byte
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// Insert adds a private key to the agent.
|
||||
func (c *client) insertKey(s interface{}, comment string, constraints []byte) error {
|
||||
var req []byte
|
||||
switch k := s.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
if len(k.Primes) != 2 {
|
||||
return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes))
|
||||
}
|
||||
k.Precompute()
|
||||
req = ssh.Marshal(rsaKeyMsg{
|
||||
Type: ssh.KeyAlgoRSA,
|
||||
N: k.N,
|
||||
E: big.NewInt(int64(k.E)),
|
||||
D: k.D,
|
||||
Iqmp: k.Precomputed.Qinv,
|
||||
P: k.Primes[0],
|
||||
Q: k.Primes[1],
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
case *dsa.PrivateKey:
|
||||
req = ssh.Marshal(dsaKeyMsg{
|
||||
Type: ssh.KeyAlgoDSA,
|
||||
P: k.P,
|
||||
Q: k.Q,
|
||||
G: k.G,
|
||||
Y: k.Y,
|
||||
X: k.X,
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
case *ecdsa.PrivateKey:
|
||||
nistID := fmt.Sprintf("nistp%d", k.Params().BitSize)
|
||||
req = ssh.Marshal(ecdsaKeyMsg{
|
||||
Type: "ecdsa-sha2-" + nistID,
|
||||
Curve: nistID,
|
||||
KeyBytes: elliptic.Marshal(k.Curve, k.X, k.Y),
|
||||
D: k.D,
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
case *ed25519.PrivateKey:
|
||||
req = ssh.Marshal(ed25519KeyMsg{
|
||||
Type: ssh.KeyAlgoED25519,
|
||||
Pub: []byte(*k)[32:],
|
||||
Priv: []byte(*k),
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("agent: unsupported key type %T", s)
|
||||
}
|
||||
|
||||
// if constraints are present then the message type needs to be changed.
|
||||
if len(constraints) != 0 {
|
||||
req[0] = agentAddIdConstrained
|
||||
}
|
||||
|
||||
resp, err := c.call(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := resp.(*successAgentMsg); ok {
|
||||
return nil
|
||||
}
|
||||
return errors.New("agent: failure")
|
||||
}
|
||||
|
||||
type rsaCertMsg struct {
|
||||
Type string `sshtype:"17|25"`
|
||||
CertBytes []byte
|
||||
D *big.Int
|
||||
Iqmp *big.Int // IQMP = Inverse Q Mod P
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type dsaCertMsg struct {
|
||||
Type string `sshtype:"17|25"`
|
||||
CertBytes []byte
|
||||
X *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type ecdsaCertMsg struct {
|
||||
Type string `sshtype:"17|25"`
|
||||
CertBytes []byte
|
||||
D *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type ed25519CertMsg struct {
|
||||
Type string `sshtype:"17|25"`
|
||||
CertBytes []byte
|
||||
Pub []byte
|
||||
Priv []byte
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// Add adds a private key to the agent. If a certificate is given,
|
||||
// that certificate is added instead as public key.
|
||||
func (c *client) Add(key AddedKey) error {
|
||||
var constraints []byte
|
||||
|
||||
if secs := key.LifetimeSecs; secs != 0 {
|
||||
constraints = append(constraints, ssh.Marshal(constrainLifetimeAgentMsg{secs})...)
|
||||
}
|
||||
|
||||
if key.ConfirmBeforeUse {
|
||||
constraints = append(constraints, agentConstrainConfirm)
|
||||
}
|
||||
|
||||
if cert := key.Certificate; cert == nil {
|
||||
return c.insertKey(key.PrivateKey, key.Comment, constraints)
|
||||
} else {
|
||||
return c.insertCert(key.PrivateKey, cert, key.Comment, constraints)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string, constraints []byte) error {
|
||||
var req []byte
|
||||
switch k := s.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
if len(k.Primes) != 2 {
|
||||
return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes))
|
||||
}
|
||||
k.Precompute()
|
||||
req = ssh.Marshal(rsaCertMsg{
|
||||
Type: cert.Type(),
|
||||
CertBytes: cert.Marshal(),
|
||||
D: k.D,
|
||||
Iqmp: k.Precomputed.Qinv,
|
||||
P: k.Primes[0],
|
||||
Q: k.Primes[1],
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
case *dsa.PrivateKey:
|
||||
req = ssh.Marshal(dsaCertMsg{
|
||||
Type: cert.Type(),
|
||||
CertBytes: cert.Marshal(),
|
||||
X: k.X,
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
case *ecdsa.PrivateKey:
|
||||
req = ssh.Marshal(ecdsaCertMsg{
|
||||
Type: cert.Type(),
|
||||
CertBytes: cert.Marshal(),
|
||||
D: k.D,
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
case *ed25519.PrivateKey:
|
||||
req = ssh.Marshal(ed25519CertMsg{
|
||||
Type: cert.Type(),
|
||||
CertBytes: cert.Marshal(),
|
||||
Pub: []byte(*k)[32:],
|
||||
Priv: []byte(*k),
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("agent: unsupported key type %T", s)
|
||||
}
|
||||
|
||||
// if constraints are present then the message type needs to be changed.
|
||||
if len(constraints) != 0 {
|
||||
req[0] = agentAddIdConstrained
|
||||
}
|
||||
|
||||
signer, err := ssh.NewSignerFromKey(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 {
|
||||
return errors.New("agent: signer and cert have different public key")
|
||||
}
|
||||
|
||||
resp, err := c.call(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := resp.(*successAgentMsg); ok {
|
||||
return nil
|
||||
}
|
||||
return errors.New("agent: failure")
|
||||
}
|
||||
|
||||
// Signers provides a callback for client authentication.
|
||||
func (c *client) Signers() ([]ssh.Signer, error) {
|
||||
keys, err := c.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []ssh.Signer
|
||||
for _, k := range keys {
|
||||
result = append(result, &agentKeyringSigner{c, k})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type agentKeyringSigner struct {
|
||||
agent *client
|
||||
pub ssh.PublicKey
|
||||
}
|
||||
|
||||
func (s *agentKeyringSigner) PublicKey() ssh.PublicKey {
|
||||
return s.pub
|
||||
}
|
||||
|
||||
func (s *agentKeyringSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
|
||||
// The agent has its own entropy source, so the rand argument is ignored.
|
||||
return s.agent.Sign(s.pub, data)
|
||||
}
|
||||
|
|
@ -1,348 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// startAgent executes ssh-agent, and returns a Agent interface to it.
|
||||
func startAgent(t *testing.T) (client Agent, socket string, cleanup func()) {
|
||||
if testing.Short() {
|
||||
// ssh-agent is not always available, and the key
|
||||
// types supported vary by platform.
|
||||
t.Skip("skipping test due to -short")
|
||||
}
|
||||
|
||||
bin, err := exec.LookPath("ssh-agent")
|
||||
if err != nil {
|
||||
t.Skip("could not find ssh-agent")
|
||||
}
|
||||
|
||||
cmd := exec.Command(bin, "-s")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t.Fatalf("cmd.Output: %v", err)
|
||||
}
|
||||
|
||||
/* Output looks like:
|
||||
|
||||
SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_AUTH_SOCK;
|
||||
SSH_AGENT_PID=15542; export SSH_AGENT_PID;
|
||||
echo Agent pid 15542;
|
||||
*/
|
||||
fields := bytes.Split(out, []byte(";"))
|
||||
line := bytes.SplitN(fields[0], []byte("="), 2)
|
||||
line[0] = bytes.TrimLeft(line[0], "\n")
|
||||
if string(line[0]) != "SSH_AUTH_SOCK" {
|
||||
t.Fatalf("could not find key SSH_AUTH_SOCK in %q", fields[0])
|
||||
}
|
||||
socket = string(line[1])
|
||||
|
||||
line = bytes.SplitN(fields[2], []byte("="), 2)
|
||||
line[0] = bytes.TrimLeft(line[0], "\n")
|
||||
if string(line[0]) != "SSH_AGENT_PID" {
|
||||
t.Fatalf("could not find key SSH_AGENT_PID in %q", fields[2])
|
||||
}
|
||||
pidStr := line[1]
|
||||
pid, err := strconv.Atoi(string(pidStr))
|
||||
if err != nil {
|
||||
t.Fatalf("Atoi(%q): %v", pidStr, err)
|
||||
}
|
||||
|
||||
conn, err := net.Dial("unix", string(socket))
|
||||
if err != nil {
|
||||
t.Fatalf("net.Dial: %v", err)
|
||||
}
|
||||
|
||||
ac := NewClient(conn)
|
||||
return ac, socket, func() {
|
||||
proc, _ := os.FindProcess(pid)
|
||||
if proc != nil {
|
||||
proc.Kill()
|
||||
}
|
||||
conn.Close()
|
||||
os.RemoveAll(filepath.Dir(socket))
|
||||
}
|
||||
}
|
||||
|
||||
func testAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||
agent, _, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
testAgentInterface(t, agent, key, cert, lifetimeSecs)
|
||||
}
|
||||
|
||||
func testKeyring(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||
a := NewKeyring()
|
||||
testAgentInterface(t, a, key, cert, lifetimeSecs)
|
||||
}
|
||||
|
||||
func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||
signer, err := ssh.NewSignerFromKey(key)
|
||||
if err != nil {
|
||||
t.Fatalf("NewSignerFromKey(%T): %v", key, err)
|
||||
}
|
||||
// The agent should start up empty.
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Fatalf("RequestIdentities: %v", err)
|
||||
} else if len(keys) > 0 {
|
||||
t.Fatalf("got %d keys, want 0: %v", len(keys), keys)
|
||||
}
|
||||
|
||||
// Attempt to insert the key, with certificate if specified.
|
||||
var pubKey ssh.PublicKey
|
||||
if cert != nil {
|
||||
err = agent.Add(AddedKey{
|
||||
PrivateKey: key,
|
||||
Certificate: cert,
|
||||
Comment: "comment",
|
||||
LifetimeSecs: lifetimeSecs,
|
||||
})
|
||||
pubKey = cert
|
||||
} else {
|
||||
err = agent.Add(AddedKey{PrivateKey: key, Comment: "comment", LifetimeSecs: lifetimeSecs})
|
||||
pubKey = signer.PublicKey()
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("insert(%T): %v", key, err)
|
||||
}
|
||||
|
||||
// Did the key get inserted successfully?
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Fatalf("List: %v", err)
|
||||
} else if len(keys) != 1 {
|
||||
t.Fatalf("got %v, want 1 key", keys)
|
||||
} else if keys[0].Comment != "comment" {
|
||||
t.Fatalf("key comment: got %v, want %v", keys[0].Comment, "comment")
|
||||
} else if !bytes.Equal(keys[0].Blob, pubKey.Marshal()) {
|
||||
t.Fatalf("key mismatch")
|
||||
}
|
||||
|
||||
// Can the agent make a valid signature?
|
||||
data := []byte("hello")
|
||||
sig, err := agent.Sign(pubKey, data)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign(%s): %v", pubKey.Type(), err)
|
||||
}
|
||||
|
||||
if err := pubKey.Verify(data, sig); err != nil {
|
||||
t.Fatalf("Verify(%s): %v", pubKey.Type(), err)
|
||||
}
|
||||
|
||||
// If the key has a lifetime, is it removed when it should be?
|
||||
if lifetimeSecs > 0 {
|
||||
time.Sleep(time.Second*time.Duration(lifetimeSecs) + 100*time.Millisecond)
|
||||
keys, err := agent.List()
|
||||
if err != nil {
|
||||
t.Fatalf("List: %v", err)
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
t.Fatalf("key not expired")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAgent(t *testing.T) {
|
||||
for _, keyType := range []string{"rsa", "dsa", "ecdsa", "ed25519"} {
|
||||
testAgent(t, testPrivateKeys[keyType], nil, 0)
|
||||
testKeyring(t, testPrivateKeys[keyType], nil, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCert(t *testing.T) {
|
||||
cert := &ssh.Certificate{
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: ssh.CertTimeInfinity,
|
||||
CertType: ssh.UserCert,
|
||||
}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
|
||||
testAgent(t, testPrivateKeys["rsa"], cert, 0)
|
||||
testKeyring(t, testPrivateKeys["rsa"], cert, 1)
|
||||
}
|
||||
|
||||
// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
|
||||
// therefore is buffered (net.Pipe deadlocks if both sides start with
|
||||
// a write.)
|
||||
func netPipe() (net.Conn, net.Conn, error) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
listener, err = net.Listen("tcp", "[::1]:0")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
defer listener.Close()
|
||||
c1, err := net.Dial("tcp", listener.Addr().String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c2, err := listener.Accept()
|
||||
if err != nil {
|
||||
c1.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return c1, c2, nil
|
||||
}
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
agent, _, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment"}); err != nil {
|
||||
t.Errorf("Add: %v", err)
|
||||
}
|
||||
|
||||
serverConf := ssh.ServerConfig{}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
serverConf.PublicKeyCallback = func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("pubkey rejected")
|
||||
}
|
||||
|
||||
go func() {
|
||||
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Server: %v", err)
|
||||
}
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
conf := ssh.ClientConfig{
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
conf.Auth = append(conf.Auth, ssh.PublicKeysCallback(agent.Signers))
|
||||
conn, _, _, err := ssh.NewClientConn(b, "", &conf)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientConn: %v", err)
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func TestLockClient(t *testing.T) {
|
||||
agent, _, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
testLockAgent(agent, t)
|
||||
}
|
||||
|
||||
func testLockAgent(agent Agent, t *testing.T) {
|
||||
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment 1"}); err != nil {
|
||||
t.Errorf("Add: %v", err)
|
||||
}
|
||||
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["dsa"], Comment: "comment dsa"}); err != nil {
|
||||
t.Errorf("Add: %v", err)
|
||||
}
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Errorf("List: %v", err)
|
||||
} else if len(keys) != 2 {
|
||||
t.Errorf("Want 2 keys, got %v", keys)
|
||||
}
|
||||
|
||||
passphrase := []byte("secret")
|
||||
if err := agent.Lock(passphrase); err != nil {
|
||||
t.Errorf("Lock: %v", err)
|
||||
}
|
||||
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Errorf("List: %v", err)
|
||||
} else if len(keys) != 0 {
|
||||
t.Errorf("Want 0 keys, got %v", keys)
|
||||
}
|
||||
|
||||
signer, _ := ssh.NewSignerFromKey(testPrivateKeys["rsa"])
|
||||
if _, err := agent.Sign(signer.PublicKey(), []byte("hello")); err == nil {
|
||||
t.Fatalf("Sign did not fail")
|
||||
}
|
||||
|
||||
if err := agent.Remove(signer.PublicKey()); err == nil {
|
||||
t.Fatalf("Remove did not fail")
|
||||
}
|
||||
|
||||
if err := agent.RemoveAll(); err == nil {
|
||||
t.Fatalf("RemoveAll did not fail")
|
||||
}
|
||||
|
||||
if err := agent.Unlock(nil); err == nil {
|
||||
t.Errorf("Unlock with wrong passphrase succeeded")
|
||||
}
|
||||
if err := agent.Unlock(passphrase); err != nil {
|
||||
t.Errorf("Unlock: %v", err)
|
||||
}
|
||||
|
||||
if err := agent.Remove(signer.PublicKey()); err != nil {
|
||||
t.Fatalf("Remove: %v", err)
|
||||
}
|
||||
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Errorf("List: %v", err)
|
||||
} else if len(keys) != 1 {
|
||||
t.Errorf("Want 1 keys, got %v", keys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentLifetime(t *testing.T) {
|
||||
agent, _, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
for _, keyType := range []string{"rsa", "dsa", "ecdsa"} {
|
||||
// Add private keys to the agent.
|
||||
err := agent.Add(AddedKey{
|
||||
PrivateKey: testPrivateKeys[keyType],
|
||||
Comment: "comment",
|
||||
LifetimeSecs: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("add: %v", err)
|
||||
}
|
||||
// Add certs to the agent.
|
||||
cert := &ssh.Certificate{
|
||||
Key: testPublicKeys[keyType],
|
||||
ValidBefore: ssh.CertTimeInfinity,
|
||||
CertType: ssh.UserCert,
|
||||
}
|
||||
cert.SignCert(rand.Reader, testSigners[keyType])
|
||||
err = agent.Add(AddedKey{
|
||||
PrivateKey: testPrivateKeys[keyType],
|
||||
Certificate: cert,
|
||||
Comment: "comment",
|
||||
LifetimeSecs: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("add: %v", err)
|
||||
}
|
||||
}
|
||||
time.Sleep(1100 * time.Millisecond)
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Errorf("List: %v", err)
|
||||
} else if len(keys) != 0 {
|
||||
t.Errorf("Want 0 keys, got %v", len(keys))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent_test
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
func ExampleClientAgent() {
|
||||
// ssh-agent has a UNIX socket under $SSH_AUTH_SOCK
|
||||
socket := os.Getenv("SSH_AUTH_SOCK")
|
||||
conn, err := net.Dial("unix", socket)
|
||||
if err != nil {
|
||||
log.Fatalf("net.Dial: %v", err)
|
||||
}
|
||||
agentClient := agent.NewClient(conn)
|
||||
config := &ssh.ClientConfig{
|
||||
User: "username",
|
||||
Auth: []ssh.AuthMethod{
|
||||
// Use a callback rather than PublicKeys
|
||||
// so we only consult the agent once the remote server
|
||||
// wants it.
|
||||
ssh.PublicKeysCallback(agentClient.Signers),
|
||||
},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
sshc, err := ssh.Dial("tcp", "localhost:22", config)
|
||||
if err != nil {
|
||||
log.Fatalf("Dial: %v", err)
|
||||
}
|
||||
// .. use sshc
|
||||
sshc.Close()
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// RequestAgentForwarding sets up agent forwarding for the session.
|
||||
// ForwardToAgent or ForwardToRemote should be called to route
|
||||
// the authentication requests.
|
||||
func RequestAgentForwarding(session *ssh.Session) error {
|
||||
ok, err := session.SendRequest("auth-agent-req@openssh.com", true, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return errors.New("forwarding request denied")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForwardToAgent routes authentication requests to the given keyring.
|
||||
func ForwardToAgent(client *ssh.Client, keyring Agent) error {
|
||||
channels := client.HandleChannelOpen(channelType)
|
||||
if channels == nil {
|
||||
return errors.New("agent: already have handler for " + channelType)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for ch := range channels {
|
||||
channel, reqs, err := ch.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
go func() {
|
||||
ServeAgent(keyring, channel)
|
||||
channel.Close()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
const channelType = "auth-agent@openssh.com"
|
||||
|
||||
// ForwardToRemote routes authentication requests to the ssh-agent
|
||||
// process serving on the given unix socket.
|
||||
func ForwardToRemote(client *ssh.Client, addr string) error {
|
||||
channels := client.HandleChannelOpen(channelType)
|
||||
if channels == nil {
|
||||
return errors.New("agent: already have handler for " + channelType)
|
||||
}
|
||||
conn, err := net.Dial("unix", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
go func() {
|
||||
for ch := range channels {
|
||||
channel, reqs, err := ch.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
go forwardUnixSocket(channel, addr)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func forwardUnixSocket(channel ssh.Channel, addr string) {
|
||||
conn, err := net.Dial("unix", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
io.Copy(conn, channel)
|
||||
conn.(*net.UnixConn).CloseWrite()
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
io.Copy(channel, conn)
|
||||
channel.CloseWrite()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
conn.Close()
|
||||
channel.Close()
|
||||
}
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type privKey struct {
|
||||
signer ssh.Signer
|
||||
comment string
|
||||
expire *time.Time
|
||||
}
|
||||
|
||||
type keyring struct {
|
||||
mu sync.Mutex
|
||||
keys []privKey
|
||||
|
||||
locked bool
|
||||
passphrase []byte
|
||||
}
|
||||
|
||||
var errLocked = errors.New("agent: locked")
|
||||
|
||||
// NewKeyring returns an Agent that holds keys in memory. It is safe
|
||||
// for concurrent use by multiple goroutines.
|
||||
func NewKeyring() Agent {
|
||||
return &keyring{}
|
||||
}
|
||||
|
||||
// RemoveAll removes all identities.
|
||||
func (r *keyring) RemoveAll() error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
|
||||
r.keys = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeLocked does the actual key removal. The caller must already be holding the
|
||||
// keyring mutex.
|
||||
func (r *keyring) removeLocked(want []byte) error {
|
||||
found := false
|
||||
for i := 0; i < len(r.keys); {
|
||||
if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) {
|
||||
found = true
|
||||
r.keys[i] = r.keys[len(r.keys)-1]
|
||||
r.keys = r.keys[:len(r.keys)-1]
|
||||
continue
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return errors.New("agent: key not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes all identities with the given public key.
|
||||
func (r *keyring) Remove(key ssh.PublicKey) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
|
||||
return r.removeLocked(key.Marshal())
|
||||
}
|
||||
|
||||
// Lock locks the agent. Sign and Remove will fail, and List will return an empty list.
|
||||
func (r *keyring) Lock(passphrase []byte) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
|
||||
r.locked = true
|
||||
r.passphrase = passphrase
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock undoes the effect of Lock
|
||||
func (r *keyring) Unlock(passphrase []byte) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if !r.locked {
|
||||
return errors.New("agent: not locked")
|
||||
}
|
||||
if len(passphrase) != len(r.passphrase) || 1 != subtle.ConstantTimeCompare(passphrase, r.passphrase) {
|
||||
return fmt.Errorf("agent: incorrect passphrase")
|
||||
}
|
||||
|
||||
r.locked = false
|
||||
r.passphrase = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// expireKeysLocked removes expired keys from the keyring. If a key was added
|
||||
// with a lifetimesecs contraint and seconds >= lifetimesecs seconds have
|
||||
// ellapsed, it is removed. The caller *must* be holding the keyring mutex.
|
||||
func (r *keyring) expireKeysLocked() {
|
||||
for _, k := range r.keys {
|
||||
if k.expire != nil && time.Now().After(*k.expire) {
|
||||
r.removeLocked(k.signer.PublicKey().Marshal())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List returns the identities known to the agent.
|
||||
func (r *keyring) List() ([]*Key, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
// section 2.7: locked agents return empty.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
r.expireKeysLocked()
|
||||
var ids []*Key
|
||||
for _, k := range r.keys {
|
||||
pub := k.signer.PublicKey()
|
||||
ids = append(ids, &Key{
|
||||
Format: pub.Type(),
|
||||
Blob: pub.Marshal(),
|
||||
Comment: k.comment})
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// Insert adds a private key to the keyring. If a certificate
|
||||
// is given, that certificate is added as public key. Note that
|
||||
// any constraints given are ignored.
|
||||
func (r *keyring) Add(key AddedKey) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
signer, err := ssh.NewSignerFromKey(key.PrivateKey)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cert := key.Certificate; cert != nil {
|
||||
signer, err = ssh.NewCertSigner(cert, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
p := privKey{
|
||||
signer: signer,
|
||||
comment: key.Comment,
|
||||
}
|
||||
|
||||
if key.LifetimeSecs > 0 {
|
||||
t := time.Now().Add(time.Duration(key.LifetimeSecs) * time.Second)
|
||||
p.expire = &t
|
||||
}
|
||||
|
||||
r.keys = append(r.keys, p)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign returns a signature for the data.
|
||||
func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return nil, errLocked
|
||||
}
|
||||
|
||||
r.expireKeysLocked()
|
||||
wanted := key.Marshal()
|
||||
for _, k := range r.keys {
|
||||
if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) {
|
||||
return k.signer.Sign(rand.Reader, data)
|
||||
}
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
// Signers returns signers for all the known keys.
|
||||
func (r *keyring) Signers() ([]ssh.Signer, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return nil, errLocked
|
||||
}
|
||||
|
||||
r.expireKeysLocked()
|
||||
s := make([]ssh.Signer, 0, len(r.keys))
|
||||
for _, k := range r.keys {
|
||||
s = append(s, k.signer)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import "testing"
|
||||
|
||||
func addTestKey(t *testing.T, a Agent, keyName string) {
|
||||
err := a.Add(AddedKey{
|
||||
PrivateKey: testPrivateKeys[keyName],
|
||||
Comment: keyName,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add key %q: %v", keyName, err)
|
||||
}
|
||||
}
|
||||
|
||||
func removeTestKey(t *testing.T, a Agent, keyName string) {
|
||||
err := a.Remove(testPublicKeys[keyName])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to remove key %q: %v", keyName, err)
|
||||
}
|
||||
}
|
||||
|
||||
func validateListedKeys(t *testing.T, a Agent, expectedKeys []string) {
|
||||
listedKeys, err := a.List()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to list keys: %v", err)
|
||||
return
|
||||
}
|
||||
actualKeys := make(map[string]bool)
|
||||
for _, key := range listedKeys {
|
||||
actualKeys[key.Comment] = true
|
||||
}
|
||||
|
||||
matchedKeys := make(map[string]bool)
|
||||
for _, expectedKey := range expectedKeys {
|
||||
if !actualKeys[expectedKey] {
|
||||
t.Fatalf("expected key %q, but was not found", expectedKey)
|
||||
} else {
|
||||
matchedKeys[expectedKey] = true
|
||||
}
|
||||
}
|
||||
|
||||
for actualKey := range actualKeys {
|
||||
if !matchedKeys[actualKey] {
|
||||
t.Fatalf("key %q was found, but was not expected", actualKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyringAddingAndRemoving(t *testing.T) {
|
||||
keyNames := []string{"dsa", "ecdsa", "rsa", "user"}
|
||||
|
||||
// add all test private keys
|
||||
k := NewKeyring()
|
||||
for _, keyName := range keyNames {
|
||||
addTestKey(t, k, keyName)
|
||||
}
|
||||
validateListedKeys(t, k, keyNames)
|
||||
|
||||
// remove a key in the middle
|
||||
keyToRemove := keyNames[1]
|
||||
keyNames = append(keyNames[:1], keyNames[2:]...)
|
||||
|
||||
removeTestKey(t, k, keyToRemove)
|
||||
validateListedKeys(t, k, keyNames)
|
||||
|
||||
// remove all keys
|
||||
err := k.RemoveAll()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to remove all keys: %v", err)
|
||||
}
|
||||
validateListedKeys(t, k, []string{})
|
||||
}
|
||||
|
|
@ -1,523 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/big"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Server wraps an Agent and uses it to implement the agent side of
|
||||
// the SSH-agent, wire protocol.
|
||||
type server struct {
|
||||
agent Agent
|
||||
}
|
||||
|
||||
func (s *server) processRequestBytes(reqData []byte) []byte {
|
||||
rep, err := s.processRequest(reqData)
|
||||
if err != nil {
|
||||
if err != errLocked {
|
||||
// TODO(hanwen): provide better logging interface?
|
||||
log.Printf("agent %d: %v", reqData[0], err)
|
||||
}
|
||||
return []byte{agentFailure}
|
||||
}
|
||||
|
||||
if err == nil && rep == nil {
|
||||
return []byte{agentSuccess}
|
||||
}
|
||||
|
||||
return ssh.Marshal(rep)
|
||||
}
|
||||
|
||||
func marshalKey(k *Key) []byte {
|
||||
var record struct {
|
||||
Blob []byte
|
||||
Comment string
|
||||
}
|
||||
record.Blob = k.Marshal()
|
||||
record.Comment = k.Comment
|
||||
|
||||
return ssh.Marshal(&record)
|
||||
}
|
||||
|
||||
// See [PROTOCOL.agent], section 2.5.1.
|
||||
const agentV1IdentitiesAnswer = 2
|
||||
|
||||
type agentV1IdentityMsg struct {
|
||||
Numkeys uint32 `sshtype:"2"`
|
||||
}
|
||||
|
||||
type agentRemoveIdentityMsg struct {
|
||||
KeyBlob []byte `sshtype:"18"`
|
||||
}
|
||||
|
||||
type agentLockMsg struct {
|
||||
Passphrase []byte `sshtype:"22"`
|
||||
}
|
||||
|
||||
type agentUnlockMsg struct {
|
||||
Passphrase []byte `sshtype:"23"`
|
||||
}
|
||||
|
||||
func (s *server) processRequest(data []byte) (interface{}, error) {
|
||||
switch data[0] {
|
||||
case agentRequestV1Identities:
|
||||
return &agentV1IdentityMsg{0}, nil
|
||||
|
||||
case agentRemoveAllV1Identities:
|
||||
return nil, nil
|
||||
|
||||
case agentRemoveIdentity:
|
||||
var req agentRemoveIdentityMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wk wireKey
|
||||
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, s.agent.Remove(&Key{Format: wk.Format, Blob: req.KeyBlob})
|
||||
|
||||
case agentRemoveAllIdentities:
|
||||
return nil, s.agent.RemoveAll()
|
||||
|
||||
case agentLock:
|
||||
var req agentLockMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, s.agent.Lock(req.Passphrase)
|
||||
|
||||
case agentUnlock:
|
||||
var req agentLockMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, s.agent.Unlock(req.Passphrase)
|
||||
|
||||
case agentSignRequest:
|
||||
var req signRequestAgentMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wk wireKey
|
||||
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k := &Key{
|
||||
Format: wk.Format,
|
||||
Blob: req.KeyBlob,
|
||||
}
|
||||
|
||||
sig, err := s.agent.Sign(k, req.Data) // TODO(hanwen): flags.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil
|
||||
|
||||
case agentRequestIdentities:
|
||||
keys, err := s.agent.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rep := identitiesAnswerAgentMsg{
|
||||
NumKeys: uint32(len(keys)),
|
||||
}
|
||||
for _, k := range keys {
|
||||
rep.Keys = append(rep.Keys, marshalKey(k)...)
|
||||
}
|
||||
return rep, nil
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
if k.E.BitLen() > 30 {
|
||||
return nil, errors.New("agent: RSA public exponent too large")
|
||||
}
|
||||
priv := &rsa.PrivateKey{
|
||||
PublicKey: rsa.PublicKey{
|
||||
E: int(k.E.Int64()),
|
||||
N: k.N,
|
||||
},
|
||||
D: k.D,
|
||||
Primes: []*big.Int{k.P, k.Q},
|
||||
}
|
||||
priv.Precompute()
|
||||
|
||||
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) {
|
||||
var k ed25519KeyMsg
|
||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
priv := ed25519.PrivateKey(k.Priv)
|
||||
|
||||
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) {
|
||||
var k dsaKeyMsg
|
||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
priv := &dsa.PrivateKey{
|
||||
PublicKey: dsa.PublicKey{
|
||||
Parameters: dsa.Parameters{
|
||||
P: k.P,
|
||||
Q: k.Q,
|
||||
G: k.G,
|
||||
},
|
||||
Y: k.Y,
|
||||
},
|
||||
X: k.X,
|
||||
}
|
||||
|
||||
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) {
|
||||
priv = &ecdsa.PrivateKey{
|
||||
D: privScalar,
|
||||
}
|
||||
|
||||
switch curveName {
|
||||
case "nistp256":
|
||||
priv.Curve = elliptic.P256()
|
||||
case "nistp384":
|
||||
priv.Curve = elliptic.P384()
|
||||
case "nistp521":
|
||||
priv.Curve = elliptic.P521()
|
||||
default:
|
||||
return nil, fmt.Errorf("agent: unknown curve %q", curveName)
|
||||
}
|
||||
|
||||
priv.X, priv.Y = elliptic.Unmarshal(priv.Curve, keyBytes)
|
||||
if priv.X == nil || priv.Y == nil {
|
||||
return nil, errors.New("agent: point not on curve")
|
||||
}
|
||||
|
||||
return priv, nil
|
||||
}
|
||||
|
||||
func parseEd25519Cert(req []byte) (*AddedKey, error) {
|
||||
var k ed25519CertMsg
|
||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
priv := ed25519.PrivateKey(k.Priv)
|
||||
cert, ok := pubKey.(*ssh.Certificate)
|
||||
if !ok {
|
||||
return nil, errors.New("agent: bad ED25519 certificate")
|
||||
}
|
||||
|
||||
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) {
|
||||
var k ecdsaKeyMsg
|
||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
priv, err := unmarshalECDSA(k.Curve, k.KeyBytes, k.D)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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) {
|
||||
var k rsaCertMsg
|
||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, ok := pubKey.(*ssh.Certificate)
|
||||
if !ok {
|
||||
return nil, errors.New("agent: bad RSA certificate")
|
||||
}
|
||||
|
||||
// An RSA publickey as marshaled by rsaPublicKey.Marshal() in keys.go
|
||||
var rsaPub struct {
|
||||
Name string
|
||||
E *big.Int
|
||||
N *big.Int
|
||||
}
|
||||
if err := ssh.Unmarshal(cert.Key.Marshal(), &rsaPub); err != nil {
|
||||
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
|
||||
}
|
||||
|
||||
if rsaPub.E.BitLen() > 30 {
|
||||
return nil, errors.New("agent: RSA public exponent too large")
|
||||
}
|
||||
|
||||
priv := rsa.PrivateKey{
|
||||
PublicKey: rsa.PublicKey{
|
||||
E: int(rsaPub.E.Int64()),
|
||||
N: rsaPub.N,
|
||||
},
|
||||
D: k.D,
|
||||
Primes: []*big.Int{k.Q, k.P},
|
||||
}
|
||||
priv.Precompute()
|
||||
|
||||
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) {
|
||||
var k dsaCertMsg
|
||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, ok := pubKey.(*ssh.Certificate)
|
||||
if !ok {
|
||||
return nil, errors.New("agent: bad DSA certificate")
|
||||
}
|
||||
|
||||
// A DSA publickey as marshaled by dsaPublicKey.Marshal() in keys.go
|
||||
var w struct {
|
||||
Name string
|
||||
P, Q, G, Y *big.Int
|
||||
}
|
||||
if err := ssh.Unmarshal(cert.Key.Marshal(), &w); err != nil {
|
||||
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
|
||||
}
|
||||
|
||||
priv := &dsa.PrivateKey{
|
||||
PublicKey: dsa.PublicKey{
|
||||
Parameters: dsa.Parameters{
|
||||
P: w.P,
|
||||
Q: w.Q,
|
||||
G: w.G,
|
||||
},
|
||||
Y: w.Y,
|
||||
},
|
||||
X: k.X,
|
||||
}
|
||||
|
||||
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) {
|
||||
var k ecdsaCertMsg
|
||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, ok := pubKey.(*ssh.Certificate)
|
||||
if !ok {
|
||||
return nil, errors.New("agent: bad ECDSA certificate")
|
||||
}
|
||||
|
||||
// An ECDSA publickey as marshaled by ecdsaPublicKey.Marshal() in keys.go
|
||||
var ecdsaPub struct {
|
||||
Name string
|
||||
ID string
|
||||
Key []byte
|
||||
}
|
||||
if err := ssh.Unmarshal(cert.Key.Marshal(), &ecdsaPub); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
priv, err := unmarshalECDSA(ecdsaPub.ID, ecdsaPub.Key, k.D)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 {
|
||||
var record struct {
|
||||
Type string `sshtype:"17|25"`
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
if err := ssh.Unmarshal(req, &record); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var addedKey *AddedKey
|
||||
var err error
|
||||
|
||||
switch record.Type {
|
||||
case ssh.KeyAlgoRSA:
|
||||
addedKey, err = parseRSAKey(req)
|
||||
case ssh.KeyAlgoDSA:
|
||||
addedKey, err = parseDSAKey(req)
|
||||
case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521:
|
||||
addedKey, err = parseECDSAKey(req)
|
||||
case ssh.KeyAlgoED25519:
|
||||
addedKey, err = parseEd25519Key(req)
|
||||
case ssh.CertAlgoRSAv01:
|
||||
addedKey, err = parseRSACert(req)
|
||||
case ssh.CertAlgoDSAv01:
|
||||
addedKey, err = parseDSACert(req)
|
||||
case ssh.CertAlgoECDSA256v01, ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01:
|
||||
addedKey, err = parseECDSACert(req)
|
||||
case ssh.CertAlgoED25519v01:
|
||||
addedKey, err = parseEd25519Cert(req)
|
||||
default:
|
||||
return fmt.Errorf("agent: not implemented: %q", record.Type)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.agent.Add(*addedKey)
|
||||
}
|
||||
|
||||
// ServeAgent serves the agent protocol on the given connection. It
|
||||
// returns when an I/O error occurs.
|
||||
func ServeAgent(agent Agent, c io.ReadWriter) error {
|
||||
s := &server{agent}
|
||||
|
||||
var length [4]byte
|
||||
for {
|
||||
if _, err := io.ReadFull(c, length[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
l := binary.BigEndian.Uint32(length[:])
|
||||
if l > maxAgentResponseBytes {
|
||||
// We also cap requests.
|
||||
return fmt.Errorf("agent: request too large: %d", l)
|
||||
}
|
||||
|
||||
req := make([]byte, l)
|
||||
if _, err := io.ReadFull(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repData := s.processRequestBytes(req)
|
||||
if len(repData) > maxAgentResponseBytes {
|
||||
return fmt.Errorf("agent: reply too large: %d bytes", len(repData))
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(length[:], uint32(len(repData)))
|
||||
if _, err := c.Write(length[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.Write(repData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,259 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
pseudorand "math/rand"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestServer(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
client := NewClient(c1)
|
||||
|
||||
go ServeAgent(NewKeyring(), c2)
|
||||
|
||||
testAgentInterface(t, client, testPrivateKeys["rsa"], nil, 0)
|
||||
}
|
||||
|
||||
func TestLockServer(t *testing.T) {
|
||||
testLockAgent(NewKeyring(), t)
|
||||
}
|
||||
|
||||
func TestSetupForwardAgent(t *testing.T) {
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
_, socket, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
serverConf := ssh.ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
incoming := make(chan *ssh.ServerConn, 1)
|
||||
go func() {
|
||||
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Server: %v", err)
|
||||
}
|
||||
incoming <- conn
|
||||
}()
|
||||
|
||||
conf := ssh.ClientConfig{
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
conn, chans, reqs, err := ssh.NewClientConn(b, "", &conf)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientConn: %v", err)
|
||||
}
|
||||
client := ssh.NewClient(conn, chans, reqs)
|
||||
|
||||
if err := ForwardToRemote(client, socket); err != nil {
|
||||
t.Fatalf("SetupForwardAgent: %v", err)
|
||||
}
|
||||
|
||||
server := <-incoming
|
||||
ch, reqs, err := server.OpenChannel(channelType, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenChannel(%q): %v", channelType, err)
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
agentClient := NewClient(ch)
|
||||
testAgentInterface(t, agentClient, testPrivateKeys["rsa"], nil, 0)
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func TestV1ProtocolMessages(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
c := NewClient(c1)
|
||||
|
||||
go ServeAgent(NewKeyring(), c2)
|
||||
|
||||
testV1ProtocolMessages(t, c.(*client))
|
||||
}
|
||||
|
||||
func testV1ProtocolMessages(t *testing.T, c *client) {
|
||||
reply, err := c.call([]byte{agentRequestV1Identities})
|
||||
if err != nil {
|
||||
t.Fatalf("v1 request all failed: %v", err)
|
||||
}
|
||||
if msg, ok := reply.(*agentV1IdentityMsg); !ok || msg.Numkeys != 0 {
|
||||
t.Fatalf("invalid request all response: %#v", reply)
|
||||
}
|
||||
|
||||
reply, err = c.call([]byte{agentRemoveAllV1Identities})
|
||||
if err != nil {
|
||||
t.Fatalf("v1 remove all failed: %v", err)
|
||||
}
|
||||
if _, ok := reply.(*successAgentMsg); !ok {
|
||||
t.Fatalf("invalid remove all response: %#v", reply)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyKey(sshAgent Agent) error {
|
||||
keys, err := sshAgent.List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("listing keys: %v", err)
|
||||
}
|
||||
|
||||
if len(keys) != 1 {
|
||||
return fmt.Errorf("bad number of keys found. expected 1, got %d", len(keys))
|
||||
}
|
||||
|
||||
buf := make([]byte, 128)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
return fmt.Errorf("rand: %v", err)
|
||||
}
|
||||
|
||||
sig, err := sshAgent.Sign(keys[0], buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sign: %v", err)
|
||||
}
|
||||
|
||||
if err := keys[0].Verify(buf, sig); err != nil {
|
||||
return fmt.Errorf("verify: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addKeyToAgent(key crypto.PrivateKey) error {
|
||||
sshAgent := NewKeyring()
|
||||
if err := sshAgent.Add(AddedKey{PrivateKey: key}); err != nil {
|
||||
return fmt.Errorf("add: %v", err)
|
||||
}
|
||||
return verifyKey(sshAgent)
|
||||
}
|
||||
|
||||
func TestKeyTypes(t *testing.T) {
|
||||
for k, v := range testPrivateKeys {
|
||||
if err := addKeyToAgent(v); err != nil {
|
||||
t.Errorf("error adding key type %s, %v", k, err)
|
||||
}
|
||||
if err := addCertToAgentSock(v, nil); err != nil {
|
||||
t.Errorf("error adding key type %s, %v", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addCertToAgentSock(key crypto.PrivateKey, cert *ssh.Certificate) error {
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
agentServer := NewKeyring()
|
||||
go ServeAgent(agentServer, a)
|
||||
|
||||
agentClient := NewClient(b)
|
||||
if err := agentClient.Add(AddedKey{PrivateKey: key, Certificate: cert}); err != nil {
|
||||
return fmt.Errorf("add: %v", err)
|
||||
}
|
||||
return verifyKey(agentClient)
|
||||
}
|
||||
|
||||
func addCertToAgent(key crypto.PrivateKey, cert *ssh.Certificate) error {
|
||||
sshAgent := NewKeyring()
|
||||
if err := sshAgent.Add(AddedKey{PrivateKey: key, Certificate: cert}); err != nil {
|
||||
return fmt.Errorf("add: %v", err)
|
||||
}
|
||||
return verifyKey(sshAgent)
|
||||
}
|
||||
|
||||
func TestCertTypes(t *testing.T) {
|
||||
for keyType, key := range testPublicKeys {
|
||||
cert := &ssh.Certificate{
|
||||
ValidPrincipals: []string{"gopher1"},
|
||||
ValidAfter: 0,
|
||||
ValidBefore: ssh.CertTimeInfinity,
|
||||
Key: key,
|
||||
Serial: 1,
|
||||
CertType: ssh.UserCert,
|
||||
SignatureKey: testPublicKeys["rsa"],
|
||||
Permissions: ssh.Permissions{
|
||||
CriticalOptions: map[string]string{},
|
||||
Extensions: map[string]string{},
|
||||
},
|
||||
}
|
||||
if err := cert.SignCert(rand.Reader, testSigners["rsa"]); err != nil {
|
||||
t.Fatalf("signcert: %v", err)
|
||||
}
|
||||
if err := addCertToAgent(testPrivateKeys[keyType], cert); err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if err := addCertToAgentSock(testPrivateKeys[keyType], cert); err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseConstraints(t *testing.T) {
|
||||
// Test LifetimeSecs
|
||||
var msg = constrainLifetimeAgentMsg{pseudorand.Uint32()}
|
||||
lifetimeSecs, _, _, err := parseConstraints(ssh.Marshal(msg))
|
||||
if err != nil {
|
||||
t.Fatalf("parseConstraints: %v", err)
|
||||
}
|
||||
if lifetimeSecs != msg.LifetimeSecs {
|
||||
t.Errorf("got lifetime %v, want %v", lifetimeSecs, msg.LifetimeSecs)
|
||||
}
|
||||
|
||||
// Test ConfirmBeforeUse
|
||||
_, confirmBeforeUse, _, err := parseConstraints([]byte{agentConstrainConfirm})
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if !confirmBeforeUse {
|
||||
t.Error("got comfirmBeforeUse == false")
|
||||
}
|
||||
|
||||
// Test ConstraintExtensions
|
||||
var data []byte
|
||||
var expect []ConstraintExtension
|
||||
for i := 0; i < 10; i++ {
|
||||
var ext = ConstraintExtension{
|
||||
ExtensionName: fmt.Sprintf("name%d", i),
|
||||
ExtensionDetails: []byte(fmt.Sprintf("details: %d", i)),
|
||||
}
|
||||
expect = append(expect, ext)
|
||||
data = append(data, agentConstrainExtension)
|
||||
data = append(data, ssh.Marshal(ext)...)
|
||||
}
|
||||
_, _, extensions, err := parseConstraints(data)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(expect, extensions) {
|
||||
t.Errorf("got extension %v, want %v", extensions, expect)
|
||||
}
|
||||
|
||||
// Test Unknown Constraint
|
||||
_, _, _, err = parseConstraints([]byte{128})
|
||||
if err == nil || !strings.Contains(err.Error(), "unknown constraint") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// IMPLEMENTATION NOTE: To avoid a package loop, this file is in three places:
|
||||
// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three
|
||||
// instances.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
)
|
||||
|
||||
var (
|
||||
testPrivateKeys map[string]interface{}
|
||||
testSigners map[string]ssh.Signer
|
||||
testPublicKeys map[string]ssh.PublicKey
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
n := len(testdata.PEMBytes)
|
||||
testPrivateKeys = make(map[string]interface{}, n)
|
||||
testSigners = make(map[string]ssh.Signer, n)
|
||||
testPublicKeys = make(map[string]ssh.PublicKey, n)
|
||||
for t, k := range testdata.PEMBytes {
|
||||
testPrivateKeys[t], err = ssh.ParseRawPrivateKey(k)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err))
|
||||
}
|
||||
testSigners[t], err = ssh.NewSignerFromKey(testPrivateKeys[t])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err))
|
||||
}
|
||||
testPublicKeys[t] = testSigners[t].PublicKey()
|
||||
}
|
||||
|
||||
// Create a cert and sign it for use in tests.
|
||||
testCert := &ssh.Certificate{
|
||||
Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
|
||||
ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage
|
||||
ValidAfter: 0, // unix epoch
|
||||
ValidBefore: ssh.CertTimeInfinity, // The end of currently representable time.
|
||||
Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
|
||||
Key: testPublicKeys["ecdsa"],
|
||||
SignatureKey: testPublicKeys["rsa"],
|
||||
Permissions: ssh.Permissions{
|
||||
CriticalOptions: map[string]string{},
|
||||
Extensions: map[string]string{},
|
||||
},
|
||||
}
|
||||
testCert.SignCert(rand.Reader, testSigners["rsa"])
|
||||
testPrivateKeys["cert"] = testPrivateKeys["ecdsa"]
|
||||
testSigners["cert"], err = ssh.NewCertSigner(testCert, testSigners["ecdsa"])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to create certificate signer: %v", err))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
*ServerConn
|
||||
chans <-chan NewChannel
|
||||
}
|
||||
|
||||
func newServer(c net.Conn, conf *ServerConfig) (*server, error) {
|
||||
sconn, chans, reqs, err := NewServerConn(c, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go DiscardRequests(reqs)
|
||||
return &server{sconn, chans}, nil
|
||||
}
|
||||
|
||||
func (s *server) Accept() (NewChannel, error) {
|
||||
n, ok := <-s.chans
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func sshPipe() (Conn, *server, error) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
}
|
||||
serverConf := ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||
done := make(chan *server, 1)
|
||||
go func() {
|
||||
server, err := newServer(c2, &serverConf)
|
||||
if err != nil {
|
||||
done <- nil
|
||||
}
|
||||
done <- server
|
||||
}()
|
||||
|
||||
client, _, reqs, err := NewClientConn(c1, "", &clientConf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
server := <-done
|
||||
if server == nil {
|
||||
return nil, nil, errors.New("server handshake failed.")
|
||||
}
|
||||
go DiscardRequests(reqs)
|
||||
|
||||
return client, server, nil
|
||||
}
|
||||
|
||||
func BenchmarkEndToEnd(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
client, server, err := sshPipe()
|
||||
if err != nil {
|
||||
b.Fatalf("sshPipe: %v", err)
|
||||
}
|
||||
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
|
||||
size := (1 << 20)
|
||||
input := make([]byte, size)
|
||||
output := make([]byte, size)
|
||||
b.SetBytes(int64(size))
|
||||
done := make(chan int, 1)
|
||||
|
||||
go func() {
|
||||
newCh, err := server.Accept()
|
||||
if err != nil {
|
||||
b.Fatalf("Client: %v", err)
|
||||
}
|
||||
ch, incoming, err := newCh.Accept()
|
||||
go DiscardRequests(incoming)
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := io.ReadFull(ch, output); err != nil {
|
||||
b.Fatalf("ReadFull: %v", err)
|
||||
}
|
||||
}
|
||||
ch.Close()
|
||||
done <- 1
|
||||
}()
|
||||
|
||||
ch, in, err := client.OpenChannel("speed", nil)
|
||||
if err != nil {
|
||||
b.Fatalf("OpenChannel: %v", err)
|
||||
}
|
||||
go DiscardRequests(in)
|
||||
|
||||
b.ResetTimer()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := ch.Write(input); err != nil {
|
||||
b.Fatalf("WriteFull: %v", err)
|
||||
}
|
||||
}
|
||||
ch.Close()
|
||||
b.StopTimer()
|
||||
|
||||
<-done
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// buffer provides a linked list buffer for data exchange
|
||||
// between producer and consumer. Theoretically the buffer is
|
||||
// of unlimited capacity as it does no allocation of its own.
|
||||
type buffer struct {
|
||||
// protects concurrent access to head, tail and closed
|
||||
*sync.Cond
|
||||
|
||||
head *element // the buffer that will be read first
|
||||
tail *element // the buffer that will be read last
|
||||
|
||||
closed bool
|
||||
}
|
||||
|
||||
// An element represents a single link in a linked list.
|
||||
type element struct {
|
||||
buf []byte
|
||||
next *element
|
||||
}
|
||||
|
||||
// newBuffer returns an empty buffer that is not closed.
|
||||
func newBuffer() *buffer {
|
||||
e := new(element)
|
||||
b := &buffer{
|
||||
Cond: newCond(),
|
||||
head: e,
|
||||
tail: e,
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// write makes buf available for Read to receive.
|
||||
// buf must not be modified after the call to write.
|
||||
func (b *buffer) write(buf []byte) {
|
||||
b.Cond.L.Lock()
|
||||
e := &element{buf: buf}
|
||||
b.tail.next = e
|
||||
b.tail = e
|
||||
b.Cond.Signal()
|
||||
b.Cond.L.Unlock()
|
||||
}
|
||||
|
||||
// eof closes the buffer. Reads from the buffer once all
|
||||
// the data has been consumed will receive os.EOF.
|
||||
func (b *buffer) eof() error {
|
||||
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
|
||||
// if no data is available, or until the buffer is closed.
|
||||
func (b *buffer) Read(buf []byte) (n int, err error) {
|
||||
b.Cond.L.Lock()
|
||||
defer b.Cond.L.Unlock()
|
||||
|
||||
for len(buf) > 0 {
|
||||
// if there is data in b.head, copy it
|
||||
if len(b.head.buf) > 0 {
|
||||
r := copy(buf, b.head.buf)
|
||||
buf, b.head.buf = buf[r:], b.head.buf[r:]
|
||||
n += r
|
||||
continue
|
||||
}
|
||||
// if there is a next buffer, make it the head
|
||||
if len(b.head.buf) == 0 && b.head != b.tail {
|
||||
b.head = b.head.next
|
||||
continue
|
||||
}
|
||||
|
||||
// if at least one byte has been copied, return
|
||||
if n > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// if nothing was read, and there is nothing outstanding
|
||||
// check to see if the buffer is closed.
|
||||
if b.closed {
|
||||
err = io.EOF
|
||||
break
|
||||
}
|
||||
// out of buffers, wait for producer
|
||||
b.Cond.Wait()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var alphabet = []byte("abcdefghijklmnopqrstuvwxyz")
|
||||
|
||||
func TestBufferReadwrite(t *testing.T) {
|
||||
b := newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
r, _ := b.Read(make([]byte, 10))
|
||||
if r != 10 {
|
||||
t.Fatalf("Expected written == read == 10, written: 10, read %d", r)
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:5])
|
||||
r, _ = b.Read(make([]byte, 10))
|
||||
if r != 5 {
|
||||
t.Fatalf("Expected written == read == 5, written: 5, read %d", r)
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
r, _ = b.Read(make([]byte, 5))
|
||||
if r != 5 {
|
||||
t.Fatalf("Expected written == 10, read == 5, written: 10, read %d", r)
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:5])
|
||||
b.write(alphabet[5:15])
|
||||
r, _ = b.Read(make([]byte, 10))
|
||||
r2, _ := b.Read(make([]byte, 10))
|
||||
if r != 10 || r2 != 5 || 15 != r+r2 {
|
||||
t.Fatal("Expected written == read == 15")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferClose(t *testing.T) {
|
||||
b := newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
b.eof()
|
||||
_, err := b.Read(make([]byte, 5))
|
||||
if err != nil {
|
||||
t.Fatal("expected read of 5 to not return EOF")
|
||||
}
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
b.eof()
|
||||
r, err := b.Read(make([]byte, 5))
|
||||
r2, err2 := b.Read(make([]byte, 10))
|
||||
if r != 5 || r2 != 5 || err != nil || err2 != nil {
|
||||
t.Fatal("expected reads of 5 and 5")
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
b.eof()
|
||||
r, err = b.Read(make([]byte, 5))
|
||||
r2, err2 = b.Read(make([]byte, 10))
|
||||
r3, err3 := b.Read(make([]byte, 10))
|
||||
if r != 5 || r2 != 5 || r3 != 0 || err != nil || err2 != nil || err3 != io.EOF {
|
||||
t.Fatal("expected reads of 5 and 5 and 0, with EOF")
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(make([]byte, 5))
|
||||
b.write(make([]byte, 10))
|
||||
b.eof()
|
||||
r, err = b.Read(make([]byte, 9))
|
||||
r2, err2 = b.Read(make([]byte, 3))
|
||||
r3, err3 = b.Read(make([]byte, 3))
|
||||
r4, err4 := b.Read(make([]byte, 10))
|
||||
if err != nil || err2 != nil || err3 != nil || err4 != io.EOF {
|
||||
t.Fatalf("Expected EOF on forth read only, err=%v, err2=%v, err3=%v, err4=%v", err, err2, err3, err4)
|
||||
}
|
||||
if r != 9 || r2 != 3 || r3 != 3 || r4 != 0 {
|
||||
t.Fatal("Expected written == read == 15", r, r2, r3, r4)
|
||||
}
|
||||
}
|
||||
519
ssh/certs.go
519
ssh/certs.go
|
|
@ -1,519 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// These constants from [PROTOCOL.certkeys] represent the algorithm names
|
||||
// for certificate types supported by this package.
|
||||
const (
|
||||
CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com"
|
||||
CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com"
|
||||
CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
|
||||
CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
|
||||
CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
|
||||
CertAlgoED25519v01 = "ssh-ed25519-cert-v01@openssh.com"
|
||||
)
|
||||
|
||||
// Certificate types distinguish between host and user
|
||||
// certificates. The values can be set in the CertType field of
|
||||
// Certificate.
|
||||
const (
|
||||
UserCert = 1
|
||||
HostCert = 2
|
||||
)
|
||||
|
||||
// Signature represents a cryptographic signature.
|
||||
type Signature struct {
|
||||
Format string
|
||||
Blob []byte
|
||||
}
|
||||
|
||||
// CertTimeInfinity can be used for OpenSSHCertV01.ValidBefore to indicate that
|
||||
// a certificate does not expire.
|
||||
const CertTimeInfinity = 1<<64 - 1
|
||||
|
||||
// An Certificate represents an OpenSSH certificate as defined in
|
||||
// [PROTOCOL.certkeys]?rev=1.8.
|
||||
type Certificate struct {
|
||||
Nonce []byte
|
||||
Key PublicKey
|
||||
Serial uint64
|
||||
CertType uint32
|
||||
KeyId string
|
||||
ValidPrincipals []string
|
||||
ValidAfter uint64
|
||||
ValidBefore uint64
|
||||
Permissions
|
||||
Reserved []byte
|
||||
SignatureKey PublicKey
|
||||
Signature *Signature
|
||||
}
|
||||
|
||||
// genericCertData holds the key-independent part of the certificate data.
|
||||
// Overall, certificates contain an nonce, public key fields and
|
||||
// key-independent fields.
|
||||
type genericCertData struct {
|
||||
Serial uint64
|
||||
CertType uint32
|
||||
KeyId string
|
||||
ValidPrincipals []byte
|
||||
ValidAfter uint64
|
||||
ValidBefore uint64
|
||||
CriticalOptions []byte
|
||||
Extensions []byte
|
||||
Reserved []byte
|
||||
SignatureKey []byte
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
func marshalStringList(namelist []string) []byte {
|
||||
var to []byte
|
||||
for _, name := range namelist {
|
||||
s := struct{ N string }{name}
|
||||
to = append(to, Marshal(&s)...)
|
||||
}
|
||||
return to
|
||||
}
|
||||
|
||||
type optionsTuple struct {
|
||||
Key string
|
||||
Value []byte
|
||||
}
|
||||
|
||||
type optionsTupleValue struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
// serialize a map of critical options or extensions
|
||||
// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation,
|
||||
// we need two length prefixes for a non-empty string value
|
||||
func marshalTuples(tups map[string]string) []byte {
|
||||
keys := make([]string, 0, len(tups))
|
||||
for key := range tups {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var ret []byte
|
||||
for _, key := range keys {
|
||||
s := optionsTuple{Key: key}
|
||||
if value := tups[key]; len(value) > 0 {
|
||||
s.Value = Marshal(&optionsTupleValue{value})
|
||||
}
|
||||
ret = append(ret, Marshal(&s)...)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation,
|
||||
// we need two length prefixes for a non-empty option value
|
||||
func parseTuples(in []byte) (map[string]string, error) {
|
||||
tups := map[string]string{}
|
||||
var lastKey string
|
||||
var haveLastKey bool
|
||||
|
||||
for len(in) > 0 {
|
||||
var key, val, extra []byte
|
||||
var ok bool
|
||||
|
||||
if key, in, ok = parseString(in); !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
keyStr := string(key)
|
||||
// according to [PROTOCOL.certkeys], the names must be in
|
||||
// lexical order.
|
||||
if haveLastKey && keyStr <= lastKey {
|
||||
return nil, fmt.Errorf("ssh: certificate options are not in lexical order")
|
||||
}
|
||||
lastKey, haveLastKey = keyStr, true
|
||||
// the next field is a data field, which if non-empty has a string embedded
|
||||
if val, in, ok = parseString(in); !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
if len(val) > 0 {
|
||||
val, extra, ok = parseString(val)
|
||||
if !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
if len(extra) > 0 {
|
||||
return nil, fmt.Errorf("ssh: unexpected trailing data after certificate option value")
|
||||
}
|
||||
tups[keyStr] = string(val)
|
||||
} else {
|
||||
tups[keyStr] = ""
|
||||
}
|
||||
}
|
||||
return tups, nil
|
||||
}
|
||||
|
||||
func parseCert(in []byte, privAlgo string) (*Certificate, error) {
|
||||
nonce, rest, ok := parseString(in)
|
||||
if !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
|
||||
key, rest, err := parsePubKey(rest, privAlgo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var g genericCertData
|
||||
if err := Unmarshal(rest, &g); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &Certificate{
|
||||
Nonce: nonce,
|
||||
Key: key,
|
||||
Serial: g.Serial,
|
||||
CertType: g.CertType,
|
||||
KeyId: g.KeyId,
|
||||
ValidAfter: g.ValidAfter,
|
||||
ValidBefore: g.ValidBefore,
|
||||
}
|
||||
|
||||
for principals := g.ValidPrincipals; len(principals) > 0; {
|
||||
principal, rest, ok := parseString(principals)
|
||||
if !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
c.ValidPrincipals = append(c.ValidPrincipals, string(principal))
|
||||
principals = rest
|
||||
}
|
||||
|
||||
c.CriticalOptions, err = parseTuples(g.CriticalOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Extensions, err = parseTuples(g.Extensions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Reserved = g.Reserved
|
||||
k, err := ParsePublicKey(g.SignatureKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.SignatureKey = k
|
||||
c.Signature, rest, ok = parseSignatureBody(g.Signature)
|
||||
if !ok || len(rest) > 0 {
|
||||
return nil, errors.New("ssh: signature parse error")
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type openSSHCertSigner struct {
|
||||
pub *Certificate
|
||||
signer Signer
|
||||
}
|
||||
|
||||
// NewCertSigner returns a Signer that signs with the given Certificate, whose
|
||||
// private key is held by signer. It returns an error if the public key in cert
|
||||
// doesn't match the key used by signer.
|
||||
func NewCertSigner(cert *Certificate, signer Signer) (Signer, error) {
|
||||
if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 {
|
||||
return nil, errors.New("ssh: signer and cert have different public key")
|
||||
}
|
||||
|
||||
return &openSSHCertSigner{cert, signer}, nil
|
||||
}
|
||||
|
||||
func (s *openSSHCertSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
||||
return s.signer.Sign(rand, data)
|
||||
}
|
||||
|
||||
func (s *openSSHCertSigner) PublicKey() PublicKey {
|
||||
return s.pub
|
||||
}
|
||||
|
||||
const sourceAddressCriticalOption = "source-address"
|
||||
|
||||
// CertChecker does the work of verifying a certificate. Its methods
|
||||
// can be plugged into ClientConfig.HostKeyCallback and
|
||||
// ServerConfig.PublicKeyCallback. For the CertChecker to work,
|
||||
// minimally, the IsAuthority callback should be set.
|
||||
type CertChecker struct {
|
||||
// SupportedCriticalOptions lists the CriticalOptions that the
|
||||
// server application layer understands. These are only used
|
||||
// for user certificates.
|
||||
SupportedCriticalOptions []string
|
||||
|
||||
// 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.
|
||||
Clock func() time.Time
|
||||
|
||||
// UserKeyFallback is called when CertChecker.Authenticate encounters a
|
||||
// public key that is not a certificate. It must implement validation
|
||||
// of user keys or else, if nil, all such keys are rejected.
|
||||
UserKeyFallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
|
||||
|
||||
// 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 HostKeyCallback
|
||||
|
||||
// IsRevoked is called for each certificate so that revocation checking
|
||||
// can be implemented. It should return true if the given certificate
|
||||
// is revoked and false otherwise. If nil, no certificates are
|
||||
// considered to have been revoked.
|
||||
IsRevoked func(cert *Certificate) bool
|
||||
}
|
||||
|
||||
// CheckHostKey checks a host key certificate. This method can be
|
||||
// plugged into ClientConfig.HostKeyCallback.
|
||||
func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey) error {
|
||||
cert, ok := key.(*Certificate)
|
||||
if !ok {
|
||||
if c.HostKeyFallback != nil {
|
||||
return c.HostKeyFallback(addr, remote, key)
|
||||
}
|
||||
return errors.New("ssh: non-certificate host key")
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
// a value for ServerConfig.PublicKeyCallback.
|
||||
func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permissions, error) {
|
||||
cert, ok := pubKey.(*Certificate)
|
||||
if !ok {
|
||||
if c.UserKeyFallback != nil {
|
||||
return c.UserKeyFallback(conn, pubKey)
|
||||
}
|
||||
return nil, errors.New("ssh: normal key pairs not accepted")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return &cert.Permissions, nil
|
||||
}
|
||||
|
||||
// CheckCert checks CriticalOptions, ValidPrincipals, revocation, timestamp and
|
||||
// the signature of the certificate.
|
||||
func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
||||
if c.IsRevoked != nil && c.IsRevoked(cert) {
|
||||
return fmt.Errorf("ssh: certicate serial %d revoked", cert.Serial)
|
||||
}
|
||||
|
||||
for opt, _ := range cert.CriticalOptions {
|
||||
// sourceAddressCriticalOption will be enforced by
|
||||
// serverAuthenticate
|
||||
if opt == sourceAddressCriticalOption {
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, supp := range c.SupportedCriticalOptions {
|
||||
if supp == opt {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("ssh: unsupported critical option %q in certificate", opt)
|
||||
}
|
||||
}
|
||||
|
||||
if len(cert.ValidPrincipals) > 0 {
|
||||
// By default, certs are valid for all users/hosts.
|
||||
found := false
|
||||
for _, p := range cert.ValidPrincipals {
|
||||
if p == principal {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("ssh: principal %q not in the set of valid principals for given certificate: %q", principal, cert.ValidPrincipals)
|
||||
}
|
||||
}
|
||||
|
||||
clock := c.Clock
|
||||
if clock == nil {
|
||||
clock = time.Now
|
||||
}
|
||||
|
||||
unixNow := clock().Unix()
|
||||
if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) {
|
||||
return fmt.Errorf("ssh: cert is not yet valid")
|
||||
}
|
||||
if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) {
|
||||
return fmt.Errorf("ssh: cert has expired")
|
||||
}
|
||||
if err := cert.SignatureKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil {
|
||||
return fmt.Errorf("ssh: certificate signature does not verify")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignCert sets c.SignatureKey to the authority's public key and stores a
|
||||
// Signature, by authority, in the certificate.
|
||||
func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
|
||||
c.Nonce = make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand, c.Nonce); err != nil {
|
||||
return err
|
||||
}
|
||||
c.SignatureKey = authority.PublicKey()
|
||||
|
||||
sig, err := authority.Sign(rand, c.bytesForSigning())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Signature = sig
|
||||
return nil
|
||||
}
|
||||
|
||||
var certAlgoNames = map[string]string{
|
||||
KeyAlgoRSA: CertAlgoRSAv01,
|
||||
KeyAlgoDSA: CertAlgoDSAv01,
|
||||
KeyAlgoECDSA256: CertAlgoECDSA256v01,
|
||||
KeyAlgoECDSA384: CertAlgoECDSA384v01,
|
||||
KeyAlgoECDSA521: CertAlgoECDSA521v01,
|
||||
KeyAlgoED25519: CertAlgoED25519v01,
|
||||
}
|
||||
|
||||
// certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
|
||||
// Panics if a non-certificate algorithm is passed.
|
||||
func certToPrivAlgo(algo string) string {
|
||||
for privAlgo, pubAlgo := range certAlgoNames {
|
||||
if pubAlgo == algo {
|
||||
return privAlgo
|
||||
}
|
||||
}
|
||||
panic("unknown cert algorithm")
|
||||
}
|
||||
|
||||
func (cert *Certificate) bytesForSigning() []byte {
|
||||
c2 := *cert
|
||||
c2.Signature = nil
|
||||
out := c2.Marshal()
|
||||
// Drop trailing signature length.
|
||||
return out[:len(out)-4]
|
||||
}
|
||||
|
||||
// Marshal serializes c into OpenSSH's wire format. It is part of the
|
||||
// PublicKey interface.
|
||||
func (c *Certificate) Marshal() []byte {
|
||||
generic := genericCertData{
|
||||
Serial: c.Serial,
|
||||
CertType: c.CertType,
|
||||
KeyId: c.KeyId,
|
||||
ValidPrincipals: marshalStringList(c.ValidPrincipals),
|
||||
ValidAfter: uint64(c.ValidAfter),
|
||||
ValidBefore: uint64(c.ValidBefore),
|
||||
CriticalOptions: marshalTuples(c.CriticalOptions),
|
||||
Extensions: marshalTuples(c.Extensions),
|
||||
Reserved: c.Reserved,
|
||||
SignatureKey: c.SignatureKey.Marshal(),
|
||||
}
|
||||
if c.Signature != nil {
|
||||
generic.Signature = Marshal(c.Signature)
|
||||
}
|
||||
genericBytes := Marshal(&generic)
|
||||
keyBytes := c.Key.Marshal()
|
||||
_, keyBytes, _ = parseString(keyBytes)
|
||||
prefix := Marshal(&struct {
|
||||
Name string
|
||||
Nonce []byte
|
||||
Key []byte `ssh:"rest"`
|
||||
}{c.Type(), c.Nonce, keyBytes})
|
||||
|
||||
result := make([]byte, 0, len(prefix)+len(genericBytes))
|
||||
result = append(result, prefix...)
|
||||
result = append(result, genericBytes...)
|
||||
return result
|
||||
}
|
||||
|
||||
// Type returns the key name. It is part of the PublicKey interface.
|
||||
func (c *Certificate) Type() string {
|
||||
algo, ok := certAlgoNames[c.Key.Type()]
|
||||
if !ok {
|
||||
panic("unknown cert key type " + c.Key.Type())
|
||||
}
|
||||
return algo
|
||||
}
|
||||
|
||||
// Verify verifies a signature against the certificate's public
|
||||
// key. It is part of the PublicKey interface.
|
||||
func (c *Certificate) Verify(data []byte, sig *Signature) error {
|
||||
return c.Key.Verify(data, sig)
|
||||
}
|
||||
|
||||
func parseSignatureBody(in []byte) (out *Signature, rest []byte, ok bool) {
|
||||
format, in, ok := parseString(in)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
out = &Signature{
|
||||
Format: string(format),
|
||||
}
|
||||
|
||||
if out.Blob, in, ok = parseString(in); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
return out, in, ok
|
||||
}
|
||||
|
||||
func parseSignature(in []byte) (out *Signature, rest []byte, ok bool) {
|
||||
sigBytes, rest, ok := parseString(in)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
out, trailing, ok := parseSignatureBody(sigBytes)
|
||||
if !ok || len(trailing) > 0 {
|
||||
return nil, nil, false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -1,222 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cert generated by ssh-keygen 6.0p1 Debian-4.
|
||||
// % ssh-keygen -s ca-key -I test user-key
|
||||
const exampleSSHCert = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgb1srW/W3ZDjYAO45xLYAwzHBDLsJ4Ux6ICFIkTjb1LEAAAADAQABAAAAYQCkoR51poH0wE8w72cqSB8Sszx+vAhzcMdCO0wqHTj7UNENHWEXGrU0E0UQekD7U+yhkhtoyjbPOVIP7hNa6aRk/ezdh/iUnCIt4Jt1v3Z1h1P+hA4QuYFMHNB+rmjPwAcAAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAHcAAAAHc3NoLXJzYQAAAAMBAAEAAABhANFS2kaktpSGc+CcmEKPyw9mJC4nZKxHKTgLVZeaGbFZOvJTNzBspQHdy7Q1uKSfktxpgjZnksiu/tFF9ngyY2KFoc+U88ya95IZUycBGCUbBQ8+bhDtw/icdDGQD5WnUwAAAG8AAAAHc3NoLXJzYQAAAGC8Y9Z2LQKhIhxf52773XaWrXdxP0t3GBVo4A10vUWiYoAGepr6rQIoGGXFxT4B9Gp+nEBJjOwKDXPrAevow0T9ca8gZN+0ykbhSrXLE5Ao48rqr3zP4O1/9P7e6gp0gw8=`
|
||||
|
||||
func TestParseCert(t *testing.T) {
|
||||
authKeyBytes := []byte(exampleSSHCert)
|
||||
|
||||
key, _, _, rest, err := ParseAuthorizedKey(authKeyBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAuthorizedKey: %v", err)
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
t.Errorf("rest: got %q, want empty", rest)
|
||||
}
|
||||
|
||||
if _, ok := key.(*Certificate); !ok {
|
||||
t.Fatalf("got %v (%T), want *Certificate", key, key)
|
||||
}
|
||||
|
||||
marshaled := MarshalAuthorizedKey(key)
|
||||
// Before comparison, remove the trailing newline that
|
||||
// MarshalAuthorizedKey adds.
|
||||
marshaled = marshaled[:len(marshaled)-1]
|
||||
if !bytes.Equal(authKeyBytes, marshaled) {
|
||||
t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Cert generated by ssh-keygen OpenSSH_6.8p1 OS X 10.10.3
|
||||
// % ssh-keygen -s ca -I testcert -O source-address=192.168.1.0/24 -O force-command=/bin/sleep user.pub
|
||||
// user.pub key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMN
|
||||
// Critical Options:
|
||||
// force-command /bin/sleep
|
||||
// source-address 192.168.1.0/24
|
||||
// Extensions:
|
||||
// permit-X11-forwarding
|
||||
// permit-agent-forwarding
|
||||
// permit-port-forwarding
|
||||
// permit-pty
|
||||
// permit-user-rc
|
||||
const exampleSSHCertWithOptions = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgDyysCJY0XrO1n03EeRRoITnTPdjENFmWDs9X58PP3VUAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMNAAAAAAAAAAAAAAABAAAACHRlc3RjZXJ0AAAAAAAAAAAAAAAA//////////8AAABLAAAADWZvcmNlLWNvbW1hbmQAAAAOAAAACi9iaW4vc2xlZXAAAAAOc291cmNlLWFkZHJlc3MAAAASAAAADjE5Mi4xNjguMS4wLzI0AAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAwU+c5ui5A8+J/CFpjW8wCa52bEODA808WWQDCSuTG/eMXNf59v9Y8Pk0F1E9dGCosSNyVcB/hacUrc6He+i97+HJCyKavBsE6GDxrjRyxYqAlfcOXi/IVmaUGiO8OQ39d4GHrjToInKvExSUeleQyH4Y4/e27T/pILAqPFL3fyrvMLT5qU9QyIt6zIpa7GBP5+urouNavMprV3zsfIqNBbWypinOQAw823a5wN+zwXnhZrgQiHZ/USG09Y6k98y1dTVz8YHlQVR4D3lpTAsKDKJ5hCH9WU4fdf+lU8OyNGaJ/vz0XNqxcToe1l4numLTnaoSuH89pHryjqurB7lJKwAAAQ8AAAAHc3NoLXJzYQAAAQCaHvUIoPL1zWUHIXLvu96/HU1s/i4CAW2IIEuGgxCUCiFj6vyTyYtgxQxcmbfZf6eaITlS6XJZa7Qq4iaFZh75C1DXTX8labXhRSD4E2t//AIP9MC1rtQC5xo6FmbQ+BoKcDskr+mNACcbRSxs3IL3bwCfWDnIw2WbVox9ZdcthJKk4UoCW4ix4QwdHw7zlddlz++fGEEVhmTbll1SUkycGApPFBsAYRTMupUJcYPIeReBI/m8XfkoMk99bV8ZJQTAd7OekHY2/48Ff53jLmyDjP7kNw1F8OaPtkFs6dGJXta4krmaekPy87j+35In5hFj7yoOqvSbmYUkeX70/GGQ`
|
||||
|
||||
func TestParseCertWithOptions(t *testing.T) {
|
||||
opts := map[string]string{
|
||||
"source-address": "192.168.1.0/24",
|
||||
"force-command": "/bin/sleep",
|
||||
}
|
||||
exts := map[string]string{
|
||||
"permit-X11-forwarding": "",
|
||||
"permit-agent-forwarding": "",
|
||||
"permit-port-forwarding": "",
|
||||
"permit-pty": "",
|
||||
"permit-user-rc": "",
|
||||
}
|
||||
authKeyBytes := []byte(exampleSSHCertWithOptions)
|
||||
|
||||
key, _, _, rest, err := ParseAuthorizedKey(authKeyBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAuthorizedKey: %v", err)
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
t.Errorf("rest: got %q, want empty", rest)
|
||||
}
|
||||
cert, ok := key.(*Certificate)
|
||||
if !ok {
|
||||
t.Fatalf("got %v (%T), want *Certificate", key, key)
|
||||
}
|
||||
if !reflect.DeepEqual(cert.CriticalOptions, opts) {
|
||||
t.Errorf("unexpected critical options - got %v, want %v", cert.CriticalOptions, opts)
|
||||
}
|
||||
if !reflect.DeepEqual(cert.Extensions, exts) {
|
||||
t.Errorf("unexpected Extensions - got %v, want %v", cert.Extensions, exts)
|
||||
}
|
||||
marshaled := MarshalAuthorizedKey(key)
|
||||
// Before comparison, remove the trailing newline that
|
||||
// MarshalAuthorizedKey adds.
|
||||
marshaled = marshaled[:len(marshaled)-1]
|
||||
if !bytes.Equal(authKeyBytes, marshaled) {
|
||||
t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCert(t *testing.T) {
|
||||
key, _, _, _, err := ParseAuthorizedKey([]byte(exampleSSHCert))
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAuthorizedKey: %v", err)
|
||||
}
|
||||
validCert, ok := key.(*Certificate)
|
||||
if !ok {
|
||||
t.Fatalf("got %v (%T), want *Certificate", key, key)
|
||||
}
|
||||
checker := CertChecker{}
|
||||
checker.IsUserAuthority = func(k PublicKey) bool {
|
||||
return bytes.Equal(k.Marshal(), validCert.SignatureKey.Marshal())
|
||||
}
|
||||
|
||||
if err := checker.CheckCert("user", validCert); err != nil {
|
||||
t.Errorf("Unable to validate certificate: %v", err)
|
||||
}
|
||||
invalidCert := &Certificate{
|
||||
Key: testPublicKeys["rsa"],
|
||||
SignatureKey: testPublicKeys["ecdsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
Signature: &Signature{},
|
||||
}
|
||||
if err := checker.CheckCert("user", invalidCert); err == nil {
|
||||
t.Error("Invalid cert signature passed validation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCertTime(t *testing.T) {
|
||||
cert := Certificate{
|
||||
ValidPrincipals: []string{"user"},
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidAfter: 50,
|
||||
ValidBefore: 100,
|
||||
}
|
||||
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
|
||||
for ts, ok := range map[int64]bool{
|
||||
25: false,
|
||||
50: true,
|
||||
99: true,
|
||||
100: false,
|
||||
125: false,
|
||||
} {
|
||||
checker := CertChecker{
|
||||
Clock: func() time.Time { return time.Unix(ts, 0) },
|
||||
}
|
||||
checker.IsUserAuthority = func(k PublicKey) bool {
|
||||
return bytes.Equal(k.Marshal(),
|
||||
testPublicKeys["ecdsa"].Marshal())
|
||||
}
|
||||
|
||||
if v := checker.CheckCert("user", &cert); (v == nil) != ok {
|
||||
t.Errorf("Authenticate(%d): %v", ts, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(hanwen): tests for
|
||||
//
|
||||
// host keys:
|
||||
// * fallbacks
|
||||
|
||||
func TestHostKeyCert(t *testing.T) {
|
||||
cert := &Certificate{
|
||||
ValidPrincipals: []string{"hostname", "hostname.domain", "otherhost"},
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
CertType: HostCert,
|
||||
}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
|
||||
checker := &CertChecker{
|
||||
IsHostAuthority: func(p PublicKey, addr string) bool {
|
||||
return addr == "hostname:22" && bytes.Equal(testPublicKeys["ecdsa"].Marshal(), p.Marshal())
|
||||
},
|
||||
}
|
||||
|
||||
certSigner, err := NewCertSigner(cert, testSigners["rsa"])
|
||||
if err != nil {
|
||||
t.Errorf("NewCertSigner: %v", err)
|
||||
}
|
||||
|
||||
for _, test := range []struct {
|
||||
addr string
|
||||
succeed bool
|
||||
}{
|
||||
{addr: "hostname:22", succeed: true},
|
||||
{addr: "otherhost:22", succeed: false}, // The certificate is valid for 'otherhost' as hostname, but we only recognize the authority of the signer for the address 'hostname:22'
|
||||
{addr: "lasthost:22", succeed: false},
|
||||
} {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
errc := make(chan error)
|
||||
|
||||
go func() {
|
||||
conf := ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
conf.AddHostKey(certSigner)
|
||||
_, _, _, err := NewServerConn(c1, &conf)
|
||||
errc <- err
|
||||
}()
|
||||
|
||||
config := &ClientConfig{
|
||||
User: "user",
|
||||
HostKeyCallback: checker.CheckHostKey,
|
||||
}
|
||||
_, _, _, err = NewClientConn(c2, test.addr, config)
|
||||
|
||||
if (err == nil) != test.succeed {
|
||||
t.Fatalf("NewClientConn(%q): %v", test.addr, err)
|
||||
}
|
||||
|
||||
err = <-errc
|
||||
if (err == nil) != test.succeed {
|
||||
t.Fatalf("NewServerConn(%q): %v", test.addr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
633
ssh/channel.go
633
ssh/channel.go
|
|
@ -1,633 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
minPacketLength = 9
|
||||
// channelMaxPacket contains the maximum number of bytes that will be
|
||||
// sent in a single packet. As per RFC 4253, section 6.1, 32k is also
|
||||
// the minimum.
|
||||
channelMaxPacket = 1 << 15
|
||||
// We follow OpenSSH here.
|
||||
channelWindowSize = 64 * channelMaxPacket
|
||||
)
|
||||
|
||||
// NewChannel represents an incoming request to a channel. It must either be
|
||||
// accepted for use by calling Accept, or rejected by calling Reject.
|
||||
type NewChannel interface {
|
||||
// Accept accepts the channel creation request. It returns the Channel
|
||||
// and a Go channel containing SSH requests. The Go channel must be
|
||||
// serviced otherwise the Channel will hang.
|
||||
Accept() (Channel, <-chan *Request, error)
|
||||
|
||||
// Reject rejects the channel creation request. After calling
|
||||
// this, no other methods on the Channel may be called.
|
||||
Reject(reason RejectionReason, message string) error
|
||||
|
||||
// ChannelType returns the type of the channel, as supplied by the
|
||||
// client.
|
||||
ChannelType() string
|
||||
|
||||
// ExtraData returns the arbitrary payload for this channel, as supplied
|
||||
// by the client. This data is specific to the channel type.
|
||||
ExtraData() []byte
|
||||
}
|
||||
|
||||
// A Channel is an ordered, reliable, flow-controlled, duplex stream
|
||||
// that is multiplexed over an SSH connection.
|
||||
type Channel interface {
|
||||
// Read reads up to len(data) bytes from the channel.
|
||||
Read(data []byte) (int, error)
|
||||
|
||||
// Write writes len(data) bytes to the channel.
|
||||
Write(data []byte) (int, error)
|
||||
|
||||
// Close signals end of channel use. No data may be sent after this
|
||||
// call.
|
||||
Close() error
|
||||
|
||||
// CloseWrite signals the end of sending in-band
|
||||
// data. Requests may still be sent, and the other side may
|
||||
// still send data
|
||||
CloseWrite() error
|
||||
|
||||
// SendRequest sends a channel request. If wantReply is true,
|
||||
// it will wait for a reply and return the result as a
|
||||
// boolean, otherwise the return value will be false. Channel
|
||||
// requests are out-of-band messages so they may be sent even
|
||||
// if the data stream is closed or blocked by flow control.
|
||||
// If the channel is closed before a reply is returned, io.EOF
|
||||
// is returned.
|
||||
SendRequest(name string, wantReply bool, payload []byte) (bool, error)
|
||||
|
||||
// Stderr returns an io.ReadWriter that writes to this channel
|
||||
// with the extended data type set to stderr. Stderr may
|
||||
// safely be read and written from a different goroutine than
|
||||
// Read and Write respectively.
|
||||
Stderr() io.ReadWriter
|
||||
}
|
||||
|
||||
// Request is a request sent outside of the normal stream of
|
||||
// data. Requests can either be specific to an SSH channel, or they
|
||||
// can be global.
|
||||
type Request struct {
|
||||
Type string
|
||||
WantReply bool
|
||||
Payload []byte
|
||||
|
||||
ch *channel
|
||||
mux *mux
|
||||
}
|
||||
|
||||
// Reply sends a response to a request. It must be called for all requests
|
||||
// where WantReply is true and is a no-op otherwise. The payload argument is
|
||||
// ignored for replies to channel-specific requests.
|
||||
func (r *Request) Reply(ok bool, payload []byte) error {
|
||||
if !r.WantReply {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r.ch == nil {
|
||||
return r.mux.ackRequest(ok, payload)
|
||||
}
|
||||
|
||||
return r.ch.ackRequest(ok)
|
||||
}
|
||||
|
||||
// RejectionReason is an enumeration used when rejecting channel creation
|
||||
// requests. See RFC 4254, section 5.1.
|
||||
type RejectionReason uint32
|
||||
|
||||
const (
|
||||
Prohibited RejectionReason = iota + 1
|
||||
ConnectionFailed
|
||||
UnknownChannelType
|
||||
ResourceShortage
|
||||
)
|
||||
|
||||
// String converts the rejection reason to human readable form.
|
||||
func (r RejectionReason) String() string {
|
||||
switch r {
|
||||
case Prohibited:
|
||||
return "administratively prohibited"
|
||||
case ConnectionFailed:
|
||||
return "connect failed"
|
||||
case UnknownChannelType:
|
||||
return "unknown channel type"
|
||||
case ResourceShortage:
|
||||
return "resource shortage"
|
||||
}
|
||||
return fmt.Sprintf("unknown reason %d", int(r))
|
||||
}
|
||||
|
||||
func min(a uint32, b int) uint32 {
|
||||
if a < uint32(b) {
|
||||
return a
|
||||
}
|
||||
return uint32(b)
|
||||
}
|
||||
|
||||
type channelDirection uint8
|
||||
|
||||
const (
|
||||
channelInbound channelDirection = iota
|
||||
channelOutbound
|
||||
)
|
||||
|
||||
// channel is an implementation of the Channel interface that works
|
||||
// with the mux class.
|
||||
type channel struct {
|
||||
// R/O after creation
|
||||
chanType string
|
||||
extraData []byte
|
||||
localId, remoteId uint32
|
||||
|
||||
// maxIncomingPayload and maxRemotePayload are the maximum
|
||||
// payload sizes of normal and extended data packets for
|
||||
// receiving and sending, respectively. The wire packet will
|
||||
// be 9 or 13 bytes larger (excluding encryption overhead).
|
||||
maxIncomingPayload uint32
|
||||
maxRemotePayload uint32
|
||||
|
||||
mux *mux
|
||||
|
||||
// decided is set to true if an accept or reject message has been sent
|
||||
// (for outbound channels) or received (for inbound channels).
|
||||
decided bool
|
||||
|
||||
// direction contains either channelOutbound, for channels created
|
||||
// locally, or channelInbound, for channels created by the peer.
|
||||
direction channelDirection
|
||||
|
||||
// Pending internal channel messages.
|
||||
msg chan interface{}
|
||||
|
||||
// Since requests have no ID, there can be only one request
|
||||
// with WantReply=true outstanding. This lock is held by a
|
||||
// goroutine that has such an outgoing request pending.
|
||||
sentRequestMu sync.Mutex
|
||||
|
||||
incomingRequests chan *Request
|
||||
|
||||
sentEOF bool
|
||||
|
||||
// thread-safe data
|
||||
remoteWin window
|
||||
pending *buffer
|
||||
extPending *buffer
|
||||
|
||||
// windowMu protects myWindow, the flow-control window.
|
||||
windowMu sync.Mutex
|
||||
myWindow uint32
|
||||
|
||||
// writeMu serializes calls to mux.conn.writePacket() and
|
||||
// protects sentClose and packetPool. This mutex must be
|
||||
// different from windowMu, as writePacket can block if there
|
||||
// is a key exchange pending.
|
||||
writeMu sync.Mutex
|
||||
sentClose bool
|
||||
|
||||
// packetPool has a buffer for each extended channel ID to
|
||||
// save allocations during writes.
|
||||
packetPool map[uint32][]byte
|
||||
}
|
||||
|
||||
// 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()
|
||||
return io.EOF
|
||||
}
|
||||
c.sentClose = (packet[0] == msgChannelClose)
|
||||
err := c.mux.conn.writePacket(packet)
|
||||
c.writeMu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *channel) sendMessage(msg interface{}) error {
|
||||
if debugMux {
|
||||
log.Printf("send(%d): %#v", c.mux.chanList.offset, msg)
|
||||
}
|
||||
|
||||
p := Marshal(msg)
|
||||
binary.BigEndian.PutUint32(p[1:], c.remoteId)
|
||||
return c.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 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
// 1 byte message type, 4 bytes remoteId, 4 bytes data length
|
||||
opCode := byte(msgChannelData)
|
||||
headerLength := uint32(9)
|
||||
if extendedCode > 0 {
|
||||
headerLength += 4
|
||||
opCode = msgChannelExtendedData
|
||||
}
|
||||
|
||||
c.writeMu.Lock()
|
||||
packet := c.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()
|
||||
|
||||
for len(data) > 0 {
|
||||
space := min(c.maxRemotePayload, len(data))
|
||||
if space, err = c.remoteWin.reserve(space); err != nil {
|
||||
return n, err
|
||||
}
|
||||
if want := headerLength + space; uint32(cap(packet)) < want {
|
||||
packet = make([]byte, want)
|
||||
} else {
|
||||
packet = packet[:want]
|
||||
}
|
||||
|
||||
todo := data[:space]
|
||||
|
||||
packet[0] = opCode
|
||||
binary.BigEndian.PutUint32(packet[1:], c.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 {
|
||||
return n, err
|
||||
}
|
||||
|
||||
n += len(todo)
|
||||
data = data[len(todo):]
|
||||
}
|
||||
|
||||
c.writeMu.Lock()
|
||||
c.packetPool[extendedCode] = packet
|
||||
c.writeMu.Unlock()
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *channel) handleData(packet []byte) error {
|
||||
headerLen := 9
|
||||
isExtendedData := packet[0] == msgChannelExtendedData
|
||||
if isExtendedData {
|
||||
headerLen = 13
|
||||
}
|
||||
if len(packet) < headerLen {
|
||||
// malformed data packet
|
||||
return parseError(packet[0])
|
||||
}
|
||||
|
||||
var extended uint32
|
||||
if isExtendedData {
|
||||
extended = binary.BigEndian.Uint32(packet[5:])
|
||||
}
|
||||
|
||||
length := binary.BigEndian.Uint32(packet[headerLen-4 : headerLen])
|
||||
if length == 0 {
|
||||
return nil
|
||||
}
|
||||
if length > c.maxIncomingPayload {
|
||||
// TODO(hanwen): should send Disconnect?
|
||||
return errors.New("ssh: incoming packet exceeds maximum payload size")
|
||||
}
|
||||
|
||||
data := packet[headerLen:]
|
||||
if length != uint32(len(data)) {
|
||||
return errors.New("ssh: wrong packet length")
|
||||
}
|
||||
|
||||
c.windowMu.Lock()
|
||||
if c.myWindow < length {
|
||||
c.windowMu.Unlock()
|
||||
// TODO(hanwen): should send Disconnect with reason?
|
||||
return errors.New("ssh: remote side wrote too much")
|
||||
}
|
||||
c.myWindow -= length
|
||||
c.windowMu.Unlock()
|
||||
|
||||
if extended == 1 {
|
||||
c.extPending.write(data)
|
||||
} else if extended > 0 {
|
||||
// discard other extended data.
|
||||
} else {
|
||||
c.pending.write(data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *channel) adjustWindow(n uint32) error {
|
||||
c.windowMu.Lock()
|
||||
// Since myWindow is managed on our side, and can never exceed
|
||||
// the initial window setting, we don't worry about overflow.
|
||||
c.myWindow += uint32(n)
|
||||
c.windowMu.Unlock()
|
||||
return c.sendMessage(windowAdjustMsg{
|
||||
AdditionalBytes: uint32(n),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *channel) ReadExtended(data []byte, extended uint32) (n int, err error) {
|
||||
switch extended {
|
||||
case 1:
|
||||
n, err = c.extPending.Read(data)
|
||||
case 0:
|
||||
n, err = c.pending.Read(data)
|
||||
default:
|
||||
return 0, fmt.Errorf("ssh: extended code %d unimplemented", extended)
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
err = c.adjustWindow(uint32(n))
|
||||
// sendWindowAdjust can return io.EOF if the remote
|
||||
// peer has closed the connection, however we want to
|
||||
// defer forwarding io.EOF to the caller of Read until
|
||||
// the buffer has been drained.
|
||||
if n > 0 && err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *channel) close() {
|
||||
c.pending.eof()
|
||||
c.extPending.eof()
|
||||
close(c.msg)
|
||||
close(c.incomingRequests)
|
||||
c.writeMu.Lock()
|
||||
// This is not necessary for a normal channel teardown, but if
|
||||
// there was another error, it is.
|
||||
c.sentClose = true
|
||||
c.writeMu.Unlock()
|
||||
// Unblock writers.
|
||||
c.remoteWin.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 {
|
||||
return errors.New("ssh: channel response message received on inbound channel")
|
||||
}
|
||||
if c.decided {
|
||||
return errors.New("ssh: duplicate response received for channel")
|
||||
}
|
||||
c.decided = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *channel) handlePacket(packet []byte) error {
|
||||
switch packet[0] {
|
||||
case msgChannelData, msgChannelExtendedData:
|
||||
return c.handleData(packet)
|
||||
case msgChannelClose:
|
||||
c.sendMessage(channelCloseMsg{PeersId: c.remoteId})
|
||||
c.mux.chanList.remove(c.localId)
|
||||
c.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()
|
||||
return nil
|
||||
}
|
||||
|
||||
decoded, err := decode(packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch msg := decoded.(type) {
|
||||
case *channelOpenFailureMsg:
|
||||
if err := c.responseMessageReceived(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.mux.chanList.remove(msg.PeersId)
|
||||
c.msg <- msg
|
||||
case *channelOpenConfirmMsg:
|
||||
if err := c.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
|
||||
case *windowAdjustMsg:
|
||||
if !c.remoteWin.add(msg.AdditionalBytes) {
|
||||
return fmt.Errorf("ssh: invalid window update for %d bytes", msg.AdditionalBytes)
|
||||
}
|
||||
case *channelRequestMsg:
|
||||
req := Request{
|
||||
Type: msg.Request,
|
||||
WantReply: msg.WantReply,
|
||||
Payload: msg.RequestSpecificData,
|
||||
ch: c,
|
||||
}
|
||||
|
||||
c.incomingRequests <- &req
|
||||
default:
|
||||
c.msg <- msg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mux) newChannel(chanType string, direction channelDirection, extraData []byte) *channel {
|
||||
ch := &channel{
|
||||
remoteWin: window{Cond: newCond()},
|
||||
myWindow: channelWindowSize,
|
||||
pending: newBuffer(),
|
||||
extPending: newBuffer(),
|
||||
direction: direction,
|
||||
incomingRequests: make(chan *Request, chanSize),
|
||||
msg: make(chan interface{}, chanSize),
|
||||
chanType: chanType,
|
||||
extraData: extraData,
|
||||
mux: m,
|
||||
packetPool: make(map[uint32][]byte),
|
||||
}
|
||||
ch.localId = m.chanList.add(ch)
|
||||
return ch
|
||||
}
|
||||
|
||||
var errUndecided = errors.New("ssh: must Accept or Reject channel")
|
||||
var errDecidedAlready = errors.New("ssh: can call Accept or Reject only once")
|
||||
|
||||
type extChannel struct {
|
||||
code uint32
|
||||
ch *channel
|
||||
}
|
||||
|
||||
func (e *extChannel) Write(data []byte) (n int, err error) {
|
||||
return e.ch.WriteExtended(data, e.code)
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, nil, errDecidedAlready
|
||||
}
|
||||
c.maxIncomingPayload = channelMaxPacket
|
||||
confirm := channelOpenConfirmMsg{
|
||||
PeersId: c.remoteId,
|
||||
MyId: c.localId,
|
||||
MyWindow: c.myWindow,
|
||||
MaxPacketSize: c.maxIncomingPayload,
|
||||
}
|
||||
c.decided = true
|
||||
if err := c.sendMessage(confirm); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return c, c.incomingRequests, nil
|
||||
}
|
||||
|
||||
func (ch *channel) Reject(reason RejectionReason, message string) error {
|
||||
if ch.decided {
|
||||
return errDecidedAlready
|
||||
}
|
||||
reject := channelOpenFailureMsg{
|
||||
PeersId: ch.remoteId,
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
Language: "en",
|
||||
}
|
||||
ch.decided = true
|
||||
return ch.sendMessage(reject)
|
||||
}
|
||||
|
||||
func (ch *channel) Read(data []byte) (int, error) {
|
||||
if !ch.decided {
|
||||
return 0, errUndecided
|
||||
}
|
||||
return ch.ReadExtended(data, 0)
|
||||
}
|
||||
|
||||
func (ch *channel) Write(data []byte) (int, error) {
|
||||
if !ch.decided {
|
||||
return 0, errUndecided
|
||||
}
|
||||
return ch.WriteExtended(data, 0)
|
||||
}
|
||||
|
||||
func (ch *channel) CloseWrite() error {
|
||||
if !ch.decided {
|
||||
return errUndecided
|
||||
}
|
||||
ch.sentEOF = true
|
||||
return ch.sendMessage(channelEOFMsg{
|
||||
PeersId: ch.remoteId})
|
||||
}
|
||||
|
||||
func (ch *channel) Close() error {
|
||||
if !ch.decided {
|
||||
return errUndecided
|
||||
}
|
||||
|
||||
return ch.sendMessage(channelCloseMsg{
|
||||
PeersId: ch.remoteId})
|
||||
}
|
||||
|
||||
// Extended returns an io.ReadWriter that sends and receives data on the given,
|
||||
// SSH extended stream. Such streams are used, for example, for stderr.
|
||||
func (ch *channel) Extended(code uint32) io.ReadWriter {
|
||||
if !ch.decided {
|
||||
return nil
|
||||
}
|
||||
return &extChannel{code, ch}
|
||||
}
|
||||
|
||||
func (ch *channel) Stderr() io.ReadWriter {
|
||||
return ch.Extended(1)
|
||||
}
|
||||
|
||||
func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
|
||||
if !ch.decided {
|
||||
return false, errUndecided
|
||||
}
|
||||
|
||||
if wantReply {
|
||||
ch.sentRequestMu.Lock()
|
||||
defer ch.sentRequestMu.Unlock()
|
||||
}
|
||||
|
||||
msg := channelRequestMsg{
|
||||
PeersId: ch.remoteId,
|
||||
Request: name,
|
||||
WantReply: wantReply,
|
||||
RequestSpecificData: payload,
|
||||
}
|
||||
|
||||
if err := ch.sendMessage(msg); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if wantReply {
|
||||
m, ok := (<-ch.msg)
|
||||
if !ok {
|
||||
return false, io.EOF
|
||||
}
|
||||
switch m.(type) {
|
||||
case *channelRequestFailureMsg:
|
||||
return false, nil
|
||||
case *channelRequestSuccessMsg:
|
||||
return true, nil
|
||||
default:
|
||||
return false, fmt.Errorf("ssh: unexpected response to channel request: %#v", m)
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ackRequest either sends an ack or nack to the channel request.
|
||||
func (ch *channel) ackRequest(ok bool) error {
|
||||
if !ch.decided {
|
||||
return errUndecided
|
||||
}
|
||||
|
||||
var msg interface{}
|
||||
if !ok {
|
||||
msg = channelRequestFailureMsg{
|
||||
PeersId: ch.remoteId,
|
||||
}
|
||||
} else {
|
||||
msg = channelRequestSuccessMsg{
|
||||
PeersId: ch.remoteId,
|
||||
}
|
||||
}
|
||||
return ch.sendMessage(msg)
|
||||
}
|
||||
|
||||
func (ch *channel) ChannelType() string {
|
||||
return ch.chanType
|
||||
}
|
||||
|
||||
func (ch *channel) ExtraData() []byte {
|
||||
return ch.extraData
|
||||
}
|
||||
629
ssh/cipher.go
629
ssh/cipher.go
|
|
@ -1,629 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"crypto/rc4"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
const (
|
||||
packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher.
|
||||
|
||||
// RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations
|
||||
// MUST be able to process (plus a few more kilobytes for padding and mac). The RFC
|
||||
// indicates implementations SHOULD be able to handle larger packet sizes, but then
|
||||
// waffles on about reasonable limits.
|
||||
//
|
||||
// OpenSSH caps their maxPacket at 256kB so we choose to do
|
||||
// the same. maxPacket is also used to ensure that uint32
|
||||
// length fields do not overflow, so it should remain well
|
||||
// below 4G.
|
||||
maxPacket = 256 * 1024
|
||||
)
|
||||
|
||||
// noneCipher implements cipher.Stream and provides no encryption. It is used
|
||||
// by the transport before the first key-exchange.
|
||||
type noneCipher struct{}
|
||||
|
||||
func (c noneCipher) XORKeyStream(dst, src []byte) {
|
||||
copy(dst, src)
|
||||
}
|
||||
|
||||
func newAESCTR(key, iv []byte) (cipher.Stream, error) {
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipher.NewCTR(c, iv), nil
|
||||
}
|
||||
|
||||
func newRC4(key, iv []byte) (cipher.Stream, error) {
|
||||
return rc4.NewCipher(key)
|
||||
}
|
||||
|
||||
type streamCipherMode struct {
|
||||
keySize int
|
||||
ivSize int
|
||||
skip int
|
||||
createFunc func(key, iv []byte) (cipher.Stream, error)
|
||||
}
|
||||
|
||||
func (c *streamCipherMode) createStream(key, iv []byte) (cipher.Stream, error) {
|
||||
if len(key) < c.keySize {
|
||||
panic("ssh: key length too small for cipher")
|
||||
}
|
||||
if len(iv) < c.ivSize {
|
||||
panic("ssh: iv too small for cipher")
|
||||
}
|
||||
|
||||
stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var streamDump []byte
|
||||
if c.skip > 0 {
|
||||
streamDump = make([]byte, 512)
|
||||
}
|
||||
|
||||
for remainingToDump := c.skip; remainingToDump > 0; {
|
||||
dumpThisTime := remainingToDump
|
||||
if dumpThisTime > len(streamDump) {
|
||||
dumpThisTime = len(streamDump)
|
||||
}
|
||||
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
|
||||
remainingToDump -= dumpThisTime
|
||||
}
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
// cipherModes documents properties of supported ciphers. Ciphers not included
|
||||
// are not supported and will not be negotiated, even if explicitly requested in
|
||||
// ClientConfig.Crypto.Ciphers.
|
||||
var cipherModes = map[string]*streamCipherMode{
|
||||
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
|
||||
// are defined in the order specified in the RFC.
|
||||
"aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
|
||||
"aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
|
||||
"aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
|
||||
|
||||
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
|
||||
// They are defined in the order specified in the RFC.
|
||||
"arcfour128": {16, 0, 1536, newRC4},
|
||||
"arcfour256": {32, 0, 1536, newRC4},
|
||||
|
||||
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
|
||||
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
|
||||
// RC4) has problems with weak keys, and should be used with caution."
|
||||
// RFC4345 introduces improved versions of Arcfour.
|
||||
"arcfour": {16, 0, 0, newRC4},
|
||||
|
||||
// AES-GCM is not a stream cipher, so it is constructed with a
|
||||
// special case. If we add any more non-stream ciphers, we
|
||||
// should invest a cleaner way to do this.
|
||||
gcmCipherID: {16, 12, 0, nil},
|
||||
|
||||
// CBC mode is insecure and so is not included in the default config.
|
||||
// (See http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf). If absolutely
|
||||
// needed, it's possible to specify a custom Config to enable it.
|
||||
// You should expect that an active attacker can recover plaintext if
|
||||
// you do.
|
||||
aes128cbcID: {16, aes.BlockSize, 0, nil},
|
||||
|
||||
// 3des-cbc is insecure and is disabled by default.
|
||||
tripledescbcID: {24, des.BlockSize, 0, nil},
|
||||
}
|
||||
|
||||
// prefixLen is the length of the packet prefix that contains the packet length
|
||||
// and number of padding bytes.
|
||||
const prefixLen = 5
|
||||
|
||||
// streamPacketCipher is a packetCipher using a stream cipher.
|
||||
type streamPacketCipher struct {
|
||||
mac hash.Hash
|
||||
cipher cipher.Stream
|
||||
etm bool
|
||||
|
||||
// The following members are to avoid per-packet allocations.
|
||||
prefix [prefixLen]byte
|
||||
seqNumBytes [4]byte
|
||||
padding [2 * packetSizeMultiple]byte
|
||||
packetData []byte
|
||||
macResult []byte
|
||||
}
|
||||
|
||||
// readPacket reads and decrypt a single packet from the reader argument.
|
||||
func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||
if _, err := io.ReadFull(r, s.prefix[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var encryptedPaddingLength [1]byte
|
||||
if s.mac != nil && s.etm {
|
||||
copy(encryptedPaddingLength[:], s.prefix[4:5])
|
||||
s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5])
|
||||
} else {
|
||||
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
|
||||
}
|
||||
|
||||
length := binary.BigEndian.Uint32(s.prefix[0:4])
|
||||
paddingLength := uint32(s.prefix[4])
|
||||
|
||||
var macSize uint32
|
||||
if s.mac != nil {
|
||||
s.mac.Reset()
|
||||
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
|
||||
s.mac.Write(s.seqNumBytes[:])
|
||||
if s.etm {
|
||||
s.mac.Write(s.prefix[:4])
|
||||
s.mac.Write(encryptedPaddingLength[:])
|
||||
} else {
|
||||
s.mac.Write(s.prefix[:])
|
||||
}
|
||||
macSize = uint32(s.mac.Size())
|
||||
}
|
||||
|
||||
if length <= paddingLength+1 {
|
||||
return nil, errors.New("ssh: invalid packet length, packet too small")
|
||||
}
|
||||
|
||||
if length > maxPacket {
|
||||
return nil, errors.New("ssh: invalid packet length, packet too large")
|
||||
}
|
||||
|
||||
// the maxPacket check above ensures that length-1+macSize
|
||||
// does not overflow.
|
||||
if uint32(cap(s.packetData)) < length-1+macSize {
|
||||
s.packetData = make([]byte, length-1+macSize)
|
||||
} else {
|
||||
s.packetData = s.packetData[:length-1+macSize]
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(r, s.packetData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mac := s.packetData[length-1:]
|
||||
data := s.packetData[:length-1]
|
||||
|
||||
if s.mac != nil && s.etm {
|
||||
s.mac.Write(data)
|
||||
}
|
||||
|
||||
s.cipher.XORKeyStream(data, data)
|
||||
|
||||
if s.mac != nil {
|
||||
if !s.etm {
|
||||
s.mac.Write(data)
|
||||
}
|
||||
s.macResult = s.mac.Sum(s.macResult[:0])
|
||||
if subtle.ConstantTimeCompare(s.macResult, mac) != 1 {
|
||||
return nil, errors.New("ssh: MAC failure")
|
||||
}
|
||||
}
|
||||
|
||||
return s.packetData[:length-paddingLength-1], nil
|
||||
}
|
||||
|
||||
// writePacket encrypts and sends a packet of data to the writer argument
|
||||
func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
||||
if len(packet) > maxPacket {
|
||||
return errors.New("ssh: packet too large")
|
||||
}
|
||||
|
||||
aadlen := 0
|
||||
if s.mac != nil && s.etm {
|
||||
// packet length is not encrypted for EtM modes
|
||||
aadlen = 4
|
||||
}
|
||||
|
||||
paddingLength := packetSizeMultiple - (prefixLen+len(packet)-aadlen)%packetSizeMultiple
|
||||
if paddingLength < 4 {
|
||||
paddingLength += packetSizeMultiple
|
||||
}
|
||||
|
||||
length := len(packet) + 1 + paddingLength
|
||||
binary.BigEndian.PutUint32(s.prefix[:], uint32(length))
|
||||
s.prefix[4] = byte(paddingLength)
|
||||
padding := s.padding[:paddingLength]
|
||||
if _, err := io.ReadFull(rand, padding); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.mac != nil {
|
||||
s.mac.Reset()
|
||||
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
|
||||
s.mac.Write(s.seqNumBytes[:])
|
||||
|
||||
if s.etm {
|
||||
// For EtM algorithms, the packet length must stay unencrypted,
|
||||
// but the following data (padding length) must be encrypted
|
||||
s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5])
|
||||
}
|
||||
|
||||
s.mac.Write(s.prefix[:])
|
||||
|
||||
if !s.etm {
|
||||
// For non-EtM algorithms, the algorithm is applied on unencrypted data
|
||||
s.mac.Write(packet)
|
||||
s.mac.Write(padding)
|
||||
}
|
||||
}
|
||||
|
||||
if !(s.mac != nil && s.etm) {
|
||||
// For EtM algorithms, the padding length has already been encrypted
|
||||
// and the packet length must remain unencrypted
|
||||
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
|
||||
}
|
||||
|
||||
s.cipher.XORKeyStream(packet, packet)
|
||||
s.cipher.XORKeyStream(padding, padding)
|
||||
|
||||
if s.mac != nil && s.etm {
|
||||
// For EtM algorithms, packet and padding must be encrypted
|
||||
s.mac.Write(packet)
|
||||
s.mac.Write(padding)
|
||||
}
|
||||
|
||||
if _, err := w.Write(s.prefix[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write(packet); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write(padding); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.mac != nil {
|
||||
s.macResult = s.mac.Sum(s.macResult[:0])
|
||||
if _, err := w.Write(s.macResult); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type gcmCipher struct {
|
||||
aead cipher.AEAD
|
||||
prefix [4]byte
|
||||
iv []byte
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func newGCMCipher(iv, key, macKey []byte) (packetCipher, error) {
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aead, err := cipher.NewGCM(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gcmCipher{
|
||||
aead: aead,
|
||||
iv: iv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
const gcmTagSize = 16
|
||||
|
||||
func (c *gcmCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
||||
// Pad out to multiple of 16 bytes. This is different from the
|
||||
// stream cipher because that encrypts the length too.
|
||||
padding := byte(packetSizeMultiple - (1+len(packet))%packetSizeMultiple)
|
||||
if padding < 4 {
|
||||
padding += packetSizeMultiple
|
||||
}
|
||||
|
||||
length := uint32(len(packet) + int(padding) + 1)
|
||||
binary.BigEndian.PutUint32(c.prefix[:], length)
|
||||
if _, err := w.Write(c.prefix[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cap(c.buf) < int(length) {
|
||||
c.buf = make([]byte, length)
|
||||
} else {
|
||||
c.buf = c.buf[:length]
|
||||
}
|
||||
|
||||
c.buf[0] = padding
|
||||
copy(c.buf[1:], packet)
|
||||
if _, err := io.ReadFull(rand, c.buf[1+len(packet):]); err != nil {
|
||||
return err
|
||||
}
|
||||
c.buf = c.aead.Seal(c.buf[:0], c.iv, c.buf, c.prefix[:])
|
||||
if _, err := w.Write(c.buf); err != nil {
|
||||
return err
|
||||
}
|
||||
c.incIV()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *gcmCipher) incIV() {
|
||||
for i := 4 + 7; i >= 4; i-- {
|
||||
c.iv[i]++
|
||||
if c.iv[i] != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||
if _, err := io.ReadFull(r, c.prefix[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length := binary.BigEndian.Uint32(c.prefix[:])
|
||||
if length > maxPacket {
|
||||
return nil, errors.New("ssh: max packet length exceeded.")
|
||||
}
|
||||
|
||||
if cap(c.buf) < int(length+gcmTagSize) {
|
||||
c.buf = make([]byte, length+gcmTagSize)
|
||||
} else {
|
||||
c.buf = c.buf[:length+gcmTagSize]
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(r, c.buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plain, err := c.aead.Open(c.buf[:0], c.iv, c.buf, c.prefix[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.incIV()
|
||||
|
||||
padding := plain[0]
|
||||
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)
|
||||
}
|
||||
|
||||
if int(padding+1) >= len(plain) {
|
||||
return nil, fmt.Errorf("ssh: padding %d too large", padding)
|
||||
}
|
||||
plain = plain[1 : length-uint32(padding)]
|
||||
return plain, nil
|
||||
}
|
||||
|
||||
// cbcCipher implements aes128-cbc cipher defined in RFC 4253 section 6.1
|
||||
type cbcCipher struct {
|
||||
mac hash.Hash
|
||||
macSize uint32
|
||||
decrypter cipher.BlockMode
|
||||
encrypter cipher.BlockMode
|
||||
|
||||
// The following members are to avoid per-packet allocations.
|
||||
seqNumBytes [4]byte
|
||||
packetData []byte
|
||||
macResult []byte
|
||||
|
||||
// Amount of data we should still read to hide which
|
||||
// verification error triggered.
|
||||
oracleCamouflage uint32
|
||||
}
|
||||
|
||||
func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||
cbc := &cbcCipher{
|
||||
mac: macModes[algs.MAC].new(macKey),
|
||||
decrypter: cipher.NewCBCDecrypter(c, iv),
|
||||
encrypter: cipher.NewCBCEncrypter(c, iv),
|
||||
packetData: make([]byte, 1024),
|
||||
}
|
||||
if cbc.mac != nil {
|
||||
cbc.macSize = uint32(cbc.mac.Size())
|
||||
}
|
||||
|
||||
return cbc, nil
|
||||
}
|
||||
|
||||
func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cbc, nil
|
||||
}
|
||||
|
||||
func newTripleDESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||
c, err := des.NewTripleDESCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cbc, nil
|
||||
}
|
||||
|
||||
func maxUInt32(a, b int) uint32 {
|
||||
if a > b {
|
||||
return uint32(a)
|
||||
}
|
||||
return uint32(b)
|
||||
}
|
||||
|
||||
const (
|
||||
cbcMinPacketSizeMultiple = 8
|
||||
cbcMinPacketSize = 16
|
||||
cbcMinPaddingSize = 4
|
||||
)
|
||||
|
||||
// cbcError represents a verification error that may leak information.
|
||||
type cbcError string
|
||||
|
||||
func (e cbcError) Error() string { return string(e) }
|
||||
|
||||
func (c *cbcCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||
p, err := c.readPacketLeaky(seqNum, r)
|
||||
if err != nil {
|
||||
if _, ok := err.(cbcError); ok {
|
||||
// Verification error: read a fixed amount of
|
||||
// data, to make distinguishing between
|
||||
// failing MAC and failing length check more
|
||||
// difficult.
|
||||
io.CopyN(ioutil.Discard, r, int64(c.oracleCamouflage))
|
||||
}
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (c *cbcCipher) readPacketLeaky(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||
blockSize := c.decrypter.BlockSize()
|
||||
|
||||
// Read the header, which will include some of the subsequent data in the
|
||||
// case of block ciphers - this is copied back to the payload later.
|
||||
// How many bytes of payload/padding will be read with this first read.
|
||||
firstBlockLength := uint32((prefixLen + blockSize - 1) / blockSize * blockSize)
|
||||
firstBlock := c.packetData[:firstBlockLength]
|
||||
if _, err := io.ReadFull(r, firstBlock); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.oracleCamouflage = maxPacket + 4 + c.macSize - firstBlockLength
|
||||
|
||||
c.decrypter.CryptBlocks(firstBlock, firstBlock)
|
||||
length := binary.BigEndian.Uint32(firstBlock[:4])
|
||||
if length > maxPacket {
|
||||
return nil, cbcError("ssh: packet too large")
|
||||
}
|
||||
if length+4 < maxUInt32(cbcMinPacketSize, blockSize) {
|
||||
// The minimum size of a packet is 16 (or the cipher block size, whichever
|
||||
// is larger) bytes.
|
||||
return nil, cbcError("ssh: packet too small")
|
||||
}
|
||||
// The length of the packet (including the length field but not the MAC) must
|
||||
// be a multiple of the block size or 8, whichever is larger.
|
||||
if (length+4)%maxUInt32(cbcMinPacketSizeMultiple, blockSize) != 0 {
|
||||
return nil, cbcError("ssh: invalid packet length multiple")
|
||||
}
|
||||
|
||||
paddingLength := uint32(firstBlock[4])
|
||||
if paddingLength < cbcMinPaddingSize || length <= paddingLength+1 {
|
||||
return nil, cbcError("ssh: invalid packet length")
|
||||
}
|
||||
|
||||
// Positions within the c.packetData buffer:
|
||||
macStart := 4 + length
|
||||
paddingStart := macStart - paddingLength
|
||||
|
||||
// Entire packet size, starting before length, ending at end of mac.
|
||||
entirePacketSize := macStart + c.macSize
|
||||
|
||||
// Ensure c.packetData is large enough for the entire packet data.
|
||||
if uint32(cap(c.packetData)) < entirePacketSize {
|
||||
// Still need to upsize and copy, but this should be rare at runtime, only
|
||||
// on upsizing the packetData buffer.
|
||||
c.packetData = make([]byte, entirePacketSize)
|
||||
copy(c.packetData, firstBlock)
|
||||
} else {
|
||||
c.packetData = c.packetData[:entirePacketSize]
|
||||
}
|
||||
|
||||
if n, err := io.ReadFull(r, c.packetData[firstBlockLength:]); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.oracleCamouflage -= uint32(n)
|
||||
}
|
||||
|
||||
remainingCrypted := c.packetData[firstBlockLength:macStart]
|
||||
c.decrypter.CryptBlocks(remainingCrypted, remainingCrypted)
|
||||
|
||||
mac := c.packetData[macStart:]
|
||||
if c.mac != nil {
|
||||
c.mac.Reset()
|
||||
binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum)
|
||||
c.mac.Write(c.seqNumBytes[:])
|
||||
c.mac.Write(c.packetData[:macStart])
|
||||
c.macResult = c.mac.Sum(c.macResult[:0])
|
||||
if subtle.ConstantTimeCompare(c.macResult, mac) != 1 {
|
||||
return nil, cbcError("ssh: MAC failure")
|
||||
}
|
||||
}
|
||||
|
||||
return c.packetData[prefixLen:paddingStart], nil
|
||||
}
|
||||
|
||||
func (c *cbcCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
||||
effectiveBlockSize := maxUInt32(cbcMinPacketSizeMultiple, c.encrypter.BlockSize())
|
||||
|
||||
// Length of encrypted portion of the packet (header, payload, padding).
|
||||
// Enforce minimum padding and packet size.
|
||||
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPaddingSize)
|
||||
// Enforce block size.
|
||||
encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize
|
||||
|
||||
length := encLength - 4
|
||||
paddingLength := int(length) - (1 + len(packet))
|
||||
|
||||
// Overall buffer contains: header, payload, padding, mac.
|
||||
// Space for the MAC is reserved in the capacity but not the slice length.
|
||||
bufferSize := encLength + c.macSize
|
||||
if uint32(cap(c.packetData)) < bufferSize {
|
||||
c.packetData = make([]byte, encLength, bufferSize)
|
||||
} else {
|
||||
c.packetData = c.packetData[:encLength]
|
||||
}
|
||||
|
||||
p := c.packetData
|
||||
|
||||
// Packet header.
|
||||
binary.BigEndian.PutUint32(p, length)
|
||||
p = p[4:]
|
||||
p[0] = byte(paddingLength)
|
||||
|
||||
// Payload.
|
||||
p = p[1:]
|
||||
copy(p, packet)
|
||||
|
||||
// Padding.
|
||||
p = p[len(packet):]
|
||||
if _, err := io.ReadFull(rand, p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.mac != nil {
|
||||
c.mac.Reset()
|
||||
binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum)
|
||||
c.mac.Write(c.seqNumBytes[:])
|
||||
c.mac.Write(c.packetData)
|
||||
// The MAC is now appended into the capacity reserved for it earlier.
|
||||
c.packetData = c.mac.Sum(c.packetData)
|
||||
}
|
||||
|
||||
c.encrypter.CryptBlocks(c.packetData[:encLength], c.packetData[:encLength])
|
||||
|
||||
if _, err := w.Write(c.packetData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDefaultCiphersExist(t *testing.T) {
|
||||
for _, cipherAlgo := range supportedCiphers {
|
||||
if _, ok := cipherModes[cipherAlgo]; !ok {
|
||||
t.Errorf("default cipher %q is unknown", cipherAlgo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPacketCiphers(t *testing.T) {
|
||||
// Still test aes128cbc cipher although it's commented out.
|
||||
cipherModes[aes128cbcID] = &streamCipherMode{16, aes.BlockSize, 0, nil}
|
||||
defer delete(cipherModes, aes128cbcID)
|
||||
|
||||
for cipher := range cipherModes {
|
||||
for mac := range macModes {
|
||||
kr := &kexResult{Hash: crypto.SHA1}
|
||||
algs := directionAlgorithms{
|
||||
Cipher: cipher,
|
||||
MAC: mac,
|
||||
Compression: "none",
|
||||
}
|
||||
client, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Errorf("newPacketCipher(client, %q, %q): %v", cipher, mac, err)
|
||||
continue
|
||||
}
|
||||
server, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Errorf("newPacketCipher(client, %q, %q): %v", cipher, mac, err)
|
||||
continue
|
||||
}
|
||||
|
||||
want := "bla bla"
|
||||
input := []byte(want)
|
||||
buf := &bytes.Buffer{}
|
||||
if err := client.writePacket(0, buf, rand.Reader, input); err != nil {
|
||||
t.Errorf("writePacket(%q, %q): %v", cipher, mac, err)
|
||||
continue
|
||||
}
|
||||
|
||||
packet, err := server.readPacket(0, buf)
|
||||
if err != nil {
|
||||
t.Errorf("readPacket(%q, %q): %v", cipher, mac, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if string(packet) != want {
|
||||
t.Errorf("roundtrip(%q, %q): got %q, want %q", cipher, mac, packet, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCBCOracleCounterMeasure(t *testing.T) {
|
||||
cipherModes[aes128cbcID] = &streamCipherMode{16, aes.BlockSize, 0, nil}
|
||||
defer delete(cipherModes, aes128cbcID)
|
||||
|
||||
kr := &kexResult{Hash: crypto.SHA1}
|
||||
algs := directionAlgorithms{
|
||||
Cipher: aes128cbcID,
|
||||
MAC: "hmac-sha1",
|
||||
Compression: "none",
|
||||
}
|
||||
client, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Fatalf("newPacketCipher(client): %v", err)
|
||||
}
|
||||
|
||||
want := "bla bla"
|
||||
input := []byte(want)
|
||||
buf := &bytes.Buffer{}
|
||||
if err := client.writePacket(0, buf, rand.Reader, input); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
|
||||
packetSize := buf.Len()
|
||||
buf.Write(make([]byte, 2*maxPacket))
|
||||
|
||||
// We corrupt each byte, but this usually will only test the
|
||||
// 'packet too large' or 'MAC failure' cases.
|
||||
lastRead := -1
|
||||
for i := 0; i < packetSize; i++ {
|
||||
server, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Fatalf("newPacketCipher(client): %v", err)
|
||||
}
|
||||
|
||||
fresh := &bytes.Buffer{}
|
||||
fresh.Write(buf.Bytes())
|
||||
fresh.Bytes()[i] ^= 0x01
|
||||
|
||||
before := fresh.Len()
|
||||
_, err = server.readPacket(0, fresh)
|
||||
if err == nil {
|
||||
t.Errorf("corrupt byte %d: readPacket succeeded ", i)
|
||||
continue
|
||||
}
|
||||
if _, ok := err.(cbcError); !ok {
|
||||
t.Errorf("corrupt byte %d: got %v (%T), want cbcError", i, err, err)
|
||||
continue
|
||||
}
|
||||
|
||||
after := fresh.Len()
|
||||
bytesRead := before - after
|
||||
if bytesRead < maxPacket {
|
||||
t.Errorf("corrupt byte %d: read %d bytes, want more than %d", i, bytesRead, maxPacket)
|
||||
continue
|
||||
}
|
||||
|
||||
if i > 0 && bytesRead != lastRead {
|
||||
t.Errorf("corrupt byte %d: read %d bytes, want %d bytes read", i, bytesRead, lastRead)
|
||||
}
|
||||
lastRead = bytesRead
|
||||
}
|
||||
}
|
||||
257
ssh/client.go
257
ssh/client.go
|
|
@ -1,257 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Client implements a traditional SSH client that supports shells,
|
||||
// subprocesses, TCP port/streamlocal forwarding and tunneled dialing.
|
||||
type Client struct {
|
||||
Conn
|
||||
|
||||
forwards forwardList // forwarded tcpip connections from the remote side
|
||||
mu sync.Mutex
|
||||
channelHandlers map[string]chan NewChannel
|
||||
}
|
||||
|
||||
// HandleChannelOpen returns a channel on which NewChannel requests
|
||||
// for the given type are sent. If the type already is being handled,
|
||||
// nil is returned. The channel is closed when the connection is closed.
|
||||
func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.channelHandlers == nil {
|
||||
// The SSH channel has been closed.
|
||||
c := make(chan NewChannel)
|
||||
close(c)
|
||||
return c
|
||||
}
|
||||
|
||||
ch := c.channelHandlers[channelType]
|
||||
if ch != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ch = make(chan NewChannel, chanSize)
|
||||
c.channelHandlers[channelType] = ch
|
||||
return ch
|
||||
}
|
||||
|
||||
// NewClient creates a Client on top of the given connection.
|
||||
func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
|
||||
conn := &Client{
|
||||
Conn: c,
|
||||
channelHandlers: make(map[string]chan NewChannel, 1),
|
||||
}
|
||||
|
||||
go conn.handleGlobalRequests(reqs)
|
||||
go conn.handleChannelOpens(chans)
|
||||
go func() {
|
||||
conn.Wait()
|
||||
conn.forwards.closeAll()
|
||||
}()
|
||||
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip"))
|
||||
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-streamlocal@openssh.com"))
|
||||
return conn
|
||||
}
|
||||
|
||||
// NewClientConn establishes an authenticated SSH connection using c
|
||||
// as the underlying transport. The Request and NewChannel channels
|
||||
// must be serviced or the connection will hang.
|
||||
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},
|
||||
}
|
||||
|
||||
if err := conn.clientHandshake(addr, &fullConf); err != nil {
|
||||
c.Close()
|
||||
return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err)
|
||||
}
|
||||
conn.mux = newMux(conn.transport)
|
||||
return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
|
||||
}
|
||||
|
||||
// clientHandshake performs the client side key exchange. See RFC 4253 Section
|
||||
// 7.
|
||||
func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error {
|
||||
if config.ClientVersion != "" {
|
||||
c.clientVersion = []byte(config.ClientVersion)
|
||||
} else {
|
||||
c.clientVersion = []byte(packageVersion)
|
||||
}
|
||||
var err error
|
||||
c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.transport = newClientTransport(
|
||||
newTransport(c.sshConn.conn, config.Rand, true /* is client */),
|
||||
c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
|
||||
if err := c.transport.waitSession(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.sessionID = c.transport.getSessionID()
|
||||
return c.clientAuthenticate(config)
|
||||
}
|
||||
|
||||
// verifyHostKeySignature verifies the host key obtained in the key
|
||||
// exchange.
|
||||
func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error {
|
||||
sig, rest, ok := parseSignatureBody(result.Signature)
|
||||
if len(rest) > 0 || !ok {
|
||||
return errors.New("ssh: signature parse error")
|
||||
}
|
||||
|
||||
return hostKey.Verify(result.H, sig)
|
||||
}
|
||||
|
||||
// NewSession opens a new Session for this client. (A session is a remote
|
||||
// execution of a program.)
|
||||
func (c *Client) NewSession() (*Session, error) {
|
||||
ch, in, err := c.OpenChannel("session", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newSession(ch, in)
|
||||
}
|
||||
|
||||
func (c *Client) handleGlobalRequests(incoming <-chan *Request) {
|
||||
for r := range incoming {
|
||||
// This handles keepalive messages and matches
|
||||
// the behaviour of OpenSSH.
|
||||
r.Reply(false, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// handleChannelOpens channel open messages from the remote side.
|
||||
func (c *Client) handleChannelOpens(in <-chan NewChannel) {
|
||||
for ch := range in {
|
||||
c.mu.Lock()
|
||||
handler := c.channelHandlers[ch.ChannelType()]
|
||||
c.mu.Unlock()
|
||||
|
||||
if handler != nil {
|
||||
handler <- ch
|
||||
} else {
|
||||
ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType()))
|
||||
}
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
for _, ch := range c.channelHandlers {
|
||||
close(ch)
|
||||
}
|
||||
c.channelHandlers = nil
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Dial starts a client connection to the given SSH server. It is a
|
||||
// convenience function that connects to the given network address,
|
||||
// initiates the SSH handshake, and then sets up a Client. For access
|
||||
// to incoming channels and requests, use net.Dial with NewClientConn
|
||||
// instead.
|
||||
func Dial(network, addr string, config *ClientConfig) (*Client, error) {
|
||||
conn, err := net.DialTimeout(network, addr, config.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, chans, reqs, err := NewClientConn(conn, addr, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
|
||||
// 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 {
|
||||
// Config contains configuration that is shared between clients and
|
||||
// servers.
|
||||
Config
|
||||
|
||||
// User contains the username to authenticate as.
|
||||
User string
|
||||
|
||||
// Auth contains possible authentication methods to use with the
|
||||
// server. Only the first instance of a particular RFC 4252 method will
|
||||
// be used during authentication.
|
||||
Auth []AuthMethod
|
||||
|
||||
// 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
|
||||
|
||||
// ClientVersion contains the version identification string that will
|
||||
// be used for the connection. If empty, a reasonable default is used.
|
||||
ClientVersion string
|
||||
|
||||
// HostKeyAlgorithms lists the key types that the client will
|
||||
// accept from the server as host key, in order of
|
||||
// preference. If empty, a reasonable default is used. Any
|
||||
// string returned from PublicKey.Type method may be used, or
|
||||
// any of the CertAlgoXxxx and KeyAlgoXxxx constants.
|
||||
HostKeyAlgorithms []string
|
||||
|
||||
// Timeout is the maximum amount of time for the TCP connection to establish.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
|
@ -1,486 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// clientAuthenticate authenticates with the remote server. See RFC 4252.
|
||||
func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
||||
// initiate user auth session
|
||||
if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil {
|
||||
return err
|
||||
}
|
||||
packet, err := c.transport.readPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var serviceAccept serviceAcceptMsg
|
||||
if err := Unmarshal(packet, &serviceAccept); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// during the authentication phase the client first attempts the "none" method
|
||||
// then any untried methods suggested by the server.
|
||||
tried := make(map[string]bool)
|
||||
var lastMethods []string
|
||||
|
||||
sessionID := c.transport.getSessionID()
|
||||
for auth := AuthMethod(new(noneAuth)); auth != nil; {
|
||||
ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
// success
|
||||
return nil
|
||||
}
|
||||
tried[auth.method()] = true
|
||||
if methods == nil {
|
||||
methods = lastMethods
|
||||
}
|
||||
lastMethods = methods
|
||||
|
||||
auth = nil
|
||||
|
||||
findNext:
|
||||
for _, a := range config.Auth {
|
||||
candidateMethod := a.method()
|
||||
if tried[candidateMethod] {
|
||||
continue
|
||||
}
|
||||
for _, meth := range methods {
|
||||
if meth == candidateMethod {
|
||||
auth = a
|
||||
break findNext
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
|
||||
}
|
||||
|
||||
func keys(m map[string]bool) []string {
|
||||
s := make([]string, 0, len(m))
|
||||
|
||||
for key := range m {
|
||||
s = append(s, key)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// An AuthMethod represents an instance of an RFC 4252 authentication method.
|
||||
type AuthMethod interface {
|
||||
// auth authenticates user over transport t.
|
||||
// Returns true if authentication is successful.
|
||||
// If authentication is not successful, a []string of alternative
|
||||
// method names is returned. If the slice is nil, it will be ignored
|
||||
// and the previous set of possible methods will be reused.
|
||||
auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
|
||||
|
||||
// method returns the RFC 4252 method name.
|
||||
method() string
|
||||
}
|
||||
|
||||
// "none" authentication, RFC 4252 section 5.2.
|
||||
type noneAuth int
|
||||
|
||||
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
if err := c.writePacket(Marshal(&userAuthRequestMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: "none",
|
||||
})); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
return handleAuthResponse(c)
|
||||
}
|
||||
|
||||
func (n *noneAuth) method() string {
|
||||
return "none"
|
||||
}
|
||||
|
||||
// passwordCallback is an AuthMethod that fetches the password through
|
||||
// a function call, e.g. by prompting the user.
|
||||
type passwordCallback func() (password string, err error)
|
||||
|
||||
func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
type passwordAuthMsg struct {
|
||||
User string `sshtype:"50"`
|
||||
Service string
|
||||
Method string
|
||||
Reply bool
|
||||
Password string
|
||||
}
|
||||
|
||||
pw, err := cb()
|
||||
// REVIEW NOTE: is there a need to support skipping a password attempt?
|
||||
// The program may only find out that the user doesn't have a password
|
||||
// when prompting.
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if err := c.writePacket(Marshal(&passwordAuthMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: cb.method(),
|
||||
Reply: false,
|
||||
Password: pw,
|
||||
})); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
return handleAuthResponse(c)
|
||||
}
|
||||
|
||||
func (cb passwordCallback) method() string {
|
||||
return "password"
|
||||
}
|
||||
|
||||
// Password returns an AuthMethod using the given password.
|
||||
func Password(secret string) AuthMethod {
|
||||
return passwordCallback(func() (string, error) { return secret, nil })
|
||||
}
|
||||
|
||||
// PasswordCallback returns an AuthMethod that uses a callback for
|
||||
// fetching a password.
|
||||
func PasswordCallback(prompt func() (secret string, err error)) AuthMethod {
|
||||
return passwordCallback(prompt)
|
||||
}
|
||||
|
||||
type publickeyAuthMsg struct {
|
||||
User string `sshtype:"50"`
|
||||
Service string
|
||||
Method string
|
||||
// HasSig indicates to the receiver packet that the auth request is signed and
|
||||
// should be used for authentication of the request.
|
||||
HasSig bool
|
||||
Algoname string
|
||||
PubKey []byte
|
||||
// Sig is tagged with "rest" so Marshal will exclude it during
|
||||
// validateKey
|
||||
Sig []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// publicKeyCallback is an AuthMethod that uses a set of key
|
||||
// pairs for authentication.
|
||||
type publicKeyCallback func() ([]Signer, error)
|
||||
|
||||
func (cb publicKeyCallback) method() string {
|
||||
return "publickey"
|
||||
}
|
||||
|
||||
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
// 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 methods []string
|
||||
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,
|
||||
Service: serviceSSH,
|
||||
Method: cb.method(),
|
||||
}, []byte(pub.Type()), pubKey))
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// manually wrap the serialized signature in a string
|
||||
s := Marshal(sign)
|
||||
sig := make([]byte, stringLength(len(s)))
|
||||
marshalString(sig, s)
|
||||
msg := publickeyAuthMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: cb.method(),
|
||||
HasSig: true,
|
||||
Algoname: pub.Type(),
|
||||
PubKey: pubKey,
|
||||
Sig: sig,
|
||||
}
|
||||
p := Marshal(&msg)
|
||||
if err := c.writePacket(p); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
var success bool
|
||||
success, methods, err = handleAuthResponse(c)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// 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()
|
||||
msg := publickeyAuthMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: "publickey",
|
||||
HasSig: false,
|
||||
Algoname: key.Type(),
|
||||
PubKey: pubKey,
|
||||
}
|
||||
if err := c.writePacket(Marshal(&msg)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return confirmKeyAck(key, c)
|
||||
}
|
||||
|
||||
func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
|
||||
pubKey := key.Marshal()
|
||||
algoname := key.Type()
|
||||
|
||||
for {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch packet[0] {
|
||||
case msgUserAuthBanner:
|
||||
// TODO(gpaul): add callback to present the banner to the user
|
||||
case msgUserAuthPubKeyOk:
|
||||
var msg userAuthPubKeyOkMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
case msgUserAuthFailure:
|
||||
return false, nil
|
||||
default:
|
||||
return false, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PublicKeys returns an AuthMethod that uses the given key
|
||||
// pairs.
|
||||
func PublicKeys(signers ...Signer) AuthMethod {
|
||||
return publicKeyCallback(func() ([]Signer, error) { return signers, nil })
|
||||
}
|
||||
|
||||
// PublicKeysCallback returns an AuthMethod that runs the given
|
||||
// function to obtain a list of key pairs.
|
||||
func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod {
|
||||
return publicKeyCallback(getSigners)
|
||||
}
|
||||
|
||||
// handleAuthResponse returns whether the preceding authentication request succeeded
|
||||
// along with a list of remaining authentication methods to try next and
|
||||
// an error if an unexpected response was received.
|
||||
func handleAuthResponse(c packetConn) (bool, []string, error) {
|
||||
for {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
switch packet[0] {
|
||||
case msgUserAuthBanner:
|
||||
// TODO: add callback to present the banner to the user
|
||||
case msgUserAuthFailure:
|
||||
var msg userAuthFailureMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return false, msg.Methods, nil
|
||||
case msgUserAuthSuccess:
|
||||
return true, nil, nil
|
||||
default:
|
||||
return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// successful authentication, the server may send a challenge with no
|
||||
// questions, for which the user and instruction messages should be
|
||||
// printed. RFC 4256 section 3.3 details how the UI should behave for
|
||||
// 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
|
||||
// sequence controlled by the server.
|
||||
func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod {
|
||||
return challenge
|
||||
}
|
||||
|
||||
func (cb KeyboardInteractiveChallenge) method() string {
|
||||
return "keyboard-interactive"
|
||||
}
|
||||
|
||||
func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
type initiateMsg struct {
|
||||
User string `sshtype:"50"`
|
||||
Service string
|
||||
Method string
|
||||
Language string
|
||||
Submethods string
|
||||
}
|
||||
|
||||
if err := c.writePacket(Marshal(&initiateMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: "keyboard-interactive",
|
||||
})); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// like handleAuthResponse, but with less options.
|
||||
switch packet[0] {
|
||||
case msgUserAuthBanner:
|
||||
// TODO: Print banners during userauth.
|
||||
continue
|
||||
case msgUserAuthInfoRequest:
|
||||
// OK
|
||||
case msgUserAuthFailure:
|
||||
var msg userAuthFailureMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return false, msg.Methods, nil
|
||||
case msgUserAuthSuccess:
|
||||
return true, nil, nil
|
||||
default:
|
||||
return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
|
||||
}
|
||||
|
||||
var msg userAuthInfoRequestMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// Manually unpack the prompt/echo pairs.
|
||||
rest := msg.Prompts
|
||||
var prompts []string
|
||||
var echos []bool
|
||||
for i := 0; i < int(msg.NumPrompts); i++ {
|
||||
prompt, r, ok := parseString(rest)
|
||||
if !ok || len(r) == 0 {
|
||||
return false, nil, errors.New("ssh: prompt format error")
|
||||
}
|
||||
prompts = append(prompts, string(prompt))
|
||||
echos = append(echos, r[0] != 0)
|
||||
rest = r[1:]
|
||||
}
|
||||
|
||||
if len(rest) != 0 {
|
||||
return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
|
||||
}
|
||||
|
||||
answers, err := cb(msg.User, msg.Instruction, prompts, echos)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if len(answers) != len(prompts) {
|
||||
return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
|
||||
}
|
||||
responseLength := 1 + 4
|
||||
for _, a := range answers {
|
||||
responseLength += stringLength(len(a))
|
||||
}
|
||||
serialized := make([]byte, responseLength)
|
||||
p := serialized
|
||||
p[0] = msgUserAuthInfoResponse
|
||||
p = p[1:]
|
||||
p = marshalUint32(p, uint32(len(answers)))
|
||||
for _, a := range answers {
|
||||
p = marshalString(p, []byte(a))
|
||||
}
|
||||
|
||||
if err := c.writePacket(serialized); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type retryableAuthMethod struct {
|
||||
authMethod AuthMethod
|
||||
maxTries int
|
||||
}
|
||||
|
||||
func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok bool, methods []string, err error) {
|
||||
for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
|
||||
ok, methods, err = r.authMethod.auth(session, user, c, rand)
|
||||
if ok || err != nil { // either success or error terminate
|
||||
return ok, methods, err
|
||||
}
|
||||
}
|
||||
return ok, methods, err
|
||||
}
|
||||
|
||||
func (r *retryableAuthMethod) method() string {
|
||||
return r.authMethod.method()
|
||||
}
|
||||
|
||||
// RetryableAuthMethod is a decorator for other auth methods enabling them to
|
||||
// be retried up to maxTries before considering that AuthMethod itself failed.
|
||||
// If maxTries is <= 0, will retry indefinitely
|
||||
//
|
||||
// This is useful for interactive clients using challenge/response type
|
||||
// authentication (e.g. Keyboard-Interactive, Password, etc) where the user
|
||||
// could mistype their response resulting in the server issuing a
|
||||
// SSH_MSG_USERAUTH_FAILURE (rfc4252 #8 [password] and rfc4256 #3.4
|
||||
// [keyboard-interactive]); Without this decorator, the non-retryable
|
||||
// AuthMethod would be removed from future consideration, and never tried again
|
||||
// (and so the user would never be able to retry their entry).
|
||||
func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod {
|
||||
return &retryableAuthMethod{authMethod: auth, maxTries: maxTries}
|
||||
}
|
||||
|
|
@ -1,628 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type keyboardInteractive map[string]string
|
||||
|
||||
func (cr keyboardInteractive) Challenge(user string, instruction string, questions []string, echos []bool) ([]string, error) {
|
||||
var answers []string
|
||||
for _, q := range questions {
|
||||
answers = append(answers, cr[q])
|
||||
}
|
||||
return answers, nil
|
||||
}
|
||||
|
||||
// reused internally by tests
|
||||
var clientPassword = "tiger"
|
||||
|
||||
// tryAuth runs a handshake with a given config against an SSH server
|
||||
// with config serverConfig
|
||||
func tryAuth(t *testing.T, config *ClientConfig) error {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
certChecker := CertChecker{
|
||||
IsUserAuthority: func(k PublicKey) bool {
|
||||
return bytes.Equal(k.Marshal(), testPublicKeys["ecdsa"].Marshal())
|
||||
},
|
||||
UserKeyFallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
if conn.User() == "testuser" && bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User())
|
||||
},
|
||||
IsRevoked: func(c *Certificate) bool {
|
||||
return c.Serial == 666
|
||||
},
|
||||
}
|
||||
|
||||
serverConfig := &ServerConfig{
|
||||
PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) {
|
||||
if conn.User() == "testuser" && string(pass) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("password auth failed")
|
||||
},
|
||||
PublicKeyCallback: certChecker.Authenticate,
|
||||
KeyboardInteractiveCallback: func(conn ConnMetadata, challenge KeyboardInteractiveChallenge) (*Permissions, error) {
|
||||
ans, err := challenge("user",
|
||||
"instruction",
|
||||
[]string{"question1", "question2"},
|
||||
[]bool{true, true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ok := conn.User() == "testuser" && ans[0] == "answer1" && ans[1] == "answer2"
|
||||
if ok {
|
||||
challenge("user", "motd", nil, nil)
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("keyboard-interactive failed")
|
||||
},
|
||||
}
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
|
||||
go newServer(c1, serverConfig)
|
||||
_, _, _, err = NewClientConn(c2, "", config)
|
||||
return err
|
||||
}
|
||||
|
||||
func TestClientAuthPublicKey(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodPassword(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
Password(clientPassword),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodFallback(t *testing.T) {
|
||||
var passwordCalled bool
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
PasswordCallback(
|
||||
func() (string, error) {
|
||||
passwordCalled = true
|
||||
return "WRONG", nil
|
||||
}),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
|
||||
if passwordCalled {
|
||||
t.Errorf("password auth tried before public-key auth.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodWrongPassword(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
Password("wrong"),
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodKeyboardInteractive(t *testing.T) {
|
||||
answers := keyboardInteractive(map[string]string{
|
||||
"question1": "answer1",
|
||||
"question2": "answer2",
|
||||
})
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
KeyboardInteractive(answers.Challenge),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodWrongKeyboardInteractive(t *testing.T) {
|
||||
answers := keyboardInteractive(map[string]string{
|
||||
"question1": "answer1",
|
||||
"question2": "WRONG",
|
||||
})
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
KeyboardInteractive(answers.Challenge),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err == nil {
|
||||
t.Fatalf("wrong answers should not have authenticated with KeyboardInteractive")
|
||||
}
|
||||
}
|
||||
|
||||
// the mock server will only authenticate ssh-rsa keys
|
||||
func TestAuthMethodInvalidPublicKey(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["dsa"]),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err == nil {
|
||||
t.Fatalf("dsa private key should not have authenticated with rsa public key")
|
||||
}
|
||||
}
|
||||
|
||||
// the client should authenticate with the second key
|
||||
func TestAuthMethodRSAandDSA(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["dsa"], testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("client could not authenticate with rsa key: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientHMAC(t *testing.T) {
|
||||
for _, mac := range supportedMACs {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
Config: Config{
|
||||
MACs: []string{mac},
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("client could not authenticate with mac algo %s: %v", mac, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// issue 4285.
|
||||
func TestClientUnsupportedCipher(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(),
|
||||
},
|
||||
Config: Config{
|
||||
Ciphers: []string{"aes128-cbc"}, // not currently supported
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, config); err == nil {
|
||||
t.Errorf("expected no ciphers in common")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientUnsupportedKex(t *testing.T) {
|
||||
if os.Getenv("GO_BUILDER_NAME") != "" {
|
||||
t.Skip("skipping known-flaky test on the Go build dashboard; see golang.org/issue/15198")
|
||||
}
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(),
|
||||
},
|
||||
Config: Config{
|
||||
KeyExchanges: []string{"diffie-hellman-group-exchange-sha256"}, // not currently supported
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if err := tryAuth(t, config); err == nil || !strings.Contains(err.Error(), "common algorithm") {
|
||||
t.Errorf("got %v, expected 'common algorithm'", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientLoginCert(t *testing.T) {
|
||||
cert := &Certificate{
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
CertType: UserCert,
|
||||
}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
certSigner, err := NewCertSigner(cert, testSigners["rsa"])
|
||||
if err != nil {
|
||||
t.Fatalf("NewCertSigner: %v", err)
|
||||
}
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
User: "user",
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
clientConfig.Auth = append(clientConfig.Auth, PublicKeys(certSigner))
|
||||
|
||||
// should succeed
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Errorf("cert login failed: %v", err)
|
||||
}
|
||||
|
||||
// corrupted signature
|
||||
cert.Signature.Blob[0]++
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with corrupted sig")
|
||||
}
|
||||
|
||||
// revoked
|
||||
cert.Serial = 666
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("revoked cert login succeeded")
|
||||
}
|
||||
cert.Serial = 1
|
||||
|
||||
// sign with wrong key
|
||||
cert.SignCert(rand.Reader, testSigners["dsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with non-authoritative key")
|
||||
}
|
||||
|
||||
// host cert
|
||||
cert.CertType = HostCert
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with wrong type")
|
||||
}
|
||||
cert.CertType = UserCert
|
||||
|
||||
// principal specified
|
||||
cert.ValidPrincipals = []string{"user"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Errorf("cert login failed: %v", err)
|
||||
}
|
||||
|
||||
// wrong principal specified
|
||||
cert.ValidPrincipals = []string{"fred"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with wrong principal")
|
||||
}
|
||||
cert.ValidPrincipals = nil
|
||||
|
||||
// added critical option
|
||||
cert.CriticalOptions = map[string]string{"root-access": "yes"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with unrecognized critical option")
|
||||
}
|
||||
|
||||
// allowed source address
|
||||
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42/24,::42/120"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Errorf("cert login with source-address failed: %v", err)
|
||||
}
|
||||
|
||||
// disallowed source address
|
||||
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42,::42"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login with source-address succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func testPermissionsPassing(withPermissions bool, t *testing.T) {
|
||||
serverConfig := &ServerConfig{
|
||||
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
if conn.User() == "nopermissions" {
|
||||
return nil, nil
|
||||
}
|
||||
return &Permissions{}, nil
|
||||
},
|
||||
}
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if withPermissions {
|
||||
clientConfig.User = "permissions"
|
||||
} else {
|
||||
clientConfig.User = "nopermissions"
|
||||
}
|
||||
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
go NewClientConn(c2, "", clientConfig)
|
||||
serverConn, err := newServer(c1, serverConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if p := serverConn.Permissions; (p != nil) != withPermissions {
|
||||
t.Fatalf("withPermissions is %t, but Permissions object is %#v", withPermissions, p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermissionsPassing(t *testing.T) {
|
||||
testPermissionsPassing(true, t)
|
||||
}
|
||||
|
||||
func TestNoPermissionsPassing(t *testing.T) {
|
||||
testPermissionsPassing(false, t)
|
||||
}
|
||||
|
||||
func TestRetryableAuth(t *testing.T) {
|
||||
n := 0
|
||||
passwords := []string{"WRONG1", "WRONG2"}
|
||||
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
RetryableAuthMethod(PasswordCallback(func() (string, error) {
|
||||
p := passwords[n]
|
||||
n++
|
||||
return p, nil
|
||||
}), 2),
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
if n != 2 {
|
||||
t.Fatalf("Did not try all passwords")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleRetryableAuthMethod(t *testing.T) {
|
||||
user := "testuser"
|
||||
NumberOfPrompts := 3
|
||||
|
||||
// Normally this would be a callback that prompts the user to answer the
|
||||
// provided questions
|
||||
Cb := func(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
|
||||
return []string{"answer1", "answer2"}, nil
|
||||
}
|
||||
|
||||
config := &ClientConfig{
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
User: user,
|
||||
Auth: []AuthMethod{
|
||||
RetryableAuthMethod(KeyboardInteractiveChallenge(Cb), NumberOfPrompts),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test if username is received on server side when NoClientAuth is used
|
||||
func TestClientAuthNone(t *testing.T) {
|
||||
user := "testuser"
|
||||
serverConfig := &ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
User: user,
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
go NewClientConn(c2, "", clientConfig)
|
||||
serverConn, err := newServer(c1, serverConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("newServer: %v", err)
|
||||
}
|
||||
if serverConn.User() != user {
|
||||
t.Fatalf("server: got %q, want %q", serverConn.User(), user)
|
||||
}
|
||||
}
|
||||
|
||||
// Test if authentication attempts are limited on server when MaxAuthTries is set
|
||||
func TestClientAuthMaxAuthTries(t *testing.T) {
|
||||
user := "testuser"
|
||||
|
||||
serverConfig := &ServerConfig{
|
||||
MaxAuthTries: 2,
|
||||
PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) {
|
||||
if conn.User() == "testuser" && string(pass) == "right" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("password auth failed")
|
||||
},
|
||||
}
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
|
||||
expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{
|
||||
Reason: 2,
|
||||
Message: "too many authentication failures",
|
||||
})
|
||||
|
||||
for tries := 2; tries < 4; tries++ {
|
||||
n := tries
|
||||
clientConfig := &ClientConfig{
|
||||
User: user,
|
||||
Auth: []AuthMethod{
|
||||
RetryableAuthMethod(PasswordCallback(func() (string, error) {
|
||||
n--
|
||||
if n == 0 {
|
||||
return "right", nil
|
||||
}
|
||||
return "wrong", nil
|
||||
}), tries),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
go newServer(c1, serverConfig)
|
||||
_, _, _, err = NewClientConn(c2, "", clientConfig)
|
||||
if tries > 2 {
|
||||
if err == nil {
|
||||
t.Fatalf("client: got no error, want %s", expectedErr)
|
||||
} else if err.Error() != expectedErr.Error() {
|
||||
t.Fatalf("client: got %s, want %s", err, expectedErr)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("client: got %s, want no error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test if authentication attempts are correctly limited on server
|
||||
// when more public keys are provided then MaxAuthTries
|
||||
func TestClientAuthMaxAuthTriesPublicKey(t *testing.T) {
|
||||
signers := []Signer{}
|
||||
for i := 0; i < 6; i++ {
|
||||
signers = append(signers, testSigners["dsa"])
|
||||
}
|
||||
|
||||
validConfig := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(append([]Signer{testSigners["rsa"]}, signers...)...),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if err := tryAuth(t, validConfig); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
|
||||
expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{
|
||||
Reason: 2,
|
||||
Message: "too many authentication failures",
|
||||
})
|
||||
invalidConfig := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(append(signers, testSigners["rsa"])...),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if err := tryAuth(t, invalidConfig); err == nil {
|
||||
t.Fatalf("client: got no error, want %s", expectedErr)
|
||||
} else if err.Error() != expectedErr.Error() {
|
||||
t.Fatalf("client: got %s, want %s", err, expectedErr)
|
||||
}
|
||||
}
|
||||
|
||||
// Test whether authentication errors are being properly logged if all
|
||||
// authentication methods have been exhausted
|
||||
func TestClientAuthErrorList(t *testing.T) {
|
||||
publicKeyErr := errors.New("This is an error from PublicKeyCallback")
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
serverConfig := &ServerConfig{
|
||||
PublicKeyCallback: func(_ ConnMetadata, _ PublicKey) (*Permissions, error) {
|
||||
return nil, publicKeyErr
|
||||
},
|
||||
}
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
go NewClientConn(c2, "", clientConfig)
|
||||
_, err = newServer(c1, serverConfig)
|
||||
if err == nil {
|
||||
t.Fatal("newServer: got nil, expected errors")
|
||||
}
|
||||
|
||||
authErrs, ok := err.(*ServerAuthError)
|
||||
if !ok {
|
||||
t.Fatalf("errors: got %T, want *ssh.ServerAuthError", err)
|
||||
}
|
||||
for i, e := range authErrs.Errors {
|
||||
switch i {
|
||||
case 0:
|
||||
if e.Error() != "no auth passed yet" {
|
||||
t.Fatalf("errors: got %v, want no auth passed yet", e.Error())
|
||||
}
|
||||
case 1:
|
||||
if e != publicKeyErr {
|
||||
t.Fatalf("errors: got %v, want %v", e, publicKeyErr)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("errors: got %v, expected 2 errors", authErrs.Errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testClientVersion(t *testing.T, config *ClientConfig, expected string) {
|
||||
clientConn, serverConn := net.Pipe()
|
||||
defer clientConn.Close()
|
||||
receivedVersion := make(chan string, 1)
|
||||
config.HostKeyCallback = InsecureIgnoreHostKey()
|
||||
go func() {
|
||||
version, err := readVersion(serverConn)
|
||||
if err != nil {
|
||||
receivedVersion <- ""
|
||||
} else {
|
||||
receivedVersion <- string(version)
|
||||
}
|
||||
serverConn.Close()
|
||||
}()
|
||||
NewClientConn(clientConn, "", config)
|
||||
actual := <-receivedVersion
|
||||
if actual != expected {
|
||||
t.Fatalf("got %s; want %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomClientVersion(t *testing.T) {
|
||||
version := "Test-Client-Version-0.0"
|
||||
testClientVersion(t, &ClientConfig{ClientVersion: version}, version)
|
||||
}
|
||||
|
||||
func TestDefaultClientVersion(t *testing.T) {
|
||||
testClientVersion(t, &ClientConfig{}, packageVersion)
|
||||
}
|
||||
|
||||
func TestHostKeyCheck(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
wantError string
|
||||
key PublicKey
|
||||
}{
|
||||
{"no callback", "must specify HostKeyCallback", nil},
|
||||
{"correct key", "", testSigners["rsa"].PublicKey()},
|
||||
{"mismatch", "mismatch", testSigners["ecdsa"].PublicKey()},
|
||||
} {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
serverConf := &ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
|
||||
go NewServerConn(c1, serverConf)
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
}
|
||||
if tt.key != nil {
|
||||
clientConf.HostKeyCallback = FixedHostKey(tt.key)
|
||||
}
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||
if err != nil {
|
||||
if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
|
||||
t.Errorf("%s: got error %q, missing %q", tt.name, err.Error(), tt.wantError)
|
||||
}
|
||||
} else if tt.wantError != "" {
|
||||
t.Errorf("%s: succeeded, but want error string %q", tt.name, tt.wantError)
|
||||
}
|
||||
}
|
||||
}
|
||||
373
ssh/common.go
373
ssh/common.go
|
|
@ -1,373 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
_ "crypto/sha1"
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha512"
|
||||
)
|
||||
|
||||
// These are string constants in the SSH protocol.
|
||||
const (
|
||||
compressionNone = "none"
|
||||
serviceUserAuth = "ssh-userauth"
|
||||
serviceSSH = "ssh-connection"
|
||||
)
|
||||
|
||||
// supportedCiphers specifies the supported ciphers in preference order.
|
||||
var supportedCiphers = []string{
|
||||
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
||||
"aes128-gcm@openssh.com",
|
||||
"arcfour256", "arcfour128",
|
||||
}
|
||||
|
||||
// supportedKexAlgos specifies the supported key-exchange algorithms in
|
||||
// preference order.
|
||||
var supportedKexAlgos = []string{
|
||||
kexAlgoCurve25519SHA256,
|
||||
// P384 and P521 are not constant-time yet, but since we don't
|
||||
// reuse ephemeral keys, using them for ECDH should be OK.
|
||||
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
||||
kexAlgoDH14SHA1, kexAlgoDH1SHA1,
|
||||
}
|
||||
|
||||
// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods
|
||||
// of authenticating servers) in preference order.
|
||||
var supportedHostKeyAlgos = []string{
|
||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
|
||||
CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01,
|
||||
|
||||
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
||||
KeyAlgoRSA, KeyAlgoDSA,
|
||||
|
||||
KeyAlgoED25519,
|
||||
}
|
||||
|
||||
// supportedMACs specifies a default set of MAC algorithms in preference order.
|
||||
// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed
|
||||
// because they have reached the end of their useful life.
|
||||
var supportedMACs = []string{
|
||||
"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96",
|
||||
}
|
||||
|
||||
var supportedCompressions = []string{compressionNone}
|
||||
|
||||
// hashFuncs keeps the mapping of supported algorithms to their respective
|
||||
// hashes needed for signature verification.
|
||||
var hashFuncs = map[string]crypto.Hash{
|
||||
KeyAlgoRSA: crypto.SHA1,
|
||||
KeyAlgoDSA: crypto.SHA1,
|
||||
KeyAlgoECDSA256: crypto.SHA256,
|
||||
KeyAlgoECDSA384: crypto.SHA384,
|
||||
KeyAlgoECDSA521: crypto.SHA512,
|
||||
CertAlgoRSAv01: crypto.SHA1,
|
||||
CertAlgoDSAv01: crypto.SHA1,
|
||||
CertAlgoECDSA256v01: crypto.SHA256,
|
||||
CertAlgoECDSA384v01: crypto.SHA384,
|
||||
CertAlgoECDSA521v01: crypto.SHA512,
|
||||
}
|
||||
|
||||
// unexpectedMessageError results when the SSH message that we received didn't
|
||||
// match what we wanted.
|
||||
func unexpectedMessageError(expected, got uint8) error {
|
||||
return fmt.Errorf("ssh: unexpected message type %d (expected %d)", got, expected)
|
||||
}
|
||||
|
||||
// parseError results from a malformed SSH message.
|
||||
func parseError(tag uint8) error {
|
||||
return fmt.Errorf("ssh: parse error in message type %d", tag)
|
||||
}
|
||||
|
||||
func findCommon(what string, client []string, server []string) (common string, err error) {
|
||||
for _, c := range client {
|
||||
for _, s := range server {
|
||||
if c == s {
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("ssh: no common algorithm for %s; client offered: %v, server offered: %v", what, client, server)
|
||||
}
|
||||
|
||||
type directionAlgorithms struct {
|
||||
Cipher string
|
||||
MAC string
|
||||
Compression string
|
||||
}
|
||||
|
||||
// rekeyBytes returns a rekeying intervals in bytes.
|
||||
func (a *directionAlgorithms) rekeyBytes() int64 {
|
||||
// According to RFC4344 block ciphers should rekey after
|
||||
// 2^(BLOCKSIZE/4) blocks. For all AES flavors BLOCKSIZE is
|
||||
// 128.
|
||||
switch a.Cipher {
|
||||
case "aes128-ctr", "aes192-ctr", "aes256-ctr", gcmCipherID, aes128cbcID:
|
||||
return 16 * (1 << 32)
|
||||
|
||||
}
|
||||
|
||||
// For others, stick with RFC4253 recommendation to rekey after 1 Gb of data.
|
||||
return 1 << 30
|
||||
}
|
||||
|
||||
type algorithms struct {
|
||||
kex string
|
||||
hostKey string
|
||||
w directionAlgorithms
|
||||
r directionAlgorithms
|
||||
}
|
||||
|
||||
func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms, err error) {
|
||||
result := &algorithms{}
|
||||
|
||||
result.kex, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.hostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.w.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.r.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.w.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.r.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.w.Compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.r.Compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// If rekeythreshold is too small, we can't make any progress sending
|
||||
// stuff.
|
||||
const minRekeyThreshold uint64 = 256
|
||||
|
||||
// Config contains configuration data common to both ServerConfig and
|
||||
// ClientConfig.
|
||||
type Config struct {
|
||||
// Rand provides the source of entropy for cryptographic
|
||||
// primitives. If Rand is nil, the cryptographic random reader
|
||||
// in package crypto/rand will be used.
|
||||
Rand io.Reader
|
||||
|
||||
// The maximum number of bytes sent or received after which a
|
||||
// new key is negotiated. It must be at least 256. If
|
||||
// unspecified, a size suitable for the chosen cipher is used.
|
||||
RekeyThreshold uint64
|
||||
|
||||
// The allowed key exchanges algorithms. If unspecified then a
|
||||
// default set of algorithms is used.
|
||||
KeyExchanges []string
|
||||
|
||||
// The allowed cipher algorithms. If unspecified then a sensible
|
||||
// default is used.
|
||||
Ciphers []string
|
||||
|
||||
// The allowed MAC algorithms. If unspecified then a sensible default
|
||||
// is used.
|
||||
MACs []string
|
||||
}
|
||||
|
||||
// SetDefaults sets sensible values for unset fields in config. This is
|
||||
// exported for testing: Configs passed to SSH functions are copied and have
|
||||
// default values set automatically.
|
||||
func (c *Config) SetDefaults() {
|
||||
if c.Rand == nil {
|
||||
c.Rand = rand.Reader
|
||||
}
|
||||
if c.Ciphers == nil {
|
||||
c.Ciphers = supportedCiphers
|
||||
}
|
||||
var ciphers []string
|
||||
for _, c := range c.Ciphers {
|
||||
if cipherModes[c] != nil {
|
||||
// reject the cipher if we have no cipherModes definition
|
||||
ciphers = append(ciphers, c)
|
||||
}
|
||||
}
|
||||
c.Ciphers = ciphers
|
||||
|
||||
if c.KeyExchanges == nil {
|
||||
c.KeyExchanges = supportedKexAlgos
|
||||
}
|
||||
|
||||
if c.MACs == nil {
|
||||
c.MACs = supportedMACs
|
||||
}
|
||||
|
||||
if c.RekeyThreshold == 0 {
|
||||
// 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 {
|
||||
data := struct {
|
||||
Session []byte
|
||||
Type byte
|
||||
User string
|
||||
Service string
|
||||
Method string
|
||||
Sign bool
|
||||
Algo []byte
|
||||
PubKey []byte
|
||||
}{
|
||||
sessionId,
|
||||
msgUserAuthRequest,
|
||||
req.User,
|
||||
req.Service,
|
||||
req.Method,
|
||||
true,
|
||||
algo,
|
||||
pubKey,
|
||||
}
|
||||
return Marshal(data)
|
||||
}
|
||||
|
||||
func appendU16(buf []byte, n uint16) []byte {
|
||||
return append(buf, byte(n>>8), byte(n))
|
||||
}
|
||||
|
||||
func appendU32(buf []byte, n uint32) []byte {
|
||||
return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
|
||||
}
|
||||
|
||||
func appendU64(buf []byte, n uint64) []byte {
|
||||
return append(buf,
|
||||
byte(n>>56), byte(n>>48), byte(n>>40), byte(n>>32),
|
||||
byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
|
||||
}
|
||||
|
||||
func appendInt(buf []byte, n int) []byte {
|
||||
return appendU32(buf, uint32(n))
|
||||
}
|
||||
|
||||
func appendString(buf []byte, s string) []byte {
|
||||
buf = appendU32(buf, uint32(len(s)))
|
||||
buf = append(buf, s...)
|
||||
return buf
|
||||
}
|
||||
|
||||
func appendBool(buf []byte, b bool) []byte {
|
||||
if b {
|
||||
return append(buf, 1)
|
||||
}
|
||||
return append(buf, 0)
|
||||
}
|
||||
|
||||
// newCond is a helper to hide the fact that there is no usable zero
|
||||
// value for sync.Cond.
|
||||
func newCond() *sync.Cond { return sync.NewCond(new(sync.Mutex)) }
|
||||
|
||||
// window represents the buffer available to clients
|
||||
// wishing to write to a channel.
|
||||
type window struct {
|
||||
*sync.Cond
|
||||
win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1
|
||||
writeWaiters int
|
||||
closed bool
|
||||
}
|
||||
|
||||
// add adds win to the amount of window available
|
||||
// for consumers.
|
||||
func (w *window) add(win uint32) bool {
|
||||
// a zero sized window adjust is a noop.
|
||||
if win == 0 {
|
||||
return true
|
||||
}
|
||||
w.L.Lock()
|
||||
if w.win+win < win {
|
||||
w.L.Unlock()
|
||||
return false
|
||||
}
|
||||
w.win += win
|
||||
// It is unusual that multiple goroutines would be attempting to reserve
|
||||
// window space, but not guaranteed. Use broadcast to notify all waiters
|
||||
// that additional window is available.
|
||||
w.Broadcast()
|
||||
w.L.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
// close sets the window to closed, so all reservations fail
|
||||
// immediately.
|
||||
func (w *window) close() {
|
||||
w.L.Lock()
|
||||
w.closed = true
|
||||
w.Broadcast()
|
||||
w.L.Unlock()
|
||||
}
|
||||
|
||||
// reserve reserves win from the available window capacity.
|
||||
// If no capacity remains, reserve will block. reserve may
|
||||
// return less than requested.
|
||||
func (w *window) reserve(win uint32) (uint32, error) {
|
||||
var err error
|
||||
w.L.Lock()
|
||||
w.writeWaiters++
|
||||
w.Broadcast()
|
||||
for w.win == 0 && !w.closed {
|
||||
w.Wait()
|
||||
}
|
||||
w.writeWaiters--
|
||||
if w.win < win {
|
||||
win = w.win
|
||||
}
|
||||
w.win -= win
|
||||
if w.closed {
|
||||
err = io.EOF
|
||||
}
|
||||
w.L.Unlock()
|
||||
return win, err
|
||||
}
|
||||
|
||||
// waitWriterBlocked waits until some goroutine is blocked for further
|
||||
// writes. It is used in tests only.
|
||||
func (w *window) waitWriterBlocked() {
|
||||
w.Cond.L.Lock()
|
||||
for w.writeWaiters == 0 {
|
||||
w.Cond.Wait()
|
||||
}
|
||||
w.Cond.L.Unlock()
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// OpenChannelError is returned if the other side rejects an
|
||||
// OpenChannel request.
|
||||
type OpenChannelError struct {
|
||||
Reason RejectionReason
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *OpenChannelError) Error() string {
|
||||
return fmt.Sprintf("ssh: rejected: %s (%s)", e.Reason, e.Message)
|
||||
}
|
||||
|
||||
// ConnMetadata holds metadata for the connection.
|
||||
type ConnMetadata interface {
|
||||
// User returns the user ID for this connection.
|
||||
User() string
|
||||
|
||||
// SessionID returns the session hash, also denoted by H.
|
||||
SessionID() []byte
|
||||
|
||||
// ClientVersion returns the client's version string as hashed
|
||||
// into the session ID.
|
||||
ClientVersion() []byte
|
||||
|
||||
// ServerVersion returns the server's version string as hashed
|
||||
// into the session ID.
|
||||
ServerVersion() []byte
|
||||
|
||||
// RemoteAddr returns the remote address for this connection.
|
||||
RemoteAddr() net.Addr
|
||||
|
||||
// LocalAddr returns the local address for this connection.
|
||||
LocalAddr() net.Addr
|
||||
}
|
||||
|
||||
// Conn represents an SSH connection for both server and client roles.
|
||||
// Conn is the basis for implementing an application layer, such
|
||||
// as ClientConn, which implements the traditional shell access for
|
||||
// clients.
|
||||
type Conn interface {
|
||||
ConnMetadata
|
||||
|
||||
// SendRequest sends a global request, and returns the
|
||||
// reply. If wantReply is true, it returns the response status
|
||||
// and payload. See also RFC4254, section 4.
|
||||
SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error)
|
||||
|
||||
// OpenChannel tries to open an channel. If the request is
|
||||
// rejected, it returns *OpenChannelError. On success it returns
|
||||
// the SSH Channel and a Go channel for incoming, out-of-band
|
||||
// requests. The Go channel must be serviced, or the
|
||||
// connection will hang.
|
||||
OpenChannel(name string, data []byte) (Channel, <-chan *Request, error)
|
||||
|
||||
// Close closes the underlying network connection
|
||||
Close() error
|
||||
|
||||
// Wait blocks until the connection has shut down, and returns the
|
||||
// error causing the shutdown.
|
||||
Wait() error
|
||||
|
||||
// TODO(hanwen): consider exposing:
|
||||
// RequestKeyChange
|
||||
// Disconnect
|
||||
}
|
||||
|
||||
// DiscardRequests consumes and rejects all requests from the
|
||||
// passed-in channel.
|
||||
func DiscardRequests(in <-chan *Request) {
|
||||
for req := range in {
|
||||
if req.WantReply {
|
||||
req.Reply(false, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A connection represents an incoming connection.
|
||||
type connection struct {
|
||||
transport *handshakeTransport
|
||||
sshConn
|
||||
|
||||
// The connection protocol.
|
||||
*mux
|
||||
}
|
||||
|
||||
func (c *connection) Close() error {
|
||||
return c.sshConn.conn.Close()
|
||||
}
|
||||
|
||||
// sshconn provides net.Conn metadata, but disallows direct reads and
|
||||
// writes.
|
||||
type sshConn struct {
|
||||
conn net.Conn
|
||||
|
||||
user string
|
||||
sessionID []byte
|
||||
clientVersion []byte
|
||||
serverVersion []byte
|
||||
}
|
||||
|
||||
func dup(src []byte) []byte {
|
||||
dst := make([]byte, len(src))
|
||||
copy(dst, src)
|
||||
return dst
|
||||
}
|
||||
|
||||
func (c *sshConn) User() string {
|
||||
return c.user
|
||||
}
|
||||
|
||||
func (c *sshConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *sshConn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *sshConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *sshConn) SessionID() []byte {
|
||||
return dup(c.sessionID)
|
||||
}
|
||||
|
||||
func (c *sshConn) ClientVersion() []byte {
|
||||
return dup(c.clientVersion)
|
||||
}
|
||||
|
||||
func (c *sshConn) ServerVersion() []byte {
|
||||
return dup(c.serverVersion)
|
||||
}
|
||||
26
ssh/doc.go
26
ssh/doc.go
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package ssh implements an SSH client and server.
|
||||
|
||||
SSHPiper provides additional APIs to ssh that allows you to build a piped ssh connection.
|
||||
Also, package ssh in sshpiper is compatible with golang.org/x/crypto/ssh.
|
||||
All func and datatype left unchanged. You can use it like golang.org/x/crypto/ssh.
|
||||
|
||||
|
||||
SSH is a transport security protocol, an authentication protocol and a
|
||||
family of application protocols. The most typical application level
|
||||
protocol is a remote shell and this is specifically implemented. However,
|
||||
the multiplexed nature of SSH is exposed to users that wish to support
|
||||
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
|
||||
|
|
@ -1,320 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func ExampleNewServerConn() {
|
||||
// Public key authentication is done by comparing
|
||||
// the public key of a received connection
|
||||
// with the entries in the authorized_keys file.
|
||||
authorizedKeysBytes, err := ioutil.ReadFile("authorized_keys")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load authorized_keys, err: %v", err)
|
||||
}
|
||||
|
||||
authorizedKeysMap := map[string]bool{}
|
||||
for len(authorizedKeysBytes) > 0 {
|
||||
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
authorizedKeysMap[string(pubKey.Marshal())] = true
|
||||
authorizedKeysBytes = rest
|
||||
}
|
||||
|
||||
// An SSH server is represented by a ServerConfig, which holds
|
||||
// certificate details and handles authentication of ServerConns.
|
||||
config := &ssh.ServerConfig{
|
||||
// Remove to disable password auth.
|
||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
// Should use constant-time compare (or better, salt+hash) in
|
||||
// a production setting.
|
||||
if c.User() == "testuser" && string(pass) == "tiger" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("password rejected for %q", c.User())
|
||||
},
|
||||
|
||||
// Remove to disable public key auth.
|
||||
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
if authorizedKeysMap[string(pubKey.Marshal())] {
|
||||
return &ssh.Permissions{
|
||||
// Record the public key used for authentication.
|
||||
Extensions: map[string]string{
|
||||
"pubkey-fp": ssh.FingerprintSHA256(pubKey),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown public key for %q", c.User())
|
||||
},
|
||||
}
|
||||
|
||||
privateBytes, err := ioutil.ReadFile("id_rsa")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load private key: ", err)
|
||||
}
|
||||
|
||||
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse private key: ", err)
|
||||
}
|
||||
|
||||
config.AddHostKey(private)
|
||||
|
||||
// Once a ServerConfig has been configured, connections can be
|
||||
// accepted.
|
||||
listener, err := net.Listen("tcp", "0.0.0.0:2022")
|
||||
if err != nil {
|
||||
log.Fatal("failed to listen for connection: ", err)
|
||||
}
|
||||
nConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("failed to accept incoming connection: ", err)
|
||||
}
|
||||
|
||||
// Before use, a handshake must be performed on the incoming
|
||||
// net.Conn.
|
||||
conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
|
||||
if err != nil {
|
||||
log.Fatal("failed to handshake: ", err)
|
||||
}
|
||||
log.Printf("logged in with key %s", conn.Permissions.Extensions["pubkey-fp"])
|
||||
|
||||
// The incoming Request channel must be serviced.
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
// Service the incoming Channel channel.
|
||||
for newChannel := range chans {
|
||||
// Channels have a type, depending on the application level
|
||||
// protocol intended. In the case of a shell, the type is
|
||||
// "session" and ServerShell may be used to present a simple
|
||||
// terminal interface.
|
||||
if newChannel.ChannelType() != "session" {
|
||||
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||
continue
|
||||
}
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
log.Fatalf("Could not accept channel: %v", err)
|
||||
}
|
||||
|
||||
// Sessions have out-of-band requests such as "shell",
|
||||
// "pty-req" and "env". Here we handle only the
|
||||
// "shell" request.
|
||||
go func(in <-chan *ssh.Request) {
|
||||
for req := range in {
|
||||
req.Reply(req.Type == "shell", nil)
|
||||
}
|
||||
}(requests)
|
||||
|
||||
term := terminal.NewTerminal(channel, "> ")
|
||||
|
||||
go func() {
|
||||
defer channel.Close()
|
||||
for {
|
||||
line, err := term.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
fmt.Println(line)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleHostKeyCheck() {
|
||||
// Every client must provide a host key check. Here is a
|
||||
// simple-minded parse of OpenSSH's known_hosts file
|
||||
host := "hostname"
|
||||
file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
var hostKey ssh.PublicKey
|
||||
for scanner.Scan() {
|
||||
fields := strings.Split(scanner.Text(), " ")
|
||||
if len(fields) != 3 {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(fields[0], host) {
|
||||
var err error
|
||||
hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
|
||||
if err != nil {
|
||||
log.Fatalf("error parsing %q: %v", fields[2], err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hostKey == nil {
|
||||
log.Fatalf("no hostkey for %s", host)
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: os.Getenv("USER"),
|
||||
HostKeyCallback: ssh.FixedHostKey(hostKey),
|
||||
}
|
||||
|
||||
_, err = ssh.Dial("tcp", host+":22", &config)
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
func ExampleDial() {
|
||||
var hostKey ssh.PublicKey
|
||||
// An SSH client is represented with a ClientConn.
|
||||
//
|
||||
// To authenticate with the remote server you must pass at least one
|
||||
// implementation of AuthMethod via the Auth field in ClientConfig,
|
||||
// and provide a HostKeyCallback.
|
||||
config := &ssh.ClientConfig{
|
||||
User: "username",
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password("yourpassword"),
|
||||
},
|
||||
HostKeyCallback: ssh.FixedHostKey(hostKey),
|
||||
}
|
||||
client, err := ssh.Dial("tcp", "yourserver.com:22", config)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to dial: ", err)
|
||||
}
|
||||
|
||||
// Each ClientConn can support multiple interactive sessions,
|
||||
// represented by a Session.
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to create session: ", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// Once a Session is created, you can execute a single command on
|
||||
// the remote side using the Run method.
|
||||
var b bytes.Buffer
|
||||
session.Stdout = &b
|
||||
if err := session.Run("/usr/bin/whoami"); err != nil {
|
||||
log.Fatal("Failed to run: " + err.Error())
|
||||
}
|
||||
fmt.Println(b.String())
|
||||
}
|
||||
|
||||
func ExamplePublicKeys() {
|
||||
var hostKey ssh.PublicKey
|
||||
// A public key may be used to authenticate against the remote
|
||||
// server by using an unencrypted PEM-encoded private key file.
|
||||
//
|
||||
// If you have an encrypted private key, the crypto/x509 package
|
||||
// can be used to decrypt it.
|
||||
key, err := ioutil.ReadFile("/home/user/.ssh/id_rsa")
|
||||
if err != nil {
|
||||
log.Fatalf("unable to read private key: %v", err)
|
||||
}
|
||||
|
||||
// Create the Signer for this private key.
|
||||
signer, err := ssh.ParsePrivateKey(key)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to parse private key: %v", err)
|
||||
}
|
||||
|
||||
config := &ssh.ClientConfig{
|
||||
User: "user",
|
||||
Auth: []ssh.AuthMethod{
|
||||
// Use the PublicKeys method for remote authentication.
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
HostKeyCallback: ssh.FixedHostKey(hostKey),
|
||||
}
|
||||
|
||||
// Connect to the remote server and perform the SSH handshake.
|
||||
client, err := ssh.Dial("tcp", "host.com:22", config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
}
|
||||
|
||||
func ExampleClient_Listen() {
|
||||
var hostKey ssh.PublicKey
|
||||
config := &ssh.ClientConfig{
|
||||
User: "username",
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password("password"),
|
||||
},
|
||||
HostKeyCallback: ssh.FixedHostKey(hostKey),
|
||||
}
|
||||
// Dial your ssh server.
|
||||
conn, err := ssh.Dial("tcp", "localhost:22", config)
|
||||
if err != nil {
|
||||
log.Fatal("unable to connect: ", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Request the remote side to open port 8080 on all interfaces.
|
||||
l, err := conn.Listen("tcp", "0.0.0.0:8080")
|
||||
if err != nil {
|
||||
log.Fatal("unable to register tcp forward: ", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
// Serve HTTP with your SSH server acting as a reverse proxy.
|
||||
http.Serve(l, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
fmt.Fprintf(resp, "Hello world!\n")
|
||||
}))
|
||||
}
|
||||
|
||||
func ExampleSession_RequestPty() {
|
||||
var hostKey ssh.PublicKey
|
||||
// Create client config
|
||||
config := &ssh.ClientConfig{
|
||||
User: "username",
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password("password"),
|
||||
},
|
||||
HostKeyCallback: ssh.FixedHostKey(hostKey),
|
||||
}
|
||||
// Connect to ssh server
|
||||
conn, err := ssh.Dial("tcp", "localhost:22", config)
|
||||
if err != nil {
|
||||
log.Fatal("unable to connect: ", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
// Create a session
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
log.Fatal("unable to create session: ", err)
|
||||
}
|
||||
defer session.Close()
|
||||
// Set up terminal modes
|
||||
modes := ssh.TerminalModes{
|
||||
ssh.ECHO: 0, // disable echoing
|
||||
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
|
||||
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
||||
}
|
||||
// Request pseudo terminal
|
||||
if err := session.RequestPty("xterm", 40, 80, modes); err != nil {
|
||||
log.Fatal("request for pseudo terminal failed: ", err)
|
||||
}
|
||||
// Start remote shell
|
||||
if err := session.Shell(); err != nil {
|
||||
log.Fatal("failed to start shell: ", err)
|
||||
}
|
||||
}
|
||||
640
ssh/handshake.go
640
ssh/handshake.go
|
|
@ -1,640 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// debugHandshake, if set, prints messages sent and received. Key
|
||||
// exchange messages are printed as if DH were used, so the debug
|
||||
// messages are wrong when using ECDH.
|
||||
const debugHandshake = false
|
||||
|
||||
// chanSize sets the amount of buffering SSH connections. This is
|
||||
// primarily for testing: setting chanSize=0 uncovers deadlocks more
|
||||
// quickly.
|
||||
const chanSize = 16
|
||||
|
||||
// keyingTransport is a packet based transport that supports key
|
||||
// changes. It need not be thread-safe. It should pass through
|
||||
// msgNewKeys in both directions.
|
||||
type keyingTransport interface {
|
||||
packetConn
|
||||
|
||||
// prepareKeyChange sets up a key change. The key change for a
|
||||
// direction will be effected if a msgNewKeys message is sent
|
||||
// or received.
|
||||
prepareKeyChange(*algorithms, *kexResult) error
|
||||
}
|
||||
|
||||
// handshakeTransport implements rekeying on top of a keyingTransport
|
||||
// and offers a thread-safe writePacket() interface.
|
||||
type handshakeTransport struct {
|
||||
conn keyingTransport
|
||||
config *Config
|
||||
|
||||
serverVersion []byte
|
||||
clientVersion []byte
|
||||
|
||||
// hostKeys is non-empty if we are the server. In that case,
|
||||
// it contains all host keys that can be used to sign the
|
||||
// connection.
|
||||
hostKeys []Signer
|
||||
|
||||
// hostKeyAlgorithms is non-empty if we are the client. In that case,
|
||||
// we accept these key types from the server as host key.
|
||||
hostKeyAlgorithms []string
|
||||
|
||||
// On read error, incoming is closed, and readError is set.
|
||||
incoming chan []byte
|
||||
readError error
|
||||
|
||||
mu sync.Mutex
|
||||
writeError error
|
||||
sentInitPacket []byte
|
||||
sentInitMsg *kexInitMsg
|
||||
pendingPackets [][]byte // Used when a key exchange is in progress.
|
||||
|
||||
// If the read loop wants to schedule a kex, it pings this
|
||||
// channel, and the write loop will send out a kex
|
||||
// message.
|
||||
requestKex chan struct{}
|
||||
|
||||
// If the other side requests or confirms a kex, its kexInit
|
||||
// packet is sent here for the write loop to find it.
|
||||
startKex chan *pendingKex
|
||||
|
||||
// data for host key checking
|
||||
hostKeyCallback HostKeyCallback
|
||||
dialAddress string
|
||||
remoteAddr net.Addr
|
||||
|
||||
// Algorithms agreed in the last key exchange.
|
||||
algorithms *algorithms
|
||||
|
||||
readPacketsLeft uint32
|
||||
readBytesLeft int64
|
||||
|
||||
writePacketsLeft uint32
|
||||
writeBytesLeft int64
|
||||
|
||||
// The session ID or nil if first kex did not complete yet.
|
||||
sessionID []byte
|
||||
}
|
||||
|
||||
type pendingKex struct {
|
||||
otherInit []byte
|
||||
done chan error
|
||||
}
|
||||
|
||||
func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, serverVersion []byte) *handshakeTransport {
|
||||
t := &handshakeTransport{
|
||||
conn: conn,
|
||||
serverVersion: serverVersion,
|
||||
clientVersion: clientVersion,
|
||||
incoming: make(chan []byte, chanSize),
|
||||
requestKex: make(chan struct{}, 1),
|
||||
startKex: make(chan *pendingKex, 1),
|
||||
|
||||
config: config,
|
||||
}
|
||||
t.resetReadThresholds()
|
||||
t.resetWriteThresholds()
|
||||
|
||||
// We always start with a mandatory key exchange.
|
||||
t.requestKex <- struct{}{}
|
||||
return t
|
||||
}
|
||||
|
||||
func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ClientConfig, dialAddr string, addr net.Addr) *handshakeTransport {
|
||||
t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion)
|
||||
t.dialAddress = dialAddr
|
||||
t.remoteAddr = addr
|
||||
t.hostKeyCallback = config.HostKeyCallback
|
||||
if config.HostKeyAlgorithms != nil {
|
||||
t.hostKeyAlgorithms = config.HostKeyAlgorithms
|
||||
} else {
|
||||
t.hostKeyAlgorithms = supportedHostKeyAlgos
|
||||
}
|
||||
go t.readLoop()
|
||||
go t.kexLoop()
|
||||
return t
|
||||
}
|
||||
|
||||
func newServerTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ServerConfig) *handshakeTransport {
|
||||
t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion)
|
||||
t.hostKeys = config.hostKeys
|
||||
go t.readLoop()
|
||||
go t.kexLoop()
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) getSessionID() []byte {
|
||||
return t.sessionID
|
||||
}
|
||||
|
||||
// waitSession waits for the session to be established. This should be
|
||||
// the first thing to call after instantiating handshakeTransport.
|
||||
func (t *handshakeTransport) waitSession() error {
|
||||
p, err := t.readPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p[0] != msgNewKeys {
|
||||
return fmt.Errorf("ssh: first packet should be msgNewKeys")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) id() string {
|
||||
if len(t.hostKeys) > 0 {
|
||||
return "server"
|
||||
}
|
||||
return "client"
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) printPacket(p []byte, write bool) {
|
||||
action := "got"
|
||||
if write {
|
||||
action = "sent"
|
||||
}
|
||||
|
||||
if p[0] == msgChannelData || p[0] == msgChannelExtendedData {
|
||||
log.Printf("%s %s data (packet %d bytes)", t.id(), action, len(p))
|
||||
} else {
|
||||
msg, err := decode(p)
|
||||
log.Printf("%s %s %T %v (%v)", t.id(), action, msg, msg, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) readPacket() ([]byte, error) {
|
||||
p, ok := <-t.incoming
|
||||
if !ok {
|
||||
return nil, t.readError
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) readLoop() {
|
||||
first := true
|
||||
for {
|
||||
p, err := t.readOnePacket(first)
|
||||
first = false
|
||||
if err != nil {
|
||||
t.readError = err
|
||||
close(t.incoming)
|
||||
break
|
||||
}
|
||||
if p[0] == msgIgnore || p[0] == msgDebug {
|
||||
continue
|
||||
}
|
||||
t.incoming <- p
|
||||
}
|
||||
|
||||
// Stop writers too.
|
||||
t.recordWriteError(t.readError)
|
||||
|
||||
// Unblock the writer should it wait for this.
|
||||
close(t.startKex)
|
||||
|
||||
// Don't close t.requestKex; it's also written to from writePacket.
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) pushPacket(p []byte) error {
|
||||
if debugHandshake {
|
||||
t.printPacket(p, true)
|
||||
}
|
||||
return t.conn.writePacket(p)
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) getWriteError() error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
return t.writeError
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) recordWriteError(err error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.writeError == nil && err != nil {
|
||||
t.writeError = err
|
||||
}
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) requestKeyExchange() {
|
||||
select {
|
||||
case t.requestKex <- struct{}{}:
|
||||
default:
|
||||
// something already requested a kex, so do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
for t.getWriteError() == nil {
|
||||
var request *pendingKex
|
||||
var sent bool
|
||||
|
||||
for request == nil || !sent {
|
||||
var ok bool
|
||||
select {
|
||||
case request, ok = <-t.startKex:
|
||||
if !ok {
|
||||
break write
|
||||
}
|
||||
case <-t.requestKex:
|
||||
break
|
||||
}
|
||||
|
||||
if !sent {
|
||||
if err := t.sendKexInit(); err != nil {
|
||||
t.recordWriteError(err)
|
||||
break
|
||||
}
|
||||
sent = true
|
||||
}
|
||||
}
|
||||
|
||||
if err := t.getWriteError(); err != nil {
|
||||
if request != nil {
|
||||
request.done <- err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// We're not servicing t.requestKex, but that is OK:
|
||||
// we never block on sending to t.requestKex.
|
||||
|
||||
// We're not servicing t.startKex, but the remote end
|
||||
// has just sent us a kexInitMsg, so it can't send
|
||||
// another key change request, until we close the done
|
||||
// channel on the pendingKex request.
|
||||
|
||||
err := t.enterKeyExchange(request.otherInit)
|
||||
|
||||
t.mu.Lock()
|
||||
t.writeError = err
|
||||
t.sentInitPacket = nil
|
||||
t.sentInitMsg = nil
|
||||
|
||||
t.resetWriteThresholds()
|
||||
|
||||
// we have completed the key exchange. Since the
|
||||
// reader is still blocked, it is safe to clear out
|
||||
// the requestKex channel. This avoids the situation
|
||||
// where: 1) we consumed our own request for the
|
||||
// initial kex, and 2) the kex from the remote side
|
||||
// caused another send on the requestKex channel,
|
||||
clear:
|
||||
for {
|
||||
select {
|
||||
case <-t.requestKex:
|
||||
//
|
||||
default:
|
||||
break clear
|
||||
}
|
||||
}
|
||||
|
||||
request.done <- t.writeError
|
||||
|
||||
// kex finished. Push packets that we received while
|
||||
// the kex was in progress. Don't look at t.startKex
|
||||
// and don't increment writtenSinceKex: if we trigger
|
||||
// another kex while we are still busy with the last
|
||||
// one, things will become very confusing.
|
||||
for _, p := range t.pendingPackets {
|
||||
t.writeError = t.pushPacket(p)
|
||||
if t.writeError != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.pendingPackets = t.pendingPackets[:0]
|
||||
t.mu.Unlock()
|
||||
}
|
||||
|
||||
// drain startKex channel. We don't service t.requestKex
|
||||
// because nobody does blocking sends there.
|
||||
go func() {
|
||||
for init := range t.startKex {
|
||||
init.done <- t.writeError
|
||||
}
|
||||
}()
|
||||
|
||||
// Unblock reader.
|
||||
t.conn.Close()
|
||||
}
|
||||
|
||||
// The protocol uses uint32 for packet counters, so we can't let them
|
||||
// reach 1<<32. We will actually read and write more packets than
|
||||
// this, though: the other side may send more packets, and after we
|
||||
// hit this limit on writing we will send a few more packets for the
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t.readPacketsLeft > 0 {
|
||||
t.readPacketsLeft--
|
||||
} else {
|
||||
t.requestKeyExchange()
|
||||
}
|
||||
|
||||
if t.readBytesLeft > 0 {
|
||||
t.readBytesLeft -= int64(len(p))
|
||||
} else {
|
||||
t.requestKeyExchange()
|
||||
}
|
||||
|
||||
if debugHandshake {
|
||||
t.printPacket(p, false)
|
||||
}
|
||||
|
||||
if first && p[0] != msgKexInit {
|
||||
return nil, fmt.Errorf("ssh: first packet should be msgKexInit")
|
||||
}
|
||||
|
||||
if p[0] != msgKexInit {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
firstKex := t.sessionID == nil
|
||||
|
||||
kex := pendingKex{
|
||||
done: make(chan error, 1),
|
||||
otherInit: p,
|
||||
}
|
||||
t.startKex <- &kex
|
||||
err = <-kex.done
|
||||
|
||||
if debugHandshake {
|
||||
log.Printf("%s exited key exchange (first %v), err %v", t.id(), firstKex, err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.resetReadThresholds()
|
||||
|
||||
// By default, a key exchange is hidden from higher layers by
|
||||
// translating it into msgIgnore.
|
||||
successPacket := []byte{msgIgnore}
|
||||
if firstKex {
|
||||
// sendKexInit() for the first kex waits for
|
||||
// msgNewKeys so the authentication process is
|
||||
// guaranteed to happen over an encrypted transport.
|
||||
successPacket = []byte{msgNewKeys}
|
||||
}
|
||||
|
||||
return successPacket, nil
|
||||
}
|
||||
|
||||
// sendKexInit sends a key change message.
|
||||
func (t *handshakeTransport) sendKexInit() error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.sentInitMsg != nil {
|
||||
// kexInits may be sent either in response to the other side,
|
||||
// or because our side wants to initiate a key change, so we
|
||||
// may have already sent a kexInit. In that case, don't send a
|
||||
// second kexInit.
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := &kexInitMsg{
|
||||
KexAlgos: t.config.KeyExchanges,
|
||||
CiphersClientServer: t.config.Ciphers,
|
||||
CiphersServerClient: t.config.Ciphers,
|
||||
MACsClientServer: t.config.MACs,
|
||||
MACsServerClient: t.config.MACs,
|
||||
CompressionClientServer: supportedCompressions,
|
||||
CompressionServerClient: supportedCompressions,
|
||||
}
|
||||
io.ReadFull(rand.Reader, msg.Cookie[:])
|
||||
|
||||
if len(t.hostKeys) > 0 {
|
||||
for _, k := range t.hostKeys {
|
||||
msg.ServerHostKeyAlgos = append(
|
||||
msg.ServerHostKeyAlgos, k.PublicKey().Type())
|
||||
}
|
||||
} else {
|
||||
msg.ServerHostKeyAlgos = t.hostKeyAlgorithms
|
||||
}
|
||||
packet := Marshal(msg)
|
||||
|
||||
// writePacket destroys the contents, so save a copy.
|
||||
packetCopy := make([]byte, len(packet))
|
||||
copy(packetCopy, packet)
|
||||
|
||||
if err := t.pushPacket(packetCopy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.sentInitMsg = msg
|
||||
t.sentInitPacket = packet
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) writePacket(p []byte) error {
|
||||
switch p[0] {
|
||||
case msgKexInit:
|
||||
return errors.New("ssh: only handshakeTransport can send kexInit")
|
||||
case msgNewKeys:
|
||||
return errors.New("ssh: only handshakeTransport can send newKeys")
|
||||
}
|
||||
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.writeError != nil {
|
||||
return t.writeError
|
||||
}
|
||||
|
||||
if t.sentInitMsg != nil {
|
||||
// Copy the packet so the writer can reuse the buffer.
|
||||
cp := make([]byte, len(p))
|
||||
copy(cp, p)
|
||||
t.pendingPackets = append(t.pendingPackets, cp)
|
||||
return nil
|
||||
}
|
||||
|
||||
if t.writeBytesLeft > 0 {
|
||||
t.writeBytesLeft -= int64(len(p))
|
||||
} else {
|
||||
t.requestKeyExchange()
|
||||
}
|
||||
|
||||
if t.writePacketsLeft > 0 {
|
||||
t.writePacketsLeft--
|
||||
} else {
|
||||
t.requestKeyExchange()
|
||||
}
|
||||
|
||||
if err := t.pushPacket(p); err != nil {
|
||||
t.writeError = err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) Close() error {
|
||||
return t.conn.Close()
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
||||
if debugHandshake {
|
||||
log.Printf("%s entered key exchange", t.id())
|
||||
}
|
||||
|
||||
otherInit := &kexInitMsg{}
|
||||
if err := Unmarshal(otherInitPacket, otherInit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
magics := handshakeMagics{
|
||||
clientVersion: t.clientVersion,
|
||||
serverVersion: t.serverVersion,
|
||||
clientKexInit: otherInitPacket,
|
||||
serverKexInit: t.sentInitPacket,
|
||||
}
|
||||
|
||||
clientInit := otherInit
|
||||
serverInit := t.sentInitMsg
|
||||
if len(t.hostKeys) == 0 {
|
||||
clientInit, serverInit = serverInit, clientInit
|
||||
|
||||
magics.clientKexInit = t.sentInitPacket
|
||||
magics.serverKexInit = otherInitPacket
|
||||
}
|
||||
|
||||
var err error
|
||||
t.algorithms, err = findAgreedAlgorithms(clientInit, serverInit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We don't send FirstKexFollows, but we handle receiving it.
|
||||
//
|
||||
// RFC 4253 section 7 defines the kex and the agreement method for
|
||||
// first_kex_packet_follows. It states that the guessed packet
|
||||
// should be ignored if the "kex algorithm and/or the host
|
||||
// key algorithm is guessed wrong (server and client have
|
||||
// different preferred algorithm), or if any of the other
|
||||
// algorithms cannot be agreed upon". The other algorithms have
|
||||
// already been checked above so the kex algorithm and host key
|
||||
// algorithm are checked here.
|
||||
if otherInit.FirstKexFollows && (clientInit.KexAlgos[0] != serverInit.KexAlgos[0] || clientInit.ServerHostKeyAlgos[0] != serverInit.ServerHostKeyAlgos[0]) {
|
||||
// other side sent a kex message for the wrong algorithm,
|
||||
// which we have to ignore.
|
||||
if _, err := t.conn.readPacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
kex, ok := kexAlgoMap[t.algorithms.kex]
|
||||
if !ok {
|
||||
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", t.algorithms.kex)
|
||||
}
|
||||
|
||||
var result *kexResult
|
||||
if len(t.hostKeys) > 0 {
|
||||
result, err = t.server(kex, t.algorithms, &magics)
|
||||
} else {
|
||||
result, err = t.client(kex, t.algorithms, &magics)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t.sessionID == nil {
|
||||
t.sessionID = result.H
|
||||
}
|
||||
result.SessionID = t.sessionID
|
||||
|
||||
if err := t.conn.prepareKeyChange(t.algorithms, result); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil {
|
||||
return err
|
||||
}
|
||||
if packet, err := t.conn.readPacket(); err != nil {
|
||||
return err
|
||||
} else if packet[0] != msgNewKeys {
|
||||
return unexpectedMessageError(msgNewKeys, packet[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) server(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) {
|
||||
var hostKey Signer
|
||||
for _, k := range t.hostKeys {
|
||||
if algs.hostKey == k.PublicKey().Type() {
|
||||
hostKey = k
|
||||
}
|
||||
}
|
||||
|
||||
r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey)
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) {
|
||||
result, err := kex.Client(t.conn, t.config.Rand, magics)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostKey, err := ParsePublicKey(result.HostKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := verifyHostKeySignature(hostKey, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
@ -1,559 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testChecker struct {
|
||||
calls []string
|
||||
}
|
||||
|
||||
func (t *testChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error {
|
||||
if dialAddr == "bad" {
|
||||
return fmt.Errorf("dialAddr is bad")
|
||||
}
|
||||
|
||||
if tcpAddr, ok := addr.(*net.TCPAddr); !ok || tcpAddr == nil {
|
||||
return fmt.Errorf("testChecker: got %T want *net.TCPAddr", addr)
|
||||
}
|
||||
|
||||
t.calls = append(t.calls, fmt.Sprintf("%s %v %s %x", dialAddr, addr, key.Type(), key.Marshal()))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
|
||||
// therefore is buffered (net.Pipe deadlocks if both sides start with
|
||||
// a write.)
|
||||
func netPipe() (net.Conn, net.Conn, error) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
listener, err = net.Listen("tcp", "[::1]:0")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
defer listener.Close()
|
||||
c1, err := net.Dial("tcp", listener.Addr().String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c2, err := listener.Accept()
|
||||
if err != nil {
|
||||
c1.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return c1, c2, nil
|
||||
}
|
||||
|
||||
// noiseTransport inserts ignore messages to check that the read loop
|
||||
// and the key exchange filters out these messages.
|
||||
type noiseTransport struct {
|
||||
keyingTransport
|
||||
}
|
||||
|
||||
func (t *noiseTransport) writePacket(p []byte) error {
|
||||
ignore := []byte{msgIgnore}
|
||||
if err := t.keyingTransport.writePacket(ignore); err != nil {
|
||||
return err
|
||||
}
|
||||
debug := []byte{msgDebug, 1, 2, 3}
|
||||
if err := t.keyingTransport.writePacket(debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return t.keyingTransport.writePacket(p)
|
||||
}
|
||||
|
||||
func addNoiseTransport(t keyingTransport) keyingTransport {
|
||||
return &noiseTransport{t}
|
||||
}
|
||||
|
||||
// handshakePair creates two handshakeTransports connected with each
|
||||
// other. If the noise argument is true, both transports will try to
|
||||
// confuse the other side by sending ignore and debug messages.
|
||||
func handshakePair(clientConf *ClientConfig, addr string, noise bool) (client *handshakeTransport, server *handshakeTransport, err error) {
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var trC, trS keyingTransport
|
||||
|
||||
trC = newTransport(a, rand.Reader, true)
|
||||
trS = newTransport(b, rand.Reader, false)
|
||||
if noise {
|
||||
trC = addNoiseTransport(trC)
|
||||
trS = addNoiseTransport(trS)
|
||||
}
|
||||
clientConf.SetDefaults()
|
||||
|
||||
v := []byte("version")
|
||||
client = newClientTransport(trC, v, v, clientConf, addr, a.RemoteAddr())
|
||||
|
||||
serverConf := &ServerConfig{}
|
||||
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
serverConf.SetDefaults()
|
||||
server = newServerTransport(trS, v, v, serverConf)
|
||||
|
||||
if err := server.waitSession(); err != nil {
|
||||
return nil, nil, fmt.Errorf("server.waitSession: %v", err)
|
||||
}
|
||||
if err := client.waitSession(); err != nil {
|
||||
return nil, nil, fmt.Errorf("client.waitSession: %v", err)
|
||||
}
|
||||
|
||||
return client, server, nil
|
||||
}
|
||||
|
||||
func TestHandshakeBasic(t *testing.T) {
|
||||
if runtime.GOOS == "plan9" {
|
||||
t.Skip("see golang.org/issue/7237")
|
||||
}
|
||||
|
||||
checker := &syncChecker{
|
||||
waitCall: make(chan int, 10),
|
||||
called: make(chan int, 10),
|
||||
}
|
||||
|
||||
checker.waitCall <- 1
|
||||
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false)
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
// Let first kex complete normally.
|
||||
<-checker.called
|
||||
|
||||
clientDone := make(chan int, 0)
|
||||
gotHalf := make(chan int, 0)
|
||||
const N = 20
|
||||
|
||||
go func() {
|
||||
defer close(clientDone)
|
||||
// Client writes a bunch of stuff, and does a key
|
||||
// change in the middle. This should not confuse the
|
||||
// handshake in progress. We do this twice, so we test
|
||||
// that the packet buffer is reset correctly.
|
||||
for i := 0; i < N; i++ {
|
||||
p := []byte{msgRequestSuccess, byte(i)}
|
||||
if err := trC.writePacket(p); err != nil {
|
||||
t.Fatalf("sendPacket: %v", err)
|
||||
}
|
||||
if (i % 10) == 5 {
|
||||
<-gotHalf
|
||||
// halfway through, we request a key change.
|
||||
trC.requestKeyExchange()
|
||||
|
||||
// Wait until we can be sure the key
|
||||
// change has really started before we
|
||||
// write more.
|
||||
<-checker.called
|
||||
}
|
||||
if (i % 10) == 7 {
|
||||
// write some packets until the kex
|
||||
// completes, to test buffering of
|
||||
// packets.
|
||||
checker.waitCall <- 1
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Server checks that client messages come in cleanly
|
||||
i := 0
|
||||
err = nil
|
||||
for ; i < N; i++ {
|
||||
var p []byte
|
||||
p, err = trS.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if (i % 10) == 5 {
|
||||
gotHalf <- 1
|
||||
}
|
||||
|
||||
want := []byte{msgRequestSuccess, byte(i)}
|
||||
if bytes.Compare(p, want) != 0 {
|
||||
t.Errorf("message %d: got %v, want %v", i, p, want)
|
||||
}
|
||||
}
|
||||
<-clientDone
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatalf("server error: %v", err)
|
||||
}
|
||||
if i != N {
|
||||
t.Errorf("received %d messages, want 10.", i)
|
||||
}
|
||||
|
||||
close(checker.called)
|
||||
if _, ok := <-checker.called; ok {
|
||||
// If all went well, we registered exactly 2 key changes: one
|
||||
// that establishes the session, and one that we requested
|
||||
// additionally.
|
||||
t.Fatalf("got another host key checks after 2 handshakes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForceFirstKex(t *testing.T) {
|
||||
// like handshakePair, but must access the keyingTransport.
|
||||
checker := &testChecker{}
|
||||
clientConf := &ClientConfig{HostKeyCallback: checker.Check}
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
|
||||
var trC, trS keyingTransport
|
||||
|
||||
trC = newTransport(a, rand.Reader, true)
|
||||
|
||||
// This is the disallowed packet:
|
||||
trC.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth}))
|
||||
|
||||
// Rest of the setup.
|
||||
trS = newTransport(b, rand.Reader, false)
|
||||
clientConf.SetDefaults()
|
||||
|
||||
v := []byte("version")
|
||||
client := newClientTransport(trC, v, v, clientConf, "addr", a.RemoteAddr())
|
||||
|
||||
serverConf := &ServerConfig{}
|
||||
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
serverConf.SetDefaults()
|
||||
server := newServerTransport(trS, v, v, serverConf)
|
||||
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
|
||||
// We setup the initial key exchange, but the remote side
|
||||
// tries to send serviceRequestMsg in cleartext, which is
|
||||
// disallowed.
|
||||
|
||||
if err := server.waitSession(); err == nil {
|
||||
t.Errorf("server first kex init should reject unexpected packet")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeAutoRekeyWrite(t *testing.T) {
|
||||
checker := &syncChecker{
|
||||
called: make(chan int, 10),
|
||||
waitCall: nil,
|
||||
}
|
||||
clientConf := &ClientConfig{HostKeyCallback: checker.Check}
|
||||
clientConf.RekeyThreshold = 500
|
||||
trC, trS, err := handshakePair(clientConf, "addr", false)
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
input := make([]byte, 251)
|
||||
input[0] = msgRequestSuccess
|
||||
|
||||
done := make(chan int, 1)
|
||||
const numPacket = 5
|
||||
go func() {
|
||||
defer close(done)
|
||||
j := 0
|
||||
for ; j < numPacket; j++ {
|
||||
if p, err := trS.readPacket(); err != nil {
|
||||
break
|
||||
} else if !bytes.Equal(input, p) {
|
||||
t.Errorf("got packet type %d, want %d", p[0], input[0])
|
||||
}
|
||||
}
|
||||
|
||||
if j != numPacket {
|
||||
t.Errorf("got %d, want 5 messages", j)
|
||||
}
|
||||
}()
|
||||
|
||||
<-checker.called
|
||||
|
||||
for i := 0; i < numPacket; i++ {
|
||||
p := make([]byte, len(input))
|
||||
copy(p, input)
|
||||
if err := trC.writePacket(p); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
if i == 2 {
|
||||
// Make sure the kex is in progress.
|
||||
<-checker.called
|
||||
}
|
||||
|
||||
}
|
||||
<-done
|
||||
}
|
||||
|
||||
type syncChecker struct {
|
||||
waitCall chan int
|
||||
called chan int
|
||||
}
|
||||
|
||||
func (c *syncChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error {
|
||||
c.called <- 1
|
||||
if c.waitCall != nil {
|
||||
<-c.waitCall
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestHandshakeAutoRekeyRead(t *testing.T) {
|
||||
sync := &syncChecker{
|
||||
called: make(chan int, 2),
|
||||
waitCall: nil,
|
||||
}
|
||||
clientConf := &ClientConfig{
|
||||
HostKeyCallback: sync.Check,
|
||||
}
|
||||
clientConf.RekeyThreshold = 500
|
||||
|
||||
trC, trS, err := handshakePair(clientConf, "addr", false)
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
packet := make([]byte, 501)
|
||||
packet[0] = msgRequestSuccess
|
||||
if err := trS.writePacket(packet); err != nil {
|
||||
t.Fatalf("writePacket: %v", err)
|
||||
}
|
||||
|
||||
// While we read out the packet, a key change will be
|
||||
// initiated.
|
||||
done := make(chan int, 1)
|
||||
go func() {
|
||||
defer close(done)
|
||||
if _, err := trC.readPacket(); err != nil {
|
||||
t.Fatalf("readPacket(client): %v", err)
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
<-done
|
||||
<-sync.called
|
||||
}
|
||||
|
||||
// errorKeyingTransport generates errors after a given number of
|
||||
// read/write operations.
|
||||
type errorKeyingTransport struct {
|
||||
packetConn
|
||||
readLeft, writeLeft int
|
||||
}
|
||||
|
||||
func (n *errorKeyingTransport) prepareKeyChange(*algorithms, *kexResult) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *errorKeyingTransport) getSessionID() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *errorKeyingTransport) writePacket(packet []byte) error {
|
||||
if n.writeLeft == 0 {
|
||||
n.Close()
|
||||
return errors.New("barf")
|
||||
}
|
||||
|
||||
n.writeLeft--
|
||||
return n.packetConn.writePacket(packet)
|
||||
}
|
||||
|
||||
func (n *errorKeyingTransport) readPacket() ([]byte, error) {
|
||||
if n.readLeft == 0 {
|
||||
n.Close()
|
||||
return nil, errors.New("barf")
|
||||
}
|
||||
|
||||
n.readLeft--
|
||||
return n.packetConn.readPacket()
|
||||
}
|
||||
|
||||
func TestHandshakeErrorHandlingRead(t *testing.T) {
|
||||
for i := 0; i < 20; i++ {
|
||||
testHandshakeErrorHandlingN(t, i, -1, false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeErrorHandlingWrite(t *testing.T) {
|
||||
for i := 0; i < 20; i++ {
|
||||
testHandshakeErrorHandlingN(t, -1, i, false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeErrorHandlingReadCoupled(t *testing.T) {
|
||||
for i := 0; i < 20; i++ {
|
||||
testHandshakeErrorHandlingN(t, i, -1, true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeErrorHandlingWriteCoupled(t *testing.T) {
|
||||
for i := 0; i < 20; i++ {
|
||||
testHandshakeErrorHandlingN(t, -1, i, true)
|
||||
}
|
||||
}
|
||||
|
||||
// testHandshakeErrorHandlingN runs handshakes, injecting errors. If
|
||||
// handshakeTransport deadlocks, the go runtime will detect it and
|
||||
// panic.
|
||||
func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int, coupled bool) {
|
||||
msg := Marshal(&serviceRequestMsg{strings.Repeat("x", int(minRekeyThreshold)/4)})
|
||||
|
||||
a, b := memPipe()
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
key := testSigners["ecdsa"]
|
||||
serverConf := Config{RekeyThreshold: minRekeyThreshold}
|
||||
serverConf.SetDefaults()
|
||||
serverConn := newHandshakeTransport(&errorKeyingTransport{a, readLimit, writeLimit}, &serverConf, []byte{'a'}, []byte{'b'})
|
||||
serverConn.hostKeys = []Signer{key}
|
||||
go serverConn.readLoop()
|
||||
go serverConn.kexLoop()
|
||||
|
||||
clientConf := Config{RekeyThreshold: 10 * minRekeyThreshold}
|
||||
clientConf.SetDefaults()
|
||||
clientConn := newHandshakeTransport(&errorKeyingTransport{b, -1, -1}, &clientConf, []byte{'a'}, []byte{'b'})
|
||||
clientConn.hostKeyAlgorithms = []string{key.PublicKey().Type()}
|
||||
clientConn.hostKeyCallback = InsecureIgnoreHostKey()
|
||||
go clientConn.readLoop()
|
||||
go clientConn.kexLoop()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, hs := range []packetConn{serverConn, clientConn} {
|
||||
if !coupled {
|
||||
wg.Add(2)
|
||||
go func(c packetConn) {
|
||||
for i := 0; ; i++ {
|
||||
str := fmt.Sprintf("%08x", i) + strings.Repeat("x", int(minRekeyThreshold)/4-8)
|
||||
err := c.writePacket(Marshal(&serviceRequestMsg{str}))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
c.Close()
|
||||
}(hs)
|
||||
go func(c packetConn) {
|
||||
for {
|
||||
_, err := c.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}(hs)
|
||||
} else {
|
||||
wg.Add(1)
|
||||
go func(c packetConn) {
|
||||
for {
|
||||
_, err := c.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if err := c.writePacket(msg); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
wg.Done()
|
||||
}(hs)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestDisconnect(t *testing.T) {
|
||||
if runtime.GOOS == "plan9" {
|
||||
t.Skip("see golang.org/issue/7237")
|
||||
}
|
||||
checker := &testChecker{}
|
||||
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false)
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
trC.writePacket([]byte{msgRequestSuccess, 0, 0})
|
||||
errMsg := &disconnectMsg{
|
||||
Reason: 42,
|
||||
Message: "such is life",
|
||||
}
|
||||
trC.writePacket(Marshal(errMsg))
|
||||
trC.writePacket([]byte{msgRequestSuccess, 0, 0})
|
||||
|
||||
packet, err := trS.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("readPacket 1: %v", err)
|
||||
}
|
||||
if packet[0] != msgRequestSuccess {
|
||||
t.Errorf("got packet %v, want packet type %d", packet, msgRequestSuccess)
|
||||
}
|
||||
|
||||
_, err = trS.readPacket()
|
||||
if err == nil {
|
||||
t.Errorf("readPacket 2 succeeded")
|
||||
} else if !reflect.DeepEqual(err, errMsg) {
|
||||
t.Errorf("got error %#v, want %#v", err, errMsg)
|
||||
}
|
||||
|
||||
_, err = trS.readPacket()
|
||||
if err == nil {
|
||||
t.Errorf("readPacket 3 succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeRekeyDefault(t *testing.T) {
|
||||
clientConf := &ClientConfig{
|
||||
Config: Config{
|
||||
Ciphers: []string{"aes128-ctr"},
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
trC, trS, err := handshakePair(clientConf, "addr", false)
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
trC.writePacket([]byte{msgRequestSuccess, 0, 0})
|
||||
trC.Close()
|
||||
|
||||
rgb := (1024 + trC.readBytesLeft) >> 30
|
||||
wgb := (1024 + trC.writeBytesLeft) >> 30
|
||||
|
||||
if rgb != 64 {
|
||||
t.Errorf("got rekey after %dG read, want 64G", rgb)
|
||||
}
|
||||
if wgb != 64 {
|
||||
t.Errorf("got rekey after %dG write, want 64G", wgb)
|
||||
}
|
||||
}
|
||||
540
ssh/kex.go
540
ssh/kex.go
|
|
@ -1,540 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
const (
|
||||
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
|
||||
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
|
||||
kexAlgoECDH256 = "ecdh-sha2-nistp256"
|
||||
kexAlgoECDH384 = "ecdh-sha2-nistp384"
|
||||
kexAlgoECDH521 = "ecdh-sha2-nistp521"
|
||||
kexAlgoCurve25519SHA256 = "curve25519-sha256@libssh.org"
|
||||
)
|
||||
|
||||
// kexResult captures the outcome of a key exchange.
|
||||
type kexResult struct {
|
||||
// Session hash. See also RFC 4253, section 8.
|
||||
H []byte
|
||||
|
||||
// Shared secret. See also RFC 4253, section 8.
|
||||
K []byte
|
||||
|
||||
// Host key as hashed into H.
|
||||
HostKey []byte
|
||||
|
||||
// Signature of H.
|
||||
Signature []byte
|
||||
|
||||
// A cryptographic hash function that matches the security
|
||||
// level of the key exchange algorithm. It is used for
|
||||
// calculating H, and for deriving keys from H and K.
|
||||
Hash crypto.Hash
|
||||
|
||||
// The session ID, which is the first H computed. This is used
|
||||
// to derive key material inside the transport.
|
||||
SessionID []byte
|
||||
}
|
||||
|
||||
// handshakeMagics contains data that is always included in the
|
||||
// session hash.
|
||||
type handshakeMagics struct {
|
||||
clientVersion, serverVersion []byte
|
||||
clientKexInit, serverKexInit []byte
|
||||
}
|
||||
|
||||
func (m *handshakeMagics) write(w io.Writer) {
|
||||
writeString(w, m.clientVersion)
|
||||
writeString(w, m.serverVersion)
|
||||
writeString(w, m.clientKexInit)
|
||||
writeString(w, m.serverKexInit)
|
||||
}
|
||||
|
||||
// kexAlgorithm abstracts different key exchange algorithms.
|
||||
type kexAlgorithm interface {
|
||||
// Server runs server-side key agreement, signing the result
|
||||
// with a hostkey.
|
||||
Server(p packetConn, rand io.Reader, magics *handshakeMagics, s Signer) (*kexResult, error)
|
||||
|
||||
// Client runs the client-side key agreement. Caller is
|
||||
// responsible for verifying the host key signature.
|
||||
Client(p packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error)
|
||||
}
|
||||
|
||||
// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement.
|
||||
type dhGroup struct {
|
||||
g, p, pMinus1 *big.Int
|
||||
}
|
||||
|
||||
func (group *dhGroup) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) {
|
||||
if theirPublic.Cmp(bigOne) <= 0 || theirPublic.Cmp(group.pMinus1) >= 0 {
|
||||
return nil, errors.New("ssh: DH parameter out of bounds")
|
||||
}
|
||||
return new(big.Int).Exp(theirPublic, myPrivate, group.p), nil
|
||||
}
|
||||
|
||||
func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
||||
hashFunc := crypto.SHA1
|
||||
|
||||
var x *big.Int
|
||||
for {
|
||||
var err error
|
||||
if x, err = rand.Int(randSource, group.pMinus1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if x.Sign() > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
X := new(big.Int).Exp(group.g, x, group.p)
|
||||
kexDHInit := kexDHInitMsg{
|
||||
X: X,
|
||||
}
|
||||
if err := c.writePacket(Marshal(&kexDHInit)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kexDHReply kexDHReplyMsg
|
||||
if err = Unmarshal(packet, &kexDHReply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kInt, err := group.diffieHellman(kexDHReply.Y, x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := hashFunc.New()
|
||||
magics.write(h)
|
||||
writeString(h, kexDHReply.HostKey)
|
||||
writeInt(h, X)
|
||||
writeInt(h, kexDHReply.Y)
|
||||
K := make([]byte, intLength(kInt))
|
||||
marshalInt(K, kInt)
|
||||
h.Write(K)
|
||||
|
||||
return &kexResult{
|
||||
H: h.Sum(nil),
|
||||
K: K,
|
||||
HostKey: kexDHReply.HostKey,
|
||||
Signature: kexDHReply.Signature,
|
||||
Hash: crypto.SHA1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
|
||||
hashFunc := crypto.SHA1
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var kexDHInit kexDHInitMsg
|
||||
if err = Unmarshal(packet, &kexDHInit); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var y *big.Int
|
||||
for {
|
||||
if y, err = rand.Int(randSource, group.pMinus1); err != nil {
|
||||
return
|
||||
}
|
||||
if y.Sign() > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Y := new(big.Int).Exp(group.g, y, group.p)
|
||||
kInt, err := group.diffieHellman(kexDHInit.X, y)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostKeyBytes := priv.PublicKey().Marshal()
|
||||
|
||||
h := hashFunc.New()
|
||||
magics.write(h)
|
||||
writeString(h, hostKeyBytes)
|
||||
writeInt(h, kexDHInit.X)
|
||||
writeInt(h, Y)
|
||||
|
||||
K := make([]byte, intLength(kInt))
|
||||
marshalInt(K, kInt)
|
||||
h.Write(K)
|
||||
|
||||
H := h.Sum(nil)
|
||||
|
||||
// H is already a hash, but the hostkey signing will apply its
|
||||
// own key-specific hash algorithm.
|
||||
sig, err := signAndMarshal(priv, randSource, H)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kexDHReply := kexDHReplyMsg{
|
||||
HostKey: hostKeyBytes,
|
||||
Y: Y,
|
||||
Signature: sig,
|
||||
}
|
||||
packet = Marshal(&kexDHReply)
|
||||
|
||||
err = c.writePacket(packet)
|
||||
return &kexResult{
|
||||
H: H,
|
||||
K: K,
|
||||
HostKey: hostKeyBytes,
|
||||
Signature: sig,
|
||||
Hash: crypto.SHA1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ecdh performs Elliptic Curve Diffie-Hellman key exchange as
|
||||
// described in RFC 5656, section 4.
|
||||
type ecdh struct {
|
||||
curve elliptic.Curve
|
||||
}
|
||||
|
||||
func (kex *ecdh) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
||||
ephKey, err := ecdsa.GenerateKey(kex.curve, rand)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kexInit := kexECDHInitMsg{
|
||||
ClientPubKey: elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y),
|
||||
}
|
||||
|
||||
serialized := Marshal(&kexInit)
|
||||
if err := c.writePacket(serialized); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reply kexECDHReplyMsg
|
||||
if err = Unmarshal(packet, &reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
x, y, err := unmarshalECKey(kex.curve, reply.EphemeralPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// generate shared secret
|
||||
secret, _ := kex.curve.ScalarMult(x, y, ephKey.D.Bytes())
|
||||
|
||||
h := ecHash(kex.curve).New()
|
||||
magics.write(h)
|
||||
writeString(h, reply.HostKey)
|
||||
writeString(h, kexInit.ClientPubKey)
|
||||
writeString(h, reply.EphemeralPubKey)
|
||||
K := make([]byte, intLength(secret))
|
||||
marshalInt(K, secret)
|
||||
h.Write(K)
|
||||
|
||||
return &kexResult{
|
||||
H: h.Sum(nil),
|
||||
K: K,
|
||||
HostKey: reply.HostKey,
|
||||
Signature: reply.Signature,
|
||||
Hash: ecHash(kex.curve),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// unmarshalECKey parses and checks an EC key.
|
||||
func unmarshalECKey(curve elliptic.Curve, pubkey []byte) (x, y *big.Int, err error) {
|
||||
x, y = elliptic.Unmarshal(curve, pubkey)
|
||||
if x == nil {
|
||||
return nil, nil, errors.New("ssh: elliptic.Unmarshal failure")
|
||||
}
|
||||
if !validateECPublicKey(curve, x, y) {
|
||||
return nil, nil, errors.New("ssh: public key not on curve")
|
||||
}
|
||||
return x, y, nil
|
||||
}
|
||||
|
||||
// validateECPublicKey checks that the point is a valid public key for
|
||||
// the given curve. See [SEC1], 3.2.2
|
||||
func validateECPublicKey(curve elliptic.Curve, x, y *big.Int) bool {
|
||||
if x.Sign() == 0 && y.Sign() == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if x.Cmp(curve.Params().P) >= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if y.Cmp(curve.Params().P) >= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if !curve.IsOnCurve(x, y) {
|
||||
return false
|
||||
}
|
||||
|
||||
// We don't check if N * PubKey == 0, since
|
||||
//
|
||||
// - the NIST curves have cofactor = 1, so this is implicit.
|
||||
// (We don't foresee an implementation that supports non NIST
|
||||
// curves)
|
||||
//
|
||||
// - for ephemeral keys, we don't need to worry about small
|
||||
// subgroup attacks.
|
||||
return true
|
||||
}
|
||||
|
||||
func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kexECDHInit kexECDHInitMsg
|
||||
if err = Unmarshal(packet, &kexECDHInit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientX, clientY, err := unmarshalECKey(kex.curve, kexECDHInit.ClientPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We could cache this key across multiple users/multiple
|
||||
// connection attempts, but the benefit is small. OpenSSH
|
||||
// generates a new key for each incoming connection.
|
||||
ephKey, err := ecdsa.GenerateKey(kex.curve, rand)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostKeyBytes := priv.PublicKey().Marshal()
|
||||
|
||||
serializedEphKey := elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y)
|
||||
|
||||
// generate shared secret
|
||||
secret, _ := kex.curve.ScalarMult(clientX, clientY, ephKey.D.Bytes())
|
||||
|
||||
h := ecHash(kex.curve).New()
|
||||
magics.write(h)
|
||||
writeString(h, hostKeyBytes)
|
||||
writeString(h, kexECDHInit.ClientPubKey)
|
||||
writeString(h, serializedEphKey)
|
||||
|
||||
K := make([]byte, intLength(secret))
|
||||
marshalInt(K, secret)
|
||||
h.Write(K)
|
||||
|
||||
H := h.Sum(nil)
|
||||
|
||||
// H is already a hash, but the hostkey signing will apply its
|
||||
// own key-specific hash algorithm.
|
||||
sig, err := signAndMarshal(priv, rand, H)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply := kexECDHReplyMsg{
|
||||
EphemeralPubKey: serializedEphKey,
|
||||
HostKey: hostKeyBytes,
|
||||
Signature: sig,
|
||||
}
|
||||
|
||||
serialized := Marshal(&reply)
|
||||
if err := c.writePacket(serialized); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &kexResult{
|
||||
H: H,
|
||||
K: K,
|
||||
HostKey: reply.HostKey,
|
||||
Signature: sig,
|
||||
Hash: ecHash(kex.curve),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var kexAlgoMap = map[string]kexAlgorithm{}
|
||||
|
||||
func init() {
|
||||
// This is the group called diffie-hellman-group1-sha1 in RFC
|
||||
// 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,
|
||||
pMinus1: new(big.Int).Sub(p, bigOne),
|
||||
}
|
||||
|
||||
// This is the group called diffie-hellman-group14-sha1 in RFC
|
||||
// 4253 and Oakley Group 14 in RFC 3526.
|
||||
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
|
||||
|
||||
kexAlgoMap[kexAlgoDH14SHA1] = &dhGroup{
|
||||
g: new(big.Int).SetInt64(2),
|
||||
p: p,
|
||||
pMinus1: new(big.Int).Sub(p, bigOne),
|
||||
}
|
||||
|
||||
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
|
||||
kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()}
|
||||
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
|
||||
kexAlgoMap[kexAlgoCurve25519SHA256] = &curve25519sha256{}
|
||||
}
|
||||
|
||||
// curve25519sha256 implements the curve25519-sha256@libssh.org key
|
||||
// agreement protocol, as described in
|
||||
// https://git.libssh.org/projects/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt
|
||||
type curve25519sha256 struct{}
|
||||
|
||||
type curve25519KeyPair struct {
|
||||
priv [32]byte
|
||||
pub [32]byte
|
||||
}
|
||||
|
||||
func (kp *curve25519KeyPair) generate(rand io.Reader) error {
|
||||
if _, err := io.ReadFull(rand, kp.priv[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
curve25519.ScalarBaseMult(&kp.pub, &kp.priv)
|
||||
return nil
|
||||
}
|
||||
|
||||
// curve25519Zeros is just an array of 32 zero bytes so that we have something
|
||||
// convenient to compare against in order to reject curve25519 points with the
|
||||
// wrong order.
|
||||
var curve25519Zeros [32]byte
|
||||
|
||||
func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
||||
var kp curve25519KeyPair
|
||||
if err := kp.generate(rand); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.writePacket(Marshal(&kexECDHInitMsg{kp.pub[:]})); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reply kexECDHReplyMsg
|
||||
if err = Unmarshal(packet, &reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(reply.EphemeralPubKey) != 32 {
|
||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong length")
|
||||
}
|
||||
|
||||
var servPub, secret [32]byte
|
||||
copy(servPub[:], reply.EphemeralPubKey)
|
||||
curve25519.ScalarMult(&secret, &kp.priv, &servPub)
|
||||
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
|
||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
|
||||
}
|
||||
|
||||
h := crypto.SHA256.New()
|
||||
magics.write(h)
|
||||
writeString(h, reply.HostKey)
|
||||
writeString(h, kp.pub[:])
|
||||
writeString(h, reply.EphemeralPubKey)
|
||||
|
||||
kInt := new(big.Int).SetBytes(secret[:])
|
||||
K := make([]byte, intLength(kInt))
|
||||
marshalInt(K, kInt)
|
||||
h.Write(K)
|
||||
|
||||
return &kexResult{
|
||||
H: h.Sum(nil),
|
||||
K: K,
|
||||
HostKey: reply.HostKey,
|
||||
Signature: reply.Signature,
|
||||
Hash: crypto.SHA256,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var kexInit kexECDHInitMsg
|
||||
if err = Unmarshal(packet, &kexInit); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(kexInit.ClientPubKey) != 32 {
|
||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong length")
|
||||
}
|
||||
|
||||
var kp curve25519KeyPair
|
||||
if err := kp.generate(rand); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var clientPub, secret [32]byte
|
||||
copy(clientPub[:], kexInit.ClientPubKey)
|
||||
curve25519.ScalarMult(&secret, &kp.priv, &clientPub)
|
||||
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
|
||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
|
||||
}
|
||||
|
||||
hostKeyBytes := priv.PublicKey().Marshal()
|
||||
|
||||
h := crypto.SHA256.New()
|
||||
magics.write(h)
|
||||
writeString(h, hostKeyBytes)
|
||||
writeString(h, kexInit.ClientPubKey)
|
||||
writeString(h, kp.pub[:])
|
||||
|
||||
kInt := new(big.Int).SetBytes(secret[:])
|
||||
K := make([]byte, intLength(kInt))
|
||||
marshalInt(K, kInt)
|
||||
h.Write(K)
|
||||
|
||||
H := h.Sum(nil)
|
||||
|
||||
sig, err := signAndMarshal(priv, rand, H)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply := kexECDHReplyMsg{
|
||||
EphemeralPubKey: kp.pub[:],
|
||||
HostKey: hostKeyBytes,
|
||||
Signature: sig,
|
||||
}
|
||||
if err := c.writePacket(Marshal(&reply)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &kexResult{
|
||||
H: H,
|
||||
K: K,
|
||||
HostKey: hostKeyBytes,
|
||||
Signature: sig,
|
||||
Hash: crypto.SHA256,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
// Key exchange tests.
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKexes(t *testing.T) {
|
||||
type kexResultErr struct {
|
||||
result *kexResult
|
||||
err error
|
||||
}
|
||||
|
||||
for name, kex := range kexAlgoMap {
|
||||
a, b := memPipe()
|
||||
|
||||
s := make(chan kexResultErr, 1)
|
||||
c := make(chan kexResultErr, 1)
|
||||
var magics handshakeMagics
|
||||
go func() {
|
||||
r, e := kex.Client(a, rand.Reader, &magics)
|
||||
a.Close()
|
||||
c <- kexResultErr{r, e}
|
||||
}()
|
||||
go func() {
|
||||
r, e := kex.Server(b, rand.Reader, &magics, testSigners["ecdsa"])
|
||||
b.Close()
|
||||
s <- kexResultErr{r, e}
|
||||
}()
|
||||
|
||||
clientRes := <-c
|
||||
serverRes := <-s
|
||||
if clientRes.err != nil {
|
||||
t.Errorf("client: %v", clientRes.err)
|
||||
}
|
||||
if serverRes.err != nil {
|
||||
t.Errorf("server: %v", serverRes.err)
|
||||
}
|
||||
if !reflect.DeepEqual(clientRes.result, serverRes.result) {
|
||||
t.Errorf("kex %q: mismatch %#v, %#v", name, clientRes.result, serverRes.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
1006
ssh/keys.go
1006
ssh/keys.go
File diff suppressed because it is too large
Load diff
500
ssh/keys_test.go
500
ssh/keys_test.go
|
|
@ -1,500 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
)
|
||||
|
||||
func rawKey(pub PublicKey) interface{} {
|
||||
switch k := pub.(type) {
|
||||
case *rsaPublicKey:
|
||||
return (*rsa.PublicKey)(k)
|
||||
case *dsaPublicKey:
|
||||
return (*dsa.PublicKey)(k)
|
||||
case *ecdsaPublicKey:
|
||||
return (*ecdsa.PublicKey)(k)
|
||||
case ed25519PublicKey:
|
||||
return (ed25519.PublicKey)(k)
|
||||
case *Certificate:
|
||||
return k
|
||||
}
|
||||
panic("unknown key type")
|
||||
}
|
||||
|
||||
func TestKeyMarshalParse(t *testing.T) {
|
||||
for _, priv := range testSigners {
|
||||
pub := priv.PublicKey()
|
||||
roundtrip, err := ParsePublicKey(pub.Marshal())
|
||||
if err != nil {
|
||||
t.Errorf("ParsePublicKey(%T): %v", pub, err)
|
||||
}
|
||||
|
||||
k1 := rawKey(pub)
|
||||
k2 := rawKey(roundtrip)
|
||||
|
||||
if !reflect.DeepEqual(k1, k2) {
|
||||
t.Errorf("got %#v in roundtrip, want %#v", k2, k1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsupportedCurves(t *testing.T) {
|
||||
raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
|
||||
if _, err = NewSignerFromKey(raw); err == nil || !strings.Contains(err.Error(), "only P-256") {
|
||||
t.Fatalf("NewPrivateKey should not succeed with P-224, got: %v", err)
|
||||
}
|
||||
|
||||
if _, err = NewPublicKey(&raw.PublicKey); err == nil || !strings.Contains(err.Error(), "only P-256") {
|
||||
t.Fatalf("NewPublicKey should not succeed with P-224, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPublicKey(t *testing.T) {
|
||||
for _, k := range testSigners {
|
||||
raw := rawKey(k.PublicKey())
|
||||
// Skip certificates, as NewPublicKey does not support them.
|
||||
if _, ok := raw.(*Certificate); ok {
|
||||
continue
|
||||
}
|
||||
pub, err := NewPublicKey(raw)
|
||||
if err != nil {
|
||||
t.Errorf("NewPublicKey(%#v): %v", raw, err)
|
||||
}
|
||||
if !reflect.DeepEqual(k.PublicKey(), pub) {
|
||||
t.Errorf("NewPublicKey(%#v) = %#v, want %#v", raw, pub, k.PublicKey())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeySignVerify(t *testing.T) {
|
||||
for _, priv := range testSigners {
|
||||
pub := priv.PublicKey()
|
||||
|
||||
data := []byte("sign me")
|
||||
sig, err := priv.Sign(rand.Reader, data)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign(%T): %v", priv, err)
|
||||
}
|
||||
|
||||
if err := pub.Verify(data, sig); err != nil {
|
||||
t.Errorf("publicKey.Verify(%T): %v", priv, err)
|
||||
}
|
||||
sig.Blob[5]++
|
||||
if err := pub.Verify(data, sig); err == nil {
|
||||
t.Errorf("publicKey.Verify on broken sig did not fail")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRSAPrivateKey(t *testing.T) {
|
||||
key := testPrivateKeys["rsa"]
|
||||
|
||||
rsa, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
t.Fatalf("got %T, want *rsa.PrivateKey", rsa)
|
||||
}
|
||||
|
||||
if err := rsa.Validate(); err != nil {
|
||||
t.Errorf("Validate: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseECPrivateKey(t *testing.T) {
|
||||
key := testPrivateKeys["ecdsa"]
|
||||
|
||||
ecKey, ok := key.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
t.Fatalf("got %T, want *ecdsa.PrivateKey", ecKey)
|
||||
}
|
||||
|
||||
if !validateECPublicKey(ecKey.Curve, ecKey.X, ecKey.Y) {
|
||||
t.Fatalf("public key does not validate.")
|
||||
}
|
||||
}
|
||||
|
||||
// See Issue https://github.com/golang/go/issues/6650.
|
||||
func TestParseEncryptedPrivateKeysFails(t *testing.T) {
|
||||
const wantSubstring = "encrypted"
|
||||
for i, tt := range testdata.PEMEncryptedKeys {
|
||||
_, err := ParsePrivateKey(tt.PEMBytes)
|
||||
if err == nil {
|
||||
t.Errorf("#%d key %s: ParsePrivateKey successfully parsed, expected an error", i, tt.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), wantSubstring) {
|
||||
t.Errorf("#%d key %s: got error %q, want substring %q", i, tt.Name, err, wantSubstring)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse encrypted private keys with passphrase
|
||||
func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) {
|
||||
data := []byte("sign me")
|
||||
for _, tt := range testdata.PEMEncryptedKeys {
|
||||
s, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
|
||||
if err != nil {
|
||||
t.Fatalf("ParsePrivateKeyWithPassphrase returned error: %s", err)
|
||||
continue
|
||||
}
|
||||
sig, err := s.Sign(rand.Reader, data)
|
||||
if err != nil {
|
||||
t.Fatalf("dsa.Sign: %v", err)
|
||||
}
|
||||
if err := s.PublicKey().Verify(data, sig); err != nil {
|
||||
t.Errorf("Verify failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
tt := testdata.PEMEncryptedKeys[0]
|
||||
_, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte("incorrect"))
|
||||
if err != x509.IncorrectPasswordError {
|
||||
t.Fatalf("got %v want IncorrectPasswordError", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDSA(t *testing.T) {
|
||||
// We actually exercise the ParsePrivateKey codepath here, as opposed to
|
||||
// using the ParseRawPrivateKey+NewSignerFromKey path that testdata_test.go
|
||||
// uses.
|
||||
s, err := ParsePrivateKey(testdata.PEMBytes["dsa"])
|
||||
if err != nil {
|
||||
t.Fatalf("ParsePrivateKey returned error: %s", err)
|
||||
}
|
||||
|
||||
data := []byte("sign me")
|
||||
sig, err := s.Sign(rand.Reader, data)
|
||||
if err != nil {
|
||||
t.Fatalf("dsa.Sign: %v", err)
|
||||
}
|
||||
|
||||
if err := s.PublicKey().Verify(data, sig); err != nil {
|
||||
t.Errorf("Verify failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for authorized_keys parsing.
|
||||
|
||||
// getTestKey returns a public key, and its base64 encoding.
|
||||
func getTestKey() (PublicKey, string) {
|
||||
k := testPublicKeys["rsa"]
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
e := base64.NewEncoder(base64.StdEncoding, b)
|
||||
e.Write(k.Marshal())
|
||||
e.Close()
|
||||
|
||||
return k, b.String()
|
||||
}
|
||||
|
||||
func TestMarshalParsePublicKey(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
line := fmt.Sprintf("%s %s user@host", pub.Type(), pubSerialized)
|
||||
|
||||
authKeys := MarshalAuthorizedKey(pub)
|
||||
actualFields := strings.Fields(string(authKeys))
|
||||
if len(actualFields) == 0 {
|
||||
t.Fatalf("failed authKeys: %v", authKeys)
|
||||
}
|
||||
|
||||
// drop the comment
|
||||
expectedFields := strings.Fields(line)[0:2]
|
||||
|
||||
if !reflect.DeepEqual(actualFields, expectedFields) {
|
||||
t.Errorf("got %v, expected %v", actualFields, expectedFields)
|
||||
}
|
||||
|
||||
actPub, _, _, _, err := ParseAuthorizedKey([]byte(line))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot parse %v: %v", line, err)
|
||||
}
|
||||
if !reflect.DeepEqual(actPub, pub) {
|
||||
t.Errorf("got %v, expected %v", actPub, pub)
|
||||
}
|
||||
}
|
||||
|
||||
type authResult struct {
|
||||
pubKey PublicKey
|
||||
options []string
|
||||
comments string
|
||||
rest string
|
||||
ok bool
|
||||
}
|
||||
|
||||
func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []authResult) {
|
||||
rest := authKeys
|
||||
var values []authResult
|
||||
for len(rest) > 0 {
|
||||
var r authResult
|
||||
var err error
|
||||
r.pubKey, r.comments, r.options, rest, err = ParseAuthorizedKey(rest)
|
||||
r.ok = (err == nil)
|
||||
t.Log(err)
|
||||
r.rest = string(rest)
|
||||
values = append(values, r)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(values, expected) {
|
||||
t.Errorf("got %#v, expected %#v", values, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizedKeyBasic(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
line := "ssh-rsa " + pubSerialized + " user@host"
|
||||
testAuthorizedKeys(t, []byte(line),
|
||||
[]authResult{
|
||||
{pub, nil, "user@host", "", true},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
authWithOptions := []string{
|
||||
`# comments to ignore before any keys...`,
|
||||
``,
|
||||
`env="HOME=/home/root",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`,
|
||||
`# comments to ignore, along with a blank line`,
|
||||
``,
|
||||
`env="HOME=/home/root2" ssh-rsa ` + pubSerialized + ` user2@host2`,
|
||||
``,
|
||||
`# more comments, plus a invalid entry`,
|
||||
`ssh-rsa data-that-will-not-parse user@host3`,
|
||||
}
|
||||
for _, eol := range []string{"\n", "\r\n"} {
|
||||
authOptions := strings.Join(authWithOptions, eol)
|
||||
rest2 := strings.Join(authWithOptions[3:], eol)
|
||||
rest3 := strings.Join(authWithOptions[6:], eol)
|
||||
testAuthorizedKeys(t, []byte(authOptions), []authResult{
|
||||
{pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true},
|
||||
{pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true},
|
||||
{nil, nil, "", "", false},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthWithQuotedSpaceInEnv(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
authWithQuotedSpaceInEnv := []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
|
||||
testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []authResult{
|
||||
{pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthWithQuotedCommaInEnv(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
authWithQuotedCommaInEnv := []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
|
||||
testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []authResult{
|
||||
{pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthWithQuotedQuoteInEnv(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
authWithQuotedQuoteInEnv := []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + pubSerialized + ` user@host`)
|
||||
authWithDoubleQuotedQuote := []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + pubSerialized + "\t" + `user@host`)
|
||||
testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []authResult{
|
||||
{pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true},
|
||||
})
|
||||
|
||||
testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []authResult{
|
||||
{pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthWithInvalidSpace(t *testing.T) {
|
||||
_, pubSerialized := getTestKey()
|
||||
authWithInvalidSpace := []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
|
||||
#more to follow but still no valid keys`)
|
||||
testAuthorizedKeys(t, []byte(authWithInvalidSpace), []authResult{
|
||||
{nil, nil, "", "", false},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthWithMissingQuote(t *testing.T) {
|
||||
pub, pubSerialized := getTestKey()
|
||||
authWithMissingQuote := []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
|
||||
env="HOME=/home/root",shared-control ssh-rsa ` + pubSerialized + ` user@host`)
|
||||
|
||||
testAuthorizedKeys(t, []byte(authWithMissingQuote), []authResult{
|
||||
{pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInvalidEntry(t *testing.T) {
|
||||
authInvalid := []byte(`ssh-rsa`)
|
||||
_, _, _, _, err := ParseAuthorizedKey(authInvalid)
|
||||
if err == nil {
|
||||
t.Errorf("got valid entry for %q", authInvalid)
|
||||
}
|
||||
}
|
||||
|
||||
var knownHostsParseTests = []struct {
|
||||
input string
|
||||
err string
|
||||
|
||||
marker string
|
||||
comment string
|
||||
hosts []string
|
||||
rest string
|
||||
}{
|
||||
{
|
||||
"",
|
||||
"EOF",
|
||||
|
||||
"", "", nil, "",
|
||||
},
|
||||
{
|
||||
"# Just a comment",
|
||||
"EOF",
|
||||
|
||||
"", "", nil, "",
|
||||
},
|
||||
{
|
||||
" \t ",
|
||||
"EOF",
|
||||
|
||||
"", "", nil, "",
|
||||
},
|
||||
{
|
||||
"localhost ssh-rsa {RSAPUB}",
|
||||
"",
|
||||
|
||||
"", "", []string{"localhost"}, "",
|
||||
},
|
||||
{
|
||||
"localhost\tssh-rsa {RSAPUB}",
|
||||
"",
|
||||
|
||||
"", "", []string{"localhost"}, "",
|
||||
},
|
||||
{
|
||||
"localhost\tssh-rsa {RSAPUB}\tcomment comment",
|
||||
"",
|
||||
|
||||
"", "comment comment", []string{"localhost"}, "",
|
||||
},
|
||||
{
|
||||
"localhost\tssh-rsa {RSAPUB}\tcomment comment\n",
|
||||
"",
|
||||
|
||||
"", "comment comment", []string{"localhost"}, "",
|
||||
},
|
||||
{
|
||||
"localhost\tssh-rsa {RSAPUB}\tcomment comment\r\n",
|
||||
"",
|
||||
|
||||
"", "comment comment", []string{"localhost"}, "",
|
||||
},
|
||||
{
|
||||
"localhost\tssh-rsa {RSAPUB}\tcomment comment\r\nnext line",
|
||||
"",
|
||||
|
||||
"", "comment comment", []string{"localhost"}, "next line",
|
||||
},
|
||||
{
|
||||
"localhost,[host2:123]\tssh-rsa {RSAPUB}\tcomment comment",
|
||||
"",
|
||||
|
||||
"", "comment comment", []string{"localhost", "[host2:123]"}, "",
|
||||
},
|
||||
{
|
||||
"@marker \tlocalhost,[host2:123]\tssh-rsa {RSAPUB}",
|
||||
"",
|
||||
|
||||
"marker", "", []string{"localhost", "[host2:123]"}, "",
|
||||
},
|
||||
{
|
||||
"@marker \tlocalhost,[host2:123]\tssh-rsa aabbccdd",
|
||||
"short read",
|
||||
|
||||
"", "", nil, "",
|
||||
},
|
||||
}
|
||||
|
||||
func TestKnownHostsParsing(t *testing.T) {
|
||||
rsaPub, rsaPubSerialized := getTestKey()
|
||||
|
||||
for i, test := range knownHostsParseTests {
|
||||
var expectedKey PublicKey
|
||||
const rsaKeyToken = "{RSAPUB}"
|
||||
|
||||
input := test.input
|
||||
if strings.Contains(input, rsaKeyToken) {
|
||||
expectedKey = rsaPub
|
||||
input = strings.Replace(test.input, rsaKeyToken, rsaPubSerialized, -1)
|
||||
}
|
||||
|
||||
marker, hosts, pubKey, comment, rest, err := ParseKnownHosts([]byte(input))
|
||||
if err != nil {
|
||||
if len(test.err) == 0 {
|
||||
t.Errorf("#%d: unexpectedly failed with %q", i, err)
|
||||
} else if !strings.Contains(err.Error(), test.err) {
|
||||
t.Errorf("#%d: expected error containing %q, but got %q", i, test.err, err)
|
||||
}
|
||||
continue
|
||||
} else if len(test.err) != 0 {
|
||||
t.Errorf("#%d: succeeded but expected error including %q", i, test.err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expectedKey, pubKey) {
|
||||
t.Errorf("#%d: expected key %#v, but got %#v", i, expectedKey, pubKey)
|
||||
}
|
||||
|
||||
if marker != test.marker {
|
||||
t.Errorf("#%d: expected marker %q, but got %q", i, test.marker, marker)
|
||||
}
|
||||
|
||||
if comment != test.comment {
|
||||
t.Errorf("#%d: expected comment %q, but got %q", i, test.comment, comment)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(test.hosts, hosts) {
|
||||
t.Errorf("#%d: expected hosts %#v, but got %#v", i, test.hosts, hosts)
|
||||
}
|
||||
|
||||
if rest := string(rest); rest != test.rest {
|
||||
t.Errorf("#%d: expected remaining input to be %q, but got %q", i, test.rest, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFingerprintLegacyMD5(t *testing.T) {
|
||||
pub, _ := getTestKey()
|
||||
fingerprint := FingerprintLegacyMD5(pub)
|
||||
want := "fb:61:6d:1a:e3:f0:95:45:3c:a0:79:be:4a:93:63:66" // ssh-keygen -lf -E md5 rsa
|
||||
if fingerprint != want {
|
||||
t.Errorf("got fingerprint %q want %q", fingerprint, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFingerprintSHA256(t *testing.T) {
|
||||
pub, _ := getTestKey()
|
||||
fingerprint := FingerprintSHA256(pub)
|
||||
want := "SHA256:Anr3LjZK8YVpjrxu79myrW9Hrb/wpcMNpVvTq/RcBm8" // ssh-keygen -lf rsa
|
||||
if fingerprint != want {
|
||||
t.Errorf("got fingerprint %q want %q", fingerprint, want)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,546 +0,0 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package knownhosts implements a parser for the OpenSSH
|
||||
// known_hosts host key database.
|
||||
package knownhosts
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// See the sshd manpage
|
||||
// (http://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT) for
|
||||
// background.
|
||||
|
||||
type addr struct{ host, port string }
|
||||
|
||||
func (a *addr) String() string {
|
||||
h := a.host
|
||||
if strings.Contains(h, ":") {
|
||||
h = "[" + h + "]"
|
||||
}
|
||||
return h + ":" + a.port
|
||||
}
|
||||
|
||||
type matcher interface {
|
||||
match([]addr) bool
|
||||
}
|
||||
|
||||
type hostPattern struct {
|
||||
negate bool
|
||||
addr addr
|
||||
}
|
||||
|
||||
func (p *hostPattern) String() string {
|
||||
n := ""
|
||||
if p.negate {
|
||||
n = "!"
|
||||
}
|
||||
|
||||
return n + p.addr.String()
|
||||
}
|
||||
|
||||
type hostPatterns []hostPattern
|
||||
|
||||
func (ps hostPatterns) match(addrs []addr) bool {
|
||||
matched := false
|
||||
for _, p := range ps {
|
||||
for _, a := range addrs {
|
||||
m := p.match(a)
|
||||
if !m {
|
||||
continue
|
||||
}
|
||||
if p.negate {
|
||||
return false
|
||||
}
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
// See
|
||||
// https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/addrmatch.c
|
||||
// The matching of * has no regard for separators, unlike filesystem globs
|
||||
func wildcardMatch(pat []byte, str []byte) bool {
|
||||
for {
|
||||
if len(pat) == 0 {
|
||||
return len(str) == 0
|
||||
}
|
||||
if len(str) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if pat[0] == '*' {
|
||||
if len(pat) == 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
for j := range str {
|
||||
if wildcardMatch(pat[1:], str[j:]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if pat[0] == '?' || pat[0] == str[0] {
|
||||
pat = pat[1:]
|
||||
str = str[1:]
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *hostPattern) match(a addr) bool {
|
||||
return wildcardMatch([]byte(l.addr.host), []byte(a.host)) && l.addr.port == a.port
|
||||
}
|
||||
|
||||
type keyDBLine struct {
|
||||
cert bool
|
||||
matcher matcher
|
||||
knownKey KnownKey
|
||||
}
|
||||
|
||||
func serialize(k ssh.PublicKey) string {
|
||||
return k.Type() + " " + base64.StdEncoding.EncodeToString(k.Marshal())
|
||||
}
|
||||
|
||||
func (l *keyDBLine) match(addrs []addr) bool {
|
||||
return l.matcher.match(addrs)
|
||||
}
|
||||
|
||||
type hostKeyDB struct {
|
||||
// Serialized version of revoked keys
|
||||
revoked map[string]*KnownKey
|
||||
lines []keyDBLine
|
||||
}
|
||||
|
||||
func newHostKeyDB() *hostKeyDB {
|
||||
db := &hostKeyDB{
|
||||
revoked: make(map[string]*KnownKey),
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func keyEq(a, b ssh.PublicKey) bool {
|
||||
return bytes.Equal(a.Marshal(), b.Marshal())
|
||||
}
|
||||
|
||||
// IsAuthorityForHost can be used as a callback in ssh.CertChecker
|
||||
func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool {
|
||||
h, p, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
a := addr{host: h, port: p}
|
||||
|
||||
for _, l := range db.lines {
|
||||
if l.cert && keyEq(l.knownKey.Key, remote) && l.match([]addr{a}) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsRevoked can be used as a callback in ssh.CertChecker
|
||||
func (db *hostKeyDB) IsRevoked(key *ssh.Certificate) bool {
|
||||
_, ok := db.revoked[string(key.Marshal())]
|
||||
return ok
|
||||
}
|
||||
|
||||
const markerCert = "@cert-authority"
|
||||
const markerRevoked = "@revoked"
|
||||
|
||||
func nextWord(line []byte) (string, []byte) {
|
||||
i := bytes.IndexAny(line, "\t ")
|
||||
if i == -1 {
|
||||
return string(line), nil
|
||||
}
|
||||
|
||||
return string(line[:i]), bytes.TrimSpace(line[i:])
|
||||
}
|
||||
|
||||
func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error) {
|
||||
if w, next := nextWord(line); w == markerCert || w == markerRevoked {
|
||||
marker = w
|
||||
line = next
|
||||
}
|
||||
|
||||
host, line = nextWord(line)
|
||||
if len(line) == 0 {
|
||||
return "", "", nil, errors.New("knownhosts: missing host pattern")
|
||||
}
|
||||
|
||||
// ignore the keytype as it's in the key blob anyway.
|
||||
_, line = nextWord(line)
|
||||
if len(line) == 0 {
|
||||
return "", "", nil, errors.New("knownhosts: missing key type pattern")
|
||||
}
|
||||
|
||||
keyBlob, _ := nextWord(line)
|
||||
|
||||
keyBytes, err := base64.StdEncoding.DecodeString(keyBlob)
|
||||
if err != nil {
|
||||
return "", "", nil, err
|
||||
}
|
||||
key, err = ssh.ParsePublicKey(keyBytes)
|
||||
if err != nil {
|
||||
return "", "", nil, err
|
||||
}
|
||||
|
||||
return marker, host, key, nil
|
||||
}
|
||||
|
||||
func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error {
|
||||
marker, pattern, key, err := parseLine(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if marker == markerRevoked {
|
||||
db.revoked[string(key.Marshal())] = &KnownKey{
|
||||
Key: key,
|
||||
Filename: filename,
|
||||
Line: linenum,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
entry := keyDBLine{
|
||||
cert: marker == markerCert,
|
||||
knownKey: KnownKey{
|
||||
Filename: filename,
|
||||
Line: linenum,
|
||||
Key: key,
|
||||
},
|
||||
}
|
||||
|
||||
if pattern[0] == '|' {
|
||||
entry.matcher, err = newHashedHost(pattern)
|
||||
} else {
|
||||
entry.matcher, err = newHostnameMatcher(pattern)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db.lines = append(db.lines, entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newHostnameMatcher(pattern string) (matcher, error) {
|
||||
var hps hostPatterns
|
||||
for _, p := range strings.Split(pattern, ",") {
|
||||
if len(p) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var a addr
|
||||
var negate bool
|
||||
if p[0] == '!' {
|
||||
negate = true
|
||||
p = p[1:]
|
||||
}
|
||||
|
||||
if len(p) == 0 {
|
||||
return nil, errors.New("knownhosts: negation without following hostname")
|
||||
}
|
||||
|
||||
var err error
|
||||
if p[0] == '[' {
|
||||
a.host, a.port, err = net.SplitHostPort(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
a.host, a.port, err = net.SplitHostPort(p)
|
||||
if err != nil {
|
||||
a.host = p
|
||||
a.port = "22"
|
||||
}
|
||||
}
|
||||
hps = append(hps, hostPattern{
|
||||
negate: negate,
|
||||
addr: a,
|
||||
})
|
||||
}
|
||||
return hps, nil
|
||||
}
|
||||
|
||||
// KnownKey represents a key declared in a known_hosts file.
|
||||
type KnownKey struct {
|
||||
Key ssh.PublicKey
|
||||
Filename string
|
||||
Line int
|
||||
}
|
||||
|
||||
func (k *KnownKey) String() string {
|
||||
return fmt.Sprintf("%s:%d: %s", k.Filename, k.Line, serialize(k.Key))
|
||||
}
|
||||
|
||||
// KeyError is returned if we did not find the key in the host key
|
||||
// database, or there was a mismatch. Typically, in batch
|
||||
// applications, this should be interpreted as failure. Interactive
|
||||
// applications can offer an interactive prompt to the user.
|
||||
type KeyError struct {
|
||||
// Want holds the accepted host keys. For each key algorithm,
|
||||
// there can be one hostkey. If Want is empty, the host is
|
||||
// unknown. If Want is non-empty, there was a mismatch, which
|
||||
// can signify a MITM attack.
|
||||
Want []KnownKey
|
||||
}
|
||||
|
||||
func (u *KeyError) Error() string {
|
||||
if len(u.Want) == 0 {
|
||||
return "knownhosts: key is unknown"
|
||||
}
|
||||
return "knownhosts: key mismatch"
|
||||
}
|
||||
|
||||
// RevokedError is returned if we found a key that was revoked.
|
||||
type RevokedError struct {
|
||||
Revoked KnownKey
|
||||
}
|
||||
|
||||
func (r *RevokedError) Error() string {
|
||||
return "knownhosts: key is revoked"
|
||||
}
|
||||
|
||||
// check checks a key against the host database. This should not be
|
||||
// used for verifying certificates.
|
||||
func (db *hostKeyDB) check(address string, remote net.Addr, remoteKey ssh.PublicKey) error {
|
||||
if revoked := db.revoked[string(remoteKey.Marshal())]; revoked != nil {
|
||||
return &RevokedError{Revoked: *revoked}
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(remote.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", remote, err)
|
||||
}
|
||||
|
||||
addrs := []addr{
|
||||
{host, port},
|
||||
}
|
||||
|
||||
if address != "" {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", address, err)
|
||||
}
|
||||
|
||||
addrs = append(addrs, addr{host, port})
|
||||
}
|
||||
|
||||
return db.checkAddrs(addrs, remoteKey)
|
||||
}
|
||||
|
||||
// checkAddrs checks if we can find the given public key for any of
|
||||
// the given addresses. If we only find an entry for the IP address,
|
||||
// or only the hostname, then this still succeeds.
|
||||
func (db *hostKeyDB) checkAddrs(addrs []addr, remoteKey ssh.PublicKey) error {
|
||||
// TODO(hanwen): are these the right semantics? What if there
|
||||
// is just a key for the IP address, but not for the
|
||||
// hostname?
|
||||
|
||||
// Algorithm => key.
|
||||
knownKeys := map[string]KnownKey{}
|
||||
for _, l := range db.lines {
|
||||
if l.match(addrs) {
|
||||
typ := l.knownKey.Key.Type()
|
||||
if _, ok := knownKeys[typ]; !ok {
|
||||
knownKeys[typ] = l.knownKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keyErr := &KeyError{}
|
||||
for _, v := range knownKeys {
|
||||
keyErr.Want = append(keyErr.Want, v)
|
||||
}
|
||||
|
||||
// Unknown remote host.
|
||||
if len(knownKeys) == 0 {
|
||||
return keyErr
|
||||
}
|
||||
|
||||
// If the remote host starts using a different, unknown key type, we
|
||||
// also interpret that as a mismatch.
|
||||
if known, ok := knownKeys[remoteKey.Type()]; !ok || !keyEq(known.Key, remoteKey) {
|
||||
return keyErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// The Read function parses file contents.
|
||||
func (db *hostKeyDB) Read(r io.Reader, filename string) error {
|
||||
scanner := bufio.NewScanner(r)
|
||||
|
||||
lineNum := 0
|
||||
for scanner.Scan() {
|
||||
lineNum++
|
||||
line := scanner.Bytes()
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := db.parseLine(line, filename, lineNum); err != nil {
|
||||
return fmt.Errorf("knownhosts: %s:%d: %v", filename, lineNum, err)
|
||||
}
|
||||
}
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
// New creates a host key callback from the given OpenSSH host key
|
||||
// files. The returned callback is for use in
|
||||
// ssh.ClientConfig.HostKeyCallback. Hashed hostnames are not supported.
|
||||
func New(files ...string) (ssh.HostKeyCallback, error) {
|
||||
db := newHostKeyDB()
|
||||
for _, fn := range files {
|
||||
f, err := os.Open(fn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := db.Read(f, fn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var certChecker ssh.CertChecker
|
||||
certChecker.IsHostAuthority = db.IsHostAuthority
|
||||
certChecker.IsRevoked = db.IsRevoked
|
||||
certChecker.HostKeyFallback = db.check
|
||||
|
||||
return certChecker.CheckHostKey, nil
|
||||
}
|
||||
|
||||
// Normalize normalizes an address into the form used in known_hosts
|
||||
func Normalize(address string) string {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
host = address
|
||||
port = "22"
|
||||
}
|
||||
entry := host
|
||||
if port != "22" {
|
||||
entry = "[" + entry + "]:" + port
|
||||
} else if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") {
|
||||
entry = "[" + entry + "]"
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
// Line returns a line to add append to the known_hosts files.
|
||||
func Line(addresses []string, key ssh.PublicKey) string {
|
||||
var trimmed []string
|
||||
for _, a := range addresses {
|
||||
trimmed = append(trimmed, Normalize(a))
|
||||
}
|
||||
|
||||
return strings.Join(trimmed, ",") + " " + serialize(key)
|
||||
}
|
||||
|
||||
// HashHostname hashes the given hostname. The hostname is not
|
||||
// normalized before hashing.
|
||||
func HashHostname(hostname string) string {
|
||||
// TODO(hanwen): check if we can safely normalize this always.
|
||||
salt := make([]byte, sha1.Size)
|
||||
|
||||
_, err := rand.Read(salt)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("crypto/rand failure %v", err))
|
||||
}
|
||||
|
||||
hash := hashHost(hostname, salt)
|
||||
return encodeHash(sha1HashType, salt, hash)
|
||||
}
|
||||
|
||||
func decodeHash(encoded string) (hashType string, salt, hash []byte, err error) {
|
||||
if len(encoded) == 0 || encoded[0] != '|' {
|
||||
err = errors.New("knownhosts: hashed host must start with '|'")
|
||||
return
|
||||
}
|
||||
components := strings.Split(encoded, "|")
|
||||
if len(components) != 4 {
|
||||
err = fmt.Errorf("knownhosts: got %d components, want 3", len(components))
|
||||
return
|
||||
}
|
||||
|
||||
hashType = components[1]
|
||||
if salt, err = base64.StdEncoding.DecodeString(components[2]); err != nil {
|
||||
return
|
||||
}
|
||||
if hash, err = base64.StdEncoding.DecodeString(components[3]); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func encodeHash(typ string, salt []byte, hash []byte) string {
|
||||
return strings.Join([]string{"",
|
||||
typ,
|
||||
base64.StdEncoding.EncodeToString(salt),
|
||||
base64.StdEncoding.EncodeToString(hash),
|
||||
}, "|")
|
||||
}
|
||||
|
||||
// See https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
|
||||
func hashHost(hostname string, salt []byte) []byte {
|
||||
mac := hmac.New(sha1.New, salt)
|
||||
mac.Write([]byte(hostname))
|
||||
return mac.Sum(nil)
|
||||
}
|
||||
|
||||
type hashedHost struct {
|
||||
salt []byte
|
||||
hash []byte
|
||||
}
|
||||
|
||||
const sha1HashType = "1"
|
||||
|
||||
func newHashedHost(encoded string) (*hashedHost, error) {
|
||||
typ, salt, hash, err := decodeHash(encoded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The type field seems for future algorithm agility, but it's
|
||||
// actually hardcoded in openssh currently, see
|
||||
// https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
|
||||
if typ != sha1HashType {
|
||||
return nil, fmt.Errorf("knownhosts: got hash type %s, must be '1'", typ)
|
||||
}
|
||||
|
||||
return &hashedHost{salt: salt, hash: hash}, nil
|
||||
}
|
||||
|
||||
func (h *hashedHost) match(addrs []addr) bool {
|
||||
for _, a := range addrs {
|
||||
if bytes.Equal(hashHost(Normalize(a.String()), h.salt), h.hash) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -1,329 +0,0 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package knownhosts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
const edKeyStr = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGBAarftlLeoyf+v+nVchEZII/vna2PCV8FaX4vsF5BX"
|
||||
const alternateEdKeyStr = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIXffBYeYL+WVzVru8npl5JHt2cjlr4ornFTWzoij9sx"
|
||||
const ecKeyStr = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNLCu01+wpXe3xB5olXCN4SqU2rQu0qjSRKJO4Bg+JRCPU+ENcgdA5srTU8xYDz/GEa4dzK5ldPw4J/gZgSXCMs="
|
||||
|
||||
var ecKey, alternateEdKey, edKey ssh.PublicKey
|
||||
var testAddr = &net.TCPAddr{
|
||||
IP: net.IP{198, 41, 30, 196},
|
||||
Port: 22,
|
||||
}
|
||||
|
||||
var testAddr6 = &net.TCPAddr{
|
||||
IP: net.IP{198, 41, 30, 196,
|
||||
1, 2, 3, 4,
|
||||
1, 2, 3, 4,
|
||||
1, 2, 3, 4,
|
||||
},
|
||||
Port: 22,
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
ecKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(ecKeyStr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
edKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(edKeyStr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
alternateEdKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(alternateEdKeyStr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testDB(t *testing.T, s string) *hostKeyDB {
|
||||
db := newHostKeyDB()
|
||||
if err := db.Read(bytes.NewBufferString(s), "testdb"); err != nil {
|
||||
t.Fatalf("Read: %v", err)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func TestRevoked(t *testing.T) {
|
||||
db := testDB(t, "\n\n@revoked * "+edKeyStr+"\n")
|
||||
want := &RevokedError{
|
||||
Revoked: KnownKey{
|
||||
Key: edKey,
|
||||
Filename: "testdb",
|
||||
Line: 3,
|
||||
},
|
||||
}
|
||||
if err := db.check("", &net.TCPAddr{
|
||||
Port: 42,
|
||||
}, edKey); err == nil {
|
||||
t.Fatal("no error for revoked key")
|
||||
} else if !reflect.DeepEqual(want, err) {
|
||||
t.Fatalf("got %#v, want %#v", want, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostAuthority(t *testing.T) {
|
||||
for _, m := range []struct {
|
||||
authorityFor string
|
||||
address string
|
||||
|
||||
good bool
|
||||
}{
|
||||
{authorityFor: "localhost", address: "localhost:22", good: true},
|
||||
{authorityFor: "localhost", address: "localhost", good: false},
|
||||
{authorityFor: "localhost", address: "localhost:1234", good: false},
|
||||
{authorityFor: "[localhost]:1234", address: "localhost:1234", good: true},
|
||||
{authorityFor: "[localhost]:1234", address: "localhost:22", good: false},
|
||||
{authorityFor: "[localhost]:1234", address: "localhost", good: false},
|
||||
} {
|
||||
db := testDB(t, `@cert-authority `+m.authorityFor+` `+edKeyStr)
|
||||
if ok := db.IsHostAuthority(db.lines[0].knownKey.Key, m.address); ok != m.good {
|
||||
t.Errorf("IsHostAuthority: authority %s, address %s, wanted good = %v, got good = %v",
|
||||
m.authorityFor, m.address, m.good, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBracket(t *testing.T) {
|
||||
db := testDB(t, `[git.eclipse.org]:29418,[198.41.30.196]:29418 `+edKeyStr)
|
||||
|
||||
if err := db.check("git.eclipse.org:29418", &net.TCPAddr{
|
||||
IP: net.IP{198, 41, 30, 196},
|
||||
Port: 29418,
|
||||
}, edKey); err != nil {
|
||||
t.Errorf("got error %v, want none", err)
|
||||
}
|
||||
|
||||
if err := db.check("git.eclipse.org:29419", &net.TCPAddr{
|
||||
Port: 42,
|
||||
}, edKey); err == nil {
|
||||
t.Fatalf("no error for unknown address")
|
||||
} else if ke, ok := err.(*KeyError); !ok {
|
||||
t.Fatalf("got type %T, want *KeyError", err)
|
||||
} else if len(ke.Want) > 0 {
|
||||
t.Fatalf("got Want %v, want []", ke.Want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewKeyType(t *testing.T) {
|
||||
str := fmt.Sprintf("%s %s", testAddr, edKeyStr)
|
||||
db := testDB(t, str)
|
||||
if err := db.check("", testAddr, ecKey); err == nil {
|
||||
t.Fatalf("no error for unknown address")
|
||||
} else if ke, ok := err.(*KeyError); !ok {
|
||||
t.Fatalf("got type %T, want *KeyError", err)
|
||||
} else if len(ke.Want) == 0 {
|
||||
t.Fatalf("got empty KeyError.Want")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameKeyType(t *testing.T) {
|
||||
str := fmt.Sprintf("%s %s", testAddr, edKeyStr)
|
||||
db := testDB(t, str)
|
||||
if err := db.check("", testAddr, alternateEdKey); err == nil {
|
||||
t.Fatalf("no error for unknown address")
|
||||
} else if ke, ok := err.(*KeyError); !ok {
|
||||
t.Fatalf("got type %T, want *KeyError", err)
|
||||
} else if len(ke.Want) == 0 {
|
||||
t.Fatalf("got empty KeyError.Want")
|
||||
} else if got, want := ke.Want[0].Key.Marshal(), edKey.Marshal(); !bytes.Equal(got, want) {
|
||||
t.Fatalf("got key %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPAddress(t *testing.T) {
|
||||
str := fmt.Sprintf("%s %s", testAddr, edKeyStr)
|
||||
db := testDB(t, str)
|
||||
if err := db.check("", testAddr, edKey); err != nil {
|
||||
t.Errorf("got error %q, want none", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPv6Address(t *testing.T) {
|
||||
str := fmt.Sprintf("%s %s", testAddr6, edKeyStr)
|
||||
db := testDB(t, str)
|
||||
|
||||
if err := db.check("", testAddr6, edKey); err != nil {
|
||||
t.Errorf("got error %q, want none", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
str := fmt.Sprintf("#comment\n\nserver.org,%s %s\notherhost %s", testAddr, edKeyStr, ecKeyStr)
|
||||
db := testDB(t, str)
|
||||
if err := db.check("server.org:22", testAddr, edKey); err != nil {
|
||||
t.Errorf("got error %q, want none", err)
|
||||
}
|
||||
|
||||
want := KnownKey{
|
||||
Key: edKey,
|
||||
Filename: "testdb",
|
||||
Line: 3,
|
||||
}
|
||||
if err := db.check("server.org:22", testAddr, ecKey); err == nil {
|
||||
t.Errorf("succeeded, want KeyError")
|
||||
} else if ke, ok := err.(*KeyError); !ok {
|
||||
t.Errorf("got %T, want *KeyError", err)
|
||||
} else if len(ke.Want) != 1 {
|
||||
t.Errorf("got %v, want 1 entry", ke)
|
||||
} else if !reflect.DeepEqual(ke.Want[0], want) {
|
||||
t.Errorf("got %v, want %v", ke.Want[0], want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNegate(t *testing.T) {
|
||||
str := fmt.Sprintf("%s,!server.org %s", testAddr, edKeyStr)
|
||||
db := testDB(t, str)
|
||||
if err := db.check("server.org:22", testAddr, ecKey); err == nil {
|
||||
t.Errorf("succeeded")
|
||||
} else if ke, ok := err.(*KeyError); !ok {
|
||||
t.Errorf("got error type %T, want *KeyError", err)
|
||||
} else if len(ke.Want) != 0 {
|
||||
t.Errorf("got expected keys %d (first of type %s), want []", len(ke.Want), ke.Want[0].Key.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcard(t *testing.T) {
|
||||
str := fmt.Sprintf("server*.domain %s", edKeyStr)
|
||||
db := testDB(t, str)
|
||||
|
||||
want := &KeyError{
|
||||
Want: []KnownKey{{
|
||||
Filename: "testdb",
|
||||
Line: 1,
|
||||
Key: edKey,
|
||||
}},
|
||||
}
|
||||
|
||||
got := db.check("server.domain:22", &net.TCPAddr{}, ecKey)
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine(t *testing.T) {
|
||||
for in, want := range map[string]string{
|
||||
"server.org": "server.org " + edKeyStr,
|
||||
"server.org:22": "server.org " + edKeyStr,
|
||||
"server.org:23": "[server.org]:23 " + edKeyStr,
|
||||
"[c629:1ec4:102:304:102:304:102:304]:22": "[c629:1ec4:102:304:102:304:102:304] " + edKeyStr,
|
||||
"[c629:1ec4:102:304:102:304:102:304]:23": "[c629:1ec4:102:304:102:304:102:304]:23 " + edKeyStr,
|
||||
} {
|
||||
if got := Line([]string{in}, edKey); got != want {
|
||||
t.Errorf("Line(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcardMatch(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
pat, str string
|
||||
want bool
|
||||
}{
|
||||
{"a?b", "abb", true},
|
||||
{"ab", "abc", false},
|
||||
{"abc", "ab", false},
|
||||
{"a*b", "axxxb", true},
|
||||
{"a*b", "axbxb", true},
|
||||
{"a*b", "axbxbc", false},
|
||||
{"a*?", "axbxc", true},
|
||||
{"a*b*", "axxbxxxxxx", true},
|
||||
{"a*b*c", "axxbxxxxxxc", true},
|
||||
{"a*b*?", "axxbxxxxxxc", true},
|
||||
{"a*b*z", "axxbxxbxxxz", true},
|
||||
{"a*b*z", "axxbxxzxxxz", true},
|
||||
{"a*b*z", "axxbxxzxxx", false},
|
||||
} {
|
||||
got := wildcardMatch([]byte(c.pat), []byte(c.str))
|
||||
if got != c.want {
|
||||
t.Errorf("wildcardMatch(%q, %q) = %v, want %v", c.pat, c.str, got, c.want)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(hanwen): test coverage for certificates.
|
||||
|
||||
const testHostname = "hostname"
|
||||
|
||||
// generated with keygen -H -f
|
||||
const encodedTestHostnameHash = "|1|IHXZvQMvTcZTUU29+2vXFgx8Frs=|UGccIWfRVDwilMBnA3WJoRAC75Y="
|
||||
|
||||
func TestHostHash(t *testing.T) {
|
||||
testHostHash(t, testHostname, encodedTestHostnameHash)
|
||||
}
|
||||
|
||||
func TestHashList(t *testing.T) {
|
||||
encoded := HashHostname(testHostname)
|
||||
testHostHash(t, testHostname, encoded)
|
||||
}
|
||||
|
||||
func testHostHash(t *testing.T, hostname, encoded string) {
|
||||
typ, salt, hash, err := decodeHash(encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("decodeHash: %v", err)
|
||||
}
|
||||
|
||||
if got := encodeHash(typ, salt, hash); got != encoded {
|
||||
t.Errorf("got encoding %s want %s", got, encoded)
|
||||
}
|
||||
|
||||
if typ != sha1HashType {
|
||||
t.Fatalf("got hash type %q, want %q", typ, sha1HashType)
|
||||
}
|
||||
|
||||
got := hashHost(hostname, salt)
|
||||
if !bytes.Equal(got, hash) {
|
||||
t.Errorf("got hash %x want %x", got, hash)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalize(t *testing.T) {
|
||||
for in, want := range map[string]string{
|
||||
"127.0.0.1:22": "127.0.0.1",
|
||||
"[127.0.0.1]:22": "127.0.0.1",
|
||||
"[127.0.0.1]:23": "[127.0.0.1]:23",
|
||||
"127.0.0.1:23": "[127.0.0.1]:23",
|
||||
"[a.b.c]:22": "a.b.c",
|
||||
"[abcd:abcd:abcd:abcd]": "[abcd:abcd:abcd:abcd]",
|
||||
"[abcd:abcd:abcd:abcd]:22": "[abcd:abcd:abcd:abcd]",
|
||||
"[abcd:abcd:abcd:abcd]:23": "[abcd:abcd:abcd:abcd]:23",
|
||||
} {
|
||||
got := Normalize(in)
|
||||
if got != want {
|
||||
t.Errorf("Normalize(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashedHostkeyCheck(t *testing.T) {
|
||||
str := fmt.Sprintf("%s %s", HashHostname(testHostname), edKeyStr)
|
||||
db := testDB(t, str)
|
||||
if err := db.check(testHostname+":22", testAddr, edKey); err != nil {
|
||||
t.Errorf("check(%s): %v", testHostname, err)
|
||||
}
|
||||
want := &KeyError{
|
||||
Want: []KnownKey{{
|
||||
Filename: "testdb",
|
||||
Line: 1,
|
||||
Key: edKey,
|
||||
}},
|
||||
}
|
||||
if got := db.check(testHostname+":22", testAddr, alternateEdKey); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got error %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
61
ssh/mac.go
61
ssh/mac.go
|
|
@ -1,61 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
// Message authentication support
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"hash"
|
||||
)
|
||||
|
||||
type macMode struct {
|
||||
keySize int
|
||||
etm bool
|
||||
new func(key []byte) hash.Hash
|
||||
}
|
||||
|
||||
// truncatingMAC wraps around a hash.Hash and truncates the output digest to
|
||||
// a given size.
|
||||
type truncatingMAC struct {
|
||||
length int
|
||||
hmac hash.Hash
|
||||
}
|
||||
|
||||
func (t truncatingMAC) Write(data []byte) (int, error) {
|
||||
return t.hmac.Write(data)
|
||||
}
|
||||
|
||||
func (t truncatingMAC) Sum(in []byte) []byte {
|
||||
out := t.hmac.Sum(in)
|
||||
return out[:len(in)+t.length]
|
||||
}
|
||||
|
||||
func (t truncatingMAC) Reset() {
|
||||
t.hmac.Reset()
|
||||
}
|
||||
|
||||
func (t truncatingMAC) Size() int {
|
||||
return t.length
|
||||
}
|
||||
|
||||
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
|
||||
|
||||
var macModes = map[string]*macMode{
|
||||
"hmac-sha2-256-etm@openssh.com": {32, true, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha256.New, key)
|
||||
}},
|
||||
"hmac-sha2-256": {32, false, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha256.New, key)
|
||||
}},
|
||||
"hmac-sha1": {20, false, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha1.New, key)
|
||||
}},
|
||||
"hmac-sha1-96": {20, false, func(key []byte) hash.Hash {
|
||||
return truncatingMAC{12, hmac.New(sha1.New, key)}
|
||||
}},
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// An in-memory packetConn. It is safe to call Close and writePacket
|
||||
// from different goroutines.
|
||||
type memTransport struct {
|
||||
eof bool
|
||||
pending [][]byte
|
||||
write *memTransport
|
||||
sync.Mutex
|
||||
*sync.Cond
|
||||
}
|
||||
|
||||
func (t *memTransport) readPacket() ([]byte, error) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
for {
|
||||
if len(t.pending) > 0 {
|
||||
r := t.pending[0]
|
||||
t.pending = t.pending[1:]
|
||||
return r, nil
|
||||
}
|
||||
if t.eof {
|
||||
return nil, io.EOF
|
||||
}
|
||||
t.Cond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *memTransport) closeSelf() error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
if t.eof {
|
||||
return io.EOF
|
||||
}
|
||||
t.eof = true
|
||||
t.Cond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *memTransport) Close() error {
|
||||
err := t.write.closeSelf()
|
||||
t.closeSelf()
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *memTransport) writePacket(p []byte) error {
|
||||
t.write.Lock()
|
||||
defer t.write.Unlock()
|
||||
if t.write.eof {
|
||||
return io.EOF
|
||||
}
|
||||
c := make([]byte, len(p))
|
||||
copy(c, p)
|
||||
t.write.pending = append(t.write.pending, c)
|
||||
t.write.Cond.Signal()
|
||||
return nil
|
||||
}
|
||||
|
||||
func memPipe() (a, b packetConn) {
|
||||
t1 := memTransport{}
|
||||
t2 := memTransport{}
|
||||
t1.write = &t2
|
||||
t2.write = &t1
|
||||
t1.Cond = sync.NewCond(&t1.Mutex)
|
||||
t2.Cond = sync.NewCond(&t2.Mutex)
|
||||
return &t1, &t2
|
||||
}
|
||||
|
||||
func TestMemPipe(t *testing.T) {
|
||||
a, b := memPipe()
|
||||
if err := a.writePacket([]byte{42}); err != nil {
|
||||
t.Fatalf("writePacket: %v", err)
|
||||
}
|
||||
if err := a.Close(); err != nil {
|
||||
t.Fatal("Close: ", err)
|
||||
}
|
||||
p, err := b.readPacket()
|
||||
if err != nil {
|
||||
t.Fatal("readPacket: ", err)
|
||||
}
|
||||
if len(p) != 1 || p[0] != 42 {
|
||||
t.Fatalf("got %v, want {42}", p)
|
||||
}
|
||||
p, err = b.readPacket()
|
||||
if err != io.EOF {
|
||||
t.Fatalf("got %v, %v, want EOF", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoubleClose(t *testing.T) {
|
||||
a, _ := memPipe()
|
||||
err := a.Close()
|
||||
if err != nil {
|
||||
t.Errorf("Close: %v", err)
|
||||
}
|
||||
err = a.Close()
|
||||
if err != io.EOF {
|
||||
t.Errorf("expect EOF on double close.")
|
||||
}
|
||||
}
|
||||
758
ssh/messages.go
758
ssh/messages.go
|
|
@ -1,758 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// These are SSH message type numbers. They are scattered around several
|
||||
// documents but many were taken from [SSH-PARAMETERS].
|
||||
const (
|
||||
msgIgnore = 2
|
||||
msgUnimplemented = 3
|
||||
msgDebug = 4
|
||||
msgNewKeys = 21
|
||||
|
||||
// Standard authentication messages
|
||||
msgUserAuthSuccess = 52
|
||||
msgUserAuthBanner = 53
|
||||
)
|
||||
|
||||
// SSH messages:
|
||||
//
|
||||
// These structures mirror the wire format of the corresponding SSH messages.
|
||||
// They are marshaled using reflection with the marshal and unmarshal functions
|
||||
// in this file. The only wrinkle is that a final member of type []byte with a
|
||||
// ssh tag of "rest" receives the remainder of a packet when unmarshaling.
|
||||
|
||||
// See RFC 4253, section 11.1.
|
||||
const msgDisconnect = 1
|
||||
|
||||
// disconnectMsg is the message that signals a disconnect. It is also
|
||||
// the error type returned from mux.Wait()
|
||||
type disconnectMsg struct {
|
||||
Reason uint32 `sshtype:"1"`
|
||||
Message string
|
||||
Language string
|
||||
}
|
||||
|
||||
func (d *disconnectMsg) Error() string {
|
||||
return fmt.Sprintf("ssh: disconnect, reason %d: %s", d.Reason, d.Message)
|
||||
}
|
||||
|
||||
// See RFC 4253, section 7.1.
|
||||
const msgKexInit = 20
|
||||
|
||||
type kexInitMsg struct {
|
||||
Cookie [16]byte `sshtype:"20"`
|
||||
KexAlgos []string
|
||||
ServerHostKeyAlgos []string
|
||||
CiphersClientServer []string
|
||||
CiphersServerClient []string
|
||||
MACsClientServer []string
|
||||
MACsServerClient []string
|
||||
CompressionClientServer []string
|
||||
CompressionServerClient []string
|
||||
LanguagesClientServer []string
|
||||
LanguagesServerClient []string
|
||||
FirstKexFollows bool
|
||||
Reserved uint32
|
||||
}
|
||||
|
||||
// See RFC 4253, section 8.
|
||||
|
||||
// Diffie-Helman
|
||||
const msgKexDHInit = 30
|
||||
|
||||
type kexDHInitMsg struct {
|
||||
X *big.Int `sshtype:"30"`
|
||||
}
|
||||
|
||||
const msgKexECDHInit = 30
|
||||
|
||||
type kexECDHInitMsg struct {
|
||||
ClientPubKey []byte `sshtype:"30"`
|
||||
}
|
||||
|
||||
const msgKexECDHReply = 31
|
||||
|
||||
type kexECDHReplyMsg struct {
|
||||
HostKey []byte `sshtype:"31"`
|
||||
EphemeralPubKey []byte
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
const msgKexDHReply = 31
|
||||
|
||||
type kexDHReplyMsg struct {
|
||||
HostKey []byte `sshtype:"31"`
|
||||
Y *big.Int
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
// See RFC 4253, section 10.
|
||||
const msgServiceRequest = 5
|
||||
|
||||
type serviceRequestMsg struct {
|
||||
Service string `sshtype:"5"`
|
||||
}
|
||||
|
||||
// See RFC 4253, section 10.
|
||||
const msgServiceAccept = 6
|
||||
|
||||
type serviceAcceptMsg struct {
|
||||
Service string `sshtype:"6"`
|
||||
}
|
||||
|
||||
// See RFC 4252, section 5.
|
||||
const msgUserAuthRequest = 50
|
||||
|
||||
type userAuthRequestMsg struct {
|
||||
User string `sshtype:"50"`
|
||||
Service string
|
||||
Method string
|
||||
Payload []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// Used for debug printouts of packets.
|
||||
type userAuthSuccessMsg struct {
|
||||
}
|
||||
|
||||
// See RFC 4252, section 5.1
|
||||
const msgUserAuthFailure = 51
|
||||
|
||||
type userAuthFailureMsg struct {
|
||||
Methods []string `sshtype:"51"`
|
||||
PartialSuccess bool
|
||||
}
|
||||
|
||||
// See RFC 4256, section 3.2
|
||||
const msgUserAuthInfoRequest = 60
|
||||
const msgUserAuthInfoResponse = 61
|
||||
|
||||
type userAuthInfoRequestMsg struct {
|
||||
User string `sshtype:"60"`
|
||||
Instruction string
|
||||
DeprecatedLanguage string
|
||||
NumPrompts uint32
|
||||
Prompts []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.1.
|
||||
const msgChannelOpen = 90
|
||||
|
||||
type channelOpenMsg struct {
|
||||
ChanType string `sshtype:"90"`
|
||||
PeersId uint32
|
||||
PeersWindow uint32
|
||||
MaxPacketSize uint32
|
||||
TypeSpecificData []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
const msgChannelExtendedData = 95
|
||||
const msgChannelData = 94
|
||||
|
||||
// Used for debug print outs of packets.
|
||||
type channelDataMsg struct {
|
||||
PeersId uint32 `sshtype:"94"`
|
||||
Length uint32
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.1.
|
||||
const msgChannelOpenConfirm = 91
|
||||
|
||||
type channelOpenConfirmMsg struct {
|
||||
PeersId uint32 `sshtype:"91"`
|
||||
MyId uint32
|
||||
MyWindow uint32
|
||||
MaxPacketSize uint32
|
||||
TypeSpecificData []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.1.
|
||||
const msgChannelOpenFailure = 92
|
||||
|
||||
type channelOpenFailureMsg struct {
|
||||
PeersId uint32 `sshtype:"92"`
|
||||
Reason RejectionReason
|
||||
Message string
|
||||
Language string
|
||||
}
|
||||
|
||||
const msgChannelRequest = 98
|
||||
|
||||
type channelRequestMsg struct {
|
||||
PeersId uint32 `sshtype:"98"`
|
||||
Request string
|
||||
WantReply bool
|
||||
RequestSpecificData []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.4.
|
||||
const msgChannelSuccess = 99
|
||||
|
||||
type channelRequestSuccessMsg struct {
|
||||
PeersId uint32 `sshtype:"99"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.4.
|
||||
const msgChannelFailure = 100
|
||||
|
||||
type channelRequestFailureMsg struct {
|
||||
PeersId uint32 `sshtype:"100"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.3
|
||||
const msgChannelClose = 97
|
||||
|
||||
type channelCloseMsg struct {
|
||||
PeersId uint32 `sshtype:"97"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.3
|
||||
const msgChannelEOF = 96
|
||||
|
||||
type channelEOFMsg struct {
|
||||
PeersId uint32 `sshtype:"96"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 4
|
||||
const msgGlobalRequest = 80
|
||||
|
||||
type globalRequestMsg struct {
|
||||
Type string `sshtype:"80"`
|
||||
WantReply bool
|
||||
Data []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 4
|
||||
const msgRequestSuccess = 81
|
||||
|
||||
type globalRequestSuccessMsg struct {
|
||||
Data []byte `ssh:"rest" sshtype:"81"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 4
|
||||
const msgRequestFailure = 82
|
||||
|
||||
type globalRequestFailureMsg struct {
|
||||
Data []byte `ssh:"rest" sshtype:"82"`
|
||||
}
|
||||
|
||||
// See RFC 4254, section 5.2
|
||||
const msgChannelWindowAdjust = 93
|
||||
|
||||
type windowAdjustMsg struct {
|
||||
PeersId uint32 `sshtype:"93"`
|
||||
AdditionalBytes uint32
|
||||
}
|
||||
|
||||
// See RFC 4252, section 7
|
||||
const msgUserAuthPubKeyOk = 60
|
||||
|
||||
type userAuthPubKeyOkMsg struct {
|
||||
Algo string `sshtype:"60"`
|
||||
PubKey []byte
|
||||
}
|
||||
|
||||
// typeTags returns the possible type bytes for the given reflect.Type, which
|
||||
// should be a struct. The possible values are separated by a '|' character.
|
||||
func typeTags(structType reflect.Type) (tags []byte) {
|
||||
tagStr := structType.Field(0).Tag.Get("sshtype")
|
||||
|
||||
for _, tag := range strings.Split(tagStr, "|") {
|
||||
i, err := strconv.Atoi(tag)
|
||||
if err == nil {
|
||||
tags = append(tags, byte(i))
|
||||
}
|
||||
}
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
func fieldError(t reflect.Type, field int, problem string) error {
|
||||
if problem != "" {
|
||||
problem = ": " + problem
|
||||
}
|
||||
return fmt.Errorf("ssh: unmarshal error for field %s of type %s%s", t.Field(field).Name, t.Name(), problem)
|
||||
}
|
||||
|
||||
var errShortRead = errors.New("ssh: short read")
|
||||
|
||||
// Unmarshal parses data in SSH wire format into a structure. The out
|
||||
// argument should be a pointer to struct. If the first member of the
|
||||
// struct has the "sshtype" tag set to a '|'-separated set of numbers
|
||||
// in decimal, the packet must start with one of those numbers. In
|
||||
// case of error, Unmarshal returns a ParseError or
|
||||
// UnexpectedMessageError.
|
||||
func Unmarshal(data []byte, out interface{}) error {
|
||||
v := reflect.ValueOf(out).Elem()
|
||||
structType := v.Type()
|
||||
expectedTypes := typeTags(structType)
|
||||
|
||||
var expectedType byte
|
||||
if len(expectedTypes) > 0 {
|
||||
expectedType = expectedTypes[0]
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return parseError(expectedType)
|
||||
}
|
||||
|
||||
if len(expectedTypes) > 0 {
|
||||
goodType := false
|
||||
for _, e := range expectedTypes {
|
||||
if e > 0 && data[0] == e {
|
||||
goodType = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !goodType {
|
||||
return fmt.Errorf("ssh: unexpected message type %d (expected one of %v)", data[0], expectedTypes)
|
||||
}
|
||||
data = data[1:]
|
||||
}
|
||||
|
||||
var ok bool
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
t := field.Type()
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
if len(data) < 1 {
|
||||
return errShortRead
|
||||
}
|
||||
field.SetBool(data[0] != 0)
|
||||
data = data[1:]
|
||||
case reflect.Array:
|
||||
if t.Elem().Kind() != reflect.Uint8 {
|
||||
return fieldError(structType, i, "array of unsupported type")
|
||||
}
|
||||
if len(data) < t.Len() {
|
||||
return errShortRead
|
||||
}
|
||||
for j, n := 0, t.Len(); j < n; j++ {
|
||||
field.Index(j).Set(reflect.ValueOf(data[j]))
|
||||
}
|
||||
data = data[t.Len():]
|
||||
case reflect.Uint64:
|
||||
var u64 uint64
|
||||
if u64, data, ok = parseUint64(data); !ok {
|
||||
return errShortRead
|
||||
}
|
||||
field.SetUint(u64)
|
||||
case reflect.Uint32:
|
||||
var u32 uint32
|
||||
if u32, data, ok = parseUint32(data); !ok {
|
||||
return errShortRead
|
||||
}
|
||||
field.SetUint(uint64(u32))
|
||||
case reflect.Uint8:
|
||||
if len(data) < 1 {
|
||||
return errShortRead
|
||||
}
|
||||
field.SetUint(uint64(data[0]))
|
||||
data = data[1:]
|
||||
case reflect.String:
|
||||
var s []byte
|
||||
if s, data, ok = parseString(data); !ok {
|
||||
return fieldError(structType, i, "")
|
||||
}
|
||||
field.SetString(string(s))
|
||||
case reflect.Slice:
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Uint8:
|
||||
if structType.Field(i).Tag.Get("ssh") == "rest" {
|
||||
field.Set(reflect.ValueOf(data))
|
||||
data = nil
|
||||
} else {
|
||||
var s []byte
|
||||
if s, data, ok = parseString(data); !ok {
|
||||
return errShortRead
|
||||
}
|
||||
field.Set(reflect.ValueOf(s))
|
||||
}
|
||||
case reflect.String:
|
||||
var nl []string
|
||||
if nl, data, ok = parseNameList(data); !ok {
|
||||
return errShortRead
|
||||
}
|
||||
field.Set(reflect.ValueOf(nl))
|
||||
default:
|
||||
return fieldError(structType, i, "slice of unsupported type")
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if t == bigIntType {
|
||||
var n *big.Int
|
||||
if n, data, ok = parseInt(data); !ok {
|
||||
return errShortRead
|
||||
}
|
||||
field.Set(reflect.ValueOf(n))
|
||||
} else {
|
||||
return fieldError(structType, i, "pointer to unsupported type")
|
||||
}
|
||||
default:
|
||||
return fieldError(structType, i, fmt.Sprintf("unsupported type: %v", t))
|
||||
}
|
||||
}
|
||||
|
||||
if len(data) != 0 {
|
||||
return parseError(expectedType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal serializes the message in msg to SSH wire format. The msg
|
||||
// argument should be a struct or pointer to struct. If the first
|
||||
// member has the "sshtype" tag set to a number in decimal, that
|
||||
// number is prepended to the result. If the last of member has the
|
||||
// "ssh" tag set to "rest", its contents are appended to the output.
|
||||
func Marshal(msg interface{}) []byte {
|
||||
out := make([]byte, 0, 64)
|
||||
return marshalStruct(out, msg)
|
||||
}
|
||||
|
||||
func marshalStruct(out []byte, msg interface{}) []byte {
|
||||
v := reflect.Indirect(reflect.ValueOf(msg))
|
||||
msgTypes := typeTags(v.Type())
|
||||
if len(msgTypes) > 0 {
|
||||
out = append(out, msgTypes[0])
|
||||
}
|
||||
|
||||
for i, n := 0, v.NumField(); i < n; i++ {
|
||||
field := v.Field(i)
|
||||
switch t := field.Type(); t.Kind() {
|
||||
case reflect.Bool:
|
||||
var v uint8
|
||||
if field.Bool() {
|
||||
v = 1
|
||||
}
|
||||
out = append(out, v)
|
||||
case reflect.Array:
|
||||
if t.Elem().Kind() != reflect.Uint8 {
|
||||
panic(fmt.Sprintf("array of non-uint8 in field %d: %T", i, field.Interface()))
|
||||
}
|
||||
for j, l := 0, t.Len(); j < l; j++ {
|
||||
out = append(out, uint8(field.Index(j).Uint()))
|
||||
}
|
||||
case reflect.Uint32:
|
||||
out = appendU32(out, uint32(field.Uint()))
|
||||
case reflect.Uint64:
|
||||
out = appendU64(out, uint64(field.Uint()))
|
||||
case reflect.Uint8:
|
||||
out = append(out, uint8(field.Uint()))
|
||||
case reflect.String:
|
||||
s := field.String()
|
||||
out = appendInt(out, len(s))
|
||||
out = append(out, s...)
|
||||
case reflect.Slice:
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Uint8:
|
||||
if v.Type().Field(i).Tag.Get("ssh") != "rest" {
|
||||
out = appendInt(out, field.Len())
|
||||
}
|
||||
out = append(out, field.Bytes()...)
|
||||
case reflect.String:
|
||||
offset := len(out)
|
||||
out = appendU32(out, 0)
|
||||
if n := field.Len(); n > 0 {
|
||||
for j := 0; j < n; j++ {
|
||||
f := field.Index(j)
|
||||
if j != 0 {
|
||||
out = append(out, ',')
|
||||
}
|
||||
out = append(out, f.String()...)
|
||||
}
|
||||
// overwrite length value
|
||||
binary.BigEndian.PutUint32(out[offset:], uint32(len(out)-offset-4))
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("slice of unknown type in field %d: %T", i, field.Interface()))
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if t == bigIntType {
|
||||
var n *big.Int
|
||||
nValue := reflect.ValueOf(&n)
|
||||
nValue.Elem().Set(field)
|
||||
needed := intLength(n)
|
||||
oldLength := len(out)
|
||||
|
||||
if cap(out)-len(out) < needed {
|
||||
newOut := make([]byte, len(out), 2*(len(out)+needed))
|
||||
copy(newOut, out)
|
||||
out = newOut
|
||||
}
|
||||
out = out[:oldLength+needed]
|
||||
marshalInt(out[oldLength:], n)
|
||||
} else {
|
||||
panic(fmt.Sprintf("pointer to unknown type in field %d: %T", i, field.Interface()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
var bigOne = big.NewInt(1)
|
||||
|
||||
func parseString(in []byte) (out, rest []byte, ok bool) {
|
||||
if len(in) < 4 {
|
||||
return
|
||||
}
|
||||
length := binary.BigEndian.Uint32(in)
|
||||
in = in[4:]
|
||||
if uint32(len(in)) < length {
|
||||
return
|
||||
}
|
||||
out = in[:length]
|
||||
rest = in[length:]
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
comma = []byte{','}
|
||||
emptyNameList = []string{}
|
||||
)
|
||||
|
||||
func parseNameList(in []byte) (out []string, rest []byte, ok bool) {
|
||||
contents, rest, ok := parseString(in)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if len(contents) == 0 {
|
||||
out = emptyNameList
|
||||
return
|
||||
}
|
||||
parts := bytes.Split(contents, comma)
|
||||
out = make([]string, len(parts))
|
||||
for i, part := range parts {
|
||||
out[i] = string(part)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseInt(in []byte) (out *big.Int, rest []byte, ok bool) {
|
||||
contents, rest, ok := parseString(in)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
out = new(big.Int)
|
||||
|
||||
if len(contents) > 0 && contents[0]&0x80 == 0x80 {
|
||||
// This is a negative number
|
||||
notBytes := make([]byte, len(contents))
|
||||
for i := range notBytes {
|
||||
notBytes[i] = ^contents[i]
|
||||
}
|
||||
out.SetBytes(notBytes)
|
||||
out.Add(out, bigOne)
|
||||
out.Neg(out)
|
||||
} else {
|
||||
// Positive number
|
||||
out.SetBytes(contents)
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
func parseUint32(in []byte) (uint32, []byte, bool) {
|
||||
if len(in) < 4 {
|
||||
return 0, nil, false
|
||||
}
|
||||
return binary.BigEndian.Uint32(in), in[4:], true
|
||||
}
|
||||
|
||||
func parseUint64(in []byte) (uint64, []byte, bool) {
|
||||
if len(in) < 8 {
|
||||
return 0, nil, false
|
||||
}
|
||||
return binary.BigEndian.Uint64(in), in[8:], true
|
||||
}
|
||||
|
||||
func intLength(n *big.Int) int {
|
||||
length := 4 /* length bytes */
|
||||
if n.Sign() < 0 {
|
||||
nMinus1 := new(big.Int).Neg(n)
|
||||
nMinus1.Sub(nMinus1, bigOne)
|
||||
bitLen := nMinus1.BitLen()
|
||||
if bitLen%8 == 0 {
|
||||
// The number will need 0xff padding
|
||||
length++
|
||||
}
|
||||
length += (bitLen + 7) / 8
|
||||
} else if n.Sign() == 0 {
|
||||
// A zero is the zero length string
|
||||
} else {
|
||||
bitLen := n.BitLen()
|
||||
if bitLen%8 == 0 {
|
||||
// The number will need 0x00 padding
|
||||
length++
|
||||
}
|
||||
length += (bitLen + 7) / 8
|
||||
}
|
||||
|
||||
return length
|
||||
}
|
||||
|
||||
func marshalUint32(to []byte, n uint32) []byte {
|
||||
binary.BigEndian.PutUint32(to, n)
|
||||
return to[4:]
|
||||
}
|
||||
|
||||
func marshalUint64(to []byte, n uint64) []byte {
|
||||
binary.BigEndian.PutUint64(to, n)
|
||||
return to[8:]
|
||||
}
|
||||
|
||||
func marshalInt(to []byte, n *big.Int) []byte {
|
||||
lengthBytes := to
|
||||
to = to[4:]
|
||||
length := 0
|
||||
|
||||
if n.Sign() < 0 {
|
||||
// A negative number has to be converted to two's-complement
|
||||
// form. So we'll subtract 1 and invert. If the
|
||||
// most-significant-bit isn't set then we'll need to pad the
|
||||
// beginning with 0xff in order to keep the number negative.
|
||||
nMinus1 := new(big.Int).Neg(n)
|
||||
nMinus1.Sub(nMinus1, bigOne)
|
||||
bytes := nMinus1.Bytes()
|
||||
for i := range bytes {
|
||||
bytes[i] ^= 0xff
|
||||
}
|
||||
if len(bytes) == 0 || bytes[0]&0x80 == 0 {
|
||||
to[0] = 0xff
|
||||
to = to[1:]
|
||||
length++
|
||||
}
|
||||
nBytes := copy(to, bytes)
|
||||
to = to[nBytes:]
|
||||
length += nBytes
|
||||
} else if n.Sign() == 0 {
|
||||
// A zero is the zero length string
|
||||
} else {
|
||||
bytes := n.Bytes()
|
||||
if len(bytes) > 0 && bytes[0]&0x80 != 0 {
|
||||
// We'll have to pad this with a 0x00 in order to
|
||||
// stop it looking like a negative number.
|
||||
to[0] = 0
|
||||
to = to[1:]
|
||||
length++
|
||||
}
|
||||
nBytes := copy(to, bytes)
|
||||
to = to[nBytes:]
|
||||
length += nBytes
|
||||
}
|
||||
|
||||
lengthBytes[0] = byte(length >> 24)
|
||||
lengthBytes[1] = byte(length >> 16)
|
||||
lengthBytes[2] = byte(length >> 8)
|
||||
lengthBytes[3] = byte(length)
|
||||
return to
|
||||
}
|
||||
|
||||
func writeInt(w io.Writer, n *big.Int) {
|
||||
length := intLength(n)
|
||||
buf := make([]byte, length)
|
||||
marshalInt(buf, n)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
func writeString(w io.Writer, s []byte) {
|
||||
var lengthBytes [4]byte
|
||||
lengthBytes[0] = byte(len(s) >> 24)
|
||||
lengthBytes[1] = byte(len(s) >> 16)
|
||||
lengthBytes[2] = byte(len(s) >> 8)
|
||||
lengthBytes[3] = byte(len(s))
|
||||
w.Write(lengthBytes[:])
|
||||
w.Write(s)
|
||||
}
|
||||
|
||||
func stringLength(n int) int {
|
||||
return 4 + n
|
||||
}
|
||||
|
||||
func marshalString(to []byte, s []byte) []byte {
|
||||
to[0] = byte(len(s) >> 24)
|
||||
to[1] = byte(len(s) >> 16)
|
||||
to[2] = byte(len(s) >> 8)
|
||||
to[3] = byte(len(s))
|
||||
to = to[4:]
|
||||
copy(to, s)
|
||||
return to[len(s):]
|
||||
}
|
||||
|
||||
var bigIntType = reflect.TypeOf((*big.Int)(nil))
|
||||
|
||||
// Decode a packet into its corresponding message.
|
||||
func decode(packet []byte) (interface{}, error) {
|
||||
var msg interface{}
|
||||
switch packet[0] {
|
||||
case msgDisconnect:
|
||||
msg = new(disconnectMsg)
|
||||
case msgServiceRequest:
|
||||
msg = new(serviceRequestMsg)
|
||||
case msgServiceAccept:
|
||||
msg = new(serviceAcceptMsg)
|
||||
case msgKexInit:
|
||||
msg = new(kexInitMsg)
|
||||
case msgKexDHInit:
|
||||
msg = new(kexDHInitMsg)
|
||||
case msgKexDHReply:
|
||||
msg = new(kexDHReplyMsg)
|
||||
case msgUserAuthRequest:
|
||||
msg = new(userAuthRequestMsg)
|
||||
case msgUserAuthSuccess:
|
||||
return new(userAuthSuccessMsg), nil
|
||||
case msgUserAuthFailure:
|
||||
msg = new(userAuthFailureMsg)
|
||||
case msgUserAuthPubKeyOk:
|
||||
msg = new(userAuthPubKeyOkMsg)
|
||||
case msgGlobalRequest:
|
||||
msg = new(globalRequestMsg)
|
||||
case msgRequestSuccess:
|
||||
msg = new(globalRequestSuccessMsg)
|
||||
case msgRequestFailure:
|
||||
msg = new(globalRequestFailureMsg)
|
||||
case msgChannelOpen:
|
||||
msg = new(channelOpenMsg)
|
||||
case msgChannelData:
|
||||
msg = new(channelDataMsg)
|
||||
case msgChannelOpenConfirm:
|
||||
msg = new(channelOpenConfirmMsg)
|
||||
case msgChannelOpenFailure:
|
||||
msg = new(channelOpenFailureMsg)
|
||||
case msgChannelWindowAdjust:
|
||||
msg = new(windowAdjustMsg)
|
||||
case msgChannelEOF:
|
||||
msg = new(channelEOFMsg)
|
||||
case msgChannelClose:
|
||||
msg = new(channelCloseMsg)
|
||||
case msgChannelRequest:
|
||||
msg = new(channelRequestMsg)
|
||||
case msgChannelSuccess:
|
||||
msg = new(channelRequestSuccessMsg)
|
||||
case msgChannelFailure:
|
||||
msg = new(channelRequestFailureMsg)
|
||||
default:
|
||||
return nil, unexpectedMessageError(0, packet[0])
|
||||
}
|
||||
if err := Unmarshal(packet, msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
|
@ -1,288 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
)
|
||||
|
||||
var intLengthTests = []struct {
|
||||
val, length int
|
||||
}{
|
||||
{0, 4 + 0},
|
||||
{1, 4 + 1},
|
||||
{127, 4 + 1},
|
||||
{128, 4 + 2},
|
||||
{-1, 4 + 1},
|
||||
}
|
||||
|
||||
func TestIntLength(t *testing.T) {
|
||||
for _, test := range intLengthTests {
|
||||
v := new(big.Int).SetInt64(int64(test.val))
|
||||
length := intLength(v)
|
||||
if length != test.length {
|
||||
t.Errorf("For %d, got length %d but expected %d", test.val, length, test.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type msgAllTypes struct {
|
||||
Bool bool `sshtype:"21"`
|
||||
Array [16]byte
|
||||
Uint64 uint64
|
||||
Uint32 uint32
|
||||
Uint8 uint8
|
||||
String string
|
||||
Strings []string
|
||||
Bytes []byte
|
||||
Int *big.Int
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
func (t *msgAllTypes) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
m := &msgAllTypes{}
|
||||
m.Bool = rand.Intn(2) == 1
|
||||
randomBytes(m.Array[:], rand)
|
||||
m.Uint64 = uint64(rand.Int63n(1<<63 - 1))
|
||||
m.Uint32 = uint32(rand.Intn((1 << 31) - 1))
|
||||
m.Uint8 = uint8(rand.Intn(1 << 8))
|
||||
m.String = string(m.Array[:])
|
||||
m.Strings = randomNameList(rand)
|
||||
m.Bytes = m.Array[:]
|
||||
m.Int = randomInt(rand)
|
||||
m.Rest = m.Array[:]
|
||||
return reflect.ValueOf(m)
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshal(t *testing.T) {
|
||||
rand := rand.New(rand.NewSource(0))
|
||||
iface := &msgAllTypes{}
|
||||
ty := reflect.ValueOf(iface).Type()
|
||||
|
||||
n := 100
|
||||
if testing.Short() {
|
||||
n = 5
|
||||
}
|
||||
for j := 0; j < n; j++ {
|
||||
v, ok := quick.Value(ty, rand)
|
||||
if !ok {
|
||||
t.Errorf("failed to create value")
|
||||
break
|
||||
}
|
||||
|
||||
m1 := v.Elem().Interface()
|
||||
m2 := iface
|
||||
|
||||
marshaled := Marshal(m1)
|
||||
if err := Unmarshal(marshaled, m2); err != nil {
|
||||
t.Errorf("Unmarshal %#v: %s", m1, err)
|
||||
break
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(v.Interface(), m2) {
|
||||
t.Errorf("got: %#v\nwant:%#v\n%x", m2, m1, marshaled)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalEmptyPacket(t *testing.T) {
|
||||
var b []byte
|
||||
var m channelRequestSuccessMsg
|
||||
if err := Unmarshal(b, &m); err == nil {
|
||||
t.Fatalf("unmarshal of empty slice succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalUnexpectedPacket(t *testing.T) {
|
||||
type S struct {
|
||||
I uint32 `sshtype:"43"`
|
||||
S string
|
||||
B bool
|
||||
}
|
||||
|
||||
s := S{11, "hello", true}
|
||||
packet := Marshal(s)
|
||||
packet[0] = 42
|
||||
roundtrip := S{}
|
||||
err := Unmarshal(packet, &roundtrip)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, not nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalPtr(t *testing.T) {
|
||||
s := struct {
|
||||
S string
|
||||
}{"hello"}
|
||||
|
||||
m1 := Marshal(s)
|
||||
m2 := Marshal(&s)
|
||||
if !bytes.Equal(m1, m2) {
|
||||
t.Errorf("got %q, want %q for marshaled pointer", m2, m1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBareMarshalUnmarshal(t *testing.T) {
|
||||
type S struct {
|
||||
I uint32
|
||||
S string
|
||||
B bool
|
||||
}
|
||||
|
||||
s := S{42, "hello", true}
|
||||
packet := Marshal(s)
|
||||
roundtrip := S{}
|
||||
Unmarshal(packet, &roundtrip)
|
||||
|
||||
if !reflect.DeepEqual(s, roundtrip) {
|
||||
t.Errorf("got %#v, want %#v", roundtrip, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBareMarshal(t *testing.T) {
|
||||
type S2 struct {
|
||||
I uint32
|
||||
}
|
||||
s := S2{42}
|
||||
packet := Marshal(s)
|
||||
i, rest, ok := parseUint32(packet)
|
||||
if len(rest) > 0 || !ok {
|
||||
t.Errorf("parseInt(%q): parse error", packet)
|
||||
}
|
||||
if i != s.I {
|
||||
t.Errorf("got %d, want %d", i, s.I)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalShortKexInitPacket(t *testing.T) {
|
||||
// This used to panic.
|
||||
// Issue 11348
|
||||
packet := []byte{0x14, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xff, 0xff, 0xff, 0xff}
|
||||
kim := &kexInitMsg{}
|
||||
if err := Unmarshal(packet, kim); err == nil {
|
||||
t.Error("truncated packet unmarshaled without error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalMultiTag(t *testing.T) {
|
||||
var res struct {
|
||||
A uint32 `sshtype:"1|2"`
|
||||
}
|
||||
|
||||
good1 := struct {
|
||||
A uint32 `sshtype:"1"`
|
||||
}{
|
||||
1,
|
||||
}
|
||||
good2 := struct {
|
||||
A uint32 `sshtype:"2"`
|
||||
}{
|
||||
1,
|
||||
}
|
||||
|
||||
if e := Unmarshal(Marshal(good1), &res); e != nil {
|
||||
t.Errorf("error unmarshaling multipart tag: %v", e)
|
||||
}
|
||||
|
||||
if e := Unmarshal(Marshal(good2), &res); e != nil {
|
||||
t.Errorf("error unmarshaling multipart tag: %v", e)
|
||||
}
|
||||
|
||||
bad1 := struct {
|
||||
A uint32 `sshtype:"3"`
|
||||
}{
|
||||
1,
|
||||
}
|
||||
if e := Unmarshal(Marshal(bad1), &res); e == nil {
|
||||
t.Errorf("bad struct unmarshaled without error")
|
||||
}
|
||||
}
|
||||
|
||||
func randomBytes(out []byte, rand *rand.Rand) {
|
||||
for i := 0; i < len(out); i++ {
|
||||
out[i] = byte(rand.Int31())
|
||||
}
|
||||
}
|
||||
|
||||
func randomNameList(rand *rand.Rand) []string {
|
||||
ret := make([]string, rand.Int31()&15)
|
||||
for i := range ret {
|
||||
s := make([]byte, 1+(rand.Int31()&15))
|
||||
for j := range s {
|
||||
s[j] = 'a' + uint8(rand.Int31()&15)
|
||||
}
|
||||
ret[i] = string(s)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func randomInt(rand *rand.Rand) *big.Int {
|
||||
return new(big.Int).SetInt64(int64(int32(rand.Uint32())))
|
||||
}
|
||||
|
||||
func (*kexInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
ki := &kexInitMsg{}
|
||||
randomBytes(ki.Cookie[:], rand)
|
||||
ki.KexAlgos = randomNameList(rand)
|
||||
ki.ServerHostKeyAlgos = randomNameList(rand)
|
||||
ki.CiphersClientServer = randomNameList(rand)
|
||||
ki.CiphersServerClient = randomNameList(rand)
|
||||
ki.MACsClientServer = randomNameList(rand)
|
||||
ki.MACsServerClient = randomNameList(rand)
|
||||
ki.CompressionClientServer = randomNameList(rand)
|
||||
ki.CompressionServerClient = randomNameList(rand)
|
||||
ki.LanguagesClientServer = randomNameList(rand)
|
||||
ki.LanguagesServerClient = randomNameList(rand)
|
||||
if rand.Int31()&1 == 1 {
|
||||
ki.FirstKexFollows = true
|
||||
}
|
||||
return reflect.ValueOf(ki)
|
||||
}
|
||||
|
||||
func (*kexDHInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
dhi := &kexDHInitMsg{}
|
||||
dhi.X = randomInt(rand)
|
||||
return reflect.ValueOf(dhi)
|
||||
}
|
||||
|
||||
var (
|
||||
_kexInitMsg = new(kexInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface()
|
||||
_kexDHInitMsg = new(kexDHInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface()
|
||||
|
||||
_kexInit = Marshal(_kexInitMsg)
|
||||
_kexDHInit = Marshal(_kexDHInitMsg)
|
||||
)
|
||||
|
||||
func BenchmarkMarshalKexInitMsg(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Marshal(_kexInitMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalKexInitMsg(b *testing.B) {
|
||||
m := new(kexInitMsg)
|
||||
for i := 0; i < b.N; i++ {
|
||||
Unmarshal(_kexInit, m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalKexDHInitMsg(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Marshal(_kexDHInitMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalKexDHInitMsg(b *testing.B) {
|
||||
m := new(kexDHInitMsg)
|
||||
for i := 0; i < b.N; i++ {
|
||||
Unmarshal(_kexDHInit, m)
|
||||
}
|
||||
}
|
||||
330
ssh/mux.go
330
ssh/mux.go
|
|
@ -1,330 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// debugMux, if set, causes messages in the connection protocol to be
|
||||
// logged.
|
||||
const debugMux = false
|
||||
|
||||
// chanList is a thread safe channel list.
|
||||
type chanList struct {
|
||||
// protects concurrent access to chans
|
||||
sync.Mutex
|
||||
|
||||
// chans are indexed by the local id of the channel, which the
|
||||
// other side should send in the PeersId field.
|
||||
chans []*channel
|
||||
|
||||
// This is a debugging aid: it offsets all IDs by this
|
||||
// amount. This helps distinguish otherwise identical
|
||||
// server/client muxes
|
||||
offset uint32
|
||||
}
|
||||
|
||||
// Assigns a channel ID to the given channel.
|
||||
func (c *chanList) add(ch *channel) uint32 {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
for i := range c.chans {
|
||||
if c.chans[i] == nil {
|
||||
c.chans[i] = ch
|
||||
return uint32(i) + c.offset
|
||||
}
|
||||
}
|
||||
c.chans = append(c.chans, ch)
|
||||
return uint32(len(c.chans)-1) + c.offset
|
||||
}
|
||||
|
||||
// getChan returns the channel for the given ID.
|
||||
func (c *chanList) getChan(id uint32) *channel {
|
||||
id -= c.offset
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if id < uint32(len(c.chans)) {
|
||||
return c.chans[id]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chanList) remove(id uint32) {
|
||||
id -= c.offset
|
||||
c.Lock()
|
||||
if id < uint32(len(c.chans)) {
|
||||
c.chans[id] = nil
|
||||
}
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// dropAll forgets all channels it knows, returning them in a slice.
|
||||
func (c *chanList) dropAll() []*channel {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
var r []*channel
|
||||
|
||||
for _, ch := range c.chans {
|
||||
if ch == nil {
|
||||
continue
|
||||
}
|
||||
r = append(r, ch)
|
||||
}
|
||||
c.chans = nil
|
||||
return r
|
||||
}
|
||||
|
||||
// mux represents the state for the SSH connection protocol, which
|
||||
// multiplexes many channels onto a single packet transport.
|
||||
type mux struct {
|
||||
conn packetConn
|
||||
chanList chanList
|
||||
|
||||
incomingChannels chan NewChannel
|
||||
|
||||
globalSentMu sync.Mutex
|
||||
globalResponses chan interface{}
|
||||
incomingRequests chan *Request
|
||||
|
||||
errCond *sync.Cond
|
||||
err error
|
||||
}
|
||||
|
||||
// When debugging, each new chanList instantiation has a different
|
||||
// offset.
|
||||
var globalOff uint32
|
||||
|
||||
func (m *mux) Wait() error {
|
||||
m.errCond.L.Lock()
|
||||
defer m.errCond.L.Unlock()
|
||||
for m.err == nil {
|
||||
m.errCond.Wait()
|
||||
}
|
||||
return m.err
|
||||
}
|
||||
|
||||
// newMux returns a mux that runs over the given connection.
|
||||
func newMux(p packetConn) *mux {
|
||||
m := &mux{
|
||||
conn: p,
|
||||
incomingChannels: make(chan NewChannel, chanSize),
|
||||
globalResponses: make(chan interface{}, 1),
|
||||
incomingRequests: make(chan *Request, chanSize),
|
||||
errCond: newCond(),
|
||||
}
|
||||
if debugMux {
|
||||
m.chanList.offset = atomic.AddUint32(&globalOff, 1)
|
||||
}
|
||||
|
||||
go m.loop()
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *mux) sendMessage(msg interface{}) error {
|
||||
p := Marshal(msg)
|
||||
if debugMux {
|
||||
log.Printf("send global(%d): %#v", m.chanList.offset, msg)
|
||||
}
|
||||
return m.conn.writePacket(p)
|
||||
}
|
||||
|
||||
func (m *mux) SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) {
|
||||
if wantReply {
|
||||
m.globalSentMu.Lock()
|
||||
defer m.globalSentMu.Unlock()
|
||||
}
|
||||
|
||||
if err := m.sendMessage(globalRequestMsg{
|
||||
Type: name,
|
||||
WantReply: wantReply,
|
||||
Data: payload,
|
||||
}); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if !wantReply {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
msg, ok := <-m.globalResponses
|
||||
if !ok {
|
||||
return false, nil, io.EOF
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case *globalRequestFailureMsg:
|
||||
return false, msg.Data, nil
|
||||
case *globalRequestSuccessMsg:
|
||||
return true, msg.Data, nil
|
||||
default:
|
||||
return false, nil, fmt.Errorf("ssh: unexpected response to request: %#v", msg)
|
||||
}
|
||||
}
|
||||
|
||||
// ackRequest must be called after processing a global request that
|
||||
// has WantReply set.
|
||||
func (m *mux) ackRequest(ok bool, data []byte) error {
|
||||
if ok {
|
||||
return m.sendMessage(globalRequestSuccessMsg{Data: data})
|
||||
}
|
||||
return m.sendMessage(globalRequestFailureMsg{Data: data})
|
||||
}
|
||||
|
||||
func (m *mux) Close() error {
|
||||
return m.conn.Close()
|
||||
}
|
||||
|
||||
// loop runs the connection machine. It will process packets until an
|
||||
// error is encountered. To synchronize on loop exit, use mux.Wait.
|
||||
func (m *mux) loop() {
|
||||
var err error
|
||||
for err == nil {
|
||||
err = m.onePacket()
|
||||
}
|
||||
|
||||
for _, ch := range m.chanList.dropAll() {
|
||||
ch.close()
|
||||
}
|
||||
|
||||
close(m.incomingChannels)
|
||||
close(m.incomingRequests)
|
||||
close(m.globalResponses)
|
||||
|
||||
m.conn.Close()
|
||||
|
||||
m.errCond.L.Lock()
|
||||
m.err = err
|
||||
m.errCond.Broadcast()
|
||||
m.errCond.L.Unlock()
|
||||
|
||||
if debugMux {
|
||||
log.Println("loop exit", err)
|
||||
}
|
||||
}
|
||||
|
||||
// onePacket reads and processes one packet.
|
||||
func (m *mux) onePacket() error {
|
||||
packet, err := m.conn.readPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if debugMux {
|
||||
if packet[0] == msgChannelData || packet[0] == msgChannelExtendedData {
|
||||
log.Printf("decoding(%d): data packet - %d bytes", m.chanList.offset, len(packet))
|
||||
} else {
|
||||
p, _ := decode(packet)
|
||||
log.Printf("decoding(%d): %d %#v - %d bytes", m.chanList.offset, packet[0], p, len(packet))
|
||||
}
|
||||
}
|
||||
|
||||
switch packet[0] {
|
||||
case msgChannelOpen:
|
||||
return m.handleChannelOpen(packet)
|
||||
case msgGlobalRequest, msgRequestSuccess, msgRequestFailure:
|
||||
return m.handleGlobalPacket(packet)
|
||||
}
|
||||
|
||||
// assume a channel packet.
|
||||
if len(packet) < 5 {
|
||||
return parseError(packet[0])
|
||||
}
|
||||
id := binary.BigEndian.Uint32(packet[1:])
|
||||
ch := m.chanList.getChan(id)
|
||||
if ch == nil {
|
||||
return fmt.Errorf("ssh: invalid channel %d", id)
|
||||
}
|
||||
|
||||
return ch.handlePacket(packet)
|
||||
}
|
||||
|
||||
func (m *mux) handleGlobalPacket(packet []byte) error {
|
||||
msg, err := decode(packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *globalRequestMsg:
|
||||
m.incomingRequests <- &Request{
|
||||
Type: msg.Type,
|
||||
WantReply: msg.WantReply,
|
||||
Payload: msg.Data,
|
||||
mux: m,
|
||||
}
|
||||
case *globalRequestSuccessMsg, *globalRequestFailureMsg:
|
||||
m.globalResponses <- msg
|
||||
default:
|
||||
panic(fmt.Sprintf("not a global message %#v", msg))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleChannelOpen schedules a channel to be Accept()ed.
|
||||
func (m *mux) handleChannelOpen(packet []byte) error {
|
||||
var msg channelOpenMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
||||
failMsg := channelOpenFailureMsg{
|
||||
PeersId: msg.PeersId,
|
||||
Reason: ConnectionFailed,
|
||||
Message: "invalid request",
|
||||
Language: "en_US.UTF-8",
|
||||
}
|
||||
return m.sendMessage(failMsg)
|
||||
}
|
||||
|
||||
c := m.newChannel(msg.ChanType, channelInbound, msg.TypeSpecificData)
|
||||
c.remoteId = msg.PeersId
|
||||
c.maxRemotePayload = msg.MaxPacketSize
|
||||
c.remoteWin.add(msg.PeersWindow)
|
||||
m.incomingChannels <- c
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mux) OpenChannel(chanType string, extra []byte) (Channel, <-chan *Request, error) {
|
||||
ch, err := m.openChannel(chanType, extra)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return ch, ch.incomingRequests, nil
|
||||
}
|
||||
|
||||
func (m *mux) openChannel(chanType string, extra []byte) (*channel, error) {
|
||||
ch := m.newChannel(chanType, channelOutbound, extra)
|
||||
|
||||
ch.maxIncomingPayload = channelMaxPacket
|
||||
|
||||
open := channelOpenMsg{
|
||||
ChanType: chanType,
|
||||
PeersWindow: ch.myWindow,
|
||||
MaxPacketSize: ch.maxIncomingPayload,
|
||||
TypeSpecificData: extra,
|
||||
PeersId: ch.localId,
|
||||
}
|
||||
if err := m.sendMessage(open); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch msg := (<-ch.msg).(type) {
|
||||
case *channelOpenConfirmMsg:
|
||||
return ch, nil
|
||||
case *channelOpenFailureMsg:
|
||||
return nil, &OpenChannelError{msg.Reason, msg.Message}
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unexpected packet in response to channel open: %T", msg)
|
||||
}
|
||||
}
|
||||
505
ssh/mux_test.go
505
ssh/mux_test.go
|
|
@ -1,505 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func muxPair() (*mux, *mux) {
|
||||
a, b := memPipe()
|
||||
|
||||
s := newMux(a)
|
||||
c := newMux(b)
|
||||
|
||||
return s, c
|
||||
}
|
||||
|
||||
// Returns both ends of a channel, and the mux for the the 2nd
|
||||
// channel.
|
||||
func channelPair(t *testing.T) (*channel, *channel, *mux) {
|
||||
c, s := muxPair()
|
||||
|
||||
res := make(chan *channel, 1)
|
||||
go func() {
|
||||
newCh, ok := <-s.incomingChannels
|
||||
if !ok {
|
||||
t.Fatalf("No incoming channel")
|
||||
}
|
||||
if newCh.ChannelType() != "chan" {
|
||||
t.Fatalf("got type %q want chan", newCh.ChannelType())
|
||||
}
|
||||
ch, _, err := newCh.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("Accept %v", err)
|
||||
}
|
||||
res <- ch.(*channel)
|
||||
}()
|
||||
|
||||
ch, err := c.openChannel("chan", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenChannel: %v", err)
|
||||
}
|
||||
|
||||
return <-res, ch, c
|
||||
}
|
||||
|
||||
// Test that stderr and stdout can be addressed from different
|
||||
// goroutines. This is intended for use with the race detector.
|
||||
func TestMuxChannelExtendedThreadSafety(t *testing.T) {
|
||||
writer, reader, mux := channelPair(t)
|
||||
defer writer.Close()
|
||||
defer reader.Close()
|
||||
defer mux.Close()
|
||||
|
||||
var wr, rd sync.WaitGroup
|
||||
magic := "hello world"
|
||||
|
||||
wr.Add(2)
|
||||
go func() {
|
||||
io.WriteString(writer, magic)
|
||||
wr.Done()
|
||||
}()
|
||||
go func() {
|
||||
io.WriteString(writer.Stderr(), magic)
|
||||
wr.Done()
|
||||
}()
|
||||
|
||||
rd.Add(2)
|
||||
go func() {
|
||||
c, err := ioutil.ReadAll(reader)
|
||||
if string(c) != magic {
|
||||
t.Fatalf("stdout read got %q, want %q (error %s)", c, magic, err)
|
||||
}
|
||||
rd.Done()
|
||||
}()
|
||||
go func() {
|
||||
c, err := ioutil.ReadAll(reader.Stderr())
|
||||
if string(c) != magic {
|
||||
t.Fatalf("stderr read got %q, want %q (error %s)", c, magic, err)
|
||||
}
|
||||
rd.Done()
|
||||
}()
|
||||
|
||||
wr.Wait()
|
||||
writer.CloseWrite()
|
||||
rd.Wait()
|
||||
}
|
||||
|
||||
func TestMuxReadWrite(t *testing.T) {
|
||||
s, c, mux := channelPair(t)
|
||||
defer s.Close()
|
||||
defer c.Close()
|
||||
defer mux.Close()
|
||||
|
||||
magic := "hello world"
|
||||
magicExt := "hello stderr"
|
||||
go func() {
|
||||
_, err := s.Write([]byte(magic))
|
||||
if err != nil {
|
||||
t.Fatalf("Write: %v", err)
|
||||
}
|
||||
_, err = s.Extended(1).Write([]byte(magicExt))
|
||||
if err != nil {
|
||||
t.Fatalf("Write: %v", err)
|
||||
}
|
||||
err = s.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Close: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
var buf [1024]byte
|
||||
n, err := c.Read(buf[:])
|
||||
if err != nil {
|
||||
t.Fatalf("server Read: %v", err)
|
||||
}
|
||||
got := string(buf[:n])
|
||||
if got != magic {
|
||||
t.Fatalf("server: got %q want %q", got, magic)
|
||||
}
|
||||
|
||||
n, err = c.Extended(1).Read(buf[:])
|
||||
if err != nil {
|
||||
t.Fatalf("server Read: %v", err)
|
||||
}
|
||||
|
||||
got = string(buf[:n])
|
||||
if got != magicExt {
|
||||
t.Fatalf("server: got %q want %q", got, magic)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxChannelOverflow(t *testing.T) {
|
||||
reader, writer, mux := channelPair(t)
|
||||
defer reader.Close()
|
||||
defer writer.Close()
|
||||
defer mux.Close()
|
||||
|
||||
wDone := make(chan int, 1)
|
||||
go func() {
|
||||
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
|
||||
t.Errorf("could not fill window: %v", err)
|
||||
}
|
||||
writer.Write(make([]byte, 1))
|
||||
wDone <- 1
|
||||
}()
|
||||
writer.remoteWin.waitWriterBlocked()
|
||||
|
||||
// Send 1 byte.
|
||||
packet := make([]byte, 1+4+4+1)
|
||||
packet[0] = msgChannelData
|
||||
marshalUint32(packet[1:], writer.remoteId)
|
||||
marshalUint32(packet[5:], uint32(1))
|
||||
packet[9] = 42
|
||||
|
||||
if err := writer.mux.conn.writePacket(packet); err != nil {
|
||||
t.Errorf("could not send packet")
|
||||
}
|
||||
if _, err := reader.SendRequest("hello", true, nil); err == nil {
|
||||
t.Errorf("SendRequest succeeded.")
|
||||
}
|
||||
<-wDone
|
||||
}
|
||||
|
||||
func TestMuxChannelCloseWriteUnblock(t *testing.T) {
|
||||
reader, writer, mux := channelPair(t)
|
||||
defer reader.Close()
|
||||
defer writer.Close()
|
||||
defer mux.Close()
|
||||
|
||||
wDone := make(chan int, 1)
|
||||
go func() {
|
||||
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
|
||||
t.Errorf("could not fill window: %v", err)
|
||||
}
|
||||
if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
|
||||
t.Errorf("got %v, want EOF for unblock write", err)
|
||||
}
|
||||
wDone <- 1
|
||||
}()
|
||||
|
||||
writer.remoteWin.waitWriterBlocked()
|
||||
reader.Close()
|
||||
<-wDone
|
||||
}
|
||||
|
||||
func TestMuxConnectionCloseWriteUnblock(t *testing.T) {
|
||||
reader, writer, mux := channelPair(t)
|
||||
defer reader.Close()
|
||||
defer writer.Close()
|
||||
defer mux.Close()
|
||||
|
||||
wDone := make(chan int, 1)
|
||||
go func() {
|
||||
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
|
||||
t.Errorf("could not fill window: %v", err)
|
||||
}
|
||||
if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
|
||||
t.Errorf("got %v, want EOF for unblock write", err)
|
||||
}
|
||||
wDone <- 1
|
||||
}()
|
||||
|
||||
writer.remoteWin.waitWriterBlocked()
|
||||
mux.Close()
|
||||
<-wDone
|
||||
}
|
||||
|
||||
func TestMuxReject(t *testing.T) {
|
||||
client, server := muxPair()
|
||||
defer server.Close()
|
||||
defer client.Close()
|
||||
|
||||
go func() {
|
||||
ch, ok := <-server.incomingChannels
|
||||
if !ok {
|
||||
t.Fatalf("Accept")
|
||||
}
|
||||
if ch.ChannelType() != "ch" || string(ch.ExtraData()) != "extra" {
|
||||
t.Fatalf("unexpected channel: %q, %q", ch.ChannelType(), ch.ExtraData())
|
||||
}
|
||||
ch.Reject(RejectionReason(42), "message")
|
||||
}()
|
||||
|
||||
ch, err := client.openChannel("ch", []byte("extra"))
|
||||
if ch != nil {
|
||||
t.Fatal("openChannel not rejected")
|
||||
}
|
||||
|
||||
ocf, ok := err.(*OpenChannelError)
|
||||
if !ok {
|
||||
t.Errorf("got %#v want *OpenChannelError", err)
|
||||
} else if ocf.Reason != 42 || ocf.Message != "message" {
|
||||
t.Errorf("got %#v, want {Reason: 42, Message: %q}", ocf, "message")
|
||||
}
|
||||
|
||||
want := "ssh: rejected: unknown reason 42 (message)"
|
||||
if err.Error() != want {
|
||||
t.Errorf("got %q, want %q", err.Error(), want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxChannelRequest(t *testing.T) {
|
||||
client, server, mux := channelPair(t)
|
||||
defer server.Close()
|
||||
defer client.Close()
|
||||
defer mux.Close()
|
||||
|
||||
var received int
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for r := range server.incomingRequests {
|
||||
received++
|
||||
r.Reply(r.Type == "yes", nil)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
_, err := client.SendRequest("yes", false, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SendRequest: %v", err)
|
||||
}
|
||||
ok, err := client.SendRequest("yes", true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SendRequest: %v", err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
t.Errorf("SendRequest(yes): %v", ok)
|
||||
|
||||
}
|
||||
|
||||
ok, err = client.SendRequest("no", true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SendRequest: %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Errorf("SendRequest(no): %v", ok)
|
||||
|
||||
}
|
||||
|
||||
client.Close()
|
||||
wg.Wait()
|
||||
|
||||
if received != 3 {
|
||||
t.Errorf("got %d requests, want %d", received, 3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxGlobalRequest(t *testing.T) {
|
||||
clientMux, serverMux := muxPair()
|
||||
defer serverMux.Close()
|
||||
defer clientMux.Close()
|
||||
|
||||
var seen bool
|
||||
go func() {
|
||||
for r := range serverMux.incomingRequests {
|
||||
seen = seen || r.Type == "peek"
|
||||
if r.WantReply {
|
||||
err := r.Reply(r.Type == "yes",
|
||||
append([]byte(r.Type), r.Payload...))
|
||||
if err != nil {
|
||||
t.Errorf("AckRequest: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
_, _, err := clientMux.SendRequest("peek", false, nil)
|
||||
if err != nil {
|
||||
t.Errorf("SendRequest: %v", err)
|
||||
}
|
||||
|
||||
ok, data, err := clientMux.SendRequest("yes", true, []byte("a"))
|
||||
if !ok || string(data) != "yesa" || err != nil {
|
||||
t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v",
|
||||
ok, data, err)
|
||||
}
|
||||
if ok, data, err := clientMux.SendRequest("yes", true, []byte("a")); !ok || string(data) != "yesa" || err != nil {
|
||||
t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v",
|
||||
ok, data, err)
|
||||
}
|
||||
|
||||
if ok, data, err := clientMux.SendRequest("no", true, []byte("a")); ok || string(data) != "noa" || err != nil {
|
||||
t.Errorf("SendRequest(\"no\", true, \"a\"): %v %v %v",
|
||||
ok, data, err)
|
||||
}
|
||||
|
||||
if !seen {
|
||||
t.Errorf("never saw 'peek' request")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxGlobalRequestUnblock(t *testing.T) {
|
||||
clientMux, serverMux := muxPair()
|
||||
defer serverMux.Close()
|
||||
defer clientMux.Close()
|
||||
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
_, _, err := clientMux.SendRequest("hello", true, nil)
|
||||
result <- err
|
||||
}()
|
||||
|
||||
<-serverMux.incomingRequests
|
||||
serverMux.conn.Close()
|
||||
err := <-result
|
||||
|
||||
if err != io.EOF {
|
||||
t.Errorf("want EOF, got %v", io.EOF)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxChannelRequestUnblock(t *testing.T) {
|
||||
a, b, connB := channelPair(t)
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
defer connB.Close()
|
||||
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := a.SendRequest("hello", true, nil)
|
||||
result <- err
|
||||
}()
|
||||
|
||||
<-b.incomingRequests
|
||||
connB.conn.Close()
|
||||
err := <-result
|
||||
|
||||
if err != io.EOF {
|
||||
t.Errorf("want EOF, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxCloseChannel(t *testing.T) {
|
||||
r, w, mux := channelPair(t)
|
||||
defer mux.Close()
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
var b [1024]byte
|
||||
_, err := r.Read(b[:])
|
||||
result <- err
|
||||
}()
|
||||
if err := w.Close(); err != nil {
|
||||
t.Errorf("w.Close: %v", err)
|
||||
}
|
||||
|
||||
if _, err := w.Write([]byte("hello")); err != io.EOF {
|
||||
t.Errorf("got err %v, want io.EOF after Close", err)
|
||||
}
|
||||
|
||||
if err := <-result; err != io.EOF {
|
||||
t.Errorf("got %v (%T), want io.EOF", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxCloseWriteChannel(t *testing.T) {
|
||||
r, w, mux := channelPair(t)
|
||||
defer mux.Close()
|
||||
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
var b [1024]byte
|
||||
_, err := r.Read(b[:])
|
||||
result <- err
|
||||
}()
|
||||
if err := w.CloseWrite(); err != nil {
|
||||
t.Errorf("w.CloseWrite: %v", err)
|
||||
}
|
||||
|
||||
if _, err := w.Write([]byte("hello")); err != io.EOF {
|
||||
t.Errorf("got err %v, want io.EOF after CloseWrite", err)
|
||||
}
|
||||
|
||||
if err := <-result; err != io.EOF {
|
||||
t.Errorf("got %v (%T), want io.EOF", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxInvalidRecord(t *testing.T) {
|
||||
a, b := muxPair()
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
packet := make([]byte, 1+4+4+1)
|
||||
packet[0] = msgChannelData
|
||||
marshalUint32(packet[1:], 29348723 /* invalid channel id */)
|
||||
marshalUint32(packet[5:], 1)
|
||||
packet[9] = 42
|
||||
|
||||
a.conn.writePacket(packet)
|
||||
go a.SendRequest("hello", false, nil)
|
||||
// 'a' wrote an invalid packet, so 'b' has exited.
|
||||
req, ok := <-b.incomingRequests
|
||||
if ok {
|
||||
t.Errorf("got request %#v after receiving invalid packet", req)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZeroWindowAdjust(t *testing.T) {
|
||||
a, b, mux := channelPair(t)
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
defer mux.Close()
|
||||
|
||||
go func() {
|
||||
io.WriteString(a, "hello")
|
||||
// bogus adjust.
|
||||
a.sendMessage(windowAdjustMsg{})
|
||||
io.WriteString(a, "world")
|
||||
a.Close()
|
||||
}()
|
||||
|
||||
want := "helloworld"
|
||||
c, _ := ioutil.ReadAll(b)
|
||||
if string(c) != want {
|
||||
t.Errorf("got %q want %q", c, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuxMaxPacketSize(t *testing.T) {
|
||||
a, b, mux := channelPair(t)
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
defer mux.Close()
|
||||
|
||||
large := make([]byte, a.maxRemotePayload+1)
|
||||
packet := make([]byte, 1+4+4+1+len(large))
|
||||
packet[0] = msgChannelData
|
||||
marshalUint32(packet[1:], a.remoteId)
|
||||
marshalUint32(packet[5:], uint32(len(large)))
|
||||
packet[9] = 42
|
||||
|
||||
if err := a.mux.conn.writePacket(packet); err != nil {
|
||||
t.Errorf("could not send packet")
|
||||
}
|
||||
|
||||
go a.SendRequest("hello", false, nil)
|
||||
|
||||
_, ok := <-b.incomingRequests
|
||||
if ok {
|
||||
t.Errorf("connection still alive after receiving large packet.")
|
||||
}
|
||||
}
|
||||
|
||||
// Don't ship code with debug=true.
|
||||
func TestDebug(t *testing.T) {
|
||||
if debugMux {
|
||||
t.Error("mux debug switched on")
|
||||
}
|
||||
if debugHandshake {
|
||||
t.Error("handshake debug switched on")
|
||||
}
|
||||
if debugTransport {
|
||||
t.Error("transport debug switched on")
|
||||
}
|
||||
}
|
||||
563
ssh/server.go
563
ssh/server.go
|
|
@ -1,563 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The Permissions type holds fine-grained permissions that are
|
||||
// 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 {
|
||||
// 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. 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
|
||||
}
|
||||
|
||||
// ServerConfig holds server specific configuration data.
|
||||
type ServerConfig struct {
|
||||
// Config contains configuration shared between client and server.
|
||||
Config
|
||||
|
||||
hostKeys []Signer
|
||||
|
||||
// NoClientAuth is true if clients are allowed to connect without
|
||||
// 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
|
||||
// offers a public key for authentication. It must return true
|
||||
// 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
|
||||
// keyboard-interactive authentication is selected (RFC
|
||||
// 4256). The client object's Challenge function should be
|
||||
// used to query the user. The callback may offer multiple
|
||||
// Challenge rounds. To avoid information leaks, the client
|
||||
// should be presented a challenge even if the user is
|
||||
// unknown.
|
||||
KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error)
|
||||
|
||||
// AuthLogCallback, if non-nil, is called to log all authentication
|
||||
// attempts.
|
||||
AuthLogCallback func(conn ConnMetadata, method string, err error)
|
||||
|
||||
// ServerVersion is the version identification string to announce in
|
||||
// the public handshake.
|
||||
// If empty, a reasonable default is used.
|
||||
// Note that RFC 4253 section 4.2 requires that this string start with
|
||||
// "SSH-2.0-".
|
||||
ServerVersion string
|
||||
}
|
||||
|
||||
// AddHostKey adds a private key as a host key. If an existing host
|
||||
// key exists with the same algorithm, it is overwritten. Each server
|
||||
// config must have at least one host key.
|
||||
func (s *ServerConfig) AddHostKey(key Signer) {
|
||||
for i, k := range s.hostKeys {
|
||||
if k.PublicKey().Type() == key.PublicKey().Type() {
|
||||
s.hostKeys[i] = key
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s.hostKeys = append(s.hostKeys, key)
|
||||
}
|
||||
|
||||
// cachedPubKey contains the results of querying whether a public key is
|
||||
// acceptable for a user.
|
||||
type cachedPubKey struct {
|
||||
user string
|
||||
pubKeyData []byte
|
||||
result error
|
||||
perms *Permissions
|
||||
}
|
||||
|
||||
const maxCachedPubKeys = 16
|
||||
|
||||
// pubKeyCache caches tests for public keys. Since SSH clients
|
||||
// will query whether a public key is acceptable before attempting to
|
||||
// authenticate with it, we end up with duplicate queries for public
|
||||
// key validity. The cache only applies to a single ServerConn.
|
||||
type pubKeyCache struct {
|
||||
keys []cachedPubKey
|
||||
}
|
||||
|
||||
// get returns the result for a given user/algo/key tuple.
|
||||
func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) {
|
||||
for _, k := range c.keys {
|
||||
if k.user == user && bytes.Equal(k.pubKeyData, pubKeyData) {
|
||||
return k, true
|
||||
}
|
||||
}
|
||||
return cachedPubKey{}, false
|
||||
}
|
||||
|
||||
// add adds the given tuple to the cache.
|
||||
func (c *pubKeyCache) add(candidate cachedPubKey) {
|
||||
if len(c.keys) < maxCachedPubKeys {
|
||||
c.keys = append(c.keys, candidate)
|
||||
}
|
||||
}
|
||||
|
||||
// ServerConn is an authenticated SSH connection, as seen from the
|
||||
// server
|
||||
type ServerConn struct {
|
||||
Conn
|
||||
|
||||
// If the succeeding authentication callback returned a
|
||||
// non-nil Permissions pointer, it is stored here.
|
||||
Permissions *Permissions
|
||||
}
|
||||
|
||||
// NewServerConn starts a new SSH server with c as the underlying
|
||||
// transport. It starts with a handshake and, if the handshake is
|
||||
// unsuccessful, it closes the connection and returns an error. The
|
||||
// Request and NewChannel channels must be serviced, or the connection
|
||||
// will hang.
|
||||
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},
|
||||
}
|
||||
perms, err := s.serverHandshake(&fullConf)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return &ServerConn{s, perms}, s.mux.incomingChannels, s.mux.incomingRequests, nil
|
||||
}
|
||||
|
||||
// signAndMarshal signs the data with the appropriate algorithm,
|
||||
// and serializes the result in SSH wire format.
|
||||
func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) {
|
||||
sig, err := k.Sign(rand, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Marshal(sig), nil
|
||||
}
|
||||
|
||||
// handshake performs key exchange and user authentication.
|
||||
func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error) {
|
||||
if len(config.hostKeys) == 0 {
|
||||
return nil, errors.New("ssh: server has no host keys")
|
||||
}
|
||||
|
||||
if !config.NoClientAuth && config.PasswordCallback == nil && config.PublicKeyCallback == nil && config.KeyboardInteractiveCallback == nil {
|
||||
return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
|
||||
}
|
||||
|
||||
if config.ServerVersion != "" {
|
||||
s.serverVersion = []byte(config.ServerVersion)
|
||||
} else {
|
||||
s.serverVersion = []byte(packageVersion)
|
||||
}
|
||||
var err error
|
||||
s.clientVersion, err = exchangeVersions(s.sshConn.conn, s.serverVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */)
|
||||
s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config)
|
||||
|
||||
if err := s.transport.waitSession(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We just did the key change, so the session ID is established.
|
||||
s.sessionID = s.transport.getSessionID()
|
||||
|
||||
var packet []byte
|
||||
if packet, err = s.transport.readPacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var serviceRequest serviceRequestMsg
|
||||
if err = Unmarshal(packet, &serviceRequest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if serviceRequest.Service != serviceUserAuth {
|
||||
return nil, errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating")
|
||||
}
|
||||
serviceAccept := serviceAcceptMsg{
|
||||
Service: serviceUserAuth,
|
||||
}
|
||||
if err := s.transport.writePacket(Marshal(&serviceAccept)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
perms, err := s.serverAuthenticate(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.mux = newMux(s.transport)
|
||||
return perms, err
|
||||
}
|
||||
|
||||
func isAcceptableAlgo(algo string) bool {
|
||||
switch algo {
|
||||
case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoED25519,
|
||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
|
||||
if addr == nil {
|
||||
return errors.New("ssh: no address known for client, but source-address match required")
|
||||
}
|
||||
|
||||
tcpAddr, ok := addr.(*net.TCPAddr)
|
||||
if !ok {
|
||||
return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr)
|
||||
}
|
||||
|
||||
for _, sourceAddr := range strings.Split(sourceAddrs, ",") {
|
||||
if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil {
|
||||
if allowedIP.Equal(tcpAddr.IP) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
_, ipNet, err := net.ParseCIDR(sourceAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err)
|
||||
}
|
||||
|
||||
if ipNet.Contains(tcpAddr.IP) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if userAuthReq.Service != serviceSSH {
|
||||
return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
|
||||
}
|
||||
|
||||
s.user = userAuthReq.User
|
||||
perms = nil
|
||||
authErr := errors.New("no auth passed yet")
|
||||
|
||||
switch userAuthReq.Method {
|
||||
case "none":
|
||||
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")
|
||||
break
|
||||
}
|
||||
payload := userAuthReq.Payload
|
||||
if len(payload) < 1 || payload[0] != 0 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
payload = payload[1:]
|
||||
password, payload, ok := parseString(payload)
|
||||
if !ok || len(payload) > 0 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
|
||||
perms, authErr = config.PasswordCallback(s, password)
|
||||
case "keyboard-interactive":
|
||||
if config.KeyboardInteractiveCallback == nil {
|
||||
authErr = errors.New("ssh: keyboard-interactive auth not configubred")
|
||||
break
|
||||
}
|
||||
|
||||
prompter := &sshClientKeyboardInteractive{s}
|
||||
perms, authErr = config.KeyboardInteractiveCallback(s, prompter.Challenge)
|
||||
case "publickey":
|
||||
if config.PublicKeyCallback == nil {
|
||||
authErr = errors.New("ssh: publickey auth not configured")
|
||||
break
|
||||
}
|
||||
payload := userAuthReq.Payload
|
||||
if len(payload) < 1 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
isQuery := payload[0] == 0
|
||||
payload = payload[1:]
|
||||
algoBytes, payload, ok := parseString(payload)
|
||||
if !ok {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
algo := string(algoBytes)
|
||||
if !isAcceptableAlgo(algo) {
|
||||
authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo)
|
||||
break
|
||||
}
|
||||
|
||||
pubKeyData, payload, ok := parseString(payload)
|
||||
if !ok {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
|
||||
pubKey, err := ParsePublicKey(pubKeyData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
candidate, ok := cache.get(s.user, pubKeyData)
|
||||
if !ok {
|
||||
candidate.user = s.user
|
||||
candidate.pubKeyData = pubKeyData
|
||||
candidate.perms, candidate.result = config.PublicKeyCallback(s, pubKey)
|
||||
if candidate.result == nil && candidate.perms != nil && candidate.perms.CriticalOptions != nil && candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" {
|
||||
candidate.result = checkSourceAddress(
|
||||
s.RemoteAddr(),
|
||||
candidate.perms.CriticalOptions[sourceAddressCriticalOption])
|
||||
}
|
||||
cache.add(candidate)
|
||||
}
|
||||
|
||||
if isQuery {
|
||||
// The client can query if the given public key
|
||||
// would be okay.
|
||||
|
||||
if len(payload) > 0 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
|
||||
if candidate.result == nil {
|
||||
okMsg := userAuthPubKeyOkMsg{
|
||||
Algo: algo,
|
||||
PubKey: pubKeyData,
|
||||
}
|
||||
if err = s.transport.writePacket(Marshal(&okMsg)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue userAuthLoop
|
||||
}
|
||||
authErr = candidate.result
|
||||
} else {
|
||||
sig, payload, ok := parseSignature(payload)
|
||||
if !ok || len(payload) > 0 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
// Ensure the public key algo and signature algo
|
||||
// are supported. Compare the private key
|
||||
// algorithm name that corresponds to algo with
|
||||
// sig.Format. This is usually the same, but
|
||||
// for certs, the names differ.
|
||||
if !isAcceptableAlgo(sig.Format) {
|
||||
break
|
||||
}
|
||||
signedData := buildDataSignedForAuth(sessionID, userAuthReq, algoBytes, pubKeyData)
|
||||
|
||||
if err := pubKey.Verify(signedData, sig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authErr = candidate.result
|
||||
perms = candidate.perms
|
||||
}
|
||||
default:
|
||||
authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method)
|
||||
}
|
||||
|
||||
authErrs = append(authErrs, authErr)
|
||||
|
||||
if config.AuthLogCallback != nil {
|
||||
config.AuthLogCallback(s, userAuthReq.Method, authErr)
|
||||
}
|
||||
|
||||
if authErr == nil {
|
||||
break userAuthLoop
|
||||
}
|
||||
|
||||
authFailures++
|
||||
|
||||
var failureMsg userAuthFailureMsg
|
||||
if config.PasswordCallback != nil {
|
||||
failureMsg.Methods = append(failureMsg.Methods, "password")
|
||||
}
|
||||
if config.PublicKeyCallback != nil {
|
||||
failureMsg.Methods = append(failureMsg.Methods, "publickey")
|
||||
}
|
||||
if config.KeyboardInteractiveCallback != nil {
|
||||
failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive")
|
||||
}
|
||||
|
||||
if len(failureMsg.Methods) == 0 {
|
||||
return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
|
||||
}
|
||||
|
||||
if err := s.transport.writePacket(Marshal(&failureMsg)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return perms, nil
|
||||
}
|
||||
|
||||
// sshClientKeyboardInteractive implements a ClientKeyboardInteractive by
|
||||
// asking the client on the other side of a ServerConn.
|
||||
type sshClientKeyboardInteractive struct {
|
||||
*connection
|
||||
}
|
||||
|
||||
func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
|
||||
if len(questions) != len(echos) {
|
||||
return nil, errors.New("ssh: echos and questions must have equal length")
|
||||
}
|
||||
|
||||
var prompts []byte
|
||||
for i := range questions {
|
||||
prompts = appendString(prompts, questions[i])
|
||||
prompts = appendBool(prompts, echos[i])
|
||||
}
|
||||
|
||||
if err := c.transport.writePacket(Marshal(&userAuthInfoRequestMsg{
|
||||
Instruction: instruction,
|
||||
NumPrompts: uint32(len(questions)),
|
||||
Prompts: prompts,
|
||||
})); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
packet, err := c.transport.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if packet[0] != msgUserAuthInfoResponse {
|
||||
return nil, unexpectedMessageError(msgUserAuthInfoResponse, packet[0])
|
||||
}
|
||||
packet = packet[1:]
|
||||
|
||||
n, packet, ok := parseUint32(packet)
|
||||
if !ok || int(n) != len(questions) {
|
||||
return nil, parseError(msgUserAuthInfoResponse)
|
||||
}
|
||||
|
||||
for i := uint32(0); i < n; i++ {
|
||||
ans, rest, ok := parseString(packet)
|
||||
if !ok {
|
||||
return nil, parseError(msgUserAuthInfoResponse)
|
||||
}
|
||||
|
||||
answers = append(answers, string(ans))
|
||||
packet = rest
|
||||
}
|
||||
if len(packet) != 0 {
|
||||
return nil, errors.New("ssh: junk at end of message")
|
||||
}
|
||||
|
||||
return answers, nil
|
||||
}
|
||||
647
ssh/session.go
647
ssh/session.go
|
|
@ -1,647 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
// Session implements an interactive session described in
|
||||
// "RFC 4254, section 6".
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Signal string
|
||||
|
||||
// POSIX signals as listed in RFC 4254 Section 6.10.
|
||||
const (
|
||||
SIGABRT Signal = "ABRT"
|
||||
SIGALRM Signal = "ALRM"
|
||||
SIGFPE Signal = "FPE"
|
||||
SIGHUP Signal = "HUP"
|
||||
SIGILL Signal = "ILL"
|
||||
SIGINT Signal = "INT"
|
||||
SIGKILL Signal = "KILL"
|
||||
SIGPIPE Signal = "PIPE"
|
||||
SIGQUIT Signal = "QUIT"
|
||||
SIGSEGV Signal = "SEGV"
|
||||
SIGTERM Signal = "TERM"
|
||||
SIGUSR1 Signal = "USR1"
|
||||
SIGUSR2 Signal = "USR2"
|
||||
)
|
||||
|
||||
var signals = map[Signal]int{
|
||||
SIGABRT: 6,
|
||||
SIGALRM: 14,
|
||||
SIGFPE: 8,
|
||||
SIGHUP: 1,
|
||||
SIGILL: 4,
|
||||
SIGINT: 2,
|
||||
SIGKILL: 9,
|
||||
SIGPIPE: 13,
|
||||
SIGQUIT: 3,
|
||||
SIGSEGV: 11,
|
||||
SIGTERM: 15,
|
||||
}
|
||||
|
||||
type TerminalModes map[uint8]uint32
|
||||
|
||||
// POSIX terminal mode flags as listed in RFC 4254 Section 8.
|
||||
const (
|
||||
tty_OP_END = 0
|
||||
VINTR = 1
|
||||
VQUIT = 2
|
||||
VERASE = 3
|
||||
VKILL = 4
|
||||
VEOF = 5
|
||||
VEOL = 6
|
||||
VEOL2 = 7
|
||||
VSTART = 8
|
||||
VSTOP = 9
|
||||
VSUSP = 10
|
||||
VDSUSP = 11
|
||||
VREPRINT = 12
|
||||
VWERASE = 13
|
||||
VLNEXT = 14
|
||||
VFLUSH = 15
|
||||
VSWTCH = 16
|
||||
VSTATUS = 17
|
||||
VDISCARD = 18
|
||||
IGNPAR = 30
|
||||
PARMRK = 31
|
||||
INPCK = 32
|
||||
ISTRIP = 33
|
||||
INLCR = 34
|
||||
IGNCR = 35
|
||||
ICRNL = 36
|
||||
IUCLC = 37
|
||||
IXON = 38
|
||||
IXANY = 39
|
||||
IXOFF = 40
|
||||
IMAXBEL = 41
|
||||
ISIG = 50
|
||||
ICANON = 51
|
||||
XCASE = 52
|
||||
ECHO = 53
|
||||
ECHOE = 54
|
||||
ECHOK = 55
|
||||
ECHONL = 56
|
||||
NOFLSH = 57
|
||||
TOSTOP = 58
|
||||
IEXTEN = 59
|
||||
ECHOCTL = 60
|
||||
ECHOKE = 61
|
||||
PENDIN = 62
|
||||
OPOST = 70
|
||||
OLCUC = 71
|
||||
ONLCR = 72
|
||||
OCRNL = 73
|
||||
ONOCR = 74
|
||||
ONLRET = 75
|
||||
CS7 = 90
|
||||
CS8 = 91
|
||||
PARENB = 92
|
||||
PARODD = 93
|
||||
TTY_OP_ISPEED = 128
|
||||
TTY_OP_OSPEED = 129
|
||||
)
|
||||
|
||||
// A Session represents a connection to a remote command or shell.
|
||||
type Session struct {
|
||||
// Stdin specifies the remote process's standard input.
|
||||
// If Stdin is nil, the remote process reads from an empty
|
||||
// bytes.Buffer.
|
||||
Stdin io.Reader
|
||||
|
||||
// Stdout and Stderr specify the remote process's standard
|
||||
// output and error.
|
||||
//
|
||||
// If either is nil, Run connects the corresponding file
|
||||
// descriptor to an instance of ioutil.Discard. There is a
|
||||
// fixed amount of buffering that is shared for the two streams.
|
||||
// If either blocks it may eventually cause the remote
|
||||
// command to block.
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
|
||||
ch Channel // the channel backing this session
|
||||
started bool // true once Start, Run or Shell is invoked.
|
||||
copyFuncs []func() error
|
||||
errors chan error // one send per copyFunc
|
||||
|
||||
// true if pipe method is active
|
||||
stdinpipe, stdoutpipe, stderrpipe bool
|
||||
|
||||
// stdinPipeWriter is non-nil if StdinPipe has not been called
|
||||
// and Stdin was specified by the user; it is the write end of
|
||||
// a pipe connecting Session.Stdin to the stdin channel.
|
||||
stdinPipeWriter io.WriteCloser
|
||||
|
||||
exitStatus chan error
|
||||
}
|
||||
|
||||
// SendRequest sends an out-of-band channel request on the SSH channel
|
||||
// underlying the session.
|
||||
func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
|
||||
return s.ch.SendRequest(name, wantReply, payload)
|
||||
}
|
||||
|
||||
func (s *Session) Close() error {
|
||||
return s.ch.Close()
|
||||
}
|
||||
|
||||
// RFC 4254 Section 6.4.
|
||||
type setenvRequest struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Setenv sets an environment variable that will be applied to any
|
||||
// command executed by Shell or Run.
|
||||
func (s *Session) Setenv(name, value string) error {
|
||||
msg := setenvRequest{
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
ok, err := s.ch.SendRequest("env", true, Marshal(&msg))
|
||||
if err == nil && !ok {
|
||||
err = errors.New("ssh: setenv failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RFC 4254 Section 6.2.
|
||||
type ptyRequestMsg struct {
|
||||
Term string
|
||||
Columns uint32
|
||||
Rows uint32
|
||||
Width uint32
|
||||
Height uint32
|
||||
Modelist string
|
||||
}
|
||||
|
||||
// RequestPty requests the association of a pty with the session on the remote host.
|
||||
func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error {
|
||||
var tm []byte
|
||||
for k, v := range termmodes {
|
||||
kv := struct {
|
||||
Key byte
|
||||
Val uint32
|
||||
}{k, v}
|
||||
|
||||
tm = append(tm, Marshal(&kv)...)
|
||||
}
|
||||
tm = append(tm, tty_OP_END)
|
||||
req := ptyRequestMsg{
|
||||
Term: term,
|
||||
Columns: uint32(w),
|
||||
Rows: uint32(h),
|
||||
Width: uint32(w * 8),
|
||||
Height: uint32(h * 8),
|
||||
Modelist: string(tm),
|
||||
}
|
||||
ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req))
|
||||
if err == nil && !ok {
|
||||
err = errors.New("ssh: pty-req failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RFC 4254 Section 6.5.
|
||||
type subsystemRequestMsg struct {
|
||||
Subsystem string
|
||||
}
|
||||
|
||||
// RequestSubsystem requests the association of a subsystem with the session on the remote host.
|
||||
// A subsystem is a predefined command that runs in the background when the ssh session is initiated
|
||||
func (s *Session) RequestSubsystem(subsystem string) error {
|
||||
msg := subsystemRequestMsg{
|
||||
Subsystem: subsystem,
|
||||
}
|
||||
ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg))
|
||||
if err == nil && !ok {
|
||||
err = errors.New("ssh: subsystem request failed")
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// Signal sends the given signal to the remote process.
|
||||
// sig is one of the SIG* constants.
|
||||
func (s *Session) Signal(sig Signal) error {
|
||||
msg := signalMsg{
|
||||
Signal: string(sig),
|
||||
}
|
||||
|
||||
_, err := s.ch.SendRequest("signal", false, Marshal(&msg))
|
||||
return err
|
||||
}
|
||||
|
||||
// RFC 4254 Section 6.5.
|
||||
type execMsg struct {
|
||||
Command string
|
||||
}
|
||||
|
||||
// Start runs cmd on the remote host. Typically, the remote
|
||||
// server passes cmd to the shell for interpretation.
|
||||
// A Session only accepts one call to Run, Start or Shell.
|
||||
func (s *Session) Start(cmd string) error {
|
||||
if s.started {
|
||||
return errors.New("ssh: session already started")
|
||||
}
|
||||
req := execMsg{
|
||||
Command: cmd,
|
||||
}
|
||||
|
||||
ok, err := s.ch.SendRequest("exec", true, Marshal(&req))
|
||||
if err == nil && !ok {
|
||||
err = fmt.Errorf("ssh: command %v failed", cmd)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.start()
|
||||
}
|
||||
|
||||
// Run runs cmd on the remote host. Typically, the remote
|
||||
// server passes cmd to the shell for interpretation.
|
||||
// A Session only accepts one call to Run, Start, Shell, Output,
|
||||
// or CombinedOutput.
|
||||
//
|
||||
// The returned error is nil if the command runs, has no problems
|
||||
// copying stdin, stdout, and stderr, and exits with a zero exit
|
||||
// status.
|
||||
//
|
||||
// If the remote server does not send an exit status, an error of type
|
||||
// *ExitMissingError is returned. If the command completes
|
||||
// unsuccessfully or is interrupted by a signal, the error is of type
|
||||
// *ExitError. Other error types may be returned for I/O problems.
|
||||
func (s *Session) Run(cmd string) error {
|
||||
err := s.Start(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Wait()
|
||||
}
|
||||
|
||||
// Output runs cmd on the remote host and returns its standard output.
|
||||
func (s *Session) Output(cmd string) ([]byte, error) {
|
||||
if s.Stdout != nil {
|
||||
return nil, errors.New("ssh: Stdout already set")
|
||||
}
|
||||
var b bytes.Buffer
|
||||
s.Stdout = &b
|
||||
err := s.Run(cmd)
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
type singleWriter struct {
|
||||
b bytes.Buffer
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (w *singleWriter) Write(p []byte) (int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.b.Write(p)
|
||||
}
|
||||
|
||||
// CombinedOutput runs cmd on the remote host and returns its combined
|
||||
// standard output and standard error.
|
||||
func (s *Session) CombinedOutput(cmd string) ([]byte, error) {
|
||||
if s.Stdout != nil {
|
||||
return nil, errors.New("ssh: Stdout already set")
|
||||
}
|
||||
if s.Stderr != nil {
|
||||
return nil, errors.New("ssh: Stderr already set")
|
||||
}
|
||||
var b singleWriter
|
||||
s.Stdout = &b
|
||||
s.Stderr = &b
|
||||
err := s.Run(cmd)
|
||||
return b.b.Bytes(), err
|
||||
}
|
||||
|
||||
// Shell starts a login shell on the remote host. A Session only
|
||||
// accepts one call to Run, Start, Shell, Output, or CombinedOutput.
|
||||
func (s *Session) Shell() error {
|
||||
if s.started {
|
||||
return errors.New("ssh: session already started")
|
||||
}
|
||||
|
||||
ok, err := s.ch.SendRequest("shell", true, nil)
|
||||
if err == nil && !ok {
|
||||
return errors.New("ssh: could not start shell")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.start()
|
||||
}
|
||||
|
||||
func (s *Session) start() error {
|
||||
s.started = true
|
||||
|
||||
type F func(*Session)
|
||||
for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
|
||||
setupFd(s)
|
||||
}
|
||||
|
||||
s.errors = make(chan error, len(s.copyFuncs))
|
||||
for _, fn := range s.copyFuncs {
|
||||
go func(fn func() error) {
|
||||
s.errors <- fn()
|
||||
}(fn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait waits for the remote command to exit.
|
||||
//
|
||||
// The returned error is nil if the command runs, has no problems
|
||||
// copying stdin, stdout, and stderr, and exits with a zero exit
|
||||
// status.
|
||||
//
|
||||
// If the remote server does not send an exit status, an error of type
|
||||
// *ExitMissingError is returned. If the command completes
|
||||
// unsuccessfully or is interrupted by a signal, the error is of type
|
||||
// *ExitError. Other error types may be returned for I/O problems.
|
||||
func (s *Session) Wait() error {
|
||||
if !s.started {
|
||||
return errors.New("ssh: session not started")
|
||||
}
|
||||
waitErr := <-s.exitStatus
|
||||
|
||||
if s.stdinPipeWriter != nil {
|
||||
s.stdinPipeWriter.Close()
|
||||
}
|
||||
var copyError error
|
||||
for _ = range s.copyFuncs {
|
||||
if err := <-s.errors; err != nil && copyError == nil {
|
||||
copyError = err
|
||||
}
|
||||
}
|
||||
if waitErr != nil {
|
||||
return waitErr
|
||||
}
|
||||
return copyError
|
||||
}
|
||||
|
||||
func (s *Session) wait(reqs <-chan *Request) error {
|
||||
wm := Waitmsg{status: -1}
|
||||
// Wait for msg channel to be closed before returning.
|
||||
for msg := range reqs {
|
||||
switch msg.Type {
|
||||
case "exit-status":
|
||||
wm.status = int(binary.BigEndian.Uint32(msg.Payload))
|
||||
case "exit-signal":
|
||||
var sigval struct {
|
||||
Signal string
|
||||
CoreDumped bool
|
||||
Error string
|
||||
Lang string
|
||||
}
|
||||
if err := Unmarshal(msg.Payload, &sigval); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Must sanitize strings?
|
||||
wm.signal = sigval.Signal
|
||||
wm.msg = sigval.Error
|
||||
wm.lang = sigval.Lang
|
||||
default:
|
||||
// This handles keepalives and matches
|
||||
// OpenSSH's behaviour.
|
||||
if msg.WantReply {
|
||||
msg.Reply(false, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
if wm.status == 0 {
|
||||
return nil
|
||||
}
|
||||
if wm.status == -1 {
|
||||
// exit-status was never sent from server
|
||||
if wm.signal == "" {
|
||||
// signal was not sent either. RFC 4254
|
||||
// section 6.10 recommends against this
|
||||
// behavior, but it is allowed, so we let
|
||||
// clients handle it.
|
||||
return &ExitMissingError{}
|
||||
}
|
||||
wm.status = 128
|
||||
if _, ok := signals[Signal(wm.signal)]; ok {
|
||||
wm.status += signals[Signal(wm.signal)]
|
||||
}
|
||||
}
|
||||
|
||||
return &ExitError{wm}
|
||||
}
|
||||
|
||||
// ExitMissingError is returned if a session is torn down cleanly, but
|
||||
// the server sends no confirmation of the exit status.
|
||||
type ExitMissingError struct{}
|
||||
|
||||
func (e *ExitMissingError) Error() string {
|
||||
return "wait: remote command exited without exit status or exit signal"
|
||||
}
|
||||
|
||||
func (s *Session) stdin() {
|
||||
if s.stdinpipe {
|
||||
return
|
||||
}
|
||||
var stdin io.Reader
|
||||
if s.Stdin == nil {
|
||||
stdin = new(bytes.Buffer)
|
||||
} else {
|
||||
r, w := io.Pipe()
|
||||
go func() {
|
||||
_, err := io.Copy(w, s.Stdin)
|
||||
w.CloseWithError(err)
|
||||
}()
|
||||
stdin, s.stdinPipeWriter = r, w
|
||||
}
|
||||
s.copyFuncs = append(s.copyFuncs, func() error {
|
||||
_, err := io.Copy(s.ch, stdin)
|
||||
if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Session) stdout() {
|
||||
if s.stdoutpipe {
|
||||
return
|
||||
}
|
||||
if s.Stdout == nil {
|
||||
s.Stdout = ioutil.Discard
|
||||
}
|
||||
s.copyFuncs = append(s.copyFuncs, func() error {
|
||||
_, err := io.Copy(s.Stdout, s.ch)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Session) stderr() {
|
||||
if s.stderrpipe {
|
||||
return
|
||||
}
|
||||
if s.Stderr == nil {
|
||||
s.Stderr = ioutil.Discard
|
||||
}
|
||||
s.copyFuncs = append(s.copyFuncs, func() error {
|
||||
_, err := io.Copy(s.Stderr, s.ch.Stderr())
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// sessionStdin reroutes Close to CloseWrite.
|
||||
type sessionStdin struct {
|
||||
io.Writer
|
||||
ch Channel
|
||||
}
|
||||
|
||||
func (s *sessionStdin) Close() error {
|
||||
return s.ch.CloseWrite()
|
||||
}
|
||||
|
||||
// StdinPipe returns a pipe that will be connected to the
|
||||
// remote command's standard input when the command starts.
|
||||
func (s *Session) StdinPipe() (io.WriteCloser, error) {
|
||||
if s.Stdin != nil {
|
||||
return nil, errors.New("ssh: Stdin already set")
|
||||
}
|
||||
if s.started {
|
||||
return nil, errors.New("ssh: StdinPipe after process started")
|
||||
}
|
||||
s.stdinpipe = true
|
||||
return &sessionStdin{s.ch, s.ch}, nil
|
||||
}
|
||||
|
||||
// StdoutPipe returns a pipe that will be connected to the
|
||||
// remote command's standard output when the command starts.
|
||||
// There is a fixed amount of buffering that is shared between
|
||||
// stdout and stderr streams. If the StdoutPipe reader is
|
||||
// not serviced fast enough it may eventually cause the
|
||||
// remote command to block.
|
||||
func (s *Session) StdoutPipe() (io.Reader, error) {
|
||||
if s.Stdout != nil {
|
||||
return nil, errors.New("ssh: Stdout already set")
|
||||
}
|
||||
if s.started {
|
||||
return nil, errors.New("ssh: StdoutPipe after process started")
|
||||
}
|
||||
s.stdoutpipe = true
|
||||
return s.ch, nil
|
||||
}
|
||||
|
||||
// StderrPipe returns a pipe that will be connected to the
|
||||
// remote command's standard error when the command starts.
|
||||
// There is a fixed amount of buffering that is shared between
|
||||
// stdout and stderr streams. If the StderrPipe reader is
|
||||
// not serviced fast enough it may eventually cause the
|
||||
// remote command to block.
|
||||
func (s *Session) StderrPipe() (io.Reader, error) {
|
||||
if s.Stderr != nil {
|
||||
return nil, errors.New("ssh: Stderr already set")
|
||||
}
|
||||
if s.started {
|
||||
return nil, errors.New("ssh: StderrPipe after process started")
|
||||
}
|
||||
s.stderrpipe = true
|
||||
return s.ch.Stderr(), nil
|
||||
}
|
||||
|
||||
// newSession returns a new interactive session on the remote host.
|
||||
func newSession(ch Channel, reqs <-chan *Request) (*Session, error) {
|
||||
s := &Session{
|
||||
ch: ch,
|
||||
}
|
||||
s.exitStatus = make(chan error, 1)
|
||||
go func() {
|
||||
s.exitStatus <- s.wait(reqs)
|
||||
}()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// An ExitError reports unsuccessful completion of a remote command.
|
||||
type ExitError struct {
|
||||
Waitmsg
|
||||
}
|
||||
|
||||
func (e *ExitError) Error() string {
|
||||
return e.Waitmsg.String()
|
||||
}
|
||||
|
||||
// Waitmsg stores the information about an exited remote command
|
||||
// as reported by Wait.
|
||||
type Waitmsg struct {
|
||||
status int
|
||||
signal string
|
||||
msg string
|
||||
lang string
|
||||
}
|
||||
|
||||
// ExitStatus returns the exit status of the remote command.
|
||||
func (w Waitmsg) ExitStatus() int {
|
||||
return w.status
|
||||
}
|
||||
|
||||
// Signal returns the exit signal of the remote command if
|
||||
// it was terminated violently.
|
||||
func (w Waitmsg) Signal() string {
|
||||
return w.signal
|
||||
}
|
||||
|
||||
// Msg returns the exit message given by the remote command
|
||||
func (w Waitmsg) Msg() string {
|
||||
return w.msg
|
||||
}
|
||||
|
||||
// Lang returns the language tag. See RFC 3066
|
||||
func (w Waitmsg) Lang() string {
|
||||
return w.lang
|
||||
}
|
||||
|
||||
func (w Waitmsg) String() string {
|
||||
str := fmt.Sprintf("Process exited with status %v", w.status)
|
||||
if w.signal != "" {
|
||||
str += fmt.Sprintf(" from signal %v", w.signal)
|
||||
}
|
||||
if w.msg != "" {
|
||||
str += fmt.Sprintf(". Reason was: %v", w.msg)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
|
@ -1,774 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
// Session tests.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
crypto_rand "crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
type serverType func(Channel, <-chan *Request, *testing.T)
|
||||
|
||||
// dial constructs a new test server and returns a *ClientConn.
|
||||
func dial(handler serverType, t *testing.T) *Client {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer c1.Close()
|
||||
conf := ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
conf.AddHostKey(testSigners["rsa"])
|
||||
|
||||
_, chans, reqs, err := NewServerConn(c1, &conf)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to handshake: %v", err)
|
||||
}
|
||||
go DiscardRequests(reqs)
|
||||
|
||||
for newCh := range chans {
|
||||
if newCh.ChannelType() != "session" {
|
||||
newCh.Reject(UnknownChannelType, "unknown channel type")
|
||||
continue
|
||||
}
|
||||
|
||||
ch, inReqs, err := newCh.Accept()
|
||||
if err != nil {
|
||||
t.Errorf("Accept: %v", err)
|
||||
continue
|
||||
}
|
||||
go func() {
|
||||
handler(ch, inReqs, t)
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
conn, chans, reqs, err := NewClientConn(c2, "", config)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to dial remote side: %v", err)
|
||||
}
|
||||
|
||||
return NewClient(conn, chans, reqs)
|
||||
}
|
||||
|
||||
// Test a simple string is returned to session.Stdout.
|
||||
func TestSessionShell(t *testing.T) {
|
||||
conn := dial(shellHandler, t)
|
||||
defer conn.Close()
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to request new session: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
stdout := new(bytes.Buffer)
|
||||
session.Stdout = stdout
|
||||
if err := session.Shell(); err != nil {
|
||||
t.Fatalf("Unable to execute command: %s", err)
|
||||
}
|
||||
if err := session.Wait(); err != nil {
|
||||
t.Fatalf("Remote command did not exit cleanly: %v", err)
|
||||
}
|
||||
actual := stdout.String()
|
||||
if actual != "golang" {
|
||||
t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(dfc) add support for Std{in,err}Pipe when the Server supports it.
|
||||
|
||||
// Test a simple string is returned via StdoutPipe.
|
||||
func TestSessionStdoutPipe(t *testing.T) {
|
||||
conn := dial(shellHandler, t)
|
||||
defer conn.Close()
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to request new session: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
stdout, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to request StdoutPipe(): %v", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := session.Shell(); err != nil {
|
||||
t.Fatalf("Unable to execute command: %v", err)
|
||||
}
|
||||
done := make(chan bool, 1)
|
||||
go func() {
|
||||
if _, err := io.Copy(&buf, stdout); err != nil {
|
||||
t.Errorf("Copy of stdout failed: %v", err)
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
if err := session.Wait(); err != nil {
|
||||
t.Fatalf("Remote command did not exit cleanly: %v", err)
|
||||
}
|
||||
<-done
|
||||
actual := buf.String()
|
||||
if actual != "golang" {
|
||||
t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that a simple string is returned via the Output helper,
|
||||
// and that stderr is discarded.
|
||||
func TestSessionOutput(t *testing.T) {
|
||||
conn := dial(fixedOutputHandler, t)
|
||||
defer conn.Close()
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to request new session: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
buf, err := session.Output("") // cmd is ignored by fixedOutputHandler
|
||||
if err != nil {
|
||||
t.Error("Remote command did not exit cleanly:", err)
|
||||
}
|
||||
w := "this-is-stdout."
|
||||
g := string(buf)
|
||||
if g != w {
|
||||
t.Error("Remote command did not return expected string:")
|
||||
t.Logf("want %q", w)
|
||||
t.Logf("got %q", g)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that both stdout and stderr are returned
|
||||
// via the CombinedOutput helper.
|
||||
func TestSessionCombinedOutput(t *testing.T) {
|
||||
conn := dial(fixedOutputHandler, t)
|
||||
defer conn.Close()
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to request new session: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
buf, err := session.CombinedOutput("") // cmd is ignored by fixedOutputHandler
|
||||
if err != nil {
|
||||
t.Error("Remote command did not exit cleanly:", err)
|
||||
}
|
||||
const stdout = "this-is-stdout."
|
||||
const stderr = "this-is-stderr."
|
||||
g := string(buf)
|
||||
if g != stdout+stderr && g != stderr+stdout {
|
||||
t.Error("Remote command did not return expected string:")
|
||||
t.Logf("want %q, or %q", stdout+stderr, stderr+stdout)
|
||||
t.Logf("got %q", g)
|
||||
}
|
||||
}
|
||||
|
||||
// Test non-0 exit status is returned correctly.
|
||||
func TestExitStatusNonZero(t *testing.T) {
|
||||
conn := dial(exitStatusNonZeroHandler, t)
|
||||
defer conn.Close()
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to request new session: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
if err := session.Shell(); err != nil {
|
||||
t.Fatalf("Unable to execute command: %v", err)
|
||||
}
|
||||
err = session.Wait()
|
||||
if err == nil {
|
||||
t.Fatalf("expected command to fail but it didn't")
|
||||
}
|
||||
e, ok := err.(*ExitError)
|
||||
if !ok {
|
||||
t.Fatalf("expected *ExitError but got %T", err)
|
||||
}
|
||||
if e.ExitStatus() != 15 {
|
||||
t.Fatalf("expected command to exit with 15 but got %v", e.ExitStatus())
|
||||
}
|
||||
}
|
||||
|
||||
// Test 0 exit status is returned correctly.
|
||||
func TestExitStatusZero(t *testing.T) {
|
||||
conn := dial(exitStatusZeroHandler, t)
|
||||
defer conn.Close()
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to request new session: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
if err := session.Shell(); err != nil {
|
||||
t.Fatalf("Unable to execute command: %v", err)
|
||||
}
|
||||
err = session.Wait()
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil but got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test exit signal and status are both returned correctly.
|
||||
func TestExitSignalAndStatus(t *testing.T) {
|
||||
conn := dial(exitSignalAndStatusHandler, t)
|
||||
defer conn.Close()
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to request new session: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
if err := session.Shell(); err != nil {
|
||||
t.Fatalf("Unable to execute command: %v", err)
|
||||
}
|
||||
err = session.Wait()
|
||||
if err == nil {
|
||||
t.Fatalf("expected command to fail but it didn't")
|
||||
}
|
||||
e, ok := err.(*ExitError)
|
||||
if !ok {
|
||||
t.Fatalf("expected *ExitError but got %T", err)
|
||||
}
|
||||
if e.Signal() != "TERM" || e.ExitStatus() != 15 {
|
||||
t.Fatalf("expected command to exit with signal TERM and status 15 but got signal %s and status %v", e.Signal(), e.ExitStatus())
|
||||
}
|
||||
}
|
||||
|
||||
// Test exit signal and status are both returned correctly.
|
||||
func TestKnownExitSignalOnly(t *testing.T) {
|
||||
conn := dial(exitSignalHandler, t)
|
||||
defer conn.Close()
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to request new session: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
if err := session.Shell(); err != nil {
|
||||
t.Fatalf("Unable to execute command: %v", err)
|
||||
}
|
||||
err = session.Wait()
|
||||
if err == nil {
|
||||
t.Fatalf("expected command to fail but it didn't")
|
||||
}
|
||||
e, ok := err.(*ExitError)
|
||||
if !ok {
|
||||
t.Fatalf("expected *ExitError but got %T", err)
|
||||
}
|
||||
if e.Signal() != "TERM" || e.ExitStatus() != 143 {
|
||||
t.Fatalf("expected command to exit with signal TERM and status 143 but got signal %s and status %v", e.Signal(), e.ExitStatus())
|
||||
}
|
||||
}
|
||||
|
||||
// Test exit signal and status are both returned correctly.
|
||||
func TestUnknownExitSignal(t *testing.T) {
|
||||
conn := dial(exitSignalUnknownHandler, t)
|
||||
defer conn.Close()
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to request new session: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
if err := session.Shell(); err != nil {
|
||||
t.Fatalf("Unable to execute command: %v", err)
|
||||
}
|
||||
err = session.Wait()
|
||||
if err == nil {
|
||||
t.Fatalf("expected command to fail but it didn't")
|
||||
}
|
||||
e, ok := err.(*ExitError)
|
||||
if !ok {
|
||||
t.Fatalf("expected *ExitError but got %T", err)
|
||||
}
|
||||
if e.Signal() != "SYS" || e.ExitStatus() != 128 {
|
||||
t.Fatalf("expected command to exit with signal SYS and status 128 but got signal %s and status %v", e.Signal(), e.ExitStatus())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExitWithoutStatusOrSignal(t *testing.T) {
|
||||
conn := dial(exitWithoutSignalOrStatus, t)
|
||||
defer conn.Close()
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to request new session: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
if err := session.Shell(); err != nil {
|
||||
t.Fatalf("Unable to execute command: %v", err)
|
||||
}
|
||||
err = session.Wait()
|
||||
if err == nil {
|
||||
t.Fatalf("expected command to fail but it didn't")
|
||||
}
|
||||
if _, ok := err.(*ExitMissingError); !ok {
|
||||
t.Fatalf("got %T want *ExitMissingError", err)
|
||||
}
|
||||
}
|
||||
|
||||
// windowTestBytes is the number of bytes that we'll send to the SSH server.
|
||||
const windowTestBytes = 16000 * 200
|
||||
|
||||
// TestServerWindow writes random data to the server. The server is expected to echo
|
||||
// the same data back, which is compared against the original.
|
||||
func TestServerWindow(t *testing.T) {
|
||||
origBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
|
||||
io.CopyN(origBuf, crypto_rand.Reader, windowTestBytes)
|
||||
origBytes := origBuf.Bytes()
|
||||
|
||||
conn := dial(echoHandler, t)
|
||||
defer conn.Close()
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer session.Close()
|
||||
result := make(chan []byte)
|
||||
|
||||
go func() {
|
||||
defer close(result)
|
||||
echoedBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
|
||||
serverStdout, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Errorf("StdoutPipe failed: %v", err)
|
||||
return
|
||||
}
|
||||
n, err := copyNRandomly("stdout", echoedBuf, serverStdout, windowTestBytes)
|
||||
if err != nil && err != io.EOF {
|
||||
t.Errorf("Read only %d bytes from server, expected %d: %v", n, windowTestBytes, err)
|
||||
}
|
||||
result <- echoedBuf.Bytes()
|
||||
}()
|
||||
|
||||
serverStdin, err := session.StdinPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("StdinPipe failed: %v", err)
|
||||
}
|
||||
written, err := copyNRandomly("stdin", serverStdin, origBuf, windowTestBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to copy origBuf to serverStdin: %v", err)
|
||||
}
|
||||
if written != windowTestBytes {
|
||||
t.Fatalf("Wrote only %d of %d bytes to server", written, windowTestBytes)
|
||||
}
|
||||
|
||||
echoedBytes := <-result
|
||||
|
||||
if !bytes.Equal(origBytes, echoedBytes) {
|
||||
t.Fatalf("Echoed buffer differed from original, orig %d, echoed %d", len(origBytes), len(echoedBytes))
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the client can handle a keepalive packet from the server.
|
||||
func TestClientHandlesKeepalives(t *testing.T) {
|
||||
conn := dial(channelKeepaliveSender, t)
|
||||
defer conn.Close()
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer session.Close()
|
||||
if err := session.Shell(); err != nil {
|
||||
t.Fatalf("Unable to execute command: %v", err)
|
||||
}
|
||||
err = session.Wait()
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type exitStatusMsg struct {
|
||||
Status uint32
|
||||
}
|
||||
|
||||
type exitSignalMsg struct {
|
||||
Signal string
|
||||
CoreDumped bool
|
||||
Errmsg string
|
||||
Lang string
|
||||
}
|
||||
|
||||
func handleTerminalRequests(in <-chan *Request) {
|
||||
for req := range in {
|
||||
ok := false
|
||||
switch req.Type {
|
||||
case "shell":
|
||||
ok = true
|
||||
if len(req.Payload) > 0 {
|
||||
// We don't accept any commands, only the default shell.
|
||||
ok = false
|
||||
}
|
||||
case "env":
|
||||
ok = true
|
||||
}
|
||||
req.Reply(ok, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func newServerShell(ch Channel, in <-chan *Request, prompt string) *terminal.Terminal {
|
||||
term := terminal.NewTerminal(ch, prompt)
|
||||
go handleTerminalRequests(in)
|
||||
return term
|
||||
}
|
||||
|
||||
func exitStatusZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
|
||||
defer ch.Close()
|
||||
// this string is returned to stdout
|
||||
shell := newServerShell(ch, in, "> ")
|
||||
readLine(shell, t)
|
||||
sendStatus(0, ch, t)
|
||||
}
|
||||
|
||||
func exitStatusNonZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
|
||||
defer ch.Close()
|
||||
shell := newServerShell(ch, in, "> ")
|
||||
readLine(shell, t)
|
||||
sendStatus(15, ch, t)
|
||||
}
|
||||
|
||||
func exitSignalAndStatusHandler(ch Channel, in <-chan *Request, t *testing.T) {
|
||||
defer ch.Close()
|
||||
shell := newServerShell(ch, in, "> ")
|
||||
readLine(shell, t)
|
||||
sendStatus(15, ch, t)
|
||||
sendSignal("TERM", ch, t)
|
||||
}
|
||||
|
||||
func exitSignalHandler(ch Channel, in <-chan *Request, t *testing.T) {
|
||||
defer ch.Close()
|
||||
shell := newServerShell(ch, in, "> ")
|
||||
readLine(shell, t)
|
||||
sendSignal("TERM", ch, t)
|
||||
}
|
||||
|
||||
func exitSignalUnknownHandler(ch Channel, in <-chan *Request, t *testing.T) {
|
||||
defer ch.Close()
|
||||
shell := newServerShell(ch, in, "> ")
|
||||
readLine(shell, t)
|
||||
sendSignal("SYS", ch, t)
|
||||
}
|
||||
|
||||
func exitWithoutSignalOrStatus(ch Channel, in <-chan *Request, t *testing.T) {
|
||||
defer ch.Close()
|
||||
shell := newServerShell(ch, in, "> ")
|
||||
readLine(shell, t)
|
||||
}
|
||||
|
||||
func shellHandler(ch Channel, in <-chan *Request, t *testing.T) {
|
||||
defer ch.Close()
|
||||
// this string is returned to stdout
|
||||
shell := newServerShell(ch, in, "golang")
|
||||
readLine(shell, t)
|
||||
sendStatus(0, ch, t)
|
||||
}
|
||||
|
||||
// Ignores the command, writes fixed strings to stderr and stdout.
|
||||
// Strings are "this-is-stdout." and "this-is-stderr.".
|
||||
func fixedOutputHandler(ch Channel, in <-chan *Request, t *testing.T) {
|
||||
defer ch.Close()
|
||||
_, err := ch.Read(nil)
|
||||
|
||||
req, ok := <-in
|
||||
if !ok {
|
||||
t.Fatalf("error: expected channel request, got: %#v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// ignore request, always send some text
|
||||
req.Reply(true, nil)
|
||||
|
||||
_, err = io.WriteString(ch, "this-is-stdout.")
|
||||
if err != nil {
|
||||
t.Fatalf("error writing on server: %v", err)
|
||||
}
|
||||
_, err = io.WriteString(ch.Stderr(), "this-is-stderr.")
|
||||
if err != nil {
|
||||
t.Fatalf("error writing on server: %v", err)
|
||||
}
|
||||
sendStatus(0, ch, t)
|
||||
}
|
||||
|
||||
func readLine(shell *terminal.Terminal, t *testing.T) {
|
||||
if _, err := shell.ReadLine(); err != nil && err != io.EOF {
|
||||
t.Errorf("unable to read line: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func sendStatus(status uint32, ch Channel, t *testing.T) {
|
||||
msg := exitStatusMsg{
|
||||
Status: status,
|
||||
}
|
||||
if _, err := ch.SendRequest("exit-status", false, Marshal(&msg)); err != nil {
|
||||
t.Errorf("unable to send status: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func sendSignal(signal string, ch Channel, t *testing.T) {
|
||||
sig := exitSignalMsg{
|
||||
Signal: signal,
|
||||
CoreDumped: false,
|
||||
Errmsg: "Process terminated",
|
||||
Lang: "en-GB-oed",
|
||||
}
|
||||
if _, err := ch.SendRequest("exit-signal", false, Marshal(&sig)); err != nil {
|
||||
t.Errorf("unable to send signal: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func discardHandler(ch Channel, t *testing.T) {
|
||||
defer ch.Close()
|
||||
io.Copy(ioutil.Discard, ch)
|
||||
}
|
||||
|
||||
func echoHandler(ch Channel, in <-chan *Request, t *testing.T) {
|
||||
defer ch.Close()
|
||||
if n, err := copyNRandomly("echohandler", ch, ch, windowTestBytes); err != nil {
|
||||
t.Errorf("short write, wrote %d, expected %d: %v ", n, windowTestBytes, err)
|
||||
}
|
||||
}
|
||||
|
||||
// copyNRandomly copies n bytes from src to dst. It uses a variable, and random,
|
||||
// buffer size to exercise more code paths.
|
||||
func copyNRandomly(title string, dst io.Writer, src io.Reader, n int) (int, error) {
|
||||
var (
|
||||
buf = make([]byte, 32*1024)
|
||||
written int
|
||||
remaining = n
|
||||
)
|
||||
for remaining > 0 {
|
||||
l := rand.Intn(1 << 15)
|
||||
if remaining < l {
|
||||
l = remaining
|
||||
}
|
||||
nr, er := src.Read(buf[:l])
|
||||
nw, ew := dst.Write(buf[:nr])
|
||||
remaining -= nw
|
||||
written += nw
|
||||
if ew != nil {
|
||||
return written, ew
|
||||
}
|
||||
if nr != nw {
|
||||
return written, io.ErrShortWrite
|
||||
}
|
||||
if er != nil && er != io.EOF {
|
||||
return written, er
|
||||
}
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
|
||||
func channelKeepaliveSender(ch Channel, in <-chan *Request, t *testing.T) {
|
||||
defer ch.Close()
|
||||
shell := newServerShell(ch, in, "> ")
|
||||
readLine(shell, t)
|
||||
if _, err := ch.SendRequest("keepalive@openssh.com", true, nil); err != nil {
|
||||
t.Errorf("unable to send channel keepalive request: %v", err)
|
||||
}
|
||||
sendStatus(0, ch, t)
|
||||
}
|
||||
|
||||
func TestClientWriteEOF(t *testing.T) {
|
||||
conn := dial(simpleEchoHandler, t)
|
||||
defer conn.Close()
|
||||
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer session.Close()
|
||||
stdin, err := session.StdinPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("StdinPipe failed: %v", err)
|
||||
}
|
||||
stdout, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("StdoutPipe failed: %v", err)
|
||||
}
|
||||
|
||||
data := []byte(`0000`)
|
||||
_, err = stdin.Write(data)
|
||||
if err != nil {
|
||||
t.Fatalf("Write failed: %v", err)
|
||||
}
|
||||
stdin.Close()
|
||||
|
||||
res, err := ioutil.ReadAll(stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("Read failed: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(data, res) {
|
||||
t.Fatalf("Read differed from write, wrote: %v, read: %v", data, res)
|
||||
}
|
||||
}
|
||||
|
||||
func simpleEchoHandler(ch Channel, in <-chan *Request, t *testing.T) {
|
||||
defer ch.Close()
|
||||
data, err := ioutil.ReadAll(ch)
|
||||
if err != nil {
|
||||
t.Errorf("handler read error: %v", err)
|
||||
}
|
||||
_, err = ch.Write(data)
|
||||
if err != nil {
|
||||
t.Errorf("handler write error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionID(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serverID := make(chan []byte, 1)
|
||||
clientID := make(chan []byte, 1)
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||
clientConf := &ClientConfig{
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
User: "user",
|
||||
}
|
||||
|
||||
go func() {
|
||||
conn, chans, reqs, err := NewServerConn(c1, serverConf)
|
||||
if err != nil {
|
||||
t.Fatalf("server handshake: %v", err)
|
||||
}
|
||||
serverID <- conn.SessionID()
|
||||
go DiscardRequests(reqs)
|
||||
for ch := range chans {
|
||||
ch.Reject(Prohibited, "")
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
conn, chans, reqs, err := NewClientConn(c2, "", clientConf)
|
||||
if err != nil {
|
||||
t.Fatalf("client handshake: %v", err)
|
||||
}
|
||||
clientID <- conn.SessionID()
|
||||
go DiscardRequests(reqs)
|
||||
for ch := range chans {
|
||||
ch.Reject(Prohibited, "")
|
||||
}
|
||||
}()
|
||||
|
||||
s := <-serverID
|
||||
c := <-clientID
|
||||
if bytes.Compare(s, c) != 0 {
|
||||
t.Errorf("server session ID (%x) != client session ID (%x)", s, c)
|
||||
} else if len(s) == 0 {
|
||||
t.Errorf("client and server SessionID were empty.")
|
||||
}
|
||||
}
|
||||
|
||||
type noReadConn struct {
|
||||
readSeen bool
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *noReadConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *noReadConn) Read(b []byte) (int, error) {
|
||||
c.readSeen = true
|
||||
return 0, errors.New("noReadConn error")
|
||||
}
|
||||
|
||||
func TestInvalidServerConfiguration(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serveConn := noReadConn{Conn: c1}
|
||||
serverConf := &ServerConfig{}
|
||||
|
||||
NewServerConn(&serveConn, serverConf)
|
||||
if serveConn.readSeen {
|
||||
t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing host key")
|
||||
}
|
||||
|
||||
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||
|
||||
NewServerConn(&serveConn, serverConf)
|
||||
if serveConn.readSeen {
|
||||
t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing authentication method")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostKeyAlgorithms(t *testing.T) {
|
||||
serverConf := &ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||
|
||||
connect := func(clientConf *ClientConfig, want string) {
|
||||
var alg string
|
||||
clientConf.HostKeyCallback = func(h string, a net.Addr, key PublicKey) error {
|
||||
alg = key.Type()
|
||||
return nil
|
||||
}
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
go NewServerConn(c1, serverConf)
|
||||
_, _, _, err = NewClientConn(c2, "", clientConf)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientConn: %v", err)
|
||||
}
|
||||
if alg != want {
|
||||
t.Errorf("selected key algorithm %s, want %s", alg, want)
|
||||
}
|
||||
}
|
||||
|
||||
// By default, we get the preferred algorithm, which is ECDSA 256.
|
||||
|
||||
clientConf := &ClientConfig{
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
connect(clientConf, KeyAlgoECDSA256)
|
||||
|
||||
// Client asks for RSA explicitly.
|
||||
clientConf.HostKeyAlgorithms = []string{KeyAlgoRSA}
|
||||
connect(clientConf, KeyAlgoRSA)
|
||||
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
go NewServerConn(c1, serverConf)
|
||||
clientConf.HostKeyAlgorithms = []string{"nonexistent-hostkey-algo"}
|
||||
_, _, _, err = NewClientConn(c2, "", clientConf)
|
||||
if err == nil {
|
||||
t.Fatal("succeeded connecting with unknown hostkey algorithm")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
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",
|
||||
}
|
||||
}
|
||||
465
ssh/tcpip.go
465
ssh/tcpip.go
|
|
@ -1,465 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Listen requests the remote peer open a listening socket on
|
||||
// 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) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// Automatic port allocation is broken with OpenSSH before 6.0. See
|
||||
// also https://bugzilla.mindrot.org/show_bug.cgi?id=2017. In
|
||||
// particular, OpenSSH 5.9 sends a channelOpenMsg with port number 0,
|
||||
// rather than the actual port number. This means you can never open
|
||||
// two different listeners with auto allocated ports. We work around
|
||||
// this by trying explicit ports until we succeed.
|
||||
|
||||
const openSSHPrefix = "OpenSSH_"
|
||||
|
||||
var portRandomizer = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
// isBrokenOpenSSHVersion returns true if the given version string
|
||||
// specifies a version of OpenSSH that is known to have a bug in port
|
||||
// forwarding.
|
||||
func isBrokenOpenSSHVersion(versionStr string) bool {
|
||||
i := strings.Index(versionStr, openSSHPrefix)
|
||||
if i < 0 {
|
||||
return false
|
||||
}
|
||||
i += len(openSSHPrefix)
|
||||
j := i
|
||||
for ; j < len(versionStr); j++ {
|
||||
if versionStr[j] < '0' || versionStr[j] > '9' {
|
||||
break
|
||||
}
|
||||
}
|
||||
version, _ := strconv.Atoi(versionStr[i:j])
|
||||
return version < 6
|
||||
}
|
||||
|
||||
// autoPortListenWorkaround simulates automatic port allocation by
|
||||
// trying random ports repeatedly.
|
||||
func (c *Client) autoPortListenWorkaround(laddr *net.TCPAddr) (net.Listener, error) {
|
||||
var sshListener net.Listener
|
||||
var err error
|
||||
const tries = 10
|
||||
for i := 0; i < tries; i++ {
|
||||
addr := *laddr
|
||||
addr.Port = 1024 + portRandomizer.Intn(60000)
|
||||
sshListener, err = c.ListenTCP(&addr)
|
||||
if err == nil {
|
||||
laddr.Port = addr.Port
|
||||
return sshListener, err
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("ssh: listen on random port failed after %d tries: %v", tries, err)
|
||||
}
|
||||
|
||||
// RFC 4254 7.1
|
||||
type channelForwardMsg struct {
|
||||
addr string
|
||||
rport uint32
|
||||
}
|
||||
|
||||
// ListenTCP requests the remote peer open a listening socket
|
||||
// on laddr. Incoming connections will be available by calling
|
||||
// Accept on the returned net.Listener.
|
||||
func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
|
||||
if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) {
|
||||
return c.autoPortListenWorkaround(laddr)
|
||||
}
|
||||
|
||||
m := channelForwardMsg{
|
||||
laddr.IP.String(),
|
||||
uint32(laddr.Port),
|
||||
}
|
||||
// send message
|
||||
ok, resp, err := c.SendRequest("tcpip-forward", true, Marshal(&m))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, errors.New("ssh: tcpip-forward request denied by peer")
|
||||
}
|
||||
|
||||
// If the original port was 0, then the remote side will
|
||||
// supply a real port number in the response.
|
||||
if laddr.Port == 0 {
|
||||
var p struct {
|
||||
Port uint32
|
||||
}
|
||||
if err := Unmarshal(resp, &p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
laddr.Port = int(p.Port)
|
||||
}
|
||||
|
||||
// Register this forward, using the port number we obtained.
|
||||
ch := c.forwards.add(laddr)
|
||||
|
||||
return &tcpListener{laddr, c, ch}, nil
|
||||
}
|
||||
|
||||
// forwardList stores a mapping between remote
|
||||
// forward requests and the tcpListeners.
|
||||
type forwardList struct {
|
||||
sync.Mutex
|
||||
entries []forwardEntry
|
||||
}
|
||||
|
||||
// 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.Addr
|
||||
c chan forward
|
||||
}
|
||||
|
||||
// forward represents an incoming forwarded tcpip connection. The
|
||||
// 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.Addr // the raddr of the incoming connection
|
||||
}
|
||||
|
||||
func (l *forwardList) add(addr net.Addr) chan forward {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
f := forwardEntry{
|
||||
laddr: addr,
|
||||
c: make(chan forward, 1),
|
||||
}
|
||||
l.entries = append(l.entries, f)
|
||||
return f.c
|
||||
}
|
||||
|
||||
// See RFC 4254, section 7.2
|
||||
type forwardedTCPPayload struct {
|
||||
Addr string
|
||||
Port uint32
|
||||
OriginAddr string
|
||||
OriginPort uint32
|
||||
}
|
||||
|
||||
// parseTCPAddr parses the originating address from the remote into a *net.TCPAddr.
|
||||
func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) {
|
||||
if port == 0 || port > 65535 {
|
||||
return nil, fmt.Errorf("ssh: port number out of range: %d", port)
|
||||
}
|
||||
ip := net.ParseIP(string(addr))
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("ssh: cannot parse IP address %q", addr)
|
||||
}
|
||||
return &net.TCPAddr{IP: ip, Port: int(port)}, nil
|
||||
}
|
||||
|
||||
func (l *forwardList) handleChannels(in <-chan NewChannel) {
|
||||
for ch := range in {
|
||||
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
|
||||
}
|
||||
|
||||
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.Addr) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
for i, f := range l.entries {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// closeAll closes and clears all forwards.
|
||||
func (l *forwardList) closeAll() {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
for _, f := range l.entries {
|
||||
close(f.c)
|
||||
}
|
||||
l.entries = nil
|
||||
}
|
||||
|
||||
func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
for _, f := range l.entries {
|
||||
if laddr.Network() == f.laddr.Network() && laddr.String() == f.laddr.String() {
|
||||
f.c <- forward{newCh: ch, raddr: raddr}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type tcpListener struct {
|
||||
laddr *net.TCPAddr
|
||||
|
||||
conn *Client
|
||||
in <-chan forward
|
||||
}
|
||||
|
||||
// Accept waits for and returns the next connection to the listener.
|
||||
func (l *tcpListener) 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: l.laddr,
|
||||
raddr: s.raddr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close closes the listener.
|
||||
func (l *tcpListener) Close() error {
|
||||
m := channelForwardMsg{
|
||||
l.laddr.IP.String(),
|
||||
uint32(l.laddr.Port),
|
||||
}
|
||||
|
||||
// this also closes the listener.
|
||||
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")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Addr returns the listener's network address.
|
||||
func (l *tcpListener) Addr() net.Addr {
|
||||
return l.laddr
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// DialTCP connects to the remote address raddr on the network net,
|
||||
// which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is used
|
||||
// as the local address for the connection.
|
||||
func (c *Client) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) {
|
||||
if laddr == nil {
|
||||
laddr = &net.TCPAddr{
|
||||
IP: net.IPv4zero,
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
ch, err := c.dial(laddr.IP.String(), laddr.Port, raddr.IP.String(), raddr.Port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &chanConn{
|
||||
Channel: ch,
|
||||
laddr: laddr,
|
||||
raddr: raddr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RFC 4254 7.2
|
||||
type channelOpenDirectMsg struct {
|
||||
raddr string
|
||||
rport uint32
|
||||
laddr string
|
||||
lport uint32
|
||||
}
|
||||
|
||||
func (c *Client) dial(laddr string, lport int, raddr string, rport int) (Channel, error) {
|
||||
msg := channelOpenDirectMsg{
|
||||
raddr: raddr,
|
||||
rport: uint32(rport),
|
||||
laddr: laddr,
|
||||
lport: uint32(lport),
|
||||
}
|
||||
ch, in, err := c.OpenChannel("direct-tcpip", Marshal(&msg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go DiscardRequests(in)
|
||||
return ch, err
|
||||
}
|
||||
|
||||
type tcpChan struct {
|
||||
Channel // the backing channel
|
||||
}
|
||||
|
||||
// chanConn fulfills the net.Conn interface without
|
||||
// the tcpChan having to hold laddr or raddr directly.
|
||||
type chanConn struct {
|
||||
Channel
|
||||
laddr, raddr net.Addr
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
func (t *chanConn) LocalAddr() net.Addr {
|
||||
return t.laddr
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (t *chanConn) RemoteAddr() net.Addr {
|
||||
return t.raddr
|
||||
}
|
||||
|
||||
// SetDeadline sets the read and write deadlines associated
|
||||
// with the connection.
|
||||
func (t *chanConn) SetDeadline(deadline time.Time) error {
|
||||
if err := t.SetReadDeadline(deadline); err != nil {
|
||||
return err
|
||||
}
|
||||
return t.SetWriteDeadline(deadline)
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the read deadline.
|
||||
// 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 *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 *chanConn) SetWriteDeadline(deadline time.Time) error {
|
||||
return errors.New("ssh: tcpChan: deadline not supported")
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAutoPortListenBroken(t *testing.T) {
|
||||
broken := "SSH-2.0-OpenSSH_5.9hh11"
|
||||
works := "SSH-2.0-OpenSSH_6.1"
|
||||
if !isBrokenOpenSSHVersion(broken) {
|
||||
t.Errorf("version %q not marked as broken", broken)
|
||||
}
|
||||
if isBrokenOpenSSHVersion(works) {
|
||||
t.Errorf("version %q marked as broken", works)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,951 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// EscapeCodes contains escape sequences that can be written to the terminal in
|
||||
// order to achieve different styles of text.
|
||||
type EscapeCodes struct {
|
||||
// Foreground colors
|
||||
Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
|
||||
|
||||
// Reset all attributes
|
||||
Reset []byte
|
||||
}
|
||||
|
||||
var vt100EscapeCodes = EscapeCodes{
|
||||
Black: []byte{keyEscape, '[', '3', '0', 'm'},
|
||||
Red: []byte{keyEscape, '[', '3', '1', 'm'},
|
||||
Green: []byte{keyEscape, '[', '3', '2', 'm'},
|
||||
Yellow: []byte{keyEscape, '[', '3', '3', 'm'},
|
||||
Blue: []byte{keyEscape, '[', '3', '4', 'm'},
|
||||
Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
|
||||
Cyan: []byte{keyEscape, '[', '3', '6', 'm'},
|
||||
White: []byte{keyEscape, '[', '3', '7', 'm'},
|
||||
|
||||
Reset: []byte{keyEscape, '[', '0', 'm'},
|
||||
}
|
||||
|
||||
// Terminal contains the state for running a VT100 terminal that is capable of
|
||||
// reading lines of input.
|
||||
type Terminal struct {
|
||||
// AutoCompleteCallback, if non-null, is called for each keypress with
|
||||
// the full input line and the current position of the cursor (in
|
||||
// bytes, as an index into |line|). If it returns ok=false, the key
|
||||
// press is processed normally. Otherwise it returns a replacement line
|
||||
// and the new cursor position.
|
||||
AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
|
||||
|
||||
// Escape contains a pointer to the escape codes for this terminal.
|
||||
// It's always a valid pointer, although the escape codes themselves
|
||||
// may be empty if the terminal doesn't support them.
|
||||
Escape *EscapeCodes
|
||||
|
||||
// lock protects the terminal and the state in this object from
|
||||
// concurrent processing of a key press and a Write() call.
|
||||
lock sync.Mutex
|
||||
|
||||
c io.ReadWriter
|
||||
prompt []rune
|
||||
|
||||
// line is the current line being entered.
|
||||
line []rune
|
||||
// pos is the logical position of the cursor in line
|
||||
pos int
|
||||
// echo is true if local echo is enabled
|
||||
echo bool
|
||||
// pasteActive is true iff there is a bracketed paste operation in
|
||||
// progress.
|
||||
pasteActive bool
|
||||
|
||||
// cursorX contains the current X value of the cursor where the left
|
||||
// edge is 0. cursorY contains the row number where the first row of
|
||||
// the current line is 0.
|
||||
cursorX, cursorY int
|
||||
// maxLine is the greatest value of cursorY so far.
|
||||
maxLine int
|
||||
|
||||
termWidth, termHeight int
|
||||
|
||||
// outBuf contains the terminal data to be sent.
|
||||
outBuf []byte
|
||||
// remainder contains the remainder of any partial key sequences after
|
||||
// a read. It aliases into inBuf.
|
||||
remainder []byte
|
||||
inBuf [256]byte
|
||||
|
||||
// history contains previously entered commands so that they can be
|
||||
// accessed with the up and down keys.
|
||||
history stRingBuffer
|
||||
// historyIndex stores the currently accessed history entry, where zero
|
||||
// means the immediately previous entry.
|
||||
historyIndex int
|
||||
// When navigating up and down the history it's possible to return to
|
||||
// the incomplete, initial line. That value is stored in
|
||||
// historyPending.
|
||||
historyPending string
|
||||
}
|
||||
|
||||
// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
|
||||
// a local terminal, that terminal must first have been put into raw mode.
|
||||
// prompt is a string that is written at the start of each input line (i.e.
|
||||
// "> ").
|
||||
func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
|
||||
return &Terminal{
|
||||
Escape: &vt100EscapeCodes,
|
||||
c: c,
|
||||
prompt: []rune(prompt),
|
||||
termWidth: 80,
|
||||
termHeight: 24,
|
||||
echo: true,
|
||||
historyIndex: -1,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
keyCtrlD = 4
|
||||
keyCtrlU = 21
|
||||
keyEnter = '\r'
|
||||
keyEscape = 27
|
||||
keyBackspace = 127
|
||||
keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota
|
||||
keyUp
|
||||
keyDown
|
||||
keyLeft
|
||||
keyRight
|
||||
keyAltLeft
|
||||
keyAltRight
|
||||
keyHome
|
||||
keyEnd
|
||||
keyDeleteWord
|
||||
keyDeleteLine
|
||||
keyClearScreen
|
||||
keyPasteStart
|
||||
keyPasteEnd
|
||||
)
|
||||
|
||||
var (
|
||||
crlf = []byte{'\r', '\n'}
|
||||
pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
|
||||
pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'}
|
||||
)
|
||||
|
||||
// bytesToKey tries to parse a key sequence from b. If successful, it returns
|
||||
// the key and the remainder of the input. Otherwise it returns utf8.RuneError.
|
||||
func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
|
||||
if len(b) == 0 {
|
||||
return utf8.RuneError, nil
|
||||
}
|
||||
|
||||
if !pasteActive {
|
||||
switch b[0] {
|
||||
case 1: // ^A
|
||||
return keyHome, b[1:]
|
||||
case 5: // ^E
|
||||
return keyEnd, b[1:]
|
||||
case 8: // ^H
|
||||
return keyBackspace, b[1:]
|
||||
case 11: // ^K
|
||||
return keyDeleteLine, b[1:]
|
||||
case 12: // ^L
|
||||
return keyClearScreen, b[1:]
|
||||
case 23: // ^W
|
||||
return keyDeleteWord, b[1:]
|
||||
}
|
||||
}
|
||||
|
||||
if b[0] != keyEscape {
|
||||
if !utf8.FullRune(b) {
|
||||
return utf8.RuneError, b
|
||||
}
|
||||
r, l := utf8.DecodeRune(b)
|
||||
return r, b[l:]
|
||||
}
|
||||
|
||||
if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
|
||||
switch b[2] {
|
||||
case 'A':
|
||||
return keyUp, b[3:]
|
||||
case 'B':
|
||||
return keyDown, b[3:]
|
||||
case 'C':
|
||||
return keyRight, b[3:]
|
||||
case 'D':
|
||||
return keyLeft, b[3:]
|
||||
case 'H':
|
||||
return keyHome, b[3:]
|
||||
case 'F':
|
||||
return keyEnd, b[3:]
|
||||
}
|
||||
}
|
||||
|
||||
if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
|
||||
switch b[5] {
|
||||
case 'C':
|
||||
return keyAltRight, b[6:]
|
||||
case 'D':
|
||||
return keyAltLeft, b[6:]
|
||||
}
|
||||
}
|
||||
|
||||
if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) {
|
||||
return keyPasteStart, b[6:]
|
||||
}
|
||||
|
||||
if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) {
|
||||
return keyPasteEnd, b[6:]
|
||||
}
|
||||
|
||||
// If we get here then we have a key that we don't recognise, or a
|
||||
// partial sequence. It's not clear how one should find the end of a
|
||||
// sequence without knowing them all, but it seems that [a-zA-Z~] only
|
||||
// appears at the end of a sequence.
|
||||
for i, c := range b[0:] {
|
||||
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' {
|
||||
return keyUnknown, b[i+1:]
|
||||
}
|
||||
}
|
||||
|
||||
return utf8.RuneError, b
|
||||
}
|
||||
|
||||
// queue appends data to the end of t.outBuf
|
||||
func (t *Terminal) queue(data []rune) {
|
||||
t.outBuf = append(t.outBuf, []byte(string(data))...)
|
||||
}
|
||||
|
||||
var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'}
|
||||
var space = []rune{' '}
|
||||
|
||||
func isPrintable(key rune) bool {
|
||||
isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
|
||||
return key >= 32 && !isInSurrogateArea
|
||||
}
|
||||
|
||||
// moveCursorToPos appends data to t.outBuf which will move the cursor to the
|
||||
// given, logical position in the text.
|
||||
func (t *Terminal) moveCursorToPos(pos int) {
|
||||
if !t.echo {
|
||||
return
|
||||
}
|
||||
|
||||
x := visualLength(t.prompt) + pos
|
||||
y := x / t.termWidth
|
||||
x = x % t.termWidth
|
||||
|
||||
up := 0
|
||||
if y < t.cursorY {
|
||||
up = t.cursorY - y
|
||||
}
|
||||
|
||||
down := 0
|
||||
if y > t.cursorY {
|
||||
down = y - t.cursorY
|
||||
}
|
||||
|
||||
left := 0
|
||||
if x < t.cursorX {
|
||||
left = t.cursorX - x
|
||||
}
|
||||
|
||||
right := 0
|
||||
if x > t.cursorX {
|
||||
right = x - t.cursorX
|
||||
}
|
||||
|
||||
t.cursorX = x
|
||||
t.cursorY = y
|
||||
t.move(up, down, left, right)
|
||||
}
|
||||
|
||||
func (t *Terminal) move(up, down, left, right int) {
|
||||
movement := make([]rune, 3*(up+down+left+right))
|
||||
m := movement
|
||||
for i := 0; i < up; i++ {
|
||||
m[0] = keyEscape
|
||||
m[1] = '['
|
||||
m[2] = 'A'
|
||||
m = m[3:]
|
||||
}
|
||||
for i := 0; i < down; i++ {
|
||||
m[0] = keyEscape
|
||||
m[1] = '['
|
||||
m[2] = 'B'
|
||||
m = m[3:]
|
||||
}
|
||||
for i := 0; i < left; i++ {
|
||||
m[0] = keyEscape
|
||||
m[1] = '['
|
||||
m[2] = 'D'
|
||||
m = m[3:]
|
||||
}
|
||||
for i := 0; i < right; i++ {
|
||||
m[0] = keyEscape
|
||||
m[1] = '['
|
||||
m[2] = 'C'
|
||||
m = m[3:]
|
||||
}
|
||||
|
||||
t.queue(movement)
|
||||
}
|
||||
|
||||
func (t *Terminal) clearLineToRight() {
|
||||
op := []rune{keyEscape, '[', 'K'}
|
||||
t.queue(op)
|
||||
}
|
||||
|
||||
const maxLineLength = 4096
|
||||
|
||||
func (t *Terminal) setLine(newLine []rune, newPos int) {
|
||||
if t.echo {
|
||||
t.moveCursorToPos(0)
|
||||
t.writeLine(newLine)
|
||||
for i := len(newLine); i < len(t.line); i++ {
|
||||
t.writeLine(space)
|
||||
}
|
||||
t.moveCursorToPos(newPos)
|
||||
}
|
||||
t.line = newLine
|
||||
t.pos = newPos
|
||||
}
|
||||
|
||||
func (t *Terminal) advanceCursor(places int) {
|
||||
t.cursorX += places
|
||||
t.cursorY += t.cursorX / t.termWidth
|
||||
if t.cursorY > t.maxLine {
|
||||
t.maxLine = t.cursorY
|
||||
}
|
||||
t.cursorX = t.cursorX % t.termWidth
|
||||
|
||||
if places > 0 && t.cursorX == 0 {
|
||||
// Normally terminals will advance the current position
|
||||
// when writing a character. But that doesn't happen
|
||||
// for the last character in a line. However, when
|
||||
// writing a character (except a new line) that causes
|
||||
// a line wrap, the position will be advanced two
|
||||
// places.
|
||||
//
|
||||
// So, if we are stopping at the end of a line, we
|
||||
// need to write a newline so that our cursor can be
|
||||
// advanced to the next line.
|
||||
t.outBuf = append(t.outBuf, '\r', '\n')
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminal) eraseNPreviousChars(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if t.pos < n {
|
||||
n = t.pos
|
||||
}
|
||||
t.pos -= n
|
||||
t.moveCursorToPos(t.pos)
|
||||
|
||||
copy(t.line[t.pos:], t.line[n+t.pos:])
|
||||
t.line = t.line[:len(t.line)-n]
|
||||
if t.echo {
|
||||
t.writeLine(t.line[t.pos:])
|
||||
for i := 0; i < n; i++ {
|
||||
t.queue(space)
|
||||
}
|
||||
t.advanceCursor(n)
|
||||
t.moveCursorToPos(t.pos)
|
||||
}
|
||||
}
|
||||
|
||||
// countToLeftWord returns then number of characters from the cursor to the
|
||||
// start of the previous word.
|
||||
func (t *Terminal) countToLeftWord() int {
|
||||
if t.pos == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
pos := t.pos - 1
|
||||
for pos > 0 {
|
||||
if t.line[pos] != ' ' {
|
||||
break
|
||||
}
|
||||
pos--
|
||||
}
|
||||
for pos > 0 {
|
||||
if t.line[pos] == ' ' {
|
||||
pos++
|
||||
break
|
||||
}
|
||||
pos--
|
||||
}
|
||||
|
||||
return t.pos - pos
|
||||
}
|
||||
|
||||
// countToRightWord returns then number of characters from the cursor to the
|
||||
// start of the next word.
|
||||
func (t *Terminal) countToRightWord() int {
|
||||
pos := t.pos
|
||||
for pos < len(t.line) {
|
||||
if t.line[pos] == ' ' {
|
||||
break
|
||||
}
|
||||
pos++
|
||||
}
|
||||
for pos < len(t.line) {
|
||||
if t.line[pos] != ' ' {
|
||||
break
|
||||
}
|
||||
pos++
|
||||
}
|
||||
return pos - t.pos
|
||||
}
|
||||
|
||||
// visualLength returns the number of visible glyphs in s.
|
||||
func visualLength(runes []rune) int {
|
||||
inEscapeSeq := false
|
||||
length := 0
|
||||
|
||||
for _, r := range runes {
|
||||
switch {
|
||||
case inEscapeSeq:
|
||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
|
||||
inEscapeSeq = false
|
||||
}
|
||||
case r == '\x1b':
|
||||
inEscapeSeq = true
|
||||
default:
|
||||
length++
|
||||
}
|
||||
}
|
||||
|
||||
return length
|
||||
}
|
||||
|
||||
// handleKey processes the given key and, optionally, returns a line of text
|
||||
// that the user has entered.
|
||||
func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
||||
if t.pasteActive && key != keyEnter {
|
||||
t.addKeyToLine(key)
|
||||
return
|
||||
}
|
||||
|
||||
switch key {
|
||||
case keyBackspace:
|
||||
if t.pos == 0 {
|
||||
return
|
||||
}
|
||||
t.eraseNPreviousChars(1)
|
||||
case keyAltLeft:
|
||||
// move left by a word.
|
||||
t.pos -= t.countToLeftWord()
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyAltRight:
|
||||
// move right by a word.
|
||||
t.pos += t.countToRightWord()
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyLeft:
|
||||
if t.pos == 0 {
|
||||
return
|
||||
}
|
||||
t.pos--
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyRight:
|
||||
if t.pos == len(t.line) {
|
||||
return
|
||||
}
|
||||
t.pos++
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyHome:
|
||||
if t.pos == 0 {
|
||||
return
|
||||
}
|
||||
t.pos = 0
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyEnd:
|
||||
if t.pos == len(t.line) {
|
||||
return
|
||||
}
|
||||
t.pos = len(t.line)
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyUp:
|
||||
entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if t.historyIndex == -1 {
|
||||
t.historyPending = string(t.line)
|
||||
}
|
||||
t.historyIndex++
|
||||
runes := []rune(entry)
|
||||
t.setLine(runes, len(runes))
|
||||
case keyDown:
|
||||
switch t.historyIndex {
|
||||
case -1:
|
||||
return
|
||||
case 0:
|
||||
runes := []rune(t.historyPending)
|
||||
t.setLine(runes, len(runes))
|
||||
t.historyIndex--
|
||||
default:
|
||||
entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
|
||||
if ok {
|
||||
t.historyIndex--
|
||||
runes := []rune(entry)
|
||||
t.setLine(runes, len(runes))
|
||||
}
|
||||
}
|
||||
case keyEnter:
|
||||
t.moveCursorToPos(len(t.line))
|
||||
t.queue([]rune("\r\n"))
|
||||
line = string(t.line)
|
||||
ok = true
|
||||
t.line = t.line[:0]
|
||||
t.pos = 0
|
||||
t.cursorX = 0
|
||||
t.cursorY = 0
|
||||
t.maxLine = 0
|
||||
case keyDeleteWord:
|
||||
// Delete zero or more spaces and then one or more characters.
|
||||
t.eraseNPreviousChars(t.countToLeftWord())
|
||||
case keyDeleteLine:
|
||||
// Delete everything from the current cursor position to the
|
||||
// end of line.
|
||||
for i := t.pos; i < len(t.line); i++ {
|
||||
t.queue(space)
|
||||
t.advanceCursor(1)
|
||||
}
|
||||
t.line = t.line[:t.pos]
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyCtrlD:
|
||||
// Erase the character under the current position.
|
||||
// The EOF case when the line is empty is handled in
|
||||
// readLine().
|
||||
if t.pos < len(t.line) {
|
||||
t.pos++
|
||||
t.eraseNPreviousChars(1)
|
||||
}
|
||||
case keyCtrlU:
|
||||
t.eraseNPreviousChars(t.pos)
|
||||
case keyClearScreen:
|
||||
// Erases the screen and moves the cursor to the home position.
|
||||
t.queue([]rune("\x1b[2J\x1b[H"))
|
||||
t.queue(t.prompt)
|
||||
t.cursorX, t.cursorY = 0, 0
|
||||
t.advanceCursor(visualLength(t.prompt))
|
||||
t.setLine(t.line, t.pos)
|
||||
default:
|
||||
if t.AutoCompleteCallback != nil {
|
||||
prefix := string(t.line[:t.pos])
|
||||
suffix := string(t.line[t.pos:])
|
||||
|
||||
t.lock.Unlock()
|
||||
newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key)
|
||||
t.lock.Lock()
|
||||
|
||||
if completeOk {
|
||||
t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos]))
|
||||
return
|
||||
}
|
||||
}
|
||||
if !isPrintable(key) {
|
||||
return
|
||||
}
|
||||
if len(t.line) == maxLineLength {
|
||||
return
|
||||
}
|
||||
t.addKeyToLine(key)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// addKeyToLine inserts the given key at the current position in the current
|
||||
// line.
|
||||
func (t *Terminal) addKeyToLine(key rune) {
|
||||
if len(t.line) == cap(t.line) {
|
||||
newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
|
||||
copy(newLine, t.line)
|
||||
t.line = newLine
|
||||
}
|
||||
t.line = t.line[:len(t.line)+1]
|
||||
copy(t.line[t.pos+1:], t.line[t.pos:])
|
||||
t.line[t.pos] = key
|
||||
if t.echo {
|
||||
t.writeLine(t.line[t.pos:])
|
||||
}
|
||||
t.pos++
|
||||
t.moveCursorToPos(t.pos)
|
||||
}
|
||||
|
||||
func (t *Terminal) writeLine(line []rune) {
|
||||
for len(line) != 0 {
|
||||
remainingOnLine := t.termWidth - t.cursorX
|
||||
todo := len(line)
|
||||
if todo > remainingOnLine {
|
||||
todo = remainingOnLine
|
||||
}
|
||||
t.queue(line[:todo])
|
||||
t.advanceCursor(visualLength(line[:todo]))
|
||||
line = line[todo:]
|
||||
}
|
||||
}
|
||||
|
||||
// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n.
|
||||
func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) {
|
||||
for len(buf) > 0 {
|
||||
i := bytes.IndexByte(buf, '\n')
|
||||
todo := len(buf)
|
||||
if i >= 0 {
|
||||
todo = i
|
||||
}
|
||||
|
||||
var nn int
|
||||
nn, err = w.Write(buf[:todo])
|
||||
n += nn
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
buf = buf[todo:]
|
||||
|
||||
if i >= 0 {
|
||||
if _, err = w.Write(crlf); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n += 1
|
||||
buf = buf[1:]
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (t *Terminal) Write(buf []byte) (n int, err error) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if t.cursorX == 0 && t.cursorY == 0 {
|
||||
// This is the easy case: there's nothing on the screen that we
|
||||
// have to move out of the way.
|
||||
return writeWithCRLF(t.c, buf)
|
||||
}
|
||||
|
||||
// We have a prompt and possibly user input on the screen. We
|
||||
// have to clear it first.
|
||||
t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */)
|
||||
t.cursorX = 0
|
||||
t.clearLineToRight()
|
||||
|
||||
for t.cursorY > 0 {
|
||||
t.move(1 /* up */, 0, 0, 0)
|
||||
t.cursorY--
|
||||
t.clearLineToRight()
|
||||
}
|
||||
|
||||
if _, err = t.c.Write(t.outBuf); err != nil {
|
||||
return
|
||||
}
|
||||
t.outBuf = t.outBuf[:0]
|
||||
|
||||
if n, err = writeWithCRLF(t.c, buf); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.writeLine(t.prompt)
|
||||
if t.echo {
|
||||
t.writeLine(t.line)
|
||||
}
|
||||
|
||||
t.moveCursorToPos(t.pos)
|
||||
|
||||
if _, err = t.c.Write(t.outBuf); err != nil {
|
||||
return
|
||||
}
|
||||
t.outBuf = t.outBuf[:0]
|
||||
return
|
||||
}
|
||||
|
||||
// ReadPassword temporarily changes the prompt and reads a password, without
|
||||
// echo, from the terminal.
|
||||
func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
oldPrompt := t.prompt
|
||||
t.prompt = []rune(prompt)
|
||||
t.echo = false
|
||||
|
||||
line, err = t.readLine()
|
||||
|
||||
t.prompt = oldPrompt
|
||||
t.echo = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ReadLine returns a line of input from the terminal.
|
||||
func (t *Terminal) ReadLine() (line string, err error) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
return t.readLine()
|
||||
}
|
||||
|
||||
func (t *Terminal) readLine() (line string, err error) {
|
||||
// t.lock must be held at this point
|
||||
|
||||
if t.cursorX == 0 && t.cursorY == 0 {
|
||||
t.writeLine(t.prompt)
|
||||
t.c.Write(t.outBuf)
|
||||
t.outBuf = t.outBuf[:0]
|
||||
}
|
||||
|
||||
lineIsPasted := t.pasteActive
|
||||
|
||||
for {
|
||||
rest := t.remainder
|
||||
lineOk := false
|
||||
for !lineOk {
|
||||
var key rune
|
||||
key, rest = bytesToKey(rest, t.pasteActive)
|
||||
if key == utf8.RuneError {
|
||||
break
|
||||
}
|
||||
if !t.pasteActive {
|
||||
if key == keyCtrlD {
|
||||
if len(t.line) == 0 {
|
||||
return "", io.EOF
|
||||
}
|
||||
}
|
||||
if key == keyPasteStart {
|
||||
t.pasteActive = true
|
||||
if len(t.line) == 0 {
|
||||
lineIsPasted = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
} else if key == keyPasteEnd {
|
||||
t.pasteActive = false
|
||||
continue
|
||||
}
|
||||
if !t.pasteActive {
|
||||
lineIsPasted = false
|
||||
}
|
||||
line, lineOk = t.handleKey(key)
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
n := copy(t.inBuf[:], rest)
|
||||
t.remainder = t.inBuf[:n]
|
||||
} else {
|
||||
t.remainder = nil
|
||||
}
|
||||
t.c.Write(t.outBuf)
|
||||
t.outBuf = t.outBuf[:0]
|
||||
if lineOk {
|
||||
if t.echo {
|
||||
t.historyIndex = -1
|
||||
t.history.Add(line)
|
||||
}
|
||||
if lineIsPasted {
|
||||
err = ErrPasteIndicator
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// t.remainder is a slice at the beginning of t.inBuf
|
||||
// containing a partial key sequence
|
||||
readBuf := t.inBuf[len(t.remainder):]
|
||||
var n int
|
||||
|
||||
t.lock.Unlock()
|
||||
n, err = t.c.Read(readBuf)
|
||||
t.lock.Lock()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.remainder = t.inBuf[:n+len(t.remainder)]
|
||||
}
|
||||
}
|
||||
|
||||
// SetPrompt sets the prompt to be used when reading subsequent lines.
|
||||
func (t *Terminal) SetPrompt(prompt string) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
t.prompt = []rune(prompt)
|
||||
}
|
||||
|
||||
func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) {
|
||||
// Move cursor to column zero at the start of the line.
|
||||
t.move(t.cursorY, 0, t.cursorX, 0)
|
||||
t.cursorX, t.cursorY = 0, 0
|
||||
t.clearLineToRight()
|
||||
for t.cursorY < numPrevLines {
|
||||
// Move down a line
|
||||
t.move(0, 1, 0, 0)
|
||||
t.cursorY++
|
||||
t.clearLineToRight()
|
||||
}
|
||||
// Move back to beginning.
|
||||
t.move(t.cursorY, 0, 0, 0)
|
||||
t.cursorX, t.cursorY = 0, 0
|
||||
|
||||
t.queue(t.prompt)
|
||||
t.advanceCursor(visualLength(t.prompt))
|
||||
t.writeLine(t.line)
|
||||
t.moveCursorToPos(t.pos)
|
||||
}
|
||||
|
||||
func (t *Terminal) SetSize(width, height int) error {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if width == 0 {
|
||||
width = 1
|
||||
}
|
||||
|
||||
oldWidth := t.termWidth
|
||||
t.termWidth, t.termHeight = width, height
|
||||
|
||||
switch {
|
||||
case width == oldWidth:
|
||||
// If the width didn't change then nothing else needs to be
|
||||
// done.
|
||||
return nil
|
||||
case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
|
||||
// If there is nothing on current line and no prompt printed,
|
||||
// just do nothing
|
||||
return nil
|
||||
case width < oldWidth:
|
||||
// Some terminals (e.g. xterm) will truncate lines that were
|
||||
// too long when shinking. Others, (e.g. gnome-terminal) will
|
||||
// attempt to wrap them. For the former, repainting t.maxLine
|
||||
// works great, but that behaviour goes badly wrong in the case
|
||||
// of the latter because they have doubled every full line.
|
||||
|
||||
// We assume that we are working on a terminal that wraps lines
|
||||
// and adjust the cursor position based on every previous line
|
||||
// wrapping and turning into two. This causes the prompt on
|
||||
// xterms to move upwards, which isn't great, but it avoids a
|
||||
// huge mess with gnome-terminal.
|
||||
if t.cursorX >= t.termWidth {
|
||||
t.cursorX = t.termWidth - 1
|
||||
}
|
||||
t.cursorY *= 2
|
||||
t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2)
|
||||
case width > oldWidth:
|
||||
// If the terminal expands then our position calculations will
|
||||
// be wrong in the future because we think the cursor is
|
||||
// |t.pos| chars into the string, but there will be a gap at
|
||||
// the end of any wrapped line.
|
||||
//
|
||||
// But the position will actually be correct until we move, so
|
||||
// we can move back to the beginning and repaint everything.
|
||||
t.clearAndRepaintLinePlusNPrevious(t.maxLine)
|
||||
}
|
||||
|
||||
_, err := t.c.Write(t.outBuf)
|
||||
t.outBuf = t.outBuf[:0]
|
||||
return err
|
||||
}
|
||||
|
||||
type pasteIndicatorError struct{}
|
||||
|
||||
func (pasteIndicatorError) Error() string {
|
||||
return "terminal: ErrPasteIndicator not correctly handled"
|
||||
}
|
||||
|
||||
// ErrPasteIndicator may be returned from ReadLine as the error, in addition
|
||||
// to valid line data. It indicates that bracketed paste mode is enabled and
|
||||
// that the returned line consists only of pasted data. Programs may wish to
|
||||
// interpret pasted data more literally than typed data.
|
||||
var ErrPasteIndicator = pasteIndicatorError{}
|
||||
|
||||
// SetBracketedPasteMode requests that the terminal bracket paste operations
|
||||
// with markers. Not all terminals support this but, if it is supported, then
|
||||
// enabling this mode will stop any autocomplete callback from running due to
|
||||
// pastes. Additionally, any lines that are completely pasted will be returned
|
||||
// from ReadLine with the error set to ErrPasteIndicator.
|
||||
func (t *Terminal) SetBracketedPasteMode(on bool) {
|
||||
if on {
|
||||
io.WriteString(t.c, "\x1b[?2004h")
|
||||
} else {
|
||||
io.WriteString(t.c, "\x1b[?2004l")
|
||||
}
|
||||
}
|
||||
|
||||
// stRingBuffer is a ring buffer of strings.
|
||||
type stRingBuffer struct {
|
||||
// entries contains max elements.
|
||||
entries []string
|
||||
max int
|
||||
// head contains the index of the element most recently added to the ring.
|
||||
head int
|
||||
// size contains the number of elements in the ring.
|
||||
size int
|
||||
}
|
||||
|
||||
func (s *stRingBuffer) Add(a string) {
|
||||
if s.entries == nil {
|
||||
const defaultNumEntries = 100
|
||||
s.entries = make([]string, defaultNumEntries)
|
||||
s.max = defaultNumEntries
|
||||
}
|
||||
|
||||
s.head = (s.head + 1) % s.max
|
||||
s.entries[s.head] = a
|
||||
if s.size < s.max {
|
||||
s.size++
|
||||
}
|
||||
}
|
||||
|
||||
// NthPreviousEntry returns the value passed to the nth previous call to Add.
|
||||
// If n is zero then the immediately prior value is returned, if one, then the
|
||||
// next most recent, and so on. If such an element doesn't exist then ok is
|
||||
// false.
|
||||
func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
|
||||
if n >= s.size {
|
||||
return "", false
|
||||
}
|
||||
index := s.head - n
|
||||
if index < 0 {
|
||||
index += s.max
|
||||
}
|
||||
return s.entries[index], true
|
||||
}
|
||||
|
||||
// readPasswordLine reads from reader until it finds \n or io.EOF.
|
||||
// The slice returned does not include the \n.
|
||||
// readPasswordLine also ignores any \r it finds.
|
||||
func readPasswordLine(reader io.Reader) ([]byte, error) {
|
||||
var buf [1]byte
|
||||
var ret []byte
|
||||
|
||||
for {
|
||||
n, err := reader.Read(buf[:])
|
||||
if n > 0 {
|
||||
switch buf[0] {
|
||||
case '\n':
|
||||
return ret, nil
|
||||
case '\r':
|
||||
// remove \r from passwords on Windows
|
||||
default:
|
||||
ret = append(ret, buf[0])
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF && len(ret) > 0 {
|
||||
return ret, nil
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,350 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type MockTerminal struct {
|
||||
toSend []byte
|
||||
bytesPerRead int
|
||||
received []byte
|
||||
}
|
||||
|
||||
func (c *MockTerminal) Read(data []byte) (n int, err error) {
|
||||
n = len(data)
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
if n > len(c.toSend) {
|
||||
n = len(c.toSend)
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if c.bytesPerRead > 0 && n > c.bytesPerRead {
|
||||
n = c.bytesPerRead
|
||||
}
|
||||
copy(data, c.toSend[:n])
|
||||
c.toSend = c.toSend[n:]
|
||||
return
|
||||
}
|
||||
|
||||
func (c *MockTerminal) Write(data []byte) (n int, err error) {
|
||||
c.received = append(c.received, data...)
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
func TestClose(t *testing.T) {
|
||||
c := &MockTerminal{}
|
||||
ss := NewTerminal(c, "> ")
|
||||
line, err := ss.ReadLine()
|
||||
if line != "" {
|
||||
t.Errorf("Expected empty line but got: %s", line)
|
||||
}
|
||||
if err != io.EOF {
|
||||
t.Errorf("Error should have been EOF but got: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
var keyPressTests = []struct {
|
||||
in string
|
||||
line string
|
||||
err error
|
||||
throwAwayLines int
|
||||
}{
|
||||
{
|
||||
err: io.EOF,
|
||||
},
|
||||
{
|
||||
in: "\r",
|
||||
line: "",
|
||||
},
|
||||
{
|
||||
in: "foo\r",
|
||||
line: "foo",
|
||||
},
|
||||
{
|
||||
in: "a\x1b[Cb\r", // right
|
||||
line: "ab",
|
||||
},
|
||||
{
|
||||
in: "a\x1b[Db\r", // left
|
||||
line: "ba",
|
||||
},
|
||||
{
|
||||
in: "a\177b\r", // backspace
|
||||
line: "b",
|
||||
},
|
||||
{
|
||||
in: "\x1b[A\r", // up
|
||||
},
|
||||
{
|
||||
in: "\x1b[B\r", // down
|
||||
},
|
||||
{
|
||||
in: "line\x1b[A\x1b[B\r", // up then down
|
||||
line: "line",
|
||||
},
|
||||
{
|
||||
in: "line1\rline2\x1b[A\r", // recall previous line.
|
||||
line: "line1",
|
||||
throwAwayLines: 1,
|
||||
},
|
||||
{
|
||||
// recall two previous lines and append.
|
||||
in: "line1\rline2\rline3\x1b[A\x1b[Axxx\r",
|
||||
line: "line1xxx",
|
||||
throwAwayLines: 2,
|
||||
},
|
||||
{
|
||||
// Ctrl-A to move to beginning of line followed by ^K to kill
|
||||
// line.
|
||||
in: "a b \001\013\r",
|
||||
line: "",
|
||||
},
|
||||
{
|
||||
// Ctrl-A to move to beginning of line, Ctrl-E to move to end,
|
||||
// finally ^K to kill nothing.
|
||||
in: "a b \001\005\013\r",
|
||||
line: "a b ",
|
||||
},
|
||||
{
|
||||
in: "\027\r",
|
||||
line: "",
|
||||
},
|
||||
{
|
||||
in: "a\027\r",
|
||||
line: "",
|
||||
},
|
||||
{
|
||||
in: "a \027\r",
|
||||
line: "",
|
||||
},
|
||||
{
|
||||
in: "a b\027\r",
|
||||
line: "a ",
|
||||
},
|
||||
{
|
||||
in: "a b \027\r",
|
||||
line: "a ",
|
||||
},
|
||||
{
|
||||
in: "one two thr\x1b[D\027\r",
|
||||
line: "one two r",
|
||||
},
|
||||
{
|
||||
in: "\013\r",
|
||||
line: "",
|
||||
},
|
||||
{
|
||||
in: "a\013\r",
|
||||
line: "a",
|
||||
},
|
||||
{
|
||||
in: "ab\x1b[D\013\r",
|
||||
line: "a",
|
||||
},
|
||||
{
|
||||
in: "Ξεσκεπάζω\r",
|
||||
line: "Ξεσκεπάζω",
|
||||
},
|
||||
{
|
||||
in: "£\r\x1b[A\177\r", // non-ASCII char, enter, up, backspace.
|
||||
line: "",
|
||||
throwAwayLines: 1,
|
||||
},
|
||||
{
|
||||
in: "£\r££\x1b[A\x1b[B\177\r", // non-ASCII char, enter, 2x non-ASCII, up, down, backspace, enter.
|
||||
line: "£",
|
||||
throwAwayLines: 1,
|
||||
},
|
||||
{
|
||||
// Ctrl-D at the end of the line should be ignored.
|
||||
in: "a\004\r",
|
||||
line: "a",
|
||||
},
|
||||
{
|
||||
// a, b, left, Ctrl-D should erase the b.
|
||||
in: "ab\x1b[D\004\r",
|
||||
line: "a",
|
||||
},
|
||||
{
|
||||
// a, b, c, d, left, left, ^U should erase to the beginning of
|
||||
// the line.
|
||||
in: "abcd\x1b[D\x1b[D\025\r",
|
||||
line: "cd",
|
||||
},
|
||||
{
|
||||
// Bracketed paste mode: control sequences should be returned
|
||||
// verbatim in paste mode.
|
||||
in: "abc\x1b[200~de\177f\x1b[201~\177\r",
|
||||
line: "abcde\177",
|
||||
},
|
||||
{
|
||||
// Enter in bracketed paste mode should still work.
|
||||
in: "abc\x1b[200~d\refg\x1b[201~h\r",
|
||||
line: "efgh",
|
||||
throwAwayLines: 1,
|
||||
},
|
||||
{
|
||||
// Lines consisting entirely of pasted data should be indicated as such.
|
||||
in: "\x1b[200~a\r",
|
||||
line: "a",
|
||||
err: ErrPasteIndicator,
|
||||
},
|
||||
}
|
||||
|
||||
func TestKeyPresses(t *testing.T) {
|
||||
for i, test := range keyPressTests {
|
||||
for j := 1; j < len(test.in); j++ {
|
||||
c := &MockTerminal{
|
||||
toSend: []byte(test.in),
|
||||
bytesPerRead: j,
|
||||
}
|
||||
ss := NewTerminal(c, "> ")
|
||||
for k := 0; k < test.throwAwayLines; k++ {
|
||||
_, err := ss.ReadLine()
|
||||
if err != nil {
|
||||
t.Errorf("Throwaway line %d from test %d resulted in error: %s", k, i, err)
|
||||
}
|
||||
}
|
||||
line, err := ss.ReadLine()
|
||||
if line != test.line {
|
||||
t.Errorf("Line resulting from test %d (%d bytes per read) was '%s', expected '%s'", i, j, line, test.line)
|
||||
break
|
||||
}
|
||||
if err != test.err {
|
||||
t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordNotSaved(t *testing.T) {
|
||||
c := &MockTerminal{
|
||||
toSend: []byte("password\r\x1b[A\r"),
|
||||
bytesPerRead: 1,
|
||||
}
|
||||
ss := NewTerminal(c, "> ")
|
||||
pw, _ := ss.ReadPassword("> ")
|
||||
if pw != "password" {
|
||||
t.Fatalf("failed to read password, got %s", pw)
|
||||
}
|
||||
line, _ := ss.ReadLine()
|
||||
if len(line) > 0 {
|
||||
t.Fatalf("password was saved in history")
|
||||
}
|
||||
}
|
||||
|
||||
var setSizeTests = []struct {
|
||||
width, height int
|
||||
}{
|
||||
{40, 13},
|
||||
{80, 24},
|
||||
{132, 43},
|
||||
}
|
||||
|
||||
func TestTerminalSetSize(t *testing.T) {
|
||||
for _, setSize := range setSizeTests {
|
||||
c := &MockTerminal{
|
||||
toSend: []byte("password\r\x1b[A\r"),
|
||||
bytesPerRead: 1,
|
||||
}
|
||||
ss := NewTerminal(c, "> ")
|
||||
ss.SetSize(setSize.width, setSize.height)
|
||||
pw, _ := ss.ReadPassword("Password: ")
|
||||
if pw != "password" {
|
||||
t.Fatalf("failed to read password, got %s", pw)
|
||||
}
|
||||
if string(c.received) != "Password: \r\n" {
|
||||
t.Errorf("failed to set the temporary prompt expected %q, got %q", "Password: ", c.received)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadPasswordLineEnd(t *testing.T) {
|
||||
var tests = []struct {
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{"\n", ""},
|
||||
{"\r\n", ""},
|
||||
{"test\r\n", "test"},
|
||||
{"testtesttesttes\n", "testtesttesttes"},
|
||||
{"testtesttesttes\r\n", "testtesttesttes"},
|
||||
{"testtesttesttesttest\n", "testtesttesttesttest"},
|
||||
{"testtesttesttesttest\r\n", "testtesttesttesttest"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
buf := new(bytes.Buffer)
|
||||
if _, err := buf.WriteString(test.input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
have, err := readPasswordLine(buf)
|
||||
if err != nil {
|
||||
t.Errorf("readPasswordLine(%q) failed: %v", test.input, err)
|
||||
continue
|
||||
}
|
||||
if string(have) != test.want {
|
||||
t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err = buf.WriteString(test.input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
have, err = readPasswordLine(buf)
|
||||
if err != nil {
|
||||
t.Errorf("readPasswordLine(%q) failed: %v", test.input, err)
|
||||
continue
|
||||
}
|
||||
if string(have) != test.want {
|
||||
t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeRawState(t *testing.T) {
|
||||
fd := int(os.Stdout.Fd())
|
||||
if !IsTerminal(fd) {
|
||||
t.Skip("stdout is not a terminal; skipping test")
|
||||
}
|
||||
|
||||
st, err := GetState(fd)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get terminal state from GetState: %s", err)
|
||||
}
|
||||
defer Restore(fd, st)
|
||||
raw, err := MakeRaw(fd)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get terminal state from MakeRaw: %s", err)
|
||||
}
|
||||
|
||||
if *st != *raw {
|
||||
t.Errorf("states do not match; was %v, expected %v", raw, st)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputNewlines(t *testing.T) {
|
||||
// \n should be changed to \r\n in terminal output.
|
||||
buf := new(bytes.Buffer)
|
||||
term := NewTerminal(buf, ">")
|
||||
|
||||
term.Write([]byte("1\n2\n"))
|
||||
output := string(buf.Bytes())
|
||||
const expected = "1\r\n2\r\n"
|
||||
|
||||
if output != expected {
|
||||
t.Errorf("incorrect output: was %q, expected %q", output, expected)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd
|
||||
|
||||
// Package terminal provides support functions for dealing with terminals, as
|
||||
// commonly found on UNIX systems.
|
||||
//
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
||||
//
|
||||
// oldState, err := terminal.MakeRaw(0)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer terminal.Restore(0, oldState)
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// State contains the state of a terminal.
|
||||
type State struct {
|
||||
termios syscall.Termios
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd int) bool {
|
||||
var termios syscall.Termios
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||
return err == 0
|
||||
}
|
||||
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd int) (*State, error) {
|
||||
var oldState State
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newState := oldState.termios
|
||||
// This attempts to replicate the behaviour documented for cfmakeraw in
|
||||
// the termios(3) manpage.
|
||||
newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
|
||||
newState.Oflag &^= syscall.OPOST
|
||||
newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
|
||||
newState.Cflag &^= syscall.CSIZE | syscall.PARENB
|
||||
newState.Cflag |= syscall.CS8
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oldState, nil
|
||||
}
|
||||
|
||||
// GetState returns the current state of a terminal which may be useful to
|
||||
// restore the terminal after a signal.
|
||||
func GetState(fd int) (*State, error) {
|
||||
var oldState State
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oldState, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, state *State) error {
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0); err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSize returns the dimensions of the given terminal.
|
||||
func GetSize(fd int) (width, height int, err error) {
|
||||
var dimensions [4]uint16
|
||||
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 {
|
||||
return -1, -1, err
|
||||
}
|
||||
return int(dimensions[1]), int(dimensions[0]), nil
|
||||
}
|
||||
|
||||
// passwordReader is an io.Reader that reads from a specific file descriptor.
|
||||
type passwordReader int
|
||||
|
||||
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||
return syscall.Read(int(r), buf)
|
||||
}
|
||||
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||
// returned does not include the \n.
|
||||
func ReadPassword(fd int) ([]byte, error) {
|
||||
var oldState syscall.Termios
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newState := oldState
|
||||
newState.Lflag &^= syscall.ECHO
|
||||
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
||||
newState.Iflag |= syscall.ICRNL
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0)
|
||||
}()
|
||||
|
||||
return readPasswordLine(passwordReader(fd))
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package terminal
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TIOCGETA
|
||||
const ioctlWriteTermios = unix.TIOCSETA
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package terminal
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TCGETS
|
||||
const ioctlWriteTermios = unix.TCSETS
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package terminal provides support functions for dealing with terminals, as
|
||||
// commonly found on UNIX systems.
|
||||
//
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
||||
//
|
||||
// oldState, err := terminal.MakeRaw(0)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer terminal.Restore(0, oldState)
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type State struct{}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd int) (*State, error) {
|
||||
return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
// GetState returns the current state of a terminal which may be useful to
|
||||
// restore the terminal after a signal.
|
||||
func GetState(fd int) (*State, error) {
|
||||
return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, state *State) error {
|
||||
return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
// GetSize returns the dimensions of the given terminal.
|
||||
func GetSize(fd int) (width, height int, err error) {
|
||||
return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||
// returned does not include the \n.
|
||||
func ReadPassword(fd int) ([]byte, error) {
|
||||
return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build solaris
|
||||
|
||||
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// State contains the state of a terminal.
|
||||
type State struct {
|
||||
state *unix.Termios
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd int) bool {
|
||||
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||
// returned does not include the \n.
|
||||
func ReadPassword(fd int) ([]byte, error) {
|
||||
// see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c
|
||||
val, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldState := *val
|
||||
|
||||
newState := oldState
|
||||
newState.Lflag &^= syscall.ECHO
|
||||
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
||||
newState.Iflag |= syscall.ICRNL
|
||||
err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState)
|
||||
|
||||
var buf [16]byte
|
||||
var ret []byte
|
||||
for {
|
||||
n, err := syscall.Read(fd, buf[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
if len(ret) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
break
|
||||
}
|
||||
if buf[n-1] == '\n' {
|
||||
n--
|
||||
}
|
||||
ret = append(ret, buf[:n]...)
|
||||
if n < len(buf) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// MakeRaw puts the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
// see http://cr.illumos.org/~webrev/andy_js/1060/
|
||||
func MakeRaw(fd int) (*State, error) {
|
||||
oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldTermios := *oldTermiosPtr
|
||||
|
||||
newTermios := oldTermios
|
||||
newTermios.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
|
||||
newTermios.Oflag &^= syscall.OPOST
|
||||
newTermios.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
|
||||
newTermios.Cflag &^= syscall.CSIZE | syscall.PARENB
|
||||
newTermios.Cflag |= syscall.CS8
|
||||
newTermios.Cc[unix.VMIN] = 1
|
||||
newTermios.Cc[unix.VTIME] = 0
|
||||
|
||||
if err := unix.IoctlSetTermios(fd, unix.TCSETS, &newTermios); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &State{
|
||||
state: oldTermiosPtr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, oldState *State) error {
|
||||
return unix.IoctlSetTermios(fd, unix.TCSETS, oldState.state)
|
||||
}
|
||||
|
||||
// GetState returns the current state of a terminal which may be useful to
|
||||
// restore the terminal after a signal.
|
||||
func GetState(fd int) (*State, error) {
|
||||
oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &State{
|
||||
state: oldTermiosPtr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetSize returns the dimensions of the given terminal.
|
||||
func GetSize(fd int) (width, height int, err error) {
|
||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(ws.Col), int(ws.Row), nil
|
||||
}
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
// Package terminal provides support functions for dealing with terminals, as
|
||||
// commonly found on UNIX systems.
|
||||
//
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
||||
//
|
||||
// oldState, err := terminal.MakeRaw(0)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer terminal.Restore(0, oldState)
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
enableLineInput = 2
|
||||
enableEchoInput = 4
|
||||
enableProcessedInput = 1
|
||||
enableWindowInput = 8
|
||||
enableMouseInput = 16
|
||||
enableInsertMode = 32
|
||||
enableQuickEditMode = 64
|
||||
enableExtendedFlags = 128
|
||||
enableAutoPosition = 256
|
||||
enableProcessedOutput = 1
|
||||
enableWrapAtEolOutput = 2
|
||||
)
|
||||
|
||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
var (
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
)
|
||||
|
||||
type (
|
||||
short int16
|
||||
word uint16
|
||||
|
||||
coord struct {
|
||||
x short
|
||||
y short
|
||||
}
|
||||
smallRect struct {
|
||||
left short
|
||||
top short
|
||||
right short
|
||||
bottom short
|
||||
}
|
||||
consoleScreenBufferInfo struct {
|
||||
size coord
|
||||
cursorPosition coord
|
||||
attributes word
|
||||
window smallRect
|
||||
maximumWindowSize coord
|
||||
}
|
||||
)
|
||||
|
||||
type State struct {
|
||||
mode uint32
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd int) bool {
|
||||
var st uint32
|
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||
return r != 0 && e == 0
|
||||
}
|
||||
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd int) (*State, error) {
|
||||
var st uint32
|
||||
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||
if e != 0 {
|
||||
return nil, error(e)
|
||||
}
|
||||
raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)
|
||||
_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0)
|
||||
if e != 0 {
|
||||
return nil, error(e)
|
||||
}
|
||||
return &State{st}, nil
|
||||
}
|
||||
|
||||
// GetState returns the current state of a terminal which may be useful to
|
||||
// restore the terminal after a signal.
|
||||
func GetState(fd int) (*State, error) {
|
||||
var st uint32
|
||||
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||
if e != 0 {
|
||||
return nil, error(e)
|
||||
}
|
||||
return &State{st}, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, state *State) error {
|
||||
_, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSize returns the dimensions of the given terminal.
|
||||
func GetSize(fd int) (width, height int, err error) {
|
||||
var info consoleScreenBufferInfo
|
||||
_, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0)
|
||||
if e != 0 {
|
||||
return 0, 0, error(e)
|
||||
}
|
||||
return int(info.size.x), int(info.size.y), nil
|
||||
}
|
||||
|
||||
// passwordReader is an io.Reader that reads from a specific Windows HANDLE.
|
||||
type passwordReader int
|
||||
|
||||
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||
return syscall.Read(syscall.Handle(r), buf)
|
||||
}
|
||||
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||
// returned does not include the \n.
|
||||
func ReadPassword(fd int) ([]byte, error) {
|
||||
var st uint32
|
||||
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||
if e != 0 {
|
||||
return nil, error(e)
|
||||
}
|
||||
old := st
|
||||
|
||||
st &^= (enableEchoInput)
|
||||
st |= (enableProcessedInput | enableLineInput | enableProcessedOutput)
|
||||
_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0)
|
||||
if e != 0 {
|
||||
return nil, error(e)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0)
|
||||
}()
|
||||
|
||||
return readPasswordLine(passwordReader(fd))
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
func TestAgentForward(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
keyring := agent.NewKeyring()
|
||||
if err := keyring.Add(agent.AddedKey{PrivateKey: testPrivateKeys["dsa"]}); err != nil {
|
||||
t.Fatalf("Error adding key: %s", err)
|
||||
}
|
||||
if err := keyring.Add(agent.AddedKey{
|
||||
PrivateKey: testPrivateKeys["dsa"],
|
||||
ConfirmBeforeUse: true,
|
||||
LifetimeSecs: 3600,
|
||||
}); err != nil {
|
||||
t.Fatalf("Error adding key with constraints: %s", err)
|
||||
}
|
||||
pub := testPublicKeys["dsa"]
|
||||
|
||||
sess, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("NewSession: %v", err)
|
||||
}
|
||||
if err := agent.RequestAgentForwarding(sess); err != nil {
|
||||
t.Fatalf("RequestAgentForwarding: %v", err)
|
||||
}
|
||||
|
||||
if err := agent.ForwardToAgent(conn, keyring); err != nil {
|
||||
t.Fatalf("SetupForwardKeyring: %v", err)
|
||||
}
|
||||
out, err := sess.CombinedOutput("ssh-add -L")
|
||||
if err != nil {
|
||||
t.Fatalf("running ssh-add: %v, out %s", err, out)
|
||||
}
|
||||
key, _, _, _, err := ssh.ParseAuthorizedKey(out)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAuthorizedKey(%q): %v", out, err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(key.Marshal(), pub.Marshal()) {
|
||||
t.Fatalf("got key %s, want %s", ssh.MarshalAuthorizedKey(key), ssh.MarshalAuthorizedKey(pub))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Test both logging in with a cert, and also that the certificate presented by an OpenSSH host can be validated correctly
|
||||
func TestCertLogin(t *testing.T) {
|
||||
s := newServer(t)
|
||||
defer s.Shutdown()
|
||||
|
||||
// Use a key different from the default.
|
||||
clientKey := testSigners["dsa"]
|
||||
caAuthKey := testSigners["ecdsa"]
|
||||
cert := &ssh.Certificate{
|
||||
Key: clientKey.PublicKey(),
|
||||
ValidPrincipals: []string{username()},
|
||||
CertType: ssh.UserCert,
|
||||
ValidBefore: ssh.CertTimeInfinity,
|
||||
}
|
||||
if err := cert.SignCert(rand.Reader, caAuthKey); err != nil {
|
||||
t.Fatalf("SetSignature: %v", err)
|
||||
}
|
||||
|
||||
certSigner, err := ssh.NewCertSigner(cert, clientKey)
|
||||
if err != nil {
|
||||
t.Fatalf("NewCertSigner: %v", err)
|
||||
}
|
||||
|
||||
conf := &ssh.ClientConfig{
|
||||
User: username(),
|
||||
HostKeyCallback: (&ssh.CertChecker{
|
||||
IsHostAuthority: func(pk ssh.PublicKey, addr string) bool {
|
||||
return bytes.Equal(pk.Marshal(), testPublicKeys["ca"].Marshal())
|
||||
},
|
||||
}).CheckHostKey,
|
||||
}
|
||||
conf.Auth = append(conf.Auth, ssh.PublicKeys(certSigner))
|
||||
|
||||
for _, test := range []struct {
|
||||
addr string
|
||||
succeed bool
|
||||
}{
|
||||
{addr: "host.example.com:22", succeed: true},
|
||||
{addr: "host.example.com:10000", succeed: true}, // non-standard port must be OK
|
||||
{addr: "host.example.com", succeed: false}, // port must be specified
|
||||
{addr: "host.ex4mple.com:22", succeed: false}, // wrong host
|
||||
} {
|
||||
client, err := s.TryDialWithAddr(conf, test.addr)
|
||||
|
||||
// Always close client if opened successfully
|
||||
if err == nil {
|
||||
client.Close()
|
||||
}
|
||||
|
||||
// Now evaluate whether the test failed or passed
|
||||
if test.succeed {
|
||||
if err != nil {
|
||||
t.Fatalf("TryDialWithAddr: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Fatalf("TryDialWithAddr, unexpected success")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package test
|
||||
|
||||
// direct-tcpip and direct-streamlocal functional tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type dialTester interface {
|
||||
TestServerConn(t *testing.T, c net.Conn)
|
||||
TestClientConn(t *testing.T, c net.Conn)
|
||||
}
|
||||
|
||||
func testDial(t *testing.T, n, listenAddr string, x dialTester) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
sshConn := server.Dial(clientConfig())
|
||||
defer sshConn.Close()
|
||||
|
||||
l, err := net.Listen(n, listenAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("Listen: %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
testData := fmt.Sprintf("hello from %s, %s", n, listenAddr)
|
||||
go func() {
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
x.TestServerConn(t, c)
|
||||
|
||||
io.WriteString(c, testData)
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := sshConn.Dial(n, l.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("Dial: %v", err)
|
||||
}
|
||||
x.TestClientConn(t, conn)
|
||||
defer conn.Close()
|
||||
b, err := ioutil.ReadAll(conn)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadAll: %v", err)
|
||||
}
|
||||
t.Logf("got %q", string(b))
|
||||
if string(b) != testData {
|
||||
t.Fatalf("expected %q, got %q", testData, string(b))
|
||||
}
|
||||
}
|
||||
|
||||
type tcpDialTester struct {
|
||||
listenAddr string
|
||||
}
|
||||
|
||||
func (x *tcpDialTester) TestServerConn(t *testing.T, c net.Conn) {
|
||||
host := strings.Split(x.listenAddr, ":")[0]
|
||||
prefix := host + ":"
|
||||
if !strings.HasPrefix(c.LocalAddr().String(), prefix) {
|
||||
t.Fatalf("expected to start with %q, got %q", prefix, c.LocalAddr().String())
|
||||
}
|
||||
if !strings.HasPrefix(c.RemoteAddr().String(), prefix) {
|
||||
t.Fatalf("expected to start with %q, got %q", prefix, c.RemoteAddr().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (x *tcpDialTester) TestClientConn(t *testing.T, c net.Conn) {
|
||||
// we use zero addresses. see *Client.Dial.
|
||||
if c.LocalAddr().String() != "0.0.0.0:0" {
|
||||
t.Fatalf("expected \"0.0.0.0:0\", got %q", c.LocalAddr().String())
|
||||
}
|
||||
if c.RemoteAddr().String() != "0.0.0.0:0" {
|
||||
t.Fatalf("expected \"0.0.0.0:0\", got %q", c.RemoteAddr().String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialTCP(t *testing.T) {
|
||||
x := &tcpDialTester{
|
||||
listenAddr: "127.0.0.1:0",
|
||||
}
|
||||
testDial(t, "tcp", x.listenAddr, x)
|
||||
}
|
||||
|
||||
type unixDialTester struct {
|
||||
listenAddr string
|
||||
}
|
||||
|
||||
func (x *unixDialTester) TestServerConn(t *testing.T, c net.Conn) {
|
||||
if c.LocalAddr().String() != x.listenAddr {
|
||||
t.Fatalf("expected %q, got %q", x.listenAddr, c.LocalAddr().String())
|
||||
}
|
||||
if c.RemoteAddr().String() != "@" {
|
||||
t.Fatalf("expected \"@\", got %q", c.RemoteAddr().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (x *unixDialTester) TestClientConn(t *testing.T, c net.Conn) {
|
||||
if c.RemoteAddr().String() != x.listenAddr {
|
||||
t.Fatalf("expected %q, got %q", x.listenAddr, c.RemoteAddr().String())
|
||||
}
|
||||
if c.LocalAddr().String() != "@" {
|
||||
t.Fatalf("expected \"@\", got %q", c.LocalAddr().String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialUnix(t *testing.T) {
|
||||
addr, cleanup := newTempSocket(t)
|
||||
defer cleanup()
|
||||
x := &unixDialTester{
|
||||
listenAddr: addr,
|
||||
}
|
||||
testDial(t, "unix", x.listenAddr, x)
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This package contains integration tests for the
|
||||
// golang.org/x/crypto/ssh package.
|
||||
package test
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type closeWriter interface {
|
||||
CloseWrite() error
|
||||
}
|
||||
|
||||
func testPortForward(t *testing.T, n, listenAddr string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
sshListener, err := conn.Listen(n, listenAddr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
sshConn, err := sshListener.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("listen.Accept failed: %v", err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(sshConn, sshConn)
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatalf("ssh client copy: %v", err)
|
||||
}
|
||||
sshConn.Close()
|
||||
}()
|
||||
|
||||
forwardedAddr := sshListener.Addr().String()
|
||||
netConn, err := net.Dial(n, forwardedAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("net dial failed: %v", err)
|
||||
}
|
||||
|
||||
readChan := make(chan []byte)
|
||||
go func() {
|
||||
data, _ := ioutil.ReadAll(netConn)
|
||||
readChan <- data
|
||||
}()
|
||||
|
||||
// Invent some data.
|
||||
data := make([]byte, 100*1000)
|
||||
for i := range data {
|
||||
data[i] = byte(i % 255)
|
||||
}
|
||||
|
||||
var sent []byte
|
||||
for len(sent) < 1000*1000 {
|
||||
// Send random sized chunks
|
||||
m := rand.Intn(len(data))
|
||||
n, err := netConn.Write(data[:m])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
sent = append(sent, data[:n]...)
|
||||
}
|
||||
if err := netConn.(closeWriter).CloseWrite(); err != nil {
|
||||
t.Errorf("netConn.CloseWrite: %v", err)
|
||||
}
|
||||
|
||||
read := <-readChan
|
||||
|
||||
if len(sent) != len(read) {
|
||||
t.Fatalf("got %d bytes, want %d", len(read), len(sent))
|
||||
}
|
||||
if bytes.Compare(sent, read) != 0 {
|
||||
t.Fatalf("read back data does not match")
|
||||
}
|
||||
|
||||
if err := sshListener.Close(); err != nil {
|
||||
t.Fatalf("sshListener.Close: %v", err)
|
||||
}
|
||||
|
||||
// Check that the forward disappeared.
|
||||
netConn, err = net.Dial(n, forwardedAddr)
|
||||
if err == nil {
|
||||
netConn.Close()
|
||||
t.Errorf("still listening to %s after closing", forwardedAddr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortForwardTCP(t *testing.T) {
|
||||
testPortForward(t, "tcp", "localhost:0")
|
||||
}
|
||||
|
||||
func TestPortForwardUnix(t *testing.T) {
|
||||
addr, cleanup := newTempSocket(t)
|
||||
defer cleanup()
|
||||
testPortForward(t, "unix", addr)
|
||||
}
|
||||
|
||||
func testAcceptClose(t *testing.T, n, listenAddr string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
|
||||
sshListener, err := conn.Listen(n, listenAddr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
quit := make(chan error, 1)
|
||||
go func() {
|
||||
for {
|
||||
c, err := sshListener.Accept()
|
||||
if err != nil {
|
||||
quit <- err
|
||||
break
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
sshListener.Close()
|
||||
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Errorf("timeout: listener did not close.")
|
||||
case err := <-quit:
|
||||
t.Logf("quit as expected (error %v)", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcceptCloseTCP(t *testing.T) {
|
||||
testAcceptClose(t, "tcp", "localhost:0")
|
||||
}
|
||||
|
||||
func TestAcceptCloseUnix(t *testing.T) {
|
||||
addr, cleanup := newTempSocket(t)
|
||||
defer cleanup()
|
||||
testAcceptClose(t, "unix", addr)
|
||||
}
|
||||
|
||||
// Check that listeners exit if the underlying client transport dies.
|
||||
func testPortForwardConnectionClose(t *testing.T, n, listenAddr string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
|
||||
sshListener, err := conn.Listen(n, listenAddr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
quit := make(chan error, 1)
|
||||
go func() {
|
||||
for {
|
||||
c, err := sshListener.Accept()
|
||||
if err != nil {
|
||||
quit <- err
|
||||
break
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// It would be even nicer if we closed the server side, but it
|
||||
// is more involved as the fd for that side is dup()ed.
|
||||
server.clientConn.Close()
|
||||
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Errorf("timeout: listener did not close.")
|
||||
case err := <-quit:
|
||||
t.Logf("quit as expected (error %v)", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortForwardConnectionCloseTCP(t *testing.T) {
|
||||
testPortForwardConnectionClose(t, "tcp", "localhost:0")
|
||||
}
|
||||
|
||||
func TestPortForwardConnectionCloseUnix(t *testing.T) {
|
||||
addr, cleanup := newTempSocket(t)
|
||||
defer cleanup()
|
||||
testPortForwardConnectionClose(t, "unix", addr)
|
||||
}
|
||||
|
|
@ -1,413 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package test
|
||||
|
||||
// Session functional tests.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestRunCommandSuccess(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("session failed: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
err = session.Run("true")
|
||||
if err != nil {
|
||||
t.Fatalf("session failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostKeyCheck(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
|
||||
conf := clientConfig()
|
||||
hostDB := hostKeyDB()
|
||||
conf.HostKeyCallback = hostDB.Check
|
||||
|
||||
// change the keys.
|
||||
hostDB.keys[ssh.KeyAlgoRSA][25]++
|
||||
hostDB.keys[ssh.KeyAlgoDSA][25]++
|
||||
hostDB.keys[ssh.KeyAlgoECDSA256][25]++
|
||||
|
||||
conn, err := server.TryDial(conf)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
t.Fatalf("dial should have failed.")
|
||||
} else if !strings.Contains(err.Error(), "host key mismatch") {
|
||||
t.Fatalf("'host key mismatch' not found in %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCommandStdin(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("session failed: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
r, w := io.Pipe()
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
session.Stdin = r
|
||||
|
||||
err = session.Run("true")
|
||||
if err != nil {
|
||||
t.Fatalf("session failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCommandStdinError(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("session failed: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
r, w := io.Pipe()
|
||||
defer r.Close()
|
||||
session.Stdin = r
|
||||
pipeErr := errors.New("closing write end of pipe")
|
||||
w.CloseWithError(pipeErr)
|
||||
|
||||
err = session.Run("true")
|
||||
if err != pipeErr {
|
||||
t.Fatalf("expected %v, found %v", pipeErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCommandFailed(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("session failed: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
err = session.Run(`bash -c "kill -9 $$"`)
|
||||
if err == nil {
|
||||
t.Fatalf("session succeeded: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCommandWeClosed(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("session failed: %v", err)
|
||||
}
|
||||
err = session.Shell()
|
||||
if err != nil {
|
||||
t.Fatalf("shell failed: %v", err)
|
||||
}
|
||||
err = session.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("shell failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuncLargeRead(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create new session: %s", err)
|
||||
}
|
||||
|
||||
stdout, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to acquire stdout pipe: %s", err)
|
||||
}
|
||||
|
||||
err = session.Start("dd if=/dev/urandom bs=2048 count=1024")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to execute remote command: %s", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
n, err := io.Copy(buf, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("error reading from remote stdout: %s", err)
|
||||
}
|
||||
|
||||
if n != 2048*1024 {
|
||||
t.Fatalf("Expected %d bytes but read only %d from remote command", 2048, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyChange(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conf := clientConfig()
|
||||
hostDB := hostKeyDB()
|
||||
conf.HostKeyCallback = hostDB.Check
|
||||
conf.RekeyThreshold = 1024
|
||||
conn := server.Dial(conf)
|
||||
defer conn.Close()
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create new session: %s", err)
|
||||
}
|
||||
|
||||
stdout, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to acquire stdout pipe: %s", err)
|
||||
}
|
||||
|
||||
err = session.Start("dd if=/dev/urandom bs=1024 count=1")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to execute remote command: %s", err)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
n, err := io.Copy(buf, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("error reading from remote stdout: %s", err)
|
||||
}
|
||||
|
||||
want := int64(1024)
|
||||
if n != want {
|
||||
t.Fatalf("Expected %d bytes but read only %d from remote command", want, n)
|
||||
}
|
||||
}
|
||||
|
||||
if changes := hostDB.checkCount; changes < 4 {
|
||||
t.Errorf("got %d key changes, want 4", changes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidTerminalMode(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("session failed: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
if err = session.RequestPty("vt100", 80, 40, ssh.TerminalModes{255: 1984}); err == nil {
|
||||
t.Fatalf("req-pty failed: successful request with invalid mode")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidTerminalMode(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("session failed: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
stdout, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to acquire stdout pipe: %s", err)
|
||||
}
|
||||
|
||||
stdin, err := session.StdinPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to acquire stdin pipe: %s", err)
|
||||
}
|
||||
|
||||
tm := ssh.TerminalModes{ssh.ECHO: 0}
|
||||
if err = session.RequestPty("xterm", 80, 40, tm); err != nil {
|
||||
t.Fatalf("req-pty failed: %s", err)
|
||||
}
|
||||
|
||||
err = session.Shell()
|
||||
if err != nil {
|
||||
t.Fatalf("session failed: %s", err)
|
||||
}
|
||||
|
||||
stdin.Write([]byte("stty -a && exit\n"))
|
||||
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, stdout); err != nil {
|
||||
t.Fatalf("reading failed: %s", err)
|
||||
}
|
||||
|
||||
if sttyOutput := buf.String(); !strings.Contains(sttyOutput, "-echo ") {
|
||||
t.Fatalf("terminal mode failure: expected -echo in stty output, got %s", sttyOutput)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWindowChange(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("session failed: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
stdout, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to acquire stdout pipe: %s", err)
|
||||
}
|
||||
|
||||
stdin, err := session.StdinPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to acquire stdin pipe: %s", err)
|
||||
}
|
||||
|
||||
tm := ssh.TerminalModes{ssh.ECHO: 0}
|
||||
if err = session.RequestPty("xterm", 80, 40, tm); err != nil {
|
||||
t.Fatalf("req-pty failed: %s", err)
|
||||
}
|
||||
|
||||
if err := session.WindowChange(100, 100); err != nil {
|
||||
t.Fatalf("window-change failed: %s", err)
|
||||
}
|
||||
|
||||
err = session.Shell()
|
||||
if err != nil {
|
||||
t.Fatalf("session failed: %s", err)
|
||||
}
|
||||
|
||||
stdin.Write([]byte("stty size && exit\n"))
|
||||
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, stdout); err != nil {
|
||||
t.Fatalf("reading failed: %s", err)
|
||||
}
|
||||
|
||||
if sttyOutput := buf.String(); !strings.Contains(sttyOutput, "100 100") {
|
||||
t.Fatalf("terminal WindowChange failure: expected \"100 100\" stty output, got %s", sttyOutput)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCiphers(t *testing.T) {
|
||||
var config ssh.Config
|
||||
config.SetDefaults()
|
||||
cipherOrder := config.Ciphers
|
||||
// These ciphers will not be tested when commented out in cipher.go it will
|
||||
// fallback to the next available as per line 292.
|
||||
cipherOrder = append(cipherOrder, "aes128-cbc", "3des-cbc")
|
||||
|
||||
for _, ciph := range cipherOrder {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conf := clientConfig()
|
||||
conf.Ciphers = []string{ciph}
|
||||
// Don't fail if sshd doesn't have the cipher.
|
||||
conf.Ciphers = append(conf.Ciphers, cipherOrder...)
|
||||
conn, err := server.TryDial(conf)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
} else {
|
||||
t.Fatalf("failed for cipher %q", ciph)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMACs(t *testing.T) {
|
||||
var config ssh.Config
|
||||
config.SetDefaults()
|
||||
macOrder := config.MACs
|
||||
|
||||
for _, mac := range macOrder {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conf := clientConfig()
|
||||
conf.MACs = []string{mac}
|
||||
// Don't fail if sshd doesn't have the MAC.
|
||||
conf.MACs = append(conf.MACs, macOrder...)
|
||||
if conn, err := server.TryDial(conf); err == nil {
|
||||
conn.Close()
|
||||
} else {
|
||||
t.Fatalf("failed for MAC %q", mac)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyExchanges(t *testing.T) {
|
||||
var config ssh.Config
|
||||
config.SetDefaults()
|
||||
kexOrder := config.KeyExchanges
|
||||
for _, kex := range kexOrder {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conf := clientConfig()
|
||||
// Don't fail if sshd doesn't have the kex.
|
||||
conf.KeyExchanges = append([]string{kex}, kexOrder...)
|
||||
conn, err := server.TryDial(conf)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
} else {
|
||||
t.Errorf("failed for kex %q", kex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientAuthAlgorithms(t *testing.T) {
|
||||
for _, key := range []string{
|
||||
"rsa",
|
||||
"dsa",
|
||||
"ecdsa",
|
||||
"ed25519",
|
||||
} {
|
||||
server := newServer(t)
|
||||
conf := clientConfig()
|
||||
conf.SetDefaults()
|
||||
conf.Auth = []ssh.AuthMethod{
|
||||
ssh.PublicKeys(testSigners[key]),
|
||||
}
|
||||
|
||||
conn, err := server.TryDial(conf)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
} else {
|
||||
t.Errorf("failed for key %q", key)
|
||||
}
|
||||
|
||||
server.Shutdown()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,295 +0,0 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd plan9
|
||||
|
||||
package test
|
||||
|
||||
// functional test harness for unix.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
)
|
||||
|
||||
const sshd_config = `
|
||||
Protocol 2
|
||||
HostKey {{.Dir}}/id_rsa
|
||||
HostKey {{.Dir}}/id_dsa
|
||||
HostKey {{.Dir}}/id_ecdsa
|
||||
HostCertificate {{.Dir}}/id_rsa-cert.pub
|
||||
Pidfile {{.Dir}}/sshd.pid
|
||||
#UsePrivilegeSeparation no
|
||||
KeyRegenerationInterval 3600
|
||||
ServerKeyBits 768
|
||||
SyslogFacility AUTH
|
||||
LogLevel DEBUG2
|
||||
LoginGraceTime 120
|
||||
PermitRootLogin no
|
||||
StrictModes no
|
||||
RSAAuthentication yes
|
||||
PubkeyAuthentication yes
|
||||
AuthorizedKeysFile {{.Dir}}/authorized_keys
|
||||
TrustedUserCAKeys {{.Dir}}/id_ecdsa.pub
|
||||
IgnoreRhosts yes
|
||||
RhostsRSAAuthentication no
|
||||
HostbasedAuthentication no
|
||||
PubkeyAcceptedKeyTypes=*
|
||||
`
|
||||
|
||||
var configTmpl = template.Must(template.New("").Parse(sshd_config))
|
||||
|
||||
type server struct {
|
||||
t *testing.T
|
||||
cleanup func() // executed during Shutdown
|
||||
configfile string
|
||||
cmd *exec.Cmd
|
||||
output bytes.Buffer // holds stderr from sshd process
|
||||
|
||||
// Client half of the network connection.
|
||||
clientConn net.Conn
|
||||
}
|
||||
|
||||
func username() string {
|
||||
var username string
|
||||
if user, err := user.Current(); err == nil {
|
||||
username = user.Username
|
||||
} else {
|
||||
// user.Current() currently requires cgo. If an error is
|
||||
// returned attempt to get the username from the environment.
|
||||
log.Printf("user.Current: %v; falling back on $USER", err)
|
||||
username = os.Getenv("USER")
|
||||
}
|
||||
if username == "" {
|
||||
panic("Unable to get username")
|
||||
}
|
||||
return username
|
||||
}
|
||||
|
||||
type storedHostKey struct {
|
||||
// keys map from an algorithm string to binary key data.
|
||||
keys map[string][]byte
|
||||
|
||||
// checkCount counts the Check calls. Used for testing
|
||||
// rekeying.
|
||||
checkCount int
|
||||
}
|
||||
|
||||
func (k *storedHostKey) Add(key ssh.PublicKey) {
|
||||
if k.keys == nil {
|
||||
k.keys = map[string][]byte{}
|
||||
}
|
||||
k.keys[key.Type()] = key.Marshal()
|
||||
}
|
||||
|
||||
func (k *storedHostKey) Check(addr string, remote net.Addr, key ssh.PublicKey) error {
|
||||
k.checkCount++
|
||||
algo := key.Type()
|
||||
|
||||
if k.keys == nil || bytes.Compare(key.Marshal(), k.keys[algo]) != 0 {
|
||||
return fmt.Errorf("host key mismatch. Got %q, want %q", key, k.keys[algo])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hostKeyDB() *storedHostKey {
|
||||
keyChecker := &storedHostKey{}
|
||||
keyChecker.Add(testPublicKeys["ecdsa"])
|
||||
keyChecker.Add(testPublicKeys["rsa"])
|
||||
keyChecker.Add(testPublicKeys["dsa"])
|
||||
return keyChecker
|
||||
}
|
||||
|
||||
func clientConfig() *ssh.ClientConfig {
|
||||
config := &ssh.ClientConfig{
|
||||
User: username(),
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(testSigners["user"]),
|
||||
},
|
||||
HostKeyCallback: hostKeyDB().Check,
|
||||
HostKeyAlgorithms: []string{ // by default, don't allow certs as this affects the hostKeyDB checker
|
||||
ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521,
|
||||
ssh.KeyAlgoRSA, ssh.KeyAlgoDSA,
|
||||
ssh.KeyAlgoED25519,
|
||||
},
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// unixConnection creates two halves of a connected net.UnixConn. It
|
||||
// is used for connecting the Go SSH client with sshd without opening
|
||||
// ports.
|
||||
func unixConnection() (*net.UnixConn, *net.UnixConn, error) {
|
||||
dir, err := ioutil.TempDir("", "unixConnection")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer os.Remove(dir)
|
||||
|
||||
addr := filepath.Join(dir, "ssh")
|
||||
listener, err := net.Listen("unix", addr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer listener.Close()
|
||||
c1, err := net.Dial("unix", addr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c2, err := listener.Accept()
|
||||
if err != nil {
|
||||
c1.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return c1.(*net.UnixConn), c2.(*net.UnixConn), nil
|
||||
}
|
||||
|
||||
func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.Client, error) {
|
||||
return s.TryDialWithAddr(config, "")
|
||||
}
|
||||
|
||||
// addr is the user specified host:port. While we don't actually dial it,
|
||||
// we need to know this for host key matching
|
||||
func (s *server) TryDialWithAddr(config *ssh.ClientConfig, addr string) (*ssh.Client, error) {
|
||||
sshd, err := exec.LookPath("sshd")
|
||||
if err != nil {
|
||||
s.t.Skipf("skipping test: %v", err)
|
||||
}
|
||||
|
||||
c1, c2, err := unixConnection()
|
||||
if err != nil {
|
||||
s.t.Fatalf("unixConnection: %v", err)
|
||||
}
|
||||
|
||||
s.cmd = exec.Command(sshd, "-f", s.configfile, "-i", "-e")
|
||||
f, err := c2.File()
|
||||
if err != nil {
|
||||
s.t.Fatalf("UnixConn.File: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
s.cmd.Stdin = f
|
||||
s.cmd.Stdout = f
|
||||
s.cmd.Stderr = &s.output
|
||||
if err := s.cmd.Start(); err != nil {
|
||||
s.t.Fail()
|
||||
s.Shutdown()
|
||||
s.t.Fatalf("s.cmd.Start: %v", err)
|
||||
}
|
||||
s.clientConn = c1
|
||||
conn, chans, reqs, err := ssh.NewClientConn(c1, addr, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ssh.NewClient(conn, chans, reqs), nil
|
||||
}
|
||||
|
||||
func (s *server) Dial(config *ssh.ClientConfig) *ssh.Client {
|
||||
conn, err := s.TryDial(config)
|
||||
if err != nil {
|
||||
s.t.Fail()
|
||||
s.Shutdown()
|
||||
s.t.Fatalf("ssh.Client: %v", err)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func (s *server) Shutdown() {
|
||||
if s.cmd != nil && s.cmd.Process != nil {
|
||||
// Don't check for errors; if it fails it's most
|
||||
// likely "os: process already finished", and we don't
|
||||
// care about that. Use os.Interrupt, so child
|
||||
// processes are killed too.
|
||||
s.cmd.Process.Signal(os.Interrupt)
|
||||
s.cmd.Wait()
|
||||
}
|
||||
if s.t.Failed() {
|
||||
// log any output from sshd process
|
||||
s.t.Logf("sshd: %s", s.output.String())
|
||||
}
|
||||
s.cleanup()
|
||||
}
|
||||
|
||||
func writeFile(path string, contents []byte) {
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := f.Write(contents); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// newServer returns a new mock ssh server.
|
||||
func newServer(t *testing.T) *server {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test due to -short")
|
||||
}
|
||||
dir, err := ioutil.TempDir("", "sshtest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := os.Create(filepath.Join(dir, "sshd_config"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = configTmpl.Execute(f, map[string]string{
|
||||
"Dir": dir,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
for k, v := range testdata.PEMBytes {
|
||||
filename := "id_" + k
|
||||
writeFile(filepath.Join(dir, filename), v)
|
||||
writeFile(filepath.Join(dir, filename+".pub"), ssh.MarshalAuthorizedKey(testPublicKeys[k]))
|
||||
}
|
||||
|
||||
for k, v := range testdata.SSHCertificates {
|
||||
filename := "id_" + k + "-cert.pub"
|
||||
writeFile(filepath.Join(dir, filename), v)
|
||||
}
|
||||
|
||||
var authkeys bytes.Buffer
|
||||
for k, _ := range testdata.PEMBytes {
|
||||
authkeys.Write(ssh.MarshalAuthorizedKey(testPublicKeys[k]))
|
||||
}
|
||||
writeFile(filepath.Join(dir, "authorized_keys"), authkeys.Bytes())
|
||||
|
||||
return &server{
|
||||
t: t,
|
||||
configfile: f.Name(),
|
||||
cleanup: func() {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newTempSocket(t *testing.T) (string, func()) {
|
||||
dir, err := ioutil.TempDir("", "socket")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
deferFunc := func() { os.RemoveAll(dir) }
|
||||
addr := filepath.Join(dir, "sock")
|
||||
return addr, deferFunc
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// IMPLEMENTATION NOTE: To avoid a package loop, this file is in three places:
|
||||
// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three
|
||||
// instances.
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
)
|
||||
|
||||
var (
|
||||
testPrivateKeys map[string]interface{}
|
||||
testSigners map[string]ssh.Signer
|
||||
testPublicKeys map[string]ssh.PublicKey
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
n := len(testdata.PEMBytes)
|
||||
testPrivateKeys = make(map[string]interface{}, n)
|
||||
testSigners = make(map[string]ssh.Signer, n)
|
||||
testPublicKeys = make(map[string]ssh.PublicKey, n)
|
||||
for t, k := range testdata.PEMBytes {
|
||||
testPrivateKeys[t], err = ssh.ParseRawPrivateKey(k)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err))
|
||||
}
|
||||
testSigners[t], err = ssh.NewSignerFromKey(testPrivateKeys[t])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err))
|
||||
}
|
||||
testPublicKeys[t] = testSigners[t].PublicKey()
|
||||
}
|
||||
|
||||
// Create a cert and sign it for use in tests.
|
||||
testCert := &ssh.Certificate{
|
||||
Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
|
||||
ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage
|
||||
ValidAfter: 0, // unix epoch
|
||||
ValidBefore: ssh.CertTimeInfinity, // The end of currently representable time.
|
||||
Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
|
||||
Key: testPublicKeys["ecdsa"],
|
||||
SignatureKey: testPublicKeys["rsa"],
|
||||
Permissions: ssh.Permissions{
|
||||
CriticalOptions: map[string]string{},
|
||||
Extensions: map[string]string{},
|
||||
},
|
||||
}
|
||||
testCert.SignCert(rand.Reader, testSigners["rsa"])
|
||||
testPrivateKeys["cert"] = testPrivateKeys["ecdsa"]
|
||||
testSigners["cert"], err = ssh.NewCertSigner(testCert, testSigners["ecdsa"])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to create certificate signer: %v", err))
|
||||
}
|
||||
}
|
||||
8
ssh/testdata/doc.go
vendored
8
ssh/testdata/doc.go
vendored
|
|
@ -1,8 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This package contains test data shared between the various subpackages of
|
||||
// the golang.org/x/crypto/ssh package. Under no circumstance should
|
||||
// this data be used for production code.
|
||||
package testdata
|
||||
177
ssh/testdata/keys.go
vendored
177
ssh/testdata/keys.go
vendored
|
|
@ -1,177 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package testdata
|
||||
|
||||
var PEMBytes = map[string][]byte{
|
||||
"dsa": []byte(`-----BEGIN DSA PRIVATE KEY-----
|
||||
MIIBuwIBAAKBgQD6PDSEyXiI9jfNs97WuM46MSDCYlOqWw80ajN16AohtBncs1YB
|
||||
lHk//dQOvCYOsYaE+gNix2jtoRjwXhDsc25/IqQbU1ahb7mB8/rsaILRGIbA5WH3
|
||||
EgFtJmXFovDz3if6F6TzvhFpHgJRmLYVR8cqsezL3hEZOvvs2iH7MorkxwIVAJHD
|
||||
nD82+lxh2fb4PMsIiaXudAsBAoGAQRf7Q/iaPRn43ZquUhd6WwvirqUj+tkIu6eV
|
||||
2nZWYmXLlqFQKEy4Tejl7Wkyzr2OSYvbXLzo7TNxLKoWor6ips0phYPPMyXld14r
|
||||
juhT24CrhOzuLMhDduMDi032wDIZG4Y+K7ElU8Oufn8Sj5Wge8r6ANmmVgmFfynr
|
||||
FhdYCngCgYEA3ucGJ93/Mx4q4eKRDxcWD3QzWyqpbRVRRV1Vmih9Ha/qC994nJFz
|
||||
DQIdjxDIT2Rk2AGzMqFEB68Zc3O+Wcsmz5eWWzEwFxaTwOGWTyDqsDRLm3fD+QYj
|
||||
nOwuxb0Kce+gWI8voWcqC9cyRm09jGzu2Ab3Bhtpg8JJ8L7gS3MRZK4CFEx4UAfY
|
||||
Fmsr0W6fHB9nhS4/UXM8
|
||||
-----END DSA PRIVATE KEY-----
|
||||
`),
|
||||
"ecdsa": []byte(`-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEINGWx0zo6fhJ/0EAfrPzVFyFC9s18lBt3cRoEDhS3ARooAoGCCqGSM49
|
||||
AwEHoUQDQgAEi9Hdw6KvZcWxfg2IDhA7UkpDtzzt6ZqJXSsFdLd+Kx4S3Sx4cVO+
|
||||
6/ZOXRnPmNAlLUqjShUsUBBngG0u2fqEqA==
|
||||
-----END EC PRIVATE KEY-----
|
||||
`),
|
||||
"rsa": []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQC8A6FGHDiWCSREAXCq6yBfNVr0xCVG2CzvktFNRpue+RXrGs/2
|
||||
a6ySEJQb3IYquw7HlJgu6fg3WIWhOmHCjfpG0PrL4CRwbqQ2LaPPXhJErWYejcD8
|
||||
Di00cF3677+G10KMZk9RXbmHtuBFZT98wxg8j+ZsBMqGM1+7yrWUvynswQIDAQAB
|
||||
AoGAJMCk5vqfSRzyXOTXLGIYCuR4Kj6pdsbNSeuuRGfYBeR1F2c/XdFAg7D/8s5R
|
||||
38p/Ih52/Ty5S8BfJtwtvgVY9ecf/JlU/rl/QzhG8/8KC0NG7KsyXklbQ7gJT8UT
|
||||
Ojmw5QpMk+rKv17ipDVkQQmPaj+gJXYNAHqImke5mm/K/h0CQQDciPmviQ+DOhOq
|
||||
2ZBqUfH8oXHgFmp7/6pXw80DpMIxgV3CwkxxIVx6a8lVH9bT/AFySJ6vXq4zTuV9
|
||||
6QmZcZzDAkEA2j/UXJPIs1fQ8z/6sONOkU/BjtoePFIWJlRxdN35cZjXnBraX5UR
|
||||
fFHkePv4YwqmXNqrBOvSu+w2WdSDci+IKwJAcsPRc/jWmsrJW1q3Ha0hSf/WG/Bu
|
||||
X7MPuXaKpP/DkzGoUmb8ks7yqj6XWnYkPNLjCc8izU5vRwIiyWBRf4mxMwJBAILa
|
||||
NDvRS0rjwt6lJGv7zPZoqDc65VfrK2aNyHx2PgFyzwrEOtuF57bu7pnvEIxpLTeM
|
||||
z26i6XVMeYXAWZMTloMCQBbpGgEERQpeUknLBqUHhg/wXF6+lFA+vEGnkY+Dwab2
|
||||
KCXFGd+SQ5GdUcEMe9isUH6DYj/6/yCDoFrXXmpQb+M=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`),
|
||||
"ed25519": []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACA+3f7hS7g5UWwXOGVTrMfhmxyrjqz7Sxxbx7I1j8DvvwAAAJhAFfkOQBX5
|
||||
DgAAAAtzc2gtZWQyNTUxOQAAACA+3f7hS7g5UWwXOGVTrMfhmxyrjqz7Sxxbx7I1j8Dvvw
|
||||
AAAEAaYmXltfW6nhRo3iWGglRB48lYq0z0Q3I3KyrdutEr6j7d/uFLuDlRbBc4ZVOsx+Gb
|
||||
HKuOrPtLHFvHsjWPwO+/AAAAE2dhcnRvbm1AZ2FydG9ubS14cHMBAg==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
`),
|
||||
"rsa-openssh-format": []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn
|
||||
NhAAAAAwEAAQAAAIEAwa48yfWFi3uIdqzuf9X7C2Zxfea/Iaaw0zIwHudpF8U92WVIiC5l
|
||||
oEuW1+OaVi3UWfIEjWMV1tHGysrHOwtwc34BPCJqJknUQO/KtDTBTJ4Pryhw1bWPC999Lz
|
||||
a+yrCTdNQYBzoROXKExZgPFh9pTMi5wqpHDuOQ2qZFIEI3lT0AAAIQWL0H31i9B98AAAAH
|
||||
c3NoLXJzYQAAAIEAwa48yfWFi3uIdqzuf9X7C2Zxfea/Iaaw0zIwHudpF8U92WVIiC5loE
|
||||
uW1+OaVi3UWfIEjWMV1tHGysrHOwtwc34BPCJqJknUQO/KtDTBTJ4Pryhw1bWPC999Lza+
|
||||
yrCTdNQYBzoROXKExZgPFh9pTMi5wqpHDuOQ2qZFIEI3lT0AAAADAQABAAAAgCThyTGsT4
|
||||
IARDxVMhWl6eiB2ZrgFgWSeJm/NOqtppWgOebsIqPMMg4UVuVFsl422/lE3RkPhVkjGXgE
|
||||
pWvZAdCnmLmApK8wK12vF334lZhZT7t3Z9EzJps88PWEHo7kguf285HcnUM7FlFeissJdk
|
||||
kXly34y7/3X/a6Tclm+iABAAAAQE0xR/KxZ39slwfMv64Rz7WKk1PPskaryI29aHE3mKHk
|
||||
pY2QA+P3QlrKxT/VWUMjHUbNNdYfJm48xu0SGNMRdKMAAABBAORh2NP/06JUV3J9W/2Hju
|
||||
X1ViJuqqcQnJPVzpgSL826EC2xwOECTqoY8uvFpUdD7CtpksIxNVqRIhuNOlz0lqEAAABB
|
||||
ANkaHTTaPojClO0dKJ/Zjs7pWOCGliebBYprQ/Y4r9QLBkC/XaWMS26gFIrjgC7D2Rv+rZ
|
||||
wSD0v0RcmkITP1ZR0AAAAYcHF1ZXJuYUBMdWNreUh5ZHJvLmxvY2FsAQID
|
||||
-----END OPENSSH PRIVATE KEY-----`),
|
||||
"user": []byte(`-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEILYCAeq8f7V4vSSypRw7pxy8yz3V5W4qg8kSC3zJhqpQoAoGCCqGSM49
|
||||
AwEHoUQDQgAEYcO2xNKiRUYOLEHM7VYAp57HNyKbOdYtHD83Z4hzNPVC4tM5mdGD
|
||||
PLL8IEwvYu2wq+lpXfGQnNMbzYf9gspG0w==
|
||||
-----END EC PRIVATE KEY-----
|
||||
`),
|
||||
"ca": []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAvg9dQ9IRG59lYJb+GESfKWTch4yBpr7Ydw1jkK6vvtrx9jLo
|
||||
5hkA8X6+ElRPRqTAZSlN5cBm6YCAcQIOsmXDUn6Oj1lVPQAoOjTBTvsjM3NjGhvv
|
||||
52kHTY0nsMsBeY9q5DTtlzmlYkVUq2a6Htgf2mNi01dIw5fJ7uTTo8EbNf7O0i3u
|
||||
c9a8P19HaZl5NKiWN4EIZkfB2WdXYRJCVBsGgQj3dE/GrEmH9QINq1A+GkNvK96u
|
||||
vZm8H1jjmuqzHplWa7lFeXcx8FTVTbVb/iJrZ2Lc/JvIPitKZWhqbR59yrGjpwEp
|
||||
Id7bo4WhO5L3OB0fSIJYvfu+o4WYnt4f3UzecwIDAQABAoIBABRD9yHgKErVuC2Q
|
||||
bA+SYZY8VvdtF/X7q4EmQFORDNRA7EPgMc03JU6awRGbQ8i4kHs46EFzPoXvWcKz
|
||||
AXYsO6N0Myc900Tp22A5d9NAHATEbPC/wdje7hRq1KyZONMJY9BphFv3nZbY5apR
|
||||
Dc90JBFZP5RhXjTc3n9GjvqLAKfFEKVmPRCvqxCOZunw6XR+SgIQLJo36nsIsbhW
|
||||
QUXIVaCI6cXMN8bRPm8EITdBNZu06Fpu4ZHm6VaxlXN9smERCDkgBSNXNWHKxmmA
|
||||
c3Glo2DByUr2/JFBOrLEe9fkYgr24KNCQkHVcSaFxEcZvTggr7StjKISVHlCNEaB
|
||||
7Q+kPoECgYEA3zE9FmvFGoQCU4g4Nl3dpQHs6kaAW8vJlrmq3xsireIuaJoa2HMe
|
||||
wYdIvgCnK9DIjyxd5OWnE4jXtAEYPsyGD32B5rSLQrRO96lgb3f4bESCLUb3Bsn/
|
||||
sdgeE3p1xZMA0B59htqCrvVgN9k8WxyevBxYl3/gSBm/p8OVH1RTW/ECgYEA2f9Z
|
||||
95OLj0KQHQtxQXf+I3VjhCw3LkLW39QZOXVI0QrCJfqqP7uxsJXH9NYX0l0GFTcR
|
||||
kRrlyoaSU1EGQosZh+n1MvplGBTkTSV47/bPsTzFpgK2NfEZuFm9RoWgltS+nYeH
|
||||
Y2k4mnAN3PhReCMwuprmJz8GRLsO3Cs2s2YylKMCgYEA2UX+uO/q7jgqZ5UJW+ue
|
||||
1H5+W0aMuFA3i7JtZEnvRaUVFqFGlwXin/WJ2+WY1++k/rPrJ+Rk9IBXtBUIvEGw
|
||||
FC5TIfsKQsJyyWgqx/jbbtJ2g4s8+W/1qfTAuqeRNOg5d2DnRDs90wJuS4//0JaY
|
||||
9HkHyVwkQyxFxhSA/AHEMJECgYA2MvyFR1O9bIk0D3I7GsA+xKLXa77Ua53MzIjw
|
||||
9i4CezBGDQpjCiFli/fI8am+jY5DnAtsDknvjoG24UAzLy5L0mk6IXMdB6SzYYut
|
||||
7ak5oahqW+Y9hxIj+XvLmtGQbphtxhJtLu35x75KoBpxSh6FZpmuTEccs31AVCYn
|
||||
eFM/DQKBgQDOPUwbLKqVi6ddFGgrV9MrWw+SWsDa43bPuyvYppMM3oqesvyaX1Dt
|
||||
qDvN7owaNxNM4OnfKcZr91z8YPVCFo4RbBif3DXRzjNNBlxEjHBtuMOikwvsmucN
|
||||
vIrbeEpjTiUMTEAr6PoTiVHjsfS8WAM6MDlF5M+2PNswDsBpa2yLgA==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`),
|
||||
}
|
||||
|
||||
var SSHCertificates = map[string][]byte{
|
||||
// The following are corresponding certificates for the private keys above, signed by the CA key
|
||||
// Generated by the following commands:
|
||||
//
|
||||
// 1. Assumes "rsa" key above in file named "rsa", write out the public key to "rsa.pub":
|
||||
// ssh-keygen -y -f rsa > rsa.pu
|
||||
//
|
||||
// 2. Assumes "ca" key above in file named "ca", sign a cert for "rsa.pub":
|
||||
// ssh-keygen -s ca -h -n host.example.com -V +500w -I host.example.com-key rsa.pub
|
||||
"rsa": []byte(`ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgLjYqmmuTSEmjVhSfLQphBSTJMLwIZhRgmpn8FHKLiEIAAAADAQABAAAAgQC8A6FGHDiWCSREAXCq6yBfNVr0xCVG2CzvktFNRpue+RXrGs/2a6ySEJQb3IYquw7HlJgu6fg3WIWhOmHCjfpG0PrL4CRwbqQ2LaPPXhJErWYejcD8Di00cF3677+G10KMZk9RXbmHtuBFZT98wxg8j+ZsBMqGM1+7yrWUvynswQAAAAAAAAAAAAAAAgAAABRob3N0LmV4YW1wbGUuY29tLWtleQAAABQAAAAQaG9zdC5leGFtcGxlLmNvbQAAAABZHN8UAAAAAGsjIYUAAAAAAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQC+D11D0hEbn2Vglv4YRJ8pZNyHjIGmvth3DWOQrq++2vH2MujmGQDxfr4SVE9GpMBlKU3lwGbpgIBxAg6yZcNSfo6PWVU9ACg6NMFO+yMzc2MaG+/naQdNjSewywF5j2rkNO2XOaViRVSrZroe2B/aY2LTV0jDl8nu5NOjwRs1/s7SLe5z1rw/X0dpmXk0qJY3gQhmR8HZZ1dhEkJUGwaBCPd0T8asSYf1Ag2rUD4aQ28r3q69mbwfWOOa6rMemVZruUV5dzHwVNVNtVv+ImtnYtz8m8g+K0plaGptHn3KsaOnASkh3tujhaE7kvc4HR9Igli9+76jhZie3h/dTN5zAAABDwAAAAdzc2gtcnNhAAABALeDea+60H6xJGhktAyosHaSY7AYzLocaqd8hJQjEIDifBwzoTlnBmcK9CxGhKuaoJFThdCLdaevCeOSuquh8HTkf+2ebZZc/G5T+2thPvPqmcuEcmMosWo+SIjYhbP3S6KD49aLC1X0kz8IBQeauFvURhkZ5ZjhA1L4aQYt9NjL73nqOl8PplRui+Ov5w8b4ldul4zOvYAFrzfcP6wnnXk3c1Zzwwf5wynD5jakO8GpYKBuhM7Z4crzkKSQjU3hla7xqgfomC5Gz4XbR2TNjcQiRrJQ0UlKtX3X3ObRCEhuvG0Kzjklhv+Ddw6txrhKjMjiSi/Yyius/AE8TmC1p4U= host.example.com
|
||||
`),
|
||||
}
|
||||
|
||||
var PEMEncryptedKeys = []struct {
|
||||
Name string
|
||||
EncryptionKey string
|
||||
PEMBytes []byte
|
||||
}{
|
||||
0: {
|
||||
Name: "rsa-encrypted",
|
||||
EncryptionKey: "r54-G0pher_t3st$",
|
||||
PEMBytes: []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,3E1714DE130BC5E81327F36564B05462
|
||||
|
||||
MqW88sud4fnWk/Jk3fkjh7ydu51ZkHLN5qlQgA4SkAXORPPMj2XvqZOv1v2LOgUV
|
||||
dUevUn8PZK7a9zbZg4QShUSzwE5k6wdB7XKPyBgI39mJ79GBd2U4W3h6KT6jIdWA
|
||||
goQpluxkrzr2/X602IaxLEre97FT9mpKC6zxKCLvyFWVIP9n3OSFS47cTTXyFr+l
|
||||
7PdRhe60nn6jSBgUNk/Q1lAvEQ9fufdPwDYY93F1wyJ6lOr0F1+mzRrMbH67NyKs
|
||||
rG8J1Fa7cIIre7ueKIAXTIne7OAWqpU9UDgQatDtZTbvA7ciqGsSFgiwwW13N+Rr
|
||||
hN8MkODKs9cjtONxSKi05s206A3NDU6STtZ3KuPDjFE1gMJODotOuqSM+cxKfyFq
|
||||
wxpk/CHYCDdMAVBSwxb/vraOHamylL4uCHpJdBHypzf2HABt+lS8Su23uAmL87DR
|
||||
yvyCS/lmpuNTndef6qHPRkoW2EV3xqD3ovosGf7kgwGJUk2ZpCLVteqmYehKlZDK
|
||||
r/Jy+J26ooI2jIg9bjvD1PZq+Mv+2dQ1RlDrPG3PB+rEixw6vBaL9x3jatCd4ej7
|
||||
XG7lb3qO9xFpLsx89tkEcvpGR+broSpUJ6Mu5LBCVmrvqHjvnDhrZVz1brMiQtU9
|
||||
iMZbgXqDLXHd6ERWygk7OTU03u+l1gs+KGMfmS0h0ZYw6KGVLgMnsoxqd6cFSKNB
|
||||
8Ohk9ZTZGCiovlXBUepyu8wKat1k8YlHSfIHoRUJRhhcd7DrmojC+bcbMIZBU22T
|
||||
Pl2ftVRGtcQY23lYd0NNKfebF7ncjuLWQGy+vZW+7cgfI6wPIbfYfP6g7QAutk6W
|
||||
KQx0AoX5woZ6cNxtpIrymaVjSMRRBkKQrJKmRp3pC/lul5E5P2cueMs1fj4OHTbJ
|
||||
lAUv88ywr+R+mRgYQlFW/XQ653f6DT4t6+njfO9oBcPrQDASZel3LjXLpjjYG/N5
|
||||
+BWnVexuJX9ika8HJiFl55oqaKb+WknfNhk5cPY+x7SDV9ywQeMiDZpr0ffeYAEP
|
||||
LlwwiWRDYpO+uwXHSFF3+JjWwjhs8m8g99iFb7U93yKgBB12dCEPPa2ZeH9wUHMJ
|
||||
sreYhNuq6f4iWWSXpzN45inQqtTi8jrJhuNLTT543ErW7DtntBO2rWMhff3aiXbn
|
||||
Uy3qzZM1nPbuCGuBmP9L2dJ3Z5ifDWB4JmOyWY4swTZGt9AVmUxMIKdZpRONx8vz
|
||||
I9u9nbVPGZBcou50Pa0qTLbkWsSL94MNXrARBxzhHC9Zs6XNEtwN7mOuii7uMkVc
|
||||
adrxgknBH1J1N+NX/eTKzUwJuPvDtA+Z5ILWNN9wpZT/7ed8zEnKHPNUexyeT5g3
|
||||
uw9z9jH7ffGxFYlx87oiVPHGOrCXYZYW5uoZE31SCBkbtNuffNRJRKIFeipmpJ3P
|
||||
7bpAG+kGHMelQH6b+5K1Qgsv4tpuSyKeTKpPFH9Av5nN4P1ZBm9N80tzbNWqjSJm
|
||||
S7rYdHnuNEVnUGnRmEUMmVuYZnNBEVN/fP2m2SEwXcP3Uh7TiYlcWw10ygaGmOr7
|
||||
MvMLGkYgQ4Utwnd98mtqa0jr0hK2TcOSFir3AqVvXN3XJj4cVULkrXe4Im1laWgp
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`),
|
||||
},
|
||||
|
||||
1: {
|
||||
Name: "dsa-encrypted",
|
||||
EncryptionKey: "qG0pher-dsa_t3st$",
|
||||
PEMBytes: []byte(`-----BEGIN DSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,7CE7A6E4A647DC01AF860210B15ADE3E
|
||||
|
||||
hvnBpI99Hceq/55pYRdOzBLntIEis02JFNXuLEydWL+RJBFDn7tA+vXec0ERJd6J
|
||||
G8JXlSOAhmC2H4uK3q2xR8/Y3yL95n6OIcjvCBiLsV+o3jj1MYJmErxP6zRtq4w3
|
||||
JjIjGHWmaYFSxPKQ6e8fs74HEqaeMV9ONUoTtB+aISmgaBL15Fcoayg245dkBvVl
|
||||
h5Kqspe7yvOBmzA3zjRuxmSCqKJmasXM7mqs3vIrMxZE3XPo1/fWKcPuExgpVQoT
|
||||
HkJZEoIEIIPnPMwT2uYbFJSGgPJVMDT84xz7yvjCdhLmqrsXgs5Qw7Pw0i0c0BUJ
|
||||
b7fDJ2UhdiwSckWGmIhTLlJZzr8K+JpjCDlP+REYBI5meB7kosBnlvCEHdw2EJkH
|
||||
0QDc/2F4xlVrHOLbPRFyu1Oi2Gvbeoo9EsM/DThpd1hKAlb0sF5Y0y0d+owv0PnE
|
||||
R/4X3HWfIdOHsDUvJ8xVWZ4BZk9Zk9qol045DcFCehpr/3hslCrKSZHakLt9GI58
|
||||
vVQJ4L0aYp5nloLfzhViZtKJXRLkySMKdzYkIlNmW1oVGl7tce5UCNI8Nok4j6yn
|
||||
IiHM7GBn+0nJoKTXsOGMIBe3ulKlKVxLjEuk9yivh/8=
|
||||
-----END DSA PRIVATE KEY-----
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// IMPLEMENTATION NOTE: To avoid a package loop, this file is in three places:
|
||||
// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three
|
||||
// instances.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
)
|
||||
|
||||
var (
|
||||
testPrivateKeys map[string]interface{}
|
||||
testSigners map[string]Signer
|
||||
testPublicKeys map[string]PublicKey
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
n := len(testdata.PEMBytes)
|
||||
testPrivateKeys = make(map[string]interface{}, n)
|
||||
testSigners = make(map[string]Signer, n)
|
||||
testPublicKeys = make(map[string]PublicKey, n)
|
||||
for t, k := range testdata.PEMBytes {
|
||||
testPrivateKeys[t], err = ParseRawPrivateKey(k)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err))
|
||||
}
|
||||
testSigners[t], err = NewSignerFromKey(testPrivateKeys[t])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err))
|
||||
}
|
||||
testPublicKeys[t] = testSigners[t].PublicKey()
|
||||
}
|
||||
|
||||
// Create a cert and sign it for use in tests.
|
||||
testCert := &Certificate{
|
||||
Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
|
||||
ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage
|
||||
ValidAfter: 0, // unix epoch
|
||||
ValidBefore: CertTimeInfinity, // The end of currently representable time.
|
||||
Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
|
||||
Key: testPublicKeys["ecdsa"],
|
||||
SignatureKey: testPublicKeys["rsa"],
|
||||
Permissions: Permissions{
|
||||
CriticalOptions: map[string]string{},
|
||||
Extensions: map[string]string{},
|
||||
},
|
||||
}
|
||||
testCert.SignCert(rand.Reader, testSigners["rsa"])
|
||||
testPrivateKeys["cert"] = testPrivateKeys["ecdsa"]
|
||||
testSigners["cert"], err = NewCertSigner(testCert, testSigners["ecdsa"])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to create certificate signer: %v", err))
|
||||
}
|
||||
}
|
||||
375
ssh/transport.go
375
ssh/transport.go
|
|
@ -1,375 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
// debugTransport if set, will print packet types as they go over the
|
||||
// wire. No message decoding is done, to minimize the impact on timing.
|
||||
const debugTransport = false
|
||||
|
||||
const (
|
||||
gcmCipherID = "aes128-gcm@openssh.com"
|
||||
aes128cbcID = "aes128-cbc"
|
||||
tripledescbcID = "3des-cbc"
|
||||
)
|
||||
|
||||
// packetConn represents a transport that implements packet based
|
||||
// operations.
|
||||
type packetConn interface {
|
||||
// Encrypt and send a packet of data to the remote peer.
|
||||
writePacket(packet []byte) error
|
||||
|
||||
// Read a packet from the connection. The read is blocking,
|
||||
// i.e. if error is nil, then the returned byte slice is
|
||||
// always non-empty.
|
||||
readPacket() ([]byte, error)
|
||||
|
||||
// Close closes the write-side of the connection.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// transport is the keyingTransport that implements the SSH packet
|
||||
// protocol.
|
||||
type transport struct {
|
||||
reader connectionState
|
||||
writer connectionState
|
||||
|
||||
bufReader *bufio.Reader
|
||||
bufWriter *bufio.Writer
|
||||
rand io.Reader
|
||||
isClient bool
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// packetCipher represents a combination of SSH encryption/MAC
|
||||
// protocol. A single instance should be used for one direction only.
|
||||
type packetCipher interface {
|
||||
// writePacket encrypts the packet and writes it to w. The
|
||||
// contents of the packet are generally scrambled.
|
||||
writePacket(seqnum uint32, w io.Writer, rand io.Reader, packet []byte) error
|
||||
|
||||
// readPacket reads and decrypts a packet of data. The
|
||||
// returned packet may be overwritten by future calls of
|
||||
// readPacket.
|
||||
readPacket(seqnum uint32, r io.Reader) ([]byte, error)
|
||||
}
|
||||
|
||||
// connectionState represents one side (read or write) of the
|
||||
// connection. This is necessary because each direction has its own
|
||||
// keys, and can even have its own algorithms
|
||||
type connectionState struct {
|
||||
packetCipher
|
||||
seqNum uint32
|
||||
dir direction
|
||||
pendingKeyChange chan packetCipher
|
||||
}
|
||||
|
||||
// prepareKeyChange sets up key material for a keychange. The key changes in
|
||||
// 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 {
|
||||
return err
|
||||
} else {
|
||||
t.reader.pendingKeyChange <- ciph
|
||||
}
|
||||
|
||||
if ciph, err := newPacketCipher(t.writer.dir, algs.w, kexResult); err != nil {
|
||||
return err
|
||||
} else {
|
||||
t.writer.pendingKeyChange <- ciph
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *transport) printPacket(p []byte, write bool) {
|
||||
if len(p) == 0 {
|
||||
return
|
||||
}
|
||||
who := "server"
|
||||
if t.isClient {
|
||||
who = "client"
|
||||
}
|
||||
what := "read"
|
||||
if write {
|
||||
what = "write"
|
||||
}
|
||||
|
||||
log.Println(what, who, p[0])
|
||||
}
|
||||
|
||||
// Read and decrypt next packet.
|
||||
func (t *transport) readPacket() (p []byte, err error) {
|
||||
for {
|
||||
p, err = t.reader.readPacket(t.bufReader)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(p) == 0 || (p[0] != msgIgnore && p[0] != msgDebug) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if debugTransport {
|
||||
t.printPacket(p, false)
|
||||
}
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) {
|
||||
packet, err := s.packetCipher.readPacket(s.seqNum, r)
|
||||
s.seqNum++
|
||||
if err == nil && len(packet) == 0 {
|
||||
err = errors.New("ssh: zero length packet")
|
||||
}
|
||||
|
||||
if len(packet) > 0 {
|
||||
switch packet[0] {
|
||||
case msgNewKeys:
|
||||
select {
|
||||
case cipher := <-s.pendingKeyChange:
|
||||
s.packetCipher = cipher
|
||||
default:
|
||||
return nil, errors.New("ssh: got bogus newkeys message.")
|
||||
}
|
||||
|
||||
case msgDisconnect:
|
||||
// Transform a disconnect message into an
|
||||
// error. Since this is lowest level at which
|
||||
// we interpret message types, doing it here
|
||||
// ensures that we don't have to handle it
|
||||
// elsewhere.
|
||||
var msg disconnectMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, &msg
|
||||
}
|
||||
}
|
||||
|
||||
// The packet may point to an internal buffer, so copy the
|
||||
// packet out here.
|
||||
fresh := make([]byte, len(packet))
|
||||
copy(fresh, packet)
|
||||
|
||||
return fresh, err
|
||||
}
|
||||
|
||||
func (t *transport) writePacket(packet []byte) error {
|
||||
if debugTransport {
|
||||
t.printPacket(packet, true)
|
||||
}
|
||||
return t.writer.writePacket(t.bufWriter, t.rand, packet)
|
||||
}
|
||||
|
||||
func (s *connectionState) writePacket(w *bufio.Writer, rand io.Reader, packet []byte) error {
|
||||
changeKeys := len(packet) > 0 && packet[0] == msgNewKeys
|
||||
|
||||
err := s.packetCipher.writePacket(s.seqNum, w, rand, packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = w.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.seqNum++
|
||||
if changeKeys {
|
||||
select {
|
||||
case cipher := <-s.pendingKeyChange:
|
||||
s.packetCipher = cipher
|
||||
default:
|
||||
panic("ssh: no key material for msgNewKeys")
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func newTransport(rwc io.ReadWriteCloser, rand io.Reader, isClient bool) *transport {
|
||||
t := &transport{
|
||||
bufReader: bufio.NewReader(rwc),
|
||||
bufWriter: bufio.NewWriter(rwc),
|
||||
rand: rand,
|
||||
reader: connectionState{
|
||||
packetCipher: &streamPacketCipher{cipher: noneCipher{}},
|
||||
pendingKeyChange: make(chan packetCipher, 1),
|
||||
},
|
||||
writer: connectionState{
|
||||
packetCipher: &streamPacketCipher{cipher: noneCipher{}},
|
||||
pendingKeyChange: make(chan packetCipher, 1),
|
||||
},
|
||||
Closer: rwc,
|
||||
}
|
||||
t.isClient = isClient
|
||||
|
||||
if isClient {
|
||||
t.reader.dir = serverKeys
|
||||
t.writer.dir = clientKeys
|
||||
} else {
|
||||
t.reader.dir = clientKeys
|
||||
t.writer.dir = serverKeys
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
type direction struct {
|
||||
ivTag []byte
|
||||
keyTag []byte
|
||||
macKeyTag []byte
|
||||
}
|
||||
|
||||
var (
|
||||
serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}}
|
||||
clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
|
||||
)
|
||||
|
||||
// generateKeys generates key material for IV, MAC and encryption.
|
||||
func generateKeys(d direction, algs directionAlgorithms, kex *kexResult) (iv, key, macKey []byte) {
|
||||
cipherMode := cipherModes[algs.Cipher]
|
||||
macMode := macModes[algs.MAC]
|
||||
|
||||
iv = make([]byte, cipherMode.ivSize)
|
||||
key = make([]byte, cipherMode.keySize)
|
||||
macKey = make([]byte, macMode.keySize)
|
||||
|
||||
generateKeyMaterial(iv, d.ivTag, kex)
|
||||
generateKeyMaterial(key, d.keyTag, kex)
|
||||
generateKeyMaterial(macKey, d.macKeyTag, kex)
|
||||
return
|
||||
}
|
||||
|
||||
// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
|
||||
// described in RFC 4253, section 6.4. direction should either be serverKeys
|
||||
// (to setup server->client keys) or clientKeys (for client->server keys).
|
||||
func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) {
|
||||
iv, key, macKey := generateKeys(d, algs, kex)
|
||||
|
||||
if algs.Cipher == gcmCipherID {
|
||||
return newGCMCipher(iv, key, macKey)
|
||||
}
|
||||
|
||||
if algs.Cipher == aes128cbcID {
|
||||
return newAESCBCCipher(iv, key, macKey, algs)
|
||||
}
|
||||
|
||||
if algs.Cipher == tripledescbcID {
|
||||
return newTripleDESCBCCipher(iv, key, macKey, algs)
|
||||
}
|
||||
|
||||
c := &streamPacketCipher{
|
||||
mac: macModes[algs.MAC].new(macKey),
|
||||
etm: macModes[algs.MAC].etm,
|
||||
}
|
||||
c.macResult = make([]byte, c.mac.Size())
|
||||
|
||||
var err error
|
||||
c.cipher, err = cipherModes[algs.Cipher].createStream(key, iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// generateKeyMaterial fills out with key material generated from tag, K, H
|
||||
// and sessionId, as specified in RFC 4253, section 7.2.
|
||||
func generateKeyMaterial(out, tag []byte, r *kexResult) {
|
||||
var digestsSoFar []byte
|
||||
|
||||
h := r.Hash.New()
|
||||
for len(out) > 0 {
|
||||
h.Reset()
|
||||
h.Write(r.K)
|
||||
h.Write(r.H)
|
||||
|
||||
if len(digestsSoFar) == 0 {
|
||||
h.Write(tag)
|
||||
h.Write(r.SessionID)
|
||||
} else {
|
||||
h.Write(digestsSoFar)
|
||||
}
|
||||
|
||||
digest := h.Sum(nil)
|
||||
n := copy(out, digest)
|
||||
out = out[n:]
|
||||
if len(out) > 0 {
|
||||
digestsSoFar = append(digestsSoFar, digest...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const packageVersion = "SSH-2.0-Go"
|
||||
|
||||
// Sends and receives a version line. The versionLine string should
|
||||
// be US ASCII, start with "SSH-2.0-", and should not include a
|
||||
// newline. exchangeVersions returns the other side's version line.
|
||||
func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err error) {
|
||||
// Contrary to the RFC, we do not ignore lines that don't
|
||||
// start with "SSH-2.0-" to make the library usable with
|
||||
// nonconforming servers.
|
||||
for _, c := range versionLine {
|
||||
// The spec disallows non US-ASCII chars, and
|
||||
// specifically forbids null chars.
|
||||
if c < 32 {
|
||||
return nil, errors.New("ssh: junk character in version line")
|
||||
}
|
||||
}
|
||||
if _, err = rw.Write(append(versionLine, '\r', '\n')); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
them, err = readVersion(rw)
|
||||
return them, err
|
||||
}
|
||||
|
||||
// maxVersionStringBytes is the maximum number of bytes that we'll
|
||||
// accept as a version string. RFC 4253 section 4.2 limits this at 255
|
||||
// chars
|
||||
const maxVersionStringBytes = 255
|
||||
|
||||
// Read version string as specified by RFC 4253, section 4.2.
|
||||
func readVersion(r io.Reader) ([]byte, error) {
|
||||
versionString := make([]byte, 0, 64)
|
||||
var ok bool
|
||||
var buf [1]byte
|
||||
|
||||
for len(versionString) < maxVersionStringBytes {
|
||||
_, err := io.ReadFull(r, buf[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The RFC says that the version should be terminated with \r\n
|
||||
// but several SSH servers actually only send a \n.
|
||||
if buf[0] == '\n' {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
|
||||
// non ASCII chars are disallowed, but we are lenient,
|
||||
// since Go doesn't use null-terminated strings.
|
||||
|
||||
// The RFC allows a comment after a space, however,
|
||||
// all of it (version and comments) goes into the
|
||||
// session hash.
|
||||
versionString = append(versionString, buf[0])
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return nil, errors.New("ssh: overflow reading version string")
|
||||
}
|
||||
|
||||
// There might be a '\r' on the end which we should remove.
|
||||
if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' {
|
||||
versionString = versionString[:len(versionString)-1]
|
||||
}
|
||||
return versionString, nil
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadVersion(t *testing.T) {
|
||||
longversion := strings.Repeat("SSH-2.0-bla", 50)[:253]
|
||||
cases := map[string]string{
|
||||
"SSH-2.0-bla\r\n": "SSH-2.0-bla",
|
||||
"SSH-2.0-bla\n": "SSH-2.0-bla",
|
||||
longversion + "\r\n": longversion,
|
||||
}
|
||||
|
||||
for in, want := range cases {
|
||||
result, err := readVersion(bytes.NewBufferString(in))
|
||||
if err != nil {
|
||||
t.Errorf("readVersion(%q): %s", in, err)
|
||||
}
|
||||
got := string(result)
|
||||
if got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadVersionError(t *testing.T) {
|
||||
longversion := strings.Repeat("SSH-2.0-bla", 50)[:253]
|
||||
cases := []string{
|
||||
longversion + "too-long\r\n",
|
||||
}
|
||||
for _, in := range cases {
|
||||
if _, err := readVersion(bytes.NewBufferString(in)); err == nil {
|
||||
t.Errorf("readVersion(%q) should have failed", in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExchangeVersionsBasic(t *testing.T) {
|
||||
v := "SSH-2.0-bla"
|
||||
buf := bytes.NewBufferString(v + "\r\n")
|
||||
them, err := exchangeVersions(buf, []byte("xyz"))
|
||||
if err != nil {
|
||||
t.Errorf("exchangeVersions: %v", err)
|
||||
}
|
||||
|
||||
if want := "SSH-2.0-bla"; string(them) != want {
|
||||
t.Errorf("got %q want %q for our version", them, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExchangeVersions(t *testing.T) {
|
||||
cases := []string{
|
||||
"not\x000allowed",
|
||||
"not allowed\n",
|
||||
}
|
||||
for _, c := range cases {
|
||||
buf := bytes.NewBufferString("SSH-2.0-bla\r\n")
|
||||
if _, err := exchangeVersions(buf, []byte(c)); err == nil {
|
||||
t.Errorf("exchangeVersions(%q): should have failed", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type closerBuffer struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (b *closerBuffer) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestTransportMaxPacketWrite(t *testing.T) {
|
||||
buf := &closerBuffer{}
|
||||
tr := newTransport(buf, rand.Reader, true)
|
||||
huge := make([]byte, maxPacket+1)
|
||||
err := tr.writePacket(huge)
|
||||
if err == nil {
|
||||
t.Errorf("transport accepted write for a huge packet.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransportMaxPacketReader(t *testing.T) {
|
||||
var header [5]byte
|
||||
huge := make([]byte, maxPacket+128)
|
||||
binary.BigEndian.PutUint32(header[0:], uint32(len(huge)))
|
||||
// padding.
|
||||
header[4] = 0
|
||||
|
||||
buf := &closerBuffer{}
|
||||
buf.Write(header[:])
|
||||
buf.Write(huge)
|
||||
|
||||
tr := newTransport(buf, rand.Reader, true)
|
||||
_, err := tr.readPacket()
|
||||
if err == nil {
|
||||
t.Errorf("transport succeeded reading huge packet.")
|
||||
} else if !strings.Contains(err.Error(), "large") {
|
||||
t.Errorf("got %q, should mention %q", err.Error(), "large")
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/tg123/sshpiper/ssh"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package challenger
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/tg123/sshpiper/ssh"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"sort"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ package challenger
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/tg123/sshpiper/ssh"
|
||||
"golang.org/x/crypto/ssh"
|
||||
pam "github.com/vvanpo/golang-pam"
|
||||
"os"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package challenger
|
||||
|
||||
import (
|
||||
"github.com/tg123/sshpiper/ssh"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// piper.AdditionalChallenge = challenger.MakeWelcomeChallenger("Please Use your phone to do the authentication")
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import (
|
|||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/tg123/sshpiper/ssh"
|
||||
"github.com/tg123/sshpiper/sshpiperd/challenger"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/tg123/sshpiper/ssh"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type userFile string
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/tg123/sshpiper/ssh"
|
||||
"github.com/tg123/sshpiper/ssh/testdata"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
|||
2
vendor/golang.org/x/crypto/AUTHORS
generated
vendored
2
vendor/golang.org/x/crypto/AUTHORS
generated
vendored
|
|
@ -1,3 +1,3 @@
|
|||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
||||
# visible at https://tip.golang.org/AUTHORS.
|
||||
|
|
|
|||
2
vendor/golang.org/x/crypto/CONTRIBUTORS
generated
vendored
2
vendor/golang.org/x/crypto/CONTRIBUTORS
generated
vendored
|
|
@ -1,3 +1,3 @@
|
|||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
||||
# visible at https://tip.golang.org/CONTRIBUTORS.
|
||||
|
|
|
|||
3
vendor/golang.org/x/crypto/README
generated
vendored
3
vendor/golang.org/x/crypto/README
generated
vendored
|
|
@ -1,3 +0,0 @@
|
|||
This repository holds supplementary Go cryptography libraries.
|
||||
|
||||
To submit changes to this repository, see http://golang.org/doc/contribute.html.
|
||||
21
vendor/golang.org/x/crypto/README.md
generated
vendored
Normal file
21
vendor/golang.org/x/crypto/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Go Cryptography
|
||||
|
||||
This repository holds supplementary Go cryptography libraries.
|
||||
|
||||
## Download/Install
|
||||
|
||||
The easiest way to install is to run `go get -u golang.org/x/crypto/...`. You
|
||||
can also manually git clone the repository to `$GOPATH/src/golang.org/x/crypto`.
|
||||
|
||||
## Report Issues / Send Patches
|
||||
|
||||
This repository uses Gerrit for code changes. To learn how to submit changes to
|
||||
this repository, see https://golang.org/doc/contribute.html.
|
||||
|
||||
The main issue tracker for the crypto repository is located at
|
||||
https://github.com/golang/go/issues. Prefix your issue with "x/crypto:" in the
|
||||
subject line, so it is easy to find.
|
||||
|
||||
Note that contributions to the cryptography package receive additional scrutiny
|
||||
due to their sensitive nature. Patches may take longer than normal to receive
|
||||
feedback.
|
||||
48
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
48
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
|
|
@ -51,38 +51,6 @@ const (
|
|||
maxNonces = 100
|
||||
)
|
||||
|
||||
// CertOption is an optional argument type for Client methods which manipulate
|
||||
// certificate data.
|
||||
type CertOption interface {
|
||||
privateCertOpt()
|
||||
}
|
||||
|
||||
// WithKey creates an option holding a private/public key pair.
|
||||
// The private part signs a certificate, and the public part represents the signee.
|
||||
func WithKey(key crypto.Signer) CertOption {
|
||||
return &certOptKey{key}
|
||||
}
|
||||
|
||||
type certOptKey struct {
|
||||
key crypto.Signer
|
||||
}
|
||||
|
||||
func (*certOptKey) privateCertOpt() {}
|
||||
|
||||
// WithTemplate creates an option for specifying a certificate template.
|
||||
// See x509.CreateCertificate for template usage details.
|
||||
//
|
||||
// In TLSSNIxChallengeCert methods, the template is also used as parent,
|
||||
// resulting in a self-signed certificate.
|
||||
// The DNSNames field of t is always overwritten for tls-sni challenge certs.
|
||||
func WithTemplate(t *x509.Certificate) CertOption {
|
||||
return (*certOptTemplate)(t)
|
||||
}
|
||||
|
||||
type certOptTemplate x509.Certificate
|
||||
|
||||
func (*certOptTemplate) privateCertOpt() {}
|
||||
|
||||
// Client is an ACME client.
|
||||
// The only required field is Key. An example of creating a client with a new key
|
||||
// is as follows:
|
||||
|
|
@ -174,7 +142,7 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
|
|||
//
|
||||
// In the case where CA server does not provide the issued certificate in the response,
|
||||
// CreateCert will poll certURL using c.FetchCert, which will result in additional round-trips.
|
||||
// In such scenario the caller can cancel the polling with ctx.
|
||||
// In such a scenario, the caller can cancel the polling with ctx.
|
||||
//
|
||||
// CreateCert returns an error if the CA's response or chain was unreasonably large.
|
||||
// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features.
|
||||
|
|
@ -207,7 +175,7 @@ func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration,
|
|||
return nil, "", responseError(res)
|
||||
}
|
||||
|
||||
curl := res.Header.Get("location") // cert permanent URL
|
||||
curl := res.Header.Get("Location") // cert permanent URL
|
||||
if res.ContentLength == 0 {
|
||||
// no cert in the body; poll until we get it
|
||||
cert, err := c.FetchCert(ctx, curl, bundle)
|
||||
|
|
@ -240,7 +208,7 @@ func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]by
|
|||
if res.StatusCode > 299 {
|
||||
return nil, responseError(res)
|
||||
}
|
||||
d := retryAfter(res.Header.Get("retry-after"), 3*time.Second)
|
||||
d := retryAfter(res.Header.Get("Retry-After"), 3*time.Second)
|
||||
select {
|
||||
case <-time.After(d):
|
||||
// retry
|
||||
|
|
@ -289,7 +257,7 @@ func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte,
|
|||
func AcceptTOS(tosURL string) bool { return true }
|
||||
|
||||
// Register creates a new account registration by following the "new-reg" flow.
|
||||
// It returns registered account. The a argument is not modified.
|
||||
// It returns the registered account. The account is not modified.
|
||||
//
|
||||
// The registration may require the caller to agree to the CA's Terms of Service (TOS).
|
||||
// If so, and the account has not indicated the acceptance of the terms (see Account for details),
|
||||
|
|
@ -444,7 +412,7 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
retry := res.Header.Get("retry-after")
|
||||
retry := res.Header.Get("Retry-After")
|
||||
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted {
|
||||
res.Body.Close()
|
||||
if err := sleep(retry, 1); err != nil {
|
||||
|
|
@ -703,7 +671,7 @@ func (c *Client) retryPostJWS(ctx context.Context, key crypto.Signer, url string
|
|||
// clear any nonces that we might've stored that might now be
|
||||
// considered bad
|
||||
c.clearNonces()
|
||||
retry := res.Header.Get("retry-after")
|
||||
retry := res.Header.Get("Retry-After")
|
||||
if err := sleep(retry, 1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1027,6 +995,7 @@ func keyAuth(pub crypto.PublicKey, token string) (string, error) {
|
|||
|
||||
// tlsChallengeCert creates a temporary certificate for TLS-SNI challenges
|
||||
// with the given SANs and auto-generated public/private key pair.
|
||||
// The Subject Common Name is set to the first SAN to aid debugging.
|
||||
// To create a cert with a custom key pair, specify WithKey option.
|
||||
func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
||||
var (
|
||||
|
|
@ -1065,6 +1034,9 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
|||
}
|
||||
}
|
||||
tmpl.DNSNames = san
|
||||
if len(san) > 0 {
|
||||
tmpl.Subject.CommonName = san[0]
|
||||
}
|
||||
|
||||
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
||||
if err != nil {
|
||||
|
|
|
|||
60
vendor/golang.org/x/crypto/acme/acme_test.go
generated
vendored
60
vendor/golang.org/x/crypto/acme/acme_test.go
generated
vendored
|
|
@ -74,7 +74,7 @@ func TestDiscover(t *testing.T) {
|
|||
revoke = "https://example.com/acme/revoke-cert"
|
||||
)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("content-type", "application/json")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{
|
||||
"new-reg": %q,
|
||||
"new-authz": %q,
|
||||
|
|
@ -107,7 +107,7 @@ func TestRegister(t *testing.T) {
|
|||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "test-nonce")
|
||||
w.Header().Set("Replay-Nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
|
|
@ -173,7 +173,7 @@ func TestUpdateReg(t *testing.T) {
|
|||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "test-nonce")
|
||||
w.Header().Set("Replay-Nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
|
|
@ -234,7 +234,7 @@ func TestGetReg(t *testing.T) {
|
|||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "test-nonce")
|
||||
w.Header().Set("Replay-Nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
|
|
@ -290,7 +290,7 @@ func TestGetReg(t *testing.T) {
|
|||
func TestAuthorize(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "test-nonce")
|
||||
w.Header().Set("Replay-Nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
|
|
@ -371,7 +371,7 @@ func TestAuthorize(t *testing.T) {
|
|||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
|
||||
}
|
||||
if c.Token != "token1" {
|
||||
t.Errorf("c.Token = %q; want token1", c.Type)
|
||||
t.Errorf("c.Token = %q; want token1", c.Token)
|
||||
}
|
||||
|
||||
c = auth.Challenges[1]
|
||||
|
|
@ -382,7 +382,7 @@ func TestAuthorize(t *testing.T) {
|
|||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI)
|
||||
}
|
||||
if c.Token != "token2" {
|
||||
t.Errorf("c.Token = %q; want token2", c.Type)
|
||||
t.Errorf("c.Token = %q; want token2", c.Token)
|
||||
}
|
||||
|
||||
combs := [][]int{{0}, {1}}
|
||||
|
|
@ -394,7 +394,7 @@ func TestAuthorize(t *testing.T) {
|
|||
func TestAuthorizeValid(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "nonce")
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
|
|
@ -464,7 +464,7 @@ func TestGetAuthorization(t *testing.T) {
|
|||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
|
||||
}
|
||||
if c.Token != "token1" {
|
||||
t.Errorf("c.Token = %q; want token1", c.Type)
|
||||
t.Errorf("c.Token = %q; want token1", c.Token)
|
||||
}
|
||||
|
||||
c = auth.Challenges[1]
|
||||
|
|
@ -475,7 +475,7 @@ func TestGetAuthorization(t *testing.T) {
|
|||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI)
|
||||
}
|
||||
if c.Token != "token2" {
|
||||
t.Errorf("c.Token = %q; want token2", c.Type)
|
||||
t.Errorf("c.Token = %q; want token2", c.Token)
|
||||
}
|
||||
|
||||
combs := [][]int{{0}, {1}}
|
||||
|
|
@ -488,7 +488,7 @@ func TestWaitAuthorization(t *testing.T) {
|
|||
var count int
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
count++
|
||||
w.Header().Set("retry-after", "0")
|
||||
w.Header().Set("Retry-After", "0")
|
||||
if count > 1 {
|
||||
fmt.Fprintf(w, `{"status":"valid"}`)
|
||||
return
|
||||
|
|
@ -551,7 +551,7 @@ func TestWaitAuthorizationInvalid(t *testing.T) {
|
|||
|
||||
func TestWaitAuthorizationCancel(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("retry-after", "60")
|
||||
w.Header().Set("Retry-After", "60")
|
||||
fmt.Fprintf(w, `{"status":"pending"}`)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
|
@ -579,7 +579,7 @@ func TestWaitAuthorizationCancel(t *testing.T) {
|
|||
func TestRevokeAuthorization(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "nonce")
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
return
|
||||
}
|
||||
switch r.URL.Path {
|
||||
|
|
@ -645,14 +645,14 @@ func TestPollChallenge(t *testing.T) {
|
|||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", chall.URI)
|
||||
}
|
||||
if chall.Token != "token1" {
|
||||
t.Errorf("c.Token = %q; want token1", chall.Type)
|
||||
t.Errorf("c.Token = %q; want token1", chall.Token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcceptChallenge(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "test-nonce")
|
||||
w.Header().Set("Replay-Nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
|
|
@ -707,7 +707,7 @@ func TestAcceptChallenge(t *testing.T) {
|
|||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
|
||||
}
|
||||
if c.Token != "token1" {
|
||||
t.Errorf("c.Token = %q; want token1", c.Type)
|
||||
t.Errorf("c.Token = %q; want token1", c.Token)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -718,7 +718,7 @@ func TestNewCert(t *testing.T) {
|
|||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "test-nonce")
|
||||
w.Header().Set("Replay-Nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
|
|
@ -801,7 +801,7 @@ func TestFetchCert(t *testing.T) {
|
|||
count++
|
||||
if count < 3 {
|
||||
up := fmt.Sprintf("<%s>;rel=up", ts.URL)
|
||||
w.Header().Set("link", up)
|
||||
w.Header().Set("Link", up)
|
||||
}
|
||||
w.Write([]byte{count})
|
||||
}))
|
||||
|
|
@ -820,7 +820,7 @@ func TestFetchCertRetry(t *testing.T) {
|
|||
var count int
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if count < 1 {
|
||||
w.Header().Set("retry-after", "0")
|
||||
w.Header().Set("Retry-After", "0")
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
count++
|
||||
return
|
||||
|
|
@ -840,7 +840,7 @@ func TestFetchCertRetry(t *testing.T) {
|
|||
|
||||
func TestFetchCertCancel(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("retry-after", "0")
|
||||
w.Header().Set("Retry-After", "0")
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
|
@ -867,7 +867,7 @@ func TestFetchCertDepth(t *testing.T) {
|
|||
t.Errorf("count = %d; want at most %d", count, maxChainLen+1)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
w.Header().Set("link", fmt.Sprintf("<%s>;rel=up", ts.URL))
|
||||
w.Header().Set("Link", fmt.Sprintf("<%s>;rel=up", ts.URL))
|
||||
w.Write([]byte{count})
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
|
@ -881,7 +881,7 @@ func TestFetchCertBreadth(t *testing.T) {
|
|||
var ts *httptest.Server
|
||||
ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
for i := 0; i < maxChainLen+1; i++ {
|
||||
w.Header().Add("link", fmt.Sprintf("<%s>;rel=up", ts.URL))
|
||||
w.Header().Add("Link", fmt.Sprintf("<%s>;rel=up", ts.URL))
|
||||
}
|
||||
w.Write([]byte{1})
|
||||
}))
|
||||
|
|
@ -907,7 +907,7 @@ func TestFetchCertSize(t *testing.T) {
|
|||
func TestRevokeCert(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "nonce")
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -946,7 +946,7 @@ func TestNonce_add(t *testing.T) {
|
|||
c.addNonce(http.Header{"Replay-Nonce": {}})
|
||||
c.addNonce(http.Header{"Replay-Nonce": {"nonce"}})
|
||||
|
||||
nonces := map[string]struct{}{"nonce": struct{}{}}
|
||||
nonces := map[string]struct{}{"nonce": {}}
|
||||
if !reflect.DeepEqual(c.nonces, nonces) {
|
||||
t.Errorf("c.nonces = %q; want %q", c.nonces, nonces)
|
||||
}
|
||||
|
|
@ -977,7 +977,7 @@ func TestNonce_fetch(t *testing.T) {
|
|||
if r.Method != "HEAD" {
|
||||
t.Errorf("%d: r.Method = %q; want HEAD", i, r.Method)
|
||||
}
|
||||
w.Header().Set("replay-nonce", tests[i].nonce)
|
||||
w.Header().Set("Replay-Nonce", tests[i].nonce)
|
||||
w.WriteHeader(tests[i].code)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
|
@ -1018,7 +1018,7 @@ func TestNonce_postJWS(t *testing.T) {
|
|||
seen := make(map[string]bool)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
count++
|
||||
w.Header().Set("replay-nonce", fmt.Sprintf("nonce%d", count))
|
||||
w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count))
|
||||
if r.Method == "HEAD" {
|
||||
// We expect the client do a HEAD request
|
||||
// but only to fetch the first nonce.
|
||||
|
|
@ -1072,7 +1072,7 @@ func TestRetryPostJWS(t *testing.T) {
|
|||
var count int
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
count++
|
||||
w.Header().Set("replay-nonce", fmt.Sprintf("nonce%d", count))
|
||||
w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count))
|
||||
if r.Method == "HEAD" {
|
||||
// We expect the client to do 2 head requests to fetch
|
||||
// nonces, one to start and another after getting badNonce
|
||||
|
|
@ -1186,6 +1186,9 @@ func TestTLSSNI01ChallengeCert(t *testing.T) {
|
|||
if cert.DNSNames[0] != name {
|
||||
t.Errorf("cert.DNSNames[0] != name: %q vs %q", cert.DNSNames[0], name)
|
||||
}
|
||||
if cn := cert.Subject.CommonName; cn != san {
|
||||
t.Errorf("cert.Subject.CommonName = %q; want %q", cn, san)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSSNI02ChallengeCert(t *testing.T) {
|
||||
|
|
@ -1219,6 +1222,9 @@ func TestTLSSNI02ChallengeCert(t *testing.T) {
|
|||
if i >= len(cert.DNSNames) || cert.DNSNames[i] != name {
|
||||
t.Errorf("%v doesn't have %q", cert.DNSNames, name)
|
||||
}
|
||||
if cn := cert.Subject.CommonName; cn != sanA {
|
||||
t.Errorf("CommonName = %q; want %q", cn, sanA)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSChallengeCertOpt(t *testing.T) {
|
||||
|
|
|
|||
340
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
340
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
|
|
@ -24,7 +24,9 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
mathrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -80,11 +82,14 @@ func defaultHostPolicy(context.Context, string) error {
|
|||
}
|
||||
|
||||
// Manager is a stateful certificate manager built on top of acme.Client.
|
||||
// It obtains and refreshes certificates automatically,
|
||||
// as well as providing them to a TLS server via tls.Config.
|
||||
// It obtains and refreshes certificates automatically using "tls-sni-01",
|
||||
// "tls-sni-02" and "http-01" challenge types, as well as providing them
|
||||
// to a TLS server via tls.Config.
|
||||
//
|
||||
// To preserve issued certificates and improve overall performance,
|
||||
// use a cache implementation of Cache. For instance, DirCache.
|
||||
// You must specify a cache implementation, such as DirCache,
|
||||
// to reuse obtained certificates across program restarts.
|
||||
// Otherwise your server is very likely to exceed the certificate
|
||||
// issuer's request rate limits.
|
||||
type Manager struct {
|
||||
// Prompt specifies a callback function to conditionally accept a CA's Terms of Service (TOS).
|
||||
// The registration may require the caller to agree to the CA's TOS.
|
||||
|
|
@ -148,15 +153,26 @@ type Manager struct {
|
|||
stateMu sync.Mutex
|
||||
state map[string]*certState // keyed by domain name
|
||||
|
||||
// tokenCert is keyed by token domain name, which matches server name
|
||||
// of ClientHello. Keys always have ".acme.invalid" suffix.
|
||||
tokenCertMu sync.RWMutex
|
||||
tokenCert map[string]*tls.Certificate
|
||||
|
||||
// renewal tracks the set of domains currently running renewal timers.
|
||||
// It is keyed by domain name.
|
||||
renewalMu sync.Mutex
|
||||
renewal map[string]*domainRenewal
|
||||
|
||||
// tokensMu guards the rest of the fields: tryHTTP01, certTokens and httpTokens.
|
||||
tokensMu sync.RWMutex
|
||||
// tryHTTP01 indicates whether the Manager should try "http-01" challenge type
|
||||
// during the authorization flow.
|
||||
tryHTTP01 bool
|
||||
// httpTokens contains response body values for http-01 challenges
|
||||
// and is keyed by the URL path at which a challenge response is expected
|
||||
// to be provisioned.
|
||||
// The entries are stored for the duration of the authorization flow.
|
||||
httpTokens map[string][]byte
|
||||
// certTokens contains temporary certificates for tls-sni challenges
|
||||
// and is keyed by token domain name, which matches server name of ClientHello.
|
||||
// Keys always have ".acme.invalid" suffix.
|
||||
// The entries are stored for the duration of the authorization flow.
|
||||
certTokens map[string]*tls.Certificate
|
||||
}
|
||||
|
||||
// GetCertificate implements the tls.Config.GetCertificate hook.
|
||||
|
|
@ -183,14 +199,16 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
|||
return nil, errors.New("acme/autocert: server name contains invalid character")
|
||||
}
|
||||
|
||||
// In the worst-case scenario, the timeout needs to account for caching, host policy,
|
||||
// domain ownership verification and certificate issuance.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// check whether this is a token cert requested for TLS-SNI challenge
|
||||
if strings.HasSuffix(name, ".acme.invalid") {
|
||||
m.tokenCertMu.RLock()
|
||||
defer m.tokenCertMu.RUnlock()
|
||||
if cert := m.tokenCert[name]; cert != nil {
|
||||
m.tokensMu.RLock()
|
||||
defer m.tokensMu.RUnlock()
|
||||
if cert := m.certTokens[name]; cert != nil {
|
||||
return cert, nil
|
||||
}
|
||||
if cert, err := m.cacheGet(ctx, name); err == nil {
|
||||
|
|
@ -222,6 +240,68 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
|||
return cert, nil
|
||||
}
|
||||
|
||||
// HTTPHandler configures the Manager to provision ACME "http-01" challenge responses.
|
||||
// It returns an http.Handler that responds to the challenges and must be
|
||||
// running on port 80. If it receives a request that is not an ACME challenge,
|
||||
// it delegates the request to the optional fallback handler.
|
||||
//
|
||||
// If fallback is nil, the returned handler redirects all GET and HEAD requests
|
||||
// to the default TLS port 443 with 302 Found status code, preserving the original
|
||||
// request path and query. It responds with 400 Bad Request to all other HTTP methods.
|
||||
// The fallback is not protected by the optional HostPolicy.
|
||||
//
|
||||
// Because the fallback handler is run with unencrypted port 80 requests,
|
||||
// the fallback should not serve TLS-only requests.
|
||||
//
|
||||
// If HTTPHandler is never called, the Manager will only use TLS SNI
|
||||
// challenges for domain verification.
|
||||
func (m *Manager) HTTPHandler(fallback http.Handler) http.Handler {
|
||||
m.tokensMu.Lock()
|
||||
defer m.tokensMu.Unlock()
|
||||
m.tryHTTP01 = true
|
||||
|
||||
if fallback == nil {
|
||||
fallback = http.HandlerFunc(handleHTTPRedirect)
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge/") {
|
||||
fallback.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
// A reasonable context timeout for cache and host policy only,
|
||||
// because we don't wait for a new certificate issuance here.
|
||||
ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
|
||||
defer cancel()
|
||||
if err := m.hostPolicy()(ctx, r.Host); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
data, err := m.httpToken(ctx, r.URL.Path)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
})
|
||||
}
|
||||
|
||||
func handleHTTPRedirect(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" && r.Method != "HEAD" {
|
||||
http.Error(w, "Use HTTPS", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
target := "https://" + stripPort(r.Host) + r.URL.RequestURI()
|
||||
http.Redirect(w, r, target, http.StatusFound)
|
||||
}
|
||||
|
||||
func stripPort(hostport string) string {
|
||||
host, _, err := net.SplitHostPort(hostport)
|
||||
if err != nil {
|
||||
return hostport
|
||||
}
|
||||
return net.JoinHostPort(host, "443")
|
||||
}
|
||||
|
||||
// cert returns an existing certificate either from m.state or cache.
|
||||
// If a certificate is found in cache but not in m.state, the latter will be filled
|
||||
// with the cached value.
|
||||
|
|
@ -369,7 +449,7 @@ func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certifica
|
|||
|
||||
// We are the first; state is locked.
|
||||
// Unblock the readers when domain ownership is verified
|
||||
// and the we got the cert or the process failed.
|
||||
// and we got the cert or the process failed.
|
||||
defer state.Unlock()
|
||||
state.locked = false
|
||||
|
||||
|
|
@ -437,16 +517,17 @@ func (m *Manager) certState(domain string) (*certState, error) {
|
|||
return state, nil
|
||||
}
|
||||
|
||||
// authorizedCert starts domain ownership verification process and requests a new cert upon success.
|
||||
// authorizedCert starts the domain ownership verification process and requests a new cert upon success.
|
||||
// The key argument is the certificate private key.
|
||||
func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain string) (der [][]byte, leaf *x509.Certificate, err error) {
|
||||
if err := m.verify(ctx, domain); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
client, err := m.acmeClient(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := m.verify(ctx, client, domain); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
csr, err := certRequest(key, domain)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
@ -462,98 +543,171 @@ func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain
|
|||
return der, leaf, nil
|
||||
}
|
||||
|
||||
// verify starts a new identifier (domain) authorization flow.
|
||||
// It prepares a challenge response and then blocks until the authorization
|
||||
// is marked as "completed" by the CA (either succeeded or failed).
|
||||
//
|
||||
// verify returns nil iff the verification was successful.
|
||||
func (m *Manager) verify(ctx context.Context, domain string) error {
|
||||
client, err := m.acmeClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
// verify runs the identifier (domain) authorization flow
|
||||
// using each applicable ACME challenge type.
|
||||
func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string) error {
|
||||
// The list of challenge types we'll try to fulfill
|
||||
// in this specific order.
|
||||
challengeTypes := []string{"tls-sni-02", "tls-sni-01"}
|
||||
m.tokensMu.RLock()
|
||||
if m.tryHTTP01 {
|
||||
challengeTypes = append(challengeTypes, "http-01")
|
||||
}
|
||||
m.tokensMu.RUnlock()
|
||||
|
||||
// start domain authorization and get the challenge
|
||||
authz, err := client.Authorize(ctx, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// maybe don't need to at all
|
||||
if authz.Status == acme.StatusValid {
|
||||
return nil
|
||||
}
|
||||
|
||||
// pick a challenge: prefer tls-sni-02 over tls-sni-01
|
||||
// TODO: consider authz.Combinations
|
||||
var chal *acme.Challenge
|
||||
for _, c := range authz.Challenges {
|
||||
if c.Type == "tls-sni-02" {
|
||||
chal = c
|
||||
break
|
||||
var nextTyp int // challengeType index of the next challenge type to try
|
||||
for {
|
||||
// Start domain authorization and get the challenge.
|
||||
authz, err := client.Authorize(ctx, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.Type == "tls-sni-01" {
|
||||
chal = c
|
||||
// No point in accepting challenges if the authorization status
|
||||
// is in a final state.
|
||||
switch authz.Status {
|
||||
case acme.StatusValid:
|
||||
return nil // already authorized
|
||||
case acme.StatusInvalid:
|
||||
return fmt.Errorf("acme/autocert: invalid authorization %q", authz.URI)
|
||||
}
|
||||
|
||||
// Pick the next preferred challenge.
|
||||
var chal *acme.Challenge
|
||||
for chal == nil && nextTyp < len(challengeTypes) {
|
||||
chal = pickChallenge(challengeTypes[nextTyp], authz.Challenges)
|
||||
nextTyp++
|
||||
}
|
||||
if chal == nil {
|
||||
return fmt.Errorf("acme/autocert: unable to authorize %q; tried %q", domain, challengeTypes)
|
||||
}
|
||||
cleanup, err := m.fulfill(ctx, client, chal)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer cleanup()
|
||||
if _, err := client.Accept(ctx, chal); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// A challenge is fulfilled and accepted: wait for the CA to validate.
|
||||
if _, err := client.WaitAuthorization(ctx, authz.URI); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if chal == nil {
|
||||
return errors.New("acme/autocert: no supported challenge type found")
|
||||
}
|
||||
|
||||
// create a token cert for the challenge response
|
||||
var (
|
||||
cert tls.Certificate
|
||||
name string
|
||||
)
|
||||
switch chal.Type {
|
||||
case "tls-sni-01":
|
||||
cert, name, err = client.TLSSNI01ChallengeCert(chal.Token)
|
||||
case "tls-sni-02":
|
||||
cert, name, err = client.TLSSNI02ChallengeCert(chal.Token)
|
||||
default:
|
||||
err = fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.putTokenCert(ctx, name, &cert)
|
||||
defer func() {
|
||||
// verification has ended at this point
|
||||
// don't need token cert anymore
|
||||
go m.deleteTokenCert(name)
|
||||
}()
|
||||
|
||||
// ready to fulfill the challenge
|
||||
if _, err := client.Accept(ctx, chal); err != nil {
|
||||
return err
|
||||
}
|
||||
// wait for the CA to validate
|
||||
_, err = client.WaitAuthorization(ctx, authz.URI)
|
||||
return err
|
||||
}
|
||||
|
||||
// putTokenCert stores the cert under the named key in both m.tokenCert map
|
||||
// and m.Cache.
|
||||
func (m *Manager) putTokenCert(ctx context.Context, name string, cert *tls.Certificate) {
|
||||
m.tokenCertMu.Lock()
|
||||
defer m.tokenCertMu.Unlock()
|
||||
if m.tokenCert == nil {
|
||||
m.tokenCert = make(map[string]*tls.Certificate)
|
||||
// fulfill provisions a response to the challenge chal.
|
||||
// The cleanup is non-nil only if provisioning succeeded.
|
||||
func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge) (cleanup func(), err error) {
|
||||
switch chal.Type {
|
||||
case "tls-sni-01":
|
||||
cert, name, err := client.TLSSNI01ChallengeCert(chal.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.putCertToken(ctx, name, &cert)
|
||||
return func() { go m.deleteCertToken(name) }, nil
|
||||
case "tls-sni-02":
|
||||
cert, name, err := client.TLSSNI02ChallengeCert(chal.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.putCertToken(ctx, name, &cert)
|
||||
return func() { go m.deleteCertToken(name) }, nil
|
||||
case "http-01":
|
||||
resp, err := client.HTTP01ChallengeResponse(chal.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := client.HTTP01ChallengePath(chal.Token)
|
||||
m.putHTTPToken(ctx, p, resp)
|
||||
return func() { go m.deleteHTTPToken(p) }, nil
|
||||
}
|
||||
m.tokenCert[name] = cert
|
||||
return nil, fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type)
|
||||
}
|
||||
|
||||
func pickChallenge(typ string, chal []*acme.Challenge) *acme.Challenge {
|
||||
for _, c := range chal {
|
||||
if c.Type == typ {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// putCertToken stores the cert under the named key in both m.certTokens map
|
||||
// and m.Cache.
|
||||
func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certificate) {
|
||||
m.tokensMu.Lock()
|
||||
defer m.tokensMu.Unlock()
|
||||
if m.certTokens == nil {
|
||||
m.certTokens = make(map[string]*tls.Certificate)
|
||||
}
|
||||
m.certTokens[name] = cert
|
||||
m.cachePut(ctx, name, cert)
|
||||
}
|
||||
|
||||
// deleteTokenCert removes the token certificate for the specified domain name
|
||||
// from both m.tokenCert map and m.Cache.
|
||||
func (m *Manager) deleteTokenCert(name string) {
|
||||
m.tokenCertMu.Lock()
|
||||
defer m.tokenCertMu.Unlock()
|
||||
delete(m.tokenCert, name)
|
||||
// deleteCertToken removes the token certificate for the specified domain name
|
||||
// from both m.certTokens map and m.Cache.
|
||||
func (m *Manager) deleteCertToken(name string) {
|
||||
m.tokensMu.Lock()
|
||||
defer m.tokensMu.Unlock()
|
||||
delete(m.certTokens, name)
|
||||
if m.Cache != nil {
|
||||
m.Cache.Delete(context.Background(), name)
|
||||
}
|
||||
}
|
||||
|
||||
// httpToken retrieves an existing http-01 token value from an in-memory map
|
||||
// or the optional cache.
|
||||
func (m *Manager) httpToken(ctx context.Context, tokenPath string) ([]byte, error) {
|
||||
m.tokensMu.RLock()
|
||||
defer m.tokensMu.RUnlock()
|
||||
if v, ok := m.httpTokens[tokenPath]; ok {
|
||||
return v, nil
|
||||
}
|
||||
if m.Cache == nil {
|
||||
return nil, fmt.Errorf("acme/autocert: no token at %q", tokenPath)
|
||||
}
|
||||
return m.Cache.Get(ctx, httpTokenCacheKey(tokenPath))
|
||||
}
|
||||
|
||||
// putHTTPToken stores an http-01 token value using tokenPath as key
|
||||
// in both in-memory map and the optional Cache.
|
||||
//
|
||||
// It ignores any error returned from Cache.Put.
|
||||
func (m *Manager) putHTTPToken(ctx context.Context, tokenPath, val string) {
|
||||
m.tokensMu.Lock()
|
||||
defer m.tokensMu.Unlock()
|
||||
if m.httpTokens == nil {
|
||||
m.httpTokens = make(map[string][]byte)
|
||||
}
|
||||
b := []byte(val)
|
||||
m.httpTokens[tokenPath] = b
|
||||
if m.Cache != nil {
|
||||
m.Cache.Put(ctx, httpTokenCacheKey(tokenPath), b)
|
||||
}
|
||||
}
|
||||
|
||||
// deleteHTTPToken removes an http-01 token value from both in-memory map
|
||||
// and the optional Cache, ignoring any error returned from the latter.
|
||||
//
|
||||
// If m.Cache is non-nil, it blocks until Cache.Delete returns without a timeout.
|
||||
func (m *Manager) deleteHTTPToken(tokenPath string) {
|
||||
m.tokensMu.Lock()
|
||||
defer m.tokensMu.Unlock()
|
||||
delete(m.httpTokens, tokenPath)
|
||||
if m.Cache != nil {
|
||||
m.Cache.Delete(context.Background(), httpTokenCacheKey(tokenPath))
|
||||
}
|
||||
}
|
||||
|
||||
// httpTokenCacheKey returns a key at which an http-01 token value may be stored
|
||||
// in the Manager's optional Cache.
|
||||
func httpTokenCacheKey(tokenPath string) string {
|
||||
return "http-01-" + path.Base(tokenPath)
|
||||
}
|
||||
|
||||
// renew starts a cert renewal timer loop, one per domain.
|
||||
//
|
||||
// The loop is scheduled in two cases:
|
||||
|
|
|
|||
157
vendor/golang.org/x/crypto/acme/autocert/autocert_test.go
generated
vendored
157
vendor/golang.org/x/crypto/acme/autocert/autocert_test.go
generated
vendored
|
|
@ -23,6 +23,7 @@ import (
|
|||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -48,6 +49,16 @@ var authzTmpl = template.Must(template.New("authz").Parse(`{
|
|||
"uri": "{{.}}/challenge/2",
|
||||
"type": "tls-sni-02",
|
||||
"token": "token-02"
|
||||
},
|
||||
{
|
||||
"uri": "{{.}}/challenge/dns-01",
|
||||
"type": "dns-01",
|
||||
"token": "token-dns-01"
|
||||
},
|
||||
{
|
||||
"uri": "{{.}}/challenge/http-01",
|
||||
"type": "http-01",
|
||||
"token": "token-http-01"
|
||||
}
|
||||
]
|
||||
}`))
|
||||
|
|
@ -278,7 +289,7 @@ func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string,
|
|||
// ACME CA server stub
|
||||
var ca *httptest.Server
|
||||
ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("replay-nonce", "nonce")
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
if r.Method == "HEAD" {
|
||||
// a nonce request
|
||||
return
|
||||
|
|
@ -295,7 +306,7 @@ func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string,
|
|||
w.Write([]byte("{}"))
|
||||
// domain authorization
|
||||
case "/new-authz":
|
||||
w.Header().Set("location", ca.URL+"/authz/1")
|
||||
w.Header().Set("Location", ca.URL+"/authz/1")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := authzTmpl.Execute(w, ca.URL); err != nil {
|
||||
t.Errorf("authzTmpl: %v", err)
|
||||
|
|
@ -326,7 +337,7 @@ func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string,
|
|||
t.Errorf("new-cert: dummyCert: %v", err)
|
||||
}
|
||||
chainUp := fmt.Sprintf("<%s/ca-cert>; rel=up", ca.URL)
|
||||
w.Header().Set("link", chainUp)
|
||||
w.Header().Set("Link", chainUp)
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write(der)
|
||||
// CA chain cert
|
||||
|
|
@ -419,6 +430,146 @@ func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.Cl
|
|||
|
||||
}
|
||||
|
||||
func TestVerifyHTTP01(t *testing.T) {
|
||||
var (
|
||||
http01 http.Handler
|
||||
|
||||
authzCount int // num. of created authorizations
|
||||
didAcceptHTTP01 bool
|
||||
)
|
||||
|
||||
verifyHTTPToken := func() {
|
||||
r := httptest.NewRequest("GET", "/.well-known/acme-challenge/token-http-01", nil)
|
||||
w := httptest.NewRecorder()
|
||||
http01.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("http token: w.Code = %d; want %d", w.Code, http.StatusOK)
|
||||
}
|
||||
if v := string(w.Body.Bytes()); !strings.HasPrefix(v, "token-http-01.") {
|
||||
t.Errorf("http token value = %q; want 'token-http-01.' prefix", v)
|
||||
}
|
||||
}
|
||||
|
||||
// ACME CA server stub, only the needed bits.
|
||||
// TODO: Merge this with startACMEServerStub, making it a configurable CA for testing.
|
||||
var ca *httptest.Server
|
||||
ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
if r.Method == "HEAD" {
|
||||
// a nonce request
|
||||
return
|
||||
}
|
||||
|
||||
switch r.URL.Path {
|
||||
// Discovery.
|
||||
case "/":
|
||||
if err := discoTmpl.Execute(w, ca.URL); err != nil {
|
||||
t.Errorf("discoTmpl: %v", err)
|
||||
}
|
||||
// Client key registration.
|
||||
case "/new-reg":
|
||||
w.Write([]byte("{}"))
|
||||
// New domain authorization.
|
||||
case "/new-authz":
|
||||
authzCount++
|
||||
w.Header().Set("Location", fmt.Sprintf("%s/authz/%d", ca.URL, authzCount))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := authzTmpl.Execute(w, ca.URL); err != nil {
|
||||
t.Errorf("authzTmpl: %v", err)
|
||||
}
|
||||
// Accept tls-sni-02.
|
||||
case "/challenge/2":
|
||||
w.Write([]byte("{}"))
|
||||
// Reject tls-sni-01.
|
||||
case "/challenge/1":
|
||||
http.Error(w, "won't accept tls-sni-01", http.StatusBadRequest)
|
||||
// Should not accept dns-01.
|
||||
case "/challenge/dns-01":
|
||||
t.Errorf("dns-01 challenge was accepted")
|
||||
http.Error(w, "won't accept dns-01", http.StatusBadRequest)
|
||||
// Accept http-01.
|
||||
case "/challenge/http-01":
|
||||
didAcceptHTTP01 = true
|
||||
verifyHTTPToken()
|
||||
w.Write([]byte("{}"))
|
||||
// Authorization statuses.
|
||||
// Make tls-sni-xxx invalid.
|
||||
case "/authz/1", "/authz/2":
|
||||
w.Write([]byte(`{"status": "invalid"}`))
|
||||
case "/authz/3", "/authz/4":
|
||||
w.Write([]byte(`{"status": "valid"}`))
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
t.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer ca.Close()
|
||||
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m := &Manager{
|
||||
Client: &acme.Client{
|
||||
Key: key,
|
||||
DirectoryURL: ca.URL,
|
||||
},
|
||||
}
|
||||
http01 = m.HTTPHandler(nil)
|
||||
if err := m.verify(context.Background(), m.Client, "example.org"); err != nil {
|
||||
t.Errorf("m.verify: %v", err)
|
||||
}
|
||||
// Only tls-sni-01, tls-sni-02 and http-01 must be accepted
|
||||
// The dns-01 challenge is unsupported.
|
||||
if authzCount != 3 {
|
||||
t.Errorf("authzCount = %d; want 3", authzCount)
|
||||
}
|
||||
if !didAcceptHTTP01 {
|
||||
t.Error("did not accept http-01 challenge")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPHandlerDefaultFallback(t *testing.T) {
|
||||
tt := []struct {
|
||||
method, url string
|
||||
wantCode int
|
||||
wantLocation string
|
||||
}{
|
||||
{"GET", "http://example.org", 302, "https://example.org/"},
|
||||
{"GET", "http://example.org/foo", 302, "https://example.org/foo"},
|
||||
{"GET", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"},
|
||||
{"GET", "http://example.org/?a=b", 302, "https://example.org/?a=b"},
|
||||
{"GET", "http://example.org/foo?a=b", 302, "https://example.org/foo?a=b"},
|
||||
{"GET", "http://example.org:80/foo?a=b", 302, "https://example.org:443/foo?a=b"},
|
||||
{"GET", "http://example.org:80/foo%20bar", 302, "https://example.org:443/foo%20bar"},
|
||||
{"GET", "http://[2602:d1:xxxx::c60a]:1234", 302, "https://[2602:d1:xxxx::c60a]:443/"},
|
||||
{"GET", "http://[2602:d1:xxxx::c60a]", 302, "https://[2602:d1:xxxx::c60a]/"},
|
||||
{"GET", "http://[2602:d1:xxxx::c60a]/foo?a=b", 302, "https://[2602:d1:xxxx::c60a]/foo?a=b"},
|
||||
{"HEAD", "http://example.org", 302, "https://example.org/"},
|
||||
{"HEAD", "http://example.org/foo", 302, "https://example.org/foo"},
|
||||
{"HEAD", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"},
|
||||
{"HEAD", "http://example.org/?a=b", 302, "https://example.org/?a=b"},
|
||||
{"HEAD", "http://example.org/foo?a=b", 302, "https://example.org/foo?a=b"},
|
||||
{"POST", "http://example.org", 400, ""},
|
||||
{"PUT", "http://example.org", 400, ""},
|
||||
{"GET", "http://example.org/.well-known/acme-challenge/x", 404, ""},
|
||||
}
|
||||
var m Manager
|
||||
h := m.HTTPHandler(nil)
|
||||
for i, test := range tt {
|
||||
r := httptest.NewRequest(test.method, test.url, nil)
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, r)
|
||||
if w.Code != test.wantCode {
|
||||
t.Errorf("%d: w.Code = %d; want %d", i, w.Code, test.wantCode)
|
||||
t.Errorf("%d: body: %s", i, w.Body.Bytes())
|
||||
}
|
||||
if v := w.Header().Get("Location"); v != test.wantLocation {
|
||||
t.Errorf("%d: Location = %q; want %q", i, v, test.wantLocation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountKeyCache(t *testing.T) {
|
||||
m := Manager{Cache: newMemCache()}
|
||||
ctx := context.Background()
|
||||
|
|
|
|||
1
vendor/golang.org/x/crypto/acme/autocert/cache_test.go
generated
vendored
1
vendor/golang.org/x/crypto/acme/autocert/cache_test.go
generated
vendored
|
|
@ -21,6 +21,7 @@ func TestDirCache(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
dir = filepath.Join(dir, "certs") // a nonexistent dir
|
||||
cache := DirCache(dir)
|
||||
ctx := context.Background()
|
||||
|
|
|
|||
4
vendor/golang.org/x/crypto/acme/autocert/example_test.go
generated
vendored
4
vendor/golang.org/x/crypto/acme/autocert/example_test.go
generated
vendored
|
|
@ -22,10 +22,12 @@ func ExampleNewListener() {
|
|||
}
|
||||
|
||||
func ExampleManager() {
|
||||
m := autocert.Manager{
|
||||
m := &autocert.Manager{
|
||||
Cache: autocert.DirCache("secret-dir"),
|
||||
Prompt: autocert.AcceptTOS,
|
||||
HostPolicy: autocert.HostWhitelist("example.org"),
|
||||
}
|
||||
go http.ListenAndServe(":http", m.HTTPHandler(nil))
|
||||
s := &http.Server{
|
||||
Addr: ":https",
|
||||
TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
|
||||
|
|
|
|||
6
vendor/golang.org/x/crypto/acme/autocert/renewal_test.go
generated
vendored
6
vendor/golang.org/x/crypto/acme/autocert/renewal_test.go
generated
vendored
|
|
@ -53,7 +53,7 @@ func TestRenewFromCache(t *testing.T) {
|
|||
// ACME CA server stub
|
||||
var ca *httptest.Server
|
||||
ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("replay-nonce", "nonce")
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
if r.Method == "HEAD" {
|
||||
// a nonce request
|
||||
return
|
||||
|
|
@ -70,7 +70,7 @@ func TestRenewFromCache(t *testing.T) {
|
|||
w.Write([]byte("{}"))
|
||||
// domain authorization
|
||||
case "/new-authz":
|
||||
w.Header().Set("location", ca.URL+"/authz/1")
|
||||
w.Header().Set("Location", ca.URL+"/authz/1")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write([]byte(`{"status": "valid"}`))
|
||||
// cert request
|
||||
|
|
@ -89,7 +89,7 @@ func TestRenewFromCache(t *testing.T) {
|
|||
t.Fatalf("new-cert: dummyCert: %v", err)
|
||||
}
|
||||
chainUp := fmt.Sprintf("<%s/ca-cert>; rel=up", ca.URL)
|
||||
w.Header().Set("link", chainUp)
|
||||
w.Header().Set("Link", chainUp)
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write(der)
|
||||
// CA chain cert
|
||||
|
|
|
|||
34
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
34
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
|
|
@ -5,6 +5,8 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
|
@ -293,3 +295,35 @@ func (e *wireError) error(h http.Header) *Error {
|
|||
Header: h,
|
||||
}
|
||||
}
|
||||
|
||||
// CertOption is an optional argument type for the TLSSNIxChallengeCert methods for
|
||||
// customizing a temporary certificate for TLS-SNI challenges.
|
||||
type CertOption interface {
|
||||
privateCertOpt()
|
||||
}
|
||||
|
||||
// WithKey creates an option holding a private/public key pair.
|
||||
// The private part signs a certificate, and the public part represents the signee.
|
||||
func WithKey(key crypto.Signer) CertOption {
|
||||
return &certOptKey{key}
|
||||
}
|
||||
|
||||
type certOptKey struct {
|
||||
key crypto.Signer
|
||||
}
|
||||
|
||||
func (*certOptKey) privateCertOpt() {}
|
||||
|
||||
// WithTemplate creates an option for specifying a certificate template.
|
||||
// See x509.CreateCertificate for template usage details.
|
||||
//
|
||||
// In TLSSNIxChallengeCert methods, the template is also used as parent,
|
||||
// resulting in a self-signed certificate.
|
||||
// The DNSNames field of t is always overwritten for tls-sni challenge certs.
|
||||
func WithTemplate(t *x509.Certificate) CertOption {
|
||||
return (*certOptTemplate)(t)
|
||||
}
|
||||
|
||||
type certOptTemplate x509.Certificate
|
||||
|
||||
func (*certOptTemplate) privateCertOpt() {}
|
||||
|
|
|
|||
228
vendor/golang.org/x/crypto/argon2/argon2.go
generated
vendored
Normal file
228
vendor/golang.org/x/crypto/argon2/argon2.go
generated
vendored
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package argon2 implements the key derivation function Argon2.
|
||||
// Argon2 was selected as the winner of the Password Hashing Competition and can
|
||||
// be used to derive cryptographic keys from passwords.
|
||||
// Argon2 is specfifed at https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
|
||||
package argon2
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
// The Argon2 version implemented by this package.
|
||||
const Version = 0x13
|
||||
|
||||
const (
|
||||
argon2d = iota
|
||||
argon2i
|
||||
argon2id
|
||||
)
|
||||
|
||||
// Key derives a key from the password, salt, and cost parameters using Argon2i
|
||||
// returning a byte slice of length keyLen that can be used as cryptographic key.
|
||||
// The CPU cost and parallism degree must be greater than zero.
|
||||
//
|
||||
// For example, you can get a derived key for e.g. AES-256 (which needs a 32-byte key) by doing:
|
||||
// `key := argon2.Key([]byte("some password"), salt, 4, 32*1024, 4, 32)`
|
||||
//
|
||||
// The recommended parameters for interactive logins as of 2017 are time=4, memory=32*1024.
|
||||
// The number of threads can be adjusted to the numbers of available CPUs.
|
||||
// The time parameter specifies the number of passes over the memory and the memory
|
||||
// parameter specifies the size of the memory in KiB. For example memory=32*1024 sets the
|
||||
// memory cost to ~32 MB.
|
||||
// The cost parameters should be increased as memory latency and CPU parallelism increases.
|
||||
// Remember to get a good random salt.
|
||||
func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
|
||||
return deriveKey(argon2i, password, salt, nil, nil, time, memory, threads, keyLen)
|
||||
}
|
||||
|
||||
func deriveKey(mode int, password, salt, secret, data []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
|
||||
if time < 1 {
|
||||
panic("argon2: number of rounds too small")
|
||||
}
|
||||
if threads < 1 {
|
||||
panic("argon2: parallelism degree too low")
|
||||
}
|
||||
h0 := initHash(password, salt, secret, data, time, memory, uint32(threads), keyLen, mode)
|
||||
|
||||
memory = memory / (syncPoints * uint32(threads)) * (syncPoints * uint32(threads))
|
||||
if memory < 2*syncPoints*uint32(threads) {
|
||||
memory = 2 * syncPoints * uint32(threads)
|
||||
}
|
||||
B := initBlocks(&h0, memory, uint32(threads))
|
||||
processBlocks(B, time, memory, uint32(threads), mode)
|
||||
return extractKey(B, memory, uint32(threads), keyLen)
|
||||
}
|
||||
|
||||
const (
|
||||
blockLength = 128
|
||||
syncPoints = 4
|
||||
)
|
||||
|
||||
type block [blockLength]uint64
|
||||
|
||||
func initHash(password, salt, key, data []byte, time, memory, threads, keyLen uint32, mode int) [blake2b.Size + 8]byte {
|
||||
var (
|
||||
h0 [blake2b.Size + 8]byte
|
||||
params [24]byte
|
||||
tmp [4]byte
|
||||
)
|
||||
|
||||
b2, _ := blake2b.New512(nil)
|
||||
binary.LittleEndian.PutUint32(params[0:4], threads)
|
||||
binary.LittleEndian.PutUint32(params[4:8], keyLen)
|
||||
binary.LittleEndian.PutUint32(params[8:12], memory)
|
||||
binary.LittleEndian.PutUint32(params[12:16], time)
|
||||
binary.LittleEndian.PutUint32(params[16:20], uint32(Version))
|
||||
binary.LittleEndian.PutUint32(params[20:24], uint32(mode))
|
||||
b2.Write(params[:])
|
||||
binary.LittleEndian.PutUint32(tmp[:], uint32(len(password)))
|
||||
b2.Write(tmp[:])
|
||||
b2.Write(password)
|
||||
binary.LittleEndian.PutUint32(tmp[:], uint32(len(salt)))
|
||||
b2.Write(tmp[:])
|
||||
b2.Write(salt)
|
||||
binary.LittleEndian.PutUint32(tmp[:], uint32(len(key)))
|
||||
b2.Write(tmp[:])
|
||||
b2.Write(key)
|
||||
binary.LittleEndian.PutUint32(tmp[:], uint32(len(data)))
|
||||
b2.Write(tmp[:])
|
||||
b2.Write(data)
|
||||
b2.Sum(h0[:0])
|
||||
return h0
|
||||
}
|
||||
|
||||
func initBlocks(h0 *[blake2b.Size + 8]byte, memory, threads uint32) []block {
|
||||
var block0 [1024]byte
|
||||
B := make([]block, memory)
|
||||
for lane := uint32(0); lane < threads; lane++ {
|
||||
j := lane * (memory / threads)
|
||||
binary.LittleEndian.PutUint32(h0[blake2b.Size+4:], lane)
|
||||
|
||||
binary.LittleEndian.PutUint32(h0[blake2b.Size:], 0)
|
||||
blake2bHash(block0[:], h0[:])
|
||||
for i := range B[j+0] {
|
||||
B[j+0][i] = binary.LittleEndian.Uint64(block0[i*8:])
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(h0[blake2b.Size:], 1)
|
||||
blake2bHash(block0[:], h0[:])
|
||||
for i := range B[j+1] {
|
||||
B[j+1][i] = binary.LittleEndian.Uint64(block0[i*8:])
|
||||
}
|
||||
}
|
||||
return B
|
||||
}
|
||||
|
||||
func processBlocks(B []block, time, memory, threads uint32, mode int) {
|
||||
lanes := memory / threads
|
||||
segments := lanes / syncPoints
|
||||
|
||||
processSegment := func(n, slice, lane uint32, wg *sync.WaitGroup) {
|
||||
var addresses, in, zero block
|
||||
if mode == argon2i || (mode == argon2id && n == 0 && slice < syncPoints/2) {
|
||||
in[0] = uint64(n)
|
||||
in[1] = uint64(lane)
|
||||
in[2] = uint64(slice)
|
||||
in[3] = uint64(memory)
|
||||
in[4] = uint64(time)
|
||||
in[5] = uint64(mode)
|
||||
}
|
||||
|
||||
index := uint32(0)
|
||||
if n == 0 && slice == 0 {
|
||||
index = 2 // we have already generated the first two blocks
|
||||
if mode == argon2i || mode == argon2id {
|
||||
in[6]++
|
||||
processBlock(&addresses, &in, &zero)
|
||||
processBlock(&addresses, &addresses, &zero)
|
||||
}
|
||||
}
|
||||
|
||||
offset := lane*lanes + slice*segments + index
|
||||
var random uint64
|
||||
for index < segments {
|
||||
prev := offset - 1
|
||||
if index == 0 && slice == 0 {
|
||||
prev += lanes // last block in lane
|
||||
}
|
||||
if mode == argon2i || (mode == argon2id && n == 0 && slice < syncPoints/2) {
|
||||
if index%blockLength == 0 {
|
||||
in[6]++
|
||||
processBlock(&addresses, &in, &zero)
|
||||
processBlock(&addresses, &addresses, &zero)
|
||||
}
|
||||
random = addresses[index%blockLength]
|
||||
} else {
|
||||
random = B[prev][0]
|
||||
}
|
||||
newOffset := indexAlpha(random, lanes, segments, threads, n, slice, lane, index)
|
||||
processBlockXOR(&B[offset], &B[prev], &B[newOffset])
|
||||
index, offset = index+1, offset+1
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
for n := uint32(0); n < time; n++ {
|
||||
for slice := uint32(0); slice < syncPoints; slice++ {
|
||||
var wg sync.WaitGroup
|
||||
for lane := uint32(0); lane < threads; lane++ {
|
||||
wg.Add(1)
|
||||
go processSegment(n, slice, lane, &wg)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func extractKey(B []block, memory, threads, keyLen uint32) []byte {
|
||||
lanes := memory / threads
|
||||
for lane := uint32(0); lane < threads-1; lane++ {
|
||||
for i, v := range B[(lane*lanes)+lanes-1] {
|
||||
B[memory-1][i] ^= v
|
||||
}
|
||||
}
|
||||
|
||||
var block [1024]byte
|
||||
for i, v := range B[memory-1] {
|
||||
binary.LittleEndian.PutUint64(block[i*8:], v)
|
||||
}
|
||||
key := make([]byte, keyLen)
|
||||
blake2bHash(key, block[:])
|
||||
return key
|
||||
}
|
||||
|
||||
func indexAlpha(rand uint64, lanes, segments, threads, n, slice, lane, index uint32) uint32 {
|
||||
refLane := uint32(rand>>32) % threads
|
||||
if n == 0 && slice == 0 {
|
||||
refLane = lane
|
||||
}
|
||||
m, s := 3*segments, ((slice+1)%syncPoints)*segments
|
||||
if lane == refLane {
|
||||
m += index
|
||||
}
|
||||
if n == 0 {
|
||||
m, s = slice*segments, 0
|
||||
if slice == 0 || lane == refLane {
|
||||
m += index
|
||||
}
|
||||
}
|
||||
if index == 0 || lane == refLane {
|
||||
m--
|
||||
}
|
||||
return phi(rand, uint64(m), uint64(s), refLane, lanes)
|
||||
}
|
||||
|
||||
func phi(rand, m, s uint64, lane, lanes uint32) uint32 {
|
||||
p := rand & 0xFFFFFFFF
|
||||
p = (p * p) >> 32
|
||||
p = (p * m) >> 32
|
||||
return lane*lanes + uint32((s+m-(p+1))%uint64(lanes))
|
||||
}
|
||||
233
vendor/golang.org/x/crypto/argon2/argon2_test.go
generated
vendored
Normal file
233
vendor/golang.org/x/crypto/argon2/argon2_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package argon2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
genKatPassword = []byte{
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
}
|
||||
genKatSalt = []byte{0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02}
|
||||
genKatSecret = []byte{0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03}
|
||||
genKatAAD = []byte{0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}
|
||||
)
|
||||
|
||||
func TestArgon2(t *testing.T) {
|
||||
defer func(sse4 bool) { useSSE4 = sse4 }(useSSE4)
|
||||
|
||||
if useSSE4 {
|
||||
t.Log("SSE4.1 version")
|
||||
testArgon2i(t)
|
||||
testArgon2d(t)
|
||||
testArgon2id(t)
|
||||
useSSE4 = false
|
||||
}
|
||||
t.Log("generic version")
|
||||
testArgon2i(t)
|
||||
testArgon2d(t)
|
||||
testArgon2id(t)
|
||||
}
|
||||
|
||||
func testArgon2d(t *testing.T) {
|
||||
want := []byte{
|
||||
0x51, 0x2b, 0x39, 0x1b, 0x6f, 0x11, 0x62, 0x97,
|
||||
0x53, 0x71, 0xd3, 0x09, 0x19, 0x73, 0x42, 0x94,
|
||||
0xf8, 0x68, 0xe3, 0xbe, 0x39, 0x84, 0xf3, 0xc1,
|
||||
0xa1, 0x3a, 0x4d, 0xb9, 0xfa, 0xbe, 0x4a, 0xcb,
|
||||
}
|
||||
hash := deriveKey(argon2d, genKatPassword, genKatSalt, genKatSecret, genKatAAD, 3, 32, 4, 32)
|
||||
if !bytes.Equal(hash, want) {
|
||||
t.Errorf("derived key does not match - got: %s , want: %s", hex.EncodeToString(hash), hex.EncodeToString(want))
|
||||
}
|
||||
}
|
||||
|
||||
func testArgon2i(t *testing.T) {
|
||||
want := []byte{
|
||||
0xc8, 0x14, 0xd9, 0xd1, 0xdc, 0x7f, 0x37, 0xaa,
|
||||
0x13, 0xf0, 0xd7, 0x7f, 0x24, 0x94, 0xbd, 0xa1,
|
||||
0xc8, 0xde, 0x6b, 0x01, 0x6d, 0xd3, 0x88, 0xd2,
|
||||
0x99, 0x52, 0xa4, 0xc4, 0x67, 0x2b, 0x6c, 0xe8,
|
||||
}
|
||||
hash := deriveKey(argon2i, genKatPassword, genKatSalt, genKatSecret, genKatAAD, 3, 32, 4, 32)
|
||||
if !bytes.Equal(hash, want) {
|
||||
t.Errorf("derived key does not match - got: %s , want: %s", hex.EncodeToString(hash), hex.EncodeToString(want))
|
||||
}
|
||||
}
|
||||
|
||||
func testArgon2id(t *testing.T) {
|
||||
want := []byte{
|
||||
0x0d, 0x64, 0x0d, 0xf5, 0x8d, 0x78, 0x76, 0x6c,
|
||||
0x08, 0xc0, 0x37, 0xa3, 0x4a, 0x8b, 0x53, 0xc9,
|
||||
0xd0, 0x1e, 0xf0, 0x45, 0x2d, 0x75, 0xb6, 0x5e,
|
||||
0xb5, 0x25, 0x20, 0xe9, 0x6b, 0x01, 0xe6, 0x59,
|
||||
}
|
||||
hash := deriveKey(argon2id, genKatPassword, genKatSalt, genKatSecret, genKatAAD, 3, 32, 4, 32)
|
||||
if !bytes.Equal(hash, want) {
|
||||
t.Errorf("derived key does not match - got: %s , want: %s", hex.EncodeToString(hash), hex.EncodeToString(want))
|
||||
}
|
||||
}
|
||||
|
||||
func TestVectors(t *testing.T) {
|
||||
password, salt := []byte("password"), []byte("somesalt")
|
||||
for i, v := range testVectors {
|
||||
want, err := hex.DecodeString(v.hash)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: failed to decode hash: %v", i, err)
|
||||
}
|
||||
hash := deriveKey(v.mode, password, salt, nil, nil, v.time, v.memory, v.threads, uint32(len(want)))
|
||||
if !bytes.Equal(hash, want) {
|
||||
t.Errorf("Test %d - got: %s want: %s", i, hex.EncodeToString(hash), hex.EncodeToString(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkArgon2(mode int, time, memory uint32, threads uint8, keyLen uint32, b *testing.B) {
|
||||
password := []byte("password")
|
||||
salt := []byte("choosing random salts is hard")
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
deriveKey(mode, password, salt, nil, nil, time, memory, threads, keyLen)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkArgon2i(b *testing.B) {
|
||||
b.Run(" Time: 3 Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2i, 3, 32*1024, 1, 32, b) })
|
||||
b.Run(" Time: 4 Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2i, 4, 32*1024, 1, 32, b) })
|
||||
b.Run(" Time: 5 Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2i, 5, 32*1024, 1, 32, b) })
|
||||
b.Run(" Time: 3 Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2i, 3, 64*1024, 4, 32, b) })
|
||||
b.Run(" Time: 4 Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2i, 4, 64*1024, 4, 32, b) })
|
||||
b.Run(" Time: 5 Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2i, 5, 64*1024, 4, 32, b) })
|
||||
}
|
||||
|
||||
func BenchmarkArgon2d(b *testing.B) {
|
||||
b.Run(" Time: 3, Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2d, 3, 32*1024, 1, 32, b) })
|
||||
b.Run(" Time: 4, Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2d, 4, 32*1024, 1, 32, b) })
|
||||
b.Run(" Time: 5, Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2d, 5, 32*1024, 1, 32, b) })
|
||||
b.Run(" Time: 3, Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2d, 3, 64*1024, 4, 32, b) })
|
||||
b.Run(" Time: 4, Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2d, 4, 64*1024, 4, 32, b) })
|
||||
b.Run(" Time: 5, Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2d, 5, 64*1024, 4, 32, b) })
|
||||
}
|
||||
|
||||
func BenchmarkArgon2id(b *testing.B) {
|
||||
b.Run(" Time: 3, Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2id, 3, 32*1024, 1, 32, b) })
|
||||
b.Run(" Time: 4, Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2id, 4, 32*1024, 1, 32, b) })
|
||||
b.Run(" Time: 5, Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2id, 5, 32*1024, 1, 32, b) })
|
||||
b.Run(" Time: 3, Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2id, 3, 64*1024, 4, 32, b) })
|
||||
b.Run(" Time: 4, Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2id, 4, 64*1024, 4, 32, b) })
|
||||
b.Run(" Time: 5, Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2id, 5, 64*1024, 4, 32, b) })
|
||||
}
|
||||
|
||||
// Generated with the CLI of https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
|
||||
var testVectors = []struct {
|
||||
mode int
|
||||
time, memory uint32
|
||||
threads uint8
|
||||
hash string
|
||||
}{
|
||||
{
|
||||
mode: argon2i, time: 1, memory: 64, threads: 1,
|
||||
hash: "b9c401d1844a67d50eae3967dc28870b22e508092e861a37",
|
||||
},
|
||||
{
|
||||
mode: argon2d, time: 1, memory: 64, threads: 1,
|
||||
hash: "8727405fd07c32c78d64f547f24150d3f2e703a89f981a19",
|
||||
},
|
||||
{
|
||||
mode: argon2id, time: 1, memory: 64, threads: 1,
|
||||
hash: "655ad15eac652dc59f7170a7332bf49b8469be1fdb9c28bb",
|
||||
},
|
||||
{
|
||||
mode: argon2i, time: 2, memory: 64, threads: 1,
|
||||
hash: "8cf3d8f76a6617afe35fac48eb0b7433a9a670ca4a07ed64",
|
||||
},
|
||||
{
|
||||
mode: argon2d, time: 2, memory: 64, threads: 1,
|
||||
hash: "3be9ec79a69b75d3752acb59a1fbb8b295a46529c48fbb75",
|
||||
},
|
||||
{
|
||||
mode: argon2id, time: 2, memory: 64, threads: 1,
|
||||
hash: "068d62b26455936aa6ebe60060b0a65870dbfa3ddf8d41f7",
|
||||
},
|
||||
{
|
||||
mode: argon2i, time: 2, memory: 64, threads: 2,
|
||||
hash: "2089f3e78a799720f80af806553128f29b132cafe40d059f",
|
||||
},
|
||||
{
|
||||
mode: argon2d, time: 2, memory: 64, threads: 2,
|
||||
hash: "68e2462c98b8bc6bb60ec68db418ae2c9ed24fc6748a40e9",
|
||||
},
|
||||
{
|
||||
mode: argon2id, time: 2, memory: 64, threads: 2,
|
||||
hash: "350ac37222f436ccb5c0972f1ebd3bf6b958bf2071841362",
|
||||
},
|
||||
{
|
||||
mode: argon2i, time: 3, memory: 256, threads: 2,
|
||||
hash: "f5bbf5d4c3836af13193053155b73ec7476a6a2eb93fd5e6",
|
||||
},
|
||||
{
|
||||
mode: argon2d, time: 3, memory: 256, threads: 2,
|
||||
hash: "f4f0669218eaf3641f39cc97efb915721102f4b128211ef2",
|
||||
},
|
||||
{
|
||||
mode: argon2id, time: 3, memory: 256, threads: 2,
|
||||
hash: "4668d30ac4187e6878eedeacf0fd83c5a0a30db2cc16ef0b",
|
||||
},
|
||||
{
|
||||
mode: argon2i, time: 4, memory: 4096, threads: 4,
|
||||
hash: "a11f7b7f3f93f02ad4bddb59ab62d121e278369288a0d0e7",
|
||||
},
|
||||
{
|
||||
mode: argon2d, time: 4, memory: 4096, threads: 4,
|
||||
hash: "935598181aa8dc2b720914aa6435ac8d3e3a4210c5b0fb2d",
|
||||
},
|
||||
{
|
||||
mode: argon2id, time: 4, memory: 4096, threads: 4,
|
||||
hash: "145db9733a9f4ee43edf33c509be96b934d505a4efb33c5a",
|
||||
},
|
||||
{
|
||||
mode: argon2i, time: 4, memory: 1024, threads: 8,
|
||||
hash: "0cdd3956aa35e6b475a7b0c63488822f774f15b43f6e6e17",
|
||||
},
|
||||
{
|
||||
mode: argon2d, time: 4, memory: 1024, threads: 8,
|
||||
hash: "83604fc2ad0589b9d055578f4d3cc55bc616df3578a896e9",
|
||||
},
|
||||
{
|
||||
mode: argon2id, time: 4, memory: 1024, threads: 8,
|
||||
hash: "8dafa8e004f8ea96bf7c0f93eecf67a6047476143d15577f",
|
||||
},
|
||||
{
|
||||
mode: argon2i, time: 2, memory: 64, threads: 3,
|
||||
hash: "5cab452fe6b8479c8661def8cd703b611a3905a6d5477fe6",
|
||||
},
|
||||
{
|
||||
mode: argon2d, time: 2, memory: 64, threads: 3,
|
||||
hash: "22474a423bda2ccd36ec9afd5119e5c8949798cadf659f51",
|
||||
},
|
||||
{
|
||||
mode: argon2id, time: 2, memory: 64, threads: 3,
|
||||
hash: "4a15b31aec7c2590b87d1f520be7d96f56658172deaa3079",
|
||||
},
|
||||
{
|
||||
mode: argon2i, time: 3, memory: 1024, threads: 6,
|
||||
hash: "d236b29c2b2a09babee842b0dec6aa1e83ccbdea8023dced",
|
||||
},
|
||||
{
|
||||
mode: argon2d, time: 3, memory: 1024, threads: 6,
|
||||
hash: "a3351b0319a53229152023d9206902f4ef59661cdca89481",
|
||||
},
|
||||
{
|
||||
mode: argon2id, time: 3, memory: 1024, threads: 6,
|
||||
hash: "1640b932f4b60e272f5d2207b9a9c626ffa1bd88d2349016",
|
||||
},
|
||||
}
|
||||
53
vendor/golang.org/x/crypto/argon2/blake2b.go
generated
vendored
Normal file
53
vendor/golang.org/x/crypto/argon2/blake2b.go
generated
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package argon2
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
// blake2bHash computes an arbitrary long hash value of in
|
||||
// and writes the hash to out.
|
||||
func blake2bHash(out []byte, in []byte) {
|
||||
var b2 hash.Hash
|
||||
if n := len(out); n < blake2b.Size {
|
||||
b2, _ = blake2b.New(n, nil)
|
||||
} else {
|
||||
b2, _ = blake2b.New512(nil)
|
||||
}
|
||||
|
||||
var buffer [blake2b.Size]byte
|
||||
binary.LittleEndian.PutUint32(buffer[:4], uint32(len(out)))
|
||||
b2.Write(buffer[:4])
|
||||
b2.Write(in)
|
||||
|
||||
if len(out) <= blake2b.Size {
|
||||
b2.Sum(out[:0])
|
||||
return
|
||||
}
|
||||
|
||||
outLen := len(out)
|
||||
b2.Sum(buffer[:0])
|
||||
b2.Reset()
|
||||
copy(out, buffer[:32])
|
||||
out = out[32:]
|
||||
for len(out) > blake2b.Size {
|
||||
b2.Write(buffer[:])
|
||||
b2.Sum(buffer[:0])
|
||||
copy(out, buffer[:32])
|
||||
out = out[32:]
|
||||
b2.Reset()
|
||||
}
|
||||
|
||||
if outLen%blake2b.Size > 0 { // outLen > 64
|
||||
r := ((outLen + 31) / 32) - 2 // ⌈τ /32⌉-2
|
||||
b2, _ = blake2b.New(outLen-32*r, nil)
|
||||
}
|
||||
b2.Write(buffer[:])
|
||||
b2.Sum(out[:0])
|
||||
}
|
||||
61
vendor/golang.org/x/crypto/argon2/blamka_amd64.go
generated
vendored
Normal file
61
vendor/golang.org/x/crypto/argon2/blamka_amd64.go
generated
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
package argon2
|
||||
|
||||
func init() {
|
||||
useSSE4 = supportsSSE4()
|
||||
}
|
||||
|
||||
//go:noescape
|
||||
func supportsSSE4() bool
|
||||
|
||||
//go:noescape
|
||||
func mixBlocksSSE2(out, a, b, c *block)
|
||||
|
||||
//go:noescape
|
||||
func xorBlocksSSE2(out, a, b, c *block)
|
||||
|
||||
//go:noescape
|
||||
func blamkaSSE4(b *block)
|
||||
|
||||
func processBlockSSE(out, in1, in2 *block, xor bool) {
|
||||
var t block
|
||||
mixBlocksSSE2(&t, in1, in2, &t)
|
||||
if useSSE4 {
|
||||
blamkaSSE4(&t)
|
||||
} else {
|
||||
for i := 0; i < blockLength; i += 16 {
|
||||
blamkaGeneric(
|
||||
&t[i+0], &t[i+1], &t[i+2], &t[i+3],
|
||||
&t[i+4], &t[i+5], &t[i+6], &t[i+7],
|
||||
&t[i+8], &t[i+9], &t[i+10], &t[i+11],
|
||||
&t[i+12], &t[i+13], &t[i+14], &t[i+15],
|
||||
)
|
||||
}
|
||||
for i := 0; i < blockLength/8; i += 2 {
|
||||
blamkaGeneric(
|
||||
&t[i], &t[i+1], &t[16+i], &t[16+i+1],
|
||||
&t[32+i], &t[32+i+1], &t[48+i], &t[48+i+1],
|
||||
&t[64+i], &t[64+i+1], &t[80+i], &t[80+i+1],
|
||||
&t[96+i], &t[96+i+1], &t[112+i], &t[112+i+1],
|
||||
)
|
||||
}
|
||||
}
|
||||
if xor {
|
||||
xorBlocksSSE2(out, in1, in2, &t)
|
||||
} else {
|
||||
mixBlocksSSE2(out, in1, in2, &t)
|
||||
}
|
||||
}
|
||||
|
||||
func processBlock(out, in1, in2 *block) {
|
||||
processBlockSSE(out, in1, in2, false)
|
||||
}
|
||||
|
||||
func processBlockXOR(out, in1, in2 *block) {
|
||||
processBlockSSE(out, in1, in2, true)
|
||||
}
|
||||
252
vendor/golang.org/x/crypto/argon2/blamka_amd64.s
generated
vendored
Normal file
252
vendor/golang.org/x/crypto/argon2/blamka_amd64.s
generated
vendored
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
DATA ·c40<>+0x00(SB)/8, $0x0201000706050403
|
||||
DATA ·c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b
|
||||
GLOBL ·c40<>(SB), (NOPTR+RODATA), $16
|
||||
|
||||
DATA ·c48<>+0x00(SB)/8, $0x0100070605040302
|
||||
DATA ·c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a
|
||||
GLOBL ·c48<>(SB), (NOPTR+RODATA), $16
|
||||
|
||||
#define SHUFFLE(v2, v3, v4, v5, v6, v7, t1, t2) \
|
||||
MOVO v4, t1; \
|
||||
MOVO v5, v4; \
|
||||
MOVO t1, v5; \
|
||||
MOVO v6, t1; \
|
||||
PUNPCKLQDQ v6, t2; \
|
||||
PUNPCKHQDQ v7, v6; \
|
||||
PUNPCKHQDQ t2, v6; \
|
||||
PUNPCKLQDQ v7, t2; \
|
||||
MOVO t1, v7; \
|
||||
MOVO v2, t1; \
|
||||
PUNPCKHQDQ t2, v7; \
|
||||
PUNPCKLQDQ v3, t2; \
|
||||
PUNPCKHQDQ t2, v2; \
|
||||
PUNPCKLQDQ t1, t2; \
|
||||
PUNPCKHQDQ t2, v3
|
||||
|
||||
#define SHUFFLE_INV(v2, v3, v4, v5, v6, v7, t1, t2) \
|
||||
MOVO v4, t1; \
|
||||
MOVO v5, v4; \
|
||||
MOVO t1, v5; \
|
||||
MOVO v2, t1; \
|
||||
PUNPCKLQDQ v2, t2; \
|
||||
PUNPCKHQDQ v3, v2; \
|
||||
PUNPCKHQDQ t2, v2; \
|
||||
PUNPCKLQDQ v3, t2; \
|
||||
MOVO t1, v3; \
|
||||
MOVO v6, t1; \
|
||||
PUNPCKHQDQ t2, v3; \
|
||||
PUNPCKLQDQ v7, t2; \
|
||||
PUNPCKHQDQ t2, v6; \
|
||||
PUNPCKLQDQ t1, t2; \
|
||||
PUNPCKHQDQ t2, v7
|
||||
|
||||
#define HALF_ROUND(v0, v1, v2, v3, v4, v5, v6, v7, t0, c40, c48) \
|
||||
MOVO v0, t0; \
|
||||
PMULULQ v2, t0; \
|
||||
PADDQ v2, v0; \
|
||||
PADDQ t0, v0; \
|
||||
PADDQ t0, v0; \
|
||||
PXOR v0, v6; \
|
||||
PSHUFD $0xB1, v6, v6; \
|
||||
MOVO v4, t0; \
|
||||
PMULULQ v6, t0; \
|
||||
PADDQ v6, v4; \
|
||||
PADDQ t0, v4; \
|
||||
PADDQ t0, v4; \
|
||||
PXOR v4, v2; \
|
||||
PSHUFB c40, v2; \
|
||||
MOVO v0, t0; \
|
||||
PMULULQ v2, t0; \
|
||||
PADDQ v2, v0; \
|
||||
PADDQ t0, v0; \
|
||||
PADDQ t0, v0; \
|
||||
PXOR v0, v6; \
|
||||
PSHUFB c48, v6; \
|
||||
MOVO v4, t0; \
|
||||
PMULULQ v6, t0; \
|
||||
PADDQ v6, v4; \
|
||||
PADDQ t0, v4; \
|
||||
PADDQ t0, v4; \
|
||||
PXOR v4, v2; \
|
||||
MOVO v2, t0; \
|
||||
PADDQ v2, t0; \
|
||||
PSRLQ $63, v2; \
|
||||
PXOR t0, v2; \
|
||||
MOVO v1, t0; \
|
||||
PMULULQ v3, t0; \
|
||||
PADDQ v3, v1; \
|
||||
PADDQ t0, v1; \
|
||||
PADDQ t0, v1; \
|
||||
PXOR v1, v7; \
|
||||
PSHUFD $0xB1, v7, v7; \
|
||||
MOVO v5, t0; \
|
||||
PMULULQ v7, t0; \
|
||||
PADDQ v7, v5; \
|
||||
PADDQ t0, v5; \
|
||||
PADDQ t0, v5; \
|
||||
PXOR v5, v3; \
|
||||
PSHUFB c40, v3; \
|
||||
MOVO v1, t0; \
|
||||
PMULULQ v3, t0; \
|
||||
PADDQ v3, v1; \
|
||||
PADDQ t0, v1; \
|
||||
PADDQ t0, v1; \
|
||||
PXOR v1, v7; \
|
||||
PSHUFB c48, v7; \
|
||||
MOVO v5, t0; \
|
||||
PMULULQ v7, t0; \
|
||||
PADDQ v7, v5; \
|
||||
PADDQ t0, v5; \
|
||||
PADDQ t0, v5; \
|
||||
PXOR v5, v3; \
|
||||
MOVO v3, t0; \
|
||||
PADDQ v3, t0; \
|
||||
PSRLQ $63, v3; \
|
||||
PXOR t0, v3
|
||||
|
||||
#define LOAD_MSG_0(block, off) \
|
||||
MOVOU 8*(off+0)(block), X0; \
|
||||
MOVOU 8*(off+2)(block), X1; \
|
||||
MOVOU 8*(off+4)(block), X2; \
|
||||
MOVOU 8*(off+6)(block), X3; \
|
||||
MOVOU 8*(off+8)(block), X4; \
|
||||
MOVOU 8*(off+10)(block), X5; \
|
||||
MOVOU 8*(off+12)(block), X6; \
|
||||
MOVOU 8*(off+14)(block), X7
|
||||
|
||||
#define STORE_MSG_0(block, off) \
|
||||
MOVOU X0, 8*(off+0)(block); \
|
||||
MOVOU X1, 8*(off+2)(block); \
|
||||
MOVOU X2, 8*(off+4)(block); \
|
||||
MOVOU X3, 8*(off+6)(block); \
|
||||
MOVOU X4, 8*(off+8)(block); \
|
||||
MOVOU X5, 8*(off+10)(block); \
|
||||
MOVOU X6, 8*(off+12)(block); \
|
||||
MOVOU X7, 8*(off+14)(block)
|
||||
|
||||
#define LOAD_MSG_1(block, off) \
|
||||
MOVOU 8*off+0*8(block), X0; \
|
||||
MOVOU 8*off+16*8(block), X1; \
|
||||
MOVOU 8*off+32*8(block), X2; \
|
||||
MOVOU 8*off+48*8(block), X3; \
|
||||
MOVOU 8*off+64*8(block), X4; \
|
||||
MOVOU 8*off+80*8(block), X5; \
|
||||
MOVOU 8*off+96*8(block), X6; \
|
||||
MOVOU 8*off+112*8(block), X7
|
||||
|
||||
#define STORE_MSG_1(block, off) \
|
||||
MOVOU X0, 8*off+0*8(block); \
|
||||
MOVOU X1, 8*off+16*8(block); \
|
||||
MOVOU X2, 8*off+32*8(block); \
|
||||
MOVOU X3, 8*off+48*8(block); \
|
||||
MOVOU X4, 8*off+64*8(block); \
|
||||
MOVOU X5, 8*off+80*8(block); \
|
||||
MOVOU X6, 8*off+96*8(block); \
|
||||
MOVOU X7, 8*off+112*8(block)
|
||||
|
||||
#define BLAMKA_ROUND_0(block, off, t0, t1, c40, c48) \
|
||||
LOAD_MSG_0(block, off); \
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, t0, t1); \
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, t0, t1); \
|
||||
STORE_MSG_0(block, off)
|
||||
|
||||
#define BLAMKA_ROUND_1(block, off, t0, t1, c40, c48) \
|
||||
LOAD_MSG_1(block, off); \
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, t0, t1); \
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, t0, t1); \
|
||||
STORE_MSG_1(block, off)
|
||||
|
||||
// func blamkaSSE4(b *block)
|
||||
TEXT ·blamkaSSE4(SB), 4, $0-8
|
||||
MOVQ b+0(FP), AX
|
||||
|
||||
MOVOU ·c40<>(SB), X10
|
||||
MOVOU ·c48<>(SB), X11
|
||||
|
||||
BLAMKA_ROUND_0(AX, 0, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 16, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 32, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 48, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 64, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 80, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 96, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_0(AX, 112, X8, X9, X10, X11)
|
||||
|
||||
BLAMKA_ROUND_1(AX, 0, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 2, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 4, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 6, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 8, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 10, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 12, X8, X9, X10, X11)
|
||||
BLAMKA_ROUND_1(AX, 14, X8, X9, X10, X11)
|
||||
RET
|
||||
|
||||
// func mixBlocksSSE2(out, a, b, c *block)
|
||||
TEXT ·mixBlocksSSE2(SB), 4, $0-32
|
||||
MOVQ out+0(FP), DX
|
||||
MOVQ a+8(FP), AX
|
||||
MOVQ b+16(FP), BX
|
||||
MOVQ a+24(FP), CX
|
||||
MOVQ $128, BP
|
||||
|
||||
loop:
|
||||
MOVOU 0(AX), X0
|
||||
MOVOU 0(BX), X1
|
||||
MOVOU 0(CX), X2
|
||||
PXOR X1, X0
|
||||
PXOR X2, X0
|
||||
MOVOU X0, 0(DX)
|
||||
ADDQ $16, AX
|
||||
ADDQ $16, BX
|
||||
ADDQ $16, CX
|
||||
ADDQ $16, DX
|
||||
SUBQ $2, BP
|
||||
JA loop
|
||||
RET
|
||||
|
||||
// func xorBlocksSSE2(out, a, b, c *block)
|
||||
TEXT ·xorBlocksSSE2(SB), 4, $0-32
|
||||
MOVQ out+0(FP), DX
|
||||
MOVQ a+8(FP), AX
|
||||
MOVQ b+16(FP), BX
|
||||
MOVQ a+24(FP), CX
|
||||
MOVQ $128, BP
|
||||
|
||||
loop:
|
||||
MOVOU 0(AX), X0
|
||||
MOVOU 0(BX), X1
|
||||
MOVOU 0(CX), X2
|
||||
MOVOU 0(DX), X3
|
||||
PXOR X1, X0
|
||||
PXOR X2, X0
|
||||
PXOR X3, X0
|
||||
MOVOU X0, 0(DX)
|
||||
ADDQ $16, AX
|
||||
ADDQ $16, BX
|
||||
ADDQ $16, CX
|
||||
ADDQ $16, DX
|
||||
SUBQ $2, BP
|
||||
JA loop
|
||||
RET
|
||||
|
||||
// func supportsSSE4() bool
|
||||
TEXT ·supportsSSE4(SB), 4, $0-1
|
||||
MOVL $1, AX
|
||||
CPUID
|
||||
SHRL $19, CX // Bit 19 indicates SSE4 support
|
||||
ANDL $1, CX // CX != 0 if support SSE4
|
||||
MOVB CX, ret+0(FP)
|
||||
RET
|
||||
163
vendor/golang.org/x/crypto/argon2/blamka_generic.go
generated
vendored
Normal file
163
vendor/golang.org/x/crypto/argon2/blamka_generic.go
generated
vendored
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package argon2
|
||||
|
||||
var useSSE4 bool
|
||||
|
||||
func processBlockGeneric(out, in1, in2 *block, xor bool) {
|
||||
var t block
|
||||
for i := range t {
|
||||
t[i] = in1[i] ^ in2[i]
|
||||
}
|
||||
for i := 0; i < blockLength; i += 16 {
|
||||
blamkaGeneric(
|
||||
&t[i+0], &t[i+1], &t[i+2], &t[i+3],
|
||||
&t[i+4], &t[i+5], &t[i+6], &t[i+7],
|
||||
&t[i+8], &t[i+9], &t[i+10], &t[i+11],
|
||||
&t[i+12], &t[i+13], &t[i+14], &t[i+15],
|
||||
)
|
||||
}
|
||||
for i := 0; i < blockLength/8; i += 2 {
|
||||
blamkaGeneric(
|
||||
&t[i], &t[i+1], &t[16+i], &t[16+i+1],
|
||||
&t[32+i], &t[32+i+1], &t[48+i], &t[48+i+1],
|
||||
&t[64+i], &t[64+i+1], &t[80+i], &t[80+i+1],
|
||||
&t[96+i], &t[96+i+1], &t[112+i], &t[112+i+1],
|
||||
)
|
||||
}
|
||||
if xor {
|
||||
for i := range t {
|
||||
out[i] ^= in1[i] ^ in2[i] ^ t[i]
|
||||
}
|
||||
} else {
|
||||
for i := range t {
|
||||
out[i] = in1[i] ^ in2[i] ^ t[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func blamkaGeneric(t00, t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, t13, t14, t15 *uint64) {
|
||||
v00, v01, v02, v03 := *t00, *t01, *t02, *t03
|
||||
v04, v05, v06, v07 := *t04, *t05, *t06, *t07
|
||||
v08, v09, v10, v11 := *t08, *t09, *t10, *t11
|
||||
v12, v13, v14, v15 := *t12, *t13, *t14, *t15
|
||||
|
||||
v00 += v04 + 2*uint64(uint32(v00))*uint64(uint32(v04))
|
||||
v12 ^= v00
|
||||
v12 = v12>>32 | v12<<32
|
||||
v08 += v12 + 2*uint64(uint32(v08))*uint64(uint32(v12))
|
||||
v04 ^= v08
|
||||
v04 = v04>>24 | v04<<40
|
||||
|
||||
v00 += v04 + 2*uint64(uint32(v00))*uint64(uint32(v04))
|
||||
v12 ^= v00
|
||||
v12 = v12>>16 | v12<<48
|
||||
v08 += v12 + 2*uint64(uint32(v08))*uint64(uint32(v12))
|
||||
v04 ^= v08
|
||||
v04 = v04>>63 | v04<<1
|
||||
|
||||
v01 += v05 + 2*uint64(uint32(v01))*uint64(uint32(v05))
|
||||
v13 ^= v01
|
||||
v13 = v13>>32 | v13<<32
|
||||
v09 += v13 + 2*uint64(uint32(v09))*uint64(uint32(v13))
|
||||
v05 ^= v09
|
||||
v05 = v05>>24 | v05<<40
|
||||
|
||||
v01 += v05 + 2*uint64(uint32(v01))*uint64(uint32(v05))
|
||||
v13 ^= v01
|
||||
v13 = v13>>16 | v13<<48
|
||||
v09 += v13 + 2*uint64(uint32(v09))*uint64(uint32(v13))
|
||||
v05 ^= v09
|
||||
v05 = v05>>63 | v05<<1
|
||||
|
||||
v02 += v06 + 2*uint64(uint32(v02))*uint64(uint32(v06))
|
||||
v14 ^= v02
|
||||
v14 = v14>>32 | v14<<32
|
||||
v10 += v14 + 2*uint64(uint32(v10))*uint64(uint32(v14))
|
||||
v06 ^= v10
|
||||
v06 = v06>>24 | v06<<40
|
||||
|
||||
v02 += v06 + 2*uint64(uint32(v02))*uint64(uint32(v06))
|
||||
v14 ^= v02
|
||||
v14 = v14>>16 | v14<<48
|
||||
v10 += v14 + 2*uint64(uint32(v10))*uint64(uint32(v14))
|
||||
v06 ^= v10
|
||||
v06 = v06>>63 | v06<<1
|
||||
|
||||
v03 += v07 + 2*uint64(uint32(v03))*uint64(uint32(v07))
|
||||
v15 ^= v03
|
||||
v15 = v15>>32 | v15<<32
|
||||
v11 += v15 + 2*uint64(uint32(v11))*uint64(uint32(v15))
|
||||
v07 ^= v11
|
||||
v07 = v07>>24 | v07<<40
|
||||
|
||||
v03 += v07 + 2*uint64(uint32(v03))*uint64(uint32(v07))
|
||||
v15 ^= v03
|
||||
v15 = v15>>16 | v15<<48
|
||||
v11 += v15 + 2*uint64(uint32(v11))*uint64(uint32(v15))
|
||||
v07 ^= v11
|
||||
v07 = v07>>63 | v07<<1
|
||||
|
||||
v00 += v05 + 2*uint64(uint32(v00))*uint64(uint32(v05))
|
||||
v15 ^= v00
|
||||
v15 = v15>>32 | v15<<32
|
||||
v10 += v15 + 2*uint64(uint32(v10))*uint64(uint32(v15))
|
||||
v05 ^= v10
|
||||
v05 = v05>>24 | v05<<40
|
||||
|
||||
v00 += v05 + 2*uint64(uint32(v00))*uint64(uint32(v05))
|
||||
v15 ^= v00
|
||||
v15 = v15>>16 | v15<<48
|
||||
v10 += v15 + 2*uint64(uint32(v10))*uint64(uint32(v15))
|
||||
v05 ^= v10
|
||||
v05 = v05>>63 | v05<<1
|
||||
|
||||
v01 += v06 + 2*uint64(uint32(v01))*uint64(uint32(v06))
|
||||
v12 ^= v01
|
||||
v12 = v12>>32 | v12<<32
|
||||
v11 += v12 + 2*uint64(uint32(v11))*uint64(uint32(v12))
|
||||
v06 ^= v11
|
||||
v06 = v06>>24 | v06<<40
|
||||
|
||||
v01 += v06 + 2*uint64(uint32(v01))*uint64(uint32(v06))
|
||||
v12 ^= v01
|
||||
v12 = v12>>16 | v12<<48
|
||||
v11 += v12 + 2*uint64(uint32(v11))*uint64(uint32(v12))
|
||||
v06 ^= v11
|
||||
v06 = v06>>63 | v06<<1
|
||||
|
||||
v02 += v07 + 2*uint64(uint32(v02))*uint64(uint32(v07))
|
||||
v13 ^= v02
|
||||
v13 = v13>>32 | v13<<32
|
||||
v08 += v13 + 2*uint64(uint32(v08))*uint64(uint32(v13))
|
||||
v07 ^= v08
|
||||
v07 = v07>>24 | v07<<40
|
||||
|
||||
v02 += v07 + 2*uint64(uint32(v02))*uint64(uint32(v07))
|
||||
v13 ^= v02
|
||||
v13 = v13>>16 | v13<<48
|
||||
v08 += v13 + 2*uint64(uint32(v08))*uint64(uint32(v13))
|
||||
v07 ^= v08
|
||||
v07 = v07>>63 | v07<<1
|
||||
|
||||
v03 += v04 + 2*uint64(uint32(v03))*uint64(uint32(v04))
|
||||
v14 ^= v03
|
||||
v14 = v14>>32 | v14<<32
|
||||
v09 += v14 + 2*uint64(uint32(v09))*uint64(uint32(v14))
|
||||
v04 ^= v09
|
||||
v04 = v04>>24 | v04<<40
|
||||
|
||||
v03 += v04 + 2*uint64(uint32(v03))*uint64(uint32(v04))
|
||||
v14 ^= v03
|
||||
v14 = v14>>16 | v14<<48
|
||||
v09 += v14 + 2*uint64(uint32(v09))*uint64(uint32(v14))
|
||||
v04 ^= v09
|
||||
v04 = v04>>63 | v04<<1
|
||||
|
||||
*t00, *t01, *t02, *t03 = v00, v01, v02, v03
|
||||
*t04, *t05, *t06, *t07 = v04, v05, v06, v07
|
||||
*t08, *t09, *t10, *t11 = v08, v09, v10, v11
|
||||
*t12, *t13, *t14, *t15 = v12, v13, v14, v15
|
||||
}
|
||||
15
vendor/golang.org/x/crypto/argon2/blamka_ref.go
generated
vendored
Normal file
15
vendor/golang.org/x/crypto/argon2/blamka_ref.go
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !amd64 appengine gccgo
|
||||
|
||||
package argon2
|
||||
|
||||
func processBlock(out, in1, in2 *block) {
|
||||
processBlockGeneric(out, in1, in2, false)
|
||||
}
|
||||
|
||||
func processBlockXOR(out, in1, in2 *block) {
|
||||
processBlockGeneric(out, in1, in2, true)
|
||||
}
|
||||
4
vendor/golang.org/x/crypto/bcrypt/bcrypt.go
generated
vendored
4
vendor/golang.org/x/crypto/bcrypt/bcrypt.go
generated
vendored
|
|
@ -241,11 +241,11 @@ func (p *hashed) Hash() []byte {
|
|||
n = 3
|
||||
}
|
||||
arr[n] = '$'
|
||||
n += 1
|
||||
n++
|
||||
copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost)))
|
||||
n += 2
|
||||
arr[n] = '$'
|
||||
n += 1
|
||||
n++
|
||||
copy(arr[n:], p.salt)
|
||||
n += encodedSaltSize
|
||||
copy(arr[n:], p.hash)
|
||||
|
|
|
|||
47
vendor/golang.org/x/crypto/blake2b/blake2b.go
generated
vendored
47
vendor/golang.org/x/crypto/blake2b/blake2b.go
generated
vendored
|
|
@ -2,9 +2,19 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package blake2b implements the BLAKE2b hash algorithm as
|
||||
// defined in RFC 7693.
|
||||
package blake2b // import "golang.org/x/crypto/blake2b"
|
||||
// Package blake2b implements the BLAKE2b hash algorithm defined by RFC 7693
|
||||
// and the extendable output function (XOF) BLAKE2Xb.
|
||||
//
|
||||
// For a detailed specification of BLAKE2b see https://blake2.net/blake2.pdf
|
||||
// and for BLAKE2Xb see https://blake2.net/blake2x.pdf
|
||||
//
|
||||
// If you aren't sure which function you need, use BLAKE2b (Sum512 or New512).
|
||||
// If you need a secret-key MAC (message authentication code), use the New512
|
||||
// function with a non-nil key.
|
||||
//
|
||||
// BLAKE2X is a construction to compute hash values larger than 64 bytes. It
|
||||
// can produce hash values between 0 and 4 GiB.
|
||||
package blake2b
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
|
@ -29,7 +39,10 @@ var (
|
|||
useSSE4 bool
|
||||
)
|
||||
|
||||
var errKeySize = errors.New("blake2b: invalid key size")
|
||||
var (
|
||||
errKeySize = errors.New("blake2b: invalid key size")
|
||||
errHashSize = errors.New("blake2b: invalid hash size")
|
||||
)
|
||||
|
||||
var iv = [8]uint64{
|
||||
0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
|
||||
|
|
@ -73,7 +86,18 @@ func New384(key []byte) (hash.Hash, error) { return newDigest(Size384, key) }
|
|||
// key turns the hash into a MAC. The key must between zero and 64 bytes long.
|
||||
func New256(key []byte) (hash.Hash, error) { return newDigest(Size256, key) }
|
||||
|
||||
// New returns a new hash.Hash computing the BLAKE2b checksum with a custom length.
|
||||
// A non-nil key turns the hash into a MAC. The key must between zero and 64 bytes long.
|
||||
// The hash size can be a value between 1 and 64 but it is highly recommended to use
|
||||
// values equal or greater than:
|
||||
// - 32 if BLAKE2b is used as a hash function (The key is zero bytes long).
|
||||
// - 16 if BLAKE2b is used as a MAC function (The key is at least 16 bytes long).
|
||||
func New(size int, key []byte) (hash.Hash, error) { return newDigest(size, key) }
|
||||
|
||||
func newDigest(hashSize int, key []byte) (*digest, error) {
|
||||
if hashSize < 1 || hashSize > Size {
|
||||
return nil, errHashSize
|
||||
}
|
||||
if len(key) > Size {
|
||||
return nil, errKeySize
|
||||
}
|
||||
|
|
@ -171,7 +195,13 @@ func (d *digest) Write(p []byte) (n int, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (d *digest) Sum(b []byte) []byte {
|
||||
func (d *digest) Sum(sum []byte) []byte {
|
||||
var hash [Size]byte
|
||||
d.finalize(&hash)
|
||||
return append(sum, hash[:d.size]...)
|
||||
}
|
||||
|
||||
func (d *digest) finalize(hash *[Size]byte) {
|
||||
var block [BlockSize]byte
|
||||
copy(block[:], d.block[:d.offset])
|
||||
remaining := uint64(BlockSize - d.offset)
|
||||
|
|
@ -185,10 +215,7 @@ func (d *digest) Sum(b []byte) []byte {
|
|||
h := d.h
|
||||
hashBlocks(&h, &c, 0xFFFFFFFFFFFFFFFF, block[:])
|
||||
|
||||
var sum [Size]byte
|
||||
for i, v := range h[:(d.size+7)/8] {
|
||||
binary.LittleEndian.PutUint64(sum[8*i:], v)
|
||||
for i, v := range h {
|
||||
binary.LittleEndian.PutUint64(hash[8*i:], v)
|
||||
}
|
||||
|
||||
return append(b, sum[:d.size]...)
|
||||
}
|
||||
|
|
|
|||
350
vendor/golang.org/x/crypto/blake2b/blake2b_test.go
generated
vendored
350
vendor/golang.org/x/crypto/blake2b/blake2b_test.go
generated
vendored
|
|
@ -9,6 +9,7 @@ import (
|
|||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
@ -44,6 +45,30 @@ func TestHashes(t *testing.T) {
|
|||
testHashes(t)
|
||||
}
|
||||
|
||||
func TestHashes2X(t *testing.T) {
|
||||
defer func(sse4, avx, avx2 bool) {
|
||||
useSSE4, useAVX, useAVX2 = sse4, avx, avx2
|
||||
}(useSSE4, useAVX, useAVX2)
|
||||
|
||||
if useAVX2 {
|
||||
t.Log("AVX2 version")
|
||||
testHashes2X(t)
|
||||
useAVX2 = false
|
||||
}
|
||||
if useAVX {
|
||||
t.Log("AVX version")
|
||||
testHashes2X(t)
|
||||
useAVX = false
|
||||
}
|
||||
if useSSE4 {
|
||||
t.Log("SSE4 version")
|
||||
testHashes2X(t)
|
||||
useSSE4 = false
|
||||
}
|
||||
t.Log("generic version")
|
||||
testHashes2X(t)
|
||||
}
|
||||
|
||||
func testHashes(t *testing.T) {
|
||||
key, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f")
|
||||
|
||||
|
|
@ -77,6 +102,72 @@ func testHashes(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func testHashes2X(t *testing.T) {
|
||||
key, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f")
|
||||
|
||||
input := make([]byte, 256)
|
||||
for i := range input {
|
||||
input[i] = byte(i)
|
||||
}
|
||||
|
||||
for i, expectedHex := range hashes2X {
|
||||
length := uint32(len(expectedHex) / 2)
|
||||
sum := make([]byte, int(length))
|
||||
|
||||
h, err := NewXOF(length, key)
|
||||
if err != nil {
|
||||
t.Fatalf("#%d: error from NewXOF: %v", i, err)
|
||||
}
|
||||
|
||||
if _, err := h.Write(input); err != nil {
|
||||
t.Fatalf("#%d (single write): error from Write: %v", i, err)
|
||||
}
|
||||
if _, err := h.Read(sum); err != nil {
|
||||
t.Fatalf("#%d (single write): error from Read: %v", i, err)
|
||||
}
|
||||
if n, err := h.Read(sum); n != 0 || err != io.EOF {
|
||||
t.Fatalf("#%d (single write): Read did not return (0, io.EOF) after exhaustion, got (%v, %v)", i, n, err)
|
||||
}
|
||||
if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex {
|
||||
t.Fatalf("#%d (single write): got %s, wanted %s", i, gotHex, expectedHex)
|
||||
}
|
||||
|
||||
h.Reset()
|
||||
for j := 0; j < len(input); j++ {
|
||||
h.Write(input[j : j+1])
|
||||
}
|
||||
for j := 0; j < len(sum); j++ {
|
||||
h = h.Clone()
|
||||
if _, err := h.Read(sum[j : j+1]); err != nil {
|
||||
t.Fatalf("#%d (byte-by-byte) - Read %d: error from Read: %v", i, j, err)
|
||||
}
|
||||
}
|
||||
if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex {
|
||||
t.Fatalf("#%d (byte-by-byte): got %s, wanted %s", i, gotHex, expectedHex)
|
||||
}
|
||||
}
|
||||
|
||||
h, err := NewXOF(OutputLengthUnknown, key)
|
||||
if err != nil {
|
||||
t.Fatalf("#unknown length: error from NewXOF: %v", err)
|
||||
}
|
||||
if _, err := h.Write(input); err != nil {
|
||||
t.Fatalf("#unknown length: error from Write: %v", err)
|
||||
}
|
||||
|
||||
var result [64]byte
|
||||
if n, err := h.Read(result[:]); err != nil {
|
||||
t.Fatalf("#unknown length: error from Read: %v", err)
|
||||
} else if n != len(result) {
|
||||
t.Fatalf("#unknown length: Read returned %d bytes, want %d", n, len(result))
|
||||
}
|
||||
|
||||
const expected = "3dbba8516da76bf7330055c66ea36cf1005e92714262b24d9710f51d9e126406e1bcd6497059f9331f1091c3634b695428d475ed432f987040575520a1c29f5e"
|
||||
if fmt.Sprintf("%x", result) != expected {
|
||||
t.Fatalf("#unknown length: bad result %x, wanted %s", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func generateSequence(out []byte, seed uint32) {
|
||||
a := 0xDEAD4BAD * seed // prime
|
||||
b := uint32(1)
|
||||
|
|
@ -446,3 +537,262 @@ var hashes = []string{
|
|||
"d444bfa2362a96df213d070e33fa841f51334e4e76866b8139e8af3bb3398be2dfaddcbc56b9146de9f68118dc5829e74b0c28d7711907b121f9161cb92b69a9",
|
||||
"142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461",
|
||||
}
|
||||
|
||||
var hashes2X = []string{
|
||||
"64",
|
||||
"f457",
|
||||
"e8c045",
|
||||
"a74c6d0d",
|
||||
"eb02ae482a",
|
||||
"be65b981275e",
|
||||
"8540ccd083a455",
|
||||
"074a02fa58d7c7c0",
|
||||
"da6da05e10db3022b6",
|
||||
"542a5aae2f28f2c3b68c",
|
||||
"ca3af2afc4afe891da78b1",
|
||||
"e0f66b8dcebf4edc85f12c85",
|
||||
"744224d383733b3fa2c53bfcf5",
|
||||
"b09b653e85b72ef5cdf8fcfa95f3",
|
||||
"dd51877f31f1cf7b9f68bbb09064a3",
|
||||
"f5ebf68e7ebed6ad445ffc0c47e82650",
|
||||
"ebdcfe03bcb7e21a9091202c5938c0a1bb",
|
||||
"860fa5a72ff92efafc48a89df1632a4e2809",
|
||||
"0d6d49daa26ae2818041108df3ce0a4db48c8d",
|
||||
"e5d7e1bc5715f5ae991e4043e39533af5d53e47f",
|
||||
"5232028a43b9d4dfa7f37439b49495926481ab8a29",
|
||||
"c118803c922f9ae2397fb676a2ab7603dd9c29c21fe4",
|
||||
"2af924f48b9bd7076bfd68794bba6402e2a7ae048de3ea",
|
||||
"61255ac38231087c79ea1a0fa14538c26be1c851b6f318c0",
|
||||
"f9712b8e42f0532162822f142cb946c40369f2f0e77b6b186e",
|
||||
"76da0b89558df66f9b1e66a61d1e795b178ce77a359087793ff2",
|
||||
"9036fd1eb32061bdecebc4a32aa524b343b8098a16768ee774d93c",
|
||||
"f4ce5a05934e125d159678bea521f585574bcf9572629f155f63efcc",
|
||||
"5e1c0d9fae56393445d3024d6b82692d1339f7b5936f68b062c691d3bf",
|
||||
"538e35f3e11111d7c4bab69f83b30ade4f67addf1f45cdd2ac74bf299509",
|
||||
"17572c4dcbb17faf8785f3bba9f6903895394352eae79b01ebd758377694cc",
|
||||
"29f6bb55de7f8868e053176c878c9fe6c2055c4c5413b51ab0386c277fdbac75",
|
||||
"bad026c8b2bd3d294907f2280a7145253ec2117d76e3800357be6d431b16366e41",
|
||||
"386b7cb6e0fd4b27783125cbe80065af8eb9981fafc3ed18d8120863d972fa7427d9",
|
||||
"06e8e6e26e756fff0b83b226dce974c21f970e44fb5b3e5bbada6e4b12f81cca666f48",
|
||||
"2f9bd300244f5bc093ba6dcdb4a89fa29da22b1de9d2c9762af919b5fedf6998fbda305b",
|
||||
"cf6bdcc46d788074511f9e8f0a4b86704365b2d3f98340b8db53920c385b959a38c8869ae7",
|
||||
"1171e603e5cdeb4cda8fd7890222dd8390ede87b6f3284cac0f0d832d8250c9200715af7913d",
|
||||
"bda7b2ad5d02bd35ffb009bdd72b7d7bc9c28b3a32f32b0ba31d6cbd3ee87c60b7b98c03404621",
|
||||
"2001455324e748503aa08eff2fb2e52ae0170e81a6e9368ada054a36ca340fb779393fb045ac72b3",
|
||||
"45f0761aefafbf87a68f9f1f801148d9bba52616ad5ee8e8ac9207e9846a782f487d5cca8b20355a18",
|
||||
"3a7e05708be62f087f17b41ac9f20e4ef8115c5ab6d08e84d46af8c273fb46d3ce1aabebae5eea14e018",
|
||||
"ea318da9d042ca337ccdfb2bee3e96ecb8f907876c8d143e8e44569178353c2e593e4a82c265931ba1dd79",
|
||||
"e0f7c08f5bd712f87094b04528fadb283d83c9ceb82a3e39ec31c19a42a1a1c3bee5613b5640abe069b0d690",
|
||||
"d35e63fb1f3f52ab8f7c6cd7c8247e9799042e53922fbaea808ab979fa0c096588cfea3009181d2f93002dfc11",
|
||||
"b8b0ab69e3ae55a8699eb481dd665b6a2424c89bc6b7cca02d15fdf1b9854139cab49d34de498b50b2c7e8b910cf",
|
||||
"fb65e3222a2950eae1701d4cdd4736266f65bf2c0d2e77968996eadb60ef74fb786f6234973a2524bdfe32d100aa0e",
|
||||
"f28b4bb3a2e2c4d5c01a23ff134558559a2d3d704b75402983ee4e0f71d273ae056842c4153b18ee5c47e2bfa54313d4",
|
||||
"7bb78794e58a53c3e4b1aeb161e756af051583d14e0a5a3205e094b7c9a8cf62d098fa9ea1db12f330a51ab9852c17f983",
|
||||
"a879a8ebae4d0987789bcc58ec3448e35ba1fa1ee58c668d8295aba4eaeaf2762b053a677e25404f635a53037996974d418a",
|
||||
"695865b353ec701ecc1cb38f3154489eed0d39829fc192bb68db286d20fa0a64235cde5639137819f7e99f86bd89afcef84a0f",
|
||||
"a6ec25f369f71176952fb9b33305dc768589a6070463ee4c35996e1ced4964a865a5c3dc8f0d809eab71366450de702318e4834d",
|
||||
"604749f7bfadb069a036409ffac5ba291fa05be8cba2f141554132f56d9bcb88d1ce12f2004cd3ade1aa66a26e6ef64e327514096d",
|
||||
"daf9fa7dc2464a899533594e7916fc9bc585bd29dd60c930f3bfa78bc47f6c8439448043a45119fc9228c15bce5fd24f46baf9de736b",
|
||||
"943ea5647a8666763084da6a6f15dcf0e8dc24f27fd0d9194805d25180fe3a6d98f4b2b5e0d6a04e9b41869817030f16ae975dd41fc35c",
|
||||
"af4f73cbfc093760dfeb52d57ef45207bbd1a515f5523404e5d95a73c237d97ae65bd195b472de6d514c2c448b12fafc282166da132258e9",
|
||||
"605f4ed72ed7f5046a342fe4cf6808100d4632e610d59f7ebb016e367d0ff0a95cf45b02c727ba71f147e95212f52046804d376c918cadd260",
|
||||
"3750d8ab0a6b13f78e51d321dfd1aa801680e958de45b7b977d05732ee39f856b27cb2bcce8fbf3db6666d35e21244c2881fdcc27fbfea6b1672",
|
||||
"8f1b929e80ab752b58abe9731b7b34eb61369536995abef1c0980d93903c1880da3637d367456895f0cb4769d6de3a979e38ed6f5f6ac4d48e9b32",
|
||||
"d8469b7aa538b36cdc711a591d60dafecca22bd421973a70e2deef72f69d8014a6f0064eabfbebf5383cbb90f452c6e113d2110e4b1092c54a38b857",
|
||||
"7d1f1ad2029f4880e1898af8289c23bc933a40863cc4ab697fead79c58b6b8e25b68cf5324579b0fe879fe7a12e6d03907f0140dfe7b29d33d6109ecf1",
|
||||
"87a77aca6d551642288a0dff66078225ae39d288801607429d6725ca949eed7a6f199dd8a65523b4ee7cfa4187400e96597bfffc3e38ade0ae0ab88536a9",
|
||||
"e101f43179d8e8546e5ce6a96d7556b7e6b9d4a7d00e7aade5579d085d527ce34a9329551ebcaf6ba946949bbe38e30a62ae344c1950b4bde55306b3bac432",
|
||||
"4324561d76c370ef35ac36a4adf8f3773a50d86504bd284f71f7ce9e2bc4c1f1d34a7fb2d67561d101955d448b67577eb30dfee96a95c7f921ef53e20be8bc44",
|
||||
"78f0ed6e220b3da3cc9381563b2f72c8dc830cb0f39a48c6ae479a6a78dcfa94002631dec467e9e9b47cc8f0887eb680e340aec3ec009d4a33d241533c76c8ca8c",
|
||||
"9f6589c31a472e0a736f4eb22b6c70a9d332cc15304ccb66a6b97cd051b6ed82f8990e1d9bee2e4bb1c3c45e550ae0e7b96e93ae23f2fb8f63b309131e72b36cba6a",
|
||||
"c138077ee4ed3d7ffa85ba851dfdf6e9843fc1dc00889d117237bfaad9aa757192f73556b959f98e6d24886ce48869f2a01a48c371785f12b6484eb2078f08c22066e1",
|
||||
"f83e7c9e0954a500576ea1fc90a3db2cbd7994eaef647dab5b34e88ab9dc0b47addbc807b21c8e6dd3d0bd357f008471d4f3e0abb18450e1d4919e03a34545b9643f870e",
|
||||
"3277a11f2628544fc66f50428f1ad56bcba6ee36ba2ca6ecdf7e255effc0c30235c039d13e01f04cf1efe95b5c2033ab72adda30994b62f2851d17c9920eadca9a251752dc",
|
||||
"c2a834281a06fe7b730d3a03f90761daf02714c066e33fc07e1f59ac801ec2f4433486b5a2da8faa51a0cf3c34e29b2960cd0013378938dbd47c3a3d12d70db01d7d06c3e91e",
|
||||
"47680182924a51cabe142a6175c9253e8ba7ea579ece8d9bcb78b1e9ca00db844fa08abcf41702bd758ee2c608d9612fed50e85854469cb4ef3038acf1e35b6ba4390561d8ae82",
|
||||
"cec45830cd71869e83b109a99a3cd7d935f83a95de7c582f3adbd34e4938fa2f3f922f52f14f169c38cc6618d3f306a8a4d607b345b8a9c48017136fbf825aecf7b620e85f837fae",
|
||||
"46fb53c70ab105079d5d78dc60eaa30d938f26e4d0b9df122e21ec85deda94744c1daf8038b8a6652d1ff3e7e15376f5abd30e564784a999f665078340d66b0e939e0c2ef03f9c08bb",
|
||||
"7b0dcb52791a170cc52f2e8b95d8956f325c3751d3ef3b2b83b41d82d4496b46228a750d02b71a96012e56b0720949ca77dc68be9b1ef1ad6d6a5ceb86bf565cb972279039e209dddcdc",
|
||||
"7153fd43e6b05f5e1a4401e0fef954a737ed142ec2f60bc4daeef9ce73ea1b40a0fcaf1a1e03a3513f930dd5335723632f59f7297fe3a98b68e125eadf478eb045ed9fc4ee566d13f537f5",
|
||||
"c7f569c79c801dab50e9d9ca6542f25774b3841e49c83efe0b89109f569509ce7887bc0d2b57b50320eb81fab9017f16c4c870e59edb6c26620d93748500231d70a36f48a7c60747ca2d5986",
|
||||
"0a81e0c547648595adca65623ce783411aac7f7d30c3ad269efafab288e7186f6895261972f5137877669c550f34f5128850ebb50e1884814ea1055ee29a866afd04b2087abed02d9592573428",
|
||||
"6a7b6769e1f1c95314b0c7fe77013567891bd23416374f23e4f43e27bc4c55cfada13b53b1581948e07fb96a50676baa2756db0988077b0f27d36ac088e0ff0fe72eda1e8eb4b8facff3218d9af0",
|
||||
"a399474595cb1ccab6107f18e80f03b1707745c7bf769fc9f260094dc9f8bc6fe09271cb0b131ebb2acd073de4a6521c8368e664278be86be216d1622393f23435fae4fbc6a2e7c961282a777c2d75",
|
||||
"4f0fc590b2755a515ae6b46e9628092369d9c8e589e3239320639aa8f7aa44f8111c7c4b3fdbe6e55e036fbf5ebc9c0aa87a4e66851c11e86f6cbf0bd9eb1c98a378c7a7d3af900f55ee108b59bc9e5c",
|
||||
"ed96a046f08dd675107331d267379c6fce3c352a9f8d7b243008a74cb4e9410836afaabe871dab6038ca94ce5f6d41fa922ce08aba58169f94cfc86d9f688f396abd24c11a6a9b0830572105a477c33e92",
|
||||
"379955f539abf0eb2972ee99ed9546c4bbee363403991833005dc27904c271ef22a799bc32cb39f08d2e4ba6717d55153feb692d7c5efae70890bf29d96df02333c7b05ccc314e4835b018fec9141a82c745",
|
||||
"e16cc8d41b96547ede0d0cf4d908c5fa393399daa4a9696e76a4c1f6a2a9fef70f17fb53551a8145ed88f18db8fe780a079d94732437023f7c1d1849ef69ad536a76204239e8ba5d97e507c36c7d042f87fe0e",
|
||||
"a81de50750ece3f84536728f227208bf01ec5b7721579d007de72c88ee20663318332efe5bc7c09ad1fa8342be51f0609046ccf760a7957a7d8dc88941adb93666a4521ebe76618e5ddc2dd3261493d400b50073",
|
||||
"b72c5fb7c7f60d243928fa41a2d711157b96aef290185c64b4de3dcfa3d644da67a8f37c2ac55caad79ec695a473e8b481f658c497edb8a191526592b11a412282d2a4010c90ef4647bd6ce745ebc9244a71d4876b",
|
||||
"9550703877079c90e200e830f277b605624954c549e729c359ee01ee2b07741ecc4255cb37f96682dafcdbaade1063e2c5ccbd1918fb669926a67744101fb6de3ac016be4c74165a1e5a696b704ba2ebf4a953d44b95",
|
||||
"a17eb44d4de502dc04a80d5a5e9507d17f27c96467f24c79b06bc98a4c410741d4ac2db98ec02c2a976d788531f1a4451b6c6204cef6dae1b6ebbcd0bde23e6fffb02754043c8fd3c783d90a670b16879ce68b5554fe1c",
|
||||
"41d3ea1eaba5be4a206732dbb5b70b79b66a6e5908795ad4fb7cf9e67efb13f06fef8f90acb080ce082aadec6a1b543af759ab63fa6f1d3941186482b0c2b312f1151ea8386253a13ed3708093279b8eb04185636488b226",
|
||||
"5e7cdd8373dc42a243c96013cd29df9283b5f28bb50453a903c85e2ce57f35861bf93f03029072b70dac0804e7d51fd0c578c8d9fa619f1e9ce3d8044f65d55634dba611280c1d5cfb59c836a595c803124f696b07ddfac718",
|
||||
"26a14c4aa168907cb5de0d12a82e1373a128fb21f2ed11feba108b1bebce934ad63ed89f4ed7ea5e0bc8846e4fc10142f82de0bebd39d68f7874f615c3a9c896bab34190e85df05aaa316e14820b5e478d838fa89dfc94a7fc1e",
|
||||
"0211dfc3c35881adc170e4ba6daab1b702dff88933db9a6829a76b8f4a7c2a6d658117132a974f0a0b3a38ceea1efc2488da21905345909e1d859921dc2b5054f09bce8eeb91fa2fc6d048ce00b9cd655e6aafbdaa3a2f19270a16",
|
||||
"ddf015b01b68c4f5f72c3145d54049867d99ee6bef24282abf0eecdb506e295bacf8f23ffa65a4cd891f76a046b9dd82cae43a8d01e18a8dff3b50aeb92672be69d7c087ec1fa2d3b2a39196ea5b49b7baede37a586fea71aded587f",
|
||||
"6ee721f71ca4dd5c9ce7873c5c04c6ce76a2c824b984251c15535afc96adc9a4d48ca314bfeb6b8ee65092f14cf2a7ca9614e1dcf24c2a7f0f0c11207d3d8aed4af92873b56e8b9ba2fbd659c3f4ca90fa24f113f74a37181bf0fdf758",
|
||||
"689bd150e65ac123612524f720f54def78c095eaab8a87b8bcc72b443408e3227f5c8e2bd5af9bcac684d497bc3e41b7a022c28fb5458b95e8dfa2e8caccde0492936ff1902476bb7b4ef2125b19aca2cd3384d922d9f36dddbcd96ae0d6",
|
||||
"3a3c0ef066fa4390ec76ad6be1dc9c31ddf45fef43fbfa1f49b439caa2eb9f3042253a9853e96a9cf86b4f873785a5d2c5d3b05f6501bc876e09031188e05f48937bf3c9b667d14800db62437590b84ce96aa70bb5141ee2ea41b55a6fd944",
|
||||
"741ce384e5e0edaebb136701ce38b3d33215415197758ae81235307a4115777d4dab23891db530c6d28f63a957428391421f742789a0e04c99c828373d9903b64dd57f26b3a38b67df829ae243feef731ead0abfca049924667fdec49d40f665",
|
||||
"a513f450d66cd5a48a115aee862c65b26e836f35a5eb6894a80519e2cd96cc4cad8ed7eb922b4fc9bbc55c973089d627b1da9c3a95f6c019ef1d47143cc545b15e4244424be28199c51a5efc7234dcd94e72d229897c392af85f523c2633427825",
|
||||
"71f1554d2d49bb7bd9e62e71fa049fb54a2c097032f61ebda669b3e1d4593962e47fc62a0ab5d85706aebd6a2f9a192c88aa1ee2f6a46710cf4af6d3c25b7e68ad5c3db23ac009c8f13625ff85dc8e50a9a1b2682d3329330b973ec8cbb7bb73b2bd",
|
||||
"167cc1067bc08a8d2c1a0c10041ebe1fc327b37043f6bd8f1c63569e9d36ded58519e66b162f34b6d8f1107ef1e3de199d97b36b44141a1fc4f49b883f40507ff11f909a017869dc8a2357fc7336ae68703d25f75710b0ff5f9765321c0fa53a51675c",
|
||||
"cb859b35dc70e264efaad2a809fea1e71cd4a3f924be3b5a13f8687a1166b538c40b2ad51d5c3e47b0de482497382673140f547068ff0b3b0fb7501209e1bf36082509ae85f60bb98fd02ac50d883a1a8daa704952d83c1f6da60c9624bc7c99912930bf",
|
||||
"afb1f0c6b7125b04fa2578dd40f60cb411b35ebc7026c702e25b3f0ae3d4695d44cfdf37cb755691dd9c365edadf21ee44245620e6a24d4c2497135b37cd7ac67e3bd0aaee9f63f107746f9b88859ea902bc7d6895406aa2161f480cad56327d0a5bba2836",
|
||||
"13e9c0522587460d90c7cb354604de8f1bf850e75b4b176bda92862d35ec810861f7d5e7ff6ba9302f2c2c8642ff8b7776a2f53665790f570fcef3cac069a90d50db42227331c4affb33d6c040d75b9aeafc9086eb83ced38bb02c759e95ba08c92b17031288",
|
||||
"0549812d62d3ed497307673a4806a21060987a4dbbf43d352b9b170a29240954cf04bc3e1e250476e6800b79e843a8bd8253b7d743de01ab336e978d4bea384eaff700ce020691647411b10a60acacb6f8837fb08ad666b8dcc9eaa87ccb42aef6914a3f3bc30a",
|
||||
"3a263efbe1f2d463f20526e1d0fd735035fd3f808925f058b32c4d8788aeeab9b8ce233b3c34894731cd73361f465bd350395aebcabd2fb63010298ca025d849c1fa3cd573309b74d7f824bbfe383f09db24bcc565f636b877333206a6ad70815c3bef5574c5fc1c",
|
||||
"3c6a7d8a84ef7e3eaa812fc1eb8e85105467230d2c9e4562edbfd808f4d1ac15d16b786cc6a02959c2bc17149c2ce74c6f85ee5ef22a8a96b9be1f197cffd214c1ab02a06a9227f37cd432579f8c28ff2b5ac91cca8ffe6240932739d56788c354e92c591e1dd76499",
|
||||
"b571859294b02af17541a0b5e899a5f67d6f5e36d38255bc417486e69240db56b09cf2607fbf4f95d085a779358a8a8b41f36503438c1860c8f361ce0f2783a08b21bd7232b50ca6d35428335272a5c05b436b2631d8d5c84d60e8040083768ce56a250727fb0579dd5c",
|
||||
"98ee1b7269d2a0dd490ca38d447279870ea55326571a1b430adbb2cf65c492131136f504145df3ab113a13abfb72c33663266b8bc9c458db4bf5d7ef03e1d3b8a99d5de0c024be8fabc8dc4f5dac82a0342d8ed65c329e7018d6997e69e29a01350516c86beaf153da65ac",
|
||||
"41c5c95f088df320d35269e5bf86d10248f17aec6776f0fe653f1c356aae409788c938befeb67c86d1c8870e8099ca0ce61a80fbb5a6654c44529368f70fc9b9c2f912f5092047d0ffc339577d24142300e34948e086f62e23ecaca410d24f8a36b5c8c5a80e0926bc8aa16a",
|
||||
"9f93c41f533b2a82a4df893c78faaaa793c1506974ba2a604cd33101713ca4adfd30819ffd8403402b8d40aff78106f3357f3e2c24312c0d3603a17184d7b999fc9908d14d50192aebabd90d05073da7af4be37dd3d81c90acc80e8333df546f17ab6874f1ec204392d1c0571e",
|
||||
"3da5207245ac270a915fc91cdb314e5a2577c4f8e269c4e701f0d7493ba716de79935918b917a2bd5db98050dbd1eb3894b65fac5abf13e075abebc011e651c03cafb6127147771a5c8418223e1548137a89206635c26ca9c235ccc108dc25cf846e4732444bd0c2782b197b262b",
|
||||
"96011af3965bb941dc8f749932ea484eccb9ba94e34b39f24c1e80410f96ce1d4f6e0aa5be606def4f54301e930493d4b55d484d93ab9dd4dc2c9cfb79345363af31ad42f4bd1aa6c77b8afc9f0d551bef7570b13b927afe3e7ac4de7603a0876d5edb1ad9be05e9ee8b53941e8f59",
|
||||
"51dbbf2a7ca224e524e3454fe82ddc901fafd2120fa8603bc343f129484e9600f688586e040566de0351d1693829045232d04ff31aa6b80125c763faab2a9b233313d931903dcfaba490538b06e4688a35886dc24cdd32a13875e6acf45454a8eb8a315ab95e608ad8b6a49aef0e299a",
|
||||
"5a6a422529e22104681e8b18d64bc0463a45df19ae2633751c7aae412c250f8fb2cd5e1270d3d0cf009c8aa69688ccd4e2b6536f5747a5bc479b20c135bf4e89d33a26118705a614c6be7ecfe766932471ad4ba01c4f045b1abb5070f90ec78439a27a1788db9327d1c32f939e5fb1d5ba",
|
||||
"5d26c983642093cb12ff0afabd87b7c56e211d01844ad6da3f623b9f20a0c968034299f2a65e6673530c5980a532beb831c7d0697d12760445986681076dfb6fae5f3a4d8f17a0db5008ce8619f566d2cfe4cf2a6d6f9c3664e3a48564a351c0b3c945c5ee24587521e4112c57e318be1b6a",
|
||||
"52641dbc6e36be4d905d8d60311e303e8e859cc47901ce30d6f67f152343e3c4030e3a33463793c19effd81fb7c4d631a9479a7505a983a052b1e948ce093b30efa595fab3a00f4cef9a2f664ceeb07ec61719212d58966bca9f00a7d7a8cb4024cf6476bab7fbccee5fd4e7c3f5e2b2975aa2",
|
||||
"a34ce135b37bf3db1c4aaa4878b4499bd2ee17b85578fcaf605d41e1826b45fdaa1b083d8235dc642787f11469a5493e36806504fe2a2063905e821475e2d5ee217057950370492f5024995e77b82aa51b4f5bd8ea24dc71e0a8a640b0592c0d80c24a726169cf0a10b40944747113d03b52708c",
|
||||
"46b3cdf4946e15a5334fc3244d6680f5fc132afa67bf43bfade23d0c9e0ec64e7dab76faaeca1870c05f96b7d019411d8b0873d9fed04fa5057c039d5949a4d592827f619471359d6171691cfa8a5d7cb07ef2804f6ccad4821c56d4988bea7765f660f09ef87405f0a80bcf8559efa111f2a0b419",
|
||||
"8b9fc21691477f11252fca050b121c5334eb4280aa11659e267297de1fec2b2294c7ccee9b59a149b9930b08bd320d3943130930a7d931b71d2f10234f4480c67f1de883d9894ada5ed5071660e221d78ae402f1f05af47761e13fec979f2671e3c63fb0ae7aa1327cf9b8313adab90794a52686bbc4",
|
||||
"cd6598924ce847de7ff45b20ac940aa6292a8a99b56a74eddc24f2cfb45797188614a21d4e8867e23ff75afd7cd324248d58fcf1ddc73fbd115dfa8c09e62022fab540a59f87c989c12a86ded05130939f00cd2f3b512963dfe0289f0e54acad881c1027d2a0292138fdee902d67d9669c0ca1034a9456",
|
||||
"594e1cd7337248704e691854af0fdb021067ddf7832b049ba7b684438c32b029eded2df2c89a6ff5f2f2c311522ae2dc6db5a815afc60637b15ec24ef9541f1550409db2a006da3affffe548a1eaee7bd114e9b805d0756c8e90c4dc33cb05226bc2b393b18d953f8730d4c7ae693159cdba758ad28964e2",
|
||||
"1f0d292453f04406ada8be4c161b82e3cdd69099a8637659e0ee40b8f6da46005cfc6085db9804852decfbe9f7b4dda019a7112612895a144ed430a960c8b2f5458d3d56b7f427cee6358915aee7146278aed2a0296cdd929e4d21ef95a3adf8b7a6beba673cdccdbdcfb2474711732d972ad054b2dc64f38d",
|
||||
"b65a72d4e1f9f9f75911cc46ad0806b9b18c87d105332a3fe183f45f063a746c892dc6c4b9181b1485b3e3a2cc3b453eba2d4c39d6905a774ed3fb755468beb190925ecd8e57ecb0d985125741650c6b6a1b2a3a50e93e3892c21d47ed5884eed83aa94e1602288f2f49fe286624de9d01fcb54433a0dc4ad70b",
|
||||
"705ce0ffa469250782aff725248fc88fe98eb76659e8407edc1c4842c9867d61fe64fb86f74e980598b92bc213d06f337bd5654fc28643c7ba769a4c31563427543c00808b627a19c90d86c322f33566ce020121cc322229c3337943d46f68ef939d613dcef0077269f88151d6398b6b009abb763410b154ad76a3",
|
||||
"7fa881ce87498440ab6af13854f0d851a7e0404de33896999a9b3292a5d2f5b3ad033530c558168fe5d2fdb9b89a2354c46cf32a0e612afc6c6485d789511bfef26800c74bf1a4cfbe30bda310d5f6029c3dccdedb6149e4971274e276dccfabd63bc4b9955e8303feb57f8a688db55ecb4b33d1f9fe1b3a8ba7ac32",
|
||||
"23a98f71c01c0408ae16843dc03be7db0aeaf055f951709d4e0dfdf64fffbffaf900ee592ee10929648e56f6c1e9f5be5793f7df66453eb56502c7c56c0f0c88da77abc8fa371e434104627ef7c663c49f40998dbad63fa6c7aa4fac17ae138d8bbe081f9bd168cd33c1fbc92fa35ed687679f48a64b87db1fe5bae675",
|
||||
"7b8970b6a33237e5a7bcb39272703edb92285c55842b30b9a48834b1b507cc02a6764739f2f7ee6ae02a7b715a1c455e59e8c77a1ae98abb10161853f1234d20da99016588cd8602d6b7ec7e177d4011edfa61e6b3766a3c6f8d6e9eac893c568903eb6e6aba9c4725774f6b4343b7acaa6c031593a36eef6c72806ff309",
|
||||
"f7f4d328ba108b7b1de4443e889a985ed52f485f3ca4e0c246aa5526590cbed344e9f4fe53e4eea0e761c82324649206ca8c2b45152157d4115e68c818644b03b65bb47ad79f94d37cb03c1d953b74c2b8adfa0e1c418bda9c518ddcd7050e0f149044740a2b16479413b63fc13c36144f80c73687513dca761ba8642a8ae0",
|
||||
"2d7dc80c19a1d12d5fe3963569547a5d1d3e821e6f06c5d5e2c09401f946c9f7e13cd019f2f9a878b62dd850453b6294b99ccaa068e542993524b0f63832d48e865be31e8ec1ee103c718340c904b32efb69170b67f038d50a3252794b1b4076c0620621ab3d91215d55ffea99f23d54e161a90d8d4902fda5931d9f6a27146a",
|
||||
"77dff4c7ad30c954338c4b23639dae4b275086cbe654d401a2343528065e4c9f1f2eca22aa025d49ca823e76fdbb35df78b1e5075ff2c82b680bca385c6d57f7ea7d1030bb392527b25dd73e9eeff97bea397cf3b9dda0c817a9c870ed12c006cc054968c64000e0da874e9b7d7d621b0679866912243ea096c7b38a1344e98f74",
|
||||
"83bed0d556798f2b419f7056e6d3ffada06e939b95a688d0ec8c6ac5ea45ab73a4cf01043e0a170766e21395f27ab4b78c435f5f0dfe6e93ab80df38610e41158429ddf20296f53a06a017723359fe22dc08b5da33f0800a4fe50118e8d7eab2f83a85cd764bf8a166903bd0e9dcfeeceba44ff4ca4439846458d31ea2bb564645d1",
|
||||
"ea12cf5a113543e39504123036f15a5bafa9c555562469f99cd29996a4dfaaab2a34b00557ccf15f37fc0cc1b3be427e725f2cd952e50af7970dda9200cd5ce252b1f29c40067fea3027ed686190803b59d834179d1b8f5b55abe55ad174b2a1188f7753ec0ae2fc01316e7d498b68ee3598a0e9baaaa664a60f7fb4f90edbed494ad7",
|
||||
"55266358332d8d9e68bd13432088beadf95833aab67a0eb3b10650414255f299e2670c3e1a5b2976159a46c72a7ce57d59b7be14c15798e09ed50fa312a431b0264d7a1396aa6168bde897e208ece53d2cfc83786113b1e6eac5e9bb98984abb6c8d64eebb991903254abc650c999bb9958a5d7937434b869bc940e21b9dc1cc8982f2ba",
|
||||
"4d6104ded730aefe02873f4c741232c8234a6d66d85393aff57fbf56ba6347666988dfc4d58f3cc895a0da598822edeee4533d24ec0ee292fd5e1ad04898ffbc1ff4bef14dec220babcb0f28fffe32a6e2c28aaaac16442bf4feb02917d18bb3a415d84fa9358d5a9852688d846c92271911f934181c30f82434d915f93f155a1ffbf0b125",
|
||||
"eb5f579a4c476af554aac11e5719d378549497e613b35a929d6f36bb8831d7a466aa76de9be24ebb55543f1c13924f64cfd648a5b3fa90387315c16174dbf1e9a183c196d9bb8f84af65f1f8212429aadc11ef2426d07d4716062b85c8d5d2dff8e21b9e62b7fa7dbd57d72633054b464fb28583a56ca13ccc5ddc74dae942492f31731e7046",
|
||||
"ebddec3dcaf18063e45a76ebeac39af85a1adc2818881ccce48c106288f5988365cca2b4b1d7f037322da46840f42bebdcbc7193838d426e101087d8cea03aaff743d573eb4f4e9a71a2c884390769a6503874125d194bee8d46a3a0d5e4fcf28ff8465887d8e9df771d70157e75df3642b331d2778ceb32ceba868640171ab7a5d22eede1ee44",
|
||||
"26d87ec70b57691e3bb359633d3ddba17f029d62cdfe977f5fd42274d79b444a32494d1c01e9f72d03cce78c806df96e93ea78da3a054209924ed765edc4d570f66168dc25ee3114e4017e387440349c8f0a94804761c3055f88e4fda2a49b860b1486a9609095f6250f268b6a4d1aecc03a505632ebf0b9dc22d0755a736faf7ad7000858b5864b",
|
||||
"3880f5cc2d08fa70ef44b1f263fcf534d062a298c1bd5ee2eee8c3265806c4ce50b004f3a1fc1fa5b024aaac7f528c023c8181f67c6e1c357425dc4d573bd46b93a542afa3a19bdb140a2ce666e1a01f5c4d2dcd681fa9f5839b797813c394738d5ee4971386c12c7c117d17c7bec324b760aa30cda9ab2aa850284ba6fa97946f710f02449d1883c6",
|
||||
"3317d2f452105dd3f4a96f9257af8285a80be58066b50f6f54bd633749b49f6ab9d57d45652d2ae852a2f6940cd5ec3159dd7f333358b12f502325df38843508faf7e246352d201280babd90b14fbf7722641c3601d0e458474439973c611bb5502fd0eb3078f87124ca7e1a016fcb6cfeff65f6a565985aca7122cfa8c5a11da0cb47797c5132333179",
|
||||
"f2c5c955d0224e784a46b9125f8fef8a5e1271e145eb08bbbd07ca8e1cfc848cef14fa3b36221ac62006403dbb7f7d77958ccc54a8566c837858b809f3e310ace8ca682515bc655d2a397cab238a663b464d511f02dc5d033dad4cb5e0e519e94a54b62a3896e460ec70e5716b5921bf8396aa86a60123e6287e34570bb01bdc602e113670bf498af2ff10",
|
||||
"180e275205691a83630cf4b0c7b80e6df8fad6ef1c23ba8013d2f09aef7abade1827f23af230de90676240b4b3b0673f8afdea0327330055041741f65560d90348de696d34ca80dfe8afae582fe4879d4594b80e9408fb53e800e01ca58552b905c365e7f1416e51c080f517d6bbd30e64ae1535d59decdc76c6624d737868f49f2f719da39ba1344d59eab9",
|
||||
"c517a84e4631a7f65ace170d1e5c2fdb259841535d88da323e68c0883e6af7b041cfe05908815a5a9d1b14fa712c2c16fadcf1ca54d3aa954d411240df331b2aebdfb65aced84d0b8aace56ec0aa7c13ec7d75ca883b6bcf6db74c9e98463c484a8262684f29910373430651f90ecffe18b072170e61ee58de20e2a6ff67b3ab00fccbb80af943f20b56b98107",
|
||||
"d1a56a5ee990e02b84b5862fde62f69ec07567be2d7ccb769a461c4989d11fdda6c945d942fb8b2da795ed97e43a5b7dbdde7f8fd2ff7154544336d5c50fb7380341e660d4898c7fbc39b2b782f28defac6873523c7c1de8e52c65e4395c686ba483c35a220b0416d46357a063fa4c33fa9c52d5c207a1304ae141c791e62ba6a7374ed922b8dd94079b72b69302",
|
||||
"4720b88d6bfb1ab43958e26827730d852d9ec30173ebd0fe0d273edcece2e788558984cd9306fe5978086a5cb6d37975755d2a3daeb16f99a8a11544b8247a8b7ed5587afc5bea1daf85dcea5703c5905cf56ae7cc76408ccabb8fcc25cacc5ff456db3f62fa559c45b9c71505eb5073df1f10fc4c9060843f0cd68bbb4e8edfb48d0fd81d9c21e53b28a2aae4f7ba",
|
||||
"f4639b511db9e092823d47d2947efacbaae0e5b912dec3b284d2350b9262f3a51796a0cd9f8bc5a65879d6578ec24a060e293100c2e12ad82d5b2a0e9d22965858030e7cdf2ab3562bfa8ac084c6e8237aa22f54b94c4e92d69f22169ced6c85a293f5e16bfc326153bf629cdd6393675c6627cd949cd367eef02e0f54779f4d5210197698e4754a5fe490a3a7521c1c",
|
||||
"3d9e7a860a718565e3670c29079ce80e381969fea91017cfd5952e0d8a4a79bb08e2cd1e26161f30ee03a24891d1bfa8c212861b51618d07429fb48000ff87ef09c6fca526567777e9c076d58a642d5c521b1caa5fb0fb3a4b8982dc14a444732b72b239b8f01fc8ba8ee86b3013b5d3e98a92b2aeaecd4879fca5d5e9e0bd880dbfffa6f96f94f3998812aac6a714f331",
|
||||
"4d9bf551d7fd531e7482e2ec875c0651b0bcc6caa738f7497befd11e67ae0e036c9d7ae4301cc3c7906f0d0e1ed4738753f414f9b3cd9b8a71176e325c4c74ce020680ecbfb146889597f5b40487e93f974cd866817fb9fb24c7c7c16177e6e120bfe349e83aa82ba40e59e917565788658a2b254f25cf99bc65070b3794cea2259eb10e42bb54852cba3110baa773dcd70c",
|
||||
"b91f65ab5bc059bfa5b43b6ebae243b1c46826f3da061338b5af02b2da76bb5ebad2b426de3c3134a633499c7c36a120369727cb48a0c6cbab0acecdda137057159aa117a5d687c4286868f561a272e0c18966b2fec3e55d75abea818ce2d339e26adc005c2658493fe06271ad0cc33fcb25065e6a2a286af45a518aee5e2532f81ec9256f93ff2d0d41c9b9a2efdb1a2af899",
|
||||
"736f6e387acb9acbee026a6080f8a9eb8dbb5d7c54ac7053ce75dd184b2cb7b942e22a3497419ddb3a04cf9e4eb9340a1a6f9474c06ee1dcfc8513979fee1fc4768087617fd424f4d65f54782c787a1d2de6efc81534343e855f20b3f3589027a5436201eee747d45b9b8375e4294d72ab6a52e04dfbb2914db92ee58f134b026527ed52d4f794459e02a43a17b0d51ea69bd7f3",
|
||||
"9242d3eb31d26d923b99d66954cfade94f25a18912e6356810b63b971ae74bb53bc58b3c01424208ea1e0b1499936daea27e63d904f9ed65fdf69de40780a3027b2e89d94bdf214f585472613ce328f628f4f0d56217dfb53db5f7a07f54c8d71db16e27de7cdb8d23988837b49b65c12f1771d979e8b192c9f4a16b8d9fba917bcf74ce5a82aac2075608ba6c2d485fa59864b9de",
|
||||
"5da68704f4b592d41f08aca08f62d85e2e2466e5f3be010315d11d113db674c4b98764a509a2f5aacc7ae72c9deff2bcc42810b47f64d429b35745b9efff0b18c58653461e968aaa3c2c7fc455bc5771a8f10cd184be831040df767201ab8d32cb9a58c89afbebecb524502c9b940c1b838f8361bbcde90d272715017f67609ea39b20fac985332d82daaa023999e3f8bfa5f3758bb8",
|
||||
"71ea2af9c8ac2e5ae44a176662882e01027ca3cdb41ec2c6785606a07d7231cd4a2bded7155c2feef3d44d8fd42afa73265cef826f6e03aa761c5c51d5b1f129ddc27503ff50d9c2d748322df4b13dd5cdc7d46381528ab22b79b0049011e4d2e57fe2735e0d58d8d56e92c75dbeac8c76c4239d7f3f24fb56697593b3e4afa6671d5bbc96c079a1c154fe20212ade67b05d49ceaa7a84",
|
||||
"1d133170582fa4bff59a21953ebbc01bc202d43cd79c083d1f5c02fa15a43a0f519e36acb710bdabac880f04bc003800641c2487930de9c03c0e0deb347fa815efca0a38c6c5de694db698743bc955581f6a945deec4ae988ef7cdf40498b77796ddea3fae0ea844891ab751c7ee20917c5a4af53cd4ebd82170078f41ada2795e6eea17593fa90cbf5290a1095e299fc7f507f360f187cd",
|
||||
"5ec4ac45d48fc15c72471d795066bdf8e99a483d5fdd599511b9cdc408de7c0616491b73924d0266da34a495331a935c4b8884f57d7ad8cce4cbe586875aa52482215ed39d7626cce55d50349c7767981c8bd6890f132a196184247343566fc972b86fe3c5369d6a6519e9f07942f0522b77ad01c751dcf7defe31e471a0ec00963765dd8518144a3b8c3c978ad108056516a25dbe3092e73c",
|
||||
"0d5e74b78290c689f2b3cfea45fc9b6a84c822639cd438a7f05c07c374adced42cdc12d2a9233a4ffe80307efc1ac13cb04300e165f8d90dd01c0ea955e7657332c6e86ad6b43e78ba4c13c675aed83192d8427866fb6484e6a3071b2369a46fba9005f31232da7ffec7952f831aaaddf63e225263531c2cf387f8cc14fa856c8795137142c3a52ffa69b8e30ebc88ce3bbc227597bcc8dddd89",
|
||||
"a0fe36f983259921dc2fa7d89002b3066241d63bfc2448caf7e10522a35562be0bfedc3dce49cfce2e614a04d4c64cfc0ab898873a7fc26928dc1927c009d12f6f9b7a278205d3d0057604f4ac746f8b9287c3bc6b929832bf253b6586192ac43fdd29ba585dbd9059aab9c6ff6000a7867c67fec1457b733f6b620881166b8fed92bc8d84f0426002e7be7fcd6ee0abf3755e2babfe5636ca0b37",
|
||||
"1d29b6d8eca793bb801becf90b7d7de215b17618ec32340da4bac707cdbb58b951d5036ec02e105d83b5960e2a72002d19b7fa8e1128cc7c5049ed1f76b82a59eac6ed09e56eb73d9ade38a6739f0e07155afa6ec0d9f5cf13c4b30f5f9a465b162a9c3ba04b5a0b3363c2a63f13f2a3b57c590ec6aa7f64f4dcf7f1582d0ca157eb3b3e53b20e306b1f24e9bda87397d413f01b453ceffeca1fb1e7",
|
||||
"6a2860c110cd0fc5a19bcaafcd30762ee10242d34739638e716bd89fd537ea4dc630e6f85d1bd88a25ad3892ca554c232c9830bd56980c9f08d378d28f7fa6fa7df4fcbf6ad98b1adfff3ec1f63310e50f920c99a5200b8e64c2c2ca249399a149942261f737d5d72da949e914c024d57c4b639cb89990fed2b38a37e5bcd24d17ca12dfcd36ce04691fd03c32f6ed5de2a2191ed7c826375ba81f78d0",
|
||||
"7132aa291ddc9210c60dbe7eb3c19f9053f2dd74742cf57fdc5df98312adbf4710a73245de4a0c3b24e21ab8b466a77ae29d15500d5142555ef3088cbccbe685ed9119a10755148f0b9f0dbcf02b2b9bcadc8517c88346ea4e78285e9cbab122f824cc18faf53b742a87c008bb6aa47eed8e1c8709b8c2b9adb4cc4f07fb423e5830a8e503ab4f7945a2a02ab0a019b65d4fd71dc364d07bdc6e637990e3",
|
||||
"3e664da330f2c6007bff0d5101d88288aaacd3c07913c09e871cce16e55a39fde1ce4db6b8379977c46cce08983ca686778afe0a77a41baf447854b9aa286c398c2b83c95a127b053101b6799c1638e5efd67273b2618df6ec0b96d8d040e8c1ee01a99b9b5c8fe63fea2f749e6c90d31f6fae4e1469ac09884c4fe1a8539acb313f42c941224a0e79c059e18affc2bcb6724975c436f7bf949ebdd8aef51c",
|
||||
"7a6ea63a271eb49470f5ce77519ed61ae9b2f1be07a96855726bc3df1d0723af3a703fdfc2e739c9d31d25814daf661a23558b50982e66ee37ad880f5c8f11c8130fac8a5d0250583700d5a324894fae6d61993f6bf9327214f8674649f355b23fd634940b2c467973a839e659169c773119919f5b81ee171edb2e5f6940d7551f9e5a70625d9ea88711ad0ed8ab2da720ad358bef954456cb2d5636425717c2",
|
||||
"c5106bbda114168c449172e49590c7eeb827fa4e1a2a7a87a3c1f721a9047d0c0a50fbf244731be1b7eb1a2ef30f5ae846a9f38f0df44f32af61b68dbdcd0226e741dfb6ef81a2503691af5e4b3171f48c59ba4ef91eba344b5b697f261df7bbbb734ca6e6daebaa4a179feb17002823281b8534d55a6531c59305f6e3fd3fa63b747bcf0deb654c392a02fe687a269effb1238f38bcaea6b208b221c45fe7fbe7",
|
||||
"597716a5ebeebc4bf524c15518816f0b5dcda39cc833c3d66b6368ce39f3fd02ceba8d12072bfe6137c68d3acd50c849873150928b320b4fbc31c1456679ea1d0acaeeabf666d1f1bad3e6b9312c5cbdecf9b799d3e30b0316bed5f41245107b693366accc8b2bcef2a6be54209ffabc0bb6f93377abdcd57d1b25a89e046f16d8fd00f99d1c0cd247aafa72234386ae484510c084ee609f08aad32a005a0a5710cb",
|
||||
"0771ffe789f4135704b6970b617bae41666bc9a6939d47bd04282e140d5a861c44cf05e0aa57190f5b02e298f1431265a365d29e3127d6fccd86ec0df600e26bcdda2d8f487d2e4b38fbb20f1667591f9b5730930788f2691b9ee1564829d1ada15fffc53e785e0c5e5dd11705a5a71e390ca66f4a592785be188fefe89b4bd085b2024b22a210cb7f4a71c2ad215f082ec63746c7367c22aedb5601f513d9f1ffc1f3",
|
||||
"be6556c94313739c115895a7bad2b620c0708e24f0390daa55521c31d2c6782acf41156271238885c367a57c72b4fe999c160e804ad58d8e565edbce14a2dd90e443eb80626b3eab9d7ab75d6f8a062d7ca89b7af8eb292c98eaf87ad1dfd0db103d1bb6188bd7e7a63502153cf3ce23d43b60c5782602bac8ad92fb2324f5a79453898c5de18415639ecc5c7974d3077f76fc1df5b956723bb19a624d7ea3ec13ba3d86",
|
||||
"4bc33729f14cd2f1dc2ff459abee8f6860dda1062845e4adab78b53c835d106bdfa35dd9e77219eaef403d4e80488ca6bd1c93dd76ef9d543fbb7c8904dccc5f71509a6214f73d0f4e467c3e038ea639b29e7fc442ee29f57117740576188ada15a739827c647a46b0271817ab235c023c30c90f2115e5c90cd8501e7b286962fc66ffc3fe7e8978746168314908a41998bd83a1eeffda9d714b864f4d490fdeb9c7a6edfa",
|
||||
"ab12faea205b3d3a803cf6cb32b9698c32301a1e7f7c6c23a20174c95e98b7c3cfe93fffb3c970face8f5751312a261741141b948d777b8a2ea286fe69fc8ac84d34116a4674bb09a1a0b6af90a748e511749de4697908f4acb22be08e96ebc58ab1690acf73914286c198a2b57f1dd70ea8a52325d3045b8bdfe9a09792521526b7564a2a5fcd01e291f1f8894017ce7d3e8a5dba15332fb410fcfc8d62195a48a9e7c86fc4",
|
||||
"7d421e59a567af70594757a49809a9c22e07fe14061090b9a041875bb77933deae36c823a9b47044fa0599187c75426b6b5ed94982ab1af7882d9e952eca399ee80a8903c4bc8ebe7a0fb035b6b26a2a013536e57fa9c94b16f8c2753c9dd79fb568f638966b06da81ce87cd77ac0793b7a36c45b8687c995bf4414d28289dbee977e77bf05d931b4feaa359a397ca41be529910077c8d498e0e8fb06e8e660cc6ebf07b77a02f",
|
||||
"0c18ab727725d62fd3a2714b7185c09faca130438eff1675b38beca7f93a6962d7b98cb300ea33067a2035cdd694348784aa2eda2f16c731eca119a050d3b3ce7d5c0fd6c234354a1da98c0642451922f670984d035f8c6f35031d6188bbeb31a95e99e21b26f6eb5e2af3c7f8eea426357b3b5f83e0029f4c4732bca366c9aa625748297f039327c276cd8d9c9bf692a47af098aa50ca97b99961bef8bc2a7a802e0b8cfdb84319",
|
||||
"92d5909d18a8b2b9971cd1627b461e98a74ba377186a6a9df5bd133635250b300abccb2254cacb775df6d99f7c7d0952653c28e6909b9f9a45adce691f7adc1afffcd9b06e49f775364cc2c62825b9c1a86089080e26b57e732aac98d80d009bfe50df01b95205aa07ed8ec5c873da3b92d00d53af825aa64b3c634c5ece40bff152c331222d3453fd92e0ca17cef19ecb96a6eed4961b627aca48b12fecd091754f770d52ba861546",
|
||||
"802f22e4a388e874927fef24c797408254e03910bab5bf372320207f8067f2b1ea543917d4a27df89f5bf936ba12e04302bde23119533d0976beca9e20cc16b4dbf17a2ddc44b66aba76c61ad59d5e90de02a88327ead0a8b75463a1a68e307a6e2e53ecc1986274b9ee80bc9f3140671d5285bc5fb57b281042a8978a1175900c6073fd7bd740122956602c1aa773dd2896674d0a6beab24454b107f7c847acb31a0d332b4dfc5e3f2f",
|
||||
"3844fe65db11c92fb90bf15e2e0cd216b5b5be91604baf3b84a0ca480e41ecfaca3709b32f8c6e8761406a635b88eec91e075c48799a16ca08f295d9766d74475c47f3f2a274eae8a6ee1d191a7f37ee413a4bf42cad52acd5564a651715ae42ac2cddd52f819c692ecdef52ecb763270322cdca7bd5aef71428fa73e844568b96b43c89bf1ed42a0abf209ffad0eeec286c6f141e8af073ba4adfbbdeda253752ae36c9957dfc905b4c49",
|
||||
"329377f7bf3c8d74991a7d61b0cf39baff5d485d79751b0d5ad017d23bec570fb19810105bab79ab5acb102ab972165224d4ec888ec7de5148077fa9c1bb6820e0d91ae4e2591a21fec2f820606ce4bafc1e377f8dc3a5bd1a9e2772a57abccd0b757164d768872c91d02789545ab5b203f688d71dd08522a3fd2f5bcd7df507aebf1ca27ddff0a82afb7aa9c180008f49d1325adf97d047e77238fc75f56356de4e87d8c961575c9f6362c9",
|
||||
"f7f269929b0d71ea8eef7120e55ccba691c582dd534692abef35c0fe9dec7dae973cd9702e5ad420d278fe0e653fdcb22fdcb63148109ec7e94f2d0750b28157dd1764376ae10fdb0a4aef3b304bd82793e0595f941226a2d72abbc929f53134dc495b0d65ced409914f94c2523f3dfbbdeeac84ae247ab5d1b9ea33dce1a808885a55be1f3683b46f4be73d9b62eec2585f690056858dfc427aabf591cd276724885bcd4c00b93bb51fb7484d",
|
||||
"ac022309aa2c4d7fb628255b8b7fb4c3e3ae64b1cb65e0de711a6def1653d95d8088871cb8905fe8ae76423604988a8f77589f3f776dc1e4b30dbe9dd262b2187db02518a132d219bd1a06ebac13132b5164b6c420b37dd2ccee7d69b3b7fa12e54f0a53b853d490a68379ea1fa2d79762830ffb71bf86aab506b51f85c4b6a41b69325c7d0c7aa85b93b7144489d213e8f33dbb879fce22849865337b620b155cb2d2d36a68832889e30194d36d",
|
||||
"d009c2b78a8f02e5e5dbb586ef71fc324b375092e15913ca1a5bfd22d516baadb96867bee3562e77c4a4852344a1a76c30728be5e22400b4cc41711f66754c246a520498d8c24f0205b9c873748dbeb67fe1ad099ad04cf89f4b517f0aa481136d9f6de2d727df01c6aa4099da59d4382b51e25fd47c33d9842c32b62331e50794bfe8b61b3ba9de1b8b704779c6d65edff3af00f121ab4a7ea384edabe47c6d0098a48991f387ca4444135ec59d46",
|
||||
"c00bab36cce69899817d1425016d222d7303197ed3e3fdcac744705e7f178a1ac745968900f69299163e19b3161f3e0a4cc55aa2e4e71e0ee6ac427d1f4d14e063f68d303ddfbb18118335cfa7a6a90d99c38319ee76f7a884846a9e0b68030bf28e78bfbd56359b9368842814da42b04cb0e307d5d846dc22f049147bae31b9a956d17676a8cc348dafa3cabc2007a30e730e3894dddf9999fb8819086311f0703e141613ed6dcd7af8510e2dc435b0",
|
||||
"c9789152a9fc29698d49ed95f09bd11b75f18a8c5615a73dbe54ae5e550027fd0ae6a8b60667040c1b12de3d1ee3f6bf061c78c951a3210effc912e19f482dd4de152063c588c44903bc11761706fd935afa040df085b08144d83d0dde32b46ab52f4fae98ac116c7ff11d7f553450c2e37b9c5f0b1dd9e0b8640a24cba6f2a5246c41f197f46e3dc8a29131c79bef3351c6e277a0a34442274d546ccd058891277473d668420f121750d19cd684267405",
|
||||
"06a15a0731ce52557e368bcbaa11ef3399299e36fb9f2eda6e5726907c1d29c5c6fc581405ba48c7e2e522206a8f128d7c1c939d1132a00bd7d6366aa82724e968964eb2e373563f607dfa649590dcf5589114df69da5547fef8d1604cc4c6de1ed5783c8746918a4dd31168d6bc8784cd0c769206bd803d6ca8557b66748770402b075ef44b38157d4c0da7c6281725a2065d087b1f7b23455fa673bdeeba45b983311c44eabe9ef4b7bde3420ae9881863",
|
||||
"d08aacef2d7a41aec09473bd8a44f628e15addb7b9e5b77a1e09c8ab4942f379a0bfcb324d580b774666f18ae78dd36710824ff12393f059068fe4b559c53662c2b0e6c69e23785c8f32554e837ec1714bee902e60737b639dd933af4f68cb9d7de77e1f3b28e5b122891afce62b79acd5b1ab4ba411662cc77d806449e69c5a45a143b742d98ac84a0826d68433b9b700ace6cd472ba2d58a90847f42ce9c43f38ffc017db4bf40450b2eee1f4594dc740c0f",
|
||||
"6a6058b0a498b7ea76a93c646eb9b8629f0cba4a0c726420c5f67ba9b0412cade356abdf0a4fb94384bad32ce0d5dd9e23dcaae1d6f28ff8683616b30f1392890c67b3a2c04b360893b801f127e527e4da82e239f4c878da13f4a4f1c76db07190e77ec123995168102fb274434a2d1e12913b9b5cbab4aacaad2bd89d88b3ca2b8e60dacf7c22c9379097ff60880f552e320ca3b571994f52534470feee2b39e0dadb5cd88257a3e459a4cc6f12f17b8d54e1bb",
|
||||
"adeced01fc5671531cbb45679f5ddd42b3a95151677b6125aaf6f5e8f82fbabaa5ecf7c3552c2458587224f0042870f178f5fca5465250e75d71352e652eeed23cdb7f915f5ebb44099b6db116ca1be45530ac8ed32b7f161d60ed4397ad3d7d649ae6bf75ca5bec891d8e595605be9764f3a03965e1fe0eaffbf212e3df4f0fa35e08ff9d0091e6d4ac4748edfe43b611085a6ffec163014655fdd839fd9e81b63b1fa8cae4ec335ec343289758e389a79ceedfae",
|
||||
"d014592f3a83ba40af366f137c674724916c3cdd3f6cf9d4c5c7c8d6d51ebf26e315e2c12b3546be56fb52382904046ecbd2f5b883aa4ff473de6f0c26ab862c3fa34bf3d880cc1911ce39a4088c6617c179dc5faf68a2c488bbde12d67b50f73abcfab0e3b062e68c95363e11f5f1de8ec36ed01ea21442518089045df67d346135283ad5b3fff80cf57f20876849f6db9fa139728358415a90610f69ec720fc92d8234e3e122551e9df2c644c4a2c4e3734d07de8e",
|
||||
"c0d0c37838873ba8757d6e41b409605043bc1635edcd731219587676d94217e9f0ab44b71de25000661ce7303b7015f45e6eaa7b7ebef92b8f4a34c902c908d2172185505fa33aca5a41be83079316cdfdd430fc2c45f505f85d867e6d516f7e1bf19c001d9f43018968aab65ec031b3801399231c83ec9e622dab5629922a6b424cab938c135ff7310501c2c02971bfd2f577e25904d1a618baf0859f77f4e8b1d0cde9544e95ec52ff710c0672fdb3d891feeea2b017",
|
||||
"7022e7f00902219ba97baa0e940e8ac7727f58955aa068c29680fac4a16bcd812c03eeb5adbcfe867a7f7c6b5d89f4641adb9173b76a1a8438866f9b4f640ce2aedf5f1080c890bcf515b4be4e3e512352f1e5323c62ec46cb73f3d71be8235fee55a154763f7c3f9aeb61ffd28f4cd93d3310f608e2133586bf1ab3f102de96f64c68a4668de8acb2a76a7ce0cddddc8fa3df5e9d230823da16ed9ebb402d36e38e6e018795e5a71517ecab5f9ca472b9ced8ff69d2d195",
|
||||
"acaf4baf3681ab865ab9abfae41697141ead9d5e98523c2e0e1eeb6373dd15405242a3393611e19b693cabaa4e45ac866cc66663a6e898dc73095a4132d43fb78ff7166724f06562fc6c546c78f2d5087467fcfb780478ec871ac38d9516c2f62bdb66c00218747e959b24f1f1795fafe39ee4109a1f84e3f82e96436a3f8e2c74ef1a665b0daaa459c7a80757b52c905e2fb4e30c4a3f882e87bce35d70e2925a1671205c28c89886a49e045e31434abaab4a7aed077ff22c",
|
||||
"84cb6ec8a2da4f6c3b15edf77f9af9e44e13d67acc17b24bd4c7a33980f37050c0301ba3aa15ad92efe842cd3ebd3636cf945bb1f199fe0682037b9dacf86f162dadabfa625239c37f8b8db9901df0e618ff56fa62a57499f7ba83baebc085eaf3dda850835520344a67e09419368d81012168e5de5ea45158397af9a5c6a1657b26f319b66f816cd2c28996547d697e8df2bb163ccb9dda4d6691dffd102a13667ab9cde60ffbfb872187d9c425a7f67c1d9fffff9276ed0aeb",
|
||||
"6a52c9bbbba454c14540b2be58230d78ecbeb391646a0c6fcce2f789086a78364b81ae85d5396d7cfa8b46bda41e3083ec5cf7b4c47dc601c8a697df52f557defca248506dbebab25657f5a561d09625b7f4b2f0119a12beeac087efc9d350a735c35d2431c1da7dda99befb17f41a3dc4da0f00bb95366be128538ce27763d81f832fe3c1d4efc07b5b08ad8dc9e65fb5e48546664e18cb2d3bb3fe1f56fa7aae718c5e3bbdeaf70e15023f6a25b72a2d177fcfd04211d40664fe",
|
||||
"c3c4d3b31f1f5f9538923df3478c84fffaef411520a542da9a220ee4132eabb9d718b5076fb2f985485e8ba058330aed27ddfd3afa3db34aa60301088caec3d0053828c0c2bc87e2e61db5ea5a29f62fdad9c8b5fc5063ec4ee865e5b2e35fac0c7a835d5f57a1b1079833c25fc38fcb14311c54f8a3bd251bca19342d69e5785f9c2e43cf189d421c76c8e8db925d70fa0fae5ee3a28c4047c23a2b8a167ce53f35ced33bec822b88b06f41558c47d4fed1bfa3e21eb060df4d8ba1",
|
||||
"8d55e92136992ba23856c1aea109766fc44772477efc932b3194af2265e433ed77d63b44d2a1cff2e8680eff120a430fe012f0f09c6201d546e13ad46fc4ce910eab27bb1569879abed2d9c37fae9f1267c2216ec5debcb20d4de58461a621e6ce8946899de81c0add44d35e27b7982a97f2a5e6314901caebe41dbba35f48bc9244ca6dca2bdde7306435892f287036df088633a070c2e385815ab3e2bfc1a47c05a5b9fe0e80dd6e38e4713a70c8f82bd32475eea8400c7bc67f59cf",
|
||||
"5016284e20362610fa05ca9d789cad25f6d43263787e7e085476764ce4a8908ce99b262b375e9d106170b1bec1f473d5e777e0c1896533040e39c8c1465e07907ef5860e14e4d8310013e35f12090e0bfc687474b1f15f3dd2033a0edac5246102da4deec7e188c3517d84d9c2a0a4497a4c5f82a30f1ba009e45ee6eb3ab4368c720ea6feee428ffd2c4cc52debb8d634a64176572c72368f94a66689f23f8a01218f532117af5a8060d140e7ca435a92882fcb5630ebe14a4805f1dc83",
|
||||
"05456ec59b8d41bbd736727976b96b38c43827f9e16169be673ff37870c2ecd5f0d1ea1a136be4cc7b047a02a4421d484fd2a12ece418e42ee391a13a0b1df5a0162b29ab70d3fe3e04ba6ab26b37d62b7cf05a5e2f033611bf970b8e1f30e198e483e740fa9618c1e8677e07b61296b94a9787a68fba622d7653b5568f4a8628025939b0f74389ea8fced6098c065bf2a869fd8e07d705eadb53006be2abb716a3114ceb0236d7e916f037cb954cf977720855d12be76d900ca124a2a66bb",
|
||||
"eb6f60b83fcee77060ff346aaf6ec34d82a8af469947d3b5074cde8eb26566eb1fa039bcc707738df1e95869bd827c246e88436f0614d9834ead5392ef376105c4a9f370071cdeaaff6ca0f18b74c3a48d19a717253c49bd9009ccbfdd5728a08b7d112a2ed8dbafbbb46d7a75dc9a05e09bfde1a0a92d74a51887f9d123d7896e9f9d0057b660ed7d55454c069d3c5260411db4cdc67e7b74f680d7ac4b9dcc2f8baf72e15e6b3cafebcdf449a6436ed2c398b675f79c644747c57553bf7ea2",
|
||||
"187a88e88514f6c4157c1ba40b442baae1ae563a6c989277443b12a219aa484cb9fa8adbb9a29d429f50155321b15664926317477079c7060dfdaa84c1d74bba78892c34e6f21ad35208d2ae622012401696bff5cd57b6485944b3db7b9071fa5f57fbfb1085d91bb9cff5808d662cdc6c8157249478262c44b7fbc397ed42a4977b202e817717bfccc9f0467294062313f7705251ed09573f16d23429361fada259dfb300369c4198f07341b38e84d02cdb74af5de6aab1fc2026208ea7c418c0",
|
||||
"be31bc96606d0fab007e5caeded2f1c9f747c759777e9b6eef962bed49e45a1d4fc993e279d024915e600865ecb087b960584be18c41114d3c43f92169b9e0e1f85a0ebcd4e196376ccdc920e66103cd3b1c58407d0aafd0e003c4e341a1daddb9f4faba974362a32f35db83384b05ae8e3322d728893861afd8b1c940de5a17f691e763ce4969b6d94f67fb4a0235d100225bd8602f291388f0ca4a568748ad0d6040f1262eac2aede6cd27419bb78a394c1ffad72c262be8c3f9d9619d633e51d0",
|
||||
"4d83d85ca838b4518588f2a90228a4dd18f14dd5b4c012d26298a97d848abbd825d221d02cceb6e8c701b4ad00e1dee4889b5c533e4bb60f1f41a4a61ee5478be2c1b1016c30345afd7a5253668260515e70751f22c8b4022d7fe4877d7bbce90b46531507dd3e89549e7fd58ea28f4cb23d33662bd003c1345ba94cc4b06867f778957901a8c441bee0f3b12e16463a51f7e50690356971dd73a686a49fda1eae46c9d54fba262811d698025d0ee053f1c58591c3bb3cbde69de0b31549ef5b69cf10",
|
||||
"cdeb07d36dc5f9a1cd717a9e9cca37a2ce93caa298eee63571f7d6c5fde2a11c666cf53cf2dcb41ca2ea2319e7230ca68e38c647905928713a13982bf47fe33d7095ebd50b2df976208920a43eb2e29b942f32467403c45cea18bf44e0f6aeb155b48a8e5c471fec972a9d62f7ae093d2758f0aaec7ca50cb4725bfa219f1a3a46ad6bde7361f445f86b94d66b8ece080e56c510250693a5d0ea0ae87b4421860b853bcf0381eae4f1bf7c5c0472a93ad18407bc88475ab8560d344a921d3e86a02da397",
|
||||
"a598fad52852c5d51ae3b10528fc1f722e21d44fbd42ae5acdf20e85a28532e646a223d27fd907bfd38eb8bb75175636892f8242877aab89e8c0824d368f3339ce7a82aa4e5af6db1f3b588a4d667a00f67bee37cfd2724dde06d2909fb9e58d892f4cfd2c4ca85acdf8256f5458b030a6bda151154ff2e6d7a8da90b54a2884c8a99fab5a4ac211ff23dc0975f4f592fd1b6b9dc7783bdcd2d4ca4e68d2902f2013e122cb62e2bff6b0a98ec55ba25837e21f1cfe67739b568d43e6413dab2bd1dc471e5a",
|
||||
"17b68c74c9fe4926e8102070916a4e381b9fe25f5973c9bd4b04ce25749fc18931f37a65a356d3f5e5a1ef125d546f4f0ea797c15fb2efea6fbfcc5739c564693d47adeb12dcb3d98a2830719b13247792cb2491dca159a28138c6cff925aca42f4fdb02e73fbd508ec49b25c60703a7595a3e8f44b155b371d525e48e7e5dc84ac7b17c52bf5e526a67e7187234a2f19f57c548c70fc0b27183df73ffa53fa58b658034c896fa791ae9a7fd2620f5e46ce84c842a6e60e9324ae4db224ffc87d9617cb85ca2",
|
||||
"b9e4267ea39e1de1fed0579f93bb351007c9f8fcdd811053fae33f09e2753d7428f04e1a9efcd45ea701a5d87a35b3afb2e6b65365dee6ead0bbb611b7797b212ac688653f542e604a39df277f12514ddfee3b4e27b98395c2cd97a203f1f1153c50327965770802ec2c9783edc428271762b275471e7ac65ac36523df28b0d7e6e6ccc7674268a132a63411fc82c0738dbb68af003b769a0bf9e6587b36476cb465350fee13f88ea355d47ffac7b0f964f4139db11b7642cb8d75fe1bc74d859b6d9e884f75ac",
|
||||
"8ca704fe7208fe5f9c23110c0b3b4eee0ef632cae82bda68d8db2436ad409aa05cf159223586e1e6d8bdae9f316ea786809fbe7fe81ec61c61552d3a83cd6beaf652d1263862664df6aae321d0323440430f400f291c3efbe5d5c690b0cc6b0bf871b3933befb40bc870e2ee1ebb68025a2dcc11b68daadef6be29b5f21e440374301bde1e80dcfade4c9d681480e65ec494a6af48df232c3d51447b9d06be714949249c44c43cf73ed13ef0d533e770284e51369d94ae241a5fb2f163893071b2b4c118aeaf9eae",
|
||||
"4fd8dd01012bb4df82bf42e0683f998e6f52dd9c5617bae33f867d6c0b69798cead8179346d70acc941abbbdd26e3229d5651361d2252c72ff22db2938d06ff6fc29a42fdf800ae967d06479bc7bbb8e71f40b1190a4b7189ffc9a7096cdb76d40aec424e1388e1eb7ef4ac3b34f3f089da8fda7d1927f5d775c0b2801d22dd1265c973158f640cec93edfed06dc80b20ef8c496b98289d54d46ccd205951cbb0f4e7daeb866b60bacb483411e4382b6f04d472843186bd0e31fbaa93e5c901ec028efafeb45fc551a",
|
||||
"e9ee1b22b04b321a5fdd8301627011f583887d77560fb0f35552e207561f81e38ac58a0d0aeaf832d1ee72d913720d01f75574e9a321864fe95f4d0d8f0b8db97649a53e71e940aede5c40b4b9105daa42a6fb2811b61209247534cbaf830b07abe338d75d2f5f4eb1c3cf151e9edabe2c8d5f6fff08fac1495ef48160b100d30dcb0676700bcceb28723a29980ab0766a93abb8cb3d1963007db8458ed99b689d2a7c28c788743c80e8c1239b20982c81dadd0eed6740c65fbc4ef15c7b5569cb9fc997c6550a34b3b2",
|
||||
"ec01e3a60964360f7f23ab0b22e021815765ad706f242265ebc19a2bb9e4eac94393952dcf61aae47682671a10f9165f0b20adf83a6706bfbdcf04c6faba6114653a35584267267873291c6fe7ff5f7695243143421509502c8875aafa9e9afe5be5ef2c851c7f35d69be5d3896000ccdbbfab5c238bb34d607cfe2d55d748880545b4aa7ca61137992925189025c62654b1f20d49c3ccd75aa73ce99cd7258dabedd6480a9f5185531fc0118beb68cc0a9cd182f6973287cf9252e12be5b619f15c25b65c71b7a316ebfd",
|
||||
"db51a2f84704b78414093aa93708ec5e78573595c6e3a16c9e15744fa0f98ec78a1b3ed1e16f9717c01f6cab1bff0d56367ffc516c2e33261074935e0735ccf0d018744b4d28450f9a4db0dcf7ff504d3183aa967f76a507357948da9018fc38f150db53e2df6cea14466f03792f8bc11bdb5266dd6d508cde9e12ff04305c0295de29de19d491ad86e766774bb517e7e65befb1c5e2c267f013e235d8483e177214f89978b4cdc81aa7eff8b39f2825ad3a1b6ac1424e30edd49b067d770f16e74dd7a9c3af2ad74289a676",
|
||||
"00e40f30ae3746edad0f5dd03d0e640933cf3d1694804c1e1ed6399ac36611d405196ee48f129344a8512feda16a354517871322bd5d9c6a1b592933eab531923efb393ffb23d9109cbe1075cebfa5fb917b40df028a621460ff6783c798792cb1d9635b5a6f84ec13918fa302924649b5c7fcb1f7007f0d2f06e9cfd7c27491e565a96c68a0c3644f92cd8f38857258c33801c5d537a83dfe583cba59d7eec7e394199c0a2660a62fabe3ed2099d57f315a6cd8de1a4ade29d977f15d65759cff433e5ac0c182aef3761163e1",
|
||||
"3c5ea24d0d9b618294a263f062b2414a722be4eb10dfc346a6ec3b821d7396eba61cd6ef33618b04cd087a811f299d4606820227f16000d7c839062b96d3e3f59cd1a082448d13fc8f56b3fa7fb5f66d0350aa3b72dd7c165d590282f7da2e12cfe9e60e1796122bb8c2d40fdc2997af634b9c6b127a893dfb3467909378300db3da911be1d7b616bb8e0572433e65527e15d936500a2c60e9f9909dcf22ab5e4b6700f0238c205b4a813626fac3d945bab2637fb08203044a73d20c9a3fcf7c3fc4eb7807c3276dd5f73ce89597",
|
||||
"9271aeeebfac46f4de85df78f1bfd36136aa8905e15835c9e1941176f71e3aa5b1b131843d40479735e23e182a2bd71f66f6149dccb7ed8c16469079dc8590bbf165374951785f4531f7e7361de62f936cfb23a2b5bdf186632e7042a0dd451fdc9b7208f923f3a5f250ae590ec348c63a16c3aacaf7379f53b5dd4152dcd40d23e683e2156e64c592ffc07e2cd6bbeebef4dd590b2f6b2bcbf08fcd111c079f5c4033adb6c17574f8756ecd87be27eff1d7c8e8d0324438d59ae171d5a17128fbcb5533d921bd044a2038a5046b33",
|
||||
"4e3e533d5bcb15793d1b9d0468aaee801f32fdb486b11027183553a09ddbee8213924296f2815dc61577297459e834bf1c7a53f87d43782209e589b8295219ba7073a8fff18ad647fdb474fa39e1faa69911bf83438d5f64fe52f38ce6a991f25812c8f548de7bf2fdea7e9b4782beb4011d3567184c817521a2ba0ebad75b892f7f8e35d68b099827a1b08a84ec5e8125651d6f260295684d0ab1011a9209d2bdeb75128bf5364774d7df91e0746b7b08bda9185035f4f226e7d0a1946fcaa9c607a66b185d8546aac2800e85b74e67",
|
||||
"b5d89fa2d94531093365d1259cc6fe8827fea48e6374c8b9a8c4d2209c280fa5c44958a1847222a692a59e6aa2696e6cdc8a543dd89b0ce03bc293b4e78d6ef48e1839694ccd5c65661143095c705b07e3ced84a0f5959114dd89deb956ab3fac8130eb4a878278205b801ae41a29e34146192308c4e759b374757b0c3b00319bce92a1b95a4d2ee179fd6714ff96155d26f693a5bc973f84ac8b3b91e3926276297532d98b46992a3f104c08100bf1671c43134bac280c617da711e90a0100137525375ebb12802a428885ae7fce6514a",
|
||||
"40e3d8048fc10650cb8a7fc2e7113e26dec34f9ca2d5129cd10a8e8e44d113d61ee48c7d003e19fd307fc6debd70feb30243f298c510ccc4418355ce143066f067ad7c6de7288c3080e7ad46a23c8d34deb55a43e652fe90444ad3c57d3ec1e1c489d63ef915a24bc74a7925a0a7b1e1523f21ca8fee78df24e3d0a68d0013423db97c280799a0618229c0f2c167289a891e5c8d6661ab21285951c31710e3b5fe55f6347fe16d9b40507948a59252efeb616df83e5c098b07d0a7247cd371daff0e50491c582503fd89f79ba94d6af9ed76",
|
||||
"1fa444de01dd3901e2b4684e3d7a799ffa02d85afd35fb30fe4c9d672837bee6dd8a3b8608b4bb5e589220ad5a854f46b46e41c6d57ad124a46beab4169ff69fee7e3838a6165e19dad8eb5d7bf53d4edd3cd2769daf219510a02fdd2afe0c0e1da3cd30fcd1aa88b68965586f07a25a1720fbd90a096ea30fc8e945e3637d7857c8a9c0ab4154ffb2000e57b5f9adfa4e4eaf8065bc3c2b2e75f495963325588785a6ce417dcddffd299873b15dcccca128d63cd4eeeadb64cda28099a9ad7c80d34844901f26b88b00b9aafeb2f90286d29d",
|
||||
"fde0a0d9d813983bd1f55cf778a003a2023b34a555322ab280584537bc6bdd844d22a7d6066c18da83ec09f3d8d5a1aab4be0d5ce19b436052f6e259a4b49017a1f47f1fe2bf115d5bc8599fb216351c60dd6b1bedb2e6f4dcadf424b833501b6f099cbfad9e2290680fb69c25032b42a6274f7cb9b5c5950401354838a45f7cb77b95bf54718e2f3d3d9fb91eb2311903980277396398d9736d8e92fd838594ac8a537c6c529db5a8a4f89290e6ba6f20ac0e5ed6fef40901d0e0e8e3e502990811f9acaae555dd54eb1bcd96b513e2fe751bec",
|
||||
"9f8e0caec87858599f5ab29bff86da78a841a918a023a111098687ecdf2747612d3f3809d9ca400b878bd4f92c43a1004f1c17c7f19a3cd1ce449bd2b23aff551623c37dd8c0be56bf3fd857b500c2b9f9ccea62481944090a3cf3b6ee81d9af8eeb60f65ef150f9fa4d3ed6ce4762d3d4f174ee8ccd460c25cafac0ea5ec8a6a4b2f9e8c0520cb7061155e532cb65f188b01e4b9086db951f504b060c296b326b3fc1c590498ecce594f828f4a10ea416675720ae505295d38a791bd0e93f428448a8f4c1fc0af53604a9e8255384d29ae5c334e2",
|
||||
"33d1e683a4c97ee6bbaa5f9df1a88cb53b7f3c157b6045d70a56fda0ccbd3a1fa1f049cd564da072b53f415bf5fb843771c1d2551fd075d33377362b2f7c0645f9723123d11975991db8a2b518f02e2c7c30342a044754290bae2c77496d755e5981f12e6b0a0174280b958bf11ed628a9062775993ced04bf752ea8d165e3ac2177d7cd1b9371c44efa98f0b3e68602a839d384eec007979f46429dafb138cbc231ad928a9f65f7d66fac77416395e8f1debaaf76ec2e4e03e8674102cd26f614739f3ec9f949033df1fb97e87c2326d65aef94ed5f",
|
||||
"180048f09d0b480887af7fd548a85abf605440c1ddde6afe4c30c30670233f7bf928f43b4681f59279ebbda5e8f8f2a1abefdee129e18ac60f9224e90b38b0aabd01308e0a27f41b6fb2ee07ee176ec9048c5fe33c3f7c791469c81f30e28170585b9f3e7e3c8c2e9d74370cb4518f13bf2dee048cbd98ffa32d85e43bcc64a626b40efb51ce712925fdd6fee006dc68b88004a81549d2121986dd1966084cd654a7c6686b3bae32afbd9625e09344e85cf9611ea08dfce835a2e5b3726e69ae8a76a97db60fcc539944ba4b1e8449e4d9802ae99fae86",
|
||||
"13c0bc2f5eb887cd90eae426143764cf82b3545998c386007cca871890912217aa143ac4ed4ddb5a7495b704aa4de18419b8664b15bc26cfc6596a4d2ae408f98b47a566476d5802d594ba84c2f538def9d016661f6404bb2337a3932a24f6e30073a6c9c274b940c62c727242e24466084a3ea336365d71ea8fa6499c0ea8d59eea505f1126b99c795023c4963aa0d99323d0391e8701110edf551b2d3799e1063ca443f1add162156e445502ca1a052fe70c289838593b58839fc63de128a03e2bbf389e22ae0cf957fd03315ee407b096cc1cfd92dee6",
|
||||
"6f1eb607d679efef065df08987a1174aab41bdac8aece7726dfa65805d6fff5b3d17a672d96b770dc32165f144f0f7324822a5c87563b7cd9e37a742ae83ef245d09006d91576f435a03476f509ea2936636232f66aa7f6cdf1ac187bbd1fcb8e20f8791866e60ed96c73374c12ac16795e999b891c64507d2dbd97e5fc29fac750ad27f2937cbcd29fdafccf27ab22453834d475f6186eaf975a36fad5c8bd61c21da554e1ded46c4c39765dcf5c8f5ccfb49b6a4dc562c919d0c7d8940ec536ab2448ec3c9a9c8b0e8fd4870cad9de2577c7b0c38563f355",
|
||||
"dcdd993c94d3acbc555f464871a32c5da6f13b3d5bbc3e34429705e8ad2e76393fdd96a69a94acb652f5dc3c120d41187e9aa919669f727c4868013b0cb6acc165c1b7706c52248e15c3bf81eb6c147619467945c7c48fa14a73e7c3d5bec91706c567145342a026c9d97eff97ec672c5debb9df1a998083b0b0081d65c517b3e5634c95e347e781aa30ca1c8af815e2e494d844e847fdcb41622894a518dc36571123a40bfdbe8c4f4cff44d83c61dd9dcd24c464c53b395edb31efee9f3aa080e87cdc3d22d613ae84a53c9249c32c96f9a3bc4629bb126a70",
|
||||
"49971f9823e63c3a72574d977953329e813b22a8387cd13f56d8ea77a5d1a8a20012632d1d8732bbcb9f756b9675aab5db927beacab7ca263e5718b8dfa7b2eed9a91bf5ed163b16139d45f7b8cc7e3f7bdda6202106f67dfb23b7c315ee3e17a09d466b1e6b13e7c7428184a979f5358667b4fa8bd40bcc8ea46058db44587a85377ac46bf155136c09ac58cb6c27f28e17028c91e7e8f74d5b500e56293b316974f02b9d9ea205d9b6ac4cfb74eb8eb0c944577fd2f41316368307beab3e327bf7dbaa0a4428836ec4e895dea635234abeaf113ceeadac33c7a3",
|
||||
"c57a9cc958cee983599b04fe694f15fb470fcbc53e4bfcc00a27351b12d5d2434444253ad4184e87b81b738922ffd7ff1dc1e54f39c5518b49fb8fe50d63e3935f99e4bd125e8dc0ba8a17fd62de709339a43fabe15cf86d96a54010112170c340cfac4132182eed7301402bc7c8276089dec38488af145cb6222525894658f03501204b7a66aba0be1b557b28a2f652d66f7313ed825ecc4d8596c1be7420d4425b86a1a90a5b7f30d0f24e0d1aae0eb619ca457a71699e44be612a4011c597ee80b94d5507e429d7fc6af22579cd6ad642723b05ef169fade526fb",
|
||||
"0568a672cd1ecbaa947045b712e2ac27995392fbef8f9488f79803cbee561c212287f080eca95adb5ba42739d78e3ba667f06045d87850d3a0499358649caa257ad29f1a9c511e7054db20554d15cbb55ff854afa45cae475c729cea72ede953522031865bc02b95589ed4d9841c552a8cc94904a93ed09ed77222f6c178195056be59bc4e96a815adf534e6b466fb47e262ff79c803c157a21b6e2269c2e0abeb494113cd868d8466e82d4b2f6a28b73645853d96bc9242515d803e33294848d3fe42fdff68da53c03491636beede47ff1399dd3d54a5e914d55d7adf",
|
||||
"3f19f61a4cd085796731ac9f85a75a8bce77031932c31762d87d8b8d07b8bd19ff78d6b7d1bd1e87f3a4f41aad03b6c4d17a6cbc86be55f7c8b88ada047bb04f8d49f1c34bcf81cc0f3389ad01a758fc7eeb0072aa9ad1481992bfdde82e438e75590a4423832dfbe3756e2229ea873bc3606e6d72174cb2163bf40b5d49c81009dab85ecc03e311351bbf96e32c030a2b276a7698cb25bc2c967acb3213161a1fdde7d912cd6a804490f8056c47da1333f6e35c41e749c2c23919cb9af5eec5652e6e072b034fb1682e9aaa194a9c0bd456ea0b008d14dbce37967a7a8e",
|
||||
"705f98f632d99d3651793825c38dc4deda56c59eac539da6a0159c83131cf8ab6f2ee0c3b74111fde351f7aa1a8c500a0cecab17c212d2c58ca09eae608c8eefc922b9902ef8d6832f799ba48c3c28aa702b3242107edeba01daafe424406a3822965056cfe8783455a671e93b1e2eae2321364f1871471c82124df33bc09e1b52882bd7e1c4c7d0b2f3dd4a28c2a002a43246768af0700f9659de99d62167be93177aabf19d678e79e9c726ac510d94e74873eda99620a3961930cd91937c88a06d8153d64fd60da7ca38cf26d1d4f04a0df273f52127c53fdc593f0f8df9",
|
||||
"ea6f8e977c954657b45f25480ff42c36c7a10c77caa26eb1c907062e24fbca5aebc65cacca0de10abea8c78322f08672e13d8ac16996eca1aa17402eaea4c1cc6c800b22dc18cb8d620192d74bac02c07b5cfa61e513c7f28b7e29b9700e0e442720bf4c669d4995da19d19f841d9eb68cc74153592591e3bf059ef616b95305aa453b32fe99a91afb35bd482cf2b7aa42702837a53be3c38883d2963020e347556f841254ec6b85854485fe8c520b05f2ea67a9bf3981555c20991e2bacd4db5b418228b6002d8d41c025cb472bf5443aaa885974a408ea7f2e3f932c600deb",
|
||||
"408190134ed06556811b1af808ab2d986aff152a28de2c41a2207c0ccc18125ac20f48384de89ea7c80cda1da14e60cc1599943646b4c0082bbcda2d9fa55a13e9df2934edf15eb4fd41f25fa3dd706ab6de522ed351b106321e494e7a27d5f7caf44ec6fadf1122d227eefc0f57aefc140d2c63d07dcbfd65790b1099745ed042cfd1548242076b98e616b76ff0d53db5179df8dd62c06a36a8b9e95a671e2a9b9dd3fb187a31ae5828d218ec5851913e0b52e2532bd4bf9e7b349f32de2b6d5d3cdf9f372d49617b6220c93c05962327e99a0480488443349f0fd54c1860f7c8",
|
||||
"5f9e5c6f38573a85010a9d84d33f29c057003b2645e3ea6f72cbc7af95d197ce6a06b13fea81722853e6991791b8b15091cd066f5ed913592ed3d3af5370d39ba22beeb2a582a414b16824b77e194a094c2afdcc09aa73ce36f4943cca5ae32c5017dc398801dd92a47382d9327c9f6cffd38ca4167cd836f7855fc5ff048d8efba378cdde224905a0425e6b1de061fc951c5e624a5153b008ad41160a710b3ff2081748d5e02deb9f841f4fc6cf4a15153dd4fe874fd447482696283e79ee0e6bc8c1c0409baa5ab02c5209c319e3169b2476149c0c6e541c6197ca46e004eef533",
|
||||
"218c6b3508aec69574f2b5039b30b942b72a8349d05f48ff945bbbe5c8957d5a6199492a6bf54bab821c9377e2edfa4c908384664d2c80112d5e805d66e0a551b941021be17dd20bd825bea9a3b6afb1b8c605805b3bda58750f03ea5c953a698494b425d8980c69f34d1c3f6b5866e8717031152a127215c256e08873c21b0f5cc85875d0f7c94601659150c04cd5fe5d381ba29983a2d94fcd3a65a94c53c7279cd000dddd4253d8cff8d7f6ace10247fe3bc30d63ba4bb54f557b3d22a3924369430d71ab37b701e9500bda70b5a643704858beed4726a889b6c9c91584194c68f1",
|
||||
"dac26aa7273fc25d6e044c79fc2bfa46e59892a42bbca59a86826c91e76ab03e4bd9f7c0b5f08d1931d88b36ea77d94f7ba67cd4f1d3086e529427201119096ae066ae6f170940830ed7900de7bb9d66e09788287403a4ecc93c6da975d2fb08e918840a236c15f5d3a8f7375c2eeebbf6f01a6e7f29ca2b8d42df158414c320777433663c59fdcd1f39ca68e3473db721be7ce8c6dba5fddc024f94fedb286b0477581d451313ca8c737484daf60d67f9b2d56d4bcc271f7e9ae958c7f258efbc74d25753e0516f28282461941bf2dcc7dd8c7df6173b89760cefcac07190243ff863fb",
|
||||
"c46e6512e6797cc7a54254a1b26b2de29aa83d6c4b1ea5a2786fbcec388270625b12635eae39e1fba013f8a65219421bca8b52a8ddfd431cda60299bdf160734d5a7450ec79620058522702174ae451b9bfa7c4a455fbbee3e1d048c7d4bac5131018228f137c8e130440c7059b4f15eaa34ce872a851a16ce86f982df78a00be4d564da2003a450ddee9ab43ea876b8b4b65c84f0b39265fd5456417afb5bc54997c986e66fc222f2123ba5e719c4d6b9a177b188277df384f1125821cf19d5248cef0be183ccdc84ac194506f740ed2188b2689ea4c9236a9e9e3a2fff85b6af4e9b49a3",
|
||||
"1ccd4d278d67b65cf2564ecd4de1b55fe07adc80e1f735fe2f08ea53fd3977323689122c29c798957abaff6aba09bdcbf661d77f4dc8913ab1fe2bef38846166e3834785e7105d746484eff8c656af5d8c7854abc1c62b7fadb65521dc6f793d978bda9838eb3800417d32e8a24d8c8cb1d18a5de6ca79d9e1b0ff9aa25e6218fe944cf18666fecc1e31334b390260dbe0997539e1b02f6366b2aea4f4a21efe04f4b97568fcb39e59919d5ebac6543d5d0f48fc66b923c34aac377dc95c20329b837b6ed5e8d9a3d2089cd0d8f025658006ff41cbdaccca618822ca590ab155253f8bc1c7f5",
|
||||
"9875209588395ee3c9fdd793fd48717cc84c8c3ea622b2ccc4a1be4448e6034b7810569855255031f10be5ffd714b05f9ce01972d712d40abf03d4d0ce175813a7a668f761324996093fc2aa5912f7fc2abdadd8775d2b4d9ad492216293381460ed8f6db3d641d1525f4242c348bbfe504c704f215dc461de51b5c75c1aae967936963848f16c673eca5e78dfd47eb19001d52d1bcf96c98956dad5ddf594a5da757e7ca35f2f69803b784e66ac5a58b75c228b8266ec592505e5d1ca87d81225738855f15bc0914677e81593fd409e77d159f8a908f67788de9eb06c5561547aada96c47c535",
|
||||
"40c90e375e366f3756d89091eb3eed9fe0fbfc5638700af4617d358812bac53124a2205dd6756456787d49cd6a35e302479a0992288f47532e4ea7ab62fc5ad5adc690a5d9a446f7e035ad4641bd8dae83946aee3338ec984ccb5cc633e1409f2531eeffe05532a8b0062ba99454c9aeabf8ecb94db195af7032bfebc22912f49d39330add47ff8fa5720612d697f0b602738930e060a1bb214efc5e292224cf34e29deaea6b1b1ff847e94ecc997325ac38df61db45d82bf0e74a664d2fe085c20b04c39e90d6a170b68d2f1d373f00c731c524456ada73d659aaac9df3191a7a3865083343fc13",
|
||||
"e8800d82e072210ca6d7fa2472028974780b76aad4bcb9ad362422dd05ae3232668251d164daa375a43b26a38cce28dbeb3dee1a4a579f70d0fe7febb29b5ece8aa836e050fb3d188c63aa9c3c0da6c717d86458a6096b5effceb964efdec7035960c09ccd10dea3c5f1c7f9f478d5887ebbe2e15c5ff85dbacbc444bb951c4eec7abecb89ed80187e409e2972ffe1a5f01562af109f2cf09471cf72cf83a3bb8f4e2ef38ed0e326b698296394e5b2718a5000c01425708e8ad0461e62462d8819c2377f13ab1be2c7c9f33dc06fe23cad27b87569f2ce2e56e4b2c60c7b1b3d370841d89ebdc1f192",
|
||||
"796d6d1447d5b7e8c55cd8b2f8b7010db39f27565f907e3fc0e464ea2d4bb52b37f10e7c6dcfc59231b9cdee12c32aeb4adbc42b86e86eb6defb5b69e6ca75e1f4d0dae3e124e5a1b8b6697f7e10b0403f1f0a5ff848eef3752837a9ba17780f16a9a709188a8d5b89a2fa74adb2e651163b1c2b3d261e225c9158dcd9eb7ac3d6704cee290cdff6bcb3cb90cee030aa0d19d4693655c3c30ac6fc06d2ae37787c47126d57ed9a6bef5f8a6c56859aefc08755739a95aac57a4dd916a92ba9f3afbf969df8085949615033365c751a9a3e1a18cee98a69d22e64009bebf8307169b6c61de0617ecfafdf",
|
||||
"4f9057183566153cf337b07c3f5556006de54c56b2a1e5326c07aaeabd1886ec6f1641358925db232b2f0dbf75229c796a7395b2f934c1f99090bec1123f3c841b1cb3c5b1ec42ed5408f2940f0c48a9470b852c46d6557853d459cecd2c32bbcd8ee21fa11e385eef0857cba4d8545a61b52a484cdd779db4739fbc7aa9860dcabe0488b98fa0b60c3f7d6153db279000a52ffb573dab37d2ab1896a90e5deb7ac6bbe56239085c325d83a917dc6e8a448425b718c2356b9f3066163555ec444f372e184e02c8c4c69b1c1c2ae2b51e45b98f73d933d18750968945ca85d6bbb22014b4c4015262e3c40d",
|
||||
"79dcca7d8b81a61359e4aece21f3df7b99518ce70bd2f57a18bab5e7114af2add0a0cea7f319d69f231f060e0a539d9a23fb3e95451ce8c6340cfb09edf931df84203a39226dd9eb278f11b691ef612585b973daab373e65d11325898badf6732100371fd759960fa8fec373268421d28bffdb9b12a430b92fe4b07566ca0c89e616e49f8fc75ccd9cdc66db820d7c02e109aa5ed86b89770262918a518f90a2292f6b68d68ae03992e4259a17a23c84ec2a417f082b5abf3a26e44d2278ecb8ba9456965303a75f25394d1aaf5544590e74b14d8a4cc4050be2b0ebcfe4d2db6b12a02c68a3bcdda70301f3",
|
||||
"848755dc31e25e9a42f9ec12d847d19f292c14c162c9aba49e972cb123b58b8e57bb263a923929833373858594ff52dbc298dbbc078599194e4c07b0e5fc1e10808bbacdb6e93c72b333685cf961f28eb0d5a395c63266b01f130d25db384b356e5da6d01042fc2359581b89c63b3bb2d1ce897fbc9e83fe85d9666cb60e6a8c657f70caad5387b8a045bf91095606802c8424ea8ac52ef29386dc46183378a5fcb2cb927428b8c070f1c42aafd3bc70ca25437807696a46873cfeb7b80ba2ebc3c4272443d445e46343a1465253a9eebd532a0d1d2c18264b91ff45159f245404ae9335f2af55c802772426b4",
|
||||
"ecaa6e999ef355a0768730edb835db411829a3764f79d764bb5682af6d00f51b313e017b83fffe2e332cd4a3de0a81d6a52084d5748346a1f81eb9b183ff6d93d05edc00e938d001c90872dfe234e8dd085f639af168af4a07e18f1c56ca6c7c1addffc4a70eb4660666dda0321636c3f83479ad3b64e23d749620413a2ecdcc52ad4e6e63f2b817ce99c15b5d2da3792721d7158297cce65e0c04fe810d7e2434b969e4c7892b3840623e153576356e9a696fd9e7a801c25de621a7849da3f99158d3d09bf039f43c510c8ffb00fa3e9a3c12d2c8062dd25b8dabe53d8581e30427e81c3dfc2d455352487e1255",
|
||||
"23a3fe80e3636313fdf922a1359514d9f31775e1adf24285e8001c04dbce866df055edf25b506e18953492a173ba5aa0c1ec758123406a97025ba9b6b7a97eb14734424d1a7841ec0eaeba0051d6e9734263bea1af9895a3b8c83d8c854da2ae7832bdd7c285b73f8113c3821cced38b3656b4e6369a9f8327cd368f04128f1d78b6b4260f55995277feffa15e34532cd0306c1f47354667c17018ee012a791af2dbbc7afc92c388008c601740cccbbe66f1eb06ea657e9d478066c2bd2093ab62cd94abadc002722f50968e8acf361658fc64f50685a5b1b004888b3b4f64a4ddb67bec7e4ac64c9ee8deeda896b9",
|
||||
"758f3567cd992228386a1c01930f7c52a9dcce28fdc1aaa54b0fed97d9a54f1df805f31bac12d559e90a2063cd7df8311a148f6904f78c5440f75e49877c0c0855d59c7f7ee52837e6ef3e54a568a7b38a0d5b896e298c8e46a56d24d8cabda8aeff85a622a3e7c87483ba921f34156defd185f608e2241224286e38121a162c2ba7604f68484717196f6628861a948180e8f06c6cc1ec66d032cf8d16da039cd74277cde31e535bc1692a44046e16881c954af3cd91dc49b443a3680e4bc42a954a46ebd1368b1398edd7580f935514b15c7fbfa9b40048a35122283af731f5e460aa85b66e65f49a9d158699bd2870",
|
||||
"fe511e86971cea2b6af91b2afa898d9b067fa71780790bb409189f5debe719f405e16acf7c4306a6e6ac5cd535290efe088943b9e6c5d25bfc508023c1b105d20d57252fee8cdbddb4d34a6ec2f72e8d55be55afcafd2e922ab8c31888bec4e816d04f0b2cd23df6e04720969c5152b3563c6da37e4608554cc7b8715bc10aba6a2e3b6fbcd35408df0dd73a9076bfad32b741fcdb0edfb563b3f753508b9b26f0a91673255f9bcda2b9a120f6bfa0632b6551ca517d846a747b66ebda1b2170891ece94c19ce8bf682cc94afdf0053fba4e4f0530935c07cdd6f879c999a8c4328ef6d3e0a37974a230ada83910604337",
|
||||
"a6024f5b959698c0de45f4f29e1803f99dc8112989c536e5a1337e281bc856ff721e986de183d7b0ea9eb61166830ae5d6d6bc857dc833ff189b52889b8e2bd3f35b4937624d9b36dc5f19db44f0772508029784c7dac9568d28609058bc437e2f79f95b12307d8a8fb042d7fd6ee910a9e8df609ede3283f958ba918a9925a0b1d0f9f9f232062315f28a52cbd60e71c09d83e0f6600f508f0ae8ad7642c080ffc618fcd2314e26f67f1529342569f6df37017f7e3b2dac32ad88d56d175ab22205ee7e3ee94720d76933a21132e110fefbb0689a3adbaa4c685f43652136d09b3a359b5c671e38f11915cb5612db2ae294",
|
||||
"af6de0e227bd78494acb559ddf34d8a7d55a03912384831be21c38376f39cda8a864aff7a48aed758f6bdf777779a669068a75ce82a06f6b3325c855ed83daf5513a078a61f7dc6c1622a633367e5f3a33e765c8ec5d8d54f48494006fdbf8922063e5340013e312871b7f8f8e5ea439c0d4cb78e2f19dd11f010729b692c65dd0d347f0ce53de9d849224666ea2f6487f1c6f953e8f9dbfd3d6de291c3e9d045e633cfd83c89d2f2327d0b2f31f72ac1604a3db1febc5f22cad08153278047210cc2894582c251a014c652e3951593e70e52a5d7451be8924b64f85c8247dab6268d24710b39fc1c07b4ac829fbda34ed79b5",
|
||||
"d7314e8b1ff82100b8f5870da62b61c31ab37ace9e6a7b6f7d294571523783c1fdedcbc00dd487dd6f848c34aab493507d07071b5eb59d1a2346068c7f356755fbde3d2cab67514f8c3a12d6ff9f96a977a9ac9263491bd33122a904da5386b943d35a6ba383932df07f259b6b45f69e9b27b4ca124fb3ae143d709853eed86690bc2754d5f8865c355a44b5279d8eb31cdc00f7407fb5f5b34edc57fc7ace943565da2222dc80632ccf42f2f125ceb19714ea964c2e50603c9f8960c3f27c2ed0e18a559931c4352bd7422109a28c5e145003f55c9b7c664fdc985168868950396eaf6fefc7b73d815c1aca721d7c67da632925",
|
||||
"2928b55c0e4d0f5cb4b60af59e9a702e3d616a8cf427c8bb03981fb8c29026d8f7d89161f36c11654f9a5e8ccb703595a58d671ecdc22c6a784abe363158682be4643002a7da5c9d268a30ea9a8d4cc24f562ab59f55c2b43af7dbcecc7e5ebe7494e82d74145a1e7d442125eb0431c5ea0939b27afa47f8ca97849f341f707660c7fbe49b7a0712fbcb6f7562ae2961425f27c7779c7534ecdeb8047ff3cb89a25159f3e1cefe42f9ef16426241f2c4d62c11d7ac43c4500dfcd184436bb4ef33260366f875230f26d81613c334dbda4736ba9d1d2966502914ec01bbe72d885606ec11da7a2cb01b29d35eebedbb0ecc73ed6c35",
|
||||
"fd993f50e8a68c7b2c7f87511ce65b93c0aa94dcbdf2c9cca93816f0f3b2ab34c62c586fc507b4900a34cf9d0517e0fe10a89d154c5419c1f5e38de00e8834fe3dc1032abdeb10729a81655a69a12856a78ca6e12110580de879b086fd6608726541cfa9616326bdd36064bc0d1e5f9c93b41278bff6a13b2494b81e238c0c45aea1b07d855e8f3fe1478e373bd9d3957cf8a5e5b9003386793d994c7c575cff2322e2428cbbaa4f47560316ae3354a7478842ff7cc5dcbacb6e871e72b36f06d63a9aaeb9044cfb7974afdc238a5816f537dcf33ee40b4e1a5eb3cff2402b46d548264e133008d284f11b7e4e450bc3c5ff9f79b9c4",
|
||||
"8df21892f5fc303b0de4adef1970186db6fe71bb3ea3094922e13afcfabf1d0be009f36d6f6310c5f9fda51f1a946507a055b645c296370440e5e83d8e906a2fb51f2b42de8856a81a4f28a73a8825c68ea08e5e366730bce8047011cb7d6d9be8c6f4211308fad21856284d5bc47d199988e0abf5badf8693ceeed0a2d98e8ae94b7775a42925edb1f697ffbd8e806af23145054a85e071819cca4cd48875290ca65e5ee72a9a54ff9f19c10ef4adaf8d04c9a9afcc73853fc128bbebc61f78702787c966ca6e1b1a0e4dab646acdfcd3c6bf3e5cfbec5ebe3e06c8abaa1de56e48421d87c46b5c78030afcafd91f27e7d7c85eb4872b",
|
||||
"48ec6ec520f8e593d7b3f653eb15553de246723b81a6d0c3221aaa42a37420fba98a23796338dff5f845dce6d5a449be5ecc1887356619270461087e08d05fb60433a83d7bd00c002b09ea210b428965124b9b27d9105a71c826c1a2491cfd60e4cfa86c2da0c7100a8dc1c3f2f94b280d54e01e043acf0e966200d9fa8a41daf3b9382820786c75cadbb8841a1b2be5b6cbeb64878e4a231ae063a99b4e2308960ef0c8e2a16bb3545cc43bdf171493fb89a84f47e7973dc60cf75aeeca71e0a7ebe17d161d4fb9fe009941cc438f16a5bae6c99fcad08cac486eb2a48060b023d8730bf1d82fe60a2f036e6f52a5bff95f43bbe088933f",
|
||||
"f4d84ed3e564c102600a795eaa9b1eaf4ad12f1a4deca1d042a0a2750ddf6201db03073d8bf553cb9dde48a1b0083827a609f7242b86584cc180964ae794b12ce55661e00e36a6ba4dbc389e6a5a85f1b45df9af7ead1b0a54db56e68639b9d438a91504e82c35d40c7bc7e048a53ac0b04accd0dadf4ac9884b0ca0e3cb5ba4336e3581be4c4760a553823ffa283a1120d4e145af56a59f2533903650f0b9e9ad9fe2e8a3c3c3dd03a1fcb709032c8835324839c735b0c051d0cbd8b5d867617c11023432e4bd275d3d0eb98a0b6cf58071a5b712922f2bc751ac7c2588c447444cde2f37a8ea5ec126425bf517e0d17c9e2999f52fee14b3",
|
||||
"2ccea21bac9c2b70d3923309cbf2d7cb7abd1fcc8b8b002688870a80029c62397350c3c898194e5deea360bb963d26d485cb7963f8167586976ec0556950b2e86135f4a2800991ce8473bfd44a3c5e937a48b5e355ba5141bccf2131a83988d9d2a9e8e7635a956105b3512c05ef708139ced51d7a4e204c12d8a49a21e8dc6de2629a2fd092326885d9f218745fe09f6d91fb6afce250a30a63689534b6be1f26899ffa3767d835cf586aa47776700f94241bc999b1e3deefe188f37ff734f5f16ee6a00914323dc7b8a143c9137cdcc5cd08ae9566f04bb2941532674c97dff6ffa5ce3405ef8e5d27ec403114253dd6394c0167d72a0044c5",
|
||||
"2b681c6398aee63bf862770341648bbcd31d7de7903c5903fe3d9469311320bb24d914f2af0cdca199c97214c7c679dc32a2800ba484a03c010ea6be3bb9f2c87e30a98b606050b8a3f297f12b8f92caaeceb3e844652115934874e0a1ab093a73d759b53f6a6c3096940dd22c2bb96ce6820a7b9c6d71a208de9892aa6a7209b0fff56a0cafea52b952cdd6f5752cff3309d448800b4e4c878aa595595b56b12b83fcd6ca89520c7da664e449d7b4438fc455888aad5de0fad9a06eed14afd3513b5ebbffe01775549b701181bd26370764f56eba52fdb24286ad1ac0f5418a7c429f7dfc7f3168437fa8eed7a2ed7c723a485e4c3ed14dea2e07",
|
||||
"aadfd505a89f4aade2c3018258a7e039401b1fc6a7f3d87910dddbb880d372ec8a13c70d92245de5b8e5f9a285c33b99dc82fa2b22decee72b93a72211656ad7a52696c8e570f78be28c0e427a371dafde856e8d5ed24f83b0660b51e7fac05d93a8666dfde6def59af863f80f3e5f6801182c87422203df390dcb736b8f830052a8832eeeb0b4e27e732aaf793d166b5a3ec7745aeef3766937c2b75a276bddd145f6010c29d035e343e267cb2d828436876ec3a7ebe3b6347d4172f7a99d6821ce152e039e53deb33340b324c7f068ffb94b3cde35a8eaa12d15c3806a7ad0acec3e8c7078c1d32a28fd3eec9f32cb86e4c22166ff69e83785e851",
|
||||
"1605b8cce529a9d6262fd4390d9e4ae5e14e0adc0ec89b028ef68dd0f373ea259aaa96f2967091dd0874c0105385e9e6da9ca68297c31afa44ef834535fb302ce5b4e49edacbbdf359fe1228a8172495b3e57014c27edd58b685110980056c50c398a64f4923f2d720b4df16d75cb36b4233660694182099c35028a972519c24764fc94e18e582b24deb3491535fc06b83837c7958522800e822201d694af0bd0aa3834e17d4b1ba36f470905ae5f8bbeeb6c4c8604d8af02baa347b07086d6989867ddd5e8e8ed7740c3469bfa2810519c55c6add1332c4c54ee9097961d6741cb12a09713a0d07645f784f42f5ad94b48b836b34263130b0483f15e3",
|
||||
"ff9c6125b2f60bfd6c2427b279df070e430075096647599bdc68c531152c58e13858b82385d78c856092d6c74106e87ccf51ac7e673936332d9b223444eaa0e762ee258d8a733d3a515ec68ed73285e5ca183ae3278b4820b0ab2797feb1e7d8cc864df585dfb5ebe02a993325a9ad5e2d7d49d3132cf66013898351d044e0fe908ccdfeeebf651983601e3673a1f92d36510c0cc19b2e75856db8e4a41f92a51efa66d6cc22e414944c2c34a5a89ccde0be76f51410824e330d8e7c613194338c93732e8aea651fca18bcf1ac1824340c5553aff1e58d4ab8d7c8842b4712021e517cd6c140f6743c69c7bee05b10a8f24050a8caa4f96d1664909c5a06",
|
||||
"6e85c2f8e1fdc3aaeb969da1258cb504bbf0070cd03d23b3fb5ee08feea5ee2e0ee1c71a5d0f4f701b351f4e4b4d74cb1e2ae6184814f77b62d2f08134b7236ebf6b67d8a6c9f01b4248b30667c555f5d8646dbfe291151b23c9c9857e33a4d5c847be29a5ee7b402e03bac02d1a4319acc0dd8f25e9c7a266f5e5c896cc11b5b238df96a0963ae806cb277abc515c298a3e61a3036b177acf87a56ca4478c4c6d0d468913de602ec891318bbaf52c97a77c35c5b7d164816cf24e4c4b0b5f45853882f716d61eb947a45ce2efa78f1c70a918512af1ad536cbe6148083385b34e207f5f690d7a954021e4b5f4258a385fd8a87809a481f34202af4caccb82",
|
||||
"1e9b2c454e9de3a2d723d850331037dbf54133dbe27488ff757dd255833a27d8eb8a128ad12d0978b6884e25737086a704fb289aaaccf930d5b582ab4df1f55f0c429b6875edec3fe45464fa74164be056a55e243c4222c586bec5b18f39036aa903d98180f24f83d09a454dfa1e03a60e6a3ba4613e99c35f874d790174ee48a557f4f021ade4d1b278d7997ef094569b37b3db0505951e9ee8400adaea275c6db51b325ee730c69df97745b556ae41cd98741e28aa3a49544541eeb3da1b1e8fa4e8e9100d66dd0c7f5e2c271b1ecc077de79c462b9fe4c273543ecd82a5bea63c5acc01eca5fb780c7d7c8c9fe208ae8bd50cad1769693d92c6c8649d20d8",
|
||||
}
|
||||
|
|
|
|||
177
vendor/golang.org/x/crypto/blake2b/blake2x.go
generated
vendored
Normal file
177
vendor/golang.org/x/crypto/blake2b/blake2x.go
generated
vendored
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blake2b
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// XOF defines the interface to hash functions that
|
||||
// support arbitrary-length output.
|
||||
type XOF interface {
|
||||
// Write absorbs more data into the hash's state. It panics if called
|
||||
// after Read.
|
||||
io.Writer
|
||||
|
||||
// Read reads more output from the hash. It returns io.EOF if the limit
|
||||
// has been reached.
|
||||
io.Reader
|
||||
|
||||
// Clone returns a copy of the XOF in its current state.
|
||||
Clone() XOF
|
||||
|
||||
// Reset resets the XOF to its initial state.
|
||||
Reset()
|
||||
}
|
||||
|
||||
// OutputLengthUnknown can be used as the size argument to NewXOF to indicate
|
||||
// the the length of the output is not known in advance.
|
||||
const OutputLengthUnknown = 0
|
||||
|
||||
// magicUnknownOutputLength is a magic value for the output size that indicates
|
||||
// an unknown number of output bytes.
|
||||
const magicUnknownOutputLength = (1 << 32) - 1
|
||||
|
||||
// maxOutputLength is the absolute maximum number of bytes to produce when the
|
||||
// number of output bytes is unknown.
|
||||
const maxOutputLength = (1 << 32) * 64
|
||||
|
||||
// NewXOF creates a new variable-output-length hash. The hash either produce a
|
||||
// known number of bytes (1 <= size < 2**32-1), or an unknown number of bytes
|
||||
// (size == OutputLengthUnknown). In the latter case, an absolute limit of
|
||||
// 256GiB applies.
|
||||
//
|
||||
// A non-nil key turns the hash into a MAC. The key must between
|
||||
// zero and 32 bytes long.
|
||||
func NewXOF(size uint32, key []byte) (XOF, error) {
|
||||
if len(key) > Size {
|
||||
return nil, errKeySize
|
||||
}
|
||||
if size == magicUnknownOutputLength {
|
||||
// 2^32-1 indicates an unknown number of bytes and thus isn't a
|
||||
// valid length.
|
||||
return nil, errors.New("blake2b: XOF length too large")
|
||||
}
|
||||
if size == OutputLengthUnknown {
|
||||
size = magicUnknownOutputLength
|
||||
}
|
||||
x := &xof{
|
||||
d: digest{
|
||||
size: Size,
|
||||
keyLen: len(key),
|
||||
},
|
||||
length: size,
|
||||
}
|
||||
copy(x.d.key[:], key)
|
||||
x.Reset()
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type xof struct {
|
||||
d digest
|
||||
length uint32
|
||||
remaining uint64
|
||||
cfg, root, block [Size]byte
|
||||
offset int
|
||||
nodeOffset uint32
|
||||
readMode bool
|
||||
}
|
||||
|
||||
func (x *xof) Write(p []byte) (n int, err error) {
|
||||
if x.readMode {
|
||||
panic("blake2b: write to XOF after read")
|
||||
}
|
||||
return x.d.Write(p)
|
||||
}
|
||||
|
||||
func (x *xof) Clone() XOF {
|
||||
clone := *x
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (x *xof) Reset() {
|
||||
x.cfg[0] = byte(Size)
|
||||
binary.LittleEndian.PutUint32(x.cfg[4:], uint32(Size)) // leaf length
|
||||
binary.LittleEndian.PutUint32(x.cfg[12:], x.length) // XOF length
|
||||
x.cfg[17] = byte(Size) // inner hash size
|
||||
|
||||
x.d.Reset()
|
||||
x.d.h[1] ^= uint64(x.length) << 32
|
||||
|
||||
x.remaining = uint64(x.length)
|
||||
if x.remaining == magicUnknownOutputLength {
|
||||
x.remaining = maxOutputLength
|
||||
}
|
||||
x.offset, x.nodeOffset = 0, 0
|
||||
x.readMode = false
|
||||
}
|
||||
|
||||
func (x *xof) Read(p []byte) (n int, err error) {
|
||||
if !x.readMode {
|
||||
x.d.finalize(&x.root)
|
||||
x.readMode = true
|
||||
}
|
||||
|
||||
if x.remaining == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
n = len(p)
|
||||
if uint64(n) > x.remaining {
|
||||
n = int(x.remaining)
|
||||
p = p[:n]
|
||||
}
|
||||
|
||||
if x.offset > 0 {
|
||||
blockRemaining := Size - x.offset
|
||||
if n < blockRemaining {
|
||||
x.offset += copy(p, x.block[x.offset:])
|
||||
x.remaining -= uint64(n)
|
||||
return
|
||||
}
|
||||
copy(p, x.block[x.offset:])
|
||||
p = p[blockRemaining:]
|
||||
x.offset = 0
|
||||
x.remaining -= uint64(blockRemaining)
|
||||
}
|
||||
|
||||
for len(p) >= Size {
|
||||
binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset)
|
||||
x.nodeOffset++
|
||||
|
||||
x.d.initConfig(&x.cfg)
|
||||
x.d.Write(x.root[:])
|
||||
x.d.finalize(&x.block)
|
||||
|
||||
copy(p, x.block[:])
|
||||
p = p[Size:]
|
||||
x.remaining -= uint64(Size)
|
||||
}
|
||||
|
||||
if todo := len(p); todo > 0 {
|
||||
if x.remaining < uint64(Size) {
|
||||
x.cfg[0] = byte(x.remaining)
|
||||
}
|
||||
binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset)
|
||||
x.nodeOffset++
|
||||
|
||||
x.d.initConfig(&x.cfg)
|
||||
x.d.Write(x.root[:])
|
||||
x.d.finalize(&x.block)
|
||||
|
||||
x.offset = copy(p, x.block[:todo])
|
||||
x.remaining -= uint64(todo)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *digest) initConfig(cfg *[Size]byte) {
|
||||
d.offset, d.c[0], d.c[1] = 0, 0, 0
|
||||
for i := range d.h {
|
||||
d.h[i] = iv[i] ^ binary.LittleEndian.Uint64(cfg[i*8:])
|
||||
}
|
||||
}
|
||||
28
vendor/golang.org/x/crypto/blake2s/blake2s.go
generated
vendored
28
vendor/golang.org/x/crypto/blake2s/blake2s.go
generated
vendored
|
|
@ -2,8 +2,18 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package blake2s implements the BLAKE2s hash algorithm as
|
||||
// defined in RFC 7693.
|
||||
// Package blake2s implements the BLAKE2s hash algorithm defined by RFC 7693
|
||||
// and the extendable output function (XOF) BLAKE2Xs.
|
||||
//
|
||||
// For a detailed specification of BLAKE2s see https://blake2.net/blake2.pdf
|
||||
// and for BLAKE2Xs see https://blake2.net/blake2x.pdf
|
||||
//
|
||||
// If you aren't sure which function you need, use BLAKE2s (Sum256 or New256).
|
||||
// If you need a secret-key MAC (message authentication code), use the New256
|
||||
// function with a non-nil key.
|
||||
//
|
||||
// BLAKE2X is a construction to compute hash values larger than 32 bytes. It
|
||||
// can produce hash values between 0 and 65535 bytes.
|
||||
package blake2s // import "golang.org/x/crypto/blake2s"
|
||||
|
||||
import (
|
||||
|
|
@ -152,7 +162,13 @@ func (d *digest) Write(p []byte) (n int, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (d *digest) Sum(b []byte) []byte {
|
||||
func (d *digest) Sum(sum []byte) []byte {
|
||||
var hash [Size]byte
|
||||
d.finalize(&hash)
|
||||
return append(sum, hash[:d.size]...)
|
||||
}
|
||||
|
||||
func (d *digest) finalize(hash *[Size]byte) {
|
||||
var block [BlockSize]byte
|
||||
h := d.h
|
||||
c := d.c
|
||||
|
|
@ -165,11 +181,7 @@ func (d *digest) Sum(b []byte) []byte {
|
|||
c[0] -= remaining
|
||||
|
||||
hashBlocks(&h, &c, 0xFFFFFFFF, block[:])
|
||||
|
||||
var sum [Size]byte
|
||||
for i, v := range h {
|
||||
binary.LittleEndian.PutUint32(sum[4*i:], v)
|
||||
binary.LittleEndian.PutUint32(hash[4*i:], v)
|
||||
}
|
||||
|
||||
return append(b, sum[:d.size]...)
|
||||
}
|
||||
|
|
|
|||
7
vendor/golang.org/x/crypto/blake2s/blake2s_386.go
generated
vendored
7
vendor/golang.org/x/crypto/blake2s/blake2s_386.go
generated
vendored
|
|
@ -7,10 +7,9 @@
|
|||
package blake2s
|
||||
|
||||
var (
|
||||
useSSE4 = false
|
||||
useSSSE3 = supportSSSE3()
|
||||
useSSE2 = supportSSE2()
|
||||
useGeneric = true
|
||||
useSSE4 = false
|
||||
useSSSE3 = supportSSSE3()
|
||||
useSSE2 = supportSSE2()
|
||||
)
|
||||
|
||||
//go:noescape
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue